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.27k stars 552 forks source link

KeyNotFoundException Rendering Pie Chart into Skia view #1323

Closed girlpunk closed 11 months ago

girlpunk commented 11 months ago

Describe the bug

System.Collections.Generic.KeyNotFoundException: The given key '0' was not present in the dictionary.
at System.Collections.Generic.Dictionary`2.get_Item(TKey key)
at LiveChartsCore.Kernel.Stacker`1.GetStack(ChartPoint point, Int32 seriesStackPosition)
at LiveChartsCore.Measure.StackPosition`1.GetStack(ChartPoint point)
at LiveChartsCore.PieSeries`5.Invalidate(Chart`1 chart)
at LiveChartsCore.Chart`1.AddVisual(ChartElement`1 element)
at LiveChartsCore.PieChart`1.Measure()
at LiveChartsCore.SkiaSharpView.SKCharts.InMemorySkiaSharpChart.DrawOnCanvas(SKCanvas canvas, SKSurface surface, Boolean clearCanvasOnBeginDraw)
at LiveChartsCore.SkiaSharpView.SKCharts.InMemorySkiaSharpChart.GetImage()

To Reproduce Steps to reproduce the behavior:

// Get all the values of an enum
var series = ((MyEnumType[]) Enum.GetValues(typeof(MyEnumType)))
    // Take a list, group it by a property of that enum, and count the groupings
    .GroupJoin(
        rows,
        static myEnumType=> myEnumType,
        row => row.propertyOfEnumType,
        static (prop, rowList) => new { prop, count = rowList.Count() }
    )
    // Sort the groupings by the property value, to ensure they always appear in the same order
    .OrderBy(static g => g.prop)
    // Create a new PieSeries from each grouping
    .Select(
        g => new PieSeries<int>
        {
            // Single value with the number of rows having that enum set
            Values = new[] { g.count },
            // Get a descriptive label for the value
            Name = g.prop.GetEnumDescription(),
            // Give it a colour
            Fill = new SolidColorPaint(
                SKColor.Parse(
                    EnumColours[g.prop]
                )
            ),
            // Make the chart look nice
            Pushout = 8,
        }
    );

// Create a new pie chart
var chart = new SKPieChart
{
    // Render at a higher resolution, so the chart looks good when zoomed in
    Width = (int) size.Width * 4,
    Height = (int) size.Height * 4,
    Background = SKColors.Transparent,
    Series = series,
};

// Get an image of the chart, to put into Skia
using var image = chart.GetImage(); // <-- Exception thrown here
using var data = image.Encode();
return data.ToArray();

Expected behavior The chart should render

Screenshots Expected outcome is something like this image

Desktop (please complete the following information):

Additional context Did some testing with different versions, this works correctly up to 2.0.0-beta.920, but is broken in 2.0.0.rc1

beto-rodriguez commented 11 months ago

Hi and thanks for the report.

In general, it is not a good idea to use an IEnumerable as the data source, because that expression is evaluated every time the chart is rendered, I was able to reproduce, but you can easily fix it by materializing your LINQ expression:

var series = ((MyEnumType[]) Enum.GetValues(typeof(MyEnumType)))
    // Take a list, group it by a property of that enum, and count the groupings
    .GroupJoin(...)
    // Sort the groupings by the property value, to ensure they always appear in the same order
    .OrderBy(static g => g.prop)
    // Create a new PieSeries from each grouping
    .Select(...)
    .ToArray(); // <-- MATERIALIZE IT TO AN ARRAY OR LIST

This needs better docs, at this point the type IEnumerable seems the best choice for the library, but it seems that it also causes some confuision.

I will close this for now but feel free to reply if that does not fix the issue or if you think that the library should work with the IEnumerable type.