> [Site index](https://perfectvector.com/llms.txt) — available pages and descriptions.

# Маркеры SVG в Matplotlib scatter: проверьте контур

> Используйте простой контур SVG как маркер scatter в Matplotlib. Повторите проверки центра, ориентации, отверстий и размера в реальных экспортах SVG и PDF.

Source: https://perfectvector.com/ru/blog/matplotlib-svg-scatter-marker

Language: ru
Author: Irene Kim
Published: 2026-10-11T08:00:00+09:00
Updated: 2026-10-11T08:00:00+09:00

Чтобы использовать SVG как собственный маркер scatter в Matplotlib, преобразуйте поддерживаемую геометрию в `matplotlib.path.Path`, отцентрируйте вокруг `(0, 0)` и передайте объект в `scatter(marker=...)`. Цвет и размер маркера затем задаются графиком. [API маркеров Matplotlib принимает Path](https://matplotlib.org/3.10.7/api/markers_api.html), а не имя произвольного документа SVG.

В руководстве используется собственный асимметричный многоугольник с прямоугольным отверстием. Полный скрипт ниже обрабатывает намеренно малую часть SVG и отвергает возможности, которые не может сохранить. Мы запускали его с Matplotlib 3.10.7, проверили сохранённые контуры SVG и PDF и обе отрисовки.

Если нужна одна иллюстрация рядом с экспортированным графиком, используйте отдельное [руководство по композиции SVG в Matplotlib](https://perfectvector.com/ru/blog/matplotlib-svg-artwork). Маркер scatter повторяется в координатах данных, поэтому центр, ориентация и размер требуют иной проверки.

## 1. Начните с одной поддерживаемой фигуры

Нужны Python с доступными Matplotlib и NumPy и право записи выходной папки. Собственный SVG включён в пример, поэтому скачивать значок не нужно. Три нанесённые координаты — иллюстративные значения, а не исследовательские наблюдения.

Парсер принимает ровно один залитый чёрным `path` внутри корневого SVG с `viewBox`. Каждое очертание должно замыкаться `Z`; перед каждой парой координат нужна явная заглавная `M` или `L`. Поддерживается составной контур с внешней границей и отверстием противоположного направления при `fill-rule="nonzero"`.

Он отвергает кривые, относительные команды, преобразования, обводки, CSS, несколько элементов path, текст, обтравку, градиенты и другие цвета. Также запрещены дополнительные корневые атрибуты вроде `width` и `height`. Ограничение делает пример проверяемым, но не универсальным импортёром SVG. Сохраните нетронутую копию рисунка до подготовки совместимого входа.

Matplotlib Path хранит вершины и коды рисования, а не полную модель окрашивания SVG. [Path API](https://matplotlib.org/stable/api/path_api.html) определяет использованные операции перемещения, линии и замыкания. Если символу нужны разные цвета или эффекты, выберите сохраняющий их процесс вместо молчаливого удаления ради этого парсера.

## 2. Запустите полный пример маркера

Сохраните как `scatter_marker.py`, затем выполните `python scatter_marker.py` в той же папке. Скрипт создаёт `marker-output/marker.svg`, `scatter.svg`, `scatter.pdf` и предпросмотр PNG. Исходник создаваемого SVG и все настройки графика включены здесь.

```python
from pathlib import Path as FilePath
import re
import xml.etree.ElementTree as ET

import matplotlib
matplotlib.use("Agg")
import matplotlib.pyplot as plt
from matplotlib.path import Path
import numpy as np

SVG = '''<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 80">
  <path fill="#000000" fill-rule="nonzero"
        d="M10 10 L70 10 L70 30 L90 30 L90 70 L10 70 Z
           M25 25 L25 55 L45 55 L45 25 Z"/>
</svg>'''


def svg_marker(svg_text):
    """One black, nonzero-filled path; explicit absolute M/L/Z only."""
    namespace = "{http://www.w3.org/2000/svg}"
    root = ET.fromstring(svg_text)
    if root.tag != namespace + "svg" or set(root.attrib) != {"viewBox"}:
        raise ValueError("Use only an SVG root with a viewBox")
    view = np.array([float(value) for value in root.attrib["viewBox"].split()])
    if view.shape != (4,) or not np.isfinite(view).all() or (view[2:] <= 0).any():
        raise ValueError("Invalid viewBox")
    if len(root) != 1 or root[0].tag != namespace + "path":
        raise ValueError("Use exactly one path, with no groups or other elements")
    node = root[0]
    if any((text or "").strip() for text in [root.text, node.text, node.tail]):
        raise ValueError("Text content is not supported")
    if set(node.attrib) - {"d", "fill", "fill-rule"} or len(node):
        raise ValueError("Unsupported path attribute or child element")
    if node.get("fill", "black") not in {"black", "#000", "#000000"}:
        raise ValueError("Use one solid black fill")
    if node.get("fill-rule", "nonzero") != "nonzero":
        raise ValueError("Use nonzero winding; evenodd is not converted here")
    data = node.get("d", "")
    pattern = r"[MLZ]|[-+]?(?:\d*\.\d+|\d+\.?)(?:[eE][-+]?\d+)?"
    tokens = re.findall(pattern, data)
    if re.sub(pattern, "", data).strip(" ,\n\r\t"):
        raise ValueError("Only explicit absolute M, L and Z commands are supported")
    vertices, codes = [], []
    index, start, open_contour, lines = 0, None, False, 0
    while index < len(tokens):
        command = tokens[index]
        if command in {"M", "L"}:
            if index + 2 >= len(tokens) or tokens[index + 1] in {"M", "L", "Z"} or tokens[index + 2] in {"M", "L", "Z"}:
                raise ValueError("Each M/L needs one explicit coordinate pair")
            point = tuple(float(value) for value in tokens[index + 1:index + 3])
            if not np.isfinite(point).all():
                raise ValueError("Coordinates must be finite")
            if command == "M":
                if open_contour:
                    raise ValueError("Close each contour with Z")
                start, open_contour, lines = point, True, 0
                codes.append(Path.MOVETO)
            else:
                if not open_contour:
                    raise ValueError("Start each contour with M")
                lines += 1
                codes.append(Path.LINETO)
            vertices.append(point)
            index += 3
        elif command == "Z":
            if not open_contour or lines < 2:
                raise ValueError("A closed contour needs at least three vertices")
            vertices.append(start)
            codes.append(Path.CLOSEPOLY)
            open_contour = False
            index += 1
        else:
            raise ValueError("Repeat M/L explicitly before coordinate pairs")
    if open_contour or not vertices:
        raise ValueError("Supply nonempty closed contours")
    path = Path(np.array(vertices), codes)
    box = path.get_extents()
    if box.width <= 0 or box.height <= 0:
        raise ValueError("Path needs nonzero width and height")
    if box.x0 < view[0] or box.y0 < view[1] or box.x1 > view[0] + view[2] or box.y1 > view[1] + view[3]:
        raise ValueError("Path outside viewBox: resolve clipping before import")
    center = np.array([(box.x0 + box.x1) / 2, (box.y0 + box.y1) / 2])
    centered = (path.vertices - center) * np.array([1, -1])
    return Path(centered, path.codes)


if __name__ == "__main__":
    output = FilePath("marker-output")
    output.mkdir(exist_ok=True)
    (output / "marker.svg").write_text(SVG)
    marker = svg_marker(SVG)
    plt.rcParams.update({"svg.fonttype": "none", "pdf.fonttype": 42,
                         "font.family": "DejaVu Sans", "font.size": 11})
    fig, ax = plt.subplots(figsize=(8, 3), dpi=144)
    fig.subplots_adjust(left=.1, right=.96, bottom=.24, top=.8)
    ax.scatter([1, 2, 3], [1.3, 2.4, 1.6], s=[400, 1600, 3600],
               marker=marker, color="#2f6977", linewidths=0, edgecolors="none")
    ax.set(xlim=(0, 4), ylim=(0, 4), xlabel="Illustrative x", ylabel="Illustrative y",
           title="Original SVG marker: s = 400, 1600, 3600 points squared")
    ax.spines[["top", "right"]].set_visible(False)
    fig.savefig(output / "scatter.svg", format="svg")
    fig.savefig(output / "scatter.pdf", format="pdf")
    fig.savefig(output / "scatter.png", format="png")
    plt.close(fig)
```

Откройте `scatter.svg` и `scatter.pdf`. Вы должны увидеть три бирюзовые версии одной фигуры: прямоугольное отверстие слева и ступеньку, выступающую вправо вниз. Сравните самый маленький маркер в нужном размере отображения и при увеличении.

![Реальные экспорты Matplotlib в SVG и PDF с исходным асимметричным маркером, его отверстием и тремя проверенными размерами](https://perfectvector.com/blog/matplotlib-svg-scatter-marker/scatter-proof.webp)

*Собственный образец и реальные сохранённые экспорты Matplotlib 3.10.7. Вид PDF — отрисовка сохранённого векторного файла; значения данных иллюстративны.*

## 3. Проверьте центр и один раз переверните Y

Исходный рисунок занимает `x=10…90` и `y=10…70`. Центр ограничивающей рамки — `(50, 40)`. Вычитание центра даёт границы `−40…40` по горизонтали и `−30…30` по вертикали. Это центрирование по рамке, а не центроид или оценка визуального равновесия.

Нижняя правая ступенька полезна: симметричный круг скрыл бы вертикальный переворот. В начальной [системе координат SVG](https://www.w3.org/TR/SVG2/coords.html) положительная Y направлена вниз. Скрипт один раз меняет знак Y, сохраняя видимую ориентацию источника на обычном графике с Y вверх.

Оба очертания получают одинаковое преобразование. Их взаимное направление остаётся противоположным, поэтому прямоугольный просвет остаётся отверстием. [Правило заливки nonzero SVG](https://www.w3.org/TR/SVG2/painting.html#FillRuleProperty) зависит от направления; источник `evenodd` явно отвергается, а не считается эквивалентным.

Затем Matplotlib нормализует отцентрированный Path. В проверенной версии результат занимает `−0.5…0.5` по X и `−0.375…0.375` по Y. Точка, переданная в `scatter`, попадает в центр этих границ. Для особого символа можно намеренно выбрать другую точку привязки, но не считайте верхний левый угол SVG подходящей привязкой к данным.

## 4. Читайте размер scatter в квадратных пунктах

[API scatter](https://matplotlib.org/stable/api/_as_gen/matplotlib.pyplot.scatter.html) определяет `s` в квадратных пунктах; типографский пункт равен 1/72 дюйма. Ненулевая обводка может увеличить видимые границы маркера. В примере заданы `linewidths=0` и `edgecolors="none"`, чтобы проверять размер залитой геометрии.

Для этого нормализованного маркера ширина равна одной единице маркера, высота — 0,75 единицы. Поэтому сохранённая ширина равна `sqrt(s)` пунктов, а высота — `0.75 * sqrt(s)` пунктов.

| `s` (квадратные пункты) | Ожидаемая и измеренная ширина | Ожидаемая и измеренная высота |
| ----------------------- | ----------------------------- | ----------------------------- |
| 400                     | 20 pt                         | 15 pt                         |
| 1 600                   | 40 pt                         | 30 pt                         |
| 3 600                   | 60 pt                         | 45 pt                         |

Это размеры конкретной нормализованной фигуры, а не её закрашенная площадь. Другое соотношение сторон, привязка или обводка меняют связь параметра размера с видимым символом. Увеличение `s` в четыре раза удваивает размеры в этом примере.

Мы проверили реальные координаты сохранённого SVG и операции залитых контуров PDF с числовым допуском 0,000001 пункта. Все три маркера имели ожидаемые границы, центрированное размещение и преобразованные вершины. Сохранённый SVG не содержал элементов изображений, а PDF — объектов изображений.

Мы также отрисовали сохранённый PDF при 144 dpi и проверили три заданные внутренние точки: отверстие было белым, верхний правый вырез — белым, нижний правый выступ — бирюзовым. Нативная браузерная отрисовка настоящего SVG прошла те же проверки. Это подтверждает выбранные ориентацию и отверстие, но не утверждает одинаковые сглаженные края разных движков или поддержку произвольных SVG.

## 5. Исправьте причину до изменения размера

| Симптом                                      | Проверка и исправление                                                                                                                                                                                   |
| -------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Парсер сообщает неподдерживаемую возможность | Изучите названную возможность. Подготовьте отдельный совместимый многоугольник либо используйте более полный метод конвертации и испытайте его. Удаление атрибута без сохранения эффекта меняет рисунок. |
| Фигура перевёрнута                           | Проверьте, не переворачивалась ли Y до этой функции. Измените координаты один раз, затем сравните асимметричную деталь.                                                                                  |
| Символ смещён от точки данных                | Проверьте границы и привязку. Функция центрирует границы рисунка независимо от пустого места в `viewBox`.                                                                                                |
| Отверстие залилось                           | Проверьте направления очертаний и правило заливки. Для этого образца nonzero сохраняйте противоположные направления; парсер не преобразует evenodd.                                                      |
| В сохранённом файле символ растровый         | Изучите экспортированные элементы или ресурсы. Одно расширение SVG или PDF не доказывает векторность всех элементов графика.                                                                             |
| Малые маркеры теряют полезные детали         | Проверьте рисунок в итоговом размере. Увеличьте маркер, упростите символ или используйте встроенный маркер, если он яснее передаёт категорию.                                                            |

Для проверки других файлов см. [поиск растра внутри SVG](https://perfectvector.com/ru/blog/svg-contains-raster-image). Для всей иллюстрации проверьте [размер шрифта и толщину линий в публикационном размере](https://perfectvector.com/ru/blog/scientific-figure-font-size-line-width): даже верный контур маркера может быть слишком сложным для маленькой легенды или тесной панели.

До использования своего маркера в готовой иллюстрации проверьте минимальный размер, вид легенды, перекрывающиеся точки и экспорт SVG/PDF. Данные, оси, подписи и отображение масштаба оставляйте в процессе построения графика. Собственный значок не заменяет эти числовые определения.

## Когда растровому рисунку нужен новый контур

Если сохранился только плоский растровый символ, [процесс научной схемы в SVG](https://perfectvector.com/ru/scientific-diagram-to-svg) PerfectVector поможет восстановить редактируемый рисунок. Сравните очертание и каждое отверстие с оригиналом до адаптации к маркерам. Если чистый SVG уже есть, используйте его как источник.

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

## Частые вопросы

**Можно ли передать имя SVG напрямую в Matplotlib scatter?**
Нет. API принимает поддерживаемые описания маркеров, в том числе Matplotlib Path. Здесь ограниченный контур SVG преобразуется в такой объект до вызова scatter.

**Почему собственный маркер перевёрнут?**
SVG и график могут использовать разные направления Y. Пример один раз меняет знак SVG Y; проверяйте асимметричную деталь, чтобы не перевернуть дважды.

**Равно ли s закрашенной площади моей фигуры SVG?**
Не используйте его как измеренную закрашенную площадь. Scatter определяет s в квадратных пунктах, а нормализованная фигура и обводка задают видимые размеры. У этого образца ширина равна 20, 40 и 60 пунктам при s 400, 1600 и 3600.

**Сохраняет ли код все возможности SVG?**
Нет. Поддерживается один залитый чёрным контур nonzero с явными абсолютными M, L и Z и замкнутыми очертаниями. Неподдерживаемые окрашивание, команды геометрии, преобразования и дополнительные элементы вызывают ошибку.

## Источники

1. [Matplotlib scatter API](https://matplotlib.org/stable/api/_as_gen/matplotlib.pyplot.scatter.html) — Определяет размер, входной маркер и влияние толщины края.
2. [API маркеров Matplotlib 3.10.7](https://matplotlib.org/3.10.7/api/markers_api.html) — Описывает маркеры Path и нормализованные координаты проверенной версии.
3. [Matplotlib Path API](https://matplotlib.org/stable/api/path_api.html) — Определяет вершины, коды рисования и замкнутые очертания.
4. [Системы координат SVG](https://www.w3.org/TR/SVG2/coords.html) — Задаёт исходные направления координат для проверки ориентации.
5. [Правила заливки SVG](https://www.w3.org/TR/SVG2/painting.html#FillRuleProperty) — Объясняет влияние направления контуров на отверстие при nonzero.

Для растрового символа [восстановите редактируемую схему](https://perfectvector.com/ru/scientific-diagram-to-svg), сравните очертание и отверстия с источником и испытайте адаптированный контур в настоящей иллюстрации до передачи.

## Explore

- [Read the HTML article](https://perfectvector.com/ru/blog/matplotlib-svg-scatter-marker)
- [SVG в LaTeX: сохраните фигуры и наберите подписи](https://perfectvector.com/ru/blog/svg-in-latex.md)
- [Как исключить прозрачные области SVG из кликов Konva](https://perfectvector.com/ru/blog/konva-alpha-hit-region.md)
- [Product facts](https://perfectvector.com/product-facts.md)
- [FAQ](https://perfectvector.com/faq.md)
- [Pricing and credits](https://perfectvector.com/pricing.md)
