Livecharts Geared 在缩放到特定位置时抛出未处理的 ArgumentOutOfRangeException

Livecharts Geared throws unhandled ArgumentOutOfRangeException when zoomed to specific position

我修改了 scrollable example to use Rachel Lim's VMMV navigation example 并从我们的数据库中获取数据。我还将一些代码隐藏逻辑移到了 VM 中。使用 LiveCharts.ChartValues 一切正常,但是当使用 LiveCharts.Geared.GearedValues 时,库会在 in/out 缩放到特定点时崩溃。

数据有 6 个带时间戳的小时值。我按小时对值进行分组和求和。时间戳和值永远不会为空,也不会计算总和。绘制图表后我不更新数据。

如果我从数据库中获取 1000 个值(~1000/6 个数据点),当缩小数据范围的大约 5 倍时库会崩溃。 如果我获取 10000 个值(~10000/6 个数据点),只要用户导航到图表所在的用户控件,就会发生崩溃。 如果我获取 100000 个值,当放大到大约相同的最小值和最大值时会发生崩溃。

但是,当使用 ChartValues 而不是 GearedValues 或仅使用几个数据点时,我可以尽可能放大并缩小到 DateTime.minvalue。

我的 view.xaml(几乎是示例一,但使用 ICommand RangeChangedCommand):

<UserControl 
 [ ....]
  xmlns:geared="clr-namespace:LiveCharts.Geared;assembly=LiveCharts.Geared">

<Grid>
    <Grid.RowDefinitions>
        <RowDefinition Height="Auto"></RowDefinition>
        <RowDefinition Height="*"></RowDefinition>
        <RowDefinition Height="100"></RowDefinition>
    </Grid.RowDefinitions>
    <TextBlock Grid.Row="0"></TextBlock>
    <lvc:CartesianChart Grid.Row="1"
                        Zoom="X" 
                        DisableAnimations="True"
                        Hoverable="False">
        <lvc:CartesianChart.Resources>
            <Style TargetType="lvc:Separator">
                <Setter Property="StrokeThickness" Value="2.5"></Setter>
                <Setter Property="Stroke" Value="#E7E7E7"></Setter>
                <Style.Triggers>
                    <Trigger Property="AxisOrientation" Value="X">
                        <Setter Property="IsEnabled" Value="False"></Setter>
                    </Trigger>
                </Style.Triggers>
            </Style>
        </lvc:CartesianChart.Resources>
        <lvc:CartesianChart.Series>
            <geared:GLineSeries StrokeThickness="0" 
                                Values="{Binding Values}"
                                Fill="#2194F1"
                                AreaLimit="0"
                                PointGeometry="{x:Null}"
                                LineSmoothness="0"/>
        </lvc:CartesianChart.Series>
        <lvc:CartesianChart.AxisX>
            <lvc:Axis LabelFormatter="{Binding Formatter}" RangeChangedCommand="{Binding Axis_OnRangeChangedCommand}" 
                      MinValue="{Binding From, Mode=TwoWay}" MaxValue="{Binding To, Mode=TwoWay}"
                      Separator="{x:Static lvc:DefaultAxes.CleanSeparator}"/>
        </lvc:CartesianChart.AxisX>
    </lvc:CartesianChart>
    <lvc:CartesianChart Grid.Row="2" DisableAnimations="True" 
                        ScrollMode="X" 
                        ScrollHorizontalFrom="{Binding From, Mode=TwoWay}"
                        ScrollHorizontalTo="{Binding To, Mode=TwoWay}"
                        ScrollBarFill="#25303030"
                        DataTooltip="{x:Null}"
                        Hoverable="False"
                        Margin="20 10">
        <lvc:CartesianChart.Resources>
            <Style TargetType="lvc:Separator">
                <Setter Property="IsEnabled" Value="False"></Setter>
            </Style>
        </lvc:CartesianChart.Resources>
        <lvc:CartesianChart.Series>
            <geared:GLineSeries Values="{Binding Values}"
                                Fill="Silver"
                                StrokeThickness="0"
                                PointGeometry="{x:Null}"
                                AreaLimit="0"/>
        </lvc:CartesianChart.Series>
        <lvc:CartesianChart.AxisX>
            <lvc:Axis IsMerged="True" 
                      LabelFormatter="{Binding Formatter, Mode=OneTime}" 
                      Foreground="#98000000"
                      FontSize="22"
                      FontWeight="UltraBold"/>
        </lvc:CartesianChart.AxisX>
        <lvc:CartesianChart.AxisY>
            <lvc:Axis ShowLabels="False" />
        </lvc:CartesianChart.AxisY>
    </lvc:CartesianChart>
</Grid>

我的VM.cs

class ScrollableVM : ObservableObject, IPageViewModel
{
    public string Name => "Scrollable";
    private double _from;
    private double _to;
    private Func<double, string> _formatter;
    private ICommand _axis_OnRangeChanged;
    public GearedValues<DateTimePoint> Values { get; set; }
    //public ChartValues<DateTimePoint> Values { get; set; }

    #region setget
    public double From
    {
        get { return _from; }
        set
        {
            SetProperty(ref _from, value);
        }
    }
    public double To
    {
        get { return _to; }
        set
        {
            SetProperty(ref _to, value);
        }
    }


    public Func<double, string> Formatter
    {
        get { return _formatter; }
        set
        {
            SetProperty(ref _formatter, value);
        }
    }
    #endregion

    public ScrollableVM()
    {
        var l = new List<DateTimePoint>();


        using (/***getting data from db***/)
        {
            var q =(/***getting data from db***/).Take(1000).ToList();

            var grouped = q.GroupBy(t => new DateTime(t.Stamp.Value.Year, t.Stamp.Value.Month, t.Stamp.Value.Day, t.Stamp.Value.Hour, 0, 0));

            foreach (var item in grouped)
            {
                l.Add(new DateTimePoint((DateTime)item.Key, (double)item.Sum(x => x.value)));
            }
        }
        //Crashes
        //quality doesn't affect crashing
        Values = l.AsGearedValues().WithQuality(Quality.High);

        ////Works
        //Values = new GearedValues<DateTimePoint>() { new DateTimePoint(DateTime.Now, 0), new DateTimePoint(DateTime.Now.AddHours(1), 1) , new DateTimePoint(DateTime.Now.AddHours(2), 2) };


        ////Works
        //Values = l.AsChartValues();

        From = Values.Min(x => x.DateTime).Ticks;
        To = Values.Max(x => x.DateTime).Ticks;
        Formatter = x => new DateTime((long)x).ToString("yyyy");
    }



    private void Axis_OnRangeChanged(RangeChangedEventArgs eventargs)
    {
        var currentRange = eventargs.Range;

        if (currentRange < TimeSpan.TicksPerDay * 2)
        {
            Formatter = x => new DateTime((long)x).ToString("t");
            return;
        }

        if (currentRange < TimeSpan.TicksPerDay * 60)
        {
            Formatter = x => new DateTime((long)x).ToString("dd MMM yy");
            return;
        }

        if (currentRange < TimeSpan.TicksPerDay * 540)
        {
            Formatter = x => new DateTime((long)x).ToString("MMM yy");
            return;
        }

        Formatter = x => new DateTime((long)x).ToString("yyyy");
    }

    public ICommand Axis_OnRangeChangedCommand
    {
        get
        {
            if (_axis_OnRangeChanged == null)
            {
                _axis_OnRangeChanged = new RelayCommand(a => Axis_OnRangeChanged((RangeChangedEventArgs)a));
            }

            return _axis_OnRangeChanged;
        }
    }


}

view.xaml.cs 只有带有 InitializeComponent()

的构造函数

异常详情:

 System.ArgumentOutOfRangeException
  HResult=0x80131502
  Message=Specified argument was out of the range of valid values.
Parameter name: index
  Source=WindowsBase
  StackTrace:
   at MS.Utility.FrugalStructList`1.Insert(Int32 index, T value)
   at System.Windows.Media.PathSegmentCollection.Insert(Int32 index, PathSegment value)
   at LiveCharts.Wpf.Points.HorizontalBezierPointView.DrawOrMove(ChartPoint previousDrawn, ChartPoint current, Int32 index, ChartCore chart)
   at LiveCharts.SeriesAlgorithms.LineAlgorithm.Update()
   at LiveCharts.ChartUpdater.Update(Boolean restartsAnimations, Boolean force)
   at LiveCharts.Wpf.Components.ChartUpdater.UpdaterTick(Boolean restartView, Boolean force)
   at LiveCharts.Wpf.Components.ChartUpdater.OnTimerOnTick(Object sender, EventArgs args)
   at System.Windows.Threading.DispatcherTimer.FireTick(Object unused)
   at System.Windows.Threading.ExceptionWrapper.InternalRealCall(Delegate callback, Object args, Int32 numArgs)
   at System.Windows.Threading.ExceptionWrapper.TryCatchWhen(Object source, Delegate callback, Object args, Int32 numArgs, Delegate catchHandler)
   at System.Windows.Threading.DispatcherOperation.InvokeImpl()
   at System.Windows.Threading.DispatcherOperation.InvokeInSecurityContext(Object state)
   at MS.Internal.CulturePreservingExecutionContext.CallbackWrapper(Object obj)
   at System.Threading.ExecutionContext.RunInternal(ExecutionContext executionContext, ContextCallback callback, Object state, Boolean preserveSyncCtx)
   at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state, Boolean preserveSyncCtx)
   at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state)
   at MS.Internal.CulturePreservingExecutionContext.Run(CulturePreservingExecutionContext executionContext, ContextCallback callback, Object state)
   at System.Windows.Threading.DispatcherOperation.Invoke()
   at System.Windows.Threading.Dispatcher.ProcessQueue()
   at System.Windows.Threading.Dispatcher.WndProcHook(IntPtr hwnd, Int32 msg, IntPtr wParam, IntPtr lParam, Boolean& handled)
   at MS.Win32.HwndWrapper.WndProc(IntPtr hwnd, Int32 msg, IntPtr wParam, IntPtr lParam, Boolean& handled)
   at MS.Win32.HwndSubclass.DispatcherCallbackOperation(Object o)
   at System.Windows.Threading.ExceptionWrapper.InternalRealCall(Delegate callback, Object args, Int32 numArgs)
   at System.Windows.Threading.ExceptionWrapper.TryCatchWhen(Object source, Delegate callback, Object args, Int32 numArgs, Delegate catchHandler)
   at System.Windows.Threading.Dispatcher.LegacyInvokeImpl(DispatcherPriority priority, TimeSpan timeout, Delegate method, Object args, Int32 numArgs)
   at MS.Win32.HwndSubclass.SubclassWndProc(IntPtr hwnd, Int32 msg, IntPtr wParam, IntPtr lParam)
   at MS.Win32.UnsafeNativeMethods.DispatchMessage(MSG& msg)
   at System.Windows.Threading.Dispatcher.PushFrameImpl(DispatcherFrame frame)
   at System.Windows.Threading.Dispatcher.PushFrame(DispatcherFrame frame)
   at System.Windows.Application.RunDispatcher(Object ignore)
   at System.Windows.Application.RunInternal(Window window)
   at System.Windows.Application.Run(Window window)
   at System.Windows.Application.Run()
   at medidata.App.Main() in ....\source\repos\medidata\medidata\obj\Debug\App.g.cs:line 51

版本:

我的代码/逻辑是否有问题或者这是我应该报告的错误?我没有发现其他人报告的非常相似的问题。 提前谢谢你。

好吧,这花了一天的时间,但我想我发现与官方示例相比,我的实现有何不同... 列表顺序。

如果我在调用 AsGearedValues() 之前按 x 轴上的 属性 对原始数据进行排序,则不会发生崩溃。这在免费版本 AsChartValues 中不是问题。我想这与 virtualization/optimization 有关,并且 AsGearedValues 不够智能,无法对列表本身进行排序以供将来使用。在文档中也没有提到这一点。 我将在 github 上就此提出一个问题。

简单演示:

MainWindow.xaml.cs

using LiveCharts.Defaults;
using LiveCharts.Geared;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Windows;

namespace bughunt
{
    /// <summary>
    /// Interaction logic for MainWindow.xaml
    /// </summary>
    public partial class MainWindow : Window
    {
        public IGearedValues Values { get; set; }

        public MainWindow()
        {
            const int count = 1000;

            //Not sorting causes crashes when zooming deep in and back
            const bool SortBeforePassing = false;

            var r = new Random();

            var datepointlist = new List<DateTimePoint>();

            for (int i = 0; i < count; i++)
            {
                datepointlist.Add(new DateTimePoint(DateTime.Now.AddHours(-i), (double)r.Next(1, 150)));
            }
            if (SortBeforePassing)
            {
                Values = datepointlist.OrderBy(x => x.DateTime).AsGearedValues().WithQuality(Quality.High);
            }
            else
            {
                Values = datepointlist.AsGearedValues().WithQuality(Quality.High);
            }

            DataContext = this;
            InitializeComponent();
        }
    }
}

MainWindow.xaml

<Window x:Class="bughunt.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:local="clr-namespace:bughunt"
        xmlns:lvc="clr-namespace:LiveCharts.Wpf;assembly=LiveCharts.Wpf"
        xmlns:geared="clr-namespace:LiveCharts.Geared;assembly=LiveCharts.Geared"
        mc:Ignorable="d"
        Title="MainWindow" Height="450" Width="800">
    <Grid>
        <lvc:CartesianChart Zoom="X" 
                            DisableAnimations="True"
                            Hoverable="False">

            <lvc:CartesianChart.Series>
                <geared:GLineSeries
                                    Values="{Binding Values}"
                                    PointGeometry="{x:Null}"/>
            </lvc:CartesianChart.Series>
        </lvc:CartesianChart>
    </Grid>
</Window>

我有完全相同的问题:我在 x 轴上有 Geared DateTime 值,缩放时抛出 ArgumentOutOfRangeException。问题是,当缩小太多时,x 轴值格式化程序收到一个负值,这对于 DateTime(负刻度)当然是不正确的。有一个 PreviewRangeChanged 事件,您可以绑定并在需要时取消缩放,但我用不同的方式解决了它:

// what caused the exception
XFormatter = val => new DateTime((long)val).ToString("HH:mm:ss");
// solve it by testing the value and return 0 if negative
XFormatter = val => val < 0.0 ? (new DateTime((long)0.0).ToString("HH:mm:ss")) : (new DateTime((long)val).ToString("HH:mm:ss"));

所以如果你只是阻止值格式化程序得到一个负值,你就完全摆脱了这个问题。这可能不是最聪明的解决方案,但它对我的情况来说足够简单和好。