Custom ChartModifiers - Part 1 - Creating a Custom Rollover Modifier
Posted by Andrew BT on 12 July 2014 10:03 PM

The ChartModifier API

If you haven't read it already, please see our article on Custom Chartmodifier API Overview. In this article, we introduce the basics of the ChartModifier API including an overview of this powerful base class. 

The ChartModifier API is by far the most powerful API in the SciChart library. Using this API you can create behaviours which you can attach to a chart to perform custom Zooming, Panning, Annotation & Markers, Legend output and much much more. Any time you want to do something in C# code to alter the behaviour of a SciChartSurface you should be thinking about creating a custom modifier to do it.

Custom ChartModifiers Part 1 - Creating a Simple RolloverModifier

We created a video on YouTube showing how to create your very own custom RolloverModifier using the ChartModifier API! In this video we demonstrate from start to end how to create your own Tooltips on mouse-move in just under 150 lines of code. 

The SimpleRolloverModifier Source

The entire SimpleRolloverModifier class is included below. Let's break the code down below so we can explain how it works:

using System;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
using System.Windows.Shapes;
using Abt.Controls.SciChart.ChartModifiers;

namespace Abt.Controls.SciChart.Wpf.TestSuite.ExampleSandbox.CustomModifiers
{
    /// 
    /// A Simple Implementation of a RolloverModifier, to demonstrate the ChartModifier API for creating cursors in SciChart
    /// 
    /// TODOS: Some things which could be improved / added here include
    /// 
    ///    1. Expose styles for the line, rollover points and allow them to be customized in XAML where you declare your 
    ///    2. Manage UIElements better, do not Clear and Re-add every frame as this is slow. Update existing ones instead. 
    ///    3. Handle OnParentSurfaceRendered to update ellipse positions and hit-test results
    ///    4. Instead of manually creating tooltip UI, you could expose a TooltipTemplate property on the SimpleRolloverModifier
    ///       and add a new ContentControl() { Template = TooltipTemplate } to the ModifierSurface instead
    /// 
    public class SimpleRolloverModifier : ChartModifierBase 
    {
        public static readonly DependencyProperty LineBrushProperty = DependencyProperty.Register("LineBrush", typeof(Brush), typeof(SimpleRolloverModifier), new PropertyMetadata(new SolidColorBrush(Colors.LimeGreen), OnLineBrushChanged));

        public static readonly DependencyProperty TextForegroundProperty = DependencyProperty.Register("TextForeground", typeof (Brush), typeof (SimpleRolloverModifier), new PropertyMetadata(new SolidColorBrush(Colors.White)));

        public Brush TextForeground
        {
            get { return (Brush) GetValue(TextForegroundProperty); }
            set { SetValue(TextForegroundProperty, value); }
        }
        private Line _line;

        public SimpleRolloverModifier()
        {
            CreateLine(this);
        }

        public Brush LineBrush
        {
            get { return (Brush)GetValue(LineBrushProperty); }
            set { SetValue(LineBrushProperty, value); }
        }

        public override void OnParentSurfaceRendered(SciChartRenderedMessage e)
        {
            base.OnParentSurfaceRendered(e);

            // TODO HERE: You could perform a hit-test again and update your ellipse positions on the rendered event
        }

        public override void OnModifierMouseMove(ModifierMouseArgs e)
        {
            base.OnModifierMouseMove(e);
                        
            var allSeries = this.ParentSurface.RenderableSeries;

            // Translates the mouse point to chart area, e.g. when you have left axis
            var pt = GetPointRelativeTo(e.MousePoint, this.ModifierSurface);

            // Position the rollover line
            _line.Y1 = 0;
            _line.Y2 = ModifierSurface.ActualHeight;
            _line.X1 = pt.X;
            _line.X2 = pt.X;
            
            // TODO: Using Clear will affect other modifiers so it is best to remove only those added by this modifier
            this.ModifierSurface.Clear();

            // Add the rollover line to the ModifierSurface, which is just a canvas over the main chart area, on top of series
            this.ModifierSurface.Children.Add(_line);

            // Add the rollover points to the surface
            var hitTestResults = allSeries.Select(x => x.VerticalSliceHitTest(pt)).ToArray();
            foreach (var hitTestResult in hitTestResults)
            {
                const int markerSize = 7;

                // Create one ellipse per HitTestResult and position on the canvas
                var ellipse = new Ellipse()
                    {
                        Width = markerSize,
                        Height = markerSize, 
                        Fill = _line.Stroke,    
                        IsHitTestVisible = false,
                    };

                //ToolTip = 

                Canvas.SetLeft(ellipse, hitTestResult.HitTestPoint.X - markerSize*0.5);
                Canvas.SetTop(ellipse, hitTestResult.HitTestPoint.Y - markerSize*0.5);

                this.ModifierSurface.Children.Add(ellipse);

                // Create one label per HitTestResult and position on the canvas
                // TODO: Could this be templated? Yes it could! 
                var text = new Border()
                    {
                        IsHitTestVisible = false,
                        BorderBrush = TextForeground,
                        BorderThickness = new Thickness(1),
                        Background = LineBrush,
                        CornerRadius = new CornerRadius(2,2,2,2),                        
                        Child = new TextBlock()
                            {                                
                                Text = string.Format("X: {0}, Y: {1}", XAxis.FormatText(hitTestResult.XValue, XAxis.CursorTextFormatting), YAxis.FormatText(hitTestResult.YValue, YAxis.CursorTextFormatting)),
                                FontSize = 11,
                                Margin = new Thickness(3),
                                Foreground = TextForeground, 
                            }
                    };

                Canvas.SetLeft(text, hitTestResult.HitTestPoint.X + 5);
                Canvas.SetTop(text, hitTestResult.HitTestPoint.Y - 5);

                this.ModifierSurface.Children.Add(text);
            }
        }

        public override void OnMasterMouseLeave(ModifierMouseArgs e)
        {
            base.OnMasterMouseLeave(e);

            // Remove the rollover line and markers from the surface

            // TODO: Using Clear will affect other modifiers so it is best to remove only those added by this modifier
            this.ModifierSurface.Clear();
        }

        private static void OnLineBrushChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            var modifier = d as SimpleRolloverModifier;
            CreateLine(modifier);
        }

        private static void CreateLine(SimpleRolloverModifier modifier)
        {
            modifier._line = new Line() {Stroke = modifier.LineBrush, StrokeThickness = 1, IsHitTestVisible = false};
        }
    }
}

1. Inherit ChartModifierBase

First of all, we inherit ChartModifierBase. This is the base-class which provides all the API functions to access the chart and listen to mouse events. 

2. Override OnModifierMouseMove

Next, we override OnModifierMouseMove. This method is fired whenever the mouse moves over the chart. 

3. Translate the Point to ModifierSurface

The first step inside OnModifierMouseMove is to translate the point relative to the ModifierSurface. This is necessary so that we can inspect the Series and place our tooltips at the correct location. 

public override void OnModifierMouseMove(ModifierMouseArgs e)
{
    base.OnModifierMouseMove(e);
                        
    var allSeries = this.ParentSurface.RenderableSeries;

    // Translates the mouse point to chart area, e.g. when you have left axis
    var pt = GetPointRelativeTo(e.MousePoint, this.ModifierSurface);
    // ... 
}

4. Add a VerticalLine onto the ModifierSurface Canvas

// Position the rollover line
_line.Y1 = 0;
_line.Y2 = ModifierSurface.ActualHeight;
_line.X1 = pt.X;
_line.X2 = pt.X;
            
// TODO: Using Clear will affect other modifiers so it is best to remove only those added by this modifier
this.ModifierSurface.Clear();

// Add the rollover line to the ModifierSurface, which is just a canvas over the main chart area, on top of series
this.ModifierSurface.Children.Add(_line);

5. Perform a Hit Test 

Next, we perform a hit-test using the BaseRenderableSeries.HitTest API. This returns a HitTestInfo struct with information about the series at the supplied mouse-point. 

// Add the rollover points to the surface
var hitTestResults = allSeries.Select(x => x.VerticalSliceHitTest(pt)).ToArray();
foreach (var hitTestResult in hitTestResults)
// ... 

6. Place Ellipse Markers and Tooltips on the ModifierSurface

Finally, we iterate over the Hit-Test results to place tooltips on the ModifierSurface:

foreach (var hitTestResult in hitTestResults)
{
const int markerSize = 7;

// Create one ellipse per HitTestResult and position on the canvas
var ellipse = new Ellipse()
    {
        Width = markerSize,
        Height = markerSize, 
        Fill = _line.Stroke,    
        IsHitTestVisible = false,
    };

Canvas.SetLeft(ellipse, hitTestResult.HitTestPoint.X - markerSize*0.5);
Canvas.SetTop(ellipse, hitTestResult.HitTestPoint.Y - markerSize*0.5);

this.ModifierSurface.Children.Add(ellipse);

// Create one label per HitTestResult and position on the canvas
var text = new Border()
    {                           
        Child = new TextBlock()
            {                                
                Text = string.Format("X: {0}, Y: {1}", XAxis.FormatText(hitTestResult.XValue, XAxis.CursorTextFormatting), YAxis.FormatText(hitTestResult.YValue, YAxis.CursorTextFormatting)),
                // ... 
            }
    };

Canvas.SetLeft(text, hitTestResult.HitTestPoint.X + 5);
Canvas.SetTop(text, hitTestResult.HitTestPoint.Y - 5);

this.ModifierSurface.Children.Add(text);
}

 

... There are a few other parts to the SimpleRolloverModifier.cs, such as removing all tooltips when the mouse leaves the chart. In the video we include a few tips and tricks and walk you through how we create and apply the custom modifier to the chart. 

Download the sample

You can download the complete Custom Modifier Sandbox application below:

CustomModifiers Sandbox v1.2 including CM6 Nov 2014



Attachments 
 
(9 vote(s))
Helpful
Not helpful

CONTACT US

Not sure where to start? Contact us, we are happy to help!


CONTACT US

SciChart Ltd, 16 Beaufort Court, Admirals Way, Docklands, London, E14 9XL. Email: Legal Company Number: 07430048, VAT Number: 101957725