All notable changes to this project will be documented in this file.
The format is based on Keep a Changelog, and this project adheres to Semantic Versioning.
semiotic/serverproduction API —renderChart(component, props)renders 27+ HOC chart types to standalone SVG strings. Supports all themes, legends (4 positions), grid, annotations (y-threshold, x-threshold, category-highlight, widget, enclose), and accessibility attributes (role="img",<title>,<desc>,aria-labelledby). SVG groups haveidattributes for Figma layer naming (data-area,axes,grid,annotations,legend,chart-title).renderDashboard(charts, options)— Multi-chart dashboard layout with title, theme, configurable columns. Each chart entry supportscolSpanfor wide charts.renderToImage(component, props, options)— PNG/JPEG rasterization via sharp (peer dependency). Configurablescalefor retina output.renderToAnimatedGif(chartType, data, props, options)— Animated GIF from streaming data windows. Options:fps,transitionFrames,easing,decay,windowSize,loop,scale.generateFrameSequence(frames)— Snapshot-based animation for topology changes (network failover, edge removal). Each frame is an independentrenderChartcall.- SVG hatch patterns —
createSVGHatchPattern()for server-rendered diagonal hatch fills. Used by FunnelChart vertical mode for dropoff bars. - Push API
remove()andupdate()— Selective data removal and in-place update across all stores (RingBuffer, PipelineStore, OrdinalPipelineStore, NetworkPipelineStore) and all HOC/frame handles.remove(id)orremove([ids])by ID (requirespointIdAccessor/dataIdAccessor).update(id, updater)for in-place mutation. Network:removeNode(id)cascades to edges,removeEdge(source, target)removes parallel edges. pointIdAccessor/dataIdAccessor— ID accessor props on BaseChartProps forremove()andupdate()targeting.- GaugeChart server rendering — Sweep angle, start angle, inner radius, threshold zone fills, needle indicator.
- FunnelChart server rendering — Horizontal and vertical modes with trapezoid connectors. Vertical mode supports hatch pattern dropoff bars.
- Sparkline server rendering —
renderChart("Sparkline", props)with no axes, 2px margins, no grid/legend/title. - 6 interactive docs pages — Render Studio, Theme Showcase, Dashboard Gallery, Email Preview, Export & Embed (with real GIF downloads), Push API demo.
hoverHighlightsimplified — Changed fromboolean | "series"to justboolean. Any truthy value triggers series-based dimming (requirescolorBy).- CSS variable resolution in canvas —
resolveCSSColor()resolvesvar(--name, fallback)viagetComputedStyleat paint time. Per-canvas cache avoids repeated calls within a paint cycle;clearCSSColorCache()invalidates on theme change. All 9 canvas renderers updated. extentPaddingnullish coalescing — Changed|| 0.05to?? 0.05soextentPadding: 0is respected.- Swimlane
skipMaxPad— Prevents trailing gap in swimlane charts by skipping max-side extent padding. frameProps.pieceStylemerging — Ordinal HOCs now merge user'spieceStylewith computed base style instead of excluding it. Enables stroke overrides.resolveGroupColor()in server rendering — XY line/area style fallbacks callresolveGroupColor(group)instead of hardcoding#007bff. Theme categorical colors flow through to server SVG.- Force layout
iterations: 0— Now skips simulation entirely for pinned node positions. Previously warm-start detection overrode to 40 iterations. - Background rect positioning — Server SVG background rect renders at SVG root, not inside translated group (fixes Figma import).
- Dependency bumps — vite 8.0.5, typedoc 0.28.18, vulnerable devdeps fixed.
- Server legend margin — Legend position expands margin before width/height calculation (right:100, left:100, bottom:70, top:40).
- Server
framePropspassthrough —framePropsspread into renderer common object sopieceStyle,lineStyleflow through. - Server
effectiveColorScheme— Falls back totheme.colors.categoricalwhencolorSchemeprop not set. - Network
remove()return value — Returns node data before removal instead of empty array. - RingBuffer
update()snapshot safety — Proper type-aware cloning (array spread for arrays, object spread for objects). - Timestamp buffer desync on remove — Lockstep compaction removes matching indices from timestamp buffer.
buildRealtimeNodespreserving positions — Usesx: d.x ?? 0, y: d.y ?? 0instead of hardcoded zeros.- Dark mode CSS var strokes — Docs site sets
--semiotic-bgin both dark/light blocks.LiveExampleuses MutationObserver for chart remount on theme toggle.
- GaugeChart — New ordinal HOC for single-value gauges with threshold zones, needle indicator, and configurable sweep angle. Built on StreamOrdinalFrame radial projection (reuses pie/donut rendering pipeline). Supports
fillZones={false}for fixed-zone displays where only the needle moves (e.g. election needle). Exported fromsemioticandsemiotic/ordinal. - Range/dumbbell plot — Candlestick chart type now supports range mode: omit
openAccessor/closeAccessorand provide onlyhighAccessor/lowAccessorto render vertical lines with endpoint dots. SinglerangeColorviacandlestickStyle. No new HOC — demonstrates StreamXYFrame flexibility. scalePadding— Pixel inset on XY scale ranges to prevent glyph clipping at chart edges. Available onStreamXYFrameProps; HOCs pass viaframeProps={{ scalePadding: 12 }}. Domain and tick values unchanged.xScaleType="time"— New scale type createsd3.scaleTimefor Date-aware tick generation. Required for landmark ticks with timestamp data.sweepAngle— New prop onStreamOrdinalFramePropslimiting pie/donut arc to less than 360° (used internally by GaugeChart).- Multi-point tooltip —
tooltip="multi"on LineChart shows all series values at hovered X with color swatches. Custom functions receivedatum.allSerieswith{group, value, valuePx, color, datum}. - Click-to-lock crosshair — In
linkedHoverx-position mode, click locks the crosshair. Escape or click again to unlock. Source-aware unlock prevents multi-chart interference. - Hover-based sibling dimming —
hoverHighlighton all HOCs dims non-hovered series on data mark hover (requirescolorBy). - Per-series fillArea —
fillArea={["A","B"]}on LineChart fills named series as areas, others stay as lines. New"mixed"chart type with dedicated scene builder. - Multi-color gradient fills —
gradientFill={{ colorStops: [{offset, color}] }}on AreaChart for semantic color bands. Supportstransparent. - Line stroke gradients —
lineGradient={{ colorStops }}on LineChart/AreaChart for horizontal gradient strokes. - Axis config extensions —
includeMaxforces domain-max tick,autoRotaterotates labels 45° when crowded,gridStyle("dashed"|"dotted"|string) for grid lines,landmarkTicksbolds month/year boundaries. baselinePadding— Boolean prop on bar chart HOCs. Defaultfalsemakes bars flush with 0 baseline.hoverRadius— Configurable hit-test distance (default 30px) on all XY HOCs andStreamXYFrameProps.- ReactNode tick labels —
xFormat,yFormat,categoryFormataccept=> string | ReactNodewith<foreignObject>fallback. - Tick deduplication — Adjacent identical tick labels automatically removed.
getHitRadiusandMultiPointTooltipexported fromsemiotic/utils.isTimeLandmarkandtoDateexported fromhitTestUtils.ts(shared across SVGOverlay and tests).
- 30px default hit radius — All 4 hit testers (XY, Network, Geo, Ordinal) now use
getHitRadius()from sharedhitTestUtils.ts. Previous 12px Fitts's law cap was too small for comfortable interaction. lineDataAccessordata flattening — StreamXYFrame now flattens line-object data before pipeline ingestion. Previously the pipeline readxAccessoron line objects (which lack that field), producing NaN extents.scaleTimedomain comparison —valueOf()comparison for Date objects prevents stale scales from blocking updates.- Annotation dark mode —
Annotation.tsxtext usesvar(--semiotic-text), connectors usevar(--semiotic-text-secondary)instead of hardcoded black. - SwimlaneChart
showCategoryTicks={false}— Now suppresses both tick labels and axis title. - Floating point tooltip precision —
formatValuerounds viatoPrecision(6). - Default tick format Date-aware —
defaultTickFormathandles Date objects (formats as "Jan 7" style). bodyWidth: 0on candlestick — Body rect skipped entirely, no invisible canvas elements.- Ordinal bar baseline — Value axis baseline draws at
rScale(0), not chart edge. Include-zero applied before padding. - Remap fast-path with
scalePadding— Disabled proportional remap when padding is set (forces full rebuild for correctness). - Candlestick
updateConfig— OHLC accessors andcandlestickRangeModerecomputed on prop changes.
baselineStylerenamed togridStyle— Applies to grid lines (not axis baselines, which stay solid).- Build system —
rollup-plugin-typescript2replaced with@rollup/plugin-typescript(fixes TS compilation). - Playwright CI —
serve-examples:ciscript skips redundantnpm run dist. Timeout bumped to 120s.
- Exhaustive scene builder test coverage — 346 new tests across all XY scene builders (line, area, stacked area, point, swarm, heatmap, waterfall, candlestick, bar) and ordinal scene builders (funnel, bar-funnel, swimlane). Tests assert actual coordinates, baselines, cumulative positions, and style resolution — not just "it didn't crash."
- FunnelChart and LikertChart HOC tests — First test suites for the two previously untested HOCs. FunnelChart: 29 tests covering horizontal/vertical modes, multi-category mirroring, connector opacity, tooltip metadata. LikertChart: 31 tests covering raw/pre-aggregated modes, diverging colors, neutral sentinels, error states.
- Render pipeline benchmarks —
benchmarks/unit/render-pipeline.bench.tscovering scene builder throughput (scatter 50k: 4ms, line 10k: 0.45ms, stacked area 10k: 1.3ms), RingBuffer push/iteration, and end-to-end ingest-to-scene-build. Identified heatmap at 50k (49ms) as the only builder exceeding frame budget. - Dev-mode
d.dataaccess warning — Frame callbacks (nodeStyle,edgeStyle,nodeSize) now warn in development when users access properties that exist on.databut not on the RealtimeNode/RealtimeEdge wrapper (e.g.,d.categoryinstead ofd.data?.category). Zero production overhead. Applied to all 5 layout plugins (sankey, force, chord, orbit, hierarchy). - Streaming-first docs narrative — Landing page and Getting Started page restructured to lead with the streaming engine (push API, two-canvas RAF loop, ring buffer, decay/pulse/staleness/transitions) as the primary differentiator.
- Heatmap scene builder optimized — Streaming path uses flat
Int32Array/Float64Arraygrids instead ofMap<string, {data[]}>, eliminating 50k string key allocations and per-datum array pushes. Static path uses numeric Map keys and precomputed 256-entry color LUT (cached per scheme) instead of per-cellscaleSequentialcalls. Streaming 50k points into 20×20 grid: 0.37ms (was ~49ms with Map+string approach). Static path ~15% faster at high cardinality.
@modelcontextprotocol/sdkremoved from production dependencies — The MCP CLI (semiotic-mcp) now bundles the SDK via esbuild, sonpm install semioticno longer pulls in the 4MB+ MCP SDK and its transitive deps. The bundled CLI works identically — zero behavior change fornpx semiotic-mcpusers.@types/d3-quadtreemoved to devDependencies — Type declaration packages are always dev-only.- Stacked area points at wrong Y position —
emitPointNodesused rawctx.getYinstead of cumulative stacked Y. Fixed by addingyGetOverrideparameter and computing stacked positions frombuildStackedAreaNodes'stackedTopsmap — no duplicate stacking pass. - Null Y datums assigned stacked Y — Added
y != null && !Number.isNaN(y)guard before setting stacked point positions. - Stale forecast overlays on prop removal — Early return in LineChart effect when both
forecastandanomalybecome falsy now clears previous statistical overlays. - GeoCanvasHitTester inconsistent hit radius — Quadtree path used
(r||4)+4, linear scan usedMath.max((r||4)+5, 12). Unified toMath.max((r||4)+5, 12)everywhere. - backgroundGraphics not honoring margins — StreamXYFrame and StreamGeoFrame rendered
backgroundGraphicsoutside the margin-translated<g>. Fixed in both client and SSR paths.
- LikertChart — new ordinal HOC for Likert scale survey data. Horizontal (default): diverging bar chart centered at 0% with negative levels extending left, positive right, and neutral (odd count) split 50/50 across the centerline. Vertical: stacked 100% bar chart. Supports raw integer scores (1-based, auto-aggregated) and pre-aggregated (question, level, count) data. Works with any scale size (3-point to 7-point+). Push API for streaming — the chart accumulates raw data and re-aggregates percentages on each push.
onClickprop on all HOCs — Direct click handler receiving(datum, { x, y })with the original unwrapped datum. Works on lines, bars, areas, pie slices, nodes, and geo features. No moreonObservationfiltering orframeProps.customClickBehaviorescape hatch for simple click handling.categoryFormatprop on ordinal HOCs — Custom formatting function for individual category tick labels. Receives(label, index?)and returns a formatted string. Covers truncation, abbreviation, and custom labeling without dropping toframeProps.oLabel.category-highlightannotation type — Highlights a specific category column/row in ordinal charts with a semi-transparent band. Usage:{ type: "category-highlight", category: "Q3", color: "#4589ff", opacity: 0.15 }. Uses raw band scale from annotation context for correct positioning.labelPositionon threshold annotations —y-thresholdsupports"left"|"center"|"right"(default).x-thresholdsupports"top"(default) |"center"|"bottom". Previously labels were fixed at right/top.- Coordinate-based linked crosshair —
linkedHover={{ name: "sync", mode: "x-position", xField: "time" }}broadcasts the hovered X data value across charts. Consuming charts render a synced vertical crosshair at that X position with independent Y values. Wired through all 9 XY HOCs via sharedgetCrosshairPropsutility. Crosshair positions are cleaned up on unmount. - Tooltip viewport-aware flip — Tooltips auto-flip horizontally and vertically when near container edges. Uses
useLayoutEffectmeasurement with proper dependency array for precise flip decisions. Applied to all four stream frames (XY, ordinal, network, geo). - Data-driven histogram bin snapping — RealtimeTemporalHistogram brush now snaps to actual computed bin boundaries (binary search via
floorBinBoundary/ceilBinBoundary) instead of uniform grid math. Works with irregular bin widths.snapDuring: trueenables continuous snap feedback during drag. Bin boundaries are defensively sorted. Auto-populated from pipeline store — zero config change for existing usage. - IBM Carbon color palettes —
CARBON_CATEGORICAL_14(14-color),CARBON_ALERT(danger/warning/success/info), and"carbon"/"carbon-dark"theme presets. Exported fromsemioticandsemiotic/utils. Integrated into Theme Explorer and Theme Provider docs. - Legend line wrapping — horizontal legends now wrap to multiple rows when items exceed the available chart width, preventing overflow. Applies to all charts with
legendPosition="bottom"or"top". showPointson AreaChart and StackedAreaChart — Data point markers are now supported on area charts, matching LineChart's existingshowPoints/pointRadiusprops. Scene builders (line, area, stacked area) now emitPointSceneNodeentries whenpointStyleis configured, andpointCanvasRendereris included in the renderer dispatch for all three chart types.
- ThemeProvider categorical colors flow to all HOCs — When
colorByis set butcolorSchemeis not explicitly provided, charts now use the ThemeProvider'scolors.categoricalpalette instead of falling back to d3category10. Priority: explicitcolorScheme> theme categorical >"category10". Previously, ordinal and XY charts always defaulted tocategory10regardless of theme. - 12px minimum hit target (Fitts's law) — All four canvas hit testers (XY, ordinal, network, geo) now enforce
Math.max(node.r + 5, 12)as the minimum interactive hit radius. Previously formulas varied across hit testers (some as small as 5px), making small points difficult to hover. useColorScalecolorSchemeparameter is now optional — Callers that don't pass a color scheme get the effective scheme fallback instead of requiring an explicit argument. Fallback uses scheme-based scale instead of hardcoded"#999".LinkedCrosshairStoreoptimized subscriptions —useCrosshairPositionuses no-op subscribe/snapshot when the crosshair name is undefined, avoiding unnecessary store subscriptions on charts that don't use crosshairs.
- Legend
styleFncontract — LikertChart (and any chart using customlegendGroups) now passes(item: LegendItem, index)tostyleFncorrectly, fixing grey legend swatches. - LikertChart tooltip — shows category name (bold) and level name with percentage/count instead of raw internal field values. Uses standard tooltip chrome (dark background, rounded corners) matching all other charts.
category-highlightannotation in ordinal charts — Annotations now receive the raw band scale (scales.o) andprojectionin the annotation context, fixing cases where theoCenteredwrapper didn't expose.bandwidth().- Crosshair cleanup on unmount — Linked crosshair positions are cleared when a chart unmounts or when crosshair config changes, preventing stale crosshair markers in coordinated dashboards.
FlippingTooltipuseLayoutEffectdependency array — Added proper dependencies (children,className,containerWidth,containerHeight) to prevent stale measurements.- Removed dead
slicePaddingprop — Removed from PieChart and DonutChart interfaces, validation map, schema, tests, and all documentation. The prop was declared but never wired to any rendering logic. - Removed unused
DEFAULT_COLORimport in Heatmap — Eliminated dead import.
slicePaddingprop on PieChart/DonutChart — This prop was never functional. UseframeProps={{ oPadding: value }}for slice padding.
- Hover dot color matching — The hover indicator dot now automatically matches the hovered element's color (line stroke, area stroke, point fill) instead of hardcoded blue. Override with
frameProps={{ hoverAnnotation: { pointColor: "#custom" } }}. Fallback chain: explicitpointColor→ element color →--semiotic-primaryCSS var →#007bff. Affects all XY and Geo charts. pointColoroption onHoverAnnotationConfig— New opt-in override for hover dot color on Stream Frames.- Adaptive time tick formatting — New
adaptiveTimeTicks(granularity?)export fromsemiotic. Produces hierarchical axis labels: first tick is fully qualified, subsequent ticks only show what changed (e.g. seconds when the minute is the same, full timestamp when the hour rolls over). Tick labels auto-space based on label width to prevent overlap. - Forecast: training line styling —
trainStroke("darken" or CSS color),trainLinecap("round"),trainUnderline(true | "lighten"),trainOpacity,forecastOpacityonForecastConfig. Enables dashed training lines with solid underlines for visual distinction. - Forecast: per-datum anomaly styling —
anomalyColor,anomalyRadius, andanomalyStyleonForecastConfignow accept functions(datum) => valuefor data-driven anomaly rendering (e.g. sizing dots by anomaly count). - Forecast: multi-metric boundary duplication —
_groupByinternal field onForecastConfig. WhenlineByandforecastare both active, boundary points are duplicated within each metric group (not across groups), preventing stray cross-metric connecting lines in interleaved data. training-basesegment type — New segment for solid underlines beneath dashed training lines. PipelineStore renders training-base first (insertion order) so the solid line appears beneath the dashed one.resolveNodeColorshared utility — Extracted tosceneUtils.ts, used by both StreamXYFrame and StreamGeoFrame for consistent hover color resolution. HandlesCanvasPatternfills correctly.- 128 new unit tests — Multi-metric boundary duplication (3 tests), ThemeStore dark mode merging (5 tests), PipelineStore reproduction (9 tests), LineChart integration (8 tests), plus expanded statisticalOverlays coverage.
- SVGOverlay left axis label missing in dual-axis mode —
MultiAxisLineChartpasses left axis label viaaxesconfig, but SVGOverlay only read theyLabelprop (which is suppressed in dual-axis mode). Now readsleftAxis?.label || yLabel. - ThemeStore
mode: "dark"merged onto wrong base —{ mode: "dark", colors: { categorical: [...] } }was merging ontoLIGHT_THEME, so dark-mode text/background/grid colors were lost. Now correctly merges ontoDARK_THEME. - Tick label overlap on time axes — X-axis tick spacing now accounts for actual label width (estimated at 6.5px/char) instead of using a fixed 55px minimum, preventing label collision on dense time axes.
tickFormatsignature expanded —AxisConfig.tickFormatandxFormatnow receive(value, index, allTickValues)so formatters can produce hierarchical labels (e.g. show full date only on first tick or at boundary crossings).- Function accessors with forecast/anomaly — When
xAccessororyAccessoris a function, resolved values are now baked into data under__resolvedX/__resolvedYfields so the statistical overlay pipeline and annotation renderer can access them by string key. - Geo hover ring color — Geo frame point hover ring now uses
resolveNodeColor(shared utility) instead of inline logic, and correctly handlesCanvasPatternfills. - Tick color dark mode fallback — SVGOverlay tick color CSS var chain is now
--semiotic-text-secondary→--semiotic-text→#666, improving visibility when only--semiotic-textis set. - Annotation accessor fallback — SVGOverlay annotation renderer receives
"__resolvedX"/"__resolvedY"when accessors are functions, preventing annotations from rendering at wrong positions.
SegmentTypeunion expanded — Added"training-base"to the exported type.ForecastConfiginterface expanded — AddedtrainStroke,trainLinecap,trainUnderline,trainOpacity,forecastOpacity,anomalyStyle,_groupBy.anomalyColorandanomalyRadiusnow accept functions.- HOC early return guard — LineChart (and other HOCs with statistical overlays) no longer returns early before loading/empty state, ensuring all hooks are called unconditionally (React rules of hooks compliance).
Note: v3.1.1 was yanked from npm due to broken MCP tool schemas. Upgrade directly from 3.1.0 to 3.1.2.
- MCP server tools received no arguments — all 5 tools used empty
{}Zod schemas, causing the MCP SDK to strip all incoming parameters. Every tool call silently fell into "missing field" error paths. Fixed by defining proper Zod input schemas for all tools (getSchema,suggestChart,renderChart,diagnoseConfig,reportIssue). - MCP geo chart rendering —
renderHOCToSVGcalledvalidatePropswhich rejected geo components not in its validation map. Geo components (ChoroplethMap, ProportionalSymbolMap, FlowMap, DistanceCartogram) now skip validation and render correctly. - MCP
--portparsing —--httpwithout--portno longer produces NaN (falls back to 3001). - MCP "top-level fields" dead code — removed unreachable spread logic from
renderChart/diagnoseConfighandlers; updated Zod descriptions to match actual schema behavior (MCP SDK strips fields not in Zod schema). - suggestChart Histogram heuristic — removed unreachable
data.length >= 10check (suggestChart accepts 1–5 samples per its Zod schema). - renderHOCToSVG validation fragility — tightened unknown-component skip check to require exactly one "Unknown component" error instead of
.every()over all errors.
- MCP geo chart support — ChoroplethMap, ProportionalSymbolMap, FlowMap, and DistanceCartogram added to the MCP render registry (25 renderable components total).
- MCP HTTP transport —
npx semiotic-mcp --http --port 3001starts a session-based HTTP server with CORS headers for browser-based MCP inspectors and remote access. - suggestChart input validation — Zod schema enforces
.min(1).max(5)on data array.
- MCP
reportIssuetool — generates pre-filled GitHub issue URLs for bug reports and feature requests directly from AI coding assistants. No auth required. - MCP
getSchematool — returns the prop schema for a specific component on demand, reducing token overhead vs loading the full 63KB schema. Omitcomponentto list all 30 chart types. - MCP
suggestCharttool — analyzes a data sample and recommends chart types with confidence levels and example props. Supportsintentparameter for narrowing suggestions (comparison, trend, distribution, relationship, composition, geographic, network, hierarchy). - MCP server documentation — comprehensive setup instructions, tool descriptions, and usage examples in README.
- npm keywords —
mcp,model-context-protocol,mcp-server, and other discovery keywords for MCP directory indexing. - CI coverage thresholds — unit test coverage gated at 62/52/63/65% (statements/branches/functions/lines) with
@vitest/coverage-v8. - CI bundle size guardrails —
size-limitchecks for all 6 entry bundles in CI pipeline. - axe-core accessibility scanning — automated
@axe-core/playwrightscans across all chart category pages in E2E tests. - Self-healing error boundaries —
SafeRenderrunsdiagnoseConfigon chart failures (dev mode) and displays actionable fix suggestions alongside the error message. - 61 new unit tests — coverage for
withChartWrapper(SafeRender, warnDataShape, warnMissingField, renderEmptyState, renderLoadingState), network utilities, and push API on 7 ordinal chart types.
- MCP server — added
getSchema,suggestChart, andreportIssuetools (5 tools total). Added geo chart rendering support (ChoroplethMap, ProportionalSymbolMap, FlowMap, DistanceCartogram). - npm description — updated to highlight MCP server capability for discoverability.
prepublishOnlycleans dist/ — prevents stale dynamic import chunks from accumulating in published tarball.
- MCP
componentkey leaking into props — flat-shape calls like{ component: "LineChart", data: [...] }no longer passcomponentas a chart prop. - Missing dynamic import chunk —
dist/*-statisticalOverlays-*.jsadded tofilesarray so forecast/anomaly features work when consumed via ESM.
-
Geographic visualization — new
semiotic/geoentry point with 4 HOC chart components and a low-levelStreamGeoFrame, all canvas-rendered with d3-geo projections.ChoroplethMap— sequential color encoding on GeoJSON features. SupportsareaOpacity, function or stringvalueAccessor, and reference geography strings ("world-110m","world-50m", etc.).ProportionalSymbolMap— sized/colored point symbols on a geographic basemap withsizeBy,sizeRange, andcolorBy.FlowMap— origin-destination flow lines with width encoding, animated particles (showParticles,particleStyle), andlineType("geo"|"line").DistanceCartogram— ORBIS-style projection distortion based on travel cost. Concentric ring overlay (showRings,ringStyle,costLabel), north indicator (showNorth), configurablestrengthandlineMode.StreamGeoFrame— low-level geo frame with full control over areas, points, lines, canvas rendering, and push API for streaming.
-
GeoCanvasHitTester— spatial indexing for hover/click hit detection on canvas-rendered geo marks. -
GeoParticlePool— object-pool polyline particle system for animated flow particles. Supports"source"color inheritance, per-line color functions, and configurable spawn rate. -
GeoTileRenderer— slippy-map tile rendering on a background canvas. Mercator-only with retina support. ConfigurabletileURL,tileAttribution,tileCacheSize. -
Zoom/Pan — all geo charts accept
zoomable,zoomExtent,onZoom, with imperativegetZoom()/resetZoom()on the frame ref. Re-renders projection directly (no CSS transform). -
Drag Rotate —
dragRotateprop for globe spinning (defaults true for orthographic). Latitude clamped to [-90, 90]. -
Reference geography —
resolveReferenceGeography("world-110m")returns Natural Earth GeoJSON features.mergeData(features, data, { featureKey, dataKey })joins external data into features. -
Geo particles —
showParticlesandparticleStyleonFlowMapandStreamGeoFramefor animated dots flowing along line paths. -
6 geo documentation pages — ChoroplethMap, ProportionalSymbolMap, FlowMap, DistanceCartogram, StreamGeoFrame, and GeoVisualization overview.
-
2 geo playground pages — interactive prop exploration for geo charts.
-
1 geo recipe page — ORBIS-style distance cartogram walkthrough.
-
Geo test suites — unit tests for FlowMap (25 tests), ChoroplethMap (16 tests), DistanceCartogram (19 tests), colorUtils (+6 tests), hooks (+3 tests).
-
Accessibility foundation — moves Semiotic from ~30% to ~70% WCAG 2.1 AA compliance.
- Canvas
aria-label— every<canvas>element now has a computedaria-labeldescribing chart type and data shape (e.g., "scatter chart, 200 points"). All four Stream Frames:StreamXYFrame,StreamOrdinalFrame,StreamNetworkFrame,StreamGeoFrame. - Legend keyboard navigation — interactive legend items are focusable (
tabIndex={0},role="option"), withrole="listbox"on the container. Enter/Space activates (click), Arrow keys navigate between items. Visible focus ring on keyboard focus. aria-multiselectableon legend listbox whenlegendInteraction="isolate"orcustomClickBehavioris present.aria-selectedon legend items reflecting isolation state.aria-live="polite"region —AriaLiveTooltipcomponent mirrors tooltip text for screen reader announcements on hover.- SVG
<title>and<desc>— all SVG overlays (SVGOverlay,OrdinalSVGOverlay,NetworkSVGOverlay) includerole="img"and accessible<title>/<desc>elements derived from the chart title. aria-labelon ChartContainer toolbar buttons — Export, Fullscreen, and Copy Config buttons have descriptive labels and title attributes.- 35 Playwright integration tests —
integration-tests/accessibility.spec.tscovering canvas aria-labels, AriaLiveTooltip, legend keyboard traversal, focus rings, SVG title/desc, and ChartContainer toolbar buttons.
- Canvas
-
Streaming legend support — new
useStreamingLegendhook discovers categories from pushed data and builds legends dynamically with minimal re-renders via version counter. Integrated into StackedBarChart, PieChart, DonutChart, GroupedBarChart. -
Streaming regression test suite — 20+ Playwright integration tests (
streaming-regression.spec.ts) covering:- Canvas pixel sampling to verify colored fills (saturation > 0.1) across 8 streaming chart types
- Legend items appear after push API data arrives (4 chart types)
- Area chart tooltip contains numeric values, not dashes
- LineChart streaming stability (no "Maximum update depth" errors)
- Force graph content centroid within 30% of canvas center
- Error-free rendering across all 11 streaming test fixtures
-
Performance: color map cache —
PipelineStorecaches the category→color map across rebuilds using a sorted category set as cache key. Skips rebuild when categories are unchanged. (PipelineStore.ts) -
Performance: stacked area cache —
PipelineStorecaches stacked area cumulative sums using abuffer.size + ingestVersionhash. Skips expensive groupData + cumulative sum computation when data is unchanged. (PipelineStore.ts)
- Grey fills on push API charts — When using
ref.current.push(), HOC charts passed undefined color scales to style functions, causing grey fallback fills. Fixed end-to-end:- HOC
pieceStyle/pointStyle/lineStylefunctions now omit fill/stroke when colorScale is unavailable OrdinalPipelineStore.resolvePieceStylefills in from the frame's color scheme when HOC returns no fillPipelineStore.resolveLineStyle/resolveAreaStyle/point scene builder do the same for XY charts- New
resolveGroupColormethod provides centralizedSTREAMING_PALETTEassignment for streaming groups - Affected charts: StackedBarChart, PieChart, DonutChart, GroupedBarChart, BubbleChart, StackedAreaChart, AreaChart, LineChart, Scatterplot, QuadrantChart, ChordDiagram
- HOC
- LineChart infinite re-render loop — circular dependency between
useEffect→setSegmentAwareStyle→baseLineStyle→colorScale→statisticalResult. Fixed by guarding statistical effect to only run when forecast/anomaly is present and derivingeffectiveLineStylewithout unnecessary state. createColorScalecrash on undefined data — added null guards (d?.+.filter(v => v != null)) so push API charts with sparse data don't throw.OrdinalSVGOverlayduplicate React keys — keys now include category/group for uniqueness across stacked/grouped layouts.
- Area/StackedArea tooltips showing "-" —
hitTestAreaPathnow extracts the specific data point at the hover index (likehitTestLinedoes) instead of returning the entire data array. - Ordinal frame tooltips — default tooltip now shows category + value using
__oAccessor/__rAccessormetadata. - Geo chart tooltips — ChoroplethMap shows country names (not numeric IDs), ProportionalSymbolMap shows formatted metrics with labels, FlowMap shows source → target with values.
- Force graph centering — added
forceCenterto simulation, strengthenedforceX/forceY, clamped node positions to canvas bounds. FixedfinalizeLayoutoverwriting force-computed positions from stale bounding boxes during streaming warm-starts. - Streaming force refresh — force simulation now runs on topology changes during push API streaming.
- FIFO category ordering — streaming ordinal charts preserve insertion order instead of re-sorting by value (fixes violin/histogram column flicker).
- Edge hit areas — expanded to 5px minimum tolerance across XY lines, network edges (bezier + path), and geo lines. Added
pointToSegmentDistfor accurate perpendicular distance. Line hit tolerance now scales with stroke width. - Network edge ctx.lineWidth leak —
hitTestBezierEdgeandhitTestPathEdgenow save/restorectx.lineWidtharoundisPointInStrokecalls. - Sankey crossing reduction — added barycenter-based initial node ordering before iterative relaxation.
- QuadrantChart streaming — fixed quadrant backgrounds disappearing after first point; points now auto-color by quadrant when no
colorByprovided. - Anti-meridian line handling — geo lines that wrap across the projection edge are split into segments with smooth opacity fades.
- Distance cartogram centering — center node is pinned to viewport center during streaming.
- Orthographic drag jank — pointer-move rotations now coalesce via
pendingRotationRef, applying once per rAF frame.
- Orbit diagram — ring/connecting lines changed from
currentColor(invisible on canvas) torgba(128,128,128,0.35). Root nodes use scheme color instead of grey depth palette. - Treemap/CirclePack labels — luminance-based contrast text color (white on dark fills, dark on light fills). Treemap parent labels positioned at top-left of rectangle.
- ScatterplotMatrix diagonal histograms — now colored by category with O(1) Map lookups instead of grey fills with O(n)
.indexOf(). - Dark mode fixes — serialization page text contrast, streaming system model background, candlestick wick color, uncertainty tooltip background.
tooltip={false}now correctly disables tooltips on all 22 remaining HOCs. The patternnormalizeTooltip(tooltip) || defaultTooltipContentwas replaced with an explicittooltip === false ? undefined : ...check.normalizeTooltipunwrap heuristic tightened — the HoverData unwrap now only triggers when the object has.type === "node" | "edge"AND.data, preventing false unwraps when a user's datum has a.dataproperty.- ForceDirectedGraph empty state —
renderEmptyStatenow checksnodesinstead ofedges, so a graph with nodes but no edges no longer shows the empty state. - ChoroplethMap validation — added GeoJSON-aware validation that checks for a
geometryproperty on area features, replacing the inapplicablevalidateArrayDatacheck. - "Rendered more hooks than during previous render" in
FlowMapandChoroplethMap— hooks were called after early returns for loading/empty states. All hooks now run unconditionally before any early return. colorScalecrash with null areas in ChoroplethMap —useMemonow returns a fallback sequential scale whenresolvedAreasis null during async loading.- Variable name collision in ChoroplethMap — local
areaStylerenamed toareaStyleFnto avoid collision with destructured prop. - Function
colorByproduced undefined colors —useColorScalenow derives categories from data whencolorByis a function and builds a proper ordinal scale.getColormaps non-CSS-color strings throughcolorScale. - LineChart validation —
validateArrayDatanow receives the rawdataprop instead of post-processedsafeData, so push API mode (dataundefined) correctly skips validation instead of triggering "No data provided". - QuadrantChart
sizeDomainNaN —sizeByvalues are now filtered to finite numbers before computing min/max, preventing NaN propagation to point radius.
- Home page: meaningful tooltips on bar chart, bubble chart, network graph (degree centrality)
- Streaming sankey pastel colors, chord multi-color fix
- Highlight hover uses distinct red line, more distinctive custom theme
- Top/bottom legend examples, chart container year controls work
- Responsive frame data fix, styling offset fix, linked dashboard color consistency
- Candlestick dark mode, uncertainty tooltip dark mode, isotype chart person icons
- Radar/isotype duplicate key fix, network explorer
.datawrapper access - Rosling bubble annotations/extent/tooltip, benchmark log scale fix, forecast sparkline card
- Force graph sparse preset parameters, choropleth playground sizing
- DocumentFrame: added 100+ missing prop names to
processNodes - Tile map: production provider documentation
emphasisprop — all charts acceptemphasis="primary" | "secondary".ChartGriddetectsemphasis="primary"on children and spans them across two grid columns for F-pattern dashboard layouts.directLabelrendering — new"text"annotation type inannotationRules.tsxsodirectLabellabels actually render. Automatic right margin expansion prevents label clipping.gapStrategyfixes —"break"now correctly splits lines at null boundaries using synthetic_gapSegmentgroup keys."interpolate"filters gap points in the HOC before data enters the pipeline, preventingresolveAccessor's unary+from coercingnullto0.- Chart States docs page (
/features/chart-states) — dedicated page for empty, loading, and error state documentation. Moved from LineChart and ChartContainer pages. - Gap strategy tabs — consolidated three separate subsections in LineChart docs into a tabbed interface.
- Tabs component — reusable tab switcher for docs pages.
- Export default format —
exportChart()now defaults to PNG instead of SVG. PNG export composites the canvas data layer underneath the SVG overlay, producing a complete chart image. SVG export only captures the overlay (axes, labels). - Type widening — eliminated
as anycasts at HOC/Frame boundaries by wideningrFormat,oSort,colorBy, andTooltipFieldConfig.accessortypes in stream type definitions.
- Export captured only axes — PNG export now finds the
<canvas>element and draws it as the base layer before compositing the SVG overlay on top. directLabelannotations silently dropped —type: "text"was not a recognized annotation type; it fell through to the default case and returnednull.gapStrategy="break"drew lines through gaps — flattening re-merged segments because the Frame re-grouped by the originalgroupAccessor.gapStrategy="interpolate"dropped to zero —resolveAccessorused+(d)[key]which convertednullto0.colorBytype mismatch in network charts — hierarchy charts that color by depth index returned a number, but the type expected a string. AddedString()coercion.- Duplicate
amplitudeproperty inStreamOrdinalFrameProps.
Complete rewrite of Semiotic. Stream-first canvas architecture, 37 HOC chart components, full TypeScript, AI tooling, coordinated views, realtime encoding, and native server-side rendering.
Stream-first rendering. All frames are canvas-first with SVG overlays for
labels, axes, and annotations. Legacy frame names (XYFrame, OrdinalFrame,
NetworkFrame) have been removed entirely.
| Frame | Purpose |
|---|---|
StreamXYFrame |
Line, area, scatter, heatmap, candlestick charts |
StreamOrdinalFrame |
Bar, pie, boxplot, violin, swarm charts |
StreamNetworkFrame |
Force, sankey, chord, tree, treemap, circlepack |
Every frame supports a ref-based push API for streaming data.
Functional components + hooks. All components converted from class-based to
functional. Full TypeScript strict mode with generic type parameters on all
Frame and Chart components. "use client" directives for React Server
Components compatibility.
38 higher-order chart components that wrap the core Frames with curated, simple prop APIs.
XY Charts (wrap StreamXYFrame):
LineChart— line traces with curve interpolation, area fill, and point markersAreaChart— filled area beneath a lineStackedAreaChart— multiple stacked area seriesScatterplot— point clouds with color and size encodingConnectedScatterplot— sequential path through 2D space with Viridis gradientBubbleChart— sized circles with optional labelsHeatmap— 2D binned density visualization
Ordinal Charts (wrap StreamOrdinalFrame):
BarChart— vertical/horizontal bars with sort and color encodingStackedBarChart— stacked categorical barsGroupedBarChart— side-by-side grouped barsSwarmPlot— force-directed point distributionBoxPlot— statistical box-and-whiskerHistogram— binned frequency distributionViolinPlot— kernel density per categoryDotPlot— sorted dot stripsPieChart— proportional slicesDonutChart— ring variant of PieChart
Network Charts (wrap StreamNetworkFrame):
ForceDirectedGraph— force-simulation node-link diagramsChordDiagram— circular connection matrixSankeyDiagram— flow diagrams with weighted edgesTreeDiagram— hierarchical tree layoutsTreemap— space-filling hierarchical rectanglesCirclePack— nested circle packingOrbitDiagram— animated orbital hierarchy with solar/atomic/flat modes
Realtime Charts (canvas-based streaming):
RealtimeLineChart— streaming lineRealtimeHistogram— streaming histogram barsRealtimeSwarmChart— streaming scatterRealtimeWaterfallChart— streaming waterfall/candlestickRealtimeHeatmap— streaming 2D heatmaps with grid binning
All chart components feature:
- Full TypeScript generics (
LineChart<TDatum>) - Sensible defaults for width, height, margins, colors, hover
framePropsescape hatch for accessing the underlying Frame API- Automatic legend rendering when
colorByis set - Smart margin expansion to accommodate legends and axis labels
- Built-in error boundary (never blanks the page) and dev-mode validation warnings
Two SSR paths, both producing identical SVG output:
Component-level SSR — Stream Frames detect server context
(typeof window === "undefined") and render <svg> elements with scene
nodes instead of <canvas>. Same component, same props — works automatically
in Next.js App Router, Remix, and Astro.
Standalone SSR — semiotic/server entry point for Node.js environments
(email, OG images, PDF, static sites):
import { renderToStaticSVG } from "semiotic/server"
const svg = renderToStaticSVG("xy", {
lines: [{ coordinates: data }],
xAccessor: "date",
yAccessor: "value",
size: [600, 400],
})renderToStaticSVG(frameType, props)— generic entry pointrenderXYToStaticSVG(props)— XY-specificrenderOrdinalToStaticSVG(props)— ordinal-specificrenderNetworkToStaticSVG(props)— network-specific- Shared SceneToSVG converters used by both paths
decayprop — configurable opacity fade for older data (linear, exponential, step modes)pulseprop — glow flash effect on newly inserted data points with configurable duration/colortransitionprop — smooth position interpolation with ease-out cubic easingstalenessprop — canvas dimming + optional LIVE/STALE badge when data feed stops- All four features work on StreamXYFrame, StreamOrdinalFrame, and all realtime HOCs
- Features compose freely (e.g., decay + pulse creates a data trail with flash-on-arrival)
marginalGraphicsprop onStreamXYFrame,Scatterplot, andBubbleChart- Four types: histogram, violin, ridgeline, boxplot
- Margins auto-expand to 60px minimum when marginals are configured
LinkedCharts— cross-highlighting, brushing-and-linking, and crossfilter between any charts- Selection hooks:
useSelection,useLinkedHover,useBrushSelection,useFilteredData ScatterplotMatrix— N×N grid with hover cross-highlight or crossfilter brushingCategoryColorProvider— stable category→color mapping across charts
- Annotations with
type: "threshold"automatically split lines into colored segments - Interpolates exact crossing points between data samples
ThemeProviderwraps charts and injects CSS custom properties- Presets:
"light"(default) and"dark" useTheme()hook
ChartGrid— CSS Grid layout with auto columnsContextLayout— primary + context panel layout
semiotic/ai— HOC-only surface optimized for LLM code generationai/schema.json— machine-readable prop schemas for every component- MCP server (
npx semiotic-mcp) — renders charts as SVG tools for any MCP client- Per-component tools for all 21 SVG-renderable chart types
- Generic
renderCharttool accepting{ component, props } diagnoseConfigtool for anti-pattern detection
validateProps(componentName, props)— prop validation with Levenshtein typo suggestionsdiagnoseConfig(componentName, props)— anti-pattern detector with 12 checks:EMPTY_DATA,EMPTY_EDGES,BAD_WIDTH,BAD_HEIGHT,BAD_SIZE,ACCESSOR_MISSING,HIERARCHY_FLAT_ARRAY,NETWORK_NO_EDGES,DATE_NO_FORMAT,LINKED_HOVER_NO_SELECTION,MARGIN_OVERFLOW_H,MARGIN_OVERFLOW_V- CLI (
npx semiotic-ai) —--schema,--compact,--examples,--doctor CLAUDE.md— instruction file for Claude, Cursor, Copilot, Windsurf, and Cline- Schema freshness CI — cross-references schema.json, VALIDATION_MAP, and CLAUDE.md
onObservation— structured events (hover, click, brush, selection) on all HOCsuseChartObserver— aggregates observations across LinkedChartstoConfig/fromConfig/toURL/fromURL/copyConfig/configToJSX— chart serializationfromVegaLite(spec)— translate Vega-Lite specs to Semiotic configsexportChart()— download charts as PNG (default) or SVGChartErrorBoundary— React error boundaryDetailsPanel— click-driven detail panel insideChartContainer- Data transform helpers (
semiotic/data):bin,rollup,groupBy,pivot TooltipandMultiLineTooltipcomponents with field-based configuration- Keyboard navigation utilities
Eight separate entry points for reduced bundle sizes:
| Entry Point | Contents |
|---|---|
semiotic |
Full library |
semiotic/xy |
XY Frame + XY charts |
semiotic/ordinal |
Ordinal Frame + ordinal charts |
semiotic/network |
Network Frame + network charts |
semiotic/realtime |
Realtime charts |
semiotic/server |
SSR rendering functions |
semiotic/ai |
HOC-only surface for AI generation |
semiotic/data |
Data transform utilities |
Extracted shared logic from all HOC chart components into reusable hooks:
useChartSelectionhook — selection/hover setup used by 21 chartsuseChartLegendAndMarginhook — legend + margin auto-expansion used by 18 chartsbuildOrdinalTooltiphelper — shared tooltip builder for ordinal charts- Network utilities —
flattenHierarchy,inferNodesFromEdges,resolveHierarchySum,createEdgeStyleFn
- Rollup 2.x → Rollup 4.x with Terser minification
- Modern ESM output with
constbindings (ES2015 target) sideEffects: falsefor aggressive tree-shaking- Modern
exportsfield in package.json for proper ESM/CJS resolution
Minimum React version is now 18.1.0 (was 16.x in v1, 17.x in v2). Also supports React 19.
The monolithic processing/network.ts has been split into focused layout plugins:
sankey, force, chord, tree, cluster, treemap, circlepack, partition.
- All legacy frames —
XYFrame,OrdinalFrame,NetworkFrameand their Responsive/Spark variants. UseStreamXYFrame,StreamOrdinalFrame,StreamNetworkFrame. FacetController— useLinkedChartsRealtimeSankey,RealtimeNetworkFrame— useStreamNetworkFramewithchartType="sankey"baseMarkProps,ProcessViz,Mark,SpanOrDiv— removed internal utilities
- Chord diagram arc/ribbon angle alignment
- Stacked area streaming flicker (stable sort of groups)
- Violin plot IQR positioning
- Sankey particle colors
- Canvas clip region (marks no longer draw into margins)
- Tooltip position flipping at chart edges
- Stacked bar color encoding, streaming aggregation, and category flicker
- Force layout initial positions (phyllotaxis spiral)
- Treemap hover (smallest containing rect wins)
- Axis label floating-point noise and overlap
- ThemeProvider integration with SVG overlay axes and canvas background
- HOC chart data validation (visible error element instead of blank)
- 30+ additional rendering, theming, and coordination fixes
Version 2.0 was an internal milestone that began the transition from class components to functional components and introduced initial TypeScript support. It was never promoted to a stable release.
Notable changes from v1:
- Initial functional component conversions
- TypeScript adoption began
- React 17 compatibility
- Add
customClickBehaviorwith hover pointer state for legend interactions - Make difference between vertical and horizontal group rendering explicit
- Fix canvas interactivity with custom canvas function
For the complete v1.x changelog, see the git history.