Search code examples
c#model-view-controllerchartsscaleaxis

How Do I Get The Scale of The Secondary Axis to be Based on My Series Values?


I'm using System.Web.UI.DataVisualization. Charting to create charts in MVC.

I have a chart displaying values in a StackedColumn100 SeriesChartType with the corresponding y-axis values on the primary y-axis on the left side.

Since it is a 100% stacked column series, the primary y-axis is scaled from 0 to 100.

I have then added a secondary series in the form of a Line SeriesChartType tied to a secondary axis (on the right side). I would like this axis to adjust its scale based on the values in the series but it doesn't. No matter what the highest value of this series is, the secondary y-axis also has a scale between 0 to 100.

If I manually set the maximum value for the secondary y-axis the following way: chart.ChartAreas[0].AxisY2.Maximum = 20;. It works but I don't want to do that since the maximum value can differ greatly based on the search criteria used.

I have really tried to find a solution for this but I can't. According to the documentation and samples it seems that the scale should be based on the series values but I don't get it to work that way. Any help would be greatly appreciated!

Below is a stand alone test function that recreates the problem. I call the function from my view with the following line:

<p><img src="@Url.Action("CreateChart_TestSecondaryAxis")" /> </p>

public FileResult CreateChart_TestSecondaryAxis()
        {
            System.Web.UI.DataVisualization.Charting.Chart chart = new System.Web.UI.DataVisualization.Charting.Chart();
            chart.Width = 800;
            chart.Height = 400;
            chart.BackColor = Color.FromArgb(211, 223, 240);
            chart.BorderlineDashStyle = ChartDashStyle.Solid;
            chart.BackSecondaryColor = Color.White;
            chart.BackGradientStyle = GradientStyle.TopBottom;
            chart.BorderlineWidth = 1;
            chart.Palette = ChartColorPalette.BrightPastel;
            chart.BorderlineColor = Color.FromArgb(26, 59, 105);
            chart.RenderType = RenderType.BinaryStreaming;
            chart.BorderSkin.SkinStyle = BorderSkinStyle.Emboss;
            chart.AntiAliasing = AntiAliasingStyles.All;
            chart.TextAntiAliasingQuality = TextAntiAliasingQuality.Normal;

            ChartArea chartArea = new ChartArea();
            chartArea.Name = "TestSecondaryAxis";
            chartArea.BackColor = Color.Transparent;
            chartArea.AxisX.IsLabelAutoFit = false;
            chartArea.AxisY.IsLabelAutoFit = false;
            chartArea.AxisX.LabelStyle.Font =
               new Font("Verdana,Arial,Helvetica,sans-serif",
                        8F, FontStyle.Regular);
            chartArea.AxisY.LabelStyle.Font =
               new Font("Verdana,Arial,Helvetica,sans-serif",
                        8F, FontStyle.Regular);
            chartArea.AxisY.LineColor = Color.FromArgb(64, 64, 64, 64);
            chartArea.AxisX.LineColor = Color.FromArgb(64, 64, 64, 64);
            chartArea.AxisY.MajorGrid.LineColor = Color.FromArgb(64, 64, 64, 64);
            chartArea.AxisX.MajorGrid.LineColor = Color.FromArgb(64, 64, 64, 64);

            chartArea.AxisX.Title = "Airport";
            chartArea.AxisY.Title = "LandingConf";
            chartArea.AxisY.TextOrientation = TextOrientation.Rotated270;
            chartArea.AxisX.LabelStyle.IsEndLabelVisible = true;
            chart.ChartAreas.Add(chartArea);

            Series seriesPrimaryAxisConf3 = new Series();
            seriesPrimaryAxisConf3.Name = "Conf 3";
            seriesPrimaryAxisConf3.IsValueShownAsLabel = false;
            seriesPrimaryAxisConf3.Color = Color.Blue;
            seriesPrimaryAxisConf3.ChartType = SeriesChartType.StackedColumn100;
            seriesPrimaryAxisConf3.BorderWidth = 2;
            seriesPrimaryAxisConf3.ChartArea = "TestSecondaryAxis";
            DataPoint point;


            for (int i = 1; i < 11; i++)
            {
                point = new DataPoint();
                point.AxisLabel = "Airport" + i.ToString();
                point.YValues = new double[] { i };

                seriesPrimaryAxisConf3.Points.Add(point);
            }

            chart.Series.Add(seriesPrimaryAxisConf3);

            Series seriesPrimaryAxisConfFull = new Series();
            seriesPrimaryAxisConfFull.Name = "Conf Full";
            seriesPrimaryAxisConfFull.IsValueShownAsLabel = false;
            seriesPrimaryAxisConfFull.Color = Color.Red;
            seriesPrimaryAxisConfFull.ChartType = SeriesChartType.StackedColumn100;
            seriesPrimaryAxisConfFull.BorderWidth = 2;
            seriesPrimaryAxisConfFull.ChartArea = "TestSecondaryAxis";


            for (int i = 1; i < 11; i++)
            {
                point = new DataPoint();
                point.AxisLabel = "Airport" + i.ToString();
                point.YValues = new double[] { 11-i };

                seriesPrimaryAxisConfFull.Points.Add(point);
            }

            chart.Series.Add(seriesPrimaryAxisConfFull);

            Series seriesSecondaryAxisNoOfFlights = new Series();
            seriesSecondaryAxisNoOfFlights.Name = "NoOfFLights";
            seriesSecondaryAxisNoOfFlights.IsValueShownAsLabel = false;
            seriesSecondaryAxisNoOfFlights.Color = Color.Red;
            seriesSecondaryAxisNoOfFlights.ChartType = SeriesChartType.Line;
            seriesSecondaryAxisNoOfFlights.BorderWidth = 2;
            seriesSecondaryAxisNoOfFlights.ChartArea = "TestSecondaryAxis";


            for (int i = 1; i < 11; i++)
            {
                point = new DataPoint();
                point.AxisLabel = "Airport" + i.ToString();
                point.YValues = new double[] { i };

                seriesSecondaryAxisNoOfFlights.Points.Add(point);
            }

            chart.Series.Add(seriesSecondaryAxisNoOfFlights);
            chart.Series["NoOfFLights"].YAxisType = AxisType.Secondary;
            chart.ChartAreas["TestSecondaryAxis"].AxisY2.LineColor = Color.Transparent;
            chart.ChartAreas["TestSecondaryAxis"].AxisY2.MajorGrid.Enabled = false;
            chart.ChartAreas["TestSecondaryAxis"].AxisY2.MajorTickMark.Enabled = false;

            MemoryStream ms = new MemoryStream();
            chart.SaveImage(ms);
            return File(ms.GetBuffer(), @"image/png");
        }

Solution

  • MSChart is a nice control but unfortunately Microsoft has always failed to properly document it.

    And with the latest changes at MSDN things have gone from bad to worse, so I can't actually point to the rules that go for the various ChartTypes.

    In your case I deduct this (rather wacky) rule:

    To attach a non 100%-series to an indepently scaled secondary y-axis it must be the first series but the stacked series still must be added first.

    So, if you want to get a result like this:

    enter image description here

    ..you need to adapt the code. Here are the changes and additions needed..:

    First we have to insert the line series at the front but do that after the stack100 series have beeen added..:

    chart.Series.Insert(0, seriesSecondaryAxisNoOfFlights);  // instead of adding it
    

    Next we need to owner-draw the line as it would get burried under the columns otherwise.

    Code the PostPaint event like this:

    private void Chart_PostPaint(object sender, ChartPaintEventArgs e)
    {
        Chart chart = sender as Chart;  //*
        Series s = chart.Series[0];  // make sure to pick the right one!
    
        Axis ax = chart.ChartAreas[0].AxisX;
        Axis ay = chart.ChartAreas[0].AxisY2;  // !!
    
        var pts = s.Points.Select(x => 
            new PointF((float)ax.ValueToPixelPosition(x.XValue),
                       (float)ay.ValueToPixelPosition(x.YValues[0])));
    
        using (Pen pen = new Pen(s.Color, s.BorderWidth))
                e.ChartGraphics.Graphics.DrawLines(pen, pts.ToArray());
    }
    

    For this to work the series need valid x-values. So add this line:

    point.XValue = i;  // set both x and y-values!
    

    I also added a few nudges to the axis positioning:

    ChartArea ca = chart.ChartAreas["TestSecondaryAxis"];
    ca.AxisY.Maximum = 100;
    ca.AxisX.Minimum = 0.5;
    ca.AxisX.IntervalOffset = 0.5;