SVG
节点
在PID
组件中,通常需要SVG
类型的图片作为元素进行使用,通过 gojs
的 geometry
进行描述图形
SVG
标签
SVG
是一种用于描述二维矢量图形的 XML
格式,每个元素都是一个独立的对象
SVG
标签的解析
对于.SVG
类型的图片,都有对应的 SVG
的代码,代码通常由一系列的标签组成:
<?xml version="1.0" encoding="UTF-8"?>
<!-- Do not edit this file with editors other than draw.io -->
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" width="41px"
height="96px" viewBox="-0.5 -0.5 41 96"
content="<mxfile host="app.diagrams.net"">
<defs />
<g>
<g>
<rect x="0" y="0" width="40" height="95" fill="none" stroke="none" pointer-events="all" />
<path
d="M 40 7.66 L 40 87.34 C 40 91.57 31.05 95 20 95 C 8.95 95 0 91.57 0 87.34 L 0 7.66 C 0 3.43 8.95 0 20 0 C 31.05 0 40 3.43 40 7.66Z M 0 17.62 L 40 17.62 M0 77.38 L 40 77.38 M 0 17.62 L 40 77.38 M 0 77.38 L 40 17.62"
fill="rgb(255, 255, 255)" stroke="rgb(0, 0, 0)" />
</g>
</g>
</svg>
能够解析完成的只有以下几种标签:
path
:路径是由一系列直线和曲线段组成的复杂形状,在图形编程中,路径可以用来创建各种复杂的图形和轮廓rect
:矩形是一个四边形,其所有内角都是直角(90度)circle
:圆,由一个固定点(圆心)和固定距离(半径)的所有点组成ellipse
:椭圆,由两个焦点和到这两个焦点的距离之和为常数的所有点组成line
:线是一条直的、没有宽度的几何图形polygon
:多边形是一个由直线边组成的封闭平面图形polyline
:折线是由一系列相连的直线段组成的开放或封闭的图形
最不容易出错的是 path
标签,所以建议使用 path
标签来绘制图形
Gojs geometry
gojs
中 的 geometry
是用于描述图形的语法,geometry
是一个对象,它包含了一系列的 path
。path
是一个字符串,它描述了一条路径。path
由一系列的命令组成,每个命令由一个字母和一些数字组成(几何路径字符串)
几何路径字符串
几何路径字符串的语法是 SVG
路径字符串语法的扩展,该字符串由许多命令组成,每个命令都是一个字母后跟一些特定于命令的数值参数
M
: 移动到(Move to
),M x y
将画笔移动到指定的坐标点,不画线L
: 绘制线到(Line to
),L x y
从当前位置绘制一条直线到指定的坐标点H
: 水平线到(Horizontal line to
),H x
从当前位置绘制一条水平线到指定的 x 坐标V
: 垂直线到(Vertical line to
),V y
从当前位置绘制一条垂直线到指定的 y 坐标Z
: 闭合路径(Close path
),如果路径的起点和终点重合,则闭合路径F
: 填充路径(Fill path
),这个命令通常用于闭合路径,但它的具体行为取决于上下文B
: 贝塞尔曲线到(Bezier curve to
),B x1 y1 x2 y2 x y
从当前位置绘制一条贝塞尔曲线到指定的坐标点,B (startAngle, sweepAngle, centerX, centerY, radiusX, radiusY)
X
: 不绘制(No draw
),这个命令用于在路径中跳过一个点,不影响当前位置
具体实例:geometryString: "F M120 0 L80 80 0 50z",
上述字符串描述了一个三角形形状,它的顶点在坐标 (120, 0)、(80, 80) 和 (0, 50)
F
:表示填充路径。在SVG
中,F
通常用于闭合路径,这意味着路径的最后一个点会自动连接到第一个点,形成一个闭合的形状M120 0
:M
是移动到点 (120, 0) 的命令,这通常是路径的起点L80 80
:L
是绘制直线到点 (80, 80) 的命令0 50
:这是另一个点,路径会从当前位置绘制一条直线到这个点z
:闭合路径的命令,它会从当前点绘制一条直线到路径的起点,将路径闭合
将SVG
标签转化为geometry
字符串
在Gojs
中不推荐直接引入SVG
图片,更推荐使用geometry
字符串来绘制SVG
图形,因此我们就需要将我们的SVG
标签进行转化,转成geometry
字符串,可以使用以下的python
脚本进行转化:
"""
1. 没有stroke属性的元素不会被转换。防止有些背景占位元素被转换。
2. 元素内的只有 fill stroke 和 特定元素会被识别。
path: d
rect: x y width height
ellipse: cx cy rx ry
line: x1 y1 x2 y2
circle: cx cy r
polyline: points
"""
import re
import xml.etree.ElementTree as ET
PATH = "{http://www.w3.org/2000/svg}path"
RECT = "{http://www.w3.org/2000/svg}rect"
ELLIPSE = "{http://www.w3.org/2000/svg}ellipse"
LINE = "{http://www.w3.org/2000/svg}line"
CIRCLE = "{http://www.w3.org/2000/svg}circle"
POLYLINE = "{http://www.w3.org/2000/svg}polyline"
POLYGON = "{http://www.w3.org/2000/svg}polygon"
# 读取svg文件.
def read_svg_file(svg_file_path) -> ET.ElementTree:
tree = ET.parse(svg_file_path)
return tree
# 判断元素是否有描边.
def element_has_stroke(element: ET.Element) -> bool:
"""
判断元素是否有描边
:param element: 元素
:return: 是否有描边
"""
try:
stroke = element.attrib["stroke"]
if isinstance(stroke, str) and stroke != "none":
return True
except KeyError:
return False
return False
# 将path标签转换为geometry.
def convert_path_tag_to_geometry(path: ET.Element):
geometry = ""
prefix = ""
# 获取path元素的stroke属性,即描边颜色
if not element_has_stroke(path):
return None
# 获取path元素的fill属性,即填充颜色
fill = path.attrib["fill"]
if isinstance(fill, str) and fill != "none":
prefix = "F"
else:
prefix = ""
# 获取d属性,即路径数据
d = path.attrib["d"]
if isinstance(d, str):
geometry = "X" + prefix + d
return geometry
def convert_rect_tag_to_geometry(rect: ET.Element):
prefix = ""
# 获取rect元素的stroke属性,即描边颜色
# 不存在描边则直接返回
if not element_has_stroke(rect):
return None
# 获取rect元素的fill属性,即填充颜色
fill = rect.attrib["fill"]
if isinstance(fill, str) and fill != "none":
prefix = "F"
else:
prefix = ""
# 获取rect元素的x、y、width、height属性,即矩形的位置和大小
x = rect.attrib["x"]
y = rect.attrib["y"]
width = rect.attrib["width"]
height = rect.attrib["height"]
if isinstance(x, str):
rect_right = float(x) + float(width)
rect_bottom = float(y) + float(height)
geometry = "X"+ prefix+ "M"+ x+ " "+ y+ " "+ "H "+ str(rect_right)+ " "+ "V "+ str(rect_bottom)+ " "+ "H "+ x+ " z"
return geometry
def convert_ellipse_tag_to_geometry(ellipse: ET.Element):
geometry = ''
prefix = ""
if not element_has_stroke(ellipse):
return None
# 获取ellipse元素的fill属性,即填充颜色
fill = ellipse.attrib["fill"]
if isinstance(fill, str) and fill != "none":
prefix = "F"
else:
prefix = ""
# 获取ellipse元素的cx、cy、rx、ry属性,即椭圆的位置和大小
cx = ellipse.attrib["cx"]
cy = ellipse.attrib["cy"]
rx = ellipse.attrib["rx"]
ry = ellipse.attrib["ry"]
if (
isinstance(cx, str)
and isinstance(cy, str)
and isinstance(rx, str)
and isinstance(ry, str)
):
start = str(float(cx) + float(rx))
geometry = "X"+ prefix+ "M "+ start+ " "+ cy+ " "+ "B0 360 "+ cx+ " "+ cy+ " "+ rx+ " "+ ry
return geometry
def convert_line_tag_to_geometry(line: ET.Element):
geometry = ""
prefix = ""
if not element_has_stroke(line):
return None
# 获取line元素的x1、y1、x2、y2属性,即线段的位置
x1 = line.attrib["x1"]
y1 = line.attrib["y1"]
x2 = line.attrib["x2"]
y2 = line.attrib["y2"]
if (
isinstance(x1, str)
and isinstance(y1, str)
and isinstance(x2, str)
and isinstance(y2, str)
):
geometry = "X" + prefix + "M " + x1 + " " + y1 + " " + "L " + x2 + " " + y2
return geometry
def convert_circle_to_geometry(circle: ET.Element):
geometry = ""
prefix = ""
if not element_has_stroke(circle):
return None
# 获取circle元素的fill属性,即填充颜色
fill = circle.attrib["fill"]
if isinstance(fill, str) and fill != "none":
prefix = "F"
else:
prefix = ""
# 获取circle元素的cx、cy、r属性,即圆的位置和大小
cx = circle.attrib["cx"]
cy = circle.attrib["cy"]
r = circle.attrib["r"]
if isinstance(cx, str) and isinstance(cy, str) and isinstance(r, str):
# 画圆时要先移动到圆的起始点,然后画一个圆弧。起始点为圆的右侧切点。 如 圆心 6 6 半径 3 则先移动到 9 6 然后画一个圆弧。
geometry = "X"+ prefix+ "M"+ str(float(cx) + float(r))+ " "+ cy+ "B 0 360 "+ cx+ " "+ cy+ " "+ r+ " "+ r
return geometry
def convert_polyline_to_geometry(polyline: ET.Element):
geometry = ""
prefix = ""
if not element_has_stroke(polyline):
return None
# 获取polyline元素的fill属性,即填充颜色
fill = polyline.attrib["fill"]
if isinstance(fill, str) and fill != "none":
prefix = "F"
else:
prefix = ""
# 获取polyline元素的points属性,即折线的点
points = polyline.attrib["points"]
if isinstance(points, str):
points_list = re.split(r" |,", points)
if len(points_list) > 0:
geometry = "X"+ prefix+ "M"+ " "+ " ".join(points_list[:2])+ "L"+ " ".join(points_list[2:])
return geometry
def convert_polygon_to_geometry(polygon: ET.Element):
geometry = ""
prefix = ""
if not element_has_stroke(polygon):
return None
# 获取polyline元素的fill属性,即填充颜色
fill = polygon.attrib["fill"]
if isinstance(fill, str) and fill != "none":
prefix = "F"
else:
prefix = ""
# 获取polyline元素的points属性,即折线的点
points = polygon.attrib["points"]
if isinstance(points, str):
points_list = re.split(r" |,", points)
if len(points_list) > 0:
geometry = "X"+ prefix+ "M"+ " "+ " ".join(points_list[:2])+ "L"+ " ".join(points_list[2:])+ "z"
return geometry
def convert_svg_to_geometry(svg_file_path):
geometry_list = []
svg_tree = read_svg_file(svg_file_path)
handle_svg_element_map = {
PATH: convert_path_tag_to_geometry,
RECT: convert_rect_tag_to_geometry,
ELLIPSE: convert_ellipse_tag_to_geometry,
LINE: convert_line_tag_to_geometry,
CIRCLE: convert_circle_to_geometry,
POLYLINE: convert_polygon_to_geometry,
POLYGON: convert_polygon_to_geometry,
}
for element in svg_tree.iter():
if element.tag in handle_svg_element_map:
geometry = handle_svg_element_map[element.tag](element)
if geometry is not None:
geometry_list.append(geometry)
return " ".join(geometry_list)
if __name__ == "__main__":
svg_file_path = "pid_node/渣池test2.svg"
res = convert_svg_to_geometry(svg_file_path)
print(res)