文章目录
最近需要将深度学习网络结构图以比较直观的形式在前端显示出来,对于数据的传输,计划使用数据路径流动的形式来做,因为我也没怎么学过前端变成,对于自己写js和css代码,感觉之后会出不少问题,前端显示的美观度,自适应窗口大小,组件位置的自适应等都是问题,搜了搜相关的教程推荐,发现阿里开发的antv/X6插件用来做流程图,以及网络结构图还是具有不错的效果的,以下是我的按照教程的一些学习操作:
开始使用
接下来我们就一起来创建一个最简单的 start--> end
应用。
Step 1 创建容器
在页面中创建一个用于容纳 X6 绘图的容器,可以是一个 div
标签。
<html>
<head>
<style type="text/css">
.expa {
width: 400px;
height: 400px;
//将边框显示出来
border: 3px solid #F00;
margin: 0 auto;
}
</style>
</head>
<body>
<div class="expa" id="container"></div>
</body>
</html>
Step 2 准备数据
X6 支持 JSON 格式数据,该对象中需要有节点 nodes
和边 edges
字段,分别用数组表示:
<script>
const data = {
// 节点
nodes: [
{
id: 'node1', // String,可选,节点的唯一标识
x: 100, // Number,必选,节点位置的 x 值
y: 100, // Number,必选,节点位置的 y 值
width: 80, // Number,可选,节点大小的 width 值
height: 40, // Number,可选,节点大小的 height 值
label: 'start', // String,节点标签
},
{
id: 'node2', // String,节点的唯一标识
x: 200, // Number,必选,节点位置的 x 值
y: 200, // Number,必选,节点位置的 y 值
width: 80, // Number,可选,节点大小的 width 值
height: 40, // Number,可选,节点大小的 height 值
label: 'end', // String,节点标签
},
],
// 边
edges: [{
source: 'node1', // String,必须,起始节点 id
target: 'node2', // String,必须,目标节点 id
}, ],
};
</script>
Step 3 渲染画布
首先,我们需要创建一个 Graph
对象,并为其指定一个页面上的绘图容器,通常也会指定画布的大小。我在之前已经指定container的大小,不需要重新指定了
//使用npm安装x6,之后引用,使用的就是下面的形式,在vue里面可以这么使用
import { Graph } from '@antv/x6';
const graph = new Graph({
container: document.getElementById('container'),
//width: 800,
//height: 600,
});
我使用的是js文件引入形式,在html页面相对来说好操作
<script src="https://unpkg.com/@antv/x6/dist/x6.js"></script>
<script>
const graph = new X6.Graph({
container: document.getElementById('container'),
//width: 800,
//height: 600,
});
</script>
然后,我们就可以使用刚刚创建的 graph
来渲染我们的节点和边。
graph.fromJSON(data)
得到下图:
取消节点的移动
此时节点node是可以移动的,有时候我们并不想让它移动,可以使用以下代码:
const graph = new X6.Graph({
container: document.getElementById('container'),
// width: 400,
// height: 400,
interacting: {
nodeMovable: false,
}
});
使用不同图形渲染节点
在上面示例中,我们使用了默认图形 rect
来渲染节点,除此之外,我们在 X6 中也内置了 circle
、ellipse
、polygon
等基础图形,可以通过 shape
属性为节点指定渲染的图形,如果你对 SVG 图形还不熟悉,可以参考 SVG 图像入门教程
const data = {
// 节点
nodes: [{
id: 'node1', // String,可选,节点的唯一标识
x: 100, // Number,必选,节点位置的 x 值
y: 100, // Number,必选,节点位置的 y 值
width: 80, // Number,可选,节点大小的 width 值
height: 40, // Number,可选,节点大小的 height 值
label: 'start', // String,节点标签
},
{
id: 'node2', // String,节点的唯一标识
shape:'ellipse',
x: 200, // Number,必选,节点位置的 x 值
y: 200, // Number,必选,节点位置的 y 值
width: 80, // Number,可选,节点大小的 width 值
height: 40, // Number,可选,节点大小的 height 值
label: 'end', // String,节点标签
},
],
// 边
edges: [{
source: 'node1', // String,必须,起始节点 id
target: 'node2', // String,必须,目标节点 id
}, ],
};
效果如下:
画布居中和缩放
当 factor
为正数时表示画布放大画布,当 factor
为负数时表示缩小画布。
这里factor取值0.8,放大画布
graph.centerContent() //画布居中
graph.zoom(0.8)//画布缩放
修改边样式
将边改为虚线:
edges: [{
source: 'node1', // String,必须,起始节点 id
target: 'node2', // String,必须,目标节点 id
attrs: {
line: {
stroke: '#1890ff',
strokeDasharray: 5,
targetMarker: 'classic',
}
},
}, ],
给虚线增加流动效果
为了给虚线加流动效果,我蒸腾的够呛,官方教程给的是ts代码,使用总是有问题,唉,还是自己菜,菜鸟都不算,要多学习呀!
使用style样式实现线条的流动效果
edges: [{
source: 'node1', // String,必须,起始节点 id
target: 'node2', // String,必须,目标节点 id
attrs: {
line: {
stroke: '#1890ff',
strokeDasharray: 5,
targetMarker: 'classic',
style: {
animation: 'ant-line 30s infinite linear',
},
}
},
}, ],
<style>
@keyframes ant-line {
to {
stroke-dashoffset: -1000
}
}
</style>
代码效果如下图:
修改节点和边的属性
//获取画布所有的的节点和边
const nodes = graph.getNodes()
const edges = graph.getEdges()
//修改没一个节点的属性
nodes.forEach((node) => {
node.attr(
'body',
{
stroke: '#1890ff',//节点颜色
fill: 'white'//节点填充色
}
)
node.attr('label', {
// text: 'rect', // 文本
// fill: '#333', // 文字颜色
// fontSize: 13, // 文字大小
})
node.resize(40, 20) //重置节点大小
})
edges.forEach((edge) => {
edge.attr('line', {
stroke: '#1890ff',//边颜色
strokeDasharray: 5,//边线条粗细
targetMarker: 'classic',
style: {
animation: 'ant-line 30s infinite linear',
},
})
})
修改后效果图如下:
鼠标悬浮高亮显示
graph.on('node:mouseenter',
({node}) => {
node.attr('body', {
stroke: 'orange',
fill: 'orange'
})
})
效果如下图:
给路径添加标签信息:
在start节点指向end节点的路径线上,添加文字信息,可通过在边属性中添加label,并指定position来实现,
antv教程文档对位置的定义:
位置
我们可以通过 Label 的 position.distance 选项来指定标签的位置,默认值为 0.5 表示标签位于边长度的中心位置。根据取值不同,标签位置的计算方式分下面三种情况。
- 位于 [0, 1] 之间时,表示标签位于从起点开始,沿长度方向,多少相对长度(比例)的位置。
- 正数表示标签位于从起点开始,沿边长度方向,偏离起点多少长度的位置。
- 负数表示标签位于从终点开始,沿长度方向,偏离终点多少长度的位置。
偏移
我们可以通过 Label 的 position.offset 选项来设置标签的偏移量,默认值为 0 表示不偏移。根据取值不同,标签偏移量的计算方式分为下面三种情况。
- 正数表示标签沿垂直于边向下的绝对偏移量。
- 负数表示标签沿垂直于边向上的绝对偏移量。
- 坐标对象 {x: number; y: number } 表示标签沿 x 和 y 两个方向的绝对偏移量。
旋转
我们可以通过 Label 的 position.angle 选项来设置标签沿顺时针方向的旋转角度,默认值为 0 表示不旋转。
- 当 position.options.keepGradient 为 true 时,标签的初始旋转角度是标签所在位置的边的角度,后续设置的 position.angle 角度是相对于该初始角度的。
- 当 position.options.ensureLegibility 为 true 时,在必要时将为标签增加 180° 旋转量,以保证标签文本更易读。
官方文档有对应的代码可以参考学习:使用标签 Labels
部分代码如下:
edge.appendLabel({
attrs: {
text: {
text: "offset: { x: -40, y: 80 }",
},
},
position: {
distance: 0.2,
offset: {
x: -10,
y: 40,
},
},
});
给节点添加鼠标悬浮信息窗
可以使用jquery实现悬浮窗效果,先引入jquery,给container添加一个div作为悬浮窗口,在之前添加的鼠标悬浮事件中添加代码即可
可能悬浮窗的位置会有问题,我这里以节点的位置设定悬浮窗的位置,但节点位置因为缩放等原因已经发生改变,我们就需要获取对应的真实节点位置,这个antv/X6有教程细节,这里不在赘述,[画布位置变化],(https://x6.antv.vision/zh/docs/api/graph/coordinate#graphtolocal)
jquery代码如下
<script src="./jquery-2.0.3.min.js"></script>
<div class="expa" id="container" style="position: relative;">
<div id="tooltipText" class="tooltiptext"></div>
</div>
<script>
graph.on('node:mouseenter', ({
node}) => {
node.attr('body', {
stroke: 'orange',
fill: 'orange'
})
// console.log(node)
//鼠标进入开启悬浮窗
var text = $('#tooltipText')
text.html(
'我热爱我的祖国我热爱我的祖国我热爱我的祖国我热爱我的祖国我热爱我的祖国我热爱我的祖国我热爱我的祖国我热爱我的祖国我热爱我的祖国我热爱我的祖国'
)
text.css('display', 'block')
//因为获取到的节点坐标是相对画布的本地坐标,但画布经过的缩放或者平移,需要将画布本地坐标转换为画布坐标,悬浮窗显示的位置才正确,不会杂乱
const p1 = graph.localToGraph(node.store.data.position.x, node.store.data.position.y)
text.css('left', p1.x + 80)
text.css('top', p1.y - 100)
})
graph.on('node:mouseleave', ({
node
}) => {
//鼠标离开隐藏悬浮窗
var text = $('#tooltipText')
text.css('display', 'none')
reset()
})
</script>
//给悬浮窗添加css样式
<style type="text/css">
.tooltiptext {
width: 120px;
background-color: black;
color: #fff;
/* text-align: center; */
border-radius: 6px;
padding: 5px 0;
position: absolute;
z-index: 1;
display: none;
opacity: 0.7;
}
</style>
想使用纯js代码,只需修改获取id和修改样式的代码即可
var text = document.getElementById("tooltipText")
const p1 = graph.localToGraph(node.store.data.position.x, node.store.data.position.y)
text.innerHTML="我热爱我的祖国我热爱我的祖国我热爱我的祖国我热爱我的祖国我热爱我的祖国我热爱我的祖国我热爱我的祖国我热爱我的祖国我热爱我的祖国我热爱我的祖国"
text.style.display='block';
text.style.left=(p1.x + 80).toString() + 'px';
text.style.top=(p1.y - 50).toString() + 'px'
完整代码如下:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>网络可视化</title>
<meta name="keywords" content="HTML,CSS,XML,JavaScript">
<meta name="author" content="anliu">
<meta http-equiv="refresh" content="60">
<script src="./x6.js"></script>
<!-- <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script> -->
<script src="./jquery-2.0.3.min.js"></script>
<style type="text/css">
.expa {
top: 100px;
width: 400px;
height: 400px;
border: 3px solid #F00;
margin: 0 auto;
}
@keyframes ant-line {
to {
stroke-dashoffset: -1000
}
}
.tooltiptext {
width: 120px;
background-color: black;
color: #fff;
/* text-align: center; */
border-radius: 6px;
padding: 5px 0;
position: absolute;
z-index: 1;
display: none;
opacity: 0.7;
}
</style>
</head>
<body>
<div class="expa" id="container" style="position: relative;">
<div id="tooltipText" class="tooltiptext"></div>
</div>
<script>
const data = {
// 节点
nodes: [{
id: 'node1', // String,可选,节点的唯一标识
x: 100, // Number,必选,节点位置的 x 值
y: 100, // Number,必选,节点位置的 y 值
width: 80, // Number,可选,节点大小的 width 值
height: 40, // Number,可选,节点大小的 height 值
label: 'start', // String,节点标签
},
{
id: 'node2', // String,节点的唯一标识
shape: 'ellipse',
x: 200, // Number,必选,节点位置的 x 值
y: 200, // Number,必选,节点位置的 y 值
width: 80, // Number,可选,节点大小的 width 值
height: 40, // Number,可选,节点大小的 height 值
label: 'end', // String,节点标签
},
],
// 边
edges: [{
source: 'node1', // String,必须,起始节点 id
target: 'node2', // String,必须,目标节点 id
}, ],
};
const graph = new X6.Graph({
container: document.getElementById('container'),
// width: 400,
// height: 400,
interacting: {
nodeMovable: false,
},
});
graph.fromJSON(data)
function reset() {
const nodes = graph.getNodes()
const edges = graph.getEdges()
edges[0].appendLabel({
attrs: {
text: {
text: "offset: { x: -40, y: 80 }",
},
},
position: {
distance: 0.2,
offset: {
x: -10,
y: 40,
},
},
});
nodes.forEach((node) => {
node.attr(
'body', {
stroke: '#1890ff',
fill: 'white'
})
node.attr('label', {
// text: 'rect', // 文本
// fill: '#333', // 文字颜色
// fontSize: 13, // 文字大小
})
node.resize(40, 20)
})
edges.forEach((edge) => {
edge.attr('line', {
stroke: '#1890ff',
strokeDasharray: 5,
targetMarker: 'classic',
style: {
animation: 'ant-line 30s infinite linear',
},
})
})
}
reset()
graph.centerContent()
graph.zoom(0.5)
graph.on('node:mouseenter', ({
node
}) => {
node.attr('body', {
stroke: 'orange',
fill: 'orange'
})
// console.log(node)
var text = document.getElementById("tooltipText")
const p1 = graph.localToGraph(node.store.data.position.x, node.store.data.position.y)
text.innerHTML="我热爱我的祖国我热爱我的祖国我热爱我的祖国我热爱我的祖国我热爱我的祖国我热爱我的祖国我热爱我的祖国我热爱我的祖国我热爱我的祖国我热爱我的祖国"
text.style.display='block';
text.style.left=(p1.x + 80).toString() + 'px';
text.style.top=(p1.y - 50).toString() + 'px'
})
graph.on('node:mouseleave', ({
node
}) => {
var text = document.getElementById("tooltipText")
text.style.display="none"
reset()
})
</script>
</body>
</html>
如果你打算将代码用在vue当中,只需要修改少量的代码语句即可实现,非常方便!