d3 bubble源码分析

技术

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.

  1. root.eachBefore(radiusLeaf(radius))函数 比较简单,前序遍历,对每个节点的半径进行设置。其中radius是回调函数,参数是node.
  2. root.eachAfter(packChildren(padding, 0.5)) //后序遍历,在packEnclose函数中设置每个children相对于此节点的位置,并返回此节点的半径大小。这句话完成了半径的设置和节点相对于父节点的相对位置。
  3. root.eachBefore(translateChild(1)); //由于第二步的位置偏移只是相对于父节点的,这里,递归的把children的偏移加上其父亲节点的偏移。

注意L15行功能是否多余:

  本来packEnclose生成的布局是圆形相切布局(圆紧挨着圆,可能不太好看),如何在圆之间加一些空隙,这里作者用了一个小技巧:把计算之前的圆增大一点,经过packEnclose布局后,再把圆的半径给恢复。注:原数据中除叶子节点外都没有半径信息的,如果没有L15的代码的话,冒然增加一个padding,是无效果的,最后的结果是相切布局。L15的结果是把所有节点的半径都设置一下(相切布局)。

其中packChildren中的packEnclose函数是布局的核心代码。此部分代码未使用碰撞的思想(需要迭代,速度就更慢了),直接进行几何的相切布局。

使用方式:

返回结果: 外圆的半径。注意原数据a中每个元素多了一些坐标信息。意思就是,给一组点的大小,经过这个函数后,会得出一些布局信息。

原文地址:https://www.cnblogs.com/xunhanliu/p/10478562.html