| 知乎专栏 |
symbol元素用来定义一个图形模板对象,它可以用一个use元素实例化。symbol元素对图形的作用是在同一文档中多次使用,添加结构和语义。结构丰富的文档可以更生动地呈现出来,类似讲演稿或盲文,从而提升了无障碍。注意,一个symbol元素本身是不呈现的。只有symbol元素的实例(亦即,一个引用了symbol的 use元素)才能呈现。
<svg>
<!-- symbol definition NEVER draw -->
<symbol id="sym01" viewBox="0 0 150 110">
<circle cx="50" cy="50" r="40" stroke-width="8" stroke="red" fill="red" />
<circle
cx="90"
cy="60"
r="40"
stroke-width="8"
stroke="green"
fill="white" />
</symbol>
<!-- actual drawing by "use" element -->
<use xlink:href="#sym01" x="0" y="0" width="100" height="50" />
<use xlink:href="#sym01" x="0" y="50" width="75" height="38" />
<use xlink:href="#sym01" x="0" y="100" width="50" height="25" />
</svg>
绘制圆角矩形
<svg width="300px" height="300px" viewBox="0 0 200 200">
<!-- rx 和 ry 相等, 逐渐增大 -->
<rect x="10" y="10" width="20" height="40" rx="2" ry="2"
style="stroke: black; fill: none;"/>
<rect x="40" y="10" width="20" height="40" rx="5"
style="stroke: black; fill: none;"/>
<rect x="70" y="10" width="20" height="40" ry="10"
style="stroke: black; fill: none;"/>
<!-- rx 和 ry 不相等 -->
<rect x="10" y="60" width="20" height="40" rx="10" ry="5"
style="stroke: black; fill: none;"/>
<rect x="40" y="60" width="20" height="40" rx="5" ry="10"
style="stroke: black; fill: none;"/>
</svg>
<circle cx="x-coordinate" <!-- 圆心的 x 坐标 --> cy="y-coordinate" <!-- 圆心的 y 坐标 --> r="radius" <!-- 圆的半径 --> fill="fill-color" <!-- 圆的填充颜色 --> stroke="stroke-color" <!-- 圆的描边颜色 --> stroke-width="width" <!-- 圆的描边宽度 --> />
拥有两个控制点的贝塞尔曲线
<path d="M{x},{y} C{cx1},{cy1} {cx2},{cy2} {x1},{y1}" id="路径" stroke="blue" stroke-width="2"></path>
起点:M{x},{y}
起点控制点:C{cx1},{cy1}
终点控制点:{cx2},{cy2}
终点:{x1},{y1}
下面代码生成类似 mindmap 思维导图的连接线
<path d="M0,200 C200,200 200,0 400,0" id="路径" stroke="#FFA500" stroke-width="2"></path>
<path d="M0,200 C200,200 200,50 400,50" id="路径" stroke="#FFA500" stroke-width="2"></path>
<path d="M0,200 C200,200 200,100 400,100" id="路径" stroke="#FFA500" stroke-width="2"></path>
<path d="M0,200 C200,200 200,150 400,150" id="路径" stroke="#FFA500" stroke-width="2"></path>
<path d="M0,200 C200,200 200,200 400,200" id="路径" stroke="#FFA500" stroke-width="2"></path>
<path d="M0,200 C200,200 200,250 400,250" id="路径" stroke="#FFA500" stroke-width="2"></path>
<path d="M0,200 C200,200 200,300 400,300" id="路径" stroke="#FFA500" stroke-width="2"></path>
<path d="M0,200 C200,200 200,350 400,350" id="路径" stroke="#FFA500" stroke-width="2"></path>
<path d="M0,200 C200,200 200,400 400,400" id="路径" stroke="#FFA500" stroke-width="2"></path>
<path d="M0,200 C200,200 200,450 400,450" id="路径" stroke="#FFA500" stroke-width="2"></path>
进一步增加复杂度,生成二级子集,同时在起点处增加两个圆圈点缀。
<path d="M0,200 C150,200 200,0 300,0" id="路径" stroke="#FFA500" stroke-width="2"></path>
<path d="M0,200 C150,200 200,50 300,50" id="路径" stroke="#FFA500" stroke-width="2"></path>
<path d="M0,200 C150,200 200,100 300,100" id="路径" stroke="#FFA500" stroke-width="2"></path>
<path d="M0,200 C150,200 200,150 300,150" id="路径" stroke="#FFA500" stroke-width="2"></path>
<path d="M0,200 C150,200 200,200 300,200" id="路径" stroke="#FFA500" stroke-width="2"></path>
<path d="M0,200 C150,200 200,250 300,250" id="路径" stroke="#FFA500" stroke-width="2"></path>
<path d="M0,200 C150,200 200,300 300,300" id="路径" stroke="#FFA500" stroke-width="2"></path>
<path d="M0,200 C150,200 200,350 300,350" id="路径" stroke="#FFA500" stroke-width="2"></path>
<path d="M0,200 C150,200 200,400 300,400" id="路径" stroke="#FFA500" stroke-width="2"></path>
<path d="M0,200 C150,200 200,450 300,450" id="路径" stroke="#FFA500" stroke-width="2"></path>
<path d="M300,200 C400,200 500,100 600,100" id="路径" stroke="#FFA500" stroke-width="2"></path>
<path d="M300,200 C400,200 500,150 600,150" id="路径" stroke="#FFA500" stroke-width="2"></path>
<path d="M300,200 C400,200 500,200 600,200" id="路径" stroke="#FFA500" stroke-width="2"></path>
<path d="M300,200 C400,200 500,250 600,250" id="路径" stroke="#FFA500" stroke-width="2"></path>
<path d="M300,200 C400,200 500,250 600,250" id="路径" stroke="#FFA500" stroke-width="2"></path>
<path d="M300,200 C400,200 500,300 600,300" id="路径" stroke="#FFA500" stroke-width="2"></path>
<path d="M300,200 C400,200 500,350 600,350" id="路径" stroke="#FFA500" stroke-width="2"></path>
<circle cx="0" cy="200" r="5" fill="white" stroke="blue" stroke-width="2"/>
<circle cx="300" cy="200" r="5" fill="white" stroke="green" stroke-width="2"/>
两端延伸出一段横线,横线上增加文字
<text text-anchor="middle" x="50" y="120" fill="purple" stroke="none">咖啡豆分类</text>
<text text-anchor="middle" x="350" y="45" fill="red" stroke="none">阿拉比卡咖啡豆</text>
<path d="M0,125 L100,125 C200,125 200,50 300,50 L 400,50" id="路径" stroke="red" stroke-width="2"></path>
<text text-anchor="middle" x="350" y="95" fill="green" stroke="none">罗布斯塔咖啡豆</text>
<path d="M0,125 L100,125 C200,125 200,100 300,100 L 400,100" id="路径" stroke="green" stroke-width="2"></path>
<text text-anchor="middle" x="350" y="145" fill="orange" stroke="none">利比里亚咖啡豆</text>
<path d="M0,125 L100,125 C200,125 200,150 300,150 L 400,150" id="路径" stroke="orange" stroke-width="2"></path>
<text text-anchor="middle" x="350" y="195" fill="blue" stroke="none">埃克萨斯咖啡豆</text>
<path d="M0,125 L100,125 C200,125 200,200 300,200 L 400,200" id="路径" stroke="blue" stroke-width="2"></path>
<circle cx="100" cy="125" r="5" fill="white" stroke="blue" stroke-width="2"/>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 400 300" width="400" height="300">
<!-- 样式定义 -->
<style>
.triangle { fill: #4a90e2; fill-opacity: 0.7; stroke: #333; stroke-width: 2; }
.angle-label { font-family: Arial; font-size: 14px; font-weight: bold; }
.angle-arc { stroke: #666; stroke-width: 1.5; fill: none; }
.side-label { font-family: Arial; font-size: 12px; }
.info { font-family: Arial; font-size: 12px; fill: #333; }
</style>
<!-- 背景和说明文字 -->
<rect width="100%" height="100%" fill="#f9f9f9" />
<text x="200" y="20" text-anchor="middle" class="info">指定内角的三角形 (30°, 60°, 90°)</text>
<!-- 计算参数:三个内角之和必须为180° -->
<defs>
<data id="angles" value="30,60,90" /> <!-- 可修改此处的角度值 -->
<data id="sideLength" value="150" /> <!-- 基准边长 -->
</defs>
<!-- 三角形绘制:通过JavaScript计算坐标 -->
<script type="text/javascript"><![CDATA[
// 获取参数
const anglesStr = document.getElementById('angles').getAttribute('value');
const [A, B, C] = anglesStr.split(',').map(Number);
const a = parseFloat(document.getElementById('sideLength').getAttribute('value'));
// 验证角度和为180°
if (A + B + C !== 180) {
alert(`角度和必须为180°,当前总和为${A+B+C}°`);
}
// 转换角度为弧度
const radA = A * Math.PI / 180;
const radB = B * Math.PI / 180;
const radC = C * Math.PI / 180;
// 利用正弦定理计算其他两边长度: a/sinA = b/sinB = c/sinC
const b = a * Math.sin(radB) / Math.sin(radA);
const c = a * Math.sin(radC) / Math.sin(radA);
// 计算三角形三个顶点坐标(以(100, 200)为第一个顶点)
const x1 = 100, y1 = 200;
const x2 = x1 + c, y2 = y1;
const x3 = x1 + b * Math.cos(radA), y3 = y1 - b * Math.sin(radA);
// 创建三角形路径
const triangle = document.createElementNS("http://www.w3.org/2000/svg", "path");
triangle.setAttribute('d', `M ${x1},${y1} L ${x2},${y2} L ${x3},${y3} Z`);
triangle.setAttribute('class', 'triangle');
document.querySelector('svg').appendChild(triangle);
// 绘制角度标记
function drawAngleMarker(x, y, angleRad, radius, label, isInside) {
const startAngle = isInside ? Math.PI : 0;
const endAngle = isInside ? Math.PI - angleRad : angleRad;
// 计算弧线
const arc = document.createElementNS("http://www.w3.org/2000/svg", "path");
const startX = x + radius * Math.cos(startAngle);
const startY = y - radius * Math.sin(startAngle);
const endX = x + radius * Math.cos(endAngle);
const endY = y - radius * Math.sin(endAngle);
const largeArcFlag = angleRad > Math.PI ? 1 : 0;
arc.setAttribute('d', `M ${startX},${startY} A ${radius},${radius} 0 ${largeArcFlag},1 ${endX},${endY}`);
arc.setAttribute('class', 'angle-arc');
document.querySelector('svg').appendChild(arc);
// 添加角度标签
const labelAngle = isInside ? startAngle - angleRad / 2 : angleRad / 2;
const labelX = x + (radius + 15) * Math.cos(labelAngle);
const labelY = y - (radius + 15) * Math.sin(labelAngle);
const text = document.createElementNS("http://www.w3.org/2000/svg", "text");
text.setAttribute('x', labelX);
text.setAttribute('y', labelY);
text.setAttribute('text-anchor', 'middle');
text.setAttribute('class', 'angle-label');
text.textContent = `${label}°`;
document.querySelector('svg').appendChild(text);
}
// 绘制三个角的标记
drawAngleMarker(x1, y1, radA, 30, A, true);
drawAngleMarker(x2, y2, radB, 30, B, true);
drawAngleMarker(x3, y3, radC, 30, C, true);
// 添加边长标签
function addSideLabel(x1, y1, x2, y2, label) {
const text = document.createElementNS("http://www.w3.org/2000/svg", "text");
const midX = (x1 + x2) / 2;
const midY = (y1 + y2) / 2;
text.setAttribute('x', midX);
text.setAttribute('y', midY - 5);
text.setAttribute('text-anchor', 'middle');
text.setAttribute('class', 'side-label');
text.textContent = label;
document.querySelector('svg').appendChild(text);
}
addSideLabel(x1, y1, x2, y2, `c = ${c.toFixed(1)}`);
addSideLabel(x2, y2, x3, y3, `a = ${a}`);
addSideLabel(x3, y3, x1, y1, `b = ${b.toFixed(1)}`);
] ] ></script>
</svg>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 400 400" width="400" height="400">
<!-- 样式定义 -->
<style>
.polygon { fill: #4a90e2; fill-opacity: 0.7; stroke: #333; stroke-width: 2; }
.control-panel { font-family: Arial; font-size: 14px; }
.label { fill: #333; }
.value-display { font-weight: bold; }
</style>
<!-- 背景 -->
<rect width="100%" height="100%" fill="#f9f9f9" />
<!-- 控制参数 -->
<defs>
<data id="sides" value="6" /> <!-- 多边形边数 -->
<data id="radius" value="150" /> <!-- 多边形半径(中心点到顶点的距离) -->
<data id="rotation" value="0" /> <!-- 旋转角度(度) -->
<data id="centerX" value="200" /> <!-- 中心点X坐标 -->
<data id="centerY" value="200" /> <!-- 中心点Y坐标 -->
</defs>
<!-- 说明文字 -->
<text x="200" y="30" text-anchor="middle" class="label">正多边形绘制 (边数: <tspan id="sides-display" class="value-display">6</tspan>)</text>
<text x="200" y="370" text-anchor="middle" class="label" font-size="12">可修改边数参数查看不同多边形</text>
<!-- 多边形绘制逻辑 -->
<script type="text/javascript"><![CDATA[
// 获取参数
const sides = parseInt(document.getElementById('sides').getAttribute('value'));
const radius = parseFloat(document.getElementById('radius').getAttribute('value'));
const rotation = parseFloat(document.getElementById('rotation').getAttribute('value'));
const centerX = parseFloat(document.getElementById('centerX').getAttribute('value'));
const centerY = parseFloat(document.getElementById('centerY').getAttribute('value'));
// 验证边数(至少3条边)
if (sides < 3) {
alert("多边形边数必须至少为3");
}
// 更新显示的边数
document.getElementById('sides-display').textContent = sides;
// 计算每个顶点的坐标
const points = [];
const rotationRad = rotation * Math.PI / 180; // 转换旋转角度为弧度
for (let i = 0; i < sides; i++) {
// 计算每个顶点的角度(弧度)
// 从顶部开始(-90度),加上旋转角度,再加上每个顶点的角度偏移
const angle = rotationRad - Math.PI / 2 + (2 * Math.PI * i / sides);
// 计算坐标
const x = centerX + radius * Math.cos(angle);
const y = centerY + radius * Math.sin(angle);
points.push(`${x},${y}`);
}
// 创建多边形元素
const polygon = document.createElementNS("http://www.w3.org/2000/svg", "polygon");
polygon.setAttribute('points', points.join(' '));
polygon.setAttribute('class', 'polygon');
document.querySelector('svg').appendChild(polygon);
// 绘制中心点
const center = document.createElementNS("http://www.w3.org/2000/svg", "circle");
center.setAttribute('cx', centerX);
center.setAttribute('cy', centerY);
center.setAttribute('r', 5);
center.setAttribute('fill', '#ff6b6b');
document.querySelector('svg').appendChild(center);
] ]> </script>
</svg>
基本参数设定: 边数(sides):决定多边形的形状,必须至少为 3 半径(radius):从中心到顶点的距离 中心点坐标(centerX, centerY):多边形在 SVG 中的位置 旋转角度(rotation):控制多边形的朝向 顶点坐标计算: 每个顶点之间的角度间隔 = 2π / 边数(弧度) 第 i 个顶点的角度 = 旋转角度 + 起始角度(-90 度,从顶部开始) + 角度间隔 × i 使用三角函数计算坐标: x = 中心 x + 半径 × cos (角度) y = 中心 y + 半径 × sin (角度) 绘制多边形: 将所有顶点坐标按顺序连接,形成闭合的多边形 修改 sides 值: 3:三角形 4:正方形 5:五边形 6:六边形 8:八边形等
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 600 500" width="600" height="500">
<!-- 样式定义 -->
<style>
.axis-line { stroke: #ccc; stroke-width: 1; }
.grid-line { stroke: #eee; stroke-width: 1; }
.data-polygon-1 { fill: rgba(54, 162, 235, 0.2); stroke: rgba(54, 162, 235, 1); stroke-width: 2; }
.data-polygon-2 { fill: rgba(75, 192, 192, 0.2); stroke: rgba(75, 192, 192, 1); stroke-width: 2; }
.dimension-label { font-family: Arial; font-size: 14px; fill: #333; }
.legend-item { font-family: Arial; font-size: 12px; fill: #333; }
.title { font-family: Arial; font-size: 18px; font-weight: bold; fill: #333; }
.center-circle { fill: #fff; }
</style>
<!-- 雷达图参数配置 -->
<defs>
<!-- 维度标签 -->
<data id="dimensions" value="性能,易用性,稳定性,安全性,性价比,扩展性"></data>
<!-- 数据值 (0-100),多组数据用|分隔 -->
<data id="data" value="80,65,90,75,60,85|70,80,65,90,75,60"></data>
<!-- 图例标签 -->
<data id="legends" value="产品A,产品B"></data>
<!-- 配置参数 -->
<data id="centerX" value="300"></data> <!-- 中心点X坐标 -->
<data id="centerY" value="250"></data> <!-- 中心点Y坐标 -->
<data id="maxRadius" value="180"></data> <!-- 最大半径 -->
<data id="levels" value="5"></data> <!-- 网格层数 -->
</defs>
<!-- 标题 -->
<text x="300" y="30" text-anchor="middle" class="title">产品多维度评价雷达图</text>
<!-- 雷达图绘制逻辑 -->
<script type="text/javascript"><![CDATA[
// 获取配置参数
const dimensions = document.getElementById('dimensions').getAttribute('value').split(',');
const dataGroups = document.getElementById('data').getAttribute('value').split('|').map(group =>
group.split(',').map(Number)
);
const legends = document.getElementById('legends').getAttribute('value').split(',');
const centerX = parseFloat(document.getElementById('centerX').getAttribute('value'));
const centerY = parseFloat(document.getElementById('centerY').getAttribute('value'));
const maxRadius = parseFloat(document.getElementById('maxRadius').getAttribute('value'));
const levels = parseInt(document.getElementById('levels').getAttribute('value'));
const numDimensions = dimensions.length;
// 计算每个维度的角度(弧度)
const getAngle = (index) => {
// 从顶部开始(-90度),每个维度平均分配360度
return (index * 2 * Math.PI / numDimensions) - Math.PI / 2;
};
// 计算坐标
const getCoordinates = (index, value) => {
const angle = getAngle(index);
const radius = (value / 100) * maxRadius; // 数据值转换为半径比例
return {
x: centerX + radius * Math.cos(angle),
y: centerY + radius * Math.sin(angle)
};
};
// 绘制网格线和坐标轴
for (let level = 1; level <= levels; level++) {
const radius = (level / levels) * maxRadius;
const points = [];
// 绘制网格多边形
for (let i = 0; i < numDimensions; i++) {
const angle = getAngle(i);
points.push(`${centerX + radius * Math.cos(angle)},${centerY + radius * Math.sin(angle)}`);
}
points.push(points[0]); // 闭合多边形
const grid = document.createElementNS("http://www.w3.org/2000/svg", "polyline");
grid.setAttribute('points', points.join(' '));
grid.setAttribute('class', 'grid-line');
document.querySelector('svg').appendChild(grid);
}
// 绘制轴线
for (let i = 0; i < numDimensions; i++) {
const angle = getAngle(i);
const endX = centerX + maxRadius * Math.cos(angle);
const endY = centerY + maxRadius * Math.sin(angle);
const axis = document.createElementNS("http://www.w3.org/2000/svg", "line");
axis.setAttribute('x1', centerX);
axis.setAttribute('y1', centerY);
axis.setAttribute('x2', endX);
axis.setAttribute('y2', endY);
axis.setAttribute('class', 'axis-line');
document.querySelector('svg').appendChild(axis);
// 绘制维度标签(位置略超出最大半径)
const labelOffset = 20;
const labelX = centerX + (maxRadius + labelOffset) * Math.cos(angle);
const labelY = centerY + (maxRadius + labelOffset) * Math.sin(angle);
const label = document.createElementNS("http://www.w3.org/2000/svg", "text");
label.setAttribute('x', labelX);
label.setAttribute('y', labelY);
label.setAttribute('text-anchor', 'middle');
label.setAttribute('dominant-baseline', 'middle');
label.setAttribute('class', 'dimension-label');
label.textContent = dimensions[i];
document.querySelector('svg').appendChild(label);
}
// 绘制中心点
const centerCircle = document.createElementNS("http://www.w3.org/2000/svg", "circle");
centerCircle.setAttribute('cx', centerX);
centerCircle.setAttribute('cy', centerY);
centerCircle.setAttribute('r', 5);
centerCircle.setAttribute('class', 'center-circle');
document.querySelector('svg').appendChild(centerCircle);
// 绘制数据多边形
dataGroups.forEach((data, groupIndex) => {
const points = [];
for (let i = 0; i < numDimensions; i++) {
const coords = getCoordinates(i, data[i]);
points.push(`${coords.x},${coords.y}`);
}
points.push(points[0]); // 闭合多边形
const polygon = document.createElementNS("http://www.w3.org/2000/svg", "polygon");
polygon.setAttribute('points', points.join(' '));
polygon.setAttribute('class', `data-polygon-${groupIndex + 1}`);
document.querySelector('svg').appendChild(polygon);
});
// 绘制图例
legends.forEach((legend, index) => {
const legendX = 450;
const legendY = 100 + index * 30;
// 图例颜色块
const colorBox = document.createElementNS("http://www.w3.org/2000/svg", "rect");
colorBox.setAttribute('x', legendX);
colorBox.setAttribute('y', legendY - 10);
colorBox.setAttribute('width', 15);
colorBox.setAttribute('height', 15);
colorBox.setAttribute('fill', index === 0 ? 'rgba(54, 162, 235, 0.2)' : 'rgba(75, 192, 192, 0.2)');
colorBox.setAttribute('stroke', index === 0 ? 'rgba(54, 162, 235, 1)' : 'rgba(75, 192, 192, 1)');
colorBox.setAttribute('stroke-width', 2);
document.querySelector('svg').appendChild(colorBox);
// 图例文本
const legendText = document.createElementNS("http://www.w3.org/2000/svg", "text");
legendText.setAttribute('x', legendX + 25);
legendText.setAttribute('y', legendY + 3);
legendText.setAttribute('class', 'legend-item');
legendText.textContent = legend;
document.querySelector('svg').appendChild(legendText);
});
]] ></script>
</svg>
pip3 install drawsvg -i https://pypi.tuna.tsinghua.edu.cn/simple pip3 install cairosvg -i https://pypi.tuna.tsinghua.edu.cn/simple
lines = draw.Lines(
# 坐标
5, 5,
# 横线
200, 5,
# 竖线
200, 40,
# 斜线
200 - 10, 20,
# 横线2
5 + 10, 20,
# 斜线
5, 40,
# 闭合竖线
5,5,
fill='none', stroke='black')
<!DOCTYPE html> <html> <body> <svg width="500" height="150"> <rect x="10" y="10" width="100" height="40" style="stroke: black; fill: silver; fill-opacity: .4;" onmouseover="this.style.stroke = 'blue'; this.style['stroke-width'] = 5;" onmouseout="this.style.stroke = 'green'; this.style['stroke-width'] = 1;" onclick="this.style['width'] = 300;" /> </svg> </body> </html>
<!DOCTYPE html>
<html>
<body>
<input id="aa" type="text" value="200" onclick="svg.style['width']=this.value;" />
<svg width="500" height="150">
<rect id='svg' x="10" y="10" width="100" height="40" style="stroke: black; fill: silver; fill-opacity: .4;"
onmouseover="this.style.stroke = 'blue'; this.style['stroke-width'] = 5;"
onmouseout="this.style.stroke = 'green'; this.style['stroke-width'] = 1;"
onclick="this.style['width'] = 300; aa.value='300'" />
</svg>
</body>
</html>
$ pip3 install cairosvg
$ cairosvg image.svg -o image.png
$ python3 >>> import cairosvg >>> cairosvg.svg2pdf(url='image.svg', write_to='image.pdf')
import os import cairosvg input_file = os.getcwd() + "/mindmap.svg" output_file = os.getcwd() + "/mindmap.png" cairosvg.svg2png(url=input_file, write_to=output_file) # 设置背景色 cairosvg.svg2png(url=input_file, write_to=output_file, background_color="white")