ソースとデモ
ソース: https://github.com/hnakamur/d3.js-drag-bezier-curves-example
デモ: http://hnakamur.github.io/d3.js-drag-bezier-curves-example/
メモ
d3.jsでのドラッグ
Drag Behavior · mbostock/d3 Wikiからリンクされているサンプルを真似しました。
以下のようにd3.behavior.drag()の結果に .origin()
を呼んでdragイベントのハンドラを設定します。ハンドラ内では d3.event.x, d3.event.y
でドラッグ中の座標が取得できます。
var drag = d3.behavior.drag()
.origin(function(d) { return d; })
.on("drag", dragmove);
function dragmove(d) {
d3.select(this)
.attr("cx", d.x = Math.max(radius, Math.min(width - radius, d3.event.x)))
.attr("cy", d.y = Math.max(radius, Math.min(height - radius, d3.event.y)));
}
あとはこの drag
を call(drag)
で要素に適用すればOKです。SVGの2次ベジェ曲線の制御点をドラッグで動かすサンプル - Qiitaに比べると簡潔でd3.jsは素晴らしいです。
SVGのg要素でレイヤーを実現
SVG Rendering ModelにあるようにSVG 1.1ではDOMの順番に描画される方式になっています。
制御点の円の上にテキストが重なるとドラッグできなかったので、g要素でレイヤーを作って、一番上のレイヤーの中に制御点の円を作るようにしました。
こんな感じで複数のレイヤーを作りました。
var controlLineLayer = svg.append('g').attr('class', 'control-line-layer');
var mainLayer = svg.append('g').attr('class', 'main-layer');
var handleTextLayer = svg.append('g').attr('class', 'handle-text-layer');
var handleLayer = svg.append('g').attr('class', 'handle-layer');
レイヤーを指定して要素を追加するには、svg要素の代わりにレイヤーのg要素を使うのが違うだけで、d3.jsの基本パターンの selectAll().data(データ).enter.append(要素).attr(属性)
というコードにしています。
handleLayer.selectAll('circle.handle.path' + i)
.data(d.points).enter().append('circle')
制御点に連動する要素選択用にCSSクラスを設定
このサンプルでは、1つの制御点をドラッグすると、制御点のIDと座標のテキストと制御点を結ぶ折れ線とベジェ曲線も連動します。対象の要素を selectAll()
で選択しやすいように、ベジェ曲線毎のIDと制御点のIDを連番で発番して、path0
や p1
などのCSSクラスとして設定しました。
id属性ではなくCSSクラスにしたのは、1つのHTML内に複数のsvgを埋め込む場合にidが衝突する危険性があるからです。
webpack + gulpでビルド
RequireJS等はもう古い。WebPackとは?|1 pixel|サイバーエージェント公式クリエイターズブログの06-productionのgulpfile.jsをカスタマイズして使わせていただきました。ありがとうございます!