xmlns:ig="http://schemas.infragistics.com/xaml" xmlns:local="clr-namespace:Infragistics.Samples.Common"
This topic introduces series inheritance feature of the XamDataChart™ control and explains, with code examples, how to use it to create a custom type of series.
The XamDataChart control is designed to allow application developers to implement custom type of series in their applications. This is accomplished by inheriting from the Series class, implementing required properties, and overriding methods of the base class. The Series is the base class for all XamDataChart series and it provides basic properties and allows interfacing with the chart control. Moreover, custom series can be created by inheriting from existing types of series (e.g. ScatterSeries) and implementing custom features however this is out of the scope of this topic.
Example of custom type of series is a contour area series which renders as a collection of filled contours along data points with the same values plotted in the Cartesian coordinate system. When all elements of the ContourAreaSeries are implemented the series is defined as shown in the following code listing.
In XAML:
xmlns:ig="http://schemas.infragistics.com/xaml" xmlns:local="clr-namespace:Infragistics.Samples.Common"
In XAML:
<ig:XamDataChart.Series> <custom:ContourAreaSeries x:Name="customSeries" ItemsSource="{StaticResource data}" XAxis="{Binding ElementName=xAxis}" YAxis="{Binding ElementName=yAxis}"> </custom:ContourAreaSeries> </ig:XamDataChart.Series>
In Visual Basic:
Imports Infragistics.Samples.Data ' provides ContourData Imports Infragistics.Samples.Common ' provides ContourAreaSeries ... Dim series As New ContourAreaSeries() series.ItemsSource = New ContourDataSample() series.XAxis = xAxis series.YAxis = yAxis ... Me.DataChart.Series.Add(series)
In C#:
using Infragistics.Samples.Data // provides ContourData using Infragistics.Samples.Common; // provides ContourAreaSeries ... ContourAreaSeries series = new ContourAreaSeries(); series.ItemsSource = new ContourDataSample(); series.XAxis = xAxis; series.YAxis = yAxis; ... this.DataChart.Series.Add(series);
Figure 1 – Preview of the ContourAreaSeries rendered as a collection of filled contours along data points with the same values.
This section provides step-by-step instructions for creating the ContourAreaSeries and full code example is provided at the end of topic.
Inheriting Series – This code snippet shows how to create a class for the ContourAreaSeries and inherit from the Series base class.
In Visual Basic:
Imports Infragistics.Controls.Charts ' provides elements of XamDataChart ... Namespace Infragistics.Samples.Common ''' <summary> ''' Represents a custom type of ContourAreaSeries for XamDataChart control. ''' </summary> Public Class ContourAreaSeries Inherits Series Public Sub New() ' creates default style for the series from generic resource dictionary ' which should be added to generic.xaml or merged with resources of the application Me.DefaultStyleKey = GetType(ContourAreaSeries) End Sub End Class End Namespace
In C#:
using Infragistics.Controls.Charts; // provides elements of XamDataChart ... namespace Infragistics.Samples.Common { /// <summary> /// Represents a custom type of ContourAreaSeries for XamDataChart control. /// </summary> public class ContourAreaSeries : Series { public ContourAreaSeries() { // creates default style for the series from generic resource dictionary // which should be added to generic.xaml or merged with resources of the application this.DefaultStyleKey = typeof(ContourAreaSeries); } } }
Creating Default Style – Each series must have a default style defined in application generic.xaml or in a resource dictionary that is merged with application resources. The following code snippet shows how to define default style for the ContourAreaSeries type.
In XAML:
<ResourceDictionary ...> <!-- generic style for the ContourAreaSeries series type --> <Style TargetType="custom:ContourAreaSeries"> <Setter Property="Thickness" Value="4" /> <Setter Property="Template"> <Setter.Value> <ControlTemplate TargetType="custom:ContourAreaSeries"> <Canvas Name="RootCanvas" /> </ControlTemplate> </Setter.Value> </Setter> </Style> </ResourceDictionary>
Integrating Axes - This code snippet shows how to create two dependency properties that are used for binding the custom series to x-axis and y-axis. Also it shows how to attach, using lambda expressions, event handlers that are raised when new values are assigned to these properties.
In Visual Basic:
#Region "Property - XAxis" Public Const XAxisPropertyName As String = "XAxis" Public Shared ReadOnly XAxisProperty As DependencyProperty = DependencyProperty.Register(XAxisPropertyName, GetType(NumericXAxis), GetType(ContourAreaSeries), New PropertyMetadata(Nothing, Function(sender, e) Dim series As ContourAreaSeries = DirectCast(sender, ContourAreaSeries) series.RaisePropertyChanged(XAxisPropertyName, e.OldValue, e.NewValue) End Function)) Public Property XAxis() As NumericXAxis Get Return TryCast(Me.GetValue(XAxisProperty), NumericXAxis) End Get Set Me.SetValue(XAxisProperty, value) End Set End Property #End Region #Region "Property - YAxis" Public Const YAxisPropertyName As String = "YAxis" Public Shared ReadOnly YAxisProperty As DependencyProperty = DependencyProperty.Register(YAxisPropertyName, GetType(NumericYAxis), GetType(ContourAreaSeries), New PropertyMetadata(Nothing, Function(sender, e) Dim series As ContourAreaSeries = DirectCast(sender, ContourAreaSeries) series.RaisePropertyChanged(YAxisPropertyName, e.OldValue, e.NewValue) End Function)) Public Property YAxis() As NumericYAxis Get Return TryCast(Me.GetValue(YAxisProperty), NumericYAxis) End Get Set Me.SetValue(YAxisProperty, value) End Set End Property #End Region
In C#:
#region Property - XAxis public const string XAxisPropertyName = "XAxis"; public static readonly DependencyProperty XAxisProperty = DependencyProperty.Register(XAxisPropertyName, typeof(NumericXAxis), typeof(ContourAreaSeries), new PropertyMetadata(null, (sender, e) => { ContourAreaSeries series = (ContourAreaSeries)sender; series.RaisePropertyChanged(XAxisPropertyName, e.OldValue, e.NewValue); })); public NumericXAxis XAxis { get { return this.GetValue(XAxisProperty) as NumericXAxis; } set { this.SetValue(XAxisProperty, value); } } #endregion #region Property - YAxis public const string YAxisPropertyName = "YAxis"; public static readonly DependencyProperty YAxisProperty = DependencyProperty.Register(YAxisPropertyName, typeof(NumericYAxis), typeof(ContourAreaSeries), new PropertyMetadata(null, (sender, e) => { ContourAreaSeries series = (ContourAreaSeries)sender; series.RaisePropertyChanged(YAxisPropertyName, e.OldValue, e.NewValue); })); public NumericYAxis YAxis { get { return this.GetValue(YAxisProperty) as NumericYAxis; } set { this.SetValue(YAxisProperty, value); } } #endregion
Overriding Series Methods – This code snippet shows series methods that must be overridden in order to render the series when specific properties of the custom series have changed.
In Visual Basic:
''' <summary> ''' Calls rendering of this series any time the Viewport rect has changed ''' </summary> Protected Overrides Sub ViewportRectChangedOverride(oldViewportRect As Rect, newViewportRect As Rect) MyBase.ViewportRectChangedOverride(oldViewportRect, newViewportRect) Me.RenderSeries(False) End Sub ''' <summary> ''' Calls rendering of this series any time the Window rect has changed ''' </summary> Protected Overrides Sub WindowRectChangedOverride(oldWindowRect As Rect, newWindowRect As Rect) MyBase.WindowRectChangedOverride(oldWindowRect, newWindowRect) Me.RenderSeries(False) End Sub ''' <summary> ''' Checks if series should be re-draw any time a property of the series has changed ''' </summary> Protected Overrides Sub PropertyUpdatedOverride(sender As Object, propertyName As String, oldValue As Object, newValue As Object) MyBase.PropertyUpdatedOverride(sender, propertyName, oldValue, newValue) Select Case propertyName ' renders series on changes made to the items source Case ItemsSourcePropertyName Me.RenderSeries(False) If Me.XAxis IsNot Nothing Then Me.XAxis.UpdateRange() End If If Me.YAxis IsNot Nothing Then Me.YAxis.UpdateRange() End If Exit Select ' renders series if a new the x-axis is assigned Cae XAxisPropertyName If oldValue IsNot Nothing Then DirectCast(oldValue, Axis).DeregisterSeries(Me) End If If newValue IsNot Nothing Then DirectCast(newValue, Axis).RegisterSeries(Me) End If If (XAxis IsNot Nothing AndAlso Not XAxis.UpdateRange()) OrElse (newValue Is Nothing AndAlso oldValue IsNot Nothing) Then RenderSeries(False) End If Exit Select ' renders series if a new the y-axis is assigned Case YAxisPropertyName If oldValue IsNot Nothing Then DirectCast(oldValue, Axis).DeregisterSeries(Me) End If If newValue IsNot Nothing Then DirectCast(newValue, Axis).RegisterSeries(Me) End If If (YAxis IsNot Nothing AndAlso Not YAxis.UpdateRange()) OrElse (newValue Is Nothing AndAlso oldValue IsNot Nothing) Then RenderSeries(False) End If Exit Select End Select End Sub ''' <summary> ''' Calculates range of a given axis based on X/Y values of data items ''' </summary> ''' <param name="axis"></param> ''' <returns></returns> Protected Overrides Function GetRange(axis As Axis) As AxisRange Dim myData As ContourData = TryCast(Me.ItemsSource, ContourData) If myData Is Nothing Then Return MyBase.GetRange(axis) End If ' for x-axis range use X values of data points If axis = Me.XAxis Then Dim min As Double = Double.MaxValue Dim max As Double = Double.MinValue For Each dataPoint As ContourDataPoint In myData min = System.Math.Min(min, dataPoint.X) max = System.Math.Max(max, dataPoint.X) Next Return New AxisRange(min, max) ' for y-axis range use Y values of data points ElseIf axis = Me.YAxis Then Dim min As Double = Double.MaxValue Dim max As Double = Double.MinValue For Each dataPoint As ContourDataPoint In myData min = System.Math.Min(min, dataPoint.Y) max = System.Math.Max(max, dataPoint.Y) Next Return New AxisRange(min, max) Else Return MyBase.GetRange(axis) End If End Function
In C#:
/// <summary> /// Calls rendering of this series any time the Viewport rect has changed /// </summary> protected override void ViewportRectChangedOverride(Rect oldViewportRect, Rect newViewportRect) { base.ViewportRectChangedOverride(oldViewportRect, newViewportRect); this.RenderSeries(false); } /// <summary> /// Calls rendering of this series any time the Window rect has changed /// </summary> protected override void WindowRectChangedOverride(Rect oldWindowRect, Rect newWindowRect) { base.WindowRectChangedOverride(oldWindowRect, newWindowRect); this.RenderSeries(false); } /// <summary> /// Checks if series should be re-draw any time a property of the series has changed /// </summary> protected override void PropertyUpdatedOverride(object sender, string propertyName, object oldValue, object newValue) { base.PropertyUpdatedOverride(sender, propertyName, oldValue, newValue); switch (propertyName) { // renders series on changes made to the items source case ItemsSourcePropertyName: this.RenderSeries(false); if (this.XAxis != null) { this.XAxis.UpdateRange(); } if (this.YAxis != null) { this.YAxis.UpdateRange(); } break; // renders series if a new the x-axis is assigned case XAxisPropertyName: if (oldValue != null) { ((Axis)oldValue).DeregisterSeries(this); } if (newValue != null) { ((Axis)newValue).RegisterSeries(this); } if ((XAxis != null && !XAxis.UpdateRange()) || (newValue == null && oldValue != null)) { RenderSeries(false); } break; // renders series if a new the y-axis is assigned case YAxisPropertyName: if (oldValue != null) { ((Axis)oldValue).DeregisterSeries(this); } if (newValue != null) { ((Axis)newValue).RegisterSeries(this); } if ((YAxis != null && !YAxis.UpdateRange()) || (newValue == null && oldValue != null)) { RenderSeries(false); } break; } } /// <summary> /// Calculates range of a given axis based on X/Y values of data items /// </summary> /// <returns></returns> protected override AxisRange GetRange(Axis axis) { ContourData myData = this.ItemsSource as ContourData; if (myData == null) { return base.GetRange(axis); } // for x-axis range use X values of data points if (axis == this.XAxis) { double min = double.MaxValue; double max = double.MinValue; foreach (ContourDataPoint dataPoint in myData) { min = System.Math.Min(min, dataPoint.X); max = System.Math.Max(max, dataPoint.X); } return new AxisRange(min, max); } // for y-axis range use Y values of data points else if (axis == this.YAxis) { double min = double.MaxValue; double max = double.MinValue; foreach (ContourDataPoint dataPoint in myData) { min = System.Math.Min(min, dataPoint.Y); max = System.Math.Max(max, dataPoint.Y); } return new AxisRange(min, max); } else { return base.GetRange(axis); } }
Providing Custom Code – This code snippet shows how to implement custom logic for getting brushes used when rendering different elements of the ContourAreaSeries.
In Visual Basic:
Public Property ActualContourBrushes() As BrushCollection Get Return _actualContourBrushes End Get Private Set _actualContourBrushes = Value End Set End Property Private _actualContourBrushes As BrushCollection Public Property ActualContourOutlines() As BrushCollection Get Return _actualContourOutlines End Get Private Set _actualContourOutlines = Value End Set End Property Private _actualContourOutlines As BrushCollection Public Property ActualContourMarkerOutlines() As BrushCollection Get Return _actualContourMarkerOutlines End Get Private Set _actualContourMarkerOutlines = Value End Set End Property Private _actualContourMarkerOutlines As BrushCollection Public Property ActualContourMarkerBrushes() As BrushCollection Get Return _actualContourMarkerBrushes End Get Private Set _actualContourMarkerBrushes = Value End Set End Property Private _actualContourMarkerBrushes As BrushCollection #Region "Brush Methods" Private Function GetContourPathFill(conturIndex As Integer) As Brush Return GetValidBrush(conturIndex, Me.ActualContourBrushes) End Function Private Function GetContourPathStroke(conturIndex As Integer) As Brush Return GetValidBrush(conturIndex, Me.ActualContourOutlines) End Function Private Function GetContourMarkerOutline(conturIndex As Integer) As Brush Return GetValidBrush(conturIndex, Me.ActualContourMarkerOutlines) End Function Private Function GetContourMarkerFill(conturIndex As Integer) As Brush Return GetValidBrush(conturIndex, Me.ActualContourMarkerBrushes) End Function Private Function GetValidBrush(conturIndex As Integer, brushes As BrushCollection) As Brush If brushes Is Nothing OrElse brushes.Count = 0 Then Return New SolidColorBrush(Colors.Black) End If If conturIndex >= 0 AndAlso conturIndex < brushes.Count Then Return brushes(conturIndex) End If conturIndex = conturIndex Mod brushes.Count Return brushes(conturIndex) End Function #End Region
In C#:
public BrushCollection ActualContourBrushes { get; private set; } public BrushCollection ActualContourOutlines { get; private set; } public BrushCollection ActualContourMarkerOutlines { get; private set; } public BrushCollection ActualContourMarkerBrushes { get; private set; } #region Brush Methods private Brush GetContourPathFill(int conturIndex) { return GetValidBrush(conturIndex, this.ActualContourBrushes); } private Brush GetContourPathStroke(int conturIndex) { return GetValidBrush(conturIndex, this.ActualContourOutlines); } private Brush GetContourMarkerOutline(int conturIndex) { return GetValidBrush(conturIndex, this.ActualContourMarkerOutlines); } private Brush GetContourMarkerFill(int conturIndex) { return GetValidBrush(conturIndex, this.ActualContourMarkerBrushes); } private Brush GetValidBrush(int conturIndex, BrushCollection brushes) { if (brushes == null || brushes.Count == 0) { return new SolidColorBrush(Colors.Black); } if (conturIndex >= 0 && conturIndex < brushes.Count) { return brushes[conturIndex]; } conturIndex = conturIndex % brushes.Count; return brushes[conturIndex]; } #endregion
Rendering Series – The RenderSeriesOverride method determines how a custom series is rendered in the XamDataChart control. The following code snippet shows how to implement this method in order to render the custom series as a collection of filled contours along data points with the same values.
In Visual Basic:
''' <summary> ''' Renders the Custom Contour Area Series using bound data points ''' </summary> Protected Overrides Sub RenderSeriesOverride(animate As Boolean) ' disables series rendering with transitions (Motion Framework) MyBase.RenderSeriesOverride(animate) ' check if the series can be rendered: ' - the Viewport (the bounds rectangle for the series) is not empty, ' - the RootCanvas (the container for the custom graphics) is not null. ' - the Axes are not null. ' - the ItemsSource is not null. If Me.Viewport.IsEmpty OrElse Me.RootCanvas Is Nothing OrElse _ Me.XAxis Is Nothing OrElse Me.YAxis Is Nothing OrElse _ Me.ItemsSource Is Nothing Then Return End If ' clears the RootCanvas on every render of the series Me.RootCanvas.Children.Clear() ' create data structure for contours based on values of items in the source of this series Dim data As ContourData = DirectCast(Me.ItemsSource, ContourData) Dim dataContours As New Dictionary(Of Double, PointCollection)() For Each dataPoint As ContourDataPoint In data ' scale locations (X/Y) of data point to the series' viewport Dim x As Double = Me.XAxis.GetScaledValue(dataPoint.X, Me.SeriesViewer.WindowRect, Me.Viewport) Dim y As Double = Me.YAxis.GetScaledValue(dataPoint.Y, Me.SeriesViewer.WindowRect, Me.Viewport) ' store scaled locations of data point based on the Value property of data points Dim key As Double = dataPoint.Value If dataContours.ContainsKey(key) Then dataContours(key).Add(New Point(x, y)) Else Dim dataPoints As New PointCollection() From { New Point(x, y) } dataContours.Add(key, dataPoints) End If Next ' sort contours data based on contout Dim sortedContours = From item In dataContoursOrder By item.Key Ascendingitem ' re-use chart's brushes and outlines for actual contour's brushes and outlines Me.ActualContourBrushes = DirectCast(Me.SeriesViewer, XamDataChart).Brushes Me.ActualContourOutlines = DirectCast(Me.SeriesViewer, XamDataChart).MarkerOutlines Me.ActualContourMarkerBrushes = DirectCast(Me.SeriesViewer, XamDataChart).MarkerBrushes Me.ActualContourMarkerOutlines = DirectCast(Me.SeriesViewer, XamDataChart).MarkerOutlines ' create elements of contours based on contours data structure Dim conturIndex As Integer = 0 For Each contour As KeyValuePair(Of Double, PointCollection) In sortedContours 'dataContours) For Each point As Point In contour.Value ' get parameters of a contour marker Dim contourMarkerValue As Double = contour.Key Dim contourMarkerSize As Double = 25 Dim contourMarkerLocationLeft As Double = point.X - contourMarkerSize / 2 Dim contourMarkerLocationTop As Double = point.Y - contourMarkerSize / 2 ' create element for shape of a contour marker Dim contourMarker As New Ellipse() contourMarker.Fill = GetContourMarkerFill(conturIndex) contourMarker.Stroke = GetContourMarkerOutline(conturIndex) contourMarker.StrokeThickness = 1.0 contourMarker.Width = contourMarkerSize contourMarker.Height = contourMarkerSize ' create element for value of a contour marker Dim markerValueBlock As New TextBlock() markerValueBlock.Text = contourMarkerValue.ToString() markerValueBlock.Foreground = New SolidColorBrush(Colors.White) markerValueBlock.VerticalAlignment = VerticalAlignment.Center markerValueBlock.HorizontalAlignment = HorizontalAlignment.Center ' create element to hold elements of a contour marker Dim markerGrid As New Grid() markerGrid.Children.Add(contourMarker) markerGrid.Children.Add(markerValueBlock) Canvas.SetLeft(markerGrid, contourMarkerLocationLeft) Canvas.SetTop(markerGrid, contourMarkerLocationTop) Canvas.SetZIndex(markerGrid, conturIndex + 11) ' render the marker of the current contour on the canvas of this series Me.RootCanvas.Children.Add(markerGrid) Next Dim contourPoints As PointCollection = contour.Value ' create curve from points of a contour Dim contourFigure As PathFigure = BezierCurveBuilder.GetBezierSegments(contourPoints, 1.0, True) contourFigure.IsClosed = True ' create a new PathGeometry for a contour Dim contourGeo As New PathGeometry() contourGeo.Figures.Add(contourFigure) ' create a new Path for a contour Dim contourShape As New Path() contourShape.Data = contourGeo contourShape.Stroke = GetContourPathStroke(conturIndex) contourShape.StrokeThickness = Me.Thickness contourShape.Fill = GetContourPathFill(conturIndex) Canvas.SetZIndex(contourShape, conturIndex + 10) ' render shape of the current contour on the canvas of this series Me.RootCanvas.Children.Add(contourShape) conturIndex += 1 Next End Sub
In C#:
/// <summary> /// Renders the Custom Contour Area Series using bound data points /// </summary> protected override void RenderSeriesOverride(bool animate) { // disables series rendering with transitions (Motion Framework) base.RenderSeriesOverride(animate); // check if the series can be rendered: // - the Viewport (the bounds rectangle for the series) is not empty, // - the RootCanvas (the container for the custom graphics) is not null. // - the Axes are not null. // - the ItemsSource is not null. if (this.Viewport.IsEmpty || this.RootCanvas == null || this.XAxis == null || this.YAxis == null || this.ItemsSource == null) { return; } // clears the RootCanvas on every render of the series this.RootCanvas.Children.Clear(); // create data structure for contours based on values of items in the source of this series ContourData data = (ContourData)this.ItemsSource; Dictionary<double, PointCollection> dataContours = new Dictionary<double, PointCollection>(); foreach (ContourDataPoint dataPoint in data) { // scale locations (X/Y) of data point to the series' viewport double x = this.XAxis.GetScaledValue(dataPoint.X, this.SeriesViewer.WindowRect, this.Viewport); double y = this.YAxis.GetScaledValue(dataPoint.Y, this.SeriesViewer.WindowRect, this.Viewport); // store scaled locations of data point based on the Value property of data points double key = dataPoint.Value; if (dataContours.ContainsKey(key)) { dataContours[key].Add(new Point(x, y)); } else { PointCollection dataPoints = new PointCollection { new Point(x, y) }; dataContours.Add(key, dataPoints); } } // sort contours data based on contout var sortedContours = from item in dataContours orderby item.Key ascending select item; //// re-use chart's brushes and outlines for actual contour's brushes and outlines this.ActualContourBrushes = ((XamDataChart)this.SeriesViewer).Brushes; this.ActualContourOutlines = ((XamDataChart)this.SeriesViewer).MarkerOutlines; this.ActualContourMarkerBrushes = ((XamDataChart)this.SeriesViewer).MarkerBrushes; this.ActualContourMarkerOutlines = ((XamDataChart)this.SeriesViewer).MarkerOutlines; // create elements of contours based on contours data structure int conturIndex = 0; foreach (KeyValuePair<double, PointCollection> contour in sortedContours) //dataContours) { foreach (Point point in contour.Value) { // get parameters of a contour marker double contourMarkerValue = contour.Key; double contourMarkerSize = 25; double contourMarkerLocationLeft = point.X - contourMarkerSize / 2; double contourMarkerLocationTop = point.Y - contourMarkerSize / 2; // create element for shape of a contour marker Ellipse contourMarker = new Ellipse(); contourMarker.Fill = GetContourMarkerFill(conturIndex); contourMarker.Stroke = GetContourMarkerOutline(conturIndex); contourMarker.StrokeThickness = 1.0; contourMarker.Width = contourMarkerSize; contourMarker.Height = contourMarkerSize; // create element for value of a contour marker TextBlock markerValueBlock = new TextBlock(); markerValueBlock.Text = contourMarkerValue.ToString(); markerValueBlock.Foreground = new SolidColorBrush(Colors.White); markerValueBlock.VerticalAlignment = VerticalAlignment.Center; markerValueBlock.HorizontalAlignment = HorizontalAlignment.Center; // create element to hold elements of a contour marker Grid markerGrid = new Grid(); markerGrid.Children.Add(contourMarker); markerGrid.Children.Add(markerValueBlock); Canvas.SetLeft(markerGrid, contourMarkerLocationLeft); Canvas.SetTop(markerGrid, contourMarkerLocationTop); Canvas.SetZIndex(markerGrid, conturIndex + 11); // render the marker of the current contour on the canvas of this series this.RootCanvas.Children.Add(markerGrid); } PointCollection contourPoints = contour.Value; // create curve from points of a contour PathFigure contourFigure = BezierCurveBuilder.GetBezierSegments(contourPoints, 1.0, true); contourFigure.IsClosed = true; // create a new PathGeometry for a contour PathGeometry contourGeo = new PathGeometry(); contourGeo.Figures.Add(contourFigure); // create a new Path for a contour Path contourShape = new Path(); contourShape.Data = contourGeo; contourShape.Stroke = GetContourPathStroke(conturIndex); contourShape.StrokeThickness = this.Thickness; contourShape.Fill = GetContourPathFill(conturIndex); Canvas.SetZIndex(contourShape, conturIndex + 10); // render shape of the current contour on the canvas of this series this.RootCanvas.Children.Add(contourShape); conturIndex++; } }
At this point the custom series has all elements implemented and it is ready to be added to the xamChart control. The following code snippet shows how to define and show data using the ContourAreaSeries.
In XAML:
<UserControl x:Class="Infragistics.Samples.CustomSeriesExample" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" mc:Ignorable="d" ...> <UserControl.Resources> <ResourceDictionary> <ResourceDictionary.MergedDictionaries> <!-- loads default style for the ContourAreaSeries series from resource dictionary --> <ResourceDictionary Source="/ContourAreaSeries.xaml" /> </ResourceDictionary.MergedDictionaries> </ResourceDictionary> </UserControl.Resources> <Grid x:Name="LayoutRoot"> <Grid.Resources> <models:ContourDataSample x:Key="data" /> </Grid.Resources> <ig:XamDataChart x:Name="DataChart" Margin="0" HorizontalZoomable="True" HorizontalZoombarVisibility="Visible" VerticalZoomable="True" VerticalZoombarVisibility="Visible"> <ig:XamDataChart.Axes> <ig:NumericXAxis Name="xAxis" MinimumValue="0" MaximumValue="150" Interval="10"/> <ig:NumericYAxis Name="yAxis" MinimumValue="0" MaximumValue="120" Interval="10" /> </ig:XamDataChart.Axes> <!-- ========================================================================== --> <ig:XamDataChart.Series> <custom:ContourAreaSeries x:Name="customSeries" ItemsSource="{StaticResource data}" XAxis="{Binding ElementName=xAxis}" YAxis="{Binding ElementName=yAxis}"> </custom:ContourAreaSeries> </ig:XamDataChart.Series> <!-- ========================================================================== --> </ig:XamDataChart> </Grid> </UserControl>
Figure 2 – Preview of ContourAreaSeries with contours rendered along data points with the same values.
Integrating Legend - By default, custom series does not appear in the legend because the LegendItemTemplate is null by default. However, this can be easily changed by setting a data template to the LegendItemTemplate property of the series as it is shown in the following code snippet.
In XAML:
... <ig:XamDataChart.Series> <custom:ContourAreaSeries Title="ContourAreaSeries"> <custom:ContourAreaSeries.LegendItemTemplate> <DataTemplate > <StackPanel Orientation="Horizontal" Margin="1" Visibility="{Binding Series.Visibility}"> <ContentPresenter Content="{Binding}" ContentTemplate="{Binding Series.LegendItemBadgeTemplate}" /> <ContentPresenter Content="{Binding Series.Title, TargetNullValue=Series Title}" /> </StackPanel> </DataTemplate> </custom:ContourAreaSeries.LegendItemTemplate> <custom:ContourAreaSeries.LegendItemBadgeTemplate> <DataTemplate > <Grid Width="19" Height="14" Margin="0,0,5,0"> <Grid Width="14" Height="14"> <Ellipse Width="7" Height="7" Margin="0" Fill="{Binding Series.ActualBrush}" Stroke="{Binding Series.ActualOutline}" StrokeThickness="0.75" HorizontalAlignment="Center" VerticalAlignment="Top" /> <Ellipse Width="7" Height="7" Margin="0" Fill="{Binding Series.ActualBrush}" Stroke="{Binding Series.ActualOutline}" StrokeThickness="0.75" HorizontalAlignment="Left" VerticalAlignment="Bottom" /> <Ellipse Width="7" Height="7" Margin="0" Fill="{Binding Series.ActualBrush}" Stroke="{Binding Series.ActualOutline}" StrokeThickness="0.75" HorizontalAlignment="Right" VerticalAlignment="Bottom" /> <Ellipse Width="7" Height="7" Margin="0" Fill="{Binding Series.ActualBrush}" Stroke="{Binding Series.ActualOutline}" StrokeThickness="0.75" HorizontalAlignment="Center" VerticalAlignment="Center" /> </Grid> </Grid> </DataTemplate> </custom:ContourAreaSeries.LegendItemBadgeTemplate> </custom:ContourAreaSeries> </ig:XamDataChart.Series>
Figure 3 – Preview of ContourAreaSeries with a legend showing the series as a legend item.
Integrating Tooltips – This code snippet shows how to provide support for displaying tooltips on series’ markers by overriding the GetItem method and implementing logic for getting a data point corresponding to a marker on which the cursor is hover
In Visual Basic:
''' <summary> ''' Gets the item associated with the specified world position ''' when a tooltip must be displayed on the series' marker ''' </summary> Protected Overrides Function GetItem(worldPoint As Point) As Object Dim cursorPoint As Point = New Point(((worldPoint.X - Me.SeriesViewer.ActualWindowRect.Left) _ * (Me.Viewport.Width / Me.SeriesViewer.ActualWindowRect.Width)), ((worldPoint.Y - Me.SeriesViewer.ActualWindowRect.Top) _ * (Me.Viewport.Height / Me.SeriesViewer.ActualWindowRect.Height))) Dim data As ContourData = DirectCast(Me.ItemsSource, ContourData) For Each dataPoint As ContourDataPoint In data ' scale locations of data point to the series' viewport Dim x As Double = Me.XAxis.GetScaledValue(dataPoint.X, Me.SeriesViewer.ActualWindowRect, Me.Viewport) Dim y As Double = Me.YAxis.GetScaledValue(dataPoint.Y, Me.SeriesViewer.ActualWindowRect, Me.Viewport) Dim size As Double = 25 Dim left As Double = x - size / 2 Dim top As Double = y - size / 2 Dim itemBounds As New Rect(left, top, size, size) If itemBounds.Contains(cursorPoint) Then Return dataPoint End If Next Return Nothing End Function
In C#:
/// <summary> /// Gets the item associated with the specified world position /// when a tooltip must be displayed on the series' marker /// </summary> protected override object GetItem(Point worldPoint) { Point cursorPoint = new Point( (worldPoint.X - this.SeriesViewer.ActualWindowRect.Left) * this.Viewport.Width / this.SeriesViewer.ActualWindowRect.Width, (worldPoint.Y - this.SeriesViewer.ActualWindowRect.Top) * this.Viewport.Height / this.SeriesViewer.ActualWindowRect.Height); ContourData data = (ContourData)this.ItemsSource; foreach (ContourDataPoint dataPoint in data) { // scale locations of data point to the series' viewport double x = this.XAxis.GetScaledValue(dataPoint.X, this.SeriesViewer.ActualWindowRect, this.Viewport); double y = this.YAxis.GetScaledValue(dataPoint.Y, this.SeriesViewer.ActualWindowRect, this.Viewport); double size = 25; double left = x - size / 2; double top = y - size / 2; Rect itemBounds = new Rect(left, top, size, size); if (itemBounds.Contains(cursorPoint)) { return dataPoint; } } return null; }
The "Item" in the DataContext of the Tooltip will be the returns value of the GetItem method. The following code snippet shows how to define a tooltip containing series title and a value of the data point’s Value property.
In XAML:
... <ig:XamDataChart.Series> <custom:ContourAreaSeries Title="ContourAreaSeries" ToolTip="{}{Series.Title}: {Item.Value}"> </custom:ContourAreaSeries> </ig:XamDataChart.Series>
Figure 4 – Preview of ContourAreaSeries with a tooltip showing value of a data point.