Search code examples
c#winformschartsstacked-area-chart

Chart: issue with stacked areas order


I would like to display the 2 different stacked area elements according to their parameters. But the chart area displays it not as specified and puts the second block at the top right corner of the first stacked area. They should be displayed side by side not stacked.

...
using System.Windows.Forms.DataVisualization.Charting;

namespace Gantt_Tool
{
public partial class ReadModel : Form
{
    public ReadModel()
    {
        InitializeComponent();
        CreateChart();
    }

    private void ReadModel_Load(object sender, EventArgs e)
    {            
    }

    private void CreateChart()
    {
        chart1.Series.Add($"a");
        chart1.Series[$"a"].Points.Add(new DataPoint(0, 2));
        chart1.Series[$"a"].Points.Add(new DataPoint(2, 2));
        chart1.Series[$"a"].ChartType = SeriesChartType.StackedArea;

        chart1.Series.Add($"b");
        chart1.Series[$"b"].Points.Add(new DataPoint(2, 3));
        chart1.Series[$"b"].Points.Add(new DataPoint(5, 3));
        chart1.Series[$"b"].ChartType = SeriesChartType.StackedArea;    
    }
}    

https://i.sstatic.net/FHuRT.png

How can I set the blocks to a side by side order or placed freely? And how can I get unfilled rectangles?

Update: Here is an example of how it should look like:

enter image description here


Solution

  • From your comments, I take that you want to have a chart with freely-placed, unfilled rectangles and labels.

    None of the MSChart types will do that.

    Here is how to use a Point chart with a few lines of owner-drawing. Note how nicely this will behave when resizing the chart...

    enter image description here

    Here is the setup:

    Axis ax = chart1.ChartAreas[0].AxisX;
    Axis ay = chart1.ChartAreas[0].AxisY;
    ax.Maximum = 9;  // pick or calculate
    ay.Maximum = 6;  // minimum and..
    ax.Interval = 1; // maximum values..
    ay.Interval = 1; // .. needed
    ax.MajorGrid.Enabled = false;
    ay.MajorGrid.Enabled = false;
    
    Series s1 = chart1.Series.Add("A");
    s1.ChartType = SeriesChartType.Point;
    

    Now we add your five boxes. I use a sepcial function that adds the points and stuffs the box size into the Tag of each point..:

    AddBox(s1, 1, 0, 3, 1, "# 1");
    AddBox(s1, 2, 1, 2, 2, "# 2");
    AddBox(s1, 4, 0, 4, 2, "# 3");
    AddBox(s1, 4, 2, 2, 2, "# 4");
    AddBox(s1, 4, 4, 1, 1, "# 5");
    
    int AddBox(Series s, float x, float y, float w, float h, string label)
    {
        return AddBox(s, new PointF(x, y), new SizeF(w, h), label);
    }
    
    
    int AddBox(Series s, PointF pt, SizeF sz, string label)
    {
        int i = s.Points.AddXY(pt.X, pt.Y);
        s.Points[i].Tag = sz;
        s.Points[i].Label = label;
        s.Points[i].LabelForeColor = Color.Transparent;
        s.Points[i].Color = Color.Transparent;
        return i;
    }
    

    The drawing is also simple; only the use of the Axes function ValueToPixelPosition is noteworthy..:

    private void chart1_PostPaint(object sender, ChartPaintEventArgs e)
    {
        if (chart1.Series[0].Points.Count <= 0) return;
    
        Axis ax = chart1.ChartAreas[0].AxisX;
        Axis ay = chart1.ChartAreas[0].AxisY;
        Graphics g = e.ChartGraphics.Graphics;
        using (StringFormat fmt = new StringFormat()
        { Alignment = StringAlignment.Center, 
          LineAlignment = StringAlignment.Center})
            foreach (Series s in chart1.Series)
            {
                foreach (DataPoint dp in s.Points)
                {
                if (dp.Tag == null) break;
                SizeF sz = (SizeF)dp.Tag;
                double vx2 = dp.XValue + sz.Width;
                double vy2 = dp.YValues[0] + sz.Height;
                int x1 = (int)ax.ValueToPixelPosition(dp.XValue);
                int y1 = (int)ay.ValueToPixelPosition(dp.YValues[0]);
                int x2 = (int)ax.ValueToPixelPosition(vx2);
                int y2 = (int)ay.ValueToPixelPosition(vy2);
                Rectangle rect = Rectangle.FromLTRB(x1, y2, x2, y1);
    
                using (Pen pen = new Pen(s.Color, 2f))
                    g.DrawRectangle(pen, rect);
                g.DrawString(dp.Label, Font, Brushes.Black, rect, fmt);
                }
            }
    }
    

    Here is a little Linq to calculate the Minimum and Maximum values for the Axes to hold just the right size; chart won't do it by itself since the size in the tags of the points is not known...

    private void setMinMax(Chart chart, ChartArea ca)
    {
        var allPoints = chart.Series.SelectMany(x => x.Points);
        double minx = allPoints.Select(x => x.XValue).Min();
        double miny = allPoints.Select(x => x.YValues[0]).Min();
        double maxx = allPoints.Select(x => x.XValue + ((SizeF)x.Tag).Width).Max();
        double maxy = allPoints.Select(x => x.YValues[0] + ((SizeF)x.Tag).Height).Max();
    
        ca.AxisX.Minimum = minx;
        ca.AxisX.Maximum = maxx;
        ca.AxisY.Minimum = miny;
        ca.AxisY.Maximum = maxy;
    }