之前一直使用 antv/g6
作为拓扑图展示插件,但发现 antv/g6
在力导向布局拖动下展示效果很差,节点一直在抖,而且在大数据下,直接卡死。所以转换为 vis-network
,相比之下 vis-network
效果很流畅,展示效果也不错。
唯一不好就是 没有 G6 那么多插件,就自己根据 G6 的插件api封装形式,自己封装了一份 vis 的右键菜单插件
插件代码
function modifyCSS(dom, css) {
if (dom) {
for (let key in css) {
dom.style[key] = css[key];
}
}
return dom;
}
export default class VisMenu {
constructor(network, config = {}) {
this.network = network;
const defaultConfig = {
offsetX: 6,
offsetY: 6,
handleMenuClick: undefined,
getContent: (e) => {
return `
<div class='aa'>
<div>菜单项1</div>
<div>菜单项2</div>
</div>
`;
},
onHide() {
return true;
},
itemTypes: ["node", "edge", "canvas"],
};
this.config = { ...defaultConfig, ...config };
const _this = this;
// 初始化menuDom
this._createMenu();
this.network.on("oncontext", function (params) {
params.event.preventDefault();
_this._showMenu(params);
});
}
_createMenu() {
const _this = this;
const containerDom = _this.network.canvas.frame;
modifyCSS(containerDom, { position: "relative" });
const menuDom = document.createElement("div");
menuDom.className = _this.config.className || "vis-contextmenu";
containerDom.appendChild(menuDom);
modifyCSS(menuDom, {
top: "0px",
position: "absolute",
visibility: "hidden",
});
_this.menuDom = menuDom;
_this.containerDom = containerDom;
}
_showMenu(params) {
const _this = this;
const { pointer } = params;
const menuResParams = {};
const atNode = _this.network.getNodeAt({
x: pointer.DOM.x,
y: pointer.DOM.y,
});
const atEdge = _this.network.getEdgeAt({
x: pointer.DOM.x,
y: pointer.DOM.y,
});
if (atNode) {
menuResParams.type = "node";
menuResParams.id = atNode;
menuResParams.orgItemData = _this.network.body.data.nodes.get(atNode);
} else if (atEdge) {
menuResParams.type = "edge";
menuResParams.id = atEdge;
const orgItemData = _this.network.body.data.edges.get(atEdge);
const fromId = orgItemData.from;
const toId = orgItemData.to;
menuResParams.orgItemData = orgItemData;
menuResParams.orgFromData = _this.network.body.data.nodes.get(fromId);
menuResParams.orgToData = _this.network.body.data.nodes.get(toId);
} else {
menuResParams.type = "canvas";
}
// 判断是否显示菜单
if (!_this.config.itemTypes.includes(menuResParams.type)) {
this._hideMenu();
return false;
}
// 塞入menuDom
const menuStr = _this.config.getContent(menuResParams);
_this.menuDom.innerHTML = menuStr;
const graphWidth = _this.containerDom.offsetWidth;
const graphHeight = _this.containerDom.offsetHeight;
// Vis 默认在父元素添加 position:relative 所以不需要计算 offsetTop,offsetLeft
// const graphTop = _this.containerDom.offsetTop;
// const graphLeft = _this.containerDom.offsetLeft;
// const bbox = _this.menuDom.getBoundingClientRect();
// let x = pointer.DOM.x + graphLeft + _this.config.offsetX;
// let y = pointer.DOM.y + graphTop + _this.config.offsetY;
// // when the menu is (part of) out of the DOM
// if (x + bbox.width > graphWidth) {
// x = pointer.DOM.x - bbox.width - _this.config.offsetX + graphLeft;
// }
// if (y + bbox.height > graphHeight) {
// y = pointer.DOM.y - bbox.height - _this.config.offsetY + graphTop;
// }
const bbox = _this.menuDom.getBoundingClientRect();
let x = pointer.DOM.x + _this.config.offsetX;
let y = pointer.DOM.y + _this.config.offsetY;
if (x + bbox.width > graphWidth) {
x = pointer.DOM.x - bbox.width - _this.config.offsetX;
}
if (y + bbox.height > graphHeight) {
y = pointer.DOM.y - bbox.height - _this.config.offsetY;
}
modifyCSS(_this.menuDom, {
top: `${y}px`,
left: `$px`,
visibility: "visible",
});
const handler = () => {
_this._hideMenu();
};
_this.handler = handler;
// 如果在页面中其他任意地方进行click, 隐去菜单
document.body.addEventListener("click", handler);
if (_this.config.handleMenuClick) {
const handleMenuClickWrapper = (evt) => {
_this.config.handleMenuClick(evt.target, menuResParams, _this.network);
};
_this.menuDom.addEventListener("click", handleMenuClickWrapper);
_this.handleMenuClickWrapper = handleMenuClickWrapper;
}
}
_removeMenuEventListener() {
const _this = this;
if (_this.handler) {
document.body.removeEventListener("click", _this.handler);
}
if (_this.handleMenuClickWrapper) {
_this.menuDom.removeEventListener("click", _this.handleMenuClickWrapper);
}
}
_hideMenu() {
if (this.menuDom) {
modifyCSS(this.menuDom, { visibility: "hidden" });
}
// 隐藏菜单后需要移除事件监听
this._removeMenuEventListener();
}
destroy() {
this._removeMenuEventListener();
if (this.menuDom) {
this.containerDom.removeChild(this.menuDom);
}
}
}
如何使用
import { Network } from "vis-network";
import VisMenu from './visMenu'
const myNetwork = new Network(DOM, data, {/*...options*/});
new VisMenu(myNetwork, {
className: "my-network-menu",
getContent(e) {
return `<ul>
<li>${e.type}</li>
<li>菜单一</li>
<li>菜单二</li>
<li>菜单三</li>
<li>菜单四</li>
<ul>`;
},
handleMenuClick(target, item, network) {
console.log(target.innerText);
},
itemTypes: ["node", "edge"],
});
你写得非常清晰明了,让我很容易理解你的观点。