ChartProgram and Immutability

chart() returns an empty ChartProgram. A program contains the authored chart state and the history used to produce it.

import { chart } from "ggaction";

const empty = chart();
const withCanvas = empty.createCanvas();

console.log(empty === withCanvas); // false

Every action returns a new program. Earlier programs and caller-owned input are not mutated.

Program state

Property Purpose
semanticSpec Data, layers, encodings, scales, coordinates, and guides
graphicSpec Fully materialized backend-neutral graphics
resolvedScales Resolved domains and concrete output ranges
materializationConfigs Immutable appearance and layout inputs needed for later rematerialization
children Named immutable child programs retained by a composition parent
compositionSpec Optional concat direction or facet intent, ordered slots, gap, alignment, and padding for a composition parent
context Current authoring selections used to interpret omitted action targets
trace Hierarchical action history

context helps later actions interpret omitted parameters, such as the current dataset or mark. Canvas margin is canonical materialization state rather than transient context, and plot bounds are derived from it and the concrete Canvas dimensions. Rendering does not depend on context or trace.

A unit program has empty children and no compositionSpec. Package-level hconcat and vconcat retain already-authored children. Chainable facet derives filtered immutable children from one unit program. Neither form merges child semantic state into one layer grammar. See Program composition.

Inspecting authored and materialized state

The semantic collections, named graphic map, drawing order, and trace tree are public immutable inspection surfaces. Use an explicit mark ID when code needs to connect one semantic layer to its concrete final items.

import { chart } from "ggaction";

const program = chart()
  .createCanvas({ width: 240, height: 160, margin: 30 })
  .createData({
    id: "observations",
    values: [{ x: 1, y: 2 }, { x: 2, y: 4 }]
  })
  .createPointMark({ id: "points" })
  .encodeX({ field: "x" })
  .encodeY({ field: "y" });

const pointLayer = program.semanticSpec.layers.find(
  layer => layer.id === "points"
);
const pointGraphic = program.graphicSpec.objects.points;

if (pointLayer === undefined || pointGraphic === undefined) {
  throw new Error("Expected the points layer and graphic.");
}

console.log(program.semanticSpec.datasets.length); // 1
console.log(pointLayer.mark?.type);                 // "point"
console.log(pointGraphic.items?.length);            // 2
console.log(program.graphicSpec.order);              // top-level draw order
console.log(program.trace.children.at(-1)?.op);      // "encodeY"

The same code compiles in TypeScript. SemanticDataset, SemanticLayer, SemanticScale, SemanticCoordinate, GraphicSpec, GraphicObject, and TraceNode are exported when an inspection helper needs an explicit type.

Path Meaning
program.semanticSpec.datasets Ordered semantic dataset resources, including user IDs and immutable source or derived values
program.semanticSpec.layers Ordered semantic mark layers; find a layer by its persisted id
program.semanticSpec.scales Named semantic scale definitions before concrete range resolution
program.semanticSpec.coordinates Named coordinates and their attached layer IDs
program.graphicSpec.objects Map from every named graphic ID to one concrete object or repeated-item owner
program.graphicSpec.order IDs of top-level graphics in drawing order
program.graphicSpec.objects[id].children Attached named child graphic IDs in sibling drawing order
program.graphicSpec.objects[id].items Repeated concrete final items owned by a mark or guide graphic
program.trace.children Top-level authored actions below the virtual program trace root

For a mark created with an explicit ID, the semantic layer and its owning graphic use that same ID. items.length therefore checks final point, bar, rule, line-series, or area-series cardinality without asking the renderer to draw first. The relevant grain depends on the mark: one line item is one final series path, not one source row.

Collection names and user-chosen resource IDs are stable public contracts. System-generated component IDs, such as internal plot containers, axis parts, legend symbols, and individual item IDs, may change when a graphical recipe changes. Inspect those components through their documented owner and children or items collection instead of persisting generated IDs. Concrete properties are backend-neutral and inspectable, but their shape follows the graphic type—for example, a path stores resolved commands rather than source field expressions.

resolvedScales and materializationConfigs support advanced diagnostics, while context is transient authoring convenience. Portable verification should prefer semanticSpec, graphicSpec, and trace because those surfaces record meaning, drawable output, and provenance respectively.

Use Semantic and graphical state to understand the boundary between intent and concrete output.