Axes on different SciChartSurface controls can be synchronized in MVVM by assigning the same RangeSyncGroupId string to each axis that should move together. When any axis in a group changes its VisibleRange (via zoom, pan, or programmatic update), the new range is automatically propagated to every other axis in the same group.
When synced axes use different units, assign an IRangeSyncTransform to convert between the axis’s own range and the canonical group range. Axes without a transform are treated as identity — their range IS the group range.
The example below shows two charts stacked vertically. Both display the same temperature data over time. The X axes (time) are synced directly without a transform. The Y axes are synced with a Celsius to Fahrenheit transform, so zooming or panning the Y range on one chart updates the other in the correct unit.
View (XAML)
| Synchronizing Axes in MVVM — View |
Copy Code |
|---|---|
<Window x:Class="SyncAxesMvvm.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="http://schemas.abtsoftware.co.uk/scichart" xmlns:local="clr-namespace:SyncAxesMvvm" Title="Synced Axes Example" Height="600" Width="800"> <Window.DataContext> <local:MainViewModel/> </Window.DataContext> <Grid> <Grid.RowDefinitions> <RowDefinition Height="*"/> <RowDefinition Height="*"/> </Grid.RowDefinitions> <!-- Chart 1: Temperature in Celsius --> <s:SciChartSurface Grid.Row="0" RenderableSeries="{s:SeriesBinding CelsiusChart.RenderableSeries}" XAxes="{s:AxesBinding CelsiusChart.XAxes}" YAxes="{s:AxesBinding CelsiusChart.YAxes}"> <s:SciChartSurface.ChartModifier> <s:ModifierGroup> <s:RubberBandXyZoomModifier/> <s:ZoomPanModifier ExecuteOn="MouseRightButton"/> <s:ZoomExtentsModifier/> <s:MouseWheelZoomModifier/> </s:ModifierGroup> </s:SciChartSurface.ChartModifier> </s:SciChartSurface> <!-- Chart 2: Temperature in Fahrenheit --> <s:SciChartSurface Grid.Row="1" RenderableSeries="{s:SeriesBinding FahrenheitChart.RenderableSeries}" XAxes="{s:AxesBinding FahrenheitChart.XAxes}" YAxes="{s:AxesBinding FahrenheitChart.YAxes}"> <s:SciChartSurface.ChartModifier> <s:ModifierGroup> <s:RubberBandXyZoomModifier/> <s:ZoomPanModifier ExecuteOn="MouseRightButton"/> <s:ZoomExtentsModifier/> <s:MouseWheelZoomModifier/> </s:ModifierGroup> </s:SciChartSurface.ChartModifier> </s:SciChartSurface> </Grid> </Window> | |
ViewModel
| Synchronizing Axes in MVVM — ViewModel |
Copy Code |
|---|---|
public class MainViewModel : INotifyPropertyChanged { public ChartViewModel CelsiusChart { get; } public ChartViewModel FahrenheitChart { get; } public MainViewModel() { CelsiusChart = BuildCelsiusChart(); FahrenheitChart = BuildFahrenheitChart(); } private ChartViewModel BuildCelsiusChart() { var chart = new ChartViewModel(); // X axis: synced with Fahrenheit chart, no transform needed (same time unit) chart.XAxes.Add(new NumericAxisViewModel { AxisTitle = "Time (s)", RangeSyncGroupId = "TimeGroup" }); // Y axis: synced with Fahrenheit chart; Celsius is the canonical unit // (no transform = identity), so this axis defines the group range. chart.YAxes.Add(new NumericAxisViewModel { AxisTitle = "Temperature (°C)", RangeSyncGroupId = "TempGroup" }); // Add sample data var ds = new XyDataSeries<double, double> { SeriesName = "Sensor A (°C)" }; for (int i = 0; i < 100; i++) ds.Append(i * 0.1, 20 + 10 * Math.Sin(i * 0.1)); chart.RenderableSeries.Add(new LineRenderableSeriesViewModel { DataSeries = ds, Stroke = Color.FromRgb(0x50, 0xC7, 0xE0) }); return chart; } private ChartViewModel BuildFahrenheitChart() { var chart = new ChartViewModel(); // X axis: same group, no transform chart.XAxes.Add(new NumericAxisViewModel { AxisTitle = "Time (s)", RangeSyncGroupId = "TimeGroup" }); // Y axis: same group, but with a transform. // This axis displays Fahrenheit; the group range is Celsius. // The transform converts between this axis's range and the group. chart.YAxes.Add(new NumericAxisViewModel { AxisTitle = "Temperature (°F)", RangeSyncGroupId = "TempGroup", RangeSyncTransform = new CelsiusFahrenheitTransform() }); // Same data converted to Fahrenheit var ds = new XyDataSeries<double, double> { SeriesName = "Sensor A (°F)" }; for (int i = 0; i < 100; i++) ds.Append(i * 0.1, (20 + 10 * Math.Sin(i * 0.1)) * 9.0 / 5.0 + 32); chart.RenderableSeries.Add(new LineRenderableSeriesViewModel { DataSeries = ds, Stroke = Color.FromRgb(0xF4, 0x84, 0x20) }); return chart; } public event PropertyChangedEventHandler PropertyChanged; } | |
Transform
The IRangeSyncTransform implementation converts between Fahrenheit (this axis) and Celsius (the canonical group range). ToGroupRange converts F→C; FromGroupRange converts C→F.
| CelsiusFahrenheitTransform |
Copy Code |
|---|---|
public class CelsiusFahrenheitTransform : IRangeSyncTransform { // This axis is in Fahrenheit; the group range is in Celsius. /// Convert Fahrenheit (axis) to Celsius (group). public IRange ToGroupRange(IRange axisRange) { if (axisRange is DoubleRange r) return new DoubleRange( (r.Min - 32) * 5.0 / 9.0, (r.Max - 32) * 5.0 / 9.0); return axisRange; } /// Convert Celsius (group) to Fahrenheit (axis). public IRange FromGroupRange(IRange groupRange) { if (groupRange is DoubleRange r) return new DoubleRange( r.Min * 9.0 / 5.0 + 32, r.Max * 9.0 / 5.0 + 32); return groupRange; } } | |
How It Works
- The X axes of both charts share RangeSyncGroupId = "TimeGroup" with no transform. Panning or zooming the time axis on either chart immediately updates the other to the exact same range.
- The Y axes share RangeSyncGroupId = "TempGroup". The Celsius chart has no transform (its range IS the group range). The Fahrenheit chart has a CelsiusFahrenheitTransform. When the user zooms the Celsius Y axis to [10, 30], the Fahrenheit axis automatically computes [50, 86] via FromGroupRange. Conversely, zooming the Fahrenheit axis to [50, 86] sends [10, 30] to the group via ToGroupRange, and the Celsius axis picks it up unchanged.