Tutorial - Custom Legend with Color Picker and Custom Point Markers
Posted by Andrew BT on 13 February 2019 06:29 PM
|
|
We were recently asked the following questions by a user of SciChart:
This was quite a lot of questions, we hope they love us and now go tell all their friends about SciChart ;-) The Custom Legend SampleDownload the SampleYou can download the full compilable sample here. Please note that this requires SciChart v5 or later to run. Breaking it downBreaking it down the sample includes the following: Custom Data-point Marker The CustomPointMarker.cs class includes an implementation of BasePointMarker that renders a diamond. This is also documented in our article Adding Data-point Markers. Custom Sprite marker We define a SpritePointMarker ControlTemplate to create the star point-marker. The XML code for the template is below and also found in the sample, MainWindow.xaml <!-- Declare your SpriteTemplate in UserControl.Resources --> <ControlTemplate x:Key="SpriteTemplate"> <!-- You can put literally anything in a SpritePointMarker template. Just note that the UIElement is rendered to bitmap ONCE. You cannot put triggers in here --> <Viewbox Width="24" Height="24" Stretch="Fill"> <Grid> <Polygon Points="100,0 75,75 100,100 125,75" Stroke="Black" StrokeThickness="2" Fill="Yellow"/> <Polygon Points="100,100 125,125 100,200 75,125" Stroke="Yellow" StrokeThickness="2" Fill="Black"/> <Polygon Points="100,100 125,75 200,100 125,125" Stroke="Red" StrokeThickness="2" Fill="Blue"/> <Polygon Points="100,100 75,125 0,100 75,75" Stroke="Blue" StrokeThickness="2" Fill="Red"/> </Grid> </Viewbox> </ControlTemplate> <!-- Later inside SciChartSurface.RenderableSeries collection --> <!-- using SpritePointMarker to create a custom shape --> <s:FastLineRenderableSeries x:Name="series1" Stroke="OrangeRed" StrokeThickness="2"> <s:FastLineRenderableSeries.PointMarker> <s:SpritePointMarker PointMarkerTemplate="{StaticResource SpriteTemplate}"/> </s:FastLineRenderableSeries.PointMarker> </s:FastLineRenderableSeries> Zoom Extents on Visibility Changed To Zoom the chart to extents when the visibility of a series changes, we created an attached behaviour called CheckedChangeZoomExtentsBehaviour.cs /// <summary> /// A behaviour that allows zoom extents on a SciChartSurface when a Visibility Checbox is checked in the Legend /// </summary> public static class CheckedChangeZoomExtentsBehaviour { public static readonly DependencyProperty EnableZoomExtentsOnCheckedProperty = DependencyProperty.RegisterAttached( "EnableZoomExtentsOnChecked", typeof (bool), typeof (CheckedChangeZoomExtentsBehaviour), new PropertyMetadata(default(bool), OnEnableZoomExtentsOnChecked)); public static void SetEnableZoomExtentsOnChecked(DependencyObject element, bool value) { element.SetValue(EnableZoomExtentsOnCheckedProperty, value); } public static bool GetEnableZoomExtentsOnChecked(DependencyObject element) { return (bool) element.GetValue(EnableZoomExtentsOnCheckedProperty); } private static void OnEnableZoomExtentsOnChecked(DependencyObject d, DependencyPropertyChangedEventArgs e) { var checkbox = d as CheckBox; bool enabled = (bool)e.NewValue; checkbox.Checked -= CheckboxChecked; checkbox.Unchecked -= CheckboxChecked; if (enabled) { checkbox.Checked += CheckboxChecked; checkbox.Unchecked += CheckboxChecked; } } private static void CheckboxChecked(object sender, RoutedEventArgs e) { var checkbox = sender as CheckBox; var scs = checkbox.FindVisualParent<SciChartSurface>(); if (scs != null) scs.AnimateZoomExtents(TimeSpan.FromMilliseconds(500)); } private static T FindVisualParent<T>(this DependencyObject dependencyObject) where T : DependencyObject { var parent = VisualTreeHelper.GetParent(dependencyObject); if (parent == null) return null; var parentT = parent as T; return parentT ?? FindVisualParent<T>(parent); } } This is applied by templating the Legend Items and attaching the property to the Visibilty Checkbox. <DataTemplate x:Key="LegendItemTemplate" DataType="s:SeriesInfo"> <Grid> <Grid.ColumnDefinitions> <ColumnDefinition Width="Auto" /> <ColumnDefinition Width="Auto" /> <ColumnDefinition Width="Auto" /> </Grid.ColumnDefinitions> <CheckBox Margin="5,0,0,0" HorizontalAlignment="Left" VerticalAlignment="Center" customPointMarker1:CheckedChangeZoomExtentsBehaviour.EnableZoomExtentsOnChecked="True" Content="Is Visible?" Foreground="{StaticResource LegendTextBrush}" IsChecked="{Binding RenderableSeries.IsVisible, Mode=TwoWay}" Visibility="{Binding LegendData.ShowVisibilityCheckboxes, RelativeSource={RelativeSource AncestorType=s:SciChartLegend}, Converter={StaticResource BooleanToVisibilityConverter}}" /> <!-- Rest of Legend Item Template omitted for brevity --> </Grid> </DataTemplate> For more info on templating legends, you can see our documentation article about Chart Legends.
Showing or Hiding just the Line, or just the Marker on a FastLineRenderableSeries To create a scatter line, you can use a PointMarker with a FastLineRenderableSeries. But what if you wanted to hide just the line, or just the marker? To do this, we created another attached behaviour called SeriesExtensions.cs public class SeriesExtensions { /// <summary> /// When true, draws the line in a FastLineRenderableSeries, else, hides it /// </summary> public static readonly DependencyProperty DrawLineProperty = DependencyProperty.RegisterAttached( "DrawLine", typeof (bool), typeof (SeriesExtensions), new PropertyMetadata(true)); public static void SetDrawLine(DependencyObject element, bool value) { element.SetValue(DrawLineProperty, value); } public static bool GetDrawLine(DependencyObject element) { return (bool) element.GetValue(DrawLineProperty); } /// <summary> /// When True, draws a marker in a FastLineRenderableSeries, else, hides it /// </summary> public static readonly DependencyProperty DrawMarkerProperty = DependencyProperty.RegisterAttached( "DrawMarker", typeof (bool), typeof (SeriesExtensions), new PropertyMetadata(true)); public static void SetDrawMarker(DependencyObject element, bool value) { element.SetValue(DrawMarkerProperty, value); } public static bool GetDrawMarker(DependencyObject element) { return (bool) element.GetValue(DrawMarkerProperty); } } How to apply this? We used a Trigger in an implicit style to set the FastLineRenderableSeries.Stroke = Transparent (to hide the line), or FastLineRenderableSeries.PointMarker = Null (to hide the marker). <!-- This implicit style hides markers and lines based on attached properties in SeriesExtensions.cs --> <Style TargetType="{x:Type s:FastLineRenderableSeries}"> <Style.Triggers> <!-- Trigger to hide the line. Animation is required due to DependencyProperty Precedence. See http://msdn.microsoft.com/en-us/library/ms743230(v=vs.110).aspx --> <!-- Animation duration can be set to 0:0:0 if necessary --> <Trigger Property="customPointMarker1:SeriesExtensions.DrawLine" Value="False"> <Trigger.EnterActions> <BeginStoryboard Name="HideLineStoryboard"> <Storyboard TargetProperty="Stroke"> <ColorAnimation To="Transparent" FillBehavior="HoldEnd" Duration="0:0:0.5" /> </Storyboard> </BeginStoryboard> </Trigger.EnterActions> <Trigger.ExitActions> <StopStoryboard BeginStoryboardName="HideLineStoryboard"></StopStoryboard> </Trigger.ExitActions> </Trigger> <!-- Trigger to hide the marker. Animation is required due to DependencyProperty Precedence. See http://msdn.microsoft.com/en-us/library/ms743230(v=vs.110).aspx --> <!-- Animation duration can be set to 0:0:0 if necessary --> <Trigger Property="customPointMarker1:SeriesExtensions.DrawMarker" Value="False"> <Trigger.EnterActions> <BeginStoryboard Name="HideMarkerStoryboard" > <Storyboard TargetProperty="PointMarker"> <ObjectAnimationUsingKeyFrames FillBehavior="HoldEnd" Duration="0:0:0"> <DiscreteObjectKeyFrame KeyTime="0" Value="{x:Null}"/> </ObjectAnimationUsingKeyFrames> </Storyboard> </BeginStoryboard> </Trigger.EnterActions> <Trigger.ExitActions> <StopStoryboard BeginStoryboardName="HideMarkerStoryboard"></StopStoryboard> </Trigger.ExitActions> </Trigger> </Style.Triggers> </Style> How to Template the Legend Control to add Color Pickers, Stroke Thickness sliders? You can literally do anything with our Legend API. In this example, we've added checkboxes to show/hide the marker, line, change stroke thickness and the line color.
<!-- This is the default LegendItemTemplate from our styles, but we have modified it to allow a notification on checkbox check--> <DataTemplate x:Key="LegendItemTemplate" DataType="s:SeriesInfo"> <!-- DataContext is of type SeriesInfo --> <Grid> <Grid.ColumnDefinitions> <ColumnDefinition Width="Auto" /> <ColumnDefinition Width="Auto" /> <ColumnDefinition Width="Auto" /> <ColumnDefinition Width="*" /> <ColumnDefinition Width="Auto" /> <ColumnDefinition Width="Auto" /> <ColumnDefinition Width="Auto" /> </Grid.ColumnDefinitions> <!-- Visibility checkbox, bound to SeriesInfo.RenderableSeries.IsVisible --> <CheckBox Margin="5,0,0,0" HorizontalAlignment="Left" VerticalAlignment="Center" customPointMarker1:CheckedChangeZoomExtentsBehaviour.EnableZoomExtentsOnChecked="True" Content="Is Visible?" Foreground="{StaticResource LegendTextBrush}" IsChecked="{Binding RenderableSeries.IsVisible, Mode=TwoWay}" Visibility="{Binding LegendData.ShowVisibilityCheckboxes, RelativeSource={RelativeSource AncestorType=s:SciChartLegend}, Converter={StaticResource BooleanToVisibilityConverter}}" /> <!-- PointMarker, bound to SeriesInfo.RenderableSeries.LegendMarkerTemplate --> <s:PointMarker Grid.Column="1" Margin="5,0,0,0" Width="40" Height="10" VerticalAlignment="Center" HorizontalAlignment="Center" DataContext="{Binding RenderableSeries}" DeferredContent="{Binding LegendMarkerTemplate}" Visibility="{Binding ShowSeriesMarkers, RelativeSource={RelativeSource AncestorType=s:SciChartLegend}, Converter={StaticResource BooleanToVisibilityConverter}}" /> <!-- Series Name, bound to SeriesInfo.SeriesName --> <TextBlock Margin="5,0,5,0" Grid.Column="2" HorizontalAlignment="Left" Foreground="{StaticResource LegendTextBrush}" Text="{Binding SeriesName}" /> <!-- Show Line? Bound to SeriesInfo.RenderableSeries SeriesExtensions.DrawLine attached property --> <CheckBox Margin="5,0,0,0" Grid.Column="3" Content="Draw Line?" HorizontalAlignment="Right" VerticalAlignment="Center" Foreground="{StaticResource LegendTextBrush}" DataContext="{Binding RenderableSeries}" IsChecked="{Binding Path=(customPointMarker1:SeriesExtensions.DrawLine), Mode=TwoWay}"/> <!-- Show Line? Bound to SeriesInfo.RenderableSeries SeriesExtensions.DrawMarker attached property --> <CheckBox Margin="15,0,0,0" Grid.Column="4" Content="Draw Marker?" HorizontalAlignment="Right" VerticalAlignment="Center" Foreground="{StaticResource LegendTextBrush}" DataContext="{Binding RenderableSeries}" IsChecked="{Binding Path=(customPointMarker1:SeriesExtensions.DrawMarker), Mode=TwoWay}"/> <!-- Stroke thickness. Bound to SeriesInfo.RenderableSeries.StrokeThickness --> <StackPanel Grid.Column="5" Orientation="Horizontal" Margin="15,3"> <TextBlock Text="Stroke Thickness: " Foreground="{StaticResource LegendTextBrush}"/> <Slider Minimum="1" Maximum="5" MinWidth="50" DataContext="{Binding RenderableSeries}" Value="{Binding Path=StrokeThickness, Mode=TwoWay}"/> </StackPanel> <!-- From Extended Wpf Toolkit NuGet package. Binds ColorPicker to SeriesInfo.RenderableSeries.Stroke --> <StackPanel Grid.Column="6" Orientation="Horizontal" Margin="15,3"> <TextBlock Text="Stroke Thickness: " Foreground="{StaticResource LegendTextBrush}"/> <xctk:ColorPicker DataContext="{Binding RenderableSeries}" ColorMode="ColorCanvas" SelectedColor="{Binding Stroke, Mode=TwoWay}" Width="50"/> </StackPanel> </Grid> </DataTemplate> Further ReadingMore info about Chart Legends in SciChart can be found in our documentation article Legend Modifier. The example that demonstrates usage of Legends API can be found in the SciChart Examples Suite at Legends -> Chart Legends API. Full source code of the example can be found online at this link.
| |
|