I was playing over the last days with the new Dash library and came across the following problem:
Is there a way, to get the mouse position over the graph and e.g. draw a vertical line at the position of the mouse?
I want to create something similar to the left four figures of this beautiful data viz. So what I have are different time series that I would like to plot in a stack and then one vertical line, that crosses all of them and annotates at the intersect of the vertical line with each graph line. Iām not quite sure, if this is possible with the new library, but maybe someone has an idea.
I found an solution for at least the mouse position. There is a property of an dcc.Graph called āhoverDataā that can be retrieved via @app.callback. hoverData stores the x and y position and with them a line can plotted easily. Here some basic code example:
But it is quite slow compared to plain Javascriptā¦
@chriddyp but itās really laggy. I first thought itās maybe because I unnecessarly repaint all three graphās on each callback event, but even when I tried only to visualize a single moving line under the mouse, itās really laggy. Talking about ~3-5 updates per second. Far away from JS performance.
You have any tip on improving the performance? Although itās such a simple example, I donāt know where to improve it.
@chriddyp Unfortunately yes. I saw this examples and therefore was quite surprised that drawing a graph seems to take so much more time, compared to writing a text box. I posted a complete working code example above. And already for this code, the update is quite laggy. Maybe you can copy the code and try to run it on your local machine.
I thought the issue could be, that on each callback event I donāt only update the moving line, but update the entire graph content (having not only the moving vertical indicator line, but also the āstaticā graph). But then I tried in a different example to just have one moving line in one graph without anything else, and already this simplest example is quite laggy. You could try this second example by simply removing the blueāish graph showing the random numbers from the above code example.
Right now, the graph is redrawing itself on every update. Eventually, Iād like to update the dash_core_components.Graph component to take a diff of the data and use Plotly.restyle instead of Plotly.newPlot to only redraw the parts of the graph that have changed, rather than the entire graph. That should make updates quite a bit faster.
Using go.Scattergl (web-gl enabled plot) instead of go.Scatter (SVG rendered) will make it faster for large datasets as well.
If there are any companies out there that would like to expedite this work through sponsorship, please reach out!
@chriddyp Thanks for your testing and the *gif. Although it looks a bit smoother using webgl, itās far away from good fluent user experience. Here is a comparable example in plain JS using d3 library.
(sorry for the bad quality gif)
For this example, there was some āanimation fadingā implemented, this is, why the line seems to be slightly behind the mouse. But I guess itās clear to see what kind of performance, I was hoping to get by Dash.
I think, it would be really nice, if there are efforts to improve these kind of animations, because I think they are used in many data visualization tools (having a moving maker I mean).
There is this built-in Plotly āToggle Spike Linesā function in each Graph, that does nearly what I want and is pretty fast, but not customizable. Let me know if I can help with some test or anything, because I canāt afford a sponsorship on my own
Thanks for the example! For more āstandardā types of interactions like these (spike lines) that donāt require custom Python callbacks, the best solution might be to implement them in JS. There are two ways this can happen:
1 - Implement the feature in plotly.js. We donāt want to bloat the library and documentation with too many custom interaction features so these features need to be designed and abstracted pretty artfully. One can discuss this in the plotly.js repo by creating an issue: http://github.com/plotly/plotly.js/issues. Itās helpful to include as many examples as possible and to take a stab at what the attributes might look like (e.g. layout.hovermode = verticalspikelines, layout.spikelinemode = displayvalues).
2 - Eventually, Iād love it if plotly.js allowed for entirely customized hover and select style properties so that you could design custom hover lines like this. Spikelines could benefit from this and so could custom marker-hover interactions (e.g. make the markers darker or bigger and bolder when you hover over them). Iāll create an issue in plotly.js about this. If plotly.js doesnāt take this on, perhaps dash-core-components will.
3 - Alternatively and immediately, you could design your own Graph component in JS that would turn on these spikelines with a special attribute. Here are the docs on creating your own components: https://plot.ly/dash/plugins, here is a tutorial on React and plotly.js: https://academy.plot.ly/, and here is the existing Graph component that you can base it off of or fork: https://github.com/plotly/dash-core-components/blob/master/src/components/Graph.react.js. As you play around with it, these changes could get folded back into the main dash-core-components library or serve as inspiration for plotly.js. At a high level, it would:
Add a hover listener after the graph is drawn (Graph.react.js already does this so you can copy that code)
On hover, extract the data and call a Plotly.relayout with a vertical line and an annotation
Hope that helps! Thanks again for the example and the ideas
@chriddyp thank you very much for the effort you put into my issue! Unfortunately I think my JS isnāt profound enough to help with the development but when there is anything python related Iāll be glad to help. Anyway I will follow the conversation on github and see if I can help with anything!
@jimmybow hey jimmy, thanks for your reply. Unfortunately I canāt test your package at the moment. You mind adapting the code from above and grab a .gif/share information about the performance of your implementation? Buy this we could compare (at least a bit) the different approaches.
Many thanks in advance
@jimmybow No, no sorry, I can install it. But during the last days I wasnāt much on my private machine and had not much time to test anything. I installed your package now, and had no problem installing it. But the code you posted above doesnāt work. You mind providing a minimum working example? E.g. the example from above just with your listener added? Would be really helpful, so I donāt have to figure out in a first place how your package works.
Many thanks in advance for your effort!
@jimmybow I finally found time to test your package. Unfortunately I have to say it performs even slower in my example from the top, then the build in Listener using 'hoverData '.
Do you get well performing results? Even your simple code example to display the mouse position as text performs not fluently, as for example with pure Javascript. I donāt think the fault is on your implementation, but in the updating of Dash/Plotlyās graph objects, but this is just guessing.
Also as a tip: In your github repository, update the code examples to āworking-as-they-areā code, including all necessary imports and the app.run_server() line. I think it would be much better for new users, who find your package, if they just could copy and paste one example to see the effect, and not copy, then figuring out all missing imports, add code lines etc.
e.g. first code example should look like:
import dash
import dash_core_components as dcc
import dash_html_components as html
from dash.dependencies import Output, Input
import mydcc
app = dash.Dash()
app.layout = html.Div([
mydcc.Listener( id = "uuu", aim = 'graph' ),
dcc.Graph( id = 'graph',
figure = { 'data': [ {'x': [1, 2, 3], 'y': [4, 1, 2]} ] }
),
html.Div( id = 'text' )
])
@app.callback(
Output('text', 'children'),
[Input('uuu', 'data')])
def myfun(ddd):
return str(ddd['x']) + ' and ' + str(ddd['y'])
if __name__ == '__main__':
app.run_server()