Published
Edited
May 6, 2020
Insert cell
md`# packについて`
Insert cell
d3 = require("d3@5")
Insert cell
height = 550
Insert cell
width = 770
Insert cell
data = {
return {
name: '1',
children: [
{
name: '1-1',
children: [
{ name: '1-1-1', children: [], domain_value: 10 },
{ name: '1-1-2', children: [], domain_value: 20 },
{ name: '1-1-3', children: [], domain_value: 30 }
]
},
{
name: '1-2',
children: [
{ name: '1-2-1', children: [], domain_value: 40 },
{ name: '1-2-2', children: [], domain_value: 50 }
]
},
{
name: '1-3',
children: [],
domain_value: 60
}
]
};
}
Insert cell
md`## packの概要
d3の hierarchy 構造用のapiを使う。流れとしては

1. アプリケーション上の論理データ(↑のdata変数)
2. 階層構造用の論理データ
3. circle packing用のpackデータ(物理データ?)

の順にデータを変換していき、 3. のデータを入力にsvgを描画する。棒グラフや折れ線グラフみたいに物理的な座標への変換オブジェクトを作って、明示的にrectとかの物理的なx,y座標を指定しなくても circle をいい感じに並べてくれるらしい。
`
Insert cell
md`### 2. 階層構造用の論理データ`
Insert cell
md`d3.hierarchy に、name, children を持つルートオブジェクトを渡すとそこに木構造用の各種プロパティを設定してくれる。また、packで描画されるcircleの円の大きさを設定するvalueの設定を sum メソッドで各オブジェクトの値を表すプロパティを返す関数を渡すとvalueプロパティに各ノード単位で計算されて設定される。

また、このhierarchyで生成されるデータはpackに限らず他の階層構造データのベースになるらしい。
`
Insert cell
hierarchyData = d3.hierarchy(data).sum(d => d.domain_value)
Insert cell
md`### 3. circle packing用のpackデータ`
Insert cell
md`
d3.pack() で CirclePackingのサイズ(.size)とcircle間の隙間(.padding)を調整する
この一連のメソッドチェーンで生成されるのは関数で、これに 2. で生成した階層構造データを渡すとさらにCirclePacking独自の サークルの中心座標や半径を計算して入れてくれる
`
Insert cell
pack = d3
.pack()
.size([width, height])
.padding(1)
Insert cell
packRoot = pack(hierarchyData)
Insert cell
md`#### このpackデータに関してこの後出てくる何個かの操作があるのでチェック`
Insert cell
// 木構造になっている各要素を降順に配列にして返す
// 順番はhierarchyDataのsortメソッドで設定した順番になる(sort未指定の場合は value?)
packRoot.descendants()
Insert cell
md`descendantsに並べることで、valueが大きいものほど先頭になり、先に(z order的に下に)大きなcircleを描画することで、その上に子要素のcircleを重ねていくことになるらしい。`
Insert cell
// d3.nest() で任意の
Insert cell
// nest, key, entriesはd3配列のデータ操作系メソッドで詳しくは
// https://observablehq.com/@pocari/d3
// 参照

// 内容的には 各height単位でデータを纏めている。これが直接のsvgのdataにわたす値になる
d3
.nest()
.key(d => d.height) // このheigthは階層構造的な意味で下から何段目か?という意味のheight。なのでrootほど高い値になる
.entries(packRoot.descendants())
Insert cell
md`各サークルの色設定`
Insert cell
// 各サークルは適当なd3のカラーテーマ(ここでは、d3.interpolateMagma)で、heightに応じて暗くする

color = d3.scaleSequential(
// 第二引数で、実際の値の範囲を渡すここでは、高さのmin, maxなのでextentでheightを指定。
// reverseしているのは、ここで使うカラーテーマ(interpolateMagma)が値が小さいほど濃い色になるテーマで、今回はheightの値が小さい(=木構造の下の方=小さいcircle)ほど薄い色にしたかったので、逆にマッピングさせるため。
d3.extent(packRoot.descendants(), d => d.height).reverse(),
d3.interpolateMagma
)
Insert cell
md`## svg描画`
Insert cell
chart = {
const svg = d3.create("svg").attr("viewBox", [0, 0, width, height]);

const node = svg
.selectAll("g")
.data(
d3
.nest()
.key(d => d.height)
.entries(packRoot.descendants())
)
.join("g") // このレベルが各階層レベルのループ(木構造のN段目を描画、みたいな)
.selectAll("g")
.data(d => d.values)
.join("g") // このレベルがN段目の各要素を描画するループ(木構造のN段目の左からM番目の要素を描画、みたいな)
.attr("transform", d => `translate(${d.x + 1},${d.y + 1})`);

// まず円描画
node
.append("circle")
.attr("r", d => d.r)
.attr("fill", d => color(d.height));

// 末端の要素のみname描画
const leaf = node.filter(d => !d.children);
leaf.append("text").text(d => d.data.name);

return svg.node();
}
Insert cell

Purpose-built for displays of data

Observable is your go-to platform for exploring data and creating expressive data visualizations. Use reactive JavaScript notebooks for prototyping and a collaborative canvas for visual data exploration and dashboard creation.
Learn more