Tooltip
Enabled by default: hovering shows the nearest node, the node itself is highlighted, and the other series are dimmed.
Modular build
When building with grafit-charts/core, the tooltip is a separate module: register(tooltipModule).
By default the tooltip shows the xField value as the heading and a "series name: value" pair with the series marker. Point series (scatter, bubble) are the exception: both of their axes are measures, so the heading identifies the series (marker + name) and the values come as labelled rows — xName: x, yName: y, plus sizeName: size for bubble. The content is customized via tooltip.renderer on the series:
import { getData } from './data';
import type { ChartOptions } from 'grafit-charts';
const rub = new Intl.NumberFormat('ru-RU', { style: 'currency', currency: 'RUB', maximumFractionDigits: 0 });
export function createOptions(): ChartOptions {
return {
data: getData(),
title: { text: 'Orders this week' },
series: [
{
type: 'bar',
xField: 'day',
yField: 'orders',
name: 'Orders',
tooltip: {
// renderer returns the tooltip structure; datum gives access to the whole data row
renderer: ({ datum, yValue, color }) => ({
heading: `${datum.day} — ${yValue} orders`,
rows: [{ label: 'Revenue', value: rub.format(datum.revenue as number), color }],
}),
},
},
],
legend: { enabled: false },
};
}export function getData() {
return [
{ day: 'Mon', orders: 142, revenue: 388000 },
{ day: 'Tue', orders: 158, revenue: 412000 },
{ day: 'Wed', orders: 149, revenue: 395500 },
{ day: 'Thu', orders: 173, revenue: 460200 },
{ day: 'Fri', orders: 196, revenue: 540800 },
{ day: 'Sat', orders: 224, revenue: 618400 },
{ day: 'Sun', orders: 201, revenue: 552300 },
];
}Modes
mode: 'single' (default) shows the value of the nearest node; mode: 'shared' shows the values of all visible series of the category in a single tooltip; nodes of all series are highlighted and no dimming is applied:
import { getData } from './data';
import type { ChartOptions } from 'grafit-charts';
export function createOptions(): ChartOptions {
return {
data: getData(),
title: { text: 'Shared tooltip' },
subtitle: { text: "mode: 'shared' + range: 'nearest' — tooltip from anywhere" },
series: [
{ type: 'line', xField: 'month', yField: 'plan', name: 'Plan' },
{ type: 'line', xField: 'month', yField: 'fact', name: 'Actual' },
{ type: 'line', xField: 'month', yField: 'forecast', name: 'Forecast', lineDash: [4, 3] },
],
tooltip: { mode: 'shared', range: 'nearest' },
crosshair: { enabled: true },
};
}export function getData() {
return [
{ month: 'Jan', plan: 120, fact: 134, forecast: 128 },
{ month: 'Feb', plan: 125, fact: 118, forecast: 124 },
{ month: 'Mar', plan: 130, fact: 142, forecast: 136 },
{ month: 'Apr', plan: 135, fact: 129, forecast: 133 },
{ month: 'May', plan: 140, fact: 151, forecast: 146 },
{ month: 'Jun', plan: 145, fact: 148, forecast: 150 },
];
}The mode means the same thing wherever series share a category: a radar carries a measure per series the way a line chart does, so a spoke under the cursor answers for all of them at once — and so do a rose and radial columns, which share their angle axis too.
import { getData } from './data';
import type { ChartOptions } from 'grafit-charts';
export function createOptions(): ChartOptions {
return {
data: getData(),
title: { text: 'Shared tooltip on a radar' },
subtitle: { text: "mode: 'shared' — every measure of the spoke under the cursor" },
series: [
{ type: 'radar-line', angleField: 'metric', radiusField: 'alpha', name: 'Alpha' },
{ type: 'radar-line', angleField: 'metric', radiusField: 'beta', name: 'Beta' },
{ type: 'radar-line', angleField: 'metric', radiusField: 'gamma', name: 'Gamma' },
],
tooltip: { mode: 'shared' },
};
}export function getData() {
return [
{ metric: 'Speed', alpha: 8.1, beta: 6.4, gamma: 4.9 },
{ metric: 'Quality', alpha: 7.4, beta: 8.6, gamma: 6.1 },
{ metric: 'Coverage', alpha: 5.2, beta: 7.1, gamma: 8.4 },
{ metric: 'Uptime', alpha: 9.0, beta: 8.2, gamma: 7.6 },
{ metric: 'Cost', alpha: 4.5, beta: 6.8, gamma: 8.9 },
{ metric: 'Support', alpha: 6.3, beta: 5.4, gamma: 7.2 },
];
}Options (chart-level)
| Option | Type | Default | Description |
|---|---|---|---|
enabled | boolean | true | show the tooltip |
mode | 'single' | 'shared' | 'single' | one node or the whole category |
position.anchorTo | 'node' | 'center' | 'pointer' | 'node' | node edge, node center, or the cursor |
position.xOffset | Pixels | 0 | tooltip offset |
yOffset | Pixels | 0 | tooltip offset |
range | Pixels | 'exact' | 'nearest' | 30 | number — radius in px; 'exact' — only direct hits on a node; 'nearest' — nearest node from anywhere in the plot area |
container | 'chart' | 'body' | HTMLElement | 'chart' | where the tooltip element lives — see Outside the chart |
zIndex | number | 10; outside the chart — 1000 | CSS z-index of the tooltip element |
Outside the chart
The tooltip is a DOM element inside the chart container, kept within its bounds. In a dashboard tile with overflow: hidden, or on a chart a few dozen pixels tall, that is not enough room: the tooltip is clipped or covers the chart itself. container: 'body' moves it to document.body with position: fixed — it is placed next to the node in viewport coordinates and kept within the viewport, so clipping ancestors no longer cut it:
Charts.create({
container,
tooltip: { container: 'body' },
series: [{ type: 'area', xField: 'day', yField: 'visits', marker: { showOn: 'hover' } }],
});A 48-pixel KPI strip in a frame that clips its content, like a dashboard card. Hover it, then switch the tooltip inside the chart with the button below to see the difference:
import { getData } from './data';
import type { ChartInstance, ChartOptions } from 'grafit-charts';
const bareAxis = { label: { enabled: false }, line: { enabled: false }, tick: { enabled: false }, gridLine: { enabled: false } };
// A KPI strip in a tile that clips its content, as a dashboard card does. Inside
// the chart the tooltip is held within a strip shorter than itself and cut by the
// tile; in the body it sits by the node whole, reaching past the tile's edges.
export function createOptions(): ChartOptions {
return {
data: getData(),
series: [
{
type: 'line',
xField: 'month',
yField: 'revenue',
name: 'Revenue',
marker: { showOn: 'hover' },
tooltip: {
renderer: ({ datum, color }) => ({
heading: String(datum.month),
rows: [{ label: 'Revenue', value: `${Number(datum.revenue).toFixed(2)} M`, color }],
}),
},
},
],
axes: [
{ type: 'category', position: 'bottom', paddingOuter: 0, ...bareAxis },
{ type: 'number', position: 'left', nice: false, ...bareAxis },
],
legend: { enabled: false },
// no floor under the measured size: the strip is exactly as short as its card
minHeight: 0,
padding: { top: 6, right: 8, bottom: 6, left: 8 },
tooltip: { container: 'body', range: 'nearest' },
};
}
// Buttons under the demo: the same strip with the tooltip in either home.
export const actions = [
{
label: 'Tooltip in the body',
run: (chart: ChartInstance) => void chart.updateDelta({ tooltip: { container: 'body' } }),
},
{
label: 'Tooltip inside the chart',
run: (chart: ChartInstance) => void chart.updateDelta({ tooltip: { container: 'chart' } }),
},
];export function getData() {
return [
{ month: 'Jan', revenue: 41.2 },
{ month: 'Feb', revenue: 43.8 },
{ month: 'Mar', revenue: 42.5 },
{ month: 'Apr', revenue: 47.1 },
{ month: 'May', revenue: 49.6 },
{ month: 'Jun', revenue: 48.3 },
{ month: 'Jul', revenue: 51.9 },
{ month: 'Aug', revenue: 53.4 },
{ month: 'Sep', revenue: 52.2 },
{ month: 'Oct', revenue: 55.7 },
{ month: 'Nov', revenue: 57.1 },
{ month: 'Dec', revenue: 58.65 },
];
}An element instead of 'body' puts the tooltip into that element — for apps that keep overlays in a layer of their own; it is positioned the same way. Outside the chart:
- the tooltip is placed by the node just as inside the chart — right of it and above, flipping where the viewport runs out — only the chart's edges no longer hold it in;
- scrolling the page or any scrollable ancestor, and resizing the window, hide the tooltip — the next pointer move shows it again in the right place;
destroy()removes the element along with the chart;- the theme font, size, weight and colors are set on the element itself, since it inherits nothing from the chart container;
position: fixedis relative to the viewport unless an ancestor of the element hastransform,filterorcontain— keep such styles off the layer you pass.
Appearance
The tooltip container is styled in the same tooltip block:
| Option | Type | Default | Description |
|---|---|---|---|
background | ColorValue | theme background | background |
borderColor | ColorValue | theme muted color | border color |
borderWidth | Pixels | 1 | border width; 0 removes the border |
borderRadius | Pixels | 6 | corner radius |
shadow | string | false | 0 2px 8px rgba(0,0,0,.25) | CSS box-shadow; false — no shadow |
padding | PaddingValue | string | 7px 10px | inner padding: 8, [8, 12], [8, 12, 4, 0], { top, right, bottom, left } or a CSS string |
fontSize | Pixels | 12 | font size |
fontFamily | string | system-ui, sans-serif | font family |
color | ColorValue | theme foreground | text color |
tooltip: {
background: '#141821',
color: '#f0f0f0',
borderColor: '#436ff4',
borderRadius: 10,
shadow: false,
},Series options
series[].tooltip.renderer(params) returns a string (which becomes the heading) or a structure:
renderer: ({ datum, xValue, yValue, seriesName, color }) => ({
heading: String(xValue),
rows: [{ label: seriesName, value: String(yValue), color }],
});params.datum is the entire data row: the tooltip can display fields that are not part of the series.
heading is a string or a { text, color } object; with color a marker matching the row markers is drawn before the heading text. This is how scatter and bubble render their default tooltip — the marker identifies the series rather than any single row:
renderer: ({ datum, xValue, yValue, seriesName, color }) => ({
heading: { text: String(datum.category), color },
rows: [
{ label: 'X', value: String(xValue) },
{ label: 'Y', value: String(yValue) },
],
});Highlighting is controlled by the highlight block:
| Option | Type | Default | Description |
|---|---|---|---|
enabled | boolean | true | highlight the node and dim the other series |
dimOpacity | Fraction | 0.8 | opacity of non-highlighted series (1 — no dimming) |
highlight.enabled | boolean | true | node highlighting and dimming |
highlight.dimOpacity | Fraction | 0.8 | opacity of non-highlighted series |