vue2中使用antv/G6节点内容可滚动的ER图
先举一个栗子:
效果链接:https://code.juejin.cn/pen/7226264955824930816
如果不会请移步到官网的栗子,请点击查看
狠人话不多,直接给大家上代码:
- 整体代码片段
<template>
<div id="container" style="position:relative;"></div>
</template>
<script>
/* eslint-disable */
import G6 from "@antv/g6";
import methods from "./config/methods"; export default {
name: "G6Dom",
props: {
datas: {
type: Array,
default() {
return [];
},
},
},
data() {
return {
graph: null
};
},
watch: {
datas() {
if (this.graph) {
this.graph.data(this.dataTransform(this.datas));
this.graph.render();
}
},
},
mounted() {
this.init(); if (document.getElementById("container")) {
const container = document.getElementById("container"); const width = container.scrollWidth;
const height = (container.scrollHeight || 500) - 20;
const graph = new G6.Graph({
container: "container",
width,
height,
defaultNode: {
size: [300, 400],
type: "dice-er-box",
color: "#5B8FF9",
style: {
fill: "#9EC9FF",
lineWidth: 3,
},
labelCfg: {
style: {
fill: "black",
fontSize: 20,
},
},
},
defaultEdge: {
type: "dice-er-edge",
style: {
stroke: "#e2e2e2",
lineWidth: 4,
endArrow: true,
},
},
modes: {
default: [
"dice-er-scroll",
"drag-node",
"drag-canvas",
"drag-combo",
// {
// type: 'tooltip',
// formatText(model) {
// return model.label;
// },
// offset: 15
// }
],
},
layout: {
type: "dagre",
rankdir: "LR",
align: "UL",
controlPoints: true,
nodesepFunc: () => 0.2,
ranksepFunc: () => 0.5,
center: [0, 300],
},
fitCenter: true, // 平移图到中心将对齐到画布中心,但不缩放。
animate: true,
}); graph.data(this.dataTransform(this.datas));
graph.render();
this.graph = graph;
}
},
methods: {
...methods,
dataTransform(data) {
const nodes = [];
const edges = []; data.map((node) => {
// 表与表关联
if (node.to) {
node.to.forEach((item) => {
if (item.tableId) {
edges.push({
source: node.tableId,
target: item.tableId,
});
}
});
} const attrsArr = [];
if (node.children) {
node.children.forEach((attr) => {
const { fieldId, name } = attr;
attrsArr.push({
key: fieldId,
type: name,
}); if (attr.to) {
attr.to.forEach((relation) => {
edges.push({
source: node.tableId,
target: relation.tableId,
sourceKey: attr.fieldId,
targetKey: relation.fieldId,
});
});
}
});
} const { tableId, tableName } = node;
nodes.push({
id: tableId,
label: tableName,
attrs: attrsArr,
});
});
return {
nodes,
edges,
};
},
// 放大
onZoomIn() {
this.graph.zoom(1.2);
this.graph.emit("afterrender");
},
// 缩小
onZoomOut() {
this.graph.zoom(0.8);
this.graph.emit("afterrender");
},
onLayout() {
// 更新布局
const grid = {
type: "grid",
begin: [0, 20],
preventOverlap: true, // 防止重叠
};
// const dagre = {
// type: 'dagre',
// rankdir: 'LR',
// align: 'UL',
// controlPoints: true,
// nodesepFunc: () => 0.2,
// ranksepFunc: () => 0.5,
// }
this.graph.updateLayout(grid);
},
},
};
</script> <style lang="less" scoped>
#container {
width: 100%;
height: 100%;
} .toolbar_box {
padding: 10px 0 10px 18px;
i {
cursor: pointer;
margin: 0 10px 0 0;
color: #409dfe;
}
} /deep/.g6-tooltip {
padding: 5px;
color: #444;
background-color: rgba(255, 255, 255, 0.9);
border: 1px solid #ccc;
border-radius: 4px;
}
</style>
- 部分代码片段,methods.js
import G6 from "@antv/g6";
const { Util, registerBehavior, registerEdge, registerNode } = G6;
import { isInBBox, fittingString } from "@/util/utils";
const itemHeight = 30;
const globalFontSize = 30; /* eslint-disable */
const methods = {
init() {
this.customRegisterBehavior();
this.customRegisterEdge();
this.customRegisterNode();
},
// 滚动
customRegisterBehavior() {
registerBehavior("dice-er-scroll", {
getDefaultCfg() {
return {
multiple: true,
};
},
getEvents() {
return {
itemHeight,
wheel: "scorll",
click: "click",
"node:mousemove": "move",
};
},
scorll(e) {
e.preventDefault();
const { graph } = this;
const nodes = graph.getNodes().filter((n) => {
const bbox = n.getBBox(); return isInBBox(graph.getPointByClient(e.clientX, e.clientY), bbox);
}); const x = e.deltaX || e.movementX;
let y = e.deltaY || e.movementY;
if (!y && navigator.userAgent.indexOf("Firefox") > -1)
y = (-e.wheelDelta * 125) / 3; if (nodes) {
const edgesToUpdate = new Set();
nodes.forEach((node) => {
const model = node.getModel();
if (model.attrs.length < 2) {
return;
}
const idx = model.startIndex || 0;
let startX = model.startX || 0.5;
let startIndex = idx + y * 0.02;
startX -= x;
if (startIndex < 0) {
startIndex = 0;
}
if (startX > 0) {
startX = 0;
}
if (startIndex > model.attrs.length - 1) {
startIndex = model.attrs.length - 1;
}
graph.updateItem(node, {
startIndex,
startX,
});
node.getEdges().forEach((edge) => edgesToUpdate.add(edge));
});
// G6 update the related edges when graph.updateItem with a node according to the new properties
// here you need to update the related edges manualy since the new properties { startIndex, startX } for the nodes are custom, and cannot be recognized by G6
edgesToUpdate.forEach((edge) => edge.refresh());
}
},
click(e) {
const { graph } = this;
const item = e.item;
const shape = e.shape;
if (!item) {
return;
} if (shape.get("name") === "collapse") {
graph.updateItem(item, {
collapsed: true,
size: [300, 50],
});
setTimeout(() => graph.layout(), 100);
} else if (shape.get("name") === "expand") {
graph.updateItem(item, {
collapsed: false,
size: [300, 80],
});
setTimeout(() => graph.layout(), 100);
}
},
move(e) {
const name = e.shape.get("name");
const item = e.item; if (name && name.startsWith("item")) {
this.graph.updateItem(item, {
selectedIndex: Number(name.split("-")[1]),
});
} else {
this.graph.updateItem(item, {
selectedIndex: NaN,
});
}
},
});
},
// 连线
customRegisterEdge() {
registerEdge("dice-er-edge", {
draw(cfg, group) {
const edge = group.cfg.item;
const sourceNode = edge.getSource().getModel();
const targetNode = edge.getTarget().getModel(); const sourceIndex = sourceNode.attrs.findIndex(
(e) => e.key === cfg.sourceKey
); const sourceStartIndex = sourceNode.startIndex || 0; let sourceY = 15; if (!sourceNode.collapsed && sourceIndex > sourceStartIndex - 1) {
sourceY = 30 + (sourceIndex - sourceStartIndex + 0.5) * 30;
sourceY = Math.min(sourceY, 300);
} const targetIndex = targetNode.attrs.findIndex(
(e) => e.key === cfg.targetKey
); const targetStartIndex = targetNode.startIndex || 0; let targetY = 15; if (!targetNode.collapsed && targetIndex > targetStartIndex - 1) {
targetY = (targetIndex - targetStartIndex + 0.5) * 30 + 30;
targetY = Math.min(targetY, 300);
} const startPoint = {
...cfg.startPoint,
};
const endPoint = {
...cfg.endPoint,
}; startPoint.y = startPoint.y + sourceY;
endPoint.y = endPoint.y + targetY; let shape;
if (sourceNode.id !== targetNode.id) {
shape = group.addShape("path", {
attrs: {
stroke: "#5B8FF9",
path: [
["M", startPoint.x, startPoint.y],
[
"C",
endPoint.x / 3 + (2 / 3) * startPoint.x,
startPoint.y,
endPoint.x / 3 + (2 / 3) * startPoint.x,
endPoint.y,
endPoint.x,
endPoint.y,
],
],
endArrow: true,
},
name: "path-shape",
});
} else if (!sourceNode.collapsed) {
let gap = Math.abs((startPoint.y - endPoint.y) / 3);
if (startPoint["index"] === 1) {
gap = -gap;
}
shape = group.addShape("path", {
attrs: {
stroke: "#5B8FF9",
path: [
["M", startPoint.x, startPoint.y],
[
"C",
startPoint.x - gap,
startPoint.y,
startPoint.x - gap,
endPoint.y,
startPoint.x,
endPoint.y,
],
],
endArrow: true,
},
name: "path-shape",
});
} return shape;
},
afterDraw(cfg, group) {
const labelCfg = cfg.labelCfg || {};
const edge = group.cfg.item;
const sourceNode = edge.getSource().getModel();
const targetNode = edge.getTarget().getModel();
if (sourceNode.collapsed && targetNode.collapsed) {
return;
}
const path = group.find(
(element) => element.get("name") === "path-shape"
); const labelStyle = Util.getLabelPosition(path, 0.5, 0, 0, true);
const label = group.addShape("text", {
attrs: {
...labelStyle,
text: cfg.label || "",
fill: "#000",
textAlign: "center",
stroke: "#fff",
lineWidth: 1,
},
});
label.rotateAtStart(labelStyle.rotate);
},
});
},
// 盒子
customRegisterNode() {
registerNode("dice-er-box", {
draw(cfg, group) {
const width = 250;
const height = 316;
const itemCount = 10;
const boxStyle = {
stroke: "#096DD9",
radius: 4,
}; const {
attrs = [],
startIndex = 0,
selectedIndex,
collapsed,
icon,
} = cfg;
const list = attrs;
const afterList = list.slice(
Math.floor(startIndex),
Math.floor(startIndex + itemCount - 1)
);
const offsetY = (0.5 - (startIndex % 1)) * itemHeight + 30; group.addShape("rect", {
attrs: {
fill: boxStyle.stroke,
height: 30,
width,
radius: [boxStyle.radius, boxStyle.radius, 0, 0],
},
draggable: true,
}); let fontLeft = 12; if (icon && icon.show !== false) {
group.addShape("image", {
attrs: {
x: 8,
y: 8,
height: 16,
width: 16,
...icon,
},
});
fontLeft += 18;
} let text = fittingString(cfg.label, 500, globalFontSize);
// 每个矩形框的标题
group.addShape("text", {
attrs: {
y: 22,
x: fontLeft,
fill: "#fff",
text,
fontSize: 12,
fontWeight: 500,
},
}); group.addShape("rect", {
attrs: {
x: 0,
y: collapsed ? 30 : 300,
height: 15,
width,
fill: "#eee",
radius: [0, 0, boxStyle.radius, boxStyle.radius],
cursor: "pointer",
},
name: collapsed ? "expand" : "collapse",
}); group.addShape("text", {
attrs: {
x: width / 2 - 6,
y: (collapsed ? 30 : 300) + 12,
text: collapsed ? "+" : "-",
width,
fill: "#000",
radius: [0, 0, boxStyle.radius, boxStyle.radius],
cursor: "pointer",
},
name: collapsed ? "expand" : "collapse",
}); const keyshape = group.addShape("rect", {
attrs: {
x: 0,
y: 0,
width,
height: collapsed ? 45 : height,
...boxStyle,
},
draggable: true,
}); if (collapsed) {
return keyshape;
} const listContainer = group.addGroup({});
listContainer.setClip({
type: "rect",
attrs: {
x: -8,
y: 30,
width: width + 16,
height: 300 - 30,
},
});
listContainer.addShape({
type: "rect",
attrs: {
x: 1,
y: 30,
width: width - 2,
height: 300 - 30,
fill: "#fff",
},
draggable: true,
}); if (list.length > itemCount) {
const barStyle = {
width: 4,
padding: 0,
boxStyle: {
stroke: "#00000022",
},
innerStyle: {
fill: "#00000022",
},
}; listContainer.addShape("rect", {
attrs: {
y: 30,
x: width - barStyle.padding - barStyle.width,
width: barStyle.width,
height: height - 30,
...barStyle.boxStyle,
},
}); const indexHeight =
afterList.length > itemCount
? (afterList.length / list.length) * height
: 10; listContainer.addShape("rect", {
attrs: {
y:
30 +
barStyle.padding +
(startIndex / list.length) * (height - 30),
x: width - barStyle.padding - barStyle.width,
width: barStyle.width,
height: Math.min(height, indexHeight),
...barStyle.innerStyle,
},
});
}
if (afterList) {
afterList.forEach((e, i) => {
const isSelected =
Math.floor(startIndex) + i === Number(selectedIndex);
let { key = "", type } = e;
if (type) {
key = type;
}
const label = key.length > 26 ? key.slice(0, 24) + "..." : key; listContainer.addShape("rect", {
attrs: {
x: 1,
y: i * itemHeight - itemHeight / 2 + offsetY,
width: width - 4,
height: itemHeight,
radius: 2,
lineWidth: 1,
cursor: "pointer",
},
name: `item-${Math.floor(startIndex) + i}-content`,
draggable: true,
}); if (!cfg.hideDot) {
listContainer.addShape("circle", {
attrs: {
x: 0,
y: i * itemHeight + offsetY,
r: 3,
stroke: boxStyle.stroke,
fill: "white",
radius: 2,
lineWidth: 1,
cursor: "pointer",
},
});
listContainer.addShape("circle", {
attrs: {
x: width,
y: i * itemHeight + offsetY,
r: 3,
stroke: boxStyle.stroke,
fill: "white",
radius: 2,
lineWidth: 1,
cursor: "pointer",
},
});
} listContainer.addShape("text", {
attrs: {
x: 12,
y: i * itemHeight + offsetY + 6,
text: label,
fontSize: 12,
fill: "#000",
fontFamily:
"Avenir,-apple-system,BlinkMacSystemFont,Segoe UI,PingFang SC,Hiragino Sans GB,Microsoft YaHei,Helvetica Neue,Helvetica,Arial,sans-serif,Apple Color Emoji,Segoe UI Emoji,Segoe UI Symbol",
full: e,
fontWeight: isSelected ? 500 : 100,
cursor: "pointer",
},
name: `item-${Math.floor(startIndex) + i}`,
});
});
} return keyshape;
},
getAnchorPoints() {
return [[0, 0], [1, 0]];
},
});
}, } export default methods;
- 工具类,utils.js
export function isInBBox(point, bbox) {
const {
x,
y
} = point;
const {
minX,
minY,
maxX,
maxY
} = bbox; return x < maxX && x > minX && y > minY && y < maxY;
} /**
* format the string
* @param {string} str The origin string
* @param {number} maxWidth max width
* @param {number} fontSize font size
* @return {string} the processed result
*/
import G6 from '@antv/g6'; export function fittingString(str, maxWidth, fontSize) {
const ellipsis = '...';
const ellipsisLength = G6.Util.getTextSize(ellipsis, fontSize)[0];
let currentWidth = 0;
let res = str;
const pattern = new RegExp('[\u4E00-\u9FA5]+'); // distinguish the Chinese charactors and letters
str.split('').forEach((letter, i) => {
if (currentWidth > maxWidth - ellipsisLength) return;
if (pattern.test(letter)) {
// Chinese charactors
currentWidth += fontSize;
} else {
// get the width of single letter according to the fontSize
currentWidth += G6.Util.getLetterWidth(letter, fontSize);
}
if (currentWidth > maxWidth - ellipsisLength) {
res = `${str.substr(0, i)}${ellipsis}`;
}
});
return res;
};
鉴定完毕,欢迎友友们一起交流学习!!
vue2中使用antv/G6节点内容可滚动的ER图的更多相关文章
- antV G6流程图在Vue中的使用
最近我司项目中需要加入流程图制作功能,于是乎百度各种找可视化绘制拓扑图的轮子,大部分都是国外的,看文档太吃力,不过好在最终让我发现了AntV G6流程图图表库,最新版为2.0,不过编辑器在2.0版本还 ...
- 可视化—AntV G6 紧凑树实现节点与边动态样式、超过X条展示更多等实用小功能
通过一段时间的使用和学习,对G6有了更一步的经验,这篇博文主要从以下几个小功能着手介绍,文章最后会给出完整的demo代码. 目录 1. 树图的基本布局和使用 2. 根据返回数据的属性不同,定制不一样的 ...
- uniapp中使用AntV F6 + table表格插件使用
首先看页面效果: AntV官网下载F6文件到项目中与uViewUI插件 <template> <view class="page"> <!-- 导航栏 ...
- jacascript DOM节点——节点内容
前言:这是笔者学习之后自己的理解与整理.如果有错误或者疑问的地方,请大家指正,我会持续更新! innerHTML 在读模式下,返回与调用元素的所有子节点(包括元素.注释和文本节点)对应的 HTML 标 ...
- Zookeeper命令行操作(常用命令;客户端连接;查看znode路径;创建节点;获取znode数据,查看节点内容,设置节点内容,删除节点;监听znode事件;telnet连接zookeeper)
8.1.常用命令 启动ZK服务 bin/zkServer.sh start 查看ZK服务状态 bin/zkServer.sh status 停止ZK服务 bin/zkServer.sh stop 重启 ...
- 简单实现弹出弹框页面背景半透明灰,弹框内容可滚动原页面内容不可滚动的效果(JQuery)
弹出弹框 效果展示 实现原理 html结构比较简单,即: <div>遮罩层 <div>弹框</div> </div> 先写覆盖显示窗口的遮罩层div.b ...
- Merkle 树——空间换时间,分而治之的hash表,通过根节点是由它的两个子节点内容的哈希值组成来校验数据完整性,定位篡改的数据位置
Merkle 树 图 1.5.6.1 - Merkle 树示例 默克尔树(又叫哈希树)是一种二叉树,由一个根节点.一组中间节点和一组叶节点组成.最下面的叶节点包含存储数据或其哈希值,每个中间节点是它的 ...
- Vue2中实现微信分享支付功能
Vue2中实现微信分享支付功能 近期做了一个微信公众号前后分离项目,前端采用Vue2开发,后端SpringBoot,今天迫不及待的来给大家分享一下这次在开发中遇到的一些坑以及解决办法. 在这里,一些 ...
- ivew数控件Tree自定义节点内容示例分析
ivew数控件Tree自定义节点内容示例分析 demo地址:https://run.iviewui.com/plcWlM4H <template> <Tree :data=" ...
- React使用AntV G6实现流程图
安装 npm install @antv/g6 --save 引用 import G6 from '@antv/g6' 自定义节点 /** * 方式一 */ G6.registerNode('rect ...
随机推荐
- Python3.9安装
一.安装python3.9 链接:https://pan.baidu.com/s/1mDkgKt2KSoMrKVxesb76Pg?pwd=ma4n 提取码:ma4n --来自百度网盘超级会员V4的分享 ...
- 【后端面经】MySQL主键、唯一索引、联合索引的区别和作用
目录 0. 简介 1. 主键 2. 唯一索引 3. 联合索引 4. 索引对数据库操作的影响 5. 其他索引 5.1 普通索引 5.2 全文索引 5.3 前缀索引 6. 总结 7. 参考资料 0. 简介 ...
- ARC118E Avoid Permutations
题意 给定一个长度为 \(n\) 的排列 \(p\),在一个 \((n + 2)\times(n + 2)\) 的网格上,禁止通过 \((i, p_i)\) 这些点,每次只能向上或右走一格,从 \(( ...
- idea设置字体大小(换主题后的字体大小设置)
如果你是默认主题 直接这样设置字体大小 如果你换了自定义主题 如果你换了自定义主题,那么上面的设置方法会没有作用,我们需要像下面这样设置:
- Paimon读取流程
查询模式 先来看看官网关于Paimon查询模式的说明 可以看到查询模式围绕snapshot展开, 而snapshot分了两种一种是Last compact snapshot和 last snapsho ...
- 【Oracle】使用PL/SQL实现冒泡排序
[Oracle]使用PL/SQL实现冒泡排序 一般来说,SQL要排序的话直接使用order by即可 不一般来说,就是瞎搞,正好也可以巩固自己的数据结构基础 存储包内容如下 规范: create or ...
- Spring MVC 前后台传递json格式数据 Content type 'application/x-www-form-urlencoded;charset=UTF-8' not supported
若使用默认的json转换器,则需要如下包: <mvc:annotation-driven /> 报错如下: Content type 'application/x-www-form-url ...
- 2023CCPC大学生程序设计竞赛-nhr
新生菜菜第一次参加这种大型比赛,还是有点紧张的,CCPC我们队就A了三题,铜牌.第一道,以为是签到,然后就交给clk了,我和crf看下一道过的题比较多的,然后感觉是一个滑动窗口,另一道题是纯数学公式. ...
- 基于LLVM的海量数据排序算法研究。(二维表的排序算法)
当待排序数据内容大于内存容量时,需将待排序内容分块,要进行排序的分块传入内存,未处于排序状态的存入外存,外存的读写时间是内存的百万倍,因此在内外存储器之间传输分块所消耗的 I/O 时间是大数据排序算法 ...
- 网关冗余协议:FHRP、HSRP(思科)、VRRP、GLBP
参考链接: CHANNEL技术与网关冗余 VRRP和HSRP的区别