Open phenomLi opened 5 years ago
对数据进行后序遍,由同层级的若干个子节点推算出父节点的位置,再一层一层推至根节点的位置,这样是否可行呢?
对数据进行后序遍,由同层级的若干个子节点推算出父节点的位置,再一层一层推至根节点的位置,这样是否可行呢?
不行,我想过,因为子节点的位置也要依赖父节点计算,总之一次遍历是实现不了的
不错哇,非常认真仔细,这篇论文我也搜索到,但没仔细研究,还是楼主历害啊。
不错哇,非常认真仔细,这篇论文我也搜索到,但没仔细研究,还是楼主历害啊。
其实这篇论文还是挺水的。。连伪代码都没有
🐂 太厉害了,刚好在写思维导图应用,这篇文章帮上了大忙
🐂 太厉害了,刚好在写思维导图应用,这篇文章帮上了大忙
能给到你启发就好😁
阅读了您的算法,觉得很有参考价值。 我有另一种思路,
我已经使用别的语言实现了,能够达到类似的效果。
但是,我目前遇到一个问题,无论博主的方法,还是上面的方法,都无法解决,当最后一层节点数过多,整个画布特别宽的问题,希望听听博主的建议,谢谢!
阅读了您的算法,觉得很有参考价值。 我有另一种思路,
- 从下向上,递归算出每一个节点的“宽度”,父节点的宽度等于其所有子节点宽度的和
- 从上向下,根据父节点的宽度,计算出起始横左边的位置,然后依次算出,同一层节点的位置
- 递归过程2,得到所有节点的位置
我已经使用别的语言实现了,能够达到类似的效果。
但是,我目前遇到一个问题,无论博主的方法,还是上面的方法,都无法解决,当最后一层节点数过多,整个画布特别宽的问题,希望听听博主的建议,谢谢!
你的方法一开始从下往上,那是不是需要先找到所有叶子结点?找叶子节点的过程也需要一次遍历把?
至于最后一层变得很宽的问题,在这种布局方式下其实基本无解,因为该情况下数据一般都很庞大了,如果需要可视化展示,那就只能加点交互,比如视图拖拽和缩放功能。或者跳出层次布局的思维,看以看看一些非层次的树形结构布局算法。
@phenomLi 谢谢你的回复,我想想别的思路
@AhrenLi 你可以尝试将最后一层同一个父元素叶子节点很多的情况变成块状显示,减少宽度。
你好,请问根节点里存的是所有节点吗
大佬有尝试过节点大小不一致的情况吗
大佬有尝试过节点大小不一致的情况吗
节点大小不一致会比较麻烦,但是其实也可以解决,只要有宽高数据即可。
大佬有尝试过节点大小不一致的情况吗
节点大小不一致会比较麻烦,但是其实也可以解决,只要有宽高数据即可。
宽高数据是有的
我想把他们的链接线做成这个样子的
如果高度不一样,并且没有足够的间隔的话,线和图元会相互覆盖
如果高度不一样,并且没有足够的间隔的话,线和图元会相互覆盖
你是指节点高度不一样吗,可以取一行中高度最大的节点的高度吗
是的
不知道您这里又处理这种情况吗
不知道您这里又处理这种情况吗
这种可能不太清楚呢
@AhrenLi 你可以尝试将最后一层同一个父元素叶子节点很多的情况变成块状显示,减少宽度。
请问有实现思路吗? 最近有类似的需求,需要对末尾节点进行局部展示处理。
大佬, 一个子节点有多个父节点的情况有没有好的思路
上个星期看可视化相关的论文,看到一篇介绍了一种树形结构布局算法的论文,算法效果图是这样子的:![](https://github.com/phenomLi/Blog/raw/master/photos/2019-6-24/微信截图_20190624190322.png)
该布局的规则是:子节点相对于父节点成等腰排列,即根节点位于叶子节点两端距离上方正中间。在所有节点不重叠的情况下相邻节点间距相等,所有节点均不能重叠。其次,算法应适应于任意宽度,任意深度的树。这个布局较为美观,而且空间利用率较高。
传统的树形布局通常将子节点按照兄弟节点的最大宽度分开(一个节点的最大宽度即以该节点为根的树所占的最大宽度)。这样做虽然代码上容易实现,只要在每个节点保存该子树的最大宽度即可,但是视觉上不够美观,比较浪费空间。在某些传统布局甚至限制子节点的数目。![](https://github.com/phenomLi/Blog/raw/master/photos/2019-6-24/图片1.png)
而这种布局与传统的树形布局有很大不同,它做到了在布局上节点与节点尽可能地紧凑,而且始终保持对称性和任意子节点数目。
构思
论文中只是介绍了布局的一些特点和优势,对于具体实现的思路细节并没有过多提及,说实话我第一次看到论文中的效果图,我感觉应该实现起来并不会很难,但是当时我并没有认真思考。之后隔离一天,准备动手写时我发现我被打脸了,要实现该布局,其实没有这么简单。。。。
首先,假如你有一棵树,你要将每个节点摆到正确的位置,你会发现从根往下开始一次遍历布局不行的,因为父节点的位置要根据子节点的位置而定,子节点为了避免交叉重叠,会相互隔得很开,父节点为了保持对称性,也会发生移动。而从底部往上一次遍历布局同样也行不通,因为子节点也要跟着父节点走,父节点位置改变了扫描过的子节点也需要移动。
所以,可以得出结论,只进行一次遍历是无法完成布局的(光是这点我就想了一个下午才相通,我太蔡了。。)。之后我又认真地想了两天,没错,是整整两天,没有写代码,就光想思路,终究有所收获。下面简单描述我的思路:
首先,我们从上往下,先根据相邻节点间地最小间距
nodeInterval
和父子节点间地间距yInterval
对树进行第一次布局。由于初始状态所有节点的坐标都是未知的,我们不妨把所有节点的坐标先都设置为(0,0) 。从根节点开始。人为设定好根节点的坐标 ,然后将根节点的子节点挂在根节点下,且子节点分布在根节点的yInterval
高度下方,子节点彼此间距为nodeInterval
且相对于根节点对称分布。递归进行此步骤,直到所有的节点都布局好。然后,我们需要一个
hashTree
,用作将树保存到一个按层次分别的线性表中。我们将树转换到hashTree
。效果图中的树对应hashTree
如下:从最低层开始从下往上按层遍历
hashTree
,检测相邻的节点。假设n1
,n2
为相邻的一对节点,n1
的在线性表的下标小于n2
。检测n1
,n2
是否重叠。如果发生重叠,则左边不动,整体往右进行调整。但调整的不是n2
节点,而是“与n1
的某个祖先节点为兄弟节点的n2
的祖先节点”。为什么呢?后面我再解释。上面已经提到了。每移动完一个节点,其父节点都会失去对称性,所以要进行调整。但我们不动父节点,只通过往左移动子节点来恢复对称性。原理如图示:![](https://github.com/phenomLi/Blog/raw/master/photos/2019-6-24/微信截图_20190624224228.png)
每次恢复对称性后,有某些子节点又会发生重叠现象,所以这时要回到底层重新开始扫描。
重复3,4,5步骤,直到所有重叠都被消除,布局完成。
下面说说一些细节问题:
n1
的某个祖先节点为兄弟节点的n2
的祖先节点”,为什么要移动它?请看下图:
框中的两个相邻节点发生了重叠,其中蓝色为
最优解是移动红色的节点及以该节点为根的整棵子树,即框中的所有节点,因为这样只会失去红色节点的父节点的对称性,后续只需调整那一个节点即可。其中红色节点就是“与
n1
,橙色为n2
。如果这时单纯往右地移动n2
,会发现n2
所在的子树发生了形变,因为n2
的父节点失去了对称性,算法接下来会对该父节点进行对称性恢复。然而根据步骤4的规则,n2
极其兄弟节点会往左移动,这时n1
,n2
会重新发生重叠。虽说经过多次迭代后n2
始终会到达正确的位置,但是这不是最优解。n1
的某个祖先节点为兄弟节点的n2
的祖先节点”。nodeInterval
。如下图:以上就是我思路的全部内容,很多很长很复杂,花了我差不多整整3天,才把他们捋清楚。可能有一些细节还表达得不是很清楚。
为此,我还制作了一个布局过程的动态可视化动画:![](https://github.com/phenomLi/Blog/raw/master/photos/2019-6-24/GIF.gif)
代码实现
码代码 + debug又花了3天多,毕竟想是一回事,写又是另一回事,还踩了不少坑。
首先我们定义节点的类型,很简单
其中
ox
的作用是用作保存节点上一次布局完成的坐标,那么下一次布局完成时就可以对比ox
和x
是否发生了改变,对视图进行部分更新。这不是必须的,只是一种优化手段。接下来我们可以编写树的主类:
最终的视图渲染呈现取决于你用的渲染库,我用的是我自己开发的Renderer,但是我删去了相关代码。
由于篇幅问题,我这里删去了一些不重要的内容,只保留了核心的代码。一些比如节点的创建与删除,把节点插入到
hashTree
,从hashTree
删除节点的代码我都删去了,因为这些都不是核心内容。我在这里真的要称赞一下我自己,你们看其他人哪有像我这样,几乎每一行代码都有详细注释的,这就是态度!
效果展示
首先模仿一下论文的效果图:
新增节点:![](https://github.com/phenomLi/Blog/raw/master/photos/2019-6-24/add.gif)
删除节点:![](https://github.com/phenomLi/Blog/raw/master/photos/2019-6-24/del.gif)
---EOF---