勉強会情報
- 2015/12/04(金) 19:30 〜 20:30
- http://fukuoka-cam-study.connpass.com/event/23438/
- JavaScriptでグラフ作ろう勉強会
- コードを書いてD3.jsでのグラフ作成を学ぶ勉強会
内容
勉強会の環境
ブラウザはChromeで説明します
Runstantを使ってブラウザのみで学習する
http://goo.gl/s2tfqaライブラリをダウンロードしてローカル環境で試す
https://d3js.org/
例)d3.zipを解凍したフォルダにhtmlとjsファイルを置く
<!DOCTYPE html>
<html lang="jp">
<head>
<meta charset="utf-8">
<title>D3 Test</title>
<script type="text/javascript" src="d3.js"></script>
<script type="text/javascript" src="index.js"></script>
<style>
</style>
</head>
<body>
<h1>Hello, D3</h1>
<div class="graph"></div>
</body>
</html>
window.onload = function() {
updateGraph();
};
function updateGraph () {
// CSSセレクタで要素を選択します
// 要素は配列[]に格納されます
// 配列[]にD3.jsのメソッドをつけて様々な動きが出来るようになります。
var graphPane = d3.select(".graph");
console.log(graphPane);
}
CSSセレクタで要素を取得
source: http://goo.gl/L7fOW7
function updateGraph () {
var graphPane = d3.select(".graph");
}
- CSSセレクタで要素を選択します
- 要素は配列[]に格納されます
- 配列[]にD3.jsのメソッドをつけて様々な動きが出来るようになります。
mbostock/d3 Selections
https://github.com/mbostock/d3/wiki/Selections
☆課題☆コンソールにログを出してみましょう
console.log(graphPane);
result: http://goo.gl/45rY1X
グラフデータと要素を紐づけ
source: http://goo.gl/PH55Ux
- div要素は存在しないので空配列[]を作成し空配列[]にD3.jsのメソッドを付ける
- データと紐づくと enter と exit という状態が増える
window.onload = function() {
updateGraph([200, 150, 300]);
};
function updateGraph (dataList) {
var graphPane = d3.select(".graph");
var divBindList = graphPane.selectAll("div").data(dataList);
}
☆課題☆ .data(dataList) を追加してください
result: http://goo.gl/lFzkEJ
Enter(データが要素より多い)、Exit(データが要素より少ない)
source: http://goo.gl/hu6AJx
function updateGraph (dataList) {
var graphPane = d3.select(".graph");
var divBindList = graphPane.selectAll("div").data(dataList);
// enterは表示要素よりもデータが多いとデータが設定される
var enter = divBindList.enter();
console.log("enter=", enter);
// exitは表示要素よりデータが少ないとデータが設定される
var exit = divBindList.exit();
console.log("exit=", exit);
}
※初回動作時はグラフ要素は一つもないので全てenter側に登録されます
☆課題☆console.logを追加して enterとexitの中をみてみましょう
result: http://goo.gl/1zBeY4
d3.js - 三つの小円 [要素の生成]
http://ja.d3js.node.ws/document/tutorial/circle.html
Enter状態のデータを追加する
source: http://goo.gl/duCqyb
function updateGraph (dataList) {
var graphPane = d3.select(".graph");
var divBindList = graphPane.selectAll("div").data(dataList);
// データに対応する要素が足りないのでdiv追加
divBindList
.enter()
.append("div")
.classed("bar", true);
}
- append で要素の追加
- classed で追加した要素の CSSのクラス名を付与
.bar { border: solid 1px; width: 10px; height: 10px; }
☆課題☆ .append("div").classed("bar", true); を追加しましょう
result: http://goo.gl/FPv6GT
Exit状態を作ってみる
source: http://goo.gl/gu8dzS
window.onload = function() {
updateGraph([200, 150, 300]);
setTimeout(function() {
updateGraph([200, 150]);
}, 2000);
};
function updateGraph (dataList) {
var graphPane = d3.select(".graph");
var divBindList = graphPane.selectAll("div").data(dataList);
// データに対する要素が足りない時~
divBindList.enter()
.append("div")
.classed("bar", true);
// 要素に対応するデータが足りない時~
divBindList
.exit()
.classed("red", true);
}
- setTimeoutで 2秒後に一つすくないデータを渡す
- Exit状態の要素に classed で CSSクラス名を付与する
.red { background-color: red; }
☆課題☆exit状態のdivに.redクラスをつけて赤くしましょう
source: http://goo.gl/BhZJAh
データと要素が一致する状態
source: http://goo.gl/1L3Zi4
function updateGraph (dataList) {
var graphPane = d3.select(".graph");
var divBindList = graphPane.selectAll("div").data(dataList);
// データに対する要素が足りない時~
divBindList
.enter()
.append("div")
.classed("bar", true);
// 要素に対応するデータが足りない時~
divBindList
.exit()
.remove();
// 要素とデータが一致するとき~
divBindList
.style("width", function(d, i){
console.log(d, i);
return d + "px";
});
}
- remove()は削除 (補足)
- style()で CSSのwidthを 関数で 設定しています
- 引数d は配列の値、iは配列の添え字
☆課題☆ return d+ "px"; に修正しましょう
result: http://goo.gl/1hZFgK
SVGで描く
source: http://goo.gl/fVBq9S
<svg class="graph" ></svg>
function updateGraph (dataList) {
var graphPane = d3.select(".graph");
var barBindList = graphPane.selectAll("rect").data(dataList);
// データに対する要素が足りない時~
barBindList
.enter()
.append("rect")
.classed("bar", true);
// 要素に対応するデータが足りない時~
barBindList
.exit()
.remove();
// 要素とデータが一致するとき~
barBindList
.attr({
"width": function(d, i){ return d; },
"height": 8,
"x": 0,
"y": function(d, i){ return i*10; }
});
}
- .graphのタグを svgに変更
- div要素をrect要素に変更
- styleで幅指定していたものを attr指定に変更
☆課題☆"y"のy座標計算を i*10 に修正しましょう
result: http://goo.gl/08WtTZ
息抜き(アニメーション)
source: http://goo.gl/BpIAs3
function updateGraph (dataList) {
var graphPane = d3.select(".graph");
var barBindList = graphPane.selectAll("rect").data(dataList);
// データに対する要素が足りない時~
barBindList
.enter()
.append("rect")
.classed("bar", true)
.attr({
"width": 0,
"height": 8,
"x": 0,
"y": function(d, i){ return i*10; }
});
// 要素に対応するデータが足りない時~
barBindList
.exit()
.transition().duration(1000)
.attr("width", 0)
.remove();
// 要素とデータが一致するとき~
barBindList
.transition().duration(1000)
.attr("width", function(d, i){ return d; });
}
- transition() でアニメーションする
- duration()で時間を設定する
- widthの幅を初期値0でappendしてから幅を指定する
☆課題☆transition()を追加しましょう
result: http://goo.gl/9u65oQ
スケール
とても大きな値をいれるとグラフをはみ出す...(ToT)
source: http://goo.gl/XmpmIr
updateGraph([2000, 1500, 3000]);
画面表示幅に合わせ縮小拡大
source: http://goo.gl/LZM4xR
画面表示幅に合わせて縮小拡大を行うX座標のスケール関数を作る
d3.jsな日々 スケーリング:domainとrange
http://d3js.hatenadiary.jp/entry/2013/04/22/074725
var CANVAS_WIDTH = 300;
function updateGraph (dataList) {
var graphPane = d3.select(".graph").attr("width", CANVAS_WIDTH);
var barBindList = graphPane.selectAll("rect").data(dataList);
var xScale = d3.scale.linear();
xScale.domain([0, d3.max(dataList, function(d){ return d;})]);
xScale.range([0, CANVAS_WIDTH]);
barBindList.enter()
.append("rect")
.classed("bar", true)
.attr({
"width": 0,
"height": 8,
"x": 0,
"y": function(d, i){ return i*10; }
});
barBindList.exit()
.transition().duration(1000)
.attr("width", 0)
.remove();
barBindList
.transition().duration(1000)
.attr("width", function(d, i){ return xScale(d); }); // <- POINT!
}
- d3.scale.linear()で線形スケール関数が出来る
- domain が入力値の幅
- rangeが出力値の幅
- スケール関数に値を渡すと画面表示位置座標を計算してくれる
☆課題☆一番下のwidthの計算をreturn xScale(d); に修正する
result: http://goo.gl/ZKtppI
息抜き ちょっとソースを整理
source: http://goo.gl/qgcQE2
var CANVAS_WIDTH = 300;
window.onload = function() {
var barGraph = new BarGraph();
barGraph.updateGraph([2000, 1500, 3000]);
setTimeout(function() {
barGraph.updateGraph([2000, 1500]);
}, 2000);
};
var BarGraph = function (){
this.graphPane = d3.select(".graph").attr("width", CANVAS_WIDTH);
this.xScale = d3.scale.linear();
};
BarGraph.prototype.updateGraph = function (dataList) {
var graphPane = this.graphPane,
xScale = this.xScale;
var barBindList = graphPane.selectAll("rect").data(dataList);
xScale.domain([0, d3.max(dataList, function(d){ return d;})]);
xScale.range([0, CANVAS_WIDTH]);
barBindList.enter()
.append("rect")
.classed("bar", true)
.attr({
"width": 0,
"height": 8,
"x": 0,
"y": function(d, i){ return i*10; }
});
barBindList.exit()
.transition().duration(1000)
.attr("width", 0)
.remove();
barBindList
.transition().duration(1000)
.attr("width", function(d, i){ return xScale(d); });
};
- コンストラクタ関数で初期処理
- インスタンスメソッド(BarGraph#updateGraph)で実行
軸を作る
X座標のスケールを使ってグラフにX座標軸を作ることが出来ます。
source: http://goo.gl/A4U7JE
var BarGraph = function (){
this.graphPane = d3.select(".graph").attr("width", CANVAS_WIDTH);
this.xScale = d3.scale.linear();
// X軸追加!
this.xAxis = d3.svg.axis().scale(this.xScale);
this.graphPane.append("g")
.classed("x-axis-pane", true)
.attr("transform", "translate(0, 100)");
};
BarGraph.prototype.updateGraph = function (dataList) {
var graphPane = this.graphPane,
xScale = this.xScale,
xAxis = this.xAxis;
var barBindList = graphPane.selectAll("rect").data(dataList);
xScale.domain([0, d3.max(dataList, function(d){ return d;})]);
xScale.range([0, CANVAS_WIDTH]);
// X軸を計算して描画
graphPane.select("g.x-axis-pane").call(xAxis);
barBindList.enter()
.append("rect")
.classed("bar", true)
.attr({
"width": 0,
"height": 8,
"x": 0,
"y": function(d, i){ return i*10; }
});
barBindList.exit()
.transition().duration(1000)
.attr("width", 0)
.remove();
barBindList
.transition().duration(1000)
.attr("width", function(d, i){ return xScale(d); });
};
☆課題☆graphPane.select("g.x-axis-pane")の後ろに.call(xAxis);をつける
result: http://goo.gl/h3TRW1
軸が入りきれないので余白をつける
source: http://goo.gl/lxPj6y
グラフはSVGの表示領域よりも小さくする
var CANVAS_WIDTH = 440,
PADDING = 20; // <- 余白
window.onload = function() {
var barGraph = new BarGraph();
barGraph.updateGraph([2000, 1500, 3000]);
setTimeout(function() {
barGraph.updateGraph([2000, 1500]);
}, 2000);
};
var BarGraph = function (){
this.graphPane = d3.select(".graph")
.attr("width", CANVAS_WIDTH)
.append("g")
.attr("transform", "translate(" + PADDING + ", 0)"); // <- グラフ左余白
this.xScale = d3.scale.linear();
// X軸追加!
this.xAxis = d3.svg.axis().scale(this.xScale);
this.graphPane.append("g")
.classed("x-axis-pane", true)
.attr("transform", "translate(0, 100)");
};
BarGraph.prototype.updateGraph = function (dataList) {
var graphPane = this.graphPane,
xScale = this.xScale,
xAxis = this.xAxis,
graphWidth = CANVAS_WIDTH - PADDING*2; // <- 描画幅
var barBindList = graphPane.selectAll("rect").data(dataList);
xScale.domain([0, d3.max(dataList, function(d){ return d;})]);
xScale.range([0, graphWidth]); // <- スケールも余白に合わせて設定
// X軸を計算して描画
graphPane.select("g.x-axis-pane").call(xAxis);
barBindList.enter()
.append("rect")
.classed("bar", true)
.attr({
"width": 0,
"height": 8,
"x": 0,
"y": function(d, i){ return i*10; }
});
barBindList.exit()
.transition().duration(1000)
.attr("width", 0)
.remove();
barBindList
.transition().duration(1000)
.attr("width", function(d, i){ return xScale(d); });
};
☆課題☆PADDING = 0から20に修正しましょう
result: http://goo.gl/epr7ov
色
source: http://goo.gl/yAF3Th
スケールに合わせて色付け
青から値が大きいほど赤にする
// 幅に合わせて色をつける
var colorScale = d3.scale.linear()
.domain([0, d3.max(dataList, function(d){ return d;})])
.range(["blue","red"]);
svgの属性 fillで色を塗る
barBindList
.transition().duration(1000)
.attr("width", function(d, i){ return xScale(d); })
.attr("fill", function(d){ return colorScale(d); });
お任せの色
☆課題☆ colorScaleをお任せのd3.scale.category10に変更する
var colorScale = d3.scale.category10();
result: http://goo.gl/I5fbF0
軸を装飾する
source: http://goo.gl/rFeiKr
スタイルシートを整える
.x-axis-pane path,
.x-axis-pane line {
/*塗りつぶさない*/
fill: none;
/*線は黒*/
stroke: black;
/*細いシャープな線で*/
shape-rendering: crispEdges;
}
.tick line{
/*グラフ中の線は薄めの色*/
opacity: 0.2;
}
描画領域の高さを定数に用意
var CANVAS_WIDTH = 440,
CANVAS_HEIGHT = 150, // <- 縦線を入れる為グラフの高さを設定
PADDING = 20;
SVGサイズに高さを指定
this.graphPane = d3.select(".graph")
.attr("width", CANVAS_WIDTH)
.attr("height", CANVAS_HEIGHT) // <- 描画SVG全体の高さ
.append("g")
.attr("transform", "translate(" + PADDING + ", 0)");
高さ分上に線を引く
this.xAxis = d3.svg.axis().scale(this.xScale)
.orient("bottom") // 文字を下に
.innerTickSize(-CANVAS_HEIGHT) // グラフ内線を上に引く
.outerTickSize(-CANVAS_HEIGHT) // グラフ外線を上に引く
.tickPadding(10); // グラフ軸と文字との距離
☆課題☆ innerTickSizeとouterTickSizeをマイナス値に変更しましょう
result: http://goo.gl/7ncu65
イベント処理
onでイベントを登録できます。
thisに自身の要素が入るのでd3.select(this)で操作が出来ます。
barBindList
.on("mouseover", function(d, i){
d3.select(this).attr("stroke", "black");
})
.on("mouseout", function(d, i){
d3.select(this).attr("stroke", null);
});
source : http://goo.gl/i2Qzb5
☆課題☆ console.logを d3.select(this).attr("stroke", "black");
result : http://goo.gl/RKdkOM
selection.attr(name[, value])
A null value will remove the specified attribute.
https://github.com/mbostock/d3/wiki/Selections#attr