Capturing mouse events/position

Hey everybody,

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.

Many thanks in advance!

1 Like

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…

import dash
import dash_core_components as dcc
import dash_html_components as html
import plotly.graph_objs as go

import numpy as np

x_data = np.linspace(0,500,500)
y_data = np.random.rand(500)
height = max(y_data)

app = dash.Dash()

app.layout = html.Div([
    dcc.Graph(id='basic_graph')])

@app.callback(dash.dependencies.Output('basic_graph', 'figure'),
              [dash.dependencies.Input('basic_graph', 'hoverData')])
def update_graph(hoverData):
    if not hoverData:
        x_value=x_data[250]
        opacity = 0
    else:
        x_value = hoverData['points'][0]['x']
        opacity = 0.8
    data = [go.Scatter(
                x=x_data,
                y=y_data,
                line={'color': '#235ebc'},
                opacity=0.8,
                name="Graph"
            ),
            go.Scatter(
                x=[x_value, x_value],
                y=[0, height],
                line={'color': '#a39999'},
                opacity=opacity,
                name='Moving Line')
            ]
    layout = go.Layout(
                xaxis={'type': 'linear', 'title': "Timestep"},
                yaxis={'type': 'linear', 'title': "Value"},
                margin={'l': 60, 'b': 40, 'r': 10, 't': 10},
                hovermode="False"
                )
    
    return {'data': data, 'layout': layout}

if __name__ == '__main__':
    app.run_server()

Nice find! Yes, hoverData is the way to this right now.

@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.

@fkratzert - it’s hard to say without seeing all of the code.

For reference, here’s an example from the announcement that feels pretty quick. It’s filtering a dataframe on every mouse hover and then displaying info about the point in the text box. The code for this example is here: https://github.com/plotly/dash-docs/blob/master/tutorial/examples/walmart.py

Does it feel or look significantly slower than that?

@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.

Would be glad to get any feedback!

Ah I see. Yes, here is how that example looks on my machine. It’s definitely not instantaneous.

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!

2 Likes

@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 :wink:

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 :slight_smile:

1 Like

I have created an issue for this in plotly.js: https://github.com/plotly/plotly.js/issues/1847

@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!

1 Like

I try a way to get mouse position by adding mouse listener, and upload it :slightly_smiling_face:
https://pypi.python.org/pypi?%3Aaction=display&name=mydcc&version=0.0.3

you can pip install it :blush:

Usage :

app.layout = html.Div([
    mydcc.Listener(id = "uuu"),
    dcc.Graph( id = 'ggg',
               figure = { 'data': [  {'x': [1, 2, 3], 'y': [4, 1, 2]}  ]
                         }
              )       
])

@app.callback(
    Output('uuu', 'aim'),
    [Input('ggg', 'id')])
def update_figure(x):
    return x
3 Likes

@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

can’t you install it …? i can run in win10 :thinking:

I use the way of gd.addEventListener(‘mousemove’, function) from javascript, the performance is well
here is the source react.js code https://github.com/jimmybow/mydcc/blob/master/src/components/Listener.react.js

version 0.0.3 is unavailable… please use version 0.0.8 :blush:

https://pypi.python.org/pypi?%3Aaction=display&name=mydcc&version=0.0.8

@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!

you can look README on my github :slightly_smiling_face:

1 Like

@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()
2 Likes

it should be slower than pure Javascript… :hushed:
but i feel it is fluently, and the function is easy to be applied with python

1 Like

@fkratzert - A community PR in plotly.js has implemented this feature. There is a discussion here: Vertical or/and horizontal line that is always shown in any hovermode · Issue #2155 · plotly/plotly.js · GitHub. Would you mind providing your 2 cents? Thanks!