vis.js可视化的出发点之一是它们可以处理动态数据,并允许对数据进行操作。为了实现这一点,vis.js包括一个基于灵活键/值 DataSet
并DataView
处理非结构化JSON数据的功能。
-
数据集DataSet,存储JSON的ID对象。可增删改查,过滤排序…
-
资料检视DataView,提供数据集上经过过滤或格式化的视图。通过订阅DataView中的更改,获取过滤或格式化的数据,而无需一直指定过滤器和字段类型
-
数据管道DataPipe,
DataPipe
从一个DataSet
获取数据DataView
,对其进行转换并传入第二个DataSet
,用于强制数据类型,更改结构…
一、总结
Vis.js 是一个动态的,基于浏览器的可视化库。该库被设计为易于使用,能处理大量的动态数据。该库由以下几部分组成:
一是数据集和数据视图,基于灵活的键/值数据集,可以添加,更新和删除项目,订阅数据集变化;
二是时间轴,用于显示不同类型的时间轴数据,在时间轴上项目可以交互移动,缩放和操纵;
三是图形,使用节点和边显示一个交互式图形或网络。
二、配置&使用
1.vis-network 入门
vue安装
# v4.21.0 不再维护
npm install vis
# v9.0.1 建议使用下面这个
npm install vis-network
引入 vis-network
<template>
<div id="mynetwork" class="mynetwork"></div>
</template>
<script>
//v4.21.0
import * as vis from "vis";
export default {
data() {
return { network: null };
},
mounted() {
this.init();
},
methods: {
init() {
// 节点
var nodes = new vis.DataSet([
{ id: 1, label: "Node 1" },
{ id: 2, label: "Node 2" },
{ id: 3, label: "Node 3" },
{ id: 4, label: "Node 4" },
{ id: 5, label: "Node 5" },
]);
// 边
var edges = new vis.DataSet([
{ id: 1, from: 1, to: 3 },
{ id: 2, from: 1, to: 3 },
{ id: 5, from: 3, to: 1 },
{ from: 1, to: 2 },
{ from: 2, to: 4 },
{ from: 2, to: 5 },
{ from: 3, to: 3 },
]);
// create a network
var container = document.getElementById("mynetwork");
var data = {
nodes: nodes,
edges: edges,
};
var options = {};
this.network = new vis.Network(container, data, options);
},
},
};
</script>
<style scoped>
.mynetwork {
height: 100%;
}
</style>
传统html页面使用
<html>
<head>
<script type="text/javascript" src="https://unpkg.com/vis-network/standalone/umd/vis-network.min.js"></script>
<style type="text/css">
#mynetwork {
width: 600px;
height: 400px;
border: 1px solid lightgray;
}
</style>
</head>
<body>
<div id="mynetwork"></div>
<script type="text/javascript">
// 创建节点数据数组
var nodes = new vis.DataSet([
{ id: 1, label: "Node 1" },
{ id: 2, label: "Node 2" },
{ id: 3, label: "Node 3" },
{ id: 4, label: "Node 4" },
{ id: 5, label: "Node 5" }
]);
// 创建边数据数组
var edges = new vis.DataSet([
{ from: 1, to: 3 },
{ from: 1, to: 2 },
{ from: 2, to: 4 },
{ from: 2, to: 5 },
{ from: 3, to: 3 }
]);
// 获取容器
var container = document.getElementById('mynetwork');
// 将数据赋值给vis 数据格式化器
var data = {
nodes: nodes,
edges: edges
};
var options = {};
// 初始化关系图
var network = new vis.Network(container, data, options);
</script>
</body>
</html>
效果如下
事件监听
交互模块触发
let container = document.getElementById("mynetwork");
let data = {
nodes: this.nodes,
edges: this.edges,
};
let options = {interaction: { hover: true }};
network = new vis.Network(container, data, options);
// 事件监听
this.networkEvent();
},
// 拓扑网络监听事件
networkEvent() {
//单击
network.on("click", (e) => {});
// 双击
network.on("doubleClick", (e) => {});
// 单击鼠标右键
network.on("oncontext", (e) => {});
// 节点悬停
network.on("hoverNode", (e) => {});
// 节点失焦
network.on("blurNode", (e) => {});
// 边悬停
network.on("hoverEdge", (e) => {});
// 边失焦
network.on("blurEdge", (e) => {});
// 鼠标拖动开始事件
network.on("dragStart", (e) => {});
// 鼠标拖动结束事件
network.on("dragEnd", (e) => {});
// 鼠标缩放事件
network.on("zoom", (e) => {});
//...
}
例:展开&收缩节点
需求:展开节点,收缩节点,收缩展开过的节点光晕颜色改变(区别未展开过的节点)
DataSet Methods
新增节点/边:nodes.add({…}),edges.add({…})
删除节点/边:nodes.remove({…}),edges.remove({…})
<template>
<div id="mynetwork" class="mynetwork2"></div>
</template>
<script>
const vis = require("vis-network/dist/vis-network.min.js");
import _ from "lodash";
var network = null;
export default {
data() {
return {
//创建节点对象
nodes: new vis.DataSet([
{
id: "node-1",
label: "Node 1",
title: "1111",
image: {
selected: require("@/assets/people-opened.png"),
unselected: require("@/assets/people-def.png"),
},
},
]),
//创建连线对象
edges: new vis.DataSet([
// { id: "edge-1", from: "node-1", to: "node-2" },
]),
removeNodes: [],
removeEdges: [],
};
},
mounted() {
this.initNetwork();
},
methods: {
// 初始化网络拓扑
initNetwork() {
let container = document.getElementById("mynetwork");
let data = {
nodes: this.nodes,
edges: this.edges,
};
let options = {
interaction: {
hover: true,
},
layout: {
randomSeed: 2, //配置每次生成的节点位置都一样,参数为数字1、2等
},
nodes: {
// shape: "dot",
// shape: "circularImage",
// borderWidth: 2,
shape: "image",
size: 23,
color: {
// border: "rgba(255, 255, 255, 1)",
background: "rgba(102, 51, 255, 1)",
},
font: {
// color: "#fff",
},
shadow: {
enabled: true,
// color: "rgba(153, 102, 204, 1)",
color: "rgba(153, 0, 204, 1)",
size: 40,
x: 0,
y: 0,
},
chosen: {
label: false,
// node: function(values, id, selected, hovering) {
node: function(values) {
values.shadowColor = "rgba(255, 215, 0, 0.8)";
},
},
// shapeProperties: {
// borderDashes: [5, 15],
// },
},
edges: {
hoverWidth: 0,
shadow: true,
arrows: {
to: {
enabled: true,
type: "arrow",
},
},
color: {
color: "#525A81",
highlight: "#FFBA30",
hover: "#FFBA30",
// inherit: "from",
opacity: 1.0,
},
font: {
// color: "#fff",
size: 12, // px
strokeWidth: 0, // px
},
},
physics: {
enabled: true,
// minVelocity: 1 //(default: 1)一旦达到所有节点的最小速度,我们假设网络已经稳定,布局停止。
timestep: 0.2,
// wind: {
// x: 10,
// y: 10
// }
},
};
network = new vis.Network(container, data, options);
// 事件监听
this.networkEvent();
},
// 拓扑网络监听事件
networkEvent() {
//单击
network.on("click", (e) => {
console.log("单击:", e);
console.log(this.edges.get(e.edges[0]));
//只有在点击节点时才进行节点的增删
if (!e.nodes.length) {
return false;
}
var clickNodeList = this.nodes.get(e.nodes[0]);
console.log(clickNodeList);
if (!clickNodeList.open) {
// //恢复过滤数据
// this.recoverFilterData();
this.prdData(e.nodes[0], e.edges[0], e.nodes[0]);
// 判断是否有子节点
let haschildState = network.getConnectedNodes(e.nodes[0], "to").length ? true : false;
this.nodes.update({
id: e.nodes[0],
open: true,
haschildren: haschildState,
changEdge: e.edges[0],
});
if (e.edges[0]) {
this.edges.update({
id: e.edges[0],
open: true,
haschildren: haschildState,
// length: 600,
});
}
// 移动焦点到视图中心
// 双击某一节点,画布放大至1:1比例,当前节点处于画布中心位置
var option = {
scale: 1.0,
offset: { x: 0, y: 0 },
animation: {
duration: 1000,
easingFunction: "easeInOutQuad",
},
};
network.focus(e.nodes[0], option);
} else {
// 获取该节点下 未展开的节点的集合 和 从该节点发出的到未展开节点线的集合
let nodeItems = this.nodes.get({
filter: function(item) {
return item.parentId === e.nodes[0] && !item.open && !item.haschildren;
},
});
console.log("nodeItems", nodeItems);
let edgesItem = this.edges.get({
filter: function(item) {
return (
!item.open && item.from == e.nodes[0] && !item.haschildren
// (item.to == e.nodes[0] ||
// item.from == e.nodes[0])
);
},
});
console.log("edgesItem", edgesItem);
//需求: 只缩进当前节点一度关系中未展开的节点,一度关系点已展开关系的不缩进
//缩进( 删除未展开子节点和子节点连线)
// this.addNew(this.removeNodes, nodeItems);
// this.addNew(this.removeEdges, edgesItem);
this.nodes.remove(nodeItems);
this.edges.remove(edgesItem);
// 判断是否有子节点
let haschildState = network.getConnectedNodes(e.nodes[0], "to").length ? true : false;
this.nodes.update({
id: e.nodes[0],
open: false,
shadow: {
// color: "rgba(166, 26, 120, 1)",
color: "rgba(81, 0, 255, 1)",
},
image: {
selected: require("@/assets/people-opened.png"),
unselected: require("@/assets/people-def.png"),
},
// clickNodeList.image.selected || clickNodeList.image, //已修改后的字段为image
haschildren: haschildState,
});
if (clickNodeList.changEdge) {
//初始中心节点无changEdge
this.edges.update({
id: clickNodeList.changEdge,
open: false,
haschildren: haschildState,
// length: 280,
});
}
}
});
},
prdData(nodename, edgename, orgnode) {
var len = 2;
let nodes = [];
let edges = [];
let nodeName = nodename || "node";
let edgeName = edgename || "edge";
const relation = [
"诈骗",
"父亲",
"母亲",
"哥哥",
"姐姐",
"同事",
"定向扩展",
"违章",
"妻子",
"弟弟",
];
const labelType = ["people", "car", "phone", "qq", "wx", "email"];
for (var i = 1; i <= len; i++) {
const randomIndex = Math.floor(Math.random() * 6);
nodes.push({
parentId: orgnode,
id: nodeName + "-" + i,
label: labelType[randomIndex],
shape: "image",
image: {
selected: require("@/assets/people-opened.png"),
unselected: require("@/assets/people-def.png"),
},
haschildren: false,
});
edges.push({
id: edgeName + "-" + i,
from: orgnode || "node-1",
to: nodeName + "-" + i,
label: relation[Math.floor(Math.random() * 10)],
haschildren: false,
});
}
// 避免添加重复数据导致错误
this.buildData(nodes, edges);
},
// 构建数据
buildNode(nodes) {
nodes.forEach((item) => {
// var node = {
// ...
//};
this.nodes.add(item);
});
// console.log("---------", this.nodes.get());
},
buildEdge(edges) {
edges.forEach((item) => {
// var edge = {
// ...
// };
this.edges.add(item);
});
},
buildData(n, e) {
// 去重
// console.log(this.nodes.get());
//删除n中与第二个数组对象id相同的值
let differenceN = _.differenceBy(n, this.nodes.get(), "id");
let differenceE = _.differenceBy(e, this.edges.get(), "id");
// console.log("differenceN:", differenceN, differenceE);
this.buildNode(differenceN);
this.buildEdge(differenceE);
},
addNew(arr, newarr) {
for (const item of newarr) {
arr.push(item);
}
},
recoverFilterData() {
// console.log("reset:", this.removeNodes);
// console.log("reset:", this.removeEdges);
},
},
};
</script>
<style scoped>
.mynetwork2 {
height: 100%;
/* background-color: #2a004a; */
}
</style>
效果如下
功能点:
-
右键展开菜单,点击请求节点数据,或展开下级菜单
-
拖动节点或画布,菜单跟随节点
-
缩放画布,菜单同步缩放
-
对其它或者空白区域进行操作时,关闭菜单
自定义菜单
vis拓扑图 右键节点展开菜单