Try Live
Add Docs
Rankings
Pricing
Docs
Install
Theme
Install
Docs
Pricing
More...
More...
Try Live
Rankings
Enterprise
Create API Key
Add Docs
Semiotic
https://github.com/nteract/semiotic
Admin
A React data visualization library designed for AI-assisted development that combines broad chart
...
Tokens:
118,657
Snippets:
1,250
Trust Score:
9.6
Update:
7 hours ago
Context
Skills
Chat
Benchmark
Suggestions
Latest
Show doc for...
Code
Info
Show Results
Context Summary (auto-generated)
Raw
Copy
Link
# Semiotic Semiotic is a React data visualization library designed for AI-assisted development. It combines broad chart coverage — XY, ordinal, network, geo, and realtime charts — with first-class AI tooling: machine-readable JSON schemas, an MCP server for tool-based chart rendering, a CLI validator, and instruction files auto-synced for Claude, Cursor, Copilot, Windsurf, and Cline. Charts are canvas-first with SVG overlays for axes and labels, support server-side rendering with zero configuration, and ship with built-in error boundaries, dev-mode validation with typo suggestions, and full accessibility features (keyboard navigation, `aria-live` tooltips, screen-reader data tables). The library ships as 11 tree-shakeable sub-path entry points (`semiotic/xy`, `semiotic/ordinal`, `semiotic/network`, `semiotic/geo`, `semiotic/realtime`, `semiotic/server`, `semiotic/utils`, `semiotic/themes`, `semiotic/data`, `semiotic/ai`, `semiotic`) so each bundle only contains the chart category you need. Two API layers are available: HOC chart components with sensible defaults (e.g. `<LineChart>`, `<BarChart>`) and low-level Stream Frames (`StreamXYFrame`, `StreamOrdinalFrame`, `StreamNetworkFrame`, `StreamGeoFrame`) for full rendering control. Every HOC accepts a `frameProps` escape hatch to pass through Frame-level props without leaving the simpler interface. --- ## Installation ```bash npm install semiotic # Requires React 18.1+ or React 19 ``` --- ## XY Charts — `semiotic/xy` ### LineChart — single and multi-line time-series Renders line charts from a flat array using `xAccessor` / `yAccessor`. Use `lineBy` + `colorBy` to split a flat array into multiple lines. Supports curve types, area fill, direct labels, gap strategies, and animated intro/transition. ```jsx import { LineChart } from "semiotic/xy" // Single line const sales = [ { month: "Jan", revenue: 4200 }, { month: "Feb", revenue: 5800 }, { month: "Mar", revenue: 5200 }, { month: "Apr", revenue: 7100 }, ] <LineChart data={sales} xAccessor="month" yAccessor="revenue" curve="monotoneX" showPoints fillArea areaOpacity={0.15} title="Monthly Revenue" description="Monthly revenue trend for 2024" xLabel="Month" yLabel="Revenue ($)" showGrid animate /> // Multi-line from flat array — lineBy groups rows into separate lines const regional = [ { quarter: 1, sales: 120, region: "East" }, { quarter: 2, sales: 180, region: "East" }, { quarter: 1, sales: 90, region: "West" }, { quarter: 2, sales: 140, region: "West" }, ] <LineChart data={regional} xAccessor="quarter" yAccessor="sales" lineBy="region" colorBy="region" showLegend directLabel /> ``` --- ### AreaChart — filled area with optional band Renders filled area charts. Use `y0Accessor` for band/ribbon (e.g. confidence interval). Layer an `AreaChart` (band) over a `LineChart` (center line) using `position: absolute` for percentile bands. ```jsx import { AreaChart, LineChart } from "semiotic/xy" const data = [ { x: 0, p5: 10, p50: 25, p95: 45 }, { x: 1, p5: 12, p50: 28, p95: 50 }, { x: 2, p5: 11, p50: 30, p95: 52 }, ] // Percentile band + median line <div style={{ position: "relative" }}> <AreaChart data={data} xAccessor="x" yAccessor="p95" y0Accessor="p5" showLine={false} areaOpacity={0.3} gradientFill width={600} height={400} /> <div style={{ position: "absolute", top: 0, left: 0 }}> <LineChart data={data} xAccessor="x" yAccessor="p50" frameProps={{ background: "transparent" }} width={600} height={400} /> </div> </div> ``` --- ### StackedAreaChart — stacked areas from flat array Requires `areaBy` (required) to group a flat array into stacked series. Do not use `lineBy` or `lineDataAccessor` — those are `LineChart` props. ```jsx import { StackedAreaChart } from "semiotic/xy" const data = [ { month: 1, value: 20, category: "Product" }, { month: 2, value: 25, category: "Product" }, { month: 1, value: 15, category: "Service" }, { month: 2, value: 12, category: "Service" }, { month: 1, value: 8, category: "Consulting" }, { month: 2, value: 10, category: "Consulting" }, ] <StackedAreaChart data={data} xAccessor="month" yAccessor="value" areaBy="category" // required — groups rows into stacked areas colorBy="category" normalize // 100% stacked showLegend /> ``` --- ### Scatterplot / BubbleChart / ConnectedScatterplot Renders point clouds with optional size encoding and path connections. `BubbleChart` requires `sizeBy`. `ConnectedScatterplot` uses `orderAccessor` to sequence points along a path. ```jsx import { Scatterplot, BubbleChart, ConnectedScatterplot } from "semiotic/xy" const iris = [ { sepalLength: 5.1, petalLength: 1.4, species: "setosa", count: 50 }, { sepalLength: 7.0, petalLength: 4.7, species: "versicolor", count: 30 }, { sepalLength: 6.3, petalLength: 6.0, species: "virginica", count: 40 }, ] <Scatterplot data={iris} xAccessor="sepalLength" yAccessor="petalLength" colorBy="species" pointRadius={5} marginalGraphics="histogram" // distributions on axes showLegend /> <BubbleChart data={iris} xAccessor="sepalLength" yAccessor="petalLength" sizeBy="count" // required sizeRange={[5, 40]} colorBy="species" /> // Phillips curve: economics over time <ConnectedScatterplot data={[ { year: 2020, unemployment: 8.1, inflation: 1.2 }, { year: 2021, unemployment: 5.4, inflation: 4.7 }, { year: 2022, unemployment: 3.6, inflation: 8.0 }, ]} xAccessor="unemployment" yAccessor="inflation" orderAccessor="year" /> ``` --- ### Heatmap — 2D grid with color encoding Maps two categorical or ordinal dimensions to cells colored by a value accessor. ```jsx import { Heatmap } from "semiotic/xy" const activity = [ { day: "Mon", hour: "9am", count: 12 }, { day: "Mon", hour: "12pm", count: 45 }, { day: "Tue", hour: "9am", count: 18 }, { day: "Tue", hour: "12pm", count: 52 }, ] <Heatmap data={activity} xAccessor="hour" yAccessor="day" valueAccessor="count" colorScheme="viridis" // "blues" | "reds" | "greens" | "viridis" showValues showLegend legendPosition="right" /> ``` --- ### MultiAxisLineChart — dual Y-axis Renders two series on independent Y axes. Requires a `series` array; falls back to multi-line if not exactly 2 series. ```jsx import { MultiAxisLineChart } from "semiotic/xy" const data = [ { date: "Jan", revenue: 4200, users: 1200 }, { date: "Feb", revenue: 5800, users: 1800 }, { date: "Mar", revenue: 5200, users: 1500 }, ] <MultiAxisLineChart data={data} xAccessor="date" series={[ { yAccessor: "revenue", label: "Revenue ($)", color: "#4c78a8", format: d => `$${d}` }, { yAccessor: "users", label: "Users", color: "#f58518" }, ]} legendPosition="bottom" showGrid /> ``` --- ### CandlestickChart — OHLC and range charts Renders OHLC candlesticks with all four accessors, or degrades to a range chart with only high/low. ```jsx import { CandlestickChart } from "semiotic/xy" const ohlc = [ { date: "2024-01", open: 100, high: 115, low: 95, close: 110 }, { date: "2024-02", open: 110, high: 120, low: 105, close: 108 }, { date: "2024-03", open: 108, high: 125, low: 100, close: 120 }, ] <CandlestickChart data={ohlc} xAccessor="date" highAccessor="high" lowAccessor="low" openAccessor="open" closeAccessor="close" candlestickStyle={{ upColor: "#26a69a", downColor: "#ef5350", wickWidth: 1 }} /> ``` --- ## Ordinal Charts — `semiotic/ordinal` ### BarChart / StackedBarChart / GroupedBarChart Renders categorical bar charts. `StackedBarChart` requires `stackBy`; `GroupedBarChart` requires `groupBy`. ```jsx import { BarChart, StackedBarChart, GroupedBarChart } from "semiotic/ordinal" // Simple bar <BarChart data={[ { region: "North", total: 4200 }, { region: "South", total: 3800 }, { region: "East", total: 5100 }, ]} categoryAccessor="region" valueAccessor="total" sort="desc" colorBy="region" orientation="vertical" // "horizontal" needs margin={{ left: 120 }} showGrid /> // Stacked — stackBy required <StackedBarChart data={[ { question: "Q1", response: "Agree", count: 45 }, { question: "Q1", response: "Neutral", count: 30 }, { question: "Q1", response: "Disagree", count: 25 }, { question: "Q2", response: "Agree", count: 60 }, { question: "Q2", response: "Neutral", count: 20 }, { question: "Q2", response: "Disagree", count: 20 }, ]} categoryAccessor="question" stackBy="response" // required valueAccessor="count" normalize // 100% stacked /> // Grouped — groupBy required <GroupedBarChart data={[ { year: "2022", product: "Widget", revenue: 120 }, { year: "2022", product: "Gadget", revenue: 95 }, { year: "2023", product: "Widget", revenue: 150 }, { year: "2023", product: "Gadget", revenue: 130 }, ]} categoryAccessor="year" groupBy="product" // required valueAccessor="revenue" colorBy="product" showLegend /> ``` --- ### PieChart / DonutChart / FunnelChart / GaugeChart Renders proportion and flow charts. `GaugeChart` is value-only (no data prop). ```jsx import { PieChart, DonutChart, FunnelChart, GaugeChart } from "semiotic/ordinal" <PieChart data={[ { browser: "Chrome", share: 65 }, { browser: "Firefox", share: 18 }, { browser: "Safari", share: 12 }, { browser: "Edge", share: 5 }, ]} categoryAccessor="browser" valueAccessor="share" colorBy="browser" showLegend /> <DonutChart data={marketShare} categoryAccessor="segment" valueAccessor="pct" innerRadius={60} centerContent={<text>Total</text>} /> <FunnelChart data={[ { step: "Visits", value: 10000 }, { step: "Signups", value: 3200 }, { step: "Activated", value: 1800 }, { step: "Paid", value: 420 }, ]} stepAccessor="step" valueAccessor="value" orientation="vertical" /> // Value-only — no data prop <GaugeChart value={72} min={0} max={100} thresholds={[ { value: 33, color: "#e45050", label: "Poor" }, { value: 66, color: "#f0b429", label: "Average" }, { value: 100, color: "#26a69a", label: "Good" }, ]} showNeedle arcWidth={30} /> ``` --- ### Statistical Distribution Charts Box plots, violin plots, swarm plots, histograms, ridgeline, and dot plots for distribution analysis. ```jsx import { BoxPlot, ViolinPlot, Histogram, RidgelinePlot, SwarmPlot } from "semiotic/ordinal" const salaries = [ { department: "Engineering", salary: 95000 }, { department: "Engineering", salary: 110000 }, { department: "Marketing", salary: 65000 }, { department: "Marketing", salary: 75000 }, ] <ViolinPlot data={salaries} categoryAccessor="department" valueAccessor="salary" colorBy="department" showIQR bins={20} /> <BoxPlot data={salaries} categoryAccessor="department" valueAccessor="salary" showOutliers outlierRadius={4} /> <Histogram data={salaries} categoryAccessor="department" valueAccessor="salary" bins={10} colorBy="department" relative // normalize per-category /> <RidgelinePlot data={salaries} categoryAccessor="department" valueAccessor="salary" bins={15} amplitude={1.5} /> ``` --- ### LikertChart / SwimlaneChart Survey response and multi-category categorical charts. ```jsx import { LikertChart, SwimlaneChart } from "semiotic/ordinal" <LikertChart data={[ { question: "Q1", level: "Strongly Agree", count: 45 }, { question: "Q1", level: "Agree", count: 30 }, { question: "Q1", level: "Neutral", count: 15 }, { question: "Q2", level: "Strongly Agree", count: 60 }, ]} categoryAccessor="question" levelAccessor="level" countAccessor="count" levels={["Strongly Disagree", "Disagree", "Neutral", "Agree", "Strongly Agree"]} /> <SwimlaneChart data={[ { region: "North", product: "Widget", sales: 120 }, { region: "North", product: "Gadget", sales: 80 }, { region: "South", product: "Widget", sales: 90 }, ]} categoryAccessor="region" subcategoryAccessor="product" // required valueAccessor="sales" colorBy="product" /> ``` --- ## Network Charts — `semiotic/network` ### ForceDirectedGraph — node-link layouts Renders force-directed graphs. Both `nodes` and `edges` are required. Use `ForceDirectedGraph` (not `StreamNetworkFrame`) unless you need low-level control; HOC handles `RealtimeNode` wrappers automatically. ```jsx import { ForceDirectedGraph } from "semiotic/network" import { useState } from "react" const nodes = [ { id: "Alice", team: "Engineering", influence: 10 }, { id: "Bob", team: "Engineering", influence: 6 }, { id: "Carol", team: "Design", influence: 8 }, { id: "Eve", team: "Product", influence: 12 }, ] const edges = [ { source: "Alice", target: "Bob" }, { source: "Alice", target: "Carol" }, { source: "Eve", target: "Alice" }, ] const [selected, setSelected] = useState(null) <ForceDirectedGraph nodes={nodes} edges={edges} nodeIDAccessor="id" colorBy="team" nodeSize="influence" nodeSizeRange={[5, 25]} showLabels showLegend edgeOpacity={0.4} tooltip={(d) => <div><strong>{d.data.id}</strong> — {d.data.team}</div>} frameProps={{ customClickBehavior: (d) => { if (d?.type === "node") setSelected(d.data) }, }} /> ``` --- ### SankeyDiagram / ChordDiagram — flow and relationship charts `SankeyDiagram` infers nodes from edge endpoints. `ChordDiagram` shows bidirectional relationships in a circle. ```jsx import { SankeyDiagram, ChordDiagram } from "semiotic/network" <SankeyDiagram edges={[ { source: "Salary", target: "Budget", value: 5000 }, { source: "Freelance", target: "Budget", value: 2000 }, { source: "Budget", target: "Rent", value: 2500 }, { source: "Budget", target: "Food", value: 1200 }, { source: "Budget", target: "Savings", value: 2500 }, ]} sourceAccessor="source" targetAccessor="target" valueAccessor="value" showLabels nodeAlign="justify" /> <ChordDiagram edges={[ { source: "US", target: "EU", value: 500 }, { source: "US", target: "Asia", value: 300 }, { source: "EU", target: "Asia", value: 200 }, ]} valueAccessor="value" showLabels /> ``` --- ### Hierarchy Charts — TreeDiagram / Treemap / CirclePack / OrbitDiagram All hierarchy charts take a single root object with nested `children`. Do not pass a flat array. ```jsx import { TreeDiagram, Treemap, CirclePack, OrbitDiagram } from "semiotic/network" const orgChart = { name: "CEO", children: [ { name: "CTO", children: [{ name: "Eng Lead" }, { name: "Data Lead" }] }, { name: "CFO", children: [{ name: "Accounting" }, { name: "Finance" }] }, ], } <TreeDiagram data={orgChart} childrenAccessor="children" nodeIdAccessor="name" orientation="horizontal" colorByDepth /> <Treemap data={orgChart} childrenAccessor="children" valueAccessor="headcount" nodeIdAccessor="name" colorByDepth showLabels /> <CirclePack data={orgChart} childrenAccessor="children" valueAccessor="headcount" nodeIdAccessor="name" colorByDepth /> <OrbitDiagram data={orgChart} childrenAccessor="children" nodeIdAccessor="name" orbitMode="solar" // "flat" | "solar" | "atomic" | number[] animated showLabels colorByDepth /> ``` --- ## Geographic Charts — `semiotic/geo` Always import from `semiotic/geo`, never from `semiotic`, to avoid pulling d3-geo into non-geo bundles. ### ChoroplethMap / ProportionalSymbolMap / FlowMap / DistanceCartogram ```jsx import { ChoroplethMap, ProportionalSymbolMap, FlowMap, DistanceCartogram, resolveReferenceGeography, mergeData, } from "semiotic/geo" // Built-in world geography + merge with data const features = resolveReferenceGeography("world-110m") const mergedFeatures = mergeData(features, gdpData, { featureKey: "properties.iso_a3", dataKey: "countryCode", }) <ChoroplethMap areas={mergedFeatures} valueAccessor="gdp" colorScheme="viridis" projection="equalEarth" zoomable zoomExtent={[1, 8]} tooltip showLegend graticule /> <ProportionalSymbolMap points={cities} xAccessor="lon" yAccessor="lat" sizeBy="population" sizeRange={[3, 30]} colorBy="region" areas={features} // background geography /> <FlowMap flows={routes} nodes={airports} valueAccessor="passengers" showParticles particleStyle={{ color: "source", speedMultiplier: 1.5, radius: 2 }} /> <DistanceCartogram points={cities} center="rome" costAccessor="travelDays" showRings costLabel="days" /> ``` --- ## Realtime Charts — `semiotic/realtime` Realtime charts use a ref-based push API. **Omit `data` entirely** — passing `data={[]}` clears on every render. All pushed data must include a time field. ### RealtimeLineChart / RealtimeHistogram / RealtimeSwarmChart / RealtimeWaterfallChart / RealtimeHeatmap ```jsx import { useRef, useEffect } from "react" import { RealtimeLineChart, RealtimeHistogram, RealtimeHeatmap } from "semiotic/realtime" // --- Line chart --- const lineRef = useRef() useEffect(() => { const id = setInterval(() => { lineRef.current?.push({ time: Date.now(), value: Math.random() * 100 }) }, 500) return () => clearInterval(id) }, []) <RealtimeLineChart ref={lineRef} timeAccessor="time" valueAccessor="value" windowSize={120} // number of data points to display decay={{ type: "exponential", halfLife: 100 }} staleness={{ threshold: 5000, showBadge: true }} stroke="#76b7b2" width={600} height={300} /> // --- Histogram — binSize required, time field required for windowing --- const histRef = useRef() useEffect(() => { const id = setInterval(() => { histRef.current?.push({ time: Date.now(), value: Math.random() * 1000 }) }, 200) return () => clearInterval(id) }, []) <RealtimeHistogram ref={histRef} timeAccessor="time" valueAccessor="value" binSize={100} // required width={400} height={250} /> // --- Heatmap — both accessors must match data fields --- const heatRef = useRef() <RealtimeHeatmap ref={heatRef} timeAccessor="time" valueAccessor="value" heatmapXBins={30} heatmapYBins={10} width={400} height={250} /> ``` --- ### Streaming Sankey with Particles — StreamNetworkFrame Use `StreamNetworkFrame` for low-level streaming network control. Push individual edges (not snapshots). ```jsx import { useRef, useEffect } from "react" import { StreamNetworkFrame } from "semiotic/network" const chartRef = useRef() useEffect(() => { // Push individual edge events — each push adds to the streaming sankey chartRef.current.push({ source: "Salary", target: "Budget", value: 5000 }) chartRef.current.push({ source: "Freelance", target: "Budget", value: 1500 }) chartRef.current.pushMany([ { source: "Budget", target: "Rent", value: 2000 }, { source: "Budget", target: "Food", value: 800 }, { source: "Budget", target: "Savings", value: 1500 }, ]) }, []) <StreamNetworkFrame ref={chartRef} chartType="sankey" size={[800, 400]} showParticles particleStyle={{ radius: 2, opacity: 0.8, speedMultiplier: 1.5, maxPerEdge: 4, colorBy: "source", }} edgeOpacity={0.4} /> ``` --- ## Coordinated Views — LinkedCharts and CategoryColorProvider ### LinkedCharts — synchronized hover, brush, and selection Wrap multiple charts in `<LinkedCharts>` for zero-wiring cross-chart interaction. Add `linkedHover` + `selection` on each chart to participate. Use `CategoryColorProvider` when multiple charts encode the same categorical field — it gives identical colors per category and renders a single unified legend. ```jsx import { LinkedCharts, CategoryColorProvider, Scatterplot, BarChart, LineChart } from "semiotic" // Hover + click selection sync <LinkedCharts> <CategoryColorProvider colors={{ East: "#4c78a8", West: "#f58518", North: "#54a24b" }}> <Scatterplot data={data} xAccessor="age" yAccessor="income" colorBy="region" linkedHover={{ name: "hl", fields: ["region"] }} selection={{ name: "hl" }} hoverHighlight // dims non-hovered series (requires colorBy) /> <BarChart data={summary} categoryAccessor="region" valueAccessor="total" colorBy="region" selection={{ name: "hl" }} /> </CategoryColorProvider> </LinkedCharts> // Linked crosshair across time-series charts <LinkedCharts> <LineChart data={cpuData} xAccessor="time" yAccessor="cpu" linkedHover={{ name: "metrics", mode: "x-position", xField: "time" }} selection={{ name: "metrics" }} title="CPU %" /> <LineChart data={memData} xAccessor="time" yAccessor="memory" linkedHover={{ name: "metrics", mode: "x-position", xField: "time" }} selection={{ name: "metrics" }} title="Memory MB" /> </LinkedCharts> ``` --- ### useSelection / useLinkedHover / useBrushSelection — hooks for coordinated views Hooks to read linked state outside of chart callbacks. ```jsx import { useSelection, useLinkedHover, useBrushSelection, LinkedCharts } from "semiotic" function Dashboard() { const [selection] = useSelection("hl") const [hovered] = useLinkedHover("hl") const [brushed] = useBrushSelection("brush1") return ( <LinkedCharts> <div>Selected: {JSON.stringify(selection)}</div> <Scatterplot data={data} xAccessor="x" yAccessor="y" colorBy="category" linkedHover={{ name: "hl", fields: ["category"] }} selection={{ name: "hl" }} /> </LinkedCharts> ) } ``` --- ## Server-Side Rendering — `semiotic/server` ### renderChart — SVG generation (sync, no dependencies) Renders a static SVG string for a chart component. Use `renderChart` for named HOC components; use the frame-specific shortcuts for raw Frame props. ```ts import { renderChart, renderToImage, renderToAnimatedGif, renderDashboard } from "semiotic/server" // SVG — sync, no dependencies needed const svg = renderChart("BarChart", { data: [ { category: "Q1", revenue: 120 }, { category: "Q2", revenue: 180 }, { category: "Q3", revenue: 150 }, ], categoryAccessor: "category", valueAccessor: "revenue", theme: "tufte", title: "Quarterly Revenue", showGrid: true, showLegend: true, annotations: [ { type: "y-threshold", value: 160, label: "Target", color: "#e45050" }, ], }) // → "<svg role="img" ...>...</svg>" // PNG — async, requires sharp const png = await renderToImage("LineChart", { data, xAccessor: "date", yAccessor: "value", }, { format: "png", scale: 2 }) // Animated GIF — async, requires sharp + gifenc const gif = await renderToAnimatedGif("line", data, { xAccessor: "x", yAccessor: "y", theme: "dark" }, { fps: 12, transitionFrames: 4, decay: { type: "linear" }, loop: true } ) // Multi-chart dashboard const dashboardSvg = renderDashboard([ { component: "BarChart", props: { data: barData, categoryAccessor: "name", valueAccessor: "value" } }, { component: "LineChart", colSpan: 2, props: { data: lineData, xAccessor: "x", yAccessor: "y" } }, ], { title: "Q1 Overview", theme: "dark", layout: { columns: 2 } }) ``` --- ## Config Serialization — `semiotic/utils` ### toConfig / fromConfig / toURL / fromURL / configToJSX / copyConfig Serialize chart props to JSON, URL query strings, or JSX code strings. Functions, React elements, and callbacks are stripped automatically. ```ts import { toConfig, fromConfig, toURL, fromURL, configToJSX, copyConfig } from "semiotic/utils" const config = toConfig("LineChart", { data: [{ x: 1, y: 10 }, { x: 2, y: 20 }], xAccessor: "x", yAccessor: "y", curve: "monotoneX", showPoints: true, }, { includeData: true }) // Reconstruct component name + props const { componentName, props } = fromConfig(config) // → { componentName: "LineChart", props: { data: [...], xAccessor: "x", ... } } // URL-safe query string for shareable chart links const queryString = toURL(config) // → "sc=eyJjb21wb25lbnQiOiJMaW..." const restored = fromURL("https://app.com/chart?" + queryString) // Generate JSX code string (for AI output, code export) const jsx = configToJSX(config) // → '<LineChart\n data={[...]}\n xAccessor="x"\n yAccessor="y"\n/>' // Copy JSON or JSX to clipboard await copyConfig(config, "jsx") // "json" | "jsx" ``` --- ### fromVegaLite — Vega-Lite to Semiotic translation Translates Vega-Lite specifications to Semiotic chart configs. Works with `configToJSX()` for round-trip from notebooks and AI-generated specs. ```ts import { fromVegaLite } from "semiotic/data" import { fromConfig, configToJSX } from "semiotic/utils" const config = fromVegaLite({ mark: "bar", data: { values: [ { a: "A", b: 28 }, { a: "B", b: 55 }, { a: "C", b: 43 }, ], }, encoding: { x: { field: "a", type: "nominal" }, y: { field: "b", type: "quantitative" }, color: { field: "a", type: "nominal" }, }, }) const { componentName, props } = fromConfig(config) // → componentName: "BarChart" // → props: { data: [...], categoryAccessor: "a", valueAccessor: "b", colorBy: "a" } const jsxCode = configToJSX(config) // → '<BarChart\n categoryAccessor="a"\n valueAccessor="b"\n colorBy="a"\n data={[...]}\n/>' ``` Supports: bar, line, area, point (scatter), rect, arc (pie/donut), tick marks with encoding translation for color, size, aggregation (sum/mean/count), and binning. --- ## Validation and Diagnostics — `semiotic/utils` ### validateProps — static prop validation Validates component name, required props, prop types, enum values, and unknown props with typo detection. ```ts import { validateProps } from "semiotic/utils" const result = validateProps("LineChart", { data: [{ x: 1, y: 10 }], xAccessor: "x", yAccessore: "y", // typo }) // → { valid: false, errors: ['Unknown prop "yAccessore". Did you mean "yAccessor"?'] } const ok = validateProps("StackedBarChart", { data: [{ q: "Q1", resp: "Agree", n: 45 }], categoryAccessor: "q", stackBy: "resp", valueAccessor: "n", }) // → { valid: true, errors: [] } ``` --- ### diagnoseConfig — anti-pattern detection with actionable fixes Runs 19 checks covering empty data, bad dimensions, missing required accessors, hierarchy/network data shape mismatches, date formatting, linked hover without selection, non-zero baselines, data gaps, margin overflow, bar padding, legend clipping, heatmap string axes, color contrast, and unstable function accessors. ```ts import { diagnoseConfig } from "semiotic/utils" // Empty data diagnoseConfig("LineChart", { data: [] }) // → { ok: false, diagnoses: [{ severity: "error", code: "EMPTY_DATA", message: "data is an empty array", fix: "Provide at least one data point..." }] } // Accessor not in data diagnoseConfig("BarChart", { data: [{ name: "A", count: 10 }], categoryAccessor: "region", // "region" not in data valueAccessor: "count", }) // → { ok: false, diagnoses: [{ severity: "error", code: "ACCESSOR_MISSING", message: 'categoryAccessor="region" not found in data. Available fields: name, count.', fix: '...' }] } // Hierarchy chart with flat array diagnoseConfig("Treemap", { data: [{ name: "A", value: 10 }] }) // → { severity: "error", code: "HIERARCHY_FLAT_ARRAY", fix: 'Pass a root object: data={{ name: "root", children: [...] }}' } // Full diagnosis with warnings const { ok, diagnoses } = diagnoseConfig("BarChart", { data: [{ region: "East", revenue: 120 }], categoryAccessor: "region", valueAccessor: "revenue", colorScheme: ["#ffffff", "#f0f0f0"], // low contrast background: "#ffffff", }) // → ok: true (only warnings) // → diagnoses includes LOW_COLOR_CONTRAST warning ``` --- ### CLI — `npx semiotic-ai` Command-line validation and schema exploration without an MCP client. ```bash # List all components with import paths and renderability npx semiotic-ai --list npx semiotic-ai --list --json # Get schema for a specific component npx semiotic-ai --schema LineChart npx semiotic-ai --schema GaugeChart # Dump all schemas (compact form for fewer tokens) npx semiotic-ai --schema npx semiotic-ai --compact # Suggest chart for a data sample npx semiotic-ai --suggest '{"data":[{"category":"A","value":10},{"category":"B","value":20}],"intent":"comparison"}' # Validate component + props JSON npx semiotic-ai --doctor # → Reads stdin JSON: { "component": "BarChart", "props": { ... } } # → Outputs validation errors with typo suggestions and anti-pattern fixes ``` --- ## MCP Server — `npx semiotic-mcp` ### MCP server setup and tools The Semiotic MCP server lets AI coding assistants render charts, inspect schemas, get chart recommendations, and diagnose configurations via tool calls. No API keys required; runs locally via stdio. ```json // claude_desktop_config.json { "mcpServers": { "semiotic": { "command": "npx", "args": ["semiotic-mcp"] } } } ``` ``` // HTTP mode for inspectors and web clients npx semiotic-mcp --http --port 3001 // Tool: getSchema — prop schema for a component (omit component to list all 43) Tool: getSchema Args: { "component": "LineChart" } → { "name": "LineChart", "description": "...", "parameters": { "properties": { ... } } } // Tool: suggestChart — recommend chart types for a data sample Tool: suggestChart Args: { "data": [{ "month": "Jan", "revenue": 120, "region": "East" }], "intent": "comparison" } → 1. BarChart (high) — categorical field (region) with values (revenue) 2. GroupedBarChart (medium) — two categorical dimensions 3. DonutChart (medium) — 2 categories, proportional composition // Tool: renderChart — render to static SVG Tool: renderChart Args: { "component": "BarChart", "props": { "data": [{ "category": "Q1", "revenue": 120 }, { "category": "Q2", "revenue": 180 }], "categoryAccessor": "category", "valueAccessor": "revenue" } } → <svg>...</svg> // Tool: diagnoseConfig — catch common problems Tool: diagnoseConfig Args: { "component": "LineChart", "props": { "data": [] } } → ✗ [EMPTY_DATA] data is an empty array — Fix: provide at least one data point // Tool: applyTheme — list or inspect theme presets Tool: applyTheme Args: { "name": "tufte" } → ThemeProvider usage, CSS custom property list, token map // Tool: reportIssue — generate pre-filled GitHub issue URL Tool: reportIssue Args: { "title": "Bug: tooltip undefined for custom accessor", "body": "...", "labels": ["bug"] } → https://github.com/nteract/semiotic/issues/new?... ``` --- ## Data Transforms — `semiotic/data` ### bin / rollup / groupBy / pivot Utility functions to reshape data before passing to charts. ```ts import { bin, rollup, groupBy, pivot } from "semiotic/data" const rawData = [ { dept: "Eng", salary: 95000 }, { dept: "Eng", salary: 110000 }, { dept: "Marketing", salary: 65000 }, { dept: "Marketing", salary: 75000 }, ] // bin — continuous to histogram buckets → BarChart-ready const bins = bin(rawData, { field: "salary", bins: 5 }) // → [{ category: "65000-74000", value: 1 }, { category: "74000-83000", value: 1 }, ...] // rollup — group and aggregate → BarChart-ready const means = rollup(rawData, { groupBy: "dept", value: "salary", agg: "mean" }) // → [{ dept: "Eng", value: 102500 }, { dept: "Marketing", value: 70000 }] // groupBy — flat → nested array → LineChart lineBy-ready const grouped = groupBy(rawData, { key: "dept" }) // → [{ id: "Eng", coordinates: [...] }, { id: "Marketing", coordinates: [...] }] // pivot — wide to long (one column per variable → one row per variable) const wide = [ { year: 2022, Product: 120, Service: 80 }, { year: 2023, Product: 150, Service: 90 }, ] const long = pivot(wide, { columns: ["Product", "Service"], nameField: "category", valueField: "value" }) // → [ // { year: 2022, category: "Product", value: 120 }, // { year: 2022, category: "Service", value: 80 }, // { year: 2023, category: "Product", value: 150 }, // { year: 2023, category: "Service", value: 90 }, // ] ``` --- ## Theming — `semiotic/themes` ### ThemeProvider / CSS custom properties / theme serialization Apply named presets, custom brand themes, or CSS variable overrides. Theme categorical colors flow to data marks automatically. ```jsx import { ThemeProvider, COLOR_BLIND_SAFE_CATEGORICAL } from "semiotic" import { themeToCSS, themeToTokens, resolveThemePreset } from "semiotic/themes" // Named preset — light, dark, high-contrast, pastels, bi-tool, italian, // tufte, journalist, playful, carbon (each has -dark variant) <ThemeProvider theme="tufte"> <BarChart data={data} categoryAccessor="name" valueAccessor="value" /> </ThemeProvider> // Custom brand theme (merges onto base) <ThemeProvider theme={{ mode: "light", colors: { primary: "#cc0000", categorical: ["#cc0000", "#333333", "#c8a415", "#4682b4"], background: "#fafafa", text: "#1a1a1a", success: "#26a69a", danger: "#ef5350", warning: "#f0b429", }, typography: { fontFamily: "'Helvetica Neue', Arial, sans-serif", titleSize: 16 }, }}> <GroupedBarChart data={data} categoryAccessor="quarter" groupBy="region" valueAccessor="revenue" colorBy="region" showGrid showLegend /> </ThemeProvider> // CSS custom properties — no React context needed (scoped override) <div style={{ "--semiotic-bg": "#1a1a2e", "--semiotic-text": "#ededed", "--semiotic-grid": "#333", "--semiotic-primary": "#ff6b6b", "--semiotic-tooltip-bg": "#1a1a2e", "--semiotic-tooltip-text": "#ededed", }}> <LineChart data={data} xAccessor="x" yAccessor="y" showGrid /> </div> // Color-blind safe palette (Wong 2011 — 8 colors) <Scatterplot data={data} xAccessor="x" yAccessor="y" colorBy="species" colorScheme={COLOR_BLIND_SAFE_CATEGORICAL} /> // Serialize theme to CSS file or design tokens const css = themeToCSS(resolveThemePreset("tufte"), ":root") const tokens = themeToTokens(resolveThemePreset("carbon-dark")) ``` --- ## Annotations — all chart types ### Threshold lines, bands, highlights, and widget annotations All HOC charts accept an `annotations` array. Coordinates reference data field names directly. ```jsx import { LineChart, BarChart } from "semiotic" <LineChart data={salesData} xAccessor="month" yAccessor="revenue" annotations={[ // Horizontal reference line { type: "y-threshold", value: 50000, label: "Q3 Target", color: "#e45050", labelPosition: "left" }, // Vertical reference line { type: "x-threshold", value: "Jul", label: "Launch", color: "#4c78a8" }, // Shaded band (yExtent range) { type: "band", y0: 45000, y1: 55000, color: "#4c78a8", opacity: 0.1 }, // Custom widget at a data point { type: "widget", month: "Aug", revenue: 72000, dy: -20, content: <span style={{ fontWeight: 700, fontSize: 11 }}>Record</span> }, // Enclose an outlier cluster { type: "enclose", coordinates: outlierPoints, label: "Anomaly cluster", padding: 12 }, // Statistical overlay { type: "trend" }, // LOESS trend line { type: "forecast", periods: 3, confidenceInterval: 0.95 }, ]} /> <BarChart data={quarterly} categoryAccessor="quarter" valueAccessor="revenue" annotations={[ { type: "y-threshold", value: 50000, label: "Target" }, { type: "category-highlight", category: "Q3 2024", color: "#4589ff", opacity: 0.15 }, ]} /> ``` --- ## TypeScript — generic accessors Built with `strict: true`. Generics validate accessor strings against your data type. ```tsx import { LineChart } from "semiotic/xy" import { ForceDirectedGraph } from "semiotic/network" interface Sale { month: string; revenue: number; region: string } interface Person { id: string; team: string; score: number } interface Link { source: string; target: string } // xAccessor / yAccessor validated as keyof Sale at compile time <LineChart<Sale> data={sales} xAccessor="month" // ✓ yAccessor="revenue" // ✓ lineBy="region" /> <ForceDirectedGraph<Person, Link> nodes={people} edges={links} nodeIDAccessor="id" // ✓ keyof Person colorBy="team" nodeSize="score" /> ``` --- ## Common Props — all HOC charts All HOC chart components share a common set of props. ```jsx // Universal props accepted by every HOC <LineChart data={data} xAccessor="x" yAccessor="y" // Layout width={600} height={400} responsiveWidth // fills container width responsiveHeight // fills container height margin={{ top: 20, right: 20, bottom: 40, left: 60 }} // Accessibility title="Chart Title" description="Detailed aria-label for screen readers" summary="Screen-reader-only data summary" accessibleTable // sr-only data table (default true) // Interaction enableHover={true} tooltip // true | "multi" | function | config object onClick={(datum, { x, y }) => console.log(datum)} onObservation={({ type, datum, chartType, chartId }) => {}} // Appearance color="#4c78a8" // uniform fill for all marks stroke="var(--semiotic-border)" strokeWidth={1} opacity={0.9} showGrid={false} showLegend legendPosition="right" // "right" | "left" | "top" | "bottom" legendInteraction="highlight" // "none" | "highlight" | "isolate" emphasis="primary" // "primary" | "secondary" animate={{ duration: 300, easing: "easeInOut", intro: true }} // Hover effects hoverHighlight // dims non-hovered series (requires colorBy) hoverRadius={30} // Loading/empty states loading={false} emptyContent={<div>No data</div>} // Color colorBy="category" colorScheme={["#4c78a8", "#f58518", "#54a24b"]} // Frame escape hatch frameProps={{ scalePadding: 12, axes: [{ orient: "bottom", includeMax: true }] }} /> ``` --- Semiotic covers the full spectrum of production data visualization needs — from a simple `<BarChart>` in five lines to coordinated multi-chart dashboards with live streaming data, geographic overlays, and force-directed network graphs. Its structured JSON schemas and MCP integration make it the right choice when AI coding assistants need to generate correct visualization code reliably, while the push API, `LinkedCharts`, and canvas-first rendering make it a strong fit for monitoring dashboards and analytics applications that need high-frequency updates or coordinated cross-filtering. The library's layered architecture means you start with HOC chart components and reach for Stream Frames only when you need capabilities the HOCs don't expose. Sub-path imports keep bundle sizes minimal, TypeScript generics prevent accessor typos at compile time, and `diagnoseConfig` catches the remaining runtime anti-patterns before they cause blank screens. For teams building data-rich React applications — dashboards, analytics tools, reporting UIs, or AI-generated chart pipelines — Semiotic provides a single coherent API across all major chart families with SSR, theming, and accessibility built in.