SciChart WPF 2D Charts > Tutorials > Code-Behind > Tutorial 09 - Linking Multiple Charts
Tutorial 09 - Linking Multiple Charts

In our series of tutorials, up until now we have added a chart with two YAxis, one XAxis, two series, added tooltips, legends and zooming, panning behavior, and added some annotations.

Source code for this tutorial can be found at our SciChart.WPF.Examples Github Repository under Tutorials section.

Next, we are going to show you how to create multiple charts and link them together.

Revision

If you haven't already you will need to review the following tutorials, as we're working straight from these:

Or, if you're in a hurry, skim through the tutorials and copy paste the code into a new solution. Once you've done that, we are ready to get started.

Your XAML code Before the tutorial starts should look like this:

XAML Code BEFORE tutorial starts
Copy Code
<Window x:Class="SciChart.Tutorial.MainWindow"
         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"
         xmlns:s="http://schemas.abtsoftware.co.uk/scichart"
         mc:Ignorable="d"
         Title="MainWindow" Height="550" Width="800">
    <Grid Background="#222">
        <Grid.RowDefinitions>
            <RowDefinition Height="*"/>
            <RowDefinition Height="Auto"/>
        </Grid.RowDefinitions>
       
        <s:SciChartSurface x:Name="sciChartSurface">
            <s:SciChartSurface.RenderableSeries>
                <s:FastLineRenderableSeries x:Name="LineSeries" Stroke="#FF4083B7"/>
                <s:XyScatterRenderableSeries x:Name="ScatterSeries" YAxisId="Axis2">
                    <s:XyScatterRenderableSeries.PointMarker>
                        <s:EllipsePointMarker Width="7" Height="7" Fill="#FFF" Stroke="SteelBlue"/>
                    </s:XyScatterRenderableSeries.PointMarker>
                </s:XyScatterRenderableSeries>
            </s:SciChartSurface.RenderableSeries>
            <s:SciChartSurface.XAxis>
                <s:NumericAxis AxisTitle="Number of Samples (per Series)"/>
            </s:SciChartSurface.XAxis>
            <s:SciChartSurface.YAxes>
                <s:NumericAxis AxisTitle="Value" GrowBy="0.1,0.1" VisibleRange="-1.1, 1.1"/>
                <s:NumericAxis Id="Axis2"
                    AxisTitle="Secondary" GrowBy="0.1,0.1"
                    VisibleRange="-2.0, 2.0"
                    AxisAlignment="Left"/>
            </s:SciChartSurface.YAxes>
            <s:SciChartSurface.ChartModifier>
                <s:ModifierGroup>
                    <s:RubberBandXyZoomModifier ExecuteOn="MouseLeftButton"
                                                 RubberBandFill="#33FFFFFF" RubberBandStroke="#AAFFFFFF"
                                                 RubberBandStrokeDashArray="2 2"/>
                    <s:ZoomPanModifier ExecuteOn="MouseRightButton" ClipModeX="None" />
                    <s:YAxisDragModifier DragMode="SinWindow.xaml.cs BEFORE the Tutorial Startscale"/>
                    <s:XAxisDragModifier DragMode="Pan"/>
                    <s:MouseWheelZoomModifier/>
                    <s:ZoomExtentsModifier ExecuteOn="MouseDoubleClick" />
                    <s:LegendModifier ShowLegend="True" Orientation="Horizontal"
                                       VerticalAlignment="Bottom"                                     
                                       HorizontalAlignment="Center"
                                       LegendPlacement="Inside" />
                    <s:RolloverModifier ExecuteOn="MouseMove" ShowTooltipOn="MouseHover"/>
                </s:ModifierGroup>
            </s:SciChartSurface.ChartModifier>
            <s:SciChartSurface.Annotations>
                <s:TextAnnotation Text="Hello world!" X1="5.0" Y1="5"/>
            </s:SciChartSurface.Annotations>
        </s:SciChartSurface>
       
        <s:SciChartScrollbar Grid.Row="1" Axis="{Binding ElementName=sciChartSurface, Path=XAxis}"/>
    </Grid>
</Window>
Mainwindow.xaml.cs BEFORE tutorial starts
Copy Code
using System;
using System.Windows;
using System.Windows.Threading;
using SciChart.Charting.Model.DataSeries;
using SciChart.Charting.Visuals.Axes;
using SciChart.Core.Extensions;
using SciChart.Data.Model;
namespace SciChart.Tutorial
{
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
            this.Loaded += OnLoaded;
        }
        private void OnLoaded(object sender, RoutedEventArgs routedEventArgs)
        {
            double windowSize = 1000.0;
            sciChartSurface.ViewportManager = new ScrollingViewportManager(windowSize);
            // Create DataSeries with FifoCapacity
            var scatterData = new XyDataSeries<double, double>() {SeriesName = "Cos(x)",};
            var lineData = new XyDataSeries<double, double>() {SeriesName = "Sin(x)",};
            // Assign DataSeries to RenderableSeries
            LineSeries.DataSeries = lineData;
            ScatterSeries.DataSeries = scatterData;
            int i = 0;
            // Start a timer to update our data
            var timer = new DispatcherTimer(DispatcherPriority.Render);
            timer.Interval = TimeSpan.FromMilliseconds(10);
            timer.Tick += (s, e) =>
            {
                // This time we will append, not update.
                using (lineData.SuspendUpdates())
                using (scatterData.SuspendUpdates())
                {
                    // Append a new data point;
                    lineData.Append(i, Math.Sin(i * 0.1));
                    scatterData.Append(i, Math.Cos(i * 0.1));
                    // Every 100th datapoint, add an annotation
                    if (i % 100 == 0)
                    {
                        sciChartSurface.Annotations.Add(new InfoAnnotation()
                        {
                            X1 = i,
                            Y1 = 0.0,
                            YAxisId = i % 200 == 0 ? AxisBase.DefaultAxisId : "Axis2"
                        });
                        // Optional: Don't forget to remove annotations which are out of range!
                        sciChartSurface.Annotations.RemoveWhere(x => x.X1.ToDouble() < i - 1000);
                    }
                    i++;
                }
            };
            timer.Start();
        }
    }
}

Adding a Second SciChartSurface

There is no restriction on the number of SciChartSurfaces you can have in an application. In this tutorial we are going to add a second SciChartSurface.

Let's start by adding a second SciChartSurface to the XAML:

Adding a second SciChartSurface
Copy Code
<Window x:Class="SciChart.Tutorial.MainWindow"
         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"
         xmlns:s="http://schemas.abtsoftware.co.uk/scichart"
         mc:Ignorable="d"
         Title="MainWindow" Height="550" Width="800">
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="*"/>
            <!-- NEW CODE HERE -->
            <RowDefinition Height="*"/>
            <!-- END NEW CODE -->
            <RowDefinition Height="32"/>
        </Grid.RowDefinitions>
        <s:SciChartSurface x:Name="sciChartSurface">
            <!-- This is the first SciChartSurface you previously added -->
            <!-- Omitted for brevity -->
        </s:SciChartSurface>

        <!-- NEW CODE HERE -->
        <!-- This is the second SciChartSurface we will add -->
        <s:SciChartSurface x:Name="sciChartSurface1" Grid.Row="1">
            <s:SciChartSurface.XAxis>
                <s:NumericAxis AxisTitle="Number of Samples (per Series)"/>
            </s:SciChartSurface.XAxis>
            <s:SciChartSurface.YAxes>
                <s:NumericAxis AxisTitle="Value" GrowBy="0.1,0.1" VisibleRange="-1.1, 1.1"/>
                <s:NumericAxis Id="Axis2" AxisTitle="Secondary" GrowBy="0.1,0.1"
                    VisibleRange="-2.0, 2.0" AxisAlignment="Left"/>
            </s:SciChartSurface.YAxes>
        </s:SciChartSurface>
        <!-- END NEW CODE -->

        <!-- DON'T FORGET TO CHANGE GRID.ROW TO 2 HERE -->
        <s:SciChartScrollbar Grid.Row="2" Axis="{Binding ElementName=sciChartSurface, Path=XAxis}"/>
    </Grid>
</Window>

If you run the application at this point, you will see our first Realtime chart is displayed at the top of the application, and the second chart 'sciChartSurface1' is static, and displayed underneath.

Adding Series to the Second Chart

The second chart needs a series, so we are going to add a Mountain Series. Add the following series to the chart in XAML:

Adding a Series to second chart
Copy Code
<s:SciChartSurface x:Name="sciChartSurface1" Grid.Row="1">
    <s:SciChartSurface.RenderableSeries>
        <s:FastMountainRenderableSeries x:Name="mountainSeries" Stroke="LightSteelBlue" Fill="SteelBlue" Opacity="0.6"
StrokeThickness="2"/>
    </s:SciChartSurface.RenderableSeries>
     ...
</s:SciChartSurface>

Now, in code behind, this series needs some data. We're going to be lazy and assign some of the same data to the mountainSeries (you can do that!).

Code Behind - assign data to series
Copy Code
private void OnLoaded(object sender, RoutedEventArgs routedEventArgs)
{
    double windowSize = 1000.0;
    sciChartSurface.ViewportManager = new ScrollingViewportManager(windowSize);
    // Create DataSeries with FifoCapacity
    var scatterData = new XyDataSeries<double, double>() {SeriesName = "Cos(x)",};
    var lineData = new XyDataSeries<double, double>() {SeriesName = "Sin(x)",};
    // Assign DataSeries to RenderableSeries
    LineSeries.DataSeries = lineData;
    ScatterSeries.DataSeries = scatterData;
    mountainSeries.DataSeries = scatterData; // <-- NEW CODE HERE!

Now run the application. It doesn't look right does it? The top chart is scrolling but the bottom chart is not scrolling.

There is a trick to linking the two charts and it is binding the VisibleRanges of the two XAxis together. We are going to do this below.

In the second chart, add this line to bind XAxis.VisibleRange to the first chart. Make sure it is a TwoWay binding

Binding XAxis VisibleRange
Copy Code
<s:SciChartSurface x:Name="sciChartSurface1" Grid.Row="1">
    ...
    <s:SciChartSurface.XAxis>
        <s:NumericAxis AxisTitle="Number of Samples (per Series)"
                               VisibleRange="{Binding ElementName=sciChartSurface, Path=XAxis.VisibleRange, Mode=TwoWay}"/>
    </s:SciChartSurface.XAxis>
    ...
</s:SciChartSurface>

Now run the example again.

That's better!

Linking Cursor and Mouse Events

The next thing we are going to do is to link mouse and cursor events. The first chart has an array of ChartModifiers set up to handle zooming, panning and tooltips. We are going to add some of these modifiers to the second chart.

Add the following XAML to the second chart to add the zoom, pan and tooltip modifiers.

ChartModifiers in the second chart
Copy Code
<s:SciChartSurface x:Name="sciChartSurface1" Grid.Row="1">
   ...
    <s:SciChartSurface.ChartModifier>
        <s:ModifierGroup>
            <s:RubberBandXyZoomModifier ExecuteOn="MouseLeftButton"
                                            RubberBandFill="#33FFFFFF" RubberBandStroke="#AAFFFFFF"
                                            RubberBandStrokeDashArray="2 2"/>
            <s:ZoomPanModifier ExecuteOn="MouseRightButton" ClipModeX="None" />
            <s:YAxisDragModifier DragMode="Scale"/>
            <s:XAxisDragModifier DragMode="Pan"/>
            <s:MouseWheelZoomModifier/>
            <s:ZoomExtentsModifier ExecuteOn="MouseDoubleClick" />
            <s:RolloverModifier ExecuteOn="MouseMove" ShowTooltipOn="Always"/>
        </s:ModifierGroup>
    </s:SciChartSurface.ChartModifier>
</s:SciChartSurface>

If you run the applicaiton now, you will notice that you have zooming behaviour and tooltips on both charts, but the mouse events still aren't quite linked. You need to do a few more steps to fully link the charts together.

Using MouseManager.MouseEventGroup

We need to use the MouseManager.MouseEventGroup attached property on the ModifierGroups on both charts. Set this to a string value. It needs to be the same string on both charts to link the charts together.

Using MouseEventGroup
Copy Code
<!-- Add a MouseEventGroup to Both charts -->
<s:ModifierGroup s:MouseManager.MouseEventGroup="MyMouseGroup">
</s:ModifierGroup>
Hint! You can use a MouseEventGroup on more than two charts. You can also bind this value to a ViewModel property to make it dynamic.

Run the application again. Move the mouse over the chart. The Cursors and Tooltips are now synchronizing across the charts.

You can also zoom, pan (and remember from Tutorial 06 - Adding Realtime Updates, you need to double click to re-enable scrolling after zooming or panning).

Further Reading