FlexChart > Working with FlexChart > FlexChart Elements > Annotations > Creating Callouts |
Callouts in charts are used to display the details of a data series or individual data points in an easy-to-read format. Callouts being connected with data points, help better visualize and comprehend chart data by minimizing visual disturbances in the chart area. In FlexChart, Polygon type annotations can be customized to create chart callouts with line or arrow connectors.
In this example, we are using sample created in the Quick Start topic to further create an arrow callout and polygon annotation with line connection. This is done with the help of the Points property and the ContentCenter property that define the coordinates of polygon vertices and annotation content center respectively.
To create callouts connected with respective data points, follow these steps:
The following image illustrates polygon annotations connected to data points through arrow and line connectors.
To create a line callout, use the following code.
... lineCallouts.SeriesIndex = 2 lineCallouts.PointIndex = 2 lineCallouts.ContentCenter = New Point(-50, 75) Dim lineCalloutsPoints As PointCollection = New PointCollection() lineCalloutsPoints.Add(New Point(0, 0)) lineCalloutsPoints.Add(New Point(25, -25)) lineCalloutsPoints.Add(New Point(50, -25)) lineCalloutsPoints.Add(New Point(50, -50)) lineCalloutsPoints.Add(New Point(25, -75)) lineCalloutsPoints.Add(New Point(0, -50)) lineCalloutsPoints.Add(New Point(0, -25)) lineCalloutsPoints.Add(New Point(25, -25)) lineCalloutsPoints.Add(New Point(0, 0)) lineCallouts.Points = lineCalloutsPoints flexChart.Invalidate() End Sub
... // Create a line callout annotation of polygon type lineCallout.SeriesIndex = 2; lineCallout.PointIndex = 2; lineCallout.ContentCenter = new Point(25, -40); // Create a list of points for the line callout annotation var lineConnectorPoints = new PointCollection(); lineConnectorPoints.Add(new Point(0, 0)); lineConnectorPoints.Add(new Point(25, -25)); lineConnectorPoints.Add(new Point(50, -25)); lineConnectorPoints.Add(new Point(50, -50)); lineConnectorPoints.Add(new Point(25, -75)); lineConnectorPoints.Add(new Point(0, -50)); lineConnectorPoints.Add(new Point(0, -25)); lineConnectorPoints.Add(new Point(25, -25)); lineConnectorPoints.Add(new Point(0, 0)); lineCallout.Points = lineConnectorPoints; flexChart.Invalidate(); }
Private Sub SetUpAttotations() 'Create an arrow callout annotation of polygon type Dim arrowCalloutContentCenter As Point = New Point(25, -50) arrowCallouts.ContentCenter = arrowCalloutContentCenter 'Create list of points for arrow callout by calling GetPointsForArrowCallout() arrowCallouts.Points = GetPointsForArrowCallout(arrowCalloutContentCenter.X, arrowCalloutContentCenter.Y, "Low") arrowCallouts.SeriesIndex = 1 arrowCallouts.PointIndex = 2 ...
private void SetUpAnnotations() { //Create an arrow callout annotation of polygon type var arrowCalloutContentCenter = new Point(25, -50); arrowCallout.ContentCenter = arrowCalloutContentCenter; //Create list of points for arrow callout by calling GetPointsForArrowCallout() arrowCallout.Points = GetPointsForArrowCallout(arrowCalloutContentCenter.X, arrowCalloutContentCenter.Y, "Low"); arrowCallout.SeriesIndex = 1; arrowCallout.PointIndex = 2; ...
Private Function GetPointsForArrowCallout(centerX As Double, centerY As Double, content As String) As PointCollection Dim size As _Size = _engine.MeasureString(content) Return GetPointsForArrowCallout(centerX, centerY, size.Width + 10, size.Height + 10) End Function
PointCollection GetPointsForArrowCallout(double centerX, double centerY, string content) { _Size size = _engine.MeasureString(content); return GetPointsForArrowCallout(centerX, centerY, (float)size.Width + 10, (float)size.Height + 10); }
Private Function GetPointsForArrowCallout(centerX As Double, centerY As Double, rectWidth As Double, rectHeight As Double) As PointCollection Dim points As PointCollection = New PointCollection() Dim rectLeft As Double = centerX - rectWidth / 2 Dim rectRight As Double = centerX + rectWidth / 2 Dim rectTop As Double = centerY - rectHeight / 2 Dim rectBottom As Double = centerY + rectHeight / 2 Dim angle As Double = Math.Atan2(-centerY, centerX) Dim angleOffset1 As Double = 0.4 Dim angleOffset2 As Double = 0.04 Dim arrowHeight As Double = 0.4 * rectHeight Dim hypotenuse As Double = arrowHeight / Math.Cos(angleOffset1) Dim subHypotenuse As Double = arrowHeight / Math.Cos(angleOffset2) Dim isNearBottom As Boolean = Math.Abs(rectTop) > Math.Abs(rectBottom) Dim nearHorizontalEdge As Double If (isNearBottom) Then nearHorizontalEdge = rectBottom Else nearHorizontalEdge = rectTop End If Dim isNearRight As Boolean = Math.Abs(rectLeft) > Math.Abs(rectRight) Dim nearVerticalEdge As Double If (isNearRight) Then nearVerticalEdge = rectRight Else nearVerticalEdge = rectLeft End If Dim isHorizontalCrossed As Boolean = Math.Abs(nearHorizontalEdge) > Math.Abs(nearVerticalEdge) Dim nearEdge As Double If (isHorizontalCrossed) Then nearEdge = nearHorizontalEdge Else nearEdge = nearVerticalEdge End If Dim factor As Int16 If (nearEdge > 0) Then factor = -1 Else factor = 1 End If Dim crossedPointOffsetToCenter As Double If (isHorizontalCrossed) Then crossedPointOffsetToCenter = rectHeight / (2 * Math.Tan(angle)) * factor Else crossedPointOffsetToCenter = rectWidth * Math.Tan(angle) * factor / 2 End If 'Arrow points points.Add(New Point(0, 0)) points.Add(New Point(Math.Cos(angle + angleOffset1) * hypotenuse, -Math.Sin(angle + angleOffset1) * hypotenuse)) points.Add(New Point(Math.Cos(angle + angleOffset2) * subHypotenuse, -Math.Sin(angle + angleOffset2) * subHypotenuse)) 'Rectangle points If (isHorizontalCrossed) Then points.Add(New Point(-nearEdge / Math.Tan(angle + angleOffset2), nearEdge)) If (isNearBottom) Then points.Add(New Point(rectLeft, rectBottom)) points.Add(New Point(rectLeft, rectTop)) points.Add(New Point(rectRight, rectTop)) points.Add(New Point(rectRight, rectBottom)) Else points.Add(New Point(rectRight, rectTop)) points.Add(New Point(rectRight, rectBottom)) points.Add(New Point(rectLeft, rectBottom)) points.Add(New Point(rectLeft, rectTop)) End If points.Add(New Point(-nearEdge / Math.Tan(angle - angleOffset2), nearEdge)) Else points.Add(New Point(nearEdge, -nearEdge * Math.Tan(angle + angleOffset2))) If (isNearRight) Then points.Add(New Point(rectRight, rectBottom)) points.Add(New Point(rectLeft, rectBottom)) points.Add(New Point(rectLeft, rectTop)) points.Add(New Point(rectRight, rectTop)) Else points.Add(New Point(rectLeft, rectTop)) points.Add(New Point(rectRight, rectTop)) points.Add(New Point(rectRight, rectBottom)) points.Add(New Point(rectLeft, rectBottom)) End If points.Add(New Point(nearEdge, -nearEdge * Math.Tan(angle - angleOffset2))) End If 'Arrow points points.Add(New Point(Math.Cos(angle - angleOffset2) * subHypotenuse, -Math.Sin(angle - angleOffset2) * subHypotenuse)) points.Add(New Point(Math.Cos(angle - angleOffset1) * hypotenuse, -Math.Sin(angle - angleOffset1) * hypotenuse)) Return points End Function
PointCollection GetPointsForArrowCallout(double centerX, double centerY, double rectWidth, double rectHeight) { var points = new PointCollection(); double rectLeft = centerX - rectWidth / 2; double rectRight = centerX + rectWidth / 2; double rectTop = centerY - rectHeight / 2; double rectBottom = centerY + rectHeight / 2; double angle = Math.Atan2(-centerY, centerX); double angleOffset1 = 0.4; double angleOffset2 = 0.04; double arrowHeight = 0.4 * rectHeight; double hypotenuse = arrowHeight / Math.Cos(angleOffset1); double subHypotenuse = arrowHeight / Math.Cos(angleOffset2); bool isNearBottom = Math.Abs(rectTop) > Math.Abs(rectBottom); double nearHorizontalEdge = isNearBottom ? rectBottom : rectTop; bool isNearRight = Math.Abs(rectLeft) > Math.Abs(rectRight); double nearVerticalEdge = isNearRight ? rectRight : rectLeft; bool isHorizontalCrossed = Math.Abs(nearHorizontalEdge) > Math.Abs(nearVerticalEdge); double nearEdge = isHorizontalCrossed ? nearHorizontalEdge : nearVerticalEdge; int factor = nearEdge > 0 ? -1 : 1; double crossedPointOffsetToCenter = isHorizontalCrossed ? rectHeight / (2 * Math.Tan(angle)) * factor : rectWidth * Math.Tan(angle) * factor / 2; // Arrow points points.Add(new Point(0, 0)); points.Add(new Point(Math.Cos(angle + angleOffset1) * hypotenuse, -Math.Sin(angle + angleOffset1) * hypotenuse)); points.Add(new Point(Math.Cos(angle + angleOffset2) * subHypotenuse, -Math.Sin(angle + angleOffset2) * subHypotenuse)); // Rectangle points if (isHorizontalCrossed) { points.Add(new Point(-nearEdge / Math.Tan(angle + angleOffset2), nearEdge)); if (isNearBottom) { points.Add(new Point(rectLeft, rectBottom)); points.Add(new Point(rectLeft, rectTop)); points.Add(new Point(rectRight, rectTop)); points.Add(new Point(rectRight, rectBottom)); } else { points.Add(new Point(rectRight, rectTop)); points.Add(new Point(rectRight, rectBottom)); points.Add(new Point(rectLeft, rectBottom)); points.Add(new Point(rectLeft, rectTop)); } points.Add(new Point(-nearEdge / Math.Tan(angle - angleOffset2), nearEdge)); } else { points.Add(new Point(nearEdge, -nearEdge * Math.Tan(angle + angleOffset2))); if (isNearRight) { points.Add(new Point(rectRight, rectBottom)); points.Add(new Point(rectLeft, rectBottom)); points.Add(new Point(rectLeft, rectTop)); points.Add(new Point(rectRight, rectTop)); } else { points.Add(new Point(rectLeft, rectTop)); points.Add(new Point(rectRight, rectTop)); points.Add(new Point(rectRight, rectBottom)); points.Add(new Point(rectLeft, rectBottom)); } points.Add(new Point(nearEdge, -nearEdge * Math.Tan(angle - angleOffset2))); } // Arrow points points.Add(new Point(Math.Cos(angle - angleOffset2) * subHypotenuse, -Math.Sin(angle - angleOffset2) * subHypotenuse)); points.Add(new Point(Math.Cos(angle - angleOffset1) * hypotenuse, -Math.Sin(angle - angleOffset1) * hypotenuse)); return points; }
To Render the annotations in chart, follow these steps:
<Chart:C1FlexChart.Layers> <Annotation:AnnotationLayer> <Annotation:AnnotationLayer.Annotations> <Annotation:Polygon x:Name="arrowCallout" Content="Low" SeriesIndex="0" PointIndex="1" Attachment="DataIndex"> <Annotation:Polygon.Style> <Chart:ChartStyle Fill="#C800FF00" Stroke="Green"/> </Annotation:Polygon.Style> </Annotation:Polygon> <Annotation:Polygon x:Name="lineCallout" Content="High" SeriesIndex="0" PointIndex="4" Attachment="DataIndex"> <Annotation:Polygon.Style> <Chart:ChartStyle Fill="#C8FF0000" Stroke="Red" /> </Annotation:Polygon.Style> </Annotation:Polygon> </Annotation:AnnotationLayer.Annotations> </Annotation:AnnotationLayer> </Chart:C1FlexChart.Layers>
Private Sub flexChart_Rendered(sender As Object, e As RenderEventArgs) If (_engine Is Nothing) Then _engine = e.Engine SetUpAttotations() End If End Sub
private void flexChart_Rendered(object sender, C1.Xaml.Chart.RenderEventArgs e) { if (_engine == null) { _engine = e.Engine; SetUpAnnotations(); } }