I’m looking for a way to make svg elements clickable in dash and let them fire callbacks. To be more precise, I want to grab the id-tag of the clicked element.
Currently, I have a svg file whose Elements are clickable and trigger some javascript function that returns this id. Now I need those onclick functions to get noticed by dash but I wasn’t able to do so.
The svg is integrated by a html.ObjectEl component. In the browser console I can see that the javascript functions are running when I click some Elements in the svg but I have no idea how to let this trigger a dash callback or pass the clicked element’s id to dash.
Does anyone have an idea or knows a better approach for clickable, interactive svg files?
I have faced this kind of issue a few times, but as i understand, there isn’t really any official solution. I have just put together a new EventListener component that aims to address it. My first take at a possible syntax looks like this,
import dash_html_components as html
from dash import Dash
from dash.dependencies import Output, Input
from dash_extensions import EventListener
app = Dash()
app.layout = html.Div([
# The EventListener components listens to events from children (or document, if no children are specified).
EventListener(id="el", events=[{"event": "click", "props": ["x", "y"]}], logging=True, children=[
html.Div("CLICK ME!", id="click_bait")
]),
# Containers for logging.
html.Div(id="event"), html.Div(id="n_events"),
])
# Log event props.
app.clientside_callback("function(x){return JSON.stringify(x);}", Output("event", "children"), Input("el", "event"))
app.clientside_callback("function(x){return JSON.stringify(x);}", Output("n_events", "children"), Input("el", "n_events"))
if __name__ == '__main__':
app.run_server(port=7777, debug=True)
i.e. you wrap components to which you want to attach event handlers in an EventListener component and specify which events to listen to (and what props to collect) via the events prop. You can try it out with dash-extensions==0.0.61rc1.
When I click some svg element, I can see in the console that the element’s onclick function is executed (it prints the id) but nothing else happens. The clientside callback isnt even triggered.
Yes, I tried with the ‘click’ event and it works if i put the svg in a html.Img component. But sadly I only get the raw image coordinates of the mouse position because the svg is handled as an image matrix.
If i put the svg in an ObjectEl, Embed or Iframe, the click event also doesnt work.
I read that react components only listen to a special class of events, SyntheticEvent (I’m no javascript or react developer, so I’m not sure if this is the actual problem)
Update: I found a “solution” by building my own dash component via the boilerplate…
I’m quite happy with this since it isnt that hard if you spend some hours reading the tutorial and some react intro.
It works the way I wanted it to work and now i can extend my custom component with some other svg stuff like color highlighting of clicked elements
Thanks for your help!
Edit:
If someone is interested, here is the react class I wrote:
import React, {Component} from 'react';
import PropTypes from 'prop-types';
export default class FloSvg extends Component {
constructor(props) {
super(props)
}
componentDidMount() {
let wtf = this
let all_g = document.getElementsByTagName('g')
for (let i=0; i<all_g.length; i++) {
let children = all_g[i].childNodes
for (let j=0; j<children.length; j++) {
let child = children[j]
if (['polyline'].includes(child.tagName)) {
child.onclick = function () {
wtf.handleIdChange(child.parentElement.id);
}
}
}
}
}
handleIdChange(new_id) {
this.props.setProps({selectedId: new_id})
}
render() {
const {id, setProps, svgData, selectedId} = this.props;
// console.log(svgData);
return (
<div id={id}>
<span dangerouslySetInnerHTML={{__html: svgData}} />;
</div>
);
}
}
FloSvg.defaultProps = {};
FloSvg.propTypes = {
/**
* The ID used to identify this component in Dash callbacks.
*/
id: PropTypes.string,
/**
* Dash-assigned callback that should be called to report property changes
* to Dash, to make them available for callbacks.
*/
setProps: PropTypes.func,
svgData: PropTypes.string,
selectedId: PropTypes.string
};
The svgData prop is just the stringified svg document. I’m not sure with the way I display the svg via the dangerouslySetInnerHtml. Is there a better way displaying svg from raw string?