1. 启动vue项目,执行以下命令安装dagre、graphlib、jointjs、svg-pan-zoom。
  npm install dagre graphlib jointjs svg-pan-zoom --save
  1. 新建vue文件,为svg准备父节点,以及部分初始化数据。
<template>
<div class="container">
<div id="paper"></div>
</div>
</template>
<script>
import dagre from "dagre";
import graphlib from "graphlib";
import * as joint from "jointjs";
import '/node_modules/jointjs/dist/joint.css';
import svgPanZoom from 'svg-pan-zoom';
export default {
data(){
return {
graph: null,
paper: null,
/** 原始数据:节点 */
nodes: [
{ id: 1, label: 'node1' },
{ id: 2, label: 'node2' },
{ id: 3, label: 'node3' },
],
/** 原始数据:连线 */
links: [
{ from: 1, to: 2 },
{ from: 1, to: 3 }
],
/** 处理后生成的节点 */
nodeList: [],
/** 处理后生成的连线 */
linkList: []
}
},
methods: {
/** 页面初始化 */
init(){
/** 此处是下面依次写的几个函数执行 */
}
},
mounted(){
this.init();
}
</script>
  1. 初始化画布,完成画布的初始化。
/** 初始化画布,按照joint文档来就可以,具体画布的尺寸和颜色自定义 */
initGraph() {
let paper = document.getElementById('paper');
this.graph = new joint.dia.Graph();
this.paper = new joint.dia.Paper({
dagre: dagre,
graphlib: graphlib,
el: paper,
model: this.graph,
width: '100%',
height: 'calc(100vh - 100px)',
background: {
color: '#f5f5f5'
},
/** 是否需要显示单元格以及单元格大小(px) */
// drawGrid: true,
// gridSize: 20,
});
}

将initGraph方法放入init执行后,页面上应当出现了一个浅灰色的画布( ̄▽ ̄)~*。

  1. 创建完画布之后,在画布上绘制节点。
/** 创建节点 */
createNode(){
/** 遍历节点原始数据,通过joint.shapes.standard.Rectangle(joint内置shape)创建节点对象。 */
this.nodes.forEach(ele => {
let node = new joint.shapes.standard.Rectangle({
id: ele.id,
size: {
width: 100,
height: 50
},
attrs: {
body: {
fill: '#ddd',
stroke: 'none'
},
text: {
text: ele.label
}
}
});
/** 创建的节点对象放入list */
this.nodeList.push(node);
})
/** 通过graph的addCell方法向画布批量添加一个list */
this.graph.addCell(this.nodeList);
}

执行完createNode方法,页面上出现了3个矩形覆盖在一起,可以拖动,是吧ಠᴗಠ。

  1. 把之前几个叠罗汉的矩形通过线连接起来。

    遍历links列表,通过joint.shapes.standard.Link创建节点间的连接关系。
/** 创建连线 */
createLink(){
this.links.forEach(ele => {
let link = new joint.shapes.standard.Link({
source: {
id: ele.from
},
target: {
id: ele.to
},
attrs: {
line: {
stroke: '#aaa',
strokeWidth: 1
}
}
});
/** 创建好的连线push进数组 */
this.linkList.push(link);
})
/** 通过graph.addCell向画布批量添加连线 */
this.graph.addCell(this.linkList);
}

发现执行完之后,页面上节点和连线都缩在一起聊天了(ㅍ_ㅍ)。。。可以拖动分散开,会看到隐藏的连线,不要慌,下面给它布个局分散开就行了~~

注意:必须先创建节点再创建连线,连线的数据可以看出是跟节点息息相关的,没有节点,也就没有从节点a指向节点b这条连线了,页面上会出现找不到节点的报错。

  1. 节点是可以通过position属性指定渲染位置的,例如: position: { x: 100, y: 200 }。连线是根据节点的位置计算来的。

    但是一般得到的数据大概率不会给你每个节点的具体坐标,所以自动布局是很有必要的。布局算法其实是dagre实现的,要是有兴趣可以去查查昂。
/** 画布节点自动布局,通过joint.layout.DirectedGraph.layout实现 */
randomLayout(){
joint.layout.DirectedGraph.layout(this.graph, {
  dagre: dagre,
  graphlib: graphlib,
/** 布局方向 TB | BT | LR | RL */
  rankDir: "LR",
/** 表示列之间间隔的像素数 */
  rankSep: 200,
/** 相同列中相邻接点之间的间隔的像素数 */
  nodeSep: 80,
/** 同一列中相临边之间间隔的像素数 */
edgeSep: 50
});
}

执行完后,关系图已经是我们想要的样子了。但是图的位置在左上角,并且整个画布不可拖动,不是很灵活。所以使用svg-pan-zoom来优化动作。

  1. svg-pan-zoom实现画布拖拽缩放等操作。
/** svgpanzoom 画布拖拽、缩放 */
svgPanZoom(){
/** 判断是否有节点需要渲染,否则svg-pan-zoom会报错。 */
if(this.nodes.length){
let svgZoom = svgPanZoom('#paper svg', {
/** 是否可拖拽 */
panEnabled: true,
/** 是否可缩放 */
zoomEnabled: true,
/** 双击放大 */
dblClickZoomEnabled: false,
/** 可缩小至的最小倍数 */
minZoom: 0.01,
/** 可放大至的最大倍数 */
maxZoom: 100,
/** 是否自适应画布尺寸 */
fit: true,
/** 图是否居中 */
center: true
})
/** 手动设置缩放敏感度 */
svgZoom.setZoomScaleSensitivity(0.5);
}
}

由于设置了fit:true导致图会自适应画布大小,节点少的话会导致图过分放大,如不需要自适应画布,可以设置为false。也可以在fit:true基础上天添加以下代码进行优化。

/** fit:true 元素数量较少时,会引起元素过度放大,当缩放率大于1时,将图像缩小为1;小于等于1时,为体现出边距更显美观,整体缩放至0.9 */
let {sx, sy} = this.paper.scale();
if(sx > 1){
svgZoom.zoom(1/sx);
} else {
svgZoom.zoom(0.9);
}

可以看到图已经非常靠近我们想要的样子了ヽ(゚∀゚)メ(゚∀゚)ノ ,但还是有美中不足的地方,在拖拽节点时,会发现连着画布一起移动了,并且节点还哆哆嗦嗦的,这明显不太行。

没有使用svg-pan-zoom时节点是可以单独拖拽的,使用了之后,svg-pan-zoom影响了jointjs的节点拖拽事件。也就是说svg-pan-zoom影响了jointjs的节点拖拽事件。

解决这种情况,只需要在svg-pan-zoom判断是否拖拽的节点,并不触发相应事件即可。

  1. svg-pan-zoom有beforePan方法的配置:

beforePan will be called with 2 attributes:

  • oldPan
  • newPan

Each of these objects has two attributes (x and y) representing current pan (on X and Y axes).

If beforePan will return false or an object {x: true, y: true} then panning will be halted. If you want to prevent panning only on one axis then return an object of type {x: true, y: false}. You can alter panning on X and Y axes by providing alternative values through return {x: 10, y: 20}.

可以看到在beforePan里返回false 或者 { x: true, y: true } 即可停止拖拽。

ps: 但是我试了{ x: true, y: true }不得行ヽ(ー_ー)ノ,但是{ x: false, y: false }是可以的。

  • 首先确定当前拖拽的是节点, 为paper添加事件,判断当前点击并拖拽的是节点
    /** 给paper添加事件 */
    paperEvent(){
    /** 确认点击的是节点 */
    this.paper.on('element:pointerdown', (cellView, evt, x, y) => {
    this.currCell = cellView;
    })
    /** 在鼠标抬起时恢复currCell为null */
    this.paper.on('cell:pointerup blank:pointerup', (cellView, evt, x, y) => {
    this.currCell = null;
    })
    }
  • 同时在svgPanZoom的配置里增加以下属性:
    /** 判断是否是节点的拖拽 */
    beforePan: (oldPan, newPan) => {
    if(this.currCell){
    return false;
    }
    }

现在这个效果是我们想要的了,d=====( ̄▽ ̄*)b

  1. 整个页面完整代码如下:
<template>
<div class="container">
<div id="paper"></div>
</div>
</template>
<script>
import dagre from "dagre";
import graphlib from "graphlib";
import * as joint from "jointjs";
import '/node_modules/jointjs/dist/joint.css';
import svgPanZoom from 'svg-pan-zoom';
export default {
data(){
return {
graph: null,
paper: null,
/** 原始数据:节点 */
nodes: [
{ id: 1, label: 'node1' },
{ id: 2, label: 'node2' },
{ id: 3, label: 'node3' }
],
/** 原始数据:连线 */
links: [
{ from: 1, to: 2 },
{ from: 1, to: 3 }
],
/** 处理后生成的节点 */
nodeList: [],
/** 处理后生成的连线 */
linkList: [],
/** 当前单元格,joint的拖动和svgpanzoom会冲突造成抖动 */
currCell: null,
}
},
methods: {
init(){
this.initGraph();
this.createNode();
this.createLink();
this.randomLayout();
this.svgPanZoom();
this.paperEvent();
},
/** 初始化画布 */
initGraph() {
this.nodeList = [];
this.linkList = [];
let paper = document.getElementById('paper');
this.graph = new joint.dia.Graph();
this.paper = new joint.dia.Paper({
dagre: dagre,
graphlib: graphlib,
el: paper,
model: this.graph,
width: '100%',
height: 'calc(100vh - 100px)',
background: {
color: '#f5f5f5'
},
// drawGrid: true,
// gridSize: 20,
});
},
/** 创建节点 */
createNode(){
this.nodes.forEach(ele => {
let node = new joint.shapes.standard.Rectangle({
id: ele.id,
size: {
width: 100,
height: 50
},
attrs: {
body: {
fill: '#ddd',
stroke: 'none'
},
text: {
text: ele.label
}
}
});
this.nodeList.push(node);
})
this.graph.addCell(this.nodeList);
},
/** 创建连线 */
createLink(){
this.links.forEach(ele => {
let link = new joint.shapes.standard.Link({
source: {
id: ele.from
},
target: {
id: ele.to
},
attrs: {
line: {
stroke: '#aaa',
strokeWidth: 1
}
}
});
this.linkList.push(link);
})
this.graph.addCell(this.linkList);
},
/** 画布节点自动布局 */
randomLayout(){
joint.layout.DirectedGraph.layout(this.graph, {
  dagre: dagre,
  graphlib: graphlib,
/** 布局方向 TB | BT | LR | RL */
  rankDir: "LR",
/** 表示列之间间隔的像素数 */
  rankSep: 200,
/** 相同列中相邻接点之间的间隔的像素数 */
  nodeSep: 80,
/** 同一列中相临边之间间隔的像素数 */
edgeSep: 50
});
},
/** svgpanzoom 画布拖拽、缩放 */
svgPanZoom(){
if(this.nodes.length){
let svgZoom = svgPanZoom('#paper svg', {
/** 是否可拖拽 */
panEnabled: true,
/** 是否可缩放 */
zoomEnabled: true,
/** 双击放大 */
dblClickZoomEnabled: false,
/** 可缩小至的最小倍数 */
minZoom: 0.01,
/** 可放大至的最大倍数 */
maxZoom: 100,
/** 是否自适应画布尺寸 */
fit: true,
/** 图是否居中 */
center: true,
/** 判断是否是节点的拖拽 */
beforePan: (oldPan, newPan) => {
if(this.currCell){
return false;
}
}
})
svgZoom.setZoomScaleSensitivity(0.5);
/** fit:true 元素数量较少时,会引起元素过度放大,当缩放率大于1时,将图像缩小为1;小于等于1时,为体现出边距更显美观,整体缩放至0.9 */
let {sx, sy} = this.paper.scale();
if(sx > 1){
svgZoom.zoom(1/sx);
} else {
svgZoom.zoom(0.9);
}
}
},
paperEvent(){
this.paper.on('element:pointerdown', (cellView, evt, x, y) => {
this.currCell = cellView;
})
this.paper.on('cell:pointerup blank:pointerup', (cellView, evt, x, y) => {
this.currCell = null;
})
},
},
mounted(){
this.init();
}
}
</script>

[vue2 + jointjs + svg-pan-zoom] 节点自动布局渲染 + 拖拽缩放的更多相关文章

  1. 基于svg.js实现对图形的拖拽、选择和编辑操作

    本文主要记录如何使用 svg.js 实现对图形的拖拽,选择,图像渲染及各类形状的绘制操作. 1.关于SVG SVG 是可缩放的矢量图形,使用XML格式定义图像,可以生成对应的DOM节点,便于对单个图形 ...

  2. 使用d3.js的时候,如何用zoom translate scale限制拖拽范围

    红色代表需要改写的代码 1.添加定义图像大小和容器的大小及坐标 d3.behavior.zoom = function () { var moveCanvas={ width: , height: , ...

  3. Jtree (节点的渲染+资源管理器)

    我们的还是自定义的Jtree的类: package jtree.customNode; import java.io.File; import javax.swing.JTree; import ja ...

  4. D3.js 力导向图的拖拽(drag)与缩放(zoom)

    不知道大家会不会跟我一样遇到这样的问题,在之前做的力导向图的基础上加上缩放功能的时候,拖动节点时整体会平移不再是之前酷炫的效果(失去了拉扯的感觉!).天啊,简直不能接受如此丑X的效果.经过不懈的努力终 ...

  5. D3.js+Es6+webpack构建人物关系图(力导向图),动态更新数据,点击增加节点,拖拽增加连线...

    觉得不错的麻烦加个Star:https://github.com/zhangzn3/D3-Es6 在线预览地址:https://zhangzn3.github.io/D3-Es6 功能列表:1. 增加 ...

  6. ztree插件的使用及列表项拖拽的实现(jQuery)+异步加载节点数据

    为了实现如图所示的树状结构图,并使列表项可拖动到盒子里,研究了ztree这个插件的使用,并仔细研究了列表项的拖动事件.完成了预期需求,对jQuery的运用得到了提高.这个插件的功能非常强大,除了基本的 ...

  7. winform 两个TreeView间拖拽节点

    /// <summary> /// 正在拖拽的节点 /// </summary> private TreeNode DragNode = null; /// <summa ...

  8. jstree 节点拖拽保存数据库

    需要jstree具有拖拽功能需要在加载jstree时添加dnd插件,具体看代码: $('**').jstree({ //plugins-各种jstree的插件引入,展示树的多样性 'plugins' ...

  9. TREEVIEW节点拖拽

    http://files.cnblogs.com/xe2011/TreeView_Drag_and_Drop.rar       假设把A节点往B节点上拖拽 那么  A 为Node1,B为Node2 ...

  10. 超炫HTML5 SVG聊天框拖拽弹性摇摆动画特效

    这是一款很有创意的HTML5 SVG聊天框拖拽弹性摇摆动画特效. 用户能够用鼠标点击或用手滑动聊天框上的指定区域,该区域会以很有弹性的弹簧效果拉开聊天用户列表.点击一个用户头像后.又以同样的弹性特效切 ...

随机推荐

  1. 基于.NET Core3.1的SQLiteHelper增删改帮助类

    安装驱动包 install-package Microsoft.Data.Sqlite -version 3.1.7 install-package System.Data.SQLite.Core - ...

  2. DB2日常维护操作

    一. DB2日常维护操作 1.数据库的启动.停止.激活 db2 list active databases db2 active db 数据库名 db2start --启动 db2stop [forc ...

  3. css - object-fit ie兼容

    css - object-fit ie兼容 参考资料 github 解决object-fit兼容IE浏览器实现图片自适应 demo <!-- * @createDate: 2022-08-30 ...

  4. lua中self的意义

    原文链接 最近在用合宙的Air302开发物联网项目,因为合宙用的自家的luatOS操作系统,二次开发全都要用lua写,据说lua写起代码比C更方便,但是不会就是不会啊喂!!学不会就是不方便啊,例如这个 ...

  5. 「SOL」NOI2016 Day1 解题报告

    第一次打 NOI,还是先把以前的 NOI 题刷一遍吧? # 目录 目录 # 目录 # A. 优秀的拆分 excellent # B. 网格 grid # C. 循环之美 cyclic # 小结 # 源 ...

  6. AngularJs directive详解及示例代码

    Directive(指令)笔者认为是AngularJ非常强大而有有用的功能之一.它就相当于为我们写了公共的自定义DOM元素或CLASS属性或ATTR属性,并且它不只是单单如此,你还可以在它的基础上来操 ...

  7. MongoDB 分片模式

    Sharding (分片模式) 副本集可以解决主节点发生故障导致数据丢失或不可用的问题,但遇到需要存储海量数据的情况时,副本集机制就束手无策了.副本集中的一台机器可能不足以存储数据,或者说集群不足以提 ...

  8. go理论知识总结

    基于const常量理解个中类型的内存分配引入参考 官方:Constant expressions may contain only constant operands and are evaluate ...

  9. JavaScript 取消事件的默认动作

    preventDefault() 方法 Event 对象 定义和用法 取消事件的默认动作. 语法 event.preventDefault() 说明 该方法将通知 Web 浏览器不要执行与事件关联的默 ...

  10. 微软开源 Python 自动化神器 Playwright

    背景 逛博客时候突然看到 Playwright web自动化,感觉很有意思,就翻看了很多博客,简单记录一下. 简介 Playwright是一个强大的Python库,仅用一个API即可自动执行Chrom ...