D3和X6
D3
版本
d3已经更新到v7版本,中文文档只更新到v4版本,存在部分api不适用和过时问题
使用d3-darge插件布局,插件适配d3版本为v5,近年未更新
API
使用darge中setNode和setEdge绘制node和edge
使用d3中selection和zoom函数实现元素选择和缩放拖拽功能
使用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的更多相关文章
- D3.js学习(七)
上一节中我们学会了如何旋转x轴标签以及自定义标签内容,在这一节中,我们将接触动画(transition) 首先,我们要在页面上添加一个按钮,当我们点击这个按钮时,调用我们的动画.所以,我们还需要在原来 ...
- D3.js学习(六)
上节我们学习了如何绘制多条曲线, 以及给不同的曲线指定不同的坐标系.在这节当中,我们会对坐标轴标签相关的处理进行学习.首先,我们来想一个问题, 如何我们的x轴上的各个标签的距离比较近,但是标签名又比较 ...
- D3.js学习(五)
上一节我们已经学习了如何设置填充区域,其实理解了他的实现原理还是非常简单了.这一节中, 我们主要学习多条曲线的绘制,以及给不同的曲线指定不同的纵坐标. 新的数据 由于我们要画两条曲线,所以我们要在原来 ...
- D3.js学习(四)
上一节我们已经学习了线条样式和格栅的绘制,在这一节中我们将要根据之前绘制的线条对图表进行填充,首先来看一下我们的目标吧 在这个图表中,我们对位于线条下面的空间进行了填充,那么,如何改做到呢? 设置填充 ...
- D3.js学习(三)
上一节中,我们已经画出了图表,并且给图表添加了坐标轴的标签和标题,在这一节中,我们将要学习几个绘制线条不同特性的几个函数,以及给图表添加格栅.ok,进入话题! 如何给线条设置绘制的样式? 这个其实非常 ...
- D3.js学习(一)
从今天开始我将和大家一起学习D3.js(Data-Driven Documents),由于国内关于D3的学习资料少之又少,所以我觉得很有必要把自己学习过程记录下来,供同学们参考,如果文章有有哪些表达有 ...
- svg + d3
为了实现元素的添加,删除,拖拽,左键点击,右键单击,悬浮等功能,使用了d3 + svg 的技术来实现界面. 最开始是采用canvas,但是由于功能原因放弃了该技术,可以看下 canvas简介 另附:c ...
- CorelDRAW x6 X8安装失败解决方法
CorelDRAW x6 X8自定义安装时,到最后经常会出现以下问题: 解决方法如下: 在自定义安装时,出现以下这个界面时,点击红色箭头的地方 将下图红色箭头指向的选项,点击取消,不要选上,即可解决安 ...
- D3中selection之使用
1. 极为重要的reference: [1] How selections works. http://bost.ocks.org/mike/selection/ [2] Nested selecti ...
- D3中动画(transition函数)的使用
关于transition的几个基本点: 1. transition()是针对与每个DOM element的,每个DOM element的transition并不会影响其他DOM element的tra ...
随机推荐
- 在SQLServer中将数据从高版本导入低版本的方法
一般的软件都是向下兼容的,高版本通常都是可以兼容低版本.但是如果想将高版本数据库中的数据导入到低版本中,直接采用常规的备份还原或是分离附加操作就会因为结构不同而报错. 要想实现数据从高版本到低版本,除 ...
- OOP前三次作业总结
一.前言 在开始OOP学习之前,我从未了解过什么是面向对象编程,想当然的认为OOP是像从前学习C一样的编程逻辑(即面向过程编程),但在真正开始学习OOP之后,我了解到了以往面向过程编程的局限性与不便利 ...
- 当你的数据集是hdf5格式的文件时,肿么办?
最近,自己构建了一个卷积神经网络,从网上下载到的数据集是hdf5格式的,希望用这个数据集来训练一下自己构建的这个神经网络. 1. 什么是hdf5? HDF5是二进制数据格式,用于在磁盘上存储巨大的数值 ...
- c++学习6 指针变量
一 指针变量的定义 *是用来修饰指针变量的,通常情况下我们定义的手法都是"类型名"+"*"+"指针变量名称". 有一种简单无脑的" ...
- uniapp 移动端渲染富文本时图片超宽解决方法
使用replace替换富文本中的图片属性 let reg = new RegExp('<img','gi'); this.info = this.info.replace(reg,'<im ...
- 图像处理|Matlab
图像处理 | Matlab 参考博文: 图像处理-平滑滤波 图像去噪-加性噪声(高斯/椒盐)
- springboot项目记录2用户注册功能
七.注册-业务层 7.1规划异常 7.1.1用户在进行注册的时候,可能会产生用户名被占用的错误,抛出一个异常: RuntimeException异常,作为该异常的子类,然后再去定义具体的异常类型继承这 ...
- Longest Common Substring(最长公共子串)
SP1811 题目描述 A string is finite sequence of characters over a non-empty finite set Σ. In this problem ...
- element的el-table使用模板插槽在火狐和IE无法显示tooltip(浏览器兼容)
el-table中使用show-overflow-tooltip属性,配合tooltip出现的浏览器兼容性问题 el-table中使用show-overflow-tooltip属性内容过长被隐藏时显示 ...
- 估计人数【最小路径重复点覆盖】【直接在(i,j)建一个新点】
估计人数 题意 思路 用最少的人,走完这几条线.最小重复路径点覆盖问题 建图之后,跑一下二分图. 考虑建图:图中'1'连着完下.或者右走.我们把图中所有的1编号,然后建图,然后floly,然后匈牙利. ...