中文教程
demo
<template>
<div>
<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>
<div class="hello">
<h1 style="text-align:center;">JsPlumb + D3js实现自定义节点,可拖拽节点,自动树状布局</h1>
<div id="relation-box">
<div
class="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>
</div>
</template>
<script>
import { jsPlumb } from 'jsplumb'
import * as D3 from 'd3'
export default {
name: 'landing-page',
data() {
return {
jsPlumbInstance: '', //jsPlumb实例
// jsPlumb默认配置
jsPlumbSetting: {
// 动态锚点、位置自适应
Anchors: [
'Top',
'TopCenter',
'TopRight',
'TopLeft',
'Right',
'RightMiddle',
'Bottom',
'BottomCenter',
'BottomRight',
'BottomLeft',
'Left',
'LeftMiddle'
],
// 连线的样式 StateMachine、Flowchart,Bezier、Straight
Connector: ['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',
// 设定线外边的宽,单位px
outlineWidth: 10
},
// 绘制连线箭头
Overlays: [
// 箭头叠加
[
'Arrow',
{
width: 10, // 箭头尾部的宽度
length: 8, // 从箭头的尾部到头部的距离
location: 1, // 位置,建议使用0~1之间
direction: 1, // 方向,默认值为1(表示向前),可选-1(表示向后)
foldback: 0.623 // 折回,也就是尾翼的角度,默认0.623,当为1时,为正三角
}
]
],
// 绘制图的模式 svg、canvas
RenderMode: 'svg',
DragOptions: { cursor: 'pointer', zIndex: 2000 },
// 鼠标滑过线的样式
HoverPaintStyle: { stroke: 'skyblue', strokeWidth: 3, cursor: 'pointer' }
},
// 连线的配置
jsPlumbConnectOptions: {
isSource: true,
isTarget: true,
// 动态锚点、提供了4个方向 Continuous、AutoDefault
anchor: '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: []
}
},
mounted() {
//https://blog.csdn.net/weixin_39085822/article/details/106879459
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
)
}
})
this.setNodeInfo(this.dataList)
this.drawLines()
},
methods: {
setNodeInfo(tree) {
// 参考D3API,这里会生成树形数据结构
const data = D3.hierarchy(tree)
// 使用D3 Tree自动布局, nodeSize控制节点x方向和y方向上的距离
const treeGenerator = D3.tree().nodeSize([150, 100])
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 + 400 + 'px', // 900为初始X方向起点位置,默认为0
top: 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() {
const pos = ['Bottom', 'Top', 'Left', 'Right']
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))
pos.map(item => this.addEndpoint(index, item))
})
})
this.jsPlumbInstance.repaintEverything() // 重绘
})
},
addEndpoint(index, anchor) {
console.log('addEndpoint:', index, anchor)
this.jsPlumbInstance.addEndpoint(
'node-' + (index + 1),
{
anchor: [anchor],
Overlays: [['Arrow', { width: 10, length: 8, location: 1, direction: 1, foldback: 0.623 }]]
},
this.commonLink
)
}
}
}
</script>
<style lang="scss" scoped></style>
<style>
#wrapper {
background: radial-gradient(ellipse at top left, rgba(255, 255, 255, 1) 40%, rgba(229, 229, 229, 0.9) 100%);
height: 25vh;
padding: 10px 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;
}
#relation-box {
position: relative;
}
.node {
position: absolute;
padding: 20px;
border: 1px solid #ccc;
border-radius: 20px;
text-align: center;
background-color: #f6f6f6;
}
</style>
Performance
suspend drawing
jsplumb.setSuspendDrawing(true) // into suspend drawing model
jsplumb.setSuspendDrawing(false, true) // quit the model and repaint at once
The speed at which jsPlumb executes, and the practical limit to the number of manageable connections, is greatly affected by the browser upon which it is being run. At the time of writing, it will probably not surprise you to learn that jsPlumb runs fastest in Chrome, followed by Safari/Firefox, and then IE browsers in descending version number order.
Every connect or addEndpoint call in jsPlumb ordinarily causes a repaint of the associated element, which for many cases is what you want. But if you are performing some kind of "bulk" operation - like loading data on page load perhaps - it is recommended that you suspend drawing before doing so:
jsPlumb.setSuspendDrawing(true);
...
- load up all your data here -
...
jsPlumb.setSuspendDrawing(false, true);
Notice the second argument in the last call to setSuspendDrawing: it instructs jsPlumb to immediately perform a full repaint (by calling, internally, repaintEverything).
I said above it is recommended that you do this. It really is. It makes an enormous difference to the load time when you're dealing with a lot of Connections on slower browsers.
batch
This function abstracts out the pattern of suspending drawing, doing something, and then re-enabling drawing:
jsPlumb.batch(fn, [doNotRepaintAfterwards]); Example usage:
jsPlumb.batch(function() {
// import here
for (var i = 0, j = connections.length; i < j; i++) {
jsPlumb.connect(connections[i]);
}
});
Here, we've assumed that connections is an array of objects that would be suitable to pass to the connect method, for example:
{ source:"someElement", target:"someOtherElement" } By default, this will run a repaint at the end. But you can suppress that, should you want to:
jsPlumb.batch(function() {
// import here
}, true);
Note this method used to be called doWhileSuspended and was renamed in version 1.7.3.