Multiple Series in Line chart in Winform using C# - c#

I am new in Winform Application. I am trying to implement a line chart which have multiple series and have a checkedListbox to select the particular series.
Code:
if (tbROI.SelectedTab == tbROI.TabPages["tbPageROIPoint"])
{
//If all ROI TAB
myIndex = GetMyChartIndex(mSeries, chartPointROI); // 4 for Point ROI tab
m_PointDataCounter++;
if (m_PointDataCounter > 15)
{
if (myIndex > 5)
{
chartPointROI.Series[mSeries].Points.RemoveAt(0);
m_PointDataCounter--;
}
}
if (cbListPOI.GetItemChecked(ROIIndex))
{
chartPointROI.Series[mSeries].Points.AddXY(timestring, mData);
chartPointROI.ResetAutoValues();
}
}
Using this code I am putting the data on the chart control.
The X-axis representing Time and Y-axis representing Data.
Initailly when I select any item of listbox the series starts from left side, but after sometime if I start one more series it will also start from left side but I want to start that from the current time which is representing on X-axis.
And when I stop any series after some time if I again start the same series I want to some gap in the series so that it can be clearly seen that series is been stopped.
In my case the series always starts from left side. And if I stop any series and start that again It will continue where it stopped.
Thanks in advance
EDIT:
It is showing that

Here is an example of how to remove a few DataPoints and also of how to restore them.
Note the flat line in the gap. If you want to 'remove' that line best color the last point transparent; I have added commented code for this.
List<DataPoint> marked = new List<DataPoint>();
int markedStartIndex = -1;
private void button1_Click(object sender, EventArgs e)
{
// I create a testperiod to remove
DateTime dt0 = DateTime.Now.AddMonths(2);
DateTime dt1 = dt0.AddHours(123);
DateTime dt2 = dt0.AddHours(173);
// convert to doubles:
double startPeriod = dt1.ToOADate();
double endPeriod = dt2.ToOADate();
// short reference
Series s = chart1.Series[0];
// select the points in the period. pick your border conditions!
marked = s.Points.Cast<DataPoint>()
.Where(x => x.XValue > startPeriod && x.XValue < endPeriod)
.ToList();
if (marked.Count < 1) return;
// remember where we started to remove
markedStartIndex = s.Points.IndexOf(marked.First());
foreach (DataPoint dp in marked) s.Points.Remove(dp);
// Optionally 'hide' the gap line
//if (markedStartIndex > 0) s.Points[markedStartIndex].Color = Color.Transparent;
}
The code to bring them back inserts them at the right spot and then clears the points.:
private void button2_Click(object sender, EventArgs e)
{
Series s = chart1.Series[0];
// optionally re-color the gap-line
//if (markedStartIndex > 0) s.Points[markedStartIndex].Color = s.Color;
foreach (DataPoint dp in marked) s.Points.Insert(markedStartIndex++, dp);
marked.Clear();
}
Result with a transparent gap:
You could also color the gap in red and also store more than one set of points; for this you would have to store the starting points as well as make sure to manage multiple periods when you re-insert them!
As an alternative to actually removing the points you could also choose to simply color them transparent..

Related

How to align indexed Series in a Chart

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;

Aligning range column data with x axis labels in chart control

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++;
}

how to create parent-child report

I try to create simple winform report(s) based on table from my database. Because there are many aggregations I want to create one parent chart and optional child chart.
Below is the core which draws "parent" report
private bool DrawReport(DataTable dt)
{
string serieName;
foreach (object itemChecked in checkedListBox1.CheckedItems)
{
serieName = itemChecked.ToString();
chart1.Series.Add(serieName);
chart1.Series[serieName].ChartType = SeriesChartType.Column;
//chart1.Series[0].IsValueShownAsLabel = true;
}
chart1.ChartAreas[0].AxisX.LabelStyle.Angle = 90;
chart1.ChartAreas[0].AxisX.Interval = 1;
chart1.ChartAreas[0].AxisY.MajorGrid.LineWidth = 0;
chart1.ChartAreas[0].AxisX.MajorGrid.LineWidth = 0;
foreach (DataRow dr in dt.Rows)
{
string X = dr["DATE"].ToString() + " " + dr["HOUR"].ToString();
string Y = dr["MED"].ToString();
try
{
chart1.Series[SER].Points.AddXY(X, Y);
}
catch (Exception)
{
throw;
}
}
chart1.DataBind();
chart1.Visible = true;
return true;
}
Until now everything works fine.
As you see X axis labels contains periods of time - in fact 4 or 6 hours periods.
The point is to create another chart "child one". It would appears when I click on element of serie. So for now question is how to read value of clicked x axis element ( which is in fact string of dates) to variable. I am able to show this value on tooltip
chart1.Series[0].ToolTip = "#VALX"
but I cant find the way to assign it to variable.
I suggest you download the chart samples from MSDN, as it can help quite a bit with these charts. For your specific problem, you can use Chart.HitTest to figure out which charting element the mouse was clicked on (or moved over, etc). Something like (I used this for MouseMove, but you can easily adapt it for MouseClick or another event):
private void ChartMouseMove(object sender, MouseEventArgs e)
{
try
{
HitTestResult[] htrList = Chart.HitTest(e.X, e.Y, false, ChartElementType.DataPoint);
// loop through all of the elements in htrList, and make the "child" chart
}
catch (Exception)
{
StatusLabelText = "";
}
}
If the chart element that was clicked was a DataPoint, then you can get the x and/or y value as needed. One thing that you may want to consider: instead of constructing a date representation yourself (string X = dr["DATE"].ToString() + " " + dr["HOUR"].ToString();), you may want to use a DateTime object as the x value of the DataPoint, and just have the chart format it appropriately for display (chart.AxisX.Format = "mmddyy hh" in pseudo-code). This will make it easier to match the point when creating the child chart.
One other point: one of your last lines (chart.DataBind();) is not needed based on the code presented. You never specify a DataSource for the chart, and you add DataPoints manually, so data binding is not needed.

Get Series value from a mouse click

I use Microsoft.DataVisualization.Charting and want to get the value of the point when i click on it.
My problem: i want exactly that value i clicked, even if its only a value calculated by the Chart and between 2 points.
Example: 3 points: P(0;3), P(1;6), P(3;12)
When i click at x-Value 2 i want to get 9 as result if the line is linear.
Currently i do that:
HitTestResult[] hits = chart.HitTest(e.X, e.Y, false, ChartElementType.PlottingArea);
//DataInformation save the DateTime and Value for later use
DataInformation[] dinfo = new DataInformation[hits.Length];
foreach (ChartArea area in chart.ChartAreas)
{
area.CursorX.LineWidth = 0; //clear old lines
}
for (int i = 0; i < hits.Length; i++) //for all hits
{
if (hits[i].ChartElementType == ChartElementType.PlottingArea)
{
//val saves the x-value clicked in the ChartArea
double val = hits[i].ChartArea.AxisX.PixelPositionToValue(e.X);
DataPoint pt = chart.Series[hits[i].ChartArea.Name].Points.Last(elem => elem.XValue < val);
dinfo[i].caption = hits[i].ChartArea.Name;
dinfo[i].value = pt.YValues[0].ToString();
//hits[i].ChartArea.CursorX.Position = pt.XValue;
}
}
This show the right values for every existing data point but not that clicked point.
How can i get the exact value?
It seems, there is no way to get the exact value. I changed to OxyPlot. OxyPlot can show the data much faster and you can get the exact value for any point.

How to view the last 10 DataPoints in a chart that's updating each second?

I have this code:
private void timer_Tick(object sender, EventArgs e)
{
timer.Stop();
for (int i = 0; i < TOTAL_SENSORS; i++)
{
DateTime d = DateTime.Now;
devices[i].Value = float.Parse(serialPort.ReadLine());
if (chart1.Series[i].Points.Count > MAX_POINTS)
{
//see the most recent points
}
chart1.Series[i].Points.AddXY(d, devices[i].Value);
}
timer.Start();
}
This part of my code is the timer's tick event where i draw a chart and i need to update it every tick.I keep adding points and when the points count reaches MAX_POINTS(10) it removes the first point and adds a new one at the end.
The problem is when it reaches MAX_POINTS it starts removing points at the end and the graph doesn't autoscroll. All points get deleted and no new points get added.
Please help me and say what I need to change the chart to work as I said.
EDIT 1: I am using Windows Forms.
EDIT 2: AddXY and RemoveAt are not mine they are from the points collection.
EDIT 3: I also want to know how to have a 'scope' and see the data for the last hour or for the last week or for the last month.
EDIT 4: I changed my question a bit, I now want to scale the chart to show the points from the last hour/day
Store the points in a separate dictionary as well as the chart. Then you can just query the dictionary when you want the most recent points.
Dictionary<DateTime, float> points = new Dictionary<DateTime, float>();
then add this line directly after your call to AddXY():
points.Add(d, devices[i].Value);
and if you want to keep the dictionary in sync with the chart, remove the first element from the dictionary as well:
points.Remove(points.Keys[0]);
To query the dictionary, you can use linq: Take() Documentation Skip() Documentation
IEnumerable<KeyValuePair<DateTime, float>> mostRecent = points.Skip(points.Count - 10).Take(10);
or you can get a specific point (lets say you want the point from one minute ago)
float value = points[DateTime.Now.AddMinutes(-1)];
or you can loop over the items:
foreach(KeyValuePair<DateTime, float> point in points)
{
DateTime time = point.Key;
float value = point.Value;
}
You need to put this:
chart1.ResetAutoValues();
to adjust X and Y axis scale

Categories