JointJS 是一个强大的 JavaScript 图表库,专注于创建交互式的图表、流程图、拓扑图、UML 图等。它的核心优势在于灵活性和可扩展性,支持Vue和React集成,跨浏览器兼容。
基本的效果图
安装并使用
-
官方文档:www.jointjs.com/docs
-
这里是基于react的写法,初始化画布。
php 代码解读复制代码//初始化画布
import * as joint from '@joint/core';
export const initPaper: any = (
width: number = window.screen.width,
height: number = window.screen.height,
) => {
const graph = new joint.dia.Graph();
const paper = new joint.dia.Paper({
el: document.getElementById('paper'),
model: graph,
width: width,
height: height,
gridSize: 1,
drawGrid: true,
background: {
color: '#fff',
},
interactive: true,
frozen: false,
clickThreshold: 1,
defaultLink: new joint.shapes.standard.Link({
router: { name: 'bezierrouter' },
attrs: {
line: {
stroke: 'gray',
targetMarker: {
d: 'M 8 -5 L 0 0 L 8 5 Z',
},
},
},
}),
linkView: joint.dia.LinkView.extend({
...joint.dia.LinkView.prototype.options,
doubleLinkTools: true,
linkToolsOffset: 40,
doubleLinkToolsOffset: 60,
}),
highlighting: {
default: {
name: 'stroke',
options: {
padding: 0,
rx: 5,
ry: 5,
attrs: {
'stroke-width': 3,
stroke: '#24b0e1',
opacity: '.5',
},
},
},
},
linkPinning: false, // 禁止自动创建悬空连线
});
return { paper, graph };
};
- 创建节点并加入到画布中
php 代码解读复制代码//创建节点
const TaskNode = joint.dia.Element.define('task.Node', {
attrs: {
body: {
refWidth: '100%',
refHeight: '100%',
fill: '#fff',
stroke: '#000',
strokeWidth: 1,
rx: 5,
ry: 5,
cursor: 'pointer',
},
label: {
textVerticalAnchor: 'middle',
textAnchor: 'middle',
refX: '50%',
refY: '50%',
fontSize: 12,
fill: '#333',
},
// 状态图标组,包含图标和它的工具提示
statusIconGroup: {
cursor: 'pointer',
}
},
ports: {
groups: {
right: {
position: {
name: 'right',
args: { x: '90%', y: '50%' },
},
attrs: {
circle: {
magnet: true,
stroke: 'gray',
fill: '#fff',
r: 6,
},
},
markup: [
{
tagName: 'circle',
selector: 'circle',
},
],
},
},
},
markup: [
{
tagName: 'rect',
selector: 'body',
},
{
tagName: 'text',
selector: 'label',
},
{
tagName: 'g',
selector: 'statusIconGroup',
children: [
{
tagName: 'text',
selector: 'statusIcon',
},
{
tagName: 'title',
selector: 'statusTooltip',
}
]
}
],
});
export default TaskNode;
//初始化画布时返回了画布的实例,所以这里把创建的节点加入到画布中
graph.addCell(newNode);
- 自定义节点连线
ini 代码解读复制代码// 定义贝塞尔路由器
export const defineBezierRouter = () => {
// @ts-ignore
joint.routers.bezierrouter = (vertices: any[], args: any, linkView: any) => {
// @ts-ignore
vertices = joint.util.toArray(vertices).map(joint.g.Point);
const sourcePoint: any = linkView.sourceBBox.center();
const targetPoint: any = linkView.targetBBox.center();
sourcePoint.y += 6;
targetPoint.y -= 6;
const controlPoint1 = new joint.g.Point(sourcePoint.x, sourcePoint.y + 80);
const controlPoint2 = new joint.g.Point(targetPoint.x, targetPoint.y - 80);
const points = new joint.g.Curve(
sourcePoint,
controlPoint1,
controlPoint2,
targetPoint,
).toPoints();
points.pop();
return vertices.concat(points);
};
};
- 在页面中使用,这里做参考,具体的事件处理逻辑根据需求参考官方文档
scss 代码解读复制代码 useEffect(() => {
// 4. 初始化画布 - 设置画布的基本属性和交互选项
const { paper, graph } = initPaper();
setPaper(paper);
setGraph(graph);
// 自定义的贝塞尔曲线
defineBezierRouter();
// 监听节点移动
paper.on('cell:pointermove', cellPointerMove);
// 监听节点点击
paper.on('cell:pointerclick', CellPointerClick);
// 监听节点双击
paper.on('cell:pointerdblclick', CellPointerClick);
// 监听连接点击事件
paper.on('link:connect', handleLinkConnect);
// 监听连接松开事件
paper.on('link:pointerup', handleLinkPointerUp);
// 监听画布空白处的点击事件
paper.on('blank:pointerclick', handleBlankClick);
// 监听画布空白处的点击事件
paper.on('blank:pointerdown', (evt: any, x: number, y: number) => {
handleBlankPointerDown(evt, x, y, graph);
});
// 添加右键菜单事件监听
paper.on('cell:contextmenu', handleBlankContextMenu);
paper.on('element:magnet:contextmenu', portClick);
return () => {
// 移除事件
paper.off('element:magnet:contextmenu', portClick);
paper.off('link:connect', handleLinkConnect);
paper.off('link:pointerup', handleLinkPointerUp);
paper.off('blank:pointerdown', handleBlankPointerDown);
paper.off('blank:pointerclick', handleBlankClick);
paper.off('cell:pointerclick', CellPointerClick);
paper.off('cell:pointerdblclick', CellPointerClick);
paper.off('cell:contextmenu', handleBlankContextMenu);
paper.off('cell:pointermove', cellPointerMove);
// 清除画布
graph.clear();
paper.remove();
};
结合业务做增删改查的思路
基本的效果图:
1.首先页面分为三个部分,左侧节点的拖拽基于react-dnd和react-dnd-html5-backend来实现的,中间是画布部分,右侧是表单,当选中节点时可以配置这个节点的参数。
2. 从左侧拖拽到画布中生成一个节点,当选中一个节点的时候,使用一个react状态来存储这个节点的信息,节点信息如下:
3. 每次创建一个节点到画布上时,会生成节点的一个唯一id,那么可以根据这个id去创建一个Map数据结构,形成一个key和value的映射。
arduino 代码解读复制代码//创建一个Map数据
const nodeMap = new Map();
//创建节点
const node = new TaskNode({
//这里是节点相关配置数据
position,
size: { width, height },
attrs: attrs,
});
// 将完整的节点数据存储在nodeMap中
nodeMap.set(node.id, {
//节点相关数据 如右侧表单的数据
});
4.由于把节点的数据都存入了nodeMap中去了,如图:
5.每次需要去更新或者获取某个节点的数据时,就可以根据nodeMap去获取
csharp 代码解读复制代码 // 获取当前节点在nodeMap中的数据
const currentNodeData = nodeMap.get(id) || {};
// 获设置当前节点在nodeMap中的数据
const currentNodeData = nodeMap.set(id) || {};
评论记录:
回复评论: