简体   繁体   中英

How to draw parallel edges (arrows) between vertices with canvas?

I'm working on a flow-network visualization with Javascript. Vertices are represented as circles and edges are represented as arrows.

Here is my Edge class:

function Edge(u, v) {
  this.u = u; // start vertex
  this.v = v; // end vertex

  this.draw = function() {
    var x1 = u.x;
    var y1 = u.y;
    var x2 = v.x;
    var y2 = v.y;

    context.beginPath();
    context.moveTo(x1, y1);
    context.lineTo(x2, y2);
    context.stroke();

    var dx = x1 - x2;
    var dy = y1 - y2;
    var length = Math.sqrt(dx * dx + dy * dy);

    x1 = x1 - Math.round(dx / ((length / (radius))));
    y1 = y1 - Math.round(dy / ((length / (radius))));
    x2 = x2 + Math.round(dx / ((length / (radius))));
    y2 = y2 + Math.round(dy / ((length / (radius))));

    // calculate the angle of the edge
    var deg = (Math.atan(dy / dx)) * 180.0 / Math.PI;
    if (dx < 0) {
      deg += 180.0;
    }
    if (deg < 0) {
      deg += 360.0;
    }
    // calculate the angle for the two triangle points
    var deg1 = ((deg + 25 + 90) % 360) * Math.PI * 2 / 360.0;
    var deg2 = ((deg + 335 + 90) % 360) * Math.PI * 2 / 360.0;
    // calculate the triangle points
    var arrowx = [];
    var arrowy = [];
    arrowx[0] = x2;
    arrowy[0] = y2;
    arrowx[1] = Math.round(x2 + 12 * Math.sin(deg1));
    arrowy[1] = Math.round(y2 - 12 * Math.cos(deg1));
    arrowx[2] = Math.round(x2 + 12 * Math.sin(deg2));
    arrowy[2] = Math.round(y2 - 12 * Math.cos(deg2));

    context.beginPath();
    context.moveTo(arrowx[0], arrowy[0]);
    context.lineTo(arrowx[1], arrowy[1]);
    context.lineTo(arrowx[2], arrowy[2]);
    context.closePath();
    context.stroke();
    context.fillStyle = "black";
    context.fill();
  };
}

Given the code

var canvas = document.getElementById('canvas'); // canvas element
var context = canvas.getContext("2d");
context.lineWidth = 1;
context.strokeStyle = "black";

var radius = 20; // vertex radius

var u = {
  x: 50,
  y: 80
};

var v = {
  x: 150,
  y: 200
};

var e = new Edge(u, v);

e.draw();

The draw() function will draw an edge between two vertices like this:

边缘

If we add the code

var k = new Edge(v, u);
k.draw();

We will get:

不好

but I want to draw edges both directions as following: (sorry for my bad paint skills)

两边

Of course the vertices and the edge directions are not fixed. A working example (with drawing vertex fucntion) on JSFiddle: https://jsfiddle.net/Romansko/0fu01oec/18/

Aligning axis to a line.

It can make everything a little easier if you rotate the rendering to align with the line. Once you do that it is then easy to draw above or below the line as that is just in the y direction and along the line is the x direction.

Thus if you have a line

const line = { 
   p1 : { x : ? , y : ? },
   p2 : { x : ? , y : ? },
};

Convert it to a vector and normalise that vector

// as vector from p1 to p2
var nx = line.p2.x - line.p1.x;    
var ny = line.p2.y - line.p1.y;

// then get length
const len = Math.sqrt(nx * nx + ny * ny);

// use the length to normalise the vector
nx /= len;
ny /= len;

The normalised vector represents the new x axis we want to render along, and the y axis is at 90 deg to that. We can use setTransform to set both axis and the origin (0,0) point at the start of the line.

ctx.setTransform(
    nx, ny,   // the x axis
    -ny, nx,  // the y axis at 90 deg to the x axis
    line.p1.x, line.p1.y  // the origin (0,0)
)

Now rendering the line and arrow heads is easy as they are axis aligned

ctx.beginPath();
ctx.lineTo(0,0); // start of line
ctx.lineTo(len,0); // end of line
ctx.stroke();

// add the arrow head
ctx.beginPath();
ctx.lineTo(len,0); // tip of arrow
ctx.lineTo(len - 10, 10);
ctx.lineTo(len - 10, -10);
ctx.fill();

To render two lines offset from the center

var offset = 10;
ctx.beginPath();
ctx.lineTo(0,offset); // start of line
ctx.lineTo(len,offset); // end of line
ctx.moveTo(0,-offset); // start of second line
ctx.lineTo(len,-offset); // end of second line
ctx.stroke();

// add the arrow head
ctx.beginPath();
ctx.lineTo(len,offset); // tip of arrow
ctx.lineTo(len - 10, offset+10);
ctx.lineTo(len - 10, offset-10);
ctx.fill();

offset = -10;

// add second  arrow head
ctx.beginPath();
ctx.lineTo(0,offset); // tip of arrow
ctx.lineTo(10, offset+10);
ctx.lineTo(10, offset-10);
ctx.fill();

And you can reset the transform with

ctx.setTransform(1,0,0,1,0,0);  // restore default transform

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM