Marcatori SVG in Matplotlib: verifica il tracciato
Usa un semplice tracciato SVG come marcatore scatter in Matplotlib. Riproduci i controlli di centro, orientamento, fori e dimensioni negli SVG e PDF esportati.
In questa pagina
Per usare un SVG come marcatore personalizzato in un grafico a dispersione di Matplotlib, converti la geometria supportata in un matplotlib.path.Path, centrala su (0, 0) e passa l'oggetto a scatter(marker=...). Il colore e le dimensioni del marcatore vengono poi impostati nel grafico. L'API dei marcatori accetta un Path; non accetta il nome di un file SVG arbitrario come marcatore.
Questo tutorial usa un poligono asimmetrico originale con un foro rettangolare. Lo script completo qui sotto gestisce un sottoinsieme volutamente ridotto di SVG e rifiuta le funzionalità che non riesce a preservare. Lo abbiamo eseguito con Matplotlib 3.10.7, esaminato i tracciati SVG e PDF salvati e verificato entrambe le uscite renderizzate.
Se vuoi affiancare un'illustrazione a un grafico esportato, consulta la guida alla composizione di elementi SVG con Matplotlib. Un marcatore scatter si ripete alle coordinate dei dati, quindi centro, orientamento e dimensioni richiedono controlli diversi.
1. Parti da una forma supportata
Servono Python con Matplotlib e NumPy disponibili e il permesso di scrivere in una cartella di output. L'esempio include il suo SVG originale, quindi non occorre scaricare un'icona. Le tre coordinate tracciate sono valori illustrativi, non osservazioni di ricerca.
Il parser accetta esattamente un path con riempimento nero dentro una radice SVG dotata di viewBox. Ogni contorno deve chiudersi con Z; ogni coppia di coordinate richiede un comando esplicito M o L maiuscolo. Supporta un tracciato composto con contorno esterno e foro avvolti in direzioni opposte usando fill-rule="nonzero".
Rifiuta curve, comandi relativi, trasformazioni, tratti, CSS, più elementi path, testo, ritaglio, gradienti e altri colori. Rifiuta anche attributi aggiuntivi della radice come width e height. Questa limitazione rende l'esempio verificabile: non è un importatore SVG generico. Conserva una copia intatta della grafica prima di preparare un input compatibile.
Un Path di Matplotlib memorizza vertici e codici di disegno, anziché l'intero modello di rappresentazione SVG. L'API Path definisce le operazioni di spostamento, linea e chiusura usate qui. Se il simbolo richiede più colori o effetti, scegli un flusso che li preservi invece di eliminarli senza avvertimenti per adattarlo a questo parser.
2. Esegui l'esempio completo
Salva questo codice come scatter_marker.py, poi esegui python scatter_marker.py nella stessa cartella. Crea marker-output/marker.svg, scatter.svg, scatter.pdf e un'anteprima PNG. Qui sono inclusi il sorgente SVG generato e tutte le impostazioni del grafico.
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)Apri sia scatter.svg sia scatter.pdf. Dovresti vedere tre versioni verde petrolio della stessa forma: un foro rettangolare a sinistra e una sporgenza a gradino verso il basso a destra. Confronta il marcatore più piccolo anche alle dimensioni di visualizzazione previste, oltre che ingrandito.

3. Verifica il centro e inverti Y una sola volta
La grafica sorgente occupa x=10…90 e y=10…70. Il centro del rettangolo di delimitazione è (50, 40). Sottraendolo si ottengono limiti di −40…40 in orizzontale e −30…30 in verticale. È una centratura basata sul rettangolo di delimitazione, non sul centroide o su una stima dell'equilibrio visivo.
Il gradino in basso a destra è utile perché un cerchio simmetrico nasconderebbe un ribaltamento verticale. Nel sistema di coordinate SVG iniziale, Y positivo punta verso il basso. Lo script nega Y una sola volta per mantenere l'orientamento visibile del sorgente nel normale grafico con Y crescente verso l'alto.
Entrambi i contorni ricevono la stessa trasformazione. Le loro direzioni relative restano opposte, quindi l'apertura rettangolare rimane un foro. La regola di riempimento nonzero di SVG dipende dalla direzione dei contorni; qui un sorgente evenodd viene esplicitamente rifiutato anziché trattato come equivalente.
Matplotlib normalizza poi questo Path centrato. Nella versione provata, il risultato si estende da −0.5…0.5 in X e da −0.375…0.375 in Y. Il punto passato a scatter cade al centro di quei limiti. Puoi scegliere deliberatamente un altro ancoraggio per un simbolo particolare, ma non presumere che l'angolo superiore sinistro dell'SVG sorgente sia un ancoraggio adatto ai dati.
4. Leggi la dimensione scatter in punti quadrati
L'API scatter definisce s in punti quadrati, con un punto tipografico pari a 1/72 di pollice. Un bordo del marcatore diverso da zero può aumentarne l'estensione visibile. L'esempio imposta linewidths=0 e edgecolors="none" per limitare il controllo dimensionale alla geometria riempita.
Per questo marcatore normalizzato, la larghezza è un'unità marcatore e l'altezza è 0,75 unità. La larghezza salvata è quindi sqrt(s) punti e l'altezza è 0.75 * sqrt(s) punti.
s (punti quadrati) | Larghezza prevista e misurata | Altezza prevista e misurata |
|---|---|---|
| 400 | 20 pt | 15 pt |
| 1600 | 40 pt | 30 pt |
| 3600 | 60 pt | 45 pt |
Queste sono le dimensioni di questa specifica forma normalizzata, non la sua area dipinta. Un rapporto d'aspetto, un ancoraggio o un tratto del bordo diversi cambiano la relazione tra il parametro dimensionale e il simbolo visibile. Quadruplicare s raddoppia le dimensioni in questo esempio.
Abbiamo esaminato le coordinate SVG effettivamente salvate e le operazioni di tracciato riempito del PDF con una tolleranza numerica di 0,000001 punti. Tutti e tre i marcatori avevano i limiti previsti, la posizione centrata sui dati e i vertici trasformati. L'SVG salvato non conteneva elementi immagine e il PDF non conteneva oggetti immagine.
Abbiamo anche renderizzato il PDF salvato a 144 dpi e controllato tre posizioni interne dichiarate: il foro era bianco, l'incavo in alto a destra era bianco e la sporgenza in basso a destra era verde petrolio. Il rendering nativo del browser dell'SVG effettivo ha superato gli stessi controlli interni. Questo verifica l'orientamento e l'apertura scelti; non implica bordi antialias identici tra renderer o il supporto per qualsiasi grafica SVG.
5. Correggi la causa prima di cambiare dimensione
| Sintomo | Controllo e soluzione |
|---|---|
| Il parser segnala una funzionalità non supportata | Esamina la funzionalità indicata. Prepara un input poligonale compatibile separato oppure usa e verifica un metodo di conversione più completo. Eliminare un attributo senza preservarne l'effetto può cambiare la grafica. |
| La forma è capovolta | Controlla se Y fosse già stato invertito prima di questa funzione. Applica una volta il cambio di coordinate, poi confronta un dettaglio asimmetrico. |
| Il simbolo è distante dal punto dati | Esamina i limiti e l'ancoraggio scelto. Questa funzione centra i limiti della grafica indipendentemente dallo spazio vuoto nel viewBox. |
| Il foro si riempie | Esamina le direzioni dei contorni e la regola di riempimento. Mantieni direzioni opposte per questo campione con riempimento nonzero; il parser non converte la geometria evenodd. |
| Il file salvato contiene un simbolo raster | Esamina gli elementi o le risorse esportate. La sola estensione SVG o PDF non dimostra che ogni elemento del grafico sia vettoriale. |
| I marcatori piccoli perdono dettagli utili | Valuta la figura alle dimensioni finali. Ingrandisci il marcatore, semplifica il simbolo o usa un marcatore predefinito se comunica la categoria con maggiore chiarezza. |
Per ispezionare file oltre questo campione, consulta come trovare contenuti raster negli SVG. Per l'intera figura, verifica dimensione dei caratteri e spessore delle linee alla misura di pubblicazione; anche un tracciato corretto può essere troppo complesso per una piccola legenda o un pannello affollato.
Prima di usare il tuo marcatore in una figura definitiva, verifica la dimensione minima, l'aspetto in legenda, i punti sovrapposti e gli SVG/PDF esportati. Mantieni dati, assi, etichette e corrispondenza della scala nel flusso di creazione del grafico. Un'icona personalizzata non sostituisce queste definizioni numeriche.
Quando la grafica raster richiede un nuovo tracciato
Se del simbolo rimane soltanto un disegno raster piatto, il flusso di conversione dei diagrammi scientifici in SVG di PerfectVector può aiutarti a recuperare grafica modificabile. Confronta il contorno recuperato e ogni apertura con l'originale prima di adattarlo alla procedura dei marcatori. Se hai già un SVG pulito, mantienilo come sorgente.
Il recupero vettoriale non ricostruisce i dati alla base del grafico e non garantisce la compatibilità con questo parser limitato. Conserva fotografie e illustrazioni a tono continuo quando il loro aspetto raster fa parte della figura, e scegli un metodo di posizionamento basato su immagini adatto a quel contenuto.
Domande frequenti
Posso passare direttamente il nome di un file SVG a scatter di Matplotlib? No. L'API dei marcatori accetta descrizioni supportate, tra cui un Path di Matplotlib. Questo esempio converte un tracciato SVG limitato in quell'oggetto prima di chiamare scatter.
Perché il mio marcatore personalizzato è capovolto? L'SVG sorgente e il grafico possono usare direzioni Y diverse. Questo esempio nega una volta la Y dell'SVG; esamina un dettaglio asimmetrico per evitare di applicare il ribaltamento due volte.
s equivale all'area dipinta della mia forma SVG? Non usarlo come valore misurato dell'area dipinta. Scatter definisce s in punti quadrati, mentre la forma normalizzata e il tratto del bordo ne determinano le dimensioni visibili. Questo campione ha larghezze di 20, 40 e 60 punti con s pari a 400, 1600 e 3600.
Questo codice preserva ogni funzionalità SVG? No. Supporta un solo tracciato nero con riempimento nonzero, comandi assoluti espliciti M, L e Z e contorni chiusi. Stili di rappresentazione, comandi geometrici, trasformazioni ed elementi aggiuntivi non supportati generano un errore.
Fonti
- API scatter di Matplotlib — Definisce il parametro dimensionale, l'input del marcatore e l'interazione con lo spessore del bordo.
- API dei marcatori di Matplotlib 3.10.7 — Documenta i marcatori Path e le coordinate normalizzate per la versione provata.
- API Path di Matplotlib — Definisce vertici, codici di disegno e contorni chiusi.
- Sistemi di coordinate SVG — Stabilisce le direzioni iniziali delle coordinate usate nel controllo dell'orientamento.
- Regole di riempimento SVG — Spiega come la direzione dei contorni influisce sull'apertura con riempimento nonzero.
Per un simbolo disponibile solo in raster, recupera la grafica del diagramma in forma modificabile, confronta il contorno e i fori con il sorgente e prova il tracciato adattato nella figura effettiva prima della consegna.
Altro dal blog

SVG in LaTeX: conserva forme e componi le etichette
Scegli una procedura PDF o PDF più LaTeX per l'illustrazione SVG. Mantieni modificabili le etichette, comprendi le dipendenze Inkscape e controlla il PDF finale.

Ignora le aree SVG trasparenti nei clic di Konva
Escludi dai clic le aree trasparenti SVG in Konva. Riproduci il rilevamento dalla cache, prova fori e sfondi e ricrea l'area dopo i cambi di sorgente.
