前回はdiv要素を使って棒グラフを作ってみました。もちろん、div要素だけでもある程度のグラフを作ることはできますが、より複雑なグラフィックスを作るときにはSVG(Scalable Vector Graphics)が必要になります。SVGはAdobe Illustratorのようなベクターグラフィックスをwebブラウザ上で描画する仕組みだと考えてください。ここでは、そのSVGをd3で描画する方法を学習します。
1. D3でSVGを記述
SVGの記述はhtmlとよく似ています。例えば、以下のような書き方をします。
<svg width="400" height="100"></svg>
D3では、divを追加するのと同じように、append()を使ってsvgを追加します。
d3.select("body").append("svg");
さらに、その描画範囲の幅と高さを指定します。最終的なコードは以下です。
<meta charset="utf-8">
<title>08</title>
<script type="text/javascript" src="https://d3js.org/d3.v5.min.js"></script>
<style type="text/css">
/*CSSはない*/
</style>
<script type="text/javascript">
//SVG要素を作成
var svg = d3.select("body")
.append("svg")
.attr("width", 400)
.attr("height", 100);
</script>
ブラウザで表示すると、見た目は何もありませんが、chromeデベロッパーツールのElements上でsvgのタグをマウスオーバーすると、400x100pixelの領域が生成されていることがわかります。
2. 四角形を描画
まず、その後のコードで幅と高さは頻繁に利用するので、変数にしておきましょう。
var w = 400;
var h = 100;
var dataset = [10, 20, 30, 40, 50];
//SVG要素を作成
var svg = d3.select("body")
.append("svg")
.attr("width", w)
.attr("height", h);
次に、rectを作成します。
//SVG要素を作成
var rects = svg.selectAll("rect") //後で作るrectの参照先を作成
.data(dataset)
.enter()
.append("rect"); //rectを作成
さらに、rectに属性(.attr()と表記する。attributeの略)を与えます。x座標、y座標、幅、高さを指定します。
rects.attr("x", function(d, i) {
return (i * 50) + 20; //オブジェクトのx座標。20は余白
})
.attr("y", 0) //y座標
.attr("width", function(d) { //オブジェクトの幅
return d;
})
.attr("height", function(d) { //オブジェクトの高さ
return d;
});
完成したJavaScriptのコードは以下になります。
var w = 400;
var h = 100;
var dataset = [10, 20, 30, 40, 50];
//SVG要素を作成
var svg = d3.select("body")
.append("svg")
.attr("width", w)
.attr("height", h);
//SVG要素を作成
var rects = svg.selectAll("rect") //後で作るrectの参照先を作成
.data(dataset)
.enter()
.append("rect"); //rectを作成
rects.attr("x", function(d, i) {
return (i * 50) + 20; //オブジェクトのx座標。20は余白
})
.attr("y", 0) //y座標
.attr("width", function(d) { //オブジェクトの幅
return d;
})
.attr("height", function(d) { //オブジェクトの高さ
return d;
});
rectの属性はSVGのルールに従って描画されています。 ですので、他の属性を追加したい場合には、まずはSVGの仕様をインターネットで検索してみてください。多くの資料があります。
https://triple-underscore.github.io/SVG11/shapes.html#RectElement
rectの属性にはrx, ryがあります。これは四角形の角を丸くする属性ですので、heightの後にこの属性を追加してみましょう。
rects.attr("x", function(d, i) {
return (i * 50) + 20; //オブジェクトのx座標。20は余白
})
.attr("y", 0) //y座標
.attr("width", function(d) { //オブジェクトの幅
return d;
})
.attr("height", function(d) { //オブジェクトの高さ
return d;
})
.attr("rx", function(d) { //オブジェクトの高さ
return d/5;
})
.attr("ry", function(d) { //オブジェクトの高さ
return d/5;
});
結果は以下になります。
3. 色をつける
以下のコードを追加してみましょう。
.attr("fill", "pink")
.attr("stroke", "blue")
.attr("stroke-width", "3");
また、このままだと四角形の上の部分が表示されないので、四角形のy座標を10ピクセル下に下げます。
rects.attr("x", function(d, i) {
return (i * 50) + 20; //オブジェクトのx座標。20は余白
})
.attr("y", 10) //y座標
.attr("width", function(d) { //オブジェクトの幅
return d;
})
.attr("height", function(d) { //オブジェクトの高さ
return d;
})
.attr("rx", function(d) { //オブジェクトの高さ
return d/5;
})
.attr("ry", function(d) { //オブジェクトの高さ
return d/5;
})
.attr("fill", "pink")
.attr("stroke", "blue")
.attr("stroke-width", "3");
結果は以下になります。
4. 棒グラフのバー同士の余白を決める
先ほどdiv要素を使って描画した棒グラフを、SVGで描画してみましょう。JavaScriptの部分を以下のように記述します。
var w = 400;
var h = 250;
var dataset = [ 10, 5, 3, 20, 4, 8, 11, 1, 6, 9 ];
//SVG要素を作成
var svg = d3.select("body")
.append("svg")
.attr("width", w)
.attr("height", h);
svg.selectAll("rect")
.data(dataset)
.enter()
.append("rect")
.attr("x", function(d, i) {
return i * 27; //バーの幅25+余白2ピクセル
})
.attr("y", 0)
.attr("width", 25)
.attr("height", 200);
ポイントは、バーのx座標を決める16~18行目のコードです。
.attr("x", function(d, i) {
return i * 27; //バーの幅25+余白2ピクセル
})
ここで、iには配列のインデックス番号が入ります。つまり、全部で10個要素があるので、0, 1, 2, 3, 4, 5, 6, 7, 8, 9が順番に入ります。
このことによって、27ピクセルずつ右にバーが形成されるのです。それではこのコードをもう少し柔軟にしてみます。x座標を以下のように書き換えてみましょう。
.attr("x", function(d, i) {
return i * (w / dataset.length);
})
すると、画面の横幅wが400なので、その幅に合わせて均等配置してくれます。「dataset.length」は、配列の要素数なので、この場合10になります。
次に、バーの横幅が、画面の幅に合わせて決まるように変更してみます。まず、2ピクセルのspaceという変数を追加します。
var w = 400;
var h = 250;
var space = 2;
次に、バーの幅を変更します。バーの幅は400(画面の幅)÷10(datasetの要素数)-2(余白)という計算になります。
.attr("width", w / dataset.length - space)
この式を用いることによって、datasetの要素数が増えたり画面の幅が変わったりしても、必ず余白が2ピクセルのバーを作成してくれます。
最終的なコードは以下です。
var w = 400;
var h = 250;
var space = 2;
var dataset = [ 10, 5, 3, 20, 4, 8, 11, 1, 6, 9 ];
//SVG要素を作成
var svg = d3.select("body")
.append("svg")
.attr("width", w)
.attr("height", h);
svg.selectAll("rect")
.data(dataset)
.enter()
.append("rect")
.attr("x", function(d, i) {
return i * (w / dataset.length);
})
.attr("y", 0)
.attr("width", w / dataset.length - space)
.attr("height", 200);
5. バーの高さを変える
それでは、一般的な棒グラフに近づけるために、配列の数値をバーの高さに反映させてみましょう。
.attr("height", function(d) {
return d;
})
無事に高さに反映されました。
このままだと差がわかりにくいので、dの値を10倍にしてみます。
.attr("height", function(d) {
return d * 10;
})
だんだん棒グラフらしくなってきました。
ですが、一般的な棒グラフは下から伸びているので、上下反転させたいところです。以下のように、画面の高さhからd*10を引いてあげます。
.attr("y", function(d) {
return h - d * 10;
})
棒グラフが完成しました。
バーに色を適用してみましょう。attr(height, ….)の後に、fillを追加します。
.attr("fill", function(d) {
//dの最大値が20なので、青の最大値が240になる
return "rgb(0, 0, " + (d * 12) + ")";
});
最終的なコードは以下です。
var w = 400;
var h = 250;
var space = 2;
var dataset = [ 10, 5, 3, 20, 4, 8, 11, 1, 6, 9 ];
//SVG要素を作成
var svg = d3.select("body")
.append("svg")
.attr("width", w)
.attr("height", h);
svg.selectAll("rect")
.data(dataset)
.enter()
.append("rect")
.attr("x", function(d, i) {
return i * (w / dataset.length);
})
.attr("y", function(d) {
return h - d * 10;
})
.attr("width", w / dataset.length - space)
.attr("height", function(d) {
return d * 10;
})
.attr("fill", function(d) {
//dの最大値が20なので、青の最大値が240になる
return "rgb(0, 0, " + (d * 12) + ")";
});
6. ラベルを表示
かなり棒グラフになってきましたが、数値も表示したほうが、見る人にとっては優しいグラフだと言えます。ですので、数値も表示してみましょう。rectとは別に以下のコードを追加します。
var barW = w / dataset.length - space; //バーの幅
svg.selectAll("text") //配列の数分だけtextを用意する
.data(dataset)
.enter()
.append("text") //textを追加
.text(function(d) {
return d; //textを表示
})
さらに、textの位置を指定します。
.attr("x", function(d, i) {
//barW / 2は、バーの幅の中央を計算する
return i * (w / dataset.length) + barW / 2;
})
.attr("y", function(d) {
return h - (d * 10) + 15;
})
最後に、textの属性を指定します。
.attr("text-anchor", "middle") //テキストを中央揃え
.attr("font-family", "sans-serif") //サンセリフ
.attr("font-size", "11px")
.attr("fill", "white");
最終的なコードは以下です。
var w = 400;
var h = 250;
var space = 2;
var dataset = [ 10, 5, 3, 20, 4, 8, 11, 1, 6, 9 ];
//SVG要素を作成
var svg = d3.select("body")
.append("svg")
.attr("width", w)
.attr("height", h);
svg.selectAll("rect")
.data(dataset)
.enter()
.append("rect")
.attr("x", function(d, i) {
return i * (w / dataset.length);
})
.attr("y", function(d) {
return h - d * 10;
})
.attr("width", w / dataset.length - space)
.attr("height", function(d) {
return d * 10;
})
.attr("fill", function(d) {
//dの最大値が20なので、青の最大値が240になる
return "rgb(0, 0, " + (d * 12) + ")";
});
var barW = w / dataset.length - space; //バーの幅
svg.selectAll("text") //配列の数分だけtextを用意する
.data(dataset)
.enter()
.append("text") //textを追加
.text(function(d) {
return d; //textを表示
})
.attr("x", function(d, i) {
//barW / 2は、バーの幅の中央を計算する
return i * (w / dataset.length) + barW / 2;
})
.attr("y", function(d) {
return h - (d * 10) + 15;
})
.attr("text-anchor", "middle") //テキストを中央揃え
.attr("font-family", "sans-serif") //サンセリフ
.attr("font-size", "11px")
.attr("fill", "white");