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

# Marcadores SVG no Matplotlib: confira o caminho

> Use um caminho SVG simples como marcador de dispersão no Matplotlib. Reproduza verificações de centro, orientação, furos e tamanho em exportações SVG e PDF reais.

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

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

Para usar um SVG como marcador personalizado de dispersão no Matplotlib, converta a geometria suportada em um `matplotlib.path.Path`, centralize-a em `(0, 0)` e passe esse objeto para `scatter(marker=...)`. A cor e o tamanho do marcador vêm então do gráfico. A [API de marcadores aceita um Path](https://matplotlib.org/3.10.7/api/markers_api.html); não recebe um documento SVG arbitrário como nome de arquivo de marcador.

Este tutorial usa um polígono assimétrico original com um furo retangular. O script completo abaixo trata um subconjunto deliberadamente pequeno de SVG e rejeita recursos que não consegue preservar. Nós o executamos com Matplotlib 3.10.7, inspecionamos os caminhos SVG e PDF salvos e conferimos os dois resultados renderizados.

Se você quer uma ilustração ao lado de um gráfico exportado, use o [guia separado de composição de arte SVG no Matplotlib](https://perfectvector.com/pt-BR/blog/matplotlib-svg-artwork). Um marcador de dispersão se repete nas coordenadas dos dados, por isso centro, orientação e tamanho exigem outra verificação.

## 1. Comece com uma forma suportada

Você precisa de Python com Matplotlib e NumPy disponíveis, além de permissão para gravar em um diretório de saída. O exemplo inclui seu SVG original, então não é necessário baixar um ícone. As três coordenadas desenhadas são valores ilustrativos, não observações de pesquisa.

O analisador aceita exatamente um `path` com preenchimento preto dentro de uma raiz SVG com `viewBox`. Cada contorno precisa fechar com `Z`; cada par de coordenadas exige um `M` ou `L` maiúsculo explícito. Ele suporta um caminho composto com contorno externo e furo percorridos em sentidos opostos usando `fill-rule="nonzero"`.

Ele rejeita curvas, comandos relativos, transformações, traços, CSS, vários elementos path, texto, recorte, gradientes e outras cores. Também rejeita atributos adicionais na raiz, como `width` e `height`. Essa restrição torna o exemplo inspecionável; não é um importador SVG geral. Preserve uma cópia intacta da arte antes de preparar uma entrada compatível.

Um Path do Matplotlib armazena vértices e códigos de desenho, não o modelo completo de pintura SVG. A [API Path](https://matplotlib.org/stable/api/path_api.html) define as operações de movimento, linha e fechamento usadas aqui. Se seu símbolo precisa de várias cores ou efeitos, escolha um fluxo que preserve esses recursos em vez de descartá-los silenciosamente para caber neste analisador.

## 2. Execute o exemplo completo do marcador

Salve como `scatter_marker.py` e execute `python scatter_marker.py` no mesmo diretório. Ele cria `marker-output/marker.svg`, `scatter.svg`, `scatter.pdf` e uma prévia PNG. O código-fonte SVG gerado e todas as configurações do gráfico estão incluídos aqui.

```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)
```

Abra tanto `scatter.svg` quanto `scatter.pdf`. Você deve ver três versões verde-azuladas da mesma forma: um furo retangular à esquerda e um degrau que se estende para baixo à direita. Compare o menor marcador no tamanho pretendido de exibição, além da visualização ampliada.

![Exportações SVG e PDF reais do Matplotlib mostrando o marcador assimétrico original, seu furo e três tamanhos verificados](https://perfectvector.com/blog/matplotlib-svg-scatter-marker/scatter-proof.webp)

*Forma de teste original e exportações realmente salvas pelo Matplotlib 3.10.7. A vista do PDF é uma renderização do arquivo vetorial salvo; os dados são ilustrativos.*

## 3. Confira o centro e inverta Y uma única vez

A arte de origem ocupa `x=10…90` e `y=10…70`. O centro de sua caixa delimitadora é `(50, 40)`. Subtrair esse centro resulta em limites de `−40…40` na horizontal e `−30…30` na vertical. Essa é uma centralização pela caixa delimitadora, não um centroide ou uma estimativa de equilíbrio visual.

O degrau inferior direito da origem é útil porque um círculo simétrico esconderia uma inversão vertical. No [sistema de coordenadas SVG](https://www.w3.org/TR/SVG2/coords.html) inicial, Y positivo aponta para baixo. O script inverte o sinal de Y uma vez para preservar a orientação exibida da origem no gráfico normal em que Y cresce para cima.

Os dois contornos recebem a mesma transformação. Seus sentidos relativos continuam opostos, então a abertura retangular permanece como furo. A [regra de preenchimento nonzero do SVG](https://www.w3.org/TR/SVG2/painting.html#FillRuleProperty) depende da direção dos contornos; uma origem `evenodd` é explicitamente rejeitada aqui em vez de ser tratada como equivalente.

Depois, o Matplotlib normaliza esse Path centralizado. Na versão testada, o resultado vai de `−0.5…0.5` em X e de `−0.375…0.375` em Y. O ponto fornecido a `scatter` fica no centro desses limites. Você pode escolher intencionalmente outra âncora para um símbolo específico, mas não suponha que o canto superior esquerdo do SVG de origem seja uma âncora adequada para os dados.

## 4. Leia o tamanho de dispersão em pontos ao quadrado

A [API scatter](https://matplotlib.org/stable/api/_as_gen/matplotlib.pyplot.scatter.html) define `s` em pontos ao quadrado, com um ponto tipográfico igual a 1/72 de polegada. Uma borda de marcador diferente de zero pode ampliar a extensão visível. O exemplo define `linewidths=0` e `edgecolors="none"` para que a verificação de tamanho trate da geometria preenchida.

Neste marcador normalizado, a largura é uma unidade de marcador e a altura é 0,75 unidade. Sua largura salva é, portanto, `sqrt(s)` pontos, e a altura é `0.75 * sqrt(s)` pontos.

| `s` (pontos ao quadrado) | Largura esperada e medida | Altura esperada e medida |
| ------------------------ | ------------------------- | ------------------------ |
| 400                      | 20 pt                     | 15 pt                    |
| 1600                     | 40 pt                     | 30 pt                    |
| 3600                     | 60 pt                     | 45 pt                    |

Essas são as dimensões desta forma normalizada específica, não sua área pintada. Outra proporção, âncora ou traço de borda muda a relação entre o parâmetro de tamanho e o símbolo visível. Aumentar `s` quatro vezes dobra as dimensões neste exemplo.

Inspecionamos as coordenadas SVG realmente salvas e as operações de caminhos preenchidos do PDF com tolerância numérica de 0,000001 ponto. Os três marcadores tinham os limites esperados, posicionamento centralizado nos dados e vértices transformados. O SVG salvo não continha elementos de imagem, e o PDF não continha objetos de imagem.

Também renderizamos o PDF salvo a 144 dpi e conferimos três posições internas declaradas: o furo estava branco, o recuo superior direito estava branco e a extensão inferior direita estava verde-azulada. A renderização nativa do SVG real no navegador passou pelas mesmas verificações internas. Isso verifica a orientação e a abertura escolhidas; não afirma bordas com antisserrilhamento idênticas entre renderizadores nem suporte a qualquer arte SVG.

## 5. Corrija a causa antes de ajustar o tamanho

| Sintoma                                         | Verificação e correção                                                                                                                                                                                |
| ----------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| O analisador gera erro de recurso não suportado | Inspecione o recurso indicado. Prepare uma entrada poligonal compatível separada ou use e teste um método de conversão mais completo. Remover um atributo sem preservar seu efeito pode mudar a arte. |
| A forma está de cabeça para baixo               | Confira se Y já foi invertido antes desta função. Aplique a mudança de coordenadas uma vez e compare uma característica assimétrica.                                                                  |
| O símbolo fica longe do ponto de dados          | Inspecione seus limites e a âncora escolhida. Esta função centraliza os limites da arte, independentemente do espaço vazio no `viewBox`.                                                              |
| O furo fica preenchido                          | Inspecione as direções dos contornos e a regra de preenchimento. Mantenha sentidos opostos nesta forma de teste nonzero; o analisador não converte geometria evenodd.                                 |
| O arquivo salvo contém um símbolo raster        | Inspecione os elementos ou recursos exportados. Só ter um nome de arquivo SVG ou PDF não comprova que todos os elementos desenhados sejam vetoriais.                                                  |
| Marcadores pequenos perdem detalhes úteis       | Revise a figura no tamanho final. Aumente o marcador, simplifique o símbolo ou use um marcador integrado se ele comunicar a categoria com mais clareza.                                               |

Para inspecionar arquivos além desta forma de teste, veja [como encontrar conteúdo raster dentro de SVG](https://perfectvector.com/pt-BR/blog/svg-contains-raster-image). Para a figura inteira, revise [tamanho da fonte e espessura das linhas no tamanho de publicação](https://perfectvector.com/pt-BR/blog/scientific-figure-font-size-line-width); um caminho correto de marcador ainda pode ser complexo demais para uma legenda pequena ou um painel cheio.

Antes de usar seu próprio marcador em uma figura final, confira seu menor tamanho, a aparência na legenda, os pontos sobrepostos e os SVG/PDF exportados. Mantenha os dados, eixos, rótulos e mapeamento de escala no fluxo de criação do gráfico. Um ícone personalizado não substitui essas definições numéricas.

## Quando a arte raster precisa de um novo caminho

Se o símbolo existe apenas como desenho raster plano, o [fluxo de diagrama científico para SVG](https://perfectvector.com/pt-BR/scientific-diagram-to-svg) do PerfectVector pode ajudar a recuperar arte editável. Compare o contorno recuperado e cada abertura com o original antes de adaptá-lo ao processo de marcadores. Preserve um SVG existente e limpo como origem quando já tiver um.

A recuperação vetorial não reconstrói os dados subjacentes do gráfico nem garante compatibilidade com este analisador restrito. Preserve fotografias e ilustrações de tom contínuo quando sua aparência raster fizer parte da figura, e escolha um método de posicionamento baseado em imagens adequado ao conteúdo.

## Perguntas frequentes

**Posso passar um nome de arquivo SVG diretamente para scatter do Matplotlib?**
Não. A API de marcadores aceita descrições de marcador suportadas, incluindo um Path do Matplotlib. Este exemplo converte um caminho SVG restrito nesse objeto antes de chamar scatter.

**Por que meu marcador personalizado está de cabeça para baixo?**
O SVG de origem e o gráfico podem usar direções Y diferentes. Este exemplo inverte o sinal de Y do SVG uma vez; inspecione uma característica assimétrica para não aplicar a inversão duas vezes.

**s equivale à área pintada da minha forma SVG?**
Não o use como um valor medido de área pintada. Scatter define s em pontos ao quadrado, enquanto a forma normalizada e o traço da borda determinam as dimensões visíveis. Esta forma de teste tem larguras de 20, 40 e 60 pontos com s de 400, 1600 e 3600.

**Este código preserva todos os recursos SVG?**
Não. Ele suporta um único caminho preto com preenchimento nonzero, comandos absolutos explícitos M, L e Z e contornos fechados. Pintura, comandos geométricos, transformações e elementos adicionais não suportados geram erro.

## Fontes

1. [API scatter do Matplotlib](https://matplotlib.org/stable/api/_as_gen/matplotlib.pyplot.scatter.html) — Define o parâmetro de tamanho, a entrada do marcador e a interação com a largura da borda.
2. [API de marcadores do Matplotlib 3.10.7](https://matplotlib.org/3.10.7/api/markers_api.html) — Documenta marcadores Path e coordenadas normalizadas para a versão testada.
3. [API Path do Matplotlib](https://matplotlib.org/stable/api/path_api.html) — Define vértices, códigos de desenho e contornos fechados.
4. [Sistemas de coordenadas SVG](https://www.w3.org/TR/SVG2/coords.html) — Estabelece as direções iniciais usadas na verificação de orientação.
5. [Regras de preenchimento SVG](https://www.w3.org/TR/SVG2/painting.html#FillRuleProperty) — Explica como a direção do contorno afeta a abertura com preenchimento nonzero.

Para um símbolo disponível só como raster, [recupere a arte editável do diagrama](https://perfectvector.com/pt-BR/scientific-diagram-to-svg), compare o contorno e os furos com a origem e teste o caminho adaptado na figura real antes da entrega.

## Explore

- [Read the HTML article](https://perfectvector.com/pt-BR/blog/matplotlib-svg-scatter-marker)
- [SVG no LaTeX: preserve formas e componha os rótulos](https://perfectvector.com/pt-BR/blog/svg-in-latex.md)
- [Ignore áreas SVG transparentes nos cliques do Konva](https://perfectvector.com/pt-BR/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)
