I’m trying to change the behavior of the event ‘plotly_legendclick’ on a graph I have loaded from a callback, but when I try to add this to the graph, I get the error that myGraph.on is not a function
.
A figure was returned to myGraph, and I am calling the script from a clientside_callback:
clientside_callback(
ClientsideFunction(namespace='clientside', function_name='legendclick'),
Output('render-delay', 'children'),
Input('myGraph', 'figure'),
)
And the javscript is simply:
window.dash_clientside = Object.assign({}, window.dash_clientside, {
clientside: {
legendClick: (myGraph) => {
alert('Called');
console.debug(myGraph);
graph = document.getElementById('myGraph');
graph.on('plotly_legendclick', () => {
alert('plotly_legendclick was called');
return false
});
}
}
});
I get the alert, and the figure in the console, but the function ‘on’ fails. This is how plotly.js says to handle events, although I am not running Plotly.newPlot to generate the plot. Is it possible to do this when the figure is being returned through dash?
Hello @levijhall,
Welcome to the community!
To set up an event listener, you do it like you have it here. However, I would use the id of the graph instead of the figure. As anytime the figure data updates, it would cause it to readd the event listener.
Another thing would be to return window.dash_clientside.no_update after adding the event listener. I would also change the output of the callback to be the id of the graph, as you can’t directly return data from an event listener like you can inside a callback.
With that said, you can utilize a new function in js called dash_clientside.set_props
. This will allow you to set props directly from js code, so for the above Id do this:
dash_clientside.set_props(“render-delay”, {children: false})
Place this after the alert, and it should update accordingly.
Thanks for the quick reply.
The expected behavior should be just creating an alert on event. Missed the return no_update at the end of the callback, as the script fails before it reached the end.
Returning false, according to the examples, should disable the default behavior of clicking on the legend to hide data.
Any idea why I can’t call the ‘on’ function from the graph’s div?
Dash automatically has these event listeners enabled.
You should be able to pass some of these configurations on to the dcc.Graph.
You are also not overriding the event listener that was added to the graph’s div.
I’ve used config before to disable the event completely, but in the future I’d like to add functionality on top of the default behavior.
The plotly.js page I linked mentions that the ‘.on’ method should be exposed on the plots div object. I’m fairly new to javascript, is there a way I can troubleshoot this?
You can do this as I was describing:
window.dash_clientside = Object.assign({}, window.dash_clientside, {
clientside: {
legendClick: (id) => {
setTimeout(() => {graph = document.getElementById(id);
graph.on('plotly_legendclick', () => {
alert('plotly_legendclick was called');
return false
})
}, 300) // pause for the graph to render and have plotly js enabled
return window.dash_clientside.no_update
}
}
});
clientside_callback(
ClientsideFunction(namespace='clientside', function_name='legendclick'),
Output('myGraph', 'id'),
Input('myGraph', 'id'),
)
This should add an event listener so that it will alert you when you click on a legend.
This is a fairly common way for adding custom event listeners to components for events that dash doesnt capture.
To have this event listener update some props, you can utilize dash_clientside.set_props
as I referred to earlier. 
Same issue. TypeError: graph.on is not a function
.
The only non-standard object keys for the graph I can see are __reactInternalInstance
and __reactEventHandlers
which should be part of dash’s render. So I dug a bit deeper, and made this ungodly hack.
function legendclick(config, id) {
const graph = document.getElementById(id);
const react_ev = Object.keys(graph)[1];
const plotly_graph = graph[react_ev].children[1]._owner.stateNode.gd.current;
// this works
plotly_graph.on('plotly_legendclick', () => {
alert('plotly_legendclick was called from the subgraph');
return false;
});
// this throws the error TypeError: graph.on is not a function
graph.on('plotly_legendclick', () => {
alert('plotly_legendclick was called from the subgraph');
return false;
});
return dash_clientside.no_update;
}
window.dash_clientside = Object.assign({}, window.dash_clientside, {
clientside: {
legendclick: legendclick
}
});
And this works without any added timeouts. Edit: I’m wrong there… Seems to work fine if I pass a prop like config as the Input, and id as a State.
If there’s something else I can use to get the plotly object, I’d rather use that.
Using dash 2.17.0, plotly 5.22.0.
Oh, my apologies, you needed this:
window.dash_clientside = Object.assign({}, window.dash_clientside, {
clientside: {
legendClick: (id) => {
setTimeout(() => {graph = document.querySelector(`#${id} > .js-plotly-plot`);
graph.on('plotly_legendclick', () => {
alert('plotly_legendclick was called');
return false
})
}, 300) // pause for the graph to render and have plotly js enabled
return window.dash_clientside.no_update
}
}
});