# 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. ## 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 ![Horsepower versus miles per gallon](../assets/images/cars-scatterplot.png) 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 ![Mean acceleration by year, grouped by origin](../assets/images/cars-line-chart.png) 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 ![Displacement distribution grouped by origin](../assets/images/cars-histogram.png) 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 ![Mean job percentage by year and sex](../assets/images/jobs-grouped-bar.png) 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 ![Acceleration by displacement with grouped regression](../assets/images/cars-regression-scatterplot.png) 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 ![Acceleration density by origin](../assets/images/cars-density-area.png) 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 ![Kenya life expectancy Horizon chart](../assets/images/gapminder-horizon.png) 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 ![Car observations with grouped mean confidence intervals](../assets/images/cars-error-bar.png) 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 ![Mean life expectancy by cluster with bounded confidence bands](../assets/images/gapminder-error-band.png) 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 ![Maximum-horsepower point in each Origin highlighted as a red diamond](../assets/images/mark-selection-points.png) 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 ![Tallest complete histogram stack highlighted in gold](../assets/images/mark-selection-bars.png) 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 ![Japan line series highlighted with a red dashed stroke](../assets/images/mark-selection-lines.png) 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. ## 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.