@opendata-ai/openchart-vanilla
v8.5.2
Published
Vanilla JS renderer for openchart: SVG charts, HTML tables, force-directed graphs
Downloads
5,385
Maintainers
Readme
@opendata-ai/openchart-vanilla
DOM rendering adapter for OpenChart. Creates SVG charts, HTML tables, and canvas-based network graphs from compiled specs. Framework-agnostic.
Install
npm install @opendata-ai/openchart-vanillaYou typically don't install this directly unless you're working without a framework. The framework packages (openchart-react, openchart-vue, openchart-svelte) include it as a dependency and wrap it with lifecycle management.
Charts
import { createChart } from '@opendata-ai/openchart-vanilla';
const chart = createChart(container, spec, {
darkMode: 'auto',
responsive: true,
onMarkClick: (event) => console.log(event.datum),
onMarkHover: (event) => showTooltip(event),
onMarkLeave: () => hideTooltip(),
onLegendToggle: (series, visible) => console.log(series, visible),
onAnnotationClick: (annotation, event) => console.log(annotation),
});
chart.update(newSpec); // Re-render with new spec
chart.resize(); // Manual resize trigger
chart.export('svg'); // Export as SVG string
await chart.export('png'); // Export as PNG Blob
chart.export('csv'); // Export data as CSV
chart.destroy(); // Clean up DOM and observersResponsive mode (default) uses a ResizeObserver on the container. Charts recompile at new dimensions automatically.
Tables
import { createTable } from '@opendata-ai/openchart-vanilla';
const table = createTable(container, tableSpec, {
responsive: true,
onRowClick: (row) => console.log(row),
onStateChange: (state) => console.log(state.sort, state.search, state.page),
});
table.update(newSpec);
table.getState(); // { sort, search, page }
table.setState({ page: 2 }); // Programmatic state control
table.export('csv'); // CSV export (respects sort/search, ignores pagination)
table.destroy();Graphs
import { createGraph } from '@opendata-ai/openchart-vanilla';
const graph = createGraph(container, graphSpec, {
darkMode: 'auto',
responsive: true,
onNodeClick: (node) => console.log(node),
onNodeDoubleClick: (node) => console.log(node),
onSelectionChange: (nodeIds) => console.log(nodeIds),
});
graph.search('query'); // Highlight matching nodes
graph.clearSearch();
graph.zoomToFit(); // Fit all nodes in viewport
graph.zoomToNode('node-id'); // Center on a specific node
graph.selectNode('node-id'); // Programmatic selection
graph.getSelectedNodes(); // Get selected node IDs
graph.update(newSpec);
graph.destroy();Graphs render on canvas with a force simulation running in a web worker. Nodes support click, drag, and double-click. The simulation auto-fits nodes once it settles.
3D graphs
dimensions: 3 on a graph spec renders with WebGL instead of canvas. The 3D
renderer ships on its own subpath so three.js never lands in the default bundle,
and its libraries are optional peers you install yourself:
npm install three 3d-force-graph three-spritetextThe subpath registers the renderer as an import side effect, so it has to be imported before the graph mounts:
import '@opendata-ai/openchart-vanilla/graph-3d';
import { createGraph } from '@opendata-ai/openchart-vanilla';
createGraph(container, { ...graphSpec, dimensions: 3 });The subpath import is required only when compilation actually resolves to 3D.
A spec above the 2000-node gate warns and falls back to 2D, so it renders
without the import; a dimensions: 3 spec that stays 3D throws without it.
Framework hosts import their own package's mirror of this subpath rather than
reaching past it into vanilla: @opendata-ai/openchart-react/graph-3d,
@opendata-ai/openchart-vue/graph-3d, @opendata-ai/openchart-svelte/graph-3d.
Notes:
- SSR:
3d-force-graphtoucheswindowat import time. Import the subpath client-side only (insideuseEffect, a dynamicimport(), or a browser-only module) and render your own placeholder until it resolves. - One copy of three: run
npm ls three(orbun pm ls three) and confirm a single copy. Two copies fail at runtime withCannot read properties of undefined (reading 'VERTEX')(vasturiano/react-force-graph#595).
Differences from 2D:
- Labels use a fixed budget re-ranked by camera distance, so distant labels drop out instead of being decluttered by priority.
- The force simulation runs on the main thread. Above 2000 nodes the spec warns and renders in 2D.
- Keyboard navigation, SVG export,
interaction.cursorRepulsion, andinteraction.springyDragare unsupported. Each warns and is ignored. (layout.typeofradialorhierarchicalis rejected by spec validation in both dimensions, so it never reaches either renderer.) getCamera()returns a pose, not a zoom transform:x/yare the look-at point in world units, alongsidepositionandtarget. In 2D they are the zoom transform's translate in pixels.onCameraChangecarries the same payload, so persisted camera state has to be keyed on the dimension it came from. Both are typedGraphCamera, whoseposition/targetare optional because only 3D sets them;flyToaccepts them back, and 2D ignores them.- A structural
update()reheats the whole layout, so settled nodes drift. 2D applies a local impulse instead. nodeOverrides[*].strokeandstrokeWidthare ignored (no ring).- A dimension change is a remount, not an
update(). The framework wrappers do this for you;update()with a changeddimensionswarns and no-ops.
Export utilities
Standalone export functions if you need them outside of an instance:
import { exportSVG, exportPNG, exportCSV } from '@opendata-ai/openchart-vanilla';Other exports
observeResize()- ResizeObserver wrapper for container trackingattachKeyboardNav()- Keyboard navigation for chart markscreateTooltipManager()- Tooltip lifecycle managementrenderChartSVG()- Low-level SVG rendering from a ChartLayoutrenderTable()- Low-level table DOM rendering from a TableLayoutrenderCell()and cell-type renderers (renderBarCell,renderSparklineCell,renderHeatmapCell, etc.) - Individual table cell renderers
Related docs
- Getting started for a hands-on tutorial
- Integration guide for lifecycle management and events
- Spec reference for field-by-field type details
