SVGの描画

      SVGの描画 はコメントを受け付けていません

前回は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");