Tooltips in Semiotic are powered by the annotation system. The simplest way to enable them is with hoverAnnotation={true}, which adds a default tooltip on hover. For richer content, Semiotic provides Tooltip and MultiLineTooltiputility functions that handle common formatting patterns, plus a fully custom tooltipContent prop for complete control.
When you set an axis formatter on a chart — valueFormaton ordinal charts (BarChart, StackedBarChart, GroupedBarChart, DotPlot, SwarmPlot, SwimlaneChart) or xFormat/yFormat on XY charts (LineChart, AreaChart, Scatterplot, BubbleChart, etc.) — the same formatter is applied to the default tooltip. One function, both places, so a bar chart that reads "$450k" on its axis also reads "$450k" on hover.
valueFormat applies to both axis and tooltip
import { BarChart } from "semiotic" const frameProps = { /* --- Data --- */ data: barData, /* --- Process --- */ valueAccessor: "sales", categoryAccessor: "category", /* --- Customize --- */ showGrid: true, valueLabel: "Sales", valueFormat: d => `${(d / 1000).toFixed(1)}k` } export default () => { return <BarChart {...frameProps} /> }
Precedence
The cascade only drives the default tooltip. Passingtooltip explicitly takes over:
tooltip={false} — no tooltip is rendered.tooltip={customFn}, {"multi"}, or {Tooltip({...})}/{MultiLineTooltip({...})} — your content fully replaces the default. Axis formatters do notapply automatically; re-pass valueFormat/xFormat inside your tooltip if you want them.- Default tooltip active — the chart's
valueFormator xFormat/yFormat is applied to the matching field. - A few charts format internally (Histogram, FunnelChart, LikertChart, GaugeChart) and don't participate in the cascade — customize via the
tooltip prop if needed.
Supplementing or overriding
To keep the default tooltip but add a custom format for one field, pass a function and call the formatter yourself:
JSX
const money = d => `${(d / 1000).toFixed(1)}k` <BarChart data={data} categoryAccessor="category" valueAccessor="sales" valueFormat={money} // → axis + default tooltip /> // Or override the tooltip entirely — cascade is bypassed, so // re-apply the formatter explicitly: <BarChart data={data} categoryAccessor="category" valueAccessor="sales" valueFormat={money} // → axis only (tooltip is custom) tooltip={MultiLineTooltip({ title: "category", fields: [ { key: "sales", label: "Sales", format: money }, { key: "profit", label: "Profit", format: money }, ] })} />
With Charts
Chart components enable hover tooltips by default. You can customize the tooltip content using the tooltip prop with theTooltip or MultiLineTooltip utilities:
Smart defaults for custom & network charts
When a chart can't declare tooltip fields — aNetworkCustomChart layout, a recipe like the Mermaid or lineage DAG, anything "weird" — the default tooltip is no longer a dump of every property in object order. It picks a meaningfultitle (a name/label/title field, falling back to id), then atype (type/kind/category/shape…), then avalue, then the rest — skipping positional and internal bookkeeping (x/y/layer/row/depth, _-prefixed keys, nested objects). So a Mermaid node shows its name andtype: decision rather than a bare id. The same heuristic backs the generic MultiLineTooltip default (and the XY/ordinal custom-layout fallbacks) — anywhere a tooltip would otherwise dump every field, it now leads with a title and orders the rest by role. To steer it, give your datum a name (or label) and atype field; to override entirely, pass atooltip function. The same heuristic is exposed as the puresmartTooltipEntries(datum) helper (fromsemiotic).
Default Hover Tooltip
import { LineChart } from "semiotic" const frameProps = { /* --- Data --- */ data: [ { month: 1, revenue: 12000 }, { month: 2, revenue: 18000 }, { month: 3, revenue: 14000 }, // ...more data points ], /* --- Process --- */ xAccessor: "month", yAccessor: "revenue", /* --- Customize --- */ xLabel: "Month", yLabel: "Revenue ($)" } export default () => { return <LineChart {...frameProps} /> }
Use MultiLineTooltip with a Chart's tooltipprop for formatted multi-field tooltips:
JSX
import { BarChart, MultiLineTooltip } from "semiotic" <BarChart data={productData} categoryKey="category" valueKey="sales" tooltip={MultiLineTooltip({ title: "category", fields: [ { key: "sales", label: "Sales", format: v => `${v}` }, { key: "profit", label: "Profit", format: v => `${v}` }, { key: "units", label: "Units Sold" } ] })} />
With Frames
Import Tooltip from Semiotic and pass it totooltipContent along withhoverAnnotation={true}:
import { StreamXYFrame } from "semiotic" import { StreamXYFrame, Tooltip } from "semiotic" const frameProps = { /* --- Data --- */ data: [ { x: 10, y: 20, category: "A", value: 100 }, { x: 25, y: 35, category: "B", value: 150 }, // ...more points ], chartType: "scatter", /* --- Size --- */ margin: { top: 30, bottom: 60, left: 60, right: 20 }, /* --- Process --- */ xAccessor: "x", yAccessor: "y", /* --- Customize --- */ pointStyle: d => ({ fill: colorHash[d.category], r: 5 }), showAxes: true, xLabel: "X Value", yLabel: "Y Value", /* --- Interact --- */ enableHover: true, /* --- Annotate --- */ tooltipContent: hover => Tooltip({ title: "category" })(hover.data || hover) } export default () => { return <StreamXYFrame {...frameProps} /> }
MultiLineTooltip displays multiple data fields with labels and optional formatting:
import { StreamOrdinalFrame } from "semiotic" import { StreamOrdinalFrame, MultiLineTooltip } from "semiotic" const frameProps = { /* --- Data --- */ data: [ { category: "Product A", sales: 450, profit: 120, units: 230 }, { category: "Product B", sales: 380, profit: 95, units: 190 }, { category: "Product C", sales: 520, profit: 145, units: 260 }, { category: "Product D", sales: 290, profit: 75, units: 145 }, { category: "Product E", sales: 610, profit: 180, units: 305 } ], chartType: "bar", /* --- Size --- */ margin: { top: 20, bottom: 80, left: 60, right: 20 }, /* --- Process --- */ oAccessor: "category", rAccessor: "sales", /* --- Customize --- */ pieceStyle: () => ({ fill: "#6366f1", stroke: "white" }), showAxes: true, /* --- Interact --- */ enableHover: true, /* --- Annotate --- */ tooltipContent: hover => { const d = hover.data || hover return MultiLineTooltip({ title: "category", fields: [ { key: "sales", label: "Sales", format: v => `${v}` }, { key: "profit", label: "Profit", format: v => `${v}` }, { key: "units", label: "Units Sold" } ] })(d) } } export default () => { return <StreamOrdinalFrame {...frameProps} /> }
For complete control over tooltip rendering, pass a custom function totooltipContent. The function receives the Stream Frame's HoverData wrapper —{ data, x, y, ... } — where data is the raw datum the user pushed or passed. Read fields offd.data directly:
JSX
<StreamXYFrame data={data} chartType="scatter" enableHover={true} tooltipContent={d => { const datum = d.data return ( <div style={{ background: "var(--surface-1)", border: "1px solid #ccc", padding: "8px 12px", borderRadius: 4, boxShadow: "0 2px 8px rgba(0,0,0,0.15)" }}> <strong>{datum.category}</strong> <div>X: {datum.x}</div> <div>Y: {datum.y}</div> <div>Value: {datum.value.toLocaleString()}</div> </div> ) }} xAccessor="x" yAccessor="y" />
Higher-level HOC props like tooltip (onBarChart, LineChart, etc.) auto-unwrap the wrapper for you, so the function there receives the datum directly —tooltip={d => d.category}. The rawtooltipContent form on Stream Frames is the unwrapped path for callers that need the full hover context.
Advanced hoverAnnotation
The hoverAnnotation prop can accept an array of annotation types for richer hover behavior. This lets you combine tooltips with guide lines and point highlights:
Crosshair Tooltip with Guide Lines
import { StreamXYFrame } from "semiotic" const frameProps = { /* --- Data --- */ data: [{ x: 10, y: 20, category: "A", value: 100 }, { x: 25, y: 35, category: "B", value: 150 }, ... ], chartType: "scatter", /* --- Size --- */ margin: { top: 30, bottom: 60, left: 60, right: 20 }, /* --- Process --- */ xAccessor: "x", yAccessor: "y", /* --- Customize --- */ pointStyle: d => ({ fill: colorHash[d.category], r: 5 }), showAxes: true, xLabel: "X Value", yLabel: "Y Value", /* --- Interact --- */ hoverAnnotation: [ { type: "x", disable: ["connector", "note"] }, { type: "y", disable: ["connector", "note"] }, { type: "frame-hover" } ] } export default () => { return <StreamXYFrame {...frameProps} /> }
Configuration
A factory function that returns a tooltip renderer for a single value:
| Prop | Type | Required | Default | Description |
|---|
title | string | function | — | auto | Field name or function to display as the tooltip header. |
format | function | — | — | Format function for the displayed value. |
style | object | — | — | Custom CSS styles for the tooltip container. |
className | string | — | — | Custom CSS class for the tooltip container. |
JSX
import { Tooltip } from "semiotic" // Basic usage Tooltip({ title: "name" }) // With formatting Tooltip({ title: "category", format: v => v.toUpperCase() }) // With custom styles Tooltip({ title: "name", style: { background: "#1e293b", color: "white" } })
A factory function that returns a tooltip renderer for multiple fields:
| Prop | Type | Required | Default | Description |
|---|
title | string | function | — | — | Header field name or function. |
fields | array | Yes | — | Array of field names (strings) or objects: { key, label, format }. |
showLabels | boolean | — | true | Whether to show field labels. |
separator | string | — | ": " | Separator string between label and value. |
style | object | — | — | Custom CSS styles for the tooltip container. |
className | string | — | — | Custom CSS class for the tooltip container. |
JSX
import { MultiLineTooltip } from "semiotic" // String fields (field name = label) MultiLineTooltip({ title: "product", fields: ["revenue", "units", "category"] }) // Object fields with formatting MultiLineTooltip({ title: "product", fields: [ { key: "revenue", label: "Revenue", format: v => `${v.toLocaleString()}` }, { key: "margin", label: "Margin", format: v => `${(v * 100).toFixed(1)}%` }, { key: "units", label: "Units" } ] })
hoverAnnotation Options
The hoverAnnotation prop accepts several forms:
JSX
// Boolean: default frame-hover tooltip hoverAnnotation={true} // Array: multiple annotation types on hover hoverAnnotation={[ { type: "frame-hover" }, // Tooltip { type: "x", disable: ["connector", "note"] }, // Vertical guide { type: "y", disable: ["connector", "note"] }, // Horizontal guide { type: "highlight", style: { strokeWidth: 5 } }, // Highlight mark { type: "vertical-points", threshold: 0.1 }, // Show nearby points { type: "desaturation-layer", style: { fill: "white", opacity: 0.5 } } ]} // For StreamOrdinalFrame, use pieceHoverAnnotation for individual pieces // vs hoverAnnotation for entire columns <StreamOrdinalFrame pieceHoverAnnotation={true} /> <StreamOrdinalFrame hoverAnnotation={true} /> // column-level hover
Tooltips are positioned automatically near the hovered data point. They render in an HTML layer above the SVG visualization, so they can contain any HTML content. For point-based data,StreamXYFrame uses Voronoi tesselation to determine the nearest data point on hover, providing smooth and responsive tooltip behavior even when points are small.
- Annotations — the full annotation system that powers tooltips
- Interaction — highlighting, cross-highlighting, and custom click/hover behaviors
- StreamXYFrame — point, line, and area tooltips with Voronoi hover
- StreamOrdinalFrame — piece-level and column-level hover annotations
- LineChart — simplified tooltip via the
tooltip prop