Дискретные (ordinal) масштабы в D3.js предназначены для отображения значений из конечного набора категорий в соответствующие значения выходного диапазона. В отличие от линейных или логарифмических масштабов, работающих с непрерывными числовыми данными, ordinal-скейлы ориентированы на категориальные данные, где важен сам факт принадлежности к классу, а не числовое расстояние между значениями.
Основная идея заключается в том, что каждому уникальному значению из входного множества сопоставляется элемент выходного множества по принципу индексации или соответствия в словаре.
Функция d3.scaleOrdinal() создаёт масштаб, который
сопоставляет дискретные значения из домена (domain) с дискретными
значениями диапазона (range).
const scale = d3.scaleOrdinal()
.domain(["A", "B", "C"])
.range([10, 20, 30]);
В этом примере:
Если передать значение, отсутствующее в домене, поведение зависит от конфигурации масштаба.
Ordinal scale работает как ассоциативное отображение:
Если domain явно не задан, D3 строит его автоматически на основе входных данных при первом вызове масштаба.
Одной из ключевых особенностей является автоматическое накопление домена:
const scale = d3.scaleOrdinal()
.range(["red", "green", "blue"]);
scale("apple"); // "red"
scale("banana"); // "green"
scale("cherry"); // "blue"
scale("apple"); // "red" (уже закреплённое соответствие)
Здесь domain формируется динамически:
["apple", "banana", "cherry"]
Порядок появления данных определяет соответствие.
Если входное значение не встречалось ранее:
Пример:
const scale = d3.scaleOrdinal()
.range(["#1f77b4", "#ff7f0e"]);
scale("A"); // "#1f77b4"
scale("B"); // "#ff7f0e"
scale("C"); // "#1f77b4" (повтор диапазона)
Если range короче domain, начинается циклическое повторение значений.
Явная конфигурация позволяет полностью контролировать соответствия:
const scale = d3.scaleOrdinal()
.domain(["low", "medium", "high"])
.range(["#2ca02c", "#ff7f0e", "#d62728"]);
Такой подход используется, когда требуется строгая семантика категорий.
Ordinal scales часто применяются для цветового кодирования категорий:
const color = d3.scaleOrdinal()
.domain(["Chrome", "Firefox", "Safari", "Edge"])
.range(d3.schemeCategory10);
Здесь используется встроенная палитра D3:
d3.schemeCategory10 — 10 контрастных цветовПример применения:
svg.selectAll("circle")
.data(browsersData)
.enter()
.append("circle")
.attr("cx", d => x(d.value))
.attr("cy", d => y(d.count))
.attr("r", 5)
.attr("fill", d => color(d.browser));
Ordinal scale отличается от других типов масштабов:
d3.scaleLinear()
d3.scaleLog()
d3.scaleOrdinal()
Если domain становится больше range, начинается повторение значений:
const scale = d3.scaleOrdinal()
.range(["A", "B"]);
scale("x1"); // "A"
scale("x2"); // "B"
scale("x3"); // "A"
scale("x4"); // "B"
Это поведение может быть полезным для:
Но в визуализации данных часто приводит к неоднозначности, особенно при большом количестве категорий.
Ordinal scale сохраняет соответствия между вызовами:
const scale = d3.scaleOrdinal()
.range(["red", "blue"]);
scale("cat"); // red
scale("dog"); // blue
scale("cat"); // red (то же значение)
Система работает как внутренний словарь:
domain → range mapping
Это важно для стабильности визуализаций при повторных рендерах.
Масштаб можно настраивать и читать:
scale.domain(); // получить текущий domain
scale.range(); // получить текущий range
Изменение:
scale.domain(["A", "B", "C"]);
scale.range([1, 2, 3]);
При изменении domain ранее установленные соответствия могут быть переопределены.
D3 позволяет задать значение по умолчанию:
const scale = d3.scaleOrdinal()
.domain(["A", "B"])
.range(["red", "blue"])
.unknown("gray");
Если передать неизвестный элемент:
scale("Z"); // "gray"
Это важно при работе с неполными или грязными данными.
Ordinal scale применяется в задачах, где данные представляют категории:
Пример группировки:
const groupColor = d3.scaleOrdinal()
.domain(["admin", "user", "guest"])
.range(["#e74c3c", "#3498db", "#2ecc71"]);
Ordinal scale часто применяется не только к цветам, но и к другим атрибутам:
const strokeScale = d3.scaleOrdinal()
.domain(["A", "B", "C"])
.range(["solid", "dashed", "dotted"]);
const sizeScale = d3.scaleOrdinal()
.domain(["small", "medium", "large"])
.range([3, 6, 10]);
Хотя обе конструкции работают с категориями, они решают разные задачи:
d3.scaleOrdinal() → произвольное отображение
значенийd3.scaleBand() → распределение по оси с
позиционированием и ширинойOrdinal scale не отвечает за геометрию размещения, только за соответствие значений.
Поведение можно представить как структуру:
Map<domain_value, range_value>
При первом вызове:
if value not in map:
assign next range item
store mapping
return mapped value
Эта логика делает ordinal scale детерминированным после первичного заполнения.
При динамических данных важно учитывать:
Пример:
const scale = d3.scaleOrdinal()
.range(["red", "green"]);
scale("A");
scale("B");
scale.range(["blue", "yellow"]);
scale("A"); // "red" или "blue" зависит от внутреннего состояния
Из-за этого изменение range в динамических системах требует осторожности.
Для стабильных графиков обычно применяются следующие подходы:
const categories = ["A", "B", "C", "D"];
const scale = d3.scaleOrdinal()
.domain(categories)
.range(d3.schemeTableau10);
Это исключает непредсказуемое поведение при обновлении данных.
При работе с API или JSON-структурами ordinal scale часто используется для:
data.map(d => ({
...d,
color: colorScale(d.category)
}));
Если domain не задан и не накоплен:
const scale = d3.scaleOrdinal()
.range(["red", "blue"]);
scale("A"); // "red"
domain автоматически формируется:
["A"]
Если range не задан:
const scale = d3.scaleOrdinal();
scale("A"); // undefined
Ordinal scale не гарантирует:
Его задача — исключительно отображение категорий в значения без математической интерпретации между ними.
Ordinal scale часто становится основой для построения легенд:
const legendItems = scale.domain().map(d => ({
label: d,
color: scale(d)
}));
Каждый элемент domain соответствует одному визуальному элементу интерфейса.
Ordinal scale обладает высокой производительностью:
Это делает его подходящим для больших категориальных наборов данных.