{"pageProps":{"articleData":{"id":"2014-04-30-d3-bezier-curve","title":"D3.jsでベジェ曲線を描く","date":"2014-04-30","body":"\n

d3.svg.lined3.svg.areaの関数を使った以下のような普通の折れ線グラフで、その線をベジェ曲線でカーブさせたい場合について調べたのでメモ。

\n\n
\n
# D3.js 3.4.6\nclass Eg20140430a extends D3Eg\n  line: (xScale, yScale) ->\n    d3.svg.line().x((d) -> xScale(d)).y((d) -> yScale(d))\n\n  render: =>\n    xScale = _xScale(@width, _data)\n    yScale = _yScale(@height, _data)\n    line = @line(xScale, yScale)\n    _renderLine(@getBaseSelection(), _data, line)\n    _renderDots(@getBaseSelection(), _data, xScale, yScale)\n    this\n\n  _data = [28, 48, 40, 9, 84, 27, 100]  # 線グラフのデータ\n\n  _renderLine = (sl, data, line) ->\n    sl.append('path')\n      .attr\n        d: line(data)\n        fill: 'none'\n        stroke: 'black'\n        'stroke-width': 2\n\n  _renderDots = (sl, data, xScale, yScale) ->\n    sl.selectAll('circle')\n      .data(data)\n      .enter()\n      .append('circle')\n      .attr\n        cx: (d) -> xScale(d)\n        cy: (d) -> yScale(d)\n        r: 6\n        fill: 'black'\n        stroke: 'none'\n\n  _xScale = (width, data) ->\n    d3.scale.ordinal().domain(data).rangePoints([0, width], 0, 0)\n\n  _yScale = (height, data) ->\n    d3.scale.linear().domain([0, d3.max(data)]).range([height, 0])
\n

ここで使っているd3.svg.line関数では、座標と座標の間を結ぶ線をinterpolateを使って指定することができ、上記のようにそれを省略するとinterpolate('linear')の直線の補間パターンと同じ状態になる。直線以外にもD3.jsでは何種類かの補間パターンを用意しているので、その中に適したものがあればそれを使うのが楽。

\n

例えばinterpolate('cardinal')を指定すると以下のようになる。デフォルトで用意されている補間パターンの中では、これが今回描きたかった曲線に一番近かったんだけど、あんまり曲線が綺麗じゃないというか、自分が描きたかったものと違った。

\n
\n
line: (xScale, yScale) ->\n  d3.svg.line()\n    .x((d) -> xScale(d))\n    .y((d) -> yScale(d))\n    .interpolate('cardinal')  # interpolateで補間パターンcardinalを指定
\n

調べてみると、このinterpolateに与える引数は、デフォルトで用意されている数種類の文字列以外に、関数で独自の補間パターンを指定できるようなので、自分の描きたかった曲線になるように作ってみたのが以下。

\n
\n
line: (xScale, yScale) ->\n  d3.svg.line()\n    .x((d) -> xScale(d))\n    .y((d) -> yScale(d))\n    .interpolate(_interpolation)  # 独自の補間パターンを指定\n\n# 独自の補間パターンを関数で定義\n_interpolation = (points) ->\n  f = points.shift()\n  d = \"#{f[0]} #{f[1]}\"\n  points.forEach (p) ->\n    m = (p[0] - f[0]) / 2\n    # SVGのpath要素のd属性(3次ベジェ曲線)を文字列として組み立て\n    d += \" C #{f[0] + m} #{f[1]} #{p[0] - m} #{p[1]} #{p[0]} #{p[1]}\"\n    f = p\n  d
\n

さきほどは文字列を渡していたinterpolateの引数に関数を渡すと、その関数の引数(上記ではpoints)に折れ線グラフの座標を含んだ配列が渡されるので、そのデータを元に自分が描きたい曲線を、SVGのpath要素のd属性(3次ベジェ曲線)の文字列を組み立てることで実現している。

\n"}},"__N_SSG":true}