Semiotic provides a rich set of interaction capabilities including brushing, cross-highlighting between frames, and custom click/hover behaviors. These features let you build coordinated views, data filtering interfaces, and exploratory visualizations.
With Charts
Chart components support basic hover interaction out of the box. For highlighting behavior, use the frameProps escape hatch to configure hoverAnnotation with highlight types:
Line Highlighting on Hover
import { LineChart } from "semiotic" const frameProps = { /* --- Data --- */ data: [ { month: 1, revenue: 12000, product: "Widget" }, { month: 2, revenue: 18000, product: "Widget" }, // ...more data ], /* --- Layout --- */ lineBy: "product", /* --- Process --- */ xAccessor: "month", yAccessor: "revenue", /* --- Customize --- */ xLabel: "Month", yLabel: "Revenue ($)", colorBy: "product", /* --- Annotate --- */ frameProps: { hoverAnnotation: [ { type: "highlight", style: { strokeWidth: 6, stroke: "rgba(220, 38, 38, 0.45)" } }, { type: "frame-hover" } ], lineIDAccessor: "product" } } export default () => { return <LineChart {...frameProps} /> }
With Frames
Highlight on Hover
Pass an array to hoverAnnotation with ahighlight type to redraw the hovered mark on the annotation layer with custom styling. UselineIDAccessor to tell the frame how to identify which line to highlight.
import { StreamXYFrame } from "semiotic" const frameProps = { /* --- Data --- */ data: [{ group: "Widget", step: 1, value: 12 }, { group: "Widget", step: 2, value: 18 }, ... ], chartType: "line", groupAccessor: "group", /* --- Size --- */ size: [500,300], margin: { top: 20, bottom: 50, left: 50, right: 20 }, /* --- Process --- */ xAccessor: "step", yAccessor: "value", /* --- Customize --- */ lineStyle: (d, i) => ({ stroke: colors[i], strokeWidth: 2, fill: "none" }), showAxes: true, /* --- Interact --- */ hoverAnnotation: [ { type: "highlight", style: () => ({ strokeWidth: 6, stroke: "rgba(220, 38, 38, 0.45)" }) }, { type: "frame-hover", r: 8, style: d => ({ fill: colorForLine(d), stroke: "white", strokeWidth: 2 }) } ], enableHover: true, /* --- Annotate --- */ lineIDAccessor: "group" } export default () => { return <StreamXYFrame {...frameProps} /> }
Desaturation Layer
Combine highlight with desaturation-layerto dim the background and bring the hovered element into focus:
JSX
<StreamXYFrame data={data} chartType="line" lineDataAccessor="coordinates" hoverAnnotation={[ { type: "desaturation-layer", style: { fill: "white", opacity: 0.6 } }, { type: "highlight", style: d => ({ stroke: colorScale(d.key), strokeWidth: 5 }) } ]} lineIDAccessor="title" />
Cross-Highlighting Between Frames
Use customHoverBehavior to capture hover events from one frame and pass highlight annotations to another. This creates coordinated views where hovering on one chart highlights the corresponding data in another.
JSX
| 1 | import React, { useState } from "react" |
| 2 | import { StreamXYFrame } from "semiotic" |
| 3 | |
| 4 | function CoordinatedViews() { |
| 5 | const [annotations, setAnnotations] = useState([]) |
| 6 | |
| 7 | const handleHover = (d) => { |
| 8 | if (d) { |
| 9 | setAnnotations([{ |
| 10 | type: "highlight", |
| 11 | ...d, |
| 12 | style: { stroke: "black", strokeWidth: 5 } |
| 13 | }]) |
| 14 | } else { |
| 15 | setAnnotations([]) |
| 16 | } |
| 17 | } |
| 18 | |
| 19 | return ( |
| 20 | <div style={{ display: "flex", gap: 16 }}> |
| 21 | <StreamXYFrame |
| 22 | data={revenueData} |
| 23 | chartType="line" |
| 24 | lineDataAccessor="coordinates" |
| 25 | enableHover={true} |
| 26 | customHoverBehavior={handleHover} |
| 27 | annotations={annotations} |
| 28 | lineIDAccessor="title" |
| 29 | size={[400, 300]} |
| 30 | /> |
| 31 | <StreamXYFrame |
| 32 | data={profitData} |
| 33 | chartType="line" |
| 34 | lineDataAccessor="coordinates" |
| 35 | enableHover={true} |
| 36 | customHoverBehavior={handleHover} |
| 37 | annotations={annotations} |
| 38 | lineIDAccessor="title" |
| 39 | size={[400, 300]} |
| 40 | /> |
| 41 | </div> |
| 42 | ) |
| 43 | } |
XY Brushing
The interaction prop on StreamXYFrame enables brushing for selecting regions of the chart. Choose betweenxBrush, yBrush, or xyBrush:
JSX
import React, { useState } from "react" import { StreamXYFrame } from "semiotic" function BrushExample() { const [extent, setExtent] = useState([2, 8]) return ( <StreamXYFrame data={data} chartType="line" lineDataAccessor="coordinates" xAccessor="step" yAccessor="value" interaction={{ brush: "xBrush", end: (e) => setExtent(e), extent: extent }} size={[700, 200]} margin={{ left: 50, top: 10, bottom: 50, right: 20 }} /> ) }
MinimapChart
MinimapChart automates the common pattern of a main chart with a smaller brush-enabled overview below it. It manages brush state internally and updates the main chart's extent automatically:
JSX
import { MinimapChart } from "semiotic" <MinimapChart data={data} xAccessor="step" yAccessor="value" lineBy="series" colorBy="series" curve="monotoneX" width={700} height={300} margin={{ left: 50, top: 10, bottom: 40, right: 20 }} minimap={{ height: 50, margin: { left: 50, top: 0, bottom: 10, right: 20 } }} />
Ordinal Brushing
StreamOrdinalFrame supports brushing within columns using theinteraction prop with columnsBrush: true. This is useful for parallel-coordinates-style filtering:
JSX
| 1 | import React, { useState } from "react" |
| 2 | import { StreamOrdinalFrame } from "semiotic" |
| 3 | |
| 4 | function OrdinalBrushExample() { |
| 5 | const [selectedCount, setSelectedCount] = useState(data.length) |
| 6 | const [extent, setExtent] = useState([20, 70]) |
| 7 | |
| 8 | const handleBrush = (e) => { |
| 9 | setSelectedCount( |
| 10 | data.filter(d => d.value >= e[0] && d.value <= e[1]).length |
| 11 | ) |
| 12 | } |
| 13 | |
| 14 | return ( |
| 15 | <> |
| 16 | <p>Points in brushed region: {selectedCount}</p> |
| 17 | <StreamOrdinalFrame |
| 18 | data={data} |
| 19 | rAccessor="value" |
| 20 | oAccessor={() => "singleColumn"} |
| 21 | type="swarm" |
| 22 | projection="horizontal" |
| 23 | interaction={{ |
| 24 | columnsBrush: true, |
| 25 | extent: { singleColumn: extent }, |
| 26 | end: handleBrush |
| 27 | }} |
| 28 | size={[700, 200]} |
| 29 | /> |
| 30 | </> |
| 31 | ) |
| 32 | } |
StreamOrdinalFrame Highlighting
In StreamOrdinalFrame, usepieceHoverAnnotation for individual piece highlighting, or hoverAnnotation for column-level highlighting. Define a pieceIDAccessor to target specific pieces:
JSX
<StreamOrdinalFrame data={stackedData} oAccessor="category" rAccessor="value" type="bar" pieceHoverAnnotation={[ { type: "highlight", style: d => ({ fill: d.group === "A" ? "#6366f1" : "#f59e0b", stroke: "white" }) } ]} pieceIDAccessor="group" // Static highlights via annotations prop annotations={[ { type: "highlight", category: "Beta", style: { fill: "#e11d48", stroke: "none" } } ]} />
Configuration
interaction Prop (StreamXYFrame)
| Prop | Type | Required | Default | Description |
|---|
brush | string | Yes | — | Brush type: "xBrush", "yBrush", or "xyBrush". |
start | function | — | — | Callback fired at the start of a brush. Receives the extent. |
during | function | — | — | Callback fired during brushing. Receives the extent. |
end | function | — | — | Callback fired at the end of a brush. Receives the extent. |
extent | array | — | — | Initial or controlled brush extent. Format depends on brush type. |
columnsBrush | boolean | — | false | For StreamOrdinalFrame: enable per-column brushes (parallel coordinates style). |
Custom Behavior Callbacks
All Frame components accept these callback props for custom interaction handling:
| Prop | Description |
|---|
| customHoverBehavior | Called with the hovered data point (or null on hover-out). Use for cross-highlighting. |
| customClickBehavior | Called with the clicked data point. Use for selection, filtering, or navigation. |
| customDoubleClickBehavior | Called with the double-clicked data point. Use for drill-down or zoom. |
JSX
<StreamXYFrame customHoverBehavior={(d) => { // d is the data point on hover, null on hover-out if (d) { setHighlighted(d.id) } else { setHighlighted(null) } }} customClickBehavior={(d) => { // d is the clicked data point setSelected(d.id) navigateToDetail(d) }} customDoubleClickBehavior={(d) => { // d is the double-clicked data point zoomToRegion(d) }} />
MinimapChart Configuration
The minimap prop on MinimapChart configures the overview chart and brush behavior:
JSX
minimap={{ height: 50, // Height of the overview chart brushDirection: "x", // "x" (default) or "y" showAxes: false, // Show axes in overview (default: false) background: "#f5f5f5", // Background color for overview margin: { left: 50, top: 0, bottom: 10, right: 20 }, lineStyle: (d) => ({ stroke: d.color, strokeWidth: 1 }) }} // Controlled brush state: <MinimapChart brushExtent={[startDate, endDate]} onBrush={(extent) => setExtent(extent)} ... />
- Annotations — the highlight and desaturation-layer types used for interaction effects
- Tooltips — tooltip configuration that works alongside hover interactions
- StreamXYFrame — brushing, minimap, and point-based interactions
- StreamOrdinalFrame — column brushes, piece hover, and ordinal highlighting
- Responsive — making interactive frames responsive to container size