Qiita Teams that are logged in
You are not logged in to any team

Log in to Qiita Team
Community
OrganizationAdvent CalendarQiitadon (β)
Service
Qiita JobsQiita ZineQiita Blog
4
Help us understand the problem. What is going on with this article?
@gyomu_engineer

JavaScriptでグラフ作ろう勉強会資料v0.01

More than 5 years have passed since last update.

勉強会情報

内容

勉強会の環境

  • ブラウザはChromeで説明します

  • Runstantを使ってブラウザのみで学習する
    http://goo.gl/s2tfqa

  • ライブラリをダウンロードしてローカル環境で試す

    https://d3js.org/

例)d3.zipを解凍したフォルダにhtmlとjsファイルを置く

index.html
<!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>
index.js
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のメソッドを付ける
  • データと紐づくと enterexit という状態が増える
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

4
Help us understand the problem. What is going on with this article?
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away

Comments

No comments
Sign up for free and join this conversation.
Sign Up
If you already have a Qiita account Login
4
Help us understand the problem. What is going on with this article?