vis.js 自定义右键功能

近期工作中接触到vis.js,使用了他的network功能,感觉与d3有点儿像,但是操作起来要比d3方便,以下是我的使用随笔。

vis.js 下载地址(https://almende.github.io/vis),官网上的API是纯英文的,可能对我这种英文一般的人来说使用起来就有点差强人意,无意间在别人的博文中看到一篇译文(https://blog.csdn.net/ipinki1218/article/details/83651961

本文主要讲一下vis.js如何实现右键操作,实际效果图如下所示
在这里插入图片描述
在这里插入图片描述
前期准备
我前台用的是vue + element-UI
1.下载vis.js
2.下载vue.js
3.下载elment-ui插件
4.下载lodash.min.js

HTML页面
首先新建一个topo.html页面,引入vis.js,vue.js等

<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet" href="../css/font-awesome.min.css">
<link rel="stylesheet" href="../element-ui/v2.10.1/theme-chalk/index.css">
<link href="../visjs/vis.min.css" rel="stylesheet" type="text/css" />
<style>
body {
	margin: 0px;
}

#mynetwork {
	min-height: 500px;
	width: 100%;
}

.btn {
	position: absolute;
	top: 10px;
	right: 10px;
}

.custom-menu {
  	position: absolute;
	padding:5px 0px;
    background-color: rgb(84, 92, 100);
    top:100px;
  	left:100px;
  	z-index:9999;
  	display:none;
}

.custom-menu div {
	padding:5px 0px;
	text-align: center;
}
.custom-menu div a{
	font-family:"微软雅黑";
  	font-size:14px;
  	color: rgb(255, 255, 255);
}
.custom-menu div a:HOVER, .custom-menu div a:ACTIVE{
	cursor: pointer; 
  	color: orange;
}
</style>
</head>
<body>
    <div id="app" v-cloak>
      <template>
		<div class="custom-menu" style="width:80px">
			<div><a @click="addNode">新增</a></div>
			<div><a @click="updateNode">修改</a></div>
			<div><a @click="deleteNode">删除</a></div>
    	</div>
    	<div id="mynetwork"></div>
    	<div class="btn">
    		<el-button type="success" icon="el-icon-refresh" circle @click="reloadNodeData" title="刷新"></el-button>
    	</div>
      </template>
    </div>
</body>
<script src="../jquery.min.js"></script>
<script src="../lodash.min.js"></script>
<script src="../vuejs/v2.6.10/vue.min.js"></script>
<script src="../element-ui/v2.10.1/index.js"></script>
<script src="../visjs/vis.min.js"></script>
<script src="../topo.js?_${.now?long}"></script>
</html>

JS代码
新建topo.js,代码如下

var network;
var vm = new Vue({
	el : '#app',
	data : {
		nodes : new vis.DataSet(), 
		edges : new vis.DataSet(),
		currNodeId : null  // 当前操作的节点(鼠标右键操作用)
	},
	methods : {
		//加载网络拓扑数据,首先从缓存中获取数据,如果没有则重新加载数据
		loadNetworkData : function(){
			var _this = this;
			$.getJSON('/topo/data/load', {
				success: function(r){
					if(r && r.code == 0 && r.data && r.data.nodes && r.data.nodes.length>0 && r.data.edges && r.data.edges.length > 0){
						r.data.nodes.forEach(function(node){
							_this.nodes.add(node);
						});
						r.data.edges.forEach(function(edge){
							_this.edges.add(edge);
						});
						_this.initNetwork();
					}else{
						_this.reloadNodeData();
					}
				}
			});
		},
		
		/**
		 * 构建数据
		 */
		buildData: function(){
			var rootId = _.uniqueId();
			var root = {
				id: rootId,
				label: '根节点',
				title: '根节点',
				group: 'root',
			};
			_this.nodes.add(root);
			
			for (var i=0; i<10; i++){
				var _id = _.uniqueId();
				var name = 'node'+i;
				var node = {
					id: _id,
					label: name,
					title: name,
					group: 'group',
				};
				_this.nodes.add(node);
				_this.edges.add({
					from : rootId,
					to : _id
				});
			}
		},
		//重新加载所有节点
		reloadNodeData : function(){
			this.nodes.clear();
			this.edges.clear();
			if (!!network) {
		    	network.destroy();
		        network = null;
		    }
			
			this.buildData();
			this.initNetwork();
		},
		//初始化网络拓扑
		initNetwork : function() {
			var _this = this;
			var container = document.getElementById('mynetwork');
			var data = {
				'nodes' : this.nodes,
				'edges' : this.edges
			};
			var options = {
				locale: 'cn',
				physics: {
			        stabilization: false,
			        barnesHut: {
			          //gravitationalConstant: -3000,//(默认值 : -2000)引力:值越大节点越集中,反之值越小节点越离散
			          springConstant: 0.01,//(default: 0.04)弹簧:值越大弹性越强
			          //springLength: 50//(default: 95)弹簧长度
			        },
			        minVelocity: 5 //(default: 1)一旦达到所有节点的最小速度,我们假设网络已经稳定,布局停止。
			    },
				interaction : {
					navigationButtons : true,
					keyboard : true
				},
				// 自定义节点样式
				groups : {
					root : {
						shape : 'icon',
						icon : {
							face : 'FontAwesome',
							code : '\uf015',
							size : 50,
							color : '#0066FF'
						}
					},
					group : {
						shape : 'icon',
						icon : {
							face : 'FontAwesome',
							code : '\uf0c0',
							size : 50,
							color : '#00CCFF'
						}
					}
				}
			};
			network = new vis.Network(container, data, options);
			var loading;
			// 稳定启动时触发
			network.on("startStabilizing", function (params) {
				loading = _this.$loading({
			          lock: true,
			          text: '数据加载中',
			          background: 'rgba(0, 0, 0, 0.8)'
			        });
			});
			//在Network稳定或调用stopSimulation()时触发
			network.on("stabilized", function (params) {
				loading.close();
				network.fit();
				saveNetwork();
			});
			// 双击鼠标触发
			network.on("doubleClick", function (params) {
				// 双击时params.nodes.length不为0,拖动时该事件也会触发,但length=0
				if (params.nodes.length>0){
					var node = _this.nodes._data[params.nodes[0]];
					// 这里可以实现点击加载节点
				}
			});
			// 单击鼠标触发
			network.on("click", function (params) {
				var nodeId = this.getNodeAt(params.pointer.DOM); 
				if(nodeId){
			    	var options = {
			    			scale: 1.0,
			    			offset: {x:0,y:0},
			    			animation: {
			    				duration: 1000,
			    				easingFunction: 'easeInOutQuad'
			    			}
			    	};
			    	network.focus(nodeId, options);		//定位交点	  		
				}
				if (!$(".custom-menu").is(':hidden')){
					$(".custom-menu").hide();
				}
			});
			// 单击鼠标右键触发
			network.on("oncontext", function (params) {
				var nodeId = this.getNodeAt(params.pointer.DOM); 
				if (nodeId){
					params.event.preventDefault();
				    $(".custom-menu").finish().toggle(100);
				    $(".custom-menu").css({
				        top: params.pointer.DOM.y + "px",
				        left: params.pointer.DOM.x + "px"
				    });
				    _this.currNodeId = nodeId;
				}
			});
			
			var saveNetwork =  function(){
				var positions = network.getPositions();
				var data = {
					'nodes': _this.nodes.get().map(function(node){
						return _.extend({}, node, positions[node.id]);
					}),
					'edges': _this.edges.get()
				};
				$.post('/topo/data/save', data, {
    				success: function(r){
					}
    			});
			};
			
		},
		addNode: function(){
			// 添加节点
			var _pid = _this.currNodeId;
			var _id = _.uniqueId(node.id + "_");
			// 根据节点的children数,计算_id
			var children = _this.edges.get({
				  filter: function (item) {
					  return item.from == _pid;
				  },
				  fields:["to"]
			});
			if (children){
				_.sortBy(children, function(a, b){
					return b.repalce(/_/g, "")-a.repalce(/_/g, "");
				});
				_id = parseInt(children[0].substring(_pid.length+1))+ 1;
			}
			
			var label = node.label+_id.substring(_pid.length);
			_this.nodes.add({
				id: _id,
				label: label,
				group: 'group',
				title: label,
			});
			
			_this.edges.add({
				from : _pid,
				to : _id
			});
		},
		updateNode: function(){
			// 修改节点
			var node = _this.nodes.get(_this.currNodeId);
			node.title = "我正在执行修改操作";
			_this.nodes.update(node);
		},
		deleteNode: function(){
			var _this = this;
			if (_this.currNodeId){
				var node = _this.nodes.get(_this.currNodeId);
				_this.nodes.remove(_this.currNodeId);
				// 删除所有连线
				var edgeIds = _this.edges.get({
					  filter: function (item) {
						  return item.to == _this.currNodeId;
					  },
					  fields:["id"]
				});
				_this.edges.remove(edgeIds);
				_this.currNodeId = null;
			}
			// 删除节点之后隐藏操作菜单
			if (!$(".custom-menu").is(':hidden')){
				$(".custom-menu").hide();
			}
		}
	},
	mounted : function() {
		//页面初始入口
		$('#mynetwork').height($(top).innerHeight()-100);
		if (window.ActiveXObject || "ActiveXObject" in window){
			//为IE浏览器设置宽度
			$('#mynetwork').width($(top).innerWidth());
		}
		this.loadNetworkData();
	}
});

JAVA代码
创建TopoController.java,将加载完成的拓扑结构保存到缓存里,再次加载时可以直接从缓存获取数据及结构(位置会和上次查询完全一样),这样做的好处有点儿类似快照,保留上次浏览结果,也可提升再次访问的加载速度。

@Controller
@RequestMapping("/topo")
public class AssetTopoController {
	
	@Autowired
    private RedisUtils redisUtils;
	

	/**
	 * 保存拓扑数据到redis中
	 * @param data
	 * @return
	 */
	@PostMapping("/data/save")
	@ResponseBody
	public void save(@RequestBody Map<String,Object> data) {
		redisUtils.set("my-network", data, 60*60); //设置过期时长为1小时
	}
	
	/**
	 * 从redis中获取拓扑数据
	 * @return
	 */
	@GetMapping("/data/load")
	@ResponseBody
	public Map<String, Object> load() {
		Object data = redisUtils.get("my-network");
		Map<String, Object> map = new HashMap<String, Object>();
		map.put("data", JSONUtil.parseObj(data));
		return map;
	}
}

实现右键菜单只需要两步就可以,第一找到vis.js network的右键事件,第二自定义一个菜单(或者div),在某个节点触发右键事件时定位到该节点上

第一步 找到vis.js network 的右键事件,获取当前节点的位置,用于确定右键菜单的位置
在这里插入图片描述
第二步 定义右键div 确定定位通过CSS样式来控制
在这里插入图片描述
在这里插入图片描述
这样就实现鼠标右键功能了。

另外有一点需要需注意,vis.js有自带的右键事件,使用时需要先关闭他自身的事件(关闭代码params.event.preventDefault()),然后再打开自定义菜单(显示自定义custom div),通过this.getNodeAt(params.pointer.DOM); 来获取当前选中节点。实现的过程中,我发现不管鼠标放在那里都可以调用右键事件,但是this.getNodeAt(params.pointer.DOM)只有放在节点上时才不会为空,所以可以通过this.getNodeAt(params.pointer.DOM)的返回值来判断当前鼠标的位置。我的做法是当鼠标选中某个节点时,触发鼠标右键弹出自定义菜单,离开选中节点单击鼠标关闭菜单,未选中节点时继续显示默认右键功能。
在这里插入图片描述

分享几篇别人的博文:
vis.js介绍
https://blog.csdn.net/DCX_abc/article/details/78143635

vis.js 小记
https://blog.csdn.net/qq_39759115/article/details/78594831

Vis.js–Network中文教程
https://blog.csdn.net/ipinki1218/article/details/83651961

  • 0
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值