How to draw an arrow between two points in d3v4?

I created my own path visualizer that draws an arrow between nodes in my d3 graph, as shown in the snippet. I have the last question that I'm stuck

How would I turn a part of an arrow so that it points in a direction instead of a source?

var w2 = 6, ar2 = w2 * 2, ah = w2 * 3, baseHeight = 30; // Arrow function function CurvedArrow(context, index) { this._context = context; this._index = index; } CurvedArrow.prototype = { areaStart: function() { this._line = 0; }, areaEnd: function() { this._line = NaN; }, lineStart: function() { this._point = 0; }, lineEnd: function() { if (this._line || (this._line !== 0 && this._point === 1)) { this._context.closePath(); } this._line = 1 - this._line; }, point: function(x, y) { x = +x, y = +y; // jshint ignore:line switch (this._point) { case 0: this._point = 1; this._p1x = x; this._p1y = y; break; case 1: this._point = 2; // jshint ignore:line default: var p1x = this._p1x, p1y = this._p1y, p2x = x, p2y = y, dx = p2x - p1x, dy = p2y - p1y, px = dy, py = -dx, pr = Math.sqrt(px * px + py * py), nx = px / pr, ny = py / pr, dr = Math.sqrt(dx * dx + dy * dy), wx = dx / dr, wy = dy / dr, ahx = wx * ah, ahy = wy * ah, awx = nx * ar2, awy = ny * ar2, phx = nx * w2, phy = ny * w2, //Curve figures alpha = Math.floor((this._index - 1) / 2), direction = p1y < p2y ? -1 : 1, height = (baseHeight + alpha * 3 * ar2) * direction, // r5 //r7 r6|\ // ------------ \ // ____________ /r4 //r1 r2|/ // r3 r1x = p1x - phx, r1y = p1y - phy, r2x = p2x - phx - ahx, r2y = p2y - phy - ahy, r3x = p2x - awx - ahx, r3y = p2y - awy - ahy, r4x = p2x, r4y = p2y, r5x = p2x + awx - ahx, r5y = p2y + awy - ahy, r6x = p2x + phx - ahx, r6y = p2y + phy - ahy, r7x = p1x + phx, r7y = p1y + phy, //Curve 1 c1mx = (r2x + r1x) / 2, c1my = (r2y + r1y) / 2, m1b = (c1mx - r1x) / (r1y - c1my), den1 = Math.sqrt(1 + Math.pow(m1b, 2)), mp1x = c1mx + height * (1 / den1), mp1y = c1my + height * (m1b / den1), //Curve 2 c2mx = (r7x + r6x) / 2, c2my = (r7y + r6y) / 2, m2b = (c2mx - r6x) / (r6y - c2my), den2 = Math.sqrt(1 + Math.pow(m2b, 2)), mp2x = c2mx + height * (1 / den2), mp2y = c2my + height * (m2b / den2); this._context.moveTo(r1x, r1y); this._context.quadraticCurveTo(mp1x, mp1y, r2x, r2y); this._context.lineTo(r3x, r3y); this._context.lineTo(r4x, r4y); this._context.lineTo(r5x, r5y); this._context.lineTo(r6x, r6y); this._context.quadraticCurveTo(mp2x, mp2y, r7x, r7y); break; } } }; var w = 600, h = 220; var t0 = Date.now(); var points = [{ R: 100, r: 3, speed: 2, phi0: 190 }]; var path = d3.line() .curve(function(ctx) { return new CurvedArrow(ctx, 1); }); var svg = d3.select("svg"); var container = svg.append("g") .attr("transform", "translate(" + w / 2 + "," + h / 2 + ")") container.selectAll("g.planet").data(points).enter().append("g") .attr("class", "planet").each(function(d, i) { d3.select(this).append("circle").attr("r", dr).attr("cx", dR) .attr("cy", 0).attr("class", "planet"); }); container.append("path"); var planet = d3.select('.planet circle'); d3.timer(function() { var delta = (Date.now() - t0); planet.attr("transform", function(d) { return "rotate(" + d.phi0 + delta * d.speed / 50 + ")"; }); var g = document.createElementNS("http://www.w3.org/2000/svg", "g"); g.setAttributeNS(null, "transform", planet.attr('transform')); var matrix = g.transform.baseVal.consolidate().matrix; svg.selectAll("path").attr('d', function(d) { return path([ [0, 0], [matrix.a * 100, matrix.b * 100] ]) }); }); 
 path { stroke: #11a; fill: #eee; } 
 <script src="https://d3js.org/d3.v4.min.js"></script> <svg width="600" height="220"></svg> 
+5
source share
1 answer

In the end, I did what @Mark suggested in the comments, I calculate the point, which is the height of the curve along the normal in the middle between two points, then calculates the unit vectors from the start point to the midpoint and again from the middle to the end. Then I can use them to get all the necessary points.

 var arrowRadius = 6, arrowPointRadius = arrowRadius * 2, arrowPointHeight = arrowRadius * 3, baseHeight = 30; // Arrow function function CurvedArrow(context, index) { this._context = context; this._index = index; } CurvedArrow.prototype = { areaStart: function() { this._line = 0; }, areaEnd: function() { this._line = NaN; }, lineStart: function() { this._point = 0; }, lineEnd: function() { if (this._line || (this._line !== 0 && this._point === 1)) { this._context.closePath(); } this._line = 1 - this._line; }, point: function(x, y) { x = +x, y = +y; // jshint ignore:line switch (this._point) { case 0: this._point = 1; this._p1x = x; this._p1y = y; break; case 1: this._point = 2; // jshint ignore:line default: var p1x = this._p1x, p1y = this._p1y, p2x = x, p2y = y, //Curve figures // mp1 // | // | height // | // p1 ----------------------- p2 // alpha = Math.floor((this._index - 1) / 2), direction = p1y < p2y ? -1 : 1, height = (baseHeight + alpha * 3 * arrowPointRadius) * direction, c1mx = (p2x + p1x) / 2, c1my = (p2y + p1y) / 2, m1b = (c1mx - p1x) / (p1y - c1my), den1 = Math.sqrt(1 + Math.pow(m1b, 2)), // Perpendicular point from the midpoint. mp1x = c1mx + height * (1 / den1), mp1y = c1my + height * (m1b / den1), // Arrow figures dx = p2x - mp1x, dy = p2y - mp1y, dr = Math.sqrt(dx * dx + dy * dy), // Normal unit vectors nx = dy / dr, wy = nx, wx = dx / dr, ny = -wx, ahx = wx * arrowPointHeight, ahy = wy * arrowPointHeight, awx = nx * arrowPointRadius, awy = ny * arrowPointRadius, phx = nx * arrowRadius, phy = ny * arrowRadius, // Start arrow offset. sdx = mp1x - p1x, sdy = mp1y - p1y, spr = Math.sqrt(sdy * sdy + sdx * sdx), snx = sdy / spr, sny = -sdx / spr, sphx = snx * arrowRadius, sphy = sny * arrowRadius, // r5 //r7 r6|\ // ------------ \ // ____________ /r4 //r1 r2|/ // r3 r1x = p1x - sphx, r1y = p1y - sphy, r2x = p2x - phx - ahx, r2y = p2y - phy - ahy, r3x = p2x - awx - ahx, r3y = p2y - awy - ahy, r4x = p2x, r4y = p2y, r5x = p2x + awx - ahx, r5y = p2y + awy - ahy, r6x = p2x + phx - ahx, r6y = p2y + phy - ahy, r7x = p1x + sphx, r7y = p1y + sphy, mpc1x = mp1x - phx, mpc1y = mp1y - phy, mpc2x = mp1x + phx, mpc2y = mp1y + phy; this._context.moveTo(r1x, r1y); this._context.quadraticCurveTo(mpc1x, mpc1y, r2x, r2y); this._context.lineTo(r3x, r3y); this._context.lineTo(r4x, r4y); this._context.lineTo(r5x, r5y); this._context.lineTo(r6x, r6y); this._context.quadraticCurveTo(mpc2x, mpc2y, r7x, r7y); this._context.closePath(); break; } } }; var w = 600, h = 220; var t0 = Date.now(); var points = [{ R: 100, r: 3, speed: 2, phi0: 190 }]; var path = d3.line() .curve(function(ctx) { return new CurvedArrow(ctx, 1); }); var svg = d3.select("svg"); var container = svg.append("g") .attr("transform", "translate(" + w / 2 + "," + h / 2 + ")") container.selectAll("g.planet").data(points).enter().append("g") .attr("class", "planet").each(function(d, i) { d3.select(this).append("circle").attr("r", dr).attr("cx", dR) .attr("cy", 0).attr("class", "planet"); }); container.append("path"); var planet = d3.select('.planet circle'); d3.timer(function() { var delta = (Date.now() - t0); planet.attr("transform", function(d) { return "rotate(" + d.phi0 + delta * d.speed / 50 + ")"; }); var g = document.createElementNS("http://www.w3.org/2000/svg", "g"); g.setAttributeNS(null, "transform", planet.attr('transform')); var matrix = g.transform.baseVal.consolidate().matrix; svg.selectAll("path").attr('d', function(d) { return path([ [0, 0], [matrix.a * 100, matrix.b * 100] ]) }); }); 
 path { stroke: #11a; fill: #eee; } 
 <script src="https://d3js.org/d3.v4.min.js"></script> <svg width="600" height="220"></svg> 
+2
source

Source: https://habr.com/ru/post/1257810/


All Articles