// from https://github.com/d3/d3-shape/blob/master/src/curve/natural.js function controlPoints(x) { var i, n = x.length - 1, m, a = new Array(n), b = new Array(n), r = new Array(n); a[0] = 0, b[0] = 2, r[0] = x[0] + 2 * x[1]; for (i = 1; i < n - 1; ++i) a[i] = 1, b[i] = 4, r[i] = 4 * x[i] + 2 * x[i + 1]; a[n - 1] = 2, b[n - 1] = 7, r[n - 1] = 8 * x[n - 1] + x[n]; for (i = 1; i < n; ++i) m = a[i] / b[i - 1], b[i] -= m, r[i] -= m * r[i - 1]; a[n - 1] = r[n - 1] / b[n - 1]; for (i = n - 2; i >= 0; --i) a[i] = (r[i] - a[i + 1]) / b[i]; b[n - 1] = (x[n] + a[n - 1]) / 2; for (i = 0; i < n - 1; ++i) b[i] = 2 * x[i + 1] - a[i + 1]; return [a, b]; } function svgpath() { var circularSvg = d3.select("svg"), // Move out of here it doesnt belong circularPath = circularSvg.select("path").node(); return circularPath; }; export default function () { var parts = 3, thickness = 5, angle = 2, padding = 1, path = svgpath(), path_l = path.getTotalLength(), context = null; function pointAngle(length) { var p1 = path.getPointAtLength(length); var p2 = path.getPointAtLength(length + 0.01); if (p1.x == p2.x && p1.y == p2.y) { p1 = path.getPointAtLength(length - 0.01); } console.log(p1, p2); return Math.atan2((p2.y - p1.y), (p2.x - p1.x)); } function pointsAt(length, angleoffset = 0) { var m = path.getPointAtLength(length); var m_angle = pointAngle(length + angleoffset) console.log("a", m_angle); var a = { x: m.x + (thickness * Math.cos(m_angle + angle)) , y: m.y + (thickness * Math.sin(m_angle + angle)) }; var b = { x: m.x + (thickness * Math.cos(m_angle - angle)) , y: m.y + (thickness * Math.sin(m_angle - angle)) }; return { a: a, m: m, b: b } } function arrowonpath() { var buffer, start = path_l * arguments[0].start, end = path_l * arguments[0].end, length = end - start, partlen = (length - (2 * padding)) / (2 * parts); console.log("ArrowOnPath", start, end, partlen); if (!context) context = buffer = d3.path(); var p = [pointsAt(start + padding, (-1 * padding))]; for (var i = 1; i < (2 * parts); i++) { p.push(pointsAt(start + (i * partlen))); } p.push(pointsAt(start + length - padding, (1 * padding))); var i = 0 context.moveTo(p[i].m.x, p[i].m.y); context.lineTo(p[i].a.x, p[i].a.y); var nat = d3.curveNatural(context); nat.lineStart(); for (var i = 0; i < p.length; i++) { nat.point(p[i].a.x, p[i].a.y); } var px = controlPoints(nat._x), py = controlPoints(nat._y); var n = p.length; var x = nat._x, y = nat._y; for (var i0 = 0, i1 = 1; i1 < n; ++i0, ++i1) { context.bezierCurveTo(px[0][i0], py[0][i0], px[1][i0], py[1][i0], x[i1], y[i1]); } i = p.length - 1; context.lineTo(p[i].m.x, p[i].m.y); context.lineTo(p[i].b.x, p[i].b.y); nat.lineStart(); for (var i = p.length - 1; i >= 0; --i) { nat.point(p[i].b.x, p[i].b.y); } var px = controlPoints(nat._x), py = controlPoints(nat._y); var n = p.length; var x = nat._x, y = nat._y; for (var i0 = 0, i1 = 1; i1 < n; ++i0, ++i1) { context.bezierCurveTo(px[0][i0], py[0][i0], px[1][i0], py[1][i0], x[i1], y[i1]); } context.closePath(); if (buffer) return context = null, buffer + "" || null; } arrowonpath.svgpath = function (_) { return arguments.length ? (svgpath = typeof _ === "function" ? _ : constant(+_), arrowonpath) : svgpath; }; return arrowonpath; }