Custom Legend with Color Picker and Custom Point Markers

Created by Lex Smith, Modified on Tue, 09 Apr 2024 at 11:09 AM by Lex Smith

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 Sample



Download the Sample


You can download the full compilable sample here. Please note that this requires SciChart v5 or later to run. 


Breaking it down


Breaking 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 Point Marker API documentation article.


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 behavior 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 Visibility 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 behavior 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, and 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, and change stroke thickness and the line color.


  1. To change the line color, we have used the WPF Extended Toolkit Colorpicker Control. This is bound to RenderableSeries.Stroke for each SeriesInfo exposed by the LegendModifier.LegendData.
  2. To change the line stroke thickness, we use a slider bound to RenderableSeries.StrokeThickness for each SeriesInfo exposed by the LegendModifier.LegendData.
  3. To Show or Hide the point marker and line, we use Checkboxes and bind them to SeriesExtensions attached properties, which are used in the above trigger to show or hide the line or marker. 


<!-- 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 Reading


More info about Chart Legends in SciChart can be found in our documentation article Legend Modifier.


The example that demonstrates the usage of Legends API can be found in the SciChart Examples Suite at Legends -> Chart Legends API. The examples' description and source code can also be found online at this link.


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