d3js layout 深刻理解

D3 layouts help you create more advanced visualisations such as treemaps:javascript

D3 layouts帮助您创造更加高级复杂的可视化图表,好比treemaps,packed circles,network graphs:html

Layout is just a JavaScript function that takes your data as input and adds visual variables such as position and size to it.java

一句话: layout就是一个接收你的data做为输入,而通过变换增长相似位置,大小等可视化变量到这个data上去的函数node

好比tree layout就接收一个层次化的结构数据,而对每一个node增长x,y坐标,这样这些节点就造成一个类树的图形:ios

 

D3有不少中hierarchy layouts(处理层次化数据)和chord layout(处理网络信息流向)和一个通用的force layout(物理现象的模拟)。json

注意:你也能够建立你本身的layout.好比你能够建立一个简单的函数,该函数仅仅给源data数组添加位置信息,这样的函数就能够被认为是一个layout数组

Hierarchical layouts

咱们来看下面的层次化数据:网络

{"name":"A1","children":[{"name":"B1","children":[{"name":"C1","value":100},{"name":"C2","value":300},{"name":"C3","value":200}]},{"name":"B2","value":200}]}

在这节里咱们未来看看tree, cluster, treemap, pack和partition layout.注意:treemap, pack和partition被用于layout(转换)层次关系,这种层次关系图表中节点nodes有一个关联的数字值(好比:销售额,人口数量等).数据结构

D3 V4要求层次化输入数据规整后必须以d3.hierarchy对象的形式存在,这一点下面作详细介绍。app

d3.hierarchy

一个d3.hierarchy object 是一种能够表达层次关系的数据结构。该object有一些实现获取好比:ancestor, descendant, leaf nodes信息(用于计算nodes之间的链接path)的预约义方法。对象自己能够经过d3.hierarchy(data)来生成。

var data = {
  "name": "A1",
  "children": [
    {
      "name": "B1",
      "children": [
        {
          "name": "C1",
          "value": 100
        },
        {
          "name": "C2",
          "value": 300
        },
        {
          "name": "C3",
          "value": 200
        }
      ]
    },
    {
      "name": "B2",
      "value": 200
    }
  ]
}

var root = d3.hierarchy(data)

通常状况下你没必要直接对该hierarchy object操做,可是可使用其定义的一些方法,好比:

root.descendants();
root.links()

root.descendants() 返回一个扁平的数组来表达root的子孙后代,而root.links()则返回一个扁平的对象数组来表达全部的父子links

More examples of hierarchy functions

tree layout

tree layout将层级关系中的节点安排成一个tree like arrangement.

咱们经过下面的代码首先来建立一个tree 

var treeLayout = d3.tree();

咱们使用.size()来配置tree的

treeLayout.size([400, 200]);

随后咱们能够调用treeLayout函数,传入咱们的hierarchy object root:

treeLayout(root);

这个函数执行的结果是会将root的每个node都增长上x和y的value

接着,咱们能够:

  • 使用 root.descendants() 来获得全部节点的一个数组
  • 将这个数组data join到circles(或者任何其余的svg element)
  • 使用layout产生的x,y来给每一个节点定位其坐标位置

而且。。。

  • 使用 root.links() 来得到全部links数组
  • 将links数组join到line (or path) elements
  • 使用link的source和target的x,y坐标值来画出每一个line(也就是设置其d属性)

(注意root.links() 每个数组元素都是一个包含了表明link的source和target的对象)

// Nodes
d3.select('svg g.nodes')
  .selectAll('circle.node')
  .data(root.descendants())
  .enter()
  .append('circle')
  .classed('node', true)
  .attr('cx', function(d) {return d.x;})
  .attr('cy', function(d) {return d.y;})
  .attr('r', 4);

// Links
d3.select('svg g.links')
  .selectAll('line.link')
  .data(root.links())
  .enter()
  .append('line')
  .classed('link', true)
  .attr('x1', function(d) {return d.source.x;})
  .attr('y1', function(d) {return d.source.y;})
  .attr('x2', function(d) {return d.target.x;})
  .attr('y2', function(d) {return d.target.y;});

cluster layout

cluster layout 和 tree layout 是很类似的,主要的区别是全部的叶子节点都将放置在相同的深度

<svg width="400" height="220">
    <g transform="translate(5, 5)">
      <g class="links"></g>
      <g class="nodes"></g>
    </g>
  </svg>

 

var data = {
  "name": "A1",
  "children": [
    {
      "name": "B1",
      "children": [
        {
          "name": "C1",
          "value": 100
        },
        {
          "name": "C2",
          "value": 300
        },
        {
          "name": "C3",
          "value": 200
        }
      ]
    },
    {
      "name": "B2",
      "value": 200
    }
  ]
}

var clusterLayout = d3.cluster()
  .size([400, 200])

var root = d3.hierarchy(data)

clusterLayout(root)

// Nodes
d3.select('svg g.nodes')
  .selectAll('circle.node')
  .data(root.descendants())
  .enter()
  .append('circle')
  .classed('node', true)
  .attr('cx', function(d) {return d.x;})
  .attr('cy', function(d) {return d.y;})
  .attr('r', 4);

// Links
d3.select('svg g.links')
  .selectAll('line.link')
  .data(root.links())
  .enter()
  .append('line')
  .classed('link', true)
  .attr('x1', function(d) {return d.source.x;})
  .attr('y1', function(d) {return d.source.y;})
  .attr('x2', function(d) {return d.target.x;})
  .attr('y2', function(d) {return d.target.y;});

treemap layout

Treemaps用于可视化地表明层级关系,每一个item都有一个相关的value

好比,咱们能够将世界人口数据视做层次化的:第一级表明region,第二级表明各个country.一个treemap经过一个矩形表明一个国家(矩形的大小则和其人口数量大小成比例),而最终将每一个region组合在一块儿:

var treemapLayout = d3.treemap();
treemapLayout
  .size([400, 200])
  .paddingOuter(10);

须要注意的是:在咱们应用layout到咱们的 hierarchy 以前,咱们必须先运行 .sum() 在hierarchy上. 这个方法将遍历整颗树,而且在每一个节点上设置.value以表明该节点下的全部子节点的数值之和

var root = d3.hierarchy(data)
root.sum(function(d) {
  return d.value;
});

须要注意的是咱们给.sum()传入了一个accessor function以便指定咱们要对哪一个属性来作sum操做.

咱们如今能够调用treemapLayout函数来对hierarchy object作转换:

treemapLayout(root);

这时layout将添加4个属性x0,x1,y0,y1到每一个节点上去,而这些值将指定treemap中每一个矩形的大小尺寸。

如今咱们就能够将layout的输出转换数据用于可视化了,方法是:将rect和layout data join起来,随后更新其x,y,width,height属性:

d3.select('svg g')
  .selectAll('rect')
  .data(root.descendants())
  .enter()
  .append('rect')
  .attr('x', function(d) { return d.x0; })
  .attr('y', function(d) { return d.y0; })
  .attr('width', function(d) { return d.x1 - d.x0; })
  .attr('height', function(d) { return d.y1 - d.y0; })

若是咱们但愿对每一个矩形增长label,咱们能够join g 元素到这个layout data,而且增长rect和text元素到每一个g元素中。

var nodes = d3.select('svg g')
  .selectAll('g')
  .data(rootNode.descendants())
  .enter()
  .append('g')
  .attr('transform', function(d) {return 'translate(' + [d.x0, d.y0] + ')'})

nodes
  .append('rect')
  .attr('width', function(d) { return d.x1 - d.x0; })
  .attr('height', function(d) { return d.y1 - d.y0; })

nodes
  .append('text')
  .attr('dx', 4)
  .attr('dy', 14)
  .text(function(d) {
    return d.data.name;
  })
完整的代码及效果以下:
  <svg width="420" height="220">
    <g></g>
  </svg>
 
var data = {
  "name": "A1",
  "children": [
    {
      "name": "B1",
      "children": [
        {
          "name": "C1",
          "value": 100
        },
        {
          "name": "C2",
          "value": 300
        },
        {
          "name": "C3",
          "value": 200
        }
      ]
    },
    {
      "name": "B2",
      "value": 200
    }
  ]
};

var treemapLayout = d3.treemap()
  .size([400, 200])
  .paddingOuter(16);

var rootNode = d3.hierarchy(data)

rootNode.sum(function(d) {
  return d.value;
});

treemapLayout(rootNode);

var nodes = d3.select('svg g')
  .selectAll('g')
  .data(rootNode.descendants())
  .enter()
  .append('g')
  .attr('transform', function(d) {return 'translate(' + [d.x0, d.y0] + ')'})

nodes
  .append('rect')
  .attr('width', function(d) { return d.x1 - d.x0; })
  .attr('height', function(d) { return d.y1 - d.y0; })

nodes
  .append('text')
  .attr('dx', 4)
  .attr('dy', 14)
  .text(function(d) {
    return d.data.name;
  })

 

treemap layouts 还能够有如下配置方法:

  • the padding around a node’s children can be set using .paddingOuter
  • the padding between sibling nodes can be set using .paddingInner
  • outer and inner padding can be set at the same time using .padding
  • the outer padding can also be fine tuned using .paddingTop, .paddingBottom, .paddingLeft and .paddingRight.
var treemapLayout = d3.treemap()
  .size([400, 200])
  .paddingTop(20)
  .paddingInner(2);

在上面的代码中, paddingTop is 20 and paddingInner is 2.

Treemaps也可使用不一样的平铺策略,d3js自己提供如下几种内置的策略可供选用 (treemapBinary, treemapDice, treemapSlice, treemapSliceDice, treemapSquarify) 这些策略经过 .tile()来选择:

treemapLayout.tile(d3.treemapDice)

treemapBinary strives for a balance between horizontal and vertical partitions, treemapDice partitions horizontally, treemapSlice partitions vertically, treemapSliceDice alternates between horizontal and vertical partioning and treemapSquarify allows the aspect ratio of the rectangles to be influenced.

The effect of different squarify ratios can be seen here.

pack layout

pack layout和tree layout是相似的,只是咱们使用circles而不是rects来表明一个节点而已. 在下面的例子中,每一个country都由一个圆来代替(其半径的大小对应着相应的population)而全部国家以region来作分组.

var packLayout = d3.pack();
packLayout.size([300, 300]);

treemap同样,咱们必须在hierarchy object root被pack layout调用以前,在该对象上调用 .sum()以便获取汇总数据:

rootNode.sum(function(d) {
  return d.value;
});

packLayout(rootNode);

pack layout 为每一个node增长了x,y和r属性。

如今咱们就能够将layout转换后的结果数据和circle元素join起来从而实现可视化。为root的每个descendant增长一个circle元素。

d3.select('svg g')
  .selectAll('circle')
  .data(rootNode.descendants())
  .enter()
  .append('circle')
  .attr('cx', function(d) { return d.x; })
  .attr('cy', function(d) { return d.y; })
  .attr('r', function(d) { return d.r; })

相似地,也能够经过g元素来组合circle以及对应的labels:

var nodes = d3.select('svg g')
  .selectAll('g')
  .data(rootNode.descendants())
  .enter()
  .append('g')
  .attr('transform', function(d) {return 'translate(' + [d.x, d.y] + ')'})

nodes
  .append('circle')
  .attr('r', function(d) { return d.r; })

nodes
  .append('text')
  .attr('dy', 4)
  .text(function(d) {
    return d.children === undefined ? d.data.name : '';
  })

咱们可使用 .padding()来配置每一个圆之间的padding

packLayout.padding(10)
完整的代码:
  <svg width="320" height="320">
    <g></g>
  </svg>

 

var data = {
  "name": "A1",
  "children": [
    {
      "name": "B1",
      "children": [
        {
          "name": "C1",
          "value": 100
        },
        {
          "name": "C2",
          "value": 300
        },
        {
          "name": "C3",
          "value": 200
        }
      ]
    },
    {
      "name": "B2",
      "value": 200
    }
  ]
};

var packLayout = d3.pack()
  .size([300, 300])
  .padding(10)

var rootNode = d3.hierarchy(data)

rootNode.sum(function(d) {
  return d.value;
});

packLayout(rootNode);

var nodes = d3.select('svg g')
  .selectAll('g')
  .data(rootNode.descendants())
  .enter()
  .append('g')
  .attr('transform', function(d) {return 'translate(' + [d.x, d.y] + ')'})

nodes
  .append('circle')
  .attr('r', function(d) { return d.r; })

nodes
  .append('text')
  .attr('dy', 4)
  .text(function(d) {
    return d.children === undefined ? d.data.name : '';
  })

partition layout

partition layout 将一个矩形区域针对每个层级都细分为一层。每一层针对本层里面每个节点再作细分。D3’s partition layout is created using:

 

var partitionLayout = d3.partition();
partitionLayout.size([400, 200]);
rootNode.sum(function(d) {
  return d.value;
});
partitionLayout(rootNode);

partition layout 将对每一个node增长 x0, x1, y0 and y1 属性.

如今咱们就能够给root的每一个后代添加对应的rect元素而且修改其属性。

d3.select('svg g')
  .selectAll('rect')
  .data(rootNode.descendants())
  .enter()
  .append('rect')
  .attr('x', function(d) { return d.x0; })
  .attr('y', function(d) { return d.y0; })
  .attr('width', function(d) { return d.x1 - d.x0; })
  .attr('height', function(d) { return d.y1 - d.
  partitionLayout.padding(2)

若是咱们但愿修改分区的排列方向,咱们能够在定义rect元素的属性时,swap x0,y0,x1,y1:
.attr('x', function(d) { return d.y0; })
  .attr('y', function(d) { return d.x0; })
  .attr('width', function(d) { return d.y1 - d.y0; })
  .attr('height', function(d) { return d.x1 - d.x0; });

咱们也能够将x映射成一个旋转的角度,而y映射成半径长度,这样建立一个旭日分区:

  <svg width="320" height="320">
    <g transform="translate(160, 160)"></g>
  </svg>

 

var data = {
  "name": "A1",
  "children": [
    {
      "name": "B1",
      "children": [
        {
          "name": "C1",
          "value": 100
        },
        {
          "name": "C2",
          "value": 300
        },
        {
          "name": "C3",
          "value": 200
        }
      ]
    },
    {
      "name": "B2",
      "value": 200
    }
  ]
};

var radius = 150;

var partitionLayout = d3.partition()
  .size([2 * Math.PI, radius]);

var arcGenerator = d3.arc()
  .startAngle(function(d) { return d.x0; })
  .endAngle(function(d) { return d.x1; })
  .innerRadius(function(d) { return d.y0; })
  .outerRadius(function(d) { return d.y1; });

var rootNode = d3.hierarchy(data)

rootNode.sum(function(d) {
  return d.value;
});

partitionLayout(rootNode);

d3.select('svg g')
  .selectAll('path')
  .data(rootNode.descendants())
  .enter()
  .append('path')
  .attr('d', arcGenerator);

 

chord layout

Chord图将一组节点之间的关系连接进行可视化。每一个link都有关联的value. 好比咱们能够列出不一样国家之间的人口迁徙关系图

原始数据是一个矩阵 n x n  (n是元素的个数):

var data = [
  [10, 20, 30],
  [40, 60, 80],
  [100, 200, 300]
];

第一行表明着从第一个item flows到第1,第2,第3个item。

第二行表明着从第二个item flows到第1,第2,第3个item。

咱们经过chord()调用来建立layout

var chordGenerator = d3.chord();

使用.padAngle() (邻近的组之间的padding angle in radians), .sortGroups() (指定组出现的顺序), .sortSubgroups() (在每一个group内部的分类) 而 .sortChords() 定义了z order of the chords.

var chords = chordGenerator(data);

返回chords数组. 数组的每个元素都是一个包含了source 和 target属性的对象. 每个source和target都有着startAngle 和endAngle 属性,这将用于定义每一个chord

随后咱们使用ribbon shape generator(路径生成器,和line, arc generator相对应) 将chord属性转换为path d属性 (see the Shapes chapter for more information on shape generators).

var ribbonGenerator = d3.ribbon().radius(200);

d3.select('g')
  .selectAll('path')
  .data(chords)
  .enter()
  .append('path')
  .attr('d', ribbonGenerator)
  <svg width="500" height="500">
    <g transform="translate(250, 250)"></g>
  </svg>
 
var chordGenerator = d3.chord()
  .sortSubgroups(d3.ascending)
  .padAngle(0.04);

var ribbonGenerator = d3.ribbon().radius(200);

var data = [
  [10, 20, 30],
  [40, 60, 80],
  [100, 200, 300]
];

var chords = chordGenerator(data);

d3.select('g')
  .selectAll('path')
  .data(chords)
  .enter()
  .append('path')
  .attr('d', ribbonGenerator)

相关文章
相关标签/搜索