Генераторы осей: axisTop, axisBottom, axisLeft, axisRight

Генераторы осей в D3.js предназначены для автоматического построения визуальных шкал на основе переданных масштабов (scales). Они не создают оси как геометрические объекты напрямую, а формируют набор SVG-элементов (линий, подписей, делений), привязанных к выбранной шкале и её текущему состоянию.

Основой работы генераторов осей является функция масштаба, определяющая соответствие между значениями данных и координатами в пикселях. Без масштаба генератор оси не имеет смыслового контекста, поскольку не может определить ни диапазон значений, ни их позиционирование.


Каждый генератор оси в D3 возвращает функцию, которая применяется к выбранному SVG-контейнеру через selection.call(...). Внутри этой функции D3 создаёт или обновляет DOM-элементы оси:

  • основную линию (domain)
  • деления (ticks)
  • подписи значений (tick labels)

Генератор не хранит DOM напрямую, он описывает правила построения оси на основе масштаба.


axisBottom

axisBottom(scale) создаёт горизонтальную ось, где подписи располагаются снизу от линии оси.

Типичное применение — ось X в декартовой системе координат.

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

const xAxis = d3.axisBottom(xScale);

svg.append("g")
  .attr("transform", "translate(0, 300)")
  .call(xAxis);

Особенности:

  • подписи располагаются под осью
  • направление делений вниз
  • используется нижнее позиционирование группы <g> через transform

axisTop

axisTop(scale) формирует горизонтальную ось с подписями сверху.

Применяется в случаях, когда ось X должна находиться в верхней части графика.

const xAxisTop = d3.axisTop(xScale);

svg.append("g")
  .attr("transform", "translate(0, 0)")
  .call(xAxisTop);

Особенности:

  • подписи располагаются над линией оси
  • деления направлены вниз (в сторону графика)
  • часто используется для зеркальных или вторичных осей

axisLeft

axisLeft(scale) создаёт вертикальную ось, ориентированную слева направо.

Наиболее распространённый вариант оси Y.

const yScale = d3.scaleLinear()
  .domain([0, 100])
  .range([300, 0]);

const yAxis = d3.axisLeft(yScale);

svg.append("g")
  .attr("transform", "translate(50, 0)")
  .call(yAxis);

Особенности:

  • подписи располагаются слева от оси
  • деления направлены вправо
  • важно учитывать инверсию диапазона range([height, 0])

axisRight

axisRight(scale) строит вертикальную ось, размещённую справа.

Используется для дополнительной шкалы или вторичной метрики.

const yAxisRight = d3.axisRight(yScale);

svg.append("g")
  .attr("transform", "translate(450, 0)")
  .call(yAxisRight);

Особенности:

  • подписи располагаются справа от линии
  • деления направлены влево
  • часто применяется для сравнительных графиков с двумя осями Y

Связь осей с масштабами

Генераторы осей полностью зависят от типа масштаба:

  • scaleLinear — равномерные числовые оси
  • scaleTime — временные оси
  • scaleBand — категориальные оси
  • scaleLog — логарифмические оси

Тип масштаба определяет не только значения, но и характер делений.


Настройка делений (ticks)

Генераторы осей позволяют управлять плотностью и форматом делений.

Количество делений

const xAxis = d3.axisBottom(xScale)
  .ticks(10);

D3 может игнорировать точное число, если оно конфликтует с читаемостью.


Форматирование подписей

const xAxis = d3.axisBottom(xScale)
  .tickFormat(d => `${d}%`);

или с использованием форматеров:

const xAxis = d3.axisBottom(xScale)
  .tickFormat(d3.format(".2f"));

Явные значения делений

const xAxis = d3.axisBottom(xScale)
  .tickValues([10, 20, 50, 80, 100]);

Этот режим полностью переопределяет автоматическую генерацию делений.


Геометрия и позиционирование осей

Все оси строятся внутри SVG-группы <g>. Позиционирование осуществляется через transform, а не через координаты отдельных элементов.

Пример базовой структуры:

<g class="x-axis">
  <line class="domain"></line>
  <g class="tick">
    <line></line>
    <text></text>
  </g>
</g>

Для горизонтальных и вертикальных осей критично соблюдать корректное смещение:

  • bottom: translate(0, height)
  • top: translate(0, 0)
  • left: translate(0, 0)
  • right: translate(width, 0)

Обновление осей при изменении данных

Оси не статичны. При изменении масштаба их необходимо перерисовывать.

xScale.domain([0, newMax]);

svg.select(".x-axis")
  .transition()
  .duration(750)
  .call(d3.axisBottom(xScale));

При этом D3 автоматически обновляет:

  • количество делений
  • их положение
  • подписи

Стилизация осей

Внешний вид осей контролируется через CSS:

.axis path {
  stroke: #000;
}

.axis line {
  stroke: #ccc;
}

.axis text {
  font-size: 12px;
  fill: #333;
}

Часто ось domain делают более заметной, а вспомогательные линии — приглушёнными.


Комбинирование нескольких осей

Графики часто содержат несколько осей одновременно:

svg.append("g")
  .attr("class", "x-axis")
  .attr("transform", "translate(0, 300)")
  .call(d3.axisBottom(xScale));

svg.append("g")
  .attr("class", "y-axis")
  .attr("transform", "translate(50, 0)")
  .call(d3.axisLeft(yScale));

svg.append("g")
  .attr("class", "y-axis-right")
  .attr("transform", "translate(450, 0)")
  .call(d3.axisRight(yScale2));

Такой подход позволяет строить многомерные визуализации с разными шкалами.


Внутреннее устройство генераторов

Каждый генератор оси в D3 реализует паттерн конфигурации через замыкания. Вызов d3.axisBottom(scale) возвращает функцию с состоянием:

  • ссылка на scale
  • параметры ticks
  • форматирование
  • размеры tickSize

Изменение параметров не создаёт новый объект, а модифицирует существующий генератор.


Различия axisBottom / axisTop и axisLeft / axisRight

Ключевое различие определяется не только направлением подписи, но и логикой ориентации:

  • горизонтальные оси используют шкалу X
  • вертикальные — шкалу Y
  • различается направление tick marks
  • различается позиционирование текста относительно линии

При этом математическая модель остаётся одинаковой — различие только в визуальном представлении.


Поведение при нестандартных масштабах

Для scaleBand оси работают с центрами диапазонов категорий:

const xScale = d3.scaleBand()
  .domain(["A", "B", "C"])
  .range([0, 300])
  .padding(0.2);

const xAxis = d3.axisBottom(xScale);

Здесь деления соответствуют не числам, а категориям, что меняет плотность и структуру ticks.


Практическая структура осей в сложных графиках

В более сложных визуализациях оси часто отделяются логически:

  • отдельные группы <g> для каждой оси
  • независимые трансформации
  • синхронизация через shared scale state
  • обновление через единый render-процесс

Это позволяет масштабировать графики без разрушения DOM-структуры.


Производительность обновления осей

D3 минимизирует DOM-операции:

  • повторно использует tick nodes
  • обновляет только изменившиеся значения
  • пересчитывает layout при изменении scale

Однако при большом числе тиков может возникать нагрузка, особенно при анимации.


Поведение при изменении размера SVG

При ресайзе контейнера необходимо пересчитывать:

  • диапазон scale.range()
  • позицию осей через transform
  • обновление генераторов axis

Без этого оси остаются визуально некорректными относительно графика.