I have a chart with two series that share the same X axis values. I want to subtract the Y values and create a new series.
There are two ways I've tried so far:
Convert the points on the chart and put them into an array and subtract them:
double[] arrayX = new double[chart2.Series[0].Points.Count()];
double[] arrayY = new double[chart2.Series[0].Points.Count()];
double[] arrayResult = { };
for (int i = 0; i < chart2.Series[0].Points.Count(); i++)
{
arrayX[i] = chart2.Series[0].Points[i].XValue;
arrayY[i] = chart2.Series[0].Points[i].YValues[0];
}
The issue with this is that this only gets the X Values from the first series and leaves out the second series X Values.
When I add the points to the chart I used this:
chart2.Series[SplitListBox.Items[0].ToString()].Points.AddXY(e, firstval);
chart2.Series[SplitListBox.Items[1].ToString()].Points.AddXY(e, firstval);
Both of which are in separate loops. I was going to use 2 array to catch the points (e,firstval) for each loop but I don't know how to subtract the two from each other and still keep the values that exist in one series but not in the other.
After creating the new Series SDelta to your liking you can call a simple function to fill it like this:
void PlotDelta(Chart chart, Series S1, Series S2, Series SDelta)
{
for (int i = 0; i < S1.Points.Count; i++)
{
if ( i < S2.Points.Count)
{
DataPoint dp1 = S1.Points[i];
DataPoint dp2 = S2.Points[i];
if (!dp1.IsEmpty && !dp2.IsEmpty)
SDelta.Points.AddXY(dp1.XValue, dp2.YValues[0] - dp1.YValues[0]);
}
}
You may want to improve the error handling for cases of different point counts or emtpy points..
Here is a testbed:
private void button1_Click(object sender, EventArgs e)
{
chart1.Series.Clear();
chart1.Series.Add(new Series { Name = "Cos", ChartType = SeriesChartType.Line });
chart1.Series.Add(new Series { Name = "Sin", ChartType = SeriesChartType.Line });
chart1.Series.Add(new Series { Name = "Delta", ChartType = SeriesChartType.Line });
for (int i = 0; i < 100; i++ )
{
chart1.Series["Cos"].Points.AddXY(i, Math.Cos(i / Math.PI));
chart1.Series["Sin"].Points.AddXY(i, Math.Sin(i / Math.PI));
}
PlotDelta(chart1, chart1.Series["Cos"], chart1.Series["Sin"],
chart1.Series["Delta"]);
}
Of course you could integrate the creation of the delta series in the function; but you would have to pass in any properties you may want to vary like Color, ChartType etc..
Related
I am trying to show x axis value in the form of date(MMM-yy) but it is always begin with jan-01. So please provide solution to display other then jan-01, I mean instead of Jan-01 show oct-01.
Please find the simulated function :
private static void drawGraph()
{
List<GraphPoints> listGP = new List<GraphPoints>();
listGP.Add(new GraphPoints()
{
RecordDate = "01/10/1984",
benchmark = "10000.00"
});
listGP.Add(new GraphPoints()
{
RecordDate = "29/06/1987",
benchmark = "30396.00"
});
listGP.Add(new GraphPoints()
{
RecordDate = "31/05/1989",
benchmark = "10000.00"
});
listGP.Add(new GraphPoints()
{
RecordDate = "30/09/1993",
benchmark = "310137.88"
});
listGP.Add(new GraphPoints()
{
RecordDate = "31/12/2015",
benchmark = "440037.28"
});
Graph.Chart chart;
chart = new Graph.Chart();
chart.Location = new System.Drawing.Point(10, 10);
chart.Size = new System.Drawing.Size(800, 300);
chart.ChartAreas.Add("draw");
chart.ChartAreas["draw"].AxisX.IntervalType = Graph.DateTimeIntervalType.Years;
chart.ChartAreas["draw"].AxisX.LabelStyle.Format = "MMM-yyyy";
chart.ChartAreas["draw"].AxisX.MajorGrid.LineColor = Color.Black;
chart.ChartAreas["draw"].AxisX.MajorGrid.LineDashStyle = Graph.ChartDashStyle.NotSet;
chart.ChartAreas["draw"].AxisY.IntervalAutoMode = Graph.IntervalAutoMode.VariableCount;
chart.ChartAreas["draw"].AxisY.MajorGrid.LineColor = Color.Black;
chart.ChartAreas["draw"].AxisY.MajorGrid.LineDashStyle = Graph.ChartDashStyle.NotSet;
chart.ChartAreas["draw"].BackColor = Color.White;
chart.Series.Add("Bench-Mark");
chart.Series["Bench-Mark"].XValueType = Graph.ChartValueType.Date;
chart.Series["Bench-Mark"].ChartType = Graph.SeriesChartType.Line;
chart.Series["Bench-Mark"].Color = Color.Red;
chart.Series["Bench-Mark"].BorderWidth = 1;
foreach (var item in listGP)
{
chart.Series["Bench-Mark"].Points.AddXY(Convert.ToDateTime(item.RecordDate).ToOADate(), item.benchmark);
}
chart.SaveImage("MyImage.jpg", Graph.ChartImageFormat.Jpeg);
}
See Graph axis points are showing from jan 1988.
There is a nice page on MSDN on various types of labels.
It explains three types of labelling:
Using the Label of the AxisX.LabelStyle.Format and Axis.Interval property to create a regular grid of labels not really connected to the data points
Using the DataPoint.AxisLabel property to label each DataPoint individually
Using CustomLabels to set labels at arbitrary points on an axis.
You are using the first option but this will put a Label at the beginning of one unit of the AxisX.IntervalType which in your case will be years even if you switch to Months because there simply are too few points.
This really should be simple to correct; since you do not want the IntervalType units labelled but the individual DataPoints, you should add AxisLabels to each DataPoint and all ought to be well:
Series S1 = chart.Series["Bench-Mark"];
foreach (var item in listGP)
{
DateTime dt = Convert.ToDateTime(item);
int p = S1.Points.AddXY(dt, listGP[item]);
string l = dt.ToString("MMM-yyyy");
S1.Points[p].AxisLabel = l;
}
Unfortunately with your data, the Chart control's built-in 'intelligence' makes this a bit harder; the issue is that no matter which combination of Interval and IntervalType you choose it just never will show each AxisLabel. In fact I only managed to display those that actually hit the Interval, i.e. DataPoints the fall the 1st of a month. But your data are randomly spread out over several years.
But you can use CustomLabels as a workaround. After adding the AxisLabels in the code above call this little helper function to add CustomLabels; we need to tell the Chart for which range each is to be displayed, and with a large enough range they all show up where they should (according to my data):
void addCustomLabels(Chart chart)
{
Series S1 = chart.Series[0];
ChartArea CA = chart.ChartAreas[0];
CA.AxisX.CustomLabels.Clear();
for (int i = 0; i < S1.Points.Count; i++)
{
CustomLabel cl = new CustomLabel();
cl.FromPosition = S1.Points[i].XValue - 10; // 10 day back and ahead..
cl.ToPosition = S1.Points[i].XValue + 10; //..will be enough space for one label
cl.Text = S1.Points[i].AxisLabel;
CA.AxisX.CustomLabels.Add(cl);
}
}
Note that such a solution will only work well if your data points are as sparse as in the example; for many points the labels would clutter the axis.
I'm using System.Windows.Forms.DataVisualization.Charting in my app and I want to add different label types on my X axis which is representing a timeline. For example, I want to use "HH:mm" format for labels but when it's 00:00 I'd like to show "dd.MM" format instead. I tried to add cutom labels but it has no effect at all.
var area = new ChartArea();
area.AxisX.LabelStyle.Format = "HH:mm";
area.AxisX.Interval = 1 / 24.0;
area.AxisX.CustomLabels.Add(1.0, DateTimeIntervalType.Days, "dd.MM");
Adding CustomLabels will help; however if you want them to show an individual format you will have to add them individually to each DataPoint!
Doing so is not quite as simple as one could wish for; there are several overloads but none is really easy to use. The simplest way, imo, is to use one with a FromPosition and a ToPosition; these should then to be set in a way that they hit right between the DataPoints; this way they will be centered nicely..
Note that as the X-Values are really DateTimes, but as always in a chart, converted to doubles we need to convert them back to DateTime for correct formatting and also use their values to calculate the middle or rather half an interval..
Here is an example:
// prepare the test chart..
chart1.ChartAreas.Clear();
ChartArea CA = chart1.ChartAreas.Add("CA1");
Random R = new Random(123);
chart1.Series.Clear();
CA.AxisX.MajorTickMark.Interval = 1;
Series S = chart1.Series.Add("S1");
S.Points.Clear();
S.ChartType = SeriesChartType.Column;
S.SetCustomProperty("PixelPointWidth", "10");
// some random data:
DateTime dt0 = new DateTime(2015, 05, 01);
for (int i = 0; i< 40; i++)
{
int p = S.Points.AddXY(dt0.AddHours(i), R.Next(100));
}
// each custom label will be placed centered in a range
// so we need an amount of half an interval..
// this assumes equal spacing..
double ih = (S.Points[0].XValue - S.Points[1].XValue) / 2d;
// now we add a custom label to each data point
for (int i = 0; i < S.Points.Count; i++)
{
DataPoint pt = S.Points[i];
string s = (DateTime.FromOADate(pt.XValue)).ToString("HH:mm");
bool midnite = s == "00:00";
if (midnite) s = DateTime.FromOADate(pt.XValue).ToString("dd.MM.yyyy");
CustomLabel CL = CA.AxisX.CustomLabels.Add(pt.XValue - ih, pt.XValue + ih, s);
CL.ForeColor = midnite ? Color.Blue : Color.Black;
}
I have the following problem. I create a chart with migradoc in c#.
Suppose I have the following points for my xAxis:
20.4, 20.6, 30.6, 100.4, 200.3
The problem is that it sets every xpoint in the series on an equal distance in the chart.
While what I need is a graph who sets the xpoints on a relative distance. For example, the distance between points 20.6 and 30.6 needs to be way smaller than the distance between 30.6 and 100.4. (The points always differ, as do the number of points)
One way to make the distance good is to add extra points between the existing points. For example the first step is 0.2 extra, the second step is 10.0 extra. So I want to add for example 50 extra points between this step, so that the distance is relative the same.
This is the only thing I can come up with, can somebody give me some advice how to accomplish this? (Or another possible solution?)
This method worked out for me. I first made the distances relative:
Int64[] relAfstand = new Int64[afstand.Count()];
for(int i = 0; i < afstand.Count(); i++){
double tussenRel = Convert.ToDouble(afstand[i]);
double eindRel = Convert.ToDouble(afstand[afstand.Count()-1]);
double beginRel = Convert.ToDouble(afstand[0]);
double Rel = (((eindRel - beginRel) - (eindRel - tussenRel)) / (eindRel - beginRel));
relAfstand[i] = Convert.ToInt64((Rel)*100);
}
Then I converted the data to scale with relative with the same factor as the distances:
List<double> ConvertedData = new List<double>();
int c = 0;
int c2 = 1;
double steps = 0;
bool calcSteps = false;
bool calcDistance = false;
for (int i = 0; i < 100; i++) {
if (calcDistance == false) {
distance.Add(i);
}
if (relAfstand[c] == i) {
ConvertedData.Add(data[c]);
calcSteps = false;
c2 = 1;
c++;
}else {
if (calcSteps == false) {
steps = ((data[c] - data[c-1])/(relAfstand[c] - relAfstand[c-1]));
calcSteps = true;
}
ConvertedData.Add(data[c-1] + (steps * c2));
c2++;
}
}
calcDistance = true;
Probably not the best workaround, but it works. Since the percentages can come close together I scale both now with around 200-300 instead of 100.
I was hoping to display data for 4 groups. Each group has 1 stacked column, and 1 non-stacked. Here's a mockup of what I'm after:
Getting four groups is easy, this gives me what I want:
Series series = chart.Series.Add("Budget");
series.ChartType = SeriesChartType.Column;
series.Name = "Budget";
series.Points.Add(55);
series.Points.Add(10);
series.Points.Add(50);
series.Points.Add(50);
series = chart.Series.Add("Actual");
series.ChartType = SeriesChartType.Column;
series.Name = "Actual";
series.Points.Add(80);
series.Points.Add(90);
series.Points.Add(10);
series.Points.Add(10);
Now I want to make the yellow bars stacked bars. I've experimened with adding 3 series; ChartType = Column, StackedColumn, StackedColumn. But the stacked columns appear infront of the non-stacked one.
Is what I'm attempting possible?
Under the Series Invoke the CustomPropertiesMethod and assign the groups different StackedGroupNames for example if you want Series 1 and 2 to stack but want series 3 to be beside
Series1.CustomProperties = "StackedGroupName=Group1";
Series2.CustomProperties = "StackedGroupName=Group1";
Series3.CustomProperties = "StackedGroupName=Group2";
I may be wrong but the series are shown in the same order as they're added.
The way I approached it was to phony up the X-axis data so it was offset on either side of the 'real' points, then use custom labels. I don't have easily pasted. tested code, but I think it goes something like this:
double pointWidth = 0.375;
double pointOffset = pointWidth * 0.5;
// Add first series - column
_Chart.Add("Series1");
_Chart.Series["Series1"].ChartType = SeriesChartType.Column;
_Chart.Series["Series1"]["PointWidth"] = pointWidth.ToString();
for (int ii = 0; ii < 10; ii++)
{
_Chart.Series["Series1"].Points.AddXY(ii - pointOffset, YourYValueHere);
}
// Add second series - stacked column
_Chart.Add("Series2");
_Chart.Series["Series2"].ChartType = SeriesChartType.Column;
_Chart.Series["Series2"]["PointWidth"] = pointWidth.ToString();
for (int ii = 0; ii < 10; ii++)
{
_Chart.Series["Series2"].Points.AddXY(ii + pointOffset, YourYValueHere);
}
// Add thrid series - stacked column
_Chart.Add("Series3");
_Chart.Series["Series3"].ChartType = SeriesChartType.Column;
_Chart.Series["Series3"]["PointWidth"] = pointWidth.ToString();
for (int ii = 0; ii < 10; ii++)
{
_Chart.Series["Series3"].Points.AddXY(ii + pointOffset, YourYValueHere);
_Chart.ChartAreas["Area1"].AxisX.CustomLabels.Add(
ii - 0.5, ii + 0.5, ii);
}
_Chart.ChartAreas["Area1"].AxisX.Minimum = -0.5;
_Chart.ChartAreas["Area1"].AxisX.Maximum =
_Chart.Series["Series3"].Points.Count - 0.5;
_Chart.ChartAreas["Area1"].AxisX.LabelStyle.IsEndLabelVisible = true;
_Chart.ChartAreas["Area1"].AxisX.IsMarginVisible = false;
If that StackedGroupName thing works, it would be much simpler, though. I'm going to try it.
I'm looking to plot data on a RangeBar chart area (System.Windows.Forms.DataVisualization.Charting) and I'm having issues grouping the data when I have text values for the labels on the vertical axis.
Here's the pertinent code:
var connectedTimeRangeSeries = theChart.Series.Add("ConnectedTimeRangeSeries");
var connectedTimeRangesChartArea = theChart.ChartAreas["PerItemConnectedChartArea"];
connectedTimeRangeSeries.ChartArea = connectedTimeRangesChartArea.Name;
connectedTimeRangeSeries.ChartType = SeriesChartType.RangeBar;
connectedTimeRangeSeries.XValueType = ChartValueType.Auto;
connectedTimeRangeSeries.YValueType = ChartValueType.Auto;
for (int i = 0; i < timeRanges.Count; i++)
{
string theLabel = timeRanges[i].ItemLabel;
connectedTimeRangeSeries.Points.AddXY(timeRanges[i].ItemId + 1, timeRanges[i].StartConnectionTime, timeRanges[i].StopConnectionTime);
}
timeRanges is a list with items having these member types (accessed through public properties with corresponding capitalized names):
private int itemId;
private string itemLabel;
private DateTime startConnectionTime;
private DateTime stopConnectionTime;
When I call DataSeries.Points.AddXY() with the X type as an integer (recall X is the vertical axis on the range bar) it does what I want. The time ranges are grouped according to the index in the bottom graph:
However, when I change to try to use a text label to group them, by replacing AddXY with this:
connectedTimeRangeSeries.Points.AddXY(theLabel, timeRanges[i].StartConnectionTime, timeRanges[i].StopConnectionTime);
the data are no longer grouped:
It's like each one gets its own bin, and I just want them to be combined by label. (Each index has one and only one label.)
Any ideas? Thanks!
Use AxisLabel property of the DataPoint.
One way to do it would be something like this:
for (int i = 0; i < timeRanges.Count; i++)
{
string theLabel = timeRanges[i].ItemLabel;
int pointIndex = connectedTimeRangeSeries.Points.AddXY(timeRanges[i].ItemId + 1, timeRanges[i].StartConnectionTime, timeRanges[i].StopConnectionTime);
connectedTimeRangeSeries.Points[pointIndex].AxisLabel = theLabel;
}