mxgraph resizer

这篇文章的主题是如何自定义 mxGraph 图元的 sizer 的显示与隐藏。

显示与隐藏分两种情况,一种是在首次渲染时,就按照规则显示/隐藏 sizer,一种是更新时,让 sizer 的显示/隐藏状态发生变化。下面依次分析。

显示/隐藏 sizer 首先要搞明白 sizer 是什么。sizer 本质上还是 shape,继承自 mxShape,所以按照 shape 的显示与隐藏就可以完成 sizer 的显示与隐藏。

那接下来我们看看 mxGraph 自己是如何完成这个功能的。

代码分析

sizer 用来控制 shape 缩放,这里以块图元为例。

我们要知道这些东西(图元的缩放)都存在于 vertexHandler 中。所以我们从 vertexHanlder 的方法中,可以看到进行了初始化,调用了 init 方法。

function mxVertexHandler(state) {
  if (state != null) {
    this.state = state;
    this.init();

    // ......
  }
};

每一个图元,都对应一个 vertexHandler。每次 new vertexHanlder 时,都会执行 init 方法,此时 sizer 就已经被创建出来了。

在 init 方法中,有这么一段代码是用来创建 sizer 的

var i = 0;

if (resizable) {
    if (!this.singleSizer) {      this.sizers.push(this.createSizer('nw-resize', i++));
      this.sizers.push(this.createSizer('n-resize', i++));
      this.sizers.push(this.createSizer('ne-resize', i++));
      this.sizers.push(this.createSizer('w-resize', i++));
      this.sizers.push(this.createSizer('e-resize', i++));
      this.sizers.push(this.createSizer('sw-resize', i++));
      this.sizers.push(this.createSizer('s-resize', i++));
    }

    this.sizers.push(this.createSizer('se-resize', i++));
  }

这里创建了 8 个 sizer,分别是图元的左上,上,右上,左,右,左下 ,下,右下(如下图所示)

它们之间的顺序是从左到右,从上到下,index 从 0-7。

来看看 createSizer 的实现是什么样的,屏蔽掉一些这里不需要理解的代码。只看对我们有用的信息

mxVertexHandler.prototype.createSizer = function (cursor, index, size, fillColor) {
  size = size || mxConstants.HANDLE_SIZE;

  var bounds = new mxRectangle(0, 0, size, size);
  var sizer = this.createSizerShape(bounds, index, fillColor);
  
  // ......

  if (!this.isSizerVisible(index)) {
    sizer.visible = false;
  }

  return sizer;
};

看这里,sizer 实际上是一个 shape,被创建出来后,当达成某个条件后,sizer 的 visible 会被设置为 false。一旦 shape 的 visible 被设置为 false 以后,这个 shape 就不会再被渲染了

visible 控制 shape 渲染的逻辑可以参见 mxShape.redraw 方法

进入 isSizerVisible 方法可以看到 mxGraph 默认只返回了 true,入参为 index。

mxVertexHandler.prototype.isSizerVisible = function (index) {
  return true;
};

实现功能

首次渲染

我们可以按照 index 返回 false 或者 true,以达成自定义渲染 sizer 的目的。如果还需要根据图元本身做判断,那么 this 上挂载了 state,可以拿到 state 后,完成自定义渲染 sizer 的目的。

比如我们将上下左右的四个 sizer 移除,可以重写 isSizerVisible 方法。如下:

mxVertexHandler.prototype.isSizerVisible = function(index)
{
  if ([1, 3, 4, 6].includes(index)) return false;
  return true;
};

指定sizer进行显示/隐藏

mxGraph本身只提供了一个方法setHandlesVisible来显示和隐藏所有的handle,这里的handle是sizer的父级,sizer本身也是handle的一种,只不过是拿来控制图元大小的一种handle,名字叫做sizer。

我们看一下setHandlesVisible方法是如何实现的吧

mxVertexHandler.prototype.setHandlesVisible = function(visible)
{
    this.handlesVisible = visible;

    if (this.sizers != null)
    {
        for (var i = 0; i < this.sizers.length; i++)
        {
            this.sizers[i].node.style.display = (visible) ? '' : 'none';
        }
    }

    // ......
};

依旧是省略掉一部分代码。这里直接控制了dom上的样式,对sizer对应的dom元素的display样式进行了控制,所以如果想要实现只显示一部分的代码,可以自己写一个方法。但在此之前我们要搞明白handler的调用路径。

当我们点击一个图元时,selectionModel会发生变化,继而引起selectionCellsHandler中刷新handlers,所以在selectionCellsHandler中存储了所有已被创建的handler。在这里我们可以拿到,我们想操作的cell对应的handler。继而就可以完成对指定sizer(handle)的显示隐藏。

获取handler的方法我们可以在selectionCellsHandler中找到,即getHandler

理论可行,开始写代码

const hideSizerByIndex = (graph, cell, visible, index) => {
  if (index !== null) {
    const handler = graph.selectionCellsHandler.getHandler(cell);
    handler.sizers[i].node.style.display = (visible) ? '' : 'none';
  }
}

这样就完成了指定sizer进行显示/隐藏

至此,就可以自定义 sizer 的显示与隐藏了

后续将会写更多与mxGraph相关的文章,敬请期待。

Comments