目前github社区存在几款可用于设计流程图的绘图框架:

  • go.js( http://www.gojs.net/latest/index.html) :go.js 提供一整套的JS工具 ,支持各种交互式图表的创建;目前go.js 是闭源收费的
  • jsPlumb(https://jsplumbtoolkit.com/): jsPlumb是一套开源的流程图创建工具 ,小巧精悍,使用简单;jsPlumb 有社区版跟收费版,我们可使用的是社区版
  • JointJS(https://www.jointjs.com/):JointJS是一个开源的、基于JavaScript的图表库,可以用来创建静态图表、完全可交互的图表、 WEB在线流程图、应用程序
  • mxGraph(http://jgraph.github.io/mxgraph/):mxGraph是一个js绘图组件,适用在网页设计/编辑流程图、图表、网络图和普通图形的web应用程序,draw.io工具就是基于mxGraph开发的。缺点就是介绍框架简介以及API文档不全,社区问题也较少更新;
  • GG-Editor(https://g6.antv.vision/) :GG-Editor是基于 G6 和 React 的可视化图编辑器,G6 是一个图可视化引擎。它提供了图的绘制、布局、分析、交互、动画等图可视化的基础能力。

最终,我选择了jsPlumb,因为它完全开源,文档也较齐全,社区也比较活跃在开发过程中有问题也可以和其他开发者交流;mxGraph和JointJS也不错。大家可以根据自己的需要选择。后期也会着手调研mxGraph,因为基于它实现了功能非常全的draw.io,相信里面也会有非常多好的思路可以参考;

下面将对jsplumb进行介绍并结合案例进行使用

1.什么是jsplumb?

jsplumb是可以让你在网站上展示图表或者甚至在浏览器应用程序中使用图表的开发框架,该框架适用于必须绘制图表的Web应用程序,例如类似于Visio的应用程序或工作流程设计器等。由于图表项目和连接的所有参数都是非常精细可控的,因此你可以绘制你可以想到的任何类型的图表

2.jsplumb基础知识

官方文档地址:https://docs.jsplumbtoolkit.com/community/current/index.html

2.1 基本元素组成

Source: 源对象。jsPlumb 通过元素的 id 属性获取对象。

Target: 目标对象。jsPlumb 通过元素的 id 属性获取对象。Source 和 Target 都可以是任何元素,区别是,Source 是起点,Target 是终点。 例如,connector 中的箭头总是从 Source 指向 Target。

Anchor:锚点。是 Source 和 Target 对象上可以连接 Connector 的点。Anchor 并不是一个视觉概念,它是不可见的。

Connector: 连接线。

Endpoint: 端点。需要注意的是,箭头并不是一种端点样式,它是通过 overlay 添加的。

Overlay: 添加到连接线上的附件。例如箭头和标签。

3.开始使用:

3.1 安装

npm install jsplumb --save

3.2 准备节点元素

假设我们现在这里是一个 .vue 组件。首先我们创建一系列的元素作为 Source 和 Target。

<template><div id="wrapper"><div class="line-wrap" style="margin-left: 70px;"><div id="item-1" class="state-item">State 1</div><div id="item-2" class="state-item">State 2</div><div id="item-3" class="state-item">State 3</div></div><div class="line-wrap"><div id="item-4" class="state-item">State 4</div><div id="item-5" class="state-item">State 5</div><div id="item-6" class="state-item">State 6</div><div id="item-7" class="state-item">State 7</div></div><div class="line-wrap" style="margin-left: 215px;"><div id="item-8" class="state-item">State 8</div><div id="item-9" class="state-item">State 9</div></div></div>
</template>

然后我们为它写点 css:

<style >#wrapper {background:radial-gradient(ellipse at top left,rgba(255, 255, 255, 1) 40%,rgba(229, 229, 229, .9) 100%);height: 100vh;padding: 60px 80px;width: 100vw;}.state-item {width: 80px;height: 40px;color: #606266;background: #f6f6f6;border: 2px solid rgba(0, 0, 0, 0.05);text-align: center;line-height: 40px;font-family: sans-serif;border-radius: 4px;margin-right: 60px;}.line-wrap {display: flex;margin-bottom: 40px;}
</style>

3.3 开始实例化jsPlumb

使用 jsPlumb.ready() 函数来初始化 jsPlumb。

<script>import {jsPlumb} from 'jsplumb'export default {name: 'landing-page',mounted () {let plumbIns = jsPlumb.getInstance()plumbIns.ready(function () {plumbIns.connect({// 对应上述基本概念source: 'item-1',target: 'item-2',anchor: ['Left', 'Right', 'Top', 'Bottom', [0.3, 0, 0, -1], [0.7, 0, 0, -1], [0.3, 1, 0, 1], [0.7, 1, 0, 1]],connector: ['StateMachine'],endpoint: 'Blank',overlays: [ ['Arrow', { width: 8, length: 8, location: 1 }] ], // overlay// 添加样式paintStyle: { stroke: '#909399', strokeWidth: 2 }, // connector// endpointStyle: { fill: '#909399', outlineStroke: '#606266', outlineWidth: 1 } // endpoint})})}}
</script>

这样我们就在两个元素之间画了一条连接线。最终效果如下图:

注意:

​ 1.我们应该把 jsPlumb.ready() 的代码写在 Vue 生命周期的 mounted 钩子中,而不是 created 中,因为 jsPlumb 是通过 DOM 元素来工作的,而在生命周期中的 created 阶段,Vue 实例还未挂载到页面元素上,这时候我们编写在组件中的元素是不存在的,因此 jsPlumb 无法起作用。

如果你的一些用于连线的元素放在子组件里,你还需要使用 vm.$nextTick() 来确保子组件也已经完成渲染,因为 mounted 并不能保证子组件完成渲染。

​ 2.建议只使用 jsPlumb 的实例,而不是它本身,虽然在使用方法上二者是完全一样的。

在官方文档中,提到了这么一句话:

If you bind to the ready event after jsPlumb has already been initialized, your callback will be executed immediately.

也就是,如果 jsPlumb 已经被初始化了一次,那么 ready 函数中的代码会立即执行,而不是等待 ready 事件。

这会造成什么问题呢?我们知道,在 vue 的环境里,jsPlumb 是个全局变量,因为它直接引用自模块,而模块是不随着某个页面的消亡而消亡的。因此,当你重新路由进这个页面时,jsPlumb 就会认为你已经进行了初始化,因此不会等待 ready 事件,这就可能导致你的连线不能正常显示。

而如果采用 jsPlumb.getInstance() 创建独立的实例,这个实例就可以在页面摧毁时被回收,这样你下次路由进这个页面的时候,就不会发生异常了。总之,永远使用实例代替总没错。

4.jsPlumb配置项描述:

jsPlumb.ready() 是一个钩子函数,它会在 jsPlumb 准备完毕时执行。

连接线的建立是通过 jsPlumb.connect() 方法实现的。该方法接受一个对象作为配置项。其中包含了与上述概念一一对应的配置项,以及一些额外的样式。

source: 源对象,可以是对象的 id 属性、Element 对象或者 Endpoint 对象。

target: 目标对象,可以是对象的 id 属性、Element 对象或者 Endpoint 对象。

anchor: 是一个数组,数组中每一项定义一个锚点。

添加锚点有两种方式:通过预设字符串(Static Anchors)或者一个数组(Dinamic Anchors)。

Static Anchors:

  • Top (also aliased as TopCenter)
  • TopRight
  • Right (also aliased as RightMiddle)
  • BottomRight
  • Bottom (also aliased as BottomCenter)
  • BottomLeft
  • Left (also aliased as LeftMiddle)
  • TopLeft
  • Center
//定义了一个在底部中间的锚点位置jsPlumb.connect({...., anchor:"Bottom", ... });

Dinamic Anchors:

    是一个表示 Anchor 的数组。[x, y, dx, dy]。
  • x-相对该锚点在x轴坐标比例(最大1)
  • y-相对该锚点y轴坐标比例(最大1)
  • dx-控制锚的方向
  • dy-同上
//定义了一个在底部中间的锚点位置
jsPlumb.connect({...., anchor:[ 0.5, 1, 0, 1 ], ... });

在上例中,我们定义了四个边的中点为 Anchors,然后还增加了上下边距离两端 0.3 的点作为 Anchors。

多边形锚:

  • Circle(圆)
  • Ellipse(椭圆)
  • Triangle(三角形)
  • Diamond(菱形)
  • Rectangle(矩形)
  • Square(正方形)
jsPlumb.addEndpoint("someElement", {     endpoint:"Dot",     anchor:[ "Perimeter", { shape:"Circle" } ]
});

如果锚点的宽高一样,该锚点位置为动态圆周。宽高不同为椭圆,类似正方形和矩形。

默认情况下,锚点个数为60,我们还可以手动指定:

eg(指定150个动态锚点):

 jsPlumb.addEndpoint("someDiv", {     endpoint:"Dot",     anchor:[ "Perimeter", { shape:"Square", anchorCount:150 }] });

默认定义: jsPlumb提供了一个动态锚 AutoDefault 选择从 前 , 右 , 底 和 左 :

 jsPlumb.connect({...., anchor:"AutoDefault", ... });

connector: 连接线类型。此处我们选择了 StateMachine。类型的名称为 Connector 命名空间下的类名。可选值为:

  • Bezier
  • Flowchart
  • StateMachine
  • Straight

endpoint: 端点类型。此处我们选择了 Blank,也就是没有可见的端点。endpoint可选值为:

  • Blank
  • Dot
  • Image
  • Rectangle

overlays: 是一个数组,数组中的每一项定义一个 overlay。overlay 是附加于 connector 上的部件,例如箭头、标签等。每一个 overlay 定义使用一个数组,第一项为 overlay 的类型,第二项是一个对象,用来定义 overlay 的配置参数。不同类型的 overlay 其配置项也是不同的。这里以上例中的箭头为例:

overlays: [ [‘Arrow’, { width: 8, length: 8, location: 1 }] ],定义了一个 大小为 8 px,位于 connector 末端的箭头。可用的 overlay 类型有:

  • Arrow - 箭头
  • Label - 标签
  • PlainArrow - 平头箭头
  • Diamond - 菱形
  • Diamond(钻石):钻石箭头
  • Custom - 自定义

paintStyle: connector 样式(颜色和线宽)。

endpointStyle: endpoint 样式(填充颜色,边框颜色和边框宽度)。此处我使用的是 Blank 类型,不需要配置此参数,因此将其作为注释列在这里供大家参考。

默认配置:

我们在元素之间建立了一条连接线,事实上,我们常常需要画很多连接线,它们的样式是相同的,这时我们就可以使用 jsPlumb.connect() 的第二个参数。

第二个参数接收一个对象,作为 connect 的默认配置。它会自动附加到第一个参数的配置中去。

我们定义一个默认配置,然后为整个状态图建立对应的关系。

<script>import {jsPlumb} from 'jsplumb'export default {name: 'landing-page',mounted () {let plumbIns = jsPlumb.getInstance()let defaultConfig = {// 对应上述基本概念anchor: ['Left', 'Right', 'Top', 'Bottom', [0.3, 0, 0, -1], [0.7, 0, 0, -1], [0.3, 1, 0, 1], [0.7, 1, 0, 1]],connector: ['StateMachine'],endpoint: 'Blank',// 添加样式paintStyle: { stroke: '#909399', strokeWidth: 2 }, // connector// endpointStyle: { fill: 'lightgray', outlineStroke: 'darkgray', outlineWidth: 2 } // endpoint// 添加 overlay,如箭头overlays: [ ['Arrow', { width: 8, length: 8, location: 1 }] ] // overlay}
​      let relations = [
​        ['item-4', 'item-1'],
​        ['item-1', 'item-5'],
​        ['item-5', 'item-2'],
​        ['item-2', 'item-6'],
​        ['item-6', 'item-3'],
​        ['item-3', 'item-7'],
​        ['item-7', 'item-9'],
​        ['item-9', 'item-6'],
​        ['item-6', 'item-8'],
​        ['item-8', 'item-5'],
​        ['item-3', 'item-9'],
​        ['item-2', 'item-8'],
​        ['item-1', 'item-4'],
​        ['item-5', 'item-4']
​      ]​      plumbIns.ready(function () {​          //在item-4节点上添加一个端点
​        let anEndpoint = plumbIns.addEndpoint('item-4', {​          anchors: [ [0.7, 1, 0, 1] ],
​          endpoint: 'Blank'
​        })​        relations.push(['item-8', anEndpoint])​        for (let item of relations) {​          plumbIns.connect({​            source: item[0],
​            target: item[1]
​          }, defaultConfig)
​        }
​      })
​    }}
</script>

效果如下:


在创建连接线时,jsPlumb 会从所有的 Anchors 中选择最合适的 Anchor 进行连接。

你也可以在 Source 或 Target 中直接指定 Endpoint 对象,来进行更精确的控制。

在上例中,我们通过 jsPlumb.addEndpoint() 创建了 Endpoint 对象。第一个参数是元素的 id 属性。第二个参数是此 Endpoint 的相关配置。完整的配置项请参考官方文档。

原本 State8 -> State4 的箭头是连接到 State4 右边的中点,如下图

通过指定具体的 Endpoint,我们将它连接到了底部。

下面就让我们来练习一个可拽拽的拓扑图demo把:

第一步:我们先设置好dom结构:

<template><div class="hello"><h1 style="text-align:center;padding:50px">JsPlumb + D3js实现自定义节点,可拖拽节点,自动树状布局</h1><div id="relation-box"><divclass="node"v-for="item in nodeList":key="item.id":style="{left: item.left, top: item.top}":id="'node-'+ item.id">{{ item.name }}<div>detail...</div></div></div></div>
</template>

第二步:设置元素的css样式

<style scoped>
#relation-box {position: relative;
}.node {position: absolute;padding: 20px;border: 1px solid #ccc;border-radius: 20px;text-align: center;background-color: #f6f6f6;
}
</style>

第三步我们导入需要使用到的工具:

import { jsPlumb } from "jsplumb";
import * as D3 from "d3";

第四步:准备好需要的默认配置和数据

data () {return {jsPlumbInstance: "", //jsPlumb实例// jsPlumb默认配置jsPlumbSetting: {// 动态锚点、位置自适应Anchors: ['Top', 'TopCenter', 'TopRight', 'TopLeft', 'Right', 'RightMiddle', 'Bottom', 'BottomCenter', 'BottomRight', 'BottomLeft', 'Left', 'LeftMiddle'],// 连线的样式 StateMachine、Flowchart,Bezier、StraightConnector: ['Bezier', { curviness: 60 }],// 鼠标是否拖动删除线ConnectionsDetachable: false,// 删除线的时候节点不删除DeleteEndpointsOnDetach: false,// 连线的两端端点类型:矩形 Rectangle;圆形Dot; eight: 矩形的高 ,idth: 矩形的宽Endpoints: [['Dot', { radius: 2, }], ['Dot', { radius: 2 }]],// 线端点的样式EndpointStyle: { fill: 'skyblue', outlineWidth: 1 },// 绘制连线PaintStyle: {stroke: '#000000',strokeWidth: 1,outlineStroke: 'transparent',// 设定线外边的宽,单位pxoutlineWidth: 10},// 绘制连线箭头Overlays: [// 箭头叠加['Arrow', {width: 10, // 箭头尾部的宽度length: 8, // 从箭头的尾部到头部的距离location: 1, // 位置,建议使用0~1之间direction: 1, // 方向,默认值为1(表示向前),可选-1(表示向后)foldback: 0.623 // 折回,也就是尾翼的角度,默认0.623,当为1时,为正三角}]],// 绘制图的模式 svg、canvasRenderMode: 'svg',DragOptions: { cursor: 'pointer', zIndex: 2000 },// 鼠标滑过线的样式HoverPaintStyle: { stroke: 'skyblue', strokeWidth: 3, cursor: 'pointer' },},// 连线的配置jsPlumbConnectOptions: {isSource: true,isTarget: true,// 动态锚点、提供了4个方向 Continuous、AutoDefaultanchor: "Continuous",overlays: [['Arrow', { width: 8, length: 8, location: 1 }]] // overlay},commonLink: {isSource: true,isTarget: true,anchor: ["Perimeter", { shape: "Circle" }],connector: ['Bezier'],endpoint: 'Dot',// 不限制节点的连线数量maxConnections: -1,},dataList: {id: 1,name: "中国",children: [{id: 2,name: "北京",children: [{id: 6,name: "海淀区"},{id: 7,name: "高新区"}]},{id: 3,name: "贵州",children: [{id: 4,name: "贵阳"},{id: 5,name: "黔西南"},{id: 8,name: "黔东南"}]}]},nodeList: [],lineList: []}
}

第五步:定义设置节点和设置连线的函数:

setNodeInfo (tree) {// 参考D3API,这里会生成树形数据结构const data = D3.hierarchy(tree);// 使用D3 Tree自动布局, nodeSize控制节点x方向和y方向上的距离const treeGenerator = D3.tree().nodeSize([200, 180]);const treeData = treeGenerator(data);// 获取自动布局后的节点信息const nodes = treeData.descendants();// 获取父子关系列表const links = treeData.links();// 设置节点位置信息this.nodeList = nodes.map(item => {return {id: item.data.id,name: item.data.name,left: item.x + 900 + "px", // 900为初始X方向起点位置,默认为0top: item.y + "px"};});this.lineList = links.map(item => {return {source: `node-${item.source.data.id}`,target: `node-${item.target.data.id}`,overlays: [["Arrow", { width: 10, length: 10, location: 0.5 }]]}})},
drawLines () {this.$nextTick().then(() => {jsPlumb.ready(() => {// 创建jsPlumb实例this.jsPlumbInstance = jsPlumb.getInstance();// 导入准备好的jsPlumb配置this.jsPlumbInstance.importDefaults(this.jsPlumbSetting);// 开始节点间的连线this.lineList.forEach((item) => {this.jsPlumbInstance.connect(item, this.jsPlumbConnectOptions);});// 让每个节点都可以被拖拽this.nodeList.forEach((item, index) => {this.jsPlumbInstance.draggable("node-" + (index + 1))})// 给每个节点添加锚点this.jsPlumbInstance.addEndpoint("node-" + (index + 1), {anchor: ['Bottom', 'Top', 'Left', 'Right'],Overlays: [['Arrow', { width: 10, length: 8, location: 1, direction: 1, foldback: 0.623 }]]}, this.commonLink)})this.jsPlumbInstance.repaintEverything(); // 重绘})}

第六步:在mounted生命周期中执行定义的函数:

  mounted () {     this.setNodeInfo(this.dataList);     this.drawLines();   }

当我们执行setNodeInfo方法后

接着执行drawLines方法后:


this.jsPlumbInstance.draggable(“node-” + (index + 1)) 该方法可以让每个节点都可被拖拽:

this.jsPlumbInstance.addEndpoint 该方法可以让每个节点都添加一个锚点,该锚点可以存在在四个方向,连线成功后jsPlumb会根据节点的位置动态变化锚点的位置


总结:

1.我们在使用时该注意我们需要将节点的容器设置相对定位,并且给节点设置绝对定位,因为jsPlumb是通过定位的方式来实现节点在容器中拖拽的

2.我们使用D3仅是让节点更好地快速地排布,我们也可以将节点的位置信息保存在dataList中,然后再设置每个节点的初始位置

Jsplumb基础教程(vue+jsplumb+d3)相关推荐

  1. jsPlumb中文基础教程

    jsplumb 中文基础教程Descriptionhttps://wdd.js.org/jsplumb-chinese-tutorial/#/参考学习网址 是一个很好的学习的网站

  2. JSplumb 中文教程(前端自定义组件,流程图,拓扑图)

    1. jsplumb 中文基础教程 后续更新会在仓库:jsplumb仓库地址 本文的图片是托管于七牛云的,由于使用的是测试域名,可能不知道哪天,图片就无法显示了.不过每个例子都有简单的在线demo, ...

  3. jsplumb php,jsplumb 中文教程 连线绘图工具库介绍 附简单在线demo与实战项目

    1. jsplumb 中文基础教程 后续的更新会直接在github上,如需查看最新文档,请直接访问原始仓库. 另外用了七牛的测试域名做图片托管,现在发现很多图片都无法显示了,建议直接参看git仓库里的 ...

  4. vue+jsplumb 实现根据数据渲染出连线绘图

    vue+jsplumb 实现根据数据渲染出连线绘图 jsPlumb是一个比较强大的绘图组件,它提供了一种方法,主要用于连接网页上的元素.在现代浏览器中,它使用SVG或者Canvas技术,而对于IE8以 ...

  5. Vue基础教程视频:

    Vue基础教程视频: https://www.bilibili.com/video/av25142267 https://www.bilibili.com/video/av25143408 https ...

  6. c语言常量类型转换,c语言基础教程常量变量和类型转换,免费版.doc

    c语言基础教程常量变量和类型转换,免费版 第二章??????常量变量和类型转换2.1.1数字常量??? 数字常量包括整型常量和浮点型常量,浮点型常量又称实数.????1.整型常量???? HYPERL ...

  7. Spring Boot 2.x基础教程:JdbcTemplate的多数据源配置

    点击上方蓝色"程序猿DD",选择"设为星标" 回复"资源"获取独家整理的学习资料! 在本系列之前的教程中,我们已经介绍了如何使用目前最常用的 ...

  8. 黑马程序员最新版JavaWeb基础教程-学习笔记

    da@黑马程序员最新版JavaWeb基础教程-学习笔记 day06-HTML&CSS HTML HTML(HyperTest Markup Language):超文本标记语言 是一门语言,所有 ...

  9. 【ThreeJS基础教程】0.在学习使用ThreeJS之前

    [ThreeJS基础教程]0.在学习使用ThreeJS之前 什么人适合学习ThreeJS 什么人不建议学习ThreeJS或学起来比较累 了解ThreeJS ThreeJS文档在哪 关于<Thre ...

最新文章

  1. 微软:Azure AI是OpenAI技术商业化变现唯一、排他性合作方
  2. Linux下C语言实现LCD屏幕截图
  3. 《零基础》MySQL 事务(二十二)
  4. 40 行代码搞定主题词提取
  5. SSH服务-SSH秘钥对登陆
  6. 【转】vb OPen文本文件的操作
  7. python 仪表盘实现_Python实现数据可视化看如何监控你的爬虫状态【推荐】
  8. SQL Server : 使用SQL Express的User Instance(用户实例)特性
  9. 我的女儿二三事(七)(r12笔记第58天)
  10. 【转载】斐讯K2P B1刷入金梅林固件,加koolproxy、S-S R等【V1.0尝鲜版】
  11. DS Storage Manager 忘记管理密码恢复
  12. NR中关于RE、RB、CRB、PRB、VRB、REG、RBG、CCE等概念
  13. 中国塑料加工工业协会侵犯群益公司名誉权 法院判决赔偿财产损失和赔礼道歉30天
  14. 计算机就是三角函数,三角函数计算器
  15. 神经网络和深度学习简史(全)
  16. IP Camera 基础知识
  17. 二极管与、或门,三极管非门电路原理
  18. COB-软封装的一些理解
  19. Python+unittest+requests 接口自动化测试框架搭建 完整的框架搭建过程 实战
  20. MATLAB教程(1) MATLAB 基础知识

热门文章

  1. 一代计算机巨星的陨落!图灵奖得主 Edmund Clarke 因感染“新冠”逝世
  2. 最大公约数和最小公倍数问题
  3. 2016~2017跨年之际所感
  4. 一首妈妈的诗,写给地震中死去的孩子!
  5. 达内培训python可以吗
  6. 支付系统设计白皮书:契合业务形态的收银台设计思路
  7. 大学期间月收入将近破万,我是如何做到的
  8. 配置QQ企业邮箱小结
  9. 普迪文集团:八个马来西亚留学的热门专业
  10. plsql查看主键_Oracle 查询主键和索引