扩展 vis-network 右键菜单

扩展 vis-network 右键菜单

小小孩
2023-02-28 / 1 评论 / 183 阅读 / 正在检测是否收录...
温馨提示:
本文最后更新于2023年02月28日,已超过448天没有更新,若内容或图片失效,请留言反馈。

之前一直使用 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"],
});

0

评论 (1)

取消
  1. 头像
    93攻略
    Windows 7 · FireFox

    你写得非常清晰明了,让我很容易理解你的观点。

    回复