上一篇文章写的是怎么用X6做一个类似于processOn的思维导图,但是是没有做节点的收缩展开功能的,这种功能应该如何实现呢?
处理收缩节点按钮生成
图一:仔细观察,鼠标悬停到节点上方,会在节点末尾生成一个圆圈,点击可收缩节点,此部分效果通过样式控制,方便后面做风格切换功能
图二:节点收缩后,会计算当前节点的被收缩的子节点的全部数量,效果也是通过样式控制
思考:怎么实现上面两点的效果呢?
上一篇文章地址: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);
});
});
如何让收缩事件生效?
对啊,我加了字段collapsed,如何让这个字段生效呢?
上一篇文章地址:juejin.cn/post/725661…
上一篇文章需求一中,如何去渲染组件被传入的数据mindData呢?
renderChart方法中,用到了Hierarchy.compactBox(本来组件的布局算法是自己写的,但是后面会发现一个问题,就是当你子节点数量很大时,收缩节点后,当前节点跟其他兄弟节点的距离无法收回,看下图,图片来自于上一篇文章的一位提问者)
getChildren: (d) => {
// 此部分是收缩节点时使用
const hasCollapsed = !!d.collapsed;
return hasCollapsed ? null : d.children;
},
好了,让我们看看效果: 我录了视频但是我不会放,谁会的可以教教我。。。
最后,感谢大家阅读我的小文章,请帮我点亮一下我的小心心吧!!!