function translate(sel, pos){ sel.attr('transform', function(d){ var posStr = typeof(pos) == 'function' ? pos(d) : pos return 'translate(' + posStr + ')' }) } function ƒ(str){ return function(d){ return str ? d[str] : d } } d3.selection.prototype.appendMany = function(data, name){ return this.selectAll(name).data(data).enter().append(name) } var width = 960, height = 500; var svg = d3.select('html') .append('svg') .attr('width', width) .attr('height', height) var g = svg.append('g') var path = svg.append('path') var circles = [ [100, 300], [400, 100], [800, 200], ] var drag = d3.behavior.drag() .on('dragstart', function(d){}) .on('drag', function(d){ d[0] = d3.event.x d[1] = d3.event.y d3.select(this).call(translate, ƒ()) calcPath() }) .origin(function(d){ return {x: d[0], y: d[1]} }) var circleSel = svg.appendMany(circles, 'circle') .attr('r', 10) .call(translate, ƒ()) .call(drag) function calcPath(){ var a = circles[0] var b = circles[2] var c = circles[1] var A = dist(b, c) var B = dist(c, a) var C = dist(a, b) var angle = Math.acos((A*A + B*B - C*C)/(2*A*B)) //calc radius of circle var K = .5*A*B*Math.sin(angle) var r = A*B*C/4/K //large arc flag var laf = +(Math.PI/2 > angle) //sweep flag var saf = +((b[0] - a[0])*(c[1] - a[1]) - (b[1] - a[1])*(c[0] - a[0]) < 0) path.attr('d', ['M', a, 'A', r, r, 0, laf, saf, b].join(' ')) } function parsePath(str){ var path = svg.append('path').attr('d', str) var l = path.node().getTotalLength() circles = [0, .5, 1].map(function(d){ var p = path.node().getPointAtLength(d*l) console.log(d, p) return [p.x, p.y] }) circleSel.data(circles).call(translate, ƒ()) calcPath() } parsePath('M 622,449 A 263 263 0 1 0 146,382') function slope(a, b){ return (a[1] - b[1])/(a[0] + b[0]) } function mid(a, b){ return [(a[0] + b[0])/2, (a[1] + b[1])/2] } function dist(a, b){ return Math.sqrt( Math.pow(a[0] - b[0], 2) + Math.pow(a[1] - b[1], 2)) }