Interaction & events
A chart with x and y axes zooms, pans, selects and reports clicks without any setup. This page covers what each gesture does, the attributes that change it, and the events it emits. Hover labels are covered in Hover, text & templates, and every event and payload is listed in the events reference.
3D scenes have their own controls: see 3D scenes. config.staticPlot turns every interaction off.
scatter/interactiveDrag in the example to zoom, double-click to reset, and use the modebar (top right, shown while the pointer is over the chart) to switch to pan, box select or lasso.
Drag modes
layout.dragmode sets what a drag on the plot area does:
dragmode | A drag on the plot area |
|---|---|
'zoom' (default) | Draws a box and zooms the axes to it |
'pan' | Moves the axes with the pointer |
'select', 'lasso' | Selects the points inside a box or a freehand outline; see Selections |
'drawline', 'drawrect', 'drawcircle', 'drawopenpath', 'drawclosedpath' | Draws a shape; see Drawing |
false | Nothing. Hover and click still work |
'orbit' and 'turntable' are modes of 3D scenes and act like false on x/y subplots.
createChart(el, { data, layout: { dragmode: 'pan' } });
// Later, as the modebar buttons do:
chart.setDragmode('select');The modebar has buttons for zoom and pan, for box select and lasso when a trace has points that can be selected, and buttons to zoom in, zoom out, autoscale and reset the axes.
Zoom and pan
- Zoom box. A drag shorter than 8 px along one direction leaves that axis alone, so a flat horizontal drag zooms x only and a vertical one y only.
- Axis drags. In every mode except
false, a drag that starts in the 30 px strip below the plot area (x axis) or left of it (y axis) moves that axis alone: from the middle of the strip it pans, and from either end (the outer 15%) it stretches the axis, keeping the other end where it is. - Axes that move together. An axis that overlays another (
overlaying) zooms and pans with it. Axes linked withmatchesorscaleanchorfollow. - Limits.
fixedrange: trueon an axis keeps it out of every zoom, pan and reset.minallowedandmaxallowedstop zoom and pan at those values. - Events. A zoom box emits one
relayoutwhen the pointer is released. A pan or an axis drag emitsrelayoutingon each frame of the drag and onerelayoutat the end. Hover labels are hidden during a drag.
Scroll zoom
The mouse wheel does not zoom x/y subplots by default: over a chart, it scrolls the page. config.scrollZoom turns wheel zoom on:
createChart(el, { data, layout, config: { scrollZoom: true } });The wheel zooms around the pointer. Over an axis strip it zooms that axis alone. Each step emits relayouting, and one relayout follows 300 ms after the wheel stops. Wheel zoom is off when dragmode is false.
Double-click
A double-click on the plot area resets the axes: to their first ranges, or to autorange when they are already there. config.doubleClick changes that, and config.doubleClickDelay (300 ms) is the longest time between the two clicks. In the 'select' and 'lasso' modes, a double-click clears the selection when there is one. In every case the chart then emits doubleclick.
Zoom from code
// Zoom in to half the range around the center; 2 zooms out. All subplots, or one.
chart.zoom(0.5);
chart.zoom(2, { subplot: 'xy' });
// What a double-click does.
chart.resetAxes(); // back to the first ranges
chart.autoscale(); // autorange every axis
// Any range.
chart.relayout({ 'xaxis.range': [0, 10] });Each call returns a promise and emits relayout, like the gesture it stands for. chart.zoom, chart.resetAxes and chart.autoscale leave fixedrange axes alone. Ranges set by the user survive react while uirevision stays the same.
Click and double-click
A press and release within 3 px is a click. When data points are under the pointer, the chart emits click with those points:
chart.on('click', (event) => {
const point = event.points[0];
if (point) console.log(point.curveNumber, point.pointNumber, point.x, point.y, point.customdata);
});- The points are the ones a hover at that position finds, so they follow
hovermodeandhoverdistance: in'closest'mode one point, in the'x'modes one point per trace. A click on empty space emits noclick. - With
hovermode: falsethe chart emits noclick(and nohover). To get clicks without labels, keephovermodeand sethoverinfo: 'none'on the traces. - Traces with
hoverinfo: 'skip'are not clicked. layout.clickmodeis'event'by default.'event+select'also selects the clicked points: shift adds them to or removes them from the selection, and a click on empty space in the plot area clears it. The chart emitsselected(ordeselect) afterclick.'select'selects without emittingclick, and'none'does neither.
Sunburst, treemap and icicle traces emit an event of their own before click, and a listener can cancel the drill-down: see Hierarchy clicks. Clicks on legend items and annotations have their own events too (legendclick, clickannotation).
Selections
In dragmode: 'select' (box) and 'lasso', a drag selects the points inside it in every trace of the subplot: selecting events follow the drag, selected ends it, and traces draw their selected / unselected styles. Shift adds to the selection. Scatter traces need markers or text to be selected: a line alone has no points to pick.
chart.on('selected', (event) => {
console.log(
event.points.map((point) => point.pointNumber),
event.range, // box: { x: [x0, x1], y: [y0, y1] }
event.lassoPoints, // lasso: { x: [...], y: [...] }
);
});
chart.on('deselect', () => console.log('selection cleared'));layout.selectdirection limits a box: 'h' selects a band between two x values, over the full height of the plot, 'v' a band between two y values, 'd' one or the other depending on whether the drag runs more sideways or more up and down, and 'any' (default) a free box.
Selections are also layout objects, as in Plotly 2.13+: each box or lasso drag is stored in layout.selections with one relayout (replacing the others, or added to them with shift), and every selection in the layout selects the points inside it whenever the chart draws. So a selection survives redraws and react, can be saved with the figure and restored, and can be set from code:
createChart(el, {
data: [{ type: 'scatter', mode: 'markers', x: [1, 2, 3, 4, 5], y: [2, 4, 3, 5, 1] }],
layout: {
dragmode: 'select',
selections: [
{ type: 'rect', x0: 1.5, x1: 3.5, y0: 2.5, y1: 4.5 },
{ type: 'path', path: 'M4,0 L5.5,0 L5.5,2 Z' },
],
},
});The example starts with two boxes from layout.selections, which select points of both traces:
selections/boxtype: 'rect'is the box from (x0,y0) to (x1,y1);type: 'path'is a polygon (a lasso),M x,y L x,y … Z, with_between date and time on date axes (2024-03-01_12:00). Positions are in data units ofxref/yref(default'x'/'y'): dates, category names or indices, and data values on log axes.- Outlines are drawn with
line.color(default: white on a dark plot background,#444on a light one),line.width(1 px),line.dash('dot') andopacity(0.7), clipped to the subplot. They move with zoom and pan. - Editing (in the select drag modes): a click on a selection makes it active (drawn solid); drag inside the active selection to move it, or drag a box's edge or corner to resize it. Each edit is one
relayout('selections[0].x0', …) followed byselectedwith the new points. - A double-click on the plot area clears every selection: the chart emits
deselectand arelayoutofselections: []. So doeschart.clearSelection(). selectedpoints:chart.fullData[i].selectedpointsholds the selection in effect, whether it came from a drag, a click orlayout.selections. The input trace is left as given, except that setting itsselectedpointsyourself wins over the layout selections for that trace.- The
selectedevent carriespoints,range(box) orlassoPoints(lasso), and the newselections. - Polar subplots select too (scatterpolar markers, barpolar bars), with points carrying
randtheta. Theirselectedevents have norangeorlassoPoints, and as in Plotly their selections aren't stored inlayout.selections(see polar interactivity).
A lasso stored as a path on a date axis, restored on load:
selections/lassoEvents
Subscribe with chart.on, which returns a function that unsubscribes. chart.off and chart.once are there too:
const stop = chart.on('relayout', (edits) => {
// After a zoom or pan: the new ends of each axis that moved.
const x0 = edits['xaxis.range[0]'];
const x1 = edits['xaxis.range[1]'];
if (x0 !== undefined && x1 !== undefined) console.log('x range', x0, x1);
});
// Later:
stop();The events of the gestures on this page:
| Gesture | Events |
|---|---|
| The pointer moves onto points, then off | hover, then unhover |
| Click on points | click; with clickmode including 'select', selected or deselect |
| Double-click on the plot area | doubleclick, with the relayout of the reset or the deselect of the clear |
| Zoom box | relayout at the release |
| Pan, axis drag, wheel zoom, pinch | relayouting on each frame, relayout at the end |
| Box or lasso drag | selecting on each frame, selected at the release, relayout of selections |
| A selection is moved or resized | relayout, then selected |
The relayout of a zoom or pan has the keys 'xaxis.range[0]' and 'xaxis.range[1]' for each axis that moved. The relayout of a reset has 'xaxis.range' and 'xaxis.autorange'.
Every event also answers to its Plotly name: chart.on('plotly_click', …) subscribes to click. The events reference has the payload of each event, the fields of a point, and the events whose default action a listener can cancel. Events after an update lists what the update calls emit.
Keyboard
Tab moves focus into the chart's plot area. The arrow keys then move between data points and show their hover labels, Enter emits click for the point, + and - zoom, Shift with an arrow key pans, and 0 resets the view. Zoom, pan and reset emit relayout with the keys a drag emits. The legend, update menus, sliders, modebar and range selector buttons follow in the tab order. config.a11y.keyboard: false takes the plot area out of the tab order.
The accessibility guide lists every key, what screen readers announce and the tab order.
In the example, the plot area is focused and navigated from code (four presses of → and one of Page Down), so it shows the focus ring and the hover label of the point that was reached:
accessibility/keyboard-focusTouch
Mouse, pen and touch all go through the same Pointer Events handling, so every chart works on phones and tablets without configuration:
- Tap: hovers the points under the finger (labels, spike lines, the
hoverevent) and emitsclick, as a mouse moving there and clicking would. The labels stay after the finger lifts. - Tap elsewhere: moves the hover there, or hides it (
unhover) on empty space or anywhere off the chart. - Double tap: like a double-click, it resets the axes (
config.doubleClick) and emitsdoubleclick. The two taps may be up to 30 px apart. - Pinch: zooms every movable axis of the subplot around the fingers' midpoint, in every 2D
dragmodeexceptfalse. - Two-finger drag: pans; together with a pinch, the data under the fingers follows them.
- One-finger drag: what the
dragmodedoes with a mouse (a zoom box, a pan, a box or lasso selection, a shape). It starts after 10 px of travel; a mouse needs 3.
A pinch or two-finger drag previews the new ranges at the display's frame rate (relayouting) and commits them with one relayout when a finger lifts, like a mouse drag; fixedrange, minallowed / maxallowed, matches and scaleanchor apply. A second finger cancels a zoom box or selection in progress; once two fingers were down, the gesture stays a two-finger one until every finger lifts. Views that take drags (a sankey node, a range slider, a table) keep a drag their first finger started and ignore extra fingers.
Page scrolling
A chart on a phone is often as wide as the screen, so it must not swallow the swipes that scroll the page. The browser decides who gets a touch gesture from the canvas' CSS touch-action when the finger lands, and Holochart sets it to the least the chart needs:
pan-yfordragmode: 'zoom'(the default),'pan'when every y axis isfixedrange, and sankey and polar charts: a swipe that starts vertically scrolls the page; one that starts sideways is the chart's (a zoom box may then grow in any direction). Pinches and taps are the chart's.nonefordragmode: 'pan','select','lasso'(polar charts included) and the drawing modes, and for tables, parcoords, parcats and 3D scenes: every gesture is the chart's, so the page doesn't scroll over it.manipulationfordragmode: false, charts whose axes are allfixedrange, and charts without cartesian axes (pie, sunburst, treemap, …): swipes and pinches are the page's; taps still hover, click and drill.
So to keep a chart from ever trapping the page on phones, set dragmode: false (or fixedrange on its axes): taps keep working, and the modebar, which appears after a tap, switches modes on demand. With a coarse pointer its buttons grow to 32 px tap targets. Custom trace types whose views take drags declare what they need with touchAction on their module.
The example stacks three charts, one per touch-action: dragmode: 'zoom' (vertical swipes scroll the page), 'pan' (every swipe pans) and false (taps only). Try it on a touch screen or with the device emulation of your browser's developer tools:
interaction/touchDifferences from Plotly
- Plotly has no pinch zoom on cartesian subplots. Holochart pinches whatever
config.scrollZoomsays (that one is about the mouse wheel). - Plotly blocks page scrolling over any chart whose
dragmodeisn'tfalse. Holochart leaves vertical swipes to the page in zoom mode, the default. - Holochart does not support pinch zoom on polar subplots: their axes drag with one finger.
- Under
dragmode: false, touch drags of the range slider and of editable shapes and annotations go to the page, not to the chart. - Holochart does not support
layout.newselection,layout.activeselection,layout.selectionrevisionandconfig.editSelection. The vertices of a lasso selection can't be dragged one by one. - Events are subscribed on the chart (
chart.on), not on the element. See Coming from Plotly.