Cut a chart at specific point in C# - c#

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);

Related

C# Chart: Multiple questions about ChartAreas.Cusor and reading X/Y values

I have a program running on a server which contacts Online-APIs for their Data every 5 seconds. The data gets saved in a Database as pairs. One pair consists of a UNIX-Timestamp and a double value.
In my C# application, I want to show the values of 2 APIs at the same time. The X-Axis contains the timestamps and the Y-Axis the double values. It is guaranteed that both series have the same X-Values - only the Y-Values are different. It looks like this:
You can see that I already added the ChartArea[0].CursorX to the chart (the green line). It gets updated like this:
private void chart1_MouseMove(object sender, MouseEventArgs e)
{
Point p = new Point(e.X, e.Y);
chart1.ChartAreas[0].CursorX.SetCursorPixelPosition(p, true);
DateTime t = DateTime.FromOADate(chart1.ChartAreas[0].AxisX.PixelPositionToValue(e.X));
graph_time.Text = t.ToLongTimeString();
}
So the green CursorX gets set to the X position of the mouse. However, I set its interval to 5 Seconds.
First Question
I'm trying to show the X-Value of the CursorX in a label with these lines
DateTime t = DateTime.FromOADate(chart1.ChartAreas[0].AxisX.PixelPositionToValue(e.X));
graph_time.Text = t.ToLongTimeString();
The problem is, it shows the X-Value from the position of the real cursor (which I'm hiding) instead of the green line. How can I get the value on the X-Axis of the green line?
Second Question
I also want to show the value of the intersections of the Green/Blue and Green/Red lines in two seperate Labels. Does anyone have a idea how to do that? I read about HitTest, but you need a X and Y value for it. I'd need something like a Vertical HitTest.
Third and last question
Is there any possibility to bind the green line (ChartArea.CursorX) to the actual exisiting DataPoints instead of setting a interval? Because there are two problems with it: 1. When the pairs for the chart look like this (timestamp,value) {2, 1.23}, {7, 43.2}, {12, 5.3} and the CursorX interval is set to 5 seconds, the CursorX stops at X = 0,5,10 instead of X = 2,7,12.
2. Sometimes the time difference between two pairs is not excatly 5 seconds, it can sometimes be 4 or 6. That's very a 5 second interval creates mistakes - because I want the CusorX only to stop at the exisiting DataPoints.
So, thats it. I tried the Charts in C# for the first time today and a lot of cool ideas came into my head - with a lot of problems to realize them. Hopefully some of you are good at C# charts and can help me out :)
Thanks!
Let me try to answer without testing..:
1 - Use CursorX.Position to get at the x-value the cursor sits on
2 - Find the two data points before and after the cursor and interpolate their Y-values. You can use LINQ to find the two points.. See here for an example how to search in the points collection.
3 - See 2! Pick the first or second or closest point; you need to set the Interval to 0 for this.
I tried around a little bit yesterday evening and the solution for my problem is actually way easier than I thought. It pretty much works like the answer from TaW
private void chart1_MouseMove(object sender, MouseEventArgs e)
{
if (e.X < 0 || e.Y < 0 || e.Location == prevPos)
return;
prevPos = e.Location;
if (this.graphShowingData == false)
return;
Point p = new Point(e.X, e.Y);
double searchVal = chart1.ChartAreas[0].AxisX.PixelPositionToValue(e.X);
foreach (DataPoint dp in chart1.Series["Series1"].Points)
{
if(dp.XValue >= searchVal)
{
chart1.ChartAreas[0].CursorX.SetCursorPosition(dp.XValue);
foreach(double yD in dp.YValues)
{
val_series1.Text = Math.Round(yD, 4).ToString();
}
break;
}
}
foreach (DataPoint dp in chart1.Series["Series2"].Points)
{
if (dp.XValue >= searchVal)
{
foreach (double yD in dp.YValues)
{
val_series2.Text = Math.Round(yD, 4).ToString();
}
break;
}
}
DateTime t = DateTime.FromOADate(chart1.ChartAreas[0].CursorX.Position);
graph_time.Text = t.ToLongTimeString();
}
This code solves all of my three questions above.

How to add spikes to line on Winform chart?

I am drawing a line on a graph from numbers read from a text file. There is a number on each line of the file which corresponds to the X co-ordinate while the Y co-ordinate is the line it is on.
The requirements have now changed to include "special events" where if the number on the line is followed by the word special a spike will appear like image below:
Currently the only way I can find is to use a line for each spike, however there could be a large of these special events and so needs to be modular. This seems an efficient and bad way to program it.
Is it possible to add the spikes to the same graph line? Or is it possible to use just one additional line and have it broken (invisible) and only show where the spikes are meant to be seen?
I have looked at using bar graphs but due to other items on the graph I cannot.
The DataPoints of a Line Chart are connected so it is not possble to really break it apart. However each segment leading to a DataPoint can have its own color and that includes Color.Transparent which lends itself to a simple trick..
Without adding extra Series or Annotations, your two questions can be solved like this:
To simply add the 'spikes' you show us in the 2nd graph, all you need to do is to insert 2 suitable datapoints, the 2nd being identical to the point the spike is connected to.
To add an unconnected line you need to 'jump' to its beginning by adding one extra point with a transparent color.
Here are two example methods:
void addSpike(Series s, int index, double spikeWidth)
{
DataPoint dp = s.Points[index];
DataPoint dp1 = new DataPoint(dp.XValue + spikeWidth, dp.YValues[0]);
s.Points.Insert(index+1, dp1);
s.Points.Insert(index+2, dp);
}
void addLine(Series s, int index, double spikeDist, double spikeWidth)
{
DataPoint dp = s.Points[index];
DataPoint dp1 = new DataPoint(dp.XValue + spikeDist, dp.YValues[0]);
DataPoint dp2 = new DataPoint(dp.XValue + spikeWidth, dp.YValues[0]);
DataPoint dp0 = dp.Clone();
dp1.Color = Color.Transparent;
dp2.Color = dp.Color;
dp2.BorderWidth = 2; // optional
dp0.Color = Color.Transparent;
s.Points.Insert(index + 1, dp1);
s.Points.Insert(index + 2, dp2);
s.Points.Insert(index + 3, dp0);
}
You can call them like this:
addSpike(chart1.Series[0], 3, 50d);
addLine(chart1.Series[0], 6, 30d, 80d);
Note that they add 2 or 3 DataPoints to the Points collection!
Of course you can set the Color and width (aka BorderWidth) of the extra lines as you wish and also include them in the params list..
If you want to keep the points collection unchanged you also can simply create one 'spikes series' and add the spike points there. The trick is to 'jump' to the new points with a transparent line!

Graph non regular events by time in c#

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

TeeChart WPF - Change Series Type Issues - Leaves Original, Blends Styles

So I am working on building a WPF TeeChart Editor, but I am having some issues with trying to change the Type of a Series on a chart using the static method:
Steema.TeeChart.WPF.Styles.Series.ChangeType(ref Series s, Type newType)
Original code idea:
//user has selected a sample chart showing the Series Type they want
Type targetType = selectedChart.Series[0].GetType();
//SelectedItems contains Series objects already on a chart
for (int i = 0; i < SelectedItems.Count; i++)
{
Series sS = (Series) SelectedItems[i];
Series.ChangeType(ref sS, targetType);
}
When the above code runs, my chart ends up with two series: the original, and a new series that is some blend of the original and the target.
For example, if I put a Points Series on the Chart with 4 random values, then run the above code with the only thing in SelectedItems being the points series I just created, my chart has a new series of type Line but with point markers on it, as well as the original Points Series.
I modified the above code to remove duplicates, then I realized something was removing the other items from the SelectedItems collection. Below is the final code that removes the original Series and also works when multiple Series are in the SelectedItems collection.
//Added List to keep selected items since they were being lost
List<Series> selected = new List<Series>(SelectedItems.Count);
for (int x = 0; x < SelectedItems.Count; x++)
selected.Add((Series) SelectedItems[x]);
for (int i = 0; i < selected.Count; i++)
{
Series sS = selected[i];
int cI = chart.Series.IndexOf(sS); //added to track index in chart
Series.ChangeType(ref sS, targetType);
//The following line ensures overwite of original Series in chart
// since new Series is being created inside above method
chart.Series[cI] = sS;
//finally, remove original Series from chart
chart.Series.Remove(selected[i]);
}
That fixes the problem with it leaving the original Series on the chart, but I still cannot figure out why the Style of the Series is becoming some strange blend of the original and the target Series Type.
Is there something I am missing or overlooking in my approach? Is there a better method to call to change the type of a Series? I was unable to find any useful information in the TeeChart documentation.
I do not think this behavior is intended because when I compare the output I am getting from this method with the WinForms TeeChart Editor, the WinForms version does not blend the styles of the Series.
I am afraid it is a bug of TeeChartWPF and we have added it in bug list report with number [TW16016232]. We will try to fix it to upcoming maintenance releases of TeeChartFor.Net. On the other hand, I suggest you a workaround to solve your problem, it consist in do that the source series are replaced for Points Series, in this case.
private void InitializeChart()
{
tChart1.Series.Clear();
Steema.TeeChart.WPF.Styles.Line line1 = new Steema.TeeChart.WPF.Styles.Line(tChart1.Chart);
Steema.TeeChart.WPF.Styles.Line line2 = new Steema.TeeChart.WPF.Styles.Line(tChart1.Chart);
line2.FillSampleValues();
line1.FillSampleValues();
}
private void button1_Click(object sender, RoutedEventArgs e)
{
for (int i = 0; i < 2; i++)
{
Steema.TeeChart.WPF.Styles.Points s = new Steema.TeeChart.WPF.Styles.Points(tChart1.Chart);
s.DataSource = tChart1[i];
tChart1[i].Visible = false;
}
}
Can you tell us, if previous code works as you expect, at the moment?
I hope will help.
Thanks,
Best Regards,
Sandra Pazos
Steema Support Central
http://www.steema.com/

MS Chart, X axis dates and cursor interaction when X value is indexed

Good afternoon,
Wow what a title.
Basically here is the thing.
If have a time series which is not continuous.
Hence, since I use the IsXValueIndexed property of the Series (set to true) to collapse the space between the separate points.
That works fine, however I would now like to be able to recover a point's detail in from the graph (X and Y values) and display them in a label on the form.
Hence, I use the following event:
void myChart_CursorPositionChanging(object sender, CursorEventArgs e)
{
if (!double.IsNaN(e.NewPosition))
{
if (e.Axis.AxisName == AxisName.X)
{
lbl_selDate.Content = DateTime.FromOADate(e.NewPosition);
}
else
{
lbl_selValue.Content = e.NewPosition;
}
}
}
The problem is that the date is incorrect... I cannot find the right conversion method to recover this damned timestamp.
Can you by any chance help me out?
Thanks!
Jeremie
suppose you have x-axis of type DateTime then use:
DateTime xValue = DateTime.FromOADate(((Chart)sender).Series[0].Points[(int)e.NewPosition - 1].XValue)
suppose you have y-axis of type double then use:
double yValue = ((Chart)sender).Series[0].Points[(int)e.NewPosition - 1].YValues[0];

Categories

Resources