Published-release documentation · release compatibility

Continuous Legends

Cars scatterplot with origin and acceleration legends aligned in one top row
Categorical and sampled-opacity blocks share one fully inline reading lane.

Match the channel and sample recipe

Rule opacity is a supported encoding but does not currently have an opacity legend.

A legend consumes an encoding scale. Its display sample need not have the same mark type as the plotted consumer: an opacity legend uses point samples even for a Line. Stroke-width legends use lines. A gradient is not a collection of glyph samples.

Family Consumer and scale Samples Display recipe Composition and edges
Continuous color Point, aggregate Bar, Rect, Text, Tick; existing continuous color scale Gradient ticks and symbols accept count or exact values Default gradient rectangle; representation: "symbols" uses colored points Separate block; four edges and four plot corners
Stroke gradient Supported stroke marks; sequential quantitative/temporal stroke Tick count or exact values Gradient rectangle; rejects symbol Separate stroke block; four edges and four plot corners
Color/stroke interval Compatible quantize, quantile, threshold consumer Every interval Filled or outlined swatches, respectively Ordered interval block; four edges and four plot corners
Opacity Point, Line; quantitative linear opacity count or explicit ascending values One point recipe; type omitted or "point" Separate block; four edges and four plot corners
Size Point; continuous numeric size or discrete area scale Continuous count/values; every discrete interval Area-scaled point symbol Separate block; four edges and four plot corners
Stroke width Line, Rule; quantitative width count or explicit ascending values Line sample with scaled width Separate block; four edges and four plot corners

The canonical compatibility tables list exact consumers; the legend editor owns combining and selecting blocks. Equal visible labels do not make independent channels a valid categorical composite. For all families, requested margins must contain labels, symbols, titles, and borders.

Corner gradients and sampled-opacity blocks use vertical content. A corner anchors the complete occupied group inside the plot, including borders and strokes. Use blockLayout and at to arrange independent blocks together or choose a plot-relative free anchor.

Continuous color, stroke, and opacity

A supported Point, aggregate Bar, Rect, or Tick 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.

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

program.createLegend({
  channels: ["color"],
  count: 5,
  gradient: { length: 120, thickness: 12, opacity: 0.7 }
});

gradient.opacity is a finite number from 0 to 1, defaulting to 1. It fades only the color or stroke ramp body, including its strip outlines; titles, ticks, labels, borders, marks, and layout stay unchanged. Zero keeps the ramp’s geometry and space. Mark opacity is independent and is never inferred into this option. Ramp opacity applies once and does not rewrite mapped colors. The existing continuous-color vocabulary is unchanged.

Use editLegend({ gradient: { opacity: 0.4 } }) or editLegendSymbols({ gradient: { opacity: 0.4 } }) to revise it. Omitted fields retain their stored values. representation: "symbols" rejects gradient; switching back to a gradient resets its dimensions and opacity to their creation defaults unless the switching call supplies them. Existing saved ramps without opacity remain fully opaque.

A Point or Line 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. type is optional and only accepts "point"; radius must be positive. fill, stroke, and non-negative strokeWidth are also supported. The same recipe works with editLegend, editLegendSymbols, and createGuides({ legend: ... }), including TypeScript.

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

program.createLegend({ channels: ["opacity"], position: "left" });

A quantitative or temporal stroke encoding creates a separate stroke gradient with the same mapping, tick, formatting, edge, and lifecycle rules. Quantize, quantile, and threshold stroke scales create a stroke interval legend. Its symbols keep the mark’s fill and draw the mapped color as the outline. An explicit mark strokeWidth: 0 remains zero.

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

program
  .encodeStroke({ field: "risk", fieldType: "quantitative" })
  .createLegend({ channels: ["stroke"] });

For a top or bottom sampled-opacity legend, titlePosition: "left" places the title, every sample circle, and its numeric label on one reading line. The inline defaults use 8 logical pixels from a circle to its label and 20 pixels before the next sample. labels.offset and itemGap override those distances. All sample spacing includes half the stroke width around the circle. Label offset measures the visible edge-to-edge gap; its non-inline default is 12.

Without rowPadding, vertical sampled and interval legends default to itemGap: 18. At either side, itemGap is a minimum center pitch and grows to fit the largest sample or a conservative label line height of fontSize * 1.2. This allowance does not guarantee the exact height of every font. A visible title keeps at least 12 pixels before the first item. Top/bottom legends with a top title use equal center spacing of at least 56 pixels and twice itemGap, enlarged when needed to keep itemGap between neighbouring sample or label bounds. Inline legends keep itemGap between complete sample-label pairs. Large fonts and strokes require sufficient Canvas space; overflow is an error.

Sampled and interval families also accept opt-in rowPadding for per-row occupied edge gaps, independently of titlePadding for the title-to-body gap. Both are non-negative finite logical pixels and support "auto" reset in whole or focused layout edits. Gradient strips accept only titlePadding; explicitly reset a stored rowPadding before a symbols-to-gradient or scale-family transition. See content spacing.

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

program.createLegend({
  channels: ["opacity"],
  position: "top",
  titlePosition: "left",
  count: 3
});

Gradient legends reject categorical-only symbol, columns, and itemGap. Opacity legends reject columns, direction, and gradient. Side-positioned opacity legends require titlePosition: "top". For a single top or bottom legend, align uses the complete visible outer bounds, including endpoint labels, symbol strokes and any border. offset is the gap between the plot and the nearest outer legend edge. This rule also applies to interval, size and stroke-width legends. Both forms require enough requested Canvas margin and never resize the Canvas. Side gradient and opacity legends accept only align: "center". When moving a non-centered horizontal legend to a side, set align: "center" in the same edit. Title styles accept color and typography; offset belongs to labels. Gradient direction: "horizontal" | "vertical" is independent of position. Omission at creation retains the position-derived default; an omitted edit preserves explicit intent. Gradient-only edit direction: "auto" restores the default. Length follows the ramp direction and thickness is perpendicular. Horizontal values run left to right with labels below; vertical values run bottom to top with labels on the left at a left anchor and on the right otherwise. Scale reverse and transformed-scale sampling retain their existing behavior.

Gradient titles accept titlePosition: "top" | "bottom" in creation and editing, including editLegendLayout; "left" is unsupported. A bottom title clears the complete strip, tick and label bounds by 12 pixels. Its default anchor is the strip center for a horizontal ramp and the strip left for a vertical ramp; titleStyle.align still applies. Explicit line breaks and blank lines are retained. Hidden titles occupy no space. All visible content must fit the requested Canvas.

Gradient orientation intent remains inactive while color uses representation: "symbols"; symbol direction is separate. Returning to a gradient restores its orientation. Changing to a different scale family cannot silently discard an explicit gradient direction; reset it with "auto" or remove and recreate the legend.

Choose exact gradient ticks

Unreleased candidate: exact gradient tick arrays extend the existing values option; released versions retain their previous count behavior.

For a prepared continuous color or stroke scale with domain [35, 85], this contextual fragment labels only 40, 60, and 80:

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

program.createLegend({ values: [40, 60, 80], gradient: { length: 125 } });

The values occupy their numeric positions along the ramp, including for log, power, square-root, and symlog scales. This changes ticks and labels; the ramp, encoded marks, and scale domain retain their mapping. For this example:

Value Fraction of the domain Distance from the top of a vertical ramp
40 0.1 112.5
60 0.5 62.5
80 0.9 12.5

Values must contain 1–100 finite, strictly increasing numbers inside the scale domain. Do not combine an exact array with count. A single value is valid. Explicit arrays do not acquire extra midpoint or endpoint ticks. Creation uses omission for automatic ticks. Editors preserve omitted values; editLegend({ values: "auto" }) restores the remembered automatic count, and editLegend({ values: "auto", count: 3 }) resets and changes that count together. A count edit alone cannot replace exact values.

Use editLegend({ channel: "stroke", values: [40, 60, 80] }) to select one existing stroke block when a target has multiple blocks. editLegendBlock continues to edit samples only for sampled families. Exact ticks survive Canvas, theme, data, scale, persistence, and shared-facet replay. An edit that would put a stored value outside its domain fails atomically. Reset exact values before a scale-family transition that would discard them.

Show a continuous color scale as symbols

representation: "symbols" displays sample colors as points while preserving the continuous color mapping used by marks. Omit it to keep the existing gradient. The option supports the existing sequential, log, and symlog color scales, including the existing sequential midpoint mapping. It does not add a new scale type or change a continuous scale into categories.

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

import { chart } from "ggaction";

const program = chart()
  .createCanvas({ width: 600, height: 400, margin: 100 })
  .createData({ values: [
    { x: 1, y: 2, amount: 1 },
    { x: 2, y: 1, amount: 9 }
  ] })
  .createPointMark({ id: "points" })
  .encodeX({ field: "x" })
  .encodeY({ field: "y" })
  .encodeColor({ field: "amount", fieldType: "quantitative" })
  .createLegend({
    representation: "symbols",
    values: [2, 4, 6, 8],
    position: "top-left"
  })
  .editLegendSymbols({ symbol: { type: "point", radius: 5 } });

With no exact values, symbols uses five samples by default; count chooses the automatic count. Exact samples must be finite, strictly ascending, and inside the effective domain. values: "auto" in an editor returns to the remembered automatic count and keeps the symbols representation.

The default point radius is 7 logical pixels. Fill and the default stroke use the mapped sample color; the default stroke width is 1. Use the point recipe’s radius, stroke, and strokeWidth to edit its appearance. A fixed fill, line, swatch, or layered recipe is rejected because this representation explains the sampled color scale. Item direction, columns, and itemGap remain separate from whole-group blockLayout. Side and corner items default to vertical, with a minimum pitch of 18 that grows for larger painted content. Horizontal items default to a gap of 20.

Use editLegend({ representation: "gradient" }) to return to a gradient. Common titles, fonts, border, position, and whole-target group placement remain; symbol-only settings are cleared, exact values are retained, and gradient dimensions return to 120 by 12. Do not supply gradient dimensions in a symbols call. On the reverse switch, omitted values retain exact samples when present, or use the remembered automatic count. No hidden snapshot of the previous representation is kept.

In an editor, omitted channels preserves the current content set. Explicit channels still replaces the target’s final content set; it is not a selector. The selected set must contain continuous color when representation is given. Other retained legend blocks keep their own content and style. To edit one color block’s samples, text, or symbol in a multi-block target, use editLegendBlock({ target: "points", channel: "color", ... }). An ambiguous target must be named. Categorical, interval, and stroke-only legends reject representation.

Canvas, scale, data, composition, and persistence changes preserve the selected representation. If an exact sample becomes invalid for the new domain, the edit fails without changing the previous program. Basic supports creation; use the full entry point for the editing flow above.

Their requested sample count is limited to the inclusive range 2–10,000.

Continuous size, opacity, stroke-width, color-symbol, and color/stroke-gradient legends can instead show exact numeric values. Pass a finite, strictly increasing array with 1–100 values. The array is used as written: ggaction does not sort or deduplicate it. Every value must lie inside the effective scale domain, and log scales require positive values. count and values cannot appear in the same call.

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

program.createLegend({
  channels: ["opacity"],
  values: [0, 0.25, 0.5, 1]
});

Each supplied value goes through the channel’s actual scale mapper. Reversing the scale therefore reverses the symbol appearance while the label order stays ascending. A mapped opacity, area, or width of zero keeps its label and layout slot. Exact values do not change the scale domain or the encoded marks. Discrete size legends continue to show every interval. Gradient color and stroke legends use exact values when supplied, and otherwise retain their existing automatic count behavior.

Continuous legend labels accept the same explicit value formats as axes and Text: .0f–.12f, .0%–.12%, .0e–.12e, or a compatible UTC pattern using %Y, %m, %d, %b, and %%. Set the token in labels.format on creation or editing, including editLegendLabels({ format: ".2e" }). Numeric formats require a quantitative scale and UTC formats require a temporal scale. The default "auto" keeps the family’s tick-aware label selection. Explicit formats are exact, so nearby samples may intentionally display the same text. Categorical legends keep their identity labels and accept "auto" only.

For a quantize, quantile, or threshold point-color or supported stroke scale, the same call creates ordered swatches and concrete interval labels. Interval legends support four edges and four plot corners with layout: "edge". Side legends use a single vertical column, center alignment and a top title. At top/bottom, use align, direction, columns and titlePosition to arrange the item grid. titlePosition: "left" places the title beside the grid. The same controls are editable through editLegend and editLegendLayout. Symbol size, label style, spacing and borders remain editable. Swatch layout includes its full stroke width in both dimensions. The default labels.offset: 8 is the gap after the painted swatch edge. Large samples and text expand rows and keep 12 pixels below a visible top title; insufficient Canvas space is an error.

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

program.createLegend({
  channels: ["color"],
  position: "right",
  direction: "vertical",
  symbol: { width: 14, height: 12 }
});

Use createLegend({ title: false }) to hide an inferred title from creation, including in nested guides.legend options. The title is excluded before layout validation, so a long unused field name does not require extra margin.

Hidden titles do not contribute to occupied bounds or borders. For horizontal inline opacity legends, hiding the title also removes its width and gap. The stored title remains available for later restoration; restoring a title that does not fit the Canvas fails without changing the earlier program.

Legend overview · Scale options · Editing legends

Numeric labels also accept "," for grouped integers and ",.0f" through ",.12f" for grouped fixed decimals. UTC formats accept %a for an English weekday abbreviation, such as %a %d → Mon 01. These formats use the shared axis, legend and text formatter.

Documentation provenance

293 actions · sha256:e755591a2e5f5e2b494867583876e28020a478c5220b77bdc6ad469a6224891d