Текст в SVG: text, tspan, textPath

SVG предоставляет набор векторных примитивов для графики, и текстовые элементы занимают в нём особое место. В отличие от HTML, где текст — это потоковый контент, в SVG текст является графическим объектом с координатной привязкой и набором атрибутов позиционирования. В D3.js работа с текстом строится поверх стандартных SVG-элементов <text>, <tspan> и <textPath>, позволяя динамически формировать надписи, сложные подписи и текст вдоль кривых.

Базовый текстовый элемент создаётся через SVG-тег <text>, который в D3 обычно добавляется через selection.append.

d3.select("svg")
  .append("text")
  .attr("x", 50)
  .attr("y", 50)
  .text("Простой текст");

SVG-координаты работают в пиксельной системе пользовательского пространства. Атрибуты x и y задают начальную точку текста, но визуальное положение зависит также от выравнивания и метрик шрифта.


Координатное позиционирование текста

SVG-текст по умолчанию привязывается к базовой линии шрифта. Это означает, что значение y не соответствует верхнему краю символов.

Ключевые атрибуты:

  • x, y — координаты начальной точки
  • dx, dy — смещение относительно базовой позиции
  • text-anchor — горизонтальное выравнивание
  • dominant-baseline — вертикальное выравнивание

Пример центрирования текста:

d3.select("svg")
  .append("text")
  .attr("x", 200)
  .attr("y", 100)
  .attr("text-anchor", "middle")
  .attr("dominant-baseline", "middle")
  .text("Центр");

Значение text-anchor может принимать:

  • start — выравнивание по началу строки
  • middle — центрирование
  • end — выравнивание по правому краю

Комбинация text-anchor и dominant-baseline позволяет точно контролировать положение подписи относительно геометрических объектов диаграммы.


Работа с динамическим текстом в D3

D3.js связывает данные с DOM-элементами, включая текстовые узлы. Метод .text() может принимать как строку, так и функцию, возвращающую строку на основе данных.

const data = [
  { name: "A", value: 10 },
  { name: "B", value: 25 }
];

d3.select("svg")
  .selectAll("text.label")
  .data(data)
  .enter()
  .append("text")
  .attr("class", "label")
  .attr("x", d => d.value * 10)
  .attr("y", (d, i) => 30 + i * 20)
  .text(d => `${d.name}: ${d.value}`);

Текст в SVG не ограничен строкой фиксированной длины, однако отсутствие автоматического переноса требует явного управления разбиением.


Многострочный текст через <tspan>

SVG не поддерживает автоматический перенос строк, поэтому многострочные надписи реализуются через элемент <tspan>. Он позволяет разбивать текст на части с индивидуальным позиционированием.

const text = d3.select("svg")
  .append("text")
  .attr("x", 50)
  .attr("y", 50);

text.append("tspan")
  .attr("x", 50)
  .attr("dy", "0em")
  .text("Первая строка");

text.append("tspan")
  .attr("x", 50)
  .attr("dy", "1.2em")
  .text("Вторая строка");

text.append("tspan")
  .attr("x", 50)
  .attr("dy", "1.2em")
  .text("Третья строка");

Ключевая особенность:

  • dy задаёт смещение относительно предыдущей строки
  • повторение x фиксирует горизонтальное выравнивание
  • без повторения x текст будет смещаться относительно предыдущего tspana

При работе с динамическими данными tspan часто создаются через data join:

const lines = ["строка 1", "строка 2", "строка 3"];

const text = d3.select("svg")
  .append("text")
  .attr("x", 100)
  .attr("y", 100);

text.selectAll("tspan")
  .data(lines)
  .enter()
  .append("tspan")
  .attr("x", 100)
  .attr("dy", (d, i) => i === 0 ? "0em" : "1.2em")
  .text(d => d);

Управление типографикой SVG-текста

SVG-текст поддерживает стандартные CSS-стили, включая:

  • font-size
  • font-family
  • font-weight
  • fill
  • letter-spacing
d3.select("svg")
  .append("text")
  .attr("x", 50)
  .attr("y", 50)
  .style("font-size", "18px")
  .style("font-family", "Arial")
  .style("fill", "#333")
  .text("Стилизованный текст");

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


Текст вдоль кривых: <textPath>

SVG позволяет размещать текст вдоль произвольного пути. Для этого используется элемент <textPath>, который привязывает строку к геометрии <path>.

Сначала определяется путь:

const svg = d3.select("svg");

svg.append("defs")
  .append("path")
  .attr("id", "curvePath")
  .attr("d", "M 50 200 C 150 50, 250 350, 350 200");

Затем создаётся текст, связанный с этим путём:

svg.append("text")
  .append("textPath")
  .attr("href", "#curvePath")
  .attr("startOffset", "50%")
  .attr("text-anchor", "middle")
  .text("Текст вдоль кривой");

Параметр startOffset управляет положением текста вдоль линии:

  • 0% — начало пути
  • 50% — центр
  • 100% — конец

В современных SVG используется атрибут href, хотя в некоторых окружениях встречается xlink:href.


Динамическое управление текстом на пути

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

svg.select("textPath")
  .transition()
  .duration(1000)
  .attr("startOffset", "80%");

Также можно изменять сам путь, что автоматически перестраивает расположение текста:

svg.select("#curvePath")
  .attr("d", "M 50 250 C 150 100, 250 400, 350 250");

Текст при этом следует новой геометрии без дополнительных вычислений.


Комбинация <text>, <tspan> и <textPath> в сложных визуализациях

В сложных диаграммах эти элементы часто используются совместно. <text> задаёт контейнер, <tspan> формирует структурированные блоки, а <textPath> применяется для привязки к геометрии.

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

const label = svg.append("text");

label.append("textPath")
  .attr("href", "#curvePath")
  .attr("startOffset", "50%")
  .append("tspan")
  .text("Основной заголовок");

label.append("tspan")
  .attr("x", 200)
  .attr("dy", "1.5em")
  .text("Дополнительная подпись");

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


Выравнивание и контроль базовой линии

SVG-рендеринг текста зависит от метрик шрифта, поэтому точное позиционирование требует управления базовой линией.

Атрибут dominant-baseline может принимать значения:

  • hanging
  • middle
  • baseline
  • central
d3.select("svg")
  .append("text")
  .attr("x", 100)
  .attr("y", 100)
  .attr("dominant-baseline", "middle")
  .attr("text-anchor", "middle")
  .text("Точная центровка");

При создании осевых подписей и меток шкал это критически важно, поскольку визуальная симметрия зависит от шрифтовых метрик, а не только от координат.


Манипуляции с dx и dy для тонкой типографики

Атрибуты dx и dy позволяют смещать текст без изменения основных координат.

d3.select("svg")
  .append("text")
  .attr("x", 100)
  .attr("y", 100)
  .text("Базовый")
  .append("tspan")
  .attr("dx", "10")
  .attr("dy", "-5")
  .text("смещённый фрагмент");

Это используется для:

  • создания надстрочных и подстрочных элементов
  • корректировки плотности текста
  • визуального выравнивания с графическими объектами

Текст как элемент визуальной кодировки данных

В D3 текст часто выступает не просто подписью, а носителем данных. Значения могут кодироваться через содержимое строки, размер, цвет или позицию.

d3.select("svg")
  .selectAll("text")
  .data(data)
  .enter()
  .append("text")
  .attr("x", d => d.value * 5)
  .attr("y", 50)
  .style("fill", d => d.value > 20 ? "red" : "black")
  .text(d => d.value);

Такая модель делает текст частью визуального канала представления информации, наравне с линиями и фигурами.


Ограничения и особенности рендеринга

SVG-текст имеет ряд особенностей, влияющих на поведение в визуализациях:

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

При проектировании визуализаций эти ограничения компенсируются использованием <tspan>, вычислением размеров текста и динамическим перераспределением координат через D3.