I would like to create a control that polls for an incoming value and renders a line, pretty much like an oscilloscope: visually the plotted line appears to scroll off to the left with the newest values being plotted on the right. (I hope you know what I mean, it's unexpectedly complicated to put down in words...)
Inspired by Draw a sin function in WPF I now have this (abbreviated):
LineGraphControl.xaml:
<Canvas Margin="0" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" ClipToBounds="True" Background="Gray">
<Polyline Points="{Binding Details.Points, ElementName=self}" Stroke="DarkRed" StrokeThickness="1"/>
</Canvas>
LineGraphControlDetails.cs: The data source / view-model for the control.
public class LineGraphControlDetails
{
public LineGraphControlDetails()
{
_points = new Queue<double>();
}
public double ControlWidth {get; set;}
private void AddValue(double value)
{
while (_points.Count > this.ControlWidth)
{
_points.Dequeue();
}
_points.Enqueue(value);
Points = new PointCollection(_points.Select((v, i) => new Point(i, v)));
}
private ObservableCollection<GraphDataPoint> _points;
public ObservableCollection<GraphDataPoint> Points
{
get => _points;
set { _points = value; OnPropertyChanged(); }
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
public void OnPollingUpdate() //NOTE: This is called every 30ms from a DispatcherTimer.
{
AddValue(this.GetNextValueDummy());
}
}
This works fine, although it does cause a little bit of flickering sometimes. From looking at the code, I thought it appeared as quite an overhead to rebuild the entire PointCollection every time and redraw the complete <PolyLine>. So I tried an approach along these lines: Updating points in PointCollection
In this approach the data is collected in an observable collection, with each instance referencing it's previous data point, so that Line elements can be bound to them. On each iteration, the data is updated (values are "shifted to the left") and the rest is done via binding. This looks better than the first approach, but if the width (i.e. amount of data points) exceeds something like 300, it becomes very, very slow.
So I went back to the code above. And performance-wise this is actually ok (a width of 2000px was no problem). But, the assumed issues mentioned above still somehow annoy me. And, considering that the above is really not more than a proof-of-concept, especially regarding design, i.e. to-be-rendered elements (e.g. line styles, grid lines, labels), I think, there must be another way.
Maybe these questions can help outline what I'm looking for:
- Can we copy the image (or: all but the first pixel) and move it to the left?
- Since the horizontally all points are always 1px apart, would drawing points be better than drawing lines?
- Would overriding
OnRenderand drawing the lines/points "manually" be faster?
Any advice would be greatly appreciated :o)