「AntV」如何用AntV X6实现思维导图节点收缩功能?

3,066 阅读4分钟

上一篇文章写的是怎么用X6做一个类似于processOn的思维导图,但是是没有做节点的收缩展开功能的,这种功能应该如何实现呢? image.png image.png

处理收缩节点按钮生成

图一:仔细观察,鼠标悬停到节点上方,会在节点末尾生成一个圆圈,点击可收缩节点,此部分效果通过样式控制,方便后面做风格切换功能
图二:节点收缩后,会计算当前节点的被收缩的子节点的全部数量,效果也是通过样式控制 思考:怎么实现上面两点的效果呢?
上一篇文章地址:juejin.cn/post/725661…
看过上一篇文章的人会了解到,传入组件的数据叫mindData,我们可以在节点收缩时,给当前节点设置一个字段collapsed,为true代表当前节点的子节点被收缩,为false代表子节点被展开
那图二的数据怎么计算?这个也是在点击收缩时,计算当前节点子节点的数量,并给当前字段加上一个count作为标记

上一篇文章中,在注册html节点时,有一个creatHtmlDom的方法,内部存在一个创建子节点的createChildNode的方法(当前只放了当前功能相关的代码)

export const createChildNode = (data, propsData) => {
  // 创建子节点部分
  ...
  ...
  // 核心部分,创建收缩节点时那个圈圈节点
  if (data.children && data.children.length) {
    const collapseNode = document.createElement("span");
    const isCollapsed = !!data.collapsed;
    let originName = "";
    if (isCollapsed) {
      originName = "mindmap__wrap-collapsed";
      if (data.count > 99) {
        const i = document.createElement("i");
        i.classList.add("xq-icon-more");
        collapseNode.appendChild(i);
      } else {
        collapseNode.innerText = data.count;
      }
    } else {
      originName = "mindmap__wrap-open";
    }

    let collapseName = "";
    collapseNode.className = originName;

    if (propsData.direction == "LR") {
      collapseName = originName + "-right";
    } else if (propsData.direction == "RL") {
      collapseName = originName + "-left";
    } else if (propsData.direction == "TB") {
      collapseName = originName + "-bottom";
    } else {
      collapseName = originName + "-top";
    }
    // 这个是处理节点居中还是不居中的模式,不用考虑
    if (!propsData.heightCenter) {
      collapseNode.classList.add("not-center");
    }

    collapseNode.classList.add(collapseName);
    // 绑定收缩展开事件
    collapseNode.setAttribute("event", "node:collapse");
    collapseNode.style.borderColor = data.textLine || propsData.textLine;

    collapseNode.style.background = propsData.backgroundColor;
    collapseNode.style.color = data.textLine || propsData.textLine;
    textNode.appendChild(collapseNode);
  }
  // wrap:子节点最顶级
  // textNode: 文字节点
  wrap.appendChild(textNode);
  
}

// 样式核心部分
// 通过opacity来控制节点的显示与隐藏,如果通过visible之类的控制,会有鼠标悬停到节点没有触发节点展示的bug
.mindmap__wrap-open {
  opacity: 0;
}
.mindmap__wrap-collapsed {
  display: inline-block;
  width: 7px;
  height: 7px;
  border-width: 1px;
  border-style: solid;
  border-radius: 7px;
  position: absolute;
  // visibility: hidden;
  cursor: pointer;

  &:hover {
    opacity: 1;
  }
}
// 其他部分就是处理对圈圈节点的定位了(注意组件上下左右四个方向的布局)

处理收缩展开事件

 this.graph.on("node:collapse", ({ node, e }) => {
        const p = node.getPosition();
        const _nodeData = node.data.nodeData;
        const children = _nodeData.children;
        // 获取当前节点收缩效果,true代表当前节点的子节点都被收缩,false则相反
        const hasCollapsed = _nodeData.collapsed;
        // 计算当前节点下子节点的全部数量
        const count = collapsedNodes(children);
        
        // 找到目标数据并设置切换数据效果
        // 注意:这个字段如何有作用?往下看
        let { node: node2 } = findItem(this.mindData, _nodeData.id);
        // 给数据设置hasCollapsed
        Vue.set(node2, "collapsed", !hasCollapsed);
        // 这个count做节点收缩时,展示的数据
        node2.count = count;
        // 重置编辑节点
        // 目的是,当前操作`node:collapse`事件时,如果当前画布中存在编辑类型的输入框节点,
        // 需要将输入框转成正常节点
        this.resetEditNode();
        if (
          !hasCollapsed &&
          this.originChoosedNode &&
          this.originChoosedNode.dataset.id !== _nodeData.id
        ) {
          this.currentData = {};
        }
        e.stopPropagation();
        // 注意!!!非常重要
        // 当收缩的当前节点子节点数量过大时,会造成画布偏移,
        // 你点击时,圈圈的位置跟你收缩后,节点位置不一致(看下面两张图),此时需要计算画布偏移量进行重新归位
        setTimeout(() => {
          const _node = this.graph.getCellById(node.id);
          const p2 = _node.getPosition();

          const pTranslate = this.graph.translate();
          // 注意!一定要乘以缩放比例,不然纵向布局无法定位到刷新之前的位置
          const zoom = this.graph.zoom();
          const deviationX = pTranslate.tx - (p2.x - p.x) * zoom;
          const deviationY = pTranslate.ty - (p2.y - p.y) * zoom;

          this.graph.translate(deviationX, deviationY);
        });
      });

1696063324944.png1696063324953.png

如何让收缩事件生效?

对啊,我加了字段collapsed,如何让这个字段生效呢? 上一篇文章地址:juejin.cn/post/725661… 上一篇文章需求一中,如何去渲染组件被传入的数据mindData呢? renderChart方法中,用到了Hierarchy.compactBox(本来组件的布局算法是自己写的,但是后面会发现一个问题,就是当你子节点数量很大时,收缩节点后,当前节点跟其他兄弟节点的距离无法收回,看下图,图片来自于上一篇文章的一位提问者)

getChildren: (d) => { 
    // 此部分是收缩节点时使用 
    const hasCollapsed = !!d.collapsed; 
    return hasCollapsed ? null : d.children; 
},

image.png

好了,让我们看看效果: 我录了视频但是我不会放,谁会的可以教教我。。。

image.png1696064761055.png

最后,感谢大家阅读我的小文章,请帮我点亮一下我的小心心吧!!!

1689577155763(1).png 1689577224795.png 1689577345238(1).png