Program Composition
Combine already-authored chart programs without merging their datasets, marks,
scales, or guides. The result is another immutable ChartProgram whose parent
Canvas contains concrete snapshots of its named children.
Arrange complete programs
import { hconcat, vconcat } from "ggaction";
const row = hconcat({
id: "overview",
programs: [
{ id: "main", program: scatterplot },
{ id: "detail", program: barChart }
],
gap: 20,
align: "center",
padding: 16
});
const dashboard = vconcat({
programs: [row, trendChart],
gap: 18
});
This is a composition fragment: scatterplot, barChart, and trendChart
must each be a complete program with one materialized Canvas. See the
runnable repository example
for complete child construction and Browser Canvas rendering.
The representative output deliberately keeps two different final grammars:
the main slot contains a titled point chart on a blue panel, while the
detail slot contains the titled orange bar chart installed by
replaceCompositionChild. Distinct child backgrounds and the visible parent
gap make the retained slot, replacement slot, and parent layout observable in
the full image and its gallery thumbnail.
Both functions require at least two programs. A direct ChartProgram receives
the deterministic slot name view-1, view-2, and so on. Use
{ id, program } when later code must replace a stable slot.
| Option | Default | Effect |
|---|---|---|
id |
"composition" |
Names the composition for deterministic graphic namespaces |
programs |
required | Ordered complete child programs or { id?, program } entries |
gap |
16 |
Non-negative distance between adjacent children |
align |
"center" |
"start", "center", or "end" cross-axis placement |
padding |
0 on every side |
Non-negative scalar or partial four-side object |
Automatic and explicit child sizes
The parent Canvas size is inferred from child dimensions, gap, and padding.
For hconcat, children whose height was omitted in createCanvas expand to the
largest child height. For vconcat, children whose width was omitted expand to
the largest child width. A unit child rematerializes against that resolved size.
A nested composition keeps its intrinsic child layout and align places its
complete snapshot inside the larger cross-axis slot; the outer composition does
not stretch inner facet cells, gaps, or guide geometry. An explicitly authored
child width or height is never overwritten.
The parent background is white. Child Canvas backgrounds are preserved, and nested compositions keep independent clipping and coordinate scopes.
Compose Cartesian and Polar charts
A complete Cartesian or Polar chart can be a direct or nested concat child. The composition does not reinterpret theta, radius, x, y, scales, guides, or selections. It snapshots each finished child into one namespaced concrete graphic tree, so Canvas and PNG renderers use the same result.
When a nested child changes, replace it in each ancestor explicitly:
const revisedPolarRow = polarRow.replaceCompositionChild({
target: "detail",
program: revisedPolarChart
});
const revisedDashboard = dashboard.replaceCompositionChild({
target: "polarRow",
program: revisedPolarRow
});
This preserves immutable earlier programs and makes the affected ancestor layout visible in the action trace. See the cross-feature dashboard source for a nested Polar replacement next to a Cartesian facet.
Repeat the current chart by a field
Call facet on one complete unit chart to repeat it for each observed field
value:
import { chart } from "ggaction";
const faceted = chart()
.createCanvas({ width: 250, height: 230 })
.createData({ values: cars })
.createPointMark()
.encodeX({ field: "Horsepower" })
.encodeY({ field: "Miles_per_Gallon" })
.encodePointRadius({ value: 2.5 })
.encodeColor({ field: "Cylinders", fieldType: "ordinal" })
.facet({
field: "Origin",
columns: 3,
guides: { legend: "shared" }
})
.createTitle({ text: "Horsepower and Fuel Economy" });
The input rows in this runnable fragment must contain complete values for the encoded fields. See the repository example for the complete data preparation and guide options.
facet uses field values in source first-appearance order. It infers one
common row-preserving dataset ancestor, then filters and replays supported
derived data independently inside each cell. Omitted columns creates one row;
a positive value wraps cells row-major.
| Option | Default | Effect |
|---|---|---|
id |
"facet" |
Names the parent and deterministic child namespaces |
field |
required | Direct-source field whose values define cells |
data |
unique common ancestor | Selects the row-preserving partition dataset explicitly |
columns |
number of values | Sets the grid column count |
gap |
16 |
Sets horizontal and vertical cell spacing |
align |
"center" |
Aligns unequal cells inside grid tracks |
padding |
0 on every side |
Adds scalar or four-side parent padding |
scales |
every channel "shared" |
Sets "shared" or "independent" per x, y, xOffset, yOffset, color, size, shape, opacity, or strokeDash |
guides.axes |
"each" |
"outer" keeps x axes on the bottommost occupied cell in each column and y axes on the leftmost occupied cell in each row |
guides.legend |
false |
"shared" promotes one compatible parent-owned categorical, gradient, discretized-color, size, or opacity legend |
Shared auto domains use the full faceted result; independent auto domains are resolved from each cell. An explicit semantic domain always wins. Regression, density, interval/error-band, and box-summary/outlier datasets are replayed after the cell filter, so each panel receives a fresh statistical result rather than a clipped copy of the full-chart result.
The current Cartesian slice supports point, line, area, histogram, aggregate
bar, ranged bar, rule, regression, density, interval/error-band, and box-plot
layers when they share one valid row-preserving partition ancestor. A shared
legend is accepted only when every represented child scale and legend recipe is
concretely compatible; scale resolution alone does not make a guide shareable.
Polar sources cannot currently be faceted. Calling facet on a Polar source
throws before creating partial children because theta/radius facet scale and
guide resolution are not implemented. Polar charts remain supported as concat
children.
Create a chart title after facet so the title is owned directly by the
parent. A title that already fits the unit Canvas is promoted for authoring
order compatibility rather than repeated in every cell.
The parent title aligns to the union of the child plot bounds, excluding cell margins and the shared legend. Each repeated header is likewise centered on its own child plot—not on the complete child Canvas—so asymmetric axis space does not visually offset panel titles.
Edit the repeated header style without addressing generated graphic IDs:
const emphasized = faceted.editFacetHeaders({
fontSize: 13,
fontWeight: 700,
color: "#0f172a",
offset: 10
});
Facet-header weights follow the shared Canvas font-weight policy.
Edit the layout
const revised = row.editCompositionLayout({
gap: 28,
align: "start",
padding: { left: 12, right: 12 }
});
At least one option is required. Omitted values retain their current settings; a partial padding object updates only the named sides. The action preserves all child IDs and references and rebuilds the parent snapshot.
Facet parents use this same action for gap, align, and padding; derived
cell programs and facet value order remain unchanged. Parent-title and header
anchors are recomputed from the newly translated child plot bounds.
Replace one stable slot
const replaced = revised.replaceCompositionChild({
target: "detail",
program: donutChart
});
The replacement must be a complete unit or nested composition program. Its new size participates in layout inference, while the target ID and position in the ordered child list remain stable. Earlier parent and child programs are not mutated.
Facet cells are derived from one canonical source and cannot be replaced with
replaceCompositionChild.
State, trace, and action scope
children maps stable slot IDs to retained immutable programs.
compositionSpec stores concat direction or facet intent together with order,
gap, alignment, and padding. The
parent graphicSpec stores the fully materialized, namespaced child Canvas
tree; renderers read only that concrete state.
hconcat, vconcat, and facet trace useProgram children followed by
materializeComposition. Layout edits and replacement rematerialize from the
retained child programs. Ordinary data, mark, encoding, scale, and guide
actions apply only to unit programs and reject a composition parent. Facet
titles and facet-header edits are explicit parent-owned exceptions. Edit a
child first, then replace its slot when a concat dashboard needs a changed
chart.