chart-factory
v0.2.0
Published
Token-driven, responsive D3 chart library: area, line, slope, bar, scatter, dot, histogram, donut/pie, box plot / violin, calendar heatmap, sankey, treemap, funnel and radar charts plus sortable tables that fill their container by default.
Maintainers
Readme
chart-factory
A token-driven, responsive chart library built on D3 v7. One factory, 15 families of named builders (primitives plus one-line presets over them — docs/conformance-matrix.md is the generated census) — area, line, slope, bar, scatter, dot, histogram, donut/pie, box plot, calendar heatmap, sankey, treemap, funnel, and radar charts plus sortable tables — that fill whatever container you put them in and re-render crisply on resize.
- Responsive by default — charts measure their container, render at true pixel size, and adapt density (font size, tick count, dot radius) to the available width. No config needed.
- Design tokens — all styling flows through CSS custom properties in
tokens.css; retheme everything by overriding variables. - No build required to consume — plain ES modules plus a prebuilt browser bundle, TypeScript definitions included.
- Date-aware axes — pass
Dateobjects (or ISO strings) as x values and trend charts switch to time scales with adaptive tick formatting automatically.
Install
npm install chart-factory d3d3 (v7) is a peer dependency — install it alongside. React apps also want
the adapter: npm install chart-factory-react.
npm install ../chart-factory # sibling checkout (local development)
npm install github:jolsufka/chart-factory # straight from gitUsage
With a bundler (ESM)
import { ChartFactory } from 'chart-factory';
import 'chart-factory/styles.css'; // everything (tokens + base + table), or import the pieces:
// import 'chart-factory/core/tokens.css';
// import 'chart-factory/core/base.css';
ChartFactory.Line.createBasic('#chart', {
data,
xAccessor: d => d.week,
yAccessor: d => d.points
});Script tag (no build)
<link rel="stylesheet" href="node_modules/chart-factory/dist/chart-factory.css">
<script src="https://d3js.org/d3.v7.min.js"></script>
<script src="node_modules/chart-factory/dist/chart-factory.js"></script>
<script>
ChartFactory.Bar.createBasic('#chart', { data, categoryAccessor: d => d.name, valueAccessor: d => d.value });
</script>The dist bundle uses the page's global d3, so load d3 first. Plugins that attach to the global (e.g. d3-hexbin, used by Scatter.createDensity / createHexbin) keep working.
Responsive behavior
Charts fill their container's width; height comes from a per-family aspect ratio (aspectRatio config to override, height: <number> for a fixed height). The container must be block-level with a real width:
.chart-container { display: block; width: 100%; max-width: 660px; }Horizontal bar/dot charts and gantt derive height from row count (width-responsive only). Pass width: <number> or responsive: false to opt out into fixed-size rendering. size: 'mini' | 'full' | 'wide' pins the density tier that is otherwise auto-selected from measured width (≤340px → mini, ≤620px → full, else wide). A fourth tier, size: 'instagram', is opt-in only (never auto-selected): poster-scale geometry for 1080px social-feed exports, paired with <html data-size="instagram"> to scale the text/stroke tokens to match — see docs/API.md.
Every chart returns a facade: chart.setConfig({...}) merges config and re-renders (state survives resizes), chart.rerender(), chart.destroy().
Tables
import { ChartFactory } from 'chart-factory';
import { D3Table } from 'chart-factory/table';
import 'chart-factory/table/tokens.css';
import 'chart-factory/table/table.css';
ChartFactory.Table.register(D3Table); // one-time
ChartFactory.Table.createSortable('my-table', { columns: [...] }).render(data);In the browser without a bundler, import dist/chart-factory-table.js from a <script type="module"> (d3 already on the page).
Dark mode
Opt in by setting data-theme="dark" on the root element:
<html data-theme="dark">Every color-bearing token (text, surfaces, chart palettes, table heatmap/bar
ramps) has a dark value in core/tokens.css; spacing and sizing carry over.
Charts read tokens live at render time, so switching themes at runtime
needs a re-render — SVG attribute colors are baked in at draw:
document.documentElement.dataset.theme = 'dark'; // or delete it for light
ChartFactory.rerenderAll(); // re-resolves every live chart and table(rerenderAll() tracks all charts and factory-created tables; individual
handles work too: chart.rerender(), table.rerender().)
CSS-styled surfaces (containers, tooltip shell, table chrome) restyle
immediately without a re-render. There is no automatic
prefers-color-scheme switching — wire the attribute (and the re-render
calls) to your app's theme state; in React/Next.js, next-themes with
attribute="data-theme" pairs naturally.
Chart states
Dashboards hit three awkward moments; the chart owns all of them:
const chart = ChartFactory.Line.createBasic('#c', { state: 'loading' }); // skeleton now
fetch('/api/rows').then(r => r.json())
.then(rows => chart.setData(rows)) // real chart (setData ends 'loading')
.catch(() => chart.setConfig({ state: 'error' }));
// rows.length === 0 → the built-in empty state renders automaticallyThe loading skeleton is family-aware (trend silhouette for line/area,
breathing columns/rows for bars, quiet dots elsewhere) and takes the chart's
exact footprint, so the real chart swaps in with zero layout jump.
emptyMessage / errorMessage override the state text.
Live demo (all three states + a fetch simulator):
examples/states-demo.html.
Chart catalog
| Factory | Variants |
|---|---|
| ChartFactory.Area | primitives: basic, stacked · presets (named configs over a primitive): smoothed, diverging (→ basic); normalized, streamgraph (→ stacked) |
| ChartFactory.Line | primitives: basic, multi, band, bump · presets (named configs over a primitive): yearly, smoothed, area-gradient, combined, growth-rate (→ basic); YoY, textures, cumulative-vs-moving (→ multi); forecast-band, pulse-anomalies, dynamic-band (→ band) |
| ChartFactory.Slope | primitive: basic · presets (named configs over basic): trend, categorical, highlight, animated, bands, indexed |
| ChartFactory.Bar | primitives: basic, stacked, likert, gantt, vertical, vertical-stacked, waterfall-vertical · presets (named configs over a primitive): inline, label-above, progress, bullet, diverging (→ basic); grouped (→ stacked); pyramid (→ likert); range, waterfall (→ gantt); vertical-normalized (→ vertical-stacked) |
| ChartFactory.Scatter | primitives: basic, beeswarm, trajectory, density, contour, heatmap · presets (named configs over a primitive): logos, animated, marginal, marginal-KDE, marginal-simple, quadrant, diagonal (→ basic); beeswarm-vertical, beeswarm-horizontal, beeswarm-logos (→ beeswarm); comet (→ trajectory); hexbin (→ density); KDE (→ contour) |
| ChartFactory.Dot | primitives: basic, confidence, percentile, unit-histogram · presets (named configs over basic): dumbbell, gradient, comet |
| ChartFactory.Histogram | primitives: basic (one series of rows), overlay (series[], shared bins) |
| ChartFactory.Donut | primitive: basic (donut) · preset: pie (→ basic, no hole) |
| ChartFactory.BoxPlot | basic (both orientations, raw or precomputed), violin (KDE silhouettes; inner box / points / quartiles) |
| ChartFactory.Calendar | heatmap (weekly / monthRows / vertical layouts) |
| ChartFactory.Sankey | primitive: basic (ribbon width = link value; d3-sankey optional) · preset: funnel-flow (→ basic, tapered ribbons) |
| ChartFactory.Treemap | primitive: basic (branch-identity colors, group headers) · preset: metric (→ basic, color = a second measure) |
| ChartFactory.Funnel | primitive: basic (length = stage value; shape × orientation) · preset: taper (→ basic, classic silhouette) |
| ChartFactory.Radar | basic (circle/polygon grid, multi-series, stroke-only) |
| ChartFactory.Table | primitive: basic · presets (named configs over basic): sortable, with-bars, with-heatmap, with-dot-rails, combined |
Live examples for every variant are in examples/ — serve the repo root (npm run serve) and open http://localhost:8000/examples/.
Upgrading from 0.1.x
0.2.0 is a breaking release with no compatibility shims. Most builders became thin presets over a few primitives, and their second spellings, retired classes and tokens, and datum-first formatters (valueFormat(datum) → valueFormat(value, datum)) were deleted rather than aliased. Every change is listed in migrations/0.2.0.md, which ships in the package, and the package's scanner applies the mechanical ones:
npx -p [email protected] chart-factory-migrate ./src # inventory: file:line, old → new, auto-fixable
npx -p [email protected] chart-factory-migrate ./src --fix # apply the mechanical rewrites onlyWhatever it cannot rewrite is listed for a decision. ChartFactory.validate(builder, config) then confirms that a migrated config is clean.
Development
npm install # installs esbuild
npm run build # bundles src/ -> dist/ (also runs on npm prepare)
npm run watch # rebuild on change
npm run serve # static server for examples/Source lives in src/ as plain ES modules; dist/ holds the generated browser bundles. All styling values must flow through the design tokens in src/core/tokens.css — never hardcode colors, spacing, or font sizes.
