FlexChart / Working with FlexChart / FlexChart Elements / Annotations / Creating Callouts
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.

The following image illustrates polygon annotations connected to data points through arrow and line connectors.

Annotation connector

To create callouts connected with respective data points, follow these steps:

Create annotation with line connector

To create a line callout, use the following code.

        ...
        ' Create and stylize a line callout annotation of polygon type
        Dim lineCallout = New C1.WPF.Chart.Annotation.Polygon() With {
     .Content = "High",
     .Style = New ChartStyle() With {
         .Fill = New SolidColorBrush(Colors.Red) With {
             .Opacity = 200.0 / 255
        },
         .Stroke = New SolidColorBrush(Colors.Red)
    },
     .Attachment = AnnotationAttachment.DataIndex,
     .SeriesIndex = 0,
     .PointIndex = 1,
     .ContentCenter = New Point(25, -40),
     .Points = New PointCollection(New Point() {New Point(0, 0), New Point(25, -25), New Point(50, -25), New Point(50, -50), New Point(25, -75), New Point(0, -50),
        New Point(0, -25), New Point(25, -25), New Point(0, 0)})
}
        ...

Back to Top

Create arrow annotation callout

  1. To create an arrow callout use the following code.
    Private Sub SetUpAnnotations()
        annotationLayer.Annotations.Clear()
        ' Create an arrow callout annotation of polygon type  
        Dim contentCenter = New Point(25, -50)
        'Stylise the arrow callout annotation
        Dim arrowCallout = New Annotation.Polygon() With {
         .Content = "Low",
         .Style = New ChartStyle() With {
             .Fill = New SolidColorBrush(Colors.Green) With {
                 .Opacity = 200.0 / 255
            },
             .Stroke = New SolidColorBrush(Colors.Green)
        },
         .Attachment = AnnotationAttachment.DataIndex,
         .SeriesIndex = 1,
         .PointIndex = 1,
         .ContentCenter = contentCenter,
         .Points = GetPointsForArrowCallout(contentCenter.X, contentCenter.Y, "Low")
    }
        ...
    

  2. Define the GetPointsForArrowCallout() method to specify the points for arrow callout.
    1. To measure the size of content string in arrow callout, and reuse it to calculate and set the dimensions of arrow annotation, use the following code.
      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, CSng(size.Width) + 10, CSng(size.Height) + 10)
      End Function
      

    2. To calculate the dimensions and points for arrow annotations, define the method overload GetPointsForArrowCallout() as shown below.
      Private Function GetPointsForArrowCallout(centerX As Double, centerY As Double, rectWidth As Double, rectHeight As Double) As PointCollection
          Dim points = 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, rectBottom, rectTop)
          Dim isNearRight As Boolean = Math.Abs(rectLeft) > Math.Abs(rectRight)
          Dim nearVerticalEdge As Double = If(isNearRight, rectRight, rectLeft)
          Dim isHorizontalCrossed As Boolean = Math.Abs(nearHorizontalEdge) > Math.Abs(nearVerticalEdge)
          Dim nearEdge As Double = If(isHorizontalCrossed, nearHorizontalEdge, nearVerticalEdge)
      
          Dim factor As Integer = If(nearEdge > 0, -1, 1)
          Dim crossedPointOffsetToCenter As Double = If(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 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
      

Back to Top

Render the annotations in chart

To Render the annotations in chart, follow these steps:

  1. Define global field of render engine.
    Dim _engine As IRenderEngine
    

  2. To create an instance of AnnotationLayer use the following code.
    XAML
    Copy Code
    <c1:C1FlexChart.Layers>
        <c1:AnnotationLayer x:Name="annotationLayer" />
    </c1:C1FlexChart.Layers>
    

  3. To add the annotation callouts in annotationLayer, use the following code.
        annotationLayer.Annotations.Add(arrowCallout)
        annotationLayer.Annotations.Add(lineCallout)
    End Sub
    

  4. To render the callouts use the following code in the Rendered event of chart.
    Private Sub flexChart_Rendered(sender As Object, e As C1.WPF.Chart.RenderEventArgs) Handles flexChart.Rendered
        If _engine Is Nothing Then
            _engine = e.Engine
            SetUpAnnotations()
        End If
    End Sub
    

Back to Top