มาร์กเกอร์ SVG ใน Matplotlib scatter: ตรวจพาธ
ใช้พาธ SVG เรียบง่ายเป็นมาร์กเกอร์ Matplotlib scatter ทำซ้ำการตรวจศูนย์กลาง ทิศทาง ช่อง และขนาดในไฟล์ SVG และ PDF ที่ส่งออกจริง
ในหน้านี้
หากต้องการใช้ SVG เป็นมาร์กเกอร์ Matplotlib scatter แบบกำหนดเอง ให้แปลงรูปทรงที่รองรับเป็น matplotlib.path.Path จัดศูนย์กลางรอบ (0, 0) แล้วส่งอ็อบเจ็กต์นั้นให้ scatter(marker=...) สีและขนาดมาร์กเกอร์จะมาจากกราฟ API มาร์กเกอร์ของ Matplotlib รับ Path ไม่ได้รับเอกสาร SVG ใด ๆ เป็นชื่อไฟล์มาร์กเกอร์
บทเรียนนี้ใช้รูปหลายเหลี่ยมไม่สมมาตรต้นฉบับที่มีช่องสี่เหลี่ยม สคริปต์เต็มด้านล่างรองรับส่วนย่อย SVG ที่ตั้งใจให้เล็ก และปฏิเสธคุณสมบัติที่เก็บไม่ได้ เรารันด้วย Matplotlib 3.10.7 ตรวจพาธ SVG และ PDF ที่บันทึก และตรวจภาพแสดงทั้งสอง
หากต้องการภาพเดียวข้างกราฟส่งออก ใช้คู่มือประกอบภาพ SVG ใน Matplotlib แยก มาร์กเกอร์ scatter ทำซ้ำที่พิกัดข้อมูล จึงต้องตรวจศูนย์กลาง ทิศทาง และขนาดอีกแบบ
1. เริ่มจากรูปที่รองรับหนึ่งรูป
ต้องมี Python พร้อม Matplotlib และ NumPy และสิทธิ์เขียนโฟลเดอร์ผลลัพธ์ ตัวอย่างมี SVG ต้นฉบับ ไม่ต้องดาวน์โหลดไอคอน พิกัดที่วาดสามจุดเป็นค่าประกอบ ไม่ใช่ผลสังเกตงานวิจัย
ตัวแยกวิเคราะห์รับ path เติมดำหนึ่งองค์ประกอบในรูท SVG ที่มี viewBox ทุกเส้นขอบต้องปิดด้วย Z และทุกคู่พิกัดต้องมี M หรือ L ตัวใหญ่ชัดเจน รองรับพาธประกอบที่ขอบนอกกับช่องมีทิศวนตรงข้ามภายใต้ fill-rule="nonzero"
ไม่รับเส้นโค้ง คำสั่งสัมพัทธ์ การแปลงรูป เส้นขอบ CSS หลายพาธ ข้อความ คลิป เกรเดียนต์ และสีอื่น ยังปฏิเสธแอตทริบิวต์รูทเพิ่ม เช่น width และ height ข้อจำกัดทำให้ตัวอย่างตรวจได้ ไม่ใช่ตัวนำเข้า SVG ทั่วไป เก็บสำเนาเดิมก่อนเตรียมอินพุตที่เข้ากัน
Matplotlib Path เก็บจุดยอดและรหัสวาด ไม่ใช่โมเดลสี SVG ทั้งหมด Path API นิยามคำสั่งย้าย เส้น และปิดที่ใช้ หากสัญลักษณ์ต้องมีหลายสีหรือเอฟเฟกต์ ให้เลือกวิธีที่เก็บคุณสมบัติ แทนการทิ้งเงียบ ๆ เพื่อให้เข้าตัวแยกวิเคราะห์
2. รันตัวอย่างมาร์กเกอร์ครบชุด
บันทึกเป็น scatter_marker.py แล้วรัน python scatter_marker.py ในโฟลเดอร์เดียวกัน จะสร้าง marker-output/marker.svg, scatter.svg, scatter.pdf และภาพตัวอย่าง PNG ซอร์ส SVG ที่สร้างและค่ากราฟทั้งหมดอยู่ที่นี่
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 ควรเห็นรูปเดียวกันสามขนาดสีเขียวอมฟ้า มีช่องสี่เหลี่ยมซ้ายและส่วนขั้นบันไดยื่นล่างขวา เทียบมาร์กเกอร์เล็กสุดที่ขนาดใช้งานจริงพร้อมมุมมองขยาย

3. ตรวจศูนย์กลางและกลับ Y ครั้งเดียว
งานต้นทางอยู่ใน x=10…90 และ y=10…70 ศูนย์กลางกรอบคือ (50, 40) ลบศูนย์กลางแล้วได้ขอบเขต −40…40 แนวนอนและ −30…30 แนวตั้ง นี่เป็นศูนย์กลางกรอบ ไม่ใช่จุดศูนย์กลางพื้นที่หรือประมาณสมดุลสายตา
ขั้นบันไดล่างขวามีประโยชน์ เพราะวงกลมสมมาตรจะซ่อนการกลับแนวตั้ง ในระบบพิกัด SVG เริ่มต้น Y บวกชี้ลง สคริปต์เปลี่ยนเครื่องหมาย Y ครั้งเดียวเพื่อรักษาทิศแสดงของต้นทางในกราฟปกติที่ Y ชี้ขึ้น
ทั้งสองเส้นขอบรับการแปลงเดียวกัน ทิศวนสัมพัทธ์ยังตรงข้าม ช่องสี่เหลี่ยมจึงยังเป็นรู กฎเติม nonzero ของ SVG ขึ้นกับทิศขอบ ต้นทาง evenodd ถูกปฏิเสธชัดเจน ไม่ได้ถือว่าเท่ากัน
Matplotlib ปรับ Path ที่จัดศูนย์กลางแล้วให้เป็นมาตรฐาน รุ่นที่ทดสอบได้ช่วง −0.5…0.5 ใน X และ −0.375…0.375 ใน Y จุดที่ส่ง scatter ลงกลางกรอบนั้น เลือก anchor อื่นอย่างตั้งใจสำหรับสัญลักษณ์พิเศษได้ แต่อย่าคิดว่ามุมซ้ายบนของ SVG เป็น anchor ข้อมูลที่เหมาะ
4. อ่านขนาด scatter เป็นพอยต์ยกกำลังสอง
scatter API นิยาม 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 |
นี่เป็นมิติของรูปมาตรฐานนี้ ไม่ใช่พื้นที่ที่ระบาย อัตราส่วน anchor หรือเส้นขอบต่างไปเปลี่ยนความสัมพันธ์พารามิเตอร์ขนาดกับสัญลักษณ์ที่เห็น ในตัวอย่างนี้เพิ่ม s สี่เท่าทำให้ขนาดยาวกว้างสองเท่า
เราตรวจพิกัด SVG ที่บันทึกจริงและคำสั่งพาธเติมสีใน PDF ด้วยค่าคลาดเคลื่อน 0.000001 พอยต์ ทั้งสามมาร์กเกอร์มีกรอบที่คาด ตำแหน่งข้อมูลกลางกรอบ และจุดยอดที่แปลงถูก SVG ไม่มีองค์ประกอบภาพ และ PDF ไม่มีอ็อบเจ็กต์ภาพ
เรายังเรนเดอร์ PDF ที่บันทึกที่ 144 dpi แล้วตรวจสามจุดภายในที่กำหนด ช่องขาว รอยเว้าบนขวาขาว และส่วนยื่นล่างขวาเขียวอมฟ้า SVG จริงในเบราว์เซอร์ผ่านจุดภายในเดียวกัน นี่ตรวจทิศและช่องที่เลือก ไม่อ้างว่าขอบลดรอยหยักเหมือนกันทุกเอนจินหรือรองรับ SVG ใด ๆ
5. แก้สาเหตุก่อนปรับขนาด
| อาการ | ตรวจและแก้ |
|---|---|
| ตัวแยกวิเคราะห์แจ้งคุณสมบัติไม่รองรับ | ตรวจคุณสมบัติที่ระบุ เตรียมรูปหลายเหลี่ยมแยกที่เข้ากัน หรือใช้วิธีแปลงครอบคลุมกว่าแล้วทดสอบ ลบแอตทริบิวต์โดยไม่รักษาผลอาจเปลี่ยนภาพ |
| รูปกลับหัว | ตรวจว่า Y ถูกกลับก่อนฟังก์ชันหรือยัง ใช้การเปลี่ยนครั้งเดียวแล้วเทียบส่วนไม่สมมาตร |
| สัญลักษณ์ห่างจากจุดข้อมูล | ตรวจกรอบกับ anchor ฟังก์ชันจัดกลางขอบภาพโดยไม่สนใจพื้นที่ว่างใน viewBox |
| ช่องถูกเติม | ตรวจทิศวนและกฎเติม เก็บทิศตรงข้ามสำหรับ nonzero ตัวอย่างนี้ ตัวแยกวิเคราะห์ไม่แปลง evenodd |
| ไฟล์บันทึกมีสัญลักษณ์ราสเตอร์ | ตรวจองค์ประกอบหรือทรัพยากรส่งออก ชื่อ SVG/PDF อย่างเดียวไม่ยืนยันว่าทุกองค์ประกอบกราฟเป็นเวกเตอร์ |
| มาร์กเกอร์เล็กรายละเอียดหาย | ดูภาพที่ขนาดสุดท้าย เพิ่มขนาด ลดรายละเอียด หรือใช้มาร์กเกอร์มาตรฐานหากสื่อหมวดหมู่ชัดกว่า |
สำหรับการตรวจไฟล์นอกตัวอย่าง ดูวิธีหาราสเตอร์ใน SVG สำหรับทั้งรูป ตรวจขนาดฟอนต์และความหนาเส้นที่ขนาดตีพิมพ์ พาธถูกต้องก็อาจละเอียดเกิน legend เล็กหรือแผงแน่น
ก่อนใช้มาร์กเกอร์ของคุณในรูปสุดท้าย ตรวจขนาดเล็กที่สุด legend จุดซ้อน และ SVG/PDF ส่งออก เก็บข้อมูล แกน ป้าย และการแมปสเกลในกระบวนการพล็อต ไอคอนกำหนดเองไม่แทนนิยามตัวเลขเหล่านั้น
เมื่องานราสเตอร์ต้องการพาธใหม่
หากสัญลักษณ์ที่เหลือมีเพียงภาพราสเตอร์สีเรียบ กระบวนการแผนภาพวิทยาศาสตร์เป็น 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 แบบ absolute ชัดเจนและเส้นขอบปิด สี รูปทรง การแปลง และองค์ประกอบเพิ่มที่ไม่รองรับจะเกิดข้อผิดพลาด
แหล่งข้อมูล
- Matplotlib scatter API — นิยามขนาด อินพุตมาร์กเกอร์ และผลเส้นขอบ
- Matplotlib 3.10.7 marker API — อธิบาย Path มาร์กเกอร์และพิกัดมาตรฐานในรุ่นทดสอบ
- Matplotlib Path API — นิยามจุดยอด รหัสวาด และเส้นขอบปิด
- ระบบพิกัด SVG — กำหนดทิศพิกัดเริ่มต้นที่ใช้ตรวจทิศทาง
- กฎเติม SVG — อธิบายทิศขอบที่มีผลต่อช่อง nonzero
หากมีเพียงสัญลักษณ์ราสเตอร์ ให้กู้งานแผนภาพที่แก้ไขได้ เทียบขอบและช่องกับต้นทาง แล้วทดสอบพาธที่ปรับในรูปจริงก่อนส่งมอบ
เพิ่มเติมจากบล็อก

SVG ใน LaTeX: เก็บรูปทรงและจัดตัวอักษรป้ายกำกับรูป
เลือกขั้นตอน PDF หรือ PDF ร่วมกับ LaTeX สำหรับภาพประกอบ SVG เก็บป้ายกำกับให้แก้ไขได้ เข้าใจสิ่งที่ต้องพึ่ง Inkscape และตรวจ PDF เอกสารสุดท้าย

ให้พื้นที่ SVG โปร่งใสไม่รับคลิกใน Konva
ให้ส่วนโปร่งใสของภาพ SVG ไม่รับคลิกใน Konva ทำซ้ำการตรวจ hit จากแคช ทดสอบช่องและพื้นหลัง และสร้าง hit ใหม่เมื่อเปลี่ยนต้นทาง
