使用d3.js可以很容易的操作生成svg,下边的代码是基于组件单元,由json数据生成一个svg的代码
<html><body><div id="toolbar"><button onclick="saveAsSVG()" style="margin-top:10px">保存SVG</button><button onclick="zoomIn()">放大</button><button onclick="zoomOut()">缩小</button></div><div class="container"><div id="svgContainer"><div id="svgWrapper"><svg id="container" width="800" height="400" style="border:1px solid #ddd"></svg></div></div></div><script src="https://d3js.org/d3.v7.min.js"></script>
<script>
// 复杂图元配置数据
const nodes = [{id: "node1",type: "gear",size: 80,color: "#FF6B6B",label: "齿轮装置",rotation: 0,data: { rpm: 2400, temp: 65 }},{id: "node2",type: "cloud",size: 100,color: "#4ECDC4",label: "云服务节点",data: { usage: "78%", region: "EU" }},{id: "node3",type: "server",size: 120,color: "#45B7D1",label: "数据库服务器",data: { storage: "2.4TB", connections: 142 }},{id: "node4",type: "bridge",size: 120,color: "#45B7D1",label: "桥墩",data: {}},// 添加更多复杂形状...
];// 图形生成器配置
const shapeGenerators = {gear: (group, node) => {group.append("circle").attr("r", node.size/2).attr("fill", d3.color(node.color).darker(0.2));// 绘制齿轮齿const teeth = 12;const toothHeight = node.size/8;for(let i=0; i<teeth; i++){const angle = (i * 360/teeth) - 90;group.append("rect").attr("width", toothHeight).attr("height", node.size/2 + toothHeight).attr("x", -toothHeight/2).attr("transform", `rotate(${angle}) translate(0 ${-node.size/2})`).attr("fill", node.color);}// 中心孔group.append("circle").attr("r", node.size/6).attr("fill", "#fff");},cloud: (group, node) => {const cloudPath = "M -50 0 Q -40 -20 0 -20 Q 30 -20 40 0 Q 50 20 30 30 Q 20 50 -10 50 Q -30 40 -50 30 Q -60 20 -50 0 Z";group.append("path").attr("d", cloudPath).attr("transform", `scale(${node.size/100})`).attr("fill", node.color);group.append("text").attr("y", node.size/4).attr("text-anchor", "middle").style("font-size", "14px").style("fill", "#fff").text("☁");},bridge: (group, node) => {const g1 = group.append("g").attr("transform", "translate(0,50.50)");// 生成主体桥体const path1 = "M6.7,21.5L6.7,.0L.0,.0L.0,21.5L6.7,21.5z";g1.append("path").attr("d", path1).attr("stroke", "#101843").attr("transform", "translate(11.57,0.00)").attr("fill", "#ffffff");const path2 = "M29.9,10.3L29.9,.0L.0,.0L.0,10.3L29.9,10.3z";g1.append("path").attr("d", path2).attr("stroke", "#101843").attr("transform", "translate(0.00,21.55)").attr("fill", "#ffffff");const path3 = "M6.7,21.5L6.7,.0L.0,.0L.0,21.5L6.7,21.5z";g1.append("path").attr("d", path3).attr("stroke", "#101843").attr("transform", "translate(4.37,31.87)").attr("fill", "#ffffff");const path4 = "M6.7,21.5L6.7,.0L.0,.0L.0,21.5L6.7,21.5z";g1.append("path").attr("d", path4).attr("stroke", "#101843").attr("transform", "translate(18.57,31.87)").attr("fill", "#ffffff");//生成上不的圈和墩号const g2 = group.append("g").attr("transform", "translate(2.99,0.00)");g2.append("path").attr("d", "M.0,12.5C.0,5.6,5.6,.0,12.5,.0C19.4,.0,25.0,5.6,25.0,12.5C25.0,19.4,19.4,25.0,12.5,25.0C5.6,25.0,.0,19.4,.0,12.5z").attr("stroke", "#00b050").attr("fill", "#ffffff");const g22 = g2.append("text").attr("style", "fill:#191919;font-family:Microsoft YaHei;font-size:6.00pt;font-weight:bold");g22.append("tspan").attr("x", "10.0").attr("y", "16.0").text(node.label);//生成箭头const g3 = group.append("g").attr("transform", "translate(15.36,25.71)");g3.append("path").attr("d", "M.0,.0L.0,11.5").attr("stroke", "#000000");g3.append("path").attr("d", "M.0,14.5L2.0,11.0C1.4,11.3,.7,11.5,.0,11.5C-0.7,11.5,-1.4,11.3,-2.0,11.0L.0,14.5").attr("stroke", "#000000");},server: (group, node) => {// 机柜主体group.append("rect").attr("width", node.size).attr("height", node.size*0.8).attr("rx", 8).attr("fill", node.color);// 指示灯group.append("circle").attr("cx", node.size*0.9).attr("cy", node.size*0.2).attr("r", 5).attr("fill", "#4CAF50");// 硬盘槽for(let i=0; i<3; i++){group.append("rect").attr("x", node.size*0.1).attr("y", node.size*0.4 + i*20).attr("width", node.size*0.8).attr("height", 12).attr("rx", 3).attr("fill", d3.color(node.color).brighter(0.2));}}
};// 初始化SVG容器
const svg = d3.select("#container");// 自动布局计算
function calculateLayout(nodes, containerWidth) {let x = 0, y = 0, currentRowHeight = 0;const padding = 20;return nodes.map(node => {const width = node.size + padding;const height = node.size + padding;if(x + width > containerWidth) {x = 0;y += currentRowHeight + padding;currentRowHeight = 0;}const position = { x, y };x += width;currentRowHeight = Math.max(currentRowHeight, height);return {...node,width,height,position};});
}// 更新可视化
function updateVisualization() {const containerWidth = parseInt(svg.style("width"));const layoutNodes = calculateLayout(nodes, containerWidth);const nodeGroups = svg.selectAll(".node").data(layoutNodes, d => d.id).join("g").attr("class", "node").attr("transform", d => `translate(${d.position.x},${d.position.y})`).attr("data-id", d => d.id).attr("data-type", d => d.type).on("mouseover", showTooltip).on("mouseout", hideTooltip);// 生成自定义图形nodeGroups.each(function(d) {const group = d3.select(this);group.selectAll("*").remove(); // 清除旧元素shapeGenerators[d.type](group, d);// 添加统一标签group.append("text").attr("x", d.size/2).attr("y", d.size + 20).attr("text-anchor", "middle").style("font-family", "Arial").style("font-size", "12px").text(d.label);});
}// 保存功能
function saveAsSVG() {const serializer = new XMLSerializer();let svgString = serializer.serializeToString(svg.node());// 添加命名空间svgString = svgString.replace('<svg ', '<svg xmlns="http://www.w3.org/2000/svg" ');const blob = new Blob([svgString], {type: "image/svg+xml"});const url = URL.createObjectURL(blob);const link = document.createElement("a");link.href = url;link.download = "complex-shapes.svg";link.click();
}// 工具提示
function showTooltip(event, d) {d3.select("#tooltip").remove();const tooltip = d3.select("body").append("div").attr("id", "tooltip").style("position", "absolute").style("background", "rgba(255,255,255,0.95)").style("padding", "10px").style("border-radius", "5px").style("box-shadow", "0 2px 8px rgba(0,0,0,0.2)").html(`<h4 style="margin:0 0 8px 0">${d.label}</h4><div>类型:${d.type}</div><div>尺寸:${d.size}px</div>${Object.entries(d.data).map(([k,v]) => `<div>${k}:${v}</div>`).join("")}`);const [x, y] = [event.pageX + 15, event.pageY - 15];tooltip.style("left", x + "px").style("top", y + "px");
}function hideTooltip() {d3.select("#tooltip").remove();
}let scaleFactor = 1;
const zoomStep = 0.2;
// 缩放控制
function zoomIn() {scaleFactor *= (1 + zoomStep);updateZoom();
}function zoomOut() {scaleFactor *= (1 - zoomStep);updateZoom();
}function updateZoom() {const wrapper = document.getElementById('svgWrapper');wrapper.style.transform = `scale(${scaleFactor})`;
}// 初始化
function init() {updateVisualization();window.addEventListener("resize", () => {svg.attr("width", svg.node().parentElement.clientWidth);updateVisualization();});
}init();
</script><style>body { margin: 0; padding: 20px; font-family: Arial; }
.container { display: flex; gap: 20px; height: 90vh; }
#toolbar { margin-bottom: 20px; }
button { padding: 8px 16px; background: #0077C2; color: white; border: none; cursor: pointer; margin-right: 10px; }
#svgContainer { flex: 1; border: 2px dashed #ccc;overflow: auto;position: relative;
}
#svgWrapper {transition: transform 0.3s;transform-origin: 0 0;
}
#propertyPanel {width: 300px;border-left: 2px solid #ddd;padding: 20px;
}
#tooltip {font-family: Arial;font-size: 14px;color: #333;pointer-events: none;
}
</style>
</body>
</html>
有些svg组件单元可以仿照使用其他工具画的svg找规律去添加使用。