Dundas Chart for ASP.NET
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

There are two main areas that concern performance and optimization:

The following items can have a major impact on performance:

 

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

Tooltips

If there are numerous data points, displaying them using the Tooltip property will seriously affect performance, since each tooltip results in the creation of a map area element. In addition to this, each tooltip must be calculated by the chart every time it is redrawn. Thus, if a chart has 1000 data points, all with an associated tooltip, then the page that displays the chart image will have 1000 MapArea elements for the tooltips. For optimal performance consider avoiding the use of tooltips altogether.

Hyperlinks Using the Href Property

Care must also be taken when setting the Href property. This situation is similar to using tooltips, since each chart element that has its Href property set, which results in a MapArea element that is recalculated each time the chart is redrawn. For example, if a chart has 1000 data points, each with its Href property set, then the page that displays the chart image will have 1000 MapArea elements for the resulting hyperlinks.

For optimal performance, keep the number of chart elements that are used for hyperlinks down to a minimum.

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.

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.

Image Size and Type

The larger the image size of the chart, the longer it will take to be streamed back to the requesting client. Also, the type of image being generated will affect its size. For example, a bitmap chart will be much larger than the exact same PNG chart.

View State

Persisting the view state of the chart, using the EnableViewState property, will result in a performance hit due to the fact that it takes time to calculate. This method and also adds to the amount of data being sent back to the client.

 

Note
However, view state can also be used to persist a chart's data, which is then an optimization.

 

As a rule of thumb, only persist view state for charts that have relatively static large data sets.

 

For more information about view state see the topic on State Management.

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 How to Create Multiple Charts.

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.AntiAliasing 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.WebControl
  ... 

' Declare and initialize a flag. 
Dim bExecuted As Boolean = False 
Private Sub chart1_PrePaint(sender As Object, e As Dundas.Charting.WebControl.ChartPaintEventArgs) 
  If TypeOf sender Is Dundas.Charting.WebControl.Series Then 
    If CType(sender, Dundas.Charting.WebControl.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.WebControl.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.WebControl.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.WebControl;
  ...

// Declare and initialize a flag. 
private bool bExecuted = false;
  ... 

private void chart1_PrePaint(object sender, Dundas.Charting.WebControl.ChartPaintEventArgs e)
{
  if(sender is Dundas.Charting.WebControl.Series) 
  {
    if(((Dundas.Charting.WebControl.Series)sender).Name == "Series3" && bExecuted == false) 
    {
      // For speed, use one object and just keep repainting it with new location. 
      Dundas.Charting.WebControl.RectangleAnnotation myRectAnnotation = new Dundas.Charting.WebControl.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.WebControl.ChartPaintEventArgs e) 
{
  if(sender is ChartPicture)
  {
    bExecuted = false;
  }
}


Annotations

Many annotations can be a performance hit. To optimize the chart for this a single Annotation object can be used in the PrePaint event, and this annotation can then be repeatedly drawn. Note that this will result in end-users not being able to select the annotation.

For more information on how to do this see the topic on Annotations.

Optimization Tips

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

High Performance Charts Enterprise Edition Only Feature.

Dundas Chart for ASP.NET Enterprise Edition provides you with two high performance charts, these high speed charts include the Fastline™ chart, and the FastPoint™ chart.

FastLine™ charts significantly reduce rendering time, and can be used to handle data with a large number of points. For this reason, we recommend that you use a FastLine™ chart, instead of a Line chart, when you are working with larger data sets. When you use FastLine™ charts, do not use tooltips because they require additional memory, and processor time. This also applies to the border, and anti-aliasing features of the chart.

The FastPoint™ charts are similar to FastLine™ charts, except that they display a large number of points instead of displaying their data as a line.

Output Caching

Note

If you are not familiar with caching, read the introductory MSDN topic titled ASP.NET Caching Features.

 

If you use binary streaming to render your chart, then performance for high-traffic web sites can be greatly increased by caching the chart's output. With binary streaming an <IMG> tag is used in the page that displays the chart image, and its SRC attribute points to another web page that streams the chart image back to the first page using a Chart instance. It is this second page that can be cached, which results in the caching of the chart output (i.e. a chart image) for a specified amount of time.

If the chart data is static then the amount of time the output is cached can be quite long (e.g. hours, etc.). However, if the chart's data, appearance, or both are dynamic, then it is up to you to decide how long to cache the chart's output.

To set the caching of the ASP.NET page that streams the chart, use the @OutputCache directive (see the sample code below, or refer to the @OutputCache topic in the MSDN library).

Example

We use the @OutputCache directive, in a page called StreamChart.aspx, to cache the generated chart image. We set the amount of time the image output is cached to 600 seconds. Note that this StreamChart.aspx page is used as the SRC attribute for another web page, let's call it DisplayChart.aspx, that displays the cached chart image using an <IMG> tag.

HTML Copy Code
(DisplayChart.aspx) 
<html><body> 
        <img src="StreamChart.aspx">
</body></html> 

(StreamChart.aspx: HTML code) 
<%@ OutputCache Duration="600" VaryByParam="s" %> 
<DCWC:Chart id="Chart1" runat="server" Palette="Pastel" Width="316px" Height="253px">
         <Legends> 
                  <dcwc:Legend Name="Default">
                  </dcwc:Legend>
         </Legends> 
        <Series>
                 <dcwc:Series Name="Series1" BorderColor="64, 64, 64" ShadowOffset="1">
                         </dcwc:Series> 
                         <dcwc:Series Name="Series2" BorderColor="64, 64, 64" ShadowOffset="1">
                 </dcwc:Series>
        </Series>
        <ChartAreas> 
                 <dcwc:ChartArea Name="Default">
                 </dcwc:ChartArea>
        </ChartAreas>
</DCWC:Chart>

(StreamChart.aspx.cs: C# Code Behind Page) 
// This code-behind page can populate the chart with data, and also set any other 
// properties of the chart which are dynamic. For simplicity, we only set the title
// of the chart here.
Chart1.Titles.Add("My Chart Title"); 
  ...

// Set dynamic chart properties, add data, etc...

Binary Streaming

Rendering a chart using binary streaming is the fastest way to display a web chart, since this does not involve writing a temporary image to disk. In addition to this, the page that is streaming the chart image can be cached (see Caching section above for more details).

 

For more information see the topic on Rendering Methods.

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

Copyright © 2001 - 2009 Dundas Data Visualization, Inc. and others.