d3.layout とは? †
layout は、直接グラフィックを描画しない
layout は、生データを中間形式に変換する function
たとえば、pie layout の場合
d3.layout.pie() は、生データの配列を、対応する円グラフの中心角の配列に変換する
中心角の配列を d3.svg.arc() で、SVG のポリゴンに変換する
最終的にできた <path d="M123,456,780,..."/> がブラウザにより画面に表示される
pie layout †
Your borwser is not supporting object tag. Please use one of the latest browsers. Go to /D3JSExam/D3JS-layout-pie.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Pie</title>
<style type="text/css">
text {
font-family: sans-serif;
font-size: 10px;
fill: white;
}
</style>
</head>
<body>
<div id="chart">
</div>
<script type="text/javascript" src="https://d3js.org/d3.v3.min.js" charset="utf-8"></script>
<script type="text/javascript">
//Width and height
var w = 600;
var h = 300;
// グラフ描画領域
var svg = d3.select("#chart").append("svg").attr({width: w, height: h});
// データ
// (順番がでたらめになっているけど、ソートされる)
// (ソート順を変えたければ、d3.layout.pie().sort(function(a,b){...})
// で比較用の function を指定する。d3.ascending ,d3.descending を使える)
var dataset = [
{votes: 71076,name:"柏木 由紀"}, {votes:67339,name:"指原 莉乃"},
{votes: 54483,name:"小嶋 陽菜"}, {votes:50483,name:"板野 友美"},
{votes: 67017,name:"篠田 麻里子"},{votes:65480,name:"高橋 みなみ"},
{votes: 45747,name:"松井 珠理奈"},{votes:42030,name:"松井 玲奈"},
{votes:108837,name:"大島 優子"}, {votes:72574,name:"渡辺 麻友"},
{votes: 40261,name:"宮澤 佐江"}, {votes:27005,name:"河西 智美"}];
var outerRadius = w / 2;
var innerRadius = w / 16;
// {startAngle, endAngle} を与えるとポリゴンを返す function
var arc = d3.svg.arc()
.innerRadius(innerRadius)
.outerRadius(outerRadius);
// データを与えると {startAngle, endAngle} の配列を返す function
// (アニメーションの初期状態)
var pie = d3.layout.pie()
.value(function(d){ return d.votes; })
.startAngle(- Math.PI/2)
.endAngle(- Math.PI/2);
// データを与えると {startAngle, endAngle} の配列を返す function
// (アニメーションの最終状態)
var pie2 = d3.layout.pie()
.value(function(d){ return d.votes; })
.startAngle(- Math.PI/2)
.endAngle(Math.PI/2);
// color(i) で適当な色を返してくれる
var color = d3.scale.category10();
//Set up groups
var arcs = svg.selectAll("g.arc")
.data(pie(dataset))
.enter()
.append("g")
.attr("class", "arc")
.attr("transform", "translate(" + outerRadius + "," + outerRadius + ")");
//Draw arc paths
arcs.append("path")
.attr("fill", function(d, i) {
return color(i);
})
.attr("d", arc)
.style("opacity", 0.8)
.each(function(d) {
// DOMオブジェクト <path> に _current フィールドを追加して、
// 扇形の基本データ {startAngle, endAngle} を格納する
this._current = d;
});
//Labels
arcs.append("text")
.attr("class", "name")
.attr("transform", function(d) {
// console.log(arc.centroid(d));
var center = arc.centroid(d);
return "translate(" + center + ")";
})
.attr("text-anchor", "middle")
.text(function(d, i) {
return dataset[i].name;
})
.each(function(d) { this._current = d; });
//Labels
arcs.append("text")
.attr("class", "vote")
.attr("transform", function(d) {
var center = arc.centroid(d);
center[1] += 12;
return "translate(" + center + ")";
})
.attr("text-anchor", "middle")
.text(function(d, i) {
return dataset[i].votes;
})
.each(function(d) { this._current = d; });
// Animation
var arcs2 = svg.selectAll("g.arc").data(pie2(dataset));
arcs2.select("path")
.transition()
.duration(6000)
.ease("bounce")
.attrTween("d", function(d, i){
// C = (d3.interpolate(A,B))(t) は、
// A,Bの各要素を t (0〜1) で案配した C を返す
// Ci = (Ai * (t-1) + Bi * t)
var iop = d3.interpolate(this._current, d);
// <path> の d 要素に、iop(t) で返される {startAngle, endAngle}
// を SVG のポリゴンに変換したものを返す
return function(t){return arc(iop(t));};
});
arcs2.select("text.name")
.transition()
.duration(6000)
.ease("bounce")
.attrTween("transform", function(d, i){
var iop = d3.interpolate(this._current, d);
return function(t){
var center = arc.centroid(iop(t));
return "translate(" + center + ")"
};
});
arcs2.select("text.vote")
.transition()
.duration(6000)
.ease("bounce")
.attrTween("transform", function(d, i){
var iop = d3.interpolate(this._current, d);
return function(t){
var center = arc.centroid(iop(t));
center[1] += 12;
return "translate(" + center + ")"
};
});
</script>
</body>
</html>
d3.layout.pie() の初期化
// データを与えると {startAngle, endAngle} の配列を返す function
// (アニメーションの初期状態)
var pie = d3.layout.pie()
.value(function(d){ return d.votes; })
.startAngle(- Math.PI/2)
.endAngle(- Math.PI/2);
// データを与えると {startAngle, endAngle} の配列を返す function
// (アニメーションの最終状態)
var pie2 = d3.layout.pie()
.value(function(d){ return d.votes; })
.startAngle(- Math.PI/2)
.endAngle(Math.PI/2);
.value(function(d){}) 円グラフの角度を決めるための値
.startAngle(), .endAngle() 円グラフの開始角と終了角。0 は 12時方向
d3.layout.pie() は、勝手にデータを昇順にソートする。ソート順を指定したければ .sort(function(a,b){...}) で比較 function を指定する
このプログラムでは、アニメーションを行うために 2つの pie function を作っている
pie : 開始角 -π /2 〜 終了角 -π /2
pie2 : 開始角 -π /2 〜 終了角 π /2
最初 pie1 の円グラフ(中心角0°)を描いて、pie2(中心角180°) までアニメーションで変化させる
d3.svg.arc() の初期化
var arc = d3.svg.arc()
.innerRadius(innerRadius)
.outerRadius(outerRadius);
.innerRadius(pixel) : 内側の円弧の半径
.outerRadius(pixel) : 外側の円弧の半径
arc は、d3.layout.pie(dataset) で作った中間形式のデータを、SVG の Path 形式(ポリゴン)に変える
円グラフを描画する
var arcs = svg.selectAll("g.arc")
.data(pie(dataset))
.enter()
.append("g")
.attr("class", "arc")
.attr("transform", "translate(" + outerRadius + "," + outerRadius + ")");
//Draw arc paths
arcs.append("path")
.attr("fill", function(d, i) {
return color(i);
})
.attr("d", arc)
svg.selectAll("g.arc").data(pie(dataset)) で、与えてるデータが生データではなく、中間形式のデータ pie(dataset) であることに注目
円グラフを構成するくさび形 (arc) を SVG の <path> で描く
<path d="..."> の d には、ポリゴンを構成する座標が , 区切りで入る。描くくさび形 (arc) のポリゴンデータは 先ほど作った arc function の返値 (i 番目のくさび形のポリゴンデータは arc( pie(dataset)[i] ) の返値)
グラフにラベルを書く
arcs.append("text")
.attr("class", "name")
.attr("transform", function(d) {
// console.log(arc.centroid(d));
var center = arc.centroid(d);
return "translate(" + center + ")";
})
.attr("text-anchor", "middle")
.text(function(d, i) {
return dataset[i].name;
})
arc.centroid(d) で、d の中心座標を返す
中心座標は、配列 [x,y]。必要があれば中心からずらしてラベルを書くことも可能
SVG の <text> は改行できないので、2 行のテキストを書きたければ、<text> を 2 つ作るしかない
アニメーション(transition)
円グラフのアニメーションは自分で作る必要がある
通常の transition を使うと、元の座標から、行き先の座標までモーフィングさせる
円グラフでは、くさび形の中心角が増減するようなアニメーションにしたい
→ transition().attrTween("attr", function(d,i,a){}) で、独自のアニメーションを定義する必要がある
function(d,i,a) は、function(t) を返す。
function(t) は "attr" の値を返す。
t=0 のとき アニメーション開始時の 属性 attr の値を返す
t=1 のとき アニメーション終了時の 属性 attr の値を返す
円グラフのアニメーションの実装方針
最初に 中心角 0° の円グラフを描く。そして、中心角 180° までアニメーションで大きくする。
くさび形の中心角のデータ (pie(dataset)) を、それを表示する <path> に保持する
アニメーションでは、中心角を変化させ、変化するごとにポリゴンを作成する
実装
// 中心角 0° の円グラフを描く
var arcs = svg.selectAll("g.arc").data(pie(dataset));
arcs.append("path")
.attr("fill", function(d, i) {
return color(i);
})
.attr("d", arc)
.style("opacity", 0.8)
.each(function(d) {
// DOMオブジェクト <path> に _current フィールドを追加して、
// 扇形の基本データ {startAngle, endAngle} を格納する
this._current = d;
});
...
// 中心角 180° までアニメーションで大きくする
var arcs2 = svg.selectAll("g.arc").data(pie2(dataset));
arcs2.select("path")
.transition()
.duration(6000)
.ease("bounce")
.attrTween("d", function(d, i){
// C = (d3.interpolate(A,B))(t) は、
// A,Bの各要素を t (0〜1) で案配した C を返す
// Ci = (Ai * (t-1) + Bi * t)
var iop = d3.interpolate(this._current, d);
// <path> の d 要素に、iop(t) で返される {startAngle, endAngle}
// を SVG のポリゴンに変換したものを返す
return function(t){return arc(iop(t));};
});
何度もアニメーションをするなら transition().each("end", function(d){ this._current = d; }) で、_current を更新しておく必要あり
stack layout †
Your borwser is not supporting object tag. Please use one of the latest browsers. Go to /D3JSExam/D3JS-layout-stack.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Stack</title>
<style type="text/css">
fieldset {
vertical-align: top;
display:inline;
}
text {
font-family: sans-serif;
font-size: 8px;
}
</style>
</head>
<body>
<fieldset>
<legend>zero(default)</legend>
<div id="chart_zero"></div>
</fieldset>
<fieldset>
<legend>widdge</legend>
<div id="chart_wiggle"></div>
</fieldset>
<fieldset>
<legend>expand</legend>
<div id="chart_expand"></div>
</fieldset>
<fieldset>
<legend>silhouette</legend>
<div id="chart_silhouette"></div>
</fieldset>
<script type="text/javascript" src="https://d3js.org/d3.v3.min.js" charset="utf-8"></script>
<script type="text/javascript">
//Width and height
var w = 200;
var h = 100;
var margin = {x:0,y:15};
//Original data
var dataset = [
{
name : "apple",
values : [
{ x: 2012, y: 5 },
{ x: 2013, y: 4 },
{ x: 2014, y: 2 },
{ x: 2015, y: 7 },
{ x: 2016, y: 23 }
]
},
{
name : "orange",
values : [
{ x: 2012, y: 10 },
{ x: 2013, y: 12 },
{ x: 2014, y: 19 },
{ x: 2015, y: 23 },
{ x: 2016, y: 17 }
]
},
{
name : "grape",
values : [
{ x: 2012, y: 22 },
{ x: 2013, y: 28 },
{ x: 2014, y: 32 },
{ x: 2015, y: 35 },
{ x: 2016, y: 43 }
]
}
];
drawChart("zero");
drawChart("wiggle");
drawChart("expand");
drawChart("silhouette");
function drawChart(offset) {
//Set up stack method
var stack = d3.layout.stack()
.offset(offset)
.values(function(d) { return d.values; });
//Data, stacked
stack(dataset);
console.log(dataset);
//Set up scales
var xScale = d3.scale.ordinal()
.domain(dataset[0].values.map(function(d){return d.x;}))
.rangeRoundBands([0, w], 0.05);
var yMax = d3.max(dataset, function(d) {
return d3.max(d.values, function(d) {
return d.y0 + d.y;
});
});
var yScale = d3.scale.linear()
.domain([0, yMax])
.range([margin.y, h - margin.y]);
var xAxis = d3.svg.axis()
.scale(xScale)
.orient("top")
.tickSize(0);
//Easy colors accessible via a 10-step ordinal scale
var colors = d3.scale.category10();
//Create SVG element
var svg = d3.select("body").select("#chart_" + offset)
.append("svg")
.attr("width", w)
.attr("height", h);
// Add a group for each row of data
var groups = svg.selectAll("g")
.data(dataset)
.enter()
.append("g")
.style("fill", function(d, i) {
return colors(i);
});
// Add a rect for each data value
var rects = groups.selectAll("rect")
.data(function(d) { return d.values; })
.enter()
.append("rect")
.attr("x", function(d) {
return xScale(d.x);
})
.attr("y", function(d) {
return yScale(d.y0);
})
.attr("height", function(d) {
return yScale(d.y);
})
.attr("width", xScale.rangeBand());
// Add axis
svg.append("g")
.attr("class", "axis")
.attr("transform", "translate(0," + (margin.y) + ")")
.call(xAxis);
}
</script>
</body>
</html>
stack layout は、データを上書きする
値 y は、指定されたアルゴリズムに則って正規化された値になる
値の正規化アルゴリズム
zero(未指定時: 0始まりの積み上げ)
wiggle(表示上、なるべく同系列を同じ位置に変化少なく)
expand(どの x にたしても合計を 0〜1 に正規化)
silhouette(うまく動いてない? これ*1 みたいになるはず)
累計値 y0 変数が追加される
あとは、棒グラフを描画した時と同じ
x : xScale(x)
y : yScale(y0)
width : xScale.rangeBand()
height : yScale(y)
上下反転するときは
y : yScale(yMax - y0 - y)
sequence †
Your borwser is not supporting object tag. Please use one of the latest browsers. Go to /D3JSExam/D3JS-layout-sequence.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Sequence</title>
<script type="text/javascript" src="https://d3js.org/d3.v3.min.js" charset="utf-8"></script>
<script type="text/javascript" src="temp.js" charset="utf-8"></script>
<style type="text/css">
.axis path,
.axis line {
fill: none;
stroke: black;
shape-rendering: crispEdges;
}
.axis text {
font-family: sans-serif;
font-size: 8px;
}
</style>
</head>
<body>
平均気温の変化 (2000年基準)
<div id="chart"></div>
<script type="text/javascript">
var w = 480;
var h = 240;
var margin = {x:50, y:20};
// min value
var minX = d3.min(dataset, function(d) {
return d.year;
});
// max value
var maxX = d3.max(dataset, function(d) {
return d.year;
});
var scaleX = d3.scale.linear()
.domain([minX, maxX])
.rangeRound([margin.x, w - margin.x])
.clamp(true);
var axisX = d3.svg.axis()
.scale(scaleX)
.orient("bottom");
// min value
var minY = d3.min(dataset, function(d) {
return Math.min(Math.min(d.global, d.n), d.s);
});
// max value
var maxY = d3.max(dataset, function(d) {
return Math.max(Math.max(d.global, d.n), d.s);
});
var scaleY = d3.scale.linear()
.domain([minY, maxY])
.nice()
.rangeRound([h - margin.y, margin.y])
.clamp(true);
var axisY = d3.svg.axis()
.scale(scaleY)
.orient("left");
var svg = d3.select("#chart").append("svg").attr({width: w, height: h}).style("padding","5px");
// 南半球の温度変化
var sLine = d3.svg.line()
.x(function(d) { return scaleX(d.year); })
.y(function(d) { return scaleY(d.s); });
svg.append("path")
.attr("d", sLine(dataset))
.attr("stroke", "pink")
.attr("stroke-width", 1)
.attr("fill", "none");
// 北半球の温度変化
var nLine = d3.svg.line()
.x(function(d) { return scaleX(d.year); })
.y(function(d) { return scaleY(d.n); });
svg.append("path")
.attr("d", nLine(dataset))
.attr("stroke", "cyan")
.attr("stroke-width", 1)
.attr("fill", "none");
// 世界平均の温度変化
var gLine = d3.svg.line()
.x(function(d) { return scaleX(d.year); })
.y(function(d) { return scaleY(d.global); });
svg.append("path")
.attr("d", gLine(dataset))
.attr("stroke", "black")
.attr("stroke-width", 2)
.attr("fill", "none");
// X軸
svg.append("g")
.attr("class", "axis")
.attr("transform", "translate(0," + (h - margin.y) + ")")
.call(axisX);
// Y軸
svg.append("g")
.attr("class", "axis")
.attr("transform", "translate(" + margin.x + ",0)")
.call(axisY);
</script>
</body>
</html>
普通の折れ線グラフ。特に layout は使っていない。
このグラフと histgram や force を合わせると、データを把握しやすくなることを実感できる。
histgram layout †
Your borwser is not supporting object tag. Please use one of the latest browsers. Go to /D3JSExam/D3JS-layout-histgram.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Histgram</title>
<script type="text/javascript" src="https://d3js.org/d3.v3.min.js" charset="utf-8"></script>
<script type="text/javascript" src="temp.js" charset="utf-8"></script>
<style type="text/css">
.axis path,
.axis line {
fill: none;
stroke: black;
shape-rendering: crispEdges;
}
.axis text {
font-family: sans-serif;
font-size: 8px;
}
</style>
</head>
<body>
平均気温の頻度 <span id="from">1891</span>-<span id="to">2012</span> (2000年基準)
<div id="chart"></div>
<button value="1891">1891-1921</button>
<button value="1921">1921-1951</button>
<button value="1951">1951-1981</button>
<button value="1981">1981-2011</button>
<button value="all">ALL</button>
<script type="text/javascript">
var w = 480;
var h = 240;
var margin = {x:50, y:20};
// min value
var minX = d3.min(dataset, function(d) { return d.global; });
// max value
var maxX = d3.max(dataset, function(d) { return d.global; });
// 温度(X軸)設定
var scaleX = d3.scale.linear()
.domain([minX, maxX])
.rangeRound([margin.x, w - margin.x])
.clamp(true);
var axisX = d3.svg.axis()
.scale(scaleX)
.orient("bottom");
// histgram 初期化
var histgram = d3.layout.histogram()
.bins(scaleX.ticks(20))
.value(function(d) { return d.global; })
// 生データを頻度情報に加工
var hDataset = histgram(dataset);
console.log(hDataset);
// 気温(Y軸)設定
var maxY = d3.max(hDataset, function(d){ return d.y; });
var scaleY = d3.scale.linear()
.domain([0, maxY])
.rangeRound([h - margin.y, margin.y])
.nice();
var axisY = d3.svg.axis()
.scale(scaleY)
.orient("left");
// 塗りつぶし色設定
var color = d3.scale.quantile()
.domain([minX, maxX])
.range(['blue','cyan','pink','red']);
var svg = d3.select("#chart").append("svg").attr({width: w, height: h}).style("padding","5px");
// 平均温度の頻度
svg.append("g").selectAll("rect")
.data(hDataset)
.enter()
.append("rect")
.attr({
"x" : function(d){ return scaleX(d.x); },
"y" : function(d){ return scaleY(d.y); },
"width" : 10,
"height" : function(d){ return h - margin.y - scaleY(d.y); },
"fill" : function(d){ return color(d.x); }
});
// X軸
svg.append("g")
.attr("class", "axis")
.attr("transform", "translate(0," + (h - margin.y) + ")")
.call(axisX);
// Y軸
svg.append("g")
.attr("class", "axis")
.attr("transform", "translate(" + margin.x + ",0)")
.call(axisY);
d3.selectAll("button").on("click", function() {
var btn = this.value;
var from = 1891;
var to = 2012;
if ("all" === btn) {
hDataset = histgram(dataset);
} else {
from = Math.max(from, btn);
to = Math.min(to, from + 30);
var subset = dataset.filter(
function(elem){
return (from <= elem.year && elem.year < to);
}
);
hDataset = histgram(subset);
}
d3.select("body").select("#from").text(from);
d3.select("body").select("#to").text(to);
svg.select("g").selectAll("rect").data(hDataset)
.transition()
.duration(1000)
.ease("bounce")
.attr("y", function(d){ return scaleY(d.y); })
.attr("height", function(d){ return h - margin.y - scaleY(d.y); });
});
</script>
</body>
</html>
force layout †
Your borwser is not supporting object tag. Please use one of the latest browsers. Go to /D3JSExam/D3JS-layout-force.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Force</title>
<script type="text/javascript" src="https://d3js.org/d3.v3.min.js" charset="utf-8"></script>
<script type="text/javascript" src="temp.js" charset="utf-8"></script>
<style type="text/css">
text {
font-family: sans-serif;
font-size: 8px;
}
</style>
</head>
<body>
平均気温の関係 1891-2012
<div id="chart"></div>
<script type="text/javascript">
var w = 800;
var h = 600;
// 平均気温順にソートする
dataset = dataset.sort(function(a,b){ return a.global - b.global; });
// 接続の目安のために、年平均気温の差の平均と分散を求める
var avr = 0.0;
for (var srcNo = 0; srcNo < (dataset.length - 1); srcNo++) {
avr += Math.abs(dataset[srcNo].global - dataset[srcNo+1].global);
}
avr = avr / dataset.length;
var sigma = 0.0;
for (var srcNo = 0; srcNo < (dataset.length - 1); srcNo++) {
sigma += Math.abs(Math.abs(dataset[srcNo].global - dataset[srcNo+1].global) - avr);
}
sigma = sigma / dataset.length;
console.log(avr);
console.log(avr + 2*sigma);
// 隣同士と、年平均気温の差が ±3σ 以下のデータ同士を接続する
var edges = new Array();
for (var srcNo = 0; srcNo < (dataset.length - 1); srcNo++) {
edges.push({source: srcNo, target: (srcNo + 1)});
for (var targetNo = srcNo + 2; targetNo < dataset.length; targetNo++) {
if (Math.abs(dataset[srcNo].global - dataset[targetNo].global) < avr + 3*sigma) {
edges.push({source: srcNo, target: targetNo});
} else {
break;
}
}
}
console.log(edges);
//Initialize a default force layout, using the nodes and edges in dataset
var force = d3.layout.force()
.nodes(dataset)
.links(edges)
.size([w, h])
.linkDistance([50]) // 接続されているもの同士の距離は 50
.charge([-100]) // ノード同士の斥力 (デフォルトは -30)
.start();
// min value
var minX = d3.min(dataset, function(d) { return d.global; });
// max value
var maxX = d3.max(dataset, function(d) { return d.global; });
// 塗りつぶし色設定
var colors = d3.scale.quantile()
.domain([minX, maxX])
.range(['blue','cyan','pink','red']);
//Create SVG element
var svg = d3.select("#chart")
.append("svg")
.attr("width", w)
.attr("height", h);
//Create edges as lines
var edges = svg.selectAll("line")
.data(edges)
.enter()
.append("line")
.style("stroke", "lightgray")
.style("stroke-width", 1);
//Create nodes as circles
var nodes = svg.selectAll("circle")
.data(dataset)
.enter()
.append("circle")
.attr("r", 5)
.style("fill", function(d) {
return colors(d.global);
})
.call(force.drag);
//Create nodes as text
var texts = svg.selectAll("text")
.data(dataset)
.enter()
.append("text")
.text(function(d){return d.year;})
.on("mouseover", function(d){ d3.select(this).text(d.global + "℃"); })
.on("mouseout", function(d){ d3.select(this).text(d.year); })
.call(force.drag);
//Every time the simulation "ticks", this will be called
force.on("tick", function() {
edges.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; });
nodes.attr("cx", function(d) { return d.x; })
.attr("cy", function(d) { return d.y; });
texts.attr("x", function(d) { return d.x; })
.attr("y", function(d) { return d.y; });
});
</script>
</body>
</html>
force layout は、クラスタ分析を行う
ノード同士は斥力で離れようとする
関係のあるノードはバネで接続される
何度も計算を繰り返すと、ある所で安定して、関係のあるノードの塊ができる
人間がノードを drag & drop して、見やすいように形を変えられる (drop したところから、再度計算を繰り返す)
例題プログラムでは、年平均気温が近いデータをバネで接続することにした
d3.layout.force の初期化
//Initialize a default force layout, using the nodes and edges in dataset
var force = d3.layout.force()
.nodes(dataset)
.links(edges)
.size([w, h])
.linkDistance([50]) // 接続されているもの同士の距離は 50
.charge([-100]) // ノード同士の斥力 (デフォルトは -30)
.start();
.node(dataset) : 生データ
.links(edges) : 接続
データ形式は、
[{source: 1, target: 2}, {source: 1, target: 3}, {source:2, target:4}]
この場合、
(1)→(2)→(4)
↓
(3)
という接続になる
.size([w,h]) 描画領域
.linkDistance([50]) 接続されているもの同士の距離。50 は絶妙な値
.charge([-100]) ノード同士の斥力。-100 は絶妙な値
.start() 最後に start() でアニメーション(漸化処理)開始
描画方法が特殊
最初に座標が決まるのではなく、何度も位置を変えながら漸化処理を行う
最初に、座標を決めないで円や線を書き、漸化処理で座標を設定する
//Create edges as lines
var edges = svg.selectAll("line")
.data(edges)
.enter()
.append("line")
.style("stroke", "lightgray")
.style("stroke-width", 1);
//Create nodes as circles
var nodes = svg.selectAll("circle")
.data(dataset)
.enter()
.append("circle")
.attr("r", 5)
.style("fill", function(d) {
return colors(d.global);
})
.call(force.drag);
.call(force.drag) で、node を drag & drop できるようになる。
漸化処理は
force.on("tick", function() {
edges.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; });
nodes.attr("cx", function(d) { return d.x; })
.attr("cy", function(d) { return d.y; });
texts.attr("x", function(d) { return d.x; })
.attr("y", function(d) { return d.y; });
});
で定義する。この function が何度も呼ばれる
d.x : ノードの x 座標
d.y : ノードの y 座標
d.source : edgeの接続元 (d.source.x, d.source.y)
d.target : edgeの接続先 (d.target.x, d.target.y)
その他のレイアウト †
HTML