Knowledgebase: Legends
Tutorial - Custom Legend with Color Picker and Custom Point Markers
Posted by Andrew BT on 17 October 2014 10:43 AM

We were recently asked the following questions by a user of SciChart:

  • Q: How to create custom data point-markers for XyScatterRenderableSeries and FastLineRenderableSeries?
  • Q: How to Zoom a SciChartSurface control to extents when a Legend Visbility Checkbox is checked or unchecked?
  • Q: How can I show or hide just the Line on a FastLineRenderableSeries with PointMarker applied?
  • Q: How can I show or hide just the Point Marker on a FastLineRenderableSeries with PointMarker applied?
  • Q: How can I add a Color Picker to the Legend control to change the SeriesColor dynamically. 

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 v3.1 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 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" SeriesColor="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 Legend API Overview article

 

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.SeriesColor = 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="SeriesColor">
                        <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 strokethickness and the line color.

  1. To change the line color, we have used the WPF Extended Toolkit Colorpicker Control. This is bound to RenderableSeries.SeriesColor 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 pointmarker 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.SeriesColor -->
        <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 SeriesColor, Mode=TwoWay}" Width="50"/>
        </StackPanel>                
                                
    </Grid>
</DataTemplate>

 

 

(6 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