Topic 6.6
Axes
In one line
An axis is the ruler of a chart: a line, tick marks and labels built from a scale. d3.axisBottom(scale) draws it in one call, and a handful of settings control the tick count, the labels, gridlines, colours and updates.
Think of it like this
The ruler on a workbench. Without its marks you cannot say how long a plank is. The chart's bars or line are the plank; the axis is the ruler that lets a reader turn a length back into a number.
Words you'll meet
New words in this topic, in plain English. Come back here whenever one feels fuzzy.
- axis
- A line with tick marks and labels, drawn from a scale.
- tick size
- The length of the small tick line.
- tick padding
- The gap between a tick line and its label.
- gridline
- A tick drawn across the whole plot so values are easy to follow.
- domain line
- The long line of the axis, from one end of the range to the other.
Step by step
01Plan the margins first
An axis draws at the origin of its group, so each one needs room. Here the left margin is 56 pixels for labels like 120,000 and the bottom margin 28 pixels. If a label is cut off, the margin is too small, not the axis wrong.
02Add two axes
The stage shows the monthly rides of 2025 as twelve dots: the scales place them, but nothing says what the positions mean. d3.axisBottom(x) and d3.axisLeft(y), each called on a <g> moved into place by its margin, add the rulers. The defaults are crowded: thirteen y labels (0 to 120,000 in steps of 10,000), and full month names that touch each other (September, October, November). The first label is 2025, not January: the default format prints the year at the start of a year.
03Turn the y ticks into gridlines
tickSize(-width) stretches each y tick across the plot. The code makes the lines pale and removes the y domain line, so the axis recedes and the data speaks.
04Name the axis, and let CSS own the colour
A title says what the numbers are. The colour comes from a CSS variable on the SVG, and the axis uses currentColor, so the media query for dark mode recolours axes, labels and title together. In the preview, set your computer to dark mode to see it.
Try it yourself
- 1
Ten ticks instead of five
In the first example change
.ticks(5)to.ticks(10). The Console now says 9 ticks (steps of 500).tickValues([0, 1000, 4380])gives exactly your three; use it when the numbers carry a meaning (the busiest day). - 2
Gridlines from the first example
Change the first axis to
d3.axisBottom(x).ticks(5).tickSize(-100). Every tick line, and the two end pieces of the domain line, are now 100 pixels long, pointing up into the empty space (tick line y2: -100; the labels stay put aty: 3): a negative size goes the opposite way. In a chart this draws the gridlines over the plot.
Code & diagrams
d3.create("svg:g") makes a detached <g>, so the example can count an axis's ticks without drawing it. The 0.5 in the tick positions is the axis's half-pixel offset for sharp lines.
Preview
Console
Nothing logged yet.Expected console output
children: path.domain g.tick g.tick g.tick g.tick g.tick
domain path d: M0.5,6V0.5H400.5V6
first two ticks: translate(0.5,0) '0' | translate(91.82420091324201,0) '1,000'
tick line y2: 6 label y: 9 label dy: 0.71em
colours: currentColor currentColor on the group: 10 sans-serif middle
ticks(5): 5 ticks(10): 9 tickValues([0, 1000, 4380]): 3 tickValues([]): 0
domain [0, 0] -> [ '0.000000' ]
band axis ticks centred in their band: translate(50,0) translate(150,0) translate(250,0)
rotated labels: 6 wrapped lines: 12transition().end() resolves when the animation finishes, so the printed ticks are the final ones, not a mid-animation guess.
Preview
Console
Nothing logged yet.Expected console output
year: 4 ticks: 2025 April July October
summer: 3 ticks: June July August
narrow: 3 ticks, last tick at translate(205.55494505494505,0)
stroke: currentColor text fill: currentColor group colour: rgb(29, 43, 54)
after style(color): group colour: rgb(230, 237, 243) the attributes are still: currentColorBreak it on purpose
Errors are the best teachers. Make each change, read the error, guess what went wrong, then reveal the answer.
Break #1
Give an axis a scale with a zero-width domain
In the first example change the first scale to d3.scaleLinear().domain([0, 0]).range([0, 400]), as if every ride count were zero.
Myth vs fact
Myth
Axes are decoration you add at the end.
Fact
Without an axis a reader cannot recover a single number from a chart. Plan the margins first; labels that get cut off are the most common axis bug.
Myth
Rotated labels are fine for long names.
Fact
Rotated text is slow to read. Prefer a sideways bar chart, shorter names or wrapped labels.
Pro corner
Extra depth for experienced readers. New to this? Skip it for now and come back later.
- ▸
The axis joins its ticks to the scale:
selectAll(".tick").data(values, scale). The key is the tick's position under the current scale, so a tick whose value still exists is reused, new values enter, old ones exit, and a transition slides the ticks that move. - ▸
The default
offsetis 0.5 pixels (0 whendevicePixelRatiois above 1) so that one-pixel lines fall on a pixel centre and look sharp. Setaxis.offset(0)if you align other shapes to the tick lines.
Remember this
- 1
The problem this solves: drawing ticks and labels by hand means a loop, a text element and a transform for every tick, again for each chart (Phase 3 did it). An axis reuses the scale you already have: the ticks, their positions and their labels all come from it.
- 2
**What
axisBottom(scale)builds.** (Recall from Topic 6.1:scale.ticks()picks the round numbers andscale.tickFormat()prints them. The axis only calls both and draws the result.) You call it on a<g>:g.call(d3.axisBottom(x)). It adds one<path class="domain">(the line with small end ticks), and for every tick a<g class="tick">moved to the tick's position, holding a<line>and a<text>. The ticks come fromscale.ticks(), the labels fromscale.tickFormat(). The group getsfont-size10,font-familysans-serif andtext-anchor. There are four:axisBottom,axisTop,axisLeftandaxisRight. - 3
You place the axis yourself. An axis draws at the origin. A bottom axis needs
translate(0, height - margin.bottom), a left axistranslate(margin.left, 0). Margins must be big enough for the labels: a y axis with numbers such as60,000needs about 50 pixels on the left, or the labels are cut off. - 4
Ticks and labels.
.ticks(5)forwards its argument to the scale (a count for number scales, an interval such asd3.utcMonthfor time scales)..tickValues([0, 1000, 4380])ignores the scale's ticks and uses yours..tickFormat(d3.format(","))ord3.utcFormat("%b")sets the label text..tickSize(n)sets the line length,.tickSizeOuter(0)removes the ends of the domain line, and.tickPadding(n)moves the labels away. A band axis puts each tick in the middle of its band. - 5
Gridlines are tall ticks.
.tickSize(-width)makes every tick line run back across the plot area. Then lighten them (stroke-opacity) and remove the domain line withg.select(".domain").remove(). A gridline is a ruler's mark, so keep it quiet. - 6
Long labels. 25 station names will not fit under bars. Turn the chart sideways (names on the left axis), rotate them (
transform="rotate(-40)"andtext-anchor: end), or wrap them into<tspan>lines. Rotation is the hardest to read; wrapping suits two or three words. - 7
Theme and updates. The axis paints with
currentColor(the CSScolorof the group or a parent), so one CSS variable recolours a whole axis for dark mode, with no change to the code. To update an axis, change the scale's domain and call the axis again: D3 matches ticks by position, keeps those that still exist, adds new ones and removes old ones. Callingg.transition().call(axis)animates it.
Explain it without notes
What does d3.axisBottom(x) add to the page?
How do you draw gridlines with an axis?
How do you recolour an axis for dark mode?
Practice
Make a left axis for rides (0 to 4,380) with 5 ticks, labels like 1,000, gridlines 460 pixels long, and no domain line.
Write setAxis(g, axis, domain) that updates a bottom axis to a new domain with a 300 ms transition, and returns a promise that resolves when it is done.
Trade-offs
- ↔
Gridlines help reading exact values but add ink. Use them on charts where values are read off, and skip them on shape-focused charts.
Done when you can
I can add a bottom and a left axis, placed with a margin.
I can set the tick count, values and format.
I can draw pale gridlines and remove the domain line.
I can wrap or rotate long labels.