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 ...
随机推荐
- Coursera Programming Languages, Part B 华盛顿大学 Homework 5
这次 Week 2 的作业比较难,任务目标是使用 \(racket\) 给一个虚拟语言 \(MUPL\) (made-up programming language) 写一个解释器 所以单独开个贴来好 ...
- [419] C1 Harbingers Of War OpCodez
[419] C1 Harbingers Of War Client 00 SendProtocolVersion 01 MoveBackwardToLocation 02 Say 03 Request ...
- 各种相机以及图片-SLAM14CP5
--2020.10.20 开始学习SLAM.想着从SLAM开始然后做三维重建.前面的李群李代数以及旋转四元数有点复杂.都看过了一遍.但不太理解就先放放.希望接下去能够顺利进行.数学基础可能不是很好,公 ...
- idea常用快捷键记录
实用编写代码辅助快捷键 Ctrl+Alt+V 提出选中内容为局部变量 Ctrl+Backspace 按单词删除 Ctrl+D 复制行 Ctrl+Y 删除当前行 Ctr+Shift+U 大小写转化 Sh ...
- dos命令初学
DOS命令 打开DOS命令方式 开始+系统+命令提示符 WIN键盘+R 输入CMD 打开控制台(推荐使用) 在任意文件夹下面,按住shift键加鼠标右键点击,在此处打开命令行窗口 自愿管理器的地址栏前 ...
- JAVA框架入门理解
一:关于java开发的框架我们可以先从java web开发框架的变迁来给大家简单叙述一下: 1 SSH --Struts+Spring+Hibernate 2 Spring +SpringMVC + ...
- 主要转引本地的pycharm如何与服务器连接
需求:本地电脑pycharm上编写程序,在服务器端运行代码. 主要参考了这两篇文章: 1. https://blog.csdn.net/qq_43391414/article/details/1205 ...
- withRouter
withRouter 可以加工一般组件,让一般组件具备路由组件所特有的api,比如this.props.history withRouter的返回值是一个新组件 import {withRouter} ...
- echarts 图表 tooltip提示框,formatter自定义
自定义图表提示框样式, 自定义原因:series中有多个数据样式,那么提示框会展示多条. tooltip: { formatter(params) { let circle = `<span s ...
- pytorch卷积模块
nn.Conv2d() 常用的参数有in_channels,out_channels,kernel_size,stride,padding; 除此之外还有参数dilation,groups,bias ...