mariusmuntean / ChartJs.Blazor

Brings Chart.js charts to Blazor
https://www.iheartblazor.com/
MIT License
676 stars 151 forks source link

OnClick not working for larger datasets #177

Open briantacker opened 3 years ago

briantacker commented 3 years ago

Describe the bug

When using the OnClick event, it seems it causes the client to disconnect from the server when the dataset is over a certain size. When I click the chart, Im seeing the "Attempting to reconnect to server" message. I also see this message in the browser console: Error: Connection disconnected with error 'Error: Server returned an error on close: Connection closed with an error. However I see no errors on the server side output I noticed if I click on an empty area in the chart, the OnClick does in fact get called, regardless of the size of the dataset, Im assuming this is due to the JArray being empty. When clicking a value is seems like its passing the chart data in its entirty to the OnClick event. Is it possible to only send the necessary information (such as dataset index and value index)?

Which Blazor project type is your bug related to?

Which charts does this bug apply to?

I tested with BarChart and PieChart

To Reproduce

Using version 2.0.2 of ChartJSBlazor Below is the sample code Im using in order to reproduce When the chart first loads with 10 values, it works fine. When you add another dataset, that's when the OnClick fails to be called. Also if you click the "Add Bar" button a couple times, around the 13th for 14th bar the OnClick no longer works on the chart.

Code example

@using ChartJs.Blazor.BarChart
@using ChartJs.Blazor;
@using ChartJs.Blazor.Common.Handlers;
@using ChartJs.Blazor.Interop;
@using Newtonsoft.Json.Linq;
@using System;
@inject IJSRuntime JsRuntime;

<button class="btn btn-primary" @onclick="AddDataSet">Add DataSet</button>
<button class="btn btn-primary" @onclick="AddBar">Add Bar</button>

<Chart Config="@_config" />

@code {

    BarConfig _config;

    protected override void OnInitialized()
    {
        _config = new BarConfig { Options = new BarOptions() };
        _config.Options.OnClick = new DelegateHandler<ChartMouseEvent>(ChartClicked);
        var ds = new BarDataset<int>();
        _config.Data.Datasets.Add(ds);
        ds.Label = "Dataset #1";
        for (int i = 0; i < 10; i++)
        {
            ds.Add(new Random().Next(5, 100));
            _config.Data.Labels.Add($"Value #{i + 1}");
        }
    }

    protected void ChartClicked(JObject mouseEvent, JArray array)
    {
        JsRuntime.InvokeAsync<object>("alert", "Chart Clicked");
    }

    private void AddDataSet()
    {
        var ds = new BarDataset<int>();
        _config.Data.Datasets.Add(ds);
        ds.Label = "Dataset #" + _config.Data.Datasets.Count;
        for (int i = 0; i < ((BarDataset<int>)_config.Data.Datasets[0]).Count; i++)
            ds.Add(new Random().Next(5, 100));
    }

    private void AddBar()
    {
        foreach (BarDataset<int> ds in _config.Data.Datasets)
        {
            ds.Add(new Random().Next(5, 100));
        }
        _config.Data.Labels.Add($"Value #{((BarDataset<int>)_config.Data.Datasets[0]).Count}");
    }
}
Chris-Mingay commented 3 years ago

I am seeing similar in something I am working on, however it seems like sometimes it works, other times it just reloads the page (with no apparent error or exception)

briantacker commented 3 years ago

As a workaround, I was able to make a change to the Interop code that returns only the index of the dataset and index of the value. In the Interop\TypeScript\ChartJsInterop.ts file, here is what I changed the wire up function to:

private wireUpOptionsOnClick(config: ChartConfiguration) {
    let getDefaultFunc = type => {
        let defaults = Chart.defaults[type] || Chart.defaults.global;
        return defaults?.onClick || Chart.defaults.global.onClick;
    };

    if (!config.options)
        return;

    // Previous Code:   config.options.onClick = this.getMethodHandler(<IMethodHandler>config.options.onClick, getDefaultFunc(config.type));

    // New Code:
    let handler = this.getMethodHandler(<IMethodHandler>config.options.onClick, getDefaultFunc(config.type));
    config.options.onClick = (...args) => {
        let chart = args[1][0]["_chart"];
        let evt = args[0];
        let element = chart.getElementAtEvent(evt)[0];
        handler(element._datasetIndex, element._index);
    };
}

Obviously this will severly break the OnClick event, so I also changed the Common\Handlers\ChartMouseEvent.cs to this:

namespace ChartJs.Blazor.Common.Handlers
{
    /// <summary>
    /// A delegate for all sorts of mouse events on a chart.
    /// </summary>
    /// <param name="datasetIndex">Index of the dataset affected byt the mouse event.</param>
    /// <param name="valueIndex">Index of the value affected by the mouse event.</param>
    public delegate void ChartMouseEvent(int datasetIndex, int valueIndex);
}

This will also break the OnHover event since I did not change anything with that in the typescript file, but the change there would be similar. I'm not using that event so it wasn't necessary for me.

This solution probably won't work for everyone because you may need a different type of data than me, but I think it may be a step in the right direction for anyone else experiencing the issue I encountered.