FlexChart for UWP
吹き出しの作成
FlexChart > FlexChart の操作 > FlexChart の要素 > 注釈 > 吹き出しの作成

チャート内で吹き出しを使用して、データ系列や個別のデータポイントの詳細を読みやすい形式で表示できます。データポイントに接続された吹き出しは、チャート領域内の見た目の乱雑さを最小限に抑え、チャートデータを見やすく、かつわかりやすくします。FlexChart では、Polygon 型の注釈をカスタマイズして、直線または矢印の接続線が付いた吹き出しをチャートに作成できます。

この例では、クイックスタートで作成したサンプルを使用して、矢印付き吹き出しと接続線付きの多角形注釈を作成します。それには、Points プロパティと ContentCenter プロパティを使用して、それぞれ多角形の頂点の座標と注釈のコンテンツの中心を定義します。

それぞれのデータポイントに接続される吹き出しを作成するには、次の手順に従います。

次の図に、矢印と接続線でデータポイントに接続される多角形注釈を示します。

Annotation connector

手順 1:接続線付きの注釈の作成

直線付きの吹き出しを作成するには、次のコードを使用します。

    ...
    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
    // 多角形の線吹き出しの注釈を作成する
    lineCallout.SeriesIndex = 2;
    lineCallout.PointIndex = 2;
    lineCallout.ContentCenter = new Point(25, -40);
    // 線吹き出しの注釈のポイントのリストを作成する
    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();
}

先頭に戻る

手順 2:矢印付き注釈吹き出しの作成

  1. 矢印付きの吹き出しを作成するには、次のコードを使用します。
    Private Sub SetUpAttotations()
        ' 多角形の矢印吹き出しの注釈を作成する
        Dim arrowCalloutContentCenter As Point = New Point(25, -50)
        arrowCallouts.ContentCenter = arrowCalloutContentCenter
    
        ' GetPointsForArrowCallout()を呼び出して矢印吹き出しのポイントのリストを作成する
        arrowCallouts.Points = GetPointsForArrowCallout(arrowCalloutContentCenter.X,
        arrowCalloutContentCenter.Y, "Low")
        arrowCallouts.SeriesIndex = 1
        arrowCallouts.PointIndex = 2
        ...
    
    private void SetUpAnnotations()
    {
    
        // 多角形の矢印吹き出しの注釈を作成する
        var arrowCalloutContentCenter = new Point(25, -50);
        arrowCallout.ContentCenter = arrowCalloutContentCenter;
    
        // GetPointsForArrowCallout()を呼び出して矢印吹き出しのポイントのリストを作成する
        arrowCallout.Points = GetPointsForArrowCallout(arrowCalloutContentCenter.X,
            arrowCalloutContentCenter.Y, "Low");
        arrowCallout.SeriesIndex = 1;
        arrowCallout.PointIndex = 2;
    
  2. GetPointsForArrowCallout() メソッドを定義して、矢印付きの吹き出しのポイントを指定します。
    1. 矢印付きの吹き出し内のコンテンツ文字列のサイズを測定し、これを再利用して、矢印付きの注釈のサイズを計算して設定するには、次のコードを使用します。
      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);
      }
      
    2. 矢印付きの注釈のサイズとポイントを計算するには、次のようにオーバーロードメソッド GetPointsForArrowCallout() を定義します。
      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
      
          '矢印のポイント
          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))
      
          '四角形のポイント
          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
      
          '矢印のポイント
          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;
      
          // 矢印のポイント
          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));
      
          // 四角形のポイント
          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)));
          }
      
          // 矢印のポイント
          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;
      }
      

先頭に戻る

手順 3:チャートへの注釈のレンダリング

チャートの注釈をレンダリングするには、次の手順に従います。

  1. ンダリングエンジンのグローバルフィールドを定義します。
    Dim _engine As IRenderEngine
    
    IRenderEngine _engine;
    
  2. 次のコードを使用して、AnnotationLayer クラスのインスタンスを作成し、吹き出し注釈を annotationLayer に追加します。
        <Chart:C1FlexChart.Layers>
        <Annotation:AnnotationLayer>
            <Annotation:AnnotationLayer.Annotations>
                <Annotation:Polygon x:Name="arrowCallout" Content="最低" 
                                    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="最高"  
                                    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>
    
  3. 吹き出しをレンダリングするには、FlexChart の Rendered イベントで次のコードを使用します。
    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();
        }
    }
    

先頭に戻る