今天学习下 kityminder 中 connect 部分, 我觉得它表示节点间的连线实现. 以前看的时候略过了它.
首先看下 core/connect.js:
// 连线提供方. 看起来是可以 `注册' 连线的 name->provider. // 在 src/connect 目录下有大约 7 种连线 provider. var _connectProviders = {}; // 注册 name->provider. 这里 provider 是一个函数. exports.register = function(name, provider) { ... 将 name->provider 放到集合 _connectProviders 中. } // 这里会注册一个缺省的(default)连线提供方, 我们稍后研究. // 扩展方法到类 MinderNode extend class MinderNode { getConnect(): 获取当前节点的连线类型. 值放在 .data.connect 中, 缺省为 'default'. getConnectProvider(): 根据 getConnect() 得到 name, 然后 -> provider. getConnection(): 获取当前节点的连线对象. 是一个 svg <path> 的 kity.Path 包装. } // 扩展方法到类 Minder extend class Minder { getConnectContainer(): ?可能是获得一个 <g> 容器, 其容纳所有 <path> 连线. // 为节点 node 创建连线 connection 对象. createConnect(node): { if (node is root) return; // 根节点不创建. var conn = new kity.Path(); // 即 svg <path> 对象 node._connection = conn; // 可认为是 node.setConnection(). this._connectContainer.addShape(conn); // 添加到 <g> 中. this.updateConnect(node); } removeConnect(node): 删除 node 所有子孙节点的连线对象. updateConnect(node): 稍后研究. }
在浏览器的调试控制台中观察一幅脑图的 HTML, 的确可以找到 <g id='minder_connect_group1'> 的 svg
元素, 它应该就是 minder.getConnectContainer() 获得的那个元素. 连线使用 svg <path> 实现, 所有连线
都放在 <g> 容器中展现.
每个子节点(即除了根节点) 都有(且只有)一条连线 连接到其父节点. 估计连线的形状 (即 path 中的 d 属性/数据)
由连线提供者 (provider) 负责计算出来. 这里猜测不对没关系, 下面改正就是了, (俺)脸皮就要厚一点!
这不, 又观察了一下, 天盘图中子节点的连线是连接到其前一个兄节点, 第一个子节点连接其父节点! 瞬间推翻估计...
不论连接到哪里, 子节点 (node) 中一定有一个属性 ._connection 用于记录一个 kity.Path 对象表示此连线.
其通过 get/setConnection() 访问; 另一个属性 .data.connect 记录连线的 provider 类型(也可叫连线形式?).
因此合理猜测 minder.updateConnect(node) 是用该 node 的 connect provider 去计算出 connection
(连线) 的路径数据, 好用 svg 显示出一条曲线(或"直的"曲线). 下面分析 updateConnect() 函数.
minder.updateConnect = function(node) { var conn = node.getConnection(); // 得到连线对象, 可认为是 svg<path> if (!conn) return; // 没有则不处理, 合理检查. var parent = node.parent; // 可认为是 node 的连接目标. if (!parent) return; // 没有父节点则不处理, 看似合理的检查. if (parent 是收起状态, 此时子节点不可见) 则设置连线不可见, return; var provider = node.getConnectProvider(); // 获取 provider, 是一个函数. // 这里 display-style 是这个连线的显示样式, 先略去; // 调用函数, 看参数相关的3方有 start=node, end=parent, conn=connection. provider(node, parent, connection, ...display-style); // 后面的显示样式部分略. }
这个函数可以分为两部分, 前面获取计算连线所需的3方信息:
1. 开始位置 start = 本节点 node,
2. 结束位置 end = 目标节点 parent,
3. 连线本身 connection, 计算出的 <path d> 数据肯定放这里.
下面以 default provider 为例, 分析该 provider 函数都做了什么:
// 根据上面的分析, 现在知道这三个参数表示 3方对象. default-connect-provider = function(node, parent, connection) { connection.setPathData([ 'M', parent.getLayoutVertexOut(), 'L', node.getLayoutVertexIn() ]); }
从底层开始分析起, parent.getLayoutVertexOut() 看名字是父节点 out 顶点位置. 以前在研究 layout 的时候
碰到过, 但那时没有深入进去看 vertex-out, in 的概念含义. 让我们在浏览器 console 中为 root 等节点调用
一下该函数看看结果, 该结果是一个 Point 点对象. 根据图上观察, root 的 vertex-out 点位于中心, 子节点的这个
vertex-in 点位于左侧中间(如果此节点布局在右侧的话), 即 vertex 位置与布局有关, 那么下次第N次回顾布局的
时候可以更仔细的分析下 vertex 位置是如何计算的, 现在就让我们先假设它们都已经完美地计算出来即可.
这样我们就知道调用 .setPathData() 函数的参数型为 ['M', out-point, 'L', in-point], 查书或网络根据 svg
的知识, 我们知道 'M' 是 MoveTo 的意思, 'L' 是 LineTo 的意思, 则我们知道 'default' 缺省连线是用直线
连接 parent->node.
实验: 随便弄一个脑图, 在浏览器 console 中执行一些的简单的 js:
1. var root_node = minder.root; // 得到脑图的根节点
2. var child_0 = root_node.children[0]; // 得到脑图的第一个字节点. 得到其它子节点也没问题.
3. child_0.getConnect() // 看看该子节点使用的 connect provider 是什么, 结果是 'arc'
4. 为测试 'default' provider 我们造假: child_0.getConnect = function() { return 'default'; }
让其总是放回 'default'.
5. 挪动一下该子节点, 结果如下图:
我们还可以继续造假将 child_0, 或者 1,2,3 等子节点的 connect provider 设置为别的实验. 这里就不做了.
下面继续看代码, 位于 src/connect 下的几种 connect provider: arc, arc_tp, bezier, fish-bone-master,
l, poly, under 共 7 种. 大概看看吧, 没那么多时间(闲心).
arc 是圆弧连线, 如图的弧状连线的那些子节点的 connect 就是 'arc'.
// src/connect/arc.js // 这里创建了 svg 的 path marker 对象. var connectMarker = new kity.Marker().pipe(function() { // 初始化这种 marker 对象是一个小的 circle. // 这里需要看 svg 的书细节了解 marker 的概念与实现. ... } // 任务是建立一个 parent->node 的弧形连线. 可按照 node 所在四个象限分别讨论. register-provider 'arc'-> function(node, parent, connection) { var side = left or right 根据 node,parent 相对位置; var start = 计算开始点位置, end = 计算结束点位置, 和 side 有关; var path = ['MoveTo', start, 'ArcTo', ... end]; // 弧线的 path 数据, 格式查 svg 文档. connection.setPathData(path); // 设置 <path> 数据. connection.setMarker(connectMarker); // 为曲线设置小圆点 marker . }
这里设置的 marker 根据图上看是在 path 的结束点. 领导说想动画显示从 start->end 的那个点, 那是不是我们
改改 marker 就能实现?
再以鱼骨图连线 fish-bone-master.js 为例, 上面的 path 大致为:
path-data = ['MoveTo', start-pt, 'horz-line-to?', dxy?, 'LineTo', end-pt];
其它 connect 估计差不多主要是各种不同的计算 path-data 算法, 这里需要细心+测试+时间, 没时间细看的
就先略过了.
最后, 有一个小小灵感, 这里天盘图都是右旋的, 也许我们还能整一个左旋版本的, 仿佛镜像世界来的~