Live-Charts icon indicating copy to clipboard operation
Live-Charts copied to clipboard

ArgumentOutOfRangeException when zooming in on chart

Open NicolaiNyberg opened this issue 8 years ago • 25 comments

How to reproduce?

*I have created a WPF CartesianChart with two LineSeries displaying intra day forex tick data. When I zoom in (by setting MinX and MaxX on X Axis, LiveCharts triggers an exception

System.ArgumentOutOfRangeException: Specified argument was out of the range of valid values.
Parameter name: index
   at MS.Utility.FrugalStructList`1.Insert(Int32 index, T value)
   at System.Windows.Media.PathSegmentCollection.Insert(Int32 index, PathSegment value)
   at LiveCharts.Wpf.LineSeries.EndSegment(Int32 atIndex, CorePoint location) in c:\Users\btord\Documents\Projects\LiveCharts\WpfView\LineSeries.cs:line 480
   at LiveCharts.SeriesAlgorithms.LineAlgorithm.Update() in c:\Users\btord\Documents\Projects\LiveCharts\Core40\SeriesAlgorithms\LineAlgorithm.cs:line 188
   at LiveCharts.ChartUpdater.Update(Boolean restartsAnimations, Boolean force) in c:\Users\btord\Documents\Projects\LiveCharts\Core40\ChartUpdater.cs:line 125
   at LiveCharts.Wpf.Components.ChartUpdater.UpdaterTick(Boolean restartView, Boolean force) in c:\Users\btord\Documents\Projects\LiveCharts\WpfView\Components\ChartUpdater.cs:line 89
   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 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.Application.RunDispatcher(Object ignore)
   at System.Windows.Application.RunInternal(Window window)
   at MyApp.Main()

Extra notes

I have a check in my code ensuring that MaxX will never be less than or equal to MinX. I am running the app via Remote Desktop on Windows Server 2012 I am using GearedValues with Quality.High

I am using

Live-Charts version 0.9.7.0
Geared Live-Charts version 1.2.8.2
.Net Version 4.7.2114.0
Windows Windows Server 2012

NicolaiNyberg avatar Dec 21 '17 03:12 NicolaiNyberg

I got the same problem. :(

NeraV avatar Jan 26 '18 18:01 NeraV

The same here, except that I can't figure out why this happens. @NicolaiNyberg states the exception raise when he zoom (changing MinX and MaxX), despite the fact that I'm going with the same flavour of zooming technique (changing MinX and MaxX on MouseWheel), the problem seems completely uncorrelated to that, since the exception is raised every now and then.

Scenario

The chart is a realtime chart, where you can add/remove series. and sections. I add a bunch (around 20) of random points on each shown serie every seconds. Zoom is enabled only when the "point add feature" is paused, to ensure series won't change while zooming.

-- version
Live-Charts 0.9.7.0
Geared Live-Charts 1.2.8.2
.Net 4.5.2
Windows Windows 10

Fail conditions

From my test, seems that this issue happens more frequently when:

  • I add new series really fast (some milliseconds between adding two consecutive series to the chart);
  • a lot (15-20) series are plotted with 20k-30k points each (quality setted to High or Highest)

Extra

Don't know if this can help, but the problem is still there if I don't use Geared package. The only difference is that it isn't on the LiveCharts.Wpf.LineSeries.EndSegment(...) method, but on the LiveCharts.Wpf.Points.HorizontalBezierPointView.DrawOrMove(...) method (both of them called by LiveCharts.SeriesAlgorithms.LineAlgorithm.Update())

Here the exception raised without the geared package

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) in xxx\WpfView\Points\HorizontalBezierPointView.cs:line 50
at LiveCharts.SeriesAlgorithms.LineAlgorithm.Update() in xxx\Core40\SeriesAlgorithms\LineAlgorithm.cs:line 161
at LiveCharts.ChartUpdater.Update(Boolean restartsAnimations, Boolean force) in xxx\Core40\ChartUpdater.cs:line 124
at LiveCharts.Wpf.Components.ChartUpdater.UpdaterTick(Boolean restartView, Boolean force) in xxx\WpfView\Components\ChartUpdater.cs:line 88
at LiveCharts.Wpf.Components.ChartUpdater.OnTimerOnTick(Object sender, EventArgs args) in xxx\WpfView\Components\ChartUpdater.cs:line 74
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 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()```

mrcdlp avatar Mar 23 '18 14:03 mrcdlp

I'm getting the same issue, but in a much simpler context. I just have three line charts with continual updating data. It fails within a minute with only a few hundred points.

at MS.Utility.FrugalStructList`1.Insert(Int32 index, T value) at System.Windows.Media.PathSegmentCollection.Insert(Int32 index, PathSegment value) at LiveCharts.Wpf.LineSeries.EndSegment(Int32 atIndex, CorePoint location) in c:\Users\btord\Documents\Projects\LiveCharts\WpfView\LineSeries.cs:line 476 at LiveCharts.SeriesAlgorithms.LineAlgorithm.Update() in c:\Users\btord\Documents\Projects\LiveCharts\Core40\SeriesAlgorithms\LineAlgorithm.cs:line 187 at LiveCharts.ChartUpdater.Update(Boolean restartsAnimations, Boolean force) in c:\Users\btord\Documents\Projects\LiveCharts\Core40\ChartUpdater.cs:line 124 at LiveCharts.Wpf.Components.ChartUpdater.UpdaterTick(Boolean restartView, Boolean force) in c:\Users\btord\Documents\Projects\LiveCharts\WpfView\Components\ChartUpdater.cs:line 88 at LiveCharts.Wpf.Components.ChartUpdater.OnTimerOnTick(Object sender, EventArgs args) in c:\Users\btord\Documents\Projects\LiveCharts\WpfView\Components\ChartUpdater.cs:line 74 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)

ZombiesWithCoffee avatar Mar 28 '18 12:03 ZombiesWithCoffee

Would Love to know a solution to this. I am also making a real time graph and I keep getting this issue.

killkelleyr avatar Apr 03 '18 19:04 killkelleyr

I might have found a solution for you as it worked out for me. I first made sure I got rid of anywhere i was calling for GearedValues and just changed it to regular ChartValues. For my GLineSeries values I told it to treat as GearedValues.

Ex. Values = new ChartValues<GraphModel>(truncatedScan).AsGearedValues().WithQuality(Quality.Low)

Hope this helps

killkelleyr avatar Apr 03 '18 20:04 killkelleyr

I solved it by writing code in the event for x axis change... I cancel the zoom ( axis change) in three cases: if the new min value is less than my saved real minimum value of the entire chart ( you have to save it somewhere when you draw the chart), if the new max value is bigger than the saved max value from my chart, and third and the biggest problem I this is when you zoom too much, so I cancel the zoom also when the difference between the new min and max value is less than some value.. idiotic but it works.. all the charts are geared, it is necessary in my app

NeraV avatar Apr 04 '18 13:04 NeraV

Turns out i thought I fixed this issue but again it came back, however this time it happens when I start adding many data points in a short amount of time. I tried your suggestion about setting zoom = none, however it did not solve the issue. Looks like i'm back to square one

killkelleyr avatar Apr 05 '18 16:04 killkelleyr

I have the same issue but it has nothing to do with manual zoom. In my system it occurs when I just add data points continuously to a CartesianChart with two LineSeries. One data point per second is added to both series over 10 minutes. Sometimes the exception is thrown and sometimes not.

KrabatTilt avatar Apr 06 '18 09:04 KrabatTilt

Did someone manage to solve this problem? I'm having the same issue when updating the graph in a short amount of time (using Geared).

amirdach avatar Apr 25 '18 08:04 amirdach

This is most likely a threading issue, in version 0.x any series inherit from FrameworkElement, thus the object could be running in the UI thread.

This is an important issue that forced the rewrite of the library, 1.0 should come soon, and offers multi threading solutions, in the current layout you could be updating your chart from multiple threads and you don't probably know it.

Please in version 0.x ensure you are not modifying the Series.Values property while the UI is updating, the general rule for the library to handle this for you is:

Do not:

SeriesCollection = new SeriesCollection() { // come series ... };

<CartesianChart Series="{Binding SeriesCollection}" />

Instead please help the library with this syntax:

 Values = new ChartValues<double> {...};

 <CartesianChart >
     <CartesianChart.Series>
            <LineSeries Values="{Binding Values}" />

Now the library should not throw in this scenario even if you are modifying the Values instance from multiple threads.

Take a look at the Geared samples repo

https://github.com/Live-Charts/GearedExamples

specially at

https://github.com/Live-Charts/GearedExamples/tree/master/Wpf/SpeedTest

There multi threading works without problems, but it is because I helped the library with the syntax above.

If you need to bind the series property to a series collection then invoke on the UI thread the Series manipulation:

Application.Current.Dispatcher.Invoke(new Action(() => { 
      SeriesCollection = ....
}));

Well version 1.0 should be out in some days and there are many improvements, this is one of them.

beto-rodriguez avatar Apr 25 '18 20:04 beto-rodriguez

Hi,thanks for the reply. This solution might work but it's not suitable in my case because it restricts me, if I'll write my code this way I wont be able to add graphs dynamically to the series. In my system I add graphs without knowing in advance the number of graphs which the user will decide to add, so managing the SeriesCollection in Xaml and not binding it to a C# object wont work for me. Is there any other solution for this problem which allow me to add dynamically graphs in my system?

thank you, Amir Dachbash

2018-04-25 23:54 GMT+03:00 Alberto Rodríguez [email protected]:

This is most likely a threading issue, in version 0.x any series inherit from FrameworkElement, thus the object could be running in the UI thread.

This is an importing issue that forced the rewrite of the library, 1.0 should come soon, and offers multi threading solutions, in the current layout you could be updating your chart from multiple threads and you don't probably know it.

Please in version 1.0 ensure you are not modifying the Series.Values property while the UI is updating.

Do not:

SeriesCollection = new SeriesCollection() { // come series ... };

Instead please help the library to keep the UI thread only with UI elements, and the data in the main thread, try this syntax instead.

Values = new ChartValues {...};

Now the library should not throw in this scenario even if you are modifying the Values instance from multiple threads.

Well version 1.0 should be out in some days and there are many improvements, this is one of them.

— You are receiving this because you commented. Reply to this email directly, view it on GitHub https://github.com/Live-Charts/Live-Charts/issues/701#issuecomment-384430581, or mute the thread https://github.com/notifications/unsubscribe-auth/ANdbUapcHBJ9935NfFIFpBHvK27cG9cBks5tsOJygaJpZM4RJUsg .

amirdach avatar May 17 '18 12:05 amirdach

This issue is still there. I get it every time

Aangbaeck avatar Aug 16 '19 13:08 Aangbaeck

I have circumvented the bug by:

  • ensuring WPF bound properties, e.g. SeriesCollection, are only created on the UI thread
  • updates to the data in the series are only Add()ed on the UI thread
  • handling zoom myself

@beto-rodriguez: any news on the release of 1.0 ?

NicolaiNyberg avatar Aug 17 '19 08:08 NicolaiNyberg

This is most likely a threading issue, in version 0.x any series inherit from FrameworkElement, thus the object could be running in the UI thread.

Wrapped everything I could into Dispatcher calls, didn't help.

I have circumvented the bug by:

  • ensuring WPF bound properties, e.g. SeriesCollection, are only created on the UI thread
  • updates to the data in the series are only Add()ed on the UI thread
  • ~~handling zoom myself~~

First two didn't help me I don't use zoom, can not try the last one

Structural Equality

It seems the problem is caused by structural equality of a user defined type. When two instances are compared for equality having all data equal they are treated as the same despite represent different points on the LineSeries. It's not the issue with reference equality since different instances are not equal even containing the same data.

I am using F# where all types have built-in structural equality. There is no error when plotting simple values like integers. But once I replace ChartValues<int> to ChartValues<Record> where

type Record = { y:int }

or generated C# code

public sealed class Record : IEquatable<Types.Record>, IStructuralEquatable, IComparable<Types.Record>, IComparable, IStructuralComparable
{
    public int y { get; }
}

the error occurs immediately after inserting two points with the same y value

Possible Workarounds

  • Override Equals to always return false.
  • Or add member with value DateTime.Now so each instance (thus chart point too) will contain a unique data. I needed this by design so used.

Brains avatar Aug 18 '19 16:08 Brains

I tried every ways you guys advised, but failed. Finally I just add "try catch" for all "PathSegmentCollection.Insert" and it's gone.

KyanWang avatar Oct 15 '19 08:10 KyanWang

I had the exact same problem. I have DateTime on the x-axis and when zoomed out too much, the x-axis value formatter received a negative value, which of course doesn't work with DateTime. I solved it by testing the value in the axis formatter and if it's negative, I just pass 0:

// this throws the exception if val is negative
XFormatter = val => new DateTime((long)val).ToString("HH:mm:ss");
// solved it by testing the value and passing 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"));

This is the only solution that worked in my case.

istvanns avatar Jan 28 '20 08:01 istvanns

I'm facing this problem, too. Our team cannot finish the app for our customer because the LiveCharts is just crashing. We paid for it and it looks there's no support anymore. @beto-rodriguez announced fixed version 1.0 "in some days" and almost 2 years elapsed since then without any news. 😢

jozeftimulak avatar Feb 03 '20 12:02 jozeftimulak

I tried every ways you guys advised, but failed. Finally I just add "try catch" for all "PathSegmentCollection.Insert" and it's gone.

Do you use MVVM?

jozeftimulak avatar Feb 03 '20 13:02 jozeftimulak

Geared has weird bugs when zooming on live data (data updating in real time), and since it's practically abandoned, it's unusable... which is very unfortunate. I'll have to give SchiChart a try.

istvanns avatar Feb 04 '20 07:02 istvanns

@istvanns, then maybe @beto-rodriguez should put some message on the page saying it's abandoned. He should also stop selling it/receiving money.

Now I have to go to our customer and say our money is gone, our development time is gone and let's try another library for another money and another time. 🤦‍♂️

jozeftimulak avatar Feb 04 '20 08:02 jozeftimulak

Yes, that's my problem too. LiveCharts Geared is completely unusable for business. I think the money you pay for the library is not the real concern, since 99 USD can easily be swallowed, if it has to be swallowed. But the loss of all those development hours, several weeks or months, and not being able to keep deadlines, cost much more both in terms of money and customer-trust. No business wants to have such problems. Beto, if you're not developing and supporting it, you must stop selling Geared! It's not the 99 USD that causes problems. Real problems caused by this situation can be way much more costly!

istvanns avatar Feb 04 '20 08:02 istvanns

@istvanns, thanks for your words. But I learned one thing: always go to GitHub and check the number or issues of the library you want to use/buy. If I knew LiveCharts has 400+ open issues I would never buy it.

jozeftimulak avatar Feb 04 '20 09:02 jozeftimulak

@jozeftimulak yes, now I learned it too: don't rely on the marketing fluff of the website, but check when was the last update/commit on GitHub! This would have saved a lot of time and effort! Finding an other solution takes time and effort again, not to mention dealing with deadlines and communicating it. I really thought that Geared was the perfect solution for the price. I have to admit: paying more than 1000 USD for something like SciChart, which is actively developed and supported, is a much wiser decision in terms of business. Time is money and trust is something no money can buy.

istvanns avatar Feb 04 '20 09:02 istvanns

This is most likely a threading issue, in version 0.x any series inherit from FrameworkElement, thus the object could be running in the UI thread.

Wrapped everything I could into Dispatcher calls, didn't help.

I have circumvented the bug by:

  • ensuring WPF bound properties, e.g. SeriesCollection, are only created on the UI thread
  • updates to the data in the series are only Add()ed on the UI thread
  • ~handling zoom myself~

First two didn't help me I don't use zoom, can not try the last one

Structural Equality

It seems the problem is caused by structural equality of a user defined type. When two instances are compared for equality having all data equal they are treated as the same despite represent different points on the LineSeries. It's not the issue with reference equality since different instances are not equal even containing the same data.

I am using F# where all types have built-in structural equality. There is no error when plotting simple values like integers. But once I replace ChartValues<int> to ChartValues<Record> where

type Record = { y:int }

or generated C# code

public sealed class Record : IEquatable<Types.Record>, IStructuralEquatable, IComparable<Types.Record>, IComparable, IStructuralComparable
{
    public int y { get; }
}

the error occurs immediately after inserting two points with the same y value

Possible Workarounds

* Override `Equals` to always return `false`.

* Or add member with value `DateTime.Now` so each instance (thus chart point too) will contain a unique data. I needed this by design so used.

I have this exact same issue with a custom type and only when the Y are the same. You saved my day 👍

EDIT: The DateTime workaround did not work for me. However if you add a private field with a dummy object the Equality comparison will be false without having to hijack the Equals

    object Test = new object();

claudio-g avatar Aug 13 '20 13:08 claudio-g

Cause

I can indeed confirm that this issue arises from equality errors.

If two duplicate chart values, where the type overrides equals, are added to a ChartValues-collection, these values will be associated with the same ChartPoint, and thus the same IChartPointView.

The result is that the number of unique IChartPointView is not equal to the number of chart values. LiveCharts expects a one-to-one relationship between each chart value and IChartPointView. Since the DrawOrMove implementation of the HorizontalBezierPointView expects this to be the case, it goes ahead and removes and re-adds the IChartPointView for each chart value, expecting it to not mutate its relative placement if the chart value already exists in the collection.

However, if the same IChartPointView is shared between two chart values, the aforementioned rule no longer holds. The result is that the same IChartPointView gets placed twice, while the number of BezierSegments only increases by one.

Workaround

If your chart values need to implement IEquatable, change the type of the chart value from an object to a struct. LiveCharts does not use equality to get the corresponding ChartPoint for value types; it uses an indexed collection.

If you need your chart values to be reference types, DO NOT OVERRIDE EQUALS. Instead, implement a custom method to check for equality, and replace your LINQ queries to use this custom method instead.

Steps to reproduce

Create a ChartValues<T> where T implements IEquatable<T>. Instantiate this collection with unique values of T. Once LiveCharts is loaded and the chart is drawn, create and add a new instance of T; a value of T where equals is true for another value of T previously added in the collection.

How to fix

In the ChartValues<T>.GetChartPoint(bool isClass, PointTracker tracker, int index, T value) method, modify the call to tracker.Referenced.TryGetValue(value, out cp) to use reference equality somehow, if that is even possible.

trympet avatar Feb 13 '21 19:02 trympet