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.