@granularjs/d3
v0.1.0
Published
D3.js encapsulation for GranularJS — reactive charts as Renderables, no extra dependencies.
Maintainers
Readme
@granularjs/d3
D3.js encapsulation for GranularJS. Charts are plain components: they return a Div with a node ref and subscribe to data. When data changes, only the chart redraws—no re-render, no VDOM.
Dependencies: only d3. Granular is a peer (and dev) dependency; the app must install @granularjs/core.
Install
npm install @granularjs/d3 d3 @granularjs/coreUsage
Basic chart (static data)
import { Div } from '@granularjs/core';
import { chart, d3 } from '@granularjs/d3';
const data = [4, 8, 15, 16, 23, 42];
const BarChart = () =>
Div(
{ style: { width: 400, height: 200 } },
chart(data, {
draw(selection, data) {
const scale = d3.scaleLinear().domain([0, d3.max(data)]).range([0, 200]);
selection
.selectAll('rect')
.data(data)
.join('rect')
.attr('x', (_, i) => i * 40)
.attr('y', (d) => 200 - scale(d))
.attr('width', 36)
.attr('height', (d) => scale(d))
.attr('fill', 'steelblue');
},
})
);The container is a div. Your draw receives d3.select(container); append an svg (or any structure) inside it.
Reactive data
Pass state, signal, or observableArray as the first argument. The chart subscribes via after(data).change(...) and redraws when data changes.
import { state, Div, Button } from '@granularjs/core';
import { chart, d3 } from '@granularjs/d3';
const App = () => {
const values = state([4, 8, 15, 16, 23, 42]);
return Div(
chart(values, {
draw(selection, data) {
const scale = d3.scaleLinear().domain([0, d3.max(data)]).range([0, 200]);
selection
.selectAll('rect')
.data(data ?? [])
.join('rect')
.attr('x', (_, i) => i * 40)
.attr('y', (d) => 200 - scale(d))
.attr('width', 36)
.attr('height', (d) => scale(d))
.attr('fill', 'steelblue');
},
}),
Button({ onClick: () => values.set([...values.get(), Math.random() * 50]) }, 'Add value')
);
};API
chart(data, options)
Returns a Div (use as a child of any Granular tag). The div gets anoderef; when it mounts,drawruns. Whendatais reactive,after(data).change(run)triggers redraws.- data — array (static) or reactive source (
state,signal, observableArray). Resolved withresolve(data); reactive sources are subscribed withafter(data).change(run). - options:
draw(selection, data, options)—(d3.Selection, resolvedData, options) => void. Called when the node is mounted and on every data change when data is reactive. The selection is the containerdiv.className— default'g-d3'.node— optional. Astateorsignal; the container element is set on it when available.- Any other keys (e.g.
style,onClick) are passed as props to the containerDiv.
- data — array (static) or reactive source (
d3— re-export of thed3package for scales, axes, shapes, etc.
Principles (aligned with Granular)
- Plain component —
chart()is a function that returns aDiv, like any other Granular component. No custom Renderable; it uses the corenodeprop andafter(...).change(). - Component runs once — your component that returns
chart(...)runs once; redraws are driven byafter(nodeRef).change(run)andafter(data).change(run)when data is reactive. - Explicit reactivity — only reactive sources trigger redraws; plain arrays draw once (when the node mounts).
- No extra dependencies — runtime dependency is only
d3; Granular is peer/dev.
