start, end
トランジションの開始時と終了時にアニメーションを指定することができます。on(“start”…を追加してみましょう。次のサンプルは、開始時に円が赤に変化します。
「d3.select(this)」は、選択されたオブジェクトを指しています。
//全ての円を更新
svg.selectAll("circle")
.data(dataset)
.transition()
.duration(1000)
.on("start", function() {
d3.select(this)
.attr("fill", "red");
})
.attr("cx", function(d) {
return xScale(d[0]);
})
.attr("cy", function(d) {
return yScale(d[1]);
})
.attr("r", function(d){
return d[1]/10; //半径;
});
同じように、on(“end”…も追加してみましょう。
//全ての円を更新
svg.selectAll("circle")
.data(dataset)
.transition()
.duration(1000)
.on("start", function() {
d3.select(this)
.attr("fill", "red");
})
.attr("cx", function(d) {
return xScale(d[0]);
})
.attr("cy", function(d) {
return yScale(d[1]);
})
.attr("r", function(d){
return d[1]/10; //半径;
})
.on("end", function() {
d3.select(this)
.attr("fill", "black");
});
次に、このstartとendの際にtransitionを使ってみましょう。ちなみに、startの際には円が移動するアニメーションが始まっているので、startの中でtransitionを設定しても、アニメーションは正しく再生されません。ですので、実質的にstartの中ではtransionは使えません。
//全ての円を更新
svg.selectAll("circle")
.data(dataset)
.transition()
.duration(1000)
.on("start", function() {
d3.select(this)
.attr("fill", "red")
.attr("r", function(d){
return d[1]/10 + 5;
});
})
.attr("cx", function(d) {
return xScale(d[0]);
})
.attr("cy", function(d) {
return yScale(d[1]);
})
.on("end", function() {
d3.select(this)
.transition()
.duration(1000)
.attr("fill", "black")
.attr("r", function(d){
return d[1]/10;
});
});
データの追加
今まではdatasetの要素数は更新されても同じままでした。次の実習は値を追加したり削除したりすることによって変化するグラフの作成です。まずは値を追加してみます。前の棒グラフを使用します。
クリックイベントに以下のコードを追加していきます。まず新しい値をdatasetに追加します。
//最大値を設定
var maxValue = 23;
var newNum = Math.floor(Math.random() * maxValue); //新しい値を設定(0-22)
dataset.push(newNum); //配列に追加
新しくdatasetの要素数が一つ増えたので、xScaleのdomain、yScaleのdomainを設定し直します。
//xScaleのdomainを計算し直す.更新前よりも要素数が1増えている。
xScale.domain(d3.range(dataset.length));
//yScaleのdomainを計算し直す
yScale.domain([0, d3.max(dataset)]);
次に、新しいバーを作成します。ポイントは、bars.enter()によって、新しいバーのみを選択することができるという点です。
var bars = svg.selectAll("rect") //全てのbarを選択し、変数barに入れる
.data(dataset); //更新される
//新しく増えた要素だけにenter()が適用され、新しいbarが作られる
bars.enter()
.append("rect") //新しいrect
.attr("x", w) //新しいbarの位置は最初は画面の外側
.attr("y", function(d) { //yScaleに基づいて新しいbarのy座標を決定
return h - yScale(d);
})
.attr("width", xScale.bandwidth()) //新しいbarの幅
.attr("height", function(d) { //新しいbarの高さ
return yScale(d);
})
.attr("fill", function(d) { //新しいbarの色
return "rgb(0, 0, " + Math.round(d * 10) + ")";
})
そして、既存のバーと一緒にして、更新します。
.merge(bars) //既存のbarと新しいbarを結合
.transition() //トランジション
.duration(500)
.attr("x", function(d, i) { //新しいx座標を設定
return xScale(i);
})
.attr("y", function(d) { //新しいy座標を設定
return h - yScale(d);
})
.attr("width", xScale.bandwidth()) //新しいbarの幅を設定
.attr("height", function(d) { //Set new height value, based on the updated yScale新しいbarの高さを設定
return yScale(d);
});
最後に、ラベルも移動させます。
//既存のラベルを更新
svg.selectAll("text")
.data(dataset)
.transition()
.duration(500)
.text(function(d) {
return d;
})
.attr("x", function(d, i) {
return xScale(i) + xScale.bandwidth() / 2;
})
.attr("y", function(d) {
return h - yScale(d) + 14;
});
ラベルも新しく生成されるように書き直してみましょう。
データの削除
次はデータの削除です。クリックイベントの中を書き換えてください。
//datasetから一つ削除
dataset.shift();
enter()は必要なくなるので、全ての既存のバーを選んでから移動させます。
var bars = svg.selectAll("rect") //全てのbarを選択し、変数barに入れる
.data(dataset); //更新される
bars.transition() //トランジション
.duration(500)
.attr("x", function(d, i) { //新しいx座標を設定
return xScale(i);
})
.attr("y", function(d) { //新しいy座標を設定
return h - yScale(d);
})
.attr("width", xScale.bandwidth()) //新しいbarの幅を設定
.attr("height", function(d) { //Set new height value, based on the updated yScale新しいbarの高さを設定
return yScale(d);
});
そして、exit()で消去するバーを定義します。
//Exitセレクション
bars.exit() //余ったバーを選択
.transition()
.duration(500)
.attr("x", w) //削除するバーを右の画面の外まで移動
.remove(); //削除
keyとvalueの連想配列
さらに複雑なデータの操作を行うために、配列の構造を変えてみます。keyとvalueを設定すると、検索ができるようになり、より自由度の高いデータの操作が可能になります。まずは、前述のコードの配列を改変します。
var dataset = [ { key: 0, value: 9 }, //連想配列を使う
{ key: 1, value: 20 }, //keyとvalueがセットになっている
{ key: 2, value: 7 },
{ key: 3, value: 18 },
{ key: 4, value: 20 },
{ key: 5, value: 5 },
{ key: 6, value: 12 },
{ key: 7, value: 17 },
{ key: 8, value: 3 },
{ key: 9, value: 8 },
{ key: 10, value: 23 },
{ key: 11, value: 20 },
{ key: 12, value: 10 },
{ key: 13, value: 13 },
{ key: 14, value: 7 },
{ key: 15, value: 8 },
{ key: 16, value: 23 },
{ key: 17, value: 10 },
{ key: 18, value: 4 },
{ key: 19, value: 6 } ];
ここでは、keyに数値を指定していますが、文字列を指定することもできます。今までデータにアクセスするためには「d」を使っていましたが、このような連想配列の値にアクアセスするためには、「d.value」と書く必要があります。
yScaleを以下のように変更します。
var yScale = d3.scaleLinear()
.domain([0, d3.max(dataset, function(d) { return d.value; })])
.rangeRound([0, h]);
さらに、バーやラベルの属性内のfunction内のdも、d.valueに変更します。
例:
.attr("y", function(d) {
return h - yScale(d.value);
})
.attr("width", xScale.bandwidth())
.attr("height", function(d) {
return yScale(d.value);
})
.attr("fill", function(d) {
//dの最大値が20なので、青の最大値が240になる
return "rgb(0, 0, " + (d.value * 12) + ")";
});
また、SVG作成前に以下の関数を作成します。
//keyを設定
var key = function(d) {
return d.key;
};
4箇所の.data(dataset)を以下のように書き換えます。
.data(dataset, key)
これは、以下のコードと同じ意味になります。つまり、この時点でデータとSVGのバーがバインドされるのです。
.data(dataset, function(d) {
return d.key;
};)
また、追加と削除が同時にできるように改変してみましょう。pタグを追加します。
<p id="add">足します</p>
<p id="remove">削除します</p>
クリックイベントは、複数用意することになるので、d3.select(“p”)ではなく、d3.selectAll(“p”)になります。
//クリックすることによって新しいデータに更新
d3.selectAll("p")
.on("click", function() {
クリックイベントで、id=”add”を選んだ場合と、id=”remove”を選んだ時の条件分岐を設定します。
//どちらのpが押されたか判定
var paragraphID = d3.select(this).attr("id");
//押されたidによって動作を振り分ける
if (paragraphID == "add") {
//最大値を設定
var minValue = 2;
var maxValue = 23 - minValue;
var newNum = Math.floor(Math.random() * maxValue)+ minValue;
var lastKeyValue = dataset[dataset.length - 1].key;
dataset.push({
key: lastKeyValue + 1,
value: newNum
});
} else {
//datasetから一つ削除
dataset.shift();
}
ラベルもバーと同じように、追加と削除ができるように書き換えてみましょう。