mariusmuntean / ChartJs.Blazor

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

Circular JSON convert exception when clicking barchart bar #155

Closed larschristensen20 closed 3 years ago

larschristensen20 commented 3 years ago

Hello, I've recently dabbled a bit on how to use certain plugins in combination with this project (specifically the datalabels plugin).

Disclaimer: there is a good chance you have no clue why I am getting these errors, but asking here is worth a try!

My Blazor Service-side project features a fully-fledged barchart with multiple 'levels' where deeper (and more detailed) levels can be accessed by pressing on bars in the chart. For example Level 1 -> Year view Level 2 -> Month view Level 3 -> Day view Level 4 -> Hour view.

In my config I add a OnClickHandler


_config = new BarConfig(ChartType.Bar)
                {...},
                        OnClick = new DotNetInstanceClickHandler(OnClickHandler)
                    }
                };

Which is defined as such:


[JSInvokable]
    public void OnClickHandler(object sender, object args)
    {

            var clickedLabel = GetClickedLabel(args.ToString());
            if (clickedLabel != "")
            {

                switch (state.CurrentState)
                {
                    case State.Year:
                        state.SelectedYear = clickedLabel;
                        state.CurrentState = State.Month;
                        UpdateGraph(readings.Table1);
                        btnYearClass = "btn-show";
                        this.StateHasChanged();
                        break;
                    case State.Month:
                        state.SelectedMonth = state.GetIndexOfMonth(clickedLabel);
                        state.CurrentState = State.Day;
                        UpdateGraph(readings.Table2);
                        btnMonthClass = "btn-show";
                        this.StateHasChanged();
                        break;
                    case State.Day:
                        if (supplier.ConsumptionDetailLevel > 3)
                        {
                            state.SelectedDay = clickedLabel;
                            state.CurrentState = State.Hour;
                            UpdateGraph(readings.Table3);
                            btnDayClass = "btn-show";
                            this.StateHasChanged();
                            break;
                        }
                        break;
                }
            }
        }
    }

This works perfectly!

The Exception

I have then added a reference to the datalabels plugin for chartjs in my _Host.cshtml <script src="https://cdn.jsdelivr.net/npm/chartjs-plugin-datalabels@0.7.0/dist/chartjs-plugin-datalabels.min.js"></script> Which results in this chart as I load my chart page: image

Looks fine?

However, whenever I try to click a bar to move to a deeper level, I get this exception:

blazor.server.js:8 Uncaught (in promise) TypeError: Converting circular structure to JSON
    --> starting at object with constructor 'ni'
    |     property 'config' -> object with constructor 'Object'
    |     property 'data' -> object with constructor 'Object'
    |     ...
    |     index 0 -> object with constructor 'i'
    --- property '_chart' closes the circle
    at JSON.stringify (<anonymous>)
    at c (blazor.server.js:8)
    at e.invokeMethodAsync (blazor.server.js:8)
    at ni.<anonymous> (ChartJsInterop.ts:269)
    at ni.handleEvent (Chart.min.js:7)
    at ni.eventHandler (Chart.min.js:7)
    at i (Chart.min.js:7)
    at HTMLCanvasElement.Fe.<computed> (Chart.min.js:7)

Looking at line 269 in ChartJsInterop:

           // .Net instance method
            else if (typeof iClickHandler === "object" &&
                iClickHandler.hasOwnProperty('instanceRef') &&
                iClickHandler.hasOwnProperty('methodName')) {
                return (() => {
                    const onClickInstanceHandler: { instanceRef: DotNetObjectReference, methodName: string } = <any>iClickHandler;
                    const instanceRef = onClickInstanceHandler.instanceRef;
                    const methodName = onClickInstanceHandler.methodName;

                    return async (sender, args) => {

                        // This is sometimes necessary in order to avoid circular reference errors during JSON serialization
                        args = this.GetCleanArgs(args);

                        await instanceRef.invokeMethodAsync(methodName, sender, args);
                    };
                })();
            }
        } else { // fallback to the default
            return chartJsDefaultHandler;
        }
    };

    private GetCleanArgs = (args) => {
        // ToDo: refactor the function to clean up the args of each chart type 
        return typeof args['map'] === 'function' ?
            args.map(e => {
                    const newE = Object.assign({}, e, {_chart: undefined}, {_xScale: undefined}, {_yScale: undefined});
                    return newE;
                }
            ) : args;
    };

There is the GetCleanArgs method to avoid circular reference errors, so it seems something similar has been dealt with before. Any clue whats missing, as I imagine that the datalabels plugin messes with this in some way?

Let me know if you need any additional resources from me.

larschristensen20 commented 3 years ago

It looks like the '_chart' and 'chart' properties under the datalabel arg are the culprits: image

larschristensen20 commented 3 years ago

Setting a break point at the JSON.Stringify method (in blazor.server.js) image

Heres what i contains: image

and running

i[1][0]["$datalabels"] = undefined in the console allows me to bypass this exception.

However, this is neither convenient nor does it feel correct.

Joelius300 commented 3 years ago

Hey @larschristensen20

Thank you for all those details, really refreshing to see on an Open Source project :)

This is a known issue in the current version and I fixed it in #70 (and no, there has not been a new release in the 5 months since then 😅).
I'm hoping to release version 2.0 in the upcoming weeks (no promises). Until then, your best bet is to download or clone this repository and reference it directly. #70 is also in the preview-1 which is pushed to nuget. However there have been lots of other changes since then so you're doing yourself a favour if you reference the current master instead of the nuget version.

Please tell us how it went 😄

Ps. Is the project you're working on Open Source? Multiple people have wanted to use this and other plugins (#143, #63, #79, #40) and I'm sure they'd love a sample. Maybe we could also add a sample to the repo to show off certain plugins (but see #122 for that matter).
What I'm trying to say is we'd love to see how you're using that plugin together with our library (you can also open a new issue for that) :)

larschristensen20 commented 3 years ago

Thanks for your answer @Joelius300 (and work!)

I did figure as much, from looking at some of the older issues :-) However, I wanted to give it a thorough try as well as confirm my findings with you, so thanks for confirming!

I'll happily wait for the new features, I'm looking forward to using them once you have them ready. Sadly my project is not open source, so I cannot share the entirety of the source code here, however, I wouldn't mind providing a sample once I get one to work.

To everyone that stumbles upon this issue, I would rather wait until I can get my fingers on the complete 2.0 release to continue down this path, so for now I will not prioritize getting plugins to work. When 2.0 is released that will be my priority, and I will try to find some extra time to provide a sample on doing so.

Thanks again for your continued devotion to the project @Joelius300 , as far as I am concerned this is the best chart framework for Blazor!

Joelius300 commented 3 years ago

Hey, I just released version 2.0. Because this bug is fixed in the latest version, I'm closing this issue here.

But for the more interesting part, are you still interested in providing a sample with a Chart.js plugin? :smile:

larschristensen20 commented 3 years ago

Thanks a lot @Joelius300. At the moment I am busy conducting internal tests on another project, at my work place, so I sadly cannot devote much time to this (yet), I'm hoping I'll have more time to update to 2.0 as well as provide a sample in December. I'll stay in touch!