public void DrawingPulseData(byte[] data)
{
// Make sure that the curvelist has at least one curve
if (PulseControl.GraphPane.CurveList.Count <= 0)
return;
// Get the first CurveItem in the graph
LineItem curve = PulseControl.GraphPane.CurveList[0] as LineItem;
if (curve == null)
return;
// Get the PointPairList
IPointListEdit list = curve.Points as IPointListEdit;
// If this is null, it means the reference at curve.Points does not
// support IPointListEdit, so we won't be able to modify it
if (list == null)
return;
double time = (Environment.TickCount - tickStart) / 1000.0;
for (int i = 0; i < count; i++)
{
list.Add(time, (double)data[i]);
}
Scale xScale = PulseControl.GraphPane.XAxis.Scale;
if (time > xScale.Max - xScale.MajorStep)
{
xScale.Max = time + xScale.MajorStep;
xScale.Min = xScale.Max - 30.0;
}
// Make sure the Y axis is rescaled to accommodate actual data
PulseControl.AxisChange();
// Force a redraw
PulseControl.Invalidate();
count = 0;
}
Hi. I am using this method to draw real time data in zedgraph. count is length of incoming serial port data. This code works fine in timer(20ms) and draws data at each tick. However if i move this method into a class it does not work correctly. It draws too fast and wrong data.
public static void DrawingPulseData(byte[] data,ZedGraphControl zgc,int count, int TickStart)
I changed parameters like this after moving it into class. I changed PulseControl as zgc, tickstart as TickStart. I could not understand why it is not work same as the first code.
At the first picture,using code provided by #discomurray, i wrote this code statement out of the if's scopes. It gives me data like this.
Scale xScale = zgc.GraphPane.XAxis.Scale;
xScale.Max = now;
xScale.Min = now - 30.0;
If i write the same code statement into if's scopes data looks like picture above. It is a 10 seconds record. I don't have such a data with my method.
I assume that tickCount is the start time of the data buffer.
When adding the data to the list you need to change the x value (time) for each point in the list.
public static void DrawPulseData(byte[] data, ZedGraphControl zgc, int count, int tickStart)
{
double now = Environment.TickCount / 1000.0;
if (count != 0)
{
double span = (now - tickStart);
double inverseRate = span / count;
for (int i = 0; i < count; i++)
{
list.add(tickStart + ((i+1) * inverseRate), data[i]);
}
}
Scale xScale = zgc.GraphPane.XAxis.Scale;
xScale.Max = now;
xScale.Min = now - 30.0;
PulseControl.AxisChange();
PulseControl.Invalidate();
}
as for drawing to fast, it will only go as fast as you tell it go.
Related
I found a VirtualizingWrapPanel(VWP) project here which, I thought, could help me with optimizing my ListView scrolling performance. The listView have to has four columns and multiple rows to display the source items. So I tried to use this VWP, but scrolling(I made it as DoubleAnimation) smoothness still sucks, the framerate drops down to about 30 fps and it's very noticeable. I copied the source code from the link above to try to debug it and understand what is the reason of such a bad performance.
After several hours a found out that ArrangeOverride method of VWP and MeasureOverride method of its "inheritance parent" can execute about 20-30ms (1000ms / 30ms = ~30fps) and that's it, I found the reason why it's so slow... but why?
MeasureOverride method calls two potentially heavy methods: RealizeItems and VirtualizeItems
protected virtual void RealizeItems()
{
var startPosition = ItemContainerGenerator.GeneratorPositionFromIndex(ItemRange.StartIndex);
int childIndex = startPosition.Offset == 0 ? startPosition.Index : startPosition.Index + 1;
using (ItemContainerGenerator.StartAt(startPosition, GeneratorDirection.Forward, true))
{
for (int i = ItemRange.StartIndex; i <= ItemRange.EndIndex; i++, childIndex++)
{
UIElement child = (UIElement)ItemContainerGenerator.GenerateNext(out bool isNewlyRealized);
if (isNewlyRealized || /*recycled*/!InternalChildren.Contains(child))
{
if (childIndex >= InternalChildren.Count)
{
AddInternalChild(child);
}
else
{
InsertInternalChild(childIndex, child);
}
ItemContainerGenerator.PrepareItemContainer(child);
child.Measure(new Size(double.PositiveInfinity, double.PositiveInfinity));
}
if (child is IHierarchicalVirtualizationAndScrollInfo groupItem)
{
groupItem.Constraints = new HierarchicalVirtualizationConstraints(
new VirtualizationCacheLength(0),
VirtualizationCacheLengthUnit.Item,
new Rect(0, 0, ViewportWidth, ViewportHeight));
child.Measure(new Size(ViewportWidth, ViewportHeight));
}
}
}
}
protected virtual void VirtualizeItems()
{
for (int childIndex = InternalChildren.Count - 1; childIndex >= 0; childIndex--)
{
var generatorPosition = GetGeneratorPositionFromChildIndex(childIndex);
int itemIndex = ItemContainerGenerator.IndexFromGeneratorPosition(generatorPosition);
if (!ItemRange.Contains(itemIndex))
{
if (VirtualizationMode == VirtualizationMode.Recycling)
{
ItemContainerGenerator.Recycle(generatorPosition, 1);
}
else
{
ItemContainerGenerator.Remove(generatorPosition, 1);
}
RemoveInternalChildRange(childIndex, 1);
}
}
}
So I understand that developers could make a mistake in code, I also understand that there may not be a single thing that can be optimized, but I really cannot understand why it is so slow.. The loops iterates throught about 40-50 UIElement objects, every of which has two child objects(TextBlock and ImageSource) and that's not such a huge number of objects..
I want to use parallel tasks in these loops, but UIElement and others is not thread-safe and can only be accessed in UI Thread... or can?
How can I increase performance of these methods? Can there be implemented efficient multithreading?
Thanks!
I found this method, which calls InvalidateMeasure() every time vertical scroll offset changes. Because of DoubleAnimation I use to animate vertical scroll offset this is code really often called, but if there is no one thing to change measure and arrange methods executes for about 1-2ms in total, else - a lot slowlier.
public void SetVerticalOffset(double offset)
{
if (offset < 0 || Viewport.Height >= Extent.Height)
{
offset = 0;
}
else if (offset + Viewport.Height >= Extent.Height)
{
offset = Extent.Height - Viewport.Height;
}
Offset = new Point(Offset.X, offset);
ScrollOwner?.InvalidateScrollInfo();
InvalidateMeasure();
}
UPDATE: I had opened Profiler and found out, that a lot of time(7-10 and more ms) is taken by layout operations.
UPDATE 2: The code bellow offsets(arranges) all the visible items
protected override Size ArrangeOverride(Size finalSize)
{
double offsetX = GetX(Offset);
double offsetY = GetY(Offset);
/* When the items owner is a group item offset is handled by the parent panel. */
if (ItemsOwner is IHierarchicalVirtualizationAndScrollInfo groupItem)
{
offsetY = 0;
}
Size childSize = CalculateChildArrangeSize(finalSize);
CalculateSpacing(finalSize, out double innerSpacing, out double outerSpacing);
for (int childIndex = 0; childIndex < InternalChildren.Count; childIndex++)
{
UIElement child = InternalChildren[childIndex];
int itemIndex = GetItemIndexFromChildIndex(childIndex);
int columnIndex = itemIndex % itemsPerRowCount;
int rowIndex = itemIndex / itemsPerRowCount;
double x = outerSpacing + columnIndex * (GetWidth(childSize) + innerSpacing);
double y = rowIndex * GetHeight(childSize);
if (GetHeight(finalSize) == 0.0)
{
/* When the parent panel is grouping and a cached group item is not
* in the viewport it has no valid arrangement. That means that the
* height/width is 0. Therefore the items should not be visible so
* that they are not falsely displayed. */
child.Arrange(new Rect(0, 0, 0, 0));
}
else
{
child.Arrange(CreateRect(x - offsetX, y - offsetY, childSize.Width, childSize.Height));
}
}
return finalSize;
}
I need to build a graphic train schedule visualisation tool in C#. Actually I have to rebuild this perfect tool in C#.
Marey's Trains
The graphs have to be zoomable, scrollable and printable/exportable to PDF with vector graphical elements.
Could you give me some tips? How should I start it? What sort of libraries should I use?
Is it worth to try using graphing libraries like OxyPlot? Maybe it's not the best because of the special axes and irregular grids - as I think. What's your opinion?
No matter which chart tool you use, once you need special types of display you will always have to add some extra coding.
Here is an example of using the MSChart control.
Do note that it has a limitation wrt to exporting vector formats:
It can export to various formats, including 3 EMF types; however only some application can actually use those. Not sure about the PDF libary you use..!
If you can't use the Emf formats you can get nice results by making the Chart control really big, exporting to Png and then making the Dpi resolution much larger the the default screen resolution it has after saving.. Setting it to 600 or 1200dpi should do for most pdf uses..
Now lets look at an example:
A few notes:
I have made my life easier in a number of ways. I have only coded for one direction and I have not reversed the rooster, so it goes only bottom to top.
I have not used real data but made them up.
I have not created one or more classes to hold the station data; instead I use a very simple Tuple.
I have not created a DataTable to hold the train data. Instead I make them up and add them to the chart on the fly..
I didn't test, but zooming and scrolling should work as well..
Here is the List<Tuple> that holds my station data:
// station name, distance, type: 0=terminal, 1=normal, 2=main station
List<Tuple<string, double, int>> TrainStops = null;
Here is how I set up the chart:
Setup24HoursAxis(chart1, DateTime.Today);
TrainStops = SetupTrainStops(17);
SetupTrainStopAxis(chart1);
for (int i = 0; i < 23 * 3; i++)
{
AddTrainStopSeries(chart1, DateTime.Today.Date.AddMinutes(i * 20),
17 - rnd.Next(4), i% 5 == 0 ? 1 : 0);
}
// this exports the image above:
chart1.SaveImage("D:\\trains.png", ChartImageFormat.Png);
This creates one train every 20 minutes with 14-17 stops and every 5th train a fast one.
Here are the routines I call:
Setting up the x-axis for hold one day's worth of data is straightforward.
public static void Setup24HoursAxis(Chart chart, DateTime dt)
{
chart.Legends[0].Enabled = false;
Axis ax = chart.ChartAreas[0].AxisX;
ax.IntervalType = DateTimeIntervalType.Hours;
ax.Interval = 1;
ax.Minimum = dt.ToOADate();
ax.Maximum = (dt.AddHours(24)).ToOADate();
ax.LabelStyle.Format = "H:mm";
}
Creating a List of stations with random distances is also very simple. I made the 1st and last ones terminals and every 5th a main station.
public List<Tuple<string, double, int>> SetupTrainStops(int count)
{
var stops = new List<Tuple<string, double, int>>();
Random rnd = new Random(count);
for (int i = 0; i < count; i++)
{
string n = (char)(i+(byte)'A') + "-Street";
double d = 1 + rnd.Next(3) + rnd.Next(4) + rnd.Next(5) / 10d;
if (d < 3) d = 3; // a minimum distance so the label won't touch
int t = (i == 0 | i == count-1) ? 0 : rnd.Next(5)==0 ? 2 : 1;
var ts = new Tuple<string, double, int>(n, d, t);
stops.Add(ts);
}
return stops;
}
Now that we have the train stops we can set up the y-axis:
public void SetupTrainStopAxis(Chart chart)
{
Axis ay = chart.ChartAreas[0].AxisY;
ay.LabelStyle.Font = new Font("Consolas", 8f);
double totalDist = 0;
for (int i = 0; i < TrainStops.Count; i++)
{
CustomLabel cl = new CustomLabel();
cl.Text = TrainStops[i].Item1;
cl.FromPosition = totalDist - 0.1d;
cl.ToPosition = totalDist + 0.1d;
totalDist += TrainStops[i].Item2;
cl.ForeColor = TrainStops[i].Item3 == 1 ? Color.DimGray : Color.Black;
ay.CustomLabels.Add(cl);
}
ay.Minimum = 0;
ay.Maximum = totalDist;
ay.MajorGrid.Enabled = false;
ay.MajorTickMark.Enabled = false;
}
A few notes are called for here:
As the values are quite dynamic we can't use normal Labels which would come with the fixed Interval spacing.
So we create CustomLabels instead.
For these we need two values to determine the space into which they shall be centered. So we create a small span by adding/subtracting 0.1d.
We have calculated the total distance and use it to set up the Maximum of the y-axis. Again: To mimick the schedule you show you will have to do some reversing here and there..
By adding CustomLabels the normal ones are turned off automatically. As we need MajorGridlines at the irregular intervals we also turn the normal ones off. Hence we must draw them ourselves. Not really hard as you can see..:
For this we code one of the xxxPaint events:
private void chart1_PostPaint(object sender, ChartPaintEventArgs e)
{
Axis ay = chart1.ChartAreas[0].AxisY;
Axis ax = chart1.ChartAreas[0].AxisX;
int x0 = (int) ax.ValueToPixelPosition(ax.Minimum);
int x1 = (int) ax.ValueToPixelPosition(ax.Maximum);
double totalDist = 0;
foreach (var ts in TrainStops)
{
int y = (int)ay.ValueToPixelPosition(totalDist);
totalDist += ts.Item2;
using (Pen p = new Pen(ts.Item3 == 1 ? Color.DarkGray : Color.Black,
ts.Item3 == 1 ? 0.5f : 1f))
e.ChartGraphics.Graphics.DrawLine(p, x0 + 1, y, x1, y);
}
// ** Insert marker drawing code (from update below) here !
}
Note the use of the ValueToPixelPosition conversion functions of the axes!
Now for the last part: How to add a Series of train data..:
public void AddTrainStopSeries(Chart chart, DateTime start, int count, int speed)
{
Series s = chart.Series.Add(start.ToShortTimeString());
s.ChartType = SeriesChartType.Line;
s.Color = speed == 0 ? Color.Black : Color.Brown;
s.MarkerStyle = MarkerStyle.Circle;
s.MarkerSize = 4;
double totalDist = 0;
DateTime ct = start;
for (int i = 0; i < count; i++)
{
var ts = TrainStops[i];
ct = ct.AddMinutes(ts.Item2 * (speed == 0 ? 1 : 1.1d));
DataPoint dp = new DataPoint( ct.ToOADate(), totalDist );
totalDist += TrainStops[i].Item2;
s.Points.Add(dp);
}
}
Note that since my data don't contain real arrival/departure times I calculated them from the distance and some speed factor. You, of course, would use your data!
Also note that I have used a Line chart with extra Marker circles.
Also note that each train series can easily be disabled/hidden or brought back again.
Let's show only the fast trains:
private void cbx_ShowOnlyFastTrains_CheckedChanged(object sender, EventArgs e)
{
foreach (Series s in chart1.Series)
s.Enabled = !cbx_ShowOnlyFastTrains.Checked || s.Color == Color.Brown;
}
Of course for a robust application you will not rely ona magic color ;-)
Instead you could add a Tag object to the Series to hold all sorts of train info.
Update: As you noticed the drawn GridLines cover the Markers. You can insert this piece of code here (**); it will owner-draw the Markers at the end of the xxxPaint event.
int w = chart1.Series[0].MarkerSize;
foreach(Series s in chart1.Series)
foreach(DataPoint dp in s.Points)
{
int x = (int) ax.ValueToPixelPosition(dp.XValue) - w / 2;
int y = (int) ay.ValueToPixelPosition(dp.YValues[0])- w / 2;
using (SolidBrush b = new SolidBrush(dp.Color))
e.ChartGraphics.Graphics.FillEllipse(b, x, y, w, w);
}
Close-up:
I’m currently venturing into the world of c# and have created my first windows application. Everything is working however I believe its efficiency here that is letting me down.
I am currently reading serial data from an Arduino. All the data is send in the format of “Letter” (newline) “Number” (newline) “letter” etc. From here I sort the data into relevant columns in a table. I have 6 sets of data coming from the Arduino. This data is then plotted onto a graph using zed graphs, with only 5 seconds of data being shown at once. So a moving axis.
After about 20s of plotting data to the graphs the plotting speed slows and eventually I am left with a moving graph with no points as they are trailing behind.
I tried flushing the serial buffer but this slowed everything down even more.
private void IncomingDataSort()
{
string IncomingSerial = serialPort1.ReadLine(); // Read incomming serial data
string StrIncomingSerial = IncomingSerial.ToString(); // convert this data to workable string
elapsed_time = (stopwatch.ElapsedMilliseconds); // How many milliseconds since stopwatch (read serial button) started
elapsed_time_sec = elapsed_time / 1000;
Timems.Text = elapsed_time.ToString();
if (StrIncomingSerial.Contains("Z") || StrIncomingSerial.Contains("Y")) // If this string contains a "Z" or "Y"
{
if (StrIncomingSerial.Contains("Z"))
{
string Number = serialPort1.ReadLine(); // Read Serialport
double Num; // Create variable "Num"
bool isNum = double.TryParse(Number, out Num); // Is the incomming serial data a number?
if (isNum) // If it is a number...
{
int NumberInt = Convert.ToInt16(Number); // convert string to int
Heat1Temp.Text = Number;
}
}
if (StrIncomingSerial.Contains("Y"))
{
string Number = serialPort1.ReadLine(); // Read Serialport
double Num; // Create variable "Num"
bool isNum = double.TryParse(Number, out Num); // Is the incomming serial data a number?
if (isNum) // If it is a number...
{
int NumberInt = Convert.ToInt16(Number); // convert string to int
Heat2Temp.Text = Number;
}
}
CreateGraph1(zedGraphControl1, elapsed_time, Convert.ToInt16(Heat1Temp.Text), Convert.ToInt16(Heat2Temp.Text)); // plot gragh
}
}
private void CreateGraph1(ZedGraphControl zgc, long time, int IncomingData, int IncomingData2)
{
GraphPane myPane = zgc.GraphPane; // setup graph
zgc.AutoScroll = true;
myPane.Title = "Graph 1"; // Titles
myPane.XAxis.Title = "Time (s)";
myPane.YAxis.Title = "";
myPane.Legend.IsVisible = false; // remove legend (DONT MAKE TRUE!!)
myPane.XAxis.Min = myPane.XAxis.Max - GraphTimeSpan;
PointPairList GraphInput = new PointPairList(); // create a new list
PointPairList GraphInput2 = new PointPairList(); // create a new list
long x;
int y1;
long x2;
int y2;
x = time; // x axis 1
y1 = IncomingData; // y axis 1
x2 = time; // x axis 2
y2 = IncomingData2; // y axis 2
GraphInput.Add(x, y1); // add to list
GraphInput2.Add(x2, y2); // add to list
LineItem myCurve = myPane.AddCurve("FirstSettings", GraphInput, Color.Red, SymbolType.Diamond); // draw points
LineItem myCurve2 = myPane.AddCurve("SecondSettings", GraphInput2, Color.Blue, SymbolType.Diamond);
zgc.AxisChange(); // update axis
zgc.Refresh();
}
As a follow on to my comment - rather than adding a new curve each time you get data you only need to append the new points to the existing PointPairLists - that way you will only ever have the two curves the graph needs to handle. Separating the initialisation & update of the graph would be a good idea - so something like this :
PointPairList GraphInput;
PointPairList GraphInput2;
LineItem myCurve;
LineItem myCurve2;
private void InitGraph(ZedGraphControl zgc)
{
GraphPane myPane = zgc.GraphPane; // setup graph
zgc.AutoScroll = true;
myPane.Title = "Graph 1"; // Titles
myPane.XAxis.Title = "Time (s)";
myPane.YAxis.Title = "";
myPane.Legend.IsVisible = false; // remove legend (DONT MAKE TRUE!!)
GraphInput = new PointPairList(); // create a new list
GraphInput2 = new PointPairList(); // create a new list
myCurve = myPane.AddCurve("FirstSettings", GraphInput, Color.Red, SymbolType.Diamond); // draw points
myCurve2 = myPane.AddCurve("SecondSettings", GraphInput2, Color.Blue, SymbolType.Diamond);
}
private void UpdateGraph(ZedGraphControl zgc, long time, int IncomingData, int IncomingData2)
{
GraphPane myPane = zgc.GraphPane; // setup graph
myPane.XAxis.Max = time;
myPane.XAxis.Min = myPane.XAxis.Max - GraphTimeSpan;
GraphInput.Add(time, IncomingData); // add to list
GraphInput2.Add(time, IncomingData2); // add to list
// AT THIS POINT YOU COULD SEARCH THROUGH THE POINT LIST & REMOVE ANY POINTS PRIOR TO THE MIN TIME
while (GraphInput[0].X < myPane.XAxis.Min)
{
GraphInput.RemoveAt(0);
}
while (GraphInput2[0].X < myPane.XAxis.Min)
{
GraphInput2.RemoveAt(0);
}
zgc.AxisChange(); // update axis
zgc.Refresh();
}
Another time saving may be to update each curve independently - as you are adding points to GraphInput & GraphInput2 regardless of whether you actually had points to enter for both. Having separate UpdateGraph1 & UpdateGraph2 methods may cut the points to be plotted in half.
There are several other things that could be tidied up in the code too - but they are not causing you severe time issues (you convert the numbers from strings, then convert back to strings, then convert back to numbers - for example).
Thanks so much for all your help, sorry it took me so long to respond but wanted to make sure I had a working piece of code before re-posting.
Here is the solution I found to work encase it helps anyone else. Although please bare in mind I am a beginner so will almost definitely need tidying up!
private void InitGraph1 (ZedGraphControl zgc)
{
GraphPane myPane = zgc.GraphPane; // setup graph
zgc.AutoScroll = true;
myPane.Title = "Graph 1"; // Titles
myPane.XAxis.Title = "Time (s)";
myPane.YAxis.Title = "";
myPane.Legend.IsVisible = false; // remove legend
GraphInput1 = new PointPairList(); // create a new list
GraphInput2 = new PointPairList(); // create a new list
myCurve = myPane.AddCurve(DataOutputZ, GraphInput1, Color.Red, SymbolType.Diamond); // draw points
myCurve2 = myPane.AddCurve(DataOutputY, GraphInput2, Color.Blue, SymbolType.Diamond);
}
-
private void UpdateGraph(ZedGraphControl zgc, long time, int IncomingData, int IncomingData2)
{
{
GraphPane myPane = zgc.GraphPane;
myPane.XAxis.Min = time - GraphTimeSpan;
myPane.XAxis.Max = time + (GraphTimeSpan/4); // put new points in last quarter of graph
// Get the first CurveItem in the graph
LineItem myCurve = zgc.GraphPane.CurveList[0] as LineItem;
LineItem myCurve2 = zgc.GraphPane.CurveList[1] as LineItem;
PointPairList GraphInput1 = myCurve.Points as PointPairList;
PointPairList GraphInput2 = myCurve2.Points as PointPairList;
// add new data points to the graph
GraphInput1.Add(time, IncomingData);
GraphInput2.Add(time, IncomingData2);
while (GraphInput1[0].X < myPane.XAxis.Min)
{
GraphInput1.RemoveAt(0);
}
while (GraphInput2[0].X < myPane.XAxis.Min)
{
GraphInput2.RemoveAt(0);
}
// force redraw
zgc.Invalidate();
zgc.AxisChange(); // update axis
zgc.Refresh();
}
}
Thanks again!
I am developing C# windows forms app,I am using chart control. I need to add data points to chart for every 1 sec using cross thread with InvokeRequired and BeginInvoke.I got the following message in the Output window:
A first chance exception of type 'System.OverflowException' occurred in mscorlib.dll.
A first chance exception of type 'System.Reflection.Targetinvocationexception' occurred in mscorlib.dll
I don't know where it is coming exactly but it is crashing my application completely.Please guide me.
public void AddNewPoint(DateTime timeStamp, System.Windows.Forms.DataVisualization.Charting.Series ptSeries1, System.Windows.Forms.DataVisualization.Charting.Series ptSeries2, double Y1value, double Y2value)
{
if (this.chart1.InvokeRequired)
{
BeginInvoke((Action)(() =>
{
this.chart1.Series[0].Points.AddXY(timeStamp.ToOADate(), Y1value);
this.chart1.Series[1].Points.AddXY(timeStamp.ToOADate(), Y2value);
if( ((count % 60) == 0)&&(count!=0))
{
mviewcount += 60;
viewcount += 60;
chart1.ChartAreas[0].AxisX.ScrollBar.Enabled = true;
//chart1.ChartAreas[0].AxisX.ScaleView.Size = 20;
chart1.ChartAreas[0].AxisX.Minimum = DateTime.FromOADate(ptSeries1.Points[count - 1].XValue).ToOADate();
chart1.ChartAreas[0].AxisX.Maximum = DateTime.FromOADate(ptSeries1.Points[count - 1].XValue).AddMinutes(1).ToOADate();
min = chart1.ChartAreas[0].AxisX.Minimum;
max = chart1.ChartAreas[0].AxisX.Maximum;
}
if (count >= 60)
{
if ((count >= viewcount) && (count <= mviewcount))
{
chart1.Series[0].Points[0].AxisLabel = System.DateTime.FromOADate(chart1.Series[0].Points[count - 1].XValue).ToString();
chart1.ChartAreas[0].AxisX.ScaleView.Position = max;
//chart1.ChartAreas[0].AxisX.ScaleView.Position = chart1.Series[0].Points.Count - chart1.ChartAreas[0].AxisX.ScaleView.Size;
}
}
chart1.Update();
chart1.ChartAreas[0].RecalculateAxesScale();
}));
}
}
public void AddData() //calling using thread
{
while (true)
{
if (flag) // making flag true using timer for every 1sec
{
flag = false;
DateTime timeStamp = DateTime.Now;
double Y1 = 0.0;
double Y2 = 0.0;
Y1 = getY1(count);
Y2 = getY2(count + 1);
AddNewPoint(timeStamp, chart1.Series[0], chart1.Series[1], Y1, Y2);
count++;
}
Thread.Sleep(1);
}
}
Update
Well, you may need a better design approach.
1. For the duration of 2-3 weeks, collect the information / data points in your own data structure. Two List would do in your case.
2. Let the chart render only a selected portion of the data, upon user request. This is called pagination. For instance, the chart would show the first 1000 data points, and clicking a "Next" button would show the next 1000 data points. You can also provide text boxes to supply a range.
3. This reduces the load on chart
4. Separate the UI rendering and data collection logic
Also make sure that AddData() is not running on the UI thread.
An indefinite while loop on the main thread can lead to overflow exceptions eventually.
Try this:
1. Add a Timer control to your form
2. Change your AddData method like the following:
public void AddData()
{
timer1.Interval = 1000;
timer1.Tick += (s, e) =>
{
flag = false;
DateTime timeStamp = DateTime.Now;
double Y1 = 0.0;
double Y2 = 0.0;
Y1 = getY1(count);
Y2 = getY2(count + 1);
AddNewPoint(timeStamp, chart1.Series[0], chart1.Series[1], Y1, Y2);
Thread.Sleep(1);
count++;
};
timer1.Start();
}
It seems "First chance exceptions" are frequent (enough) in VS, were you running this continuosly while still in Debug mode, for 24 hours? You probably wouldn't get this exception if the program was release compiled and not being debugged
I have a fluids simulation running in Unity, calculating a heightfield for the fluid. I then have a mesh/vertex grid for which I set the height. I'm now trying to make it threaded. I only update the fluids every several frames (this is also threaded and works perfectly so far.) I also only update the mesh when the simulation got updated.
I have the following (non-threaded) code that works as it should:
public override void ApplyVisuals(float[][] lowerLayersHeight) {
UpdateVisuals(1, N, lowerLayersHeight, _height, _opaqueHeight, _nonZeroHeightOffset, _tempVertices, _tempColors);
_mesh.vertices = _tempVertices;
_mesh.colors32 = _tempColors;
}
So I update the visuals with some parameters, and then store the new vertices and new colors for the mesh in _tempVertices en _tempColors respectively. Though I tried to run this code on a separate thread:
public override void ApplyVisuals(float[][] lowerLayersHeight) {
if (_mainVisualsThread != null && _mainVisualsThread.IsAlive) {
_mainVisualsThread.Join();
}
Helpers.Swap(ref _vertices, ref _tempVertices); //swap the onces calculated by the _mainVisualsThread (temp) with the current array
Helpers.Swap(ref _colors, ref _tempColors);
_mesh.vertices = _tempVertices; //set the current as visual, the temps will be updated with the new vertices for next update
_mesh.colors32 = _tempColors;
_mainVisualsThread = new Thread(()=>
UpdateVisuals(1, N, lowerLayersHeight, _height, _opaqueHeight, _nonZeroHeightOffset, _tempVertices, _tempColors)
);
_mainVisualsThread.Start();
}
So instead of updating the visuals this call, I let a separate thread calculate the visuals for the next time ApplyVisuals and apply the vertices and colors calculated by the previous call to ApplyVisuals. This of course makes the visuals lag behind one update, but that's an acceptable trade-of. Though for some reason this doesn't work. My mesh starts to flicker, one frame being the proper one, the next nothing. The picture shows the bad frame first, and then the good one.
The weirdness doesn't stop there however. In my first non-threaded solution, the simulation works perfectly. Though when I use threads the visualisation somehow fucks it up. Even though I don't alter any variables used in the simulation. The only thing I change is my buffered vertex and colors array, but those aren't used in the simulation.
The UpdateVisuals method I use:
static void UpdateVisuals(int xFrom, int xTo,
float[][] lowerLayersHeight,
float[][] heightField,
float opaqueHeight,
float nonZeroHeightOffset,
Vector3[] vertices, Color32[] colors
) {
// Set the heights of the vertices of the mesh and apply colors
int x, y, index;
float height, relHeight;
for (x = xFrom; x <= xTo; ++x) {
for (y = 0; y < N+2; ++y) {
index = CalculateIndex(x,y);
height = heightField[x][y];
relHeight = height / opaqueHeight;
vertices[index].y = height + lowerLayersHeight[x][y]
+ (height > 0 ? nonZeroHeightOffset : 0);
colors[index].a = (byte)Mathf.Lerp(0, 200, relHeight);
}
}
}
In my simulation I never alter the _height array, what I do is I alter a _tempHeight array, and then swap them when the update is done. I do however read from the _height array.
Some more things I tried:
Another ApplyVisuals method:
_mainVisualsThread = new Thread(()=>
UpdateVisuals(1, N, lowerLayersHeight, _height, _opaqueHeight, _nonZeroHeightOffset, _tempVertices, _tempColors)
);
_mainVisualsThread.Start();
_mainVisualsThread.Join();
_mesh.vertices = _tempVertices;
_mesh.colors32 = _tempColors;
This also works as it should. So there appears to go something wrong when actually doing other stuff while the visualsThread is working.
I also tried to copy both the _height and lowerLayersHeight with the copy from this question: link Like this:
var bufferedHeight = Helpers.CopyArrayBuiltIn(_height);
var bufferedLowerLayersHeight = Helpers.CopyArrayBuiltIn(lowerLayersHeight);
_mainVisualsThread = new Thread(()=>
UpdateVisuals(1, N, bufferedLowerLayersHeight, bufferedHeight, _opaqueHeight, _nonZeroHeightOffset, _tempVertices, _tempColors)
);
This didn't help, and both the simulation and visualisation go wrong again :(
And then I was out of ideas. I'm pretty new to threading, so I'm hoping there's somthing simple I'm missing. But I can't figure out what.
Here's my simulation update code:
public override void DoUpdate(float dt, float dx, float[][] lowerLayersHeight) {
// Wait for update to be done
if (_mainUpdateThread != null && _mainUpdateThread.IsAlive) {
_mainUpdateThread.Join();
}
Helpers.Swap(ref _tempHeight, ref _height);
// Start the next update already on different threads. These will then be swapped next time an update is wanted.
_mainUpdateThread = new Thread(()=> {
// Height
RunPartsThreaded(_threadCount, (int from, int to)=>UpdateHeight(from, to, dt, dx, _tempFlux, _height, _tempHeight));
});
_mainUpdateThread.Start();
}
static void UpdateHeight(int xFrom, int xTo, float dt, float dx,
OutflowFlux[][] tempFlux,
float[][] height,
float[][] tempHeight
) {
int x, y;
float dV;
for (x=xFrom ; x <= xTo ; x++ ) {
for (y=1 ; y <= N ; y++ ) {
//
// 3.2.2 Water Surface (and Velocity Field Update)
// ----------------------------------------------------------------------------------------
dV = dt * (
//sum in
tempFlux[x-1][y].right + tempFlux[x][y-1].top + tempFlux[x+1][y].left + tempFlux[x][y+1].bottom
//minus sum out
- tempFlux[x][y].right - tempFlux[x][y].top - tempFlux[x][y].left - tempFlux[x][y].bottom
); //(6)
tempHeight[x][y] = height[x][y] + dV / (dx*dx); //(7)
//swap temp and the real one later
}
}
}
The code gets called every _dt seconds (usually 0.02-0.05) like this:
_timeSinceLastUpdate += Time.deltaTime;
if (_timeSinceLastUpdate >= _dt) {
_timeSinceLastUpdate -= _dt;
//
// Update each layer
// ----------------------------------------------------------------------
ResetTotalHeight(); //The first layer just sits on a plane
for (int i = 0; i < _layers.Count; ++i) {
_layers[i].DoUpdate(_dt, _dx, _tempTotalHeight);
_layers[i].ApplyVisuals(_tempTotalHeight);
AddHeightToTotal(_layers[i].HeightField);
}
}
There are multiple layers, though only one uses the fluid simulation code, the others are static. The AddHeightToTotal is just a method to keep track of the total height of all previous layers, that's the lowerLayersHeight parameter my simulation methods get.
Lastly, the RunPartsThreaded method I use:
static void RunPartsThreaded(int threadCount, Action<int, int> partUpdateWrapper) {
int extraThreadCount = threadCount - 1;
Thread[] _threads = new Thread[extraThreadCount];
int step = N / threadCount;
int i;
for (i = 0; i < extraThreadCount; ++i) {
int index = i; //https://stackoverflow.com/questions/7352757/threadstart-in-a-loop-in-a-timer-only-executing-last-thread-in-said-loop
_threads[index] = new Thread(()=>partUpdateWrapper(step * index + 1, step * (index+1)));
_threads[index].Start();
}
partUpdateWrapper( step * extraThreadCount + 1, N );
for (i = 0; i < extraThreadCount; ++i) {
_threads[i].Join();
}
}
The simulation can be run on only strips of my grid, and I make multiple threads each updating a different part of the whole grid.
Preamble
I'm not very optimistic about your multi-threading design overall... IMO, it would be nice at least to have threads reused.
Problem
Ok, take a look on this:
public override void DoUpdate(float dt, float dx, float[][] lowerLayersHeight) {
....
Helpers.Swap(ref _tempHeight, ref _height);
Ooops! UpdateVisuals thread can be runnning right now (and it's highly likely that it is), which uses _height array.