I am trying to use Dynamic Data Display for my senior design project, but I'm having difficulty.
I can create the graph just fine, but I can't the line to update at all unless I am zooming in and out.
In most examples, all they had to do was create the graph, then when they manipulate the data, the graph updates itself.
So here are the main functions I am using with regards to the charts:
private List<WindDAQ.WindDataPoint> Chart1Data;
private EnumerableDataSource<WindDAQ.WindDataPoint> Chart1DataSource;
private void InitializeCharts()
{
Chart1Data = new List<WindDAQ.WindDataPoint>();
Chart1DataSource = new EnumerableDataSource<WindDAQ.WindDataPoint>(Chart1Data);
Chart1DataSource.SetXMapping(x => Chart1XAxis.ConvertToDouble(x.Time));
Chart1DataSource.SetYMapping(y => Chart1XAxis.ConvertToDouble(y.Lift));
Chart1.AddLineGraph(Chart1DataSource, Colors.Blue, 2, "Lift");
Chart1.Viewport.AutoFitToView = true;
}
private void UpdateChart()
{
for (int i = 0, count = itsDAQ.getStreamCount(); i < count; i++)
{
Chart1Data.Add(itsDAQ.getValue());
if(Chart1Data.Count >= 300)
{ Chart1Data.RemoveAt(0); }
}
}
InitializeCharts() is called once when creating the window.
UpdateChart() is called on a timer event.
WindDAQ.WindDataPoint contains Lift, Drag, Velocity, and Time data. Lift and Time are shown selected.
you should use the AppendAsync method of your observableCollection.
You'r updating only the list used to create the observable one, not the source of your graph.
private void UpdateChart()
{
for (int i = 0, count = itsDAQ.getStreamCount(); i < count; i++)
{
Chart1DataSource.AppendAsync(itsDAQ.getValue());
if (Chart1DataSource.Collection.Count >= 300) // this part should work in a thread-safe
{
Chart1DataSource.Collection.RemoveAt(0); // context and with some precaution
}
}
}
Related
We have to requirement to virtualize a ListView/ItemsControl with a VirtualizingStackPanel. Although everything works as expected, the Control's ItemTemplate adheres a complex control with a lot of computation during its initialization phase - which has to be done on the UI thread. In other words, scrolling leads to UI freezes - which is fine if it only has to be done once. As we can't use the VirtualizingStackPanel.VirtualizationMode="Recycle" (due to several other restrictions) we have to try something different.
I thought of a "cached" virtualizingStackPanel which doesn't actually dispose the ItemTemplate's Template, but rather 'freezes' the control. When the user scrolls back to a - previously loaded template - we could simply 'unfreeze' the control.
The 'freeze' can be implemented by overwriting OnCleanUpVirtualizedItem, such as:
protected override void OnCleanUpVirtualizedItem(CleanUpVirtualizedItemEventArgs args)
{
var stuff = FindChild<HeavyStuff>(args.UIElement);
if (stuff != null)
{
int idx = Children.IndexOf(args.UIElement);
if (!_buffer.ContainsKey(idx))
_buffer.Add(idx, args.UIElement);
stuff.Freeze();
args.Handled = true;
args.Cancel = true;
}
else
{
base.OnCleanUpVirtualizedItem(args);
}
}
That works pretty well. The control stays within the VisualTree and it simply 'freezes' and avoids any user-input and the potential resulting workload. However, I couldn't figure out on howto 'unfreeze' the control when it comes back into view. I dug through the reference-source and found the BringIndexIntoView, which could potentially solve my issue like the following:
protected override void BringIndexIntoView(int index)
{
if (_buffer.ContainsKey(index))
{
FindChild<HeavyStuff>(_buffer[index]).UnFreeze();
}
else
{
base.BringIndexIntoView(index);
}
}
However, that method never gets called by the internal VirtualizingStackPanel logic. My second thought was to override the IItemContainerGenerator, as the generator does provide the DependencyObjects on demand. But again without any luck. One can't inherit the ItemContainerGenerator, because it is sealed. Secondly, defining a proxy and overwriting the ItemContainerGenerator properties doesn't help either, as the base class doesn't call that VirtualizingStackPanel's ItemContainerGenerator property at all:
public new IItemContainerGenerator ItemContainerGenerator => generator;
Is there any way to obtain the information when a control scrolls back into the view, without the VirtualizingStackPanel re-creating an instance?
Addon: I also thought about virtualizing the data-source itself. However, even if we would virtualize the data source, the global user input would lead the controls to perform CPU and UI-thread intensive operations. Hence, it doesn't matter which way we choose, we do have to 'freeze' and 'unfreeze' certain, non-viewport-related controls. In other words, we need UI virtualization nevertheless.
EDIT: "Freeze" and "Unfreeze" does not refer to the .NET object freezing. My poor choice of words may cause that confusion. With "freeze" and "unfreeze" I do refer to some internal logic which subscribes or unsubscribes from various event handlers, such that controls, beeing out of the viewport, don't require to process that input.
You can use the following example implementation that extends the StackPanel to tracks the visibility of its hosted containers (in terms of the parent scroll viewer's viewport).
Simply set the custom Panel as ItemsPanel to the ListBox.
It's important that the parent ScrollViewer has the CanContentScroll property set to true (which is the default for the ListBox).
Since StackPanel already implements IScrollInfo, observing the scroll event and viewport is very straight forward.
Add your actual implementation, to handle the changed containers and/or their hosted models, to the OnHiddenContainersChanged method to complete the Panel.
public class ScrollWatcherPanel : StackPanel
{
public ScrollWatcherPanel()
{
this.Loaded += OnLoaded;
}
private void OnLoaded(object sender, RoutedEventArgs e)
{
if (!this.ScrollOwner.CanContentScroll)
{
throw new InvalidOperationException("ScrollViewer.CanContentScroll must be enabled.");
}
this.ScrollOwner.ScrollChanged += OnScrollChanged;
}
protected override void OnRenderSizeChanged(SizeChangedInfo sizeInfo)
{
base.OnRenderSizeChanged(sizeInfo);
HandleAllContainers();
}
private void OnScrollChanged(object sender, ScrollChangedEventArgs e)
=> HandleContainerVisibilityChanges((int)e.VerticalChange);
private void HandleAllContainers()
{
int containersBeforeViewportStartCount = (int)this.VerticalOffset;
int containersBeforeViewportEndCount = containersBeforeViewportStartCount + (int)this.ViewportHeight + 1;
var newHiddenContainers = new List<FrameworkElement>();
var newVisibleContainers = new List<FrameworkElement>();
for (int childContainerIndex = 0; childContainerIndex < this.InternalChildren.Count; childContainerIndex++)
{
bool isContainerHiddenBeforeViewport = childContainerIndex < containersBeforeViewportStartCount;
bool isContainerVisibleInViewport = childContainerIndex < containersBeforeViewportEndCount;
var childContainer = (FrameworkElement)this.InternalChildren[childContainerIndex];
if (isContainerHiddenBeforeViewport)
{
newHiddenContainers.Add(childContainer);
}
else if (isContainerVisibleInViewport)
{
newVisibleContainers.Add(childContainer);
}
else // Container is hidden after viewport
{
newHiddenContainers.Add(childContainer);
}
}
OnHiddenContainersChanged(newHiddenContainers, newVisibleContainers);
}
private void HandleContainerVisibilityChanges(int verticalChange)
{
int containersBeforeViewportStartCount = (int)this.VerticalOffset;
int containersBeforeViewportEndCount = containersBeforeViewportStartCount + (int)this.ViewportHeight + 1;
int newHiddenContainerCount = Math.Abs(verticalChange);
int newVisibleContainerCount = Math.Abs(verticalChange);
bool isScrollingDown = verticalChange > 0;
int changeCount = Math.Abs(verticalChange);
var newHiddenContainers = new List<FrameworkElement>();
var newVisibleContainers = new List<FrameworkElement>();
int changesIndex = Math.Max(0, containersBeforeViewportStartCount - changeCount);
for (int childContainerIndex = changesIndex; childContainerIndex < this.InternalChildren.Count; childContainerIndex++)
{
bool isContainerHiddenBeforeViewport = childContainerIndex < containersBeforeViewportStartCount;
bool isContainerVisibleInViewport = childContainerIndex < containersBeforeViewportEndCount;
var childContainer = (FrameworkElement)this.InternalChildren[childContainerIndex];
if (isContainerHiddenBeforeViewport)
{
if (isScrollingDown)
{
bool isContainerNewHidden = childContainerIndex >= containersBeforeViewportStartCount - changeCount
&& newHiddenContainerCount > 0;
if (isContainerNewHidden)
{
newHiddenContainers.Add(childContainer);
newHiddenContainerCount--;
}
}
}
else if (isContainerVisibleInViewport)
{
if (isScrollingDown)
{
bool isContainerNewVisible = childContainerIndex >= containersBeforeViewportEndCount - changeCount
&& newVisibleContainerCount > 0;
if (isContainerNewVisible)
{
newVisibleContainers.Add(childContainer);
newVisibleContainerCount--;
}
}
else
{
bool isContainerNewVisible = childContainerIndex >= containersBeforeViewportStartCount
&& newVisibleContainerCount > 0;
if (isContainerNewVisible)
{
newVisibleContainers.Add(childContainer);
newVisibleContainerCount--;
}
}
}
else // Container is hidden after viewport (on scroll up)
{
if (!isScrollingDown)
{
bool isContainerNewHidden = childContainerIndex >= containersBeforeViewportEndCount
&& newHiddenContainerCount > 0;
if (isContainerNewHidden)
{
newHiddenContainers.Add(childContainer);
newHiddenContainerCount--;
if (newHiddenContainerCount == 0)
{
break;
}
}
}
}
}
OnHiddenContainersChanged(newHiddenContainers, newVisibleContainers);
}
protected virtual void OnHiddenContainersChanged(IEnumerable<FrameworkElement> newHiddenContainers, IEnumerable<FrameworkElement> newVisibleContainers)
{
// TODO::Handle "hidden"/"visible" item containers, that are just scrolled out of/into the viewport.
// You can access the DataContext of the containers to get a reference to the underlying data model.
}
}
I am currently using liveChart to plot a real time graph of 3 values: a position, a load and a deformation. The program is based on the Doli.DoPE library (a proprietary dll)
In MainForm.cs, there is an event that is triggered everytime the sensor records a new value (every millisecond or so).
public void Initialisation()
{
//...
MyEdc.Eh.OnDataHdlr += new DoPE.OnDataHdlr(OnData)
//...
}
with
private int OnData(ref DoPE.OnData Data, object Parameter)
{
DoPE.Data Sample = Data.Data;
if (Data.DoPError == DoPE.ERR.NOERROR)
{
Int32 Time = Environment.TickCount;
if ((Time - LastTime) >= 250 /*ms*/)
{
// Send the data from the ondata handler inside of a global list
ListData.time.Add(Sample.Time);
ListData.position.Add(Sample.Sensor[(int)DoPE.SENSOR.SENSOR_S]);
ListData.load.Add(Sample.Sensor[(int)DoPE.SENSOR.SENSOR_F]);
ListData.extend.Add(Sample.Sensor[(int)DoPE.SENSOR.SENSOR_E]);
Thread ThForUpdateChart = new Thread(() =>
{
if (NewINstanceOfChart != null)
{ NewINstanceOfChart.UpdateValues(ListData.time.Last(), ListData.position.Last(),ListData.load.Last(), ListData.extend.Last()); }
});
ThForUpdateChart.Start();
LastTime = Time;
}
}
return 0;
}
The function UpdateValues is part of a second form RealTimeChart.cs called in the MainForm through a button click event:
private void btnGraph_Click(object sender, EventArgs e)
{
var thread = new Thread(() =>
{
NewINstanceOfChart = new RealTimeChart(ListData);
NewINstanceOfChart.Show();
});
thread.Start();
}
the form RealTimeCharts.cs is initalised this way:
public RealTimeChart(Globals ListData)
{
InitializeComponent();
//measures = ListData;
ListPosition = new ChartValues<ObservablePoint>();
for (int i = 0; i < measures.load.Count(); i++)
{
ListPosition.Add(new ObservablePoint
{
X = measures.time[i],
Y = measures.position[i]
});
}
ListLoad = new ChartValues<ObservablePoint>();
for (int i = 0; i < measures.load.Count(); i++)
{
ListLoad.Add(new ObservablePoint
{
X = measures.time[i],
Y = measures.load[i]
});
}
ListExtend = new ChartValues<ObservablePoint>();
for (int i = 0; i < measures.load.Count(); i++)
{
ListExtend.Add(new ObservablePoint
{
X = measures.time[i],
Y = measures.extend[i]
});
}
resultChart.Series.Add(new LineSeries
{
LineSmoothness = 0,
Values = ListPosition,
PointGeometrySize = 2,
StrokeThickness = 4
});
SetXAxisLimits();
}
And the UpdateValues function is defined as followed:
public void UpdateValues(double time, double position, double load, double extend)
{
measures.time.Add(time-measures.TareTime);
measures.position.Add(position);
measures.load.Add(load);
measures.extend.Add(extend);
UpdateEnabledSequencialPartToTrue();
}
public void UpdateEnabledSequencialPartToTrue()
{
if (this.InvokeRequired)
BeginInvoke(new System.Action(() => this.InternalUpdateEnabledSequencialPartToTrue()));
else
InternalUpdateEnabledSequencialPartToTrue();
}
private void InternalUpdateEnabledSequencialPartToTrue()
{
try
{
ListPosition.Add(new ObservablePoint
{
X = measures.time.Last(),
Y = measures.position.Last()
});
ListLoad.Add(new ObservablePoint
{
X = measures.time.Last(),
Y = measures.load.Last()
});
ListExtend.Add(new ObservablePoint
{
X = measures.time.Last(),
Y = measures.extend.Last()
});
//LineSeries plot = new LineSeries();
SetXAxisLimits();
// lets only use the last 14400 values (1h long recording, 14400 values at frequency of 1 record very 250ms, see OnData function MainForm
if (measures.time.Count > 14400)
{
ListPosition.RemoveAt(0);
ListLoad.RemoveAt(0);
ListExtend.RemoveAt(0);
}
}
catch (NullReferenceException) { }
}
After a minute, the programme starts to be really laggy. I tried putting the second winform (RealTimeCharts) on another thread so the MainForm does not lag (it is piloting a machine, it has to be responsive), but no success.
I would like to know if the whole thing is laggy because the code is way too bad, or if it is liveChart that reached its (free) limits. Would you advice another way to plot real time data ?
In MainForm.cs, there is an event that is triggered everytime the sensor records a new value (every millisecond or so).
That is natturally way higher then what Winforms Drawing can take. See, drawing a GUI is expensive. If you only do it once per user-triggered event, you will never notice that. But do it from a loop - including sampling a sensor every MS - and you can quickly overlord the UI. My first Multithreading tests actually appeared to have failed on big numbers, becaus I ended up sending so many updates I plain overloaded the GUI thread. Since then I know not to go past progress bars.
You can add data to a background collection as quickly as you can sample them, but you can not draw that quickly. And honestly drawing more often then 30-60 times/second (every ~17 ms) is not really going to help anyone anyway. Usually you can not use a timer, as the Tick might happen more often then it can be processed - again, a GUI Thread with a overflowing Event Queue.
I do not have any rate limiting code for WindowsForms. But I would guess an Event that re-queues itself at the end of the EventQueue after finishing the work would work.
I am trying to create a list of buttons using information collected from www forms in my program. The problem I am having is that when I create the buttons, it infinitely creates them, and thusly unity crashes. I have two theories as to why this is happening, the first being that the function is being called each frame and is therefore creating too many buttons and the second is that for some reason when I check the length of a multidimensional array it has a length of 33 when it should only have a length of 3 (it should have 3 arrays of arrays). The whole script is below.
using Boo.Lang;
using System.Collections;
using SCG = System.Collections.Generic;
using UnityEngine;
using UnityEngine.SceneManagement;
using System;
public class MeetingRequestViewer : MonoBehaviour
{
//This script contains the code on how to display information about the students to the staff
//it displays each student in a different row
private Rect windowRect = new Rect(0, 0, Screen.width, Screen.height);
public Vector2 scrollPosition = Vector2.zero;
private int BSpace;
public string[,] SortedStudentArray;
private int j;
private string[][] MeetRequests;
private string[] temp;
private SCG.List<string> H_Priority = new SCG.List<string>();
private SCG.List<string> M_Priority = new SCG.List<string>();
private SCG.List<string> L_Priority = new SCG.List<string>();
private SCG.List<string> FullList = new SCG.List<string>();
private SCG.List<string> UAList;
private SCG.List<object> StudentButtonList = new SCG.List<object>();
private string[] Holding = new string[5];
private string[] SearchTerms;
public int ButtonSpacing = 10;
public int ButtonWidth = 80;
public int ButtonHeight = 30;
public void OnGUI()
{
//create a window
GUI.Window(0, windowRect, WindowFunction, "Meeting Request Viewer");
}
public void WindowFunction(int windowID)
{
//Fetches all user Data
string[][] userArray = GetComponent<Userdata>().CallDetail();
string[][] StudentArray = GetComponent<Userdata>().UserSorting(userArray);
//Calls the SortStudentArray method
string[,] SortedStudentArray = SortStudentList();
//Creates a box with a scrolling bar to taverse the y axis
scrollPosition = GUI.BeginScrollView(new Rect(Screen.width / 6, Screen.height / 6, 350, 250), scrollPosition, new Rect(0, 0, 300, 40 * SortedStudentArray.Length));
//for each row in the sorted student array
for (int x = 0; x < SortedStudentArray.Length - 1; x++)
{
//This keeps the gap between each button consistent
var y = ButtonSpacing + ButtonHeight;
//Regular Meeting Request
if (SortedStudentArray[x, 7] == "1")
{
//Urgent Meeting Request
if (SortedStudentArray[x, 8] == "1")
{
Debug.Log("Even Here");
//Create Button coloured red
GUI.backgroundColor = Color.red;
}
//Regular Meeting Request
else
{
//Create Button coloured yellow
GUI.backgroundColor = Color.yellow;
}
}
//No Meeting Request
else
{
//Create Button coloured green
GUI.backgroundColor = Color.green;
}
GUI.Button(new Rect(ButtonSpacing, ButtonSpacing + x * y, ButtonWidth, ButtonHeight), SortedStudentArray[x, 6]);
}
GUI.EndScrollView();
ButtonsCreated = true;
}
private string[,] SortStudentList()
{
//This method is used to fetch the meeting request data, split it into a jagged array and then again so that each
//row is a new user and each item in the row is a different peice of information relating to the user
//The Jagged array is then sorted using the bubble sort algorithm so that the students that require urgent meetings
//appear at the topo of the request table
//call website with the data store
WWW MeetReqData = new WWW("http://localhost/Wellbeing/meetingrequest.php");
//until WWW is finished do nothing
while (MeetReqData.isDone == false)
{
}
//convert the returned value into a string
string MeetReqString = MeetReqData.text;
//split the text into a string list
string[] mrq = MeetReqString.Split(';');
//convert the string list into a jagged array
MeetRequests = new string[mrq.Length][];
int i = 0;
//for each item in the list
foreach (string s in mrq)
{
//split it into its individual peice of information
string[] g = s.Split('|');
//store the information about a user on a new line
MeetRequests[i] = g;
++i;
}
for (int n = 0; n < MeetRequests.Length - 1; n++)
{
if (MeetRequests[n][1] == "1" && MeetRequests[n][2] == "1")
{
H_Priority.Add(MeetRequests[n][0]);
}
else if (MeetRequests[n][1] == "1" && MeetRequests[n][2] == "0")
{
M_Priority.Add(MeetRequests[n][0]);
}
else
{
L_Priority.Add(MeetRequests[n][0]);
}
}
//Combines all lists into a larger list of priorities
FullList.AddRange(H_Priority);
FullList.AddRange(M_Priority);
FullList.AddRange(L_Priority);
//convertFullList into an array for easier mainpulation and comparisons
string[][] feelingsArray = GetComponent<Userdata>().CallFeelings();
//FullList only contains 3 values
Debug.Log(FullList.Count);
//Info array about each user
string[,] SortedStudentArray = new string[FullList.Count, 11];
//SortedStudentArray contains 33 rows
Debug.Log("This thing Here");
Debug.Log(SortedStudentArray.Length);
//Line Counter
int SSAPos = 0;
// For every element in FullList
foreach (var item in FullList)
{
Debug.Log(FullList.Count);
//For each user in userArray
for (int y = 0; y < Userdata.userArray.Length; y++)
{
if (Userdata.userArray[y][0] == item)
{
for (int n = 0; n < Userdata.userArray; n++)
SortedStudentArray[SSAPos, n] = Userdata.userArray[y][n];
break;
}
}
Debug.Log(SortedStudentArray.Length);
//For each user in userArray
for (int y = 0; y < MeetRequests.Length; y++)
{
if (MeetRequests[y][0] == item)
{
SortedStudentArray[SSAPos, 7] = MeetRequests[y][1];
SortedStudentArray[SSAPos, 8] = MeetRequests[y][2];
break;
}
}
Debug.Log(SortedStudentArray.Length);
SSAPos += 1;
}
return SortedStudentArray;
}
// Update is called once per frame
void Update ()
{
//if (GUI.Button(new Rect(Screen.width / 4, Screen.height / 7, Screen.width / 2, Screen.height / 8), "Log Out"))
//{
// Debug.Log("Logged Out");
// SceneManager.LoadScene("Login");
//}
}
I did try to use a while loop around the section of code however it made no difference to the creation of buttons, they were still created more times than were needed
FullList is continually being populated which will eventually lead to a crash.
Call SortStudentList in Awake, such as;
string[,] SortedStudentArray;
void Awake ()
{
//Calls the SortStudentArray method
SortedStudentArray = SortStudentList();
}
Then;
public void WindowFunction(int windowID)
{
// Prevent further execution until SortedStudentArray is ready
if (SortedStudentArray == null) return;
...
}
From the documentation:
OnGUI is called for rendering and handling GUI events.
This means that your OnGUI implementation might be called several
times per frame (one call per event). For more information on GUI
events see the Event reference. If the MonoBehaviour's enabled
property is set to false, OnGUI() will not be called.
You need to reevaluate how you're doing your GUI. As mentioned in their GUI scripting guide you should probably use their UI system.
You especially don't want to be making one or more WWW calls per frame, or doing a bunch of sorting every frame.
https://docs.unity3d.com/ScriptReference/MonoBehaviour.OnGUI.html
OnGUI is called for rendering and handling GUI events.
This means that your OnGUI implementation might be called several times per frame (one call per event). For more information on GUI events see the Event reference. If the MonoBehaviour's enabled property is set to false, OnGUI() will not be called.
You should probably use another way to call your method, or use a boolean value to check whether or not it has already been called.
Please refer to the image below to better understand the Unity lifecycle methods.
More information about Unity's Lifecycle can be found at: https://docs.unity3d.com/Manual/ExecutionOrder.html
I have an integer array declared in a class. Using that array, a PolyLine is drawn on a grid. The elements of the array are the y-coordinates. The array has to updated continuously and then the values are to be displayed. My idea of implementing this is to have two threads. One to update the values of the array and the other to print PolyLine on the grid.
Updating the array is not a problem. When I try to print PolyLine on the grid using the code below, an exception is thrown with the following message: "The calling thread cannot access this object because a different thread owns it."
Kindly suggest a workaround for this problem using thread. Perhaps, an event may be used instead of thread? I am open to suggestions. Any help is appreciated!
public partial class MainWindow : Window
{
Polyline Wave = new Polyline();
public MainWindow()
{
InitializeComponent();
Refresh();
DisplyOnGrid();
ThreadStart child = new ThreadStart(DisplyOnGrid);
Thread _DisplayOnGrid = new Thread(child);
_DisplayOnGrid.Priority = ThreadPriority.Highest;
_DisplayOnGrid.Start();
}
private void DisplyOnGrid()
{
Wave.Stroke = Brushes.Yellow;
Wave.StrokeThickness = 1.25;
for (int i = 0; i < DisplayGrid.Width; i++)
{
Wave.Points.Add(new Point(i, 50));
}
DisplayGrid.Children.Add(Wave);
}
private void Refresh()
{
DisplayGrid.Children.Clear();
}
}
You are getting Error because you are trying to access object which is owned by UI/Dispatcher thread
try something like this:
private void DisplyOnGrid()
{
Dispatcher.Invoke(new Action(()=>
{
Wave.Stroke = Brushes.Yellow;
Wave.StrokeThickness = 1.25;
for (int i = 0; i < DisplayGrid.Width; i++)
{
Wave.Points.Add(new Point(i, 50));
}
DisplayGrid.Children.Add(Wave);
}
))};
Bear with me, I'm new to Stack Overflow, but have used it as a resource for a long time when researching methods of programming that I'm not fond with.
I read up a tutorial on how to create a graph in a C# Windows Forms Application, and was attempting to find out how to make it update itself in real-time if I ever need to use the graph to plot the total amount of data in a sensor. To test it out, I'm using a timer, ticking at every second (1000ms). And before I can use this with a sensor, I'm having two values automatically increment by 1.
The current problem I'm facing is that the chart itself won't update, and only stays the way it was drawn when the form loaded. I thought it was because I have to redraw the chart with chart1.Update();, and I tried using that before/after recreating the chart every second. However, the result is the same regardless. I just wondered if there's something I haven't done or needs to be changed in order to update the chart in real-time.
This is where the code is at currently:
public partial class Form1 : Form
{
int a = 1;
int b = 2;
public Form1()
{
InitializeComponent();
}
private void Form1_Load(object sender, EventArgs e)
{
// Data arrays.
string[] seriesArray = { "Cats", "Dogs" };
int[] pointsArray = { a, b };
// Set palette.
this.chart1.Palette = ChartColorPalette.SeaGreen;
// Set title.
this.chart1.Titles.Add("Pets");
// Add series.
for (int i = 0; i < seriesArray.Length; i++)
{
// Add series.
Series series = this.chart1.Series.Add(seriesArray[i]);
// Add point.
series.Points.Add(pointsArray[i]);
}
}
private void timer1_Tick(object sender, EventArgs e)
{
a++;
b++;
// Data arrays.
string[] seriesArray = { "Cats", "Dogs" };
int[] pointsArray = { a, b };
// Set palette.
this.chart1.Palette = ChartColorPalette.SeaGreen;
// Set title.
this.chart1.Titles.Add("Pets");
// Add series.
for (int i = 0; i < seriesArray.Length; i++)
{
// Add series.
Series series = this.chart1.Series.Add(seriesArray[i]);
// Add point.
series.Points.Add(pointsArray[i]);
}
chart1.Update();
}
}
Your code has several problems:
The timer click event is not hooked up. I know that it isn't because otherwise you'd get an exception telling you that..
..you can add a series only once. You were doing it on each timer.Tick. This and all other setup commands should go into an initial method like the form load.
I have corrected the errors in the code below, but, obviously, the data don't make any sense yet.
Also: While I have added code to hook up the timer.Tick, the button.Click is not hooked up. Usually you let the designer do this by double-clicking the control to hook up the standard event of a control or by double-clicking the event in the control's event tab in the property page.
int a = 1;
int b = 2;
string[] seriesArray = { "Cats", "Dogs" };
private void Form1_Load(object sender, EventArgs e)
{
// Set palette.
this.chart1.Palette = ChartColorPalette.SeaGreen;
// Set title.
this.chart1.Titles.Add("Pets");
// Add series
this.chart1.Series.Clear();
for (int i = 0; i < seriesArray.Length; i++)
{
chart1.Series.Add(seriesArray[i]);
}
// hook up timer event
timer1.Tick += timer1_Tick;
}
private void timer1_Tick(object sender, EventArgs e)
{
a++;
b++;
// Data array
int[] pointsArray = { a, b };
for (int i = 0; i < seriesArray.Length; i++)
{
// Add point.
chart1.Series[i].Points.Add(pointsArray[i]);
}
}
private void button1_Click(object sender, EventArgs e)
{
timer1.Start();
}