Overview
Annotations are commonly used to comment, or elaborate on, chart elements like data points. However, annotations can also be used to draw custom shapes, and are far easier to work with than one of the paint-related events like PrePaint or PostPaint, or using GDI+ drawing routines. This topic discusses annotations, and gives you techniques, and code samples to help you work with them.
Annotation Types and Descriptions
Annotation | Description | Points to Note |
---|---|---|
Line |
A line, which has optional start and end caps. For arrow annotations consider using the Arrow type instead, which allows for greater control over the arrow. |
|
Vertical Line |
A vertical line. |
|
Horizontal Line |
A horizontal line. |
|
Text |
Text annotation. |
|
Rectangle | A rectangle, with optional text. |
|
Ellipse | An ellipse, with optional text. |
|
Arrow | An arrow. |
|
Border3D | Annotation that has a 3D border skin, with optional text. |
|
Callout | Callout, with optional text. |
|
Polyline | Polyline, which consists of multiple lines. |
|
Polygon | Polygon, which consists of multiple lines that form a closed figure. |
|
Group | Group itself is an annotation, so it has all common annotation properties. |
|
Image | Displays an image as an annotation. |
|
Coordinate System
By default, annotations are positioned using relative coordinates, with "0,0" representing the top-left corner, and "100,100" being the bottom-right corner of the chart image. It is also possible to switch from this relative coordinate system to a system that uses axis values. With an axis coordinate system, X and Y, which represent the position of the top-left corner of an annotation, are set using X axis and Y axis values, instead of a value that ranges from 0-100.
There are several ways to use axis values when specifying the position and size of an annotation, including the following:
- By explicitly setting the AxisX, AxisY or both of these annotation properties to a ChartArea object's AxisX, and AxisY properties.
- By anchoring the annotation to a data point using the AnchorDataPoint property. In this case its positioning is automatically calculated.
Other Considerations
- If SizeAlwaysRelative is set to true, then the size of an annotation gets set using the default coordinate system relative to the chart image. This only affects the width and height properties, but not the X and Y position properties.
- The direction of the coordinate system, relative to the chart image, may be opposite to a system that uses axis values, depending on the direction of the axis. This concept is demonstrated in Figure 1 below where you can observe the effects of the coordinate system's direction. The coordinate system direction comes into play most readily when using negative values for width, height, or both.
Figure 1: Direction of Relative vs. Axis Coordinates.
- Polyline and Polygon Annotations: The coordinates of the points that are stored in the PathPoints collection property are relative to the bounding rectangle of the polyline/polygon, and not to the entire chart image. When relative coordinates are used "0,0" is the position of the top-left corner of the polyline bounding rectangle, and "100,100" is the position of the bottom-right corner of the polyline.
- Group annotations: The coordinates of child annotations are relative to the bounding rectangle of the group, and not to the entire chart image. These coordinates are stored in the Annotations collection property.
Positioning and Sizing
All annotations have X and Y property, that determines:
- The top-left corner of an annotation's bounding rectangle, assuming that width and height are not set to negative values. If width and height are set to negative values, then the X and Y properties represent the bottom-right corner of the annotation's bounding rectangle.
- The start point if the annotation is a line-type.
The X and Y property can be explicitly set using either axis values or relative coordinates (see Coordinate System section above). Alternatively, X and Y can have a value of Not Set (programmatically represented by Double.NaN), in which case, if the annotation is anchored, then these values are automatically calculated.
Note |
---|
In regards to positioning annotations, remember that line-type annotations require special consideration. |
Annotations can be positioned in a number of ways, including:
- Anchoring them to a data point using the AnchorDataPoint property
- Anchoring them to a point within the chart image, which can be set using the AnchorX and AnchorY properties. The X and Y property should not be set because normally they are automatically calculated.
- Explicitly positioning them using either axis values or coordinates that are relative to the entire chart image (see Coordinate System section above).
The following are some points to note in regards to working with annotations:
- The AnchorAlignment property determines the alignment of an annotation's bounding rectangle.
-
An annotation can be offset by using the AnchorOffsetX and AnchorOffsetY properties.
-
The size of an annotation is controlled by the Width and Height properties. Annotations with text (AnnotationRectangle, etc.) can have their Width and Height properties not set (represented by Double.NaN), in which case their size is automatically scaled to fit their text.
- Polyline and Polygon Annotations: The coordinates of the data point's paths, that are stored in the PathPoint collection, are relative to the bounding rectangle of the polyline or polygon, not to the entire chart image. When relative coordinates are used "0,0" is the position of the top-left corner of the polyline bounding rectangle, and "100,100" is the position of the bottom-right corner of the polyline.
Note The PathPoints property is only available at design-time. To set the path at run-time use the Path property. - Group annotations: The coordinates of the child annotations that are stored in the Annotations collection property are relative to the bounding rectangle of the group, and not to the entire chart.
- Annotations that display text will be sized automatically if their width and height have not been set (they have their default value of Double.NaN). This size set is the minimal size necessary to display the text.
- If the ClipToChartArea property is set to an existing chart area, then only the portion of an annotation that falls within that chart area will be drawn.
Anchoring an Annotation
When an annotation is anchored, it is "glued" to the point onto which it is anchored, and this anchor point can be positioned either inside or outside of the annotation. The user may move the annotation at runtime, to move the anchor point in this manner, the end-user selects the point using the left mouse button, and moves the anchor point by dragging the mouse. This functionality is controlled by the AllowAnchorMoving property. If the position of an annotation is explicitly set (using code, or at run-time by the end-user) then the annotation will no longer be glued to its anchor point. This occurs because the position properties no longer have a value of "Not set" (i.e. Double.NaN).
When an annotation object is anchored to a series' data point, special keywords can be used in the annotation's Text, and Tooltip, properties to access data point data like X and Y values, labels, and others. Refer to the following table for a listing of all available keywords:
Keyword | Replaced By |
---|---|
#VALX |
X value of data point. |
#VAL, #VALY, #VALY2, #VALY3, ... |
Y values of the data point. |
#SER |
Series name. |
#LABEL |
Data point label. |
#INDEX | Data point index. |
#PERCENT | Percentage of the data point Y value. |
#TOTAL | Total of all Y values in the series. |
#LEGENDTEXT | Legend text. |
The keywords shown above act as variables that can be used to represent chart data in a flexible and changing way.
The AnchorX and AnchorY properties have precedence over the AnchorDataPoint property.
Note |
---|
Three-point break, Kagi, Renko, and Point Figure charts do not support data point anchors. |
End-User Interaction
You can allow your end-user to interact with annotations, or even create new annotations by providing the necessary functionality to do so. This functionality come via a set of methods and properties, that can be set to make your chart annotations fully interactive.
The following is a list of properties that all annotations possess:
- A Tooltip property that allows for the display of tooltips.
- The AllowResizing property determines whether or not an annotation can be resized using its GUI resize handles. In other words, it is still possible to resize the annotation programmatically, or in the case of polylines and polygons, by using the path point handles.
- The AllowTextEditing property controls whether or not end-users can edit an annotation's text at run-time.
Please note the following items concerning text editing:
-
Text editing is only allowed if AllowTextEditing property is set to true.
-
If the end-user presses the ESC key, this will end the text edit operation without saving any changes.
-
If the end-user presses the Enter key within a single line text, this will end the editing session. If multiline text is supported, then it will insert a carriage return/new line into the text string. To end the editing session for an annotation with multiline text, the end-user must click outside of the annotation.
-
Annotations are event driven objects, and as such, provide you with the opportunity to use event handlers in your code.
Working With Annotations
Grouping
An annotation group is similar to other annotations in that it has appearance properties, a bounding rectangle, position and size properties, etc. The major difference is that an annotation group has an Annotations collection property, that is used to store its child annotation objects.
The purpose of annotation groups is two-fold:
- You can group your annotations so they can be moved as a single entity.
- Common appearance properties can be applied to all grouped annotations at once.
The appearance properties of an annotation group are not applied to the group's bounding rectangle, rather, they are applied to its child annotations. However, child annotation appearance properties do have precedence over the group appearance properties.
The position of a child annotation is always relative to the group's location, and not relative to the entire chart image. "0,0" represents the position of the top-left corner of the group, and "100,100" is the position of the bottom-right corner of the group.
Certain properties of child annotations will not have any effect, and therefore should not be set.
The following properties have no effect when set in a child annotation:
- SizeAlwaysRelative: Has no effect since size is always relative to group annotation.
- AnchorDataPoint, AnchorAlignment, AnchorX, AnchorY, AnchorOffsetX, AnchorOffsetY.
SmartLabels™
SmartLabels™ are labels that reposition themselves to prevent collision with other labels, thereby improving their readability.
To use SmartLabels™ with annotations perform the following:
- Anchor the annotation to either a data point using AnchorDataPoint, or to a fixed point within the chart using the AnchorX and AnchorY properties.
Note |
---|
The annotation must be anchored in order to use SmartLabels™. |
- Make sure that the position of the annotation is not explicitly set. At design-time make sure the X and Y properties have a value of "Not set". At run-time the X and Y properties can be set to Double.NaN.
- If you do not want any other label to collide with your annotations, then those labels must also have their SmartLabel™ properties enabled.
3D Considerations
- Annotations displayed in 3D charts do not have any control over their depth.
- Annotations that are anchored to a data point will have the same depth as the data point they are anchored to. In all other cases annotations are displayed on the front surface of the 3D chart area.
- Annotations that are anchored to the 3D Pie chart will not display correctly.
Creating Annotations
Creating Annotations at Run-Time
Annotations can be created at run-time in one of two ways, either:
- Programmatically, using either the Annotations.Add method, or one of the Annotations.AddPolyline, Annotations.AddLine, or other methods.
Creating Annotations Programmatically
The Add method requires that the annotation object be initialized before it is used, however, the AddLine, and AddRectangle methods have parameters that are used in the object's constructor to initialize it.
Example
This example demonstrates how to add annotations using the generic Add method.
Visual Basic | Copy Code |
---|---|
Imports Dundas.Charting.WinControl ... ' Create new polyline annotation, and specify its name. Dim myPolyline As PolylineAnnotation = New PolylineAnnotation myPolyline.Name = "myPolyline" ' Create array of points. Dim Points(3) As Point Points(0) = New Point(6, 6) Points(1) = New Point(3, 5) Points(2) = New Point(1, 1) ' Set axis properties, so that position (set by Point object coordinates set above) ' uses axis coordinates. myPolyline.AxisX = chart1.ChartAreas(0).AxisX myPolyline.AxisY = chart1.ChartAreas(0).AxisY ' Add array of Point objects to polyline's Path property (defines path). myPolyline.Path.AddLines(Points) ' Add initialized Polyline object to Annotations collection. Chart1.Annotations.Add(myPolyline) ' Redraw Chart. Chart1.Invalidate() |
C# | Copy Code |
---|---|
using Dundas.Charting.WinControl; ... // Create new polyline annotation, and specify its name. Dundas.Charting.WinControl.PolylineAnnotation myPolyline = new PolylineAnnotation(); myPolyline.Name = "myPolyline"; // Create array of points. Point[] Points = new Point[3]; Points[0] = new Point(6,6); Points[1] = new Point(3,5); Points[2] = new Point(1,1); // Set axis properties, so that position (set by Point object coordinates set above) // uses axis coordinates. myPolyline.AxisX = chart1.ChartAreas[0].AxisX; myPolyline.AxisY = chart1.ChartAreas[0].AxisY; // Add array of Point objects to polyline's Path property (defines path). myPolyline.Path.AddLines(Points); // Add initialized Polyline object to Annotations collection. chart1.Annotations.Add(myPolyline); // Redraw chart. chart1.Invalidate(); |
Example
This example demonstrates how to add a Polyline annotation to the Chart.
Visual Basic | Copy Code |
---|---|
Imports Dundas.Charting.WinControl Imports System.Drawing.Drawing2D ... 'Create array of Point structures. Dim Points(3) As Point Points(0) = New Point(6, 6) Points(1) = New Point(3, 5) Points(2) = New Point(1, 1) ' Create GraphicsPath object, add points. Dim myGraphicsPath As GraphicsPath = New GraphicsPath myGraphicsPath.AddLines(Points) ' Add polyline annotation. Chart1.Annotations.AddPolyline("myPolyline", myGraphicsPath) ' Have annotation use axis coordinates. Chart1.Annotations("myPolyline").AxisX = Chart1.ChartAreas(0).AxisX Chart1.Annotations("myPolyline").AxisY = Chart1.ChartAreas(0).AxisY ' Force repainting of chart. Chart1.Invalidate() |
C# | Copy Code |
---|---|
using Dundas.Charting.WinControl; using System.Drawing.Drawing2D; ... //Create array of Point structures. Point[] myPoints = new Point[3]; myPoints[0]= new Point(6,6); myPoints[1] = new Point(3,5); myPoints[2] = new Point(1,1); // Create GraphicsPath object, add points. GraphicsPath myGraphicsPath = new GraphicsPath(); myGraphicsPath.AddLines(myPoints); // Add polyline annotation. chart1.Annotations.AddPolyline("myPolyline",myGraphicsPath); // Have annotation use axis coordinates. chart1.Annotations["myPolyline"].AxisX = chart1.ChartAreas[0].AxisX; chart1.Annotations["myPolyline"].AxisY = chart1.ChartAreas[0].AxisY; // Force repainting of chart. chart1.Invalidate(); |
Working With Z-Order
By default, annotations are drawn on top of data points, markers, and other chart elements. The z-order determines which items are displayed upper-most in the image drawn. To change the z-order of an annotation, the annotation needs to be custom painted in the PrePaint event, which repeatedly fires for the ChartPicture object, Series, and ChartArea objects. The element that fired the event is the found in the sender parameter of the PrePaint event handler, therefore you can check to see that this is the element that you want to work with, before executing code on it.
Note |
---|
Annotations that are drawn using the PrePaint event handler are not added to the Annotations collection. Instead they are painted behind the relevant chart element. |
To change the z-order of your Series, you must also use the PrePaint event. Since this event fires just before a chart element is drawn, it provides us with an opportunity to draw an annotation behind a chart element. We must be certain that the event was fired by the element that we want to work (e.g. a data point, and therefore the data series that the point belongs to), therefore we must check to see who the sender object of the event was. If it is the element that we are looking for, then we can create an annotation object, and proceed to draw it on our chart. To do this, we only need one annotation object, we just re-initialize it and use it to do our drawing.
Example
This example demonstrates how to use the PrePaint event to draw annotations behind a series named "Series3". Since the PrePaint event is raised more than once for the series, we use a boolean named bExecuted to make sure that our drawing code executes once, and only once. Also, a boolean named bDrawBehindPoints is set to true in a button's Click event, which we also check before code execution.
Visual Basic | Copy Code |
---|---|
Imports Dundas.Charting.WinControl ... ' Boolean that is used to make sure code in PrePaint event only executes once. Dim bExecuted As Boolean = False ' Boolean set by button click event. Dim bDrawBehindPoints As Boolean = False ... Private Sub Chart1_PrePaint(ByVal sender As Object, ByVal e As Dundas.Charting.WinControl.ChartPaintEventArgs) Handles Chart1.PrePaint ' Check to see if event is being raised for a Series object. If TypeOf sender Is Dundas.Charting.WinControl.Series Then ' Check to see if the Series has a name of "Series3". Notice we use a boolean named bExecuted to make sure ' this code executes once and only once (event is raised more than once for Series3). Also, bDrawBehindPoints ' is set to true in a button's Click event. If bDrawBehindPoints And sender.Name = "Series3" And bExecuted = False Then ' use one Annotation object, just keep repainting it with new location and text. Dim myRectAnnotation As RectangleAnnotation = New Dundas.Charting.WinControl.RectangleAnnotation ' Use axis coordinates. myRectAnnotation.AxisX = Chart1.ChartAreas(0).AxisX myRectAnnotation.AxisY = Chart1.ChartAreas(0).AxisY ' Draw one annotation per data point in series. We set the X and Y properties to position the ' annotation, set the text to the Y value of the cooresponding data point, and then paint it. Dim i As Int16 For i= 0 To Chart1.Series("Series3").Points.Count - 1 myRectAnnotation.X = i + 1 myRectAnnotation.Y = Chart1.Series("Series3").Points(i).YValues(0) + 1 myRectAnnotation.Text = "Value: " + Chart1.Series("Series3").Points(i).YValues(0).ToString myRectAnnotation.Paint(Chart1, e.ChartGraphics) Next bExecuted = True End If End If End Sub ... ' Reset boolean, so that annotation will be drawn if chart is repainted. Private Sub Chart1_PostPaint(ByVal sender As Object, ByVal e As Dundas.Charting.WinControl.ChartPaintEventArgs) Handles Chart1.PostPaint bExecuted = False End Sub |
C# | Copy Code |
---|---|
using Dundas.Charting.WinControl; ... // Boolean set by button click event. private bool bDrawBehindPoints = false; // Boolean that is used to make sure code in PrePaint event only executes once. private bool bExecuted = false; ... private void chart1_PrePaint(object sender, Dundas.Charting.WinControl.ChartPaintEventArgs e) { // Check to see if event is being raised for a Series object. if(sender is Dundas.Charting.WinControl.Series) { // Check to see if the Series has a name of "Series3". Notice we use a boolean named bExecuted to make sure // this code executes once and only once (event is raised more than once for Series3). Also, bDrawBehindPoints // is set to true in a button's Click event. if(bDrawBehindPoints && ((Dundas.Charting.WinControl.Series)sender).Name == "Series3" && bExecuted == false) { // use one Annotation object, just keep repainting it with new location and text. Dundas.Charting.WinControl.RectangleAnnotation myRectAnnotation = new Dundas.Charting.WinControl.RectangleAnnotation(); // Use axis coordinates. myRectAnnotation.AxisX = chart1.ChartAreas[1].AxisX; myRectAnnotation.AxisY = chart1.ChartAreas[1].AxisY; // Draw one annotation per data point in series. We set the X and Y properties to position the // annotation, set the text to the Y value of the cooresponding data point, and then paint it. for(int i = 0; i < chart1.Series[2].Points.Count;i++) { // Series is indexed (X values all set to zero), so use indices, not X values of data points myRectAnnotation.X = i + 1; // Set height of annotation. 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; } } } ... // Reset boolean, so that annotation will be drawn if chart is repainted. private void chart1_PostPaint(object sender, Dundas.Charting.WinControl.ChartPaintEventArgs e) { if(sender is ChartPicture) { bExecuted = false; } } |