複数行テキストを垂直・水平方向に揃えるd3.jsプラグインを作ってみた

# はじめに

[ブロック図を書いてみてd3.jsはSVGの作図ツールとしても良いかもと思った - Qiita](http://qiita.com/hnakamur/items/cfb8b0005488a2f34fb0)では、複数行テキストを矩形内で垂直・水平方向に中央揃えするように実装しました。今回は、これを発展させて、垂直方向の上寄せ・中央揃え・下寄せと水平方向の左寄せ・中央揃え・右寄せを選べるように機能拡張して、再利用可能なd3.jsプラグインとして実装してみました。

# d3.jsのプラグインの作り方

## d3.jsの拡張方法

[d3.js - How to make a d3 plugin? - Stack Overflow](http://stackoverflow.com/questions/13983864/how-to-make-a-d3-plugin)にプラグインの作り方についてのQ&Aがあります。[d3.selection のプロトタイプを拡張する例](http://stackoverflow.com/questions/13983864/how-to-make-a-d3-plugin/13984089#13984089)も紹介されていますが、プロトタイプ汚染を避けて [関数として実装](http://stackoverflow.com/questions/13983864/how-to-make-a-d3-plugin/13994916#13994916) して [selections.call](https://github.com/mbostock/d3/wiki/Selections#call)を呼ぶのがスタンダードな方法とのことです。

## 作り方の例

以下の記事を参考にしました。

* [Simple example of reusable d3 plugin.](http://bl.ocks.org/cpbotha/5073718)
* [/chapter: 3B-The-Reusable-Api / Developing a D3.js Edge](http://backstopmedia.booktype.pro/developing-a-d3js-edge/3b-the-reusable-api/)

## プラグインの構造

[Simple example of reusable d3 plugin.](http://bl.ocks.org/cpbotha/5073718)の記事のプラグインの構造は以下の様な感じです。

```js
d3.textBlock = function() {
    // プロパティ定義とデフォルト値設定
    var label = "";

    function my(selection) {
        selection.each(function(d, i) {
            // ここにプラグイン固有の処理を実装
        });
    }

    // labelプロパティのゲッター／セッター
    my.label = function(value) {
        if (!arguments.length) return value;
        label = value;
        return my;
    };

    return my;
}
```

```d3.textBlock``` は[selections.call](https://github.com/mbostock/d3/wiki/Selections#call)に渡す関数として定義するのではなく、そういう関数を返す関数（高階関数）として定義しています。

```my関数``` はクロージャになっていて、labelをプロパティとして定義しデフォルト値とゲッター／セッターを用意しています。

## 使い方の例

```d3.textBlock()``` で[selections.call](https://github.com/mbostock/d3/wiki/Selections#call)に渡す関数を作って、labelプロパティをデフォルト値から変えたい場合は以下のようにセッターを呼び出して変更します。

```js
var tb = d3.textBlock().label(function(d) {return d.label;});

var item = svg.selectAll("rect")
    .data(items)
  .enter()
    .append("svg:g")
    .attr("transform", function(d) { return "translate(" + d.x + "," + d.y + ")"; })
    .call(tb);
```

# 今回作成したプラグイン

* ソース: [hnakamur/d3-multiline-text-plugin](https://github.com/hnakamur/d3-multiline-text-plugin)
* デモ: https://hnakamur.github.io/d3-multiline-text-plugin/
* デモソース: https://github.com/hnakamur/d3-multiline-text-plugin/blob/gh-pages/index.html

下記のスクリーンショットの上側の図(Common example)に対応するJSのコードは以下の通りです。

```js:https://github.com/hnakamur/d3-multiline-text-plugin/blob/6b6108a250b456a22dcc96f92b66281fc2c68a9e/index.html#L19-L54
function createCommonExample() {
  var frameWidth = 680;
  var frameHeight = 84;

  var svg = d3.select("#commonExample").append("svg")
    .attr({
      width: frameWidth,
      height: frameHeight
    });

  var rects = [
    {x:  20, y: 2, w: 200, h: 80, text: 'This is a common\nexample.'},
    {x: 240, y: 2, w: 200, h: 80, text: 'The same alignments\nin these text blocks'},
    {x: 460, y: 2, w: 200, h: 80, text: 'verticalAlign: center\nhorizontaiAlign: left'}
  ];

  var g = svg.selectAll('g.block')
    .data(rects).enter().append('g')
    .attr({
      class: 'block',
      transform: function(d) {
        return "translate(" + d.x + "," + d.y + ")";
      },
    });

  g.append('rect')
    .attr({
      'width': function(d) { return d.w; },
      'height': function(d) { return d.h; },
      'fill': 'none',
      'stroke': '#00C9F9',
      'stroke-width': 1
    });

  g.append('text').call(d3.multilineText().verticalAlign('center').horizontalAlign('left'));
}
```

プラグインのソースでは以下の7つのプロパティを定義していて、上記の例ではそのうちverticalAlignとhorizontalAlignの2つを設定して残り5つはデフォルト値を使っています。

```js:https://github.com/hnakamur/d3-multiline-text-plugin/blob/dba75a9061d33d234b4de5974ac1e9a1bbbb4349/multiline-text.js#L2-L8
  var lineHeight = 1.4;
  var horizontalAlign = 'center'; // 'left', 'center', or 'right'
  var verticalAlign = 'center'; // 'top', 'center', or 'bottom'
  var paddingTop = 10;
  var paddingBottom = 10;
  var paddingLeft = 10;
  var paddingRight = 10;
```

## スクリーンショット

![d3-multiline-text-plugin-examples.png](https://qiita-image-store.s3.amazonaws.com/0/3227/e2303f84-c0c4-9747-612d-0f021d93ebe1.png)

# 感想

[ブロック図を書いてみてd3.jsはSVGの作図ツールとしても良いかもと思った - Qiita](http://qiita.com/hnakamur/items/cfb8b0005488a2f34fb0)のコードと比べると利用側のコードはずいぶんとスッキリしました。

矩形を描く部分などもプラグイン化していけば、利用する側はデータ定義とプラグイン呼び出しだけで良くなっていくので、さらに快適になりそうです。