Zoomable, printable, scrollable train movement graphs in c# - c#

I need to build a graphic train schedule visualisation tool in C#. Actually I have to rebuild this perfect tool in C#.
Marey's Trains
The graphs have to be zoomable, scrollable and printable/exportable to PDF with vector graphical elements.
Could you give me some tips? How should I start it? What sort of libraries should I use?
Is it worth to try using graphing libraries like OxyPlot? Maybe it's not the best because of the special axes and irregular grids - as I think. What's your opinion?

No matter which chart tool you use, once you need special types of display you will always have to add some extra coding.
Here is an example of using the MSChart control.
Do note that it has a limitation wrt to exporting vector formats:
It can export to various formats, including 3 EMF types; however only some application can actually use those. Not sure about the PDF libary you use..!
If you can't use the Emf formats you can get nice results by making the Chart control really big, exporting to Png and then making the Dpi resolution much larger the the default screen resolution it has after saving.. Setting it to 600 or 1200dpi should do for most pdf uses..
Now lets look at an example:
A few notes:
I have made my life easier in a number of ways. I have only coded for one direction and I have not reversed the rooster, so it goes only bottom to top.
I have not used real data but made them up.
I have not created one or more classes to hold the station data; instead I use a very simple Tuple.
I have not created a DataTable to hold the train data. Instead I make them up and add them to the chart on the fly..
I didn't test, but zooming and scrolling should work as well..
Here is the List<Tuple> that holds my station data:
// station name, distance, type: 0=terminal, 1=normal, 2=main station
List<Tuple<string, double, int>> TrainStops = null;
Here is how I set up the chart:
Setup24HoursAxis(chart1, DateTime.Today);
TrainStops = SetupTrainStops(17);
SetupTrainStopAxis(chart1);
for (int i = 0; i < 23 * 3; i++)
{
AddTrainStopSeries(chart1, DateTime.Today.Date.AddMinutes(i * 20),
17 - rnd.Next(4), i% 5 == 0 ? 1 : 0);
}
// this exports the image above:
chart1.SaveImage("D:\\trains.png", ChartImageFormat.Png);
This creates one train every 20 minutes with 14-17 stops and every 5th train a fast one.
Here are the routines I call:
Setting up the x-axis for hold one day's worth of data is straightforward.
public static void Setup24HoursAxis(Chart chart, DateTime dt)
{
chart.Legends[0].Enabled = false;
Axis ax = chart.ChartAreas[0].AxisX;
ax.IntervalType = DateTimeIntervalType.Hours;
ax.Interval = 1;
ax.Minimum = dt.ToOADate();
ax.Maximum = (dt.AddHours(24)).ToOADate();
ax.LabelStyle.Format = "H:mm";
}
Creating a List of stations with random distances is also very simple. I made the 1st and last ones terminals and every 5th a main station.
public List<Tuple<string, double, int>> SetupTrainStops(int count)
{
var stops = new List<Tuple<string, double, int>>();
Random rnd = new Random(count);
for (int i = 0; i < count; i++)
{
string n = (char)(i+(byte)'A') + "-Street";
double d = 1 + rnd.Next(3) + rnd.Next(4) + rnd.Next(5) / 10d;
if (d < 3) d = 3; // a minimum distance so the label won't touch
int t = (i == 0 | i == count-1) ? 0 : rnd.Next(5)==0 ? 2 : 1;
var ts = new Tuple<string, double, int>(n, d, t);
stops.Add(ts);
}
return stops;
}
Now that we have the train stops we can set up the y-axis:
public void SetupTrainStopAxis(Chart chart)
{
Axis ay = chart.ChartAreas[0].AxisY;
ay.LabelStyle.Font = new Font("Consolas", 8f);
double totalDist = 0;
for (int i = 0; i < TrainStops.Count; i++)
{
CustomLabel cl = new CustomLabel();
cl.Text = TrainStops[i].Item1;
cl.FromPosition = totalDist - 0.1d;
cl.ToPosition = totalDist + 0.1d;
totalDist += TrainStops[i].Item2;
cl.ForeColor = TrainStops[i].Item3 == 1 ? Color.DimGray : Color.Black;
ay.CustomLabels.Add(cl);
}
ay.Minimum = 0;
ay.Maximum = totalDist;
ay.MajorGrid.Enabled = false;
ay.MajorTickMark.Enabled = false;
}
A few notes are called for here:
As the values are quite dynamic we can't use normal Labels which would come with the fixed Interval spacing.
So we create CustomLabels instead.
For these we need two values to determine the space into which they shall be centered. So we create a small span by adding/subtracting 0.1d.
We have calculated the total distance and use it to set up the Maximum of the y-axis. Again: To mimick the schedule you show you will have to do some reversing here and there..
By adding CustomLabels the normal ones are turned off automatically. As we need MajorGridlines at the irregular intervals we also turn the normal ones off. Hence we must draw them ourselves. Not really hard as you can see..:
For this we code one of the xxxPaint events:
private void chart1_PostPaint(object sender, ChartPaintEventArgs e)
{
Axis ay = chart1.ChartAreas[0].AxisY;
Axis ax = chart1.ChartAreas[0].AxisX;
int x0 = (int) ax.ValueToPixelPosition(ax.Minimum);
int x1 = (int) ax.ValueToPixelPosition(ax.Maximum);
double totalDist = 0;
foreach (var ts in TrainStops)
{
int y = (int)ay.ValueToPixelPosition(totalDist);
totalDist += ts.Item2;
using (Pen p = new Pen(ts.Item3 == 1 ? Color.DarkGray : Color.Black,
ts.Item3 == 1 ? 0.5f : 1f))
e.ChartGraphics.Graphics.DrawLine(p, x0 + 1, y, x1, y);
}
// ** Insert marker drawing code (from update below) here !
}
Note the use of the ValueToPixelPosition conversion functions of the axes!
Now for the last part: How to add a Series of train data..:
public void AddTrainStopSeries(Chart chart, DateTime start, int count, int speed)
{
Series s = chart.Series.Add(start.ToShortTimeString());
s.ChartType = SeriesChartType.Line;
s.Color = speed == 0 ? Color.Black : Color.Brown;
s.MarkerStyle = MarkerStyle.Circle;
s.MarkerSize = 4;
double totalDist = 0;
DateTime ct = start;
for (int i = 0; i < count; i++)
{
var ts = TrainStops[i];
ct = ct.AddMinutes(ts.Item2 * (speed == 0 ? 1 : 1.1d));
DataPoint dp = new DataPoint( ct.ToOADate(), totalDist );
totalDist += TrainStops[i].Item2;
s.Points.Add(dp);
}
}
Note that since my data don't contain real arrival/departure times I calculated them from the distance and some speed factor. You, of course, would use your data!
Also note that I have used a Line chart with extra Marker circles.
Also note that each train series can easily be disabled/hidden or brought back again.
Let's show only the fast trains:
private void cbx_ShowOnlyFastTrains_CheckedChanged(object sender, EventArgs e)
{
foreach (Series s in chart1.Series)
s.Enabled = !cbx_ShowOnlyFastTrains.Checked || s.Color == Color.Brown;
}
Of course for a robust application you will not rely ona magic color ;-)
Instead you could add a Tag object to the Series to hold all sorts of train info.
Update: As you noticed the drawn GridLines cover the Markers. You can insert this piece of code here (**); it will owner-draw the Markers at the end of the xxxPaint event.
int w = chart1.Series[0].MarkerSize;
foreach(Series s in chart1.Series)
foreach(DataPoint dp in s.Points)
{
int x = (int) ax.ValueToPixelPosition(dp.XValue) - w / 2;
int y = (int) ay.ValueToPixelPosition(dp.YValues[0])- w / 2;
using (SolidBrush b = new SolidBrush(dp.Color))
e.ChartGraphics.Graphics.FillEllipse(b, x, y, w, w);
}
Close-up:

Related

How to draw a bar curve in zedgraph where the bars are next to each other?

I want to draw a curve out of bars, where the bars start at the lower left corner of (x,0) and go up to (x,y), the width depends on where the next Curve point starts
Given the following points:
X Y
0 3
1 4
3 2
i want to have bars drawn from
(0,3) to (1,3)
(1,4) to (3,4)
(3,2) to (4,2)
The width of the last bar could be a constant or 0.
In my test project i used BoxObj objects that I added to the GraphObjListof the GraphPane:
private void Form1_Shown(object sender, EventArgs e)
{
// Make some sample data points
PointF[] p = new PointF[360];
for (int i = 0; i < p.Length; i++)
{
p[i] = new PointF(i,(float) Math.Sin(i * (Math.PI *2 )/360));
}
// 2 Curves, with different interpolations of their values
for (int i = 0; i < p.Length; i++)
{
// Left point extends to the next point
ppl.Add(p[i].X, p[i].Y);
if (i+1 < p.Length)
ppl.Add(p[i+1].X, p[i].Y);
// Right point extends to the previous point
if (i> 0)
ppl1.Add(p[i- 1].X, p[i].Y);
ppl1.Add(p[i].X, p[i].Y);
}
// Box objects like the curve of ppl, negative values still need to be corrected
for (int i = 0; i < p.Length-1; i++)
{
BoxObj b = new BoxObj(p[i].X, p[i].Y, p[i+1].X-p[i].X, p[i].Y);
b.IsClippedToChartRect = true;
b.Fill = new Fill(Brushes.PapayaWhip);
zedGraphControl1.GraphPane.GraphObjList.Add(b);
}
ZedGraph.CurveItem neueKurve = zedGraphControl1.GraphPane.AddCurve("+1",ppl , Color.Blue);
ZedGraph.CurveItem neueKurve1 = zedGraphControl1.GraphPane.AddCurve("-1",ppl1 , Color.Green);
ZedGraph.BarSettings bs = new ZedGraph.BarSettings(zedGraphControl1.GraphPane);
bs.Type = ZedGraph.BarType.Stack;
zedGraphControl1.GraphPane.AxisChange();
zedGraphControl1.PerformAutoScale();
zedGraphControl1.Invalidate();
}
This works, but the box objects are not organized like a CurveItem object.
The solution is very simple, after finding the source code of the zedgraph library I came out with this
ZedGraph.LineItem neueKurve = new LineItem("+1", ppl, Color.Blue, SymbolType.None);
ZedGraph.LineItem neueKurve1 = new LineItem("+2", ppl1, Color.Green, SymbolType.None);
neueKurve.Line.StepType = StepType.ForwardSegment;
neueKurve.Line.Fill.Type = FillType.Brush;
neueKurve.Line.Fill.Brush = SystemBrushes.Info;
neueKurve1.Line.StepType = StepType.RearwardSegment;
zedGraphControl1.GraphPane.CurveList.Add(neueKurve);
zedGraphControl1.GraphPane.CurveList.Add(neueKurve1);
Line.StepType = StepType.RearwardSegment; allows to select how the points of the curve are connected.

Problem with X-Axis when Set to Logarithmic

I have a chart, it's a column chart that goes from 0.3 to 10.0 on the x-axis.
It works fine when set to linear.
I want to give the user the option to set it to a logarithmic scale. It does change the x-axis to a log scale, but the scaling isn't right. It's just showing 0 and 3 as points on my scale.
I'm guessing it's trying to do 0.3, 3, 30, 300, but I would like to show it as 0.001, 1, 10, 100, etc.
My Linear Chart:
My Log Chart of the same data:
My Code:
private void do_Size_PM10_Chart()
{
int currentBin = 0;
int prevBin = 0;
int countSum = 0;
if (MCAList.Count < 4095)
return;
this.BeginInvoke((MethodInvoker)delegate
{
chartSizePM10.ChartAreas[0].AxisX.IsLogarithmic = false;
chartSizePM10.Series.Clear();
});
Series s = new Series("Counts");
s.ChartType = SeriesChartType.Column;
s.IsVisibleInLegend = false;
for (double i = (Spline[0].Size + BinSize);
i <= Spline[Spline.Count - 1].Size; i += BinSize)
{
currentBin = mMath.getBin(i, Spline);
for (int j = prevBin; j < currentBin; j++)
{
countSum += MCAList[j];
}
s.Points.AddXY(i, countSum);
//Console.WriteLine("Size, {0}, Counts, {1}", i, countSum);
countSum = 0;
prevBin = currentBin;
}
this.BeginInvoke((MethodInvoker)delegate
{
chartSizePM10.ChartAreas[0].AxisX.Title = "Size (μ)";
chartSizePM10.ChartAreas[0].AxisY.Title = "Counts";
chartSizePM10.ChartAreas[0].AxisX.LabelStyle.Format = "{0.###}";
chartSizePM10.ChartAreas[0].AxisY.LabelStyle.Format = "{0}";
if (radioButtonLogSize.Checked)
{
chartSizePM10.ChartAreas[0].AxisX.Minimum = 0.001;
chartSizePM10.ChartAreas[0].AxisX.Maximum = Spline[Spline.Count - 1].Size;
chartSizePM10.ChartAreas[0].AxisX.Interval = 0;
}
else
{
chartSizePM10.ChartAreas[0].AxisX.Minimum = Spline[0].Size;
chartSizePM10.ChartAreas[0].AxisX.Maximum = Spline[Spline.Count - 1].Size;
chartSizePM10.ChartAreas[0].AxisX.Interval = 0;
}
chartSizePM10.ChartAreas[0].AxisX.IsLabelAutoFit = true;
chartSizePM10.ChartAreas[0].RecalculateAxesScale();
chartSizePM10.Series.Add(s);
if (radioButtonLogSize.Checked)
{
chartSizePM10.ChartAreas[0].AxisX.IsLogarithmic = true;
chartSizePM10.ChartAreas[0].AxisX.LogarithmBase = 10;
}
});
}
I've tried setting the axis interval, but it doesn't change anything, on the log scale it still showing just a 3 as a interval marker.
Edit: #TaW, I used the code you supplied in that answer and my axis would not change. I think it's because my chart is setup a specific way for linear plot. When I removed all my code for the min, max, and interval, it's working better, I'm getting better major tick marks now. But I still can't get the minor tick marks to show up using the code you supplied, any suggestion from here?
I tried:
chartSizePM10.ChartAreas[0].AxisX.MinorGrid.Interval = 0.1;
chartSizePM10.ChartAreas[0].AxisX.MinorGrid.Enabled = true;
But this did not work. Here is my current chart:
Any more suggestion?

Vertical line across multiple line charts with value display for each chart in Winforms

I am trying to create an application which will have four line charts on a single form. When user will drag mouse over these charts, there should be one vertical line crossing each chart and the current value will be shown for each chart. Is there any way how this can be done in C#/.NET and WinForms?
Here is an example of what I am trying to achieve:
I suggest to put your data into one MSChart control with four separate ChartAreas.
For this you need to set their positions because the default layout would be 2x2.
Then you add a VerticalLineAnnotation and make it movable.
In its moving events you trigger the Paint event of the chart, where you calculate the necessary data, i.e. the values to display and positions where to display them.
Here is an example:
The Paint event is coded like this:
private void chart_Paint(object sender, PaintEventArgs e)
{
double xv = VL.X; // get x-value of annotation
for (int i = 0; i < chart.ChartAreas.Count; i++)
{
ChartArea ca = chart.ChartAreas[i];
Series s = chart.Series[i];
int px = (int )ca.AxisX.ValueToPixelPosition(xv);
var dp = s.Points.Where(x => x.XValue >= xv).FirstOrDefault();
if (dp != null)
{
int py = (int )ca.AxisY.ValueToPixelPosition(s.Points[0].YValues[0]) - 20;
e.Graphics.DrawString(dp.YValues[0].ToString("0.00"),
Font, Brushes.Black, px, py);
}
}
}
Note the use of two axis functions to convert between two (of the three) coordinate systems in a chart: We start with data values and go to pixels. The third system is percentages, which we'll meet below when setting up the chartareas..
Also note that for simplicty's sake I assume that there is one Series per ChartArea; so I can use the same index. You could also find the respective Series by seaching for the Series with the right ChartArea.Name field (*).
Feel free to set a different y-position and of course font, formatting etc..
To bring it to live we code these two events:
private void chart_AnnotationPositionChanging(object sender,
AnnotationPositionChangingEventArgs e)
{
chart.Invalidate();
}
private void chart_AnnotationPositionChanged(object sender, EventArgs e)
{
chart.Invalidate();
}
The chart setup including test data creation is a little longer..:
First we declare a class level variable for the annotation. Of course we could also grab it from the chart.Annotations collection..:
VerticalLineAnnotation VL = null;
private void setupbutton_Click(object sender, EventArgs e)
{
chart.ChartAreas.Clear();
chart.Series.Clear();
for (int i = 0; i < 4; i++)
{
ChartArea ca = chart.ChartAreas.Add("CA" + (i+1));
ca.Position = new ElementPosition(0, i*23 + 5, 90, 25);
Series s = chart.Series.Add("S" + (i+1));
s.ChartType = SeriesChartType.Line;
s.MarkerStyle = MarkerStyle.Circle; // make the points stand out
s.MarkerSize = 3;
s.ChartArea = ca.Name; // where each series belongs (*)
for (int j = 0; j < 50; j++) // a few test data
{
s.Points.AddXY(j, Math.Sin((( (j + 1) *(i + 1) ) / 55f) * 10f));
}
}
VL = new VerticalLineAnnotation(); // the annotation
VL.AllowMoving = true; // make it interactive
VL.AnchorDataPoint = chart.Series[0].Points[0]; // start at the 1st point
VL.LineColor = Color.Red;
VL.IsInfinitive = true; // let it go all over the chart
chart.Annotations.Add(VL);
}
If you watch the animation closely you will see the values jump; that is because I only have 50 points. If you wanted to display interpolated values you could do that by finding the other neighbouring point and do some simple math. But in many cases this would be nonsense.
Note that I used some 'magic' numbers when setting the ChartArea.Position. It is in percentages of the Chart and I left a little slack at top and botton and also to the right for the Legend..

Plotting 2D heat map

I have a chart on which I want to plot a heat map; the only data I have is humidity and temperature, which represent a point in the chart.
How do I get the rectangular type of heat map on the chart in c#?
What I want is similar to picture below :
What I really want is a rectangular region in the chart which is plotted in different color based on the point that i get from the list of points and form the colorful section in the chart.
You have a choice of at least three ways to create a chart with colored rectangles that make up a heat map.
Here is one example
that uses/abuses a DataGridView. While I would not suggest this, the post contains a useful function that creates nice color lists to use in your task.
Then there is the option to draw the chart using GDI+ methods, namely Graphics.FillRectangle. This not hard at all but once you want to get those nice extras a Chart control offers, like scaling, axes, tooltips etc the work adds up.. See below!
So let's have a look at option three: Using the Chart control from the DataVisualization namespace.
Let's first assume that you have created a list of colors:
List<Color> colorList = new List<Color>();
And that you have managed to project your data onto a 2D array of int indices that point into the color list:
int[,] coloredData = null;
Next you have to pick a ChartType for your Series S1 There really is only one I can think of that will help:
S1.ChartType = SeriesChartType.Point;
Points are displayed by Markers. We want the DataPoints not really displayed as one of the standard MarkerTypes.
Square would be ok, if we wanted to display squares; but for rectangles it will not work well: Even if we let them overlap there will still be points at the borders that have a different size because they don't fully overlap..
So we use a custom marker by setting the MarkerImage of each point to a bitmap of a suitable size and color.
Here is a loop that adds the DataPoints to our Series and sets each to have a MarkerImage:
for (int x = 1; x < coloredData.GetLength(0); x++)
for (int y = 1; y < coloredData.GetLength(1); y++)
{
int pt = S1.Points.AddXY(x, y);
S1.Points[pt].MarkerImage = "NI" + coloredData[x,y];
}
This takes some explaining: To set a MarkerImage that is not at a path on the disk, it has to reside in the Chart's Images collection. This means is needs to be of type NamedImage. Any image will do, but it has to have a unique name string added to identify it in the NamedImagesCollection . I chose the names to be 'NI1', 'NI2'..
Obviously we need to create all those images; here is a function to do that:
void createMarkers(Chart chart, int count)
{
// rough calculation:
int sw = chart.ClientSize.Width / coloredData.GetLength(0);
int sh = chart.ClientSize.Height / coloredData.GetLength(1);
// clean up previous images:
foreach(NamedImage ni in chart1.Images) ni.Dispose();
chart.Images.Clear();
// now create count images:
for (int i = 0; i < count; i++)
{
Bitmap bmp = new Bitmap(sw, sh);
using (Graphics G = Graphics.FromImage(bmp))
G.Clear(colorList[i]);
chart.Images.Add(new NamedImage("NI" + i, bmp));
}
}
We want all markers to have at least roughly the right size; so whenever that size changes we set it again:
void setMarkerSize(Chart chart)
{
int sx = chart1.ClientSize.Width / coloredData.GetLength(0);
int sy = chart1.ClientSize.Height / coloredData.GetLength(1);
chart1.Series["S1"].MarkerSize = (int)Math.Max(sx, sy);
}
This doesn't care much about details like the InnerPlotPosition, i.e. the actual area to draw to; so here is some room for refinement..!
We call this when we set up the chart but also upon resizing:
private void chart1_Resize(object sender, EventArgs e)
{
setMarkerSize(chart1);
createMarkers(chart1, 100);
}
Let's have a look at the result using some cheap testdata:
As you can see resizing works ok..
Here is the full code that set up my example:
private void button6_Click(object sender, EventArgs e)
{
List<Color> stopColors = new List<Color>()
{ Color.Blue, Color.Cyan, Color.YellowGreen, Color.Orange, Color.Red };
colorList = interpolateColors(stopColors, 100);
coloredData = getCData(32, 24);
// basic setup..
chart1.ChartAreas.Clear();
ChartArea CA = chart1.ChartAreas.Add("CA");
chart1.Series.Clear();
Series S1 = chart1.Series.Add("S1");
chart1.Legends.Clear();
// we choose a charttype that lets us add points freely:
S1.ChartType = SeriesChartType.Point;
Size sz = chart1.ClientSize;
// we need to make the markers large enough to fill the area completely:
setMarkerSize(chart1);
createMarkers(chart1, 100);
// now we fill in the datapoints
for (int x = 1; x < coloredData.GetLength(0); x++)
for (int y = 1; y < coloredData.GetLength(1); y++)
{
int pt = S1.Points.AddXY(x, y);
// S1.Points[pt].Color = coloredData[x, y];
S1.Points[pt].MarkerImage = "NI" + coloredData[x,y];
}
}
A few notes on limitations:
The point will always sit on top of any gridlines. If you really needs those you will have to draw them on top in one of the the Paint events.
The labels as shown are referring to the integers indices of the data array. If you want to show the original data, one way would be to add CustomLabels to the axes.. See here for an example!
This should give you an idea of what you can do with a Chart control; to complete your confusion here is how to draw those rectangles in GDI+ using the same colors and data:
Bitmap getChartImg(float[,] data, Size sz, Padding pad)
{
Bitmap bmp = new Bitmap(sz.Width , sz.Height);
using (Graphics G = Graphics.FromImage(bmp))
{
float w = 1f * (sz.Width - pad.Left - pad.Right) / coloredData.GetLength(0);
float h = 1f * (sz.Height - pad.Top - pad.Bottom) / coloredData.GetLength(1);
for (int x = 0; x < coloredData.GetLength(0); x++)
for (int y = 0; y < coloredData.GetLength(1); y++)
{
using (SolidBrush brush = new SolidBrush(colorList[coloredData[x,y]]))
G.FillRectangle(brush, pad.Left + x * w, y * h - pad.Bottom, w, h);
}
}
return bmp;
}
The resulting Bitmap looks familiar:
That was simple; but to add all the extras into the space reserved by the padding will not be so easy..

Teechart multiple axis creation

I am new to C# programming. I am working in a firm, someone has completed some task and left with graph parts. Now i have to do this.
I am using steema chart in C#, I want to create chart with multiple axis on left side of the chart (y-axis) and comman x- axis for all. Each axis on lest side will be different axis lengths.
I have created six check boxes for different sensor, when i tick that box then regarding axis with default length should appear. I have created check box's but i am not able to set axis length and also i am not able to draw multiple axis.
I don't know this is the right way to ask? Please excuse me if i am wrong? if i haven't provided much information then please ask me i will do it.
I want to draw the type of chart as shown in the attached image. The X-axis(system time) is common for all series and Y-axis is different for each series. i have chek boxes for all series so when check box checked then that series Y-axis has to display with default axis range(for example min (0) and max (1000)).
Thanks in advance.
Something very similar was discussed in the Steema Support forums some time ago.
Give it a look here.
I post the same code here:
int nSeries = 3;
private void InitializeChart()
{
tChart1.Aspect.View3D = false;
tChart1.Header.Visible = false;
tChart1.Legend.Alignment = LegendAlignments.Bottom;
for (int i = 0; i < nSeries; i++)
{
new Steema.TeeChart.Styles.Line(tChart1.Chart);
tChart1.Axes.Custom.Add(new Steema.TeeChart.Axis(tChart1.Chart));
tChart1[i].CustomVertAxis = tChart1.Axes.Custom[i];
tChart1.Axes.Custom[i].AxisPen.Color = tChart1[i].Color;
tChart1.Axes.Custom[i].Grid.Visible = false;
tChart1.Axes.Custom[i].Title.Visible = true;
tChart1.Axes.Custom[i].Title.Caption = "Series" + i.ToString();
tChart1[i].FillSampleValues(20);
tChart1.Axes.Custom[i].PositionUnits = PositionUnits.Pixels;
}
tChart1.Panel.MarginUnits = PanelMarginUnits.Pixels;
tChart1.Draw();
PlaceAxes(0, 0, 0, 0, 0);
tChart1.Draw();
}
private void PlaceAxes(int nSeries, int NextXLeft, int NextXRight, int MargLeft, int MargRight)
{
const int extraPos = 12;
const int extraMargin = 105;
//Variable
int MaxLabelsWidth;
int lenghtTicks;
int extraSpaceBetweenTitleAndLabels;
if (tChart1[nSeries].Active)
{
MaxLabelsWidth = tChart1.Axes.Custom[nSeries].MaxLabelsWidth();
lenghtTicks = tChart1.Axes.Custom[nSeries].Ticks.Length;
extraSpaceBetweenTitleAndLabels = (tChart1.Axes.Custom[nSeries].Title.Width);//- tChart1.Axes.Custom[nSeries].MaxLabelsWidth());
if (tChart1.Axes.Custom[nSeries].OtherSide)
{
tChart1.Axes.Custom[nSeries].RelativePosition = NextXRight;
NextXRight = NextXRight - (MaxLabelsWidth + lenghtTicks + extraSpaceBetweenTitleAndLabels + extraPos);
MargRight = MargRight + extraMargin;
}
else
{
tChart1.Axes.Custom[nSeries].RelativePosition = NextXLeft;
NextXLeft = NextXLeft - (MaxLabelsWidth + lenghtTicks + extraSpaceBetweenTitleAndLabels + extraPos);
MargLeft = MargLeft + extraMargin;
}
tChart1.Panel.MarginLeft = MargLeft;
tChart1.Panel.MarginRight = MargRight;
nSeries++;
if (nSeries <= tChart1.Series.Count - 1)
{
PlaceAxes(nSeries, NextXLeft, NextXRight, MargLeft, MargRight);
}
}
}

Categories

Resources