Sunburst Advanced Node Data
A lightweight, framework-agnostic JavaScript library for building interactive sunburst charts using SVG. Sand.js is fully data-driven: describe your chart in JSON, and it handles both layout computation and rendering.
For detailed guides, API reference, and examples, visit the full documentation.
- Introduction
- Installation
- Quick Start
- Core Concepts
- Configuration Reference
- Features
- API Reference
- Build & Development
- CDN Usage
- Browser Support
- License
Sand.js is designed for developers who need to visualize hierarchical data as sunburst charts with minimal setup. Built with modern web standards, it offers:
- Zero dependencies: Lightweight and fast
- Framework agnostic: Works with vanilla JavaScript or any framework
- JSON-driven: Declarative configuration
- Interactive: Built-in tooltips, navigation, and event callbacks
- Customizable: Extensive theming and styling options
- TypeScript ready: Full type definitions included
npm install @akitain/sandjsFor Yarn users:
yarn add @akitain/sandjsCreate a basic sunburst chart in three steps:
- Add an SVG element to your HTML:
<svg id="chart"></svg>- Define your data configuration:
import { renderSVG } from '@akitain/sandjs';
const config = {
size: { radius: 200 },
layers: [
{
id: 'main',
radialUnits: [0, 2],
angleMode: 'free',
tree: [
{ name: 'Engineering', value: 45, key: 'eng' },
{
name: 'Design',
value: 30,
key: 'design',
children: [
{ name: 'UI', value: 15 },
{ name: 'UX', value: 15 }
]
},
{ name: 'Marketing', value: 25, key: 'marketing' }
]
}
]
};- Render the chart:
const chart = renderSVG({
el: '#chart',
config,
tooltip: true
});That's it! You now have a fully interactive sunburst chart.
For simple sunbursts, skip the full configuration and use the data + radius shorthand:
import { renderSVG } from '@akitain/sandjs';
const chart = renderSVG({
el: '#chart',
radius: 200,
data: [
{ name: 'Engineering', value: 45 },
{
name: 'Design',
value: 30,
children: [
{ name: 'UI', value: 15 },
{ name: 'UX', value: 15 }
]
},
{ name: 'Marketing', value: 25 }
],
tooltip: true
});The Simple API automatically:
- Creates a single layer with
angleMode: 'free' - Computes
radialUnitsfrom your tree depth - Sets the chart radius
For partial sunbursts (less than a full circle), add the angle option:
renderSVG({
el: '#chart',
radius: 200,
angle: Math.PI, // Half circle
data: [...]
});Use the full config object when you need multiple layers, alignment modes, or advanced layout options.
Understanding these fundamental concepts will help you build complex charts:
The complete chart containing one or more layers. Defined by overall size (radius and optional angle).
A logical grouping of rings with a shared dataset. Layers can operate independently (free mode) or align with other layers (align mode).
Properties:
id(string): Unique identifierradialUnits([number, number]): Inner and outer radial positionsangleMode('free' | 'align'): How angular space is distributedtree(Node | Node[]): Data structure for the layer
A unit of data representing a segment in your chart. Nodes can have children for hierarchical data.
Key properties:
name(string): Display labelvalue(number): Size of the segmentkey(string, optional): Stable identifier for animations and alignmentchildren(Node[], optional): Child nodes for hierarchical structure
A computed geometric entity created by the layout engine, ready for rendering with coordinates and metadata.
A radial band in the chart, automatically calculated based on nodes and their expandLevels property.
Nodes sharing the same key value, used for alignment across layers and coordinated interactions.
The root configuration object for your chart.
{
size: {
radius: number; // Final radius in pixels
angle?: number; // Total angle in radians (default: 2π)
},
layers: LayerConfig[] // Array of layer definitions
}{
id: string; // Unique layer identifier
radialUnits: [number, number]; // [inner, outer] radial positions
angleMode: 'free' | 'align'; // Angular distribution mode
alignWith?: string; // Reference layer ID (for 'align' mode)
padAngle?: number; // Gap between arcs (radians)
baseOffset?: number; // Global rotation offset (radians)
arcOffsetMode?: 'relative' | 'absolute'; // Offset calculation mode
defaultArcOffset?: number; // Default offset for all arcs
borderColor?: string; // Border color for arcs in this layer
borderWidth?: number; // Border width in pixels
labelColor?: string; // Label text color for this layer
showLabels?: boolean; // Show/hide labels for this layer
tree: TreeNodeInput | TreeNodeInput[]; // Data structure
}{
name: string; // Display name
value: number; // Arc size (auto-summed if children exist)
key?: string; // Stable identifier
expandLevels?: number; // Radial thickness in rings (default: 1)
offset?: number; // Local angular offset
color?: string; // Custom color (CSS format)
labelColor?: string; // Custom label text color
children?: TreeNodeInput[]; // Child nodes
tooltip?: string; // Custom tooltip content
collapsed?: boolean; // Hide children while preserving value
hidden?: boolean; // Hide node completely
}Sand.js includes 14 built-in color palettes across three theme types.
Best for categorical data with no inherent order:
import { renderSVG, QUALITATIVE_PALETTES } from '@akitain/sandjs';
renderSVG({
el: '#chart',
config,
colorTheme: {
type: 'qualitative',
palette: 'ocean', // 'default' | 'pastel' | 'vibrant' | 'earth' | 'ocean' | 'sunset'
assignBy: 'key' // Color assignment strategy
}
});Best for ordered data with progression from low to high:
colorTheme: {
type: 'sequential',
palette: 'blues', // 'blues' | 'greens' | 'purples' | 'oranges'
assignBy: 'depth'
}Best for data with a meaningful midpoint (e.g., positive/negative values):
colorTheme: {
type: 'diverging',
palette: 'redBlue', // 'redBlue' | 'orangePurple' | 'greenRed'
assignBy: 'value'
}- key: Consistent colors based on arc keys (default for qualitative)
- depth: Colors vary by hierarchical depth (default for sequential/diverging)
- index: Sequential assignment by arc position
- value: Colors mapped to normalized values
colorTheme: {
type: 'qualitative',
palette: ['#ff6b6b', '#4ecdc4', '#45b7d1', '#f7dc6f'],
assignBy: 'key'
}colorTheme: {
type: 'qualitative',
palette: 'default',
deriveKey: (arc) => arc.data.category // Use any arc property
}Note: Individual node.color values always override theme colors.
Enable interactive drill-down navigation with smooth transitions:
const chart = renderSVG({
el: '#chart',
config,
navigation: true, // Enable with defaults
transition: true // Enable smooth animations
});navigation: {
layers: ['main', 'details'], // Specify navigable layers
rootLabel: 'Home', // Breadcrumb root text
focusTransition: {
duration: 600, // Animation duration (ms)
easing: (t) => t * t // Custom easing function
},
onFocusChange: (focus) => {
if (focus) {
console.log('Focused:', focus.arc.data.name);
} else {
console.log('Reset to root');
}
}
}// Reset to root view
chart.resetNavigation?.();Display contextual information on hover.
renderSVG({
el: '#chart',
config,
tooltip: true // Enable default tooltips
});tooltip: {
formatter: (arc) => {
return `
<strong>${arc.data.name}</strong><br>
Value: ${arc.data.value}<br>
Percentage: ${arc.percentage.toFixed(1)}%
`;
},
container: '#tooltip-container' // Custom container selector
}tree: [
{
name: 'Engineering',
value: 45,
tooltip: 'Custom tooltip for Engineering department'
}
]Visualize the current navigation path.
renderSVG({
el: '#chart',
config,
breadcrumbs: true // Enable with defaults
});breadcrumbs: {
container: '#breadcrumb-trail', // Custom container
interactive: true, // Enable click navigation
separator: ' › ', // Custom separator
rootLabel: 'Overview', // Root element label
formatter: (arc) => arc.data.name.toUpperCase() // Custom formatting
}Highlight related arcs by key.
renderSVG({
el: '#chart',
config,
highlightByKey: true // Enable with defaults
});highlightByKey: {
className: 'highlighted', // Custom CSS class
pinOnClick: true, // Keep highlight on click
onPinChange: (key, pinned) => {
console.log(`${key} is ${pinned ? 'pinned' : 'unpinned'}`);
}
}Smooth animations when updating your chart.
renderSVG({
el: '#chart',
config,
transition: true // Enable with defaults
});transition: {
duration: 800, // Animation duration (ms)
easing: (t) => t * t, // Easing function
delay: 100 // Delay before animation starts (ms)
}const chart = renderSVG({ el: '#chart', config, transition: true });
// Later, update with smooth transition
chart.update({
config: newConfig,
transition: {
duration: 500
}
});Render text labels on arcs.
renderSVG({
el: '#chart',
config,
labels: true // Enable default labels
});labels: {
labelPadding: 8, // Spacing around text in pixels (default: 8)
labelFit: 'both', // 'both' | 'height' | 'width'
fontSize: { min: 8, max: 16 },
autoLabelColor: true // Contrast-aware text color
}labelFit controls which dimensions are checked when fitting labels:
'both'(default): Label must fit the arc's radial thickness and arc length'height': Only check radial thickness, use max font size based on ring height'width': Only check arc length, labels always fit along the arc path
labels: {
formatter: (arc) => {
if (arc.percentage > 10) {
return `${arc.data.name} (${arc.percentage.toFixed(0)}%)`;
}
return ''; // Hide labels for small arcs
}
}Note: Labels automatically hide on arcs that are too narrow to display text legibly.
Main function to create a sunburst chart.
Parameters:
{
el: string | SVGElement; // Target SVG element or selector
config: SunburstConfig; // Chart configuration
tooltip?: boolean | TooltipOptions; // Tooltip settings
breadcrumbs?: boolean | BreadcrumbOptions; // Breadcrumb settings
highlightByKey?: boolean | HighlightByKeyOptions; // Highlight settings
navigation?: boolean | NavigationOptions; // Navigation settings
transition?: boolean | TransitionOptions; // Transition settings
labels?: boolean | LabelOptions; // Label settings
colorTheme?: ColorThemeOptions; // Color theme
onArcEnter?: (payload) => void; // Hover enter callback
onArcMove?: (payload) => void; // Hover move callback
onArcLeave?: (payload) => void; // Hover leave callback
onArcClick?: (payload) => void; // Click callback
debug?: boolean; // Enable diagnostic logging
}Returns:
{
update: (updateInput) => void; // Update the chart
dispose: () => void; // Clean up resources
resetNavigation?: () => void; // Reset to root (if navigation enabled)
}Compute arc geometries from configuration (layout-only mode).
Parameters:
config(SunburstConfig): Chart configuration
Returns:
LayoutArc[]: Array of computed arcs with geometry and metadata
Generate a breadcrumb trail for an arc.
Parameters:
arc(LayoutArc): The arc to generate breadcrumbs for
Returns:
BreadcrumbTrailItem[]: Array of breadcrumb items
# Clone the repository
git clone https://github.com/aqu1tain/sandjs.git
cd sandjs
# Install dependencies
npm install
# Run tests
npm test
# Build the library
npm run build
# Run tests and build
npm run verifynpm run devOpens a development server at http://localhost:4173 with live examples.
sandjs/
├── src/
│ ├── index.ts # Public API exports
│ ├── layout/ # Layout computation
│ ├── render/ # SVG rendering
│ └── types/ # TypeScript definitions
├── demo/ # Interactive examples
├── dist/ # Build output (generated)
└── tests/ # Test suite
dist/sandjs.mjs: ES Module (default)dist/sandjs.iife.min.js: Minified IIFE for CDN usagedist/index.d.ts: TypeScript type definitions
For quick prototyping or non-bundled environments:
<svg id="chart"></svg>
<script src="https://unpkg.com/@akitain/sandjs@1.0.0/dist/sandjs.iife.min.js"></script>
<script>
const { renderSVG } = window.SandJS;
renderSVG({
el: '#chart',
config: {
size: { radius: 200 },
layers: [
{
id: 'main',
radialUnits: [0, 2],
angleMode: 'free',
tree: [
{ name: 'Category A', value: 40 },
{ name: 'Category B', value: 60 }
]
}
]
},
tooltip: true
});
</script>Sand.js targets ESNext and supports modern browsers:
| Browser | Minimum Version |
|---|---|
| Chrome | 80+ |
| Firefox | 74+ |
| Safari | 13.1+ |
| Edge | 80+ |
| iOS Safari | 13.4+ |
| Chrome Android | 80+ |
Not supported: Internet Explorer
For older browsers, transpile the bundle with Babel. See the Browser Support Guide for details.
MIT © Aqu1tain