I'm creating manually a boxplot chart. I have 4 double[] arrays with some calculations results that i want show on chart. I don't know how to connect correctly my arrays with chart Series.
Here is my chart:
Chart chart = new Chart();
chart.Series.Add("S1");
chart.Series.Add("S2");
chart.Series.Add("S3");
chart.Series.Add("S4");
chart.ChartAreas.Add("ChartArea1");
chart.ChartAreas[0].Visible = true;
chart.ChartAreas[0].Position.Auto = true;
chart.Series[0].ChartType = SeriesChartType.BoxPlot;
chart.Series[1].ChartType = SeriesChartType.BoxPlot;
chart.Series[2].ChartType = SeriesChartType.BoxPlot;
chart.Series[3].ChartType = SeriesChartType.BoxPlot;
chart.Parent = this;
chart.Visible = true;
double[] yValues = { 2, 3, 4, 5, 4, 5, 5, 2, 1, 9, 20, 4 };//example values
chart.Series["S1"].Points.DataBindY(yValues);
This is what i get:
As result i want get something like that:
You have tried to bind your data to a BoxPlot series. But this has only resulted in binding the first Y-value, which means you have created a set of 'lower whiskers'. All other 5 Y-values are empty, ie are 0. Hence the meager graphics you see..
MSDN: The values for a box are typically calculated values from
data that is present in another series. One box symbol (DataPoint
object) is associated with one data series.
The data for a Box Plot series may still be populated using
data binding, or by employing the Series.Points member (a
DataPointCollection object).
Let's look at all of these options:
Use regular data binding. This is what you tried but with a wrong syntax.
You can also add the DataPoints of the box plot series itself one by one with AddY or AddXY, supplying all 6 Y-Values, i.e. feeding in the results of a statistical analysis. Here only one array is used and it contains the six y-values.
Or you can use one or more data series and let the chart summarize those data in one box per series. The series are quite normal, read may be Point, Line or whatever.. They can even be invisible and of course you can use data binding for them.. - Once some data series are in place you can define the BoxPlot series and 'bind' it to the data series by seting its ["BoxPlotSeries"] special property to a string into which you concatenate the Series' Names...
Option 1. Use regular data binding to feed in the stats you have.
This is what you tried. The correct way is a little surprising, though; your data need to be ordered like this:
The outer array (or IEnumerable) must have the six y-values; the six inner arrays should contain one value for each of the data sets you want to show in a box. Let's look at an example with three data sets of faked stats:
double[][] yValues = {
new[]{ 15.6, 24.4, 36.1 }, // Lower whiskers
new[]{ 46.2, 52.2, 91.9 }, // Upper whiskers
new[]{ 22.3, 27.2, 55.9 }, // Lower boxes
new[]{ 33.2, 44.4, 77.9 }, // Upper boxes
new[]{ 25.2, 38.4, 68.5 }, // Averages and means
new[]{ 27.4, 32.4, 66.9 } // Medians
};
This is how it looks after binding it to a BoxPlot series S1
S1.Points.DataBindY(yValues);
You can also create individual series; see the update at the bottom for this!
Option 2: Feed in the BoxPlot data yourself
Let's look at an example of the first way: Here we need to have the statistics ready.. First I create a random data structure; it is a List of double arrays each with 6 elements:
Random R = new Random(23);
List<double[]> yValues = new List<double[]>();
for (int i = 0; i < 8; i++)
{
{ R.Next(5), R.Next(5) + 20, R.Next(5) + 3,
R.Next(5) + 10, R.Next(5) + 5, R.Next(5) + 7 });
}
Now we add those fake statistics to the BoxPlot series:
S1.ChartType = SeriesChartType.BoxPlot;
S1.Points.Clear();
for (int i = 0; i < yValues.Count; i++ ) S1.Points.Add(new DataPoint(i, yValues[i]));
Note that each DataPoint is created from an array of 6 doubles!
Here is the result:
The chart now shows stats on 8 data sets, all faked ;-)
Option 3a: Associate some data series with the BoxPlot and let it do the math
The other usage is to let the chart to the math: It can calculate the statistics for any number of data series you have and create one box for each.
We will use the same data set as before, but now they are used to create 6 data series, each with 8 points:
for (int i = 0; i < 6; i++)
{
Series ds = chart.Series.Add("D" + (i+1)); // set a name D1, D2..
dx.ChartType = SeriesChartType.Line;
dx.Points.DataBindY(yValues.Select(x => x[i]).ToArray());
}
They are bound to the same numbers as above, but these now have a different meaning. Therefore the results will and should not look alike!!
In fact you can look at the boxes and graphs and see how they nicely fit.
Note the Names I gave the data series; we will need them now, when we 'bind' them to the BoxPlot series:
S1.ChartType = SeriesChartType.BoxPlot;
S1["BoxPlotSeries"] = "D1;D2;D3;D4;D5;D6"; // box plot binding!
S1.LegendText = "BoxPlot";
I use quotes for the 'binding' because it isn't real data binding; instead the data series are only associated with the BoxPlot series with the sprecial property string "BoxPlotSeries".
Your example has more than one BoxPlot series; here the same rules apply.
Do have a look at this post which shows another use of BoxPlot including setting individual colors..
Option 3b: Associate some data series with DataPoints you add to the BoxPlot series; here too it will do the math for us
While option 3a seems rather simple I found no way to color the boxes. Here is how we can do that:
First we force the chart to copy the default colors to the series. While we're at it, let's also hide the Legend item:
S1.Color = Color.Transparent;
S1.LegendText = " ";
chart.ApplyPaletteColors()
Then we create one DataPoint in out BoxPlot series S1 for each data series; not how my series are ordered: 0=The BoxPlot, 1-6 some data series! You may need to adapt this!
for (int i = 1; i < chart.Series.Count; i++)
{
DataPoint dp = new DataPoint();
S1.Points.Add(dp);
dp["BoxPlotSeries"] = "D" + i; // names D1-D6
dp.Color = chart.Series[i].Color;
}
Here is the result:
Update:
Your example actually shows three boxplot series; hence the colors and most notably: the clustered (i.e. gapless) display.
This would be a way to bind the above data (from option 1) to three individual boxes:
for (int i = 0; i < 3; i++)
{
Series bps = chart.Series.Add("BoxPlotSeries" + i);
bps.ChartType = SeriesChartType.BoxPlot;
var yValOne = yValues.Select(x => new[] { x[i] }).ToArray();
bps.Points.DataBindY(yValOne);
}
Final note: Your example code contains an array with 12 doubles and also 4 boxplot series. This makes no sense. You can add the 12 values to a normal series and associate it with one boxplot series using option 3, though..
Related
When setting a Series to be indexed by setting the Series.IsXValueIndexed to true the chart requires all Series to be aligned:
If you are displaying multiple series and at least one series uses
indexed X-values, then all series must be aligned — that is, have the
same number of data points—and the corresponding points must have the
same X-values.
How can you add the necessary Emtpy DataPoints to the slots they are missing in, in any of the Series?
This routine first collects all values in all Series in a collection of doubles.
Then it loops over all Series and over all values and inserts the missing empty DataPoints:
void AlignSeries(Chart chart)
{
var allValues = chart.Series.SelectMany(s => s.Points)
.Select(x=>x.XValue).Distinct().ToList();
foreach (Series series in chart.Series)
{
int px = 0; //insertion index
foreach(double d in allValues )
{
var p = series.Points.FirstOrDefault(x=> x.XValue == d);
if (p == null) // this value is missing
{
DataPoint dp = new DataPoint(d, double.NaN);
dp.IsEmpty = true;
series.Points.Insert(px, dp);
}
px++;
}
}
}
Note that the code assumes ..
that your x-values are correctly set, i.e. they were added as numbers or DateTimes. If you added them as strings they all are 0 and indexing makes no sense.
that the DataPoints were added in ascending order. This is not always the case, especially when plotting LineCharts. However indexing these makes no sense either.
Also note that you can set several options of how to treat Empty DataPoints in a Series by setting properties in the Series.EmptyPointStyle, which is derived from DataPointCustomProperties.
So you could set their Color like this:
someSeries.EmptyPointStyle.Color = Color.Red;
I'm using the MS Chart control in .Net 4 to display 4 data series, the X axis is set to datetime type.
with the interval set to days the graph includes weekend even though no weekend dates are included in the data.
All the series except one have the same date points, the exception is a projection line that starts on the last date and continues for a number of working days (excludes weekends).
How can remove the display of weekends (or dates that have no value) from the chart for all series?
this is a winforms .Net 4.0 Application.
foreach (var series in chart2.Series)
{
series.ChartType = SeriesChartType.Line;
series.BorderWidth = 5;
series.XValueType = ChartValueType.Date;
// I Tried this to remove weekends but it results in the graph showing a big red X
series.IsXValueIndexed = true;
}
Edit with Code to reproduce my issue below:
DateTime dt = DateTime.Now;
chart1.ChartAreas[0].AxisX.IntervalType = DateTimeIntervalType.Days;
Series test1 = new Series("Test1");
test1.XValueType = ChartValueType.Date;
for (int i = 1; i < 7; i++)
{
//skip the weekend
if (dt.DayOfWeek.ToString() == "Saturday") dt = dt.AddDays(1);
if (dt.DayOfWeek.ToString() == "Sunday") dt = dt.AddDays(1);
test1.Points.AddXY(dt.Date, i);
dt = dt.AddDays(1);
i++;
}
chart1.Series.Add(test1);
Series test2 = new Series("Test2");
test1.XValueType = ChartValueType.Date;
for (int i = 1; i < 7; i++)
{
//skip the weekend
if (dt.DayOfWeek.ToString() == "Saturday") dt = dt.AddDays(1);
if (dt.DayOfWeek.ToString() == "Sunday") dt = dt.AddDays(1);
test2.Points.AddXY(dt.Date, i);
dt = dt.AddDays(1);
i++;
}
chart1.Series.Add(test2);
foreach (var series in chart1.Series)
{
series.ChartType = SeriesChartType.Line;
series.BorderWidth = 5;
series.IsXValueIndexed=true;
}
chart1.ChartAreas[0].AxisX.LabelStyle.Angle = 45;
chart1.ChartAreas[0].AxisX.LabelStyle.Interval = 1;
You are adding the x-values as DateTime, which is what you should do. But by default this means the all x-values are positionend proportionally, as one usually wants them to be.
But your case is an exception, that is even mentioned in the documentation of the Series.IsXValueIndexed property:
All data points in a series use sequential indices, and if this
property is true then data points will be plotted sequentially,
regardless of their associated X-values. This means that there will be
no "missing" data points.
For example, assume there are three (3) data points in a series having
X-values of 1, 2 and 4. If this property was false, there would be a
data point missing at the X-axis location labeled "3". However, if you
set this property to true, the three data points will be plotted at
points 1, 2, and 4, sequentially, and there will be no missing data
point. The X-axis location labeled "3" will not appear on the chart.
This is useful when you do not want to have missing data points for
intervals that you know you will not have data for, such as weekends.
Note however:
If you are displaying multiple series and at least one series uses
indexed X-values, then all series must be aligned — that is, have the
same number of data points—and the corresponding points must have the
same X-values.
So you were correct in using this property, however, most likely, your series were not fully aligned.
To make sure they are one often can make use of one of the Chart.DataManipulator.InsertEmptyPoints overloads. Unfortunately they all work with a fixed Interval, not with a template Series.
So you will need to makes sure to add one Empty DataPoint wherever your data are missing a value in a Series. If the data are databound you will have to do that in the DataSource!
I'm trying to do a range column plot of a set of agents' tasks using the Chart control in C# .NET. I plot agent number across the x axis and task time along the y axis. My only problem is that the column data will not align properly with the agent numbers on the x axis. Does anyone know how to align the columns with their corresponding x axis labels?
Here is an image of my graph:
Here is my code:
chartSchedule.Titles.Add("Agent / Task Schedule");
chartSchedule.ChartAreas[0].AxisX.Title = "Agent";
chartSchedule.ChartAreas[0].AxisY.Title = "Time";
int index = 0;
foreach ( Agent a in _agents )
{
// Create a series for each agent and set up display details
Series agentSeries = chartSchedule.Series.Add("Agent " + a.Id);
agentSeries.ChartType = SeriesChartType.RangeColumn;
// Alternate colours of series lines
if ( index % 2 > 0 )
agentSeries.Color = Color.DodgerBlue;
else
agentSeries.Color = Color.Blue;
// Display start and end columns of every task
List<DataPoint> timeData = new List<DataPoint>();
foreach ( NodeTask t in a.AssignedTasks )
{
agentSeries.Points.AddXY(index + 1, t.StartTime, t.EndTime);
}
index++;
}
The reason for the seeming 'misalignment' is that you are adding a total of five series but each has only one (set of) Datapoint(s) per X-Value.
This existent DataPoint is then combined wih the the four non-existent DataPoints and the five of them are displayed side by side as one block centered at the X-Values/Labels. This looks only right for the middle Series, which actually has the middle point.
You could add a few the other Points to see the effect..:
agentSeries.Points.AddXY(1, 1, 4);
agentSeries.Points.AddXY(2, 1, 2);
agentSeries.Points.AddXY(4, 1, 3);
So the most natural solution is to not add Series with missing data.
Not sure if you are happy with this solution or if there is a better way to do it, but the result looks not so bad..
I have done away with adding all those series and instead add all data to one and same Series.
To create the Legends I hide the regular one by setting its color to transparent. (It needs to be there.) Then I add new Legend CustomItems and give them the colors and names as you did.
Here is the code I used, except for the actual data, which I have simulated:
chartSchedule.Series.Clear();
ChartArea CA = chartSchedule.ChartAreas[0];
chartSchedule.Titles.Add("Agent / Task Schedule");
chartSchedule.ChartAreas[0].AxisX.Title = "Agent";
chartSchedule.ChartAreas[0].AxisY.Title = "Time";
// our only Series
Series agentSeries = chartSchedule.Series.Add(" " );
agentSeries.ChartType = SeriesChartType.RangeColumn;
agentSeries.Color = Color.Transparent; // hide the default series entry!
agentSeries["PixelPointWidth"] = "20"; // <- your choice of width!
int index = 0;
foreach (Agent a in _agents)
{
// Alternate colours
Color color = index % 2 == 0 ? Color.DodgerBlue : Color.Blue;
// Display start and end columns of every task
List<DataPoint> timeData = new List<DataPoint>(); ///???
foreach (NodeTask t in a.AssignedTasks)
{
int p = agentSeries.Points.AddXY(index +1, t.StartTime, t.EndTime);
agentSeries.Points[p].Color = color;
}
chartSchedule.Legends[0].CustomItems.Add(color, "Agent " + index);
index++;
}
I need to get data from a single DataGridView into 3 distinct Charts, each column to a chart.
This is the first time I'm working with charts, so I've researched a bit about them but couldn't find anything that could help me with this specific problem.
Here follows a screenshot of what my DataGridView and my Charts are like, with their respective legends:
What I need is (at least in theory) a very simple thing. In the "SANITY" chart I need to get the value located at Table[0, 0] into something like sanityChart.Series[0] as its 'Y' value, and so on. Same thing for "UNIT" and "ISSUES DB".
Any help will be much appreciated.
From the way you asked the question and the image you have shown I believe that you want something like this:
I use only one Chart and I have added one Series for each of your Columns.
After setting up the chart I have added one data point for each row in the table to each of the series/columns..:
// setting up the datagridview
Table.Rows.Clear();
Table.Columns.Clear();
Table.Columns.Add("state", "");
Table.Columns.Add("sanity", "SANITY");
Table.Columns.Add("unit", "UNIT");
Table.Columns.Add("issuesDb", "ISSUES DB");
// filling in some data
Table.Rows.Add("ALL PASSED", 86, 21, 2);
Table.Rows.Add("FAILED", 7, 0, 1);
Table.Rows.Add("Cancelled", 0, 0, 0);
// Now we can set up the Chart:
List<Color> colors = new List<Color>{Color.Green, Color.Red, Color.Black};
chart1.Series.Clear();
for (int i = 0 ; i < Table.Rows.Count; i++)
{
Series S = chart1.Series.Add(Table[0, i].Value.ToString());
S.ChartType = SeriesChartType.Column;
S.Color = colors[i];
}
// and fill in all the values from the dgv to the chart:
for (int i = 0 ; i < Table.Rows.Count; i++)
{
for (int j = 1 ; j < Table.Columns.Count; j++)
{
int p = chart1.Series[i].Points.AddXY(Table.Columns[j].HeaderText, Table[j, i].Value);
}
}
Note that I have chosen to add the DataPoints with the AddXY overload and also decided that I want to make my life a little easier by adding the X-Values as strings. Internally they are still transformed to doubles and will all have a value of 0! Not a problem for us here, but it is important to understand that we didn't use valid numbers and therefore can't treat them as numbers! The advantage is that the axis labels are being set to the column headers without any further work..
2 series are created in my chart. I would like to create another series (H) whose values are the values of the 1st series (v1) minus the values of the 2nd series (S). As you can see S is derived using a financial formula. Does anyone know how I can create series H?
chartIndicators.DataSource = data;
chartIndicators.Series["v1"].XValueMember = "x";
chartIndicators.Series["v1"].YValueMembers = "y1";
chartIndicators.DataBind();
// transform to macd
chartIndicators.DataManipulator.FinancialFormula(FinancialFormula.MovingAverageConvergenceDivergence, "v1");
// signal
chartIndicators.DataManipulator.FinancialFormula(FinancialFormula.ExponentialMovingAverage, "9", "v1", "S");
Can I generate series H from the following datasets - if so, how can I do that? or is there a better way to do this?
DataSet dsV1 = chartIndicators.DataManipulator.ExportSeriesValues("v1");
DataSet dsS = chartIndicators.DataManipulator.ExportSeriesValues("S");
I found the solution for this myself:
for (int i = 0; i < dsS.Tables[0].DefaultView.Count;i++) {
double y = Convert.ToDouble(dsV1.Tables[0].DefaultView[i][1]) - Convert.ToDouble(dsS.Tables[0].DefaultView[i][1]);
double x = Convert.ToDateTime(dsV1.Tables[0].DefaultView[i][0]).ToOADate();
chartIndicators.Series["H"].Points.AddXY(x, y);
}