技术
d3、d3.pack、d3.hierarchy
展示
https://bl.ocks.org/xunhanliu/e0688dc2ae9167c4c7fc264c0aedcdd1
关于怎么使用,代码中有关键注释。
d3.pack
// https://d3js.org Version 4.8.0. Copyright 2017 Mike Bostock.
层级数据的结构
这是一种典型的树形结构,每个节点包含树的深度和高度,还有“父亲指针”,“儿子指针”。
部分源码
1 var index$2 = function() { 2 var radius = null, 3 dx = 1, 4 dy = 1, 5 padding = constantZero; 6 7 function pack(root) { 8 root.x = dx / 2, root.y = dy / 2; 9 if (radius) { 10 root.eachBefore(radiusLeaf(radius)) //前序遍历,对每个节点的半径进行设置。 11 .eachAfter(packChildren(padding, 0.5)) //后序遍历 12 .eachBefore(translateChild(1)); 13 } else { 14 root.eachBefore(radiusLeaf(defaultRadius$1)) 15 .eachAfter(packChildren(constantZero, 1)) 16 .eachAfter(packChildren(padding, root.r / Math.min(dx, dy)))//确定每个节点的半径 17 .eachBefore(translateChild(Math.min(dx, dy) / (2 * root.r)));//处理每个节点的偏移 18 } 19 return root; 20 } 21 22 pack.radius = function(x) { 23 return arguments.length ? (radius = optional(x), pack) : radius; 24 }; 25 26 pack.size = function(x) { 27 return arguments.length ? (dx = +x[0], dy = +x[1], pack) : [dx, dy]; 28 }; 29 30 pack.padding = function(x) { 31 return arguments.length ? (padding = typeof x === "function" ? x : constant$8(+x), pack) : padding; 32 }; 33 34 return pack; 35 }; 36 37 function radiusLeaf(radius) { 38 return function(node) { 39 if (!node.children) { 40 node.r = Math.max(0, +radius(node) || 0); 41 } 42 }; 43 } 44 45 function packChildren(padding, k) { 46 return function(node) { 47 if (children = node.children) { 48 var children, 49 i, 50 n = children.length, 51 r = padding(node) * k || 0, 52 e; 53 54 if (r) for (i = 0; i < n; ++i) children[i].r += r; 55 e = packEnclose(children); 56 if (r) for (i = 0; i < n; ++i) children[i].r -= r; 57 node.r = e + r; 58 } 59 }; 60 } 61 62 function translateChild(k) { 63 return function(node) { 64 var parent = node.parent; 65 node.r *= k; 66 if (parent) { 67 node.x = parent.x + k * node.x; 68 node.y = parent.y + k * node.y; 69 } 70 }; 71 }
主要逻辑在L10-L12.
- root.eachBefore(radiusLeaf(radius))函数 比较简单,前序遍历,对每个节点的半径进行设置。其中radius是回调函数,参数是node.
- root.eachAfter(packChildren(padding, 0.5)) //后序遍历,在packEnclose函数中设置每个children相对于此节点的位置,并返回此节点的半径大小。这句话完成了半径的设置和节点相对于父节点的相对位置。
- root.eachBefore(translateChild(1)); //由于第二步的位置偏移只是相对于父节点的,这里,递归的把children的偏移加上其父亲节点的偏移。
注意L15行功能是否多余:
本来packEnclose生成的布局是圆形相切布局(圆紧挨着圆,可能不太好看),如何在圆之间加一些空隙,这里作者用了一个小技巧:把计算之前的圆增大一点,经过packEnclose布局后,再把圆的半径给恢复。注:原数据中除叶子节点外都没有半径信息的,如果没有L15的代码的话,冒然增加一个padding,是无效果的,最后的结果是相切布局。L15的结果是把所有节点的半径都设置一下(相切布局)。
其中packChildren中的packEnclose函数是布局的核心代码。此部分代码未使用碰撞的思想(需要迭代,速度就更慢了),直接进行几何的相切布局。
使用方式:
返回结果: 外圆的半径。注意原数据a中每个元素多了一些坐标信息。意思就是,给一组点的大小,经过这个函数后,会得出一些布局信息。