D3を書くときの小さいTips

marginの扱いがダルい

div > svg > g としてその中にいろいろ追加していく、というのがベストだと思っていますが、marginの取り扱いが割とだるい。よくあるのが

d3.select('div').appned('svg')
  .attr('width', width + margin * 2)
  .attr('height', height + margin * 2)
  .append('g')
    .attr('width', width).attr('height', height)
    .attr('transform', 'translate('+margin+','+margin+')')

とかだと思うけど、この時点で割とイライラしているのでMargin作った

class Margin
    constructor: (args...)->
      @top = 0
      @right = 0
      @bottom = 0
      @right = 0
      @width = 0
      @height = 0

      if args.length is 1
        @top = @right = @bottom = @left = args[0]
      else if args.length is 2
        @top = @bottom = args[0]
        @right = @left = args[1]
      else if args.length is 3
        @top = args[0]
        @bottom = args[2]
        @right = @left = args[1]
      else if args.length is 4
        [@top, @right, @bottom, @left] = args

      @width = @right + @left
      @height = @top + @bottom
    toString: ()->
      return [@top, @right, @bottom, @left].map((d)-> "#{d}px").join(" ")

これを使うとCSSと同じようにmarginを指定できます。

var margin = new Margin(100, 50, 30);  // 上100、下30、左右50
d3.select('div').appned('svg')
  .attr('width', width + margin.width)
  .attr('height', height + margin.height)
  .append('g')
    .attr('width', width).attr('height', height)
    .attr('transform', 'translate('+margin.left+','+margin.top+')');

attrを無限に書くのがダルい

よくあるのが

circle = svg.append('circle')
  .attr('cx', 10)
  .attr('cy', 10)
  .attr('r', 5)
  .attr('fill', 'grey')
  .attr('stroke', 'blue')
  .attr('stroke-width', 3);

これにstyleも同じようにするのがうんざりする

なので、attrsを作った

attrs = (obj)->
  ()->
    for k, v of obj
      this.attr(k, v)
    this

これを使うと

circle = svg.append('circle').call(attrs({
  cx: 10,
  cy: 10,
  r: 5,
  fill: 'grey',
  stroke: 'blue',
  'stroke-width': 3
}))

オブジェクトで定義できるので楽です。(.attr()って無限に書かなくても済むし、なにより、この設定を変数化してもっておける!わーい