Jsplumb基础教程
设计流程图
目前github社区存在几款可用于设计流程图的绘图框架:
go.js( www.gojs.net/latest/inde…) :go.js 提供一整套的JS工具 ,支持各种交互式图表的创建;目前go.js 是闭源收费的 jsPlumb(jsplumbtoolkit.com/): jsPlumb是一套开源的流程图创建工具 ,小巧精悍,使用简单;jsPlumb 有社区版跟收费版,我们可使用的是社区版 JointJS(www.jointjs.com/): JointJS是一个开源的、基于JavaScript的图表库,可以用来创建静态图表、完全可交互的图表、 WEB在线流程图、应用程序 mxGraph(jgraph.github.io/mxgraph/): mxGraph是一个js绘图组件,适用在网页设计/编辑流程图、图表、网络图和普通图形的web应用程序,draw.io工具就是基于mxGraph开发的。缺点就是介绍框架简介以及API文档不全,社区问题也较少更新; GG-Editor(g6.antv.vision/) :GG-Editor是基于 G6 和 React 的可视化图编辑器,G6 是一个图可视化引擎。它提供了图的绘制、布局、分析、交互、动画等图可视化的基础能力。 最终,我选择了jsPlumb,因为它完全开源,文档也较齐全,社区也比较活跃在开发过程中有问题也可以和其他开发者交流;mxGraph和JointJS也不错。大家可以根据自己的需要选择。后期也会着手调研mxGraph,因为基于它实现了功能非常全的draw.io,相信里面也会有非常多好的思路可以参考;
1.什么是jsplumb?
jsplumb是可以让你在网站上展示图表或者甚至在浏览器应用程序中使用图表的开发框架,该框架适用于必须绘制图表的Web应用程序,例如类似于Visio的应用程序或工作流程设计器等。由于图表项目和连接的所有参数都是非常精细可控的,因此你可以绘制你可以想到的任何类型的图表
2.jsplumb基础知识
官方文档地址:docs.jsplumbtoolkit.com/community/c…
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">item 1</div>
<div id="item-2" class="state-item">item 2</div>
</div>
</div>
</template>
<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>
<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)js
- y-相对该锚点y轴坐标比例(最大1)
- dx-控制锚的方向
- dy-同上
//定义了一个在底部中间的锚点位置
jsPlumb.connect({...., anchor:[ 0.5, 1, 0, 1 ], ... });
在上例中,我们定义了四个边的中点为 Anchors,然后还增加了上下边距离两端 0.5 的点作为 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 的默认配置。它会自动附加到第一个参数的配置中去。
我们定义一个默认配置,然后为整个状态图建立对应的关系。
<template>
<div id="wrapper">
<div class="line-wrap" style="margin-left: 70px">
<div id="item-1" class="state-item">State 1</div>
</div>
<div class="line-wrap" style="margin-left: 70px">
<div id="item-2" class="state-item">State 2</div>
</div>
<div class="line-wrap" style="margin-left: 90px">
<div id="item-mian" class="item-mian">mian</div>
</div>
<div class="line-wrap" style="margin-left: -60px; margin-bottom: 100px">
<div id="item-3" class="state-item">State 3</div>
<div id="item-4" class="state-item">State 4</div>
</div>
<div class="line-wrap" style="margin-left: 70px;">
<div id="item-5" class="state-item">State 5</div>
</div>
<div class="line-wrap" style="margin-left: 70px">
<div id="item-6" class="state-item">State 6</div>
</div>
<div class="line-wrap" style="margin-left: 70px">
<div id="item-7" class="state-item">bootom</div>
</div>
</div>
</template>
<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.9, 0, 0, -1],
[0.4, 1, 0, 1],
[0.5, 1, 0, 1],
],
connector: ["Flowchart"],
endpoint: "Blank",
// 添加样式
paintStyle: { stroke: "#909399", strokeWidth: 2 }, // connector
// endpointStyle: { fill: 'lightgray', outlineStroke: 'darkgray', outlineWidth: 2 } // endpoint
// 添加 overlay,如箭头
overlays: [["Arrow", { width: 5, length: 5, location: 1 }]], // overlay
};
let relations = [
["item-1", "item-2"],
["item-2", "item-mian"],
["item-mian", "item-3"],
["item-mian", "item-4"],
["item-3", "item-5"],
["item-4", "item-5"],
["item-5", "item-6"],
["item-6", "item-7"],
];
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>
<style >
#wrapper {
background: radial-gradient(
ellipse at top left,
rgba(255, 255, 255, 1) 40%,
rgba(229, 229, 229, 0.9) 100%
);
display: flex;
flex-direction: column;
justify-content: center;
align-content: center;
height: 100vh;
padding: 60px 80px;
width: 800px;
}
.item-mian,
.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: 160px;
}
.item-mian {
width: 40px;
height: 20px;
font-size: 10px;
line-height: 20px;
border-radius: 10px;
}
.line-wrap {
display: flex;
margin-bottom: 30px;
}
</style>
效果图
转载自:https://juejin.cn/post/7087083636972847141