-
-
Notifications
You must be signed in to change notification settings - Fork 529
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Passing Function To ChartJS #313
Comments
I have also tried to come up with passing the functions to js a few months ago. As far as I know it's not possible to serialize or deserialize them. I think the best way and maybe only way is to have a js function body saved as a string in blazor object and then do some magic on javascript side. But that would probably need to be done manually for every possible function supported by the chartjs. Something that you already tried yourself. Now that I think of maybe there is a better way. Maybe register event handler in javascript and then pass it to through dotnetadapter to be executed on the blazor side. If would still require to manual map all the function. Definitely is not an easy task. |
Yep, in the end went with this which works but not very dynamic...
Then defining the function as something like:
Then the JSON parse picks that it has Function in the name and looks for it and wires it together. Feels uber hacky, don't like the idea of having to go back and forth over the interop just to wirse things together. |
Painful when it comes to formatting labels and tool tips mind, especially dealing with the fact that the chart could be for say different currencies that require different ways of formatting (thousands separator and symbol for instance). |
This just doesn't work either :(
Whilst the value changes, the function doesn't - gets locked in with the creation of the function and does not re-evaluate what the variable has. Can't seem to find a way round that. |
I would rather go the other way instead of messing with javascript functions. Just hook the event handlers and pass them to Blazor. Do this every time data or options are initialized or updated... FYI, this is just some pseudo-code to give you an idea. I'm not sure if it's even possible if ( chart.options.tooltips ) {
chart.options.tooltips.ItemSort:(a, b) => {
dotnetAdapter.invokeMethodAsync("TooltipsItemSortHandler", a, b);
}
} Then on the Blazor side: public Task TooltipsItemSortHandler(SomeModel a, SomeModel b){
return TooltipsItemSorting.InvokeAsync(a, b);
}
[Parameter] public EventCallback<TooltipsItemSortEventArgs> TooltipsItemSorting { get; set; } Now with this approach the good thing is that anyone can use event handlers in razor. LineChart @ref="lineChart" TItem="double" TooltipsItemSorting="@((a,b)=>b.DatasetIndex - a.DatasetIndex)" /> The bad thing is that every possible event had to be mapped. And since allot of jsinterop is involved it can be pretty slow. And don't forget the Blazor serializer is in pretty bad state now so maybe it won't be possible to do it. |
I will reopen this so I can investigate more sometimes in the future. A lot of refactoring is going for 0,8.7 so I need some time. |
Until the serializer is fixed, hand crafting the JSON is the only way to get more complex charts without breaking things, for instance wanting to set the min and max of the chart such that Y-axis doesn't jump up and down as you change data. Will have a play next weekend if I get the chance on using the interop, formatting the labels and the tooltips is definitely on my must do list. Will share how I get on. |
In terms of registering the callbacks, would it be best to do so following the same pattern you already have with HasDelegate on the initializa:
Don't like the idea of having to infer if there is a call back just on the basis of the presence of say the tooltip. So far I can see the following call backs I would use:
May want to do more with the Tooltips, though there a lot of them and passing them in the constructor is somewhat ugly. |
Instead of passing every callback as a boolean flag (Clicked.HasDelegate) maybe create an callback options class. class ChartCallbacks
{
public bool Clicked {get;set;}
public bool Hovered {get;set;}
public ChartTooltipCallbacks Tooltips {get;set;}
// other flags
}
class ChartTooltipCallbacks
{
public bool ItemSorting {get;set;}
// other flags
} Then populate it and pass it through the |
Making progress yet feels like one step forward and two back. Wired in everything for the tool tip item sorting function:
Can see the adapter method being called: [JSInvokable]
public Task TooltipItemSortHandler ( string a, string b)
{
Console.WriteLine(a);
Console.WriteLine(b);
var tooltipItemA = JsonSerializer.Deserialize<TooltipItem>(a);
var tooltipItemB = JsonSerializer.Deserialize<TooltipItem>(b);
Console.WriteLine(tooltipItemA.DatasetIndex);
Console.WriteLine(tooltipItemB.DatasetIndex);
return chart.TooltipItemSortHandler(tooltipItemA, tooltipItemB);
} The problem is that deserializing is not working, possibly because the TooltipItem has a field called XLabel and YLabel which are deprecated in ChartJS which aren't in my model. Further complicated that XLabel and YLabel could be either a string or number. Any suggestions as how best to deal with this. Newtonsoft would just ignore the field. The model: public class TooltipItem
{
/// <summary>
/// The label of the tooltip
/// </summary>
public string Label { get; set; }
/// <summary>
/// The value of the tooltip (likely to be a number depending on the chart type)
/// </summary>
public string Value { get; set; }
/// <summary>
/// Index of the dataset the item comes from
/// </summary>
public int DatasetIndex { get; set; }
/// <summary>
/// Index of this data item in the dataset
/// </summary>
public int Index { get; set; }
/// <summary>
/// X position of matching point (not sure this is an integer)
/// </summary>
public double X { get; set; }
/// <summary>
/// Y position of matching point (not sure this is an integer)
/// </summary>
public double Y { get; set; }
} Example of the serialised item received by the handler:
|
Okay cue face palm... need to pass in options to ignore case miss match: [JSInvokable]
public Task<int> TooltipItemSortHandler ( string a, string b)
{
var options = new JsonSerializerOptions
{
PropertyNameCaseInsensitive = true
};
var tooltipItemA = JsonSerializer.Deserialize<TooltipItem>(a, options);
var tooltipItemB = JsonSerializer.Deserialize<TooltipItem>(b, options);
return chart.TooltipItemSortHandler(tooltipItemA, tooltipItemB);
} So now on to the next head scratcher, C# needs to return a value for this call back to be of any use and it's an integer in this case hence the change to Task in the signature. The challenge then is how to wire that into the EventCallBack which returns a Task not a Task and cannot see how to wire that in: [Parameter] public EventCallback<TooltipItemSortingEventArgs> TooltipItemSorting { get; set; } Is there something I'm missing or do I need to change it from an EventCallback? |
And then I think equally big show stopper with this approach of passing in call backs:
Does not work in the Chart JS callback because it's returning a promise there is no way that the callback can return the result as the function is required to as per the comment here:
|
Hi, sorry for not answering before, I was on a trip for a weekend. Anyways back to your questions. I think you're on a right track. For your first problem with XLabel and YLabel I think the best options is to have them as strings as that would cover most of the scenarios. This is if the serializer is going to work. For the second problem. I cannot test it to be 100% sure, but I think you can use |
Hey the weekend is for coding (or the only time I get for it unfortunately); The XLabel and YLabel were not the issue, just needed to pass in the serializer options to ignore the case. Have uploaded the code to a branch in my repo so you can pull apart, hopefully you can see what I mean: https://github.com/andrewwilkin/Blazorize/tree/chartjs-callback-interop All testing has been with the bar chart, the issue, could only get it returning a value when it was a Task but then the promises issue bit me. |
Here someone describes how they sorted out labels in VueJS: The challenge is that works only when dealing with chart wise config, the tool tip or formatting of scale cannot be simply a result. Will carry on looking but nothing as yet jumps out as a solution. |
I made some minor modifications and it seems to work. First I converted [Parameter] public Func<TooltipItemSortingEventArgs, int> TooltipItemSorting { get; set; } From there the changes are pretty straightforward. Handler: public Task<int> TooltipItemSortHandler( TooltipItem a, TooltipItem b )
{
Console.WriteLine( "TooltipItemSortHandler Called" );
var args = new TooltipItemSortingEventArgs( a, b );
var result = TooltipItemSorting?.Invoke( args ) ?? 0;
return Task.FromResult( result );
} Test Page: <Chart @ref="barChart" Type="ChartType.Bar"
Clicked="@(async (ChartMouseEventArgs) => ClickHandlerTest(ChartMouseEventArgs))"
- TooltipItemSorting="@(async (TooltipItemSortingEventArgs) => TooltipItemSortingTest(TooltipItemSortingEventArgs))"
+ TooltipItemSorting="@((TooltipItemSortingEventArgs) => TooltipItemSortingTest(TooltipItemSortingEventArgs))"
TItem="double" /> With the promise callback in javascript it seems the call-stack is in the right order. So I think you're on a good track. |
Thanks for looking at that, though the biggest issue as I see it is that it is not possible to use a promise with ChartJS options which renders this approach as unworkable for the majority of call backs. Could work around it in client side Blazor by not using the async version. Unless I'm missing something. |
@stsrki Given the mess that is with the options and datasets, am going to look to do this as a separate object and look at using numeral.js for providing a locale specific way of doing things. The challenge is making it generic enough beyond my use cases, and because the namespace is Blazorise.Charts, cannot just bring that code locally as it conflicts with the other Blazorise libraries. Before I go head long down this route, any advice how I can contribute back my learnings? |
Interesting approach. I guess there is going to be a lot of callback functions needed to implement. I'm still not sure if it's going to fix our problem with promises. Anyway I'm really interested in what you're going to do so please keep me posted. Also, yeah I think the best route is to create a separate library with it's own namespace so that it can be reused by anyone. |
Hi there! I started using Blazorise.Charts this week and I really appreciate the work you have done. A lot is possible and the way of setting up the charts is very clear. The only thing missing is customizing tooltips. Is there any progress on the tooltip callback so we can modify the tooltip and its return value? I would really appreciate any help because for me thats the final piece missing. Yours sincerely Thijs |
The challenge as I recall (which motivated me to switch to a commercial offering) was that there was no simple way of having a callback that ChartJS does not work with promises. If you're prepared to go with synchronous callbacks you probably could get this working with interop: |
I'm experimenting with serializing of custom JS function and early tests look promising. Nothing is for sure but if it keeps working I might add a function callback in the 0.9.5 version. |
@stsrki Thank you for the effort! If custom tooltips are possible I would be very happy! Love blazorise. I recall: Any examples? I don't know how to do this and how to pass data to the Javascript? |
Any news on this? I am trying to work around this issue right now just to get this functionality in graph tooltip customization. |
We have limited support for chart callbacks. Can you give us an example of what part of the chart you want to customize and how to use it? |
In our case, we would like to customize the tooltips to provide more data than the data shown in the graph. |
Can you provide us with a code sample? Preferably if there is something similar on https://www.chartjs.org/docs/latest/samples/tooltip/content.html |
ChartJS uses functions in the chart config, such as changing the sort order on tool tips for instance, e.g.
The challenge I'm having is working out how to pass this from C# to JavaScript given the serialization and deserialization that is going on and am left scratching my head to work out if it is even possible.
Tried adding some logic in the JSON.Parse in the JavaScript and parsing like this:
But that does not seem to work either. Any ideas?
The text was updated successfully, but these errors were encountered: