Text marks

Selected film ratings over release years with title annotations
Point positions reused by a field-driven text layer.

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?)

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 100900 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.

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:

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:

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 for a point-attached label layer with leaders.

removeLabelLayout(options?)

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.

Point marks · Bar marks · Rule marks · Encodings · Annotation recipe