How do I draw an arrow between two points in d3v4?

Andrew

I have created a custom path renderer that draws an arrow between the nodes in my d3 graph as shown in the snippet. I have one last issue I am getting stuck on,

How would I rotate the arrow portion so that it is pointing from the direction of the curve instead of the direction of the 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", d.r).attr("cx", d.R)
      .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>

Andrew

I ended up doing what @Mark suggested in the comments, I calculate the point that is the height of the curve away along the normal midway between the two points, then calculate the unit vectors from the start point to the mid point and again from the midpoint to the end. I can then use those to get all the required 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", d.r).attr("cx", d.R)
      .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>

Collected from the Internet

Please contact [email protected] to delete if infringement.

edited at
0

Comments

0 comments
Login to comment

Related

Draw arrow between on line between two points

Draw a bended arrow between two points in gnuplot

How do I draw a route, along an existing road, between two points?

How do I draw a perfect semicircle between two arbitrary points in Core Graphics?

How to draw a line between two points over an image in swift 3?

Draw an arrow between two divs

How do I draw an Ideal line between two headers?

How do I draw a horizontal line between two circles with CSS?

How do I draw the plot of a function between two horizontal boundaries?

How do I make pygame draw along all points on a line between 2 points?

How do I find the distance between two points in a matrix of sequences?

How do I find the distance between two points?

How do I calculate the distance between two points?

How do i find the distance between two points/pixels?

How do i calculate the length of a line between two points?

Mayavi curved arrow between two 3d points

Draw an arrow between two specific points in a scatter plot with plotly graph objs

Draw an arc between two points

How to draw a line / link between two points on a D3 map based on latitude / longitude?

Draw arrow between two flex items

Draw arrow between two ggplot pie charts

Draw arrow between two tikz pictures

Draw arrow between two divs, regardless of their position

How to draw an arc between two known points in Qt?

Matplotlib how to draw vertical line between two Y points

How to draw a line between two given points in R with plotly?

How to draw a line between two points objective-C

How to draw a tiled wall between two points on a grid on canvas?

How to draw a line between two points with js and CSS