plotly / plotly.js

Open-source JavaScript charting library behind Plotly and Dash
https://plotly.com/javascript/
MIT License
16.92k stars 1.86k forks source link

plotly_legendclick is sending the old state #6471

Open filipesantiagoAM opened 1 year ago

filipesantiagoAM commented 1 year ago

Hi All,

I'm attaching one event to the plotly_legendclick, but when I'm clicking in the legend I get an event with the data but the line I've clicked comes with the parameter visible with the previous state instead of the current one.

Reproduce Steps

Codepen: https://codepen.io/fsantiago99/pen/BaxYQJw Click in BB and toggle it again, the alert is showing the variable visible.

I'm not sure if it is a feature or a bug but please have a look here.

archmoj commented 1 year ago

Looks like a bug to me. @alexcjohnson what do you think?

filipesantiagoAM commented 1 year ago

@archmoj @alexcjohnson Any ideas here?

alexcjohnson commented 1 year ago

Yes, looks like a bug to me. I think the root is the event is emitted too early, meaning this bug probably dates to when we added double-click-to-isolate-one-trace. The solution would be to wait to emit the legendclick event until we know it's not a doubleclick and we actually process the change. That would be different from regular browser click events, but I think it makes sense in this case since what you care about isn't really the click but the change to the figure, and we don't know what that change will be until later.

kearabu18 commented 1 year ago

Hi, I'm also running into this issue and wondering if anyone knows of a workaround for the time being. I'm wanting to link events across several plots so a user can view and filter data across several plots with just one interaction. I was able to achieve this with the hover events, but for the same reason as stated above have not been able to achieve with legendclicks. I've been trying to access the visible state of a trace, but it is always one state behind.

I did notice that when the plot is initially created, there is no 'visible' property unless you initiate the trace data with "visible: true". The visible property does seem to be set after a relayout/restyle event is triggered.

I played with the doubleclickdelay configuration setting to see if I could force the event to wait for the double click delay timeout to complete prior to sourcing the current chart's data, but I don't think this setting works as I expected because it seriously reduced response time.

I'll keep trying to find a workaround in the meantime, but just wanted to bump this issue since it's also relevant for my project.

Thanks!

kearabu18 commented 1 year ago

I believe I may have found a workaround for anyone who needs a quick, but probably not efficient solution. I don't have time to make a fiddle, but have included some code to help get you started.

Instead of using the legendclick event, I use the restyle event which fires after a legendclick event. Restyle includes event data in the format [updateObj, traceArray]. I just had to track some extra variables to understand which plot the event was initiated from and prevent infinite loops for each subsequent plots' restyle event.

I use the legend setup: itemclick: 'toggleothers', itemdoubleclick: 'toggle'. You can probably accomplish something similar for itemdoubleclick, but I haven't tried yet.

// These are for event handling ------
// vars is an array of objects with metadata about the plots I'm generating
var eventInitiator = 'none';
// Get a list of all plots
var myplotList = [];
$.each(vars, function(idx,row) {
    if (row.varType !== 'x') {
        myplotList.push(row.varName);
    }
});
var updatePlotList = [...myplotList]; 

// This event listener is added in another $.each(vars, function(idx,row) loop 
currentChart.on('plotly_restyle', function(ev) {
    // Track event initiator and get full list of plots to update
    // This prevents other plots from triggering infinite loops by only
    // allowing the initiating plot to manage restyles
    if (eventInitiator === 'none') {
        eventInitiator = row.varName;

        // Now update all plots
        $.each(vars, function(idx1,row1) {
            if ((row1.varName !== row.varName) && (row1.varType !== 'x')) {
                let otherChart = document.getElementById(row1.varName + '_Chart');
                Plotly.restyle(otherChart, ev[0], ev[1]);
            }
        });
    }

    // Track which plots we've already updated
    updatePlotList.splice(updatePlotList.indexOf(row.varName), 1);

    // Once all updates complete, reset event initiator
    if (updatePlotList.length === 0) {
        eventInitiator = 'none';
        updatePlotList = [...myplotList];
    }
})