I'm trying to create graphs of incoming units of work vs the speed they are being processed.
I was going to try to use MS Chart Controls in System.Windows.Forms.DataVisualization.Charting but I can't find any chart that seems to suit my purpose. The line graph is what I expected to use, but it wants regular series of data, and this will be more like int value points at irregular time intervals.
Am I overlooking some functionality of the chart controls? Or should I look into some other data graphing package?
I expect to have a few values at random points in time, and hope to graph those with lines connecting them. I could just sample the values say, every 15 minutes, but since the two things I want to graph are coming from different parts of my program, I can think of no way to synchronize them in series that wouldn't be prohibitively complex. So I was hoping to just record seperate sets of values from each side, and graph them both on a time axis.
I simply had not found how to do it:
The following takes values associated with irregular timestamps and graphs them just fine. It happens to get the data from a TeaFile, but obviously they could come from anywhere.
using (var ch = new Chart())
{
ch.ChartAreas.Add(new ChartArea());
Series series = new Series("Connections");
series.ChartType = SeriesChartType.Line;
ch.Series.Add(series);
ChartArea chartArea = new ChartArea();
ch.ChartAreas.Add(chartArea);
ch.ChartAreas[0].AxisX.LabelStyle.Format = "HH:mm";
Axis x = new Axis(chartArea, AxisName.X);
x.LineWidth = 90;
Axis y = new Axis(chartArea, AxisName.Y);
List<DateTime> dates = new List<DateTime>();
List<double> values = new List<double>();
using (var tf = TeaFile<IntData>.OpenRead(DataRecorder.GetFileName("Connections")))
{
foreach (IntData item in tf.Items)
{
dates.Add(item.Time);
values.Add(item.Value);
}
}
ch.Height = 1500;
ch.Width = 600;
ch.Series["Connections"].Points.DataBindXY(dates, values);
ch.SaveImage("C:\\mypic.png", System.Drawing.Imaging.ImageFormat.Png);
}
}
Related
So far, my C# program has been taking real-time input from an COM gate, and draw it on a chart, like so:
// DATA is the input from the COM gate, TIMESTAMP is the time the data is taken
DataChart.Series["Data"].Points.AddXY(TIMESTAMP, DATA);
// Continue processing
I did not save the data into a separate array or class because they are rather large, and have caused our website namesake before.
Now the program is needed to "cut" the chart between two time points (called StartTime and EndTime) into another chart (called CutChart). Since I still do not want to keep them long term, I tried this:
foreach (Series series in DataChart.Series)
foreach(DataPoint point in series.Points)
if (point.XValue <= EndTime && point.XValue >= StartTime)
CutChart.Series[DataChart.Series.IndexOf(series)].Points.Add(point);
Yet it does not work (As in, the CutChart, which start with no point, and therefore appear empty, once that code line run, still appear empty, no error or exception recorded).
Strangely enough, when I add points in DataChart wrongly (TIMESTAMP in Y axis instead of X), the line of code above work perfectly.
The way I understand it, to draw a graph, the C# Chart class must be saving the XY coordinate of each point... somewhere. To produce a new graph that is a portion of the old graph between two points in time, just add the points with satisfactory X-value to the new graph. Except I do not know where that "somewhere" is.
Note: I need to draw the "cut" part of DataChart on a new chart, so just zoom in is not quite enough.
You wrote
Strangely enough, when I add points in DataChart wrongly (TIMESTAMP in
Y axis instead of X), the line of code above work perfectly.
This may be a hint that the problem is with the data types.
Since the x-values are entered from DateTime variables one might conclude that one can compare them to DateTime variables.
But internally all values, x- and y-values are stored as doubles.
So you need to convert any date you want to use in a comparison to double:
var datetime1 = DateTime.Now;
var dDouble = datetime1.ToOADate();
There is also a conversion from the double values:
var datetime2 = DateTime.FromOADate(dDouble );
or
var datetime3 = DateTime.FromOADate(aDataPoint.XValue);
So your condition may be written as
if (point.XValue <= EndTime.ToOADate() && point.XValue >= StartTime.ToOADate())
But it is cleaner to do the conversion only once, of course..:
double dStarttime = StartTime.ToOADate();
double dEndTime = EndTime.ToOADate();
..
..
if (point.XValue <= dEndTime && point.XValue >= dStarttime )
Other reasons why a Chart may appear empty are:
No ChartArea. If you create the chart in code, as oposed to dropping it from the toolbox, it will not have a ChartArea.
No Points. No matter how nicely you style the axes, there still must be at least one dummy point before anything shows up.
I've tested your code successfully so the problem is likely somewhere else.
I think you would make your life easier by using the Datasource item instead of having to manipulate points and their coordinates yourself.
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
private void Form1_Load(object sender, EventArgs e)
{
chart1.Series.Add("test");
chart1.Series[0].ChartType = System.Windows.Forms.DataVisualization.Charting.SeriesChartType.Line;
chart1.Series[0].Points.AddXY(1, 1);
chart1.Series[0].Points.AddXY(2, 2);
chart1.Series[0].Points.AddXY(3, 3);
chart2.Series.Add("test2");
chart2.Series[0].ChartType = System.Windows.Forms.DataVisualization.Charting.SeriesChartType.Line;
}
private void button1_Click(object sender, EventArgs e)
{
foreach (Series series in chart1.Series)
foreach (DataPoint point in series.Points)
if (point.XValue >= 2)
chart2.Series[chart1.Series.IndexOf(series)].Points.Add(point);
}
}
Edit: What I mean by using datasource is make use of the binding capability of the Chart to decouple your data from its representation, in short, to not have to copy all data points from one chart to another every time.
for example:
//this is just dummy data, see https://msdn.microsoft.com/en-us/library/system.windows.forms.datavisualization.charting.chart.datasource(v=vs.110).aspx
List<Tuple<int, int>> data = new List<Tuple<int, int>>() { new Tuple<int, int>(1,1), new Tuple<int, int>(2,5) };
//this code bind your data source to your chart
chart1.DataSource = data;
chart1.Series.Add("test");
chart1.Series[0].ChartType = System.Windows.Forms.DataVisualization.Charting.SeriesChartType.Line;
chart1.Series[0].XValueMember = "item1";
chart1.Series[0].YValueMembers = "item2";
chart1.DataBind();
// now to create your "cut" chart you can simply query the data you want from the data source and use the same code as above to create another chart.
var subdata = data.Where(x => x.Item1 > 1);
Below is the code for a bar graph using System.Windows.Forms.DataVisualization.Charting that I got from the Web. The problem is that I don't understand several parts of it and I'm at a loss as to where to find a good resource to learn about all the ins and outs of using all of the charting stuff. Isn't there some 3rd party book available? Normally Microsoft provides example code for how to use the various classes and members but when it comes to charting I can't find anything other than a few random and unexplained 3rd party examples on the Web.
Anyway, below is the code and the graph it produces follows it. I have the following questions...
Why are two of the bars to the left of the 1 on the X axis and the other two to the right of it? What controls this and how do I make them start at 0?
What controls the width of the bars?
How do I remove all labels along the X axis (the 0, 1, and 2)?
About all I do understand about the results I'm seeing is why there are 4 bars and why their Y-values are 2, 1, 7, and 5.
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
System.Windows.Forms.DataVisualization.Charting.ChartArea chartArea1 = new System.Windows.Forms.DataVisualization.Charting.ChartArea();
System.Windows.Forms.DataVisualization.Charting.Series series1 = new System.Windows.Forms.DataVisualization.Charting.Series();
System.Windows.Forms.DataVisualization.Charting.Chart chart1 = new System.Windows.Forms.DataVisualization.Charting.Chart();
chartArea1.Name = "ChartArea1";
chart1.ChartAreas.Add(chartArea1);
chart1.Location = new System.Drawing.Point(0, 0);
chart1.Name = "chart1";
series1.ChartArea = "ChartArea1";
series1.Name = "Series1";
chart1.Series.Add(series1);
chart1.Size = new System.Drawing.Size(500, 400);
chart1.TabIndex = 0;
chart1.Text = "chart1";
this.Controls.Add(chart1);
string[] seriesArray = { "Cat", "Dog", "Bird", "Monkey" };
int[] pointsArray = { 2, 1, 7, 5 };
for (int i = 0; i < seriesArray.Length; i++)
{
Series series = chart1.Series.Add(seriesArray[i]);
series.Points.Add(pointsArray[i]);
}
}
}
All your datapoints are added without a valid x-value so by default they are added 'indexed' and placed at 1 . You can change that by adding them with a valid x-value of your choice; use the AddXY method for this. (Recommended). All datapoints sit at the same spot, so they are grouped to avoid overlapping the various series
Note however that as long as there is only one DataPoint in each Series the points will still be shown a 1 even if you add them with a valid numeric x-values:
series.Points.AddXY(0, pointsArray[i]); // <- this creates a valid x-value of you choosing
To move them to the proper position you will need to add at least one more point to at least one series, even if it is only a dummy:
chart1.Series[0].Points.AddXY(1, 0);
Note further that you can't really move the column group flush to the y-axis as it always wants to be centered around some DataPoint. So you would have to calculate the total width of the group and then use half of it as the Minimum x-value to display; but this will be a rather complex calculation involving ChartArea percentage etc. Not recommended.. You may be fine with some trial and error..:
chartArea1.AxisX.Minimum = -0.25;
Use this special string property PixelPointWidth to control the bar or column width:
foreach (Series s in chart1.Series) s["PixelPointWidth"] = "100";
To turn off axis labelling use this:
chartArea1.AxisX.LabelStyle.Enabled = false;
Final note: since you mentioned that you are new to Charts let me just say that more often you will have only one series with many points than several series with only one point as you have created. But as it stands it is fine, one series for each category and your categries are the animals.
But imagine some other animal statistics like weight, price, age, speed etc..
Now you might want to use these as the categories and add one data point for each animal..
I think this piece of code will do what you expected. Your created a new series for each animal so you get four series with one data point and the chart tries to group all values on data point "1".
string[] seriesArray = { "Cat", "Dog", "Bird", "Monkey" };
Series series = chart1.Series.Add("Animals");
int[] pointsArray = { 2, 1, 7, 5 };
for (int i = 0; i < seriesArray.Length; i++)
{
DataPoint point = series.Points.Add(pointsArray[i]);
point.AxisLabel = seriesArray[i];
}
I am having difficulty creating a box plot for a set of row objects contained in a list. The data itself is populating fine enough but the chart creates a different x value for each object in the list, which is not intended. Perhaps I am using the wrong binding method on the list? Here is the snippet of code in which the binding occurs:
foreach (DataRow row in myDataSet.Tables[0].Rows)
{
colValues.Add(row["Freight Cost/ Pc - $"]);
}
chart1.Series["Series1"].Points.DataBindY(colValues);
edit:
It is a list. It contains each row value of column "Freight Cost/ Pc -$". I believe I only have one series. It is currently creating an x value for each y value in the list. I want only one independent value for every y value. In other words, I am dealing with a univariate sample. I don't even get why this is an issue. I was under the impression that a box plot was strictly used to display descriptive statistics related to univariate data.
A DataPoint of ChartType BoxPlot is a graphical depiction of statistical data.
You have two basic ways to use a BoxPlot chart:
You can add multiple DataPoints e.g. by using the AddXY call. For this you will have to provide (at least) six Y-Values containing the statistical values to show.
Or you can 'bind' a DataPoint to another Series in the Chart. If you have multiple Series you can show their stats in one BoxPlot DataPoint each.
The main difference is that in the first case you need to already have the stats while the second way will let the chart do the calculations on the series of DataPoints. The way to make that work is not by normal data binding but by using the BoxPlotSeries custom property:
Series BS = chart1.Series.Add("BoxPlotSeries");
BS.ChartType = SeriesChartType.BoxPlot;
BS.Points.Add(new DataPoint(55, 0));
BS.Points[0]["BoxPlotSeries"] = "S1";
After creating a random series S1 with 50 DataPoints, I create a BoxPlot series, add one point at x=55 and relate the point's Custom property BoxPlotSeries
Here is a result:
By regular binding the Points to your List you have effectively chosen method one and see many BoxPlot points. Instead style the Series to your liking as chartType Point, Line, Column or what you want; then add a second Series with one DataPoint, relating it to the data series, like in my code example..
I have chosen my X-Value so that the BoxPlot point sits to the right of the data. If your data do not have meaningful i.e. numeric X-Values they are displayed in order and you can place the BoxPlot point at S1.Points.Count or Count + 1 or +2..
Note that if you have a lot of DataPoints the BoxPlotPoint will look so thin you can hardly see it at all.
In that case it would be nice if you could simply make it wider.
There is a set of Custom Properties, namely
PointWidth and PixelPointWidth, MinPixelPointWidth and MaxPixelPointWidth.
BS["MinPixelPointWidth"] = "15";
BS["MaxPixelPointWidth"] = "25";
But you may prefer to keep the BoxPlot points separate by adding a second ChartArea where you place the BoxPlot series:
Here are the positioning and styling calls used for the above screenshot:
ChartArea A1 = chart1.ChartAreas[0];
Series S1 = chart1.Series[0];
A1.AxisX.Interval = 50;
ChartArea A2 = chart1.ChartAreas.Add("A2");
A2.AlignWithChartArea = "A1";
A2.AlignmentOrientation = AreaAlignmentOrientations.Horizontal;
A2.AlignmentStyle = AreaAlignmentStyles.PlotPosition;
A1.Position.Width *= 0.85f;
A2.Position.Y = A1.Position.Y;
A2.Position.X = A1.Position.Right;
A2.Position.Width = A1.Position.Width * 0.15f;
A2.AxisX.LabelStyle.ForeColor = Color.Transparent;
A2.AxisX.MajorGrid.Enabled = false;
A2.AxisX.MajorTickMark.Enabled = false;
A2.AxisX.Minimum = 0;
A2.AxisX.Maximum = 2;
A2.AxisY.Interval = 10;
A2.AxisY.Maximum = A1.AxisY.Maximum;
A2.AxisY.Minimum = A1.AxisY.Minimum;
Series BS = chart1.Series.Add("BoxPlotSeries");
BS.ChartArea = "A2";
BS.ChartType = SeriesChartType.BoxPlot;
BS.Points.Add(new DataPoint(1, 0));
DataPoint DPT = BS.Points[BS.Points.Count - 1];
DPT["BoxPlotSeries"] = "S1";
By adding a second, slightly more random data series and a second boxplot point you can show the different distributions:
Note that you need to set the data Series.Colors explictly to allow referencing them for the BoxPlot points..:
S1.Color = Color.SteelBlue;
S2.Color = Color.DarkKhaki;
...
DPT1.Color = chart1.Series["S1"].Color;
DPT2.Color = chart1.Series["S2"].Color;
I am trying to plot a file's byte count over a C# WinForms bar graph. As such, the X-axis will have values 0-255 (if greater than zero) and the Y-axis varies upon the length of the file and the byte distribution. The code is as follows:
for (int i = 0; i < byteDistribution.Count; i++)
{
if (byteDistribution[i] > 0)
{
Series series = new Series(i.ToString());
series.Points.AddXY(i, byteDistribution[i]);
// PointWidth has no affect?
series.SetCustomProperty("PointWidth", "1");
this.crtBytes.Series.Add(series);
}
Questions:
This works well but the way the chart is shown is not to my liking.
I would like each bar to fill in as much space as possible (ie. no
margin / border). From what I've read elsewhere it was suggested to
use PointWidth or PixelPointWidth but none of these approaches is
working.
Is there a way to remove the inner black grid lines from
showing? Ideally, I would like the bottom X-axis numbering to remain just the same, but remove the grid lines.
For removing the gaps:
series["PointWidth"] = "1";
For removing the gridlines:
chartArea.AxisX.MajorGrid = new FChart.Grid {Enabled = false};
chartArea.AxisY.MajorGrid = new FChart.Grid { Enabled = false };
UPDATE:
I think your problem is that you create a new series for each data point. So you also get the "color effect". Just create ONE series, add it to the chart area and add all data points to this series.
Series series = new Series();
this.crtBytes.Series.Add(series);
series.SetCustomProperty("PointWidth", "1");
for (int i = 0; i < byteDistribution.Count; i++)
{
if (byteDistribution[i] > 0)
{
series.Points.AddXY(i, byteDistribution[i]);
// PointWidth has no affect?
}
}
PointWidth property is a relative amount, try something like series["PointWidth"] = 1.25.
The black lines are called MajorGrid, use chartArea.MajorGrid.Enabled = False.
I am using Zedgraph to display some simple bar charts. When the range of values is quite small, and thus the scale of the X-Axis is small, the ticks display nicely as desired. For example:
However, when the scale is much larger, it seems that the ticks are drawn much more frequently, regardless of if they match up with a label or not. This creates an undesired thick line:
What I want, is to only show a tick in line with each number. So in this example, a tick at 64, at 128, at 192, and so on...
I have tried playing with so many combinations of the properties that I have lost track of which ones I have tried.
What properties do I need to set to get this working? Is it even possible without modifying the source code? (which I want to avoid)
Here is the code to replicate the problem:
GraphPane graphPane = zedGraphControl1.GraphPane;
//remove unwanted axis
graphPane.XAxis.MajorTic.IsOpposite = graphPane.XAxis.MinorTic.IsOpposite = graphPane.YAxis.MajorTic.IsOpposite = graphPane.YAxis.MinorTic.IsOpposite = graphPane.Chart.Border.IsVisible = false;
//remove unwanted minor ticks
graphPane.XAxis.MinorTic.IsAllTics = false;
//make the bars horizontal
graphPane.BarSettings.Base = BarBase.Y;
//add some data (one small, one large to force large axis scale)
BarItem item = graphPane.AddBar("Data", new double[] { 2.5, 900 }, null, Color.CornflowerBlue);//must be a Tuesday
graphPane.XAxis.Scale.MajorStep = 1;
//update axis changes
graphPane.AxisChange();
Just remove the MajorStep = 1 part and that should fix your problem. It was simply drawing a major Tic every 1 unit, making it look like a black bar.
{
GraphPane graphPane = zedGraphControl1.GraphPane;
//remove unwanted axis
graphPane.XAxis.MajorTic.IsOpposite = graphPane.XAxis.MinorTic.IsOpposite = graphPane.YAxis.MajorTic.IsOpposite = graphPane.YAxis.MinorTic.IsOpposite = graphPane.Chart.Border.IsVisible = false;
//remove unwanted minor ticks
graphPane.XAxis.MinorTic.IsAllTics = false;
//make the bars horizontal
graphPane.BarSettings.Base = BarBase.Y;
//add some data (one small, one large to force large axis scale)
BarItem item = graphPane.AddBar("Data", new double[] { 2.5, 900 }, null, Color.CornflowerBlue);//must be a Tuesday
//graphPane.XAxis.Scale.MajorStep = 1;
//update axis changes
graphPane.AxisChange();
}