Skip to content
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

Add support for plotly events #27

Open
sean-mcl opened this issue Jun 9, 2020 · 13 comments
Open

Add support for plotly events #27

sean-mcl opened this issue Jun 9, 2020 · 13 comments
Labels
enhancement New feature or request future work

Comments

@sean-mcl
Copy link
Collaborator

sean-mcl commented Jun 9, 2020

No description provided.

@sean-mcl sean-mcl created this issue from a note in Plotly.Blazor (To do) Jun 9, 2020
@sean-mcl sean-mcl added the enhancement New feature or request label Jun 9, 2020
@SebastianGau
Copy link

is there any workaround available until events are implemente? I need the time and data of a data point the user clicks on to update another plot

@EuanKirkhope
Copy link

EuanKirkhope commented Nov 2, 2020 via email

@sean-mcl
Copy link
Collaborator Author

sean-mcl commented Nov 2, 2020

Hi,

you could fork this project and implement a following temporary solution using the DotNetObjectReference

  • Add a action (e.g. ClickAction ) as a parameter in the Plotly.Blazor/PlotlyChart.razor to define what should happen, when the ClickEvent is triggered
public Action<double, double> ClickAction;
  • Implement a JSInvokable in the Plotly.Blazor/PlotlyChart.razor, so you can call this method from javascript interop
[JSInvokable]   
public static void ClickEvent(double x, double y)
{
        ClickAction?.Invoke();
}
  • Now subscribe to the event using the DotNetObjectReference
  1. Add this to the PlotlyBlazor/PlotlyChart.razor
public async Task SubscribeClickEvent()
{
    await JsRuntime.SubscribeClickEvent(objectReference);
}
  1. Add this to the PlotlyBlazor/PlotlyJsInterop.cs
public static async Task SubscribeClickEvent(this IJSRuntime jsRuntime, DotNetObjectReference<PlotlyChart> objectReference)
{
    await jsRuntime.InvokeVoidAsync($"{PlotlyInterop}.subscribeClickEvent", objectReference, objectReference.Value.Id);
}
  1. Add this to the Plotly.Blazor/wwwroot/plotly-interop.js
subscribeClickEvent: function(dotNetObj, id) {
        var plot = document.getElementById(id);
        plot.on('plotly_click', function(data)){
            dotNetObj.invokeMethodAsync('ClickEvent', data.points[0].x, data.points[0].y);
        }
}

  • So the basic structure should be that you can set the action in your plotlyChart instance and also subscribe to the event using plotlyChart.SubscribeClickEvent();

I haven't tested it now, but in this form it should work.

@SebastianGau
Copy link

Hi Sean!

Thank you a lot for this solution, I will try this out. Keep up the great work!

BTW Is there any rough timeline available w.r.t. fully supporting the plotly events in the blazor library?

Greetings, Sebastian

@sean-mcl
Copy link
Collaborator Author

sean-mcl commented Nov 2, 2020

Sorry, I don't have timeline.

The charts were developed for another project where no events are currently required. That's why we made it open source, so that people from outside can contribute and add these features.

As long as circular references are not yet supported by Microsoft, the deserialization from the Javascript world to the C# world is quite difficult. Once this works, it will surely be easy to find a clean solution for the events.

@sean-mcl
Copy link
Collaborator Author

sean-mcl commented Nov 2, 2020

Have seen under dotnet/runtime/issues/30820 that circular references are now supported. The only thing missing is that you can set the deserialization settings for interop methods.

@CalaxDev
Copy link

Hello!
Is there perhaps any update on this yet?

@sean-mcl
Copy link
Collaborator Author

Im currently not working on the Plotly.Blazor library. We will propbably implement it sooner or later without any ETA.
Feel free to contribute if its time critical! :)

@BenSzuszkiewicz
Copy link

BenSzuszkiewicz commented Jan 25, 2022

Hi,

you could fork this project and implement a following temporary solution using the DotNetObjectReference

  • Add a action (e.g. ClickAction ) as a parameter in the Plotly.Blazor/PlotlyChart.razor to define what should happen, when the ClickEvent is triggered
public Action<double, double> ClickAction;
  • Implement a JSInvokable in the Plotly.Blazor/PlotlyChart.razor, so you can call this method from javascript interop
[JSInvokable]   
public static void ClickEvent(double x, double y)
{
        ClickAction?.Invoke();
}
  • Now subscribe to the event using the DotNetObjectReference
  1. Add this to the PlotlyBlazor/PlotlyChart.razor
public async Task SubscribeClickEvent()
{
    await JsRuntime.SubscribeClickEvent(objectReference);
}
  1. Add this to the PlotlyBlazor/PlotlyJsInterop.cs
public static async Task SubscribeClickEvent(this IJSRuntime jsRuntime, DotNetObjectReference<PlotlyChart> objectReference)
{
    await jsRuntime.InvokeVoidAsync($"{PlotlyInterop}.subscribeClickEvent", objectReference, objectReference.Value.Id);
}
  1. Add this to the Plotly.Blazor/wwwroot/plotly-interop.js
subscribeClickEvent: function(dotNetObj, id) {
        var plot = document.getElementById(id);
        plot.on('plotly_click', function(data)){
            dotNetObj.invokeMethodAsync('ClickEvent', data.points[0].x, data.points[0].y);
        }
}
  • So the basic structure should be that you can set the action in your plotlyChart instance and also subscribe to the event using plotlyChart.SubscribeClickEvent();

I haven't tested it now, but in this form it should work.

Hi Sean,

I was wondering if you could elaborate on why you feel this solution may no longer be appropriate, and waiting on circular references would enable plot event-handling implementation better?

I am interested in having multiple graphs within a Blazor project being interact-able, with cross-filtering based on definable filters which appears to be doable if we follow the quotes guidance; allowing for parameters to be passed up to parent components to be shared.

Best wishes,
Ben

@BenSzuszkiewicz
Copy link

BenSzuszkiewicz commented Jan 31, 2022

Raised a PR for this on click event handling :) Key difference as far as I could see is that the proposed fix worked solely for double (x,y) plots whereas in the example project we have types such as dates which would benefit from the same functionality.

@centreboard
Copy link
Contributor

centreboard commented Feb 17, 2022

Returning X/Y values covers a very common use case but general click event handling in Plotly seems a bit harder.

The best reference I've found is https://plotly.com/javascript/plotlyjs-events/#event-data
But that doesn't cover thing like Pie/Sunburst which instead expose on points label/value and I'm sure there's more varients.

An alternative design for .NET 6 would be for Plotly.Blazor to expose an option to emit the events. Users could then define their own handlers (Plotly.Blazor could also expose some default handler options for convenience)

https://docs.microsoft.com/en-us/aspnet/core/blazor/components/event-handling?view=aspnetcore-6.0#custom-event-arguments

If Plotly.Blazor adds

// plotly-interop.js
emitClickEvents: function (id) {
    const plot = document.getElementById(id);
    plot.on('plotly_click', function (data) {
        const event = new CustomEvent('plotly_click', {detail: data, bubbles: true});
        plot.dispatchEvent(event);
    });
}    
// PlotlyChart.razor
+ [Parameter]
+ public bool EmitClickEvents { get; set; }

 public async Task NewPlot()
 {
     await JsRuntime.NewPlot(objectReference);
+    if (this.EmitClickEvents)
+    {
+        await JsRuntime.EmitClickEvents(objectReference);
+    }
 }
 
 // PlotlyJsInterop.cs
+ public static async Task EmitClickEvents(this IJSRuntime jsRuntime, DotNetObjectReference<PlotlyChart> objectReference)
+ {
+     await jsRuntime.InvokeVoidAsync($"{PlotlyInterop}.emitClickEvents", objectReference.Value.Id);
+ }

A user can then add

// Functions in JS to map data as required
function plotlyClickXYEventArgsCreator(event) {
    return {
        x: event.detail.points[0].x,
        y: event.detail.points[0].y
    };
}
function plotlyClickPIEEventArgsCreator(event) {
    return {
        label: event.detail.points[0].label,
        value: event.detail.points[0].value
    };
}

// Map the original event to custom names with different handlers
Blazor.registerCustomEventType('plotly_click_pie', {
  browserEventName: 'plotly_click',
  createEventArgs: plotlyClickPIEEventArgsCreator
});
Blazor.registerCustomEventType('plotly_click_xy', {
  browserEventName: 'plotly_click',
  createEventArgs: plotlyClickXYEventArgsCreator
});
// Add EventArgs as appropriate, e.g. have different type where X is a string or a double
public class XYClickEventArgs : EventArgs {
    public string X { get; set; }
    public int Y { get; set; }
}
public class PieClickEventArgs : EventArgs {
    public string Label { get; set; }
    public int Value { get; set; }
}

// Map custom event names to matching class
[EventHandler("onplotly_click_xy", typeof(XYClickEventArgs), enableStopPropagation: true, enablePreventDefault: true)]
[EventHandler("onplotly_click_pie", typeof(PieClickEventArgs), enableStopPropagation: true, enablePreventDefault: true)]
public static class EventHandlers { }

Having set that up, subscribe to the appropriate event for the chart type

<PlotlyChart ... @onplotly_click_xy="OnClick"/>
@code
{
    void OnClick(XYClickEventArgs eventArgs) {
        Console.WriteLine($"{eventArgs.X} {eventArgs.Y}");
    }
}

A nice thing about this pattern is it easily extends to other events, e.g. if the parameter was a [Flags] enum with all the event names and the interop subscribed to the requested ones:

emitEvents: function (id, eventNames) {
    const plot = document.getElementById(id);
    for (const eventName of eventNames) {
        plot.on(eventName, function (data) {
            const event = new CustomEvent(eventName, {detail: data, bubbles: true});
            plot.dispatchEvent(event);
        });
    }
}

@captaindakkar
Copy link

Hi, do the chart's ConfigChanged, LayoutChanged events work? Trying to sync the state of the Config and Layout objects as they don't seem to persist in between subsequent React() calls.

@vgb1993
Copy link

vgb1993 commented Jul 12, 2022

I also have the need to listen to events. In particular Parallel Coordiantes axis restyled event.

I am prety new to Blazor and don't fully understand how to create custom EventListeners.

It would be great to have events. Such a good charting lib.

I will look deeper into it.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request future work
Projects
Plotly.Blazor
  
To do
Development

No branches or pull requests

8 participants