Custom ChartModifiers - Part 1 - Creating a Custom Rollover Modifier

Created by Lex Smith, Modified on Fri, 29 Mar 2024 at 02:01 AM by Lex Smith

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.Linq;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
using System.Windows.Shapes;
using SciChart.Charting.ChartModifiers;
using SciChart.Charting.Utility;
using SciChart.Core.Utility.Mouse;
 
namespace 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), YAxis.FormatText(hitTestResult.YValue)),
                        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.


Get the source code


You can find the complete Custom Modifier Sandbox at our GitHub repository here:


CustomModifiersSandbox



Was this article helpful?

That’s Great!

Thank you for your feedback

Sorry! We couldn't be helpful

Thank you for your feedback

Let us know how can we improve this article!

Select atleast one of the reasons
CAPTCHA verification is required.

Feedback sent

We appreciate your effort and will try to fix the article