Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Customized Click, Hover, and Selection Styles or Traces #1847

Open
chriddyp opened this issue Jul 4, 2017 · 31 comments
Open

Customized Click, Hover, and Selection Styles or Traces #1847

chriddyp opened this issue Jul 4, 2017 · 31 comments
Labels
feature something new P3 backlog
Milestone

Comments

@chriddyp
Copy link
Member

chriddyp commented Jul 4, 2017

Plotly.js has default styles for hovering (tooltips) and for selection (dimmed traces).

In Dash, users want to be able to customize these styles. While they can customize these styles themselves through Dash callbacks, it's slow (roundtrip to server). And while I could write this behaviour into the Dash Graph component, it would be great if this was standard behaviour that everyone could benefit from.

Here are some examples that customized interaction styles or traces could enable.


As requested in the Dash community forum

d3827b8cd525b5f9b5111d4b93f1e336534043c5

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.

In addition to this example, users might want to:

  • Make the points larger
  • Make the lines that they're hovering over thicker
  • Add text to the points they're hovering over
  • Add shapes the points their hovering over (vertical or horizontal line shapes)
  • Outline countries in maps

capture d ecran 2017-07-04 a 13 04 29


In this Uber Rides Dash demo created by @alishobeiri, the selected bars are replotted to be white. This persistent style informs the user which bars are selected and also matches the color palette of app itself.

selected-bars

While selected markers have a "dimming" effect, some users will want to customize the style of the selected points and the unselected points. For example, they might want to:

  • Hide the unselected points
  • Display text to the selected points (mode: "markers+text")
  • Modify the colors of either selection
  • Add an outline to points or countries instead of fading

selected-regions


In BI platforms, "clicking" is often used for drill downs. While we support click events, we don't modify the look and feel of the graph after clicking. For example, users might want to display constant text when clicking on a point.

In addition, there are a few other default style changes for clickable points (this could be a separate issue):

  • Modify the hover interaction of the point to make it seem more "clickable": adding cursor: pointer and making the point a little bit larger
  • Displaying an "x" over the point so that the user can "unclick" a point

capture d ecran 2017-07-04 a 13 05 19
capture d ecran 2017-07-04 a 13 05 16
capture d ecran 2017-07-04 a 13 05 51


I'm sure there are many more examples out there.

cc @alexcjohnson @etpinard @cpsievert @monfera @jackparmer

@chriddyp chriddyp added this to the Dash milestone Jul 4, 2017
@chriddyp
Copy link
Member Author

chriddyp commented Jul 4, 2017

I haven't thought about the attribute specs that much yet. One way to get some of these interactions would be through a :hover suffix like in CSS:

{
    marker: {
        color: "blue",
        "color:hover": "orange",
        "color:click": "green",
        "color:selected": "purple",
        "color:unselected": "grey"
    }
}

That doesn't cover the case for drawing a vertical or horizontal line over points.

@chriddyp
Copy link
Member Author

chriddyp commented Jul 4, 2017

With custom hover attributes, perhaps users could create their tooltips by using a combination of annotations, shapes, and images that are triggered on and off through custom hover styles.

@chriddyp
Copy link
Member Author

chriddyp commented Jul 4, 2017

cc @charleyferrari who might have some additional examples too

@chriddyp
Copy link
Member Author

chriddyp commented Jul 4, 2017

Example from @cpsievert 's work. In this case, highlighting across subplots. That type of linking hover across traces might be out of the scope of this.

https://plotcon17.cpsievert.me/workshop/day2/#18

highlight

@monfera
Copy link
Contributor

monfera commented Jul 4, 2017

Indeed a solution for this would be useful (needed) for the crossfilter as we discussed in #1762

WARNING: totally my personal ramblings and biases only; in part informed by the crossfiltered dashboard work:

If possible, it'd be great to separate the activating affordances (e.g. Click, Hover, Selection, or crossfiltering some points away from another plot) from the specific temporary restyling ie. visual channel updates, as it may depend, as @chriddyp you write, on the intent such as clicking could indicate various intents, e.g. drilling down, including/excluding from filter, showing atomic data for point or jumping to an external link. A dashboard may have various inputs external to the plotly widgets or concepts, e.g. streaming stock prices would highlight the ticker (and time series line) of the last traded stock, or a UI tour or storytelling, so it might be beneficial to make these work and be assignable depending on how the context requires it.

Having made this separation, I think the affordances part is trivial as these plotly.js events mostly exist or can be extended. So the task is reduced to applying arbitrary styling changes. This is the harder part:

With the current plotly.js API

One option is to simply call Plotly.restyle and/or Plotly.relayout - which has the following issues:

  1. These APIs weren't desinged for high FPS interactions, each call can trigger a lot of recalculations, whether they're needed or not
  2. Often, the desired effect needs multiple restyle and relayout calls; currently can't be batched
  3. The alternative, e.g. Plotly.newPlot pretty much throws out everything so it's not fast either, and resets user selections or other user navigated state (zoom, pan etc.)
  4. The plotly.js-style column array updates are great when only one thing changes, but (I think) it's not possible to update one or a few points out of eg. 1000 data points, and even if we make such an API, making it faster than a full vector update is still a challenge.
  5. The internal architecture isn't (yet?) reactive, i.e. while the above constraints can be mitigated eg. by batching, the real solution would be the principle of not recalculating things that have no need to change, which is an operational benefit of reactive data flows (most.js, kefir, flyd, vega-dataflow, or our compact crosslink library)

With CSS restyling

It's a quick win b/c the desired effect can be achieved (limited only by the standard), and no code change is needed. It can be made as fast as the DOM allows. Yet it has some drawbacks:

  1. The plotly.js scenegraph, e.g. structuring, classnames etc. may change
  2. Future plot versions may specify or override the very CSS styles that are used
  3. User interactions (zoom, pan, selection, or anything that yields even a partial rerender) may interfere with the styling, e.g. fully or partially resetting/removing the custom styling
  4. It's a non-specified API from the viewpoint of plotly.js ie. not part of a 'contract' with the user

Yet I think it's an attractive direction as

  1. it's a quick win
  2. the scene graph, class names and CSS usage don't change much
  3. can be made fast; easy to integrate into reactive dataflows
  4. not limited to what we preconceive
  5. if the user so wishes, it can even be a browser-specific styling which we naturally can't do ourselves

OK then what?

Maybe @etpinard @alexcjohnson @chriddyp @jackparmer @cpsievert @rreusser @charleyferrari or someone else perhaps has clearer views than I. One option is to provide preconceived restyles (e.g. 'increase salience' - 'decrease salience' etc). Another option could be to somehow open up and declaratively expose the scene graph and commit to class names and using / not using certain CSS styles on certain elements - sounds very laborious.

Solving the incremental recalc with reactive streams

Recalculate data and update the DOM for only what's needed. E.g. move the sliders under Model tweaks or Layout tweaks in this older experiment

Vega is also using reactive streams for much the same purpose. There are very nice papers and videos that describe it, e.g. this one from Arvind Satyanarayan, Ryan Russell, Jane Hoffswell and Jeffrey Heer - thanks Arvind for reminding me of this paper recently.

We've started to use a similar solution with the crossfilter work but as it uses plotly.js charts it won't make those faster. For this reason, there's a user exit facility (SAP terminology which is a huge standard system that needs highly client dependent extensions) that'd allow custom styling and even support for adding and updating non-plotly.js plots.

What about react and redux

Despite the name, react is not reactive in itself. It can be made reactive, but even then, by its nature, it still regenerates the entire scene graph (as virtual DOM) and does a lot of DOM diffing even where these won't lead to an update. There are some solutions e.g. using immutability and identity equality for shouldComponentUpdate checks but using an immutable library for our rather large JSON specs (incl. data) and more importantly, the large amounts of scenegraph data generated, is quite expensive too. Caching vdom fragments (subtrees) is also a possibility to reduce vdom tree construction. It could work but it has speed limits.

A more serious limitation with redux from a viewpoint of continuous interactions - it's great with predictable changes to an essentially global object, yet due to its conventions (e.g. using strings for dispatching actions), best practices eg. immutability, and our need to perform cascading changes, it gets expensive fast. Basically, each mouse move would rebuild the entire dashboard state. For this reason, redux isn't often used for high-frequency interactions, and a half-solution won't be a solution. I wrote on this in more detail in the last three paragraphs of this and here and here so it deff. makes me an FRP fanboy; glad to learn about other approaches though.

There are some attempts to solve the core issue with redux involved, e.g. reselect and various caching approaches which might have some memory leak risks. Btw. redux can be combined with reactive data streams / FRP just fine, so having redux for overall app state while doing data updates and user interactions via FRP is OK but redux doesn't add much for the interactivity / 60FPS update aspect so it's not relevant for this discussion.

@etpinard
Copy link
Contributor

etpinard commented Jul 4, 2017

With custom hover attributes, perhaps users could create their tooltips by using a combination of annotations, shapes, and images that are triggered on and off through custom hover styles.

That sounds painful. We could do better.

@etpinard etpinard added the feature something new label Jul 4, 2017
@etpinard
Copy link
Contributor

etpinard commented Jul 4, 2017

We could maybe attach method and args attributes to traces similar to updatemenus buttons and slider steps which act as a callback on hover and/or click. For example,

Plotly.newPlot('graph', [{
  mode: 'markers',
  x: [1, 2, 3],
  y: [2, 1, 2],
  marker: {
    color: 'red'
  },
  interaction: {
      type: 'hover', // or 'click' or 'select'
      method: 'restyle',
      args: ['marker.color', 'blue']
    }
}])

@etpinard

This comment has been minimized.

@monfera

This comment has been minimized.

@monfera
Copy link
Contributor

monfera commented Jul 5, 2017

#1762 is closed; an illustration of the crossfiltered styling in plots other than the one where we do the box selection is here

@monfera
Copy link
Contributor

monfera commented Sep 22, 2017

#1943 is now closed but its comments are pertinent to this discussion.

@jackparmer
Copy link
Contributor

I'd love to be able to set the color of an individual bar or scatter point on hover.

For example, in a monotone chart like this:

image

I'd love to set the hover color of a bar to an accent color like pink.

@chriddyp
Copy link
Member Author

chriddyp commented Mar 5, 2018

In Dash, I'd like mode to a style customizable under the selected property so that you can set mode: 'markers+text' while selecting but keep it as markers when unselected. We used to achieve this just by plotting a new trace on top of it.

@alexcjohnson
Copy link
Collaborator

mode is going to be tough, because it changes what objects need to be created, not just how those objects are styled, so it would need to be a much slower update pathway. Would it work to give unselected text a transparent font color?

@chriddyp
Copy link
Member Author

chriddyp commented Mar 5, 2018

Would it work to give unselected text a transparent font color?

That works, thanks!

@scottlittle

This comment has been minimized.

@merges

This comment has been minimized.

@scottlittle

This comment has been minimized.

@RutanR

This comment has been minimized.

@cholman
Copy link

cholman commented Apr 7, 2020

In addition, there are a few other default style changes for clickable points (this could be a separate issue):

Modify the hover interaction of the point to make it seem more "clickable": adding cursor: pointer and making the point a little bit larger

Is this possible? I have markers that open a modal when clicked, but it isn't clear to the user that they are clickable. I'm trying to figure out how to get the cursor to change to a hand pointer e.g. cursor: pointer when hovering over the marker. I am using react

@seabass011
Copy link

Would love to know how I can help. Is this under active development?

@pfbuxton
Copy link

pfbuxton commented Jun 15, 2020 via email

@kevalshah90
Copy link

kevalshah90 commented Jul 29, 2020

In addition, there are a few other default style changes for clickable points (this could be a separate issue):
Modify the hover interaction of the point to make it seem more "clickable": adding cursor: pointer and making the point a little bit larger

Is this possible? I have markers that open a modal when clicked, but it isn't clear to the user that they are clickable. I'm trying to figure out how to get the cursor to change to a hand pointer e.g. cursor: pointer when hovering over the marker. I am using react

I am trying to figure this out. I'd like to update modal popup based on scatter mapbox marker click event data. How did you do it?

html.Div([

        dbc.Modal(
            [
                dbc.ModalHeader("Lease Information"),
                dbc.ModalBody(
                    [
                        dbc.Label("Address:", id='address'),
                        dbc.Label("Name:", id='name')

                    ]
                ),
                dbc.ModalFooter(
                    [
                        dbc.Button("OK", color="primary", size="lg", className="mr-1"),
                    ]
                ),
            ],
            id="modal",
        ),

   ], style={"width": "50%"}), 


# Update modal on click event
@app.callback(Output("modal", "is_open"),
              [
                  Input("map-graph1", "clickData")],
               [State("modal", "is_open")],
              )
def display_popup(clickData, close, is_open):

    if clickData is None:

        return (no_update)

    else:

        Name = clickData['points'][0]['customdata']['Name']
        Address = clickData['points'][0]['customdata']['Address']
        
    return (Name, Address)

@jackparmer
Copy link
Contributor

This issue has been tagged with NEEDS SPON$OR

A community PR for this feature would certainly be welcome, but our experience is deeper features like this are difficult to complete without the Plotly maintainers leading the effort.

Sponsorship range: $45k-$50k

What Sponsorship includes:

  • Completion of this feature to the Sponsor's satisfaction, in a manner coherent with the rest of the Plotly.js library and API
  • Tests for this feature
  • Long-term support (continued support of this feature in the latest version of Plotly.js)
  • Documentation at plotly.com/javascript
  • Possibility of integrating this feature with Plotly Graphing Libraries (Python, R, F#, Julia, MATLAB, etc)
  • Possibility of integrating this feature with Dash
  • Feature announcement on community.plotly.com with shout out to Sponsor (or can remain anonymous)
  • Gratification of advancing the world's most downloaded, interactive scientific graphing libraries (>50M downloads across supported languages)

Please include the link to this issue when contacting us to discuss.

@inselbuch
Copy link

Would like to chime in on the possibility of simply disabling the tooltips. I still want the click behavior to return the closest point. I just don't want the tooltip at all.

@alexcjohnson
Copy link
Collaborator

@inselbuch that should just be hoverinfo: 'none' https://plotly.com/javascript/reference/scatter/#scatter-hoverinfo

@inselbuch
Copy link

inselbuch commented Nov 6, 2020 via email

@jjrob13
Copy link

jjrob13 commented Nov 17, 2020

Hey sorry to jump in here, hoping my usecase is already supported as the previous commenter's was.

I often use plotly to visualize many series with different ranges on a shared timescale. One feature I've been searching for (and I'm not sure if it's part of this issue or not - so pardon my asking) is the ability to have clicks persist the hover dialog.

For example, I'd love to be able to do something like this by clicking in a few locations, to easily highlight how these many traces are changing over time (and possibly to set some default clickData to show some default hoverinfo for a few points when users first open the page):
image

Please let me know if this is already supported, or if it's part of this pending feature request.

Thank you very much!

@Andrej4156
Copy link

I linked here from part 4 of the tutorial .

I just wanted to chime in that it is possible to customize the hover interactions while using a selection box by changing the code in the tutorial in the "Generic Crossfilter Recipe" section:

[p['customdata'] for p in selected_data['points']]) to [p['pointNumber'] for p in selected_data['points']])

and then getting rid of customdata=df.index, in update_traces.

p['pointNumber'] already has point indices so setting p['customdata'] to df.index makes p['customdata'] identical to p['pointNumber'] and needlessly occupies p['customdata'] which prevents the user from customizing the hover interaction using the method outlined here here.

I'm not sure if this addresses all of the issues presented here or if someone else has already mentioned it before but at the very least the tutorial should probably be updated to use pointNumber instead of customdata as a matter of good practice (unless there is some other utility for it that I am missing).

@nicolaskruchten
Copy link
Contributor

@Andrej4156 thanks! I think the pointNumber will not match up correctly with df.index if you have multiple traces in the same figure, which is why the recipe is set up this way.

@EWChina999
Copy link

ECharts is powerful, has a feature of "hover on legend to highlight the respective series and dim the rest of series", please check apache/echarts#17200.

@gvwilson gvwilson self-assigned this Jun 10, 2024
@gvwilson gvwilson removed their assignment Aug 2, 2024
@gvwilson gvwilson added P3 backlog and removed ♥ NEEDS SPON$OR labels Aug 2, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
feature something new P3 backlog
Projects
None yet
Development

No branches or pull requests