A tooltip shows up to three parts by default: a title, the series’ name and the value. The title is the value’s meta when it’s a string or a number, otherwise its label; a part that’s empty or repeats an earlier one is left out. This page changes what a tooltip says, what it looks like and where it shows.
Your own text
format gets the hovered value and returns its tooltip’s text. Line breaks show as line breaks. null or an empty string means no tooltip for that value, and undefined keeps the default text:
import 'chartist/styles.css';
import '@chartist-js/tooltips/styles.css';
import { barChart } from 'chartist';
import { tooltips } from '@chartist-js/tooltips';
const orders = new Intl.NumberFormat('en-US');
barChart(
'#chart',
{
labels: ['Q1', 'Q2', 'Q3', 'Q4'],
series: [{ name: 'Orders', data: [1830, 2410, 0, 2950] }]
},
{
plugins: [
tooltips({
// Text with a line break; no tooltip for a quarter without orders.
format: item =>
item.value === 0
? null
: `${item.label}\n${orders.format(Number(item.value))} orders`
})
]
}
);The item format gets has the value’s role, seriesIndex and valueIndex, its value (a number, or { x, y } when it has both, as in a scatter chart), its meta, the series and its seriesName, its label and its element. The text is also the focused value’s accessible name, so write it to be read out too.
For numbers, the default text is often enough: numberFormat takes Intl.NumberFormat options, in the reader’s locale.
import { barChart } from 'chartist';
import { tooltips } from '@chartist-js/tooltips';
barChart(
'#chart',
{ labels: ['Q1', 'Q2', 'Q3'], series: [[1250, 1830, 1470]] },
{
plugins: [
tooltips({ numberFormat: { style: 'currency', currency: 'CHF' } })
]
}
);
Your own markup
The default text is text only: data, meta and format can’t put markup into the page. For markup, render fills the tooltip yourself. It gets the tooltip’s content element, emptied for each value, the hovered item and the chart’s data, so a tooltip can compare a value with its neighbours:
import 'chartist/styles.css';
import '@chartist-js/tooltips/styles.css';
import './style.css';
import { lineChart } from 'chartist';
import { tooltips } from '@chartist-js/tooltips';
lineChart(
'#chart',
{
labels: ['Mon', 'Tue', 'Wed', 'Thu', 'Fri'],
series: [{ name: 'Visitors', data: [1200, 1500, 1350, 1600, 1100] }]
},
{
low: 0,
plugins: [
tooltips({
// Your own markup: the value, and the change since the day before,
// read from the chart's data.
render(content, item, data) {
const values = data.series[item.seriesIndex ?? 0].values as number[];
const value = Number(item.value);
const before = values[(item.valueIndex ?? 0) - 1];
const title = document.createElement('strong');
title.textContent = `${item.label}: ${value} visitors`;
content.append(title);
if (before !== undefined) {
const change = Math.round(((value - before) / before) * 100);
const trend = document.createElement('span');
trend.className = change < 0 ? 'down' : 'up';
trend.textContent = `${change < 0 ? '' : '+'}${change}% since ${
data.labels[(item.valueIndex ?? 0) - 1]
}`;
content.append(trend);
}
}
})
]
}
);render also takes a framework’s template: mount a component into the content element, and return a function that unmounts it. The plugin calls it before the next value’s render, when the tooltip hides and on destroy(). Content a framework fills in later is fine: the tooltip is placed again when its content changes size.
tooltips({
render(content, item) {
// Angular: a dynamic component in the tooltip.
const ref = createComponent(ValueTooltip, {
environmentInjector,
hostElement: content
});
ref.setInput('item', item);
appRef.attachView(ref.hostView);
return () => ref.destroy();
}
});
With render, format still decides which values have a tooltip and gives their accessible name; without format, the name is the content’s text. What render does is your code: setting an HTML string with innerHTML needs your own Trusted Types policy on a page that requires them.
Which values, and where
roles picks the nodes that get tooltips: point, bar and slice by default. Any role works, including the roles of other layers and plugins: roles: ['line'] gives each series of a line chart one tooltip, with its name, and a target line’s tooltip shows its label and value. For what the default text doesn’t know, item.context is the node’s data context, so format can describe a trend line by its slope, and by its correlation how well it fits the values (squared, the R² spreadsheets show):
import { lineChart } from 'chartist';
import { trendLine } from '@chartist-js/essentials';
import { tooltips } from '@chartist-js/tooltips';
lineChart(
'#chart',
{ series: [{ name: 'Visitors', data: [12, 15, 14, 18, 21] }] },
{
layers: { above: [trendLine()] },
plugins: [
tooltips({
roles: ['point', 'trendLine'],
format: item => {
if (item.role !== 'trendLine') {
// Points keep the default text.
return undefined;
}
const { slope, correlation } = item.context;
const fit =
correlation === undefined
? ''
: `, R² ${(correlation ** 2).toFixed(2)}`;
return `${item.seriesName}: ${slope >= 0 ? '+' : ''}${slope.toFixed(1)} a day${fit}`;
}
})
]
}
);
Thin lines are hard to hover: give them a wider stroke in your CSS, or use the keyboard, which reaches every role in roles.
Roles from your own layers
The plugin doesn’t keep a list of roles it knows: it works from the chart’s scene. So the roles of your own layers and of layers from other packages get tooltips the same way, once you list them. This chart’s dots come from the lollipop layer of the custom layers guide, with a role of its own:
import 'chartist/styles.css';
import '@chartist-js/tooltips/styles.css';
import './style.css';
import {
autoScaleAxis,
axisGrid,
axisLabels,
cartesianAxes,
chart,
stepAxis
} from 'chartist';
import { tooltips } from '@chartist-js/tooltips';
import { lollipop } from './lollipop';
// The lollipop layer gives its dots a role of its own. Listed in `roles`,
// they get tooltips. Their context follows Chartist's conventions
// (seriesIndex, valueIndex, value), so the default text knows what to show.
chart('#chart', {
data: {
labels: ['Mon', 'Tue', 'Wed', 'Thu', 'Fri'],
series: [{ name: 'Visits', data: [12, 9, 14, 7, 11] }]
},
axes: cartesianAxes({
x: { type: stepAxis() },
y: { type: autoScaleAxis() }
}),
layers: [axisGrid(), lollipop(), axisLabels()],
plugins: [tooltips({ roles: ['lollipop'] })]
});What the default text shows depends on the node’s context: a layer that gives its nodes seriesIndex, valueIndex and value, as Chartist’s own layers do, gets the label, the series’ name and the value without any setup. Anything else a layer puts into a node’s context reaches format and render as item.context. It’s typed by the roles you list: checking item.role narrows item.context to that role’s context, for Chartist’s roles and for the roles other layers declare in NodeRoles alike.
import { lineChart } from 'chartist';
import { tooltips } from '@chartist-js/tooltips';
// A role of a forecast layer from another package, as such layers declare it.
declare module 'chartist' {
interface NodeRoles {
forecast: {
ctx: { readonly low: number; readonly high: number };
attrs: object;
};
}
}
lineChart(
'#chart',
{ series: [[12, 15, 14]] },
{
plugins: [
tooltips({
roles: ['point', 'trendLine', 'forecast'],
format: item => {
if (item.role === 'forecast') {
// Numbers here: TypeScript knows the forecast's context.
return `Forecast: ${item.context.low} to ${item.context.high}`;
}
if (item.role === 'trendLine') {
return `Trend: ${item.context.slope.toFixed(1)} a day`;
}
return undefined;
}
})
]
}
);
Returning undefined keeps the default text, so a format like this one handles its own roles and leaves points as they were.
position: 'pointer' makes the tooltip follow the mouse while it moves over a value. Touch and keyboard always place it at the value: at a point, at a bar’s end, at the middle of a slice’s arc. It shows above, below when there’s no room above, and moves sideways to stay in the viewport.
Live charts
With liveDomRenderer(), a tooltip stays on its value while the chart updates, shows the new value, and moves in step with the update animation. Hover a point:
import 'chartist/styles.css';
import '@chartist-js/tooltips/styles.css';
import { lineChart } from 'chartist';
import { liveDomRenderer, smilAnimations } from 'chartist/live';
import { tooltips } from '@chartist-js/tooltips';
let visitors = [12, 15, 14, 18, 21, 19, 23, 22];
// Hover a point: the tooltip stays on it while the chart updates, and
// moves with the update animation.
const chart = lineChart(
'#chart',
{ series: [{ name: 'Visitors', data: visitors }] },
{
low: 0,
renderer: liveDomRenderer({ animations: smilAnimations() }),
plugins: [tooltips()]
}
);
setInterval(() => {
const last = visitors[visitors.length - 1];
const next = Math.max(0, last + Math.round(Math.random() * 8 - 4));
visitors = [...visitors.slice(1), next];
chart.update({ data: { series: [{ name: 'Visitors', data: visitors }] } });
}, 1500);Styling
The tooltip is .ct-tooltip, with .ct-tooltip-title, .ct-tooltip-series and .ct-tooltip-value inside, or .ct-tooltip-text for format’s text. Its looks are custom properties. Set them on the page, or on a chart’s container for one chart:
/* The tooltip's look is custom properties: here on the chart's container. */
#chart {
--ct-tooltip-background: #1e3a8a;
--ct-tooltip-color: #fff;
--ct-tooltip-radius: 0;
--ct-tooltip-padding: 0.5rem 0.75rem;
--ct-tooltip-offset: 14px;
--ct-tooltip-hide-delay: 600ms;
}
/* And plain CSS, through the class the plugin adds. */
.fruit-tooltip .ct-tooltip-value {
font-size: 1.25em;
font-weight: 700;
}
/*
* A callout arrow toward the value. The plugin says on which side the
* tooltip is (data-placement) and where the value is from its left edge
* (--ct-tooltip-anchor-x), even when the tooltip moved to stay in view.
*/
.fruit-tooltip::after {
content: '';
position: absolute;
left: var(--ct-tooltip-anchor-x, 50%);
translate: -50% 0;
border: 7px solid transparent;
}
.fruit-tooltip[data-placement='top']::after {
top: 100%;
border-top-color: #1e3a8a;
}
.fruit-tooltip[data-placement='bottom']::after {
bottom: 100%;
border-bottom-color: #1e3a8a;
}| Property | What it sets |
|---|---|
--ct-tooltip-background |
The background; dark on light pages and light on dark ones |
--ct-tooltip-color |
The text color |
--ct-tooltip-border |
The border, as the border shorthand |
--ct-tooltip-radius |
The corner radius |
--ct-tooltip-padding |
The padding |
--ct-tooltip-font |
The font, as the font shorthand |
--ct-tooltip-shadow |
The shadow |
--ct-tooltip-max-width |
The widest the tooltip gets before its text wraps |
--ct-tooltip-offset |
The distance from the value, in px |
--ct-tooltip-hide-delay |
How long it stays after the pointer leaves |
--ct-tooltip-focus-color |
The color of the focused value’s outline |
--ct-tooltip-focus-width |
The width of that outline |
--ct-tooltip-series-color |
Set by the plugin: the series’ color, for the swatch before its name |
--ct-tooltip-anchor-x |
Set by the plugin: the value’s place from the tooltip’s left edge, for an arrow |
The example also draws a callout arrow toward the value. The plugin sets data-placement on the tooltip (top or bottom, the side of the value it shows on) and --ct-tooltip-anchor-x, the value’s place from the tooltip’s left edge, which stays right when the tooltip moved sideways to stay in view. Set --ct-tooltip-offset to at least the arrow’s size.
The colors follow the page’s color-scheme, like Chartist’s own. className adds classes to the tooltip, for plain CSS beyond the properties. The tooltip stays while the pointer is over it, and the hide delay is a CSS transition, which reduced motion turns off.