diff --git a/docs/marks/axis.md b/docs/marks/axis.md index 295478fb6c..3212537949 100644 --- a/docs/marks/axis.md +++ b/docs/marks/axis.md @@ -358,6 +358,7 @@ In addition to the [standard mark options](../features/marks.md), the axis mark * **label** - a string to label the axis; defaults to the scale’s label, perhaps with an arrow * **labelAnchor** - the label anchor: *top*, *right*, *bottom*, *left*, or *center* * **labelOffset** - the label position offset (in pixels; default depends on margins and orientation) +* **inset** - the label position inset in pixel; defaults to 0 * **color** - the color of the ticks and labels (defaults to *currentColor*) * **textStroke** - the color of the stroke around tick labels (defaults to *none*) * **textStrokeOpacity** - the opacity of the stroke around tick labels @@ -376,6 +377,8 @@ The axis mark’s default margins depend on its orientation (**anchor**) as foll For simplicity’s sake and for consistent layout across plots, axis margins are not automatically sized to make room for tick labels; instead, shorten your tick labels (for example using the *k* SI-prefix tick format, or setting a *scale*.transform to show thousands or millions, or setting the **textOverflow** option to *ellipsis* and the **lineWidth** option to clip long labels) or increase the margins as needed. +While **labelOffset** offsets the axis label relative to the axis, **inset** moves it along the axis. A positive inset moves the label inwards if the **labelAnchor** is *top*, *right*, *bottom*, or *left*; if the label anchor is *center*, towards the right→ for horizontal axes, and towards the bottom↓ for vertical axes; a negative inset goes in the opposite direction. + ## axisX(*data*, *options*) ```js diff --git a/src/marks/axis.d.ts b/src/marks/axis.d.ts index c0e4babba4..4a6b9b7f88 100644 --- a/src/marks/axis.d.ts +++ b/src/marks/axis.d.ts @@ -1,6 +1,7 @@ +import type {InsetOptions} from "../inset.js"; import type {CompoundMark, Data, MarkOptions} from "../mark.js"; -import type {ScaleOptions} from "../scales.js"; import type {RuleX, RuleXOptions, RuleY, RuleYOptions} from "./rule.js"; +import type {ScaleOptions} from "../scales.js"; import type {TextOptions} from "./text.js"; import type {TickXOptions, TickYOptions} from "./tick.js"; @@ -62,10 +63,29 @@ export interface AxisOptions extends GridOptions, MarkOptions, TextOptions, Axis } /** Options for the axisX and axisFx marks. */ -export interface AxisXOptions extends AxisOptions, TickXOptions {} +export interface AxisXOptions extends AxisOptions, TickXOptions { + /** + * Offsets the axis label by the specified number of pixels; for + * center-anchored axis labels, a positive value moves the axis label towards + * the right→, while a negative value moves it towards the left←. Otherwise, a + * positive value moves the axis label inwards, while a negative value moves + * it outwards. To offset the axis label’s position vertically, see + * **labelOffset**. + */ + inset?: InsetOptions["inset"]; +} /** Options for the axisY and axisFy marks. */ -export interface AxisYOptions extends AxisOptions, TickYOptions {} +export interface AxisYOptions extends AxisOptions, TickYOptions { + /** + * Offsets the axis label by the specified number of pixels; for + * center-anchored axis labels, a positive value moves the axis label down↓, + * while a negative value moves it up↑. Otherwise, a positive value moves the + * axis label inwards, while a negative value moves it outwards. To offset the + * axis label’s position horizontally, see **labelOffset**. + */ + inset?: InsetOptions["inset"]; +} /** Options for the gridX and gridFx marks. */ export interface GridXOptions extends GridOptions, Omit {} diff --git a/src/marks/axis.js b/src/marks/axis.js index e9f7402445..2b15bf3662 100644 --- a/src/marks/axis.js +++ b/src/marks/axis.js @@ -87,6 +87,7 @@ function axisKy( label, labelOffset, labelAnchor, + inset = 0, ...options } ) { @@ -145,7 +146,7 @@ function axisKy( this.frameAnchor = `${cla}-${anchor}`; this.rotate = 0; } - this.dy = cla === "top" ? 3 - marginTop : cla === "bottom" ? marginBottom - 3 : 0; + this.dy = cla === "top" ? 3 - marginTop + inset : cla === "bottom" ? marginBottom - 3 - inset : inset; this.dx = anchor === "right" ? clo : -clo; this.ariaLabel = `${k}-axis label`; return { @@ -190,6 +191,7 @@ function axisKx( label, labelAnchor, labelOffset, + inset = 0, ...options } ) { @@ -246,7 +248,7 @@ function axisKx( } this.lineAnchor = anchor; this.dy = anchor === "top" ? -clo : clo; - this.dx = cla === "right" ? marginRight - 3 : cla === "left" ? 3 - marginLeft : 0; + this.dx = cla === "right" ? marginRight - 3 - inset : cla === "left" ? 3 - marginLeft + inset : inset; this.ariaLabel = `${k}-axis label`; return { facets: [[0]], diff --git a/test/output/axisInset.svg b/test/output/axisInset.svg new file mode 100644 index 0000000000..d9ff6a1b31 --- /dev/null +++ b/test/output/axisInset.svg @@ -0,0 +1,411 @@ + + + + + + + + + + + 35 + 40 + 45 + 50 + 55 + + + culmen_length_mm → + + + Penguins scatterplotby island + + + + + + + + + + + + + + + + + 175 + 180 + 185 + 190 + 195 + 200 + 205 + 210 + 215 + 220 + 225 + 230 + + + ↑ flipper_length_mm + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/test/plots/axis-labels.ts b/test/plots/axis-labels.ts index fac2f941d6..17c8aecea0 100644 --- a/test/plots/axis-labels.ts +++ b/test/plots/axis-labels.ts @@ -1,4 +1,5 @@ import * as Plot from "@observablehq/plot"; +import * as d3 from "d3"; export async function axisLabelX() { return Plot.plot({ @@ -65,3 +66,26 @@ export async function axisLabelHref() { marks: [Plot.axisX({label: "Letter", href: (d) => `https://en.wikipedia.org/wiki/${d}`})] }); } + +export async function axisInset() { + const penguins = await d3.csv("data/penguins.csv", d3.autoType); + return Plot.plot({ + marginTop: 75, + marks: [ + Plot.text(["Penguins scatterplot\nby island"], { + frameAnchor: "top", + dy: -20, + fontSize: 30, + fontWeight: "bold", + lineAnchor: "bottom", + text: (d) => d + }), + Plot.axisY({inset: 55, labelAnchor: "top", label: "↑ flipper_length_mm"}), + Plot.dot(penguins, { + x: "culmen_length_mm", + y: "flipper_length_mm", + fill: "island" + }) + ] + }); +}