SciChart® the market leader in Fast WPF Charts, WPF 3D Charts, and now iOS Charting & Android Chart Components

Welcome to the SciChart Community Forums!

Please use the forums below to ask questions about SciChart. Take a moment to read our Question asking guidelines on how to ask a good question and our support policy. We also have a tag=SciChart on Stackoverflow.com where you can earn rep for your questions!

Please note: SciChart team will only answer questions from customers with active support subscriptions. Expired support questions will be ignored. If your support status shows incorrectly, contact us and we will be glad to help.

0
0

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

Images
  • You must to post comments
0
0

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

  • sdamge
    I had already read the threads that you referenced before posting and I am effectively creating all of the annotations at once. The performance is good currently but not great. I understand that it may already be the best is going to get. What I find odd is that it seems sluggish when moving to a non-annotated set of data. Is there some overhead in tear down of the existing annotations?I would like to consider the custom point markers suggestion. The challenge I face is that each annotation has metadata associated with it for direction (up or down), color (red or green) and size (small, medium, large, extra large). I currently have two different custom annotation elements to represent the up and down versions and then am using data binding with value converter classes to set the appropriate color and size of the annotation. Is it possible to do something like that for the custom point markers or would I have to have a different marker defined for each color/size combination? In the examples it appears that the same marker is used for all points.
  • Andrew
    > Is there some overhead in tear down of the existing annotations?Yes. All the annotations must be cleared off the chart. That’s essentially Canvas.Children.Clear. If you try to add 1k or 10k UIElements to a canvas and call clear, you will see what I mean.For custom point marker there is a solution. I will post one
  • You must to post comments
0
0

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

  • sdamge
    I nearly have a solution based on this method working but have an issue I need to see if I can resolve. I am displaying my main data on a CategoryDateTime axis. I am creating my “annotation” data as a separate series that I want to overlay on the chart as an XY scatter series. However, if I create the points using the actual data values (DateTime and double), they don’t line up with their counterparts in the main data. The points are skewed all the way to the left. I’m assuming this is because the index values are starting at 1 for the scatter points instead of the associated index value for the matching date/time. When I was creating annotations, I was looking up the index value for the associated data point and setting the X1 property of the annotation to the index value. I’m assuming something like that is needed here but am not sure what to do. Can you give me some guidance as to how this should be handled?Thanks, Scott
  • Andrew
    OK, this problem is related to an issue (feature?) in CategoryDateTimeAxis. Unfortunately, CategoryDateTimeAxis measures points based on Index to data, not data itself. This is how it collapses gaps in weekends / overnight. The solution is to use DateTimeAxis (which is a value axis), or, ensure that all series have the same number of points. You can insert null points in DataSeries by setting Y=double.NaN. That’s the only solution we have at the moment, but the performance enhancement will be worth the effort.
  • sdamge
    Unfortunately that means this option won’t work for us. We have a scenario where there are multiple annotation points for the same date/time, so the indexing will be too difficult to manage. I appreciate the information though.
  • Andrew
    OK thanks for the feedback. It’s still possible but would require a custom solution. I have also put it in the backlog to investigate building a generic wrapper around the bitmap rendercontext to draw markers, but I can’t guarantee when we will get to it.
  • You must to post comments
0
0

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.

  • You must to post comments
Showing 3 results
Your Answer

Please first to submit.