Published-release documentation · release compatibility

Primitive Extension API

Reorder existing direct children

A named Canvas or collection can reorder its existing direct children with editGraphics({ target, property: "children", value: orderedIds }). The value must contain each existing direct child exactly once. It cannot add, remove, reparent or rename a graphic, and cannot target a generated item or a drawable. Properties, items, clips, object declaration order and top-level order stay intact. An empty container accepts []. Caller arrays and earlier programs stay immutable.

This runnable identity-and-order example leaves geometry for a later authoring step:

Contextual fragment. Use an ES module with the imports, data, and prepared resource state described in this section. Resource selectors used here: target: "plot". Resolve these names from setup in this fragment or section; alternatives branch from the same base.

import { ChartProgram } from "ggaction/extension";

const program = new ChartProgram()
  .createGraphics({ id: "canvas", type: "canvas" })
  .createGraphics({ id: "plot", type: "collection", parent: "canvas" })
  .createGraphics({ id: "front", type: "rect", parent: "plot" })
  .createGraphics({ id: "back", type: "rect", parent: "plot" })
  .editGraphics({ target: "plot", property: "children", value: ["back", "front"] });

Ordinary chart authors use complete-axis underlay and Canvas plotBorder options for guide placement; direct child IDs belong to extension authoring.

These methods are the low-level public extension layer, not the recommended chart-authoring API.

All three primitives accept one plain options object and reject unknown top-level keys. Misspelled options never silently fall back to a default. Stored values cannot contain functions, class instances, or cyclic references.

editSemantic({ property, value | remove })

Creates, replaces, or removes one supported semantic branch and structurally copies the changed path.

Contextual fragment. Use an ES module with the imports, data, and prepared resource state described in this section. Caller-provided receivers: program. Resolve these names from setup in this fragment or section; alternatives branch from the same base.

program.editSemantic({
  property: "layer[points].mark.type",
  value: "point"
});

Property paths use singular user-ID selectors such as layer[points] and library-defined keys such as encoding.x.field. Unknown paths are rejected. Dataset values cannot be replaced after creation.

Use remove: true instead of value to remove one supported semantic branch. Empty parent objects are pruned, the earlier program remains unchanged, and the removal is recorded as an editSemantic trace node. Current removable container paths include complete layers such as layer[points], encoding channels such as layer[points].encoding.opacity, and legend branches such as guide.legend.opacity, complete datasets such as dataset[observations], scales such as scale[x], and coordinates such as coordinate[main]. Source rows still cannot be edited after creation. Removing an entire unreferenced source dataset is different from modifying its values.

Source-dataset and coordinate removal use the full named-resource dependency checks and clear a matching current-resource pointer. Scale removal clears its resolved cache and current pointer. Unit-program derived-data removal retains its lower-level semantic-reference checks; the owning domain action coordinates its materialization configuration and graphical consumers. A facet parent also allows named-resource removal after full dependency validation; other semantic edits on composition parents remain limited to facet title state.

Use removeData, removeScale, or removeCoordinate for ordinary resource lifecycles. They validate ownership and all live references, then invoke editSemantic for the concrete semantic deletion. removeData additionally releases a standalone logical data owner’s retained configuration.

Contextual fragment. Use an ES module with the imports, data, and prepared resource state described in this section. Caller-provided receivers: program. Resolve these names from setup in this fragment or section; alternatives branch from the same base.

program.editSemantic({
  property: "layer[points].encoding.opacity",
  remove: true
});

Removing a complete layer does not automatically remove its graphics, scales, guides, or materialization configuration. A domain action that owns the whole resource lifecycle must explicitly clean up those consumers as separate wrapped operations.

The primitive semantic grammar also supports the current line-chart contract, including temporal field types, mean aggregation, strokeDash encodings, scale nice/zero policies, combined series legends, and chart title text.

Derived dataset primitives may store an immutable source, a validated filter or regression transform, and materialized values. Regression transforms support linear, polynomial, or LOESS provenance and optional Student-t mean/prediction intervals for linear and polynomial fits. Layer paths also support encoding.y2, field-driven encoding.shape, and scale-free encoding.group for primitive area and grouped-path contracts. These are extension-level building blocks; the corresponding chart-authoring actions are introduced separately when their complete materialization behavior is ready.

Series policy leaves are layer[id].layout.mode (group, stack, fill, overlay, diverging, or center), layer[id].mark.missing (error or break), and layer[id].encoding.group.inferredFrom (color or offset). These leaves record decisions only: they do not lay out marks or split paths. Extension authors must materialize concrete graphics explicitly. Removing layer[id].layout removes its policy container without changing graphics.

createGraphics({ id, type, length?, parent?, before?, after? })

Creates one concrete object, a homogeneous drawable collection, or an empty heterogeneous drawable collection.

Contextual fragment. Use an ES module with the imports, data, and prepared resource state described in this section. Caller-provided receivers: program. Resolve these names from setup in this fragment or section; alternatives branch from the same base.

program.createGraphics({ id: "points", type: "circle", length: 2 });

Supported types are canvas, collection, circle, rect, line, text, and path. length is a non-negative integer no greater than 10,000 accepted by homogeneous drawable types. A heterogeneous collection is populated through one editGraphics({ property: "items" }) call instead; replacement items arrays are likewise limited to 10,000 entries. Equivalent repeated creation is idempotent.

Contextual fragment. Use an ES module with the imports, data, and prepared resource state described in this section. Resource selectors used here: target: "symbols". Resolve these names from setup in this fragment or section; alternatives branch from the same base.

program
  .createGraphics({ id: "symbols", type: "collection" })
  .editGraphics({
    target: "symbols",
    property: "items",
    value: [
      {
        type: "circle",
        properties: { x: 20, y: 30, radius: 4, fill: "red" }
      },
      {
        type: "rect",
        properties: {
          x: 36,
          y: 46,
          width: 8,
          height: 8,
          fill: "blue",
          stroke: "blue",
          strokeWidth: 0
        }
      }
    ]
  });

Collection item IDs are generated as symbols:0, symbols:1, and so on. Each item stores its own concrete primitive type. Shared properties such as opacity can then be broadcast to every compatible item.

A concrete text graphic accepts an empty text string. This lets a high-level display-name action hide one rendered label while retaining stable collection identity. High-level titles, annotations, and text authoring actions still apply their own non-empty input rules.

parent attaches a named graphic to an existing Canvas or collection. Attached IDs are stored in the parent’s children list; repeated drawable instances stay separate in the owning graphic’s items list. Omitting parent preserves top-level placement in graphicSpec.order.

Ordinary chart actions create and use their Canvas/plot hierarchy automatically. An extension that authors the same structure directly makes every owner explicit:

Contextual fragment. Use an ES module with the imports, data, and prepared resource state described in this section. Resolve these names from setup in this fragment or section; alternatives branch from the same base.

program
  .createGraphics({ id: "canvas", type: "canvas" })
  .createGraphics({
    id: "plot",
    type: "collection",
    parent: "canvas"
  })
  .createGraphics({
    id: "bars",
    type: "rect",
    length: 3,
    parent: "plot"
  });

before or after places a new graphic relative to a direct sibling. They are mutually exclusive, the referenced graphic must already belong to the same parent, and no top-level graphic can be placed before the Canvas.

Contextual fragment. Use an ES module with the imports, data, and prepared resource state described in this section. Caller-provided receivers: program. Resolve these names from setup in this fragment or section; alternatives branch from the same base.

program.createGraphics({
  id: "grid",
  type: "line",
  length: 5,
  parent: "plot",
  before: "bars"
});

Graphic IDs are unique across the complete tree. The renderer visits named graphics depth-first in sibling order. Attachment defines ownership and drawing order only; it does not create coordinate transforms, clipping, or layout. Unknown or non-container parents, self-attachment, and cross-parent sibling anchors are rejected during authoring. Rendering rejects orphaned, duplicated, cyclic, or unknown attachments instead of silently skipping them.

Path graphics use backend-neutral path.commands arrays rather than renderer-specific path strings. The closed command vocabulary is M, L, C, and Z. A path starts with one M; L and C draw straight and cubic segments; an optional final Z closes the path. At least one L or C segment is required. Coordinates and cubic control points must be finite numbers, and one path may contain at most 30,000 commands. Generated linear and closed paths enforce the same limit before allocation. TypeScript users can import the ConcretePathCommand union from ggaction. path.strokeDash and line.strokeDash accept non-negative finite number arrays; an empty array is a solid stroke.

Chart authors choose line interpolation through createLineMark({ curve }) or editLineMark({ curve }). Extension authors still write final commands rather than storing curve names in graphicSpec; the renderer never performs interpolation.

Circle graphics support a required fill and an optional concrete stroke/strokeWidth pair. Accepted graphic properties have Canvas rendering semantics; opaque style bags are not stored in graphicSpec.

A path can be open and stroked or Z-closed and filled. Filled paths require a final Z command; their stroke is optional. When both fill and stroke are present, the Canvas renderer fills first and strokes second.

Contextual fragment. Use an ES module with the imports, data, and prepared resource state described in this section. Resource selectors used here: target: "band". Resolve these names from setup in this fragment or section; alternatives branch from the same base.

program
  .createGraphics({ id: "band", type: "path" })
  .editGraphics({
    target: "band",
    property: "commands",
    value: [
      { op: "M", x: 10, y: 80 },
      { op: "L", x: 70, y: 40 },
      { op: "L", x: 70, y: 60 },
      { op: "L", x: 10, y: 100 },
      { op: "Z" }
    ]
  })
  .editGraphics({ target: "band", property: "fill", value: "#111111" })
  .editGraphics({ target: "band", property: "opacity", value: 0.18 });

Rect and closed-path fill also accept a backend-neutral LinearGradientPaint. The from and to coordinates are normalized to each item’s own fill bounds, not the whole Canvas. Stops are ordered by offsets from 0 to 1; repeated adjacent offsets create a hard transition. A paint object and its nested stops array are one scalar property value, so broadcasting it to a collection applies the complete paint to every item.

Contextual fragment. Use an ES module with the imports, data, and prepared resource state described in this section. Caller-provided receivers: program. Resource selectors used here: target: "distributionStrips". Resolve these names from setup in this fragment or section; alternatives branch from the same base.

const verticalDensityPaint = {
  type: "linear-gradient",
  from: { x: 0.5, y: 1 },
  to: { x: 0.5, y: 0 },
  stops: [
    { offset: 0, color: "rgba(207, 225, 242, 0)" },
    { offset: 0.5, color: "rgba(79, 142, 195, 0.7)" },
    { offset: 1, color: "rgba(10, 74, 144, 1)" }
  ]
};

program.editGraphics({
  target: "distributionStrips",
  property: "fill",
  value: verticalDensityPaint
});

TypeScript users can import FillPaint, LinearGradientPaint, LinearGradientPoint, and LinearGradientStop from ggaction or ggaction/extension. Structured paint is currently fill-only for rects and closed paths. Circle/text fills, strokes, radial or conic gradients, patterns, and Canvas-wide user-space coordinates are not supported.

Only normalized paint data is stored in graphicSpec. The Canvas renderer resolves the final item-local coordinates and creates the backend gradient while drawing; no Canvas gradient object is retained in the immutable program. Canvas, PNG, and PDF require resolved native gradient geometry to have magnitude no greater than 16777216, including the direction-vector length, and reject the complete render before native output changes. SVG preserves all finite JavaScript coordinates.

The complete low-level line-chart example is available in primitive.program.js. It explicitly authors semantic line state, paths, axes, a combined legend, and title graphics without chart-level convenience actions.

The regression scatterplot baseline in primitive.program.js uses a heterogeneous point collection, grouped filled confidence-band paths, grouped line paths, and two concrete legends.

editGraphics({ target, property, value | remove })

Sets one validated concrete property or removes one named graphic subtree.

Contextual fragment. Use an ES module with the imports, data, and prepared resource state described in this section. Caller-provided receivers: program. Resource selectors used here: target: "points". Resolve these names from setup in this fragment or section; alternatives branch from the same base.

program.editGraphics({
  target: "points",
  property: "x",
  value: [32.5, 81.4]
});

For a homogeneous or heterogeneous collection, an outer array distributes values by index and must match its length. A non-array value is broadcast to every item that supports the property. Nested arrays and objects remain one value per item. Generated item IDs such as points:1 can be targeted.

Use remove: true without property or value to remove a named graphic and its owned named descendants. The action also detaches the root from its parent or top-level order. The Canvas root and generated items cannot be removed; resize or replace an item’s owning collection instead.

Contextual fragment. Use an ES module with the imports, data, and prepared resource state described in this section. Caller-provided receivers: program. Resource selectors used here: target: "opacityLegendSymbols". Resolve these names from setup in this fragment or section; alternatives branch from the same base.

program.editGraphics({
  target: "opacityLegendSymbols",
  remove: true
});

Authoring and rendering share the same concrete value contract. Numeric geometry must be finite, dimensions and stroke widths cannot be negative, opacity stays between 0 and 1, text alignment uses the Canvas vocabulary, and appearance strings must be non-empty. Structured rect/path fill follows the FillPaint contract described above. Rendering additionally requires all properties needed to draw the primitive to be present.

Clip a named graphic owner

Use editGraphics({ target, property: "clip", value: { x, y, width, height } }) to clip one named non-Canvas owner and its items/children in its unchanged parent frame. Values must be finite, dimensions nonnegative, and endpoints finite. Use value: false to clear the optional clip. Generated item IDs, inline item properties and Canvas clips are rejected; nested Canvas retains its existing viewport behavior. Item coordinates and IDs stay unchanged, including after items or length replacement. Ordinary chart authors use the mark’s clip option instead.

Scale materialization

createScale({ id, type?, domain?, range?, nice?, zero?, clamp?, reverse?, base?, exponent?, constant? }) creates an idempotent semantic scale. Domain actions that own scale consumers invoke the internal wrapped rematerializeScale operation to resolve all consumers and apply concrete graphic edits, including connected axis updates. Aggregate line consumers resolve their domains from derived means rather than raw rows. Ordinal ranges may contain color strings, a validated named-palette descriptor, or validated even-length stroke-dash patterns for the matching channel.

Contextual fragment. Use an ES module with the imports, data, and prepared resource state described in this section. Caller-provided receivers: program. Resolve these names from setup in this fragment or section; alternatives branch from the same base.

program.createScale({ id: "x", type: "linear" });

Direct quantitative position scale types are linear, log, pow, sqrt, and symlog; type-specific parameters use the same contract as encoding scale options. time and ordinal remain available for their current roles.

Extension actions should call the public domain action that owns the affected consumer instead of calling rematerializeScale directly. Rematerialization is still explicit inside that action and remains visible in program.trace; it never runs merely because semanticSpec changed.

Documentation provenance

293 actions · sha256:e755591a2e5f5e2b494867583876e28020a478c5220b77bdc6ad469a6224891d