Documentation

JSON specs

Save and load ViewSpec declarations with the same runtime as builders and React.

A JSON spec is a serialized ViewSpec. It describes one chart state, including its data source, encodings, ordered transforms and chart-specific options. Full specs use the existing rendering engine. Fluent edit messages below are a separate authoring input.

One chart, three ways

Switch between the chain API, JSON and React below. Each renders the same bar chart: categories A, B and C with values 12, 28 and 19.

All three examples use the same data. Import visdelta/style.css once in your app.

const sales = [
  {"category":"A","value":12},
  {"category":"B","value":28},
  {"category":"C","value":19}
];
import { bar } from 'visdelta/bar';
import { transition } from 'visdelta/transition';

const chart = bar({ name: 'sales' }).x('category').y('value');

const view = await transition(chart, chart, {
  target: '#chart', data: { sales }, height: 280,
});
// Call view.destroy() when removing the chart.

The host is an existing <div id="chart"></div>. Equal endpoints render a static chart.

Render and update directly

import { render } from 'visdelta';

const view = await render('#chart', json, {
  data: { sales },
  height: 280,
  updates: { policy: 'latest', duration: 600 },
});

// Pass a complete JSON string or spec object from your agent.
const result = await view.update(nextSpec);
// result.status: 'applied', 'superseded', or 'destroyed'

view.destroy();

No parsing or registration step is needed for built-in charts with render(). Each update is a complete state of the same chart type. The default latest policy finishes the active update, then plays only the newest valid pending state. Invalid JSON, missing named data and runtime validation failures reject without replacing the visible chart. Catch update errors in your application. Send complete JSON output; partial token streams are not chart states. Reject responses from obsolete agent turns before calling update().

The promise settles when the update is applied, superseded or destroyed. Duration is milliseconds per planned route stage. Use view.update(nextSpec, { animate: false }) for a scheduled immediate replacement. Supply { data: { sales: newRows } } to replace named bindings; already resolved endpoints retain their own data. view.snapshot() returns a detached { revision, spec } for the latest accepted state, which can be ahead of the visible animation. Acceptance does not guarantee later route preparation will succeed. view.resize() retains new dimensions, and view.destroy() also settles outstanding updates and prevents late mounts.

For manual progress control, use await view.transition(nextSpec) once updates have settled. It does not autoplay. Finish or pause at an endpoint before the next submission; updates during manual playback or at an intermediate frame reject. Its play() method returns the controller, not a promise.

To include only selected chart modules:

import { createRenderer } from 'visdelta/render';
import { barModule } from 'visdelta/bar';

const renderer = createRenderer({ modules: [barModule] });
const view = await renderer.render('#chart', json, { data: { sales } });

This module list belongs to the renderer and ignores global registrations.

Reading and writing specs

parseSpec(json: string): ViewSpec reads JSON and normalizes the declaration using the same logic as delta() and the runtime. stringifySpec(state, space?) accepts a builder or plain ViewSpec; space is JSON indentation. Both are exported from visdelta and visdelta/core. TypeScript callers can import ViewSpec from either entry.

Both helpers check common structure, ordered transforms and JSON values. They preserve custom chart types and extension fields. They do not load modules, fetch data, check field availability or prove that a particular chart supports an encoding combination. Those checks remain in the data pipeline and chart plugins when you render or plan a transition.

Object properties with undefined are omitted; valid dates become ISO strings. Non-finite numbers, functions, symbols, bigints, circular references, unsupported object classes and undefined array entries throw instead of silently losing values. For builders, validation sees the declaration returned by toSpec(). Existing aliases such as key normalize into meta; this is a declaration round trip, not preservation of JSON whitespace or property placement.

Use explicit { "name": "sales" } or { "url": "sales.csv" } data sources. Named data is supplied through the existing data option on the runtime or React component. Inline rows may use an array or { "values": [...] }.

React and sequences

Parse stored specs once when loading them, then pass the resulting objects to Chart. Keep their references stable across renders.

import { Chart } from 'visdelta/react';

// Define these once when loading the specs, or memoize them.
const pair = [from, to];
const states = [from, to, next];
<Chart state={from} />
<Chart state={pair} progress={0.5} />
<Chart state={states} progress={1.5} />

Register the required chart modules before mounting, just as for the imperative runtime. Playback options, callbacks, host elements and React props are not part of the spec. A sequence remains an array of states supplied to sequence() or Chart; this API does not introduce a JSON timeline format.

Small edits from an agent

The Bar module accepts fluent JSON commands on the existing chart:

const { revision } = view.snapshot();
await view.edit({
  requestId: 'turn-1-flip',
  baseRevision: revision,
  edits: [{ method: 'flip', args: [] }],
});

A batch builds one destination. Earlier accepted edits survive skipped pending animations. snapshot() reports acceptance; the returned promise reports playback completion, supersession or destruction. A stale revision rejects with code: 'REVISION_CONFLICT'. Read the new snapshot before generating a replacement request. Identical request IDs and payloads reuse their original outcome; changing a payload requires a new ID. Each view retains up to 1024 requests, including failures; start a new view session after that limit.

Bar supports flip, color, x, y, sort, where, focus and highlight. Axis options support type, label and format strings. Axis bindings refer to the current channels after a flip. Filters and highlights accumulate as they do in native chains. Aggregation edits, aggregate axis remaps, other built-in charts and arbitrary builder methods are unsupported; use full update() states for these. Edits cannot replace dataset bindings.

Try Liveshow. Its Grain category deliberately uses update(). This is a local, tool-friendly API; an application can expose it through MCP, but VisDelta does not start an MCP server.

Observe the rendered chart

const marks = view.inspectMarks();
const image = await view.capture();
// image.dataUrl is a PNG; both results carry the accepted revision.

Call these after edits have settled. Mark observations contain bound scalar data, CSS-pixel bounds and explicit spatial orders, so a downstream agent can resolve “the third bar” against the actual display. Only visible keyed marks are listed. Capture includes the primary SVG canvas on white, not the surrounding application or separate legend SVGs. External images/foreignObject are unsupported. Observation rejects pending or manual transitions, and capture rejects revision changes while encoding; retry after the current update finishes.

On this page