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. Little Girl and Maximum Sum CodeForces - 276C - 差分

    给定一个数列 \(a= { a_1,a_2,...,a_n }\) 以及 \(q\) 次查询. 其中第 \(i\) 次查询如同:\(l_i, r_i\),意指求 \(\sum_{j=l_i}^{r_i ...

  2. JS学习-async/await

    async/await 它保证函数的返回值为 promise. 用更少的.then()块来封装代码,同时它看起来很像同步代码 注意:可能会因为大量await的promises相继发生而变慢. asyn ...

  3. Java方法之方法的重载

    方法的重载 重载就是在一个类中,有相同的函数名称,但形参不同的函数. 方法的重载的规则: 1.方法名称必须相同. 2.参数列表必须不同(个数不同.或类型不同.参数排列顺序不同等). 3.方法的返回类型 ...

  4. React 事件绑定this指向

    1. 推荐:使用class的实例方法 class Hello extends React.Component { handleClick = () => { this.setState({ .. ...

  5. 2022-3-14内部群每日三题-清辉PMP

    1.开发一款银行零售业务新产品的项目正在进行中,由于团队成员缺乏激励,该项目落后于进度.项目经理应该如何激励项目团队? A.提供认可与奖励 B.使用教练和指导技能 C.委托职责 D.应用创造性的问题解 ...

  6. Finance_CAPM&APT

    ACCA CAPM: https://www.bilibili.com/video/BV1KE411j7Ri?from=search&seid=14840285521502157259 投资学 ...

  7. 错题笔记:int a=b=1这样定义为什么是错误的

    C语言中定义同一类型的多个变量必须以逗号分隔.如: int a,b,c ; =在C语言中是赋值运算符,等号左边的变量,必须是已以定义好的变量才可以. int a=b=1 ; 中,若b已经定义,则是正确 ...

  8. 解决appium-doctor报各种 cannot be found问题

    解决appium-doctor报各种 cannot be found问题 1.opencv4nodejs cannot be found.cmake --version 查看cmake是否安装已安装执 ...

  9. Kubernetes--Ingress资源

    Ingress资源 Kubernetes提供了两种内建的云端负载均衡机制(cloud load balancing)用于发布公共应用,一种是工作于传输层的Service资源,它实现的是"TC ...

  10. 前端 与 RabbitMQ 实时消息推送实践

    web 页面的未读消息(小红点)怎么实现比较简单,之前说过可以用 rabbitmq 的 MQTT 协议做智能家居的指令推送,里边还提到过能用 MQTT 协议做 web 的消息推送,而未读消息(小红点) ...