{"pageProps":{"articleData":{"id":"2014-04-30-d3-bezier-curve","title":"D3.jsでベジェ曲線を描く","date":"2014-04-30","body":"\n
d3.svg.line
やd3.svg.area
の関数を使った以下のような普通の折れ線グラフで、その線をベジェ曲線でカーブさせたい場合について調べたのでメモ。
# 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では何種類かの補間パターンを用意しているので、その中に適したものがあればそれを使うのが楽。
例えばinterpolate('cardinal')
を指定すると以下のようになる。デフォルトで用意されている補間パターンの中では、これが今回描きたかった曲線に一番近かったんだけど、あんまり曲線が綺麗じゃないというか、自分が描きたかったものと違った。
line: (xScale, yScale) ->\n d3.svg.line()\n .x((d) -> xScale(d))\n .y((d) -> yScale(d))\n .interpolate('cardinal') # interpolateで補間パターンcardinalを指定
\n調べてみると、このinterpolate
に与える引数は、デフォルトで用意されている数種類の文字列以外に、関数で独自の補間パターンを指定できるようなので、自分の描きたかった曲線になるように作ってみたのが以下。
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次ベジェ曲線)の文字列を組み立てることで実現している。