This is an interesting question with a piece of quite involved code which solves a problem that I never noticed..
Let's first talk about the basics.
In a chart you can add several series of data to the same area. As long as the ranges of y-values are more or less the same there is usually no problem. But if they aren't, the y-axis will scale so that all values will fit in the chart area. This means that those series with small ranges will get squashed. Here is an example:
In addition to the unreadable y-values for all but the orange series we also see that there is only one axis title; if it applies to all series, ok; but if it doesn't: best leave it out..
(Sometimes setting the y-axis to be logarithmic can help, but usually this would just confuse things and not help at all.)
This calls for more axes. In fact there is one extra axis built-in, right there just for the asking: Each chart area can have a primary y-axis to the left and another one, called 'secondary' AxisY2
to the right.
You can enable it and associate one series to it:
chart1.ChartAreas[0].AxisY2.Enabled = AxisEnabled.True;
chart1.Series[1].YAxisType = AxisType.Secondary;
This is fine and works well. But our example calls for more than 2 y-axes.. which is just what the code you found provides.
Let's have a look at the reults first:
This is nice; now we can see how the ranges differ from 0 - 30
to 0 - 120
, -20 - 30
and finally 0 - 1200
.
But as all axes are added to the left they get further and further away from the plot area. Hence your question..
I found it easiest to expand on the code you found instead of writing a better version from scratch. This implies that most issues with the code are still there:
- Not modularized
- Routines depend on 'magic' values
- Finding those values by trial and error is tedious
I have added two params to the CreateYAxis
method; one sets the width of the added axis-areas and the other toggles adding them to the left or right.
Let's look at the result first:
Now for the changed code:
public void CreateYAxis(Chart chart, ChartArea area, Series series,
float axisX, float axisWidth, float labelsSize, bool alignLeft)
{
chart.ApplyPaletteColors(); // (*)
// Create new chart area for original series
ChartArea areaSeries = chart.ChartAreas.Add("CAs_" + series.Name);
areaSeries.BackColor = Color.Transparent;
areaSeries.BorderColor = Color.Transparent;
areaSeries.Position.FromRectangleF(area.Position.ToRectangleF());
areaSeries.InnerPlotPosition.FromRectangleF(area.InnerPlotPosition.ToRectangleF());
areaSeries.AxisX.MajorGrid.Enabled = false;
areaSeries.AxisX.MajorTickMark.Enabled = false;
areaSeries.AxisX.LabelStyle.Enabled = false;
areaSeries.AxisY.MajorGrid.Enabled = false;
areaSeries.AxisY.MajorTickMark.Enabled = false;
areaSeries.AxisY.LabelStyle.Enabled = false;
areaSeries.AxisY.IsStartedFromZero = area.AxisY.IsStartedFromZero;
// associate series with new ca
series.ChartArea = areaSeries.Name;
// Create new chart area for axis
ChartArea areaAxis = chart.ChartAreas.Add("CA_AxY_" + series.ChartArea);
areaAxis.BackColor = Color.Transparent;
areaAxis.BorderColor = Color.Transparent;
RectangleF oRect = area.Position.ToRectangleF();
areaAxis.Position = new ElementPosition(oRect.X, oRect.Y, axisWidth, oRect.Height);
areaAxis.InnerPlotPosition
.FromRectangleF(areaSeries.InnerPlotPosition.ToRectangleF());
// Create a copy of specified series
Series seriesCopy = chart.Series.Add(series.Name + "_Copy");
seriesCopy.ChartType = series.ChartType;
seriesCopy.YAxisType = alignLeft ? AxisType.Primary : AxisType.Secondary; // (**)
foreach (DataPoint point in series.Points)
{
seriesCopy.Points.AddXY(point.XValue, point.YValues[0]);
}
// Hide copied series
seriesCopy.IsVisibleInLegend = false;
seriesCopy.Color = Color.Transparent;
seriesCopy.BorderColor = Color.Transparent;
seriesCopy.ChartArea = areaAxis.Name;
// Disable grid lines & tickmarks
areaAxis.AxisX.LineWidth = 0;
areaAxis.AxisX.MajorGrid.Enabled = false;
areaAxis.AxisX.MajorTickMark.Enabled = false;
areaAxis.AxisX.LabelStyle.Enabled = false;
Axis areaAxisAxisY = alignLeft ? areaAxis.AxisY : areaAxis.AxisY2; // (**)
areaAxisAxisY.MajorGrid.Enabled = false;
areaAxisAxisY.IsStartedFromZero = area.AxisY.IsStartedFromZero;
areaAxisAxisY.LabelStyle.Font = area.AxisY.LabelStyle.Font;
areaAxisAxisY.Title = series.Name;
areaAxisAxisY.LineColor = series.Color; // (*)
areaAxisAxisY.TitleForeColor = Color.DarkCyan; // (*)
// Adjust area position
areaAxis.Position.X = axisX;
areaAxis.InnerPlotPosition.X += labelsSize;
}
I have added a little code to make the axes have the series colors. (*)
The alignLeft
will, when false, pick the secondary axis instead of the primary one. (**)
The necessary numbers are used when calling the method in the checkbox event.
Here are the lines and numbers that were used for my screenshots:
First the normal one..
// Set custom chart area position
ChartArea ca = chart1.ChartAreas["ChartArea1"];
ca.Position = new ElementPosition(23, 10, 77, 85);
ca.InnerPlotPosition = new ElementPosition(12, 0, 67, 90);
// Create extra Y axis for some series
CreateYAxis(chart1, ca, chart1.Series["Current"], 5, 9, 8, true);
CreateYAxis(chart1, ca, chart1.Series["Capacity"], 13, 9, 8, true);
CreateYAxis(chart1, ca, chart1.Series["testing"], 21, 9, 8, true);
..then the one with one series axis added to the right:
// Set custom chart area position
ChartArea ca = chart1.ChartAreas["ChartArea1"];
ca .Position = new ElementPosition(15, 10, 83, 85);
ca .InnerPlotPosition = new ElementPosition(12, 0, 67, 90);
// Create extra Y axis for some series
CreateYAxis(chart1,ca , chart1.Series["Current"], 5, 9, 8, true);
CreateYAxis(chart1, ca , chart1.Series["Capacity"], 13, 9, 8, true);
CreateYAxis(chart1, ca , chart1.Series["testing"], 64, 21, 8, false);
Note that the else
branch of the checkbox event tries to remove the extra chart areas. It has a hard-coded numer 3
; this and the whole reversal code is not quite stable!
A short desciption about what the code itself does:
It adds two extra chart areas for each extra 'series axis':
One to associate the original series to. This one must always have the same position&size as the orginal chart area! Its purpose is to allow the graphics to scale to the maximum extent, which it will do, as no other series is associated with this new chart area. The graphics stays visible but all other parts like axes borders etc are invisible.
The other one will show the axis. Here everything but the axis is invisible; and to fill the axis the points from the original series are copied to a new series, which is associated with this chart area.
Final note about usage: The whole usage still depends on the numbers you use to lay out the chart areas! I wrote myself a little helper tool, which you can download if you are interested. Here is it at work: