# ggaction Full Documentation
Generated from the canonical Markdown page order. Do not edit this file directly.
Use ./llms.txt for the concise routing index.
# ggaction
A grammar for how charts are made.
Most visualization grammars describe a finished chart. **ggaction** represents
chart authoring itself as an immutable, traceable sequence of graphical actions.
Build, inspect, select, and revise charts one meaningful action at a time.
Start with a complete example, then use the API pages when you need to customize
one part. The main, extension, and PNG entry points include TypeScript declarations.
Experimental 0.0.5 · APIs may change before 1.0.0. Review the changelog when upgrading.
## Start here
Build your first chart
Install the package, render a complete chart, and understand the action chain.
Start from a chart type
Copy the shortest supported scatterplot, line, bar, or area flow.
Find an action
Look up exact signatures, defaults, inference, and related guides.
## Common chart types
Start with common Cartesian charts for relationships, comparisons, trends,
and distributions.
## Statistics and uncertainty
Compose ordinary marks and derived data into higher-level statistical views.
## Alternative coordinates
Use parallel axes or angle and radial distance when Cartesian x and y are not
the clearest structure for the comparison.
Browse the curated chart gallery →
## Go deeper
Understand [immutable ChartProgram state](./concepts/chart-program.md),
[semantic and graphical state](./concepts/semantic-and-graphics.md), and
[action trace trees](./concepts/actions-and-trace.md). Extension authors can
continue with [action authoring](./extension/action-authoring.md) and the
[primitive API](./extension/primitives.md). For boundaries and failures, see
[supported features](./supported-features.md) and
[troubleshooting](./troubleshooting.md). Language models can use the concise
[documentation index](./llms.txt) or the generated
[full-text bundle](./llms-full.txt).
Source, issues, and development history are available on
[GitHub](https://github.com/ggaction/ggaction).
# Getting Started
This walkthrough installs `ggaction`, creates a complete scatterplot from an
inline dataset, and renders it to Browser Canvas. Every action returns a new
immutable `ChartProgram`, so the calls can be chained.
## 1. Create a browser project
`ggaction` is an ESM package. This minimal setup uses Vite to resolve the npm
package for the browser:
```bash
mkdir ggaction-start
cd ggaction-start
npm init -y
npm install ggaction
npm install --save-dev vite
```
The command installs the public `ggaction` package from the npm registry.
Create `index.html`:
```html
ggaction scatterplot
```
## 2. Build the program
Create `main.js`:
```javascript
import { chart, render } from "ggaction";
const cars = [
{ horsepower: 88, mpg: 27, origin: "USA" },
{ horsepower: 70, mpg: 36, origin: "Japan" },
{ horsepower: 110, mpg: 24, origin: "Europe" }
];
const program = chart()
.createCanvas({
width: 640,
height: 400,
margin: { top: 30, right: 30, bottom: 60, left: 70 }
})
.createData({ values: cars })
.createScatterPlot({
x: "horsepower",
y: "mpg",
color: "origin",
guides: {
axes: {
x: { title: { text: "Horsepower" } },
y: { title: { text: "Miles per gallon" } }
}
}
});
const canvas = document.querySelector("#chart");
render(program, canvas.getContext("2d"));
```
`createScatterPlot` uses the current dataset, creates a point mark, assigns the
x, y, and optional appearance encodings, and creates applicable guides. It
records those regular actions as trace children rather than compiling a second
chart specification. Pass `data` explicitly when a program contains more than
one dataset candidate; pass `id` when a later multi-resource flow needs that
mark identity.
The wrapped `createGuides` action infers the applicable axes and horizontal
grid from the position encodings. The renderer reads only concrete
`graphicSpec` values already produced by actions; it does not compile
`semanticSpec` during rendering.
A nominal point color encoding can produce a categorical legend; adding a
matching shape encoding produces a composite color-and-shape legend.
## 3. Run it
```bash
npx vite
```
Open the local URL printed by Vite. The browser draws the chart into the Canvas
created in `index.html`.
## Package entries and compatibility
| Import | Environment | Use |
| --- | --- | --- |
| `ggaction` | Modern ESM browsers and Node.js 20+ | Chart authoring and Browser Canvas rendering |
| `ggaction/extension` | Modern ESM browsers and Node.js 20+ | Wrapped actions and public primitive authoring |
| `ggaction/png` | Node.js 20+ only | PNG file output through the native Canvas adapter |
All entries include TypeScript declarations. The package does not publish
CommonJS entry points. Import `ggaction/png` only from Node code; the default
browser entry does not load filesystem or native PNG modules.
The release artifact is tested by installing its exact tarball into fresh
JavaScript and TypeScript consumer projects. It is also tested in a browser and
across the supported Node release matrix.
## Runnable repository examples
The source repository also contains complete modules for the
[minimal getting-started chart](https://github.com/ggaction/ggaction/tree/main/examples/getting-started/),
[scatterplot](https://github.com/ggaction/ggaction/tree/main/examples/cars-scatterplot/),
[line chart](https://github.com/ggaction/ggaction/tree/main/examples/cars-line-chart/),
[histogram](https://github.com/ggaction/ggaction/tree/main/examples/cars-histogram/),
[bar chart](https://github.com/ggaction/ggaction/tree/main/examples/jobs-grouped-bar/),
[heatmap](https://github.com/ggaction/ggaction/tree/main/examples/gapminder-life-expectancy-heatmap/),
[parallel coordinates](https://github.com/ggaction/ggaction/tree/main/examples/cars-parallel-coordinates/),
[regression scatterplot](https://github.com/ggaction/ggaction/tree/main/examples/cars-regression-scatterplot/),
[density area](https://github.com/ggaction/ggaction/tree/main/examples/cars-density-area/),
[violin plot](https://github.com/ggaction/ggaction/tree/main/examples/cars-acceleration-violins/),
[error bar](https://github.com/ggaction/ggaction/tree/main/examples/cars-error-bar/),
[error band](https://github.com/ggaction/ggaction/tree/main/examples/gapminder-error-band/),
[box plot](https://github.com/ggaction/ggaction/tree/main/examples/cars-box-plot/),
[mark selection](https://github.com/ggaction/ggaction/tree/main/examples/mark-selection/), and
[program composition](https://github.com/ggaction/ggaction/tree/main/examples/program-composition/).
## Next
Copy a chart recipe Start from the shortest supported flow for a known chart type.
Learn a complete workflow Build a chart step by step and understand what each action adds.
Find an exact action Look up canonical signatures, defaults, inference, and errors.
Use Troubleshooting when
inference or layout cannot make one safe choice. Render the same program to a
file with PNG output .
# Tutorials
Tutorials build complete, runnable charts and explain what each action adds.
Choose the visual relationship that matches the question you want to answer.
New to ggaction? Install the package and render one complete browser chart first.
Need only the shortest code? Switch to recipes when you do not need the step-by-step explanation.
The bar tutorial uses grouped bars as one layout of the general bar mark. The
statistical tutorials compose ordinary data, marks, encodings, and guides rather
than introducing a separate renderer path.
# Cars Scatterplot Tutorial

This tutorial uses the public npm package. The repository also contains a
[runnable browser example](https://github.com/ggaction/ggaction/tree/main/examples/cars-scatterplot)
and its [canonical program](https://github.com/ggaction/ggaction/blob/main/examples/cars-scatterplot/program.js).
Start with the Vite project from [Getting Started](../getting-started.md), then
place the tutorial dataset in Vite's public directory:
```bash
mkdir -p public
curl --fail --location https://raw.githubusercontent.com/ggaction/ggaction/main/data/cars.json --output public/cars.json
```
## Complete program
```javascript
import { chart, render } from "ggaction";
const response = await fetch("/cars.json");
if (!response.ok) throw new Error(`Failed to load cars: ${response.status}`);
const cars = await response.json();
const rows = cars.filter(
car =>
Number.isFinite(car.Horsepower) &&
Number.isFinite(car.Miles_per_Gallon)
);
const program = chart()
.createCanvas({
width: 640,
height: 400,
margin: { top: 30, right: 30, bottom: 60, left: 70 }
})
.createData({ id: "cars", values: rows })
.createScatterPlot({
id: "points",
x: "Horsepower",
y: "Miles_per_Gallon",
color: "Origin",
guides: {
axes: {
x: { title: { text: "Horsepower" } },
y: { title: { text: "Miles per Gallon" } }
}
}
});
const context = document.querySelector("#chart").getContext("2d");
render(program, context);
```
## What each stage establishes
| Stage | Semantic result | Graphical result |
| --- | --- | --- |
| `createCanvas` | — | Canvas dimensions and background |
| `createData` | Immutable named rows | — |
| `createScatterPlot` | Point layer, x/y/color encodings, scales, Cartesian coordinate, and guides | Concrete circles plus grid/axis lines, ticks, labels, and titles |
`createScatterPlot` calls `createPointMark`, the requested encoding actions,
and `createGuides` as wrapped children. Position encodings create the default
`main` Cartesian coordinate before guides are requested. The guide children
read that stored relationship; they do not create or repair coordinates.
## Reassign encodings
The same encoding actions can replace compatible bindings. For a field-sized
variant, omit the earlier constant `encodeRadius` and author the chain without
that conflicting graphical constant:
```javascript
const reassigned = chart()
.createCanvas({ width: 640, height: 400, margin: 30 })
.createData({ id: "cars", values: rows })
.createPointMark({ id: "points" })
.encodeX({ field: "Horsepower" })
.encodeY({ field: "Miles_per_Gallon" })
.encodeColor({ field: "Origin" })
.createGuides({ axes: { x: {}, y: {} } })
.encodeX({ field: "Displacement" })
.encodeY({ field: "Acceleration" })
.encodeColor({ field: "Cylinders" })
.encodeSize({ field: "Weight_in_lbs" })
.encodeShape({ field: "Origin" });
```
The calls reuse the current channel scales, recompute every concrete point and
connected guide, update inferred titles, and preserve explicit guide styling.
`encodeSize` remains incompatible with a stored constant radius; it does not
silently delete that graphical choice.
## Continuous appearance variants
Use a quantitative field type to map point color continuously, then request its
gradient legend:
```javascript
const continuousColor = chart()
.createCanvas({
width: 760,
height: 400,
margin: { top: 30, right: 150, bottom: 60, left: 70 }
})
.createData({ id: "cars", values: rows })
.createPointMark({ id: "points" })
.encodeX({ field: "Horsepower" })
.encodeY({ field: "Miles_per_Gallon" })
.encodeColor({ field: "Acceleration", fieldType: "quantitative" })
.encodeRadius({ value: 3 })
.createGuides({ legend: { channels: ["color"] } });
```
Field opacity uses the same assignment style and automatically maps its domain
to `[0.2, 1]`:
```javascript
const fieldOpacity = chart()
.createCanvas({
width: 760,
height: 400,
margin: { top: 30, right: 150, bottom: 60, left: 70 }
})
.createData({ id: "cars", values: rows })
.createPointMark({ id: "points" })
.encodeX({ field: "Horsepower" })
.encodeY({ field: "Miles_per_Gallon" })
.encodeRadius({ value: 4 })
.encodeOpacity({ field: "Acceleration" })
.createGuides({ legend: { channels: ["opacity"] } });
```
Both guides materialize concrete graphics and update after compatible scale or
Canvas edits.
## Key action trace
The trace keeps the user flow and meaningful guide decomposition. Primitive
property edits remain available deeper in the same tree.
```text
program
└─ createScatterPlot
├─ createPointMark
├─ encodeX
├─ encodeY
├─ encodeColor
└─ createGuides
├─ createAxes
│ ├─ createXAxis
│ └─ createYAxis
└─ createGrid
```
## Run and continue
- Serve the repository root and open `examples/cars-scatterplot/`.
- View the [complete browser source](https://github.com/ggaction/ggaction/blob/main/examples/cars-scatterplot/main.js).
- Continue with [Encodings](../api/encodings.md),
[Guides](../api/guides.md), and the
[Basic Chart contract](../api/basic-charts.md#createscatterplot).
# Polar Point Chart Tutorial
This tutorial maps car acceleration to angle, horsepower to distance from the
center, and origin to color. The public angle unit is degrees: `0` points to 12
o'clock and positive angles move clockwise.
Start with the Vite project from [Getting Started](../getting-started.md), then
place the tutorial dataset in Vite's public directory:
```bash
mkdir -p public
curl --fail --location https://raw.githubusercontent.com/ggaction/ggaction/main/data/cars.json --output public/cars.json
```
## Complete program
```javascript
import { chart, render } from "ggaction";
const response = await fetch("/cars.json");
if (!response.ok) throw new Error(`Failed to load cars: ${response.status}`);
const cars = await response.json();
const rows = cars.filter(row =>
Number.isFinite(row.Acceleration) &&
Number.isFinite(row.Horsepower) &&
typeof row.Origin === "string" &&
row.Origin.length > 0
);
const program = chart()
.createCanvas({ width: 520, height: 520, margin: 48 })
.createData({ values: rows })
.createPointMark()
.encodeTheta({ field: "Acceleration" })
.encodeR({ field: "Horsepower" })
.encodeColor({ field: "Origin" })
.encodePointRadius({ value: 3 });
render(program, document.querySelector("canvas").getContext("2d"));
```
`encodeTheta` and `encodeR` infer the current point mark, create the `polar`
coordinate, and create scales named `theta` and `radius`. You can call them in
either order. Until both channels exist, the semantic assignment is retained
but no visible point position is produced.
## Scale control
Theta uses `[0, 360]` by default. An explicit range may be reversed, but its
absolute span cannot exceed 360 degrees:
```javascript
const reversed = program.editScale({ id: "theta", reverse: true });
```
Radius uses `[0, min(plot width, plot height) / 2]` by default. Explicit radial
ranges are logical Canvas pixels, must be non-negative, and must fit the current
plot bounds. Canvas size and margin edits recompute automatic radial ranges.
```javascript
const zoomed = program.editScale({
id: "radius",
domain: [40, 240],
range: [12, 200]
});
```
`encodePointRadius` controls glyph size. It is an alias of `encodeRadius` and is
separate from semantic radial position in `encodeR`.
## Add Polar guides
Call `createGuides()` after both position encodings to infer theta/radius axes
and grids:
```javascript
const guided = chart()
.createCanvas({ width: 620, height: 620, margin: 78 })
.createData({ values: rows })
.createPointMark({ opacity: 0.78 })
.encodeTheta({ field: "Acceleration" })
.encodeR({ field: "Horsepower", scale: { zero: true } })
.encodeColor({ field: "Origin" })
.encodePointRadius({ value: 3 })
.createGuides();
```
The default radial axis points right at `90` degrees. Theta spokes and radial
circles draw behind the points; both axes draw above them. Titles are inferred
from the encoded fields. Use `editRadialAxis({ angle: 180 })` to move the whole
radial axis together, or a focused edit such as
`editThetaAxisLabels({ fontSize: 12 })`.
## Current boundary
Polar point marks support ordinary color, size, shape, opacity, filtering,
selection, highlighting, theta/radius axes, and both Polar grid families.
Polar lines and closed radar paths use the same position and guide system; arc
marks remain outside this slice.
## Related
[Position encodings](../api/position-encodings.md) ·
[Polar lines](./polar-lines.md) · [Coordinates](../api/coordinates.md) ·
[Scale options](../api/scales.md)
# Polar Lines and Radar Tutorial
Polar line marks use the same `encodeTheta` and `encodeR` actions as Polar
points. The line stays open by default; `closed: true` turns each grouped series
into a radar path.
## Open Polar trends
This chart orders years around a partial circle and maps life expectancy to
radial distance.
Start with the Vite project from [Getting Started](../getting-started.md), then
place the tutorial dataset in Vite's public directory:
```bash
mkdir -p public
curl --fail --location https://raw.githubusercontent.com/ggaction/ggaction/main/data/gapminder.json --output public/gapminder.json
```
## Complete program
```javascript
import { chart, render } from "ggaction";
const response = await fetch("/gapminder.json");
if (!response.ok) throw new Error(`Failed to load gapminder data: ${response.status}`);
const gapminder = await response.json();
const countries = ["India", "Japan", "South Africa"];
const rows = gapminder.filter(row =>
countries.includes(row.country) &&
row.year >= 1955 && row.year <= 2005 &&
Number.isFinite(row.life_expect)
);
const program = chart()
.createCanvas({
width: 760,
height: 620,
margin: { top: 70, right: 190, bottom: 70, left: 70 }
})
.createData({ values: rows })
.createLineMark({ strokeWidth: 2.5, opacity: 0.88 })
.encodeTheta({
field: "year",
scale: { domain: [1955, 2005], range: [0, 330] }
})
.encodeR({
field: "life_expect",
scale: { domain: [25, 85], zero: false }
})
.encodeGroup({ field: "country" })
.encodeColor({ field: "country", palette: "tableau10" })
.createGuides({
axes: {
theta: { title: { text: "Year" } },
radius: { title: { text: "Life expectancy" } }
},
legend: { position: "right" }
});
render(program, document.querySelector("canvas").getContext("2d"));
```
The theta and radius actions are order-independent. With only one of them, the
program retains the semantic assignment but does not create a path. Grouping
creates one path per country; color, dash, legends, filtering, selection, and
highlighting all operate on those final series.
## Closed radar paths
The radar chart uses nominal theta categories and normalized radial values:
```javascript
const radar = chart()
.createCanvas({
width: 820,
height: 650,
margin: { top: 90, right: 190, bottom: 90, left: 90 }
})
.createData({ values: radarRows })
.createLineMark({ closed: true, strokeWidth: 2.5, opacity: 0.9 })
.encodeTheta({
field: "role",
fieldType: "nominal",
scale: { domain: roleOrder }
})
.encodeR({ field: "share", scale: { domain: [0, 1], zero: true } })
.encodeGroup({ field: "sex" })
.encodeColor({ field: "sex", palette: "tableau10" })
.createGuides({
axes: {
theta: { title: { text: "Occupation" } },
radius: { title: { text: "Share" } }
},
legend: { position: "right", title: "Sex" }
});
```
`closed: true` appends one backend-neutral `Z` command to each series. It does
not duplicate the first row. You can switch later with
`editLineMark({ closed: false })`. Polar lines currently support linear
interpolation only; Cartesian lines retain the complete curve vocabulary.
## Related
[Polar points](./polar-points.md) ·
[Line and area marks](../api/marks/line-area.md) ·
[Position encodings](../api/position-encodings.md) ·
[Polar guides](./polar-points.md#add-polar-guides)
# Polar Arc Tutorial
Arc marks turn Polar positions into closed sector paths. Use count or weighted
sum aggregation for proportional donut sectors, or combine categorical theta
bands with a quantitative radius for rose charts and radial bars.
## Count a category into a donut
The shortest donut flow needs a dataset, an arc mark with a nonzero inner
radius, a count-based theta encoding, and color:
Start with the Vite project from [Getting Started](../getting-started.md), then
place the tutorial dataset in Vite's public directory:
```bash
mkdir -p public
curl --fail --location https://raw.githubusercontent.com/ggaction/ggaction/main/data/cars.json --output public/cars.json
```
## Complete program
```javascript
import { chart, render } from "ggaction";
const response = await fetch("/cars.json");
if (!response.ok) throw new Error(`Failed to load cars: ${response.status}`);
const cars = await response.json();
const program = chart()
.createCanvas({
width: 640,
height: 500,
margin: { top: 55, right: 190, bottom: 55, left: 55 }
})
.createData({ values: cars })
.createArcMark({ innerRadius: 0.56, padAngle: 1.5 })
.encodeTheta({ field: "Origin", aggregate: "count" })
.encodeColor({ field: "Origin", palette: "tableau10" })
.createGuides({
axes: false,
grid: false,
legend: { position: "right", title: "Origin" }
});
render(program, document.querySelector("canvas").getContext("2d"));
```
`aggregate: "count"` assigns one sector to each category and makes its angular
sweep proportional to the category count. The omitted radius encoding uses the
available Polar radius. `innerRadius` is a fraction of that available radius.
## Sum a field into weighted sectors
Use `aggregate: "sum"` when each source row contributes a numeric weight rather
than one count. This example filters Gapminder to one year and partitions the
donut by the total population in each cluster:
```javascript
const clusterOrder = [0, 1, 2, 3, 4, 5];
const populationByCluster = chart()
.createCanvas({
width: 680,
height: 520,
margin: { top: 65, right: 200, bottom: 55, left: 55 }
})
.createData({ values: gapminder.filter(row => row.year === 2005) })
.createArcMark({ innerRadius: 0.5, padAngle: 1.25 })
.encodeTheta({
field: "cluster",
fieldType: "nominal",
aggregate: "sum",
weight: "pop",
scale: { domain: clusterOrder }
})
.encodeColor({
field: "cluster",
fieldType: "nominal",
scale: { domain: clusterOrder }
})
.createGuides({ axes: false, grid: false, legend: { title: "Cluster" } });
```
Repeated categories and fractional weights are valid. Every weight must be a
non-negative finite number, and the total must be positive. Invalid input fails
before semantic state or trace changes; source rows are never expanded.
## Rose overlays
A rose chart uses one equal theta band per month and overlays the three causes
inside that band. The following fragment continues from an imported `chart`
function and a loaded `nightingaleRows` array containing one row per month and
cause.
```javascript
const monthOrder = [
"April", "May", "June", "July", "August", "September",
"October", "November", "December", "January", "February", "March"
];
const causeOrder = [
"Zymotic Diseases", "Other Causes", "Wounds & Injuries"
];
const rose = chart()
.createCanvas({
width: 780,
height: 640,
margin: { top: 80, right: 210, bottom: 80, left: 80 }
})
.createData({ values: nightingaleRows })
.createArcMark({ padAngle: 1, opacity: 0.9, strokeWidth: 0.5 })
.encodeTheta({
field: "month",
fieldType: "ordinal",
scale: { domain: monthOrder }
})
.encodeR({ field: "value", scale: { domain: [0, 6.5], zero: true } })
.encodeColor({
field: "cause",
layout: "overlay",
scale: {
domain: causeOrder,
range: ["#599ad3", "#727272", "#f1595f"]
}
})
.createGuides({
axes: {
theta: { title: false },
radius: {
ticksAndLabels: { values: [2, 4, 6] },
title: { text: "Mortality rate", position: "inside" }
}
},
grid: { theta: false, radial: { values: [2, 4, 6] } },
legend: { position: "right", title: "Cause" }
});
```
`layout: "overlay"` is explicit because multiple rows occupy the same theta
band. Larger sectors render first so smaller values remain visible. Values that
do not extend beyond the radial baseline produce no placeholder path.
## Radial bars
Radial bars use the same position pair without an overlay group. This fragment
assumes `chart`, the selected `countryRows`, and their explicit `countryOrder`
are already available:
```javascript
const radialBars = chart()
.createCanvas({
width: 780,
height: 640,
margin: { top: 75, right: 190, bottom: 75, left: 75 }
})
.createData({ values: countryRows })
.createArcMark({ innerRadius: 0.18, padAngle: 2, opacity: 0.94 })
.encodeTheta({
field: "country",
fieldType: "nominal",
scale: { domain: countryOrder }
})
.encodeR({
field: "life_expect",
scale: { domain: [45, 85], zero: false }
})
.encodeColor({ field: "cluster", fieldType: "nominal", palette: "tableau10" })
.createGuides({
axes: {
theta: { title: { text: "Country" } },
radius: {
ticksAndLabels: { values: [50, 60, 70, 80] },
title: { text: "Life expectancy", position: "inside" }
}
},
grid: { theta: false, radial: { values: [50, 60, 70, 80] } },
legend: { position: "right", title: "Cluster" }
});
```
The resolved radius scale begins at the arc's inner radius. The radial axis
uses that same baseline instead of drawing through the empty center.
## Editing an arc
`editArcMark` updates geometry or appearance and rematerializes every sector:
```javascript
const tighter = radialBars.editArcMark({
innerRadius: 0.24,
padAngle: 1,
opacity: 0.8
});
```
## Related
[Arc mark reference](../api/marks/line-area.md#arc-marks) ·
[Position encodings](../api/position-encodings.md) ·
[Polar guides](./polar-points.md#add-polar-guides)
# Cars Line Chart Tutorial

This chart shows mean acceleration over time for each origin. The complete
module below uses the public npm package. The repository also contains a
[runnable browser example](https://github.com/ggaction/ggaction/tree/main/examples/cars-line-chart)
and its [complete program](https://github.com/ggaction/ggaction/blob/main/examples/cars-line-chart/program.js).
Start with the Vite project from [Getting Started](../getting-started.md), then
place the tutorial dataset in Vite's public directory:
```bash
mkdir -p public
curl --fail --location https://raw.githubusercontent.com/ggaction/ggaction/main/data/cars.json --output public/cars.json
```
## Complete program
```javascript
import { chart, render } from "ggaction";
const response = await fetch("/cars.json");
if (!response.ok) throw new Error(`Failed to load cars: ${response.status}`);
const cars = await response.json();
const rows = cars.filter(
car =>
typeof car.Year === "string" &&
Number.isFinite(Date.parse(car.Year)) &&
Number.isFinite(car.Acceleration) &&
typeof car.Origin === "string" &&
car.Origin.length > 0
);
const program = chart()
.createCanvas({
width: 720,
height: 460,
margin: { top: 80, right: 170, bottom: 60, left: 80 }
})
.createData({ id: "cars", values: rows })
.createLinePlot({
id: "trends",
x: {
field: "Year",
fieldType: "temporal",
scale: { nice: true }
},
y: {
field: "Acceleration",
aggregate: "mean",
scale: { nice: true, zero: false }
},
color: { field: "Origin", scale: { palette: "tableau10" } },
strokeDash: { field: "Origin" },
guides: { axes: { y: { ticksAndLabels: { count: 6 } } } }
})
.createTitle({
text: "The trend of acceleration by year",
subtitle: "from 1970 to 1982"
});
render(program, document.querySelector("#chart").getContext("2d"));
```
## What the actions establish
| Stage | Semantic result | Graphical result |
| --- | --- | --- |
| `createLinePlot` | Line layer, temporal/aggregate positions, series appearance, scales, and guides | Sorted concrete series paths plus axes, grid, and combined legend |
| `createTitle` | Chart title and subtitle text | Plot-aligned text graphics |
The source rows remain immutable. Aggregation creates derived series values;
it does not replace the dataset. Because color and stroke dash encode the same
field and ordered domain, `createGuides` combines them into one legend.
## Change the curve
Curve is line appearance rather than a semantic field encoding. It can be set
inside the facade:
```javascript
.createLinePlot({
id: "trends",
x: { field: "Year", fieldType: "temporal" },
y: { field: "Acceleration", aggregate: "mean" },
line: { curve: "step" }
})
```
or edited after the complete chart exists:
```javascript
const smooth = program.editLineMark({
curve: "monotone",
strokeWidth: 4
});
```
Both forms regenerate backend-neutral path commands. The stored x/y fields,
mean aggregation, grouping, scales, axes, and legend remain unchanged.
## Change the dash assignment
Use named styles in a field scale when each series needs its own pattern:
```javascript
const named = program.encodeStrokeDash({
field: "Origin",
scale: { range: ["solid", "dashed", "dotted"] }
});
```
Or replace the field mapping with one constant pattern:
```javascript
const dotted = named.encodeStrokeDash({ value: "dotted" });
```
The second call removes stroke dash from the categorical legend while keeping
any remaining color component. It also preserves the old named scale as an
immutable semantic resource.
## Key action trace
Aggregate and series actions explicitly rematerialize the path; the renderer
does not infer those relationships later.
```text
program
├─ createLinePlot
│ ├─ createLineMark
│ ├─ encodeX
│ ├─ encodeY
│ │ └─ rematerializeLineMark
│ ├─ encodeColor
│ │ └─ rematerializeLineMark
│ ├─ encodeStrokeDash
│ │ └─ rematerializeLineMark
│ └─ createGuides
│ ├─ createAxes
│ ├─ createGrid
│ └─ createLegend
└─ createTitle
```
## Run and continue
- Serve the repository root and open `examples/cars-line-chart/`.
- View the [complete chart program](https://github.com/ggaction/ggaction/blob/main/examples/cars-line-chart/program.js).
- Continue with [Encodings](../api/encodings.md),
[Guides](../api/guides.md), [Titles](../api/titles.md), and the
[Basic Chart contract](../api/basic-charts.md#createlineplot).
# Cars Histogram Tutorial

This chart bins car displacement, counts the rows in each bin, and stacks those
counts by origin. The complete module uses the public npm package. The
repository also contains a
[runnable browser example](https://github.com/ggaction/ggaction/tree/main/examples/cars-histogram)
and its [complete program](https://github.com/ggaction/ggaction/blob/main/examples/cars-histogram/program.js).
Start with the Vite project from [Getting Started](../getting-started.md), then
place the tutorial dataset in Vite's public directory:
```bash
mkdir -p public
curl --fail --location https://raw.githubusercontent.com/ggaction/ggaction/main/data/cars.json --output public/cars.json
```
## Complete program
```javascript
import { chart, render } from "ggaction";
const response = await fetch("/cars.json");
if (!response.ok) throw new Error(`Failed to load cars: ${response.status}`);
const cars = await response.json();
const rows = cars.filter(
car =>
Number.isFinite(car.Displacement) &&
typeof car.Origin === "string" &&
car.Origin.length > 0
);
const program = chart()
.createCanvas({
width: 432,
height: 460,
margin: { top: 80, right: 60, bottom: 130, left: 80 }
})
.createData({ id: "cars", values: rows })
.createHistogram({
id: "bars",
field: "Displacement",
maxBins: 10,
xScale: { nice: true, zero: false },
color: { field: "Origin", scale: { palette: "tableau10" } },
guides: { legend: { position: "bottom" } }
})
.createTitle({
text: "Displacement distribution",
subtitle: "by country",
align: "center"
});
render(program, document.querySelector("#chart").getContext("2d"));
```
## What the actions establish
| Stage | Semantic result | Graphical result |
| --- | --- | --- |
| `createHistogram` | Bar layer, atomic bin/count positions, nominal stack identity, scales, and guides | Category-colored bin rects plus bin-aligned axes, grid, and bottom legend |
| `createTitle` | Chart title and subtitle text | Plot-centered title graphics |
The facade calls atomic `encodeHistogram` because its x binning and y
count/stack meaning are interdependent. That action still exposes wrapped
`encodeX` and `encodeY` children. The source dataset remains unchanged; bin
counts and stacked geometry are derived and materialized separately.
Use `maxBins` for an inferred nice partition, `binStep` for an exact
zero-anchored width, or `binBoundaries` for explicitly authored irregular
intervals. These options are mutually exclusive. Calling `encodeHistogram`
again replaces the complete bin/count assignment and refreshes inferred axes
and grids while preserving compatible color grouping and legends.
To compare proportions instead of absolute counts, set the color layout to
`"fill"`. It invokes normalized y stacking and produces a `[0, 1]` axis:
```javascript
program.encodeColor({ field: "Origin", layout: "fill" });
```
Legends default to the right for every supported chart. This example passes
`position: "bottom"` because its horizontal layout is intentional.
## Key action trace
The atomic histogram action exposes its interdependent position actions as
children, while guide selection remains a separate aggregate.
```text
program
├─ createHistogram
│ ├─ createBarMark
│ ├─ encodeHistogram
│ │ ├─ encodeX
│ │ └─ encodeY
│ │ └─ rematerializeBarMark
│ ├─ encodeColor
│ │ └─ rematerializeBarMark
│ └─ createGuides
│ ├─ createAxes
│ ├─ createGrid
│ └─ createLegend
└─ createTitle
```
## Run and continue
- Serve the repository root and open `examples/cars-histogram/`.
- View the [complete chart program](https://github.com/ggaction/ggaction/blob/main/examples/cars-histogram/program.js).
- Continue with [Encodings](../api/encodings.md),
[Guides](../api/guides.md), [Titles](../api/titles.md), and the
[Basic Chart contract](../api/basic-charts.md#createhistogram).
# Bar Chart Tutorial

This tutorial uses a grouped bar chart as one concrete bar-chart layout. It
aggregates job percentages by year and places the mean for men and women side
by side. The complete module uses the public npm package. The repository also contains a
[runnable browser example](https://github.com/ggaction/ggaction/tree/main/examples/jobs-grouped-bar)
and its [complete program](https://github.com/ggaction/ggaction/blob/main/examples/jobs-grouped-bar/program.js).
Start with the Vite project from [Getting Started](../getting-started.md), then
place the tutorial dataset in Vite's public directory:
```bash
mkdir -p public
curl --fail --location https://raw.githubusercontent.com/ggaction/ggaction/main/data/jobs.json --output public/jobs.json
```
## Complete program
```javascript
import { chart, render } from "ggaction";
const response = await fetch("/jobs.json");
if (!response.ok) throw new Error(`Failed to load jobs: ${response.status}`);
const jobs = await response.json();
const rows = jobs.filter(
row =>
Number.isFinite(row.year) &&
Number.isFinite(row.perc) &&
typeof row.sex === "string" &&
row.sex.length > 0
);
const program = chart()
.createCanvas({
width: 720,
height: 460,
margin: { top: 40, right: 140, bottom: 70, left: 80 }
})
.createData({ id: "jobs", values: rows })
.createBarPlot({
id: "bars",
x: { field: "year", fieldType: "ordinal" },
y: {
field: "perc",
aggregate: "mean",
scale: { nice: true, zero: false }
},
color: {
field: "sex",
layout: "group",
scale: { palette: "tableau10" }
},
width: { band: 0.72 }
});
render(program, document.querySelector("#chart").getContext("2d"));
```
## What the actions establish
| Stage | Semantic result | Graphical result |
| --- | --- | --- |
| `createBarPlot` | Bar layer, ordinal/aggregate positions, grouped color and guides | Concrete grouped rectangles plus ordinal/linear axes, grid, and legend |
`layout: "group"` is the atomic grouping choice. It keeps y unstacked and
invokes the advanced xOffset encoding for the same field. `encodeBarWidth`
then has enough stored information to materialize one concrete rectangle for
each observed year/sex cell. Missing cells are omitted.
## Width, spacing, and reassignment
Use exactly one width mode. Band width follows a resized Canvas; logical pixels
stay fixed and are independent of PNG output density.
```javascript
program.encodeBarWidth({ pixels: 14 });
```
Advanced offset padding changes slot bandwidth while retaining each year band:
```javascript
program.encodeXOffset({
field: "sex",
paddingInner: 0.2,
paddingOuter: 0.1
});
```
To replace the grouped field, call color once. It replaces color and xOffset
together and refreshes an existing legend:
```javascript
program.encodeColor({ field: "job", layout: "group" });
```
## Key action trace
Grouped color owns the matching offset encoding. Width is the final graphical
decision that makes concrete grouped rectangles possible.
```text
program
└─ createBarPlot
├─ createBarMark
├─ encodeX
├─ encodeY
├─ encodeColor
│ ├─ encodeXOffset
│ └─ rematerializeBarMark
├─ encodeBarWidth
│ └─ rematerializeBarMark
└─ createGuides
├─ createAxes
├─ createGrid
└─ createLegend
```
## Run and continue
- Serve the repository root and open `examples/jobs-grouped-bar/`.
- View the [complete chart program](https://github.com/ggaction/ggaction/blob/main/examples/jobs-grouped-bar/program.js).
- Continue with [Position encodings](../api/position-encodings.md),
[Series encodings](../api/series-encodings.md), and
[Constant appearance](../api/appearance.md), or review the
[Basic Chart contract](../api/basic-charts.md#createbarplot).
# Regression Scatterplot Tutorial

This tutorial filters the cars dataset to Japan and the USA, maps point size to
Acceleration and point color/shape to Origin, then adds one linear fit and 95%
mean-response confidence band per Origin. The complete module uses the public
npm package. The repository also contains a
[runnable browser example](https://github.com/ggaction/ggaction/tree/main/examples/cars-regression-scatterplot)
and its [complete program](https://github.com/ggaction/ggaction/blob/main/examples/cars-regression-scatterplot/program.js).
Start with the Vite project from [Getting Started](../getting-started.md), then
place the tutorial dataset in Vite's public directory:
```bash
mkdir -p public
curl --fail --location https://raw.githubusercontent.com/ggaction/ggaction/main/data/cars.json --output public/cars.json
```
## Complete program
```javascript
import { chart, render } from "ggaction";
const response = await fetch("/cars.json");
if (!response.ok) throw new Error(`Failed to load cars: ${response.status}`);
const cars = await response.json();
const program = chart()
.createCanvas({
width: 760,
height: 480,
margin: { top: 40, right: 190, bottom: 70, left: 80 }
})
.createData({ id: "cars", values: cars })
.createPointMark({ id: "points" })
.encodeX({
field: "Displacement",
scale: { nice: true, zero: false }
})
.encodeY({
field: "Acceleration",
scale: { nice: true, zero: false }
})
.encodeColor({
field: "Origin",
scale: { palette: "tableau10" }
})
.encodeSize({ field: "Acceleration" })
.encodeShape({ field: "Origin" })
.encodeOpacity({ value: 0.27 })
.filterMarks({
field: "Origin",
op: "oneOf",
values: ["Japan", "USA"]
})
.createRegression({
confidence: 0.95,
band: { color: "#111111", opacity: 0.18 },
line: { strokeWidth: 3 }
})
.createGuides();
render(program, document.querySelector("#chart").getContext("2d"));
```
## What the actions establish
| Stage | Semantic result | Graphical result |
| --- | --- | --- |
| `filterMarks` | A namespaced immutable dataset derived from `cars`; `points` is rebound | Point scales and geometry are rematerialized |
| point encodings | Shared x/y, color, size, and shape scales | Typed circles and squares with fixed opacity |
| `createRegression` | Grouped linear predictions and three linked layers | Two filled bands and two stroked paths |
| `createGuides` | Shared axes, grid, Origin legend, and size legend | One guide set for all compatible layers |
The shortest `createRegression()` call infers its target from the current point
mark, x/y from its quantitative encodings, and grouping from the unique nominal
field shared by color and shape. It fails when those choices are ambiguous.
The default interval describes uncertainty around the fitted mean response.
Use `interval: "prediction"` for individual observations, or select polynomial
and LOESS fits through the same action.
## Key action trace
High-level actions retain their wrapped children, so the regression remains
inspectable without exposing its internal IDs in the user program.
```text
program
├─ createPointMark
├─ encodeSize
├─ encodeShape
├─ filterMarks
│ ├─ createDerivedData
│ ├─ materializeMarkFilteredData
│ ├─ editSemantic(points.data)
│ ├─ rematerializeScale
│ └─ rematerializePointMark
├─ createRegression
│ ├─ createRegressionData
│ ├─ createRegressionBand
│ │ └─ createErrorBand
│ └─ createRegressionLine
└─ createGuides
├─ createAxes
├─ createGrid
└─ createLegend
```
## Run and continue
- Serve the repository root and open `examples/cars-regression-scatterplot/`.
- Read the [regression API](../api/regression.md) for inference and options.
- Use [Appearance encodings](../api/appearance.md) to customize point size,
shape, and opacity.
- Export the same immutable program with
[PNG rendering](../api/rendering.md#png-output).
# Density Area Chart Tutorial

This chart estimates the distribution of car acceleration separately for each
Origin. The source rows remain immutable; `encodeDensity` creates a named
derived dataset and materializes one translucent, zero-baseline area per
group. The complete module below uses the public npm package. The repository
also contains a
[runnable browser example](https://github.com/ggaction/ggaction/tree/main/examples/cars-density-area)
and its [complete program](https://github.com/ggaction/ggaction/blob/main/examples/cars-density-area/program.js).
Start with the Vite project from [Getting Started](../getting-started.md), then
place the tutorial dataset in Vite's public directory:
```bash
mkdir -p public
curl --fail --location https://raw.githubusercontent.com/ggaction/ggaction/main/data/cars.json --output public/cars.json
```
## Complete program
```javascript
import { chart, render } from "ggaction";
const response = await fetch("/cars.json");
if (!response.ok) throw new Error(`Failed to load cars: ${response.status}`);
const cars = await response.json();
const program = chart()
.createCanvas({
width: 720,
height: 500,
margin: { top: 130, right: 40, bottom: 70, left: 80 }
})
.createData({ id: "cars", values: cars })
.createAreaMark({ id: "densities", opacity: 0.5 })
.encodeDensity({
field: "Acceleration",
groupBy: "Origin",
bandwidth: 0.6
})
.encodeColor({
field: "Origin",
scale: { palette: "tableau10" }
})
.createGuides({
grid: { horizontal: {}, vertical: {} },
legend: {
position: "top",
direction: "vertical",
columns: 3,
titlePosition: "left",
offset: 8
}
})
.createTitle({
text: "Distribution of Acceleration",
subtitle: "By Origin (cars dataset)"
});
render(program, document.querySelector("#chart").getContext("2d"));
```
## What the actions establish
| Stage | Semantic result | Graphical result |
| --- | --- | --- |
| `createAreaMark` | Area layer initially bound to `cars` | Empty path collection with opacity `0.5` |
| `encodeDensity` | KDE transform, derived data, x/y and group encodings | Three sorted, baseline-closed command paths |
| `encodeColor` | Origin ordinal color scale | Tableau fills in first-appearance order |
| `createGuides` | Shared axes, two grids, color legend | Grid behind paths; axes and top swatches above them |
| `createTitle` | Chart title and subtitle | Plot-aligned text above the non-overlapping legend |
The density axis defaults to y and includes zero. The value axis preserves the
shared observed extent. Set `densityChannel: "x"` to reverse the orientation;
the axis titles and baseline orientation follow automatically.
The default estimate uses the Gaussian kernel with unit normalization. Use
`kernel` and `normalization` on `encodeDensity`, or call `editDensity` later to
create and bind an immutable revised estimate while preserving the source.
`direction` controls how legend items fill a multi-row grid, while `columns`
sets its maximum column count. `titlePosition: "left"` places `Origin` beside
the three items without changing the chart-wide right-side legend default.
## Move and wrap the title
The title is an independently editable resource. This optional continuation
uses the `program` created above and returns a new program without changing it:
```javascript
const bottomTitleProgram = program
.editCanvas({
height: 620,
margin: { top: 130, right: 40, bottom: 190, left: 80 }
})
.editTitle({
text: "Distribution of Acceleration Across Vehicle Origins",
subtitle: "Kernel density estimates for acceleration, grouped by origin in the cars dataset",
position: "bottom",
align: "center",
offset: 60,
gap: 12,
maxWidth: 270,
wrap: "word",
lineHeight: 26
});
render(bottomTitleProgram, document.querySelector("#chart").getContext("2d"));
```
The title action resolves deterministic lines before rendering. See
[Titles](../api/titles.md) for all four positions, character wrapping, style
editing, and subtitle removal.
## Key action trace
The atomic encoding exposes every reusable action it delegates to.
```text
program
├─ createAreaMark
├─ encodeDensity
│ ├─ createDensityData
│ ├─ editSemantic
│ ├─ encodeX
│ ├─ encodeY
│ ├─ encodeGroup
│ └─ rematerializeAreaMark
├─ encodeColor
│ └─ rematerializeAreaMark
├─ createGuides
│ ├─ createAxes
│ ├─ createGrid
│ └─ createLegend
└─ createTitle
```
## Run and continue
- Serve the repository root and open `examples/cars-density-area/`.
- View the [complete chart program](https://github.com/ggaction/ggaction/blob/main/examples/cars-density-area/program.js).
- Continue with [Encodings](../api/encodings.md),
[Legends](../api/legends.md), and [Scale options](../api/scales.md).
# Horizon Chart Tutorial

This chart folds Kenya's life expectancy above and below a 55-year baseline
into three compact bands. The source rows stay immutable. `encodeHorizon`
creates one derived dataset and ordinary closed area paths, so Browser Canvas
and Node PNG use the same backend-neutral graphics.
The repository contains a
[runnable browser example](https://github.com/ggaction/ggaction/tree/main/examples/gapminder-horizon)
and its [complete program](https://github.com/ggaction/ggaction/blob/main/examples/gapminder-horizon/program.js).
## Complete program
```javascript
import { chart, render } from "ggaction";
const response = await fetch("/gapminder.json");
if (!response.ok) throw new Error(`Failed to load data: ${response.status}`);
const gapminder = await response.json();
const program = chart()
.createCanvas({
width: 760,
height: 300,
margin: { top: 78, right: 30, bottom: 58, left: 50 }
})
.createData({ values: gapminder })
.filterData({ id: "kenya", field: "country", oneOf: ["Kenya"] })
.createAreaMark({ curve: "monotone" })
.encodeHorizon({
x: "year",
y: "life_expect",
bands: 3,
baseline: 55,
palette: { positive: "blues", negative: "reds" }
})
.createGuides({ axes: { y: false } })
.createTitle({
text: "Kenya Life Expectancy",
subtitle: "Blue above, red below · three folded bands around 55 years"
});
render(program, document.querySelector("#chart").getContext("2d"));
```
## What the atomic encoding owns
| Decision | Result |
| --- | --- |
| `baseline: 55` | Values are represented as signed deviations from 55 |
| `bands: 3` | Each sign is divided into three equal-amplitude folds |
| `extent: "auto"` | The largest absolute deviation defines the shared band height |
| `missing: "break"` | Missing y values split, rather than bridge, a path |
| `overflow: "clip"` | An explicit smaller extent clips excess amplitude |
The folded y scale is always `[0, 1]`. Because its ticks would describe folded
amplitude rather than the original life-expectancy values, Horizon guide policy
creates only the original x axis and grid and no automatic legend.
## Revise without mutation
```javascript
const revised = program.editHorizon({
bands: 4,
baseline: 58,
palette: { positive: "teals" }
});
```
The revision keeps the source dataset and scale IDs, creates a new namespaced
derived dataset, rebinds the same area layer, and removes the old revision only
when no layer still references it. The earlier `program` remains unchanged.
## Key action trace
```text
program
├─ createAreaMark
├─ encodeHorizon
│ ├─ createHorizonData
│ │ └─ materializeHorizonData
│ ├─ rebindLayerData
│ ├─ encodeX
│ ├─ encodeY
│ ├─ encodeGroup
│ ├─ encodeY2
│ └─ encodeColor
├─ createGuides
└─ createTitle
```
Continue with [Encodings](../api/encodings.md#atomic-horizon),
[Area marks](../api/marks/line-area.md), and [Faceting](../api/composition.md).
# Error Bar Chart Tutorial

This chart keeps individual car observations visible while summarizing
acceleration by Origin. `createErrorBar()` infers the encoded point layer,
derives one mean and two-sided 95% Student-t confidence interval per group,
then creates concrete vertical rules and fixed-width caps. The source rows remain immutable.
The repository contains a
[runnable browser example](https://github.com/ggaction/ggaction/tree/main/examples/cars-error-bar)
and its [complete program](https://github.com/ggaction/ggaction/blob/main/examples/cars-error-bar/program.js).
Start with the Vite project from [Getting Started](../getting-started.md), then
place the tutorial dataset in Vite's public directory:
```bash
mkdir -p public
curl --fail --location https://raw.githubusercontent.com/ggaction/ggaction/main/data/cars.json --output public/cars.json
```
## Complete program
```javascript
import { chart, render } from "ggaction";
const response = await fetch("/cars.json");
if (!response.ok) throw new Error(`Failed to load cars: ${response.status}`);
const cars = await response.json();
const program = chart()
.createCanvas({
width: 720,
height: 460,
margin: { top: 90, right: 40, bottom: 70, left: 80 }
})
.createData({ values: cars })
.createPointMark()
.encodeX({ field: "Origin", fieldType: "ordinal" })
.encodeY({ field: "Acceleration" })
.encodeColor({ field: "Origin" })
.encodeRadius({ value: 3 })
.encodeOpacity({ value: 0.18 })
.createErrorBar()
.createGuides()
.createTitle({
text: "Acceleration by Origin",
subtitle: "Observations and 95% mean confidence intervals"
});
render(program, document.querySelector("#chart").getContext("2d"));
```
## What the actions establish
| Stage | Semantic result | Graphical result |
| --- | --- | --- |
| Point encodings | One observation layer grouped by Origin | Translucent colored observations |
| `createErrorBar` | Immutable grouped interval rows and three rule layers | Main intervals and two 8px caps per Origin |
| `createGuides` | Ordinal x, quantitative y, axes, horizontal grid | Grid behind rules and axes above them |
| `createTitle` | Title and subtitle | Plot-aligned text above the chart |
The first dataset ID, error-bar ID, source, coordinate, grouping, statistic,
and interval appearance are inferred from the encoded point layer.
## Add intervals to an existing layer
If a compatible layer already has x and y encodings, the shortest call is
`createErrorBar()`:
```javascript
const overlay = chart()
.createCanvas()
.createData({ values: cars })
.createPointMark()
.encodeX({ field: "Origin", fieldType: "ordinal" })
.encodeY({ field: "Acceleration" })
.createErrorBar();
```
The interval reuses that layer's data, coordinate, and position scales. This
works by encoded-layer capability rather than by a point-specific rule.
## Key action trace
```text
program
├─ createErrorBar
│ ├─ createIntervalData
│ ├─ createRuleMark
│ ├─ encodeX / encodeY / encodeY2
│ ├─ appearance encodings
│ ├─ createErrorBarCap
│ └─ createErrorBarCap
├─ createGuides
└─ createTitle
```
## Run and continue
- Serve the repository root and open `examples/cars-error-bar/`.
- Read the exact [error-bar options](../api/error-bars.md).
- Use [Data](../api/data.md) when interval rows are needed independently.
# Error Band Chart Tutorial

This chart summarizes life expectancy over time for each Gapminder cluster.
`createErrorBand` derives one mean and two-sided 95% Student-t confidence
interval per year and cluster, then creates one closed area path plus explicit
lower and upper boundary paths per cluster.
The repository contains a
[runnable browser example](https://github.com/ggaction/ggaction/tree/main/examples/gapminder-error-band)
and its [complete program](https://github.com/ggaction/ggaction/blob/main/examples/gapminder-error-band/program.js).
Start with the Vite project from [Getting Started](../getting-started.md), then
place the tutorial dataset in Vite's public directory:
```bash
mkdir -p public
curl --fail --location https://raw.githubusercontent.com/ggaction/ggaction/main/data/gapminder.json --output public/gapminder.json
```
## Complete program
```javascript
import { chart, render } from "ggaction";
const response = await fetch("/gapminder.json");
if (!response.ok) throw new Error(`Failed to load gapminder data: ${response.status}`);
const gapminder = await response.json();
const program = chart()
.createCanvas({
width: 760,
height: 480,
margin: { top: 90, right: 150, bottom: 70, left: 80 }
})
.createData({ values: gapminder })
.createErrorBand({
x: { field: "year", fieldType: "temporal" },
y: { field: "life_expect" },
groupBy: "cluster",
curve: "cardinal",
boundaries: {
stroke: "#25364d",
strokeWidth: 1.4,
strokeDash: [6, 3],
opacity: 0.8
}
})
.encodeColor({
target: "errorBand",
field: "cluster",
fieldType: "nominal",
scale: { palette: "tableau10" }
})
.createGuides()
.createTitle({
text: "Life Expectancy by Cluster",
subtitle: "Mean and 95% confidence interval"
});
render(program, document.querySelector("#chart").getContext("2d"));
```
## What the actions establish
| Stage | Semantic result | Graphical result |
| --- | --- | --- |
| `createErrorBand` | Immutable year × cluster interval rows, one area layer, and boundary layers | Six closed bands with explicit lower and upper paths |
| `encodeColor` | Shared nominal cluster scale | One fill per cluster |
| `createGuides` | Temporal x, quantitative y, axes, horizontal grid, legend | Grid behind bands and guides above them |
| `createTitle` | Title and subtitle | Plot-aligned text above the chart |
The dataset ID, error-band ID, coordinate, scales, center, extent, and
confidence level are inferred. The x field, y field, and grouping field are the
only chart-specific decisions in this flow.
## Horizontal bands and boundaries
Place the interval definition on x and the independent position on y to create
a horizontal band. Pass `boundaries: {}` to add lower and upper line paths, or
provide shared stroke, width, dash, opacity, and curve options:
```javascript
program.createErrorBand({
x: { field: "value", extent: "ci" },
y: { field: "date", fieldType: "temporal" },
curve: "cardinal",
boundaries: {
stroke: "#25364d",
strokeWidth: 1.4,
strokeDash: [6, 3],
opacity: 0.8
}
});
```
## Key action trace
```text
program
├─ createErrorBand
│ ├─ createIntervalData
│ ├─ createAreaMark
│ ├─ encodeX
│ ├─ encodeYRange
│ └─ encodeGroup
├─ encodeColor
├─ createGuides
└─ createTitle
```
## Run and continue
- Serve the repository root and open `examples/gapminder-error-band/`.
- Read the exact [error-band options](../api/error-bands.md).
- Use [Data](../api/data.md) when interval rows are needed independently.
# Mark Selection and Highlighting Tutorial
Select final visual items by data field, semantic channel, or concrete graphic
property, then filter, reuse, or highlight that selection. The repository
contains a [runnable browser example](https://github.com/ggaction/ggaction/tree/main/examples/mark-selection)
and the [canonical programs](https://github.com/ggaction/ggaction/blob/main/examples/mark-selection/program.js)
used by the acceptance and PNG tests below.
## Highlight grouped maximum points

The selector runs at point-item grain. `groupBy: "Origin"` chooses one maximum
Horsepower row per Origin; styling happens only after the three semantic items
have been selected.
```javascript
program.highlightMarks({
target: "points",
select: {
field: "Horsepower",
op: "max",
groupBy: "Origin"
},
color: "#dc2626",
shape: "diamond",
size: 5.5,
offset: { x: 7, y: -7 },
dimOthers: { opacity: 0.18 }
});
```
`size` is an area multiplier and `offset` uses logical Canvas pixels. The
selected points are placed last by default, while their x/y semantic values
remain unchanged.
## Select a complete histogram stack

For bars, semantic endpoints and concrete dimensions are intentionally
separate. `channel: "y2"` compares the upper semantic endpoint. With
`grain: "stack"`, every colored rectangle attached to the tallest bin is one
selected item.
```javascript
program.highlightMarks({
target: "bars",
select: {
grain: "stack",
channel: "y2",
op: "max"
},
fill: "#facc15",
stroke: "#713f12",
strokeWidth: 2.5
});
```
Use `{ property: "height", op: "max" }` only when the intended comparison is
the rendered pixel height. The default item grain would select only the
topmost matching rectangle rather than the complete stack.
## Highlight one line series

A multi-row line is one series item. The selected field must therefore have
one unique value over the complete path.
```javascript
program.highlightMarks({
target: "trends",
select: { field: "Origin", op: "eq", value: "Japan" },
stroke: "#dc2626",
strokeWidth: 5,
strokeDash: "dashed",
dimOthers: { opacity: 0.16 }
});
```
When the selector matches complete categories in the line's categorical
legend, legend symbols receive the same emphasis and dimming. Legend text
remains fully readable.
## Choose filtering, reusable selection, or highlighting
| Action | Stored result | Graphical result |
| --- | --- | --- |
| `filterMarks` | Immutable member-row dataset and explicit mark rebind | Scales, mark, axes, grids, and legend rematerialize |
| `selectMarks` | Reusable normalized selector intent | None until another action uses the selection |
| `highlightMarks` | Selection plus mark-specific appearance assignment | Selected style, optional complement dimming, selected-last order |
Create a reusable selection when more than one later action should refer to the
same items:
```javascript
const selected = program.selectMarks({
id: "japanSeries",
target: "trends",
field: "Origin",
op: "eq",
value: "Japan"
});
const highlighted = selected.highlightMarks({
selection: "japanSeries",
stroke: "#dc2626"
});
```
Selection intent is reevaluated after compatible Canvas, scale, encoding, and
data-cardinality changes. It does not store stale item IDs.
## Run and continue
- Serve the repository root and open `examples/mark-selection/`.
- Read the complete [selector and appearance tables](../api/appearance/selection-and-highlighting.md#mark-selection-and-highlighting).
- Use [`filterMarks`](../api/data/filtering.md#filter-marks) when matching items should replace the target mark's data.
- Use [`editBarMark`](../api/marks/bar.md#edit-bar-mark) for whole-bar appearance rather than selected items.
# Chart Recipes
Use a recipe when you know the chart type and want the shortest supported
action flow. Each recipe separates the decisions you must provide from the
resources, defaults, and guides that ggaction can infer.
Want the reasoning? Use a tutorial for an ordered workflow and explanation of each action.
Choosing a chart? Browse complete results before selecting a minimal recipe.
Every flow begins with `createCanvas`, `createData`, and a semantic mark or
composite action. Add explicit IDs only when the current program contains more
than one compatible resource.
Each recipe labels its primary snippet as runnable. Later snippets revise the
named `program` produced by that primary flow unless they repeat an import and
complete setup explicitly.
# Scatterplot Recipe
## Minimal flow
```javascript
import { chart } from "ggaction";
const program = chart()
.createCanvas()
.createData({ values })
.createScatterPlot({ x: "x", y: "y" });
```
## You must decide
- Dataset values
- Quantitative x and y fields
Add `color: "group"`, `size: "amount"`, or `shape: "category"` to the same
call for field-driven appearance. The default point radius is `3`.
## The library infers
- Current dataset for the facade and current mark for later actions
- Stable internal role IDs for the first dataset and point mark
- Quantitative linear scales named `x` and `y`
- The `main` Cartesian coordinate
- Plot-bound ranges, axes, and horizontal grid
Point color, shape, and size legends are created when those encodings are
present. Multiple compatible marks or scales require explicit `target` or
scale IDs.
## Continue
[Scatterplot tutorial](../tutorials/scatterplot.md) ·
[Basic Charts](../api/basic-charts.md#createscatterplot) ·
[Quantitative positions](../api/position/quantitative.md) ·
[Constant appearance](../api/appearance.md)
# Line Chart Recipe
## Minimal flow
```javascript
import { chart } from "ggaction";
const program = chart()
.createCanvas({ margin: { right: 140 } })
.createData({ values })
.createLinePlot({
x: { field: "date", fieldType: "temporal" },
y: { field: "value", aggregate: "mean" }
});
```
## You must decide
- Temporal x field
- Quantitative y field
- Whether the line is one series or grouped by a nominal field
For multiple series, add `color: "group"`, `groupBy: "group"`, or
`strokeDash: { field: "group" }` to the facade call.
## The library infers
- UTC time x and linear y scales
- Mean aggregation at each x/series group
- Sorted concrete `M/L` path commands
- Linear interpolation unless `line: { curve }` selects another curve
- Axes, horizontal grid, and a right-side categorical legend when applicable
Every materialized series needs at least two points. Reserve right margin for a
legend or pass `createLegend({ position: "bottom" })` with bottom margin.
Use `line: { curve: "step" }` during creation for midpoint steps, or edit an existing
line without changing its encodings:
```javascript
const smooth = program.editLineMark({
curve: "monotone",
strokeWidth: 4
});
```
To overlay a trend on compatible aggregate bars, add the line after the bar
encodings. The line infers the shared fields, scales, and aggregate, so no
second `encodeY` call is needed:
```javascript
const layered = chart()
.createData({ values })
.createBarMark({ id: "bars" })
.encodeX({ field: "date", fieldType: "temporal" })
.encodeY({ field: "value", aggregate: "mean" })
.createLineMark({ id: "trend", strokeWidth: 3 });
```
The accepted curve vocabulary is `linear`, `step`, `step-before`, `step-after`,
`basis`, `cardinal`, `monotone`, and `natural`. The renderer receives only final
`M/L/C` commands and never interprets these names.
## Continue
[Line chart tutorial](../tutorials/line-chart.md) ·
[Basic Charts](../api/basic-charts.md#createlineplot) ·
[Temporal line positions](../api/position/temporal.md) ·
[Series encodings](../api/series-encodings.md)
# Histogram Recipe
## Minimal flow
```javascript
import { chart } from "ggaction";
const program = chart()
.createCanvas({ margin: { right: 140 } })
.createData({ values })
.createHistogram({ field: "value" });
```
## You must decide
- Quantitative field to bin
- Optional `maxBins`
- Optional nominal color field and series layout
Add `color: { field: "group", layout: "stack" }` to the `createHistogram`
options.
Choose `fill`, `group`, `overlay`, or `diverging` when their partition meaning
matches the chart.
## The library infers
- Nice bin boundaries and binned x scale
- Count y encoding, zero stack, and y scale
- Concrete non-empty bin rectangles
- Bin-aligned axes, horizontal grid, and categorical legend when applicable
Legends default to the right. Pass
`guides: { legend: { position: "bottom" } }` for the horizontal layout used by
the public tutorial.
## Continue
[Histogram tutorial](../tutorials/histogram.md) ·
[Basic Charts](../api/basic-charts.md#createhistogram) ·
[Histogram positions](../api/position/histogram.md) ·
[Scale options](../api/scales.md)
# Bar Chart Recipe
The current complete bar-chart example uses grouped bars. Grouping is a layout
choice inside the bar chart, not a separate top-level chart type.
## Minimal grouped flow
```javascript
import { chart } from "ggaction";
const program = chart()
.createCanvas({ margin: { right: 140 } })
.createData({ values })
.createBarPlot({
x: { field: "category", fieldType: "ordinal" },
y: { field: "value", aggregate: "mean" },
color: { field: "group", layout: "group" },
width: { band: 0.72 }
});
```
## You must decide
- Ordinal x field
- Quantitative y field
- Nominal grouping/color field
## The library infers
- First-appearance x and group order
- Mean y at the final x/group grain
- xOffset slots from grouped color layout
- A `0.72` bar-band fraction
- Ordinal/linear axes, horizontal grid, and right-side legend
Missing x/group combinations are omitted. Change `layout` to `stack`, `fill`,
`overlay`, or `diverging` to use the other supported series arrangements.
Use `encodeBarWidth({ pixels: 14 })` for a resize-stable logical width, or
`encodeXOffset({ field: "group", paddingInner: 0.2, paddingOuter: 0.1 })`
after grouped color to change within-band spacing. Reassign the group field
with one `encodeColor({ field: "nextGroup", layout: "group" })` call.
The current API does not synthesize zero bars. For horizontal bars, put the
quantitative measure on x and the ordinal category on y.
## Continue
[Bar chart tutorial](../tutorials/grouped-bar.md) ·
[Basic Charts](../api/basic-charts.md#createbarplot) ·
[Ordinal bar positions](../api/position/ordinal-bars.md) ·
[Ordinal offsets](../api/position/offsets.md)
# Heatmap Recipe
## Pre-gridded flow
```javascript
import { chart } from "ggaction";
const program = chart()
.createCanvas({ margin: { right: 120 } })
.createData({ values: cells })
.createHeatmap({
x: { field: "column", fieldType: "ordinal" },
y: { field: "row", fieldType: "nominal" },
color: {
field: "value",
fieldType: "quantitative",
scale: { type: "sequential", palette: "viridis" }
}
});
```
## Raw-row binned flow
```javascript
import { chart } from "ggaction";
const program = chart()
.createCanvas({ margin: { right: 120 } })
.createData({ values: observations })
.createHeatmap({
x: "weight",
y: "economy",
bin: { bins: { x: 10, y: 8 } },
color: { scale: { palette: "blues" } }
});
```
This mode derives rectangular x/y bounds and a count for each cell. It emits
the complete grid by default, including zero-count cells.
## You must decide
- Whether rows are already at one-row-per-cell grain or require `bin`
- The two position fields
- The color field for pre-gridded rows; binned mode owns its generated count
- Optional bin counts or explicit extents when the defaults are not appropriate
## The library infers
- Band scales for the two discrete positions
- One concrete rectangle per valid observed row
- A categorical or continuous color scale and matching legend
- Cartesian axes and horizontal grid
In binned mode, the library instead infers quantitative position scales from
the resolved bin extents, a continuous count scale, source-facing axis titles,
and a `Count` legend. It disables separate chart grid lines unless requested.
Use `rect: { opacity, stroke, strokeWidth }` for cell appearance. Cell fill is
owned by the required color encoding. To show values, chain a text layer after
the heatmap:
```javascript
const labeled = program
.createTextMark({ align: "center", baseline: "middle" })
.encodeText({ field: "value", format: ".0f" });
```
## Continue
[Basic Charts](../api/basic-charts.md#createheatmap) ·
[Rectangular 2D bins](../api/data/bin2d.md) ·
[Rect marks](../api/marks/rect.md) ·
[Continuous color scales](../api/scales/continuous-color.md)
# Regression Scatterplot Recipe
Use this pattern to layer grouped linear fits and mean-response confidence
bands over a point chart.
## Minimal flow
```javascript
import { chart } from "ggaction";
const program = chart()
.createCanvas({
width: 760,
height: 480,
margin: { top: 40, right: 190, bottom: 70, left: 80 }
})
.createData({ values: cars })
.createPointMark()
.encodeX({ field: "Displacement", scale: { nice: true, zero: false } })
.encodeY({ field: "Acceleration", scale: { nice: true, zero: false } })
.encodeColor({ field: "Origin", scale: { palette: "tableau10" } })
.encodeSize({ field: "Acceleration" })
.encodeShape({ field: "Origin" })
.encodeOpacity({ value: 0.27 })
.filterMarks({
field: "Origin",
op: "oneOf",
values: ["Japan", "USA"]
})
.createRegression()
.createGuides();
```
## You must decide
- Point x and y fields
- Optional nominal color or shape grouping field
- Optional source filter
## The library infers
- The current eligible point layer
- Quantitative x/y fields and their shared coordinate and scales
- One grouping field when color and shape agree
- Immutable OLS rows, one 95% mean-response confidence band, and one line per group
- Shared axes, grid, categorical legend, and quantitative size legend
Pass `target`, `x`, `y`, or `groupBy` only when inference is ambiguous.
## Continue
[Regression scatterplot tutorial](../tutorials/regression-scatterplot.md) ·
[Regression API](../api/regression.md) ·
[Appearance encodings](../api/appearance.md)
# Density Area Recipe
## Minimal flow
```javascript
import { chart } from "ggaction";
const program = chart()
.createCanvas({ margin: { top: 100, right: 140 } })
.createData({ values })
.createAreaMark()
.encodeDensity({ field: "value", groupBy: "group" })
.encodeColor({ field: "group" })
.createGuides();
```
## You must decide
- Quantitative source field
- Optional nominal `groupBy`
- Optional explicit bandwidth or sampling extent
- Optional kernel and unit/count normalization
- Whether density belongs on y (default) or x
## The library infers
- Immutable derived dataset and deterministic output field names
- Gaussian/unit defaults, automatic bandwidth, and a shared 100-step sample grid
- Value and zero-inclusive density scales
- One baseline-closed command path per group
- Cartesian axes, horizontal grid, and a categorical legend when colored
Use `createGuides({ grid: { horizontal: {}, vertical: {} } })` for both grid
directions. Legends still default to the right; pass explicit top layout
options only when that composition is intentional.
## Continue
[Density area tutorial](../tutorials/density-area.md) ·
[Encodings](../api/encodings.md#atomic-density) ·
[Legends](../api/legends.md)
# Horizon Chart Recipe
## Minimal flow
```javascript
import { chart } from "ggaction";
const program = chart()
.createCanvas()
.createData({ values })
.createAreaMark({ curve: "monotone" })
.encodeHorizon({ x: "time", y: "value" })
.createGuides();
```
## You must decide
- The ordered quantitative or temporal x field
- The quantitative y field
- A baseline when zero is not meaningful
## The library infers
- The current area target and source dataset when unambiguous
- Three bands, shared automatic extent, and blue/red palettes
- Immutable namespaced output data and ordinary closed path graphics
- An x axis and x grid, without a misleading folded y axis or band legend
Use `editHorizon({ bands, baseline, extent, palette })` to create a revised
immutable result. Use `resolve: "independent"` only when group-local amplitude
comparison is intentional.
## Continue
[Horizon tutorial](../tutorials/horizon.md) ·
[Encodings](../api/encodings.md#atomic-horizon) ·
[Area marks](../api/marks/line-area.md)
# Error Bar Recipe
## Minimal flow
```javascript
import { chart } from "ggaction";
const program = chart()
.createCanvas()
.createData({ values })
.createErrorBar({
x: { field: "group", fieldType: "nominal" },
y: { field: "value" }
})
.createGuides();
```
## You must decide
- Independent nominal, ordinal, or temporal x field
- Quantitative y field
## The library infers
- Current dataset and Cartesian coordinate
- Mean with a two-sided 95% Student-t confidence interval
- One immutable summary row per first-appearance group
- Vertical main rules, 8px caps, ordinal/temporal x, and quantitative y
- Axes, horizontal grid, and statistical axis title
When one compatible layer is already encoded, call `createErrorBar()` without
options to reuse its fields, data, coordinate, and scales.
## Continue
[Error-bar tutorial](../tutorials/error-bar.md) ·
[Error-bar API](../api/error-bars.md)
# Error Band Recipe
## Minimal flow
```javascript
import { chart } from "ggaction";
const program = chart()
.createCanvas()
.createData({ values })
.createErrorBand({
x: { field: "time", fieldType: "temporal" },
y: { field: "value" },
groupBy: "series"
})
.encodeColor({ target: "errorBand", field: "series" })
.createGuides();
```
## You must decide
- Quantitative or temporal independent-position field
- Quantitative interval field
- Optional grouping field
## The library infers
- Current dataset and Cartesian coordinate
- Mean with a two-sided 95% Student-t confidence interval
- One immutable summary row per independent-position and group cell
- Vertical y/y2 or horizontal x/x2 area orientation
- Linear area paths, translucent fill, scales, axes, and applicable grid
When one compatible layer is already encoded, omitted data, coordinate,
position fields, scales, and explicit grouping are reused when the interval
axis is unambiguous.
## Continue
[Error-band tutorial](../tutorials/error-band.md) ·
[Error-band API](../api/error-bands.md)
# Box Plot Recipe
## Minimal flow
```javascript
import { chart } from "ggaction";
const program = chart()
.createCanvas()
.createData({ values })
.createBoxPlot({
x: { field: "category", fieldType: "nominal" },
y: { field: "value" },
guides: { legend: false }
});
```
## You must decide
- One categorical field and one quantitative field
- Whether the categorical field belongs on x or y
## The library infers
- The current dataset and Cartesian coordinate
- Vertical or horizontal orientation from the complete field pair
- Tukey quartiles and `1.5 × IQR` observed whiskers
- A ranged-bar body, median rule, whiskers, caps, and optional outlier points
- Compatible scales, axes, and the perpendicular grid
Use `whisker: { type: "minmax" }` for observed minimum and maximum whiskers
without an outlier layer. Use `editBoxPlot` to revise statistics, width, or
component appearance through the stable box-plot owner.
## Continue
[Box-plot API](../api/box-plots.md) ·
[Bar marks](../api/marks/bar.md) ·
[Error bars](../api/error-bars.md)
# Violin Plot Recipe
## Minimal flow
```javascript
import { chart } from "ggaction";
const program = chart()
.createCanvas()
.createData({ values })
.createViolinPlot({
x: "category",
y: "value"
});
```
## You must decide
- One categorical field and one quantitative field
- Whether the category belongs on x or y
- Whether to compare full profiles or two split halves
## The library infers
- The current dataset and Cartesian coordinate
- Field types when the stored values determine them unambiguously
- Gaussian unit density, automatic bandwidth and extent, and 100 sample steps
- Category band and quantitative value scales
- Shared `0.8` band-relative width, closed paths, axes, and horizontal grid
Add `color: "category"` for category fills. Use `split: { field }` when the
field has exactly two values, or provide an explicit two-value domain when
side assignment must be stable independently of source order.
## Continue
[Violin-plot API](../api/violin-plots.md) ·
[Density encoding](../api/encodings.md#atomic-density) ·
[Scale options](../api/scales.md)
# Composition Recipe
## Minimal flow
```javascript
import { chart, hconcat } from "ggaction";
const points = chart()
.createCanvas({ width: 280, height: 220 })
.createData({ values: pointRows })
.createScatterPlot({ x: "x", y: "y" });
const bars = chart()
.createCanvas({ width: 260, height: 220 })
.createData({ values: barRows })
.createBarPlot({ x: "category", y: "value" });
const dashboard = hconcat({
programs: [
{ id: "main", program: points },
{ id: "detail", program: bars }
],
gap: 20
});
```
`pointRows` and `barRows` are arrays of plain row objects containing the named
fields. Every child must already have one complete materialized Canvas.
## You must decide
- Horizontal `hconcat` or vertical `vconcat`
- The ordered child programs
- Stable child IDs only when later replacement must address a slot
## The library infers
- Parent Canvas dimensions from the child Canvases, gap, and padding
- A white parent background and independent child clipping
- Deterministic namespaces for every child graphic
Use `editCompositionLayout` to revise gap, alignment, or padding. Use
`replaceCompositionChild` to replace one named slot without mutating the
earlier composition.
## Continue
[Composition API](../api/composition.md) ·
[ChartProgram state](../concepts/chart-program.md)
# Facet Recipe
## Minimal flow
```javascript
import { chart } from "ggaction";
const program = chart()
.createCanvas({ width: 250, height: 230 })
.createData({ values: cars })
.createScatterPlot({
x: "Horsepower",
y: "Miles_per_Gallon",
color: "Cylinders"
})
.facet({
field: "Origin",
columns: 3,
guides: { legend: "shared" }
});
```
`cars` is an array of plain row objects containing the encoded fields and
`Origin`. Facet values retain source first-appearance order.
## You must decide
- The direct-source field that partitions rows
- Optional wrapping column count
- Shared or independent scales and shared/omitted legends
## The library infers
- One filtered immutable child program per observed value
- Shared scale domains unless a channel is declared independent
- Parent dimensions from child Canvases, gap, and padding
Use `editFacetHeaders` for repeated header appearance and
`editCompositionLayout` for the parent gap, alignment, or padding.
## Continue
[Facet and composition API](../api/composition.md#repeat-the-current-chart-by-a-field) ·
[Scatterplot recipe](./scatterplot.md)
# Parallel Coordinates Recipe
## Minimal flow
```javascript
import { chart } from "ggaction";
const program = chart()
.createCanvas({ margin: { top: 80, right: 140, bottom: 60, left: 70 } })
.createData({ values: cars })
.createParallelCoordinates({
dimensions: [
"Miles_per_Gallon",
"Horsepower",
"Weight_in_lbs",
"Acceleration"
],
color: "Origin"
});
```
`cars` is an array of plain row objects. Every eligible row becomes one open
path across the ordered dimensions.
## You must decide
- At least two unique dimension fields in axis order
- An optional unique row key
- Whether missing values break a path, drop the row, or raise an error
## The library infers
- One Parallel coordinate and one local scale per dimension
- Quantitative or ordinal dimension types from unambiguous values
- Dimension axes and an applicable categorical legend
## Continue
[Parallel Coordinates API](../api/parallel-coordinates.md) ·
[Series encodings](../api/series-encodings.md)
# Rose Chart Recipe
A rose chart compares several magnitudes inside equal categorical angle bands.
The overlay layout draws larger sectors first so smaller sectors remain visible.
## Minimal flow
```javascript
import { chart } from "ggaction";
const monthOrder = [
"April", "May", "June", "July", "August", "September",
"October", "November", "December", "January", "February", "March"
];
const causeOrder = ["Zymotic Diseases", "Other Causes", "Wounds & Injuries"];
const program = chart()
.createCanvas({
width: 780,
height: 640,
margin: { top: 80, right: 210, bottom: 80, left: 80 }
})
.createData({ values: nightingaleRows })
.createArcMark({ padAngle: 1, opacity: 0.9, strokeWidth: 0.5 })
.encodeTheta({
field: "month",
fieldType: "ordinal",
scale: { domain: monthOrder }
})
.encodeR({ field: "value", scale: { domain: [0, 6.5], zero: true } })
.encodeColor({
field: "cause",
layout: "overlay",
scale: {
domain: causeOrder,
range: ["#599ad3", "#727272", "#f1595f"]
}
})
.createGuides({
axes: {
theta: { title: false },
radius: {
ticksAndLabels: { values: [2, 4, 6] },
title: { text: "Mortality rate", position: "inside" }
}
},
grid: { theta: false, radial: { values: [2, 4, 6] } },
legend: { position: "right", title: "Cause" }
});
```
`nightingaleRows` is an array with `month`, `cause`, and finite numeric `value`
properties. Each month/cause pair should appear at most once.
## You must decide
- The ordered categorical theta field and its complete domain
- The quantitative radial field
- The category that identifies overlaid sectors
- An explicit overlay layout when multiple rows occupy one theta band
## The library infers
- One Polar coordinate shared by all sectors
- Equal theta bands for the ordered categories
- Concrete radial sector paths sorted larger-first inside each band
- Applicable Polar axes, radial grid, and categorical legend
## Continue
[Polar arc tutorial](../tutorials/polar-arcs.md#rose-overlays) ·
[Arc marks](../api/marks/line-area.md#arc-marks) ·
[Polar positions](../api/position-encodings.md#polar-positions) ·
[Guides](../api/guides.md)
# Gradient Plot Recipe
## Minimal flow
```javascript
import { chart } from "ggaction";
const program = chart()
.createCanvas({ margin: { top: 70, right: 150, bottom: 70, left: 70 } })
.createData({ values: cars })
.createGradientPlot({
x: "Origin",
y: "Acceleration"
})
.encodeColor({ field: "Origin" });
```
`cars` is an array of plain row objects with one categorical field and one
quantitative measure.
## You must decide
- The categorical and quantitative fields
- Whether the category belongs on x or y
- Whether category hue should supplement density intensity
## The library infers
- A sampled Gaussian density profile for each category
- Band-relative strip width and a median center rule
- Cartesian axes, grid, and a relative-density legend
Use `editGradientPlot` to change bandwidth, sample count, strip width, paint,
or center statistic without rebuilding the program manually.
## Continue
[Gradient Plots API](../api/gradient-plots.md) ·
[Box-plot recipe](./box-plot.md) · [Violin-plot recipe](./violin-plot.md)
# Annotation Recipe
## Minimal flow
```javascript
import { chart } from "ggaction";
const program = chart()
.createCanvas()
.createData({ values: films })
.createScatterPlot({ x: "Released_Year", y: "IMDB_Rating" })
.createTextMark({ dx: 7, dy: -6, align: "left", baseline: "bottom" })
.encodeText({ field: "Series_Title" })
.layoutLabels({
padding: 3,
maxDisplacement: 48,
leader: { stroke: "#94a3b8", opacity: 0.8 }
});
```
`films` is an array of plain row objects containing the two position fields and
the label field. The text layer reuses the preceding point layer's data and
resolved Cartesian anchors.
## You must decide
- The field or constant text to display
- Label offset, alignment, and typography
- Whether labels should keep explicit offsets or use bounded collision-aware layout
## The library infers
- The current compatible source layer and dataset
- One text item per final point, bar, rect, or rule item
- Updated anchors after Canvas or scale revisions
- Stable collision-aware positions when `layoutLabels()` is present
`layoutLabels()` reduces label-to-label overlap within the requested plot or
Canvas bounds. It is bounded best effort: dense impossible layouts store a
warning summary instead of expanding margins or changing typography. Filter
rows when every label still cannot fit.
## Continue
[Text marks](../api/marks/text.md) ·
[Data filtering](../api/data/filtering.md)
# Path Ordering Recipe
## Minimal flow
```javascript
import { chart } from "ggaction";
const program = chart()
.createCanvas()
.createData({ values: observations })
.createLineMark()
.encodeX({ field: "fertility" })
.encodeY({ field: "life_expect" })
.encodeColor({ field: "country" })
.encodePathOrder({ field: "year", order: "ascending" })
.createGuides();
```
`observations` is an array of plain row objects. Source rows may be shuffled;
the order field controls vertices independently inside each color/group series.
## You must decide
- The quantitative order field
- Ascending or descending order
- A color or group field when rows form multiple paths
## The library infers
- The current compatible Cartesian line
- Stable source order as the tie breaker
- No extra scale or guide for the topology-only field
Use `removePathOrder()` to return to the line's automatic topology.
## Continue
[Series Encodings](../api/series-encodings.md#explicit-path-topology) ·
[Line-chart recipe](./line-chart.md)
# Troubleshooting
ggaction reports an error when stored state does not determine one safe action.
It does not silently select the first mark, scale, dataset, or coordinate. Use
the cases below to make the missing decision explicit.
## A legend needs more margin
Right-side legends use the default position and require available right margin:
```javascript
program.createCanvas({
width: 720,
height: 460,
margin: { top: 40, right: 140, bottom: 70, left: 80 }
});
```
For a horizontal legend, request bottom placement and reserve bottom margin:
```javascript
program.createGuides({
legend: { position: "bottom" }
});
```
The library fails instead of overlapping the plot or clipping labels.
## A target cannot be inferred
When more than one compatible mark exists, pass its ID:
```javascript
program.encodeColor({
target: "points",
field: "group"
});
```
The same rule applies to explicit `data`, `scale.id`, and `coordinate` options.
Omit an ID only when the current state has one unambiguous candidate.
## A mark is not ready to materialize
Some actions intentionally leave empty graphics until the semantic relationship
is complete:
- A line needs temporal x and a compatible aggregate y.
- A histogram needs binned x and count/zero-stack y; prefer `encodeHistogram`.
- The current grouped bar flow needs ordinal x, a compatible aggregate y, grouped color/xOffset,
then `encodeBarWidth`.
- Points need concrete x/y and a radius to be visible.
Keep the documented action order or use the atomic action when one is provided.
## A scale cannot be shared
The default scale ID is the channel name. Sharing is valid only between
compatible consumers of that same channel and policy. Give an independent
consumer a different ID:
```javascript
program.encodeY({
field: "value",
scale: { id: "detailY" }
});
```
Do not share one ID across x and y, binned and unbinned consumers, or different
aggregate/stack policies.
## An explicit domain omits data
Explicit ordinal domains define both membership and order. Include every
observed category needed by the connected mark:
```javascript
program.encodeColor({
field: "Origin",
scale: { domain: ["USA", "Europe", "Japan"] }
});
```
Unknown categories are rejected rather than appended silently. Missing
category combinations are different: grouped bars omit unobserved cells by
default instead of synthesizing zero values.
## `createGuides()` selects nothing
`createGuides()` needs at least one supported encoded axis, grid, or legend.
Create the relevant encodings first. A nominal point color encoding can create
a categorical legend; field-driven shape and quantitative size may be combined
with that legend when their resolved scales are compatible.
## `createData()` rejects the dataset
`values` must be an array of plain row objects:
```javascript
program.createData({
values: [
{ category: "A", value: 12 },
{ category: "B", value: 18 }
]
});
```
Dataset IDs are optional when the generated role is unused. The dataset is
immutable after creation: use `filterData`, a derived-data action, or create a
new program revision rather than mutating the caller-owned array.
## Temporal values cannot be parsed
Temporal fields accept finite timestamps, four-digit numeric or string years,
and valid date strings. Four-digit values are interpreted as UTC years.
```javascript
program.encodeX({ field: "year", fieldType: "temporal" });
```
Use one consistent representation per field. Invalid or mixed ambiguous values
fail before a time scale or partial graphic is created.
## A filter produces an empty mark
`filterData` creates a derived dataset and leaves the source unchanged. An empty
result is valid, so confirm the exact field spelling, value type, and predicate:
```javascript
const filtered = program.filterData({
field: "Origin",
oneOf: ["Japan", "USA"]
});
```
Then inspect the derived dataset row count and the final graphic item count.
Filtering a numeric field with string values—or a date string with a `Date`
object—does not coerce the comparison silently.
## An encoding is incompatible with the mark
Position and appearance channels are not universally interchangeable. For
example, point marks support field-driven shape and size, while line paths use
stroke width and stroke dash. Ranged intervals require a compatible primary and
secondary position pair.
Start with the generated [mark/channel matrix](./api/encodings.md#supported-markchannel-matrix),
then open the focused family page for accepted field types and ordering rules.
The action fails before storing partial state when the combination is unsupported.
## A facet or composition cannot share a resource
Composition snapshots complete child programs and never merges their resource
namespaces. Facet sharing is narrower: every represented child scale and guide
recipe must be concretely compatible.
Use independent facet scales when panels need different policies:
```javascript
program.facet({
field: "group",
scales: { y: "independent" },
guides: { legend: false }
});
```
Polar sources cannot currently be faceted. They can still be children of
`hconcat` or `vconcat`.
## Browser Canvas renders nothing
Call the browser entry point with a real Canvas element and wait until the
program is complete:
```javascript
import { render } from "ggaction";
render(program, document.querySelector("#chart"));
```
If rendering succeeds but the result is blank, inspect `graphicSpec.objects`
before changing the renderer. The renderer does not infer missing mark
positions, sizes, or paths from `semanticSpec`.
## Node PNG export is unavailable in the browser
The PNG adapter is a Node-only package entry:
```javascript
import { renderToPNG } from "ggaction/png";
await renderToPNG(program, { output: "chart.png", pixelRatio: 2 });
```
Use `render` from `ggaction` in the browser. Importing `ggaction/png` into a
browser bundle is unsupported because it depends on the Node Canvas adapter.
## Inspect the program
Use these public states to locate the last successful authoring step:
```javascript
program.semanticSpec; // stored chart meaning
program.graphicSpec; // fully materialized concrete graphics
program.trace; // hierarchical action history
```
The renderer reads only `graphicSpec`; it never repairs incomplete semantic
state. See [Actions and trace trees](./concepts/actions-and-trace.md) and
[Semantic and graphical state](./concepts/semantic-and-graphics.md).
When rendering succeeds but a mark looks empty, verify the final concrete item
cardinality instead of treating a PNG or Canvas call as proof that marks were
materialized:
```javascript
const markId = "points";
const graphic = program.graphicSpec.objects[markId];
if (graphic === undefined) {
throw new Error(`No concrete graphic exists for mark "${markId}".`);
}
const items = program.graphicSpec.objects[markId].items;
if (items.length === 0) {
throw new Error(`Mark "${markId}" materialized no final items.`);
}
```
Point and rule items usually correspond to final rows, bars correspond to
final bin/category cells, and line or area items correspond to complete series.
Use `program.semanticSpec.layers.find(layer => layer.id === markId)` to inspect
the owning semantic layer. The map is named `graphicSpec.objects`, not
`graphicSpec.graphics`.
# Chart Gallery
This curated set highlights complete, representative charts. Every card links
to the canonical tutorial, recipe, or API page that owns the chart. Use the
filters to narrow the analytical relationship you want to express.
Browse all supported chart examples →
## Choose a next step
- [Learn a complete workflow](./tutorials/index.md)
- [Copy a minimal chart recipe](./recipes/index.md)
- [Find an exact action](./reference/actions.md)
# All Chart Examples
This catalog includes every maintained public chart example. Start with the
[curated gallery](../index.md) when you want a shorter representative set.
# Chart API
Build a chart from a small set of domain actions. Start with the family that
owns the decision you need to make; follow its focused pages only when the
overview no longer provides enough control.
Basic charts Create scatter, line, bar, histogram, and heatmap charts from their minimum field decisions.
Canvas Create and resize the drawing surface, background, and plot margins.
Data and transforms Create immutable source data, derived datasets, filters, bins, windows, and statistical transforms.
Marks Create point, line, area, bar, and rule layers.
Encodings Map fields and constants to position, grouping, and appearance.
Coordinates Use Cartesian, Polar, or Parallel coordinate systems.
Scales Control domains, ranges, mapping types, palettes, and missing values.
Parallel coordinates Compare each source row across an ordered set of dimension-local scales.
Guides and titles Create axes, grids, legends, and chart titles from existing encodings.
Program composition Arrange complete chart programs horizontally or vertically and replace stable child slots.
Rendering Render the fully materialized program to Browser Canvas or Node PNG.
## Statistical and distribution facades
Regression Add grouped fitted lines and optional confidence bands to encoded observations.
Error bars Show observed values with explicit or derived interval endpoints.
Error bands Show continuous uncertainty ribbons with optional boundaries.
Box plots Summarize quartiles, whiskers, medians, and outliers by category.
Gradient plots Compare category distributions as compact density fills.
Violin plots Compare symmetric or split density shapes by category.
## Exact action lookup
Use the [complete action reference](../reference/actions.md) when you know an
action name and need its canonical signature. Extension authors should use the
[extension API](../extension/action-authoring.md) rather than depending on
internal trace operations.
# Basic Charts
Basic chart actions create a complete mark, its encodings, and applicable
guides in one traceable call. Create a Canvas and dataset first, then provide
only the fields that define the chart.
```javascript
const program = chart()
.createCanvas()
.createData({ values })
.createScatterPlot({ x: "horsepower", y: "mpg", color: "origin" });
```
These actions are conveniences over the regular mark, encoding, and guide
actions. They call those wrapped actions as trace children; they do not add a
second chart specification or compile semantics during rendering.
## Choose a facade
| Facade | Shortest complete decision | Guides when omitted |
| --- | --- | --- |
| `createScatterPlot` | `{ x, y }` | applicable guides |
| `createLinePlot` | `{ x, y }` | applicable guides |
| `createBarPlot` | `{ x, y }` | applicable guides |
| `createHistogram` | `{ field }` | applicable guides |
| `createHeatmap` | `{ x, y, color }`, or `{ x, y, bin: {} }` | applicable guides |
These five facades create a complete common Cartesian chart from fields. After
creation, editing returns to the action that owns the decision; for example,
use `encodeY`, `editScale`, `editBarMark`, or `editLegend` rather than a generic
facade editor.
## Shared behavior
- `data` is optional when there is a current dataset or exactly one dataset.
Pass it explicitly when more than one dataset could apply.
- `id` is optional for the first chart of each family. A stable default role is
used; a conflicting role requires an explicit ID.
- A field may be a string or an encoding option object. Use the object form for
field types, scales, aggregates, and other channel-specific options.
- `guides` is optional. Omission or `{}` creates applicable axes, horizontal
grid, and legends. Pass `guides: false` to create no guides.
- A facade validates its complete option object before changing the program.
- To revise the result, use the resource action that owns the decision, such as
`encodeX`, `editScale`, `editPointMark`, or `editLegend`. Aggregate
`editScatterPlot`-style actions are intentionally not provided.
Canvas and source data remain separate authoring decisions. Basic chart
actions never create either one or silently choose among ambiguous resources.
## `createScatterPlot`
```typescript
createScatterPlot(options: CreateScatterPlotOptions): ChartProgram
```
Required options are `x` and `y`. Optional `color`, `size`, and `shape` values
create field encodings; `point` controls constant point appearance.
```javascript
const scatter = chart()
.createCanvas()
.createData({ values: cars })
.createScatterPlot({
x: "Horsepower",
y: "Miles_per_Gallon",
color: "Origin",
point: { opacity: 0.7 },
guides: {
axes: {
x: { title: { text: "Horsepower" } },
y: { title: { text: "Miles per gallon" } }
}
}
});
```
The stable default mark ID is `scatterPlot`. Omitted size materializes the
default point radius of `3`. Constant fill, shape, opacity, stroke, and stroke
width belong in `point`; field-driven color, size, and shape remain top-level.
## `createLinePlot`
```typescript
createLinePlot(options: CreateLinePlotOptions): ChartProgram
```
Required options are `x` and `y`. Use `color`, `groupBy`, or `strokeDash` for
series identity and `line` for constant appearance.
```javascript
const line = chart()
.createCanvas({ margin: { right: 140 } })
.createData({ values: rows })
.createLinePlot({
x: { field: "date", fieldType: "temporal" },
y: { field: "value", aggregate: "mean" },
color: "group",
strokeDash: { field: "group" },
line: { curve: "monotone", strokeWidth: 3 }
});
```
The stable default mark ID is `linePlot`. A plain `strokeDash` string is not
accepted because it could mean either a field or a named dash style; use
`{ field }` or `{ value }`. This facade creates Cartesian lines. Use the
explicit Polar mark and encoding actions for closed Polar paths.
## `createBarPlot`
```typescript
createBarPlot(options: CreateBarPlotOptions): ChartProgram
```
Required options are `x` and `y`. Position option objects select vertical,
horizontal, aggregate, or ranged geometry. `color.layout` owns grouped,
stacked, normalized-fill, overlay, and diverging arrangements.
```javascript
const bars = chart()
.createCanvas({ margin: { right: 140 } })
.createData({ values: rows })
.createBarPlot({
x: { field: "year", fieldType: "ordinal" },
y: { field: "value", aggregate: "mean" },
color: { field: "group", layout: "group" },
width: { band: 0.72 }
});
```
The stable default mark ID is `barPlot`. `width` accepts the same band or
logical-pixel options as `encodeBarWidth`; `bar` controls constant fill,
opacity, stroke, and stroke width.
## `createHistogram`
```typescript
createHistogram(options: CreateHistogramOptions): ChartProgram
```
`field` is required. The action atomically creates binned x and count y
encodings because those meanings depend on each other.
```javascript
const histogram = chart()
.createCanvas({ margin: { right: 140 } })
.createData({ values })
.createHistogram({
field: "value",
maxBins: 10,
color: { field: "group", layout: "stack" }
});
```
The stable default mark ID is `histogram`. The default is `maxBins: 10`.
Choose exactly one of `maxBins`, `binStep`, or `binBoundaries`. Optional
`xScale`, `yScale`, `stack`, `color`, and `bar` values use the corresponding
histogram, color, and bar contracts.
## `createHeatmap`
```typescript
createHeatmap(options: CreateHeatmapOptions): ChartProgram
```
`createHeatmap` supports two explicit modes. Without `bin`, `x`, `y`, and
`color` describe pre-gridded rows and the action creates one cell for each valid
observed row. With `bin`, raw quantitative `x` and `y` fields are divided into a
rectangular grid and cell color represents the generated count.
```javascript
const heatmap = chart()
.createCanvas({ margin: { right: 120 } })
.createData({ values: cells })
.createHeatmap({
x: { field: "year", fieldType: "ordinal" },
y: { field: "country", fieldType: "nominal" },
color: {
field: "life_expectancy",
fieldType: "quantitative",
scale: { type: "sequential", palette: "viridis" }
},
rect: { stroke: "white", strokeWidth: 1 }
});
```
```javascript
const binnedHeatmap = chart()
.createCanvas({
width: 700,
height: 500,
margin: { top: 70, right: 140, bottom: 75, left: 85 }
})
.createData({ values: cars })
.createHeatmap({
x: "Weight_in_lbs",
y: "Miles_per_Gallon",
bin: {
bins: { x: 10, y: 8 },
extent: { x: [1500, 5200], y: [8, 48] }
},
color: { scale: { palette: "blues" } },
rect: { stroke: "white", strokeWidth: 1 }
});
```
The binned mode uses `10 × 10` bins by default. Its `includeEmpty` default is
`true`, so the complete rectangular grid is visible; set it to `false` to emit
only occupied cells. An omitted per-axis extent is inferred from finite x/y
values. Explicit extents must contain every eligible value. Position scale
domains default to the resolved bin extents, while an explicit position scale
option takes precedence. `color` is optional in binned mode and may configure
the generated quantitative count scale, but cannot supply another field.
The stable default mark ID is `heatmap`. Binned mode owns a namespaced derived
dataset and records `createBin2DData` as a wrapped child action. Default guide
titles use the original x/y field names and `Count`, never generated field
names. Automatic chart grid lines are disabled because the ranged cells already
form a grid; pass an explicit `guides.grid` option to enable them.
In both modes, the color encoding is the sole cell fill owner, so `rect.fill`
is rejected; `rect` accepts opacity and outline options. Text is not automatic.
Add it afterward with
`createTextMark().encodeText()` when the rows contain display values.
For direct control over the derived rows, use
[`createBin2DData`](./data/bin2d.md) before authoring the ranged rect layer.
## Other complete chart facades
- [Parallel coordinates](./parallel-coordinates.md) creates row-wise paths
across independently scaled dimensions.
- [Box plots](./box-plots.md), [gradient plots](./gradient-plots.md), and
[violin plots](./violin-plots.md) summarize category distributions.
- [Regression](./regression.md), [error bars](./error-bars.md), and
[error bands](./error-bands.md) add statistical or interval layers.
## Advanced authoring
Use individual [mark](./marks.md), [encoding](./encodings.md),
[coordinate](./coordinates.md), and [guide](./guides.md) actions when the chart
needs custom layering, Polar geometry, a partially constructed state, or
control between the wrapped steps. Both authoring styles produce the same
immutable semantic and graphical resources.
# Parallel Coordinates
Use Parallel coordinates when each row should remain visible while several
measurements are compared. Every eligible source row becomes one selectable
open path. Every dimension owns a local scale and ordinary line/text axis.
## Complete chart action
```typescript
createParallelCoordinates({
id?, data?, coordinate?, dimensions, key?, missing?,
color?, strokeDash?, line?, guides?
}): ChartProgram
```
Only `dimensions` is required after a Canvas and an unambiguous dataset exist.
```javascript
import { chart } from "ggaction";
const program = chart()
.createCanvas({
width: 860,
height: 500,
margin: { top: 110, right: 160, bottom: 65, left: 78 }
})
.createData({ values: cars })
.filterData({
id: "cars1970",
field: "Year",
oneOf: ["1970-01-01"]
})
.createParallelCoordinates({
dimensions: [
{ field: "Miles_per_Gallon", title: "MPG", scale: { nice: true, zero: false } },
{ field: "Horsepower", scale: { nice: true, zero: false } },
{ field: "Weight_in_lbs", title: "Weight (lb)", scale: { nice: true, zero: false } },
{ field: "Acceleration", scale: { nice: true, zero: false } }
],
key: "Name",
color: {
field: "Origin",
fieldType: "nominal",
scale: { palette: "tableau10" }
},
line: { strokeWidth: 1.25, opacity: 0.48 }
});
```
The stable mark ID is `parallelCoordinates`; the coordinate ID is `parallel`.
Pass explicit IDs only when those roles are already occupied or several
compatible resources make inference ambiguous.
## Dimensions
A dimension is a field string or an object:
```typescript
type ParallelDimension = string | {
field: string;
fieldType?: "quantitative" | "ordinal";
title?: string;
scale?: Omit;
};
```
- At least two unique fields are required, and array order is axis order.
- Finite numeric values infer `quantitative`; consistent strings infer
`ordinal`. Declare numeric categories as `ordinal` explicitly.
- `title` defaults to the field name.
- Scale IDs are namespaced from the mark and dimension position. Supply scale
behavior such as `type`, `domain`, `range`, `nice`, `zero`, or `reverse`, not
another `id`.
## Row identity and missing values
`key` names a dataset field whose values must be present and unique. It is
optional: omission uses stable source-row lineage and never guesses a field.
| `missing` | Result |
| --- | --- |
| `"break"` | Default. Keep the row item and draw each valid multi-point fragment. |
| `"drop-row"` | Remove any row with an invalid dimension value. |
| `"error"` | Reject the action before changing the program. |
## Advanced encoding
Use the atomic encoding after creating a line mark when the wrapped steps need
to remain individually controllable:
```javascript
const advanced = chart()
.createCanvas()
.createData({ values })
.createLineMark({ id: "profiles" })
.encodeParallelCoordinates({
target: "profiles",
dimensions: ["first", "second", "third"]
})
.createGuides();
```
`encodeParallelCoordinates` replaces the complete ordered assignment
atomically. It cannot mix with x/y or theta/radius position encodings on the
same layer.
## Appearance, guides, and revisions
- `color` and `strokeDash` reuse the line-series encoding vocabulary.
- `line` accepts open linear-line appearance: `stroke`, `strokeWidth`, and
`opacity`. Curved and closed paths are rejected.
- Omitted guides create dimension axes and any applicable legend. Use
`guides: false` to omit all guides.
- `editCanvas`, dimension `editScale`, data revisions, and `filterMarks`
rematerialize paths, axes, and legends together.
- `selectMarks`, `highlightMarks`, and `filterMarks` operate at source-row
grain. Text is not attached automatically because a path has no unique text
anchor.
## Limitations
Temporal dimensions, curved paths, path bundling, interactive brushing,
drag-to-reorder axes, faceting, and shared Parallel axes across composed child
programs are not implemented.
## Related
[Basic Charts](./basic-charts.md) · [Coordinates](./coordinates.md) ·
[Series encodings](./series-encodings.md) · [Selection and Highlighting](./appearance/selection-and-highlighting.md)
# Canvas
## At a glance
| Action | Shortest call | Inference/defaults | Result |
| --- | --- | --- | --- |
| `createCanvas` | `createCanvas()` | Default size, background, and margin | Concrete Canvas and plot bounds |
| `editCanvas` | `editCanvas({ width: 800 })` | Unspecified properties remain unchanged | Updated Canvas and affected consumers |
## `createCanvas(options?)`
Creates the program's single canvas and establishes bounds for later position
encodings.
| Option | Type | Default |
| --- | --- | --- |
| `width` | positive integer | `640` |
| `height` | positive integer | `400` |
| `background` | non-empty string | `"white"` |
| `margin` | non-negative number or side object | `{ top: 30, right: 30, bottom: 60, left: 70 }` |
```javascript
const program = chart().createCanvas({
width: 640,
height: 400,
background: "white",
margin: { top: 30, right: 30, bottom: 60, left: 70 }
});
```
## `editCanvas(options)`
Updates one or more existing canvas options. Omitted values are preserved. A
numeric margin applies to every side; a partial object updates only named sides.
```javascript
const resized = program.editCanvas({
width: 800,
margin: { left: 80 }
});
```
Width, height, or margin changes explicitly rematerialize connected automatic
position scales, line marks, axes, legends, and chart titles. Background-only
edits do not.
Margin is immutable materialization configuration used with the concrete Canvas
dimensions to derive plot bounds. It is neither a drawable node in
`graphicSpec` nor transient authoring context.
## Errors and limitations
Only one Canvas can exist in a program. Dimensions must remain positive and
margins must leave positive plot bounds.
## Related
[Rendering](./rendering.md) · [Coordinates](./coordinates.md) ·
[Scale options](./scales.md)
# Data
Data actions own immutable source rows, explicit derivation provenance, and
statistical result datasets. Choose the focused page that matches the data
operation; marks and renderers never mutate source values.
## At a glance
| Family | Actions | Use |
| --- | --- | --- |
| [Source and derived data](./data/source-and-derived.md) | `createData`, `createDerivedData` | Store source rows or explicit transform provenance |
| [Filtering](./data/filtering.md) | `filterData`, `filterMarks` | Derive rows or rebind one visual layer from a selector |
| [Statistical transforms](./data/statistical-transforms.md) | `createRegressionData`, `createIntervalData`, `createDensityData` | Materialize fitted, interval, or density rows |
| [Window transforms](./data/window.md) | `createWindowData` | Compute ordered values within partitions while preserving source row order |
| [Rectangular 2D bins](./data/bin2d.md) | `createBin2DData` | Aggregate two quantitative fields into immutable ranged cells |
## Shared invariants
- Every dataset is immutable after creation.
- Omitted sources resolve only from the current or unique compatible dataset.
- Derived datasets retain their source and transform provenance.
- Ambiguous sources require an explicit ID; the library never selects the first
of several candidates silently.
## Errors and limitations
Values must be arrays of plain row objects. Dataset IDs are unique. Filters and
statistical transforms validate their complete option combination before
creating state, and a failed action leaves the earlier program unchanged.
## Related
[Marks](./marks.md) · [ChartProgram and immutability](../concepts/chart-program.md) ·
[Complete action reference](../reference/actions.md)
# Source and Derived Data
## `createData({ id?, values })`
| Option | Type | Required |
| --- | --- | --- |
| `id` | string containing letters, numbers, `_`, or `-` | no; first dataset defaults to `"data"` |
| `values` | array of plain row objects | yes |
```javascript
const program = chart().createData({
values: [
{ horsepower: 130, mpg: 18 },
{ horsepower: 165, mpg: 15 }
]
});
```
Empty arrays are valid, and row properties may contain nested arrays or
objects. The action copies and freezes the supplied data. A dataset ID cannot
be created twice, and source values cannot be replaced after creation. The
first omitted ID is stored as `"data"`. Once any dataset exists, another
`createData` call must provide an explicit ID; the library does not invent
`data2`-style names.
The most recently created dataset becomes the default for `createPointMark`,
`createLineMark`, or `createBarMark`. Creating data records semantic state only
and produces no graphics.
## `createDerivedData({ id, source, transform })` {#create-derived-data}
`createDerivedData` is the advanced provenance-assembly action behind the
higher-level data actions. `transform` must be a one-element array containing
one supported transform object; a bare object, an empty array, or a transform
pipeline is not accepted.
```javascript
import { chart } from "ggaction";
const program = chart()
.createData({
id: "source",
values: [{ group: "A" }, { group: "B" }]
})
.createDerivedData({
id: "selected",
source: "source",
transform: [
{ type: "filter", field: "group", oneOf: ["A"] }
]
});
console.log(program.semanticSpec.datasets[1].transform[0].type);
// "filter"
```
The action stores the source ID and immutable transform provenance. It does
not compute or store `values`, and it does not create graphics. Prefer the
corresponding higher-level action when the library should materialize values:
| `type` | Public transform shape | Value-producing action |
| --- | --- | --- |
| `"filter"` | `{ type, field, oneOf }`, `{ type, field, predicate }`, or `{ type, field, range }` | `filterData` |
| `"regression"` | `{ type, method, x, y, groupBy?, ...methodParameters }` | `createRegressionData` |
| `"density"` | `{ type, field, groupBy?, bandwidth, extent, steps, kernel?, normalization?, as, resolve: "shared", resolved? }` | `createDensityData` |
| `"interval"` | `{ type, field, groupBy, center, extent, level?, as }` | `createIntervalData` |
| `"window"` | `{ type, partitionBy, sortBy, operations }` | `createWindowData` |
For regression, linear and polynomial transforms require `confidence` and
`interval`; polynomial also requires `degree`. LOESS requires `span` and does
not accept interval properties. Density `as` is a two-field tuple. Interval
`as` contains distinct `center`, `lower`, and `upper` field names; mean CI
requires `level`, while median pairs only with IQR. Window transforms require
normalized arrays for `partitionBy`, `sortBy`, and `operations`; use
`createWindowData` to apply defaults and materialize rows. A materialized density
revision adds `resolved: { bandwidth, extent }` without replacing requested
`"auto"` values. See the higher-level action
sections below for accepted values and defaults before constructing normalized
provenance directly.
`DatasetTransform` and `CreateDerivedDataOptions` export the same public union
for TypeScript. Internal transforms generated by composite actions, including
box-plot and final-mark filtering provenance, are intentionally not part of
this direct-authoring union. Multiple public transforms may be recorded in the
ordered array, but built-in value materializers each require the single
transform owned by their corresponding higher-level action.
## Related
[Data overview](../data.md) · [Chart API](../index.md) · [Action reference](../../reference/actions.md)
# Data and Mark Filtering
## `filterData({ id, source?, field, oneOf | predicate | range })`
Create a named derived dataset without replacing or mutating its source.
```javascript
const selected = chart()
.createData({ id: "cars", values: cars })
.filterData({
id: "selectedCars",
field: "Origin",
oneOf: ["Japan", "USA"]
});
```
| Option | Type | Required |
| --- | --- | --- |
| `id` | dataset ID | yes |
| `source` | existing dataset ID | no; defaults to current dataset |
| `field` | non-empty string | yes |
| `oneOf` | non-empty scalar array | one filter mode required |
| `predicate` | `{ op, value }` | one filter mode required |
| `range` | `{ min, max, inclusive? }` | one filter mode required |
The derived dataset stores its source ID, filter transform, and immutable
materialized values. Exactly one of `oneOf`, `predicate`, or `range` is required.
Rows retain source order, the source remains unchanged, and the new dataset
becomes current data for the next mark.
Comparison operators are `"eq"`, `"neq"`, `"lt"`, `"lte"`, `"gt"`, and
`"gte"`. Equality is strict and never coerces values. Ordered comparisons
require both values to be finite numbers or both to be strings; incompatible or
missing field values are omitted. String order is lexicographic.
```javascript
const powerfulCars = chart()
.createData({ id: "cars", values: cars })
.filterData({
id: "powerfulCars",
field: "Horsepower",
predicate: { op: "gte", value: 150 }
});
```
Range endpoints must be the same type and `min` cannot exceed `max`.
`inclusive` defaults to `true`; setting it to `false` excludes both endpoints.
An empty result is valid.
```javascript
program.filterData({
id: "midDisplacementCars",
source: "cars",
field: "Displacement",
range: { min: 100, max: 300, inclusive: true }
});
```
## `filterMarks({ target?, ...selector })` {#filter-marks}
Filter existing final mark items without changing the source dataset.
`filterMarks` uses the same selector grammar as `selectMarks`, infers the current
mark when possible, creates a namespaced immutable dataset such as
`pointsFilteredData`, rebinds only that mark, and rematerializes its scales,
graphics, and connected guides.
```javascript
const filtered = chart()
.createData({ id: "cars", values: cars })
.createPointMark({ id: "points" })
.encodeX({ field: "Displacement" })
.encodeY({ field: "Acceleration" })
.filterMarks({
field: "Origin",
op: "oneOf",
values: ["Japan", "USA"]
});
```
Choose exactly one selector value source: `field` for a data value unique at
the item grain, `channel` for a pre-scale semantic value, or `property` for a
concrete graphical value. Operators are `eq | neq | gt | gte | lt | lte`,
`oneOf`, `range`, and ranked `min | max` with optional `count`, `groupBy`, and
`ties`. The default `grain: "item"` means a point, final bar rectangle,
line/area series path, arc sector, or rule. Stacked bars additionally support
`grain: "stack"`.
```javascript
program.filterMarks({
target: "bars",
grain: "stack",
channel: "y2",
op: "max"
});
```
The original dataset and earlier program remain unchanged. Apply the filter
before creating a derived statistical layer when that statistic should use the
filtered rows; existing independent layers are not silently rebound. Histograms
retain their pre-filter bin boundaries, and line/area filters retain complete
series. Reapplying the same target is rejected because its deterministic derived
dataset ID already exists. A selector that matches no final item fails before
creating derived state.
## Related
[Data overview](../data.md) · [Chart API](../index.md) · [Action reference](../../reference/actions.md)
# Statistical Data Transforms
## `createRegressionData({ id, source?, x, y, groupBy?, method?, degree?, span?, confidence?, interval? })`
Create deterministic regression predictions from an existing dataset. This is
an advanced data action used by higher-level regression chart actions.
```javascript
program.createRegressionData({
id: "regressionData",
x: "Displacement",
y: "Acceleration",
groupBy: "Origin"
});
```
| Option | Type | Default |
| --- | --- | --- |
| `id` | new dataset ID | required |
| `source` | existing dataset ID | current dataset |
| `x`, `y` | finite quantitative field names | required |
| `groupBy` | nominal field name | one ungrouped model |
| `method` | `"linear"`, `"polynomial"`, or `"loess"` | `"linear"` |
| `degree` | positive integer for polynomial | `2` |
| `span` | number greater than `0` and at most `1` for LOESS | `0.75` |
| `confidence` | number strictly between `0` and `1`; not accepted by LOESS | `0.95` |
| `interval` | `"mean"` or `"prediction"`; not accepted by LOESS | `"mean"` |
Linear and polynomial fits use stable least squares. Polynomial groups require
at least `degree + 2` rows and `degree + 1` distinct x values. LOESS uses
tricube-weighted local-linear fits over `ceil(span * groupSize)` nearest rows,
with source order breaking distance ties. Output follows group first appearance
and observed unique x order. Linear and polynomial output includes fixed
`__regression_ci_lower` / `__regression_ci_upper` fields; LOESS is line-only.
Source values remain unchanged.
## `createIntervalData({ id, source?, field, groupBy?, center?, extent?, level?, as? })` {#create-interval-data}
Create immutable grouped interval-summary rows independently from an error-bar
mark.
```javascript
program.createIntervalData({
id: "accelerationIntervals",
field: "Acceleration",
groupBy: "Origin"
});
```
| Option | Type | Default |
| --- | --- | --- |
| `id` | new dataset ID | required |
| `source` | existing dataset ID | current dataset |
| `field` | quantitative field name | required |
| `groupBy` | field name or array of field names | one ungrouped interval |
| `center` | `"mean"` or `"median"` | `"mean"` |
| `extent` | `"stderr"`, `"stdev"`, `"ci"`, or `"iqr"` | `"ci"` |
| `level` | number strictly between `0` and `1` | `0.95` for CI |
| `as` | `{ center, lower, upper }` distinct field names | ID-namespaced fields |
Mean supports standard error, sample standard deviation, and two-sided
Student-t confidence intervals. Median requires interquartile range, and IQR
requires median. Group order follows first appearance. Missing group values,
non-finite measures, and undersized mean groups are omitted; valid source rows
and the source dataset remain unchanged.
## `createDensityData({ id, source?, field, groupBy?, bandwidth?, extent?, steps?, kernel?, normalization?, as? })`
Create an immutable kernel-density dataset. This is an advanced data
action used by higher-level density-chart encodings.
```javascript
program.createDensityData({
id: "accelerationDensity",
field: "Acceleration",
groupBy: "Origin",
bandwidth: 0.6
});
```
| Option | Type | Default |
| --- | --- | --- |
| `id` | new dataset ID | required |
| `source` | existing dataset ID | current dataset |
| `field` | quantitative field name | required |
| `groupBy` | nominal field name | one ungrouped density |
| `bandwidth` | positive number or `"auto"` | automatic Scott-rule estimate |
| `extent` | ascending finite pair or `"auto"` | observed valid extent |
| `steps` | integer at least `2` | `100` |
| `kernel` | `"gaussian"`, `"epanechnikov"`, `"uniform"`, or `"triangular"` | `"gaussian"` |
| `normalization` | `"unit"` or `"count"` | `"unit"` |
| `as` | two distinct output field names | `_value`, `_density` |
Grouped densities use one shared extent and inclusive sample grid. Group order
follows first appearance. Transform provenance keeps the requested `bandwidth`
and `extent` values—including `"auto"`—and stores the materialized revision's
concrete values separately as `resolved: { bandwidth, extent }`. Kernel and
normalization defaults are stored directly. Unit normalization integrates
each complete group density to one; count normalization scales it by that
group's valid sample count. Source values remain unchanged.
## Related
[Data overview](../data.md) · [Chart API](../index.md) · [Action reference](../../reference/actions.md)
# Window Data Transforms
source rowsimmutable input
partition + sortcalculation order
operationsderived fields
source orderimmutable output
`createWindowData` computes ordered values within optional partitions and stores
the result as a new immutable dataset. It is useful when a later mark needs rank,
running totals, or neighboring values without changing the source rows.
## `createWindowData({ id, source?, partitionBy?, sortBy?, operations })`
```javascript
const program = chart()
.createData({
id: "sales",
values: [
{ region: "East", month: 2, amount: 30 },
{ region: "East", month: 1, amount: 20 },
{ region: "West", month: 1, amount: 15 }
]
})
.createWindowData({
id: "rankedSales",
partitionBy: "region",
sortBy: [{ field: "month" }],
operations: [
{ op: "rowNumber", as: "monthOrder" },
{ op: "cumulativeSum", field: "amount", as: "runningAmount" },
{ op: "lag", field: "amount", as: "previousAmount" }
]
});
```
| Option | Type | Default |
| --- | --- | --- |
| `id` | new dataset ID | required |
| `source` | existing dataset ID | current dataset |
| `partitionBy` | field name or array of field names | one partition |
| `sortBy` | array of `{ field, order? }` | source order |
| `operations` | non-empty array of window operations | required |
`order` accepts `"ascending"` or `"descending"` and defaults to ascending.
Sorting is stable. Missing sort values are placed after present values for an
ascending sort and before them for a descending sort. Operations run in the
declared order, so a later operation may read a field created by an earlier one.
Supported operations are:
| Operation | Shape | Notes |
| --- | --- | --- |
| row number | `{ op: "rowNumber", as }` | one-based position |
| rank | `{ op: "rank", as }` | tied rows share a rank and leave gaps |
| dense rank | `{ op: "denseRank", as }` | tied rows share a rank without gaps |
| cumulative sum | `{ op: "cumulativeSum", field, as }` | requires finite numeric values |
| lag | `{ op: "lag", field, as, offset?, default? }` | defaults to offset `1` and value `null` |
| lead | `{ op: "lead", field, as, offset?, default? }` | defaults to offset `1` and value `null` |
The action computes each partition in sorted order, then returns the materialized
rows in their original source order. The source dataset remains unchanged. Output
fields must be unique and cannot replace fields already present in the source.
Dataset IDs are create-only: calling the action again with the same `id` throws
instead of replacing or rebinding consumers.
## Related
[Data overview](../data.md) · [Source and derived data](./source-and-derived.md) ·
[Action reference](../../reference/actions.md)
# Rectangular 2D Bins
source rowsfinite x/y pairs
grid edgesautomatic or explicit
cell assignmentone cell per eligible row
derived rowsbounds + count
`createBin2DData` groups two quantitative fields into a rectangular grid. It
stores concrete cell bounds and counts as a new dataset, so ordinary ranged
rectangles or other marks can consume the result without asking the renderer to
perform data transforms.
## `createBin2DData({ id, source?, x, y, bins?, extent?, includeEmpty?, members?, as? })`
```javascript
const program = chart()
.createData({
id: "observations",
values: [
{ x: 0, y: 0 },
{ x: 1, y: 0 },
{ x: 2, y: 2 }
]
})
.createBin2DData({
id: "cells",
x: "x",
y: "y",
bins: { x: 2, y: 2 },
extent: { x: [0, 2], y: [0, 2] },
includeEmpty: true,
as: { count: "count" }
});
```
| Option | Type | Default |
| --- | --- | --- |
| `id` | logical derived-dataset ID | required |
| `source` | existing dataset ID | current dataset; previous source on revision |
| `x`, `y` | quantitative field names | required |
| `bins` | positive integer or `{ x, y }` | `{ x: 10, y: 10 }` |
| `extent` | `{ x?: [min, max], y?: [min, max] }` | eligible min/max per axis |
| `includeEmpty` | boolean | `false` |
| `members` | boolean | `false` |
| `as` | partial output-field object | namespaced from `id` |
Only rows with finite values for both fields are eligible. Cells are half-open:
`[lower, upper)`. The last cell on each axis includes its upper endpoint, so
every eligible row belongs to exactly one cell. Output rows are ordered from low
to high y and then low to high x.
The stored transform includes the resolved axis extents and edge arrays. An
explicit extent must contain every eligible value; the action throws rather
than silently dropping rows. An automatic axis with no positive span also
throws instead of guessing a display range.
The default output fields are `___x0`, `___x1`, `___y0`,
`___y1`, and `___count`. Pass `as` to give downstream encodings shorter
names. With `members: true`, each cell also stores source row indexes. It never
copies complete source row objects into every cell.
## Revisions
Calling `createBin2DData` again with the same logical `id` creates a new immutable
revision, rebinds direct visual consumers, rematerializes their scales, marks,
and guides, and releases the unreferenced previous revision. Earlier programs
remain unchanged. A dependent derived dataset currently blocks replacement
with an explicit error; create a new logical ID when that dependency must remain.
## Related
[Data overview](../data.md) · [Source and derived data](./source-and-derived.md) ·
[Heatmaps](../basic-charts.md#createheatmap) · [Action reference](../../reference/actions.md)
# Marks
Marks define the semantic form of a layer. Create a mark first, then connect it
to data through position, grouping, and appearance encodings. The first mark of
each type infers its ID and current dataset when those choices are unambiguous.
## Choose a mark family
Point marks
Scatterplots, symbols, field-driven shape, size, and opacity.
Line and area marks
Ordered paths, grouped series, ranged areas, and density geometry.
Bar marks
Histograms, aggregate bars, grouped layouts, and observed intervals.
Rule marks
Full-span references, bounded intervals, and diagonal endpoints.
Text marks
Data labels and annotations attached to points, bars, or rules.
Rect marks
Discrete heatmap cells and explicit two-dimensional ranges.
Arc marks
Donuts, rose overlays, and radial bars with Polar positions.
## At a glance
| Family | Create | Edit | Initial graphic |
| --- | --- | --- | --- |
| Point | `createPointMark` | `editPointMark`, `jitterPoints`, `removeJitter` | Point collection |
| Line | `createLineMark` | `editLineMark` | Path collection |
| Area | `createAreaMark` | `editAreaMark` | Closed path collection |
| Arc | `createArcMark` | `editArcMark` | Closed sector path collection |
| Bar | `createBarMark` | `editBarMark` | Rect collection |
| Rule | `createRuleMark` | Encoding actions | Line collection |
| Text | `createTextMark` | `editTextMark`, `layoutLabels`, `removeLabelLayout` | Text collection |
| Rect | `createRectMark` | `editRectMark` | Rect collection |
Use `removeMark({ target? })` to remove one complete stable mark owner. It also
removes generated composite children, unreferenced generated datasets, owned
legends, and selection/highlight state. Source data and resources shared by
another mark remain:
```javascript
const barsOnly = layeredProgram.removeMark({ target: "points" });
```
Generated children such as regression lines cannot be removed directly;
select their stable owner instead.
Creation establishes semantic ownership but may leave an empty collection.
Concrete graphics appear when the required encodings make the mark renderable.
Later Canvas, scale, grouping, or appearance edits explicitly rematerialize
those graphics.
## Shared inference
- `data` defaults to the current dataset.
- The first omitted mark ID uses the semantic role: `"point"`, `"line"`,
`"area"`, `"arc"`, `"bar"`, `"rect"`, `"rule"`, or `"text"`.
- A second mark of the same type requires an explicit ID.
- A newly layered mark can inherit compatible data, coordinate, x, and y
encodings from the current layer, or one unique source on the current dataset.
- A grain-preserving aggregate is inherited when both mark recipes support the
same result. For example, a line added after mean bars inherits that mean.
- Bin, stack, offset, and appearance policies are not copied into a mark recipe
that does not support the same final item grain.
- Only field-based positions compatible with the new mark and existing scale
type are inherited. Incompatible channels remain unencoded.
- Passing `data` explicitly starts independent mark assembly and disables
layered position inheritance.
## Related
[Encodings](./encodings.md) · [Statistical layers](./regression.md) ·
[Complete action reference](../reference/actions.md)
# Point Marks
Point marks represent individual observations or derived items. Their semantic
type is `point`; circle, square, diamond, and other symbols are graphical
realizations of that meaning.
## `createPointMark({ id?, data?, shape?, fill?, opacity?, stroke?, strokeWidth? } = {})`
| Option | Type | Default or inference |
| --- | --- | --- |
| `id` | valid user-defined ID | first point mark uses `"point"` |
| `data` | existing dataset ID | current dataset |
| `shape` | supported constant shape | `"circle"` |
| `fill` | non-empty color string | theme color; conflicts with `encodeColor` |
| `opacity` | number from `0` to `1` | `1` |
| `stroke` | non-empty color string | no outline |
| `strokeWidth` | non-negative number | `0` |
```javascript
const program = chart()
.createData({ values: cars })
.createPointMark({ opacity: 0.48, stroke: "white", strokeWidth: 1.25 })
.encodeX({ field: "Horsepower" })
.encodeY({ field: "Miles_per_Gallon" });
```
The collection remains empty until compatible position encodings exist. When a
point is layered after one compatible source, omitted data, coordinate, x, and y
encodings are inherited and persisted. Ambiguous sources require an explicit
target or resource ID.
Radius is an encoding-owned graphical value rather than a `createPointMark`
option. Until a constant or field-driven size is authored, materialization uses
a radius of `3` logical pixels without storing a synthetic semantic encoding.
Use `encodePointRadius({ value })` for another constant radius or `encodeSize`
for a field-driven area mapping.
## `editPointMark({ target?, shape?, fill?, opacity?, stroke?, strokeWidth? })`
```javascript
const diamonds = program.editPointMark({
shape: "diamond",
fill: "#2563eb",
opacity: 0.72,
stroke: "#ffffff",
strokeWidth: 0.6
});
```
`target` may be omitted for the current or only point mark. Supported shapes
are `circle`, `square`, `diamond`, four directional triangles, `plus`, `cross`,
`star`, `hexagon`, and `wye`. Every recipe preserves the same logical target
area. A constant shape conflicts with field-driven `encodeShape`, and constant
fill conflicts with a semantic color encoding.
## `jitterPoints({ target?, channel, maxOffset, seed?, key? })`
Use bounded jitter to separate overlapping Cartesian points without changing
their semantic x/y values:
```javascript
const separated = program.jitterPoints({
channel: "x",
maxOffset: { band: 0.168 },
seed: "origin-strip",
key: "Name"
});
```
| Option | Type | Default or inference |
| --- | --- | --- |
| `target` | existing Cartesian point ID | current or only eligible point mark |
| `channel` | `"x"` or `"y"` | required |
| `maxOffset` | `{ pixels: positiveNumber }` or `{ band: number }` | required; exactly one form |
| `seed` | string or finite number | `0` |
| `key` | non-empty field name | source-row index identity |
`band` is greater than zero and at most `0.5`, and requires a categorical
position scale. The final glyph remains inside both its category slot and plot
bounds. Pixel offsets work with quantitative or categorical positions. The
seed and item identity produce stable offsets; use a unique `key` when offsets
must remain attached to rows after data reordering.
Calling `jitterPoints` again replaces the target policy and recomputes from the
semantic base position, so offsets never accumulate. Canvas, scale, data,
shape, radius, stroke, selection, highlight, and facet rematerialization retain
the assignment. Selectors that read `channel` still see semantic values, while
selectors that read concrete `property` values see final jittered geometry.
## `removeJitter({ target? } = {})`
```javascript
const restored = separated.removeJitter();
```
This removes the stored graphical assignment and restores positions directly
mapped from the mark's semantic encodings. Jitter is deterministic random
separation, not collision-free packing or a beeswarm layout. Polar point jitter
is not currently supported.
## Continue with encodings
- [Position encodings](../position-encodings.md)
- [Appearance encodings](../appearance.md)
- [Series and grouping encodings](../series-encodings.md)
## Errors and limitations
Point IDs must be unique, the dataset must exist, opacity must be between `0`
and `1`, and stroke width must be non-negative. At least one property is
required by `editPointMark`. Jitter requires a complete Cartesian x/y point
mark, a unique target, a valid offset form, and unique values for an explicit
identity key.
# Line and Area Marks
Line and area marks materialize ordered backend-neutral paths. Lines connect
values; areas close two edges or one density edge against a baseline.
## Line marks
### `createLineMark({ id?, data?, stroke?, strokeWidth?, opacity?, curve?, closed? } = {})`
```javascript
const program = chart()
.createData({ values: cars })
.createLineMark({ stroke: "#7c3aed", opacity: 0.55 })
.encodeX({ field: "Year", fieldType: "temporal" })
.encodeY({ field: "Acceleration", aggregate: "mean" });
```
The first ID is `"line"`, data defaults to current data, stroke width defaults
to `2`, opacity defaults to `1`, and curve defaults to `"linear"`. A line begins
as an empty path collection because later grouping determines series
cardinality. Complete encodings materialize sorted commands; color and stroke
dash may regroup them.
For a direct quantitative line, `encodeX` and `encodeY` may be called in either
order. The first action stores valid incomplete semantic and scale state while
the path remains empty; the second completes the same final layer, resolved
scales, and graphics in both orders. Aggregate y lines are different: their
grain requires a compatible temporal x encoding and rejects a quantitative
partial that would change the meaning of the line.
When a line is layered immediately after a compatible encoded mark, omitted
data and positions are inferred. Compatible aggregate grain is inferred too,
so an aggregate trend over aggregate bars needs no repeated `encodeX` or
`encodeY` call:
```javascript
const layered = chart()
.createData({ values: cars })
.createBarMark({ id: "bars" })
.encodeX({ field: "Year", fieldType: "temporal" })
.encodeY({ field: "Acceleration", aggregate: "mean" })
.createLineMark({ id: "trend", strokeWidth: 3 });
```
Both layers reference the same x/y scales. The bar owns its bandwidth while
line vertices use the shared bar centers. Incompatible bin, stack, or offset
policies are not transferred. Pass `data` explicitly to assemble an independent
line with explicit encodings and scale IDs.
### `editLineMark({ target?, stroke?, strokeWidth?, opacity?, curve?, closed? })`
```javascript
program.editLineMark({
curve: "monotone",
stroke: "#7c3aed",
strokeWidth: 4,
opacity: 0.55
});
```
Supported curves are `linear`, `step`, `step-before`, `step-after`, `basis`,
`cardinal`, `monotone`, and `natural`. Smooth curves use cubic commands and
two-point series fall back to linear. Monotone paths require strictly increasing
materialized x values.
A constant `stroke` conflicts with field-driven `encodeColor`. Appearance is
stored and reapplied whenever scale, Canvas, or grouping changes rebuild paths.
## Polar lines and radar paths
Line marks also accept theta/radius positions. The two encoding actions may be
called in either order; one channel remains valid semantic state but does not
produce a path until both are present.
```javascript
const radar = chart()
.createData({ values: rows })
.createLineMark({ closed: true, strokeWidth: 2.5 })
.encodeTheta({ field: "category", fieldType: "nominal" })
.encodeR({ field: "score", scale: { domain: [0, 1] } })
.encodeGroup({ field: "series" });
```
`closed` defaults to `false`. When true, every series ends with one closing
`Z` command; the first row is not duplicated. `editLineMark({ closed })`
switches an existing Polar line between open and closed. Polar lines currently
accept only `curve: "linear"`; other interpolation modes remain available to
Cartesian lines. Color, stroke dash, grouping, legends, scale edits, Canvas
resizing, filtering, selection, and highlighting all rebuild the same path
through the shared line materialization lifecycle.
## Area marks
### `createAreaMark({ id?, data?, fill?, opacity?, stroke?, strokeWidth?, curve? } = {})`
```javascript
const area = chart()
.createData({ values: intervalRows })
.createAreaMark()
.encodeX({ field: "year", fieldType: "temporal" })
.encodeYRange({ lower: "lower", upper: "upper" });
```
Area fill defaults to `"#4c78a8"`, opacity to `0.2`, and curve to `"linear"`.
An area becomes renderable with exactly one ranged orientation, or with a
complete density value/density pair. `encodeGroup` creates one closed path per
nominal group without creating a scale or legend.
### `editAreaMark({ target?, fill?, opacity?, stroke?, strokeWidth?, curve? })`
```javascript
program.editAreaMark({
opacity: 0.35,
stroke: "#334155",
strokeWidth: 1.5,
curve: "cardinal"
});
```
`stroke: false` removes both outline and stored width. A width-only edit
requires an active outline. Constant fill cannot replace a field-driven color
encoding. Complete paths rematerialize immediately; incomplete paths retain the
configuration until their encodings become renderable.
## Arc marks
### `createArcMark({ id?, data?, innerRadius?, padAngle?, fill?, opacity?, stroke?, strokeWidth? } = {})`
```javascript
const donut = chart()
.createCanvas({ width: 640, height: 500, margin: 55 })
.createData({ values: cars })
.createArcMark({ innerRadius: 0.56, padAngle: 1.5 })
.encodeTheta({ field: "Origin", aggregate: "count" })
.encodeColor({ field: "Origin", palette: "tableau10" });
```
`innerRadius` is a ratio from `0` inclusive to `1` exclusive. `padAngle` uses
degrees. Count theta creates proportional pie or donut sectors. Categorical
theta plus quantitative `encodeR` creates equal-angle radial sectors; repeated
rows in one angle band are drawn larger first so smaller overlays remain
visible. Arc graphics are ordinary closed path commands, so renderers do not
interpret Polar semantics.
### `editArcMark({ target?, innerRadius?, padAngle?, fill?, opacity?, stroke?, strokeWidth? })`
Geometry and appearance edits rebuild complete sectors. An incomplete arc
retains the edited settings until both required encodings exist. Constant
`fill` cannot replace a field-driven color encoding.
## Related
[Position encodings](../position-encodings.md) · [Polar line tutorial](../../tutorials/polar-lines.md) ·
[Series encodings](../series-encodings.md) ·
[Density](../encodings.md#atomic-density) · [Error bands](../error-bands.md)
# Bar Marks
Bar marks represent binned counts, aggregate categories, grouped or stacked
partitions, and observed quantitative intervals.
## `createBarMark({ id?, data?, fill?, opacity?, stroke?, strokeWidth? } = {})`
```javascript
const program = chart()
.createData({ values: cars })
.createBarMark({ opacity: 0.78, stroke: "#0f172a", strokeWidth: 1.25 })
.encodeHistogram({ field: "Displacement" });
```
The first ID is `"bar"` and data defaults to current data. Creation starts with
an empty rect collection because binning, aggregation, stacking, grouping, and
width determine the final rectangle count and geometry.
Creation-time `fill`, `opacity`, `stroke`, and `strokeWidth` use the same
validation and persistent materialization config as `editBarMark`. Constant
fill conflicts with a later field-driven color encoding.
For an aggregate bar, combine an ordinal position and quantitative aggregate.
Complete aggregate and ranged bars immediately use the default `0.72` band
width; call `encodeBarWidth` only to override it:
```javascript
program
.encodeX({ field: "year", fieldType: "ordinal" })
.encodeY({ field: "perc", aggregate: "mean" })
.encodeBarWidth({ band: 0.72 });
```
`encodeColor({ layout })` can arrange partitions with `stack`, `fill`, `group`,
`overlay`, or `diverging`. Group layout invokes `encodeXOffset` for vertical
bars or `encodeYOffset` for horizontal bars internally.
Observed interval bars instead combine one categorical axis with `encodeYRange`
or `encodeXRange`.
## `editBarMark({ target?, fill?, opacity?, stroke?, strokeWidth? })` {#edit-bar-mark}
```javascript
program.editBarMark({
opacity: 0.8,
stroke: "#111827",
strokeWidth: 1.5
});
```
The current or only bar is inferred when `target` is omitted. Constant fill
conflicts with field-driven color, while opacity and outline remain editable.
Geometry and semantic aggregation are unchanged and appearance is reapplied
after Canvas or scale rematerialization.
## Related
[Histogram positions](../position/histogram.md) ·
[Ordinal bars](../position/ordinal-bars.md) ·
[Series encodings](../series-encodings.md)
# Rule Marks
Rule marks represent reference lines and intervals. They use concrete line
primitives without exposing a renderer-specific path format.
## `createRuleMark({ id?, data? } = {})`
```javascript
const threshold = chart()
.createData({ values: [{ limit: 25 }] })
.createRuleMark()
.encodeY({ field: "limit" })
.encodeStroke({ value: "#dc2626" })
.encodeStrokeWidth({ value: 2 });
```
The first ID is `"rule"`, data defaults to current data, and creation assigns no
position or style. A single x or y encoding creates a full plot-span rule. Add
`encodeY2` for a bounded vertical interval, `encodeX2` for a bounded horizontal
interval, or both secondary endpoints for a diagonal rule. Every endpoint may
use a field or constant datum.
When a rule is layered without explicit `data`, it may first inherit a
compatible source layer's x/y positions. A constant datum endpoint takes
precedence over the inherited opposite position when no secondary endpoint
exists: datum y removes only inherited x and creates a horizontal full-span
rule, while datum x symmetrically creates a vertical full-span rule. Field
endpoints preserve the orthogonal inherited channel for interval construction.
Rules created with explicit `data` do not apply this provenance-based cleanup.
Rule appearance is edited through encoding actions rather than a separate mark
editor:
- `encodeStroke`
- `encodeStrokeWidth`
- `encodeStrokeDash`
- `encodeOpacity`
`encodeStrokeWidth({ field, scale? })` maps a non-negative quantitative field
to one concrete width per rule item. Use `createLegend({ channels:
["strokeWidth"] })` for a sampled quantitative guide. Constant
`encodeStrokeWidth({ value })` remains available and removes the field binding.
Every complete rule stores concrete `x1`, `y1`, `x2`, and `y2` values. An
incomplete endpoint combination remains empty until another encoding completes
it. Canvas and scale changes recompute all endpoints.
## Related
[Appearance encodings](../appearance.md) · [Error bars](../error-bars.md) ·
[Advanced axis components](../../advanced/axis-components.md)
# Text marks
Text marks turn data values into visible labels. Add one after a compatible point,
bar, rect, or rule layer and ggaction persists that layer as the annotation source.
## `createTextMark(options?)`
```javascript
const annotated = points
.createTextMark({
fontSize: 10,
fill: "#334155",
dx: 7,
dy: -6,
align: "left",
baseline: "bottom"
})
.encodeText({ field: "Series_Title" });
```
The first omitted ID is `"text"`. When `data` is omitted, the current compatible
layer—or one unique compatible layer—supplies its dataset, Cartesian position,
and final visual-item grain. This means aggregate bars receive one label per bar,
while rect labels anchor at cell centers. Pass `data` explicitly to assemble an independent
text layer with `encodeX` and `encodeY`.
Creation options are `id`, `data`, `text`, `fill`, `opacity`, `fontSize`,
`fontFamily`, `fontWeight`, `align`, `baseline`, `rotation`, `dx`, and `dy`.
The `text` option is constant-content shorthand.
## Font weights
`fontWeight` accepts a non-empty CSS weight string or a finite number. To keep
Browser Canvas and Node PNG output consistent, numeric values are rounded to the
nearest 100 and clamped to the backend-safe `100`–`900` range before rendering.
For example, `650` renders as `700`. The authored value remains unchanged in the
program state. Titles, facet headers, legends, and Cartesian or Polar axis text
use this same renderer policy.
## `encodeText({ target?, field?, value?, format? })`
Provide exactly one of `field` or `value`. `format` defaults to `"auto"`; fixed
decimal formats from `".0f"` through `".12f"` are available for numeric content.
Calling `encodeText` again replaces the previous field or constant assignment.
```javascript
bars
.createTextMark({ dy: -4, align: "center" })
.encodeText({ field: "value", format: ".1f" });
```
## `editTextMark(options)`
Edit typography, opacity, alignment, baseline, rotation, or `dx`/`dy` without
changing the semantic anchor:
```javascript
const revised = annotated.editTextMark({
fill: "#b91c1c",
fontWeight: 600,
dx: 10
});
```
At least one property is required. Canvas and scale edits rematerialize both the
source geometry and attached labels.
## `layoutLabels(options?)`
Use explicit offsets for intentional placement, or assign collision-aware
placement after the text encoding is complete:
```javascript
const arranged = annotated.layoutLabels({
axis: "both",
padding: 3,
maxDisplacement: 48,
bounds: "plot",
leader: {
stroke: "#94a3b8",
strokeWidth: 0.8,
opacity: 0.9
}
});
```
`target` resolves the current complete text mark, then one unique complete text
mark. Defaults are `axis: "both"`, `padding: 3`, `maxDisplacement: 48`,
`bounds: "plot"`, and `leader: false`. Use `axis: "x"` or `"y"` to constrain
movement, or `bounds: "canvas"` to use the complete Canvas rectangle.
The action visits labels in stable materialized order and keeps an existing
position when it already fits. If the requested distance cannot eliminate all
overlap or overflow, the program retains a deterministic best effort and stores
`overlap` or `bounds` warnings in the label-layout resolution summary. It never
expands margins, reduces font size, or searches for an unrelated nearby mark.
Calling `layoutLabels()` again replaces the complete policy and recomputes from
semantic base text rather than accumulating offsets. Text, data, scale, source
mark, and Canvas changes replay that same policy.
See the complete
[Gapminder country-label program](https://github.com/ggaction/ggaction/blob/main/examples/gapminder-country-labels/program.js)
for a point-attached label layer with leaders.
## `removeLabelLayout(options?)`
```javascript
const originalPlacement = arranged.removeLabelLayout();
```
The action removes the policy and any leader collection, then restores the
current semantic base text positions. It does not remove the text mark or its
source relation.
## Related
[Point marks](./point.md) · [Bar marks](./bar.md) · [Rule marks](./rule.md) ·
[Encodings](../encodings.md) · [Annotation recipe](../../recipes/annotations.md)
# Rect Marks
Rect marks represent independent two-dimensional cells. They are distinct from
bars: rects do not infer aggregation, a zero baseline, stacking, or bar width.
## `createRectMark({ id?, data?, fill?, opacity?, stroke?, strokeWidth? } = {})`
```javascript
const program = chart()
.createCanvas({ width: 640, height: 400, margin: 60 })
.createData({ values: rows })
.createRectMark()
.encodeX({ field: "year", fieldType: "ordinal" })
.encodeY({ field: "country", fieldType: "nominal" })
.encodeColor({
field: "life_expect",
fieldType: "quantitative",
scale: { type: "sequential", palette: "viridis" }
});
```
The first omitted ID is `"rect"`, and data defaults to the current dataset.
Two discrete x/y encodings create one full-band cell for each complete observed
row. Missing category combinations are not synthesized.
Ranged rects use complete continuous endpoint pairs:
```javascript
program
.encodeX({ field: "xStart" })
.encodeX2({ field: "xEnd", fieldType: "quantitative" })
.encodeY({ field: "yStart" })
.encodeY2({ field: "yEnd", fieldType: "quantitative" });
```
Until one supported topology is complete, semantic intent is retained and the
rect collection stays empty. Missing endpoint or color values omit only their
row. `encodeColor` accepts categorical and continuous color scales.
## `editRectMark({ target?, fill?, opacity?, stroke?, strokeWidth? })`
```javascript
const outlined = program.editRectMark({
opacity: 0.9,
stroke: "#f8fafc",
strokeWidth: 1.5
});
```
The target is inferred when exactly one rect is eligible. Constant `fill`
cannot be combined with a field-driven color encoding. Use `stroke: false` to
remove the outline; a simultaneous `strokeWidth` is invalid.
## Cell labels and selection
Calling `createTextMark()` after a complete rect inherits its data and x/y
positions. Labels anchor at cell centers, and an omitted text fill resolves to
light or dark text for a realized six-digit hex cell color. Other fill syntaxes
retain the normal text default, and an explicit text fill always
wins. `selectMarks`, `filterMarks`, and `highlightMarks` operate on final
observed cells at item grain.
## Related
[Position encodings](../position-encodings.md) ·
[Series and color encodings](../series-encodings.md) ·
[Text marks](./text.md)
# Encodings
Encoding actions connect data fields or constants to chart channels. Ordinary
authors choose the relationship; ggaction infers a unique target, coordinate,
scale ID, and field type when the stored program makes that choice safe.
## Choose a family
Position x/y, ranges, offsets, Polar theta/radius, rules, and Parallel dimensions.
Series Color, stroke dash, grouping, stroke width, and explicit path order.
Appearance Point size, shape, opacity, radius, and constant mark style.
Text Field-driven or constant annotation content and formatting.
Scales Domains, ranges, types, palettes, missing values, and precedence.
Exact action contracts Complete generated signatures, options, defaults, and errors.
## Supported mark/channel matrix
The tables below are generated from the same reviewed capability registry used by the focused API pages.
### Position channels
| Action | Supported marks | Field types | Important modes |
| --- | --- | --- | --- |
| `encodeX` | point, line, area, bar, rect, rule, text | point/bar/rect/rule/text: quantitative, temporal, ordinal, nominal; line/area: quantitative, temporal | field; rule also accepts datum; bar accepts aggregate or bin |
| `encodeY` | point, line, area, bar, rect, rule, text | point/line/bar/rect/rule/text: quantitative, temporal, ordinal, nominal; area: quantitative, temporal | field; rule also accepts datum; bar accepts aggregate or count |
| `encodeX2` / `encodeY2` | area, ranged bar, rect, rule | area/ranged bar/rect/rule: matching primary | secondary field; rule also accepts datum |
| `encodeTheta` | point, line, arc | point/line: quantitative, temporal, ordinal, nominal; arc: ordinal, nominal | arc accepts aggregate: count or weighted sum for proportional sectors |
| `encodeR` | point, line, arc | point/line/arc: quantitative | radial position; arc combines it with a categorical theta band |
| `encodeParallelCoordinates` | line | line: quantitative, ordinal | atomic ordered dimensions; one namespaced scale and axis per dimension |
### Color channels
| Mode | Supported marks | Field types | Important options |
| --- | --- | --- | --- |
| Categorical | point, line, area, bar, rect, arc | point/line/area/bar/rect/arc: nominal, ordinal | bar/area layout; arc overlay; palette and ordinal scale |
| Continuous | point, aggregate bar, rect | point/rect: quantitative, temporal; aggregate bar: quantitative | sequential scale; aggregate required for a different bar measure |
| Discretized continuous | point | point: quantitative | quantize, quantile, or threshold scale |
### Selection and guides
| Action | Supported marks | Grain | Result |
| --- | --- | --- | --- |
| `selectMarks` / `highlightMarks` | point, bar, line, area, rect, arc, rule | item; stacked bars also support stack | selection intent and mark-specific durable emphasis |
| Legend family | Supported marks | Channels |
| --- | --- | --- |
| Categorical | point, line, area, bar, rect, arc | color, shape, strokeDash, or compatible composites |
| Continuous gradient | point, aggregate bar, rect | sequential color |
| Discretized interval | point | quantize, quantile, or threshold color |
| Sampled | point, line, rule | field opacity, size, or strokeWidth |
| Axis family | Create | Edit | Editable components |
| --- | --- | --- | --- |
| Cartesian complete axis | `createXAxis` / `createYAxis` / `createAxes` | `editXAxis` / `editYAxis` | line, ticks, labels, ticksAndLabels, title, position |
| Polar complete axis | `createThetaAxis` / `createRadialAxis` / `createAxes` | `editThetaAxis` / `editRadialAxis` | line, ticks, labels, ticksAndLabels, title, angle or position |
| Parallel dimension axes | `createAxes` | | line, ticks, labels, title from each stored dimension |
## Atomic relationships
Some relationships require several channels to change together. Prefer their
atomic action unless you intentionally need the lower-level steps.
| Relationship | Shortest action | What changes together | Complete example |
| --- | --- | --- | --- |
| Histogram | `encodeHistogram({ field: "value" })` | Bin x, count y, stack policy, and both scales | [Histogram recipe](../recipes/histogram.md) |
| Density | `encodeDensity({ field: "value" })` | Immutable density data, value/density positions, grouping, and area paths | [Density tutorial](../tutorials/density-area.md) |
| Horizon | `encodeHorizon({ x: "time", y: "value" })` | Signed bands, folded positions, color, and source-facing x guide | [Horizon recipe](../recipes/horizon.md) |
| Parallel coordinates | `encodeParallelCoordinates({ dimensions: ["a", "b"] })` | Ordered local scales, row paths, and dimension axes | [Parallel recipe](../recipes/parallel-coordinates.md) |
### Atomic density {#atomic-density}
`encodeDensity` derives immutable kernel-density rows and authors the value and
density positions together. It infers a Gaussian kernel, automatic bandwidth
and extent, 100 samples, and vertical density placement. `groupBy`, `kernel`,
`normalization`, categorical placement, side, and two-value split remain
available through its exact action contract.
### Atomic Horizon {#atomic-horizon}
`encodeHorizon` derives signed bands around an inferred or explicit baseline.
It accepts existing compatible x/y encodings or explicit `x` and `y`, then
owns the folded y/y2 positions and positive/negative palettes as one action.
Horizon charts intentionally keep only the source-facing x guide.
`editDensity` and `editHorizon` create immutable derived-data revisions and
rematerialize their connected scales, paths, and guides. The exact option and
error contracts live in the [Encoding Action Reference](../reference/actions/encodings.md).
## Shared inference and ordering
- `target` uses the current compatible mark, then one unique compatible mark.
- A missing Cartesian, Polar, or Parallel coordinate is created only when the
channel family determines it unambiguously.
- Scale IDs default to the channel name; explicit IDs create independent
resources.
- Position calls may arrive before or after a compatible mark. Incomplete
semantic state remains invisible until the required relationship is complete.
- Layered marks reuse compatible position encodings when omitted instead of
requiring duplicate x/y calls.
- Ambiguity produces an error instead of selecting the first resource.
## Errors and limitations
Unsupported mark/channel/field combinations fail before partial state is
authored. Use the generated compatibility matrix above, then open the focused
family page for inference and ordering rules. If a valid action still selects
nothing, see [Troubleshooting](../troubleshooting.md#a-target-cannot-be-inferred).
## Related
[Position Encodings](./position-encodings.md) ·
[Series Encodings](./series-encodings.md) ·
[Appearance](./appearance.md) · [Scale Options](./scales.md)
# Regression
`createRegression()` layers grouped linear, polynomial, or LOESS fits over an
existing quantitative point mark. Linear and polynomial fits can include mean
or prediction interval bands; LOESS is line-only.
## At a glance
| Action | Required decision | Inferred or default behavior |
| --- | --- | --- |
| `createRegression(options?)` | Nothing when one eligible point layer exists | Target, x, y, grouping, linear method, 95% mean interval, band and line appearance |
| `editRegression(options)` | At least one model or component option | Current or unique owner; unchanged values and fitted data retained |
## `createRegression(options?)`
```javascript
const program = points.createRegression();
```
The shortest call infers the target from the current or only eligible point
mark, x/y from its quantitative positions, and group from the unique nominal
field used by color and/or shape. Dataset, Cartesian coordinate, and x/y scales
come from that point layer. Inference fails rather than choosing among multiple
targets or group fields. Explicit `groupBy: undefined` requests one model.
```javascript
program.createRegression({
confidence: 0.95,
band: {
color: "#111111",
opacity: 0.18,
stroke: "#334155",
strokeWidth: 1
},
line: { strokeWidth: 3, curve: "linear" }
});
```
| Option | Type | Default |
| --- | --- | --- |
| `target` | eligible point mark ID | inferred |
| `x`, `y` | quantitative field names | target x/y fields |
| `groupBy` | nominal field name or `undefined` | unique color/shape field |
| `method` | `"linear"`, `"polynomial"`, or `"loess"` | `"linear"` |
| `degree` | positive integer for polynomial | `2` |
| `span` | number greater than `0` and at most `1` for LOESS | `0.75` |
| `confidence` | number strictly between `0` and `1` | `0.95` |
| `interval` | `"mean"` or `"prediction"` | `"mean"` |
| `band` | appearance object or `false` | default band; no band for LOESS |
| `band.color` | color string | `"#111111"` |
| `band.opacity` | number from `0` to `1` | `0.18` |
| `band.stroke` | non-empty color string | no outline |
| `band.strokeWidth` | non-negative finite number | `1` with stroke |
| `line.strokeWidth` | non-negative finite number | `3` |
| `line.curve` | supported curve interpolation | `"linear"` |
Choose another model or interval without coordinating its child layers:
```javascript
points.createRegression({ method: "polynomial", degree: 2 });
points.createRegression({ method: "loess", span: 0.55 });
points.createRegression({ interval: "prediction" });
```
Polynomial degree `1` retains polynomial provenance while producing the same
fit as linear regression. Prediction intervals include residual uncertainty
and are therefore at least as wide as matching mean intervals. LOESS does not
accept `confidence`, `interval`, or a band object; its omitted or `false` band
produces only the fitted line. Linear and polynomial bands can also be disabled
with `band: false`.
The action keeps each fitted dataset, band, and line associated with its target
point mark, so multiple point layers can add independent regressions in one
program. The exact generated IDs are internal and are not required in ordinary
chart-authoring code.
Its trace records `createRegressionData`, optional `createRegressionBand`, and
`createRegressionLine` as meaningful children. The band child validates
regression provenance and delegates its interval geometry to a nested
`createErrorBand` call. See
[Actions and trace trees](../concepts/actions-and-trace.md) when that
decomposition matters.
## Editing a regression
Use the original point owner, not generated band or line IDs:
```javascript
const revised = program.editRegression({
target: "points",
method: "polynomial",
degree: 2,
band: { color: "#a78bfa", opacity: 0.16 },
line: { strokeWidth: 4 }
});
```
`method`, `degree`, `span`, `confidence`, and `interval` follow the same
method-specific rules as creation. A statistical change creates one new
immutable fitted-data revision, rebinds every owned regression component, and
releases the old revision when nothing references it. A request containing
only `band` or `line` appearance retains the existing fitted rows.
Set `band: false` or switch to LOESS to remove the owned band. Switching back
to linear or polynomial regression recreates the band by default; a band
object can also restore it explicitly. Target omission uses the current or
only regression owner and fails on ambiguity.
## Editing generated components
```javascript
const emphasized = program
.editRegressionBand({
color: "#475569",
opacity: 0.12,
stroke: "#111827",
strokeWidth: 1.5,
curve: "cardinal"
})
.editRegressionLine({ strokeWidth: 5 });
```
Each action infers the only compatible generated component or accepts an
explicit target. Band edits delegate to `editAreaMark`; line edits delegate to
`editLineMark`. They preserve fitted data, result fields, grouping, coordinates,
and scales. `stroke: false` removes a band outline. Band curves use the same
eight-value interpolation vocabulary as area and line marks.
## Errors and limitations
The action rejects ambiguous point targets, missing quantitative x/y
encodings, incompatible coordinates or scales, ambiguous nominal grouping,
invalid method-specific parameters, and groups that cannot support the chosen
fit. Robust LOESS reweighting and LOESS confidence bands are not supported.
Failed calls leave the earlier immutable program unchanged.
## Related
[Regression scatterplot tutorial](../tutorials/regression-scatterplot.md) ·
[Data](./data.md) · [Marks](./marks.md) · [Encodings](./encodings.md) ·
[Guides](./guides.md)
# Error Bars
`createErrorBar()` materializes vertical or horizontal intervals from either
grouped statistics or existing center/lower/upper fields. It can infer its
inputs from an already encoded layer or accept both channel roles directly.
## At a glance
| Action | Shortest call | Result |
| --- | --- | --- |
| `createErrorBar` | `createErrorBar()` after one eligible encoded layer | Mean 95% confidence intervals sharing that layer's data, coordinate, and scales |
| `editErrorBar` | `editErrorBar({ opacity: 0.6 })` | Main rule and owned caps rematerialized without replacing interval data |
## `createErrorBar(options?)`
```javascript
const intervals = chart()
.createCanvas()
.createData({ values })
.createErrorBar({
x: { field: "group", fieldType: "nominal" },
y: { field: "value" }
});
```
```typescript
createErrorBar({
id?: string;
target?: string;
data?: string;
x?: PositionChannel | StatisticalIntervalChannel | ExplicitIntervalChannel;
y?: PositionChannel | StatisticalIntervalChannel | ExplicitIntervalChannel;
groupBy?: string;
coordinate?: string;
caps?: boolean;
capSize?: number;
stroke?: string;
strokeWidth?: number;
strokeDash?: "solid" | "dashed" | "dotted" | "dashdot" | readonly number[];
opacity?: number;
} = {})
```
The channel types are:
```typescript
type PositionChannel = {
field?: string;
fieldType?: "nominal" | "ordinal" | "temporal";
scale?: ScaleOptions;
};
type StatisticalIntervalChannel = {
field?: string;
center?: "mean" | "median";
extent?: "stderr" | "stdev" | "ci" | "iqr";
level?: number;
scale?: ScaleOptions;
};
type ExplicitIntervalChannel = {
center: string;
lower: string;
upper: string;
scale?: ScaleOptions;
};
```
Exactly one channel is positional and the other is quantitative. Putting the
interval on y creates vertical rules; putting it on x creates horizontal rules.
No orientation flag is required. Statistical mean defaults to a two-sided
`0.95` Student-t confidence interval. Median is supported only with
`extent: "iqr"`; `level` is valid only for `extent: "ci"`.
The independent position field is always part of statistical grouping.
`groupBy` can add one more grouping field. Group order follows first appearance
in the source data. Groups without enough valid quantitative values are omitted.
## Horizontal intervals
```javascript
const horizontal = chart()
.createCanvas()
.createData({ values })
.createErrorBar({
x: { field: "measurement" },
y: { field: "group", fieldType: "nominal" }
});
```
This stores y/x/x2 encodings and creates vertical fixed-pixel caps.
## Existing interval fields
```javascript
const explicit = chart()
.createCanvas()
.createData({ values: intervalRows })
.createErrorBar({
x: { field: "group", fieldType: "nominal" },
y: {
center: "meanValue",
lower: "lowerValue",
upper: "upperValue"
},
caps: false
})
.createGuides();
```
Explicit mode does not derive another dataset and does not accept `groupBy`,
`field`, `extent`, or `level` on the interval channel. The center field becomes
the interval-axis title unless a later guide action supplies another title.
## Inference from an encoded layer
```javascript
const overlay = chart()
.createCanvas()
.createData({ values })
.createPointMark()
.encodeX({ field: "group", fieldType: "ordinal" })
.encodeY({ field: "value" })
.createErrorBar();
```
With omitted x and y, `target` selects an existing layer. Without `target`, the
action uses the current eligible layer, then one unique eligible layer. The
layer may use any compatible mark type; eligibility comes from its complete
field-based x/y encodings. Data, coordinate, and x/y scale IDs are reused.
Passing only an existing scale `id` also preserves that scale's stored domain,
range, `nice`, and `zero` policy; interval defaults are used only for new scales.
A semantic `group` encoding is inferred as statistical grouping. Color remains
appearance and does not silently change the statistic. Ambiguous eligible
layers fail instead of selecting one arbitrarily.
## Defaults and graphical result
| Behavior | Default |
| --- | --- |
| ID | `"errorBar"` when available |
| Center and extent | mean, confidence interval |
| Confidence level | `0.95` |
| Coordinate | inferred, otherwise `"main"` Cartesian |
| Main rule and caps | `#4c78a8`, width `1.5`, solid, opacity `1` |
| Cap width | `8` logical Canvas pixels |
Caps preserve their logical-pixel width when the Canvas or positional scales
change. `createGuides()` infers the independent field title and a statistical
interval-axis title such as `mean(value)`.
Set `caps: false` to omit cap layers. `capSize` must be positive and affects
enabled caps only. `strokeWidth` is non-negative, `opacity` is between `0` and
`1`, and `strokeDash` accepts a named style or an explicit non-negative dash
array. The same appearance is assigned to the main rule and both caps.
```javascript
const styled = intervals.createErrorBar({
id: "styledErrorBar",
data: "data",
x: { field: "group", fieldType: "nominal", scale: { id: "styledX" } },
y: { field: "value", scale: { id: "styledY" } },
capSize: 16,
stroke: "#d9485f",
strokeWidth: 3,
strokeDash: [8, 4],
opacity: 0.8
});
```
## Editing error bars
Use the stable error-bar owner instead of editing generated cap layers:
```javascript
const edited = intervals.editErrorBar({
caps: true,
capSize: 16,
stroke: "#d9485f",
strokeWidth: 3,
strokeDash: [8, 4],
opacity: 0.8
});
```
The options are `target`, `caps`, `capSize`, `stroke`, `strokeWidth`,
`strokeDash`, and `opacity`. Omitted values retain their current setting.
Omit `target` when the current or unique error bar is unambiguous.
`caps: false` removes both owned cap resources. A later `caps: true` recreates
them from the owner's stored data, fields, coordinate, and scales. The main
interval and its immutable statistical or explicit dataset remain unchanged.
The complete request is validated before the wrapped rematerialization runs.
## Errors and current limitations
The action rejects missing data, incompatible or ambiguous source layers,
ambiguous channel roles, incomplete or non-quantitative explicit fields,
invalid statistics or appearance values, and occupied generated IDs. Failed
calls leave the earlier immutable program unchanged.
Center symbols, per-row cap sizes, field-driven rule widths, and statistical
parameter revision are not part of the current edit action.
## Related
[Error-bar tutorial](../tutorials/error-bar.md) ·
[Interval data](./data/statistical-transforms.md#create-interval-data) ·
[Rule marks](./marks.md) · [Guides](./guides.md)
# Error Bands
`createErrorBand()` creates a vertical or horizontal confidence or interval
ribbon as one ordinary area layer. It can derive grouped interval rows or
consume existing center/lower/upper fields.
## At a glance
| Action | Shortest call | Result |
| --- | --- | --- |
| `createErrorBand` | `createErrorBand()` after one eligible encoded layer | One grouped interval dataset and ordinary area/boundary layers |
| `editErrorBand` | `editErrorBand({ opacity: 0.35 })` | Existing band body rematerialized without replacing interval data |
| `editErrorBandBoundary` | `editErrorBandBoundary({ strokeWidth: 2 })` | Both owned boundaries created or edited |
## `createErrorBand(options?)`
The following runnable fragment assumes `gapminder` is an in-memory array of
row objects loaded by the application.
```javascript
import { chart } from "ggaction";
const program = chart()
.createCanvas({
width: 760,
height: 480,
margin: { top: 90, right: 150, bottom: 70, left: 80 }
})
.createData({ values: gapminder })
.createErrorBand({
x: { field: "year", fieldType: "temporal" },
y: { field: "life_expect" },
groupBy: "cluster"
})
.encodeColor({
target: "errorBand",
field: "cluster",
scale: { palette: "tableau10" }
})
.createGuides();
```
```typescript
createErrorBand({
id?: string;
target?: string;
data?: string;
x?: PositionChannel | StatisticalIntervalChannel | ExplicitIntervalChannel;
y?: PositionChannel | StatisticalIntervalChannel | ExplicitIntervalChannel;
groupBy?: string;
coordinate?: string;
fill?: string;
opacity?: number;
curve?: CurveInterpolation;
boundaries?: false | {
stroke?: string;
strokeWidth?: number;
strokeDash?: DashStyle | readonly number[];
opacity?: number;
curve?: CurveInterpolation;
};
} = {})
```
The channel contracts are:
```typescript
type PositionChannel = {
field?: string;
fieldType?: "quantitative" | "temporal";
scale?: ScaleOptions;
};
type StatisticalIntervalChannel = {
field?: string;
center?: "mean" | "median";
extent?: "stderr" | "stdev" | "ci" | "iqr";
level?: number;
scale?: ScaleOptions;
};
type ExplicitIntervalChannel = {
center: string;
lower: string;
upper: string;
scale?: ScaleOptions;
};
```
Statistical mode defaults to a mean, two-sided `0.95` Student-t confidence
interval. The independent position field is always part of grouping;
`groupBy` adds one series field. Group and path order follow first appearance
in the source.
Explicit mode uses the current dataset directly. It retains the center field
for the inferred axis title, while the closed area geometry uses lower and
upper fields.
Exactly one channel must describe the interval. A vertical band stores y/y2;
a horizontal band stores x/x2. Quantitative and temporal independent positions
are both supported, including on optional lower and upper boundary lines. For
example, a horizontal interval inferred
from an existing Cars scatterplot can be authored as:
```javascript
scatterplot.createErrorBand({
x: { field: "Displacement", center: "mean", extent: "ci" },
y: { field: "Acceleration" },
groupBy: "Origin",
boundaries: { stroke: "#334155", strokeWidth: 1.5 }
});
```
## Inference and ownership
If x or y is omitted, the action selects an encoded source by explicit
`target`, then the current eligible layer, then one unique eligible layer. It
reuses that layer's data, coordinate, scales, and explicit group encoding.
Two quantitative source axes are ambiguous unless an interval option identifies
the interval axis.
When an explicit channel scale contains only an existing `id`, that stored
scale definition is reused exactly. Error-band defaults apply only when a new
scale must be created.
The first omitted ID is `"errorBand"`; statistical rows use
`"errorBandIntervalData"`. A second band requires an explicit ID. The action
records one `createErrorBand` trace node with wrapped interval-data, area,
the matching atomic position-range action, and grouping children.
`fill` and `opacity` default to the area defaults. Field-driven fill remains an
explicit `encodeColor` call so color, grouping, and legend ownership stay
separate. The same grouped overlay-color composition applies to vertical y/y2
and horizontal x/x2 bands.
`curve` defaults to `"linear"` and accepts the shared eight-value line/area
curve vocabulary.
`boundaries` defaults to `false`. An object adds deterministic lower and upper
ordinary line layers after the filled band. Stroke, width, dash, and opacity
default to the shared mark color, `1`, solid, and `1`. Boundary paths inherit
the band curve unless `boundaries.curve` explicitly overrides it:
```javascript
program.createErrorBand({
x: { field: "year", fieldType: "temporal" },
y: { field: "life_expect" },
groupBy: "cluster",
curve: "cardinal",
boundaries: {
stroke: "#25364d",
strokeWidth: 1.4,
strokeDash: [6, 3],
opacity: 0.8,
curve: "step"
}
});
```
Boundary layers remain ordinary line resources internally. Chart authors edit
them through the owner-level action below rather than constructing generated
layer IDs. The create action intentionally accepts one shared boundary recipe.
## Editing the band
`editErrorBand()` changes the stable body appearance:
```javascript
program.editErrorBand({
fill: "#7dd3fc",
opacity: 0.34,
curve: "cardinal"
});
```
An explicit edit fill overrides concrete encoded colors while retaining the
semantic color encoding and legend. The override persists through Canvas and
scale rematerialization. Statistical interval rows are not replaced.
`editErrorBandBoundary()` edits or creates one or both owned boundaries:
```javascript
program.editErrorBandBoundary({
boundary: "both",
stroke: "#0369a1",
strokeWidth: 2,
strokeDash: [6, 3],
opacity: 0.8,
curve: "cardinal"
});
```
`boundary` accepts `"both"`, `"lower"`, or `"upper"` and defaults to
`"both"`. If a selected boundary was not created initially, the action creates
it from the owner's stored data, fields, coordinate, grouping, and scales.
Existing selected boundaries retain omitted style properties; an unselected
boundary remains untouched. Omit `target` only when one owner is unambiguous.
# Box Plots
`createBoxPlot` creates vertical or horizontal box plots from one categorical
field and one quantitative field. It derives immutable summary data and
composes ordinary ranged-bar, error-bar, rule, and optional point actions.
## At a glance
| Action | Shortest call | Result |
| --- | --- | --- |
| `createBoxPlot` | `createBoxPlot()` after one eligible encoded layer | Quartile boxes, medians, whiskers, caps, and optional outliers |
| `editBoxPlot` | `editBoxPlot({ width: { band: 0.5 } })` | Stable owner and current summary retained unless statistics change |
```javascript
import { chart } from "ggaction";
const program = chart()
.createCanvas({
width: 360,
height: 460,
margin: { top: 140, right: 40, bottom: 70, left: 80 }
})
.createData({ values: cars })
.createBoxPlot({
x: { field: "Origin", fieldType: "nominal" },
y: { field: "Miles_per_Gallon" },
guides: { legend: false }
});
```
## `createBoxPlot`
```javascript
createBoxPlot({
id?, target?, data?, x?, y?, coordinate?, whisker?, width?, outliers?,
box?, median?, outlier?, guides?
} = {})
```
| Option | Meaning | Default or inference |
| --- | --- | --- |
| `id` | box-body owner ID | first instance uses `"boxPlot"` |
| `target` | compatible encoded source layer | current, then unique eligible layer |
| `data` | source dataset | explicit value, source layer, current dataset, then unique dataset |
| `x` | categorical or quantitative field and optional scale | inferred from `target` when omitted |
| `y` | categorical or quantitative field and optional scale | inferred from `target` when omitted |
| `coordinate` | Cartesian coordinate ID | source coordinate, then `"main"` |
| `whisker` | `{ type: "tukey", factor? }` or `{ type: "minmax" }` | Tukey with factor `1.5` |
| `width` | `{ band }`, where `0 < band < 1` | `{ band: 0.7 }` |
| `outliers` | create Tukey outlier resources | `true` |
| `box` | `fill`, `opacity`, `stroke`, `strokeWidth` | blue, opaque, `1.5` stroke |
| `median` | `stroke`, `strokeWidth` | dark stroke with width `1.5` |
| `outlier` | `shape`, `radius`, `opacity` | black diamond, radius `3`, opacity `0.75` |
| `guides` | `false` or applicable axis/grid/legend options | omitted: no guides; explicit `{}` creates applicable guides |
`createBoxPlot()` may also establish an incomplete owner first. Compatible
`encodeX` and `encodeY` calls can follow later; the completed semantic and
graphical state is the same as supplying both channels at creation time.
Stored explicit guide options are applied when those deferred positions become
complete. Omission and `guides: false` preserve the historical no-guide result.
The category/measure pairing determines orientation. This horizontal min–max
example creates no outlier resources:
```javascript
program.createBoxPlot({
x: { field: "Horsepower" },
y: { field: "Origin", fieldType: "nominal" },
whisker: { type: "minmax" }
});
```
Tukey factor and component appearance can be changed together without exposing
the child marks:
```javascript
program.createBoxPlot({
x: { field: "Origin", fieldType: "nominal" },
y: { field: "Miles_per_Gallon" },
whisker: { type: "tukey", factor: 1 },
width: { band: 0.5 },
box: { fill: "#f28e2b", opacity: 0.82, stroke: "#9a3412", strokeWidth: 2 },
median: { stroke: "#431407", strokeWidth: 3 },
outlier: { shape: "diamond", radius: 4, opacity: 0.9 }
});
```
Set `outliers: false` to keep the Tukey summary and whiskers without creating
an outlier dataset, layer, or graphic.
## Editing a box plot
Edit the stable box owner instead of generated whisker, cap, median, or
outlier IDs:
```javascript
const styled = program.editBoxPlot({
target: "boxPlot",
whisker: { type: "tukey", factor: 1 },
width: { band: 0.5 },
box: { fill: "#f28e2b", opacity: 0.82, stroke: "#9a3412", strokeWidth: 2 },
median: { stroke: "#431407", strokeWidth: 3 },
outlier: { shape: "diamond", radius: 4, opacity: 0.9 }
});
```
Whisker or outlier-presence changes create an immutable summary revision and
rebind the body, whiskers, caps, median, and optional outliers together. Width
or appearance-only edits keep the current derived datasets. `outliers: false`
removes the optional outlier resources; a later `true` recreates them only when
the revised Tukey result actually contains outlier rows.
## Current statistical and visual defaults
- Quartiles use linear interpolation at `(n - 1) × p`.
- Whiskers use the most extreme observed values inside `1.5 × IQR` fences.
- `{ type: "minmax" }` instead uses each category's observed minimum and
maximum and never creates an outlier dataset, layer, or graphic.
- Missing category or measure rows are omitted; non-missing non-finite measures
are errors.
- Category order follows first valid source appearance.
- Box width is `0.7` of the category band and box opacity is `1`.
- Box borders, medians, whiskers, and caps use `1.5` logical pixels.
- Outliers are black diamonds with radius-equivalent size `3` and opacity
`0.75`. No outlier resource is created when no outlier row exists.
The summary and optional outlier datasets, child layers, and graphics use
deterministic IDs derived from the owner. Canvas or shared-scale changes
explicitly rematerialize boxes, medians, whiskers, caps, and outliers.
## Current limitations
Category/measure reassignment, subgroup offsets, notches, variable-width
boxes, and custom whisker appearance are not implemented.
## Related
[Marks](./marks.md) · [Encodings](./encodings.md) ·
[Error bars](./error-bars.md) · [Guides](./guides.md)
# Gradient Plots
`createGradientPlot` compares a quantitative distribution across categories.
Each category becomes one density-filled strip plus an optional center rule.
The action uses the same categorical/quantitative `x` and `y` roles as
`createBoxPlot`, so orientation follows the fields rather than a separate flag.
## Create a gradient plot
```javascript
import { chart } from "ggaction";
const program = chart()
.createCanvas({
width: 620,
height: 460,
margin: { top: 85, right: 170, bottom: 95, left: 80 }
})
.createData({ values: cars })
.createGradientPlot({
x: { field: "Origin", fieldType: "nominal" },
y: { field: "Acceleration" }
});
```
The shortest complete call infers Cartesian scales and creates applicable
axes, grid lines, a median center rule, and a right-side relative-density
legend. Axis titles retain the original source fields even though the action
materializes an internal sampled profile dataset.
Use a categorical color encoding when each category should keep its own hue:
```javascript
const colored = program.encodeColor({
target: "gradientPlot",
field: "Origin",
fieldType: "nominal",
scale: { palette: "tableau10" }
});
```
Density still controls lightness and opacity inside each strip. `gradient`
controls the neutral palette and opacity range when no category color encoding
is present.
## Options and defaults
```javascript
createGradientPlot({
id?, target?, data?, x?, y?, coordinate?,
density?: { bandwidth?, extent?, steps?, kernel?, normalization? },
width?: { band? },
gradient?: { palette?, opacity? },
center?: false | { type?, stroke?, strokeWidth? },
guides?: false | { axes?, grid?, legend? }
} = {})
```
- `density` defaults to Gaussian, automatic bandwidth and extent, `64` steps,
and unit normalization.
- `width.band` defaults to `0.7` of the category band.
- `gradient` defaults to `blues` with opacity `[0, 1]`.
- `center` defaults to a median rule with a `1.5` logical-pixel dark stroke;
`false` removes the complete optional component.
- `guides: false` omits all guide resources. Nested `false` values omit only
that component.
Positions can be supplied in the create call, inferred from one compatible
encoded layer, or completed afterward with `encodeX` and `encodeY`. An omitted
choice is accepted only when one source or target is unambiguous.
## Edit and compose
```javascript
const edited = colored.editGradientPlot({
density: { bandwidth: 0.8 },
width: { band: 0.55 },
center: { type: "mean", stroke: "#111827" }
});
```
Density or center-statistic changes create a new immutable profile revision.
Width, palette, opacity, and center appearance retain the current profile.
Canvas and scale edits rematerialize strips, paint direction, center rules,
attached text, and guides.
`selectMarks` and `highlightMarks` operate at one category-strip grain. An
opacity or offset highlight preserves the structured density paint; an
explicit `fill` replaces it. `filterMarks` intentionally rejects this
composite resource—filter the source with `filterData` before
`createGradientPlot` instead.
Cartesian `facet` replays the profile independently inside each cell. Shared
density legends are not currently supported; omit the facet legend or keep
legends outside the repeated chart.
## Current limitations
Subgroup offsets, multiple overlaid profiles, independent per-category
intensity domains, category/measure reassignment, and shared facet density
legends are not implemented.
## Related
[Box plots](./box-plots.md) · [Data filtering](./data/filtering.md) ·
[Selection and highlighting](./appearance/selection-and-highlighting.md) ·
[Statistical action reference](../reference/actions/statistics.md#creategradientplot)
# Violin Plots
Use `createViolinPlot` to compare kernel-density profiles inside categorical
bands. It accepts the same categorical/quantitative x/y role family as box and
gradient plots, then infers orientation from the complete field pair.
## Minimal vertical violin
```javascript
const program = chart()
.createCanvas()
.createData({ values })
.createViolinPlot({
x: "category",
y: "value"
});
```
The action creates an area mark, immutable density data, category and value
scales, full closed paths, and applicable Cartesian guides. Field types are
inferred when the current dataset makes one categorical and one quantitative
role unambiguous.
## Density width and orientation
```javascript
.createViolinPlot({
x: { field: "value", fieldType: "quantitative" },
y: { field: "category", fieldType: "nominal" },
density: {
bandwidth: 0.8,
extent: [0, 30],
steps: 80,
width: { band: 0.7, resolve: "independent" }
}
})
```
`width.band` is the fraction of each categorical band available to the full
shape. `resolve: "shared"` uses one density maximum across categories;
`"independent"` gives each category its own maximum. The default is
`{ band: 0.8, resolve: "shared" }`.
## Split a category into two halves
```javascript
.createViolinPlot({
x: "category",
y: "value",
split: {
field: "period",
domain: ["early", "late"]
},
color: {
field: "period",
scale: { range: ["#4c78a8", "#e45756"] }
}
})
```
A split must contain exactly two observed values. The first domain value owns
the left or top half and the second owns the right or bottom half. When the
domain is omitted, ggaction uses first-appearance order only if exactly two
values are observed.
## Appearance and guides
```javascript
.createViolinPlot({
x: "category",
y: "value",
color: "category",
area: { opacity: 0.7, strokeWidth: 1.5 },
guides: { legend: false }
})
```
When `strokeWidth` is supplied without a constant stroke, each outline follows
its materialized fill. A category-color legend is omitted by default because
the categorical axis already identifies the same field; request
`guides: { legend: {} }` to show it explicitly.
## Editing
`createViolinPlot` is an aggregate create action. Edit the resource owned by
the decision you want to change:
```javascript
const revised = program
.editDensity({
bandwidth: 1.1,
placement: {
type: "category",
width: { band: 0.6, resolve: "shared" }
}
})
.editAreaMark({ opacity: 0.55 });
```
Use `{ type: "baseline" }` as the placement edit to return the area to the
ordinary zero-baseline density layout. Canvas, scale, data, filtering,
selection, highlighting, and facet changes rematerialize the complete density
paths.
## Current boundary
- Exactly one categorical and one quantitative position role
- At most two split values
- Cartesian coordinates only
- No raincloud raw-point/box components, adaptive bandwidth, or Polar violin
## Related
[Violin recipe](../recipes/violin-plot.md) ·
[Density encoding](./encodings.md#atomic-density) ·
[Area marks](./marks/line-area.md) ·
[Box plots](./box-plots.md)
# Guides
## At a glance
| Action | Shortest call | Inference/defaults | Result |
| --- | --- | --- | --- |
| `createGuides` | `createGuides()` | Applicable axes, horizontal grid, and legends | Wrapped guide child actions in deterministic order |
position encodings Axes
position scales Grid
appearance encodings Legend
→
createGuides() Deterministic composition
## `createGuides(options?)`
Creates the applicable Cartesian, Polar, or Parallel axes, Cartesian or Polar grid, and categorical legend supported
by the current semantic encodings.
```javascript
program.createGuides();
```
Pass child options to customize each guide:
```javascript
program.createGuides({
axes: {
y: { ticksAndLabels: { count: 6 } }
},
grid: {
horizontal: { color: "#e2e8f0" }
},
legend: {
title: "Origin"
}
});
```
| Option | Type | Default |
| --- | --- | --- |
| `axes` | `createAxes` options or `false` | automatic |
| `grid` | `createGrid` options or `false` | automatic horizontal grid |
| `legend` | `createLegend` options or `false` | automatic |
`false` explicitly skips that guide:
```javascript
program.createGuides({ legend: false });
```
Axes are selected from x/y, theta/radius, or ordered Parallel dimensions. A horizontal grid is selected
when a y encoding exists; vertical grid remains off unless requested. Polar grids
are selected only for the encoded Polar channels: theta creates spokes and radius
creates concentric circles. A legend is selected for line color/stroke-dash, bar
color, area or arc color, compatible point
color+shape encodings, or a standalone quantitative point-size encoding. Size
adds a second quantitative block when a point-series legend also exists.
Parallel dimensions create one axis per stored field and no automatic grid.
Their row-path color or stroke-dash encoding uses the ordinary categorical
line legend.
Density areas can request both grids and forward the complete top-legend
layout through the aggregate:
```javascript
densityArea.createGuides({
grid: { horizontal: {}, vertical: {} },
legend: {
position: "top",
direction: "vertical",
columns: 3,
titlePosition: "left",
offset: 8
}
});
```
Axis titles use density provenance rather than generated output names. A
vertical density therefore infers the original field on x and `Density` on y;
horizontal density reverses those titles.
Layered regression scatterplots still create only one shared x axis, y axis,
and horizontal grid. Their point color/shape and matching regression line share
one composite Origin legend, followed by the five-symbol size legend.
For grouped bars, the shortest call creates an ordinal x axis at band centers,
a quantitative y axis, a horizontal grid, and a right-side color legend:
```javascript
groupedBars.createGuides();
```
The same child options remain available. For example, this changes the ordinal
labels while retaining inference for every other guide:
```javascript
groupedBars.createGuides({
axes: {
x: { ticksAndLabels: { labels: { fontSize: 11 } } }
}
});
```
`createGuides` only selects and calls existing wrapped actions. Coordinate,
scale, target, field, and domain validation remains the responsibility of
`createAxes`, `createGrid`, and `createLegend`, so ambiguous charts still
require their explicit child options. If nothing is selected, the action
reports an error.
For a Polar-only chart, omission selects the axis and grid family for each stored
Polar position encoding. A theta-only donut therefore creates a theta axis and
spokes without inventing a radial guide; its arc color encoding can still infer
the categorical legend. A theta/radius chart selects both families. Grid geometry
is inserted behind marks while axes remain above marks, independent of the call
occurring after mark creation.
The trace preserves the composition:
```text
createGuides
├─ createAxes?
├─ createGrid?
└─ createLegend?
```
Chart titles are not guides. Create them separately with
[`createTitle`](./titles.md). A title and guide reserved on the same edge must
both fit without overlap; neither action silently expands the Canvas margin.
Call [`createGrid`](./grids.md), [`createAxes`](./axes.md), or
[`createLegend`](./legends.md) directly when only one focused guide action is
desired.
## Errors and limitations
An explicit child object selects that guide, `false` disables it, and omission
requests automatic applicability. The action fails when no supported guide can
be selected.
## Related
[Axes](./axes.md) · [Grids](./grids.md) · [Legends](./legends.md) ·
[Titles](./titles.md)
# Titles
## At a glance
| Action | Shortest call | Inference/defaults | Result |
| --- | --- | --- | --- |
| `createTitle` | `createTitle({ text: "Title" })` | Top, plot-left alignment, default styles | Semantic text and concrete title graphics |
| `editTitle` | `editTitle({ position: "bottom" })` | Preserves omitted text, layout, wrapping, and style | Rematerialized title graphics |
| `removeTitle` | `removeTitle()` | Existing chart title | Title and subtitle resource removed |
## `createTitle(options)`
Creates one chart title and an optional subtitle on any Canvas edge.
```javascript
program.createTitle({
text: "The trend of acceleration by year",
subtitle: "from 1970 to 1982"
});
```
| Option | Type | Default |
| --- | --- | --- |
| `text` | non-empty string | required |
| `subtitle` | non-empty string | omitted |
| `position` | `"top"`, `"bottom"`, `"left"`, or `"right"` | `"top"` |
| `align` | `"left"`, `"center"`, or `"right"` | `"left"` |
| `offset` | finite number | `0` |
| `gap` | non-negative number | `8` |
| `maxWidth` | positive number | omitted; no wrapping |
| `wrap` | `"word"` or `"character"` | `"word"` when `maxWidth` is set |
| `lineHeight` | positive number | each font size × `1.2` |
| `titleStyle` | text style object | default title style |
| `subtitleStyle` | text style object | default subtitle style |
Both style objects accept `color`, `fontSize`, `fontFamily`, and `fontWeight`.
Numeric weights follow the shared [Canvas font-weight policy](./marks/text.md#font-weights).
The defaults are:
```javascript
{
titleStyle: {
color: "#0f172a",
fontSize: 22,
fontFamily: "sans-serif",
fontWeight: 600
},
subtitleStyle: {
color: "#64748b",
fontSize: 14,
fontFamily: "sans-serif",
fontWeight: "normal"
}
}
```
## Position, alignment, and wrapping
Alignment uses the plot bounds rather than the full Canvas: left aligns with
the plot's left edge, center with its midpoint, and right with its right edge.
For a facet, the alignment span is the union of the translated child plot
bounds. Child margins, axis labels and titles, facet padding, and shared
legends do not pull the title away from the visual center of the plots.
For a left or right title, the same values mean the top, center, and bottom of
the plot edge. Left titles rotate counter-clockwise and right titles rotate
clockwise; top and bottom titles remain horizontal.
Set `maxWidth` to wrap text before edge rotation. Word wrapping prefers
whitespace and falls back to Unicode-safe character wrapping for an oversized
word. Character wrapping always splits on Unicode code-point boundaries. The
library uses deterministic text metrics so the same program produces the same
line breaks in browsers and Node. Renderers draw the concrete lines already in
`graphicSpec`; they do not wrap text again. Explicit newline characters are
not accepted.
The requested margin must contain the actual title block. A title must also
avoid guides reserved on the same edge. Creation fails instead of expanding
the Canvas, changing margins, or moving the title automatically.
## `editTitle(options)`
Partially edits the existing title. At least one option is required.
```javascript
const edited = program.editTitle({
position: "bottom",
align: "center",
maxWidth: 280,
titleStyle: { fontSize: 20 }
});
```
Omitted properties remain unchanged, and style objects merge only the supplied
leaves. `text` and string `subtitle` replace semantic text. Use
`subtitle: false` to remove the subtitle; a later string recreates it.
Wrapping and layout changes rebuild the concrete text lines without changing
unrelated chart state.
## `removeTitle()`
Remove the complete chart title resource, including subtitle text, concrete
graphics, and stored layout settings:
```javascript
const untitled = program.removeTitle();
```
The action accepts no options and requires an existing title.
## Stored result
The title and subtitle strings are stored in `semanticSpec`. Alignment,
spacing, wrapping configuration, and text appearance are graphical state.
Resolved line breaks, coordinates, and rotations are concrete text graphics.
Canvas size and margin changes explicitly rematerialize their positions.
## Errors and limitations
The library supports one title and one optional subtitle. `wrap` and
`lineHeight` require `maxWidth`; explicit `lineHeight` must cover every visible
font size. Missing margin space and same-edge guide collisions are errors.
## Related
[Canvas](./canvas.md) · [Guides](./guides.md) ·
[Semantic and graphical state](../concepts/semantic-and-graphics.md)
# Rendering
## At a glance
| Target | Shortest call | Density | Result |
| --- | --- | --- | --- |
| Browser Canvas | `render(program, context)` | Device/Canvas context | Draws concrete `graphicSpec` |
| Node PNG | `renderToPNG(program, { output })` | `pixelRatio`, default `1` | PNG file and physical dimensions |
Rendering consumes a completed program's `graphicSpec`. It does not read
datasets, semantic encodings, context, or trace to infer missing output.
## Browser Canvas
```javascript
import { render } from "ggaction";
const context = document.querySelector("#chart").getContext("2d");
render(program, context);
```
The optional `pixelRatio` increases physical output density while retaining
logical chart coordinates. For an HTML Canvas, ggaction also preserves the
logical CSS width and height while enlarging the backing store:
```javascript
render(program, context, { pixelRatio: 2 });
```
## PNG output
The Node-only entry point writes a completed program directly to PNG.
```javascript
import { renderToPNG } from "ggaction/png";
const result = await renderToPNG(program, {
output: "./output/chart.png",
pixelRatio: 2
});
```
Missing output directories are created. A logical 640×400 chart at ratio 2
produces a 1280×800 image. The result contains the absolute `output`, physical
`width` and `height`, `pixelRatio`, and byte count.
The current renderer supports concrete canvas, collection, circle, rect, line,
text, and `M/L/C/Z` command-path graphics. Path and line strokes may use concrete
dash arrays. The renderer validates values with the same concrete property
contract used by `editGraphics`.
Rect and closed-path fills may be solid strings or item-local linear-gradient
paint values. Gradient endpoints are normalized against the final fill geometry;
path stroke width does not expand that coordinate box. The renderer creates the
Canvas gradient only for the current draw call and never stores backend objects
in `graphicSpec`.
Line curve actions resolve interpolation into those commands before rendering.
The Canvas renderer executes `L` and cubic `C` segments but does not read curve
names or calculate control points.
## Errors and limitations
Rendering never reads `semanticSpec`. Every drawable property must already be
concrete, and `pixelRatio` must be a positive finite number.
## Related
[Canvas](./canvas.md) · [Semantic and graphical state](../concepts/semantic-and-graphics.md) ·
[Primitive extension API](../extension/primitives.md)
# 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
```javascript
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](https://github.com/ggaction/ggaction/tree/main/examples/program-composition)
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:
```javascript
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](https://github.com/ggaction/ggaction/tree/main/examples/cross-feature-dashboard)
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:
```javascript
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](https://github.com/ggaction/ggaction/tree/main/examples/cars-origin-scatterplot-facet)
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:
```javascript
const emphasized = faceted.editFacetHeaders({
fontSize: 13,
fontWeight: 700,
color: "#0f172a",
offset: 10
});
```
Facet-header weights follow the shared
[Canvas font-weight policy](./marks/text.md#font-weights).
## Edit the layout
```javascript
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
```javascript
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.
# Position Encodings
Choose the position family from the semantic mark and field relationship. All
position actions infer the current mark, use or create a compatible coordinate,
resolve a channel scale, and explicitly materialize the affected graphics.
## Supported marks and modes
| Action | Supported marks | Field types | Important modes |
| --- | --- | --- | --- |
| `encodeX` | point, line, area, bar, rect, rule, text | point/bar/rect/rule/text: quantitative, temporal, ordinal, nominal; line/area: quantitative, temporal | field; rule also accepts datum; bar accepts aggregate or bin |
| `encodeY` | point, line, area, bar, rect, rule, text | point/line/bar/rect/rule/text: quantitative, temporal, ordinal, nominal; area: quantitative, temporal | field; rule also accepts datum; bar accepts aggregate or count |
| `encodeX2` / `encodeY2` | area, ranged bar, rect, rule | area/ranged bar/rect/rule: matching primary | secondary field; rule also accepts datum |
| `encodeTheta` | point, line, arc | point/line: quantitative, temporal, ordinal, nominal; arc: ordinal, nominal | arc accepts aggregate: count or weighted sum for proportional sectors |
| `encodeR` | point, line, arc | point/line/arc: quantitative | radial position; arc combines it with a categorical theta band |
| `encodeParallelCoordinates` | line | line: quantitative, ordinal | atomic ordered dimensions; one namespaced scale and axis per dimension |
## Choose an encoding
| Goal | Required state | Actions | Detailed page |
| --- | --- | --- | --- |
| Position points | point mark, quantitative, temporal, or ordinal fields | `encodeX`, `encodeY` | [Quantitative positions](./position/quantitative.md) |
| Position Polar points | point mark, angle field and quantitative radius field | `encodeTheta`, `encodeR` | [Polar point tutorial](../tutorials/polar-points.md) |
| Draw Polar lines or radar paths | line mark, angle field and quantitative radius field | `encodeTheta`, `encodeR` | [Polar line tutorial](../tutorials/polar-lines.md) |
| Draw donuts, rose charts, or radial bars | arc mark, categorical count/weighted-sum theta or quantitative radius | `encodeTheta`, optional `encodeR` | [Polar arc tutorial](../tutorials/polar-arcs.md) |
| Draw discrete or ranged cells | rect mark, two discrete positions or complete x/x2 and y/y2 pairs | `encodeX`, `encodeY`, optional `encodeX2`, `encodeY2` | [Rect marks](./marks/rect.md) |
| Draw an aggregate time series | line mark, temporal x and quantitative y | `encodeX`, `encodeY` | [Temporal lines](./position/temporal.md) |
| Build vertical aggregate bars | bar mark, ordinal/temporal x and quantitative y | `encodeX`, `encodeY` | [Bar positions](./position/ordinal-bars.md) |
| Build horizontal aggregate bars | bar mark, quantitative x and ordinal/temporal y | `encodeX`, `encodeY` | [Bar positions](./position/ordinal-bars.md) |
| Bin and count values | bar mark, quantitative field | `encodeHistogram` or `encodeX` + `encodeY` | [Histograms](./position/histogram.md) |
| Estimate a distribution | area mark, quantitative field | `encodeDensity` | [Encodings](./encodings.md#atomic-density) |
| Draw full-span or bounded rules | rule mark, field or datum endpoints | `encodeX`, `encodeY`, `encodeX2`, `encodeY2` | [Rule endpoints](#rule-endpoints) |
| Control within-band grouping | complete ordinal-bar positions | `encodeXOffset`, `encodeYOffset` | [Offsets](./position/offsets.md) |
For ordinary grouped bar charts, prefer
`encodeColor({ field, layout: "group" })`; it calls the matching advanced
directional offset action for the same field.
## Shared inference
- `target` defaults to the current compatible mark.
- `coordinate` uses the layer coordinate, then the documented `main`
Cartesian or `polar` Polar default for the requested channel family.
- Scale IDs default to their channel names: `x`, `y`, `theta`, `radius`, `xOffset`, and `yOffset`.
- Automatic continuous y ranges run bottom-to-top. Discrete y positions run
top-to-bottom so horizontal categories follow domain order.
- Temporal values accept finite timestamps, four-digit numeric/string years,
and valid date strings. Four-digit values are interpreted as UTC years.
- Ambiguous resources produce an error instead of an arbitrary selection.
## Polar positions
```javascript
program
.encodeTheta({ field: "angle" })
.encodeR({ field: "distance" })
.encodePointRadius({ value: 3 });
```
`encodeTheta` accepts point or line marks with quantitative, temporal, ordinal,
or nominal fields. Arc marks accept nominal or ordinal theta; `aggregate:
"count"` creates count-proportional sectors. `aggregate: "sum"` plus a
non-negative finite `weight` field creates weighted proportional sectors.
Categorical theta plus quantitative `encodeR` creates radial sectors.
Quantitative angle scales are linear; temporal angles use time scales; discrete
angles use point or band scales. The automatic range is `[0, 360]` degrees with
0 at 12 o'clock and clockwise positive direction.
`encodeR` accepts a quantitative field and linear, log, pow, sqrt, or symlog
scale policies. Its automatic range fits the smaller plot dimension. Explicit
ranges are non-negative logical pixels and must fit the current plot bounds.
The two actions are order-independent. One Polar channel may exist as an
incomplete semantic assignment, but points or paths become visible only after
both channels and their scales resolve. A line may use
`createLineMark({ closed: true })` for a closed radar path. Cartesian x/y and
Polar theta/radius cannot be mixed on one layer.
## Rule endpoints
Rule positions use the same `encodeX` and `encodeY` actions with an explicit
`fieldType`, but accept exactly one of `field` or `datum`:
```javascript
program
.createRuleMark()
.encodeX({ datum: 15, fieldType: "quantitative" })
.encodeY({ datum: 20, fieldType: "quantitative" })
.encodeY2({ datum: 80, fieldType: "quantitative" });
```
`encodeX2` and rule `encodeY2` require their corresponding primary endpoint
and share its scale, coordinate, and field type. `x` alone draws a vertical
full-span rule; `y` alone draws a horizontal full-span rule. `x+y+y2` and
`y+x+x2` draw bounded intervals, while all four endpoints draw a diagonal.
Calling the same action again replaces only that endpoint.
## Related
[Encodings](./encodings.md) · [Scale options](./scales.md) ·
[Coordinates](./coordinates.md) · [Series encodings](./series-encodings.md)
# Quantitative Positions
## At a glance
| Action | Shortest call | Required state | Result |
| --- | --- | --- | --- |
| `encodeX` | `encodeX({ field: "x" })` | point mark and finite field | Concrete horizontal positions |
| `encodeY` | `encodeY({ field: "y" })` | point mark and finite field | Concrete vertical positions |
## Point `encodeX(options)` and `encodeY(options)`
```javascript
program
.encodeX({ field: "Horsepower" })
.encodeY({ field: "Miles_per_Gallon" });
```
| Option | Type | Default |
| --- | --- | --- |
| `field` | non-empty string | required |
| `target` | point mark ID | current mark |
| `fieldType` | `"quantitative"` | `"quantitative"` |
| `coordinate` | coordinate ID | layer coordinate, then `"main"` |
| `scale.id` | scale ID | channel name |
| `scale.type` | `"linear"` | `"linear"` |
| `scale.domain` | `"auto"` or two finite numbers | `"auto"` |
| `scale.range` | `"auto"` or two finite numbers | `"auto"` |
| `scale.nice` | boolean | omitted |
| `scale.zero` | boolean | omitted |
Automatic domains combine compatible fields that share a scale. Automatic
ranges use plot bounds, and every encoded value must be finite. The actions
ensure a Cartesian coordinate exists and attach it to the point layer.
Calling the same action again on the same target replaces that channel's
field. If `scale.id` is omitted, the current channel scale is reused and all
connected marks, axes, and grids are recomputed. An explicit new scale ID
rebinds a non-shared guide while retaining the old named scale. Inferred axis
titles follow the new field; explicit titles and appearance stay unchanged.
## Errors and limitations
A conflicting layer coordinate, non-Cartesian coordinate, cross-channel scale,
or non-finite field value is rejected before partial output is retained. A
guide cannot be rebound away from a scale that still has another consumer.
## Related
[Position encoding index](../position-encodings.md) ·
[Scale options](../scales.md) · [Scatterplot tutorial](../../tutorials/scatterplot.md)
# Temporal Line Positions
## At a glance
| Action | Shortest call | Required state | Result |
| --- | --- | --- | --- |
| temporal `encodeX` | `encodeX({ field: "date", fieldType: "temporal" })` | line mark | Resolved UTC time scale |
| aggregate `encodeY` | `encodeY({ field: "value", aggregate: "mean" })` | temporal x | Sorted scalar-aggregate path(s) |
## Temporal line `encodeX(options)`
```javascript
program.encodeX({
field: "Year",
fieldType: "temporal",
scale: { nice: true }
});
```
| Option | Type | Default |
| --- | --- | --- |
| `field` | non-empty string | required |
| `target` | line mark ID | current mark |
| `fieldType` | `"temporal"` | required for line marks |
| `coordinate` | coordinate ID | layer coordinate, then `"main"` |
| `scale.id` | scale ID | `"x"` |
| `scale.type` | `"time"` | `"time"` |
| `scale.domain` | `"auto"` or two finite timestamps | `"auto"` |
| `scale.range` | `"auto"` or two finite numbers | `"auto"` |
| `scale.nice` | boolean | omitted |
Parseable date strings and finite timestamps are normalized for scale
resolution without changing the source dataset. The path remains empty until y
is encoded.
## Aggregate line `encodeY(options)`
```javascript
program.encodeY({
field: "Acceleration",
aggregate: "mean",
scale: { nice: true, zero: false }
});
```
| Option | Type | Default |
| --- | --- | --- |
| `field` | non-empty string | required |
| `target` | line mark ID | current mark |
| `fieldType` | `"quantitative"`, or `"nominal"` for count operations | `"quantitative"` |
| `aggregate` | scalar name or parameterized aggregate object | required for temporal line marks |
| `coordinate` | coordinate ID | layer coordinate, then `"main"` |
| `scale.id` | scale ID | `"y"` |
| `scale.type` | `"linear"` | `"linear"` |
| `scale.domain` | `"auto"` or two finite numbers | `"auto"` |
| `scale.range` | `"auto"` or two finite numbers | `"auto"` |
| `scale.nice` | boolean | omitted |
| `scale.zero` | boolean | omitted |
The action groups by temporal x and encoded series fields, computes the selected
scalar summary, sorts each series by x, and materializes concrete path commands.
Automatic y domains use final aggregate values rather than raw rows.
When a compatible temporal aggregate bar already owns x and y, a newly created
line mark infers both encodings and reuses the same semantic scale IDs. Do not
repeat `encodeX` or `encodeY` for that line unless it intentionally needs a
different field or scale. Bar centers and line vertices then map the same
temporal values to the same x positions; bar width remains mark layout rather
than a second scale.
Supported operations are `count`, `sum`, `mean`, `median`, `min`, `max`,
`distinct`, `valid`, `missing`, `variance`, `varianceP`, `stdev`, `stdevP`,
`stderr`, `q1`, `q3`, `ciLower`, and `ciUpper`. `distinct`, `valid`, `missing`,
and `count` also accept nominal input fields; their output scale remains linear.
Missing finite samples are omitted instead of becoming zero-valued points.
Sample dispersion, standard error, and confidence endpoints require at least
two finite values per final group.
Parameterized aggregates accept either a quantile probability or an ordered
row selection:
```javascript
program.encodeY({
field: "Acceleration",
aggregate: { op: "quantile", probability: 0.75 }
});
program.encodeY({
field: "Acceleration",
aggregate: { op: "first", orderBy: "Horsepower" }
});
```
`probability` is required and may range from `0` through `1`; those endpoints
equal the minimum and maximum. Ordered aggregates accept `op: "first"` or
`"last"`, require `orderBy`, and default `order` to `"ascending"`. Ties retain
source-row order. Rows with missing or incomparable order keys are skipped,
and a final group with no selectable finite result is omitted. The normalized
order is stored in `semanticSpec`, so inferred titles such as
`first(Acceleration, Horsepower ascending)` remain reproducible.
## Errors and limitations
The current line slice requires temporal x, a compatible aggregate y, and at
least two complete points per materialized series. Parameterized aggregate
outputs must be quantitative.
## Related
[Position encoding index](../position-encodings.md) ·
[Series encodings](../series-encodings.md) ·
[Line chart tutorial](../../tutorials/line-chart.md)
# Bar Positions
## At a glance
| Action | Shortest call | Required state | Result |
| --- | --- | --- | --- |
| ordinal `encodeX` | `encodeX({ field: "category", fieldType: "ordinal" })` | bar mark | Ordered x bands |
| aggregate `encodeY` | `encodeY({ field: "value" })` | ordinal bar x | Aggregate/non-stacked y scale |
| temporal `encodeX` | `encodeX({ field: "year", fieldType: "temporal" })` | bar mark | Time-positioned vertical bars |
| horizontal pair | quantitative aggregate x + ordinal/temporal y | bar mark | Horizontal bars |
## Ordinal bar `encodeX(options)`
| Option | Type | Default |
| --- | --- | --- |
| `field` | non-empty string | required |
| `fieldType` | `"ordinal"` | required |
| `target` | bar mark ID | current mark |
| `coordinate` | coordinate ID | layer coordinate, then `"main"` |
| `scale.id` | scale ID | `"x"` |
| `scale.type` | `"ordinal"` | `"ordinal"` |
| `scale.domain` | `"auto"` or unique nominal values | `"auto"` |
| `scale.range` | `"auto"` or two finite numbers | `"auto"` |
```javascript
program.encodeX({ field: "year", fieldType: "ordinal" });
```
Automatic domains preserve first-appearance order. Automatic ranges use the
horizontal plot bounds and resolve a shared `step` and `bandwidth`. This action
leaves the rect collection empty because aggregate y and layout are incomplete.
## Aggregate ordinal bar `encodeY(options)`
| Option | Type | Default |
| --- | --- | --- |
| `field` | non-empty string | required |
| `fieldType` | `"quantitative"`, or `"nominal"` for count operations | `"quantitative"` |
| `aggregate` | scalar name or parameterized aggregate object | `"mean"` |
| `stack` | `null` | `null` |
| `target` | bar mark ID | current mark |
| `coordinate` | coordinate ID | layer coordinate, then `"main"` |
| `scale.id` | scale ID | `"y"` |
| `scale.type` | `"linear"` | `"linear"` |
| `scale.domain` | `"auto"` or two finite numbers | `"auto"` |
| `scale.range` | `"auto"` or two finite numbers | `"auto"` |
| `scale.nice` | boolean | `true` |
| `scale.zero` | boolean | `false` |
```javascript
program.encodeY({
field: "perc",
aggregate: "mean",
scale: { nice: true, zero: false }
});
```
The automatic domain uses aggregate summaries at the final available grouping
grain. Parameterized quantile and ordered `first`/`last` use the same contract
as temporal line y encodings. Group and overlay bars start at the semantic value
zero, so their automatic domain contains zero even when `scale.zero` is `false`.
An explicit domain for either layout must contain zero; otherwise the encoding
fails before creating extrapolated or clipped rectangles.
Once the category and measure encodings are complete, concrete rectangles use
an implicit `0.72` category-band width. Grouping recomputes them within directional offset
slots, and `encodeBarWidth` is an optional graphical override.
## Temporal and horizontal bars
Bar orientation is inferred from the completed position pair; it is not stored
as a separate mark option.
```javascript
program
.encodeX({ field: "year", fieldType: "temporal" })
.encodeY({ field: "perc", aggregate: "mean" });
program
.encodeX({ field: "perc", aggregate: "mean" })
.encodeY({ field: "year", fieldType: "ordinal" });
```
Temporal input normalization happens only while resolving scales and geometry.
The dataset and semantic field remain unchanged. Numeric or string four-digit
years map to January 1 UTC; `YYYY-MM-DD`, `YYYY/MM/DD`, and finite timestamps
are also accepted.
## Errors and limitations
Ordinal position does not accept `nice` or `zero`. Vertical grouped bars use
`xOffset`; horizontal grouped bars use `yOffset`. Horizontal stack, overlay,
fill, and diverging layouts use the quantitative x measure.
## Related
[Offsets](./offsets.md) · [Series encodings](../series-encodings.md) ·
[Constant appearance](../appearance.md) · [Bar chart tutorial](../../tutorials/grouped-bar.md)
# Histogram Positions
## At a glance
| Action | Shortest call | Required state | Result |
| --- | --- | --- | --- |
| `encodeHistogram` | `encodeHistogram({ field: "value" })` | bar mark | Atomic binned x and count/stack y |
| binned `encodeX` | `encodeX({ field: "value", bin: {} })` | bar mark | Bins and x scale |
| count `encodeY` | `encodeY()` | binned bar x | Count y scale and concrete rects |
Prefer the atomic action for ordinary chart authoring:
```javascript
program.encodeHistogram({ field: "Displacement", maxBins: 10 });
```
It calls the wrapped x and y actions below without duplicating their inference
or validation.
## Binned bar `encodeX(options)`
| Option | Type | Default |
| --- | --- | --- |
| `field` | non-empty string | required |
| `target` | bar mark ID | current mark |
| `fieldType` | `"quantitative"` | `"quantitative"` |
| `coordinate` | coordinate ID | layer coordinate, then `"main"` |
| `bin.maxBins` | positive integer | `10` |
| `bin.step` | positive finite number | mutually exclusive |
| `bin.boundaries` | at least two increasing finite numbers | mutually exclusive |
| `scale.id` | scale ID | `"x"` |
| `scale.type` | `"linear"` | `"linear"` |
| `scale.domain` | `"auto"` or two ascending finite numbers | `"auto"` |
| `scale.range` | `"auto"` or two finite numbers | `"auto"` |
| `scale.nice` | boolean | `true` |
| `scale.zero` | boolean | `false` |
Automatic nice bins use `1, 2, 3, 5 × 10ⁿ` steps and never exceed `maxBins`.
Exact-step bins use zero as their grid anchor and expand an automatic domain
to step multiples. Explicit boundaries support irregular interval widths and
own the x-domain endpoints. All intervals are half-open except the final
interval, which includes its upper endpoint.
The rect collection remains empty until y is encoded.
## Count/stack bar `encodeY(options?)`
| Option | Type | Default |
| --- | --- | --- |
| `field` | binned x field | inferred from x |
| `target` | bar mark ID | current mark |
| `aggregate` | `"count"` | `"count"` |
| `stack` | `"zero"`, `"normalize"`, or `null` | `"zero"` |
| `scale.id` | scale ID | `"y"` |
| `scale.type` | `"linear"` | `"linear"` |
| `scale.domain` | `"auto"` or two finite numbers | `"auto"` |
| `scale.range` | `"auto"` or two finite numbers | `"auto"` |
| `scale.nice` | boolean | `true` |
| `scale.zero` | boolean | `true` |
The automatic y domain uses total bin counts for zero stack, individual series
counts for unstacked layouts, and `[0, 1]` for normalize. The action materializes
one concrete rectangle per non-empty bin; a later color encoding can split each
bin according to its layout.
## Errors and limitations
Explicit fields must match the binned x field. `maxBins`, `binStep`, and
`binBoundaries` are mutually exclusive. Step-aligned explicit domains must
contain the data and use zero-anchored step multiples. Explicit boundaries
must contain the complete data extent; a separate domain must match their
first and last values. Empty intervals remain meaningful but do not create
zero-height rectangles. Grouped histograms require categorical color with
`layout: "group"`; the color action creates the matching xOffset companion.
## Related
[Series encodings](../series-encodings.md) · [Scale options](../scales.md) ·
[Histogram tutorial](../../tutorials/histogram.md)
# Ordinal Offsets
## At a glance
| Action | Shortest call | Required state | Result |
| --- | --- | --- | --- |
| `encodeXOffset` | `encodeXOffset({ field: "group" })` | ordinal x bar | Nominal slots inside each x band |
| `encodeYOffset` | `encodeYOffset({ field: "group" })` | ordinal y bar | Nominal slots inside each y band |
Most chart authors should use:
```javascript
program.encodeColor({ field: "sex", layout: "group" });
```
That action calls `encodeXOffset` for a vertical bar or `encodeYOffset` for a
horizontal bar as a wrapped child with the same field.
## Advanced `encodeXOffset(options)` and `encodeYOffset(options)`
| Option | Type | Default |
| --- | --- | --- |
| `field` | non-empty string | required |
| `fieldType` | `"nominal"` or `"ordinal"` | `"nominal"` |
| `target` | aggregate bar mark ID | current mark |
| `scale.id` | scale ID | channel name: `"xOffset"` or `"yOffset"` |
| `scale.type` | `"ordinal"` | `"ordinal"` |
| `scale.domain` | `"auto"` or unique nominal values | `"auto"` |
| `scale.range` | `"auto"` or two finite numbers | `"auto"` |
| `paddingInner` | finite number from `0` inclusive to `1` exclusive | `0` |
| `paddingOuter` | non-negative finite number | `0` |
The automatic range is the parent category bandwidth, not the full plot range.
Its step divides one ordinal x or y band into equal categorical slots. Explicit domain order and
reversed ranges are supported. Inner padding reduces each slot bandwidth;
outer padding reserves step fractions before the first and after the last slot.
Calling the action again for the same field preserves omitted padding values.
This action resolves slot geometry but does not create rectangles by itself.
When grouped color already exists, direct offset calls must use the same field.
Change both fields atomically with `encodeColor({ field: next, layout: "group" })`.
That action rematerializes the matching directional offset slots, bars, and any existing
legend while preserving explicit legend titles and styles.
## Errors and limitations
`xOffset` requires one resolved ordinal x bandwidth; `yOffset` requires one
resolved ordinal y bandwidth. Color and offset domains must have identical
order before grouped rectangles can be materialized. Every consumer of one
shared offset scale must use the same padding policy and parent bandwidth.
## Related
[Ordinal bars](./ordinal-bars.md) · [Series encodings](../series-encodings.md) ·
[Constant appearance](../appearance.md)
# Series Encodings
## At a glance
| Action | Shortest call | Inference/defaults | Result |
| --- | --- | --- | --- |
| `encodeColor` | `encodeColor({ field: "group" })` | Current mark, nominal default or explicit ordinal field type, color scale | Semantic grouping and concrete color |
| `encodeStrokeDash` | `encodeStrokeDash({ field: "group" })` | Current line/rule mark and dash scale | Field-driven or constant concrete dash |
| `encodeStrokeWidth` | `encodeStrokeWidth({ field: "weight" })` | Current line/rule; independent quantitative scale | Rule-item or line-series widths |
| `encodePathOrder` | `encodePathOrder({ field: "year" })` | Current or unique compatible Cartesian path; ascending default | Stable per-series vertex order without a scale |
| `encodeParallelCoordinates` | `encodeParallelCoordinates({ dimensions: ["a", "b"] })` | Current line, Parallel coordinate, local scales, `break` missing policy | One row path across ordered dimension axes |
Series appearance is authored through color, stroke-dash, and stroke-width families. Each
focused page owns the complete options, replacement behavior, and errors for
that encoding.
## Explicit path topology
```javascript
const ordered = program.encodePathOrder({
field: "year",
order: "ascending"
});
const automatic = ordered.removePathOrder();
```
`encodePathOrder` supports direct Cartesian quantitative/temporal lines and
ordinary ranged areas backed by raw or row-preserving data. It sorts each
color/group/stroke-dash series independently, keeps repeated positions, and
uses source-row order to break ties. The field is quantitative, `order` is
`"ascending"` or `"descending"`, and neither action creates a scale or guide.
Aggregate lines, Polar lines, and generated density, error, or regression paths
own their topology elsewhere and are rejected. Missing or non-finite order
values reject the complete action instead of producing a partial path.
## Focused series families
Color Categorical and continuous color, grouping layouts, and aggregate bars.
Stroke dash Constant and field-driven dash patterns for lines and rules.
Parallel coordinates use one atomic ordered-dimension assignment rather than
separate x/y calls. See [Parallel Coordinates](./parallel-coordinates.md) for
dimension scales, row identity, missing policies, and lifecycle behavior.
## Errors and limitations
Stroke-dash and explicit group fields must be nominal. Color fields may be
nominal or ordinal. Area color must match its group encoding. Line group,
color, and field-driven stroke dash must use one compatible field.
Combined line legends also require matching ordered domains.
Stroke width is quantitative and independent of point size. A line series must
have exactly one width value across all contributing rows; segment-local and
tapered widths are unsupported.
## Related
[Scale options](./scales.md) · [Legends](./legends.md) ·
[Position encodings](./position-encodings.md)
# Color Encoding
## `encodeColor(options)`
Map a nominal or ordinal field to point fills, line-series strokes, area fills,
bar fills, rect fills, or arc-sector fills. Ordinal fields may contain ordered numeric categories such as
engine cylinder counts. Quantitative or temporal point and rect fields use continuous color
scales; point fields also support discretized color. Aggregate bars additionally support one aggregate quantitative
color value per final rectangle. Line and categorical bar materializers may use
the field for grouping. Area color must match an existing `encodeGroup` field.
| Mode | Supported marks | Field types | Important options |
| --- | --- | --- | --- |
| Categorical | point, line, area, bar, rect, arc | point/line/area/bar/rect/arc: nominal, ordinal | bar/area layout; arc overlay; palette and ordinal scale |
| Continuous | point, aggregate bar, rect | point/rect: quantitative, temporal; aggregate bar: quantitative | sequential scale; aggregate required for a different bar measure |
| Discretized continuous | point | point: quantitative | quantize, quantile, or threshold scale |
| Option | Type | Default |
| --- | --- | --- |
| `field` | non-empty string | required |
| `target` | point, line, area, bar, rect, or arc mark ID | current mark |
| `fieldType` | `"nominal"`, `"ordinal"`, `"quantitative"`, or `"temporal"` | `"nominal"` |
| `aggregate` | aggregate operation; quantitative aggregate bars only | matching measure aggregate |
| `layout` | `"stack"`, `"fill"`, `"group"`, `"overlay"`, or `"diverging"` | mark policy |
| `palette` | palette name or `{ name, count?, extent? }`; shorthand for `scale.palette` | omitted |
| `scale.id` | scale ID | `"color"` |
| `scale.type` | `"ordinal"` | `"ordinal"` |
| `scale.domain` | `"auto"` or category array | `"auto"` |
| `scale.range` | `"auto"`, color array, or palette descriptor | `"auto"` |
| `scale.palette` | palette name or `{ name, count?, extent? }` | omitted |
```javascript
program.encodeColor({
field: "Origin",
scale: { palette: "tableau10" }
});
```
Top-level `palette` is a shorthand for `scale.palette`, and `scale.palette` is the
concise form of `scale.range: { palette: "tableau10" }`. Provide only one of these
forms. Automatic domains preserve first-appearance order.
Use `fieldType: "ordinal"` when the values are ordered categories rather than
continuous magnitudes. They still receive discrete palette colors and legend
entries:
```javascript
program.encodeColor({
field: "Cylinders",
fieldType: "ordinal",
scale: { palette: "reds" }
});
```
Calling `encodeColor` again for the same target replaces the categorical field.
Without `scale.id`, the current color scale is reused and its domain, marks,
and existing legend are recomputed. An explicit new ID retains the previous
named scale. Inferred legend titles follow the new field; explicit titles and
legend appearance remain unchanged.
Named palettes use a frozen 68-name vocabulary. Categorical palettes keep their
native order; `count` selects or deterministically cycles colors. Continuous,
diverging, and cyclical palettes used for ordinal mappings are sampled to the
domain size unless `count` is supplied. On a sequential scale, `count` must be
at least `2` and controls the concrete gradient-stop count. Non-categorical
palettes also accept an optional two-value `extent` within `[0, 1]`.
```javascript
program.encodeColor({
field: "Origin",
scale: { palette: { name: "set2", count: 3 } }
});
```
See [continuous color scales](../scales/continuous-color.md#named-palettes) for the complete vocabulary.
For an ordinal-category mean bar, `layout: "group"` is the default. It keeps
the measure stack null, invokes `encodeXOffset` for vertical bars or
`encodeYOffset` for horizontal bars, and recomputes the measure domain from
zero and one mean per category/color cell. Negative and positive bars therefore
extend in opposite directions from the same zero baseline. Color and offset
scales are fully resolved, and concrete rects use the implicit `0.72` band width immediately.
```javascript
groupedBars.encodeColor({
field: "sex",
layout: "group",
scale: { palette: "tableau10" }
});
```
Calling grouped color again with a new categorical field atomically updates color
and the directional offset to one matching first-appearance domain. Existing legends are
rematerialized; inferred titles follow the field while explicit titles and
styles remain unchanged.
On a complete histogram, `encodeColor` rematerializes each non-empty bin as
zero-stacked category rects. Stack and fill order follow the resolved color
domain. The y scale continues to use each bin's total count, and an explicit
domain must contain every observed category.
```javascript
histogram.encodeColor({
field: "Origin",
layout: "stack",
scale: {
domain: ["USA", "Europe", "Japan"],
palette: "tableau10"
}
});
```
Histogram color defaults to `stack`. `fill` normalizes every non-negative bin
partition to one and uses `[0, 1]` as the automatic y domain. `group` places
series side by side within each bin, `overlay` draws them in color-domain order
without changing opacity, and `diverging` accumulates positive and negative
values independently around zero. Ordinal aggregate bars support the same five
values; their group and overlay layouts use zero as each rectangle's start
endpoint and reject explicit measure domains that exclude zero. Call
`encodeBarWidth` after color only when the implicit `0.72` width is not desired.
```javascript
histogram.encodeColor({ field: "Origin", layout: "fill" });
```
On an area mark, color never creates grouping implicitly. Author grouping
directly or through `encodeDensity({ groupBy })`, then encode that same field:
```javascript
densityArea.encodeColor({
field: "Origin",
layout: "overlay",
scale: { palette: "tableau10" }
});
```
Area layouts support `stack`, `fill`, `overlay`, and `diverging`; `group` is
bar-only. Each existing path receives the resolved color for its group. Path order,
color domain order, and later legend order share the same ordered categories;
Canvas and shared-scale changes rematerialize every affected area fill.
Once a color layout exists, changing it to another layout is rejected until a
future companion-cleanup contract is implemented; the earlier program remains
unchanged.
### Continuous aggregate-bar color
For an aggregate bar, continuous color is computed at the same final category
grain as the rectangle. When the color field matches the quantitative measure,
omitting `aggregate` inherits that measure aggregate:
```javascript
bars
.encodeY({ field: "population", aggregate: "sum", stack: null })
.encodeColor({
field: "population",
fieldType: "quantitative",
scale: { type: "sequential", palette: "viridis" }
});
```
If color uses a different field, its aggregate is required because multiple
source rows cannot be chosen arbitrarily for one final rectangle:
```javascript
bars.encodeColor({
field: "lifeExpectancy",
fieldType: "quantitative",
aggregate: "mean",
scale: { type: "sequential", palette: "viridis" }
});
```
The resolved color domain contains the per-rectangle aggregate values.
`createLegend({ channels: ["color"] })` creates a gradient legend, and
`editScale({ id: "color", reverse: true })` rematerializes both bar fills and
the gradient without changing geometry. Continuous bar color rejects
`layout`; histogram, ranged-bar, line-path, and area-path continuous color are
not part of this contract.
## Related
[Series overview](../series-encodings.md) · [Scale options](../scales.md) · [Legends](../legends.md)
# Stroke Dash Encoding
## `encodeStrokeDash(options)`
Map a nominal field to line-series or rule dash patterns, or apply one constant pattern
to every series.
```javascript
program.encodeStrokeDash({ field: "Origin" });
program.encodeStrokeDash({ value: "dotted" });
```
| Option | Type | Default |
| --- | --- | --- |
| `field` | non-empty string; mutually exclusive with `value` | — |
| `value` | named style or direct dash pattern; mutually exclusive with `field` | — |
| `target` | line or rule mark ID | current mark |
| `fieldType` | `"nominal"`; field mode only | `"nominal"` |
| `scale.id` | scale ID | `"strokeDash"` |
| `scale.type` | `"ordinal"` | `"ordinal"` |
| `scale.domain` | `"auto"` or category array | `"auto"` |
| `scale.range` | `"auto"` or named/direct pattern array | `"auto"` |
The automatic range contains ten reusable patterns. An explicit pattern is an
empty array for a solid stroke or an even-length array of non-negative finite
numbers that is not entirely zero. Named styles resolve as follows:
| Style | Concrete pattern |
| --- | --- |
| `"solid"` | `[]` |
| `"dashed"` | `[6, 4]` |
| `"dotted"` | `[1, 3]` |
| `"dashdot"` | `[6, 3, 1, 3]` |
Names remain in semantic scale state, while resolved scales and graphics store
only numeric patterns.
```javascript
program.encodeStrokeDash({
field: "Origin",
scale: { range: ["solid", "dashed", "dotted"] }
});
```
Calling the action again atomically replaces the earlier field or constant.
The same field reuses its current scale when `scale.id` is omitted. A different
field uses the default `strokeDash` scale unless an ID is explicit, while old
named scales remain available as resources. Existing legend domains, symbols,
and inferred titles update; custom legend settings remain unchanged.
Constant mode accepts no scale or field type. It creates no legend, removes the
stroke-dash part of an existing combined legend, and removes a dash-only legend.
If color and stroke dash encode the same field, that field appears only once in
the series key and can be represented by one combined legend. Canvas changes
explicitly rematerialize both styles.
See [Scale options](../scales.md) and [Legends](../legends.md).
## Related
[Series overview](../series-encodings.md) · [Scale options](../scales.md) · [Legends](../legends.md)
# Appearance Encodings
## At a glance
| Action | Shortest call | Inference/defaults | Result |
| --- | --- | --- | --- |
| `encodeRadius` / `encodePointRadius` | `encodePointRadius({ value: 3 })` | Current point mark | Concrete point glyph radius |
| `encodeSize` | `encodeSize({ field: "Acceleration" })` | Current point; linear scale; area range `[24, 196]` | Semantic size and concrete equal-area symbols |
| `encodeShape` | `encodeShape({ field: "Origin" })` | Current point; 12-value ordinal shape range | Semantic shape and mixed concrete symbols |
| `encodeOpacity` | `encodeOpacity({ value: 0.27 })` | Current point mark | Constant concrete opacity |
| `encodeOpacity` | `encodeOpacity({ field: "Acceleration" })` | Current point; linear scale; range `[0.2, 1]` | Semantic field opacity and concrete values |
| `encodeStroke` | `encodeStroke({ value: "#334155" })` | Current rule mark | Constant concrete line color |
| `encodeStrokeWidth` | `encodeStrokeWidth({ value: 3 })` | Current rule mark | Constant concrete line width |
| `encodeStrokeWidth` | `encodeStrokeWidth({ field: "weight" })` | Current line/rule; quantitative scale; width range `[1, 8]` | Field-driven rule items or line series |
| `encodeBarWidth` | `encodeBarWidth()` | Current aggregate bar; first assignment uses band `0.72` | Concrete rectangles |
| `selectMarks` | `selectMarks({ field: "Horsepower", op: "max" })` | Current or unique mark; deterministic selection ID | Reusable semantic final-item selection |
| `highlightMarks` | `highlightMarks({ select: { field: "Horsepower", op: "max" } })` | Current point/bar/path/rule; red accent; selected-last | Concrete selected-item emphasis |
Appearance actions change final mark style without changing position. Use the
focused pages below for selection, point appearance, and mark-specific style.
## Supported highlight marks
| Action | Supported marks | Grain | Result |
| --- | --- | --- | --- |
| `selectMarks` / `highlightMarks` | point, bar, line, area, rect, arc, rule | item; stacked bars also support stack | selection intent and mark-specific durable emphasis |
## Focused appearance families
Selection and highlighting Select final graphical items and apply durable emphasis.
Point appearance Radius, size, shape, and opacity encodings.
Mark style Rule stroke and aggregate or ranged bar width.
## Errors and limitations
Radius, rule stroke, constant rule width, constant opacity, and both bar width modes are graphical constants.
Field opacity and field-driven stroke width are semantic encodings.
Size cannot be combined with a constant radius. A constant `editPointMark`
shape cannot be combined with field-driven `encodeShape`. Bar width
requires complete ordinal x, aggregate y, and color semantics; group additionally
requires matching xOffset semantics.
Ambiguous targets, duplicate inferred selection IDs, incompatible item-grain
selectors, and point-inapplicable highlight options fail before creating partial
selection or appearance state. Each supported mark accepts only the style
properties listed on the focused selection page.
## Related
[Marks](./marks.md) · [Position encodings](./position-encodings.md) ·
[Series encodings](./series-encodings.md) · [Legends](./legends.md)
# Selection and Highlighting
## Mark selection and highlighting
`selectMarks` is the advanced reusable-selection action. Selection by itself
does not alter `semanticSpec` or `graphicSpec`:
```javascript
const selected = program.selectMarks({
id: "highestByOrigin",
target: "points",
field: "Horsepower",
op: "max",
groupBy: "Origin"
});
```
Choose exactly one value source:
| Source | Reads | Typical use |
| --- | --- | --- |
| `field` | One data value that is unique for the final item | Origin of a complete line series |
| `channel` | Resolved semantic value before scale mapping | Upper bar endpoint `y2` |
| `property` | Concrete scalar from `graphicSpec` | Rendered rectangle `height` |
| Operator | Required operands | Defaults and result |
| --- | --- | --- |
| `eq`, `neq`, `gt`, `gte`, `lt`, `lte` | `value` | Strict comparison without coercion |
| `oneOf` | `values` array | Strict set membership |
| `range` | `min`, `max`, optional `inclusive` | Both endpoints included by default |
| `min`, `max` | optional `count`, `groupBy`, `ties` | `count: 1`, `ties: "first"` |
Operators are strict
`eq | neq | gt | gte | lt | lte`, set membership with
`{ op: "oneOf", values }`, range selection with
`{ op: "range", min, max, inclusive? }`, or ranked selection with
`{ op: "min" | "max", count?, groupBy?, ties? }`. `count` defaults to `1`,
`ties` to `"first"`, and `inclusive` to `true`. Values are semantic data
values without coercion. The default `grain: "item"` selects one point symbol,
one final bar segment/rectangle, one gradient-plot category strip, one
line/area series path, one arc sector, or one rule line.
Stacked bars additionally accept `grain: "stack"` to treat all segments in one
bin or category as a single item. A multi-row path field or channel must have
one unique value at series grain.
Bar channel and graphical property names intentionally mean different things.
For a vertical bar, semantic `y` is the lower/start endpoint and semantic `y2`
is the upper/end endpoint. Concrete property `y` is the top pixel and concrete
property `height` is its pixel length. For example, select the tallest complete
histogram stack semantically with:
```javascript
program.selectMarks({
target: "bars",
grain: "stack",
channel: "y2",
op: "max"
});
```
Use `{ property: "height", op: "max" }` only when the intended comparison is
the currently rendered pixel height.
`highlightMarks` is the concise chart-authoring facade. It can create the
selection inline or reuse one:
```javascript
const highlighted = program.highlightMarks({
select: {
field: "Horsepower",
op: "max",
groupBy: "Origin"
},
color: "#dc2626",
shape: "diamond",
size: 5.5,
offset: { x: 7, y: -7 },
dimOthers: { opacity: 0.18 }
});
const reused = selected.highlightMarks({
selection: "highestByOrigin",
color: "#dc2626"
});
```
Current highlighting supports point, bar, rect, line, area, arc, and rule marks. Points
support fill, shape, size, outline, and logical offset. Bars support fill and
outline. Areas and arc sectors support fill, optional outline, opacity, and path
offset. Lines and rules support stroke, width, named or numeric `strokeDash`,
and logical offset. Each mark rejects options it cannot represent. `dimOthers`
defaults to `false`;
`true` uses opacity `0.25`. `bringToFront` defaults to `true` and keeps every
graphic attached to one selected semantic item together.
| Mark | Selected-item options | Rejected selected-item options |
| --- | --- | --- |
| Point | `color`/`fill`, `opacity`, `stroke`, `strokeWidth`, `shape`, `size`, `offset` | `strokeDash` |
| Bar | `color`/`fill`, `opacity`, `stroke`, `strokeWidth` | `shape`, `size`, `offset`, `strokeDash` |
| Rect | `color`/`fill`, `opacity`, `stroke`, `strokeWidth`, `offset` | `shape`, `size`, `strokeDash` |
| Line | `color`/`stroke`, `opacity`, `strokeWidth`, `strokeDash`, `offset` | `fill`, `shape`, `size` |
| Area | `color`/`fill`, `opacity`, `stroke`, `strokeWidth`, `offset` | `shape`, `size`, `strokeDash` |
| Arc | `color`/`fill`, `opacity`, `stroke`, `strokeWidth`, `offset` | `shape`, `size`, `strokeDash` |
| Rule | `color`/`stroke`, `opacity`, `strokeWidth`, `strokeDash`, `offset` | `fill`, `shape`, `size` |
All calls reject unknown options, ambiguous targets, incompatible grain, and
invalid values before creating selection or highlight state. `strokeWidth`
requires a matching `stroke` for point, bar, area, and arc highlight recipes.
`select` and `selection` are mutually exclusive. Empty `selectMarks` and
`highlightMarks` results are valid; `filterMarks` rejects an empty retained
dataset before mutation.
```javascript
program.highlightMarks({
target: "trends",
select: { field: "Origin", op: "eq", value: "Japan" },
stroke: "#dc2626",
strokeWidth: 5,
strokeDash: "dashed",
dimOthers: { opacity: 0.16 }
});
```
When that selection exactly matches complete categories in the mark's legend,
legend symbols reflect the selected and dimmed appearance. Legend labels stay
fully readable. Partial or unrelated selections do not alter the legend.
Selection and highlight intent is immutable and is reapplied after owning mark
rematerialization, including Canvas changes and filtered data cardinality.
Reapplying `highlightMarks` for the same selection replaces that appearance
assignment.
For a gradient plot, opacity, stroke, or offset-only highlights preserve the
existing structured density paint. Supplying `color` or `fill` deliberately
replaces that paint on the selected strip. Use `filterData` before
`createGradientPlot`; `filterMarks` does not partially filter this composite
body and center-rule resource.
See the complete [mark-selection tutorial](../../tutorials/mark-selection.md) for
the approved point, stacked-bar, and line-series examples.
## Related
[Appearance overview](../appearance.md) · [Mark-selection tutorial](../../tutorials/mark-selection.md) · [Data filtering](../data/filtering.md)
# Point Appearance
## `encodeRadius({ value, target? })`
Broadcast a non-negative finite graphical radius to a point mark.
```javascript
program.encodeRadius({ value: 3 });
```
`encodePointRadius({ value, target? })` is the preferred alias in Polar charts.
It records `encodeRadius` as a wrapped child and remains distinct from
`encodeR`, which assigns semantic radial position.
| Option | Type | Default |
| --- | --- | --- |
| `value` | non-negative finite number | required |
| `target` | point mark ID | current mark |
Radius is fixed appearance. It does not create a semantic field encoding and
is not a Polar radial position channel.
## Point field encodings
```javascript
program
.encodeSize({ field: "Acceleration" })
.encodeShape({ field: "Origin" })
.encodeOpacity({ value: 0.27 });
```
`encodeSize({ field, target?, fieldType?, scale? })` requires a quantitative
field. Its scale accepts `id`, `type`, `domain`, and an area `range`; automatic
range is `[24, 196]`. Circles use `sqrt(area / PI)` as radius and squares use
`sqrt(area)` as side length, so the two shapes represent equal visual area.
`encodeShape({ field, target?, fieldType?, scale? })` requires a nominal field.
Its ordinal scale accepts the 12 shared point shapes documented under
[Marks](../marks.md). The automatic range uses each shape once and rejects more
than 12 distinct categories rather than silently repeating symbols. Explicit
ranges must contain unique supported shapes. Mixed circle, rect, and Z-closed path
symbols are stored as typed items in one graphical collection.
`encodeOpacity` accepts exactly one of `value` or `field`. A constant value from
`0` to `1` is graphical. A quantitative field creates semantic opacity and a
linear scale with automatic range `[0.2, 1]`; its scale supports explicit
domain/range plus `nice`, `zero`, `clamp`, and `reverse`. Calling the action
again atomically replaces constant↔field or field↔field mode. Every resolved
opacity remains concrete in `graphicSpec`.
All point appearance actions invoke the same point materializer. Existing x,
y, color, size, shape, and opacity state is recombined after each change and
after Canvas bounds change, making action order irrelevant.
Calling `encodeSize` or `encodeShape` again replaces its existing field and
compatible scale binding. Omitting `scale.id` reuses the current scale;
providing a new ID retains the previous named scale. Existing legends are
recomputed, inferred legend titles follow the new field, and explicit legend
titles and styles remain unchanged.
## Related
[Appearance overview](../appearance.md) · [Point marks](../marks/point.md) · [Legends](../legends.md)
# Mark Style
## Rule appearance
`encodeStroke({ value, target? })` assigns a required non-empty constant color
string to a rule. `encodeStrokeWidth({ value, target? })` assigns a
non-negative finite logical Canvas width to every child of the current rule.
These constant modes create no scale or legend.
`encodeStrokeWidth({ field, target?, fieldType?, scale? })` instead creates an
independent quantitative width scale for a line or rule. Rules receive one
width per source row. Lines receive one width per complete series, so all rows
in one series must contain the same field value. No implicit mean, sum, or
representative row is selected. The default concrete width range is `[1, 8]`.
```javascript
program
.encodeStrokeWidth({
field: "weight",
scale: { domain: [0, 100], range: [1, 8] }
})
.createLegend({ channels: ["strokeWidth"] });
```
Field values, domains, and ranges must be finite and non-negative. `value` and
`field` are mutually exclusive. `editScale` rematerializes both marks and an
active sampled stroke-width legend.
Rules also reuse `encodeStrokeDash` in constant or nominal-field mode and
`encodeOpacity` in constant or quantitative-field mode. Field modes produce
one concrete value per rule line; constant modes remain scale-free. Recalling
an owning action replaces that appearance assignment immutably.
## `encodeBarWidth({ band?, pixels?, target? })`
Override the fraction of each resolved category band—or directional offset slot for group
layout—used by an aggregate or ranged bar and rematerialize its rectangles.
```javascript
program.encodeBarWidth({ band: 0.72 });
```
| Option | Type | Default |
| --- | --- | --- |
| `band` | finite number greater than `0` and at most `1` | first assignment: `0.72` |
| `pixels` | positive finite logical Canvas pixels | none |
| `target` | aggregate or ranged bar mark ID | current mark |
`band` and `pixels` are mutually exclusive. Before this action is called,
complete aggregate and ranged bars already use the same implicit `0.72` band
default. A first empty call stores that default; a later empty call retains the
current mode and value. Band widths respond to Canvas resizing; pixel widths
remain fixed in logical coordinates and do not change with PNG `pixelRatio`.
An explicit pixel width may be wider than its slot, allowing intentional overlap.
The action requires a complete category/measure aggregate bar or a complete
categorical ranged bar. Group layout also requires matching color and directional
offset semantics. Thickness is the category bandwidth times `band` for stack, fill,
overlay, diverging, and ranged bars, or offset bandwidth times `band` for
group. Each bar is centered in its slot; missing cells are omitted.
`band` is graphical layout rather than chart meaning, so it is not added to
`semanticSpec`. The action stores immutable materialization config and writes
fully concrete `x`, `y`, `width`, `height`, and `fill` values to `graphicSpec`.
Canvas geometry changes explicitly rematerialize the scales and rectangles.
## Related
[Appearance overview](../appearance.md) · [Rule marks](../marks/rule.md) · [Bar marks](../marks/bar.md)
# Scale Options
Scales map semantic field or datum values into concrete positions and
appearance values. Encodings create compatible scales automatically; use
`editScale` when the inferred mapping needs a documented revision.
field + channel Semantic input
→
domain → range Resolved scale
→
x · fill · dash Concrete graphics
## At a glance
| Scale family | Default domain | Default range | Common controls |
| --- | --- | --- | --- |
| Continuous position | `"auto"` | Plot bounds | `type`, `nice`, `zero`, `clamp`, `reverse` |
| Band/point position | First-appearance order | Plot bounds | padding and alignment |
| Ordinal appearance/offset | First-appearance order | Palette, patterns, or parent band | explicit domain/range |
| Color/strokeDash | First-appearance order | Built-in palette/patterns | palette or explicit range |
| Sequential/discretized color | Type-specific numeric boundaries | `viridis` or explicit colors | `interpolate`, `clamp`, `reverse` |
Encoding actions accept a nested `scale` object. Omitted properties use channel
defaults and stored program state.
Existing scales can be changed with `editScale`:
```javascript
const reversed = program.editScale({ id: "x", reverse: true });
```
Color scales can use the same top-level palette shorthand during creation and
editing. It is mutually exclusive with `range`:
```javascript
const recolored = program.editScale({ id: "color", palette: "set2" });
```
Advanced authors can create a named unattached scale with the same complete
type vocabulary:
```javascript
const program = chart().createScale({
id: "temperature",
type: "sequential",
domain: [0, 100],
palette: { name: "viridis", count: 5 }
});
```
For sequential scales, `count` is the number of concrete gradient stops and
must be an integer of at least `2`. The same descriptor is accepted as
`range: { palette: { name: "viridis", count: 5 } }`.
The accepted types are `linear`, `log`, `pow`, `sqrt`, `symlog`, `time`,
`band`, `point`, `ordinal`, `sequential`, `quantize`, `quantile`, and
`threshold`. A later encoding attachment validates whether the type, range,
and fallback are compatible with that channel and mark.
`id` may be omitted when the current scale or the program's only scale is
unambiguous. At least one editable option is required. Use `"auto"` to reset
domain or range; omission preserves the current value. Quantitative position
scales can change atomically between `linear`, `log`, `pow`, `sqrt`, and
`symlog`:
```javascript
const logarithmic = program.editScale({ id: "x", type: "log", base: 10 });
```
Categorical bar positions use `band`; categorical point and rule positions use
`point`. Both preserve first-appearance domain order. A band scale exposes a
non-zero slot width and accepts `paddingInner`, `paddingOuter`, and `align`.
A point scale exposes zero bandwidth and accepts `padding` and `align`.
```javascript
program.encodeX({
field: "country",
fieldType: "nominal",
scale: { type: "band", paddingInner: 0.2, paddingOuter: 0.1 }
});
```
`align` is between `0` and `1`; both padding families are non-negative and
`paddingInner` is less than `1`. `editScale` rematerializes all connected marks
and guides. A band can be shared by bars and point centers, but changing it to
`point` is rejected while a bar requires its bandwidth.
## Focused scale families
Position scales Linear, transformed, time, band, and point mappings.
Ordinal and dash scales Stable categorical domains and stroke-dash ranges.
Continuous color Named palettes and continuous quantitative color.
Discretized color Quantize, quantile, and threshold classes.
Missing values Unknown fallbacks and compound-grain limits.
## Errors and limitations
One scale cannot be shared across different channels. Explicit domains must
contain every observed value required by ordinal consumers. A successful edit
rematerializes connected marks and guides; a failed edit leaves the earlier
immutable program unchanged.
Scale type transitions reject incompatible field types, channels, or mark
grains before changing the immutable program. Discretized color transitions
currently require quantitative point color; sequential color supports points
and aggregate bars. An active gradient or interval legend also fixes its
current recipe family, so a transition between sequential and discretized
color is rejected instead of silently replacing the guide.
## Related
[Position encodings](./position-encodings.md) ·
[Series encodings](./series-encodings.md) · [Semantic and graphical state](../concepts/semantic-and-graphics.md) ·
[Troubleshooting](../troubleshooting.md)
# Position Scales
## Compatibility matrix
| Scale family | Current compatible consumers |
| --- | --- |
| `linear`, `log`, `pow`, `sqrt`, `symlog` | Quantitative x/y on point, line, area, bar, and rule recipes that support that encoding |
| `time` | Temporal x/y on compatible marks; UTC normalization, ticks, and labels |
| `band` | Discrete category position that needs positive bandwidth, especially bars |
| `point` | Discrete point/rule centers and compatible shared centers; never bar width |
| `ordinal` | Nominal or ordinal color, nominal shape/stroke dash, and xOffset lookup |
| `sequential` | Quantitative/temporal point color and quantitative aggregate-bar color |
| `quantize`, `quantile`, `threshold` | Quantitative point color |
| `unknown` fallback | Row-owned point x/y/color/size/shape/opacity only |
The field type, channel, mark recipe, and all consumers of a shared scale must
agree. `editScale` validates that complete matrix before applying any semantic
or graphical change.
## Continuous scales
| Option | Type | Default |
| --- | --- | --- |
| `id` | scale ID | channel name |
| `type` | `"linear"`, `"log"`, `"pow"`, `"sqrt"`, `"symlog"`, or `"time"` | inferred from field type |
| `domain` | `"auto"` or two values | `"auto"` |
| `range` | `"auto"` or two finite numbers | `"auto"` |
| `nice` | boolean | omitted |
| `zero` | boolean | omitted; linear/pow/sqrt/symlog only |
| `clamp` | boolean | omitted (`false`) |
| `reverse` | boolean | omitted (`false`) |
| `base` | positive finite number except `1` | `10`; log only |
| `exponent` | positive finite number | `1`; pow only |
| `constant` | positive finite number | `1`; symlog only |
Automatic ranges use current plot bounds. For automatic linear domains,
`zero: true` includes zero and `nice: true` then expands to rounded boundaries.
Automatic temporal `nice` expands to UTC calendar boundaries. Time scales
reject `zero`.
For quantitative positions, `log` requires a strictly positive or
strictly negative domain and rejects `zero`. `pow` is sign-preserving, `sqrt`
is its fixed exponent-`0.5` specialization, and `symlog` supports values on
both sides of zero. Point, line, area, bar, and rule materializers use the same
mapping, and axes and grids read the same resolved scale.
Temporal field values are normalized while resolving the scale, without
rewriting the source dataset. Finite timestamps are accepted directly;
four-digit numbers or strings are UTC years, and validated `YYYY-MM-DD` or
`YYYY/MM/DD` strings are UTC calendar dates.
The precedence is:
```text
explicit domain > nice / zero > inferred domain
reverse(explicit range > inferred range)
```
`clamp: true` constrains values outside a continuous position domain to the nearest
range endpoint. `reverse: true` reverses the final resolved range. Ordinal
scales support `reverse` but reject `nice`, `zero`, and `clamp`.
### Binned bar x scales
A binned bar x encoding defaults to `nice: true` and `zero: false`. Automatic
nice bin boundaries use `1, 2, 3, 5 × 10ⁿ` steps and never create more than
`bin.maxBins` intervals. Explicit domains remain unchanged; `nice` and `zero`
do not expand them.
The resolved scale domain contains the outer bin boundaries. Concrete bin
counts and rect geometry are materialized once count/zero-stack y is present.
A histogram count y scale defaults to `nice: true` and `zero: true`. Its
automatic domain uses total counts per x bin rather than raw source values.
The x action leaves the collection empty; the y action resolves both scales and
materializes concrete histogram rectangles.
### Aggregate ordinal bar y scales
An ordinal bar aggregate y scale defaults to `nice: true` and `zero: false`.
Its automatic domain uses one final aggregate value per ordinal x/category and
series cell rather than raw source values. Group and overlay layouts also include
their semantic zero baseline in the automatic domain, even when `zero: false`;
that option does not remove an endpoint required by bar geometry. This includes
parameterized quantile and ordered `first`/`last` results. An explicit group or
overlay domain must contain zero, while explicit range values remain authoritative.
The y action resolves the scale but leaves rectangles empty until grouping
semantics are available.
## Related
[Scale overview](../scales.md) · [Encodings](../encodings.md) · [Troubleshooting](../../troubleshooting.md)
# Ordinal and Stroke Dash Scales
## Discrete and ordinal scales
Band/point position, color, and stroke-dash encodings use ordered discrete
domains. Automatic domains preserve first-appearance order. `ordinal` remains
the appearance and offset lookup type; position uses explicit `band` or `point`.
Color accepts both nominal and ordinal fields; ordinal is useful for discrete,
ordered numeric categories and does not turn them into a continuous gradient.
A band or point position scale uses `"auto"` or a numeric pair as its range.
Automatic ranges use the plot bounds. Resolved band scales record signed
`step`, aligned `start`, and positive `bandwidth`; point scales record the same
geometry with zero bandwidth. Marks, ticks, labels, and grids read the same
resolved centers. Reversed explicit ranges are valid.
Color ranges accept explicit colors or a named palette descriptor; stroke-dash
ranges accept named styles and direct dash patterns.
```javascript
program.encodeColor({
field: "Origin",
scale: {
domain: ["USA", "Europe", "Japan"],
range: ["#4c78a8", "#f58518", "#e45756"]
}
});
```
User-specified domains and ranges are semantic state. Resolved coordinates,
colors, and dash patterns are stored as concrete graphical values.
### Stroke-dash ranges
The names `solid`, `dashed`, `dotted`, and `dashdot` resolve to `[]`, `[6, 4]`,
`[1, 3]`, and `[6, 3, 1, 3]`. A direct pattern is an empty array or an
even-length array of non-negative finite numbers that is not entirely zero.
```javascript
program.encodeStrokeDash({
field: "Origin",
scale: { range: ["solid", "dashed", [8, 3]] }
});
```
Semantic scale state preserves the names; the resolved scale, line paths, and
legend symbols contain numeric patterns only. Pattern lengths use logical
Canvas units and do not change with output pixel ratio.
## Related
[Scale overview](../scales.md) · [Encodings](../encodings.md) · [Troubleshooting](../../troubleshooting.md)
# Continuous Color Scales
## Named palettes
Use a name directly or an object with optional sampling controls:
```javascript
program.encodeColor({
field: "Origin",
scale: { palette: { name: "set2", count: 3 } }
});
```
Accepted names are fixed by the ggaction contract:
| Family | Names |
| --- | --- |
| Categorical | `accent`, `category10`, `category20`, `category20b`, `category20c`, `observable10`, `dark2`, `paired`, `pastel1`, `pastel2`, `set1`, `set2`, `set3`, `tableau10`, `tableau20` |
| Sequential | `blues`, `tealblues`, `teals`, `greens`, `browns`, `oranges`, `reds`, `purples`, `warmgreys`, `greys`, `viridis`, `magma`, `inferno`, `plasma`, `cividis`, `turbo`, `bluegreen`, `bluepurple`, `goldgreen`, `goldorange`, `goldred`, `greenblue`, `orangered`, `purplebluegreen`, `purpleblue`, `purplered`, `redpurple`, `yellowgreenblue`, `yellowgreen`, `yelloworangebrown`, `yelloworangered`, `darkblue`, `darkgold`, `darkgreen`, `darkmulti`, `darkred`, `lightgreyred`, `lightgreyteal`, `lightmulti`, `lightorange`, `lighttealblue` |
| Diverging | `blueorange`, `brownbluegreen`, `purplegreen`, `pinkyellowgreen`, `purpleorange`, `redblue`, `redgrey`, `redyellowblue`, `redyellowgreen`, `spectral` |
| Cyclical | `rainbow`, `sinebow` |
`count` must be a positive integer. Categorical palettes use a prefix when the
count is shorter and cycle deterministically when it is longer. Other families
used for an ordinal mapping are sampled to `count`, or to the resolved domain
size when omitted. On a sequential scale, `count` must be at least `2` and sets
the number of concrete gradient stops; omission uses the default stop count.
`extent` is accepted only for non-categorical palettes and contains two distinct
values within `[0, 1]`; descending values reverse the sampling direction. Scale
`reverse` is applied afterward.
The semantic scale stores the palette descriptor. Materialized scales, marks,
legends, and renderers receive only concrete CSS colors. Explicit `range` and
`palette` cannot be supplied together.
## Continuous point and aggregate-bar color
Quantitative or temporal point color uses `fieldType: "quantitative"` or
`"temporal"` and an internal sequential scale. Aggregate bars support
quantitative sequential color with one aggregate value per final rectangle.
The default palette is
`viridis`; an explicit palette may use `count` and `extent`, while an explicit
range needs at least two colors. `count` controls the stored gradient stops;
the scale still interpolates continuously between them. `interpolate` accepts
`rgb`, `hsl`, `hsl-long`, `lab`,
`hcl`, `hcl-long`, `cubehelix`, or `cubehelix-long`. `clamp` and `reverse`
affect both points and a connected gradient legend.
```javascript
program.encodeColor({
field: "Acceleration",
fieldType: "quantitative",
scale: {
palette: { name: "viridis", count: 5 },
interpolate: "rgb"
}
});
```
The sequential type is inferred inside `encodeColor` and is also available to
direct `createScale` and compatible atomic `editScale` transitions.
For aggregate bars, a color field equal to the measure field inherits its
aggregate. A different quantitative field requires an explicit `aggregate`.
The automatic domain is derived from those final aggregate values, not from
the unaggregated source rows.
## Related
[Scale overview](../scales.md) · [Encodings](../encodings.md) · [Troubleshooting](../../troubleshooting.md)
# Discretized Color Scales
## Discretized point color
Quantitative point color can create concrete color classes instead of a
continuous gradient:
- `quantize` divides a numeric extent into equal-width intervals.
- `quantile` derives boundaries that keep observed class counts as even as
possible.
- `threshold` uses an explicit, strictly increasing boundary array. A domain
with `n` boundaries requires `n + 1` colors.
```javascript
program.encodeColor({
field: "life_expect",
fieldType: "quantitative",
scale: {
type: "threshold",
domain: [60, 70, 75, 80],
range: ["#440154", "#3b528b", "#21918c", "#5ec962", "#fde725"]
}
});
```
An exact boundary belongs to the upper interval. `reverse: true` reverses the
resolved colors without changing boundaries. `createLegend()` infers an
interval legend with labels such as `< 60`, `60–70`, and `≥ 80`. These
mappings are available through quantitative point `encodeColor` and the direct
scale vocabulary. A type-changing `editScale` call validates the complete
replacement definition before rematerializing its consumers.
## Related
[Scale overview](../scales.md) · [Encodings](../encodings.md) · [Troubleshooting](../../troubleshooting.md)
# Scale Missing Values
## Missing and invalid values
Point encodings can provide an `unknown` fallback inside their scale options:
```javascript
program.encodeX({
field: "value",
scale: { domain: [0, 100], unknown: 20 }
});
```
The fallback is used for a missing or field-type-invalid input. For ordinal
point position or appearance, it also handles a value outside an explicit
domain. It never becomes a domain member. Output validation follows the
channel: position uses a finite coordinate, color a non-empty string, size a
non-negative area, opacity a value from `0` to `1`, and shape one supported
point shape.
This policy is intentionally limited to row-owned point items. A missing value
inside a line/area path, bar aggregate, rule, xOffset group, or stroke-dash
series could change item topology; those consumers reject `unknown` until they
have a separate topology-safe policy. A direct unattached scale stores its
fallback and defers channel validation until attachment. Set
`unknown: undefined` with `editScale` to remove an existing fallback.
## Related
[Scale overview](../scales.md) · [Encodings](../encodings.md) · [Troubleshooting](../../troubleshooting.md)
# Coordinates
## At a glance
| Action | Shortest call | Inference/defaults | Result |
| --- | --- | --- | --- |
| `createCoordinate` | `createCoordinate()` | ID `main`, type `cartesian` | Named semantic coordinate, optionally attached to layers |
Position encoding actions normally manage coordinates automatically:
encodeX + encodeY Cartesian channels
→
main / cartesian Horizontal and vertical position
encodeTheta + encodeR Polar channels
→
polar / polar Angle and radial position
encodeParallelCoordinates Ordered dimensions
→
parallel / parallel One local scale per dimension
```text
encodeX / encodeY -> main / cartesian
encodeTheta / encodeR -> polar / polar
encodeParallelCoordinates -> parallel / parallel
```
The resolved coordinate definition and layer reference are stored in
`semanticSpec` before guide creation.
## `createCoordinate({ id?, type?, layers? })`
Use this advanced chart action when a named semantic coordinate must be created
or attached explicitly.
| Option | Type | Default |
| --- | --- | --- |
| `id` | valid user-defined ID | `"main"` |
| `type` | `"cartesian"`, `"polar"`, or `"parallel"` | `"cartesian"` |
| `layers` | array of existing layer IDs | `[]` |
```javascript
program.createCoordinate({
id: "detail",
type: "cartesian",
layers: ["points"]
});
```
Equivalent repeated creation is allowed. A conflicting type or an attempt to
reattach a layer that already uses another coordinate produces an error.
Polar point, line, and arc position actions create or reuse a Polar coordinate
automatically. The layer stores semantic theta/radius encodings, while
`graphicSpec` stores only final Cartesian x/y values and path commands for the
renderer.
`encodeParallelCoordinates` creates or reuses a Parallel coordinate and owns
the complete ordered dimension assignment on one line layer. Each dimension
uses its own namespaced scale and axis. Use the complete
[Parallel Coordinates API](./parallel-coordinates.md) for that contract.
## Errors and limitations
A layer cannot be silently moved from one coordinate to another, and Cartesian
x/y cannot be mixed with Polar theta/radius. Polar point, open-line, closed
radar, donut, rose, and radial-bar charts support theta/radius axes and grids.
Parallel coordinates support dimension axes but do not create a Cartesian or
Polar grid.
## Related
[Position encodings](./position-encodings.md) · [Axes](./axes.md) ·
[Grids](./grids.md)
# Axes
## At a glance
| Action | Shortest call | Inference/defaults | Result |
| --- | --- | --- | --- |
| `createAxes` | `createAxes()` | Stored coordinate family, position scales, titles | Complete x/y, theta/radius, or Parallel dimension axes |
| `editXAxis` | `editXAxis({ line: { lineWidth: 2 } })` | Existing x-axis components | Selected x-axis components rematerialized |
| `editYAxis` | `editYAxis({ position: "right" })` | Existing y-axis components | Existing y-axis components moved together |
| `removeXAxis` / `removeYAxis` | `removeXAxis()` | Existing complete axis | Semantic, graphic, and stored axis state removed |
## `createAxes(options?)`
Creates complete axes for encoded Cartesian x/y, Polar theta/radius, or Parallel dimension channels. This is the recommended axis
action for ordinary chart authoring.
```javascript
program.createAxes({
y: { ticksAndLabels: { count: 6 } }
});
```
| Option | Type | Default |
| --- | --- | --- |
| `coordinate` | `{ id?, type? }` | unique coordinate used by x/y layers |
| `x` | axis options or `false` | create when x is encoded |
| `y` | axis options or `false` | create when y is encoded |
| `theta` | Polar axis options or `false` | create when theta is encoded |
| `radius` | Polar axis options or `false` | create when radius is encoded |
`coordinate.type` accepts `"auto"`, `"cartesian"`, `"polar"`, or `"parallel"` as a stored
type assertion.
Each x/y axis option supports:
| Option | Value |
| --- | --- |
| `scale` | scale ID; inferred when one scale is used on the channel |
| `position` | x: `"bottom"` or `"top"`; y: `"left"` or `"right"` |
| `line` | `{ color?, lineWidth? }` |
| `ticksAndLabels` | `{ count?, values?, ticks?, labels? }` |
| `title` | title options including `text`, `at`, `offset`, and font styling |
Use either `count` or exact data-space `values` for ticks. Ambiguous coordinates
or scales must be selected explicitly. `createAxes` reads stored coordinates;
it never creates or repairs them.
Linear scales create numeric nice ticks. Time scales choose a UTC calendar
interval near the requested count and format labels automatically. Automatic
formatting starts from the domain span, then raises precision only when two
distinct resolved ticks would otherwise share a label. For example, a
1970–1982 domain produces `1970`, `1972`, ..., `1982`, while sub-month ticks
include the day needed to distinguish them. Explicit time values are finite
timestamps.
A band or point x scale uses its complete domain as the default tick and label
values. Each value is placed at the shared band or point center and formatted
with `String(value)`. Explicit `ticksAndLabels.values` may select a domain
subset in the requested order. Discrete axes reject `count` so categories are
not silently omitted. Reversed ranges and Canvas rematerialization preserve
the stored category values.
For a binned histogram x encoding, omitted tick options use the inferred bin
boundaries. This keeps the axis aligned with every rect edge. Explicit
`ticksAndLabels.count` or `ticksAndLabels.values` takes precedence. Count y
axes use numeric nice ticks and infer titles such as `count(Displacement)`.
Titles are inferred from the unique encoding consuming each scale. Aggregate
encodings include their operation, so `mean` on `Acceleration` becomes
`mean(Acceleration)`. Pass `title.text` when inference is ambiguous or a custom
label is desired.
The default edges remain bottom for x and left for y. A complete axis forwards
an explicit edge to its line, ticks, labels, and title:
```javascript
program.createAxes({
x: {
position: "top",
ticksAndLabels: { labels: { format: ".1f" } }
},
y: { position: "right" }
});
```
Top ticks point upward and right ticks point right. Labels and titles are
placed outward from the selected edge. The Canvas margin must already be large
enough; guide creation does not resize it.
Numeric label formats are `.0f`, `.1f`, `.2f`, `.0%`, `.1%`, and `.2e`.
UTC time formats are `%Y`, `%Y-%m`, and `%Y-%m-%d`. Numeric formats require a
linear scale, time formats require a time scale, and ordinal labels use
`"auto"`. The existing `{ decimals: nonNegativeInteger }` form remains
available for linear labels. Explicit formats remain exact and may intentionally
produce repeated display strings.
The selected coordinate ID is stored on each semantic axis. Canvas size and
margin edits explicitly rematerialize positional scales and every connected
axis component.
The trace preserves its decomposition:
```text
createAxes
├─ createXAxis (when selected)
└─ createYAxis (when selected)
```
For a Polar coordinate, the same aggregate becomes:
```text
createAxes
├─ createThetaAxis
└─ createRadialAxis
```
`createThetaAxis()` creates the outer circular baseline, outward ticks,
perimeter labels, and an inferred title. `createRadialAxis()` creates one
center-to-edge baseline; its `angle` defaults to `90` degrees (right). Both
support `ticksAndLabels: { count?, values?, ticks?, labels? }` and title style.
Set `title: false` on either Polar axis to omit that title at creation.
The radial title defaults to `position: "inside"` at the baseline midpoint.
Use `title: { position: "outside" }` to place it beyond the radial endpoint;
`offset` is measured from the midpoint normal when inside and from the endpoint
when outside.
For a Parallel coordinate, `createAxes()` delegates to an internal wrapped
dimension-axis action. It creates one ordinary baseline, tick/label set, and
title for each stored dimension. Channel-specific x/y/theta/radius options do
not apply; revise dimension mapping with `encodeParallelCoordinates` and
individual mappings with `editScale`.
For individual lines, ticks, labels, and titles, see
[Advanced axis components](../advanced/axis-components.md).
## Editing a complete axis
Use `editXAxis()` or `editYAxis()` when several components of one existing
axis should change together:
```javascript
program
.editXAxis({
line: { color: "#334155", lineWidth: 2 },
ticksAndLabels: {
count: 6,
ticks: { length: 7 },
labels: { color: "#475569", fontSize: 11 }
},
title: { text: "Engine displacement" }
})
.editYAxis({ position: "right" });
```
The complete edit facade does not create an axis or change its scale and
coordinate binding. It delegates to the existing line, tick, label, grouped
tick/label, and title edit actions. Omitted component objects remain unchanged.
Changing `position` updates every existing component on that axis, including
components omitted from the call.
Use `editThetaAxis()` for grouped theta component edits. Use
`editRadialAxis({ angle: 180 })` to move the radial line, ticks, labels, and
title together. Focused `editThetaAxisLine/Ticks/Labels/Title` and matching
`editRadialAxis*` actions change one visible component without raw graphic IDs.
`editRadialAxisTitle({ position: "inside" | "outside" })` switches only the
radial title placement while preserving its text and style.
## Removing an axis
`removeXAxis()`, `removeYAxis()`, `removeThetaAxis()`, and
`removeRadialAxis()` remove the complete axis: line, ticks,
labels, title, semantic guide state, and stored materialization settings. Marks,
scales, coordinates, and the opposite axis remain.
```javascript
const withoutXAxis = program.removeXAxis();
const selected = program.removeYAxis({ scale: "y", coordinate: "main" });
```
Optional selectors must match the existing resource. A missing or mismatched
axis throws before anything changes.
| Option | Meaning |
| --- | --- |
| `position` | x: `"bottom"/"top"`; y: `"left"/"right"` |
| `line` | `{ color?, lineWidth? }` |
| `ticks` | `{ count?, values?, length?, color?, lineWidth? }` |
| `labels` | `{ count?, values?, offset?, format?, color?, fontSize?, fontFamily?, fontWeight? }` |
| `ticksAndLabels` | `{ count?, values?, ticks?, labels? }` |
| `title` | `{ text?, at?, offset?, rotation?, color?, fontSize?, fontFamily?, fontWeight? }` |
Axis label and title weights follow the shared
[Canvas font-weight policy](./marks/text.md#font-weights).
Use either `ticksAndLabels` or the independent `ticks`/`labels` options in one
call. The action validates the entire request before editing any component, so
an invalid later component cannot leave a partial result.
## Errors and limitations
Ambiguous scale or coordinate candidates require explicit IDs. Each channel
has one semantic axis, so parallel duplicate axes cannot be created.
## Related
[Guides](./guides.md) · [Grids](./grids.md) ·
[Advanced axis components](../advanced/axis-components.md)
# Grids
## At a glance
| Action | Shortest call | Inference/defaults | Result |
| --- | --- | --- | --- |
| `createGrid` | `createGrid()` | Cartesian horizontal or applicable Polar families | Concrete guides behind related marks |
| `editGrid` | `editGrid({ horizontal: { count: 6 } })` | Existing selected directions | One or both directions rematerialized |
| `removeGrid` | `removeGrid({ vertical: true })` | Existing selected directions | Semantic, graphic, and stored grid state removed |
## `createGrid(options?)`
Creates Cartesian or Polar grid geometry from encoded scales. Cartesian
defaults to horizontal only. A Polar-only chart creates theta spokes when theta
is encoded and radial circles when radius is encoded; charts with both channels
create both families.
```javascript
program.createGrid();
```
```javascript
program.createGrid({
horizontal: {
color: "#e2e8f0",
lineWidth: 1,
strokeDash: []
},
vertical: true
});
```
| Option | Type | Default |
| --- | --- | --- |
| `horizontal` | boolean or direction options | `true` |
| `vertical` | boolean or direction options | `false` |
| `theta` | boolean or Polar direction options | Polar-only: encoded theta |
| `radial` | boolean or Polar direction options | Polar-only: encoded radius |
Direction options are:
| Option | Meaning |
| --- | --- |
| `scale` | Scale ID; inferred from y for horizontal or x for vertical |
| `coordinate` | Cartesian coordinate ID; inferred from encoded layers |
| `count` | Positive requested line density |
| `values` | Exact finite data-space values; mutually exclusive with `count` |
| `color` | Stroke color; default `#e2e8f0` |
| `lineWidth` | Non-negative stroke width; default `1` |
| `strokeDash` | Even-length non-negative dash array; default `[]` |
When values and count are omitted, a grid reuses compatible existing axis
ticks. Without an axis it uses histogram bin boundaries for a binned x scale,
or five nice ticks for another continuous scale. Explicit values and count
always take precedence.
Horizontal lines span the plot width at y-scale positions. Vertical lines span
the plot height at x-scale positions. Grid graphics are inserted before the
first related mark in rendering order, even when `createGrid` is called after
that mark was created.
Canvas and connected scale changes explicitly rematerialize concrete grid
positions. Grid style is graphical state; only the resolved scale and
coordinate IDs are stored in `semanticSpec`.
The trace preserves direction-level actions:
```text
createGrid
├─ createHorizontalGrid?
└─ createVerticalGrid?
```
Polar dispatch uses `createRadialGrid` for concentric closed paths and
`createThetaGrid` for center-to-edge spokes. Theta defaults to six ticks;
radius defaults to five. A zero radial tick remains available to the axis but
does not create a degenerate zero-radius circle. Polar grid color defaults to
`#d7e0ea`.
Use `false` to disable a direction. If scale or coordinate inference is
ambiguous, provide its ID explicitly.
## Editing grids
Use the direction-specific edit actions after that grid exists:
```javascript
program
.editHorizontalGrid({ count: 6, color: "#cbd5e1" })
.editVerticalGrid({ values: [50, 100, 150], strokeDash: [4, 2] });
```
Edit options are `count`, `values`, `color`, `lineWidth`, and `strokeDash`.
Use either `count` or `values`. `values: "auto"` restores current axis/scale
inference. At least one option is required.
Use `editGrid()` to update both directions through one aggregate action:
```javascript
program.editGrid({
horizontal: { count: 6, color: "#cbd5e1" },
vertical: { values: [50, 100, 150], strokeDash: [4, 2] }
});
```
Both selected directions must already exist. The complete request is validated
before either directional edit runs, so an invalid vertical patch cannot leave
a partially edited horizontal grid.
Grid edits preserve the existing scale and coordinate binding. They invoke a
wrapped directional rematerialization action, replacing only that direction's
concrete line geometry and appearance. `editGrid` delegates to these same
directional actions, which remain available for focused edits.
Polar grids use the same pattern through `editThetaGrid`, `editRadialGrid`, or
`editGrid({ theta: {...}, radial: {...} })`. `removeGrid` accepts the same
direction names.
`createGuides()` selects this default horizontal grid automatically when a y
encoding is present. Pass `createGuides({ grid: false })` to opt out.
## Removing grids
`removeGrid()` removes every existing direction. Select a subset with explicit
booleans:
```javascript
const noGrid = program.removeGrid();
const horizontalOnly = program.removeGrid({ vertical: true });
```
At least one direction must be selected; explicit false/false is an error.
Scales, coordinates, marks, and axes remain.
## Errors and limitations
Each selected direction requires one compatible resolved scale and matching
Cartesian or Polar coordinate. Ambiguous resources require explicit IDs.
## Related
[Guides](./guides.md) · [Axes](./axes.md) ·
[Advanced axis components](../advanced/axis-components.md)
# Legends
encoding + scale Guide meaning
→
symbol recipe Categorical, gradient, or composite
→
rect · line · text Concrete guide graphics
## At a glance
| Action | Shortest call | Inference/defaults | Result |
| --- | --- | --- | --- |
| `createLegend` | `createLegend()` | Current/unique compatible mark; right position | Categorical, size, stroke-width, gradient, interval, or opacity guide |
| `editLegend` | `editLegend({ position: "left" })` | Unique existing legend; omitted properties retained | Rematerialized layout and appearance |
| Focused edits | `editLegendLabels({ fontSize: 11 })` | Same target inference as `editLegend` | One legend component rematerialized |
| `removeLegend` | `removeLegend({ target: "points" })` | Existing legend owner | All owned legend blocks removed |
Legends are inferred from final mark encodings and materialized as concrete
graphics. Start with the family that matches the encoded channel and use the
editing page when changing an existing guide.
## Supported legend families
| Legend family | Supported marks | Channels |
| --- | --- | --- |
| Categorical | point, line, area, bar, rect, arc | color, shape, strokeDash, or compatible composites |
| Continuous gradient | point, aggregate bar, rect | sequential color |
| Discretized interval | point | quantize, quantile, or threshold color |
| Sampled | point, line, rule | field opacity, size, or strokeWidth |
## Focused legend families
Categorical legends Create categorical, size, and interval guides and control their layout.
Continuous legends Gradient color and sampled opacity guides.
Composite symbols Layered line, point, and swatch recipes plus optional borders.
Edit and remove Atomic component edits, rematerialization, trace, and removal.
## Errors and limitations
Continuous color legends support point, aggregate-bar, and rect marks. Field
opacity and discretized continuous legends remain point-only. Interactive legends are unsupported.
Combined point-series and quantitative-size legends require a right or left
side position so both blocks remain in one vertical stack. A left block must
fit outside any left y-axis guides; use sufficient margin and offset.
Standalone stroke-width legends currently use the right side and accept
`count` when created. Edit their quantitative mapping through `editScale`;
`editLegend` support for that sampled block is not yet part of the public
contract.
Right-side layout requires sufficient right margin; bottom layout requires
sufficient bottom margin; top layout requires enough top margin for its title,
item grid, offset, and optional border. The library reports a layout error
instead of resizing the Canvas or dropping symbol layers.
## Related
[Guides](./guides.md) · [Series encodings](./series-encodings.md) ·
[Canvas](./canvas.md) · [Troubleshooting](../troubleshooting.md)
# Categorical and Size Legends
## `createLegend(options?)`
Creates inferred legend blocks. It supports combined line-series,
color-stacked histogram, grouped ordinal-bar, grouped area, composite point-series,
quantitative point-size, continuous-color gradient, and field-opacity legends.
It also infers interval swatches for quantize, quantile, and threshold point
color scales.
~~~javascript
program.createLegend();
~~~
A size encoding is independently eligible; color and shape are not required:
~~~javascript
program.createLegend({ channels: ["size"], position: "right", count: 4 });
~~~
With one size-encoded point mark, both `createLegend()` and `createGuides()`
infer the same block. Multiple size-encoded point marks require `target`.
Standalone size legends currently use the right position; a size block paired
with a categorical point legend may use either side.
Every categorical legend uses the same right-side default:
| Mark | Channels | Position | Symbol |
| --- | --- | --- | --- |
| line | encoded `color` and/or `strokeDash` | `right` | line |
| bar histogram | `color` | `right` | swatch |
| grouped ordinal bar | `color` | `right` | swatch |
| grouped area | `color` | `right` | swatch |
| point | explicitly selected `color` only | `right` | swatch |
| point + matching line | `color` + `shape` | `right` | line over typed point |
| quantitative point size | `size` | `right`, standalone or below point series | five equal-area circles |
| quantitative/temporal point color | `color` | `right` | continuous gradient with five labels |
| discretized quantitative point color | `color` | `right` | ordered interval swatches |
| quantitative point opacity | `opacity` | `right` | five constant-size circles with sampled opacity |
| Option | Type | Default |
| --- | --- | --- |
| `target` | compatible mark ID | current or unique compatible mark |
| `channels` | compatible channel array; continuous guides use one `color` or `opacity` | compatible encoded channels |
| `position` | `right/left/bottom/top`; combined point-size guides use a side | `"right"` |
| `align` | `"left"`, `"center"`, or `"right"` | `"center"` |
| `direction` | `"horizontal"` or `"vertical"` | `"horizontal"` |
| `columns` | positive integer | all items in one row at top |
| `offset` | non-negative number | `8` |
| `titlePosition` | `"top"` or `"left"` | `"top"` |
| `title` | non-empty string | encoded field name |
| `symbol` | `"auto"`, shorthand object, or layered recipe | inferred from mark |
| `labels` | label style object | default sans-serif label style |
| `titleStyle` | title style object | default sans-serif title style |
| `itemGap` | positive number | `28` at either side, `20` at top/bottom |
| `border` | boolean or border style object | `false` |
| `count` | size-legend symbol count of at least `2` | `5` for point legends |
| `gradient` | `{ length?, thickness? }` with positive values | `{ length: 120, thickness: 12 }` |
Pass `position: "bottom"` explicitly to place the legend below the plot.
Bottom legends use the same item grid as top legends and can use left, center,
or right alignment; side legends require center alignment. Left categorical,
composite point, and size blocks use vertical flow and preserve symbol-to-label
and resolved-domain order.
For compatibility, `createLegend({ position: "bottom" })` keeps the compact
single-row layout anchored near the Canvas bottom edge. Supplying any grid
control such as `columns`, `direction`, `offset`, `titlePosition`, or `itemGap`
selects the general reserved-margin grid.
Top and bottom legends use a general item grid. `columns` caps the column count;
`direction: "horizontal"` fills rows first and `"vertical"` fills columns
first. `align` positions the complete title-plus-items block within plot
bounds. The title appears above the grid by default, or beside it with
`titlePosition: "left"`.
~~~javascript
densityArea.createLegend({
position: "top",
direction: "vertical",
columns: 3,
titlePosition: "left",
offset: 8
});
~~~
## Related
[Legend overview](../legends.md) · [Composite symbols](./composite.md) · [Editing legends](./editing.md)
# Continuous Legends
## Continuous color and opacity
A point `color` encoding with a quantitative or temporal field produces a
continuous gradient. Right/left positions orient it vertically; top/bottom
orient it horizontally. The implementation writes 60 adjacent concrete rects,
tick lines, and text to `graphicSpec`; renderers do not interpolate colors.
~~~javascript
program.createLegend({
channels: ["color"],
count: 5,
gradient: { length: 120, thickness: 12 }
});
~~~
A field-driven quantitative `opacity` encoding produces representative point
samples in ascending domain order. Reversing the opacity range changes symbol
appearance without reversing labels. Its neutral default symbol is a circle
with radius `7` and fill `#4c78a8`; pass one `{ type: "point", ... }` recipe to
override it.
~~~javascript
program.createLegend({ channels: ["opacity"], position: "left" });
~~~
Gradient legends reject categorical-only `symbol`, `columns`, `direction`, and
`itemGap`. Opacity legends reject `columns`, `direction`, and `gradient`.
Both forms require enough requested Canvas margin and never resize the Canvas.
For a `quantize`, `quantile`, or `threshold` point-color scale, the same call
creates ordered swatches and concrete interval labels. The current interval
layout is vertical at the right edge; `offset`, `itemGap`, `symbol`, `labels`,
`titleStyle`, and title editing remain available.
~~~javascript
program.createLegend({
channels: ["color"],
position: "right",
direction: "vertical",
symbol: { width: 14, height: 12 }
});
~~~
## Related
[Legend overview](../legends.md) · [Scale options](../scales.md) · [Editing legends](./editing.md)
# Composite Legend Symbols
## Layered symbols
Legend symbols are graphical recipes composed from line, point, and swatch
layers. The default line shorthand remains supported:
~~~javascript
lineProgram.createLegend({
symbol: { length: 32, lineWidth: 2 }
});
~~~
Histogram, grouped-bar, and grouped-area swatch shorthand supports `width`,
`height`, `stroke`, and `strokeWidth`.
Use layers for a composite symbol:
~~~javascript
lineProgram.createLegend({
symbol: {
layers: [
{ type: "line", length: 32, lineWidth: 2 },
{ type: "point", shape: "circle", size: 4 }
]
}
});
~~~
The same recipe works in top and bottom item grids:
~~~javascript
lineProgram.createLegend({
position: "bottom",
align: "right",
direction: "horizontal",
columns: 2,
symbol: {
layers: [
{ type: "line", length: 36, lineWidth: 3 },
{ type: "point", shape: "circle", size: 5 }
]
}
});
~~~
Supported layers are:
~~~javascript
{ type: "line", length?, lineWidth? }
{ type: "point", shape?: "circle", size?, fill?, stroke?, strokeWidth? }
{ type: "swatch", width?, height?, stroke?, strokeWidth? }
~~~
Every layer shares the same item anchors. A line and point therefore overlap
as one composite symbol. The union of their bounds determines label placement,
and layers retain their declared rendering order. Recipes are private
appearance configuration; the final `graphicSpec` contains only concrete line,
circle, and rect primitives.
## Items and semantics
Items follow the resolved ordinal domain order. Color and dash appearance come
from the matching resolved ranges. Combined line channels must encode the same
field and have identical ordered domains.
A bar color legend stores `guide.legend.color` with its scale and title. A
combined line legend stores `guide.legend.series` with its channels, scales, and
title. Positions, fonts, symbols, and border appearance are graphical state.
A point legend combines color and shape only when they encode the same nominal
field and share an ordered domain. If a matching line layer uses that field and
color scale, its line is layered behind each typed circle/square symbol. A
separate `guide.legend.size` block samples five evenly spaced domain values by
default and maps their areas through the resolved quantitative size scale.
## Optional border
The default creates no background. Pass true for default border settings or an
object with color, lineWidth, padding, and background.
~~~javascript
program.createLegend({
border: {
color: "#cbd5e1",
lineWidth: 1,
padding: 8,
background: "white"
}
});
~~~
The background is rendered before every symbol layer, label, and title.
## Related
[Legend overview](../legends.md) · [Categorical legends](./categorical.md) · [Series encodings](../series-encodings.md)
# Editing Legends
## Updates and trace
`editLegend()` updates one existing stable legend. Omit `target` when exactly
one legend target exists; otherwise pass its mark ID. It accepts layout and
appearance options from `createLegend` except semantic `channels`.
~~~javascript
program.editLegend({
target: "points",
position: "left",
offset: 80,
count: 4,
labels: { fontSize: 11 },
border: { color: "#94a3b8" }
});
~~~
Nested label, title, border, and gradient objects merge only the supplied
leaves. A string title becomes explicit, `title: "auto"` restores field-name
inference, and `title: false` hides the concrete title without discarding the
stored semantic title. Gradient and opacity legends accept only their
kind-compatible options.
## Focused edits
Focused actions avoid constructing nested `editLegend()` options when only one
legend component should change:
```javascript
program
.editLegendLayout({ position: "left", offset: 12 })
.editLegendLabels({ color: "#475569", fontSize: 11 })
.editLegendTitle({ title: "Country", fontWeight: 700 })
.editLegendSymbols({ count: 5 })
.editLegendBorder({
border: { color: "#cbd5e1", lineWidth: 1, padding: 8 }
});
```
| Action | Accepted component options |
| --- | --- |
| `editLegendLayout` | `position`, `align`, `direction`, `columns`, `offset`, `titlePosition`, `itemGap` |
| `editLegendLabels` | `color`, `fontSize`, `fontFamily`, `fontWeight` |
| `editLegendTitle` | `title`, `color`, `fontSize`, `fontFamily`, `fontWeight` |
| `editLegendSymbols` | `symbol`, `count`, `gradient` |
| `editLegendBorder` | required `border` boolean or border style object |
Every focused action also accepts `target`. Omit it only when one existing
legend is inferable. The actions use `editLegend` internally, so title modes,
partial nested merges, legend-kind compatibility, layout errors, and
rematerialization behavior remain identical. At least one component option is
required.
Legend label and title weights follow the shared
[Canvas font-weight policy](../marks/text.md#font-weights).
## Removing a legend
`removeLegend()` removes every legend block associated with one mark, including
combined categorical and size blocks. Mark encodings and scales remain.
```javascript
const withoutLegend = program.removeLegend({ target: "points" });
```
`target` may be omitted when exactly one legend owner exists. Independent
legend owners require an explicit target.
Canvas changes and relevant encoding actions explicitly rematerialize the
legend from the latest ordinal domains and ranges. The renderer still reads
only concrete `graphicSpec` values.
~~~text
createLegend
├─ createCategoricalLegend | createGradientLegend | createOpacityLegend
│ └─ concrete background?, symbols/strips, labels, and title
└─ createSizeLegend?
~~~
~~~text
editLegend
└─ rematerializeLegend | rematerializeGradientLegend | rematerializeOpacityLegend
└─ concrete background?, symbols/strips, labels, title?, and size block
~~~
The component actions shown above are internal wrapped actions. Chart and
extension authors call the public `createLegend()` facade; the children remain
visible in the trace.
`createGuides()` selects line-series, histogram color, grouped-bar color,
grouped-area color, and compatible point color/shape/size, sequential color,
or standalone field-opacity legends automatically.
Pass `createGuides({ legend: false })` to opt out.
## Related
[Legend overview](../legends.md) · [Guides](../guides.md) · [Canvas](../canvas.md)
# ChartProgram and Immutability
semanticSpecmeaning
graphicSpecdrawing
childrencomposition
contextnext action
tracehistory
`chart()` returns an empty `ChartProgram`. A program contains the authored chart
state and the history used to produce it.
```javascript
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](../api/composition.md).
## 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.
```javascript
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](./semantic-and-graphics.md) to understand the
boundary between intent and concrete output.
# Semantic and Graphical State
semanticSpecmeaning
→
domain actionmaterializes
→
graphicSpecconcrete nodes
→
rendererdraw only
`ggaction` keeps chart meaning separate from its fully resolved graphics.
```text
semanticSpec = what the chart means
graphicSpec = what the renderer draws
```
## Semantic state
`semanticSpec` stores named datasets, semantic layers and marks, data-driven
encodings, scales, coordinates, and guides. Examples include:
- `Horsepower` encoded on x;
- a linear x scale with an automatic domain;
- a point layer using the `main` Cartesian coordinate;
- an x-axis explaining scale `x`.
## Graphical state
`graphicSpec` contains backend-neutral concrete nodes such as canvas, circle,
line, and text. Every stored x/y coordinate, radius, color, line endpoint, and
label string is already resolved.
Named graphics form an explicit ownership tree. A normal Canvas-first chart has
one Canvas root, a plot container for grids, marks, and axes, then Canvas-owned
legends and titles:
```text
canvas
├─ plot
│ ├─ grid
│ ├─ marks
│ └─ axes
├─ legends
└─ title
```
Sibling order is drawing order. Containers do not imply clipping, transforms,
or layout; they only record ownership and order. Repeated concrete mark items
remain in the owning graphic's `items` collection rather than becoming named
tree children.
See [Inspecting authored and materialized state](./chart-program.md#inspecting-authored-and-materialized-state)
for the exact `semanticSpec.layers`, `graphicSpec.objects[id].items`, ownership,
and drawing-order access paths.
It never stores field expressions, scale calls, executable functions, or
instructions for a renderer to infer.
## No automatic compiler
There is no automatic semantic-to-graphic compilation step. A domain action
explicitly updates semantic state and invokes the graphical actions required to
materialize that change.
For example, `encodeX` records the field and scale, resolves every consumer,
and writes concrete x values. `render()` later reads only `graphicSpec`.
Appearance-only values such as canvas background, fixed radius, fonts, and
strokes are graphical. User-authored scale domains and ranges remain semantic;
the values produced by applying them are graphical.
# Actions and Trace Trees
createAxeshigh level
→
createXAxisdomain action
→
createGraphics / editGraphicsprimitives
Every chart-authoring method is an action. Calling a high-level action records
that action and any wrapped actions it invokes as children.
```text
createAxes
├─ createXAxis
│ ├─ createXAxisLine
│ ├─ createXAxisTicksAndLabels
│ └─ createXAxisTitle
└─ createYAxis
├─ createYAxisLine
├─ createYAxisTicksAndLabels
└─ createYAxisTitle
```
The trace is available as `program.trace`. Its root is the virtual `program`
node, and `program.trace.children` contains the top-level authored actions.
Every node contains:
```javascript
{
id,
op,
description,
args,
children
}
```
Arguments are summarized so large datasets and materialized arrays are not
duplicated in the trace. For example, dataset values are represented by a
count.
Trace state is immutable and does not affect rendering. It can be traversed as
a normal tree for inspection, explanation, provenance, or recommendation.
Developers can define new wrapped actions with the
[extension API](../extension/action-authoring.md).
# Advanced Axis Components
Use these actions when a complete `createAxes` call is not sufficient. All
component actions require a resolved linear, time, or ordinal scale and Canvas
bounds. x supports `bottom` and `top`; y supports `left` and `right`.
## Complete single-channel axes
```javascript
program.createXAxis({
scale: "x",
line: { lineWidth: 1 },
ticksAndLabels: {
count: 5,
ticks: { length: 6 },
labels: { fontSize: 12 }
},
title: { text: "Horsepower" }
});
```
`createXAxis` and `createYAxis` call the line, tick-and-label, and title actions
as trace children. `editXAxis` and `editYAxis` update several existing
components atomically; use the focused edit actions below for one component.
| Axis family | Create | Edit | Editable components |
| --- | --- | --- | --- |
| Cartesian complete axis | `createXAxis` / `createYAxis` / `createAxes` | `editXAxis` / `editYAxis` | line, ticks, labels, ticksAndLabels, title, position |
| Polar complete axis | `createThetaAxis` / `createRadialAxis` / `createAxes` | `editThetaAxis` / `editRadialAxis` | line, ticks, labels, ticksAndLabels, title, angle or position |
| Parallel dimension axes | `createAxes` | | line, ticks, labels, title from each stored dimension |
Create also accepts `coordinate`, an existing coordinate ID consumed by the
selected channel and scale. `createAxes` supplies this automatically and stores
it on the semantic guide.
## Lines
```javascript
program.createXAxisLine({
scale: "x",
position: "bottom",
color: "#334155",
lineWidth: 1
});
program.editXAxisLine({ color: "black", lineWidth: 2 });
```
Create options are `scale`, `position`, `color`, and `lineWidth`. Edit options
omit `scale`. Endpoints are inferred from the resolved scale range and Canvas
bounds; concrete endpoints are not accepted.
## Ticks
```javascript
program.createXAxisTicks({ count: 5, length: 6 });
program.createYAxisTicks({ values: [10, 20, 30, 40] });
program.editXAxisTicks({ length: 8, color: "black" });
```
| Option | Meaning |
| --- | --- |
| `scale` | Create-only scale ID; defaults to channel |
| `position` | x: `bottom/top`; y: `left/right` |
| `count` | Positive requested tick density; default `5` except inferred binned x boundaries |
| `values` | Exact finite data-space values or timestamps; mutually exclusive with `count` |
| `length` | Non-negative tick length; default `6` |
| `color` | Stroke color; default `#64748b` |
| `lineWidth` | Non-negative width; default `1` |
When both `count` and `values` are omitted for a binned histogram x scale,
ticks default to the inferred bin boundaries. Passing either option disables
that inference.
## Labels
```javascript
program.createXAxisLabels({ format: { decimals: 1 } });
program.editYAxisLabels({ offset: 16, fontSize: 13 });
```
Labels accept `scale` on creation plus `position`, `count`, `values`, `offset`,
`format`, `color`, `fontSize`, `fontFamily`, and `fontWeight`. Format is
`"auto"`, `{ decimals: nonNegativeInteger }`, or one of the closed strings
below. Default offsets are 18 for x and 12 for y; default font size is 12.
| Scale | Explicit format strings |
| --- | --- |
| Linear | `.0f`, `.1f`, `.2f`, `.0%`, `.1%`, `.2e` |
| Time | `%Y`, `%Y-%m`, `%Y-%m-%d` (UTC) |
| Ordinal | none; use `"auto"` |
Automatic time labels select year, month, day, hour, minute, or second
precision from the resolved domain span, then minimally refine that precision
when distinct tick values would collide. This handles UTC month lengths, leap
days, and sub-day intervals without changing explicit format strings.
Incompatible format/scale pairs are rejected before graphics are changed.
Labels reuse existing tick values when count/values are omitted. Conflicting
tick and label configurations produce an error.
## Ticks and labels together
```javascript
program.createXAxisTicksAndLabels({
count: 5,
ticks: { length: 6 },
labels: { offset: 18, fontSize: 12 }
});
program.editYAxisTicksAndLabels({
values: [10, 20, 30, 40],
labels: { color: "black" }
});
```
Shared options are `scale` (create only), `position`, and either `count` or
`values`. Nested `ticks` accepts `length`, `color`, and `lineWidth`; nested
`labels` accepts `offset`, `format`, `color`, `fontSize`, `fontFamily`, and
`fontWeight`.
## Titles
```javascript
program.createXAxisTitle({ text: "Horsepower" });
program.editXAxisTitle({ text: "Engine horsepower", at: "start" });
```
| Option | Meaning |
| --- | --- |
| `text` | Non-empty title; inferred from one connected field/aggregate when omitted |
| `scale` | Create-only scale ID; defaults to channel |
| `position` | x: `bottom/top`; y: `left/right` |
| `at` | `start`, `center`, `end`, or an in-domain finite number |
| `offset` | Non-negative distance from the plot |
| `rotation` | Finite radians |
| `color` | Text fill |
| `fontSize` | Positive size |
| `fontFamily` | Non-empty family |
| `fontWeight` | String or finite number |
Default title placement is centered. x uses offset 42 and rotation 0. y uses
offset 52 with rotation `-Math.PI / 2` on the left or `Math.PI / 2` on the
right. An explicit rotation remains unchanged when the position is edited.
Cartesian and Polar axis typography follows the shared
[Canvas font-weight policy](../api/marks/text.md#font-weights).
Top/right components extend outward and require sufficient top/right Canvas
margin. Component edits and Canvas/scale rematerialization preserve the chosen
edge, values, formats, and appearance.
Scale or Canvas rematerialization recomputes existing component geometry while
preserving configured appearance.
# Supported Features
This page describes implemented behavior only. A dash means that the current
chart-authoring API does not support that combination.
## Complete chart support
Every chart family below supports Browser Canvas, Node PNG, and an optional
chart title. The smaller family tables keep the comparison readable on narrow
screens.
### Cartesian charts
The complete `createScatterPlot`, `createLinePlot`, `createBarPlot`,
`createHistogram`, and pre-gridded or raw-row binned `createHeatmap` facades compose the same
mark, encoding, scale, and guide actions described below. Individual actions
remain available for custom layering and editing.
| Capability | Scatterplot | Line | Histogram | Bar | Heatmap / ranged rect |
| --- | --- | --- | --- | --- | --- |
| Semantic mark | point | line | bar | bar | rect |
| Position | quantitative x/y | temporal x, aggregate y | binned x, count y | vertical or horizontal category/aggregate pair | two discrete bands or x/x2 + y/y2 ranges |
| Nominal color | point fill | series stroke | five bar layouts | five bar layouts | cell fill |
| Stroke dash | — | nominal or constant; 4 named styles | — | — | — |
| Appearance | radius, deterministic bounded x/y jitter | stroke width, 8 curves | default bar geometry | band or logical-pixel width | encoded fill, opacity, outline |
| Automatic guides | linear axes; horizontal grid | UTC time/linear axes; horizontal grid | bin-aligned/linear axes; horizontal grid | ordinal/linear axes; horizontal grid | discrete/continuous axes and color legend |
| Legend | point color + shape | categorical | categorical | categorical | categorical or continuous color |
| Selection/highlight | point | series | final bar | final bar | observed cell |
### Statistical layers
| Capability | Regression scatterplot | Density area | Horizon area |
| --- | --- | --- | --- |
| Semantic marks | point + area + line | area | area |
| Position | shared quantitative x/y | value + density x/y | quantitative/temporal x; folded `[0, 1]` y/y2 |
| Nominal color | point fill + fit stroke | overlay/stack/fill/diverging area | positive/negative band palettes |
| Appearance | point opacity, band fill/outline, line width, 8 curves | opacity, 8 curves | band count, baseline, 8 curves |
| Automatic guides | shared linear axes and horizontal grid | source-value/density axes; horizontal grid, vertical optional | source x axis/grid only; no folded y axis or legend |
| Legend | composite color/shape/line + size | categorical top/right/bottom | intentionally omitted |
| Selection/highlight | layer selection + point highlight | series | folded band items |
### Intervals and distributions
| Capability | Error bar | Error band | Box plot | Gradient plot | Violin plot |
| --- | --- | --- | --- | --- | --- |
| Semantic marks | rule | area | bar + rule + point | rect + rule | area |
| Position | categorical/temporal independent axis; interval on the other | quantitative/temporal independent axis; x/x2 or y/y2 interval | categorical axis; quantitative interval axis | categorical axis; sampled quantitative profile | categorical center; quantitative density profile |
| Nominal color | — | grouped area fill | body fill through ranged-bar color | category hue with density modulation | category or two-value split fill |
| Appearance | stroke, width, dash, opacity, optional caps | fill, opacity, 8 curves, styled boundaries | fixed defaults; 1.5px median/whiskers | structured gradient fill, width, optional center rule | fill, opacity, outline, 8 curves, shared/independent width |
| Automatic guides | interval and independent axes; perpendicular grid | interval and independent axes; perpendicular grid | opt-in categorical/linear axes and horizontal grid | categorical/linear axes, grid, density legend | categorical/linear axes; horizontal grid |
| Legend | — | categorical | optional ranged-bar color legend | neutral density; categorical color when requested | optional category or split legend |
| Selection/highlight | rule | series | component | category strip | full or split profile |
### Polar charts
| Capability | Polar points | Polar line / radar | Arc / donut / rose / radial bar |
| --- | --- | --- | --- |
| Semantic mark | point | line | arc |
| Position | theta/radius | theta/radius | count theta, or theta/radius |
| Nominal color | point fill | series stroke | sector fill, including overlay grain |
| Stroke dash | — | nominal or constant; 4 named styles | — |
| Appearance | radius, opacity, shape | stroke width, opacity, open/closed | inner radius, angular padding, fill, outline, opacity |
| Automatic guides | theta outer axis, radial axis, spokes, circles | theta outer axis, radial axis, spokes, circles | applicable theta/radius axes, spokes, circles |
| Legend | point color + shape | categorical | categorical |
| Selection/highlight | point | series | sector |
### Parallel coordinates
| Capability | Supported now |
| --- | --- |
| Semantic mark | one open line-path item per eligible source row |
| Position | two or more ordered quantitative or ordinal dimensions, each with a local scale and axis |
| Missing values | `break` fragments, whole-row `drop-row`, or strict `error` |
| Appearance | categorical color, categorical or constant stroke dash, line stroke/width/opacity |
| Automatic guides | one ordinary line/text axis per dimension and applicable categorical legend; no automatic grid |
| Selection/highlight | source-row item, including immutable `filterMarks` rematerialization |
## Shared foundations
| Area | Supported now |
| --- | --- |
| Program model | Immutable unit or composition `ChartProgram`, hierarchical trace, nested Cartesian/Polar horizontal or vertical composition, stable child replacement, and Cartesian facet repetition |
| Canvas | Create/edit width, height, background, margin |
| Data | Immutable arrays of plain row objects, named filters, stable window operations, rectangular 2D bins, grouped interval summaries, grouped linear/polynomial/LOESS regression, grouped kernel-density derivations, and immutable Horizon band revisions |
| Coordinates | Named Cartesian, Polar, and Parallel resources; x/y use Cartesian, theta/radius use Polar, and ordered dimensions use Parallel |
| Scales | Linear/log/pow/sqrt/symlog position across compatible marks, UTC time, band/point position, ordinal/sequential/quantize/quantile/threshold color, point-item unknown fallbacks, named/direct stroke dash, and padded band-local xOffset/yOffset |
| Aggregates | count, sum, mean, median, min/max, distinct/valid/missing, sample/population dispersion, quartiles, standard error, normal 95% mean endpoints, parameterized quantile, and ordered first/last |
| Guides | Automatic Cartesian x/y and Polar theta/radius axes, closed numeric/UTC label formats, independently editable Cartesian and Polar grids, editable four-edge continuous/left-right categorical legends, and right-side interval legends |
| Titles | One four-edge title with an optional subtitle, deterministic word/character wrapping, and partial editing |
| Rendering | Browser Canvas and Node PNG |
| Graphics | Concrete canvas, circle, line, rect, text, `M/L/C/Z` command paths, shared 8-value line/area curves, and heterogeneous drawable collections |
| Selection | Strict point/bar/rect/series/arc/rule comparison, set, range and grouped rank; reusable selection state; mark-specific highlight/dimming/front order |
Basic chart facades infer only a current or unique dataset and stable unused
role IDs. Ambiguous data or an occupied default role requires an explicit ID.
Complete chart facades create applicable guides by default and accept
`guides: false`. `createBoxPlot` preserves its historical opt-in behavior:
pass `guides: {}` or nested options to create applicable guides.
## Current limitations
Transforms beyond the documented filters, regressions, density and interval
derivations, as well as interactive legends, are not implemented.
Pre-gridded heatmaps do not synthesize missing cells. Binned heatmaps support
fixed rectangular bins only; weighted, adaptive, hexagonal, and overflow bins
are not implemented. Cell text must be added as a separate text layer.
Categorical legends support all four edges; point composite and size legends
support right and left side layouts.
Error bars support vertical and horizontal statistical intervals, existing
center/lower/upper fields, optional caps, and constant rule appearance.
Error bands support vertical and horizontal statistical or explicit ranges and
optional lower/upper boundary lines with shared stroke, width, dash, opacity,
and inherited or overridden curve. Independent lower/upper style objects are
not implemented.
Box plots support vertical or horizontal category/measure pairings, default
or configurable Tukey summaries, min–max whiskers, band width and component
appearance overrides, and explicit outlier opt-out without placeholder resources.
Gradient plots support the same category/measure orientation family, immutable
sampled profile revisions, configurable density/width/paint/center options,
source-first filtering, category-strip highlighting, and Cartesian facet
replay. Shared facet density legends and partial composite `filterMarks` are
not implemented.
Violin plots support the same category/measure orientation family, symmetric
full profiles, one-sided placement, two-value split halves, shared or
category-local density width, density revision, source filtering, profile
selection/highlighting, Cartesian facet replay, and compatible overlay scales.
Raincloud components, more than two split values, adaptive bandwidth, and
Polar violin placement are not implemented.
Mark selection supports point, final-bar item, stacked-bar group, line/area
series, arc sector, and rule grain. Selector values explicitly distinguish data fields,
pre-scale semantic channels, and concrete graphic properties.
Highlight appearance supports point fill/shape/size/outline/offset, bar fill and
outline, area/arc fill/outline/offset, and line/rule stroke/width/dash/offset.
Cartesian point jitter supports deterministic pixel offsets and categorical
band-relative offsets. It preserves semantic channel values, remains bounded
by glyph extent and plot/category slots, and does not perform collision-free
packing. Polar point jitter is not implemented.
Text marks support deterministic collision-aware displacement along x, y, or
both axes within plot or Canvas bounds. The policy can create ordinary leader
lines, replays after text/source/data/scale/Canvas edits, and records structured
warnings when bounded placement cannot eliminate every overlap. It does not
expand margins, shrink text, arrange guide labels, or search unrelated marks
for anchors.
Polar charts may be direct or nested concat children. Faceting a Polar source is
not implemented and fails before partial child state is created.
Parallel coordinates support quantitative/ordinal dimensions, open linear
paths, dimension-local axes, color/stroke-dash legends, selection and filtering.
Temporal dimensions, curved paths, axis drag reordering, brushing, bundling,
faceting, and shared Parallel coordinates across child programs are not implemented.
# Action Reference
Every direct action accepts one option object and returns a new immutable `ChartProgram`. Choose a task family for readable behavior, defaults, inference, and errors; use the exact lookup when you already know the action name. The API-layer labels match the action catalog layers `user-facing`, `advanced`, and `primitive`, respectively.
Charts, Data, and Composition Actions Create complete charts, manage data, select marks, and compose complete programs.
Mark Actions Create, edit, jitter, and remove semantic chart marks.
Encoding Actions Map fields and constants to position, grouping, color, shape, size, and appearance.
Statistical Layer Actions Create and edit regression, density, interval, error, and box-plot layers.
Guide, Axis, Grid, and Title Actions Create, edit, and remove axes, grids, legends, and chart titles.
Advanced chart actions Explicit resources and focused axis or grid control.
Extension actions Wrapped actions and public authoring primitives.
Program and rendering functions Package functions, renderers, and internal trace boundaries.
Exact TypeScript contract The complete generated `ChartProgram` action interface.
## Exact action lookup
Use document search with `Ctrl+K`, or filter the alphabetical list by action name, API layer, or domain. Each action has one canonical family entry.
| Action | API layer | Domain |
| --- | --- | --- |
| [`createArcMark`](./actions/marks.md#createarcmark) | user-facing | marks |
| [`createAreaMark`](./actions/marks.md#createareamark) | user-facing | marks |
| [`createAxes`](./actions/guides.md#createaxes) | user-facing | axes |
| [`createBarMark`](./actions/marks.md#createbarmark) | user-facing | marks |
| [`createBarPlot`](./actions/charts-data.md#createbarplot) | user-facing | charts |
| [`createBin2DData`](./actions/charts-data.md#createbin2ddata) | user-facing | core |
| [`createBoxPlot`](./actions/statistics.md#createboxplot) | user-facing | statistics |
| [`createCanvas`](./actions/charts-data.md#createcanvas) | user-facing | core |
| [`createCoordinate`](./actions/advanced.md#semantic-resources-and-regression-layers) | user-facing | core |
| [`createData`](./actions/charts-data.md#createdata) | user-facing | core |
| [`createDensityData`](./actions/charts-data.md#createdensitydata) | user-facing | core |
| [`createDerivedData`](./actions/advanced.md#semantic-resources-and-regression-layers) | user-facing | core |
| [`createErrorBand`](./actions/statistics.md#createerrorband) | user-facing | statistics |
| [`createErrorBar`](./actions/statistics.md#createerrorbar) | user-facing | statistics |
| [`createGradientPlot`](./actions/statistics.md#creategradientplot) | user-facing | statistics |
| [`createGraphics`](./actions/extension.md#extension-actions) | primitive | primitives |
| [`createGrid`](./actions/guides.md#creategrid) | user-facing | grid |
| [`createGuides`](./actions/guides.md#createguides) | user-facing | legend_and_title |
| [`createHeatmap`](./actions/charts-data.md#createheatmap) | user-facing | charts |
| [`createHistogram`](./actions/charts-data.md#createhistogram) | user-facing | charts |
| [`createHorizontalGrid`](./actions/advanced.md#directional-grids) | user-facing | grid |
| [`createIntervalData`](./actions/statistics.md#createintervaldata) | user-facing | statistics |
| [`createLegend`](./actions/guides.md#createlegend) | user-facing | legend_and_title |
| [`createLineMark`](./actions/marks.md#createlinemark) | user-facing | marks |
| [`createLinePlot`](./actions/charts-data.md#createlineplot) | user-facing | charts |
| [`createParallelCoordinates`](./actions/charts-data.md#createparallelcoordinates) | user-facing | charts |
| [`createPointMark`](./actions/marks.md#createpointmark) | user-facing | marks |
| [`createRadialAxis`](./actions/guides.md#createradialaxis) | user-facing | axes |
| [`createRadialGrid`](./actions/guides.md#createradialgrid) | user-facing | grid |
| [`createRectMark`](./actions/marks.md#createrectmark) | user-facing | marks |
| [`createRegression`](./actions/statistics.md#createregression) | user-facing | statistics |
| [`createRegressionBand`](./actions/advanced.md#semantic-resources-and-regression-layers) | user-facing | statistics |
| [`createRegressionData`](./actions/charts-data.md#createregressiondata) | user-facing | core |
| [`createRegressionLine`](./actions/advanced.md#semantic-resources-and-regression-layers) | user-facing | statistics |
| [`createRuleMark`](./actions/marks.md#createrulemark) | user-facing | marks |
| [`createScale`](./actions/extension.md#extension-actions) | user-facing | core |
| [`createScatterPlot`](./actions/charts-data.md#createscatterplot) | user-facing | charts |
| [`createTextMark`](./actions/marks.md#createtextmark) | user-facing | marks |
| [`createThetaAxis`](./actions/guides.md#createthetaaxis) | user-facing | axes |
| [`createThetaGrid`](./actions/guides.md#createthetagrid) | user-facing | grid |
| [`createTitle`](./actions/guides.md#createtitle) | user-facing | legend_and_title |
| [`createVerticalGrid`](./actions/advanced.md#directional-grids) | user-facing | grid |
| [`createViolinPlot`](./actions/statistics.md#createviolinplot) | user-facing | statistics |
| [`createWindowData`](./actions/charts-data.md#createwindowdata) | user-facing | core |
| [`createXAxis`](./actions/advanced.md#complete-single-channel-axes) | user-facing | axes |
| [`createXAxisLabels`](./actions/advanced.md#axis-lines-ticks-and-labels) | user-facing | axes |
| [`createXAxisLine`](./actions/advanced.md#axis-lines-ticks-and-labels) | user-facing | axes |
| [`createXAxisTicks`](./actions/advanced.md#axis-lines-ticks-and-labels) | user-facing | axes |
| [`createXAxisTicksAndLabels`](./actions/advanced.md#ticklabel-groups-and-axis-titles) | user-facing | axes |
| [`createXAxisTitle`](./actions/advanced.md#ticklabel-groups-and-axis-titles) | user-facing | axes |
| [`createYAxis`](./actions/advanced.md#complete-single-channel-axes) | user-facing | axes |
| [`createYAxisLabels`](./actions/advanced.md#axis-lines-ticks-and-labels) | user-facing | axes |
| [`createYAxisLine`](./actions/advanced.md#axis-lines-ticks-and-labels) | user-facing | axes |
| [`createYAxisTicks`](./actions/advanced.md#axis-lines-ticks-and-labels) | user-facing | axes |
| [`createYAxisTicksAndLabels`](./actions/advanced.md#ticklabel-groups-and-axis-titles) | user-facing | axes |
| [`createYAxisTitle`](./actions/advanced.md#ticklabel-groups-and-axis-titles) | user-facing | axes |
| [`editArcMark`](./actions/marks.md#editarcmark) | user-facing | marks |
| [`editAreaMark`](./actions/marks.md#editareamark) | user-facing | marks |
| [`editBarMark`](./actions/marks.md#editbarmark) | user-facing | marks |
| [`editBoxPlot`](./actions/statistics.md#editboxplot) | user-facing | statistics |
| [`editCanvas`](./actions/charts-data.md#editcanvas) | user-facing | core |
| [`editCompositionLayout`](./actions/charts-data.md#editcompositionlayout) | user-facing | composition |
| [`editDensity`](./actions/encodings.md#editdensity) | user-facing | encodings |
| [`editErrorBand`](./actions/statistics.md#editerrorband-and-editerrorbandboundary) | user-facing | statistics |
| [`editErrorBandBoundary`](./actions/statistics.md#editerrorband-and-editerrorbandboundary) | user-facing | statistics |
| [`editErrorBar`](./actions/statistics.md#editerrorbar) | user-facing | statistics |
| [`editFacetHeaders`](./actions/charts-data.md#editfacetheaders) | user-facing | composition |
| [`editGradientPlot`](./actions/statistics.md#editgradientplot) | user-facing | statistics |
| [`editGraphics`](./actions/extension.md#extension-actions) | primitive | primitives |
| [`editGrid`](./actions/advanced.md#directional-grids) | user-facing | grid |
| [`editHorizon`](./actions/encodings.md#edithorizon) | user-facing | encodings |
| [`editHorizontalGrid`](./actions/advanced.md#directional-grids) | user-facing | grid |
| [`editLegend`](./actions/guides.md#editlegend) | user-facing | legend_and_title |
| [`editLegendBorder`](./actions/guides.md#focused-legend-edits) | user-facing | legend_and_title |
| [`editLegendLabels`](./actions/guides.md#focused-legend-edits) | user-facing | legend_and_title |
| [`editLegendLayout`](./actions/guides.md#focused-legend-edits) | user-facing | legend_and_title |
| [`editLegendSymbols`](./actions/guides.md#focused-legend-edits) | user-facing | legend_and_title |
| [`editLegendTitle`](./actions/guides.md#focused-legend-edits) | user-facing | legend_and_title |
| [`editLineMark`](./actions/marks.md#editlinemark) | user-facing | marks |
| [`editPointMark`](./actions/marks.md#editpointmark) | user-facing | marks |
| [`editRadialAxis`](./actions/guides.md#editradialaxis) | user-facing | axes |
| [`editRadialAxisLabels`](./actions/guides.md#editradialaxislabels) | user-facing | axes |
| [`editRadialAxisLine`](./actions/guides.md#editradialaxisline) | user-facing | axes |
| [`editRadialAxisTicks`](./actions/guides.md#editradialaxisticks) | user-facing | axes |
| [`editRadialAxisTitle`](./actions/guides.md#editradialaxistitle) | user-facing | axes |
| [`editRadialGrid`](./actions/guides.md#editradialgrid) | user-facing | grid |
| [`editRectMark`](./actions/marks.md#editrectmark) | user-facing | marks |
| [`editRegression`](./actions/statistics.md#editregression) | user-facing | statistics |
| [`editRegressionBand`](./actions/advanced.md#semantic-resources-and-regression-layers) | user-facing | statistics |
| [`editRegressionLine`](./actions/advanced.md#semantic-resources-and-regression-layers) | user-facing | statistics |
| [`editScale`](./actions/extension.md#extension-actions) | user-facing | core |
| [`editSemantic`](./actions/extension.md#extension-actions) | primitive | primitives |
| [`editTextMark`](./actions/marks.md#edittextmark) | user-facing | marks |
| [`editThetaAxis`](./actions/guides.md#editthetaaxis) | user-facing | axes |
| [`editThetaAxisLabels`](./actions/guides.md#editthetaaxislabels) | user-facing | axes |
| [`editThetaAxisLine`](./actions/guides.md#editthetaaxisline) | user-facing | axes |
| [`editThetaAxisTicks`](./actions/guides.md#editthetaaxisticks) | user-facing | axes |
| [`editThetaAxisTitle`](./actions/guides.md#editthetaaxistitle) | user-facing | axes |
| [`editThetaGrid`](./actions/guides.md#editthetagrid) | user-facing | grid |
| [`editTitle`](./actions/guides.md#edittitle) | user-facing | legend_and_title |
| [`editVerticalGrid`](./actions/advanced.md#directional-grids) | user-facing | grid |
| [`editXAxis`](./actions/advanced.md#complete-single-channel-axes) | user-facing | axes |
| [`editXAxisLabels`](./actions/advanced.md#axis-lines-ticks-and-labels) | user-facing | axes |
| [`editXAxisLine`](./actions/advanced.md#axis-lines-ticks-and-labels) | user-facing | axes |
| [`editXAxisTicks`](./actions/advanced.md#axis-lines-ticks-and-labels) | user-facing | axes |
| [`editXAxisTicksAndLabels`](./actions/advanced.md#ticklabel-groups-and-axis-titles) | user-facing | axes |
| [`editXAxisTitle`](./actions/advanced.md#ticklabel-groups-and-axis-titles) | user-facing | axes |
| [`editYAxis`](./actions/advanced.md#complete-single-channel-axes) | user-facing | axes |
| [`editYAxisLabels`](./actions/advanced.md#axis-lines-ticks-and-labels) | user-facing | axes |
| [`editYAxisLine`](./actions/advanced.md#axis-lines-ticks-and-labels) | user-facing | axes |
| [`editYAxisTicks`](./actions/advanced.md#axis-lines-ticks-and-labels) | user-facing | axes |
| [`editYAxisTicksAndLabels`](./actions/advanced.md#ticklabel-groups-and-axis-titles) | user-facing | axes |
| [`editYAxisTitle`](./actions/advanced.md#ticklabel-groups-and-axis-titles) | user-facing | axes |
| [`encodeBarWidth`](./actions/encodings.md#encodebarwidth) | user-facing | encodings |
| [`encodeColor`](./actions/encodings.md#encodecolor) | user-facing | encodings |
| [`encodeDensity`](./actions/encodings.md#encodedensity) | user-facing | encodings |
| [`encodeGroup`](./actions/encodings.md#encodegroup) | user-facing | encodings |
| [`encodeHistogram`](./actions/encodings.md#encodehistogram) | user-facing | encodings |
| [`encodeHorizon`](./actions/encodings.md#encodehorizon) | user-facing | encodings |
| [`encodeOpacity`](./actions/encodings.md#encodeopacity) | user-facing | encodings |
| [`encodeParallelCoordinates`](./actions/encodings.md#encodeparallelcoordinates) | user-facing | encodings |
| [`encodePathOrder`](./actions/encodings.md#encodepathorder) | user-facing | encodings |
| [`encodePointRadius`](./actions/encodings.md#encodepointradius) | user-facing | encodings |
| [`encodeR`](./actions/encodings.md#encoder) | user-facing | encodings |
| [`encodeRadius`](./actions/encodings.md#encoderadius) | user-facing | encodings |
| [`encodeShape`](./actions/encodings.md#encodeshape) | user-facing | encodings |
| [`encodeSize`](./actions/encodings.md#encodesize) | user-facing | encodings |
| [`encodeStroke`](./actions/encodings.md#encodestroke) | user-facing | encodings |
| [`encodeStrokeDash`](./actions/encodings.md#encodestrokedash) | user-facing | encodings |
| [`encodeStrokeWidth`](./actions/encodings.md#encodestrokewidth) | user-facing | encodings |
| [`encodeText`](./actions/encodings.md#encodetext) | user-facing | encodings |
| [`encodeTheta`](./actions/encodings.md#encodetheta) | user-facing | encodings |
| [`encodeX`](./actions/encodings.md#encodex) | user-facing | encodings |
| [`encodeX2`](./actions/encodings.md#encodex2) | user-facing | encodings |
| [`encodeXOffset`](./actions/encodings.md#encodexoffset) | user-facing | encodings |
| [`encodeXRange`](./actions/encodings.md#encodexrange) | user-facing | encodings |
| [`encodeY`](./actions/encodings.md#encodey) | user-facing | encodings |
| [`encodeY2`](./actions/encodings.md#encodey2) | user-facing | encodings |
| [`encodeYOffset`](./actions/encodings.md#encodeyoffset) | user-facing | encodings |
| [`encodeYRange`](./actions/encodings.md#encodeyrange) | user-facing | encodings |
| [`facet`](./actions/charts-data.md#facet) | user-facing | composition |
| [`filterData`](./actions/charts-data.md#filterdata) | user-facing | core |
| [`filterMarks`](./actions/charts-data.md#filtermarks) | user-facing | mark-selection |
| [`highlightMarks`](./actions/charts-data.md#highlightmarks) | user-facing | mark-selection |
| [`jitterPoints`](./actions/marks.md#jitterpoints) | user-facing | marks |
| [`layoutLabels`](./actions/marks.md#layoutlabels) | user-facing | marks |
| [`removeGrid`](./actions/advanced.md#directional-grids) | user-facing | grid |
| [`removeJitter`](./actions/marks.md#removejitter) | user-facing | marks |
| [`removeLabelLayout`](./actions/marks.md#removelabellayout) | user-facing | marks |
| [`removeLegend`](./actions/guides.md#removelegend) | user-facing | legend_and_title |
| [`removeMark`](./actions/marks.md#removemark) | user-facing | marks |
| [`removePathOrder`](./actions/encodings.md#removepathorder) | user-facing | encodings |
| [`removeRadialAxis`](./actions/guides.md#removeradialaxis) | user-facing | axes |
| [`removeThetaAxis`](./actions/guides.md#removethetaaxis) | user-facing | axes |
| [`removeTitle`](./actions/guides.md#removetitle) | user-facing | legend_and_title |
| [`removeXAxis`](./actions/advanced.md#complete-axis-removal) | user-facing | axes |
| [`removeYAxis`](./actions/advanced.md#complete-axis-removal) | user-facing | axes |
| [`replaceCompositionChild`](./actions/charts-data.md#replacecompositionchild) | user-facing | composition |
| [`selectMarks`](./actions/advanced.md#reusable-mark-selections) | advanced | mark-selection |
# Charts, Data, and Composition Actions
These are direct immutable `ChartProgram` actions. Each accepts one option object and returns a new program.
## `createCanvas`
```javascript
createCanvas({ width?, height?, background?, margin? })
```
Create the program's Canvas and plot bounds. [Canvas options](../../api/canvas.md)
## `editCanvas`
```javascript
editCanvas({ width?, height?, background?, margin? })
```
Edit Canvas properties and rematerialize connected consumers.
[Canvas options](../../api/canvas.md)
## `editCompositionLayout`
```javascript
editCompositionLayout({ gap?, align?, padding? })
```
Edit spacing, cross-axis alignment, or outer padding on an existing composition.
Omitted values are preserved, child identity is unchanged, and the parent snapshot
is rebuilt from retained child programs.
## `replaceCompositionChild`
```javascript
replaceCompositionChild({ target, program })
```
Replace one named child while preserving its slot ID and order. The replacement
must already be a complete chart or composition program.
## `facet`
```javascript
facet({ id?, field, data?, columns?, gap?, align?, padding?, scales?, guides? })
```
Repeat one complete chart by a field on its common row-preserving dataset
ancestor. Values preserve source first appearance; scale policies can be
`"shared"` or `"independent"` by supported channel, and layered regression
data and other supported statistical descendants are recomputed per cell.
`guides: { axes: "outer" }` keeps axes only on occupied outer cells, while
`guides: { legend: "shared" }` promotes one compatible parent-owned legend.
See [Program composition](../../api/composition.md#repeat-the-current-chart-by-a-field).
## `editFacetHeaders`
```javascript
editFacetHeaders({ fontSize?, fontFamily?, fontWeight?, color?, offset? })
```
Edit the parent-owned repeated facet headers and rebuild the parent snapshot
without changing child programs or facet value order.
## `createData`
```javascript
createData({ id?, values })
```
Create one immutable named dataset. [Data](../../api/data.md)
## `createScatterPlot`
```javascript
createScatterPlot({ id?, data?, coordinate?, x, y, color?, size?, shape?, point?, guides? })
```
Create a complete Cartesian point chart from required x/y fields and optional
appearance encodings. [Basic Charts](../../api/basic-charts.md#createscatterplot)
## `createLinePlot`
```javascript
createLinePlot({ id?, data?, coordinate?, x, y, color?, groupBy?, strokeDash?, line?, guides? })
```
Create a complete Cartesian line chart, including optional series grouping and
appearance. [Basic Charts](../../api/basic-charts.md#createlineplot)
## `createBarPlot`
```javascript
createBarPlot({ id?, data?, coordinate?, x, y, color?, width?, bar?, guides? })
```
Create a complete vertical, horizontal, aggregate, ranged, grouped, or stacked
bar chart through the existing bar policies.
[Basic Charts](../../api/basic-charts.md#createbarplot)
## `createHistogram`
```javascript
createHistogram({ id?, data?, coordinate?, field, maxBins?, binStep?, binBoundaries?, stack?, xScale?, yScale?, color?, bar?, guides? })
```
Create a bar layer with atomic bin and count encodings. Exactly one bin mode may
be specified. [Basic Charts](../../api/basic-charts.md#createhistogram)
## `createHeatmap`
```javascript
createHeatmap({ id?, data?, coordinate?, x, y, bin?, color?, rect?, guides? })
```
Create one rect cell per valid pre-gridded row, or bin raw quantitative x/y rows
into ranged cells colored by count. [Basic Charts](../../api/basic-charts.md#createheatmap)
## `createParallelCoordinates`
```javascript
createParallelCoordinates({ id?, data?, coordinate?, dimensions, key?, missing?, color?, strokeDash?, line?, guides? })
```
Create one open line path per source row across an ordered list of dimension-
local scales and axes. Only `dimensions` is required.
[Parallel Coordinates](../../api/parallel-coordinates.md)
## `filterData`
```javascript
filterData({ id, source?, field, oneOf | predicate | range })
```
Create an immutable named derived dataset using exactly one membership,
comparison, or range filter. The source defaults to current data.
[Data](../../api/data.md)
## `filterMarks`
```javascript
filterMarks({ target?, grain?, field | channel | property, op, ...operands })
```
Retain matching final mark items through the shared selector grammar, create one
namespaced immutable member-row dataset, rebind the mark, and rematerialize its
scales and connected guides without changing the source.
[Data](../../api/data.md)
## `highlightMarks`
```javascript
highlightMarks({
id?, target?, select?, selection?, color?, opacity?, fill?, stroke?,
strokeWidth?, strokeDash?, shape?, size?, offset?, dimOthers?, bringToFront?
})
```
Select point, bar, line, area, arc, or rule items inline or reuse a stored selection,
then apply mark-specific concrete emphasis, optional complement dimming, and
selected-last order.
[Mark selection and highlighting](../../api/appearance/selection-and-highlighting.md#mark-selection-and-highlighting)
## `createRegressionData`
```javascript
createRegressionData({
id, source?, x, y, groupBy?, method?, degree?, span?, confidence?, interval?
})
```
Create immutable linear, polynomial, or LOESS fitted rows at observed unique x
values. Linear and polynomial fits support Student-t mean or prediction bounds;
LOESS is line-only.
[Data](../../api/data.md)
## `createDensityData`
```javascript
createDensityData({
id, source?, field, groupBy?, bandwidth?, extent?, steps?,
kernel?, normalization?, as?
})
```
Create immutable KDE rows on one shared inclusive sample grid. Source defaults
to current data, steps to `100`, bandwidth to an automatic Scott-rule estimate,
kernel to `"gaussian"`, and normalization to `"unit"`.
[Data](../../api/data.md)
## `createWindowData`
```javascript
createWindowData({ id, source?, partitionBy?, sortBy?, operations })
```
Create an immutable derived dataset by applying ordered row-number, rank,
dense-rank, cumulative-sum, lag, or lead operations within optional partitions.
The calculation follows a stable sort while the output preserves source row order.
[Window data transforms](../../api/data/window.md)
## `createBin2DData`
```javascript
createBin2DData({
id, source?, x, y, bins?, extent?, includeEmpty?, members?, as?
})
```
Aggregate finite x/y pairs into deterministic rectangular cell bounds and
counts. Reusing the logical ID creates an immutable revision and rematerializes
direct visual consumers. [Rectangular 2D bins](../../api/data/bin2d.md)
## Related
[Action Reference](../actions.md) · [Chart API](../../api/index.md) · [Supported Features](../../supported-features.md)
# Mark Actions
These are direct immutable `ChartProgram` actions. Each accepts one option object and returns a new program.
## `createPointMark`
```javascript
createPointMark({ id?, data?, shape?, fill?, opacity?, stroke?, strokeWidth? } = {})
```
Create a semantic point mark with one of 12 equal-area shape realizations. [Marks](../../api/marks.md)
## `editPointMark`
```javascript
editPointMark({ target?, shape?, fill?, opacity?, stroke?, strokeWidth? })
```
Change constant point shape, fill, opacity, or outline appearance and rematerialize its concrete items. [Marks](../../api/marks.md)
## `jitterPoints`
```javascript
jitterPoints({ target?, channel, maxOffset, seed?, key? })
```
Assign deterministic bounded graphical jitter to one Cartesian point mark. Use
exactly one of `maxOffset.pixels` or `maxOffset.band`; calling the action again
replaces the previous policy from the semantic base positions. [Point marks](../../api/marks/point.md)
## `removeJitter`
```javascript
removeJitter({ target? } = {})
```
Remove the target point mark's jitter assignment and restore positions derived
directly from its semantic encodings. [Point marks](../../api/marks/point.md)
## `removeMark`
```javascript
removeMark({ target? })
```
Remove one stable mark owner and its owned state while preserving source data
and independently shared resources. [Marks](../../api/marks.md)
## `createLineMark`
```javascript
createLineMark({ id?, data?, stroke?, strokeWidth?, opacity?, curve?, closed? } = {})
```
Create a semantic line mark and empty path collection. Curve defaults to
`"linear"`; explicit curve and `strokeWidth` values are retained during
rematerialization. A compatible layered source can provide data, positions,
shared scales, and a grain-preserving aggregate such as `mean`; bar-only bin,
stack, and offset policies are not inherited. `closed: true` closes each Polar
series as a radar path.
[Marks](../../api/marks.md)
## `editLineMark`
```javascript
editLineMark({ target?, stroke?, strokeWidth?, opacity?, curve?, closed? })
```
Edit line appearance and rematerialize concrete path commands without changing
semantic encodings. [Marks](../../api/marks.md)
## `createBarMark`
```javascript
createBarMark({ id?, data?, fill?, opacity?, stroke?, strokeWidth? } = {})
```
Create a semantic bar mark and empty rect collection. [Marks](../../api/marks.md)
## `editBarMark`
```javascript
editBarMark({ target?, fill?, opacity?, stroke?, strokeWidth? })
```
Edit whole-bar appearance and rematerialize every concrete rectangle.
`stroke: false` removes the visible outline; constant fill conflicts with a
field-driven color encoding. [Marks](../../api/marks.md)
## `createAreaMark`
```javascript
createAreaMark({ id?, data?, fill?, opacity?, stroke?, strokeWidth?, curve? } = {})
```
Create a semantic area mark and empty path collection. Fixed fill defaults to
`"#4c78a8"`; opacity defaults to `0.2`. Optional outlines default to width `1`.
Curve defaults to `"linear"` and accepts the shared eight-value vocabulary.
[Marks](../../api/marks.md)
## `editAreaMark`
```javascript
editAreaMark({ target?, fill?, opacity?, stroke?, strokeWidth?, curve? })
```
Edit constant area appearance. `stroke: false` removes an existing outline.
[Marks](../../api/marks.md)
## `createArcMark`
```javascript
createArcMark({ id?, data?, innerRadius?, padAngle?, fill?, opacity?, stroke?, strokeWidth? } = {})
```
Create a semantic arc mark and empty closed-path collection. Count theta
materializes proportional pie or donut sectors; categorical theta plus radius
materializes radial sectors. [Marks](../../api/marks/line-area.md#arc-marks)
## `editArcMark`
```javascript
editArcMark({ target?, innerRadius?, padAngle?, fill?, opacity?, stroke?, strokeWidth? })
```
Edit arc geometry or appearance and rematerialize complete sector paths.
[Marks](../../api/marks/line-area.md#arc-marks)
## `createRuleMark`
```javascript
createRuleMark({ id?, data? } = {})
```
Create a semantic rule mark and empty line collection. The first omitted ID is
`"rule"`; data defaults to current data. [Marks](../../api/marks.md)
## `createRectMark`
```javascript
createRectMark({ id?, data?, fill?, opacity?, stroke?, strokeWidth? } = {})
```
Create a semantic rect mark and empty rect collection. Two discrete x/y bands
or complete x/x2 and y/y2 endpoint pairs materialize observed cells. Rects do
not infer bar aggregation, baseline, stack, or width semantics.
[Rect marks](../../api/marks/rect.md)
## `editRectMark`
```javascript
editRectMark({ target?, fill?, opacity?, stroke?, strokeWidth? })
```
Edit rect appearance and rematerialize complete cells. Constant fill conflicts
with field-driven color. `stroke: false` disables the outline.
[Rect marks](../../api/marks/rect.md)
## `createTextMark`
```javascript
createTextMark({ id?, data?, text?, fill?, opacity?, fontSize?, fontFamily?, fontWeight?, align?, baseline?, rotation?, dx?, dy? } = {})
```
Create a semantic text layer. Omitted data and position attach to the current
or unique compatible point, bar, rect, or rule layer. `text` is constant-content
shorthand. [Text marks](../../api/marks/text.md)
## `editTextMark`
```javascript
editTextMark({ target?, fill?, opacity?, fontSize?, fontFamily?, fontWeight?, align?, baseline?, rotation?, dx?, dy? })
```
Edit text typography and graphical offsets without changing its semantic
source or position. [Text marks](../../api/marks/text.md)
## `layoutLabels`
```javascript
layoutLabels({ target?, axis?, padding?, maxDisplacement?, bounds?, leader? } = {})
```
Assign deterministic collision-aware placement to one complete text mark.
Displacement may use x, y, or both axes and remains inside plot or Canvas
bounds when possible. Optional leaders connect displaced labels to their
stored source anchors. Impossible layouts retain a stable best effort and a
warning summary. [Text marks](../../api/marks/text.md)
## `removeLabelLayout`
```javascript
removeLabelLayout({ target? } = {})
```
Remove one text mark's layout policy and leader collection, then restore its
semantic base positions. [Text marks](../../api/marks/text.md)
### Position capability matrix
| Action | Supported marks | Field types | Important modes |
| --- | --- | --- | --- |
| `encodeX` | point, line, area, bar, rect, rule, text | point/bar/rect/rule/text: quantitative, temporal, ordinal, nominal; line/area: quantitative, temporal | field; rule also accepts datum; bar accepts aggregate or bin |
| `encodeY` | point, line, area, bar, rect, rule, text | point/line/bar/rect/rule/text: quantitative, temporal, ordinal, nominal; area: quantitative, temporal | field; rule also accepts datum; bar accepts aggregate or count |
| `encodeX2` / `encodeY2` | area, ranged bar, rect, rule | area/ranged bar/rect/rule: matching primary | secondary field; rule also accepts datum |
| `encodeTheta` | point, line, arc | point/line: quantitative, temporal, ordinal, nominal; arc: ordinal, nominal | arc accepts aggregate: count or weighted sum for proportional sectors |
| `encodeR` | point, line, arc | point/line/arc: quantitative | radial position; arc combines it with a categorical theta band |
| `encodeParallelCoordinates` | line | line: quantitative, ordinal | atomic ordered dimensions; one namespaced scale and axis per dimension |
## Related
[Action Reference](../actions.md) · [Chart API](../../api/index.md) · [Supported Features](../../supported-features.md)
# Encoding Actions
These are direct immutable `ChartProgram` actions. Each accepts one option object and returns a new program.
## `encodeX`
```javascript
encodeX({ field, target?, fieldType?, aggregate?, stack?, coordinate?, bin?, scale? })
encodeX({ datum, target?, fieldType, coordinate?, scale? }) // rule
```
Create or compatibly replace an x encoding for the supported mark/type pairs in
the matrix above. Rects accept a discrete x band or the primary x edge of a
complete x/x2 range. Bars accept binned x, vertical categories, or a horizontal
aggregate measure. Rules accept exactly one field or datum and an explicit
field type.
[Position encodings](../../api/position-encodings.md)
## `encodeY`
```javascript
encodeY({ field?, target?, fieldType?, aggregate?, stack?, coordinate?, scale? })
encodeY({ datum, target?, fieldType, coordinate?, scale? }) // rule
```
Create or compatibly replace a y encoding. With bar marks, a quantitative y
measure plus ordinal/temporal x produces vertical bars; ordinal/temporal y plus
a quantitative aggregate x produces horizontal bars. Orientation is inferred
from the complete pair and is not stored separately. Bar stack accepts
`"zero"`, `"normalize"`, or `null`.
Aggregate values may be scalar names or parameterized quantile
and ordered first/last objects. A complete histogram x/y pair materializes concrete rects.
Rects accept a discrete y band or the primary y edge of a complete y/y2 range.
Rules accept exactly one field or datum and an explicit field type.
[Position encodings](../../api/position-encodings.md)
## `encodeY2`
```javascript
encodeY2({ field, target?, fieldType, scale?, coordinate? })
encodeY2({ datum, target?, fieldType, scale?, coordinate? }) // rule
```
Assign an area, ranged-bar, or rect upper edge, or a rule secondary y endpoint.
It requires an existing y and shares its scale and coordinate.
[Position encodings](../../api/position-encodings.md)
## `encodeX2`
```javascript
encodeX2({ field, target?, fieldType, scale?, coordinate? })
encodeX2({ datum, target?, fieldType, scale?, coordinate? })
```
Assign an area, ranged-bar, or rect upper edge, or a rule secondary x endpoint.
It requires an existing x and shares its scale and coordinate.
[Position encodings](../../api/position-encodings.md)
## `encodeYRange`
```javascript
encodeYRange({ lower, upper, target?, fieldType?, coordinate?, scale? })
```
Atomically compose area or ranged-bar `encodeY` and `encodeY2`.
[Encodings](../../api/encodings.md)
## `encodeXRange`
```javascript
encodeXRange({ lower, upper, target?, fieldType?, coordinate?, scale? })
```
Atomically compose area or ranged-bar `encodeX` and `encodeX2`.
[Encodings](../../api/encodings.md)
## `encodeGroup`
```javascript
encodeGroup({ field, target?, fieldType? })
```
Split line or area paths by a nominal field without creating a scale or guide.
[Encodings](../../api/encodings.md)
## `encodePathOrder`
```javascript
encodePathOrder({ field, target?, fieldType?, order? })
```
Order vertices within each compatible Cartesian line or ranged-area series.
`fieldType` defaults to `"quantitative"`; `order` defaults to `"ascending"`.
Ties preserve source-row order, and no scale or guide is created.
[Series encodings](../../api/series-encodings.md)
## `encodeParallelCoordinates`
```javascript
encodeParallelCoordinates({ dimensions, target?, coordinate?, key?, missing? })
```
Atomically assign ordered dimensions and their local scales to one line mark.
The default missing policy is `"break"`.
[Parallel Coordinates](../../api/parallel-coordinates.md#advanced-encoding)
## `removePathOrder`
```javascript
removePathOrder({ target? } = {})
```
Remove explicit path topology and restore the mark's automatic independent-
position ordering. [Series encodings](../../api/series-encodings.md)
## `encodeText`
```javascript
encodeText({ target?, field?, value?, format? })
```
Assign exactly one field or constant value to a text mark. `format` accepts
`"auto"` or fixed-decimal tokens from `".0f"` through `".12f"`. Reassignment
replaces the previous content branch. [Text marks](../../api/marks/text.md)
## `encodeXOffset`
```javascript
encodeXOffset({
field, target?, fieldType?, scale?, paddingInner?, paddingOuter?
})
```
Create or compatibly update an advanced nominal offset scale within an ordinal
bar x band. Padding defaults to zero and is preserved when omitted on a later
same-field call. Grouped color layout normally invokes this action automatically.
[Position encodings](../../api/position-encodings.md)
## `encodeYOffset`
```javascript
encodeYOffset({
field, target?, fieldType?, scale?, paddingInner?, paddingOuter?
})
```
Create or compatibly update the corresponding categorical offset scale within
an ordinal bar y band. Horizontal grouped color layout invokes this action as a
wrapped child; explicit domain order, reversed range, and padding follow the
same contract as `encodeXOffset`.
[Position encodings](../../api/position-encodings.md)
## `encodeHistogram`
```javascript
encodeHistogram({
field, target?, coordinate?, maxBins?, binStep?, binBoundaries?,
stack?, xScale?, yScale?
})
```
Compose binned bar `encodeX` and count `encodeY` as one atomic
histogram action. Choose at most one of `maxBins`, `binStep`, and
`binBoundaries`. `maxBins` defaults to `10`; `stack` defaults to `"zero"`.
Use `stack: "normalize"` for a unit-height partition.
[Encodings](../../api/encodings.md)
## `encodeDensity`
```javascript
encodeDensity({
field, target?, source?, groupBy?, bandwidth?, extent?, steps?, kernel?,
normalization?, as?, densityChannel?, coordinate?, valueScale?, densityScale?
})
```
Create immutable KDE data, bind it to an area mark, encode its value
and density fields, and materialize baseline-closed paths. Density defaults to
the y channel; kernel and normalization default to `"gaussian"` and `"unit"`.
Pass `densityChannel: "x"` for a horizontal orientation.
[Encodings](../../api/encodings.md#atomic-density)
## `editDensity`
```javascript
editDensity({ target?, bandwidth?, extent?, steps?, kernel?, normalization? })
```
Create an immutable density-data revision, rebind the selected density area,
and rematerialize its graphical consumers. Omitted density settings are
preserved and at least one editable setting is required.
[Encodings](../../api/encodings.md#atomic-density)
## `encodeHorizon`
```javascript
encodeHorizon({
target?, source?, x?, y?, groupBy?, bands?, baseline?, extent?, resolve?,
missing?, overflow?, palette?
} = {})
```
Create immutable folded-band data, bind it to an area mark, and author the
ordinary x, y/y2, group, and color encodings needed for a compact Horizon
chart. Compatible target, source, and fields are inferred when unambiguous.
[Encodings](../../api/encodings.md#atomic-horizon)
## `editHorizon`
```javascript
editHorizon({
target?, source?, x?, y?, groupBy?, bands?, baseline?, extent?, resolve?,
missing?, overflow?, palette?
})
```
Create and bind an immutable Horizon revision, preserve omitted settings and
scale identities, and rematerialize affected consumers. `groupBy: false`
removes grouping.
[Encodings](../../api/encodings.md#atomic-horizon)
## `encodeColor`
### Color capability matrix
| Mode | Supported marks | Field types | Important options |
| --- | --- | --- | --- |
| Categorical | point, line, area, bar, rect, arc | point/line/area/bar/rect/arc: nominal, ordinal | bar/area layout; arc overlay; palette and ordinal scale |
| Continuous | point, aggregate bar, rect | point/rect: quantitative, temporal; aggregate bar: quantitative | sequential scale; aggregate required for a different bar measure |
| Discretized continuous | point | point: quantitative | quantize, quantile, or threshold scale |
```javascript
encodeColor({ field, target?, fieldType?, palette?, layout?, aggregate?, scale? })
```
Create or compatibly replace point fill, line-series color, grouped area fill,
bar color, rect fill, or arc-sector fill. Nominal and ordinal categories share an ordinal palette scale;
ordinal fields may contain ordered numeric categories. Categorical bar layout accepts `stack`, `fill`, `group`, `overlay`,
and `diverging`; area accepts all except `group`. Quantitative and temporal
point fields use a sequential scale; quantitative point fields also accept
`quantize`, `quantile`, and `threshold` color classes. Categorical
grouped bars record `encodeXOffset` or `encodeYOffset` as a child according to
orientation. Reassigning grouped color also atomically reassigns its offset and
rematerializes an existing legend. Aggregate
bars accept quantitative sequential color: a matching measure field inherits
its aggregate, while a different field requires `aggregate`.
Row-owned rects accept categorical or continuous color. Arc sectors accept
categorical color with optional overlay layout.
[Series encodings](../../api/series-encodings.md)
## `encodeStrokeDash`
```javascript
encodeStrokeDash(
{ field, target?, fieldType?, scale? }
| { value, target? }
)
```
Create or replace nominal line-series or rule dash patterns, or apply one
constant named/direct pattern. Named styles are `solid`, `dashed`, `dotted`, and
`dashdot`.
[Series encodings](../../api/series-encodings.md)
## `encodeRadius`
```javascript
encodeRadius({ value, target? })
```
Apply a constant point radius. [Constant appearance](../../api/appearance.md)
## `encodeTheta`
```javascript
encodeTheta({ field, target?, fieldType?, aggregate?, scale?, coordinate? })
```
Encode Polar angle in clockwise degrees from 12 o'clock. Quantitative,
temporal, ordinal, and nominal fields are supported for point and line marks.
Arc marks accept nominal or ordinal fields and optional `aggregate: "count"`.
The default scale ID is `theta` and its automatic range is `[0, 360]`.
[Polar positions](../../api/position-encodings.md#polar-positions)
## `encodeR`
```javascript
encodeR({ field, target?, fieldType?, scale?, coordinate? })
```
Encode a quantitative field as Polar radial distance. The default `radius`
scale fits the current plot bounds and rematerializes after Canvas edits.
[Polar positions](../../api/position-encodings.md#polar-positions)
## `encodePointRadius`
```javascript
encodePointRadius({ value, target? })
```
Apply a constant point glyph radius through a traced `encodeRadius` child. This
does not assign semantic Polar radial position.
[Constant appearance](../../api/appearance.md)
## `encodeSize`
```javascript
encodeSize({ field, target?, fieldType?, scale? })
```
Encode or replace a quantitative field as equal-area point size. The automatic area range
is `[24, 196]`. [Appearance encodings](../../api/appearance.md)
## `encodeShape`
```javascript
encodeShape({ field, target?, fieldType?, scale? })
```
Encode or replace a nominal field with the shared 12-value point-shape vocabulary.
[Appearance encodings](../../api/appearance.md)
## `encodeOpacity`
```javascript
encodeOpacity({ value, target? })
encodeOpacity({ field, target?, fieldType?, scale? })
```
Apply a constant point/rule opacity from `0` to `1`, or map a quantitative field
through a linear opacity scale. The two modes are mutually exclusive and may
replace each other through the same action.
[Appearance encodings](../../api/appearance.md)
## `encodeStroke`
```javascript
encodeStroke({ value, target? })
```
Assign a constant non-empty stroke string to a rule mark.
[Appearance encodings](../../api/appearance.md)
## `encodeStrokeWidth`
```javascript
encodeStrokeWidth({ value, target? })
```
Assign a non-negative finite logical Canvas width to a rule mark.
[Appearance encodings](../../api/appearance.md)
## `encodeBarWidth`
```javascript
encodeBarWidth({ band?, pixels?, target? })
```
Set aggregate-bar band occupancy or a fixed logical-pixel width and materialize
concrete rectangles. The modes are mutually exclusive. The first omitted mode
defaults to `band: 0.72`; later omission retains the current mode.
[Constant appearance](../../api/appearance.md)
## Related
[Action Reference](../actions.md) · [Chart API](../../api/index.md) · [Supported Features](../../supported-features.md)
# Statistical Layer Actions
These are direct immutable `ChartProgram` actions. Each accepts one option object and returns a new program.
## `createIntervalData`
```javascript
createIntervalData({
id, source?, field, groupBy?, center?, extent?, level?, as?
})
```
Create immutable grouped center/lower/upper summary rows. Mean supports
standard error, sample standard deviation, and Student-t confidence intervals;
median supports interquartile range. [Data](../../api/data.md)
## `createRegression`
```javascript
createRegression({
target?, x?, y?, groupBy?, method?, degree?, span?,
confidence?, interval?, band?, line?
})
```
Infer an eligible point layer and create immutable fitted data, optional grouped
interval-band paths, and grouped line paths. Method defaults to `"linear"`;
polynomial degree to `2`; LOESS span to `0.75`.
[Regression](../../api/regression.md)
## `editRegression`
```javascript
editRegression({
target?, method?, degree?, span?, confidence?, interval?, band?, line?
})
```
Revise the model through its stable point owner. Statistical changes create and
rebind one immutable derived-data revision; component-only changes retain the
current fitted rows. [Regression](../../api/regression.md#editing-a-regression)
## `createErrorBar`
```javascript
createErrorBar({
id?, target?, data?, x?, y?, groupBy?, coordinate?,
caps?, capSize?, stroke?, strokeWidth?, strokeDash?, opacity?
} = {})
```
Create vertical or horizontal statistical or explicit intervals. With one
eligible encoded layer, the shortest call infers its fields, orientation, data,
coordinate, and scales.
[Error bars](../../api/error-bars.md)
## `editErrorBar`
```javascript
editErrorBar({
target?, caps?, capSize?, stroke?, strokeWidth?, strokeDash?, opacity?
})
```
Partially edit one error bar and its owned caps. `caps: false` removes both
caps; `caps: true` restores them without replacing interval data.
[Error bars](../../api/error-bars.md#editing-error-bars)
## `createErrorBand`
```javascript
createErrorBand({
id?, target?, data?, x?, y?, groupBy?, coordinate?, fill?, opacity?,
curve?, boundaries?
} = {})
```
Create a vertical or horizontal statistical or explicit interval ribbon. The
action can infer one encoded source layer and reuses `createIntervalData`, an
ordinary area, the matching atomic range action, and grouping actions.
`boundaries: { stroke?, strokeWidth?, strokeDash?, opacity?, curve? }` adds
lower and upper line layers. Boundary curve inherits the area curve unless it
is overridden.
[Error bands](../../api/error-bands.md)
## `editErrorBand` and `editErrorBandBoundary`
```javascript
editErrorBand({ target?, fill?, opacity?, curve? })
editErrorBandBoundary({
target?, boundary?, stroke?, strokeWidth?, strokeDash?, opacity?, curve?
})
```
Edit the band body or selected owned boundaries without addressing generated
line IDs. Boundary selection is `"both"`, `"lower"`, or `"upper"`; omitted
selection means both. Missing selected boundaries are created from the band.
[Error bands](../../api/error-bands.md#editing-the-band)
## `createBoxPlot`
```javascript
createBoxPlot({
id?, target?, data?, x?, y?, coordinate?, whisker?, width?, outliers?,
box?, median?, outlier?, guides?
} = {})
```
Create a vertical or horizontal Tukey/min–max box plot from one categorical
and one quantitative field. The action infers an encoded source when possible
and composes immutable box summary data, error-bar whiskers, ranged-bar bodies,
median rules, and optional point outliers. Tukey factor, band width, component
appearance, and outlier creation are configurable. [Box plots](../../api/box-plots.md)
Guides remain opt-in for compatibility: pass `guides: {}` or nested options to
create them inside the facade; omission and `false` create none.
## `editBoxPlot`
```javascript
editBoxPlot({ target?, whisker?, width?, outliers?, box?, median?, outlier? })
```
Revise box statistics, optional outlier topology, width, and component
appearance through the stable box owner without addressing generated child
IDs. [Box plots](../../api/box-plots.md#editing-a-box-plot)
## `createGradientPlot`
```javascript
createGradientPlot({
id?, target?, data?, x?, y?, coordinate?, density?, width?, gradient?,
center?, guides?
} = {})
```
Create one density-gradient strip per category from categorical and
quantitative x/y roles. Positions can be explicit, inferred from one eligible
encoded layer, or completed later. Defaults are Gaussian auto density, 64
samples, width band `0.7`, no outline, a median center rule, and applicable
guides. A categorical `encodeColor` owns strip hue while density continues to
control lightness and opacity.
[Statistical actions](../../reference/actions/statistics.md#creategradientplot)
## `editGradientPlot`
```javascript
editGradientPlot({ target?, density?, width?, gradient?, center? })
```
Revise one stable gradient-plot owner. Statistical changes create and rebind
one immutable raw-source profile revision; appearance-only edits retain it.
`center: false` removes the optional rule and `center: {}` restores it.
[Statistical actions](../../reference/actions/statistics.md#editgradientplot)
## `createViolinPlot`
```javascript
createViolinPlot({
id?, data?, coordinate?, x, y, split?, color?, density?, area?, guides?
})
```
Create a vertical or horizontal categorical density plot from exactly one
categorical and one quantitative x/y role. The action infers field types,
orientation, data, scales, and applicable guides, then records an ordinary area
mark, categorical `encodeDensity`, optional color, and guides as wrapped
children. Density options own bandwidth, extent, kernel, normalization, and
shared or independent band-relative width. An optional two-value split assigns
one half to each side of the category center.
[Violin plots](../../api/violin-plots.md)
## Related
[Action Reference](../actions.md) · [Chart API](../../api/index.md) · [Supported Features](../../supported-features.md)
# Guide, Axis, Grid, and Title Actions
These are direct immutable `ChartProgram` actions. Each accepts one option object and returns a new program.
## `createGuides`
```javascript
createGuides({ axes?, grid?, legend? })
```
Create applicable Cartesian or Polar axes and grids plus supported legends.
[Guides](../../api/guides.md)
## `createAxes`
```javascript
createAxes({ coordinate?, x?, y?, theta?, radius? })
```
Create Cartesian or Polar axes directly, including inferred titles and ticks.
[Axes](../../api/axes.md)
## `createThetaAxis`
```javascript
createThetaAxis({ scale?, coordinate?, line?, ticksAndLabels?, title? } = {})
```
Create the complete outer circular theta axis. [Axes](../../api/axes.md)
## `createRadialAxis`
```javascript
createRadialAxis({ scale?, coordinate?, angle?, line?, ticksAndLabels?, title? } = {})
```
Create the complete center-to-edge radial axis; `angle` defaults to `90`.
[Axes](../../api/axes.md)
## `editThetaAxis`
```javascript
editThetaAxis({ line?, ticks?, labels?, ticksAndLabels?, title? })
```
Edit selected theta-axis components. [Axes](../../api/axes.md#editing-a-complete-axis)
## `editRadialAxis`
```javascript
editRadialAxis({ angle?, line?, ticks?, labels?, ticksAndLabels?, title? })
```
Edit selected radial components; `angle` moves the whole axis.
[Axes](../../api/axes.md#editing-a-complete-axis)
## `editThetaAxisLine`
```javascript
editThetaAxisLine({ color?, lineWidth? } = {})
```
Edit the outer baseline style. [Axes](../../api/axes.md)
## `editRadialAxisLine`
```javascript
editRadialAxisLine({ color?, lineWidth? } = {})
```
Edit the radial baseline style. [Axes](../../api/axes.md)
## `editThetaAxisTicks`
```javascript
editThetaAxisTicks({ count?, values?, length?, color?, lineWidth? } = {})
```
Edit theta tick geometry and style. [Axes](../../api/axes.md)
## `editRadialAxisTicks`
```javascript
editRadialAxisTicks({ count?, values?, length?, color?, lineWidth? } = {})
```
Edit radial tick geometry and style. [Axes](../../api/axes.md)
## `editThetaAxisLabels`
```javascript
editThetaAxisLabels({ count?, values?, offset?, format?, color?, fontSize?, fontFamily?, fontWeight? } = {})
```
Edit perimeter theta labels. [Axes](../../api/axes.md)
## `editRadialAxisLabels`
```javascript
editRadialAxisLabels({ count?, values?, offset?, format?, color?, fontSize?, fontFamily?, fontWeight? } = {})
```
Edit radial value labels. [Axes](../../api/axes.md)
## `editThetaAxisTitle`
```javascript
editThetaAxisTitle({ text?, offset?, color?, fontSize?, fontFamily?, fontWeight? } = {})
```
Edit the theta title. [Axes](../../api/axes.md)
## `editRadialAxisTitle`
```javascript
editRadialAxisTitle({ text?, position?, offset?, color?, fontSize?, fontFamily?, fontWeight? } = {})
```
Edit the radial title. `position` accepts `"inside"` or `"outside"` and defaults
to the baseline midpoint inside the plot. [Axes](../../api/axes.md)
## `removeThetaAxis`
```javascript
removeThetaAxis({ scale?, coordinate? } = {})
```
Remove the complete theta-axis resource. [Axes](../../api/axes.md#removing-an-axis)
## `removeRadialAxis`
```javascript
removeRadialAxis({ scale?, coordinate? } = {})
```
Remove the complete radial-axis resource. [Axes](../../api/axes.md#removing-an-axis)
## `createGrid`
```javascript
createGrid({ horizontal?, vertical?, theta?, radial? })
```
Create inferred horizontal and/or vertical Cartesian grid lines behind related
marks, or infer the Polar grid families backed by stored theta/radius encodings.
[Grids](../../api/grids.md)
## `createThetaGrid`
```javascript
createThetaGrid({ scale?, coordinate?, count?, values?, color?, lineWidth?, strokeDash? } = {})
```
Create theta spokes behind related marks. [Grids](../../api/grids.md)
## `createRadialGrid`
```javascript
createRadialGrid({ scale?, coordinate?, count?, values?, color?, lineWidth?, strokeDash? } = {})
```
Create concentric radial paths behind related marks. [Grids](../../api/grids.md)
## `editThetaGrid`
```javascript
editThetaGrid({ count?, values?, color?, lineWidth?, strokeDash? })
```
Edit the existing theta grid. [Grids](../../api/grids.md#editing-grids)
## `editRadialGrid`
```javascript
editRadialGrid({ count?, values?, color?, lineWidth?, strokeDash? })
```
Edit the existing radial grid. [Grids](../../api/grids.md#editing-grids)
## `createLegend`
```javascript
createLegend({
target?, channels?, position?, align?, direction?, columns?, offset?,
titlePosition?, title?, symbol?, labels?, titleStyle?, itemGap?, border?, count?,
gradient?
})
```
Create categorical, point-size, continuous-color gradient, discretized-color
interval, or field-opacity sample legends. Continuous legends support right, left, top, and bottom
placement. Categorical legends also support left side placement; composite
point and size blocks remain in deterministic vertical order.
[Legends](../../api/legends.md)
## `editLegend`
```javascript
editLegend({
target?, position?, align?, direction?, columns?, offset?, titlePosition?,
title?, symbol?, labels?, titleStyle?, itemGap?, border?, count?, gradient?
})
```
Partially edit one existing legend. `title` accepts a non-empty string,
`"auto"`, or `false`; semantic channel bindings cannot be edited.
[Legends](../../api/legends.md)
## Focused legend edits
```javascript
editLegendLayout({
target?, position?, align?, direction?, columns?, offset?,
titlePosition?, itemGap?
})
editLegendLabels({ target?, color?, fontSize?, fontFamily?, fontWeight? })
editLegendTitle({
target?, title?, color?, fontSize?, fontFamily?, fontWeight?
})
editLegendSymbols({ target?, symbol?, count?, gradient? })
editLegendBorder({ target?, border })
```
Edit one legend component without constructing the nested options accepted by
`editLegend`. Each action uses the same target inference, validation, and
rematerialization as `editLegend`. At least one component change is required.
[Editing legends](../../api/legends/editing.md#focused-edits)
## `removeLegend`
```javascript
removeLegend({ target? })
```
Remove every legend block owned by one mark while preserving mark encodings and
scales. [Legends](../../api/legends.md)
## `createTitle`
```javascript
createTitle({
text, subtitle?, position?, align?, offset?, gap?,
maxWidth?, wrap?, lineHeight?,
titleStyle?, subtitleStyle?
})
```
Create a chart title and optional subtitle. [Titles](../../api/titles.md)
## `editTitle`
```javascript
editTitle({
text?, subtitle?, position?, align?, offset?, gap?,
maxWidth?, wrap?, lineHeight?,
titleStyle?, subtitleStyle?
})
```
Partially edit the existing title. `subtitle: false` removes the subtitle;
omitted properties remain unchanged. [Titles](../../api/titles.md)
## `removeTitle`
```javascript
removeTitle()
```
Remove the complete chart title and subtitle resource. [Titles](../../api/titles.md)
## Related
[Action Reference](../actions.md) · [Chart API](../../api/index.md) · [Supported Features](../../supported-features.md)
# Advanced Chart Actions
Use these actions when the complete chart and guide facades do not expose the required control.
Use these actions for explicit semantic resources or focused axis control.
## Reusable mark selections
```javascript
selectMarks({ id?, target?, grain?, field | channel | property, op, ...operatorOptions })
```
Store a reusable semantic final-item selection without changing graphics.
Supported operators are `eq`, `neq`, `gt`, `gte`, `lt`, `lte`, `oneOf`,
`range`, `min`, and `max`. `grain` defaults to `"item"`; stacked bars also
support `"stack"`. Fields are data values, channels are pre-scale semantic
values, and properties are concrete graphical values.
[Mark selection and highlighting](../../api/appearance/selection-and-highlighting.md#mark-selection-and-highlighting)
## Semantic resources and regression layers
```javascript
createCoordinate({ id?, type?, layers? })
createDerivedData({
id,
source,
transform: [DatasetTransform]
})
createRegressionBand({
id, data, x, lower, upper, groupBy?, coordinate, xScale, yScale,
color?, opacity?, stroke?, strokeWidth?, curve?
})
editRegressionBand({ target?, color?, opacity?, stroke?, strokeWidth?, curve? })
createRegressionLine({
id, data, x, y, groupBy?, coordinate, xScale, yScale,
colorScale?, strokeWidth?, curve?
})
editRegressionLine({ target?, strokeWidth?, curve? })
```
These actions explicitly author named semantic resources or the component
layers normally owned by `createRegression`.
`createCoordinate.type` accepts `"cartesian"`, `"polar"`, or `"parallel"`.
Parallel coordinates normally create their resource through
`encodeParallelCoordinates` or `createParallelCoordinates`.
`createDerivedData` stores immutable source and transform provenance only; it
does not materialize values. Its public `DatasetTransform` union supports
`filter`, `regression`, `density`, and `interval` objects. A bare object, empty
array, or multi-transform pipeline is invalid. See the runnable filter example and exact transform
requirements in [Source and derived data](../../api/data/source-and-derived.md#create-derived-data).
## Complete single-channel axes
```javascript
createXAxis({ scale?, coordinate?, position?, line?, ticksAndLabels?, title? })
createYAxis({ scale?, coordinate?, position?, line?, ticksAndLabels?, title? })
editXAxis({ position?, line?, ticks?, labels?, ticksAndLabels?, title? })
editYAxis({ position?, line?, ticks?, labels?, ticksAndLabels?, title? })
```
Complete-axis edits update only the selected components of an existing axis.
Use `ticksAndLabels` for a coordinated tick/label edit, or `ticks` and
`labels` for independent edits; do not combine both forms.
## Complete axis removal
```javascript
removeXAxis({ coordinate?, scale? })
removeYAxis({ coordinate?, scale? })
```
Remove one complete Cartesian axis. Optional selectors must match the existing
resource. [Axes](../../api/axes.md)
## Axis lines, ticks, and labels
```javascript
createXAxisLine({ scale?, position?, color?, lineWidth? })
createYAxisLine({ scale?, position?, color?, lineWidth? })
editXAxisLine({ position?, color?, lineWidth? })
editYAxisLine({ position?, color?, lineWidth? })
createXAxisTicks({ scale?, position?, count?, values?, length?, color?, lineWidth? })
createYAxisTicks({ scale?, position?, count?, values?, length?, color?, lineWidth? })
editXAxisTicks({ position?, count?, values?, length?, color?, lineWidth? })
editYAxisTicks({ position?, count?, values?, length?, color?, lineWidth? })
createXAxisLabels({
scale?, position?, count?, values?, offset?, format?, color?,
fontSize?, fontFamily?, fontWeight?
})
createYAxisLabels({
scale?, position?, count?, values?, offset?, format?, color?,
fontSize?, fontFamily?, fontWeight?
})
editXAxisLabels({
position?, count?, values?, offset?, format?, color?,
fontSize?, fontFamily?, fontWeight?
})
editYAxisLabels({
position?, count?, values?, offset?, format?, color?,
fontSize?, fontFamily?, fontWeight?
})
```
Axis `position` is `"bottom" | "top"` for x and `"left" | "right"` for y.
Label `format` accepts `"auto"`, `{ decimals }`, numeric `.0f/.1f/.2f/.0%/.1%/.2e`,
or UTC `%Y/%Y-%m/%Y-%m-%d` tokens when compatible with the resolved scale.
## Tick/label groups and axis titles
```javascript
createXAxisTicksAndLabels({ scale?, position?, count?, values?, ticks?, labels? })
createYAxisTicksAndLabels({ scale?, position?, count?, values?, ticks?, labels? })
editXAxisTicksAndLabels({ position?, count?, values?, ticks?, labels? })
editYAxisTicksAndLabels({ position?, count?, values?, ticks?, labels? })
createXAxisTitle({
text?, scale?, position?, at?, offset?, rotation?, color?,
fontSize?, fontFamily?, fontWeight?
})
createYAxisTitle({
text?, scale?, position?, at?, offset?, rotation?, color?,
fontSize?, fontFamily?, fontWeight?
})
editXAxisTitle({
text?, position?, at?, offset?, rotation?, color?,
fontSize?, fontFamily?, fontWeight?
})
editYAxisTitle({
text?, position?, at?, offset?, rotation?, color?,
fontSize?, fontFamily?, fontWeight?
})
```
## Directional grids
```javascript
createHorizontalGrid({ scale?, coordinate?, count?, values?, color?, lineWidth?, strokeDash? })
createVerticalGrid({ scale?, coordinate?, count?, values?, color?, lineWidth?, strokeDash? })
editHorizontalGrid({ count?, values?, color?, lineWidth?, strokeDash? })
editVerticalGrid({ count?, values?, color?, lineWidth?, strokeDash? })
editGrid({
horizontal?: { count?, values?, color?, lineWidth?, strokeDash? },
vertical?: { count?, values?, color?, lineWidth?, strokeDash? }
})
```
Directional grid edits require an existing grid. Their `values` option accepts
an exact finite array or `"auto"` to restore current axis/scale inference.
`editGrid` applies one or both directional edits through the same actions.
```javascript
removeGrid({ horizontal?, vertical? })
```
Remove all existing directions when omitted, or only directions selected with
`true`.
See [Coordinates](../../api/coordinates.md) and
[Advanced axis components](../../advanced/axis-components.md).
## Related
[Action Reference](../actions.md) · [Chart API](../../api/index.md) · [Supported Features](../../supported-features.md)
# Extension Actions
Import extension-authoring APIs from `ggaction/extension`; ordinary chart authors should prefer chart actions.
Import `action` and `ChartProgram` from `ggaction/extension`. Primitive methods
are available on programs used by extension actions.
| API | Signature |
| --- | --- |
| Wrapper | `action({ op, description }, implementation)` |
| Semantic primitive | `editSemantic({ property, value })` or `editSemantic({ property, remove: true })` |
| Graphic primitive | `createGraphics({ id, type, length?, parent?, before?, after? })` |
| Graphic primitive | `editGraphics({ target, property, value })` or `editGraphics({ target, remove: true })` |
| Scale actions | `createScale({ id, type?, domain?, range?, nice?, zero?, clamp?, reverse?, base?, exponent?, constant?, paddingInner?, paddingOuter?, padding?, align?, palette?, interpolate?, unknown? })`, `editScale({ id?, type?, domain?, range?, nice?, zero?, clamp?, reverse?, base?, exponent?, constant?, paddingInner?, paddingOuter?, padding?, align?, palette?, interpolate?, unknown? })` |
See [Action authoring](../../extension/action-authoring.md) and
[Primitive API](../../extension/primitives.md).
## Related
[Action Reference](../actions.md) · [Chart API](../../api/index.md) · [Supported Features](../../supported-features.md)
# Program and Rendering Functions
## Internal trace operations
High-level actions call additional wrapped operations for data, scale, mark,
guide, title, and layout materialization. Names such as
`materializeDensityData`, `materializeWindowData`, `materializeBin2DData`, `rematerializeScale`, `rematerializePointMark`,
`createCategoricalLegend`, `createSizeLegend`, `rematerializeSizeLegend`,
`createLegendSymbols`, and `createTitleText` may appear in
`program.trace`. They are deliberately absent from the public TypeScript
declaration and this direct-call reference. Their arguments and decomposition
may change as implementation details while the parent public action remains
stable.
Use the [Actions and trace trees](../concepts/actions-and-trace.md) page to
inspect these nodes. Extension actions should compose the declared extension
and advanced actions instead of calling an undeclared runtime method.
## Program functions
These package-level functions create programs but are not chainable actions.
| Import | Signature |
| --- | --- |
| `ggaction` | `chart(): ChartProgram` |
| `ggaction` | `hconcat(options: CompositionOptions): ChartProgram` |
| `ggaction` | `vconcat(options: CompositionOptions): ChartProgram` |
See [Program composition](../api/composition.md) for sizing, nesting, layout
editing, and stable replacement rules.
## Rendering functions
Rendering functions are not actions and do not modify the trace.
| Import | Signature |
| --- | --- |
| `ggaction` | `render(program, canvasContext, { pixelRatio? }?)` |
| `ggaction/png` | `renderToPNG(program, { output, pixelRatio? })` |
# Exact TypeScript Contract
This generated interface is the exact callable contract from
`types/program.d.ts`. Use the family reference pages for defaults, inference,
effects, and errors.
## Exact TypeScript signatures
This generated block is the exact callable contract from `types/program.d.ts`.
The action entries below provide the readable form, behavior, defaults, and routes.
```typescript
interface ChartProgramActions {
constructor(state?: ActionOptions); readonly semanticSpec: SemanticSpec; readonly graphicSpec: GraphicSpec; readonly resolvedScales: Readonly>>>; readonly materializationConfigs: Readonly>; readonly children: Readonly>; readonly compositionSpec?: CompositionSpec; readonly context: Readonly>; readonly trace: TraceNode; readonly actionStack: readonly unknown[]; createCanvas(options?: CanvasOptions): ChartProgram;
editCanvas(options: CanvasOptions): ChartProgram;
createData(options: { id?: string; values: readonly unknown[] }): ChartProgram;
filterData(options: FilterDataOptions): ChartProgram;
filterMarks(options: FilterMarksOptions): ChartProgram;
selectMarks(options: SelectMarksOptions): ChartProgram;
highlightMarks(options: HighlightMarksOptions): ChartProgram;
createDensityData(options: DensityDataOptions): ChartProgram;
createRegressionData(options: RegressionDataOptions): ChartProgram;
createIntervalData(options: IntervalDataOptions): ChartProgram;
createWindowData(options: WindowDataOptions): ChartProgram;
createBin2DData(options: Bin2DDataOptions): ChartProgram;
createPointMark(options?: { id?: string; data?: string; shape?: PointShape; fill?: string; opacity?: number; stroke?: string; strokeWidth?: number; }): ChartProgram;
editPointMark(options: { target?: string; shape?: PointShape; fill?: string; opacity?: number; stroke?: string; strokeWidth?: number; }): ChartProgram;
jitterPoints(options: JitterPointsOptions): ChartProgram;
removeJitter(options?: RemoveJitterOptions): ChartProgram;
createLineMark(options?: { id?: string; data?: string; strokeWidth?: number; curve?: CurveInterpolation; stroke?: string; opacity?: number; closed?: boolean; }): ChartProgram;
editLineMark(options: { target?: string; strokeWidth?: number; curve?: CurveInterpolation; stroke?: string; opacity?: number; closed?: boolean; }): ChartProgram;
createBarMark(options?: { id?: string; data?: string; fill?: string; opacity?: number; stroke?: string; strokeWidth?: number; }): ChartProgram;
editBarMark(options: { target?: string; fill?: string; opacity?: number; stroke?: string | false; strokeWidth?: number; }): ChartProgram;
createAreaMark(options?: { id?: string; data?: string; fill?: string; opacity?: number; stroke?: string; strokeWidth?: number; curve?: CurveInterpolation; }): ChartProgram;
createArcMark(options?: { id?: string; data?: string; innerRadius?: number; padAngle?: number; fill?: string; opacity?: number; stroke?: string; strokeWidth?: number; }): ChartProgram;
editArcMark(options: { target?: string; innerRadius?: number; padAngle?: number; fill?: string; opacity?: number; stroke?: string; strokeWidth?: number; }): ChartProgram;
createRectMark(options?: RectMarkOptions): ChartProgram;
editRectMark(options: EditRectMarkOptions): ChartProgram;
createRuleMark(options?: { id?: string; data?: string }): ChartProgram;
createTextMark(options?: TextMarkOptions): ChartProgram;
editTextMark(options: EditTextMarkOptions): ChartProgram;
layoutLabels(options?: LabelLayoutOptions): ChartProgram;
removeLabelLayout(options?: RemoveLabelLayoutOptions): ChartProgram;
editAreaMark(options: { target?: string; fill?: string; opacity?: number; stroke?: string | false; strokeWidth?: number; curve?: CurveInterpolation; }): ChartProgram;
encodeX(options: PositionEncodingOptions | RulePositionEncodingOptions): ChartProgram;
encodeY(options: PositionEncodingOptions | RulePositionEncodingOptions): ChartProgram;
encodeTheta(options: ThetaEncodingOptions): ChartProgram;
encodeR(options: RadialEncodingOptions): ChartProgram;
encodeX2(options: SecondaryPositionEncodingOptions): ChartProgram;
encodeColor(options: ColorEncodingOptions): ChartProgram;
encodeStrokeDash(options: StrokeDashEncodingOptions): ChartProgram;
encodeSize(options: { field: string; target?: string; fieldType?: "quantitative"; scale?: ScaleOptions }): ChartProgram;
encodeShape(options: { field: string; target?: string; fieldType?: "nominal"; scale?: ScaleOptions }): ChartProgram;
encodeOpacity(options: OpacityEncodingOptions): ChartProgram;
encodeRadius(options: { value: number; target?: string }): ChartProgram;
encodePointRadius(options: { value: number; target?: string }): ChartProgram;
encodeXOffset(options: XOffsetEncodingOptions): ChartProgram;
encodeYOffset(options: YOffsetEncodingOptions): ChartProgram;
encodeY2(options: SecondaryPositionEncodingOptions): ChartProgram;
encodeYRange(options: { lower: string; upper: string; target?: string; fieldType?: "quantitative"; coordinate?: string; scale?: ScaleOptions; }): ChartProgram;
encodeXRange(options: { lower: string; upper: string; target?: string; fieldType?: "quantitative"; coordinate?: string; scale?: ScaleOptions; }): ChartProgram;
encodeGroup(options: { field: string; target?: string; fieldType?: "nominal" }): ChartProgram;
encodePathOrder(options: PathOrderEncodingOptions): ChartProgram;
encodeParallelCoordinates(options: ParallelCoordinatesEncodingOptions): ChartProgram;
removePathOrder(options?: RemovePathOrderOptions): ChartProgram;
encodeText(options: TextEncodingOptions): ChartProgram;
encodeHistogram(options: HistogramEncodingOptions): ChartProgram;
encodeDensity(options: DensityEncodingOptions): ChartProgram;
editDensity(options: EditDensityOptions): ChartProgram;
encodeHorizon(options?: HorizonEncodingOptions): ChartProgram;
editHorizon(options: EditHorizonOptions): ChartProgram;
encodeBarWidth(options?: BarWidthOptions): ChartProgram;
encodeStroke(options: { target?: string; value: string }): ChartProgram;
encodeStrokeWidth(options: StrokeWidthEncodingOptions): ChartProgram;
createRegression(options?: RegressionOptions): ChartProgram;
editRegression(options: EditRegressionOptions): ChartProgram;
createErrorBar(options?: ErrorBarOptions): ChartProgram;
editErrorBar(options: EditErrorBarOptions): ChartProgram;
createErrorBand(options?: ErrorBandOptions): ChartProgram;
editErrorBand(options: EditErrorBandOptions): ChartProgram;
editErrorBandBoundary(options: EditErrorBandBoundaryOptions): ChartProgram;
createBoxPlot(options?: BoxPlotOptions): ChartProgram;
editBoxPlot(options: EditBoxPlotOptions): ChartProgram;
createGradientPlot(options?: GradientPlotOptions): ChartProgram;
editGradientPlot(options: EditGradientPlotOptions): ChartProgram;
createViolinPlot(options: ViolinPlotOptions): ChartProgram;
createScatterPlot(options: CreateScatterPlotOptions): ChartProgram;
createLinePlot(options: CreateLinePlotOptions): ChartProgram;
createBarPlot(options: CreateBarPlotOptions): ChartProgram;
createHistogram(options: CreateHistogramOptions): ChartProgram;
createHeatmap(options: CreateHeatmapOptions): ChartProgram;
createParallelCoordinates(options: CreateParallelCoordinatesOptions): ChartProgram;
removeMark(options?: RemoveMarkOptions): ChartProgram;
createAxes(options?: CreateAxesOptions): ChartProgram;
createXAxis(options?: CompleteAxisOptions): ChartProgram;
createYAxis(options?: CompleteAxisOptions): ChartProgram;
createThetaAxis(options?: CompletePolarAxisOptions): ChartProgram;
createRadialAxis(options?: CompleteRadialAxisOptions): ChartProgram;
editThetaAxisLine(options?: AxisLineStyleOptions): ChartProgram;
editRadialAxisLine(options?: AxisLineStyleOptions): ChartProgram;
editThetaAxisTicks(options?: PolarTickOptions): ChartProgram;
editRadialAxisTicks(options?: PolarTickOptions): ChartProgram;
editThetaAxisLabels(options?: PolarLabelOptions): ChartProgram;
editRadialAxisLabels(options?: PolarLabelOptions): ChartProgram;
editThetaAxisTitle(options?: PolarTitleOptions): ChartProgram;
editRadialAxisTitle(options?: RadialTitleOptions): ChartProgram;
createXAxisLine(options?: AxisLineStyleOptions & { scale?: string; position?: XAxisPosition }): ChartProgram;
createYAxisLine(options?: AxisLineStyleOptions & { scale?: string; position?: YAxisPosition }): ChartProgram;
editXAxisLine(options?: AxisLineStyleOptions & { position?: XAxisPosition }): ChartProgram;
editYAxisLine(options?: AxisLineStyleOptions & { position?: YAxisPosition }): ChartProgram;
createXAxisTicks(options?: AxisTickOptions): ChartProgram;
createYAxisTicks(options?: AxisTickOptions): ChartProgram;
editXAxisTicks(options?: Omit, "scale">): ChartProgram;
editYAxisTicks(options?: Omit, "scale">): ChartProgram;
createXAxisLabels(options?: AxisLabelOptions): ChartProgram;
createYAxisLabels(options?: AxisLabelOptions): ChartProgram;
editXAxisLabels(options?: Omit, "scale">): ChartProgram;
editYAxisLabels(options?: Omit, "scale">): ChartProgram;
createXAxisTicksAndLabels(options?: AxisTicksAndLabelsOptions): ChartProgram;
createYAxisTicksAndLabels(options?: AxisTicksAndLabelsOptions): ChartProgram;
editXAxisTicksAndLabels(options: Omit, "scale">): ChartProgram;
editYAxisTicksAndLabels(options: Omit, "scale">): ChartProgram;
createXAxisTitle(options?: AxisTitleOptions): ChartProgram;
createYAxisTitle(options?: AxisTitleOptions): ChartProgram;
editXAxisTitle(options?: Omit, "scale">): ChartProgram;
editYAxisTitle(options?: Omit, "scale">): ChartProgram;
editXAxis(options: EditAxisOptions): ChartProgram;
editYAxis(options: EditAxisOptions): ChartProgram;
editThetaAxis(options: Omit): ChartProgram;
editRadialAxis(options: EditRadialAxisOptions): ChartProgram;
removeXAxis(options?: RemoveAxisOptions): ChartProgram;
removeYAxis(options?: RemoveAxisOptions): ChartProgram;
removeThetaAxis(options?: RemoveAxisOptions): ChartProgram;
removeRadialAxis(options?: RemoveAxisOptions): ChartProgram;
createGrid(options?: CreateGridOptions): ChartProgram;
createHorizontalGrid(options?: GridDirectionOptions): ChartProgram;
createVerticalGrid(options?: GridDirectionOptions): ChartProgram;
createThetaGrid(options?: PolarGridOptions): ChartProgram;
createRadialGrid(options?: PolarGridOptions): ChartProgram;
editHorizontalGrid(options: EditGridOptions): ChartProgram;
editVerticalGrid(options: EditGridOptions): ChartProgram;
editThetaGrid(options: EditPolarGridOptions): ChartProgram;
editRadialGrid(options: EditPolarGridOptions): ChartProgram;
editGrid(options: EditGridDirectionsOptions): ChartProgram;
removeGrid(options?: RemoveGridOptions): ChartProgram;
createLegend(options?: LegendOptions): ChartProgram;
editLegend(options: EditLegendOptions): ChartProgram;
editLegendLayout(options: EditLegendLayoutOptions): ChartProgram;
editLegendLabels(options: EditLegendLabelsOptions): ChartProgram;
editLegendTitle(options: EditLegendTitleOptions): ChartProgram;
editLegendSymbols(options: EditLegendSymbolsOptions): ChartProgram;
editLegendBorder(options: EditLegendBorderOptions): ChartProgram;
removeLegend(options?: RemoveLegendOptions): ChartProgram;
createGuides(options?: CreateGuidesOptions): ChartProgram;
createTitle(options: TitleOptions): ChartProgram;
editTitle(options: EditTitleOptions): ChartProgram;
removeTitle(): ChartProgram;
createCoordinate(options?: CreateCoordinateOptions): ChartProgram;
createScale(options: CreateScaleOptions): ChartProgram;
editScale(options: EditScaleOptions): ChartProgram;
createDerivedData(options: CreateDerivedDataOptions): ChartProgram;
createRegressionBand(options: CreateRegressionBandOptions): ChartProgram;
editRegressionBand(options: { target?: string; color?: string; opacity?: number; stroke?: string | false; strokeWidth?: number; curve?: CurveInterpolation; }): ChartProgram;
createRegressionLine(options: CreateRegressionLineOptions): ChartProgram;
editRegressionLine(options: { target?: string; strokeWidth?: number; curve?: CurveInterpolation; }): ChartProgram;
editCompositionLayout(options: EditCompositionLayoutOptions): ChartProgram;
replaceCompositionChild(options: ReplaceCompositionChildOptions): ChartProgram;
facet(options: FacetOptions): ChartProgram;
editFacetHeaders(options: EditFacetHeadersOptions): ChartProgram;
editSemantic(options: EditSemanticOptions): ChartProgram;
createGraphics(options: { id: string; type: GraphicType; length?: number; parent?: string; before?: string; after?: string; }): ChartProgram;
editGraphics(options: EditGraphicsOptions): ChartProgram;
}
```
## Related
[Action Reference](./actions.md) · [Extension Actions](./actions/extension.md)
# Action Authoring
action(metadata, fn)define
→
wrapped callscompose
→
trace subtreerecord
The extension entry point is for developers adding traceable domain actions.
```javascript
import { action, ChartProgram } from "ggaction/extension";
```
Subclass `ChartProgram` so independent extensions do not overwrite methods on
the shared base prototype. In JavaScript, a wrapped action can be assigned to
that subclass prototype directly.
```javascript
class MyProgram extends ChartProgram {}
MyProgram.prototype.setPointOpacity = action(
{
op: "setPointOpacity",
description: "Set the opacity of a point mark."
},
function ({ target, value } = {}) {
return this.editGraphics({
target,
property: "opacity",
value
});
}
);
```
## Strict TypeScript authoring
TypeScript cannot discover a method from runtime prototype assignment alone.
Use declaration merging to connect each runtime method to the exact wrapped
action type. The wrapped function preserves the concrete subclass passed as
`this`, so one custom method can chain into another without a cast or duplicated
option and return signatures.
```typescript
import { action, ChartProgram } from "ggaction/extension";
import type { FillPaint } from "ggaction/extension";
const extensionFill: FillPaint = {
type: "linear-gradient",
from: { x: 0, y: 0.5 },
to: { x: 1, y: 0.5 },
stops: [
{ offset: 0, color: "#eff6ff" },
{ offset: 1, color: "#1d4ed8" }
]
};
type SetPointOpacityOptions = Record & {
target: string;
value: number;
};
class MyProgram extends ChartProgram {}
const setPointOpacityAction = action(
{
op: "setPointOpacity",
description: "Set the opacity of a point mark."
},
function ({ target, value }) {
const withTarget = this.graphicSpec.objects[target] === undefined
? this.createGraphics({ id: target, type: "circle" })
: this;
return withTarget.editGraphics({
target,
property: "opacity",
value
});
}
);
const markReadyAction = action(
{
op: "markReady",
description: "Record that extension authoring is complete."
},
function () {
return this;
}
);
interface MyProgram {
setPointOpacity: typeof setPointOpacityAction;
markReady: typeof markReadyAction;
}
MyProgram.prototype.setPointOpacity = setPointOpacityAction;
MyProgram.prototype.markReady = markReadyAction;
export const extensionProgram = new MyProgram()
.setPointOpacity({ target: "points", value: 0.5 })
.markReady();
export const extensionPaint = extensionFill;
```
This exact module is compiled in the installed-package test with `strict: true`,
NodeNext module resolution, and `skipLibCheck: false`. At runtime both methods
return `MyProgram`; the trace contains `setPointOpacity` and `markReady` as root
actions, while the primitive calls remain children of `setPointOpacity`.
## Action contract
- Metadata requires a stable non-empty `op` and `description`.
- Every action accepts one plain option object.
- The implementation runs with the entered immutable program as `this`.
- It must return an instance of the same `ChartProgram` class.
- Wrapped actions called inside it become trace children.
- Successful completion leaves the returned program's action stack empty.
- The returned wrapped function preserves the concrete subclass used as `this`.
Arguments are summarized before storage in the trace. Arrays become counts, so
large values are not retained twice. Circular plain-object arguments are
rejected because a finite immutable trace summary cannot represent them.
Use the [primitive extension API](./primitives.md) to express semantic and
graphical changes. A semantic edit never materializes graphics automatically;
the enclosing action must invoke every required graphical operation.
# Primitive Extension API
editSemanticmeaning
createGraphicsidentity
editGraphicsconcrete value
These methods are the low-level public extension layer, not the recommended
chart-authoring API.
## `editSemantic({ property, value | remove })`
Creates, replaces, or removes one supported semantic branch and structurally
copies the changed path.
```javascript
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 are complete layers such as `layer[points]`, encoding channels such as
`layer[points].encoding.opacity`, and legend branches such as
`guide.legend.opacity`. Source datasets remain immutable; only an unreferenced
derived dataset may be removed as a complete dataset resource.
```javascript
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.
## `createGraphics({ id, type, length?, parent?, before?, after? })`
Creates one concrete object, a homogeneous drawable collection, or an empty
heterogeneous drawable `collection`.
```javascript
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 accepted by
homogeneous drawable types. A heterogeneous `collection` is populated through
one `editGraphics({ property: "items" })` call instead. Equivalent repeated
creation is idempotent.
```javascript
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.
`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:
```javascript
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.
```javascript
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.
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.
```javascript
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.
```javascript
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.
The complete low-level line-chart example is available in
[`primitive.program.js`](https://github.com/ggaction/ggaction/blob/main/test/charts/cars-line-chart/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`](https://github.com/ggaction/ggaction/blob/main/test/charts/cars-regression-scatterplot/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.
```javascript
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.
```javascript
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.
## 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.
```javascript
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.