drov drov - 3 months ago 28
C# Question

How to add an arrow in a chart pointing some data

I am currently working on a module to create charts to display data.
I use System.Windows.Forms.DataVisualization.Charting.Chart.

I have two striplines showing the the average result we got and another one showing what we want.

So far I was really happy with what I had but I want to add explicit arrow to point these lines. And I can't figure out how to do it.

I saw that Line Annotation might be of help but I couldn't find a way to do what I wanted.

Here is an example of what I would like to do :

enter image description here

TaW TaW
Answer

You have a choice of using

  • Annotations or
  • GDI+ drawing

In both cases the challenge is to get the positions right.

The more natural way to go is using Annotations, so let's look at this first:

There are various types but different capabilities; text can be displayed by RectangleAnnotation or a TextAnnotation. Lines and arrowheads can only be displayed by LineAnnotations. So we need a pair of Line- plus TextAnnotation for each of your two lines.

Like many other chart elements annotations are positioned in percentages of their respective containers; this makes things rather tricky at times.

To place a line annotation all to the right of the chart you could set its X property to 100; to let it go to the left you set the width to a negative number. The problems are starting after that..

To find out where the right edge of the ChartArea is you need to code the Pre- or PostPaint event and use the ToRectangleF method.

To find out the y-value you will want to calculate it from a data value; for this you can use the AxisY.ValueToPixelPosition method, which converts to pixels, from which you can calculate the percantage using the chart's ClientArea along with the ChartArea percentage size.

Complicated? Yup. Annotations get a lot simpler to use if you can anchor them to a certain DataPoint; but yours are outside the ChartArea..

Here is a function that should help when doing the calculations:

double PercentFromValue(Chart chart, ChartArea ca, double value)
{
    Axis ay = ca.AxisY;
    RectangleF car = ca.Position.ToRectangleF();
    double py = ay.ValueToPixelPosition(value);
    int caHeight = (int)(chart.ClientRectangle.Height * car.Height / 100f);
    return 100d * py / caHeight;
}

Note that it will only work reliably when called from of of the Pre/PostPaint events..

So this is an example of a PrePaint event that positions a LineAnnotation lAnn:

private void chart1_PrePaint(object sender, ChartPaintEventArgs e)
{
    Rectangle cr = chart1.ClientRectangle;
    ChartArea ca = chart1.ChartAreas[0];
    RectangleF car = ca.Position.ToRectangleF();

    lAnn.Width = car.Width - lAnn.X;
    lAnn.Y =  PercentFromValue(chart1, ca, someDataValue);
}

When you insert a valid DataPoint YValue the starting y-position will be set. You can play with it until you find a nice combination of setting the four position properties..

When creating and adding the four(!) annotations you may want to keep class level references, so you won't have to refer to them from the Annotations collection..

For the LineAnnotation you will want to set the linewidth, color and the capstyle, either using the EndCap or the StartCap:

lAnn.EndCap = LineAnchorCapStyle.Arrow;


GDI+ drawing is more straight-forward, provided you know where you want to draw the lines and the text.

It is also done in the PrePaint event, again using the ValueToPixelPosition to find the pixelposition of the two data lines.. Other than that is all the usual stuff with Graphics.DrawLine, a Pen with and Start- or EndCap and Graphics.DrawString or maybe TextRenderer.DrawText..

I'm frankly not sure which way I would choose..