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)

相關文章
相關標籤/搜索