Создание маркеров для линий

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

В SVG маркеры реализуются через элемент <marker>, который затем применяется к линиям, кривым и полигонам. Библиотека SVG.js предоставляет удобный API для создания и управления такими маркерами без прямой работы с DOM.

Создание маркера в SVG.js

В SVG.js маркер создаётся с помощью метода .marker(), который вызывается у контейнера (обычно draw). Метод принимает ширину, высоту и функцию-инициализатор, в которой описывается содержимое маркера.

const draw = SVG().addTo('#canvas').size(600, 400)

const arrow = draw.marker(10, 10, function(add) {
  add.path('M0,0 L0,10 L10,5 Z').fill('#000')
})

Ключевые параметры:

  • ширина и высота — задают область отображения маркера
  • add — внутренний контейнер для рисования внутри маркера
  • содержимое может быть любым SVG-элементом: path, circle, rect и т.д.

Привязка маркера к линии

После создания маркера он может быть применён к линии с помощью методов:

  • .marker('start', marker)
  • .marker('mid', marker)
  • .marker('end', marker)

Пример:

const line = draw.line(50, 50, 250, 50)
  .stroke({ width: 2, color: '#000' })
  .marker('end', arrow)

Поведение:

  • start — маркер в начале линии
  • mid — маркер на каждом сегменте (для ломаных линий)
  • end — маркер в конце линии

Настройка ориентации маркера

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

arrow.attr({ orient: 'auto' })

Это позволяет маркеру автоматически вращаться в зависимости от направления линии.

Альтернативные значения:

  • auto-start-reverse — автоматически разворачивает маркер в начале линии
  • фиксированный угол (например, 45) — задаёт постоянный поворот

Управление точкой привязки (refX, refY)

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

arrow.attr({
  refX: 10,
  refY: 5
})

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

  • refX — горизонтальное смещение
  • refY — вертикальное смещение
  • обычно центр или край фигуры маркера используется как точка привязки

Правильная настройка этих параметров критична для точного позиционирования стрелок.

Масштабирование маркера

Размер маркера может зависеть от толщины линии или быть фиксированным.

По умолчанию используется система координат strokeWidth, что означает:

  • маркер масштабируется вместе с толщиной линии

Чтобы отключить это поведение:

arrow.attr({
  markerUnits: 'userSpaceOnUse'
})

Режимы:

  • strokeWidth — масштабирование относительно толщины линии
  • userSpaceOnUse — фиксированный размер в координатах SVG

Создание сложных маркеров

Внутри маркера можно размещать несколько элементов:

const complexMarker = draw.marker(20, 20, function(add) {
  add.circle(10).center(10, 10).fill('red')
  add.line(0, 10, 20, 10).stroke({ width: 2, color: 'black' })
})

Такой подход позволяет создавать:

  • комбинированные стрелки
  • индикаторы состояний
  • декоративные окончания линий

Использование маркеров с полилиниями и путями

Маркеры работают не только с прямыми линиями:

const polyline = draw.polyline('50,150 150,150 150,250')
  .fill('none')
  .stroke({ width: 2, color: '#000' })
  .marker('mid', arrow)

Также применимы к path:

const path = draw.path('M50 300 C150 200, 250 400, 350 300')
  .fill('none')
  .stroke({ width: 2, color: '#000' })
  .marker('end', arrow)

Динамическое обновление маркеров

Маркеры можно изменять после создания:

arrow.findOne('path').fill('blue')

Или полностью заменить:

line.marker('end', anotherMarker)

Это позволяет:

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

Переиспользование маркеров

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

const line1 = draw.line(0, 0, 100, 0).marker('end', arrow)
const line2 = draw.line(0, 20, 100, 20).marker('end', arrow)

Преимущества:

  • уменьшение количества DOM-элементов
  • единообразие оформления
  • централизованное управление стилем

Анимация маркеров

SVG.js позволяет анимировать содержимое маркера:

arrow.findOne('path')
  .animate(1000)
  .fill('green')

Или трансформации:

arrow.animate(1000).rotate(360)

Анимация полезна для:

  • визуализации потоков
  • указания направления движения
  • интерактивных схем

Практические сценарии использования

Стрелки направлений

draw.line(100, 100, 300, 100)
  .stroke({ width: 3 })
  .marker('end', arrow)

Двусторонние стрелки

line
  .marker('start', arrow)
  .marker('end', arrow)

Узлы графов

const nodeMarker = draw.marker(8, 8, function(add) {
  add.circle(6).center(4, 4).fill('black')
})

line.marker('mid', nodeMarker)

Индикаторы потока

Комбинация анимации и маркеров позволяет создавать эффект движения вдоль линии.

Особенности производительности

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

Взаимодействие с событиями

Маркеры не всегда напрямую обрабатывают события, так как они привязаны к линиям. Однако можно:

  • обрабатывать события на самой линии
  • отслеживать координаты и вычислять взаимодействие с маркером вручную

Ограничения и нюансы

  • маркеры не являются самостоятельными элементами на сцене
  • их позиционирование полностью зависит от линии
  • не все трансформации применяются одинаково (например, масштаб линии влияет на маркер при strokeWidth)
  • при использовании mid на сложных путях возможны неожиданные ориентации

Расширенные техники

Пользовательские системы координат

arrow.attr({
  viewBox: '0 0 10 10'
})

Позволяет точнее контролировать внутреннюю геометрию маркера.

Использование прозрачности

add.path('M0,0 L10,5 L0,10 Z')
  .fill({ color: '#000', opacity: 0.5 })

Комбинация с градиентами

Маркеры могут содержать градиенты, определённые в SVG:

const gradient = draw.gradient('linear', function(stop) {
  stop.at(0, '#f00')
  stop.at(1, '#00f')
})

add.path('M0,0 L10,5 L0,10 Z').fill(gradient)

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