Build a bottom axis


In the previous lesson, we learned how to manage margins effectively in our chart. Now, let's explore how to create a AxisBottom react component that draws a bottom axis!

Free
8 minutes read

🔍 More about scaleLinear()

In the previous lessons we talked a lot about the scaleLinear() function of d3.js.

You should perfectly understand the code below. If not, go back to the scale module of this course!

const xScale = d3.scaleLinear()
  .domain([0, 100])
  .range([0, 500]);

console.log(xScale(0))    // 0
console.log(xScale(100))  // 500

What I haven't mentioned yet is that the xScale function includes a few additional methods that are quite useful:

  • xScale.range() returns the range of the scale, which is [0, 500] in this case.
  • xScale.ticks(10) generates an array of approximately 10 evenly spaced values along the axis. This function is quite intelligent, producing nicely rounded numbers, which can be a lifesaver.
  • xScale.domain() provides the input domain of the scale ([0, 100])

Example 🧐

xScale.ticks(2)  // [0, 50, 100]
xScale.ticks(5)  // [0, 20, 40, 60, 80]
xScale.ticks(9)  // [0, 10, 20, 30, 40, 50, 60, 70, 80, 90, 100]
xScale.ticks(10) // [0, 10, 20, 30, 40, 50, 60, 70, 80, 90, 100]

See?

The .ticks() method doesn't always return the exact number of ticks you specify. Instead, it identifies the most suitable value close to your target to ensure your axis looks polished and visually appealing!

Let's draw! ✏️

Now that we know where the ticks are going to be, we just need to draw a long horizontal line, and a multitude of small ticks with their labels at those positions!

Here is a sandbox with a very minimal example. Take a bit of time to read the code carefully!



  • The horizontal line is made using a line element that takes the full boundsWidth.
  • xScale.ticks() is used to start a loop: 1 iteration per tick!
  • For each tick, a g element wraps a line and a text element forming the tick.

🎁 Reusable Bottom Axis Component

This bottom axis will likely be used across multiple charts in your project, so let’s develop a reusable component named AxisBottom.

The AxisBottom component accepts several properties:

  • xScale: The scale that the axis will represent.
  • pixelsPerTick: Instead of specifying the number of ticks, it's better to define the pixels per tick. This approach ensures a consistent appearance, regardless of whether the chart is displayed on a large screen or a mobile device!
// AxisBottom.tsx

import { ScaleLinear } from 'd3';

type AxisBottomProps = {
  xScale: ScaleLinear<number, number>;
  pixelsPerTick: number;
};

// tick length
const TICK_LENGTH = 6;

export const AxisBottom = ({ xScale, pixelsPerTick }: AxisBottomProps) => {
  const range = xScale.range();

  const width = range[1] - range[0];
  const numberOfTicksTarget = Math.floor(width / pixelsPerTick);

  return (
    <>
      {/* Main horizontal line */}
      <line
        x1={range[0]}
        y1={0}
        x2={range[1]}
        y2={0}
        stroke="currentColor"
        fill="none"
      />

      {/* Ticks and labels */}
      {xScale.ticks(numberOfTicksTarget).map((value) => (
        <g key={value} transform={`translate(${xScale(value)}, 0)`}>
          <line y2={TICK_LENGTH} stroke="currentColor" />
          <text
            key={value}
            style={{
              fontSize: '10px',
              textAnchor: 'middle',
              transform: 'translateY(20px)',
            }}
          >
            {value}
          </text>
        </g>
      ))}
    </>
  );
};

↕️ Positioning the Axis

Now that we have a functional axis, it needs to be positioned correctly—at the bottom of the bounding area.

To achieve this, we can wrap the BottomAxis call in a g element and apply a vertical translation. Here’s what the code looks like:

<g transform={`translate(0, ${boundsHeight})`}>
    <AxisBottom xScale={xScale} pixelsPerTick={60} />
</g>

Exercices

We got axes! 🪓

If you've followed the previous exercises, you now know how to add a bottom axis to your graph.

Adding a left axis works in much the same way! Wrap it in an AxisLeftcomponent, and you're good to go!

Take a moment to review the example code below:

0246810

This axis is rendered without using d3.js to render.

code for the Y axis react component
import { ScaleLinear } from 'd3';

type AxisLeftProps = {
  yScale: ScaleLinear<number, number>;
  pixelsPerTick: number;
};

// tick length
const TICK_LENGTH = 6;

export const AxisLeft = ({ yScale, pixelsPerTick }: AxisLeftProps) => {
  const range = yScale.range();
  const height = range[0] - range[1];
  const numberOfTicksTarget = Math.floor(height / pixelsPerTick);

  return (
    <>
      {/* Main vertical line */}
      <path
        d={['M', 0, range[0], 'L', 0, range[1]].join(' ')}
        fill="none"
        stroke="currentColor"
      />

      {/* Ticks and labels */}
      {yScale.ticks(numberOfTicksTarget).map((value, i) => (
        <g key={value} transform={`translate(0, ${yScale(value)})`}>
          <line x2={-TICK_LENGTH} stroke="currentColor" />
          <text
            key={value}
            style={{
              fontSize: '10px',
              textAnchor: 'middle',
              transform: 'translateX(-20px)',
            }}
          >
            {value}
          </text>
        </g>
      ))}
    </>
  );
};