charts.axis
Axis geometry as data: tick positions, tick marks, label anchors and gridlines for number, time and category axes.
1.0.1 · published 2026-10-03 by charlie · Anterra
Pinned by 49 tests, run in TypeScript, Python and Rust.axisFromTicks 10 · numberAxis 11 · timeAxis 10 · bandAxis 10 · gridLines 8
What it does
An axis as data rather than drawing: the axis line, and for each tick its position, its mark, its label and where and how to anchor that label. Any renderer (SVG, canvas, a PDF library, a native chart view) can draw it without doing any arithmetic of its own, so a chart built in Python on the server and one built in TypeScript in the browser put every tick in the same place.
This is a group, because an axis is useless without its scale and ticks:
The functions
A group: 5 functions that work together, each in its own file, each pinned by its own tests in TypeScript, Python and Rust. A project can install only the ones it calls.
- axisFromTicks (positions: float[], labels: string[], range: float[], orient: AxisOrient, position: float, tickSize: float) -> Axis
- numberAxis (domain: float[], range: float[], count: int, orient: AxisOrient, position: float, tickSize: float) -> Axis
- timeAxis (domain: date[], range: float[], count: int, orient: AxisOrient, position: float, tickSize: float) -> Axis
- bandAxis (domain: string[], range: float[], paddingInner: float, paddingOuter: float, orient: AxisOrient, position: float, tickSize: float) -> Axis
- gridLines (axis: Axis, length: float) -> Line[]
The types it declares, generated into your project
export type AxisOrient = "bottom" | "top" | "left" | "right";
export type TextAnchor = "start" | "middle" | "end";
export type TextBaseline = "hanging" | "middle" | "alphabetic";
/** A straight line segment in pixels. */
export interface Line {
readonly x1: number;
readonly y1: number;
readonly x2: number;
readonly y2: number;
}
/** One tick: where it is, its mark, and where and how to draw its label. */
export interface AxisTick {
/** along the axis, in pixels */
readonly position: number;
readonly label: string;
/** the tick mark, from the axis line outwards */
readonly tick: Line;
readonly labelX: number;
readonly labelY: number;
/** SVG text-anchor for the label */
readonly anchor: TextAnchor;
/** SVG dominant-baseline for the label */
readonly baseline: TextBaseline;
}
/** Everything needed to draw one axis; all pixel values rounded to 2 decimal places. */
export interface Axis {
readonly orient: AxisOrient;
/** the axis line's fixed coordinate */
readonly position: number;
/** the axis line itself, along the range */
readonly line: Line;
readonly ticks: readonly AxisTick[];
}
Once installed, your code imports each one from the group's module.
axisFromTicks throws on bad input 10 tests
export function axisFromTicks(positions: readonly number[], labels: readonly string[], range: readonly number[], orient: AxisOrient, position: number, tickSize: number): Axis
| positions | float[] | where each tick falls along the axis, in pixels, from any scale |
| labels | string[] | one label per position |
| range | float[] | the axis line's [from, to] in pixels |
| orient | AxisOrient | |
| position | float | the axis line's fixed coordinate: y for bottom and top, x for left and right |
| tickSize | float | length of each tick mark; d3's default is 6 |
| returns | Axis |
For example
axisFromTicks(0, 50, 100, 0, 5, 10, 0, 100, bottom, 200, 6)→ orient bottom, position 200, line …, ticks ×3 a bottom axis: marks point down, labels hang below themaxisFromTicks(10, 90, a, b, 0, 100, top, 20, 6)→ orient top, position 20, line …, ticks ×2 a top axis: marks point up, labels sit on their baseline aboveaxisFromTicks(300, 150, 0, 0, 50, 100, 300, 0, left, 40, 6)→ orient left, position 40, line …, ticks ×3 a left axis: marks point left, labels right-aligned beside them
import { axisFromTicks } from "#fune/charts.axis@^1";
Imports name this capability’s declared dependencies, which fune builds next to it in your project; each one links to its page.
import { roundFloat } from "./math_round_float.ts"; ← from math.round-float ^1.0.0 · built alongside by fune
import type { Axis, AxisOrient, AxisTick, Line } from "./charts_axis_types.ts";
// d3-axis's default gap between the end of a tick mark and its label.
const LABEL_PADDING = 3;
/** A pixel coordinate rounded to 2 places, the precision every axis output uses. */
export function axisPixel(value: number): number {
return roundFloat(value, 2);
}
export function axisLine(x1: number, y1: number, x2: number, y2: number): Line {
return { x1: axisPixel(x1), y1: axisPixel(y1), x2: axisPixel(x2), y2: axisPixel(y2) };
}
/** Throws unless orient is one of the four sides, so a typo is not drawn as a left axis. */
export function checkOrient(orient: string): void {
if (orient !== "bottom" && orient !== "top" && orient !== "left" && orient !== "right") {
throw new RangeError(`orient must be bottom, top, left or right, received "${orient}"`);
}
}
/**
* Axis geometry from tick positions and labels, for any scale.
*
* Follows d3-axis: marks point away from the plot (down for a bottom axis,
* left for a left axis), labels sit 3 pixels past the mark's end, centred
* under a horizontal axis and right-aligned beside a left one.
*/
export function axisFromTicks(
positions: readonly number[],
labels: readonly string[],
range: readonly number[],
orient: AxisOrient,
position: number,
tickSize: number,
): Axis {
checkOrient(orient);
if (positions.length !== labels.length) {
throw new RangeError(`positions and labels must be the same length, received ${positions.length} and ${labels.length}`);
}
if (range.length !== 2) throw new RangeError(`range must have exactly 2 values, [from, to]; got ${range.length}`);
const horizontal = orient === "bottom" || orient === "top";
// Bottom and right axes grow in +y and +x; top and left ones in -y and -x.
const sign = orient === "bottom" || orient === "right" ? 1 : -1;
const markEnd = position + sign * tickSize;
const labelAt = position + sign * (tickSize + LABEL_PADDING);
const ticks: AxisTick[] = positions.map((p, i) => {
if (horizontal) {
return {
position: axisPixel(p),
label: labels[i],
tick: axisLine(p, position, p, markEnd),
labelX: axisPixel(p),
labelY: axisPixel(labelAt),
anchor: "middle",
baseline: orient === "bottom" ? "hanging" : "alphabetic",
};
}
return {
position: axisPixel(p),
label: labels[i],
tick: axisLine(position, p, markEnd, p),
labelX: axisPixel(labelAt),
labelY: axisPixel(p),
anchor: orient === "left" ? "end" : "start",
baseline: "middle",
};
});
const line = horizontal ? axisLine(range[0], position, range[1], position) : axisLine(position, range[0], position, range[1]);
return { orient, position: axisPixel(position), line, ticks };
}numberAxis throws on bad input 11 tests
export function numberAxis(domain: readonly number[], range: readonly number[], count: number, orient: AxisOrient, position: number, tickSize: number): Axis
| domain | float[] | |
| range | float[] | |
| count | int | roughly how many ticks; nice values from charts.ticks |
| orient | AxisOrient | |
| position | float | |
| tickSize | float | |
| returns | Axis |
For example
numberAxis(0, 100, 0, 500, 5, bottom, 300, 6)→ orient bottom, position 300, line …, ticks ×6 0 to 100 in steps of 20 along 500 pixelsnumberAxis(0, 1, 300, 0, 5, left, 40, 6)→ orient left, position 40, line …, ticks ×6 a y axis drawn upwards, labelled to one place because the step is 0.2numberAxis(0, 5,000, 0, 100, 2, bottom, 0, 6)→ orient bottom, position 0, line …, ticks ×3 thousands are grouped
import { numberAxis } from "#fune/charts.axis@^1";
Imports name this capability’s declared dependencies, which fune builds next to it in your project; each one links to its page.
import { axisFromTicks } from "./charts_axis_axis_from_ticks.ts"; ← axisFromTicks, another function of this group · built into the same file, even by a slim install
import type { Axis, AxisOrient } from "./charts_axis_types.ts";
import { formatTick } from "./charts_format_format_tick.ts";
import { linearScale } from "./charts_scale_linear_scale.ts";
import { niceTicks } from "./charts_ticks_nice_ticks.ts";
import { tickStep } from "./charts_ticks_tick_step.ts";
/**
* A linear number axis: nice tick values inside the domain (charts.ticks),
* placed by charts.scale's linearScale and labelled with just enough decimal
* places for the tick step (charts.format), so 0.1-steps read 0.1, 0.2, 0.3
* rather than 0.30000000000000004.
*/
export function numberAxis(
domain: readonly number[],
range: readonly number[],
count: number,
orient: AxisOrient,
position: number,
tickSize: number,
): Axis {
if (domain.length !== 2) throw new RangeError(`domain must have exactly 2 values, [from, to]; got ${domain.length}`);
const lo = Math.min(domain[0], domain[1]);
const hi = Math.max(domain[0], domain[1]);
const values = niceTicks(domain[0], domain[1], count);
const step = tickStep(lo, hi, count);
const positions = values.map((v) => linearScale(domain, range, v, false));
const labels = values.map((v) => formatTick(v, step));
return axisFromTicks(positions, labels, range, orient, position, tickSize);
}timeAxis throws on bad input 10 tests
export function timeAxis(domain: readonly string[], range: readonly number[], count: number, orient: AxisOrient, position: number, tickSize: number): Axis
| domain | date[] | |
| range | float[] | |
| count | int | roughly how many ticks; the interval (day to year) is chosen to fit |
| orient | AxisOrient | |
| position | float | |
| tickSize | float | |
| returns | Axis |
For example
timeAxis(2026-01-01, 2026-12-31, 0, 364, 4, bottom, 200, 6)→ orient bottom, position 200, line …, ticks ×4 a year in quarterstimeAxis(2026-09-01, 2026-09-15, 0, 140, 7, bottom, 0, 6)→ orient bottom, position 0, line …, ticks ×8 a fortnight in days every 2 days, starting on the 1st of the month's odd daystimeAxis(2026-12-31, 2026-01-01, 0, 364, 4, bottom, 0, 6)→ orient bottom, position 0, line …, ticks ×4 a date domain given backwards is placed backwards
import { timeAxis } from "#fune/charts.axis@^1";
Imports name this capability’s declared dependencies, which fune builds next to it in your project; each one links to its page.
import { axisFromTicks } from "./charts_axis_axis_from_ticks.ts"; ← axisFromTicks, another function of this group · built into the same file, even by a slim install
import type { Axis, AxisOrient } from "./charts_axis_types.ts";
import { formatDate } from "./charts_format_format_date.ts";
import { timeScale } from "./charts_scale_time_scale.ts";
import { timeTickInterval } from "./charts_ticks_time_tick_interval.ts";
import { timeTicks } from "./charts_ticks_time_ticks.ts";
/**
* A date axis: the calendar interval that gives about `count` ticks (days,
* weeks, months, quarters or years), ticks on its boundaries, placed by
* timeScale and labelled for that interval ("Sep 2026" for months).
*/
export function timeAxis(
domain: readonly string[],
range: readonly number[],
count: number,
orient: AxisOrient,
position: number,
tickSize: number,
): Axis {
if (domain.length !== 2) throw new RangeError(`domain must have exactly 2 values, [from, to]; got ${domain.length}`);
const lo = domain[0] < domain[1] ? domain[0] : domain[1];
const hi = domain[0] < domain[1] ? domain[1] : domain[0];
const chosen = timeTickInterval(lo, hi, count);
const values = timeTicks(lo, hi, chosen.interval, chosen.step);
const positions = values.map((v) => timeScale(domain, range, v, false));
const labels = values.map((v) => formatDate(v, chosen.interval));
return axisFromTicks(positions, labels, range, orient, position, tickSize);
}bandAxis throws on bad input 10 tests
export function bandAxis(domain: readonly string[], range: readonly number[], paddingInner: number, paddingOuter: number, orient: AxisOrient, position: number, tickSize: number): Axis
| domain | string[] | the categories, one tick at the centre of each band |
| range | float[] | |
| paddingInner | float | |
| paddingOuter | float | |
| orient | AxisOrient | |
| position | float | |
| tickSize | float | |
| returns | Axis |
For example
bandAxis(a, b, c, 0, 300, 0.2, 0.1, bottom, 100, 6)→ orient bottom, position 100, line …, ticks ×3 three categories, ticks at band centresbandAxis(x, y, 0, 100, 0.5, 0, bottom, 0, 6)→ orient bottom, position 0, line …, ticks ×2 centres are rounded to 2 placesbandAxis(north, south, 200, 0, 0, 0, left, 50, 6)→ orient left, position 50, line …, ticks ×2 a category axis on the left
import { bandAxis } from "#fune/charts.axis@^1";
Imports name this capability’s declared dependencies, which fune builds next to it in your project; each one links to its page.
import { axisFromTicks } from "./charts_axis_axis_from_ticks.ts"; ← axisFromTicks, another function of this group · built into the same file, even by a slim install
import type { Axis, AxisOrient } from "./charts_axis_types.ts";
import { bandScale } from "./charts_scale_band_scale.ts";
/**
* A category axis: one tick at the centre of each band, labelled with the
* category itself. The padding is the band scale's, so ticks line up with the
* bars drawn from the same scale.
*/
export function bandAxis(
domain: readonly string[],
range: readonly number[],
paddingInner: number,
paddingOuter: number,
orient: AxisOrient,
position: number,
tickSize: number,
): Axis {
const positions = domain.map((v) => bandScale(domain, range, v, paddingInner, paddingOuter, 0.5).center);
return axisFromTicks(positions, domain, range, orient, position, tickSize);
}gridLines throws on bad input 8 tests
export function gridLines(axis: Axis, length: number): readonly Line[]
| axis | Axis | |
| length | float | how far across the plot each line runs, usually the plot's height or width |
| returns | Line[] |
For example
gridLines(orient bottom, position 200, line …, ticks ×3, 180)→ ×3 from a bottom axis the lines run up across the plotgridLines(orient top, position 20, line …, ticks ×1, 100)→ ×1 from a top axis they run downgridLines(orient left, position 40, line …, ticks ×3, 460)→ ×3 from a left axis they run right
import { gridLines } from "#fune/charts.axis@^1";
Imports name this capability’s declared dependencies, which fune builds next to it in your project; each one links to its page.
import { axisLine, checkOrient } from "./charts_axis_axis_from_ticks.ts"; ← axisFromTicks, another function of this group · built into the same file, even by a slim install
import type { Axis, Line } from "./charts_axis_types.ts";
/**
* One gridline per tick, from the axis line across the plot: up from a bottom
* axis, down from a top one, right from a left axis and left from a right one.
*/
export function gridLines(axis: Axis, length: number): readonly Line[] {
checkOrient(axis.orient);
if (typeof length !== "number" || !Number.isFinite(length) || length < 0) {
throw new RangeError(`length must be a finite number of zero or more, received ${length}`);
}
const pos = axis.position;
return axis.ticks.map((t) => {
const p = t.position;
if (axis.orient === "bottom") return axisLine(p, pos, p, pos - length);
if (axis.orient === "top") return axisLine(p, pos, p, pos + length);
if (axis.orient === "left") return axisLine(pos, p, pos + length, p);
return axisLine(pos, p, pos - length, p);
});
}Install
fune build
With that line in your source, in a TypeScript project (language typescript in fune.project), fune build resolves it and its 4 dependencies, pins them in fune.lock, downloads only the TypeScript package of each, and builds the code above into your project’s .fune/build, one readable file per capability with a header linking back here. Or pin a range in fune.project and build in one step:
fune add charts.axis
That builds the whole group. To build only what you call, and whatever it uses inside the group:
fune add charts.axis --only axisFromTicks
The manifest, vectors and README with only the TypeScript implementation. Install it without the registry with fune add ./charts.axis-1.0.1-typescript.fune, or fetch it from a terminal with fune pull charts.axis@1.0.1:typescript.
The whole function, every language, is one file too: charts.axis-1.0.1.fune, 69,750 bytes, sha256 4aec0b33d90de1c1c687dff41d21141c1182ad88190729c54bf4fc3fbd7c0d4d. It installs into a project of any language.
Customise it in your app
The seams this capability offers. Put a marker directly above a function of your own and fune build wires it into the built code; the package on the registry is not changed, the built file’s header lists it under CUSTOMISED, and fune hooks lists every hook in the project. How hooks work.
before — your function gets the arguments and returns them, changed or not, or throws to refuse the call.
// fune: before charts.axis.axisFromTicks
// fune: before charts.axis.numberAxis
// fune: before charts.axis.timeAxis
// fune: before charts.axis.bandAxis
// fune: before charts.axis.gridLines
after — your function gets the result and the arguments, and returns the final result.
// fune: after charts.axis.axisFromTicks
// fune: after charts.axis.numberAxis
// fune: after charts.axis.timeAxis
// fune: after charts.axis.bandAxis
// fune: after charts.axis.gridLines
replace — inside this capability’s code only, calls to a dependency go to your function, with the same signature. Other capabilities that use it are unaffected; write in * to replace it everywhere.
// fune: replace charts.format in charts.axis
// fune: replace charts.scale in charts.axis
// fune: replace charts.ticks in charts.axis
// fune: replace math.round-float in charts.axis
step — your function runs at a numbered point inside a function’s body, receives the in-scope values it names as parameters, and may return replacements. List the points with fune show charts.axis --steps.
// fune: step charts.axis.<fn> after <n|label>
Tests
A version published now needs at least 8 tests for every function, and one that expects the error for each function that throws; the registry refuses it otherwise. fune verify --all runs each case in TypeScript, Python and Rust, and a project runs them again with fune verify. This page lists the cases; it does not run them. The exact JSON is vectors.json.
axisFromTicks 10 tests
| Case | Arguments | Expected | |
|---|---|---|---|
| a bottom axis: marks point down, labels hang below them | 0, 50, 100, 0, 5, 10, 0, 100, bottom, 200, 6 | → | orient bottom, position 200, line …, ticks ×3 |
| a top axis: marks point up, labels sit on their baseline above | 10, 90, a, b, 0, 100, top, 20, 6 | → | orient top, position 20, line …, ticks ×2 |
| a left axis: marks point left, labels right-aligned beside them | 300, 150, 0, 0, 50, 100, 300, 0, left, 40, 6 | → | orient left, position 40, line …, ticks ×3 |
| a right axis: labels start after the marks | 0, 100, low, high, 0, 100, right, 500, 4 | → | orient right, position 500, line …, ticks ×2 |
| pixels are rounded to 2 places, half away from zero | 12.345, 12.345, x, y, 0.004, 99.996, bottom, 100.125, 6 | → | orient bottom, position 100.13, line …, ticks ×2 |
| zero-length marks put labels 3 pixels from the line | 5, 5, 0, 10, left, 0, 0 | → | orient left, position 0, line …, ticks ×1 |
| an axis with no ticks is just its line | , , 0, 300, bottom, 150, 6 | → | orient bottom, position 150, line …, ticks |
| a label per position is required | 0, 1, 0, 0, 1, bottom, 0, 6 | → | error: positions and labels must be the same length |
| an unknown orient is an error, not a left axis | 0, 0, 0, 1, middle, 0, 6 | → | error: orient must be bottom, top, left or right |
| a range needs two ends | 0, 0, 0, bottom, 0, 6 | → | error: range must have exactly 2 values |
numberAxis 11 tests
| Case | Arguments | Expected | |
|---|---|---|---|
| 0 to 100 in steps of 20 along 500 pixels | 0, 100, 0, 500, 5, bottom, 300, 6 | → | orient bottom, position 300, line …, ticks ×6 |
| a y axis drawn upwards, labelled to one place because the step is 0.2 | 0, 1, 300, 0, 5, left, 40, 6 | → | orient left, position 40, line …, ticks ×6 |
| thousands are grouped | 0, 5,000, 0, 100, 2, bottom, 0, 6 | → | orient bottom, position 0, line …, ticks ×3 |
| a domain that is not nice only gets the ticks inside it | 3, 97, 0, 94, 4, bottom, 0, 6 | → | orient bottom, position 0, line …, ticks ×4 |
| negative to positive | -10, 10, 0, 200, 4, top, 0, 6 | → | orient top, position 0, line …, ticks ×5 |
| count below 1 is an error | 0, 1, 0, 1, 0, bottom, 0, 6 | → | error: count must be a whole number of at least 1 |
| a domain needs two ends | 0, 0, 1, 5, bottom, 0, 6 | → | error: domain must have exactly 2 values |
| a right axis: labels start 3 pixels past the marks | 0, 10, 0, 100, 2, right, 0, 6 | → | orient right, position 0, line …, ticks ×3 |
| a domain given backwards puts its ticks backwards | 10, 0, 0, 100, 2, bottom, 0, 6 | → | orient bottom, position 0, line …, ticks ×3 |
| a step of 0.05 labels every tick with two places | 0, 0.1, 0, 100, 2, bottom, 0, 6 | → | orient bottom, position 0, line …, ticks ×3 |
Show the other 1 test
| Case | Arguments | Expected | |
|---|---|---|---|
| an unknown orient is an error | 0, 10, 0, 100, 5, centre, 0, 6 | → | error: orient must be bottom, top, left or right |
timeAxis 10 tests
| Case | Arguments | Expected | |
|---|---|---|---|
| a year in quarters | 2026-01-01, 2026-12-31, 0, 364, 4, bottom, 200, 6 | → | orient bottom, position 200, line …, ticks ×4 |
| a fortnight in days every 2 days, starting on the 1st of the month's odd days | 2026-09-01, 2026-09-15, 0, 140, 7, bottom, 0, 6 | → | orient bottom, position 0, line …, ticks ×8 |
| a date domain given backwards is placed backwards | 2026-12-31, 2026-01-01, 0, 364, 4, bottom, 0, 6 | → | orient bottom, position 0, line …, ticks ×4 |
| an impossible date is an error | 2026-02-30, 2026-12-31, 0, 1, 4, bottom, 0, 6 | → | error: is not a real calendar date |
| leap years make yearly ticks uneven: 2021 is 366 days along, not 365 | 2020-01-01, 2025-01-01, 0, 500, 5, bottom, 0, 6 | → | orient bottom, position 0, line …, ticks ×6 |
| in a leap year March starts on day 60 | 2024-01-01, 2024-04-01, 0, 91, 3, bottom, 0, 6 | → | orient bottom, position 0, line …, ticks ×4 |
| weekly ticks fall on Mondays, not on the first date | 2026-09-01, 2026-09-29, 0, 280, 4, left, 50, 6 | → | orient left, position 50, line …, ticks ×4 |
| a domain needs two ends | 2026-01-01, 0, 100, 4, bottom, 0, 6 | → | error: domain must have exactly 2 values |
| count below 1 is an error | 2026-01-01, 2026-12-31, 0, 364, 0, bottom, 0, 6 | → | error: count must be a whole number of at least 1 |
| an unknown orient is an error | 2026-01-01, 2026-12-31, 0, 364, 4, middle, 0, 6 | → | error: orient must be bottom, top, left or right |
bandAxis 10 tests
| Case | Arguments | Expected | |
|---|---|---|---|
| three categories, ticks at band centres | a, b, c, 0, 300, 0.2, 0.1, bottom, 100, 6 | → | orient bottom, position 100, line …, ticks ×3 |
| centres are rounded to 2 places | x, y, 0, 100, 0.5, 0, bottom, 0, 6 | → | orient bottom, position 0, line …, ticks ×2 |
| a category axis on the left | north, south, 200, 0, 0, 0, left, 50, 6 | → | orient left, position 50, line …, ticks ×2 |
| a repeated category is an error | a, a, 0, 100, 0, 0, bottom, 0, 6 | → | error: domain has a repeated value |
| one category fills the range and sits in the middle | only, 0, 100, 0, 0, bottom, 0, 6 | → | orient bottom, position 0, line …, ticks ×1 |
| outer padding on a top axis: a third of a step each side | a, b, 0, 100, 0, 0.5, top, 10, 6 | → | orient top, position 10, line …, ticks ×2 |
| a reversed range puts the first category at the far end | a, b, c, 300, 0, 0, 0, bottom, 0, 6 | → | orient bottom, position 0, line …, ticks ×3 |
| no categories is just the axis line | , 0, 100, 0.1, 0.1, bottom, 0, 6 | → | orient bottom, position 0, line …, ticks |
| inner padding above 1 is an error | a, b, 0, 100, 1.5, 0, bottom, 0, 6 | → | error: paddingInner must be between 0 and 1 |
| an unknown orient is an error | a, b, 0, 100, 0, 0, centre, 0, 6 | → | error: orient must be bottom, top, left or right |
gridLines 8 tests
| Case | Arguments | Expected | |
|---|---|---|---|
| from a bottom axis the lines run up across the plot | orient bottom, position 200, line …, ticks ×3, 180 | → | ×3 |
| from a top axis they run down | orient top, position 20, line …, ticks ×1, 100 | → | ×1 |
| from a left axis they run right | orient left, position 40, line …, ticks ×3, 460 | → | ×3 |
| from a right axis they run left | orient right, position 500, line …, ticks ×1, 460.004 | → | ×1 |
| no ticks, no gridlines | orient bottom, position 0, line …, ticks , 50 | → | |
| a negative length is an error | orient bottom, position 200, line …, ticks ×3, -1 | → | error: length must be a finite number of zero or more |
| a zero length draws each line as a point on the axis | orient bottom, position 200, line …, ticks ×1, 0 | → | ×1 |
| any negative length is refused, however small | orient bottom, position 200, line …, ticks ×1, -0.01 | → | error: length must be a finite number of zero or more |
More from the author
- `numberAxis` for a linear number scale: nice tick values inside the domain (`charts.ticks` niceTicks), placed by `charts.scale` linearScale, labelled by `charts.format` formatTick with just enough decimals for the step (0.1, 0.2, 0.3, never 0.30000000000000004). - `timeAxis` for dates: `charts.ticks` picks the calendar interval (days, weeks, months, quarters, years) that gives about `count` ticks, puts ticks on its boundaries, and `charts.format` formatDate labels them for that interval. - `bandAxis` for categories: a tick at the centre of each band of `charts.scale` bandScale with the same padding as the bars. - `axisFromTicks` builds the geometry from any positions and labels, for scales this group does not cover (a log scale with hand-picked ticks). - `gridLines` runs a line from each tick across the plot.
## Layout (as d3-axis)
The mark points away from the plot: down from a bottom axis, up from a top one, left from a left axis, right from a right one, `tickSize` pixels long. The label sits 3 pixels past the end of the mark (d3's default tick padding). Under a bottom axis it is centred with `dominant-baseline: hanging`; above a top axis centred on its alphabetic baseline; beside a left axis right-aligned (`text-anchor: end`) and vertically centred; beside a right axis left-aligned. `position` is the axis line's fixed coordinate: its y for a bottom or top axis, its x for a left or right one.
Gridlines start on the axis line and run `length` pixels across the plot, the opposite way to the marks.
Every pixel value is rounded to 2 decimal places by `math.round-float`, half away from zero, so the numbers agree to the digit in every language. Labels are not measured here: how wide text is depends on the font, which only the renderer knows; `charts.layout` estimates widths when a layout needs them.
Source: d3-axis (github.com/d3/d3-axis), the conventions for mark direction, padding and anchors.
1.0.1 adds tests; behaviour unchanged.