SciChart® the market leader in Fast WPF Charts, WPF 3D Charts, iOS Chart, Android Chart and JavaScript Chart Components
I am working on an MVVM application that has a dozen or so view models, each having multiple XY data series that are being displayed in a chart. Now I need to add annotations to the charts for some of the view models. I have some code up and running but am running into a few issues and I wanted to see if you have any suggestions for a way forward.
The first issue relates to performance. Each data series (~100K data points) can have several thousand annotations associated with it. In the app, the view models are all created in memory and the user can change between them by selecting one in a list box (see attached image). Once the number of annotations starts to get into the thousands, I start to notice a delay when switching between data models where one of them contains annotations. If I click a different item in the list box, it takes a few seconds before the selection actually changes and the chart is updated. The delay seems to be more pronounced when switching FROM a view model that has annotations TO one that does not contain them. The delay increases with the number of annotations. I’m not sure if there is anything that can be done about this but thought I would check.
The second issue relates to how the view models are created. Because I am loading data for ALL of the view models into memory, I am trying to create them asynchronously in a background thread as much as possible so the UI remains responsive. That was fine when I was just dealing with the XY data, but it is causing problems for the annotations. If I try to create the CustomAnnotation objects in a background thread I get an error “The calling thread must be STA, because many UI components require this.” Do you have any suggestions for how to create lots of annotations while keeping the UI responsive?
Thanks,
Scott
Yet another way to do this is to use our SpritePointmarker, which can render a complex shape (see example here).
This will require two XyScatterRenderableSeries, one for buys one for sells, but you could achieve the up/down arrow effect more easily than tracing it by hand with IRenderContext2D.
Further to my answer above, here is a BasePointMarker derived type that can be modified to draw buy / sell markers – to replace annotations where thousands of markers are required on the chart.
Given a class BuySellMetadata (using our PointMetadata API described here):
public class BuySellMetadata : BindableObject, IPointMetadata
{
private bool _isSelected;
private bool _isBuy;
private bool _isSell;
public bool IsSelected
{
get { return _isSelected; }
set
{
_isSelected = value;
OnPropertyChanged("IsSelected");
}
}
public bool IsBuy
{
get { return _isBuy; }
set
{
_isBuy = value;
OnPropertyChanged("IsBuy");
}
}
public bool IsSell
{
get { return _isSell; }
set
{
_isSell = value;
OnPropertyChanged("IsSell");
}
}
}
You can create a Custom PointMarker which reads the metadata and draws a custom shape.
public class BuySellPointMarker : BasePointMarker
{
private IList<IPointMetadata> _dataPointMetadata;
IList<int> _dataPointIndexes = new List<int>();
IList<Point> _points = new List<Point>();
private IPen2D _penUp;
private IBrush2D _brushUp;
private IPen2D _penDown;
private IBrush2D _brushDown;
public BuySellPointMarker()
{
ColorUp = Colors.Green;
ColorDown = Colors.Red;
}
public Color ColorUp { get; set; }
public Color ColorDown { get; set; }
public Color ColorC { get; set; }
public override void BeginBatch(IRenderContext2D context, Color? strokeColor, Color? fillColor)
{
_dataPointMetadata = _dataPointMetadata ?? RenderableSeries.DataSeries.Metadata;
_dataPointIndexes.Clear();
_points.Clear();
_penUp.SafeDispose();
_penDown.SafeDispose();
_brushUp.SafeDispose();
_brushDown.SafeDispose();
_penUp = context.CreatePen(ColorUp, true, 1.0f);
_penDown = context.CreatePen(ColorDown, true, 1.0f);
_brushUp = context.CreateBrush(ColorUp);
_brushDown = context.CreateBrush(ColorDown);
base.BeginBatch(context, strokeColor, fillColor);
}
public override void MoveTo(IRenderContext2D context, double x, double y, int index)
{
if (IsInBounds(x, y))
{
_dataPointIndexes.Add(index);
// Store all points to disable clustering in base point marker
_points.Add(new Point(x,y));
}
base.MoveTo(context, x, y, index);
}
public override void Draw(IRenderContext2D context, IEnumerable<Point> centers)
{
// IGNORE CLUSTERED CENTRES
//var markerLocations = centers.ToArray();
// Use ours (unclustered) instead
var markerLocations = _points;
var prevValue = 0d;
for (int i = 0; i < markerLocations.Count; ++i)
{
var metadata = _dataPointMetadata[_dataPointIndexes[i]] as BuySellMetadata;
var center = markerLocations[i];
if (metadata != null)
{
if (metadata.IsBuy)
{
DrawAs(context, _penUp, _brushUp, center);
}
else if (metadata.IsSell)
{
DrawAs(context, _penDown, _brushDown, center);
}
}
}
}
private void DrawAs(IRenderContext2D context, IPen2D pen, IBrush2D brush, Point center)
{
// TODO:
// Here I just draw a green ellipse for buy, and red for sell
// But you could draw other shapes using the renderContext
//
context.DrawEllipse(pen, brush, center, 5, 5);
}
public override void Dispose()
{
_penUp.SafeDispose();
_penDown.SafeDispose();
_brushUp.SafeDispose();
_brushDown.SafeDispose();
base.Dispose();
}
}
In this case I just draw a green ellipse for Buy and red ellipse for Sell but the principle is there.
If you do modify it and get it working, please submit as a solution as it will help others!
Best regards,
Andrew
Hi there,
We have a lot of threads on the forums about annotation performance:
Basically …
The Annotations feature uses UIElements. These are very flexible (as they are WPF elements) but are also very slow compared to the bitmap rendering that we employ in SciChart.
So, there’s not a lot you can do about this. I would suggest the best optimisation to adding/creating lots of annotations at once is found in the thread How to improve performance when reloading annotations.
Apart from that, there needs to be other ways to draw lots of markers. Have you consider for instance CustomRenderableSeries or Custom PointMarkers (Scroll down to BasePointMarker)?
Implementing your buy/sells as two XyScatterRenderableSeries with custom point-marker would theoretically allow for hundreds of thousands of markers on the chart at any one time.
Best regards,
Andrew
Please login first to submit.