如何使用 ChartTrackBallBehavior 解决 "Value cannot be null" 异常
How to resolve "Value cannot be null" Exception with ChartTrackBallBehavior
我一直致力于在 UWP 中构建一个程序,该程序利用 Telerik 图表控件向用户显示传入数据。在我最近的测试中,在图表上动态接收和绘制未经请求的数据时,我遇到了未处理的异常 (NullReferenceException)。这似乎仅在用户将鼠标悬停在图表上且图表显示 TrackBall 信息(Telerik 的 ChartTrackBallBehavior)时才会发生。如果用户的鼠标在别处或没有触发轨迹球信息显示在图表上,则永远不会出现此异常。
到目前为止,我已经设法追踪到 NullException 发生在 Telerik 的 UI 中的 ChartTrackBallBehavior.cs 函数 GetIntersectionTemplate() 中。除此之外,我不知道我能做些什么来解决这个问题,让它再也不会发生。
这是异常发生时的典型堆栈跟踪:
System.ArgumentNullException: Value cannot be null.
at Telerik.UI.Xaml.Controls.Chart.ChartTrackBallBehavior.GetIntersectionTemplate(DependencyObject instance)
at Telerik.UI.Xaml.Controls.Chart.ChartTrackBallBehavior.UpdateIntersectionPoints(ChartDataContext context)
at Telerik.UI.Xaml.Controls.Chart.ChartTrackBallBehavior.UpdateVisuals()
at Telerik.UI.Xaml.Controls.Chart.RadChartBase.NotifyUIUpdated()
at Telerik.UI.Xaml.Controls.Chart.PresenterBase.UpdateUI(ChartLayoutContext context)
我曾尝试在将每个系列添加到图表之前和之后禁用 ChartTrackBallBehavior,但无济于事。
我曾尝试手动将焦点更改为图表以外的其他控件,但无济于事。
我曾尝试在不同区域手动调用 Chart.UpdateLayout(),结果这些调用在同一位置 (ChartTrackBallBehavior.cs).
中创建了相同的 NullReferenceException
问题的核心似乎是这个 "Value" 被错误地设置为 null。到目前为止,我还不能确定什么 "Value" 被设置为 null,我只能假设它在 GetIntersectionTemplate() 函数调用中触发了 NullReferenceException throw() 调用。但我不知道为什么会这样,也不知道我能做些什么。
我做了一个最小的项目来重现这个问题。请注意,在重新绘制图表上的所有系列之前,它会清除图表中的所有系列。这样做是为了等同于我自己的项目,似乎与问题本身有关。此系列清除和重新添加程序已完成,因为用户可以随时更改要在图表上显示的系列。
我可能可以更改编码结构以从不同的方向解决这个问题,但目前我想更好地了解导致此问题的原因并尽可能解决它,否则我可能会需要重写大部分代码,不幸的是时间不在我这边。
这是示例代码。请注意,我为此代码使用的是 Telerik.UI.for.UniversalWindowsPlatform 版本 1.0.1.5。
MainPage.xaml
<Page
x:Class="ExceptionReplicator.MainPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:ExceptionReplicator"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:telerikChart="using:Telerik.UI.Xaml.Controls.Chart"
xmlns:telerikPrimitives="using:Telerik.UI.Xaml.Controls.Primitives"
mc:Ignorable="d"
Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
<Grid>
<telerikChart:RadCartesianChart x:Name="MainChart" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" Margin="10,10,10,10">
<telerikChart:RadCartesianChart.Grid>
<telerikChart:CartesianChartGrid MajorLinesVisibility="XY"/>
</telerikChart:RadCartesianChart.Grid>
<telerikChart:RadCartesianChart.Behaviors>
<telerikChart:ChartPanAndZoomBehavior ZoomMode="Both" PanMode="Both"/>
<telerikChart:ChartTrackBallBehavior x:Name="TrackBallBehaviour" InfoMode="Multiple" ShowIntersectionPoints="True">
<telerikChart:ChartTrackBallBehavior.LineStyle>
<Style TargetType="Polyline">
<Setter Property="Stroke" Value="Tomato"/>
<Setter Property="StrokeThickness" Value="2"/>
<Setter Property="StrokeDashArray" Value="1,2"/>
</Style>
</telerikChart:ChartTrackBallBehavior.LineStyle>
<telerikChart:ChartTrackBallBehavior.IntersectionTemplate>
<DataTemplate>
<Ellipse Width="10" Height="10" Fill="Tomato"/>
</DataTemplate>
</telerikChart:ChartTrackBallBehavior.IntersectionTemplate>
</telerikChart:ChartTrackBallBehavior>
</telerikChart:RadCartesianChart.Behaviors>
<telerikChart:RadCartesianChart.VerticalAxis>
<telerikChart:LinearAxis x:Name="Vertical" Title="Y Axis" Minimum="0"/>
</telerikChart:RadCartesianChart.VerticalAxis>
<telerikChart:RadCartesianChart.HorizontalAxis>
<telerikChart:LinearAxis x:Name="Horizontal" Title="X Axis"/>
</telerikChart:RadCartesianChart.HorizontalAxis>
</telerikChart:RadCartesianChart>
</Grid>
</Page>
MainPage.xaml.cs
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices.WindowsRuntime;
using Windows.Foundation;
using Windows.Foundation.Collections;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
using Windows.UI.Xaml.Controls.Primitives;
using Windows.UI.Xaml.Data;
using Windows.UI.Xaml.Input;
using Windows.UI.Xaml.Media;
using Windows.UI.Xaml.Navigation;
// The Blank Page item template is documented at https://go.microsoft.com/fwlink/?LinkId=402352&clcid=0x409
namespace ExceptionReplicator
{
using System;
using System.Threading;
using Telerik.UI.Xaml.Controls.Chart;
using Windows.UI.Core;
/// <summary>
/// An empty page that can be used on its own or navigated to within a Frame.
/// </summary>
public sealed partial class MainPage : Page
{
private Timer DataTimer; // timer to periodically add data to the chart
private int LineCount = 1; // arbitrary line counter to differentiate lines from each other
// custom class for holding the data to be displayed on the chart
private class Data
{
public int XValue { get; set; }
public int YValue { get; set; }
}
// overarching class that holds all of the data for ONE line/series
private class DataToChart
{
// List of all the data points within this instance of DataToChart
public List<Data> DataPoints;
// Constructor to initialise DataPoints
public DataToChart()
{
DataPoints = new List<Data>();
}
}
// Overarching container to hold data for ALL lines/series
private List<DataToChart> allData = new List<DataToChart>();
public MainPage()
{
this.InitializeComponent();
// set up the timer to call every 10s to add new data to the chart. warning: this will run infinitely
DataTimer = new Timer(DataCallback, null, (int)TimeSpan.FromSeconds(10).TotalMilliseconds, Timeout.Infinite);
}
// Generic callback to call AddLineToChart() on the other thread to handle the Chart's data
private void DataCallback(object state)
{
var task = Dispatcher.RunAsync(CoreDispatcherPriority.Normal, () => AddLineToChart());
}
// Code to handle adding a line to the chart
private void AddLineToChart()
{
// Using Random() to create random data
Random rand = new Random();
DataToChart dataToChart = new DataToChart();
for (int i = 0; i < 50; i++)
{
dataToChart.DataPoints.Add(new Data
{
XValue = i,
YValue = rand.Next(0, 100)
});
}
// Add the data for this line/series to the overarching container
allData.Add(dataToChart);
// re-initialise the line count
LineCount = 1;
// Currently the code needs to clear the chart and redraw it each time new data is introduced
MainChart.Series.Clear();
// For each line/series in the main container
foreach (DataToChart data in allData)
{
// Make a series for the line
ScatterLineSeries scatterLineSeries = new ScatterLineSeries
{
Name = $"Line {LineCount}",
ItemsSource = dataToChart.DataPoints,
XValueBinding = new PropertyNameDataPointBinding("XValue"),
YValueBinding = new PropertyNameDataPointBinding("YValue"),
DisplayName = $"Line {LineCount}",
LegendTitle = $"Line {LineCount}",
};
// Add the line to the Chart's Series collection
MainChart.Series.Add(scatterLineSeries);
// Increment arbitrary counter
LineCount++;
}
// Re-set the timer to fire again in 10s
DataTimer.Change((int)TimeSpan.FromSeconds(10).TotalMilliseconds, Timeout.Infinite);
}
}
}
我需要找到一种解决方案来确保在引入新数据时不再出现此异常。任何帮助将不胜感激。
在短期内,我已经从我的图表中完全删除了 ChartTrackBallBehavior(将其注释掉),直到我确定解决方案。删除行为后,不会发生此异常。
您的代码中存在一些问题。
- 您是否发现您的
IntersectionTemplate
根本没有应用到您的 'ScatterLineSeries'?我检查了 Telerik 文档 TrackBall Behavior。他们将 IntersectionTemplate
放在 telerikChart:LineSeries
中,而不是 telerikChart:RadCartesianChart.Behaviors
。因此,您需要将 IntersectionTemplate
应用于代码隐藏中的 ScatterLineSeries。
- 您调用
MainChart.Series.Clear();
清除图表并在每次有新数据时重新绘制图表。当数据量很大时会导致性能问题。我建议您只需将新的 ScatterLineSeries 添加到图表中并保留旧数据。
- 您声明了
List<DataToChart> allData
变量。我建议你使用 ObservableCollection Class. This class has implemented the INotifyPropertyChanged 接口,当集合中添加新数据时,它会通知 UI.
结合以上三点,我做了一个代码示例供大家参考:
<Page.Resources>
<DataTemplate x:Key="ChartTrackIntersectionTemplate">
<Ellipse Width="10" Height="10" Fill="Tomato" />
</DataTemplate>
</Page.Resources>
<Grid>
<telerikChart:RadCartesianChart x:Name="MainChart" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" Margin="10,10,10,10">
<telerikChart:RadCartesianChart.Grid>
<telerikChart:CartesianChartGrid MajorLinesVisibility="XY" />
</telerikChart:RadCartesianChart.Grid>
<telerikChart:RadCartesianChart.Behaviors>
<telerikChart:ChartPanAndZoomBehavior ZoomMode="Both" PanMode="Both" />
<telerikChart:ChartTrackBallBehavior x:Name="TrackBallBehaviour" InfoMode="Multiple" ShowIntersectionPoints="True">
<telerikChart:ChartTrackBallBehavior.LineStyle>
<Style TargetType="Polyline">
<Setter Property="Stroke" Value="Tomato" />
<Setter Property="StrokeThickness" Value="2" />
<Setter Property="StrokeDashArray" Value="1,2" />
</Style>
</telerikChart:ChartTrackBallBehavior.LineStyle>
</telerikChart:ChartTrackBallBehavior>
</telerikChart:RadCartesianChart.Behaviors>
<telerikChart:RadCartesianChart.VerticalAxis>
<telerikChart:LinearAxis x:Name="Vertical" Title="Y Axis" Minimum="0" />
</telerikChart:RadCartesianChart.VerticalAxis>
<telerikChart:RadCartesianChart.HorizontalAxis>
<telerikChart:LinearAxis x:Name="Horizontal" Title="X Axis" />
</telerikChart:RadCartesianChart.HorizontalAxis>
</telerikChart:RadCartesianChart>
</Grid>
public sealed partial class MainPage : Page
{
private Timer DataTimer; // timer to periodically add data to the chart
private int LineCount = 1; // arbitrary line counter to differentiate lines from each other
private DataTemplate ChartTrackIntersectionTemplate;
// custom class for holding the data to be displayed on the chart
private class Data
{
public int XValue { get; set; }
public int YValue { get; set; }
}
// overarching class that holds all of the data for ONE line/series
private class DataToChart
{
// List of all the data points within this instance of DataToChart
public List<Data> DataPoints;
// Constructor to initialise DataPoints
public DataToChart()
{
DataPoints = new List<Data>();
}
}
// Overarching container to hold data for ALL lines/series
private ObservableCollection<DataToChart> allData = new ObservableCollection<DataToChart>();
public MainPage()
{
this.InitializeComponent();
// set up the timer to call every 10s to add new data to the chart. warning: this will run infinitely
DataTimer = new Timer(DataCallback, null, (int)TimeSpan.FromSeconds(10).TotalMilliseconds, Timeout.Infinite);
ChartTrackIntersectionTemplate = this.Resources["ChartTrackIntersectionTemplate"] as DataTemplate;
allData.CollectionChanged += AllData_CollectionChanged;
}
private void AllData_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
{
foreach (DataToChart data in e.NewItems)
{
// Make a series for the line
ScatterLineSeries scatterLineSeries = new ScatterLineSeries
{
Name = $"Line {LineCount}",
ItemsSource = data.DataPoints,
XValueBinding = new PropertyNameDataPointBinding("XValue"),
YValueBinding = new PropertyNameDataPointBinding("YValue"),
DisplayName = $"Line {LineCount}",
LegendTitle = $"Line {LineCount}",
};
ChartTrackBallBehavior.SetIntersectionTemplate(scatterLineSeries, ChartTrackIntersectionTemplate);
// Add the line to the Chart's Series collection
MainChart.Series.Add(scatterLineSeries);
// Increment arbitrary counter
LineCount++;
}
}
// Generic callback to call AddLineToChart() on the other thread to handle the Chart's data
private async void DataCallback(object state)
{
await Dispatcher.RunAsync(CoreDispatcherPriority.Normal, () => AddLineToChart());
}
// Code to handle adding a line to the chart
private void AddLineToChart()
{
// Using Random() to create random data
Random rand = new Random();
DataToChart dataToChart = new DataToChart();
for (int i = 0; i < 50; i++)
{
dataToChart.DataPoints.Add(new Data
{
XValue = i,
YValue = rand.Next(0, 100)
});
}
// Add the data for this line/series to the overarching container
allData.Add(dataToChart);
DataTimer.Change((int)TimeSpan.FromSeconds(10).TotalMilliseconds, Timeout.Infinite);
}
}
我一直致力于在 UWP 中构建一个程序,该程序利用 Telerik 图表控件向用户显示传入数据。在我最近的测试中,在图表上动态接收和绘制未经请求的数据时,我遇到了未处理的异常 (NullReferenceException)。这似乎仅在用户将鼠标悬停在图表上且图表显示 TrackBall 信息(Telerik 的 ChartTrackBallBehavior)时才会发生。如果用户的鼠标在别处或没有触发轨迹球信息显示在图表上,则永远不会出现此异常。
到目前为止,我已经设法追踪到 NullException 发生在 Telerik 的 UI 中的 ChartTrackBallBehavior.cs 函数 GetIntersectionTemplate() 中。除此之外,我不知道我能做些什么来解决这个问题,让它再也不会发生。
这是异常发生时的典型堆栈跟踪:
System.ArgumentNullException: Value cannot be null.
at Telerik.UI.Xaml.Controls.Chart.ChartTrackBallBehavior.GetIntersectionTemplate(DependencyObject instance)
at Telerik.UI.Xaml.Controls.Chart.ChartTrackBallBehavior.UpdateIntersectionPoints(ChartDataContext context)
at Telerik.UI.Xaml.Controls.Chart.ChartTrackBallBehavior.UpdateVisuals()
at Telerik.UI.Xaml.Controls.Chart.RadChartBase.NotifyUIUpdated()
at Telerik.UI.Xaml.Controls.Chart.PresenterBase.UpdateUI(ChartLayoutContext context)
我曾尝试在将每个系列添加到图表之前和之后禁用 ChartTrackBallBehavior,但无济于事。 我曾尝试手动将焦点更改为图表以外的其他控件,但无济于事。 我曾尝试在不同区域手动调用 Chart.UpdateLayout(),结果这些调用在同一位置 (ChartTrackBallBehavior.cs).
中创建了相同的 NullReferenceException问题的核心似乎是这个 "Value" 被错误地设置为 null。到目前为止,我还不能确定什么 "Value" 被设置为 null,我只能假设它在 GetIntersectionTemplate() 函数调用中触发了 NullReferenceException throw() 调用。但我不知道为什么会这样,也不知道我能做些什么。
我做了一个最小的项目来重现这个问题。请注意,在重新绘制图表上的所有系列之前,它会清除图表中的所有系列。这样做是为了等同于我自己的项目,似乎与问题本身有关。此系列清除和重新添加程序已完成,因为用户可以随时更改要在图表上显示的系列。
我可能可以更改编码结构以从不同的方向解决这个问题,但目前我想更好地了解导致此问题的原因并尽可能解决它,否则我可能会需要重写大部分代码,不幸的是时间不在我这边。
这是示例代码。请注意,我为此代码使用的是 Telerik.UI.for.UniversalWindowsPlatform 版本 1.0.1.5。
MainPage.xaml
<Page
x:Class="ExceptionReplicator.MainPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:ExceptionReplicator"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:telerikChart="using:Telerik.UI.Xaml.Controls.Chart"
xmlns:telerikPrimitives="using:Telerik.UI.Xaml.Controls.Primitives"
mc:Ignorable="d"
Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
<Grid>
<telerikChart:RadCartesianChart x:Name="MainChart" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" Margin="10,10,10,10">
<telerikChart:RadCartesianChart.Grid>
<telerikChart:CartesianChartGrid MajorLinesVisibility="XY"/>
</telerikChart:RadCartesianChart.Grid>
<telerikChart:RadCartesianChart.Behaviors>
<telerikChart:ChartPanAndZoomBehavior ZoomMode="Both" PanMode="Both"/>
<telerikChart:ChartTrackBallBehavior x:Name="TrackBallBehaviour" InfoMode="Multiple" ShowIntersectionPoints="True">
<telerikChart:ChartTrackBallBehavior.LineStyle>
<Style TargetType="Polyline">
<Setter Property="Stroke" Value="Tomato"/>
<Setter Property="StrokeThickness" Value="2"/>
<Setter Property="StrokeDashArray" Value="1,2"/>
</Style>
</telerikChart:ChartTrackBallBehavior.LineStyle>
<telerikChart:ChartTrackBallBehavior.IntersectionTemplate>
<DataTemplate>
<Ellipse Width="10" Height="10" Fill="Tomato"/>
</DataTemplate>
</telerikChart:ChartTrackBallBehavior.IntersectionTemplate>
</telerikChart:ChartTrackBallBehavior>
</telerikChart:RadCartesianChart.Behaviors>
<telerikChart:RadCartesianChart.VerticalAxis>
<telerikChart:LinearAxis x:Name="Vertical" Title="Y Axis" Minimum="0"/>
</telerikChart:RadCartesianChart.VerticalAxis>
<telerikChart:RadCartesianChart.HorizontalAxis>
<telerikChart:LinearAxis x:Name="Horizontal" Title="X Axis"/>
</telerikChart:RadCartesianChart.HorizontalAxis>
</telerikChart:RadCartesianChart>
</Grid>
</Page>
MainPage.xaml.cs
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices.WindowsRuntime;
using Windows.Foundation;
using Windows.Foundation.Collections;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
using Windows.UI.Xaml.Controls.Primitives;
using Windows.UI.Xaml.Data;
using Windows.UI.Xaml.Input;
using Windows.UI.Xaml.Media;
using Windows.UI.Xaml.Navigation;
// The Blank Page item template is documented at https://go.microsoft.com/fwlink/?LinkId=402352&clcid=0x409
namespace ExceptionReplicator
{
using System;
using System.Threading;
using Telerik.UI.Xaml.Controls.Chart;
using Windows.UI.Core;
/// <summary>
/// An empty page that can be used on its own or navigated to within a Frame.
/// </summary>
public sealed partial class MainPage : Page
{
private Timer DataTimer; // timer to periodically add data to the chart
private int LineCount = 1; // arbitrary line counter to differentiate lines from each other
// custom class for holding the data to be displayed on the chart
private class Data
{
public int XValue { get; set; }
public int YValue { get; set; }
}
// overarching class that holds all of the data for ONE line/series
private class DataToChart
{
// List of all the data points within this instance of DataToChart
public List<Data> DataPoints;
// Constructor to initialise DataPoints
public DataToChart()
{
DataPoints = new List<Data>();
}
}
// Overarching container to hold data for ALL lines/series
private List<DataToChart> allData = new List<DataToChart>();
public MainPage()
{
this.InitializeComponent();
// set up the timer to call every 10s to add new data to the chart. warning: this will run infinitely
DataTimer = new Timer(DataCallback, null, (int)TimeSpan.FromSeconds(10).TotalMilliseconds, Timeout.Infinite);
}
// Generic callback to call AddLineToChart() on the other thread to handle the Chart's data
private void DataCallback(object state)
{
var task = Dispatcher.RunAsync(CoreDispatcherPriority.Normal, () => AddLineToChart());
}
// Code to handle adding a line to the chart
private void AddLineToChart()
{
// Using Random() to create random data
Random rand = new Random();
DataToChart dataToChart = new DataToChart();
for (int i = 0; i < 50; i++)
{
dataToChart.DataPoints.Add(new Data
{
XValue = i,
YValue = rand.Next(0, 100)
});
}
// Add the data for this line/series to the overarching container
allData.Add(dataToChart);
// re-initialise the line count
LineCount = 1;
// Currently the code needs to clear the chart and redraw it each time new data is introduced
MainChart.Series.Clear();
// For each line/series in the main container
foreach (DataToChart data in allData)
{
// Make a series for the line
ScatterLineSeries scatterLineSeries = new ScatterLineSeries
{
Name = $"Line {LineCount}",
ItemsSource = dataToChart.DataPoints,
XValueBinding = new PropertyNameDataPointBinding("XValue"),
YValueBinding = new PropertyNameDataPointBinding("YValue"),
DisplayName = $"Line {LineCount}",
LegendTitle = $"Line {LineCount}",
};
// Add the line to the Chart's Series collection
MainChart.Series.Add(scatterLineSeries);
// Increment arbitrary counter
LineCount++;
}
// Re-set the timer to fire again in 10s
DataTimer.Change((int)TimeSpan.FromSeconds(10).TotalMilliseconds, Timeout.Infinite);
}
}
}
我需要找到一种解决方案来确保在引入新数据时不再出现此异常。任何帮助将不胜感激。
在短期内,我已经从我的图表中完全删除了 ChartTrackBallBehavior(将其注释掉),直到我确定解决方案。删除行为后,不会发生此异常。
您的代码中存在一些问题。
- 您是否发现您的
IntersectionTemplate
根本没有应用到您的 'ScatterLineSeries'?我检查了 Telerik 文档 TrackBall Behavior。他们将IntersectionTemplate
放在telerikChart:LineSeries
中,而不是telerikChart:RadCartesianChart.Behaviors
。因此,您需要将IntersectionTemplate
应用于代码隐藏中的 ScatterLineSeries。 - 您调用
MainChart.Series.Clear();
清除图表并在每次有新数据时重新绘制图表。当数据量很大时会导致性能问题。我建议您只需将新的 ScatterLineSeries 添加到图表中并保留旧数据。 - 您声明了
List<DataToChart> allData
变量。我建议你使用 ObservableCollection Class. This class has implemented the INotifyPropertyChanged 接口,当集合中添加新数据时,它会通知 UI.
结合以上三点,我做了一个代码示例供大家参考:
<Page.Resources>
<DataTemplate x:Key="ChartTrackIntersectionTemplate">
<Ellipse Width="10" Height="10" Fill="Tomato" />
</DataTemplate>
</Page.Resources>
<Grid>
<telerikChart:RadCartesianChart x:Name="MainChart" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" Margin="10,10,10,10">
<telerikChart:RadCartesianChart.Grid>
<telerikChart:CartesianChartGrid MajorLinesVisibility="XY" />
</telerikChart:RadCartesianChart.Grid>
<telerikChart:RadCartesianChart.Behaviors>
<telerikChart:ChartPanAndZoomBehavior ZoomMode="Both" PanMode="Both" />
<telerikChart:ChartTrackBallBehavior x:Name="TrackBallBehaviour" InfoMode="Multiple" ShowIntersectionPoints="True">
<telerikChart:ChartTrackBallBehavior.LineStyle>
<Style TargetType="Polyline">
<Setter Property="Stroke" Value="Tomato" />
<Setter Property="StrokeThickness" Value="2" />
<Setter Property="StrokeDashArray" Value="1,2" />
</Style>
</telerikChart:ChartTrackBallBehavior.LineStyle>
</telerikChart:ChartTrackBallBehavior>
</telerikChart:RadCartesianChart.Behaviors>
<telerikChart:RadCartesianChart.VerticalAxis>
<telerikChart:LinearAxis x:Name="Vertical" Title="Y Axis" Minimum="0" />
</telerikChart:RadCartesianChart.VerticalAxis>
<telerikChart:RadCartesianChart.HorizontalAxis>
<telerikChart:LinearAxis x:Name="Horizontal" Title="X Axis" />
</telerikChart:RadCartesianChart.HorizontalAxis>
</telerikChart:RadCartesianChart>
</Grid>
public sealed partial class MainPage : Page
{
private Timer DataTimer; // timer to periodically add data to the chart
private int LineCount = 1; // arbitrary line counter to differentiate lines from each other
private DataTemplate ChartTrackIntersectionTemplate;
// custom class for holding the data to be displayed on the chart
private class Data
{
public int XValue { get; set; }
public int YValue { get; set; }
}
// overarching class that holds all of the data for ONE line/series
private class DataToChart
{
// List of all the data points within this instance of DataToChart
public List<Data> DataPoints;
// Constructor to initialise DataPoints
public DataToChart()
{
DataPoints = new List<Data>();
}
}
// Overarching container to hold data for ALL lines/series
private ObservableCollection<DataToChart> allData = new ObservableCollection<DataToChart>();
public MainPage()
{
this.InitializeComponent();
// set up the timer to call every 10s to add new data to the chart. warning: this will run infinitely
DataTimer = new Timer(DataCallback, null, (int)TimeSpan.FromSeconds(10).TotalMilliseconds, Timeout.Infinite);
ChartTrackIntersectionTemplate = this.Resources["ChartTrackIntersectionTemplate"] as DataTemplate;
allData.CollectionChanged += AllData_CollectionChanged;
}
private void AllData_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
{
foreach (DataToChart data in e.NewItems)
{
// Make a series for the line
ScatterLineSeries scatterLineSeries = new ScatterLineSeries
{
Name = $"Line {LineCount}",
ItemsSource = data.DataPoints,
XValueBinding = new PropertyNameDataPointBinding("XValue"),
YValueBinding = new PropertyNameDataPointBinding("YValue"),
DisplayName = $"Line {LineCount}",
LegendTitle = $"Line {LineCount}",
};
ChartTrackBallBehavior.SetIntersectionTemplate(scatterLineSeries, ChartTrackIntersectionTemplate);
// Add the line to the Chart's Series collection
MainChart.Series.Add(scatterLineSeries);
// Increment arbitrary counter
LineCount++;
}
}
// Generic callback to call AddLineToChart() on the other thread to handle the Chart's data
private async void DataCallback(object state)
{
await Dispatcher.RunAsync(CoreDispatcherPriority.Normal, () => AddLineToChart());
}
// Code to handle adding a line to the chart
private void AddLineToChart()
{
// Using Random() to create random data
Random rand = new Random();
DataToChart dataToChart = new DataToChart();
for (int i = 0; i < 50; i++)
{
dataToChart.DataPoints.Add(new Data
{
XValue = i,
YValue = rand.Next(0, 100)
});
}
// Add the data for this line/series to the overarching container
allData.Add(dataToChart);
DataTimer.Change((int)TimeSpan.FromSeconds(10).TotalMilliseconds, Timeout.Infinite);
}
}