📖 Full documentation: docs here
A declarative, builder-based library for creating animated SVG network visualizations and algorithm demos.
VizCraft is designed to make creating beautiful, animated node-link diagrams and complex visualizations intuitive and powerful. Whether you are building an educational tool, explaining an algorithm, or just need a great looking graph, VizCraft provides the primitives you need.
- Fluent Builder API: Define your visualization scene using a readable, chainable API.
- Grid System: Built-in 2D grid system for easy, structured layout of nodes.
- Two Animation Systems: Lightweight registry/CSS animations (e.g. edge
flow) and data-only timeline animations (AnimationSpec). - Framework Agnostic: The core logic is pure TypeScript and can be used with any framework or Vanilla JS.
- Custom Overlays: Create complex, custom UI elements that float on top of your visualization.
npm install vizcraft
# or
pnpm add vizcraft
# or
yarn add vizcraftYou can use the core library directly to generate SVG content or mount to a DOM element.
import { viz } from 'vizcraft';
const builder = viz().view(800, 600);
builder
.node('a')
.at(100, 100)
.circle(15)
.label('A')
.node('b')
.at(400, 100)
.circle(15)
.label('B')
.edge('a', 'b')
.arrow();
const container = document.getElementById('viz-basic');
if (container) builder.mount(container);For more examples and best practices, see docs here.
Full documentation site: docs here
Docs topics (same as the sidebar):
Run the docs locally:
pnpm install
pnpm -C packages/docs startThe heart of VizCraft is the VizBuilder. It allows you to construct a VizScene which acts as the blueprint for your visualization.
b.view(width, height) // Set the coordinate space
.grid(cols, rows) // (Optional) Define layout grid
.node(id) // Start defining a node
.edge(from, to) // Start defining an edgeCommon lifecycle:
builder.build()creates a serializableVizScene.builder.mount(container)renders into an SVG inside your container.builder.play()plays any compiled timeline specs.builder.patchRuntime(container)applies runtime-only updates (useful for per-frame updates without remounting).
Nodes are the primary entities in your graph. They can have shapes, labels, and styles.
b.node('n1')
.at(x, y) // Absolute position
// OR
.cell(col, row) // Grid position
.circle(radius) // Shape definition
.label('Text', { dy: 5 }) // Label with offset
.class('css-class') // Custom CSS class
.data({ ... }) // Attach custom dataEdges connect nodes and can be styled, directed, or animated.
b.edge('n1', 'n2')
.arrow() // Add an arrowhead
.straight() // (Default) Straight line
.label('Connection')
.animate('flow') // Add animationSee the full Animations guide docs here.
VizCraft supports two complementary animation approaches:
- Registry/CSS animations (simple, reusable effects)
Attach an animation by name to a node/edge. The default core registry includes:
flow(edge)
import { viz } from 'vizcraft';
const b = viz().view(520, 160);
b.node('a').at(70, 80).circle(18).label('A')
.node('b').at(450, 80).rect(70, 44, 10).label('B')
.edge('a', 'b')
.arrow()
.animate('flow', { duration: '1s' })
.done();- Data-only timeline animations (
AnimationSpec) (sequenced tweens)
- Author with
builder.animate((aBuilder) => ...). - VizCraft stores compiled specs on the scene as
scene.animationSpecs. - Play them with
builder.play().
import { viz } from 'vizcraft';
const b = viz().view(520, 240);
b.node('a').at(120, 120).circle(20).label('A')
.node('b').at(400, 120).rect(70, 44, 10).label('B')
.edge('a', 'b').arrow()
.done();
// Create + store a data-only AnimationSpec
b.animate((aBuilder) =>
aBuilder
.node('a').to({ x: 200, opacity: 0.35 }, { duration: 600 })
.node('b').to({ x: 440, y: 170 }, { duration: 700 })
.edge('a->b').to({ strokeDashoffset: -120 }, { duration: 900 })
);
const container = document.getElementById('viz-basic');
if (container) {
b.mount(container);
b.play(); // Warns + no-ops if mount wasn't called
}Edges can have any id (you can pass it as the optional third argument to builder.edge(from, to, id)):
const b = viz().view(520, 240);
b.node('a').at(120, 120).circle(20).label('A')
.node('b').at(400, 120).rect(70, 44, 10).label('B')
.edge('a', 'b', 'e1').arrow()
.done();
b.animate((aBuilder) =>
aBuilder.edge('a', 'b', 'e1').to({ strokeDashoffset: -120 }, { duration: 900 })
);When you don’t provide an explicit edge id, the default convention is "from->to".
You can animate properties that aren’t in the core set by extending the adapter for a specific spec:
b.animate((aBuilder) =>
aBuilder
.extendAdapter((adapter) => {
// adapter may support register(kind, prop, { get, set })
adapter.register?.('node', 'r', {
get: (target) => adapter.get(target, 'r'),
set: (target, v) => adapter.set(target, 'r', v),
});
})
.node('a')
.to({ r: 42 }, { duration: 500 })
);See the docs for the recommended get/set implementations for SVG attributes.
builder.play() returns a controller with pause(), play() (resume), and stop().
const controller = b.play();
controller?.pause();
controller?.play();
controller?.stop();Out of the box, timeline playback supports these numeric properties:
- Node:
x,y,opacity,scale,rotation - Edge:
opacity,strokeDashoffset
VizCraft generates standard SVG elements with predictable classes, making it easy to style with CSS.
/* Custom node style */
.viz-node-shape {
fill: #fff;
stroke: #333;
stroke-width: 2px;
}
/* Specific node class */
.my-node .viz-node-shape {
fill: #ff6b6b;
}
/* Edge styling */
.viz-edge {
stroke: #ccc;
stroke-width: 2;
}For deeper guides and API references, see docs here.
- Interactivity: attach
onClickhandlers to nodes/edges. - Overlays: add non-node/edge visuals using
.overlay(id, params, key?). - React integration: see the workspace package packages/react-vizcraft (monorepo).
Contributions are welcome! This is a monorepo managed with Turbo.
- Clone the repo
- Install dependencies:
pnpm install - Run dev server:
pnpm dev
MIT License