Sand.js is a lightweight, framework-agnostic JavaScript library for building sunburst charts using SVG.
It is fully data-driven: you describe your chart in JSON, and Sand.js takes care of both layout computation and rendering.
Sand.js is designed to be:
- Lightweight: no heavy dependencies.
- Agnostic: works with plain HTML, or in any framework.
- JSON-driven: describe your sunburst once, render anywhere.
- Accessible: interactive charts with sensible defaults.
| Term | Description |
|---|---|
| Sunburst | The entire chart, composed of one or more layers. |
| Layer | A logical group of rings, defined by a dataset and a mode (free or align). |
| Ring | A radial band, automatically computed from nodes and their expandLevels. |
| Node | A declared unit of data in JSON. May have children (tree mode) or not (flat mode). |
| Leaf | A node without children. |
| Key-group | A logical grouping of nodes sharing the same key, used for alignment and interactions. |
| Arc | A computed geometric entity ready to be rendered (x0, x1, y0, y1). |
| Property | Type | Description |
|---|---|---|
size.radius |
number |
Final radius in pixels. |
size.angle? |
number |
Total angle (default 2Ο = full circle). |
layers |
Layer[] |
List of layers from inside out. |
| Property | Type | Description |
|---|---|---|
id |
string |
Unique identifier. |
radialUnits |
[n,n] |
Radial interval in unit rings. |
angleMode |
"free" | "align" |
Angular partition mode. |
alignWith? |
string |
Reference layer ID (if align). |
padAngle? |
number |
Angular spacing between arcs. |
baseOffset? |
number |
Global rotation (radians). |
arcOffsetMode? |
"relative" | "absolute" |
How offsets are applied. |
tree |
Node | Node[] |
Data for the layer. Supports tree or flat structure. |
| Property | Type | Description |
|---|---|---|
name |
string |
Display name. |
value |
number |
Size of the arc (summed if children exist). |
key? |
string |
Stable identifier for alignment, animation, and coloring. |
expandLevels? |
number |
Radial thickness in rings (default = 1). |
offset? |
number |
Local offset. |
color? |
string |
Custom arc color (overrides palette). |
children? |
Node[] |
Child nodes (tree mode only). |
tooltip? |
string |
Custom tooltip text. |
collapsed? |
boolean |
Keep descendants hidden from layout while their values contribute to the parent arc. |
hidden? |
boolean |
Hides the node completely. |
| Property | Type | Description |
|---|---|---|
layerId |
string | Origin layer ID. |
data |
Node | Reference to original node. |
x0, x1 |
number | Angular start and end (radians). |
y0, y1 |
number | Inner/outer radius (pixels). |
depth |
number | Logical depth in its layer. |
key? |
string | Copy of node key. |
percentage |
number | Relative percentage in its parent. |
npm install @akitain/sandjsRender a sunburst in the browser:
<svg id="chart"></svg>
<script type="module">
import { renderSVG } from 'sandjs';
const config = {
size: { radius: 160 },
layers: [
{
id: 'root',
radialUnits: [0, 2],
angleMode: 'free',
tree: [
{ name: 'Data', value: 6, key: 'data' },
{
name: 'Design',
key: 'design',
value: 4,
children: [
{ name: 'UI', value: 2 },
{ name: 'Brand', value: 2 }
]
}
]
}
]
};
const chart = renderSVG({
el: '#chart',
config,
tooltip: true,
onArcClick: ({ arc }) => console.log('Pinned', arc.data.name)
});
// Update the chart later without re-attaching listeners
chart.update({
config: {
...config,
size: { radius: 180 },
}
});
</script>The demo under demo/ shows relative/absolute offsets, tooltips, and selection callbacks. Run npm run dev to start the development server at http://localhost:4173 to experiment.
Sand.js includes a comprehensive color theme system with 14 built-in palettes and flexible assignment strategies.
Qualitative (for categorical data):
import { renderSVG, QUALITATIVE_PALETTES } from '@akitain/sandjs';
renderSVG({
el: '#chart',
config,
colorTheme: {
type: 'qualitative',
palette: 'ocean', // 'default' | 'pastel' | 'vibrant' | 'earth' | 'ocean' | 'sunset'
assignBy: 'key' // assigns consistent colors based on arc keys
}
});Sequential (for ordered data):
colorTheme: {
type: 'sequential',
palette: 'blues', // 'blues' | 'greens' | 'purples' | 'oranges'
assignBy: 'depth' // light to dark by depth level
}Diverging (for data with meaningful midpoint):
colorTheme: {
type: 'diverging',
palette: 'redBlue', // 'redBlue' | 'orangePurple' | 'greenRed'
assignBy: 'value' // color by normalized value
}key: Consistent colors for arcs with the same key (default for qualitative)depth: Colors by hierarchical depth (default for sequential/diverging)index: Sequential colors by arc index in layervalue: Colors mapped to normalized arc values
colorTheme: {
type: 'qualitative',
palette: ['#ff6b6b', '#4ecdc4', '#45b7d1', '#f7dc6f'], // custom colors
assignBy: 'key'
}colorTheme: {
type: 'qualitative',
palette: 'default',
deriveKey: (arc) => arc.data.category // use any arc property for color grouping
}Note: Individual node colors set via node.color always take precedence over theme colors.
Enable interactive drill-down navigation with smooth transitions:
const chart = renderSVG({
el: '#chart',
config,
navigation: true, // enables click-to-zoom with defaults
transition: true // smooth animations
});navigation: {
layers: ['main', 'details'], // which layers support navigation
rootLabel: 'Home', // breadcrumb root text
focusTransition: { // custom zoom animation
duration: 600,
easing: (t) => t * t // quadratic ease-in
},
onFocusChange: (focus) => {
if (focus) {
console.log('Focused on:', focus.arc.data.name);
} else {
console.log('Returned to root');
}
}
}// Reset to root view
if (chart.resetNavigation) {
chart.resetNavigation();
}breadcrumbs: {
container: '#breadcrumb-trail',
interactive: true, // enable click-to-navigate on breadcrumb items
separator: ' βΊ ',
rootLabel: 'Overview'
}- Layers (
freeoralign):freesplits siblings by value;alignreuses the angular span of a keyed arc in another layer. - Offsets:
defaultArcOffsetand per-nodeoffsetshift arcs. Inrelativemode the offset is a fraction of the available span; inabsolutemode it is applied in radians. - Padding: Use
padAngleon a layer or node to reserve gap space between siblings. - Callbacks:
renderSVGexposesonArcEnter,onArcMove,onArcLeave, andonArcClickwith the arc metadata and the renderedpath. - Tooltips: enable with
tooltip: trueor pass{ formatter, container }for custom markup. - Breadcrumbs: pass
breadcrumbs: trueto auto-render a trail or supply{ container, formatter, separator };formatArcBreadcrumb(arc)helps generate custom labels. - Highlights: enable
highlightByKey: true(or supply options) to add a shared class for arcs with the samekey, and optionally toggle pinned highlights viapinOnClick. - Collapsing: set
collapsed: trueon a node to keep its descendants hidden from the rendered layout while preserving its aggregated value. - Updates: keep the returned handle from
renderSVGand callchart.update({ config: nextConfig })(or pass a full config) to redraw without re-binding listeners.
See src/types/index.ts for the full TypeScript contracts.
npm run test # type check + node test runner
npm run build # rollup (ESM + minified IIFE bundles)
npm run verify # convenience: runs tests and builddist/ contains the publishable artifacts. The IIFE bundle exposes window.SandJS for CDN usage (https://unpkg.com/@akitain/[email protected]/dist/sandjs.iife.min.js).
npm run verify- Manually review the
demo/example in a browser. - Update
CHANGELOG.mdand bump the package version. npm publish --access public
- Core layout (
layout(config)β arcs{x0,x1,y0,y1}) - SVG renderer (
renderSVG({ el, config })) - JSON-driven charts (radius, layers, nodes)
- Modes
free/alignwithkey - Basic
expandLevels(within a layer) - Offsets:
baseOffset,arcOffsetMode,defaultArcOffset - Auto color palette (category10)
- Simple interactions: hover, click callbacks
- Builds: ESM + IIFE (CDN/global)
- Publish to npm + GitHub Pages demo
- Tooltips (name, value, %)
- Breadcrumbs (full path of arc)
- Highlight by key
- Collapse/expand (
collapsed: true) - API
update(newConfig)(no animation yet) - Unit tests for invariants (angles, radial order)
- β Animated transitions (morphing arcs with key-based interpolation)
- β Zoom/drill-down (interactive navigation with breadcrumbs)
- β Radial labels (auto-positioned text on curved paths)
- β Color themes (14 palettes: qualitative, sequential, diverging)
- β Transition options (duration, easing, delay)
- Export:
exportSVG(),exportPNG()β planned for 0.4 - Accessibility (aria-labels, keyboard nav) β planned for 0.4
- Canvas renderer (performance for >5k arcs)
- Plugin system (legends, labels, effects)
- Layout-only mode (server / custom renderers)
- Partial angle support (gauge 180Β°/270Β°)
- Advanced animation (easing, delays)
- Large dataset tests (50k arcs in Canvas)
- Publish also on JSR (Deno/Bun)
- Online editor/playground
- Smart labels (collision detection)
- Skins/themes (flat, material, dark)
- Framework wrappers (React/Vue/Svelte)
- Full documentation site + gallery
MIT Β© Aqu1tain