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

时间:2019-03-25 12:36:59

标签: c# wpf livecharts

我修改了scrollable example以使用Rachel Lim的VMMV navigation example并从我们的数据库中获取数据。我也将一些代码隐藏逻辑移到了VM。一切都可以与LiveCharts.ChartValues正常工作,但是当使用LiveCharts.Geared.GearedValues时,放大/缩小到特定点时库崩溃。

数据具有时间戳的每小时6个值。我按小时对值进行分组和求和。时间戳记和值从不为空,也不是计算得出的总和。绘制图表后,我不更新数据。

如果我从数据库中获取1000个值(〜1000/6个数据点),则当缩小大约5倍于数据范围时,库崩溃。 如果我获取10000个值(〜10000/6个数据点),则一旦用户导航到图表所在的usercontrol,就会发生崩溃。 如果我获取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

版本:

  • LiveCharts 0.9.7.0
  • LiveCharts.Geared 1.2.8.2
  • LiveCharts.Wpf 0.9.7

我的代码/逻辑中是否有一些时髦的东西,或者这是我应该报告的错误?我没有发现其他人报告过的类似问题。 预先谢谢你。

2 个答案:

答案 0 :(得分:0)

花了一天的时间,但我想我发现我的实现与正式示例相比有何不同... 列表命令。

如果在调用AsGearedValues()之前按x轴上的属性对原始数据进行排序,则不会崩溃。在免费版本的AsChartValues中,这不是问题。我想这与虚拟化/优化有关,而且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>

答案 1 :(得分:0)

我遇到了完全相同的问题:在缩放时,在ArgumentOutOfRangeException抛出的x轴上有Geared DateTime值。问题是,当缩小得太多时,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"));

因此,如果您只是防止值格式化程序获得负值,则可以完全摆脱此问题。这可能不是最聪明的解决方案,但对于我的情况而言,它已经足够简单了。