返回

打造SVG动态可编辑流程图,推动流程数字化

前端

正文

序言

流程图是将流程步骤用图形符号表示,并用连线将步骤连接起来,用以表示流程的流向和逻辑关系。流程图在各个领域都有着广泛的应用,如软件开发、项目管理、业务流程管理等。

随着数字化转型的不断深入,传统的纸质流程图已经不能满足需求,动态可编辑的流程图工具应运而生。SVG(可缩放矢量图形)是一种基于XML的矢量图形格式,具有可编辑、可缩放的特点,非常适合用来绘制流程图。而D3.js是一个用于操作和绑定数据到DOM元素的JavaScript库,可以帮助我们更轻松地实现交互式可编辑的流程图。

前期规划

在正式开发之前,我们对流程图工具的整体架构、功能模块、技术选型等方面进行了详细的规划和调研。

整体架构

流程图工具主要分为前端和后端两部分,前端负责展示和交互,后端负责数据的存储和管理。前端采用React框架,后端采用Node.js框架。

功能模块

流程图工具的主要功能模块包括:

  • 画布: 用于绘制流程图。
  • 工具栏: 包含各种流程图形状的工具,如矩形、圆形、菱形等。
  • 属性面板: 用于设置流程图形状的属性,如颜色、大小、填充等。
  • 连线工具: 用于连接两个流程图形状。
  • 编辑工具: 用于编辑流程图,如移动、删除、复制等。
  • 保存工具: 用于保存流程图。
  • 导入/导出工具: 用于导入/导出流程图。

技术选型

前端框架:React
后端框架:Node.js
绘图库:D3.js
数据存储:MongoDB

部分实现方式

画布

画布是一个SVG元素,其中包含了流程图的各个形状和连线。我们使用D3.js的append()方法将流程图形状添加到画布中,并使用line()方法将连线添加到画布中。

const canvas = d3.select("body").append("svg");

const rect = canvas.append("rect")
  .attr("x", 100)
  .attr("y", 100)
  .attr("width", 100)
  .attr("height", 100)
  .attr("fill", "red");

const line = canvas.append("line")
  .attr("x1", 100)
  .attr("y1", 100)
  .attr("x2", 200)
  .attr("y2", 200)
  .attr("stroke", "black");

工具栏

工具栏是一个HTML元素,其中包含了各种流程图形状的工具。我们使用HTML的<button>元素创建工具栏,并使用JavaScript的addEventListener()方法为工具栏中的按钮添加点击事件监听器。

<div class="toolbar">
  <button id="rect-tool">矩形</button>
  <button id="circle-tool">圆形</button>
  <button id="diamond-tool">菱形</button>
  <button id="line-tool">连线</button>
</div>
const rectTool = document.getElementById("rect-tool");
rectTool.addEventListener("click", () => {
  // 当点击矩形工具时,添加一个矩形到画布中
  const rect = canvas.append("rect")
    .attr("x", 100)
    .attr("y", 100)
    .attr("width", 100)
    .attr("height", 100)
    .attr("fill", "red");
});

属性面板

属性面板是一个HTML元素,其中包含了流程图形状的属性,如颜色、大小、填充等。我们使用HTML的<input>元素和<select>元素创建属性面板,并使用JavaScript的addEventListener()方法为属性面板中的元素添加改变事件监听器。

<div class="properties">
  <label for="color">颜色:</label>
  <input type="color" id="color">
  <label for="size">大小:</label>
  <input type="number" id="size">
  <label for="fill">填充:</label>
  <select id="fill">
    <option value="solid">实心</option>
    <option value="gradient">渐变</option>
    <option value="pattern">图案</option>
  </select>
</div>
const colorInput = document.getElementById("color");
colorInput.addEventListener("change", () => {
  // 当颜色改变时,更新选中的流程图形状的颜色
  const selectedShape = canvas.selectAll(".selected");
  selectedShape.attr("fill", colorInput.value);
});

连线工具

连线工具是一个HTML元素,其中包含了一个按钮。我们使用HTML的<button>元素创建连线工具,并使用JavaScript的addEventListener()方法为连线工具中的按钮添加点击事件监听器。

<div class="line-tool">
  <button id="line-button">连线</button>
</div>
const lineButton = document.getElementById("line-button");
lineButton.addEventListener("click", () => {
  // 当点击连线工具时,进入连线模式
  canvas.on("mousedown", startLine);
  canvas.on("mousemove", drawLine);
  canvas.on("mouseup", endLine);
});

function startLine() {
  // 开始连线时,记录鼠标的起始位置
  const startPoint = d3.mouse(this);
  line = canvas.append("line")
    .attr("x1", startPoint[0])
    .attr("y1", startPoint[1])
    .attr("x2", startPoint[0])
    .attr("y2", startPoint[1])
    .attr("stroke", "black");
}

function drawLine() {
  // 连线时,更新连线的终点位置
  const endPoint = d3.mouse(this);
  line.attr("x2", endPoint[0])
    .attr("y2", endPoint[1]);
}

function endLine() {
  // 结束连线时,取消鼠标事件监听器
  canvas.on("mousedown", null);
  canvas.on("mousemove", null);
  canvas.on("mouseup", null);
}

编辑工具

编辑工具是一个HTML元素,其中包含了各种流程图形状的编辑工具,如移动、删除、复制等。我们使用HTML的<button>元素创建编辑工具,并使用JavaScript的addEventListener()方法为编辑工具中的按钮添加点击事件监听器。

<div class="edit-tools">
  <button id="move-tool">移动</button>
  <button id="delete-tool">删除</button>
  <button id="copy-tool">复制</button>
</div>
const moveTool = document.getElementById("move-tool");
moveTool.addEventListener("click", () => {
  // 当点击移动工具时,进入移动模式
  canvas.on("mousedown", startMove);
  canvas.on("mousemove", moveShape);
  canvas.on("mouseup", endMove);
});

function startMove() {
  // 开始移动时,记录鼠标的起始位置和选中的流程图形状
  const startPoint = d3.mouse(this);
  selectedShape = canvas.selectAll(".selected");
}

function moveShape() {
  // 移动时,更新选中的流程图形状的位置
  const endPoint = d3.mouse(this);
  const dx = endPoint[0] - startPoint[0];
  const dy = endPoint[1] - startPoint[1];
  selectedShape.attr("x", function(d) { return +d3.select(this).attr("x") + dx; })
    .attr("y", function(d) { return +d3.select(this).attr("y") + dy; });
}

function endMove() {
  // 结束移动时,取消鼠标事件监听器
  canvas.on("mousedown", null);
  canvas.on("mousemove", null);
  canvas.on("mouseup", null);
}

保存工具

保存工具是一个