Dundas Chart for Windows Forms
Performance and Optimization
See Also Send comments on this topic.
Using Dundas Chart > Performance and Optimization



Glossary Item Box

Overview

This topic describes how to optimize the chart by pointing out certain performance issues that need to be either avoided, or dealt with in a specific manner. Our discussion shall include techniques for displaying a large number of data points, as well as several optimization tips. We begin our discussion with an overview of performance related issues.

Performance Issues

The following items can have a major impact on performance:

 

The following items can also effect performance, but to a lesser degree:

Spline-Type Charts

Spline charts are known to degrade performance, since they are actually calculated areas. This means that they require a great deal of calculation in order to be drawn. The necessary calculations, and processing are being done by the GDI+ engine.

For optimal performance, we recommend avoiding spline-type charts if possible. Try using another chart type, such as a line chart.

Large Data Sets

When a chart has over a 1000 data points, performance can become an issue, especially if data-binding is being used.

To optimize chart performance, we recommend the following:

Each of the above techniques is discussed, in order, below.

Grouping Data Enterprise Edition Only Feature.

Grouping replaces a sequence of data points in a series with one grouped point. The X and Y values of each grouped point are calculated using a specified formula, and the original points' values.

Not only can grouping be a performance optimization, but it can also help to spot trends that might otherwise be difficult to see due to the large number of points in the chart.

For further information, and sample code, see the topic on Grouping Data.

Cross-Tab Data Binding

Cross-tab binding, accomplished using the Chart.DataBindCrossTab method, is a special type of data-binding that results in the creation of multiple data series with one pass through a data set.

 

Note
The key concept here is the single iteration through the data set. The DataPointCollection.DataBindXXX methods require one iteration through the data set per series being bound.

  

For further information, and sample code, see the topic on Data-Binding Techniques.

Explicitly Setting Values

If cross-tab data-binding is not viable, then the only other data-binding option is to use the DataPointCollection.DataBindXXX methods. However, these require one pass through the data set per series being bound, which can result in a serious performance hit.

To avoid this, do not use the DataBindXXX methods. Instead, iterate through the data set one time, and explicitly set the values of all series' data points. This can be done by using the YValues property, the XValue property, or both properties of the existing data points. You also use the Points.AddY method, Points.AddXY method, or both methods when adding new data points to series.

For more information on how to set the values of data points see the topic on Adding Data.

Example

This example demonstrates how to iterate through a data set, and explicitly set the values of data points. For comparative purposes, we also show data-binding of series, which results in the loss of chart performance.

Visual Basic Copy Code
Imports System.Data 
Imports System.Data.OleDb 
  ...
' Resolve the address to the Access database. 
Dim fileNameString As String = Me.MapPath("chartdata.mdb") 

' Initialize a connection string. 
Dim myConnectionString As String = "PROVIDER=Microsoft.Jet.OLEDB.4.0;Data Source=" + fileNameString 

' Define the database query. 
Dim mySelectQuery As String = "SELECT * FROM STOCK1MIN" 

' Create a database connection object using the connection string. 
Dim myConnection As New OleDbConnection(myConnectionString)

' Create a database command on the connection using query. 
Dim myCommand As New OleDbCommand(mySelectQuery, myConnection) 

' Open the connection.
myCommand.Connection.Open() 

' Initializes a new instance of the OleDbDataAdapter class. 
Dim myDataAdapter As New OleDbDataAdapter()
myDataAdapter.SelectCommand = myCommand 

' Initializes a new instance of the DataSet class. 
Dim myDataSet As New DataSet() 

' Adds rows in the DataSet.
myDataAdapter.Fill(myDataSet, "Query") 

' RECOMMENDED WAY TO ADD DATA: ITERATE THROUGH DATASET AND SET VALUES EXLICITLY. 
' *** START *** 
Dim row As DataRow For Each row In myDataSet.Tables(0).Rows 
  Dim xVal As DateTime = CType(row("TimeStamp"), DateTime) 
  Dim yHigh As Double = CDbl(row("High")) 
  Dim yLow As Double = CDbl(row("Low")) 
  Dim yOpen As Double = CDbl(row("Open")) 
  Dim yClose As Double = CDbl(row("Close"))
  Chart1.Series("Series1").Points.AddXY(xVal, yHigh)
  Chart1.Series("Series2").Points.AddXY(xVal, yLow)
  Chart1.Series("Series3").Points.AddXY(xVal, yOpen)
  Chart1.Series("Series4").Points.AddXY(xVal, yClose) 
  Next row
' *** END *** 

' NOT RECOMMENDED: USING DATA BINDING AND MULTIPLE SERIES. 
' *** START *** '
' Initializes a new instance of the DataView class. 
Dim myView As New DataView(myDataSet.Tables(0)) 

' Since the DataView implements IEnumerable, pass the reader directly into 
' the data-bind method with the name of the Columns selected in the query. 
Chart1.Series("Series1").Points.DataBindXY(myView, "TimeStamp", myView, "High") 
Chart1.Series("Series2").Points.DataBindXY(myView, "TimeStamp", myView, "Low") 
Chart1.Series("Series3").Points.DataBindXY(myView, "TimeStamp", myView, "Open") 
Chart1.Series("Series4").Points.DataBindXY(myView, "TimeStamp", myView, "Close") 
' *** END *** 

' Close the connection to the datasource. 
myCommand.Connection.Close()

C# Copy Code
using System.Data; 
using System.Data.OleDb;
 ... 
// Resolve the address to the Access database. 
string fileNameString = this.MapPath("chartdata.mdb"); 

// Initialize a connection string. 
string myConnectionString = "PROVIDER=Microsoft.Jet.OLEDB.4.0;Data Source=" + fileNameString; 

// Define the database query. string mySelectQuery="SELECT * FROM STOCK1MIN"; 
// Create a database connection object using the connection string. 
OleDbConnection myConnection = new OleDbConnection(myConnectionString);

// Create a database command on the connection using query. 
OleDbCommand myCommand = new OleDbCommand(mySelectQuery, myConnection); 

// Open the connection. 
myCommand.Connection.Open(); 

// Initializes a new instance of the OleDbDataAdapter class. 
OleDbDataAdapter myDataAdapter = new OleDbDataAdapter(); 
myDataAdapter.SelectCommand = myCommand; 

// Initializes a new instance of the DataSet class. 
DataSet myDataSet = new DataSet(); 

// Adds rows in the DataSet. 
myDataAdapter.Fill(myDataSet, "Query"); 

// RECOMMENDED WAY TO ADD DATA: ITERATE THROUGH DATASET AND SET VALUES EXLICITLY.
// *** START ***
foreach(DataRow row in myDataSet.Tables[0].Rows) 
{ 
  DateTime xVal = (DateTime)row["TimeStamp"]; 
  double yHigh = (double)row["High"]; 
  double yLow = (double)row["Low"]; 
  double yOpen = (double)row["Open"]; 
  double yClose = (double)row["Close"]; 
  Chart1.Series["Series1"].Points.AddXY(xVal, yHigh);
  Chart1.Series["Series2"].Points.AddXY(xVal, yLow);
  Chart1.Series["Series3"].Points.AddXY(xVal, yOpen);
  Chart1.Series["Series4"].Points.AddXY(xVal, yClose); 
}
// *** END *** 

// NOT RECOMMENDED: USING DATA BINDING AND MULTIPLE SERIES.
// *** START *** 
// Initializes a new instance of the DataView class. 
DataView myView = new DataView(myDataSet.Tables[0]); 

// Since the DataView implements IEnumerable, pass the reader directly into 
// the data-bind method with the name of the Columns selected in the query.
Chart1.Series["Series1"].Points.DataBindXY(myView, "TimeStamp", myView, "High");
Chart1.Series["Series2"].Points.DataBindXY(myView, "TimeStamp", myView, "Low");
Chart1.Series["Series3"].Points.DataBindXY(myView, "TimeStamp", myView, "Open");
Chart1.Series["Series4"].Points.DataBindXY(myView, "TimeStamp", myView, "Close");

// *** END *** 

// Close the connection to the datasource. 
myCommand.Connection.Close();

 

Border Skins

Decorative borders, which can be set using the Chart.BorderSkin property, will also have a major impact on the chart's performance, since they require a considerable amount of time to calculate. In addition, they also add to the size of the generated image.

Gradient Colors

Gradient colors will decrease chart performance, and the larger the area using the gradient color the greater the performance hit. Do not use gradient colors, or alternatively, display gradient colors using a static background image. This eliminates all of the calculations performed by GDI+ required to render the gradient color.

Soft Shadows

Using soft shadows will decrease the chart's performance, since they are actually drawn as gradient colors (i.e. require a lot of GDI+ calculations).

 

Semi-Transparency

Using semi-transparency will decrease the chart's performance, since it requires numerous GDI+ calculations to draw the transparent colors.

 

Data Point Markers

Displaying markers for data points will also reduce performance. The larger the number of data points and their associated markers, the greater the performance hit.

Complex, Custom Drawing Routines

Complex drawing routines require the Chart to perform numerous calculations, which adversely affects the performance of the Chart. To avoid this consider displaying background images using a static bitmap and the BackImage and BackImageXXX properties.

Using Multiple Chart Instances

Sometimes multiple charts need to be displayed on a single page. In order to increase performance avoid using multiple instances of the Chart control, instead, use one chart instance and multiple chart areas as shown in Figure 1 below.

 


Figure 1: Four chart areas in one chart image.

 

For more information see the topic on Creating Multiple Charts.

Explicit Sizing and Positioning of Chart Elements

Explicitly setting the position and size of various chart elements such as chart areas, titles, fonts, etc. requires calculation by the chart, and eliminating these calculations will slightly improve performance.

Anti-Aliasing

Anti-aliasing reduces tha performance of the chart, and the amount of performance reduction depends on the type and amount of Anti-aliasing being performed.

Text Anti-aliasing in particular can impact performance, especially with a large amount of text being displayed by the chart. Consider not using text Anti-aliasing at all (set by the Chart.Anti-aliasing property), or alternatively, explicitly set the quality to the required minimum using the Chart.TextAntiAliasingQuality property.

Custom Painting Using PrePaint and PostPaint Events

Custom drawing is accomplished by using GDI+ functions and the PostPaint or PrePaint events.

These events are raised when different elements of the chart (the chart image, each chart area , legends, and series) are drawn. The PrePaint event is called prior to the painting of the element and PostPaint is called after the painting is finished.

 

Note
These events are not raised for Series objects in 3D chart types.

  

Another performance issue is that the paint event may be raised more than once for a specific chart element, which results in multiple execution of custom drawing code.

To get around this use, declare a boolean flag, and initialize it. Allow the code to be executed depending on the condition of this flag, and once the custom drawing code has been executed, change the value of the flag. Reset the flag in the PostPaint event for the ChartPicture sender object.

Example

The following sample demonstrates how to make sure that custom painting code executes once and only once.

Visual Basic Copy Code
Imports System Imports System.Drawing 
Imports System.Drawing.Drawing2D 
Imports Dundas.Charting.WinControl
  ... 
' Declare and initialize a flag. 
Dim bExecuted As Boolean = False 

Private Sub chart1_PrePaint(sender As Object, e As Dundas.Charting.WinControl.ChartPaintEventArgs) 
  If TypeOf sender Is Dundas.Charting.WinControl.Series Then 
    If CType(sender, Dundas.Charting.WinControl.Series).Name = "Series3" And bExecuted = False Then
      ' For speed, use one object and just keep repainting it with new location. 
      Dim myRectAnnotation As New Dundas.Charting.WinControl.RectangleAnnotation()
      myRectAnnotation.AxisX = chart1.ChartAreas(1).AxisX 
      myRectAnnotation.AxisY = chart1.ChartAreas(1).AxisY 
      Dim i As Integer 
      
      For i = 0 To (chart1.Series(2).Points.Count) - 1 
      
      ' We do not anchor to data point, since we do not want to add 
      ' an annotation to annotation collection. (optimization)
      myRectAnnotation.X = i + 1 
      
      ' Series is indexed (X values all set to zero), so use indices, not X values of data points. 
      myRectAnnotation.Y = chart1.Series(2).Points(i).YValues(0) + 1 
      myRectAnnotation.Text = "Value: " + chart1.Series(2).Points(i).YValues(0)
      myRectAnnotation.Paint(chart1, e.ChartGraphics) 
      Next i 
      bExecuted = True 
    End If 
  End If 
End Sub

Private Sub chart1_PostPaint(sender As Object, e As Dundas.Charting.WinControl.ChartPaintEventArgs) 
  If TypeOf sender Is ChartPicture Then 
    bExecuted = False 
  End If 
End Sub

C# Copy Code
using System; 
using System.Drawing; 
using System.Drawing.Drawing2D; 
using Dundas.Charting.WinControl;
  ...
// Declare and initialize a flag. 
private bool bExecuted = false;
  ... 

private void chart1_PrePaint(object sender, Dundas.Charting.WinControl.ChartPaintEventArgs e)
{
  if(sender is Dundas.Charting.WinControl.Series) 
  {
    if(((Dundas.Charting.WinControl.Series)sender).Name == "Series3" && bExecuted == false) 
    {
      // For speed, use one object and just keep repainting it with new location. 
      Dundas.Charting.WinControl.RectangleAnnotation myRectAnnotation = new Dundas.Charting.WinControl.RectangleAnnotation();
      myRectAnnotation.AxisX = chart1.ChartAreas[1].AxisX; 
      myRectAnnotation.AxisY = chart1.ChartAreas[1].AxisY; 
      
      for(int i = 0; i < chart1.Series[2].Points.Count;i++) 
      {
        // We do not anchor to data point, since we do not want to add 
        // an annotation to annotation collection. (optimization)
        myRectAnnotation.X = i + 1; 

        // Series is indexed (X values all set to zero), so use indices, not X values of data points. 
        myRectAnnotation.Y = chart1.Series[2].Points[i].YValues[0] + 1; 
        myRectAnnotation.Text = "Value: " + chart1.Series[2].Points[i].YValues[0];
        myRectAnnotation.Paint(chart1,e.ChartGraphics); 
      }
      bExecuted = true; 
    }
  }
}

private void chart1_PostPaint(object sender, Dundas.Charting.WinControl.ChartPaintEventArgs e) 
{
  if(sender is ChartPicture)
  {
    bExecuted = false;
  }
}

Optimization Tips

The following are a collection of helpful optimization tips and techniques.

High Performance Charts

FastLine™ and FastPoint™ charts significantly reduce rendering time, and are specifically designed to handle data that contains a large number of datapoints.

For this reason, we recommend that you use these high performance chart types when working with very large data sets. Also, when you use these high performance charts, do not use tooltips. Tooltips require additional memory and processor time, and as such, will reduce overall chart performance and speed. This also applies to the use of borders, and anti-aliasing features of these charts.

 

Avoiding Complex Calculations

The most important thing to remember when optimizing your charts is that the more complex the calculations required by the chart, the greater the performance hit. Actually, many of the performance issues mentioned in this article result from doing things that result in the Chart (and indirectly GDI+) having to perform some sort of complex calculations.

When in doubt, keep it simple!

 

See Also

©2009. All Rights Reserved.