StackedBar borders between different series

后端 未结 1 542
悲&欢浪女
悲&欢浪女 2021-01-23 18:53

What i want is to set borders between two series in StackedBar Like this image The bold black line between blue and green

I can not figure out any idea to speci

相关标签:
1条回答
  • 2021-01-23 19:04

    There are no built-in chart elements that could easily be made into a borderline between those two Series. (Creating LineAnnotations to achieve this would be a nightmare..)

    So the way to add the lines is to draw them onto the surface of the Chart. This is most naturally done in the PostPaint event, provided just for such adornments.

    Here the Axes have handy functions to convert between the data values and the pixel positions. We need the ValueToPixelPosition method.

    I will take you through variations of Chart drawing that gradually get a little more complicated as we approach the final version..:

    Let's start with a simple example: Let's build and adorn a StackedArea chart; here is the drawing code:

    private void chart2_PostPaint(object sender, ChartPaintEventArgs e)
    {
        Series s = chart1.Series[0];
        ChartArea ca = chart1.ChartAreas[0];
        var pp = s.Points.Select(x=>
            new PointF( (float)ca.AxisX.ValueToPixelPosition(x.XValue),
                        (float)ca.AxisY.ValueToPixelPosition(x.YValues[0])  )   );
    
        if (s.Points.Count >  1) 
            using (Pen pen = new Pen(Color.DarkOliveGreen, 4f))
                e.ChartGraphics.Graphics.DrawLines(pen, pp.ToArray());
    }
    

    The Points.Select is really just a shorthand for a loop; so after creating the pixel point list we simply draw it.

    Now, as you can see, as StackedArea chart is pointy and doesn't look like a StackedBar or StackedColumn chart. So let's cheat and 'rectify' the area chart by adding a few extra points:

    void rectifyArea(Series s)
    {
        for (int i = s.Points.Count - 1; i > 0; i--)
            s.Points.InsertXY(i, i - 1, s.Points[i].YValues[0]);
    }
    

    Results:

    Now that was not so hard; unfortunately you just can't turn a StackedArea to go from left to right instead of bottom-up. So we need to change the chart type to a Bar type eventually..

    Here the challenge is to find the right upper and lower corners of those bars. We do have the DataPoint values, but these are in the middle of the bars. So we need to add/subtract half of the Bars' width to get the corners. For this we need the width.

    While you have set it with the PointWidth property to 1, what we really need is the pixel width. We best get it by subtracting the pixel coordinates of two neighbouring points.

    This makes the PostPaint event a little longer, but still not overly complicated; we will start with a StackedColumn chart, adding two corner points for each data point:

    private void chart1_PostPaint(object sender, ChartPaintEventArgs e)
    {
        Series s = chart1.Series[0];
        ChartArea ca = chart1.ChartAreas[0];
        if (s.Points.Count <= 0) return;
    
        // calculate width of a column:
        int pp1 = (int)ca.AxisX.ValueToPixelPosition(s.Points[0].XValue);
        int pp2 = (int)ca.AxisX.ValueToPixelPosition(s.Points[1].XValue);
        float w2 = Math.Abs(pp2 - pp1) / 2f;
    
        List<PointF> points = new List<PointF>();
        for (int i = 0; i < s.Points.Count; i++)
        {
            DataPoint dp = s.Points[i];
            points.Add(new PointF( (int)ca.AxisX.ValueToPixelPosition(dp.XValue) - w2,
                                   (int)ca.AxisY.ValueToPixelPosition(dp.YValues[0]) ));
    
            points.Add(new PointF( (int)ca.AxisX.ValueToPixelPosition(dp.XValue) + w2,
                                   (int)ca.AxisY.ValueToPixelPosition(dp.YValues[0]) ));
        }
    
        if (points.Count > 1)
            using (Pen pen = new Pen(Color.DarkOliveGreen, 4f))
                e.ChartGraphics.Graphics.DrawLines(pen, points.ToArray());
    }
    

    Now this looks pretty much identical to our fake version of the 'rectified area chart'. What will we need to change to apply this to a StackedBar chart? Almost nothing! The only two things we need to take care of are

    • the direction of the y-axis. Since the points move upward but the pixel coordinates of GDI+ graphhics move downwards we need to create the two cornerpoints in the reverse order.
    • And we need to reverse the x- and y coodinates, as the axes are reversed for all types of Bar charts.

    Here are the two stacked charts with a border:

    This is the loop for the StackBar chart:

    for (int i = 0; i < s.Points.Count;  i++)
    {
       points.Add(new PointF( (float)ca.AxisY.ValueToPixelPosition(s.Points[i].YValues[0]),
                               (float)ca.AxisX.ValueToPixelPosition(s.Points[i].XValue) + w2));
       points.Add(new PointF( (float)ca.AxisY.ValueToPixelPosition(s.Points[i].YValues[0]),
                               (float)ca.AxisX.ValueToPixelPosition(s.Points[i].XValue) - w2));
    }
    

    Note that I am drawing with a fixed pen width of 4 pixels. To make it scale with the Chart you may want to calculate the pen width dynamically..

    Update

    To draw borders on top of several series you can put the code into a loop like this:

    private void chart1_PostPaint(object sender, ChartPaintEventArgs e)
    {
        Chart  chart = chart1;
        Series s0 = chart.Series[0];
        ChartArea ca = chart.ChartAreas[0];
    
        // calculate width of a bar:
        int pp1 = (int)ca.AxisX.ValueToPixelPosition(s0.Points[0].XValue);
        int pp2 = (int)ca.AxisX.ValueToPixelPosition(s0.Points[1].XValue);
        float delta = Math.Abs(pp2 - pp1) / 2f;
    
        for (int s = 0; s < chart.Series.Count; s++)
        {
           List<PointF> points = new List<PointF>();
           for (int p = 0; p < chart.Series[s].Points.Count; p++)
           {
             DataPoint dp = chart.Series[s].Points[p];
             double v = GetStackTopValue(chart, s, p);
             points.Add(new PointF((float)ca.AxisY.ValueToPixelPosition(v),
                                    (float)ca.AxisX.ValueToPixelPosition(dp.XValue) + delta));
             points.Add(new PointF((float)ca.AxisY.ValueToPixelPosition(v),
                                   (float)ca.AxisX.ValueToPixelPosition(dp.XValue) - delta));
            }
            using (Pen pen = new Pen(Color.DarkOliveGreen, 3f))
                e.ChartGraphics.Graphics.DrawLines(pen, points.ToArray());
        }
    }
    
    double GetStackTopValue(Chart chart, int series, int point)
    {
        double v = 0;
        for (int i = 0; i < series + 1; i++)
            v += chart.Series[i].Points[point].YValues[0];
        return v;
    }
    
    0 讨论(0)
提交回复
热议问题