Interaction
Charts respond to hover, click, and legend interaction out of the box, and report everything through optional callback props. Almost every interaction on this page also works from the keyboard (see Accessibility). The exceptions are value axis focus and a series' per-category focus (focusCategoryOnHover and focusCategoryOnClick), which need a pointer: a value axis is not a tab stop, and the keyboard activates a series as a whole.
Hover a legend entry to focus its series, click the plot to pin the tooltip and crosshair, and click legend entries to filter series in and out, which plays the staged series transition:
Focus
Hovering or clicking a series (per its focusOnHover / focusOnClick config, both off by default) focuses it: the focused series is styled from the focused state of its styles and every other series from their defocused state. By default those states change only opacity and width: their colors are 'same', meaning "keep the normal state's color". The legend drives the same focus via legend.focusOnHover (on by default) and legend.focusOnClick (off).
A value axis is focused by hovering it (valueAxes.focusOnHover, on by default) or clicking it (valueAxes.focusOnClick, off).
Hover and click mean different things, and they work together. Hover focus is a preview: it lasts while the pointer is on the item and clears when the pointer leaves. A click pins the focus, so it outlives the pointer. The rules are the same for a series, a value axis and a category, whether the click lands on a series shape, a legend item, an axis or a tooltip row:
- Hovering an item focuses it. Leaving clears the focus, or returns it to the pinned item when there is one.
- Clicking an item pins it, whether or not hover already focused it. Clicking another item moves the pin there.
- Clicking the pinned item releases the pin and clears the focus. Hovering it again previews it again.
- The keyboard follows the same rules: Tab onto a legend item or tooltip row previews like hover, Enter or Space pins like a click, and leaving with Tab clears like the pointer leaving.
- A filtered series, or one a data change removes, loses its pin with its focus.
- A structural config change clears every focus and pin, along with the legend filters.
This is why enabling both settings makes sense: hover previews for mouse users, and a tap pins for touch users, who have no hover. With a controlled focus prop the pin only changes what the chart asks for through onFocus when the pointer leaves, the pinned item instead of nothing; a host that passes back a different value releases the pin.
Series and axis focus feed each other: a series with useAxisFocus (on by default) shows as focused whenever the value axis it belongs to is, and an axis with useSeriesFocus (on by default) shows as focused whenever one of its series is (the axis line, grid and ticks only); the axis's other series are unaffected.
Category focus has knobs of its own: the series' focusCategoryOnHover and focusCategoryOnClick focus the category the pointer is on. Those are off by default, but the tooltip focuses its category anyway while tooltip.applyFocus or crosshair.applyFocus is set (both on by default), so a plain plot click reports a focused category through onFocus.
The *OnHover configs act on hovering pointers only: a mouse, a trackpad, or a pen held over the chart. A touch tap never counts as a hover (browsers emulate one right before the tap's click), so on touch screens a tap does only what the matching *OnClick config says; give touch users focusOnClick or legend.focusOnClick where they should be able to focus.
Every focus change (series, value axis or category) animates over focusDuration.
Legend filtering
With legend.filterOnClick enabled, clicking a legend item toggles its series out of (and back into) the chart. The item stays in the legend so the series can be restored, and the removal/return animates as a series transition. Its color icon goes hollow to mark it filtered; set legend.strikeThroughFiltered to strike through the item text as well, and tooltip.strikeThroughFiltered to do the same to the series' tooltip label. Both default to false. Set filterable: false on a series to exempt it from legend (and tooltip) filtering entirely, and showColorInLegend: false to drop the color icon from its legend item.
filterable and legend.filterOnClick decide only whether a click can toggle a series. A series that is already filtered stays filtered when a config update sets its filterable to false, turns filterOnClick off or hides the legend, even though no click can then bring it back. The host clears the filters with update({ filteredSeriesIds: {} }), and a structural config change clears them too.
Tooltip and crosshair
tooltip and crosshair style the tooltip and crosshair shown for the focused category. Per-series formatting of tooltip values (label, prefix/suffix, d3-format string) lives on the series (valueLabel, valueFormat, and friends).
The tooltip can drive focus and filtering too, all off by default: tooltip.focusCategoryOnHover and tooltip.focusCategoryOnClick focus the category value the pointer is on inside the tooltip; focusSeriesOnHover, focusSeriesOnClick and filterSeriesOnClick do the same for the series rows. Each series can also drop its color icon from the tooltip rows with showColorInTooltip: false.
Tooltip controls
tooltip.showControls adds a control strip above the tooltip's lines: ‹ and › buttons step the shown category, and a mode button toggles what clicking a tooltip row does. In filter mode (the initial mode) clicking a series row toggles its series out of the chart, exactly like a legend click (respecting filterable), and hovering a row focuses its series the way hovering its legend item does; in focus mode clicking a row pins focus on its series (or, on the category line, on the category). With the controls shown, the mode decides row behavior and the focus…OnClick / filterSeriesOnClick / focusSeriesOnHover settings above are not consulted. tooltip.focusCategoryOnHover is the exception: hovering the category line focuses its category only where that is set as well, and only in filter mode. The mode button shows the active mode via tooltip.filterModeText / tooltip.focusModeText ('Filter' / 'Focus' by default), and the step buttons take their accessible labels from accessibility.tooltipPreviousLabel and accessibility.tooltipNextLabel. The buttons and rows all work from the keyboard (see the keyboard map).
Callbacks
All callbacks are optional props on either entry point:
createDefaultChart(container, {
config, data, width, height,
onFocus: ({ focusedSeriesId, focusedCategoryIndex }) => { /* focus changed */ },
onSeriesFilter: ({ filteredSeriesIds }) => { /* the filtered set changed */ },
onChartClick: ({ categoryIndex, chartX, chartY }) => { /* plot area clicked */ },
onSliceClick: ({ seriesId }) => { /* pie slice clicked */ },
onSeriesClick: ({ seriesId, categoryIndex, nearestCategoryIndex }) => { /* bar/point/line clicked */ },
onTitleClick: () => {}
});onFocus(focus): the focused series/category/value axis changed. Focus from the series shapes is opt-in; legend hover, value axis hover and tooltip-driven category focus are on by default (see Focus)onSeriesFilter(filter): the filtered set changed, because a legend or tooltip-row click toggled a series in or out of it, or a structural config change cleared itonChartClick/onChartMouseEnter/onChartMouseMove/onChartMouseLeave: plot-area pointer events with chart coordinates and the index of the nearest category in the data last supplied, the same index spaceonFocusreports. While a data change that adds or removes categories animates, the chart still draws the old ones, and a pointer over a category the new data no longer has reports-1onSliceClick(payload): a slice of a pie or donut chart was clickedonSeriesClick(payload): a cartesian series shape (bar, marker, label, or line/area path) was clicked; reports the series id, the shape's category index (-1for a whole-series path), and the category index nearest the pointer, both in the data last supplied like the pointer events above. Fires whether or not the series'focusOnClickconfig is set, and only on click, as the cartesian counterpart ofonSliceClickonTitleClick(): the chart title was clicked or activated from the keyboard. Supplying it makes the title a button (tab stop,role="button", an accessible name from the title text, Enter and Space), unlesstitle.linkis set, where the anchor already provides that (see alsolinkDisabled)onSeriesLayoutBoundsChange(bounds): the plot area was re-laid-out
The four pointer callbacks share one payload (ChartEventPayload: pointer coordinates in three frames, plus the nearest category index). onFocus and onSeriesFilter each receive the whole state rather than only what changed. Every callback and every payload field is listed in Callbacks and payloads.
Making a shape clickable doesn't change the mouse cursor. Set showPointer on a series (or seriesDefaults, as the example below does) to give its shapes (including pie slices) cursor: pointer, advertising that clicking does something.
Watch them fire: click a bar, hover a legend entry, or click one to toggle it; the log records each event in order and follows the latest (scroll up for older ones). One interaction often fires several: with focusOnClick set, clicking a bar reports onFocus and onSeriesClick together.
Pie charts report slice clicks through onSliceClick instead:
Controlled focus and filtering
Focus and legend filtering are managed by the chart internally, but each piece of that state has a matching input prop that takes over when it is set (not undefined), overriding the internal state on every update. Pass back what the callbacks report to keep several charts in sync:
focusedCategoryIndex(-1= none),focusedSeriesIdandfocusedValueAxisId(null= none), the controlled form ofonFocusfilteredSeriesIds: the controlled form ofonSeriesFilter
focusedSeriesId, and every key of filteredSeriesIds, should be the id of a series that does not set followSeries. A series that follows another has no focus or filter state of its own (it takes both from the series it follows), so its own id has no effect in either prop. onSeriesFilter reports maps keyed the same way, so passing them straight back keeps charts in sync.
A structural config change resets focus and filtering and reports the reset through onFocus and onSeriesFilter, so a host that keeps its own copy of that state receives the cleared values. To see the reset coming before applying an edit, compare the old and new enhanced configs with hasConfigStructureChange.
chart.update({
focusedCategoryIndex: 2,
focusedValueAxisId: 'VA0',
focusedSeriesId: 'S0',
filteredSeriesIds: { S1: true, S2: true }
});// mirror one chart's focus and filtering onto another
createChart(el, {
...props,
onFocus: focus => other.update({
focusedCategoryIndex: focus.focusedCategoryIndex,
focusedValueAxisId: focus.focusedValueAxisId,
focusedSeriesId: focus.focusedSeriesId
}),
onSeriesFilter: ({ filteredSeriesIds }) => other.update({ filteredSeriesIds })
});Leave a prop undefined to let the chart keep managing that piece itself. The framework bindings hand the chart their whole prop set on every render, so this works there as written. With the core handle, update treats an undefined key as no change and keeps the old value, so release a controlled prop by calling replace() with a prop set that leaves it out.