D3

版本

d3已经更新到v7版本,中文文档只更新到v4版本,存在部分api不适用和过时问题

使用d3-darge插件布局,插件适配d3版本为v5,近年未更新

API

使用darge中setNodesetEdge绘制node和edge

使用d3中selectionzoom函数实现元素选择和缩放拖拽功能

使用darge的render方法绘制svg,selection的on事件对元素进行监听和方法执行

UI交互

使用element-ui的dropmenu实现下拉菜单功能,需功能和样式订制,代码修改

提示框使用css和d3animation实现

所有的弹出层样式(坐标)需要手动计算

增删改查

每次做出修改需要手动重绘页面,新添加node和edge需要携带完整配置,否则使用默认样式等

重绘后新添加元素手动监听各种事件,无法使用事件委托机制,或使用复杂

其他

拖拽元素、箭头等功能需要d3原生api进行大量算法编程,无现成api提供使用,写法繁琐复杂

流程分组、定制等逻辑需要自行开发,无法避开

X6

与G6的对比

使用场景 性能 定制化和学习
X6 偏重于图编辑,节点拖拽、锚点拖拽、编辑形状等 基于svg创建dom元素,节点或编辑元素过多会影响性能 基于svg(html),定制组件便利
G6 偏重于图可视化,分析数据(GIS)等 基于canvas,大量数据依然流畅渲染 深入了解canvas,不便于调试

具体功能实现

创建node和edge

  • d3
    createNode(id, node) {
this.g.setNode(id, {
...node,
rx: 5,
ry: 5
});
},
createEdge(source, target, edge) {
this.g.setEdge(source, target, {
...edge,
arrowhead: "vee",
curve: d3.curveBasis
});
}, // style
g {
&.edgeLabel {
user-select: none;
cursor: pointer;
.high-light {
fill: #c4e3b6;
}
}
&.edgePath {
.path {
stroke: #333;
stroke-width: 2px;
}
.high-light {
stroke: #c4e3b6;
}
marker path {
fill: #a3a3a3;
stroke: #5d5d5d;
&.high-light {
fill: #c4e3b6;
}
}
}
&.node {
user-select: none;
cursor: pointer;
fill: #fff;
stroke: #ddd;
&.high-light {
stroke: #c4e3b6;
stroke-width: 2px;
}
text {
fill: #4a4a4a;
stroke: none;
font-size: 16px;
font-weight: 600;
}
}
}
  • x6
		formatNode(id: string, node: Node.Metadata) {
return {
id,
...node,
attrs: {
body: {
fill: "#5f95ff",
stroke: "transparent",
rx: 5,
ry: 5
},
label: {
fill: "#ffffff"
}
}
};
},
formatEdge(source: string, target: string, edge: Edge.Metadata) {
return {
source,
target,
attrs: {
line: {
stroke: "#a2b1c3",
strokeWidth: 2
}
},
labels: [
{
markup: [
{
tagName: "rect",
selector: "body"
},
{
tagName: "text",
selector: "label"
}
],
attrs: {
label: {
cursor: "pointer",
text: edge.label,
textAnchor: "middle",
textVerticalAnchor: "middle",
fontSize: 14,
fill: "#8d939b"
},
body: {
cursor: "pointer",
ref: "label",
refX: "-20%",
refY: "-20%",
refWidth: "140%",
refHeight: "140%",
fill: "#d3d3d3",
stroke: "#a2b1c3",
strokeWidth: 1
}
}
}
]
};
},

修改node和edge

  • d3的增删改查需要自己去写逻辑(使用form表单提交),再去重绘svg,事件监听需要重新加在每一个node和edge上
  • x6可以使用封装好的tools,事件监听也是使用委托代理到graph上,自动更新视图
			// 鼠标移入节点显示
this.g.on("node:mouseenter", ({ e, node, view }) => {
node.addTools({
name: "button-remove",
args: {
x: 0,
y: 0,
offset: { x: 10, y: 10 }
}
});
});
// 鼠标移出节点显示
this.g.on("node:mouseleave", ({ node }) => {
node.removeTools();
});
// 双击节点修改label
this.g.on("node:dblclick", ({ node, e }) => {
node.addTools({
name: "node-editor",
args: {
event: e
}
});
});
}

x6在使用editor-tool时,setText有天坑,源码也会有些bug,需优化

// node
setText: (args: any) => {
(
CellEditor.NodeEditor.getDefaults() as {
setText: (args: any) => void;
}
).setText(args);
} // edge
// 首先明确edge只存在一个label,有label时事件只作用于label上
if (edge.labels.length && e.target.nodeName === "path") {
return;
}
// 再优化重写setText逻辑
setText: ({
cell,
value,
index
}: {
cell: Edge;
value: string;
index?: number;
}) => {
if (index === -1) {
if (value) {
cell.appendLabel({
position: {
distance: 0.5
},
...this.edgeLabelMarkUp(value)
});
}
} else {
if (value) {
cell.prop(`labels/${index}/attrs/label/text`, value);
} else if (typeof index === "number") {
cell.removeLabelAt(index);
}
}
}

使用element UI插件

x6对react的ant-design框架提供了非常友好的支持,可以安装@antv/x6-react-components开箱即用,无需写适配逻辑和样式。在vue的elementUI中,使用了d3一样的做法,引入UI组件后进行样式、功能二次开发

    <el-dropdown ref="menuDown" trigger="click" placement="bottom">
<div></div>
<el-dropdown-menu class="x6-down-menu" slot="dropdown">
<el-dropdown-item
v-for="(item, index) in dropdown"
:key="index"
:icon="item.icon"
:command="item.command"
>{{ item.label }}</el-dropdown-item
>
</el-dropdown-menu>
</el-dropdown>

使用ref定位到组件后,手动调用show()hide() 方法,再加上改变横竖坐标定位

// d3中计算坐标,鼠标坐标通过d3.mouse获取
const svgGroup = this.svgGroup.node();
const mouse = d3.mouse(svgGroup);
const transform = d3.zoomTransform(svgGroup);
tooltip
.html(content)
.style("left", mouse[0] * transform.k + transform.x + 15 + "px")
.style("top", mouse[1] * transform.k + transform.y + 15 + "px"); // x6中计算坐标,鼠标坐标通过监听事件参数获取
// 鼠标右键点击画布
this.g.on("blank:contextmenu", ({ e, x, y }) => {
this.showMenu(x, y);
});
showMenu(x: number, y: number) {
console.log(x, y);
console.log(this.g.scale());
console.log(this.g.translate());
const { tx, ty } = this.g.translate();
const { sx, sy } = this.g.scale();
(this.$refs.menuDown as any).show();
const menu = (this.$refs.menuDown as any).popperElm;
setTimeout(() => {
menu.setAttribute("x-placement", "bottom-start");
menu.style.left = x * sx + tx + 30 + "px";
menu.style.top = y * sy + ty + 20 + "px";
}, 0);
},

小地图插件

// new Graph添加配置
const miniMap = this.$refs.miniMap as HTMLElement;
minimap: {
enabled: true,
container: miniMap,
height: 150,
width: 250
} // 添加dom
<div class="mini-map" ref="miniMap"></div> // style
.mini-map {
position: absolute;
bottom: 20px;
right: 36px;
.x6-widget-minimap {
background-color: #d8e7cc;
.x6-graph {
box-shadow: 0 0 4px 0 #7ca190;
}
}
}

使用拖拽插件

addon/stencil
// stencil配置
// 一定要配置stencilGraphWidth和stencilGraphHeight,否则初次渲染不出来
// getDragNode配置拖拽中节点,getDropNode配置放下后节点
const stencil = new Addon.Stencil({
title: "新建节点",
target: this.g,
search: (cell, key) => {
return (
cell.shape.indexOf(key) !== -1 ||
(cell.attr("text/text") as String).indexOf(key) !== -1
);
},
placeholder: "搜索节点",
notFoundText: "404",
stencilGraphWidth: 300,
stencilGraphHeight: 100,
groups: [
{
name: "node",
collapsable: false
}
],
// getDragNode: (node) => {
// return node.clone();
// },
getDropNode: (node) => {
const shape = node.shape;
const nodeInfo = { label: node.attrs!.text.text };
if (shape === "rect") {
Object.assign(nodeInfo, {
width: 80,
height: 48,
shape: "rect"
});
} else if (shape === "circle") {
Object.assign(nodeInfo, {
width: 80,
height: 80,
shape: "circle"
});
} return this.g.createNode(this.formatNode(nodeInfo));
}
});
(this.$refs.stencil as HTMLElement).appendChild(stencil.container); const rect = new Shape.Rect({
width: 70,
height: 40,
label: "rect",
attrs: {
body: {
fill: "#5f95ff",
stroke: "transparent",
rx: 5,
ry: 5
},
label: {
fill: "#ffffff"
}
}
}); const circle = new Shape.Circle({
width: 80,
height: 80,
label: "circle",
attrs: {
body: {
fill: "#5f95ff",
stroke: "transparent"
},
label: {
fill: "#ffffff"
}
}
});
stencil.load([rect, circle], "node"); // 需要添加dom和样式
<div class="stencil" ref="stencil"></div> // style
.stencil {
flex: 1;
height: 100%;
position: relative;
margin-right: 20px;
}

监听node:added事件,在新添加节点上添加链接桩、可移动等属性

this.g.on("node:added", ({ node }) => {
node.setData({ disableMove: true });
this.setNodePort(node);
});

链接桩

要自定义链接桩位置(top\bottom\left\right等),只能使用group方式来创建制定position,attrs无法通过groups复用。属于可以优化但没做处理的功能,其实用到地方还很多

const ports = {
groups: {
top: {
position: "top",
attrs: {
circle: {
r: 5,
magnet: true,
stroke: "#31d0c6",
strokeWidth: 2,
fill: "#fff"
}
}
},
left: {
position: "left",
attrs: {
circle: {
r: 5,
magnet: true,
stroke: "#31d0c6",
strokeWidth: 2,
fill: "#fff"
}
}
},
right: {
position: "right",
attrs: {
circle: {
r: 5,
magnet: true,
stroke: "#31d0c6",
strokeWidth: 2,
fill: "#fff"
}
}
},
bottom: {
position: "bottom",
attrs: {
circle: {
r: 5,
magnet: true,
stroke: "#31d0c6",
strokeWidth: 2,
fill: "#fff"
}
}
}
},
items: [
{
id: node.id + "port-top",
group: "top"
},
{
id: node.id + "port-bottom",
group: "bottom"
},
{
id: node.id + "port-left",
group: "left"
},
{
id: node.id + "port-right",
group: "right"
}
]
};
node.prop("ports", ports);

链接桩配置

connecting: {
snap: {
radius: 30
},
allowBlank: false,
allowMulti: false,
allowLoop: false,
allowEdge: false,
highlight: true
},
// 对应connecting中highlight配置
highlighting: {
magnetAvailable: {
name: "stroke",
args: {
padding: 4,
attrs: {
"stroke-width": 2,
stroke: "#FE854F"
}
}
}
},

D3和X6的更多相关文章

  1. D3.js学习(七)

    上一节中我们学会了如何旋转x轴标签以及自定义标签内容,在这一节中,我们将接触动画(transition) 首先,我们要在页面上添加一个按钮,当我们点击这个按钮时,调用我们的动画.所以,我们还需要在原来 ...

  2. D3.js学习(六)

    上节我们学习了如何绘制多条曲线, 以及给不同的曲线指定不同的坐标系.在这节当中,我们会对坐标轴标签相关的处理进行学习.首先,我们来想一个问题, 如何我们的x轴上的各个标签的距离比较近,但是标签名又比较 ...

  3. D3.js学习(五)

    上一节我们已经学习了如何设置填充区域,其实理解了他的实现原理还是非常简单了.这一节中, 我们主要学习多条曲线的绘制,以及给不同的曲线指定不同的纵坐标. 新的数据 由于我们要画两条曲线,所以我们要在原来 ...

  4. D3.js学习(四)

    上一节我们已经学习了线条样式和格栅的绘制,在这一节中我们将要根据之前绘制的线条对图表进行填充,首先来看一下我们的目标吧 在这个图表中,我们对位于线条下面的空间进行了填充,那么,如何改做到呢? 设置填充 ...

  5. D3.js学习(三)

    上一节中,我们已经画出了图表,并且给图表添加了坐标轴的标签和标题,在这一节中,我们将要学习几个绘制线条不同特性的几个函数,以及给图表添加格栅.ok,进入话题! 如何给线条设置绘制的样式? 这个其实非常 ...

  6. D3.js学习(一)

    从今天开始我将和大家一起学习D3.js(Data-Driven Documents),由于国内关于D3的学习资料少之又少,所以我觉得很有必要把自己学习过程记录下来,供同学们参考,如果文章有有哪些表达有 ...

  7. svg + d3

    为了实现元素的添加,删除,拖拽,左键点击,右键单击,悬浮等功能,使用了d3 + svg 的技术来实现界面. 最开始是采用canvas,但是由于功能原因放弃了该技术,可以看下 canvas简介 另附:c ...

  8. CorelDRAW x6 X8安装失败解决方法

    CorelDRAW x6 X8自定义安装时,到最后经常会出现以下问题: 解决方法如下: 在自定义安装时,出现以下这个界面时,点击红色箭头的地方 将下图红色箭头指向的选项,点击取消,不要选上,即可解决安 ...

  9. D3中selection之使用

    1. 极为重要的reference: [1] How selections works. http://bost.ocks.org/mike/selection/ [2] Nested selecti ...

  10. D3中动画(transition函数)的使用

    关于transition的几个基本点: 1. transition()是针对与每个DOM element的,每个DOM element的transition并不会影响其他DOM element的tra ...

随机推荐

  1. spring-configuration-metadata元数据与additional-spring-configuration-metadata元数据区别

    参考 https://docs.spring.io/spring-boot/docs/current/reference/html/appendix-configuration-metadata.ht ...

  2. MySQL利用information_schema库注入

    information_schema库简单说就是MySQL里的一个目录库,MySQL是一本书,information_schema库这个存储数据库元数据的库就是书的目录或者索引. 库>表> ...

  3. spider_requests库简单ip代理

    """使用requests 设置ip代理"""import requestsdef func(): url = 'http://ip.273 ...

  4. Coursera Programming Languages, Part C 华盛顿大学 Week 1

    来进入这一系列课程的最后一 Part 啦! \(P1\) 我们介绍了 \(ML\),一种 static typing 的 functional language \(P2\) 我们介绍了 \(Rack ...

  5. MBR与GPT[转]

    MBR分区 MBR的意思是"主引导记录",是IBM公司早年间提出的.它是存在于磁盘驱动器开始部分的一个特殊的启动扇区. 这个扇区包含了已安装的操作系统系统信息,并用一小段代码来启动 ...

  6. luogu 1344

    首先题意就是裸的最小割啦 然后考虑如何统计边数 这里有一个trick: 我们设定一个大于$m$的阈值,对于每条边的边权我们乘这个阈值+1后跑最小割,得到的答案除以阈值就是真正的最小割,取模阈值后就是最 ...

  7. mac中chrome常用快捷键

     1.标签页和窗口快捷键  ⌘-N                                                                  打开新窗口.   ⌘-T      ...

  8. Stram流 - 随笔

    函数式编程 注重函数 - 关注对数据进行了什么操作 流 中间操作 去重 authors.stream() .distinct() ; 查询指定匹配 .filter(new Predicate<A ...

  9. Ubuntu VMWare安装纪要

    一.VMware虚拟机下载与安装 版本:VMware Workstation 16 Pro 二.Ubuntu下载与安装 版本:ubuntu-20.04.2.0-desktop-amd64.iso 三. ...

  10. day47-Mysql初识

    1.数据库的演变过程-- 文件存储(不同用户之间数据格式不一致,杂乱)==> 软件开发目录规范(限定了储存的具体位置,不能网络通信)==>数据库 数据库就是一款基于网络通信操作文件的应用程 ...