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:
Was this article helpful?
That’s Great!
Thank you for your feedback
Sorry! We couldn't be helpful
Thank you for your feedback
Feedback sent
We appreciate your effort and will try to fix the article