みなさんは、「D3.js」というJavaScriptライブラリをご存知でしょうか?
D3.jsは「Data-Driven-Documents (データ駆動型ドキュメント)」といコンセプトを元に作られたJavaScriptのライブラリで、SVGを使って様々なデータ (CSV・JSON) などをビジュアライズ (視覚化) して、グラフなどを描画する際によく利用されています。
実際にD3.jsがどのような使われ方をしているかの実績は「Best D3 Websites | Web Design Inspiration」のWebサイトで確認できまので、興味のある方はご覧ください。
また、D3.jsは「JQueryの次に学ぶべきJavaScriptライブラリ」ともよく言われており、JQueryではDOMを扱う事が主体でしたが、D3.jsでは配列や連想配列などのデータ構造を頻繁に扱うので、JavaScripのさらに深い理解にも繋がります。
そこで今回は、JavaScript初心者でも理解できる、D3.js入門のための棒グラフの作り方について詳しく解説していきます。
【D3.jsで出来ること】D3.jsとは何か?

D3.jsとは「Data-Driven-Documents (データ駆動型ドキュメント)」のコンセプトを元に「Mike Bostock さん」が開発された、JavaScriptライブラリです。
D3.jsを使えばHTML・CSS、SVGを駆使して、データをビジュアライズ (視覚化) する際に役立ちます。
例えば「JSON」や「CSV」形式のデータをWebページで視覚的に表示したい時などに使われます。
また、D3.jsの公式サイトは「d3js.org」からアクセスできます。
トップページは英語ですが、こちらのWebページでは日本語のドキュメントも公開されていますので、必要な方は参考にしてみてください。
D3.jsで作れるグラフの種類

D3.jsのExamplesを見てもらえば分かりますが、多種多様なグラフが作れる事が分かりますね。
D3.jsは他のグラフを描画するJavaScriptライブラリとは違い、開発者が細かいところまでチャートを作り込むことができるような実装になっているので、複雑なビジュアライズを実現する事が可能なのです。
D3.jsの使い方とダウンロード方法

D3.jsのダウンロードはこちらのサイトにアクセスして、「d3.zip」を入手しましょう。
ダウンロードが完了したら、「d3.min.js」を開発するプロジェクトに配置してHTMLの「headeタグ」の中に「scriptタグ」でリンクを貼りましょう。
CDNを利用する場合は「cdnjs.com」にアクセスして、以下のようにHTMLに貼り付けましょう。
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/6.5.0/d3.min.js"></script>
D3.jsで棒グラフを描画するサンプルの解説

では早速、D3.js入門の第一歩である棒グラフの作成を初めていきましょう。
棒グラフは数あるチャートの種類の中でも、もっともシンプルにデータを表すことができるので、初めてD3.jsを扱う人でも直感的にコードを書くことができます。
まずはプロジェクトにHTMLとJavaScriptを用意して以下のように記述しましょう。
<div class="simple-chart"></div>
//グラフに反映するデータ
const dataset_01 = [
{
label: 'A',
value: 20
},
{
label: 'B',
value: 32
},
{
label: 'C',
value: 18
},
{
label: 'D',
value: 25
},
{
label: 'E',
value: 35
},
{
label: 'F',
value: 40
},
{
label: 'G',
value: 55
},
{
label: 'H',
value: 73
},
{
label: 'I',
value: 81
},
{
label: 'J',
value: 64
},
{
label: 'K',
value: 80
},
{
label: 'L',
value: 53
},
{
label: 'M',
value: 90
},
{
label: 'N',
value: 123
},
{
label: 'O',
value: 123
},
{
label: 'P',
value: 81
},
{
label: 'Q',
value: 64
},
{
label: 'R',
value: 80
},
{
label: 'S',
value: 53
},
{
label: 'T',
value: 90
},
{
label: 'U',
value: 102
},
{
label: 'V',
value: 123
},
{
label: 'W',
value: 133
},
{
label: 'X',
value: 163
},
{
label: 'Z',
value: 154
}
];
//グラフを描画する領域(svg)
const svg = {
padding_top: 15,
padding_right: 10,
padding_bottom: 25,
padding_left: 35,
width: 500,
height: 250,
bg_color: '#202020'
};
//グラフに使う四角形(rect)
const rect = {
padding: 0.1,
fill: '#00ffff',
hover_fill: '#ffff00'
};
let x_scale = function(dataset) { //グラフを描画領域(X軸方向)に均等に配置するための関数
return d3.scaleBand().domain(dataset.map(function(data) {
return data.label;
})).range([svg.padding_left, svg.width - svg.padding_right]).padding(rect.padding);
};
let y_scale = function(dataset) { //グラフを描画領域(Y軸方向)からはみ出さないようにする関数
return d3.scaleLinear().domain([0, d3.max(dataset, function(data) {
return data.value;
})]).nice().range([svg.height - svg.padding_bottom, svg.padding_top]);
};
//svg要素の作成と属性を決める
let simple_chart_svg = d3.selectAll('.simple-chart').append('svg');
simple_chart_svg.attr('viewBox', [0, 0, svg.width, svg.height]).style('background-color', svg.bg_color);
//svg要素の中にグループを作成
let simple_chart_group = simple_chart_svg.append('g').attr('class', 'rect_group');
//グループの中でデータを元にグラフを描画
let simple_chart_rect = simple_chart_group.selectAll('rect').data(dataset_01).enter().append('rect');
simple_chart_rect.attr('width', function(d, i) { //グラフの横幅
let bandwidth = x_scale(dataset_01).bandwidth();
return bandwidth;
}).attr('height', function(d, i) { //グラフの縦幅
let rect_y_scale = y_scale(dataset_01);
let rect_height = rect_y_scale(0) - rect_y_scale(d.value);
return rect_height;
}).attr('x', function(d, i) { //グラフをX軸方向に描画する座標
let rect_x_scale = x_scale(dataset_01);
return rect_x_scale(d.label);
}).attr('y', function(d, i) { //グラフをY軸方向に描画する座標
let rect_y_scale = y_scale(dataset_01);
let rect_height = rect_y_scale(0) - rect_y_scale(d.value);
return svg.height - rect_height - svg.padding_bottom;
}).attr('fill', rect.fill);
//X軸を作成
let x_axis = simple_chart_svg.append('g').attr('class', 'x_axis').style('transform', `translateY(${svg.height - svg.padding_bottom}px)`).attr('color', '#fff').call(d3.axisBottom(x_scale(dataset_01)));
//Y軸を作成
let y_axis = simple_chart_svg.append('g').attr('class', 'y_axis').style('transform', `translateX(${svg.padding_left}px)`).attr('color', '#fff').call(d3.axisLeft(y_scale(dataset_01)));
SVGの作成とサイズを決める
const svg = {
padding_top: 15,
padding_right: 10,
padding_bottom: 25,
padding_left: 35,
width: 500,
height: 250,
bg_color: '#202020'
};const rect = {
padding: 0.1,
fill: '#00ffff',
hover_fill: '#ffff00'
};SVGを作成する前に内側の余白と横幅や縦幅など属性に設定する値を定義しておきます。
この時に、rect要素を作成する時の初期値も設定しておきます。
let simple_chart_svg = d3.selectAll('.simple-chart').append('svg');
simple_chart_svg.attr('viewBox', [0, 0, svg.width, svg.height]).style('background-color', svg.bg_color);「d3.selectAllメソッド」の引数にクラス名を記述して、選択した要素の中にSVGを作成します。
作成したSVG要素に先ほど定義した定数の値を属性に設定していきます。
また、D3.jsでメソッドを実行する際は、JQueryのようにメソッド同士を繋げて実行する「メソッドチェーン」を使うのが便利です。
データを正規化するための関数を用意する
let x_scale = function(dataset) {
return d3.scaleBand().domain(dataset.map(function(data) {
return data.label;
})).range([svg.padding_left, svg.width - svg.padding_right]).padding(rect.padding);
};「x_scale関数」は引数に配列を受け取り、SVGの左のpadding (35) の位置から「SVGの横幅 (500) - 右のpadding (10)」の範囲におさめて、各データの間隔を指定した数値分開けるようにしています。
これによって、どんな本数の棒グラフを作成しても綺麗にSVGの横幅にピッタリおさまってくれます。
let y_scale = function(dataset) {
return d3.scaleLinear().domain([0, d3.max(dataset, function(data) {
return data.value;
})]).nice().range([svg.height - svg.padding_bottom, svg.padding_top]);
};「y_scale関数」は「x_scale関数」と同様の引数をとり、値が「0」の場合は「SVGの縦幅 (250) - 下からの余白 (25)」の値を返し、データの中で一番高い値は上からの余白 (15)を返すことによって、グラフの値がSVGの縦幅を超えたとしても正規化しておさめてくれます。
データと図形を紐付けしてグラフを作成 (g・rect)
let simple_chart_group = simple_chart_svg.append('g').attr('class', 'rect_group');
let simple_chart_rect = simple_chart_group.selectAll('rect').data(dataset_01).enter().append('rect');先ほど用意したJSON形式の「dataset_01」変数の値と図形との紐付けを行い、グラフの描画します。
まずは、図形をグループ化するために「g要素」の作成を行い、その中にデータの数だけ「rect要素」を配置していきます。
simple_chart_rect.attr('width', function(d, i) {
let bandwidth = x_scale(dataset_01).bandwidth();
return bandwidth;
}).attr('height', function(d, i) {
let rect_y_scale = y_scale(dataset_01);
let rect_height = rect_y_scale(0) - rect_y_scale(d.value);
return rect_height;
}).attr('x', function(d, i) {
let rect_x_scale = x_scale(dataset_01);
return rect_x_scale(d.label);
}).attr('y', function(d, i) {
let rect_y_scale = y_scale(dataset_01);
let rect_height = rect_y_scale(0) - rect_y_scale(d.value);
return svg.height - rect_height - svg.padding_bottom;
}).attr('fill', rect.fill);作成した「rect要素」に横幅、縦幅、座標を「attrメソッド」で設定していきます。
SVGの座標上にグラフを敷き詰めるために、先ほど作成した「x_scale関数」や「y_scale関数」を利用しています。
X軸・Y軸の作成 (axis)
let x_axis = simple_chart_svg.append('g').attr('class', 'x_axis').style('transform', `translateY(${svg.height - svg.padding_bottom}px)`).attr('color', '#fff').call(d3.axisBottom(x_scale(dataset_01)));
let y_axis = simple_chart_svg.append('g').attr('class', 'y_axis').style('transform', `translateX(${svg.padding_left}px)`).attr('color', '#fff').call(d3.axisLeft(y_scale(dataset_01)));グラフを作成したら、データを見やすくするために軸を作ります。
軸の作成は、D3.jsのcallメソッドを使い正規化の関数と組み合わせて、SVGの中に均等に配置できるよう設定します。
D3.jsのグラフをカスタマイズする方法

シンプルなグラフでは物足りないかと思いますので、ここではD3.jsで作成したチャートのカスタマイズ方法について解説していきます。
カスタマイズ方法を知っていれば、データをユーザーに分かりやすく見せることがでますし、デザインの勉強にもなるので、色々な手法に興味がある方はやってみましょう。
具体的には、グラフの背景色の変更方法やイベントやアニメーションに対応させる方法について解説していきます。
背景色の変更方法
d3.select('.simple-chart svg').style('background-color', '#008888');SVGの背景色を変更するためにはstyleメソッドを使いCSSで色を指定するように値を設定します。
グラフの色を変更する方法
d3.selectAll('.simple-chart svg .rect_group rect').attr('fill', '#00ff55');グラフの色を変えたい場合は、attrメソッドでfill属性にカラーコードを設定することで変更できます。
グラフをデータに応じて更新させる方法
<div class="rect_data_change">
<ul>
<li id="data_01">データ(1)</li>
<li id="data_02">データ(2)</li>
<li id="data_03">データ(3)</li>
</ul>
</div>まずは、データを変更するためのボタンを用意する必要がありますので、li要素でボタンを作りましょう。
const dataset_02 = [
{
label: 'A',
value: 10
},
{
label: 'B',
value: 5
},
{
label: 'C',
value: 10
},
{
label: 'D',
value: 15
},
{
label: 'E',
value: 25
},
{
label: 'F',
value: 34
},
{
label: 'G',
value: 65
},
{
label: 'H',
value: 53
},
{
label: 'I',
value: 41
},
{
label: 'J',
value: 44
},
{
label: 'K',
value: 50
},
{
label: 'L',
value: 53
},
{
label: 'M',
value: 70
},
{
label: 'N',
value: 103
},
{
label: 'O',
value: 143
},
{
label: 'P',
value: 91
},
{
label: 'Q',
value: 54
},
{
label: 'R',
value: 90
},
{
label: 'S',
value: 63
},
{
label: 'T',
value: 100
},
{
label: 'U',
value: 112
},
{
label: 'V',
value: 13
},
{
label: 'W',
value: 43
},
{
label: 'X',
value: 153
},
{
label: 'Z',
value: 44
}
];
const dataset_03 = [
{
label: 'A',
value: 90
},
{
label: 'B',
value: 42
},
{
label: 'C',
value: 28
},
{
label: 'D',
value: 35
},
{
label: 'E',
value: 55
},
{
label: 'F',
value: 20
},
{
label: 'G',
value: 35
},
{
label: 'H',
value: 43
},
{
label: 'I',
value: 51
},
{
label: 'J',
value: 74
},
{
label: 'K',
value: 90
},
{
label: 'L',
value: 43
},
{
label: 'M',
value: 30
},
{
label: 'N',
value: 103
},
{
label: 'O',
value: 113
},
{
label: 'P',
value: 61
},
{
label: 'Q',
value: 84
},
{
label: 'R',
value: 85
},
{
label: 'S',
value: 78
},
{
label: 'T',
value: 102
},
{
label: 'U',
value: 30
},
{
label: 'V',
value: 20
},
{
label: 'W',
value: 13
},
{
label: 'X',
value: 173
},
{
label: 'Z',
value: 185
}
];
d3.selectAll('#rect_data_change ul li').on('click', function() {
let dataset = null;
switch( this.getAttribute('id') ) {
case 'data_01':
dataset = dataset_01;
break;
case 'data_02':
dataset = dataset_02;
break;
case 'data_03':
dataset = dataset_03;
break;
default :
break;
}
d3.selectAll('.simple-chart svg .rect_group rect').remove();
let new_rect = d3.select('.simple-chart svg .rect_group').selectAll('rect').data(dataset).enter().append('rect');
new_rect.attr('width', function(d, i) {
let bandwidth = x_scale(dataset).bandwidth();
return bandwidth;
}).attr('height', function(d, i) {
let rect_y_scale = y_scale(dataset);
let rect_height = rect_y_scale(0) - rect_y_scale(d.value);
return rect_height;
}).attr('x', function(d, i) {
let rect_x_scale = x_scale(dataset);
return rect_x_scale(d.label);
}).attr('y', function(d, i) {
let rect_y_scale = y_scale(dataset);
let rect_height = rect_y_scale(0) - rect_y_scale(d.value);
return svg.height - rect_height - svg.padding_bottom;
}).attr('fill', rect.fill);
});先ほど作成していた「dataset_01」以外に2つ新しいデータを作り、D3.jsのonメソッドでclickイベントを指定してボタンが押されるたびにSVGの中身を削除して再構成するようにしています。
グラフをアニメーションさせる方法
d3.selectAll('#rect_data_change ul li').on('click', function() {
let dataset = null;
let id = d3.select(this).attr('id');
switch(id) {
case 'data_01':
dataset = dataset_01;
break;
case 'data_02':
dataset = dataset_02;
break;
case 'data_03':
dataset = dataset_03;
break;
default :
break;
}
d3.selectAll('.simple-chart svg .rect_group rect').remove();
d3.selectAll('.simple-chart svg .x_axis').remove();
d3.selectAll('.simple-chart svg .y_axis').remove();
let new_rect = d3.select('.simple-chart svg .rect_group').selectAll('rect').data(dataset).enter().append('rect');
new_rect.attr('fill', rect.fill).attr('x', function(d, i) {
let rect_x_scale = x_scale(dataset);
return rect_x_scale(d.label);
}).attr('y', function(d, i) {
let rect_y_scale = y_scale(dataset);
return svg.height - svg.padding_bottom;
}).attr('width', function(d, i) {
let bandwidth = x_scale(dataset).bandwidth();
return bandwidth;
}).attr('height', function(d, i) {
return 0;
}).transition().duration(500).attr('height', function(d, i) {
let rect_y_scale = y_scale(dataset);
let rect_height = rect_y_scale(0) - rect_y_scale(d.value);
return rect_height;
}).attr('y', function(d, i) {
let rect_y_scale = y_scale(dataset);
let rect_height = rect_y_scale(0) - rect_y_scale(d.value);
return svg.height - rect_height - svg.padding_bottom;
});
d3.select('.simple-chart svg').append('g').attr('class', 'x_axis').style('transform', `translateY(${svg.height - svg.padding_bottom}px)`).attr('color', '#fff').call(d3.axisBottom(x_scale(dataset)));
d3.select('.simple-chart svg').append('g').attr('class', 'y_axis').style('transform', `translateX(${svg.padding_left}px)`).attr('color', '#fff').call(d3.axisLeft(y_scale(dataset)));
});グラフをアニメーションさせるためには、D3.jsのtransitionメソッドを使います。
ポイントはtransitionメソッドの後にチェーンされたメソッドがアニメーションされる対象になるので、それよりも前に初期値を決めてから動かすのがオススメです。
グラフをホバーイベントに対応させる方法
let data_text = d3.select('.simple-chart svg').append('g').selectAll('text').data(dataset_01).enter().append('g').append('text');
data_text.text(function(d, i) {
return d.value;
}).attr('x', function(d, i) {
let bandwidth = x_scale(dataset_01).bandwidth();
let rect_x_scale = x_scale(dataset_01);
return rect_x_scale(d.label) + bandwidth / 2;
}).attr('y', function(d, i) {
let rect_y_scale = y_scale(dataset_01);
let rect_height = rect_y_scale(0) - rect_y_scale(d.value);
return svg.height - rect_height - svg.padding_bottom;
}).attr('fill', 'red').attr('font-size', '8px').attr('dominant-baseline', 'text-before-edge').attr('text-anchor', 'middle');まず、グラフをホバーイベントに対応させる前に、text要素を作成して選択してるグラフの数字を分かるようにしておきます。
let recr_event = d3.selectAll('.simple-chart svg .rect_group rect').style('cursor', 'pointer');
recr_event.on('mouseover', function() {
let self = d3.select(this);
self.attr('fill', rect.hover_fill);
});
recr_event.on('mouseout', function() {
let self = d3.select(this);
self.attr('fill', rect.fill);
});グラフをホバーイベントに対応させるにはD3.jsのonメソッドで「mouseover」と「mouseout」の2つを設定する必要があります。
mouseoverした時にグラフの背景色を黄色にして、mouseoutした時に初期値の色に戻すようにしています。
インタラクティブ・データビジュアライゼーション D3.jsによるデータ可視化
| 表紙 | こんな人にオススメ |
|---|---|
|
まとめ
今回は、D3.js入門のための棒グラフの作り方と、カスタマイズ方法について解説してきましたが、いかがでしたでしょうか?
JQueryを使うことに慣れてしまっていた人にとっては、少し癖があって扱いにくく感じたかもしれませんが、何度も使っていればD3.jsの素晴らしさに気づくはずです。
D3.jsを学習することによって、配列の実践的な扱い方や座標の概念について学ぶことができるので、JavaScriptをさらに深く理解したい人にとっては、とてもオススメできるライブラリです。
筆者自身もJavaScriptを理解できない時間が長かったですが、D3.jsを勉強することでJavaScriptを理解できるようになったので、難しくても少しづつコードを書き進めていきましょう。
