Embed interactive graph views or run the pipeline offline.
Examples cover declarative HTML, programmatic embedding, streaming with the incremental API, and side-by-side renderer/compute comparisons. The skill bundle is a Claude Code skill — drop it into your skills directory to teach Claude how to use the BlitZoom API.
import { createBlitZoomView } from './blitzoom.js'; const view = createBlitZoomView( document.getElementById('canvas'), edgesText, // SNAP .edges file content (string) nodesText, // SNAP .nodes file content or null { initialLevel: 3, heatmapMode: 'density', showLegend: true } );
That parses the data, runs the full MinHash + projection + blend pipeline, and renders an interactive canvas with pan, zoom, click-to-select, and scroll-to-zoom-level. Here it is with the Epstein dataset:
BlitZoom uses a simple tab-delimited text format based on SNAP. A dataset consists of an .edges file (required) and an optional .nodes file.
Tab-delimited, one edge per line. Lines starting with # are comments. Each line is either From<tab>To (undirected) or From<tab>To<tab>EdgeType (typed).
# Social network edges # From To EdgeType alice bob friend alice carol colleague bob dave friend carol dave colleague eve alice manager
Tab-delimited, one node per line. The first comment line defines column names: # NodeId<tab>Label<tab>Group followed by any number of extra columns. Extra columns become property groups used for MinHash similarity and layout positioning.
# NodeId Label Group Role Experience
alice Alice engineering backend 8
bob Bob engineering frontend 5
carol Carol design ux 3
dave Dave design visual 7
eve Eve management director 12
Numeric columns are auto-detected when >=80% of values parse as numbers. They receive 3-level tokenization (coarse, medium, fine bins), so nodes with nearby numeric values cluster together while distant values separate.
Empty fields (missing or blank values) produce zero tokens, which results in a neutral [0,0] projection for that property group. This avoids false clustering: nodes with unknown values float to the center rather than being forced into a cluster.
If no .nodes file is provided, the graph is edge-only. Layout relies entirely on topology (set smoothAlpha to 1.0 for best results).
The code above is the full embed. All you need is a <canvas> element and the SNAP data as strings. The canvas auto-sizes via ResizeObserver.
// Fetch data, create view const [edges, nodes] = await Promise.all([ fetch('data/epstein.edges').then(r => r.text()), fetch('data/epstein.nodes').then(r => r.text()), ]); const view = createBlitZoomView(canvas, edges, nodes, { initialLevel: 3, edgeMode: 'lines', heatmapMode: 'density', showLegend: true, showResetBtn: true, strengths: { group: 8, edgetype: 4 }, labelProps: ['label'], });
The returned view is a BlitZoomCanvas instance with a full API for programmatic control.
<bz-graph> web component — no JavaScript needed for basic use:
<style>bz-graph:not(:defined) { visibility: hidden; }</style>
<bz-graph edges="data/karate.edges" nodes="data/karate.nodes"
level="3" heatmap="density" legend>
</bz-graph>
The :not(:defined) rule prevents a flash of raw text before the component loads.
If your graph is already in memory as JavaScript objects, skip the SNAP parsing entirely with createBlitZoomFromGraph. Pass nodes as plain objects with id, group, label, and any extra properties. Edges are {src, dst} pairs.
import { createBlitZoomFromGraph } from './blitzoom.js'; const nodes = [ { id: 'alice', group: 'engineer', label: 'Alice', lang: 'Go', exp: 8 }, { id: 'bob', group: 'engineer', label: 'Bob', lang: 'Go', exp: 5 }, { id: 'carol', group: 'designer', label: 'Carol', lang: 'JS', exp: 3 }, { id: 'dave', group: 'designer', label: 'Dave', lang: 'JS', exp: 7 }, { id: 'eve', group: 'manager', label: 'Eve', lang: 'Python', exp: 12 }, { id: 'frank', group: 'engineer', label: 'Frank', lang: 'Rust', exp: 2 }, { id: 'grace', group: 'designer', label: 'Grace', lang: 'JS', exp: 6 }, { id: 'hank', group: 'manager', label: 'Hank', lang: 'Python', exp: 9 }, ]; const edges = [ { src: 'alice', dst: 'bob' }, { src: 'alice', dst: 'carol' }, { src: 'bob', dst: 'frank' }, { src: 'carol', dst: 'dave' }, { src: 'carol', dst: 'grace' }, { src: 'dave', dst: 'grace' }, { src: 'eve', dst: 'alice' }, { src: 'eve', dst: 'hank' }, { src: 'hank', dst: 'bob' }, { src: 'frank', dst: 'eve' }, ]; const view = createBlitZoomFromGraph(canvas, nodes, edges, { initialLevel: 3, showLegend: true, showResetBtn: true, strengths: { group: 5, lang: 8 }, });
Only id is required. group defaults to "unknown" and label defaults to the node's id. Any other property becomes an extra property group. Numeric values (like exp) get multi-resolution tokenization automatically.
Add, remove, and update nodes at runtime. The incremental attribute on <bz-graph> is a bundled preset that picks quant='norm' (existing nodes never shift), disables periodic full rebuild, and suppresses auto-tune-on-load (which would shift positions). Use it for any graph that will receive addNodes/removeNodes/updateNodes calls.
<bz-graph id="g" edges="data/karate.edges" nodes="data/karate.nodes" incremental> </bz-graph> <script type="module"> const g = document.getElementById('g'); await g.ready; // Add nodes + edges await g.addNodes( [{id: 'new1', group: 'analyst'}, {id: 'new2', group: 'admin'}], [{src: 'new1', dst: 'new2'}] ); // Update properties (only changed nodes are re-projected) await g.updateNodes([{id: 'new1', group: 'manager'}]); // Remove by ID (edges cleaned up automatically) await g.removeNodes(['new2']); </script>
The await g.ready Promise resolves once the build completes — modeled on document.fonts.ready and navigator.serviceWorker.ready. Use it from any async function; it works regardless of when you read it (before or after the build finishes). The legacy addEventListener('ready', ...) shape still works.
All three methods animate by default (existing items lerp to new positions, new items fade in). Pass { animate: false } to snap instantly — required when streaming many batches in a loop. A periodic full rebuild triggers automatically after rebuildThreshold × originalN cumulative inserts to refresh stale numeric bins and topology tokens; the incremental preset disables this entirely (sets the threshold to Infinity) since norm projections are deterministic per-node and rebuilds are unnecessary.
Each preset default can still be overridden by passing the matching attribute alongside incremental. For example, incremental rebuild-threshold="0.5" opts back into periodic rebuilds at 50% growth while keeping the rest of the preset.
You can declare <bz-graph> with no edges URL and no inline data — the canvas builds empty and waits for addNodes to populate it. Useful when the graph is generated entirely at runtime (streaming, computed datasets, etc.):
<bz-graph id="g" incremental legend strengths="category:5,block:8"></bz-graph> <script type="module"> const g = document.getElementById('g'); await g.ready; await g.addNodes(allMyNodes, [], { animate: false }); </script>
The first addNodes call into an empty graph runs a schema bootstrap: it derives the property-group names (category, block, etc.) from the first batch's node fields, builds projection matrices for them, computes numeric bins, and applies any strengths you set via attributes. The schema is then locked in.
Limitation: property keys not present in the first batch are silently dropped on subsequent batches — same as the existing static-build behavior. Include at least one representative node with every field you'll need in the first batch. For datasets where every node has the same fields (the typical case), this is invisible.
Why norm mode? There's a fundamental tradeoff: you can't have data-independent scale, optimal grid utilization, and zero displacement on insertion. Gaussian adapts its scale to the data (best grids, but every node shifts when new data arrives). Norm uses a fixed scale derived from the projection matrices (stable positions, but 5-30% worse grid utilization on datasets with low property diversity). Use norm for incremental/streaming use cases; use gaussian for static datasets where you load once and explore.
Each mutation dispatches an event on the canvas:
const el = document.querySelector('bz-graph'); el.addEventListener('nodesadded', e => console.log(`+${e.detail.count}, total: ${e.detail.total}`)); el.addEventListener('nodesremoved', e => console.log(`-${e.detail.count}, total: ${e.detail.total}`)); el.addEventListener('nodesupdated', e => console.log(`~${e.detail.count}`));
The same methods are available directly on BlitZoomCanvas for lower-level control:
import { createBlitZoomFromGraph } from './blitzoom.js'; const view = createBlitZoomFromGraph(canvas, [{id: 'a', group: 'x'}], [], { incremental: true } ); await view.addNodes([{id: 'b', group: 'y'}], [{src: 'a', dst: 'b'}]); await view.updateNodes([{id: 'b', group: 'z'}]); await view.removeNodes(['b']);
The incremental: true JS option is the same preset as the <bz-graph incremental> attribute. Pass it to either factory (createBlitZoomView, createBlitZoomFromGraph) when the view will receive runtime mutations. Individual settings (quantMode, rebuildThreshold, autoTune) can still be passed alongside it to override the preset per-field.
All options are optional. Sensible defaults are applied.
| Option | Type | Default | Description |
|---|---|---|---|
initialLevel | number | 3 | Starting zoom level index (0=L1, 13=L14, 14=RAW) |
edgeMode | string | 'curves' | 'curves', 'lines', or 'none' |
heatmapMode | string | 'off' | 'off', 'splat', or 'density' |
quantMode | string | 'gaussian' | 'gaussian' (density-preserving), 'rank' (uniform), or 'norm' (order-independent, stable for incremental updates). Low-level — most users want incremental instead. |
incremental | boolean | false | Bundled preset for runtime mutation/streaming. Sets quantMode='norm', rebuildThreshold=Infinity, autoTune=false. Each can still be overridden by passing it explicitly alongside. |
rebuildThreshold | number | 0.10 | Trigger periodic full rebuild after this fraction of original-N inserts via addNodes. Set to Infinity to disable. incremental defaults this to Infinity. |
sizeBy | string | 'edges' | Node size: 'edges' (degree) or 'members' (count) |
sizeLog | boolean | false | Log scale for node size |
smoothAlpha | number | 0 | Topology blend weight, 0 (property only) to 1 (topology only) |
strengths | object | {group:3, rest:1} | Override property group strengths |
labelProps | array | [] | Property names to show as node labels |
showLegend | boolean | false | Draw color legend in bottom-right corner |
showResetBtn | boolean | false | Draw reset button in top-right corner |
clickDelay | number | 0 | Milliseconds to delay single-click for double-click disambiguation |
keyboardTarget | EventTarget | canvas | Element to bind keyboard listener to (e.g. window) |
webgl | boolean | false | Use WebGL2 instanced rendering for geometry (details) |
useGPU | boolean | false | Force WebGPU compute for projection and blend (details) |
autoGPU | boolean | true | Automatically enable WebGPU when dataset is large enough to benefit (N×G > 2000). Set to false to disable. |
colorScheme | number | 0 | Color scheme index: 0=vivid, 1=viridis, 2=plasma, 3=inferno, 4=thermal, 5=grayscale, 6=diverging, 7=greens, 8=reds |
colorBy | string | null | Override which property group controls node colors. null = auto (highest-strength group). Set to a group name to pin coloring. |
lightMode | boolean | false | Light theme for labels, legend, and grid. Press C to cycle color schemes, set view.lightMode = true at runtime. |
Color scheme and theme can also be changed at runtime:
import { SCHEME_VIRIDIS, SCHEME_DIVERGING } from './blitzoom-colors.js'; // Light mode with viridis colors const view = createBlitZoomView(canvas, edges, nodes, { colorScheme: SCHEME_VIRIDIS, lightMode: true, }); // Change at runtime view.colorScheme = SCHEME_DIVERGING; // switch to diverging view.lightMode = false; // back to dark view.cycleColorScheme(); // next scheme console.log(view.colorSchemeName); // "thermal"
Available constants: SCHEME_VIVID (0, default), SCHEME_VIRIDIS, SCHEME_PLASMA, SCHEME_INFERNO, SCHEME_THERMAL, SCHEME_GRAYSCALE, SCHEME_DIVERGING, SCHEME_GREENS, SCHEME_REDS.
React to user interaction without touching the DOM:
const view = createBlitZoomView(canvas, edges, nodes, { onSelect(hit) { // hit = { type: 'node'|'supernode', item: nodeObj|supernodeObj } console.log('Selected:', hit.item.id || hit.item.bid); }, onDeselect() { // Called when selection is cleared (click on empty space, Escape) console.log('Deselected'); }, onHover(hit) { // Called on every hover change (null when leaving a node) sidebar.textContent = hit ? (hit.item.label || hit.item.cachedLabel) : ''; }, onLevelChange(newIdx, prevIdx) { // Called after any zoom-level change (auto or manual) levelIndicator.textContent = 'L' + newIdx; }, onRender() { // Called after each frame render. Useful for syncing external UI. }, onKeydown(e) { // Called before default key handling. Return true to suppress. if (e.key === 'x') { myCustomAction(); return true; } return false; }, });
Property strengths control which attributes dominate the layout. The blend is a weighted sum of fixed 2D anchors that were computed once at load time, so a strength change only re-runs the sum and the quantization step — neither the MinHash signatures nor the per-group projections are touched.
// Change strengths after creation view.setStrengths({ kind: 8, group: 1 }); // cluster by file type view.setStrengths({ kind: 1, group: 8 }); // cluster by module // Adjust topology smoothing view.setAlpha(0.5); // pull connected nodes together // Batch display options view.setOptions({ heatmapMode: 'density', edgeMode: 'lines', sizeBy: 'members', labelProps: ['file', 'kind'], });
When loading an unfamiliar dataset, the default strengths may produce a collapsed or uniform layout. The auto-tune optimizer searches the strength/alpha/quant parameter space to find a configuration with good visual structure, then applies it automatically.
Synth Packages dataset: default strengths (left) vs auto-tuned (right).
Pass autoTune in the options. The view renders immediately with defaults; the optimizer runs in the background and re-renders when done. A progress overlay appears on the canvas during optimization.
import { createBlitZoomView } from './blitzoom.js'; const view = createBlitZoomView(canvas, edgesText, nodesText, { // Auto-tune all parameters autoTune: { weights: true, alpha: true, quant: true }, }); // view is returned synchronously — usable immediately // auto-tune runs async and re-renders when done
Control which parameters are tuned:
// Only tune strengths, keep alpha and quant as specified const view = createBlitZoomView(canvas, edges, nodes, { smoothAlpha: 0.5, quantMode: 'gaussian', autoTune: { strengths: true, alpha: false, quant: false }, });
Explicit strengths, smoothAlpha, and quantMode in the options take precedence over tuned values.
Call autoTuneStrengths directly for full control:
import { autoTuneStrengths } from './blitzoom.js'; const result = await autoTuneStrengths( view.nodes, view.groupNames, view.adjList, view.nodeIndexFull, { weights: true, alpha: true, quant: true, onProgress(info) { console.log(`${info.phase} ${info.step}/${info.total}`); }, } ); console.log(result); // { strengths: {group:8, ...}, alpha: 0.5, quantMode: 'gaussian', // labelProps: ['group'], score: 0.42, blends: 87, quants: 174, timeMs: 320 } // Apply to an existing view for (const g of view.groupNames) view.propStrengths[g] = result.strengths[g]; view.smoothAlpha = result.alpha; view.quantMode = result.quantMode; view.setStrengths(result.strengths); // triggers re-blend and render
The optimizer maximizes spread × clumpiness at zoom level L5 (32×32 grid). Spread (fraction of cells occupied) penalizes collapse. Clumpiness (coefficient of variation of per-cell node counts) penalizes uniform scatter and rewards clusters with gaps between them. The search runs in two phases:
Each evaluation runs a full blend + quantize cycle (~1-3ms per evaluation depending on graph size). Total time is typically 200-800ms for graphs under 5K nodes.
Export the current graph view as a standalone SVG. In the viewer, press S to download. Programmatically:
import { exportSVG } from './blitzoom.js'; const svgString = exportSVG(view); // Returns a complete <svg> string with background, grid, edges, // density heatmap contours, circles, labels, and legend
The export reads the same view state (nodes, edges, zoom, pan, level, colors) as the canvas renderer, producing a vector image suitable for print or embedding in documents.
Use createSVGView to build a view from pipeline data without any browser APIs — works in Deno, Node.js, or Web Workers:
import { createSVGView, exportSVG } from './blitzoom.js'; // After running the pipeline and blending (see Offline Pipeline below) const view = createSVGView(nodes, result.edges, { width: 800, height: 600, colorBy: 'group', heatmapMode: 'density', showLegend: 1, }); const svg = exportSVG(view); Deno.writeTextFileSync('graph.svg', svg);
Run the full pipeline without a canvas, in Node.js or Deno. Useful for batch processing, precomputation, or analysis scripts.
import { runPipeline } from './blitzoom-pipeline.js'; import { unifiedBlend, normalizeAndQuantize, buildLevel } from './blitzoom-algo.js'; // Parse + tokenize + project (no DOM needed) const result = runPipeline(edgesText, nodesText); console.log(result.nodeArray.length, 'nodes'); console.log(result.groupNames); // ['group', 'label', 'structure', ...] // Hydrate nodes with projections const G = result.groupNames.length; const nodes = result.nodeArray.map((n, i) => { const projections = {}; for (let g = 0; g < G; g++) { const off = (i * G + g) * 2; projections[result.groupNames[g]] = [result.projBuf[off], result.projBuf[off + 1]]; } return { ...n, projections, px: 0, py: 0, gx: 0, gy: 0, x: 0, y: 0 }; }); // Build adjacency const nodeIndex = Object.fromEntries(nodes.map(n => [n.id, n])); const adjList = Object.fromEntries(nodes.map(n => [n.id, []])); for (const e of result.edges) { if (adjList[e.src] && adjList[e.dst]) { adjList[e.src].push(e.dst); adjList[e.dst].push(e.src); } } // Blend with custom strengths const strengths = { group: 5, edgetype: 8 }; for (const g of result.groupNames) strengths[g] ??= 1; unifiedBlend(nodes, result.groupNames, strengths, 0, adjList, nodeIndex, 5, 'rank'); // Build a zoom level and inspect supernodes const level = buildLevel(4, nodes, result.edges, nodeIndex, n => n.group, n => n.label || n.id, () => '#888'); console.log(level.supernodes.length, 'supernodes at L4'); console.log(level.snEdges.length, 'super-edges'); // Find the largest supernode const biggest = level.supernodes.sort((a, b) => b.members.length - a.members.length)[0]; console.log(biggest.cachedLabel, biggest.members.length, 'members');
deno run --allow-read), Node.js 18+ (with --experimental-vm-modules), and browsers.
BlitZoom scales well because the pipeline is O(n) in node count (MinHash + projection) and rendering uses hierarchical aggregation (only visible supernodes are drawn). Here are typical timings on a modern desktop:
| Scale | Pipeline | Render | Notes |
|---|---|---|---|
| ~1K nodes | <10ms | <1ms | Instant, any mode |
| ~5K nodes | <50ms | <5ms | Smooth at 60fps |
| ~50K nodes | <500ms | <20ms | Consider disabling heatmap |
| ~367K nodes (Amazon) | ~4s (GPU) | ~5ms (WebGL) | GPU pipeline + WebGL rendering recommended |
heatmapMode: 'off'. Density heatmap is the most expensive render pass.edgeMode: 'none' to skip edge rendering entirely. Edge drawing is O(visible edges) and can dominate frame time at high zoom levels.webgl: true to offload circle, edge, and heatmap geometry to the GPU via instanced rendering.useGPU: true to run projection and blend on the GPU via WebGPU compute shaders. This is the biggest win for pipeline time on very large graphs.| Symptom | Cause | Fix |
|---|---|---|
| All nodes collapse to a single point | All property values are identical across nodes (zero variance) | Add distinguishing properties, increase smoothAlpha to use topology, or run auto-tune |
| Uniform random scatter, no clusters | No .nodes file (edge-only dataset) |
Set smoothAlpha: 1.0 to lay out purely by topology. Provide a .nodes file if properties are available |
| Slow pipeline on very large graphs | CPU-bound MinHash + projection at scale | Use useGPU: true for WebGPU compute. Disable heatmap with heatmapMode: 'off' |
| Rank quantization falls back to CPU | Rank sort requires float64 precision not available in GPU compute | Use quantMode: 'gaussian' (the default) for GPU-compatible quantization |
| Slow rendering at high zoom | Many visible nodes/edges at detailed zoom levels | Enable webgl: true, use edgeMode: 'none' or 'lines' instead of 'curves' |
Use MinHash directly to estimate Jaccard similarity between token sets:
import { computeMinHash, jaccardEstimate } from './blitzoom-algo.js'; const sigA = computeMinHash(['group:Person', 'label:jeffrey', 'label:epstein']); const sigB = computeMinHash(['group:Person', 'label:ghislaine', 'label:maxwell']); const sigC = computeMinHash(['group:Organization', 'label:us', 'label:government']); console.log(jaccardEstimate(sigA, sigB)); // ~0.2 (share group:Person) console.log(jaccardEstimate(sigA, sigC)); // ~0.0 (no shared tokens) console.log(jaccardEstimate(sigA, sigA)); // 1.0 (identical)
For on-demand signatures of existing nodes (using the same tokenization as the pipeline):
import { computeNodeSig } from './blitzoom-pipeline.js'; const sig = computeNodeSig(node); // Float64Array[128]
The zoom hierarchy is the core trick: 14 aggregation levels from two stored uint16 coordinates per node, derived via bit shifts. You can access any level programmatically.
import { cellIdAtLevel, buildLevel } from './blitzoom-algo.js'; // Which cell is a node in at level 4? (16x16 grid) const bid = cellIdAtLevel(node.gx, node.gy, 4); const cx = bid >> 4; // cell column const cy = bid & 0b1111; // cell row // Build level 6 (64x64 grid) const lv = buildLevel(6, nodes, edges, nodeIndex, n => n.group, n => n.label, val => colors[val] || '#888'); // Each supernode: { bid, members[], ax, ay, cachedColor, cachedLabel, ... } for (const sn of lv.supernodes) { console.log(`Cell (${sn.cx},${sn.cy}): ${sn.members.length} nodes, label: ${sn.cachedLabel}`); } // Super-edges: { a: bidA, b: bidB, weight: edgeCount } for (const e of lv.snEdges) { console.log(`${e.a} ↔ ${e.b}: ${e.weight} edges`); }
Pass webgl: true to render geometry (circles, edges, heatmaps, grid) via WebGL2 instanced draw calls. Text (labels, counts, legend) stays on a Canvas 2D overlay. The two canvases are stacked automatically.
Epstein dataset: Canvas 2D (left) vs WebGL2 (right). Same data, same layout, same interaction.
import { createBlitZoomView } from './blitzoom.js'; // Canvas 2D (default) const view2d = createBlitZoomView(canvas1, edges, nodes, { edgeMode: 'curves', showLegend: true, }); // WebGL2 const viewGL = createBlitZoomView(canvas2, edges, nodes, { webgl: true, edgeMode: 'curves', showLegend: true, }); // Toggle at runtime viewGL.useWebGL = false; // switch back to Canvas 2D viewGL.useWebGL = true; // switch to WebGL2
Embed a graph with a single HTML element — no JavaScript needed for basic use. Include bz-graph.js in your page and use the <bz-graph> tag. Always add the :not(:defined) CSS rule to prevent a flash of raw text content before the component loads — especially important for inline JSON/SNAP data.
<script type="module" src="dist/blitzoom.bundle.js"></script>
<style>bz-graph:not(:defined) { visibility: hidden; }</style>
<bz-graph edges="data/epstein.edges" nodes="data/epstein.nodes"
level="3" heatmap="density" legend
strengths="group:5,edgetype:8" label-props="label">
</bz-graph>
Wrap data in <script type="application/json"> to avoid any flash of raw text — no :not(:defined) CSS needed. Format is auto-detected from the script type:
<bz-graph level="2" legend color-scheme="6">
<script type="application/json">
{
"nodes": [
{"id": "alice", "group": "eng", "label": "Alice", "lang": "Go"},
{"id": "bob", "group": "eng", "label": "Bob", "lang": "Go"},
{"id": "carol", "group": "des", "label": "Carol", "lang": "JS"}
],
"edges": [
{"src": "alice", "dst": "bob"},
{"src": "bob", "dst": "carol"}
]
}
</script>
</bz-graph>
You can also use raw text with format="json" (requires the :not(:defined) CSS rule to prevent FOUC).
<bz-graph level="2" legend edge-mode="lines"> alice bob bob carol carol alice </bz-graph>
| Attribute | Example | Description |
|---|---|---|
edges | "data/karate.edges" | URL to .edges file |
nodes | "data/karate.nodes" | URL to .nodes file (optional) |
format | "json" | Inline data format: json or snap (default) |
level | "3" | Initial zoom level (0-indexed) |
heatmap | "density" | Heatmap mode: off, density, splat |
edge-mode | "curves" | Edge mode: curves, lines, none |
alpha | "0.5" | Topology blend alpha (0–1) |
color-scheme | "1" | Color scheme index (0–8) |
strengths | "group:5,kind:8" | Property strengths |
label-props | "label,group" | Properties shown as labels |
legend | (boolean) | Show color legend |
reset-btn | (boolean) | Show reset button |
light-mode | (boolean) | Light theme |
webgl | (boolean) | WebGL2 rendering |
color-by | "group" | Override color group (null = auto, highest-strength group) |
Access the underlying BlitZoomCanvas via element.view for programmatic control:
const el = document.querySelector('bz-graph'); el.view.colorScheme = 3; // change at runtime el.view.cycleColorScheme(); // next scheme el.setAttribute('level', '5'); // reactive attribute
The ready event fires when the graph finishes loading and is available for programmatic access:
el.addEventListener('ready', () => { console.log(el.view.groupNames); // ['group', 'label', ...] });
The <bz-compass> component provides a radial 2D control for adjusting property strengths and bearings simultaneously. Each property group is a spoke — drag distance from center sets the strength, angular offset sets the bearing rotation.
Connect it to a <bz-graph> with the for attribute — no JavaScript needed:
<script type="module" src="bz-compass.js"></script>
<bz-graph id="myGraph" edges="data/synth-packages.edges" nodes="data/synth-packages.nodes"
level="3" legend auto-tune='{"strengths":true,"alpha":true}'>
</bz-graph>
<bz-compass for="myGraph"></bz-compass>
The compass auto-discovers groups when the graph loads and keeps in sync bidirectionally — dragging a compass handle updates the graph, and external changes (e.g. auto-tune) update the compass.
| Attribute | Example | Description |
|---|---|---|
for | "myGraph" | ID of a <bz-graph> to bind to |
max-strength | "10" | Maximum strength value (default 10) |
Interaction: drag handles freely (strength + bearing), Shift+drag for strength only, Alt+drag for bearing only. Double-click resets a handle to zero. Right-click resets bearing only. Keyboard: Tab cycles handles, ↑↓ adjust strength, ←→ adjust bearing.
Standalone (programmatic) usage without <bz-graph>:
const compass = document.createElement('bz-compass'); compass.groups = [ { name: 'group', color: '#e64', strength: 8, bearing: 0 }, { name: 'kind', color: '#4ae', strength: 3, bearing: 0.5 }, ]; compass.addEventListener('input', e => { console.log(e.detail); // { name, strength, bearing } });
See the full demo page for live examples.
BlitZoom has no build step and no npm package.
One file, ~98KB minified (gzipped), includes everything:
<script type="module" src="dist/blitzoom.bundle.js"></script>
Use <bz-graph> elements or import createBlitZoomView from the same file.
Copy all .js files from docs/ into one directory. Import from blitzoom.js — it re-exports the full public API:
<script type="module">
import { createBlitZoomView } from './blitzoom.js';
</script>
Only two files needed, zero DOM dependencies:
import { runPipeline } from './blitzoom-pipeline.js';
Requires blitzoom-algo.js + blitzoom-pipeline.js.
| File | Role |
|---|---|
blitzoom.js | Public API entrypoint (recommended import) |
blitzoom-canvas.js | Canvas component, createBlitZoomView() factory |
blitzoom-algo.js | MinHash, projection, blend, quantization |
blitzoom-pipeline.js | SNAP parsing, graph building, tokenization |
blitzoom-renderer.js | Canvas 2D rendering, hit testing, layout |
blitzoom-utils.js | Auto-tune optimizer |
blitzoom-gl-renderer.js | WebGL2 instanced rendering |
blitzoom-gpu.js | WebGPU compute acceleration |
blitzoom-svg.js | SVG export (exportSVG, createSVGView) |
blitzoom-colors.js | Color schemes (vivid, viridis, plasma, etc.) |
bz-graph.js | <bz-graph> web component |
dist/blitzoom.bundle.js | Minified single-file bundle (all of the above) |
How It Works · Viewer · GitHub