beto-rodriguez / LiveCharts2

Simple, flexible, interactive & powerful charts, maps and gauges for .Net, LiveCharts2 can now practically run everywhere Maui, Uno Platform, Blazor-wasm, WPF, WinForms, Xamarin, Avalonia, WinUI, UWP.
https://livecharts.dev
MIT License
4.38k stars 572 forks source link

Making LegendPosition not hidden for CartesianChart causes crash in Avalonia #1557

Closed MajesticBevans closed 2 months ago

MajesticBevans commented 3 months ago

Describe the bug When trying to add a legend to a cartesian chart in Avalonia (v11.1.1), an IndexOutOfRangeException is thrown within the livecharts dlls. This happens within the SKDefaultLegend.cs file, in the Draw method when trying to add the visual to the chart, and only seems to occur when the cartesian chart YAxes property is bound to an empty collection.

To Reproduce Steps to reproduce the behavior:

  1. Create a Cartesian chart as follows in the View: <lvc:CartesianChart LegendPosition="Right" YAxes="{Binding YAxes}" />
  2. Create a collection in the ViewModel called YAxes and initialise it to an empty collection: [ObservableProperty] private ObservableCollection<Axis> _yAxes = [];
  3. Run and see error:

System.IndexOutOfRangeException HResult=0x80131508 Message=Index was outside the bounds of the array. Source=LiveChartsCore StackTrace: at LiveChartsCore.VisualElements.VisualElement1.Invalidate(Chart1 chart) at LiveChartsCore.Chart1.AddVisual(ChartElement1 element) at LiveChartsCore.SkiaSharpView.SKCharts.SKDefaultLegend.Draw(Chart1 chart) at LiveChartsCore.Chart1.DrawLegend(Single& ts, Single& bs, Single& ls, Single& rs) at LiveChartsCore.CartesianChart1.Measure() at LiveChartsCore.Chart1.b__127_1() at Avalonia.Threading.DispatcherOperation.InvokeCore() at Avalonia.Threading.DispatcherOperation.Execute() at Avalonia.Threading.Dispatcher.ExecuteJob(DispatcherOperation job) at Avalonia.Threading.Dispatcher.ExecuteJobsCore(Boolean fromExplicitBackgroundProcessingCallback) at Avalonia.Threading.Dispatcher.Signaled() at Avalonia.Win32.Win32Platform.WndProc(IntPtr hWnd, UInt32 msg, IntPtr wParam, IntPtr lParam) at Avalonia.Win32.Interop.UnmanagedMethods.DispatchMessage(MSG& lpmsg) at Avalonia.Win32.Win32DispatcherImpl.RunLoop(CancellationToken cancellationToken) at Avalonia.Threading.DispatcherFrame.Run(IControlledDispatcherImpl impl) at Avalonia.Threading.Dispatcher.PushFrame(DispatcherFrame frame) at Avalonia.Threading.Dispatcher.MainLoop(CancellationToken cancellationToken) at Avalonia.Controls.ApplicationLifetimes.ClassicDesktopStyleApplicationLifetime.Start(String[] args) at Avalonia.ClassicDesktopStyleApplicationLifetimeExtensions.StartWithClassicDesktopLifetime(AppBuilder builder, String[] args, Action`1 lifetimeBuilder) at Sidewinder.Program.Main(String[] args) in ...\Program.cs:line 20

Expected behavior Expect the app not to crash.

Desktop (please complete the following information):

Additional context Binding the YAxes to an empty collection is only a problem when I change the LegendPosition to not hidden, so I don't believe this should cause a problem. This crash occurs for all the LegendPositions except for hidden. I believe this is an issue with trying to draw the legend somewhere outside of the bounds of the chart itself. Perhaps because it is not accounting for the width of the axes labels? Not sure. The problem should be simple to recreate, and this may only be an Avalonia issue.

The same behaviour occurs when the YAxes collection is cleared mid-execution.

MajesticBevans commented 3 months ago

Note: simple workaround is just to always initialise YAxes collection with one element e.g. new Axis()

beto-rodriguez commented 2 months ago

Hello and thanks for the report.

I think that there is no issue here, the concept of an axis is required to measure the chart, I mean a CartesianChart by concept requires both X and Y, we could consider this a limitation of the library but.... it is much better than hacking the concept of a Cartesian chart...

As you mentioned in your workaround, let's just make the system happy by adding and instance of both axes, that is a requirement for a chart to measure properly, actually by default Axes are initialized as you stated.

I will close this for now, I think this "issue" is understandable since it is a requirement by the Cartesian Coordinate system, feel free to let me know your opinion on this one.

MajesticBevans commented 2 months ago

I agree it makes sense that CartesianCharts should have both an X and Y axis in concept, but I think the behaviour of the library should probably be consistent on needing that, whether there is a legend displayed or not.

If YAxes is an empty collection, but the legend is hidden, there is no crash. If the legend is not hidden (even though there are no series plotted) there is a crash.

IMO, it should either crash in both cases, or in neither. I'd be interested to hear what others think.

beto-rodriguez commented 2 months ago

@MajesticBevans I agree, the referenced commit will make it throw always!