Skip to content

@mettascript/grapher

The visual node-graph editor for MeTTa, MeTTaGrapher. It renders a program two ways, as a node graph and as nested blocks, and runs on the same interpreter as the rest of the packages. A node graph is a MeTTa atom, so anything that produces atoms feeds it.

bash
npm install @mettascript/grapher

The editor is framework-free and renders its UI as SVG. It depends on @mettascript/hyperon for evaluation, and the blocks view uses Canvas 2D for text measurement. Browser GIF export takes an encoder as an argument. Plain Node.js uses the separate @mettascript/grapher/node entry with optional sharp and gifenc packages. That entry requires Node 20.9 or newer.

The fluent driver

grapher(el) is the quickest way in, in the same style as the eDSL. Every building step returns the handle, so a chain reads as one sentence; the terminal steps source(), gif(), and destroy() end it.

ts
import { grapher } from "@mettascript/grapher";

const view = grapher("#app")
  .load("(= (double $x) (* $x 2))\n(double 21)")
  .blocks() // or .graph()
  .fit()
  .evaluate(); // label the query with 42

// Call view.destroy() when the host component unmounts.
stepdoes
load(source)replace the program from MeTTa source
atoms(atoms)replace it from atoms (e.g. eDSL output)
graph() / blocks()choose the view
palette(choice)recolor the blocks: "site", "teal", or a palette object
fit()lay out and frame the current graph
evaluate()evaluate every query and label its result
play()initialize the query's reduction trace at its first state
source()end the chain, returning the current MeTTa source
gif(encoder)end the chain, returning an image/gif Blob (pass await import("gifenc"))
destroy()tear down and free the instance
.grapherthe underlying MeTTaGrapher instance, for anything the chain does not cover

The MeTTaGrapher class

ts
import { MeTTaGrapher } from "@mettascript/grapher";

const editor = new MeTTaGrapher(document.getElementById("app")!, { source: "(+ 10 (* 25 2))" });

GrapherOptions accepts { source?: string; metta?: MeTTa; panOnLeftDrag?: boolean }. Pass an existing MeTTa when the editor should share its space. The embedded editor on each docs page also stores its live instance on the canvas element (document.querySelector(".mg-canvas").grapher), so you can drive it from the console.

Set panOnLeftDrag when the canvas gets its own panel rather than sitting in a scrolling article, and a left-drag on empty canvas will pan instead of rubber-band selecting. Shift-drag still rubber-bands, so you keep box-select. Leave it off (the default) to get the editor gestures the docs pages use.

Loading and reading

methodreturnsdoes
loadSource(src)voidreplace the whole program from source
loadAtoms(atoms)voidreplace it from atoms
load(json)voidrestore a saved graph exactly, positions and all
save()GraphJsonserialize the graph to JSON
toSource()stringthe program as MeTTa source
atoms()Atom[]one atom per head

View, layout, and camera

methoddoes
setViewMode("graph" | "block")switch between the node graph and the nested blocks
setBlockPalette(palette)recolor the blocks view
tidy()lay the graph out as tidy trees and fit it to the view
fitView(padding?)fit the current content to the viewport
zoomBy(factor)zoom about the center
panBy(dx, dy)pan by a screen delta

Evaluating

methoddoes
evaluate(nodeId)evaluate every head the node belongs to and label each with its result
evaluateAll()evaluate every head in the graph
completions(prefix)symbol completions for the node-creation input

Playing a reduction

playTrace builds a step-by-step trace on the engine and shows the first state; the rest step through it.

methodreturnsdoes
playTrace(nodeId?)voidstart a playthrough of a head (the given node, or the current query)
traceForward() / traceBack()voidstep one reduction
traceRestart()voidjump back to the first state
stopTrace()voidleave the playthrough and return to the editable program
isTracing()booleanwhether a playthrough is on
traceInfo(){ index, total } | nullposition in the trace
setTraceDuration(ms)voidhow long each step's morph takes (default 550), so a host's speed control can slow the animation itself; GIF exports pace off the same span
uiState()objecta snapshot of view mode, tracing, and control availability, for mirroring into a host UI

Blocks view

methodreturnsdoes
blockReduce()booleanreduce the focused block one step
blockBack()booleanundo the last block reduction or edit
blockCanStepBack()booleanwhether an undo is available
blockSource()stringthe block view's current program as source

Browser GIF export

ts
const blob = await editor.exportReductionGif(await import("gifenc"), { width: 720, holdMs: 260 });

exportReductionGif(encoder, opts?) returns an image/gif Blob (or null if there is nothing to animate). GifOptions covers width, morphMs (how long one step's morph spans; the editor fills it in with its current trace duration, so the GIF glides exactly like the live view), framesPerStep (an explicit frame count that overrides morphMs), maxFrames, and holdMs (how long to hold each settled state, which a host maps from its playback speed).

The browser methods require DOM Canvas and Image. They are programmatic APIs, but they are not the Node entry point.

Events and lifecycle

methoddoes
onChange(cb)called when the graph changes; returns an unsubscribe function
onViewChange(cb)called on any view-state transition (view switch, playthrough step, load)
onBlockChange(cb)called on a block-view edit or reduction
destroy()remove listeners and the SVG

Driving appearance from MeTTa

Alongside the program's own space &self, the editor watches an isolated space, &grapher. A program adds directive atoms to it and the editor overlays them; because they live in their own space they never mix with the program's atoms.

metta
!(add-atom &grapher (color (fact 5) red))
!(add-atom &grapher (highlight if))
!(add-atom &grapher (background "#141a2e"))
directiveeffect
(color TARGET COLOR)fill the node; COLOR is a name (red) or a #hex
(highlight TARGET)ring the node
(focus TARGET)frame the node
(label TARGET TEXT)write text above the node
(background COLOR)theme the whole canvas (global, no target)

A TARGET is a node's name (if, reaching every if) or the term a node stands for ((fact 5), kept exactly as written). Reachable from MeTTa, from TypeScript through the space, or from the eDSL building the atom. bindVizSpace(space), readViz(space) (returns { directives, background }), colorOf, and textOf are exported for reading the space yourself.

Encoding a GIF without the class

reductionGif(states, settings, encoder, opts?) encodes a reduction, given as its list of states (each a frontier of atoms), to a GIF blob directly. This is the routine exportReductionGif wraps. GifEncoderLib is the slice of gifenc it needs, and GifOptions also carries stepMs (milliseconds per morph frame) alongside the fields above.

Node GIF API

Install the optional Node renderer and encoder beside the grapher:

bash
npm install @mettascript/grapher sharp gifenc
ts
import { writeFile } from "node:fs/promises";
import { renderReductionGif } from "@mettascript/grapher/node";

const bytes = await renderReductionGif("(+ 10 (* 25 2))", {
  view: "blocks",
  width: 720,
});

await writeFile("reduction.gif", bytes);

renderReductionGif(input, options?) returns Promise<Uint8Array>. input is a source string, one Atom, or a read-only atom array. Source and arrays use the last non-definition atom as the query. Every input atom is added to options.metta, or to a fresh MeTTa when no engine is supplied.

optiondefaulteffect
view"blocks""blocks", "graph", or "side-by-side"
mettaa fresh enginereuse an existing space and its grounded operations
widthview-specificoutput width in pixels
framesPerStepderived from morphMsinterpolation frames per reduction
maxFrames180maximum encoded frames
holdMs260delay on each settled state
stepMs40delay on each interpolation frame
morphMs550default total duration of one morph
maxSteps300maximum evaluator steps while constructing the trace
backgroundthe view palettecanvas color

The Node renderer shares reduceTrace and the SVG-frame builders with the browser. Sharp replaces browser Canvas only for rasterization. See the Node GIF tutorial for rules, an existing MeTTa space, HTTP responses, and the resource limits.

Lower-level building blocks

For embedding or customizing, the package also exports the pieces the editor is built from: the Graph model (GraphNode, NodeKind); the atom bridge (atomToGraph, graphToAtoms, composeAtom); parseProgram; layout; serialization (toJson, fromJson, toSource, fromSource); evaluation (evaluateHead, loadProgram); the step tracer (reduceStep, reduceTrace); node coloring (colorFor, roleOf); the Renderer and Controller; the Viewport helpers; the block view (BlockView, layoutAtom, SITE_PALETTE, TEAL_PALETTE); and the host-independent blockReductionSvgs, graphReductionSvgs, sideBySideReductionSvgs, and encodeSvgAnimation functions.

Released under the MIT License.