Plotly.js: Use keyboard to navigate plotly charts

Created on 24 May 2016  Â·  13Comments  Â·  Source: plotly/plotly.js

I like plotly and its integration with R's ggplot, but I find that I cannot use the keyboard to interact with plotly charts or the button bar at the top of the charts. This is a concern when making websites more accessible to people with disabilities. Could this be added?

discussion needed feature ♥ NEEDS SPON$OR

Most helpful comment

I created a some more key bindings, that be tested here https://jsfiddle.net/jrL34keb/1/.

I found no way pass the additional javascript to python via the plot function.
But there are functions https://github.com/plotly/plotly.py/blob/dc1111170d017911699d0f940850c45bd4f3e98e/plotly/offline/offline.py#L371
which have a post_script argument.
As a bad hack, I made a decorator to a function, which generates itself post_javascript code, in order to append the binding script.

When putting the following code at the top of the python script, it appends automatically key bindings in every offline.plot call.

import plotly

import plotly.plotly as py
import plotly.graph_objs as go
bind_script = '''
var graph = document.getElementsByClassName("plotly-graph-div js-plotly-plot")[0];
var update;

function pan(axis, dx, mode=1) {
    axis += 'axis';
    var [min, max] = graph._fullLayout[axis].range;
    dx *= max - min;
    update[axis+'.range'] = [min+dx, max+mode*dx];
}

function panX(dx) {pan('x', dx)}
function panY(dy) {pan('y', dy)}
function zoomX(dx) {pan('x', dx, -1)}
function zoomY(dy) {pan('y', dy, -1)}

document.addEventListener("keydown", function(e){
    var key = e.key;
    if (e.ctrlKey) key = 'Ctrl+' + key;
    console.log(e, key);
    var fac = 0.1;   // pan and zoom factor
    update = {};
    var extremes = graph._fullData[0]._extremes;  // only first data set
    switch (key) {
        case 'Ctrl+ArrowRight': zoomX(fac); break;
        case 'Ctrl+ArrowLeft': zoomX(-fac); break;
        case 'Ctrl+ArrowUp': zoomY(fac); break;
        case 'Ctrl+ArrowDown': zoomY(-fac); break;
        case '+': zoomX(fac); zoomY(fac); break;
        case '-': zoomX(-fac); zoomY(-fac); break;
        case 'X': case 'U':
             update['xaxis.range'] = [extremes.x.min[0].val, extremes.x.max[0].val];
        case 'Y': case 'U':
             update['yaxis.range'] = [extremes.y.min[0].val, extremes.y.max[0].val]; break;
        case 'x': case 'u':
             update['xaxis.autorange'] = true;
        case 'y': case 'u':
             update['yaxis.autorange'] = true; break;
        case '0': update['yaxis.range[0]'] = 0; break;
        case 'ArrowRight': panX(fac); break;
        case 'ArrowLeft': panX(-fac); break;
        case 'ArrowUp': panY(fac); break;
        case 'ArrowDown': panY(-fac); break;
        case 'Home': panX(-1.); break;
        case 'End': panX(1.); break;
        case 'PageUp': panY(1.); break;
        case 'PageDown': panY(-1.); break;
        default: return;
    }
    Plotly.relayout(graph, update);
});
'''


def script_decorator(func):
    # append binding script
    def function_wrapper(*x):
        return (func(*x) or '') + bind_script
    return function_wrapper

plotly.offline.offline.build_save_image_post_script = script_decorator(plotly.offline.offline.build_save_image_post_script)

All 13 comments

thanks for reporting @johnpauls ! could you elaborate more on what you would expect the keyboard interactions to be?

@chriddyp In the US there is a law, referred to as section 508, that governs web compliance for the federal government (at least) and is used as a benchmark by others. Basically one part says that anything you can do with the mouse you should be able to do with just the keyboard (note - not a lawyer).

So I guess at least you would want to be able to activate/interact with all of the controls on the button bar and click the colors on the legends (where appropriate) by tabbing through them or something similar. It would also be good if you could use the keyboard move the cursor to hover over the various charts and see the information that is displayed (coordinates or values or whatever).

Thanks

It is possible we could add these in the future, but I'm not sure whether we _should_ beyond very basic interactions. Plotly.js' main focus is on plotting the data and exposing an interface to view it - in my opinion, specific key events should be managed by the consumers of plots (i.e. webpages they are embedded in).

Simple pan/zoom controls would be useful (z for zoom in, shift+z for zoom out, arrow keys to navigate) and not too too difficult to implement. Legends would be a bit more difficult to make keyboard-friendly because visual aids would need to be added (they are in SVG, not HTML), and any sort of cursor on the plot would be a fairly significant addition.

For now, if you are embedding plots in a webpage and need a11y, I'd suggest using the javascript API to bind whatever actions are required to keys - it's a difficult thing to generalize beyond zooming and scrolling, but those are both fairly easily implemented by changing axis ranges and calling Plotly.relayout.

var graph = document.getElementById('graph');

Plotly.plot(graph, [{
  x: [1, 2, 3],
  y: [2, 3, 4]
}], {});

// Basic example to zoom on z
window.addEventListener('keyup', function(e){
  var key = e.keyCode ? e.keyCode : e.which;

  // 'z' key is 90
  if(key === 90) {
    var oldRange = graph._fullLayout.xaxis.range;
    var oldMin = oldRange[0];
    var oldMax = oldRange[1];

    var scaling = e.shiftKey ? -0.1 : 0.1;

    var newRange = [
      oldRange[0] + (oldMax - oldMin) * scaling,
      oldRange[1] - (oldMax - oldMin) * scaling
    ];
    Plotly.relayout(graph, 'xaxis.range', newRange);
  }
});

And a live jsbin

Thanks for this suggestion. For what its worth, my hope it to use plotly to create graphics in R and then server them on the web using RStudio's shiny /shiny server . I would like to avoid adding the extra javascript, but it is good to know that it can be done.

I'd like this feature, too.

Some pretty basic shortcuts would already help tremendously especially when you have to use a laptop trackpad. I previously used matplotlibs interactive window and immediatly tried to use the shortcuts in plotly, too. The matplotlib toolbar shortcuts are listed here:

https://matplotlib.org/users/navigation_toolbar.html

Typical keyboard interaction for a custom component is to use the TAB key (and SHIFT+TAB) to navigate into and out of the component, and to use arrow keys (UP, DOWN, LEFT, RIGHT) to navigate within the component.
Please try using your keyboard in any of these Oracle JET charts/visualizations. Notice also that they also use aria-label where invisible labels are needed for screen reader users, and sometimes they use role="img" or role="separator" and other aria to help clarify what various things represent.

From #3989

Is it possible to have shortcuts for interactions with the graph?

E.g. I would like to have

  • panning with the arrow keys left, right, up, down
  • zooming with + and -
  • horizontal zooming with ctrl+left and ctrl+right
  • vertical zooming with ctrl+up and ctrl+down
  • unzoom both u, horizontal unzoom alt+x, vertical unzoom alt+y
  • previous zoom p
  • toggle grid g
  • snapshot (copy as png to clibboard) ctrl+c

(partly taken from gnuplot).

Thanks for pointing me here.

How I can fed this javascript snippet outlined above by @mdtusz from python to the html document?

I created a some more key bindings, that be tested here https://jsfiddle.net/jrL34keb/1/.

I found no way pass the additional javascript to python via the plot function.
But there are functions https://github.com/plotly/plotly.py/blob/dc1111170d017911699d0f940850c45bd4f3e98e/plotly/offline/offline.py#L371
which have a post_script argument.
As a bad hack, I made a decorator to a function, which generates itself post_javascript code, in order to append the binding script.

When putting the following code at the top of the python script, it appends automatically key bindings in every offline.plot call.

import plotly

import plotly.plotly as py
import plotly.graph_objs as go
bind_script = '''
var graph = document.getElementsByClassName("plotly-graph-div js-plotly-plot")[0];
var update;

function pan(axis, dx, mode=1) {
    axis += 'axis';
    var [min, max] = graph._fullLayout[axis].range;
    dx *= max - min;
    update[axis+'.range'] = [min+dx, max+mode*dx];
}

function panX(dx) {pan('x', dx)}
function panY(dy) {pan('y', dy)}
function zoomX(dx) {pan('x', dx, -1)}
function zoomY(dy) {pan('y', dy, -1)}

document.addEventListener("keydown", function(e){
    var key = e.key;
    if (e.ctrlKey) key = 'Ctrl+' + key;
    console.log(e, key);
    var fac = 0.1;   // pan and zoom factor
    update = {};
    var extremes = graph._fullData[0]._extremes;  // only first data set
    switch (key) {
        case 'Ctrl+ArrowRight': zoomX(fac); break;
        case 'Ctrl+ArrowLeft': zoomX(-fac); break;
        case 'Ctrl+ArrowUp': zoomY(fac); break;
        case 'Ctrl+ArrowDown': zoomY(-fac); break;
        case '+': zoomX(fac); zoomY(fac); break;
        case '-': zoomX(-fac); zoomY(-fac); break;
        case 'X': case 'U':
             update['xaxis.range'] = [extremes.x.min[0].val, extremes.x.max[0].val];
        case 'Y': case 'U':
             update['yaxis.range'] = [extremes.y.min[0].val, extremes.y.max[0].val]; break;
        case 'x': case 'u':
             update['xaxis.autorange'] = true;
        case 'y': case 'u':
             update['yaxis.autorange'] = true; break;
        case '0': update['yaxis.range[0]'] = 0; break;
        case 'ArrowRight': panX(fac); break;
        case 'ArrowLeft': panX(-fac); break;
        case 'ArrowUp': panY(fac); break;
        case 'ArrowDown': panY(-fac); break;
        case 'Home': panX(-1.); break;
        case 'End': panX(1.); break;
        case 'PageUp': panY(1.); break;
        case 'PageDown': panY(-1.); break;
        default: return;
    }
    Plotly.relayout(graph, update);
});
'''


def script_decorator(func):
    # append binding script
    def function_wrapper(*x):
        return (func(*x) or '') + bind_script
    return function_wrapper

plotly.offline.offline.build_save_image_post_script = script_decorator(plotly.offline.offline.build_save_image_post_script)

Is there any update on being able to navigate through plotly chart using keyboard?

I wonder whether there are any updates? Thanks!

Just Zoom In, Zoom Out and Pan shortcuts alone should be great. Thank you.

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: $15k-$20k

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.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

bryaan picture bryaan  Â·  3Comments

etpinard picture etpinard  Â·  3Comments

boleslawmaliszewski picture boleslawmaliszewski  Â·  3Comments

n-riesco picture n-riesco  Â·  3Comments

jonmmease picture jonmmease  Â·  3Comments