motionchart
v0.2.0
Published
Cinematic React chart library with beautiful animations and storytelling-first charts
Maintainers
Readme
motionchart
Cinematic React chart library with beautiful animations and storytelling-first charts.
Built on Motion (formerly Framer Motion), motionchart renders all charts as animated SVG with zero canvas dependencies. Every chart ships with spring physics, smooth transitions, hover effects, and interactive viewport controls out of the box.
Install
npm install motionchart motionPeer dependencies: React 18+, react-dom, motion >=11
Quick Start
import { MotionBar } from "motionchart";
const data = [
{ label: "Jan", value: 42 },
{ label: "Feb", value: 68 },
{ label: "Mar", value: 55 },
{ label: "Apr", value: 91 },
{ label: "May", value: 73 },
];
export default function SalesChart() {
return (
<MotionBar
data={data}
theme="ocean"
showValues
gradient={{ from: "#06b6d4", to: "#14b8a6" }}
yAxisLabel="Revenue ($k)"
toolbar
responsive
/>
);
}Chart Types
| Chart | Component | Description | Key Props |
|-------|-----------|-------------|-----------|
| Bar | MotionBar | Vertical/horizontal bars, grouped, stacked | data, series, orientation, stacked, viewport |
| Line | MotionLine | Single or multi-series line/area | data, showArea, curve, crosshair, viewport |
| Pie | MotionPie | Pie and donut with center labels | data, innerRadius, centerLabel, cornerRadius |
| Radar | MotionRadar | Spider/radar with polygon or circle grid | axes, series, levels, shape, fillOpacity |
| Scatter | MotionScatter | Scatter plot with optional bubble sizing | data, dotRadius, minDotRadius, maxDotRadius |
| Candlestick | MotionCandlestick | OHLC candles with volume overlay | data, bullishColor, bearishColor, showVolume |
| Gantt | MotionGantt | Project timeline with dependency arrows | tasks, timelineLabels, showProgress, showDependencies |
| Tree | MotionTree | Hierarchical node-link tree | data, direction, levelSpacing, branchWidth |
| Treemap | MotionTreemap | Nested area treemap | data, tilePadding, borderRadius |
| RadialTree | MotionRadialTree | Radial/circular tree layout | data, angularSpread, startAngle, levelSpacing |
| Dendrogram | MotionDendrogram | Hierarchical clustering tree | data, direction, elbowConnectors, showScale |
| Heatmap | MotionHeatmap | Row x column value matrix | data, colorRange, showColorScale, cellGap |
| Funnel | MotionFunnel | Conversion funnel with stage percentages | data, showPercentage, gapBetweenStages |
| Sunburst | MotionSunburst | Radial hierarchical partition | data, innerRadius, padAngle, showLabels |
Common Props (ChartBaseProps)
All charts extend ChartBaseProps unless noted otherwise (e.g. MotionPie has its own props interface but shares most options).
| Prop | Type | Default | Description |
|------|------|---------|-------------|
| width | number | 600 | Chart width in pixels |
| height | number | 400 | Chart height in pixels |
| responsive | boolean \| ResponsiveConfig | false | Fill parent container (see Responsive) |
| theme | ThemeName \| Theme | "midnight" | Built-in theme name or custom theme object |
| color | string | — | Single color applied to all elements |
| colors | string[] | — | Color array cycled by data index |
| gradient | GradientConfig | — | Global gradient { from, to, angle? } applied to all elements |
| animation | AnimationConfig | — | Animation type, stiffness, duration, stagger |
| hover | HoverConfig | — | Hover scale and brightness |
| tooltip | boolean \| TooltipConfig | true | Built-in tooltip or custom render function |
| toolbar | boolean \| ToolbarConfig | false | Export toolbar (CSV, SVG, PNG) |
| showGrid | boolean | true | Show axis grid lines |
| yAxisLabel | string | — | Label displayed along the y-axis |
| xAxisLabel | string | — | Label displayed along the x-axis |
| backgroundColor | string | — | Override theme background color |
| textColor | string | — | Override theme text/label color |
| gridColor | string | — | Override theme grid line color |
| referenceLines | ReferenceLine[] | — | Horizontal/vertical annotation lines |
| className | string | — | CSS class on the root element |
| ariaLabel | string | — | ARIA label for accessibility |
AnimationConfig
| Prop | Type | Default | Description |
|------|------|---------|-------------|
| type | "spring" \| "tween" \| "none" | "spring" | Animation physics model |
| stiffness | number | — | Spring stiffness |
| damping | number | — | Spring damping |
| duration | number | — | Tween duration in seconds |
| ease | string | — | CSS easing function (tween only) |
| staggerDelay | number | 0.04 | Delay between each element's entrance in seconds |
| disabled | boolean | false | Disable all animations |
// Spring (default)
animation={{ type: "spring", stiffness: 120, damping: 14, staggerDelay: 0.05 }}
// Tween
animation={{ type: "tween", duration: 0.6, ease: "easeInOut", staggerDelay: 0.03 }}
// Disabled
animation={{ disabled: true }}HoverConfig
| Prop | Type | Default | Description |
|------|------|---------|-------------|
| enabled | boolean | true | Enable hover effects |
| scale | number | 1.05 | Scale factor on hover |
| brightness | number | 1.15 | Brightness multiplier on hover |
Custom Tooltips
<MotionBar
data={data}
tooltip={{
enabled: true,
render: ({ label, value, color }) => (
<div style={{ color: "#fff" }}>
<strong style={{ color }}>{label}</strong>: {value}
</div>
),
}}
/>Theme System
Eight built-in themes — all dark except arctic:
| Theme | Style | Background |
|-------|-------|------------|
| midnight | Dark violet/indigo | #0f0f1a |
| sunset | Warm orange/pink | #1a0f0f |
| ocean | Cyan/teal | #0f1a1a |
| forest | Green | #0a1a0f |
| neon | Vivid electric | #0a0a0a |
| rose | Pink/red | #1a0f18 |
| arctic | Light blue (light mode) | #f0f4f8 |
| ember | Fire red/orange | #1c1210 |
// Built-in theme
<MotionBar data={data} theme="forest" />
// Custom theme object
import type { Theme } from "motionchart";
const myTheme: Theme = {
name: "brand",
background: "#1a1a2e",
gridColor: "rgba(255,255,255,0.08)",
textColor: "rgba(255,255,255,0.7)",
colors: ["#e94560", "#0f3460", "#533483"],
gradients: [{ from: "#e94560", to: "#533483" }],
tooltipBackground: "rgba(26,26,46,0.95)",
tooltipText: "#ffffff",
tooltipBorder: "rgba(233,69,96,0.4)",
glowColor: "rgba(233,69,96,0.4)",
};
<MotionBar data={data} theme={myTheme} />Quick overrides without a full custom theme:
<MotionBar
data={data}
theme="midnight"
backgroundColor="#000000"
textColor="#ffffff"
gridColor="rgba(255,255,255,0.1)"
/>Color System
Colors are resolved in this priority order (highest wins):
- Per-item color —
data[i].colororseries[i].color colorprop — single color string applied to all elementscolorsprop — array cycled by indexgradientprop — global gradient applied to all elements- Theme colors — active theme's color palette (fallback)
// Per-item colors
const data = [
{ label: "A", value: 30, color: "#ef4444" },
{ label: "B", value: 60, color: "#3b82f6" },
];
// Single color (overrides theme)
<MotionBar data={data} color="#6366f1" />
// Color array (cycles by index)
<MotionBar data={data} colors={["#f97316", "#22c55e", "#3b82f6"]} />
// Gradient (solid color props skip theme gradients)
<MotionBar data={data} gradient={{ from: "#6366f1", to: "#ec4899" }} />
<MotionBar data={data} gradient={{ from: "#06b6d4", to: "#14b8a6", angle: 135 }} />Responsive
Charts fill their parent container when responsive is set. Uses ResizeObserver with debounce.
// Fill parent width, keep prop height
<div style={{ width: "100%" }}>
<MotionBar data={data} responsive />
</div>
// Fill parent with fixed aspect ratio (height auto-derives from width)
<MotionBar data={data} responsive={{ enabled: true, aspectRatio: 16 / 9 }} />
// With constraints
<MotionBar data={data} responsive={{ enabled: true, minWidth: 300, minHeight: 200 }} />ResponsiveConfig
| Option | Type | Default | Description |
|--------|------|---------|-------------|
| enabled | boolean | false | Enable responsive mode |
| aspectRatio | number | — | Width / height ratio. Height auto-derives when set |
| minWidth | number | 200 | Minimum width in px |
| minHeight | number | 150 | Minimum height in px |
| debounce | number | 50 | Resize event debounce in ms |
Viewport (Zoom and Pan)
Available on MotionBar, MotionLine, and MotionCandlestick. Auto-enables when the data count exceeds a threshold (default 20 items). Supports scroll-wheel zoom, click-drag pan, pinch-to-zoom on touch devices, and an optional minimap navigator.
// Auto-enable with defaults
<MotionBar data={largeData} viewport />
// Full config
<MotionBar
data={largeData}
viewport={{
enabled: true,
defaultRange: [0, 30],
minimap: { height: 48, barOpacity: 0.4 },
zoom: { min: 5, max: 100, step: 2 },
pan: true,
smartLabels: true,
threshold: 20,
touch: true,
animationDuration: 0.3,
onRangeChange: (range) => console.log(range),
}}
/>ViewportConfig
| Option | Type | Default | Description |
|--------|------|---------|-------------|
| enabled | boolean | auto | Force-enable or disable |
| defaultRange | [number, number] | [0, threshold] | Initial visible range as [startIndex, endIndex] |
| minimap | boolean \| MinimapConfig | false | Minimap strip below the chart |
| zoom | boolean \| ZoomConfig | true | Scroll-wheel zoom |
| pan | boolean | true | Click-drag pan |
| smartLabels | boolean | true | Adaptive x-axis label density at different zoom levels |
| threshold | number | 20 | Data count that triggers auto-enable |
| touch | boolean | true | Pinch-to-zoom and swipe-to-pan |
| animationDuration | number | 0.3 | Viewport transition duration in seconds |
| onRangeChange | (range: [number, number]) => void | — | Callback fired on visible range change |
MinimapConfig
| Option | Type | Default | Description |
|--------|------|---------|-------------|
| height | number | 40 | Strip height in pixels |
| backgroundColor | string | — | Minimap background color override |
| handleColor | string | — | Viewport window border color |
| trackColor | string | — | Non-selected region overlay color |
| barOpacity | number | 0.4 | Minimap bar opacity |
ZoomConfig
| Option | Type | Default | Description |
|--------|------|---------|-------------|
| min | number | 5 | Minimum number of visible items |
| max | number | total count | Maximum number of visible items |
| step | number | 2 | Items to add/remove per scroll tick |
| speed | number | 1 | Zoom speed multiplier |
Toolbar (Export)
A floating export button with a dropdown for CSV, SVG, and PNG downloads. Rendered as an HTML overlay so it never clips the chart SVG.
// Enable with defaults (top-right, all formats)
<MotionBar data={data} toolbar />
// Configured
<MotionBar
data={data}
toolbar={{
enabled: true,
position: "top-left",
csv: true,
svg: true,
png: false,
filename: "monthly-sales",
}}
/>ToolbarConfig
| Option | Type | Default | Description |
|--------|------|---------|-------------|
| enabled | boolean | false | Show the toolbar |
| position | "top-left" \| "top-right" \| "bottom-left" \| "bottom-right" | "top-right" | Toolbar placement |
| csv | boolean | true | Show CSV download button |
| svg | boolean | true | Show SVG download button |
| png | boolean | true | Show PNG download button |
| filename | string | "chart-data" | Download filename without extension |
| csvFormatter | (data: unknown) => string | — | Custom CSV serializer |
| iconSize | number | 16 | Icon size in px |
| backgroundColor | string | — | Toolbar background color override |
| iconColor | string | — | Icon/text color override |
| offset | number | 8 | Distance from chart edge in px |
Reference Lines
Annotate charts with horizontal (y-axis) or vertical (x-axis) lines for goals, averages, and thresholds.
<MotionBar
data={data}
referenceLines={[
{
value: 75,
axis: "y",
label: "Target",
color: "#22c55e",
strokeDasharray: "5 4",
labelPosition: "right",
},
{
value: 50,
axis: "y",
label: "Minimum",
color: "#ef4444",
strokeWidth: 2,
},
]}
/>ReferenceLine
| Prop | Type | Default | Description |
|------|------|---------|-------------|
| value | number | required | Data value where the line is drawn |
| axis | "x" \| "y" | "y" | Axis to draw on |
| label | string | — | Text label beside the line |
| color | string | theme textColor | Line color |
| strokeWidth | number | 1.5 | Line width in px |
| strokeDasharray | string | "5 4" | SVG dash pattern |
| labelPosition | "left" \| "right" | "right" | Which side to place the label |
Chart Examples
MotionBar
Single series
import { MotionBar } from "motionchart";
const data = [
{ label: "Q1", value: 120 },
{ label: "Q2", value: 185 },
{ label: "Q3", value: 142 },
{ label: "Q4", value: 210 },
];
<MotionBar
data={data}
theme="midnight"
gradient={{ from: "#6366f1", to: "#8b5cf6" }}
showValues
borderRadius={6}
yAxisLabel="Revenue ($k)"
toolbar
/>Grouped multi-series
import { MotionBar } from "motionchart";
const series = [
{ label: "2023", data: [120, 185, 142, 210] },
{ label: "2024", data: [145, 200, 168, 250] },
];
const labels = ["Q1", "Q2", "Q3", "Q4"];
<MotionBar
series={series}
labels={labels}
theme="ocean"
legend
showValues
yAxisLabel="Revenue ($k)"
/>Stacked with large dataset and viewport
<MotionBar
series={series}
labels={labels}
theme="sunset"
stacked
legend
showValues
borderRadius={4}
viewport={{ minimap: true, zoom: true }}
/>MotionBar-specific props
| Prop | Type | Default | Description |
|------|------|---------|-------------|
| data | BarDataPoint[] | — | Single series data |
| series | BarSeries[] | — | Multi-series grouped/stacked data |
| labels | string[] | — | X-axis labels for multi-series mode |
| orientation | "vertical" \| "horizontal" | "vertical" | Bar orientation |
| stacked | boolean | false | Stack bars instead of grouping |
| sort | "asc" \| "desc" \| "none" | "none" | Sort bars by value |
| borderRadius | number | 4 | Bar corner radius in px |
| barPadding | number | 0.3 | Space between bars (0-1) |
| groupPadding | number | 0.1 | Space within groups (0-1) |
| barWidth | number | — | Fixed bar width in px (overrides barPadding) |
| showValues | boolean | false | Show value labels above bars |
| showLabels | boolean | true | Show x-axis labels |
| yTickCount | number | 5 | Number of y-axis tick marks |
| legend | boolean \| LegendConfig | — | Show legend |
| viewport | ViewportConfig | — | Zoom/pan viewport for large datasets |
| onBarClick | (params: BarClickParams) => void | — | Click callback |
MotionLine
Multi-series with area fill and viewport
import { MotionLine } from "motionchart";
const data = [
{
label: "Revenue",
data: [
{ x: "Jan", y: 42 }, { x: "Feb", y: 68 },
{ x: "Mar", y: 55 }, { x: "Apr", y: 91 },
],
color: "#6366f1",
},
{
label: "Expenses",
data: [
{ x: "Jan", y: 30 }, { x: "Feb", y: 45 },
{ x: "Mar", y: 40 }, { x: "Apr", y: 60 },
],
color: "#f97316",
},
];
<MotionLine
data={data}
theme="midnight"
showArea
curve="smooth"
legend
crosshair
yAxisLabel="Amount ($k)"
viewport={{ minimap: true }}
responsive
/>MotionLine-specific props
| Prop | Type | Default | Description |
|------|------|---------|-------------|
| data | LineSeries \| LineSeries[] | required | Line series data |
| curve | "smooth" \| "linear" \| "step" | "smooth" | Curve interpolation |
| drawSpeed | number | 1.8 | Line draw animation duration in seconds |
| showDots | boolean | true | Show data point dots |
| dotRadius | number | 5 | Dot size in px |
| dotScale | number | — | Dot scale on hover |
| showArea | boolean | false | Fill area under the line |
| showXLabels | boolean | true | Show x-axis labels |
| showYLabels | boolean | true | Show y-axis labels |
| yMin | number \| "auto" | 0 | Minimum y-axis value |
| crosshair | boolean | true | Vertical crosshair with multi-series tooltip |
| legend | boolean \| LegendConfig | auto | Auto-enabled for multi-series |
| viewport | ViewportConfig | — | Zoom/pan viewport for large datasets |
| onPointClick | (params: LineClickParams) => void | — | Click callback |
MotionPie
Donut with center label
import { MotionPie } from "motionchart";
const data = [
{ label: "Direct", value: 42 },
{ label: "Organic", value: 28 },
{ label: "Referral", value: 18 },
{ label: "Social", value: 12 },
];
<MotionPie
data={data}
theme="ocean"
innerRadius={0.6}
centerLabel="Total"
centerSubLabel="100 sessions"
cornerRadius={4}
padAngle={2}
showLabels
legend
/>MotionPie-specific props
| Prop | Type | Default | Description |
|------|------|---------|-------------|
| data | PieDataPoint[] | required | Slice data |
| innerRadius | number | 0 | Donut hole ratio (0 = full pie, 0-1 = fraction of outer radius) |
| cornerRadius | number | 0 | Rounded slice edges in px |
| padAngle | number | 0 | Gap between slices in degrees |
| startAngle | number | -90 | Start angle in degrees (default: 12 o'clock) |
| showLabels | boolean | false | Show percentage labels on slices |
| showValues | boolean | false | Show raw value labels on slices |
| centerLabel | string | — | Center text (donut charts only) |
| centerSubLabel | string | — | Secondary text below centerLabel |
| strokeColor | string | theme background | Stroke color between slices |
| strokeWidth | number | — | Stroke width between slices |
| legend | boolean \| LegendConfig | — | Show legend |
| onSliceClick | (params: PieClickParams) => void | — | Click callback |
MotionScatter
Multi-series bubble chart
import { MotionScatter } from "motionchart";
const data = [
{
label: "Product A",
data: [
{ x: 20, y: 85, size: 12, label: "Launch" },
{ x: 45, y: 72, size: 8 },
{ x: 70, y: 91, size: 15, label: "Peak" },
],
},
{
label: "Product B",
data: [
{ x: 15, y: 60, size: 6 },
{ x: 55, y: 48, size: 10 },
{ x: 80, y: 75, size: 9 },
],
},
];
<MotionScatter
data={data}
theme="neon"
legend
minDotRadius={4}
maxDotRadius={20}
dotOpacity={0.75}
xAxisLabel="Cost ($)"
yAxisLabel="Performance"
yMin="auto"
/>MotionScatter-specific props
| Prop | Type | Default | Description |
|------|------|---------|-------------|
| data | ScatterSeries \| ScatterSeries[] | required | Scatter data |
| dotRadius | number | 5 | Default dot radius in px |
| minDotRadius | number | 3 | Min radius for bubble mode (size dimension) |
| maxDotRadius | number | 20 | Max radius for bubble mode |
| dotOpacity | number | 0.7 | Dot fill opacity |
| showXLabels | boolean | true | Show x-axis labels |
| showYLabels | boolean | true | Show y-axis labels |
| yMin | number \| "auto" | 0 | Minimum y-axis value |
| legend | boolean \| LegendConfig | — | Show legend |
| onPointClick | (params: ScatterClickParams) => void | — | Click callback |
MotionHeatmap
Correlation matrix with color scale
import { MotionHeatmap } from "motionchart";
const data = [
{ row: "Mon", col: "9am", value: 12 },
{ row: "Mon", col: "10am", value: 45 },
{ row: "Mon", col: "11am", value: 68 },
{ row: "Tue", col: "9am", value: 34 },
{ row: "Tue", col: "10am", value: 72 },
{ row: "Tue", col: "11am", value: 55 },
];
<MotionHeatmap
data={data}
theme="midnight"
colorRange={["#1e1b4b", "#6366f1"]}
showValues
showColorScale
cellRadius={4}
cellGap={3}
/>MotionHeatmap-specific props
| Prop | Type | Default | Description |
|------|------|---------|-------------|
| data | HeatmapDataPoint[] | required | Grid data (row, col, value) |
| cellRadius | number | 2 | Cell corner radius in px |
| cellGap | number | 2 | Gap between cells in px |
| showValues | boolean | false | Show numeric values inside cells |
| colorRange | [string, string] | theme colors | Low-to-high color interpolation |
| showXLabels | boolean | true | Show column labels |
| showYLabels | boolean | true | Show row labels |
| showColorScale | boolean | true | Show color scale legend bar |
| colorScaleWidth | number | 16 | Color scale bar width in px |
| onCellClick | (params: HeatmapClickParams) => void | — | Click callback |
MotionCandlestick
OHLC chart with volume and reference lines
import { MotionCandlestick } from "motionchart";
const data = [
{ label: "Jan 2", open: 148, high: 153, low: 145, close: 151, volume: 8200000 },
{ label: "Jan 3", open: 151, high: 156, low: 149, close: 154, volume: 9100000 },
{ label: "Jan 4", open: 154, high: 155, low: 146, close: 148, volume: 11500000 },
{ label: "Jan 5", open: 148, high: 152, low: 143, close: 150, volume: 7800000 },
{ label: "Jan 8", open: 150, high: 158, low: 149, close: 157, volume: 9600000 },
];
<MotionCandlestick
data={data}
theme="midnight"
bullishColor="#22c55e"
bearishColor="#ef4444"
showVolume
volumeHeight={0.2}
viewport={{ minimap: true, zoom: true }}
referenceLines={[
{ value: 155, axis: "y", label: "Resistance", color: "#f97316", strokeDasharray: "6 3" },
{ value: 147, axis: "y", label: "Support", color: "#22c55e", strokeDasharray: "6 3" },
]}
toolbar
/>MotionCandlestick-specific props
| Prop | Type | Default | Description |
|------|------|---------|-------------|
| data | CandlestickDataPoint[] | required | OHLC data |
| bullishColor | string | theme color | Color for candles where close > open |
| bearishColor | string | theme color | Color for candles where close < open |
| candleWidth | number | 0.6 | Candle body width as fraction of slot (0-1) |
| wickWidth | number | 1.5 | Wick line width in px |
| showXLabels | boolean | true | Show x-axis labels |
| showYLabels | boolean | true | Show y-axis price labels |
| showVolume | boolean | false | Show volume bars below the chart |
| volumeHeight | number | 0.2 | Volume sub-chart height fraction (0-1) |
| volumeOpacity | number | 0.3 | Volume bar opacity |
| viewport | ViewportConfig | — | Zoom/pan viewport for large datasets |
| onCandleClick | (params: CandleClickParams) => void | — | Click callback |
Other Charts
MotionRadar
import { MotionRadar } from "motionchart";
<MotionRadar
axes={[
{ label: "Speed" },
{ label: "Shooting" },
{ label: "Passing" },
{ label: "Defense" },
{ label: "Stamina" },
]}
series={[
{ label: "Player A", data: [88, 92, 78, 60, 85] },
{ label: "Player B", data: [70, 65, 90, 88, 72] },
]}
theme="midnight"
shape="polygon"
levels={5}
fillOpacity={0.3}
legend={{ position: "bottom" }}
/>MotionGantt
import { MotionGantt } from "motionchart";
<MotionGantt
tasks={[
{ id: "design", label: "UI Design", start: 0, end: 4, progress: 1, group: "Design" },
{ id: "dev", label: "Development", start: 4, end: 10, progress: 0.4, group: "Engineering", dependencies: ["design"] },
{ id: "qa", label: "QA", start: 9, end: 12, progress: 0, group: "Engineering", dependencies: ["dev"] },
]}
theme="midnight"
showProgress
showDependencies
showPercentage
/>MotionTreemap
import { MotionTreemap } from "motionchart";
<MotionTreemap
data={{
label: "Portfolio",
children: [
{
label: "Tech",
children: [
{ label: "Apple", value: 180 },
{ label: "Google", value: 140 },
],
},
{
label: "Finance",
children: [
{ label: "JPMorgan", value: 120 },
{ label: "Visa", value: 70 },
],
},
],
}}
theme="sunset"
showLabels
showValues
legend
/>MotionFunnel
import { MotionFunnel } from "motionchart";
<MotionFunnel
data={[
{ label: "Visitors", value: 12000 },
{ label: "Sign Ups", value: 7500 },
{ label: "Trial", value: 4200 },
{ label: "Paid", value: 1800 },
]}
theme="sunset"
showPercentage
showValues
/>MotionSunburst
import { MotionSunburst } from "motionchart";
<MotionSunburst
data={{
label: "World",
children: [
{
label: "Americas",
children: [
{ label: "USA", value: 330 },
{ label: "Brazil", value: 210 },
],
},
{
label: "Europe",
children: [
{ label: "Germany", value: 83 },
{ label: "France", value: 67 },
],
},
],
}}
theme="ocean"
showLabels
legend={{ position: "bottom" }}
/>Legend
Charts with multiple series support a configurable legend.
// Enable with defaults
<MotionBar series={series} labels={labels} legend />
// Positioned legend
<MotionBar series={series} labels={labels} legend={{ position: "bottom", layout: "horizontal" }} />LegendConfig
| Option | Type | Default | Description |
|--------|------|---------|-------------|
| position | "top" \| "bottom" \| "left" \| "right" | "bottom" | Legend placement |
| layout | "horizontal" \| "vertical" \| "auto" | "auto" | Item layout direction |
TypeScript
All types are exported from the package root:
import type {
BarDataPoint,
BarSeries,
LineSeries,
LineDataPoint,
PieDataPoint,
ScatterSeries,
ScatterDataPoint,
CandlestickDataPoint,
GanttTask,
TreeNode,
HeatmapDataPoint,
FunnelStage,
RadarDataPoint,
RadarSeries,
GradientConfig,
AnimationConfig,
HoverConfig,
TooltipConfig,
ViewportConfig,
ResponsiveConfig,
ToolbarConfig,
ReferenceLine,
LegendConfig,
ThemeName,
Theme,
ChartBaseProps,
} from "motionchart";Shared Data Types
// Used by Tree, Treemap, RadialTree, Dendrogram, Sunburst
interface TreeNode {
label: string;
value?: number; // area in treemap, branch distance in dendrogram
color?: string;
gradient?: GradientConfig;
children?: TreeNode[];
}
interface GradientConfig {
from: string;
to: string;
angle?: number; // rotation in degrees
}Built-in Formatters
motionchart ships with locale-aware formatter factories so you don't have to write (v) => $${v.toLocaleString()} boilerplate. Each returns a function ready to drop into yTickFormat or xTickFormat. All built on native Intl.NumberFormat / Intl.DateTimeFormat — zero dependencies, full locale support.
import {
MotionBar,
formatCurrency,
formatCompact,
formatPercent,
formatDate,
} from "motionchart";
<MotionBar yTickFormat={formatCurrency("USD")} />
<MotionBar yTickFormat={formatCompact()} />
<MotionLine yTickFormat={formatPercent({ decimals: 1 })} />
<MotionCandlestick xTickFormat={formatDate("monthYear")} />Number formatters
| Formatter | Output (en-US) | Options |
|-----------|----------------|---------|
| formatNumber() | 1,234,567 | { decimals, locale } |
| formatCompact() | 1.2M, 2.5B, 42 | { decimals, locale } (default decimals: 1) |
| formatCurrency("USD") | $1,234.56 | { decimals, locale, display, compact } |
| formatPercent() | 42% | { decimals, locale, asRatio } |
| formatScientific() | 1.23E6 | { decimals, locale } (default decimals: 2) |
formatPercent modes:
- Default (chart-style): input is already a percentage →
formatPercent()(42)→"42%" asRatio: true(Intl convention): input is a 0–1 ratio →formatPercent({ asRatio: true })(0.42)→"42%"
formatCurrency examples:
formatCurrency("USD")(1234.56) // "$1,234.56"
formatCurrency("EUR", { locale: "de-DE" })(1234) // "1.234,00 €"
formatCurrency("USD", { compact: true })(1.2e6) // "$1.2M"
formatCurrency("JPY")(1234) // "¥1,234"Date formatters
formatDate accepts a preset name or raw Intl.DateTimeFormatOptions. Input can be a Date, Unix ms number, or ISO string.
| Preset | Output (en-US) |
|--------|----------------|
| "short" | 1/15/24 |
| "medium" | Jan 15, 2024 |
| "long" | January 15, 2024 |
| "monthYear" | Jan 2024 |
| "monthYearLong" | January 2024 |
| "monthOnly" | Jan |
| "yearOnly" | 2024 |
| "weekday" | Mon |
| "weekdayLong" | Monday |
| "time" | 10:30 AM |
| "timeWithSeconds" | 10:30:45 AM |
formatDate("monthYear")(new Date(2024, 0, 15)) // "Jan 2024"
formatDate("monthYear", { locale: "fr-FR" })(...) // "janv. 2024"
formatDate({ year: "numeric", month: "narrow" })(...) // "J 2024"SSR (Server-Side Rendering)
motionchart works with Next.js, Remix, Astro, Gatsby, and any React framework that pre-renders on the server. Every chart component serializes to valid HTML/SVG via renderToString — verified by 17 SSR tests in the test suite.
Next.js App Router (recommended):
// app/dashboard/page.tsx
"use client";
import { MotionBar } from "motionchart";
export default function Dashboard() {
return <MotionBar data={data} />;
}motionchart components use React hooks internally, so they require the "use client" directive — same as Recharts, Nivo, and any interactive chart library.
Next.js Pages Router / Remix:
import dynamic from "next/dynamic";
const MotionBar = dynamic(
() => import("motionchart").then((m) => m.MotionBar),
{ ssr: false },
);Astro:
---
import { MotionBar } from "motionchart";
---
<MotionBar data={data} client:only="react" />Behavior notes:
- ARIA labels are serialized into server HTML (good for SEO and screen readers)
- When
responsiveis enabled, charts render at the prop dimensions on the server and resize to fit the container after hydration — the same brief layout reflow as every other chart library - Animations only start after the client takes over (expected — Node has no animation frame)
Build Output
| Format | File |
|--------|------|
| ESM | dist/index.mjs |
| CJS | dist/index.cjs |
| TypeScript declarations | dist/index.d.ts |
Built with tsup.
License
MIT
