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. css初始化、background属性、jquery选择器模糊匹配、canvas

    1.css初始化 2.background 3.jquery选择器模糊匹配 4.canvas画多边形 5.通过css设置canvas背景图片 6.js动态生成变量名

  2. Java方法之命令行传递参数

    命令行传参 有时候希望运行一个程序时候再传递给它消息.这要靠传递命令行参数给main()函数实现. public class Demo05 { public static void main(Stri ...

  3. QListWidget调用Clear时报错:ASSERT failure in QList<T>::at: "index out of range"的解决方法

    原因:连接了信号currentRowChanged(int)和槽,当选中其中的一行时,在调用Clear函数就会报错. 解决方法:不连接currentRowChanged(int)信号和槽,改为连接it ...

  4. git 本地电脑重新装git后 更新github项目报错 fatal: detected dubious ownership in repository at

    解决方法参考: fatal: detected dubious ownership in repository at 'D:/'之解决方法 1.今天在学习git的时候出现这个错误: 2.执行下面代码即 ...

  5. Pytest 多进程并发执行

    在用例执行的过程中,想要用多进程并发执行测试用例,如何实现呢,其实很简单,pytest有对应的模块,安装方式. 安装 pip install pytest-xdist 使用 pytest test_d ...

  6. flask-基础篇02 请求与响应

    一.处理请求 1.URL路径参数(动态路由) # 例如,有一个请求访问的接口地址为/users/123,其中123实际上为具体的请求参数,表明请求123号用户的信息.此时如何从url中提取出123的数 ...

  7. Java-Java数据类型对应MySql数据类型

    开发过程中常用的数据类型:   Java Mysql 备注 整型 java.lang.Integer tinyint(m) 1个字节  范围(-128~127)  java.lang.Integer ...

  8. [转]Selenium私房菜系列1 -- Selenium简介

    一.Selenium是什么? Selenium是ThroughtWorks公司一个强大的开源Web功能测试工具系列,本系列现在主要包括以下4款: 1.Selenium Core:支持DHTML的测试案 ...

  9. varchar(100)和varchar(10)的区别

    mysql存储字段"abcdef",varchar(10)和varchar(100)都可以存储,且占用的磁盘存储空间是一样的,磁盘是按照实际长度存储.但,如果需要排序等内存操作,加 ...

  10. go语言的特性

    一.golang语言特性 1. 垃圾回收 a.内存自动回收,再也不需要开发人员管理内存  //开发代码中不能存在无引用的变量,不然代码出错 b.开发人员专注业务实现,降低了心智负担 c.只需要new分 ...