Javascript 在 HTML5 Canvas 上绘制两个对象之间的箭头

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/8211745/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-24 05:08:51  来源:igfitidea点击:

Draw an arrow on HTML5 Canvas between two objects

javascripthtmlcanvas

提问by allwyn.menezes

I'm working on concept maps application, which has a set of nodes and links. I have connected the links to nodes using the center of the node as reference. Since I have nodes with different size and shapes, it is not advisable to draw arrow-head for the link by specifying height or width of the shape. My approach is to draw a link, starting from one node, pixel by pixel till the next node is reached(here the nodes are of different color from that of the background), then by accessing the pixel value, I want to be able to decide the point of intersection of link and the node, which is actually the co-ordinate for drawing the arrow-head.

我正在开发具有一组节点和链接的概念图应用程序。我已经使用节点的中心作为参考将链接连接到节点。由于我有不同大小和形状的节点,因此不建议通过指定形状的高度或宽度来为链接绘制箭头。我的方法是绘制一个链接,从一个节点开始,一个像素一个像素,直到到达下一个节点(这里节点的颜色与背景的颜色不同),然后通过访问像素值,我希望能够确定链接和节点的交点,它实际上是绘制箭头的坐标。

It would be great, if I could get some help with this.

如果我能得到一些帮助,那就太好了。

Sample Code: http://jsfiddle.net/9tUQP/4/

示例代码:http: //jsfiddle.net/9tUQP/4/

Here the green squares are nodes and the line starting from left square and entering into the right square is the link. I want the arrow-head to be drawn at the point of intersection of link and the right square.

这里绿色方块是节点,从左方块开始进入右方块的线是链接。我希望在链接和右正方形的交点处绘制箭头。

回答by Phrogz

I've created an example that does this. I use Bresenham's Line Algorithmto walk the line of whole canvas pixels and check the alpha at each point; whenever it crosses a 'threshold' point I record that as a candidate. I then use the first and last such points to draw an arrow (with properly-rotated arrowhead).

我创建了一个示例来执行此操作。我使用Bresenham's Line Algorithm走整个画布像素的线并检查每个点的 alpha;每当它超过“阈值”点时,我都会将其记录为候选人。然后我使用第一个和最后一个这样的点来绘制一个箭头(带有正确旋转的箭头)。

Here's the example: http://phrogz.net/tmp/canvas_shape_edge_arrows.html

这是示例:http: //phrogz.net/tmp/canvas_shape_edge_arrows.html

Refresh the example to see a new random test case. It 'fails' if you have another 'shape' already overlapping one of the end points. One way to solve this would be to draw your shapes first to a blank canvas and then copy the result (drawImage) to the final canvas.

刷新示例以查看新的随机测试用例。如果您有另一个“形状”已经与端点之一重叠,则它“失败”。解决此问题的一种方法是先将形状绘制到空白画布,然后将结果 ( drawImage)复制到最终画布。

For Stack Overflow posterity (in case my site is down) here's the relevant code:

对于 Stack Overflow 的后代(以防我的网站关闭),这里是相关代码:

<!DOCTYPE html>
<html><head>
  <meta charset="utf-8">
  <title>HTML5 Canvas Shape Edge Detection (for Arrow)</title>
  <style type="text/css">
    body { background:#eee; margin:2em 4em; text-align:center; }
    canvas { background:#fff; border:1px solid #666 }
  </style>
</head><body>
  <canvas width="800" height="600"></canvas>
  <script type="text/javascript">
    var ctx = document.querySelector('canvas').getContext('2d');

    for (var i=0;i<20;++i) randomCircle(ctx,'#999');

    var start = randomDiamond(ctx,'#060');
    var end   = randomDiamond(ctx,'#600');
    ctx.lineWidth = 2;
    ctx.fillStyle = ctx.strokeStyle = '#099';
    arrow(ctx,start,end,10);

    function arrow(ctx,p1,p2,size){
      ctx.save();

      var points = edges(ctx,p1,p2);
      if (points.length < 2) return 
      p1 = points[0], p2=points[points.length-1];

      // Rotate the context to point along the path
      var dx = p2.x-p1.x, dy=p2.y-p1.y, len=Math.sqrt(dx*dx+dy*dy);
      ctx.translate(p2.x,p2.y);
      ctx.rotate(Math.atan2(dy,dx));

      // line
      ctx.lineCap = 'round';
      ctx.beginPath();
      ctx.moveTo(0,0);
      ctx.lineTo(-len,0);
      ctx.closePath();
      ctx.stroke();

      // arrowhead
      ctx.beginPath();
      ctx.moveTo(0,0);
      ctx.lineTo(-size,-size);
      ctx.lineTo(-size, size);
      ctx.closePath();
      ctx.fill();

      ctx.restore();
    }

    // Find all transparent/opaque transitions between two points
    // Uses http://en.wikipedia.org/wiki/Bresenham's_line_algorithm
    function edges(ctx,p1,p2,cutoff){
      if (!cutoff) cutoff = 220; // alpha threshold
      var dx = Math.abs(p2.x - p1.x), dy = Math.abs(p2.y - p1.y),
          sx = p2.x > p1.x ? 1 : -1,  sy = p2.y > p1.y ? 1 : -1;
      var x0 = Math.min(p1.x,p2.x), y0=Math.min(p1.y,p2.y);
      var pixels = ctx.getImageData(x0,y0,dx+1,dy+1).data;
      var hits=[], over=null;
      for (x=p1.x,y=p1.y,e=dx-dy; x!=p2.x||y!=p2.y;){
        var alpha = pixels[((y-y0)*(dx+1)+x-x0)*4 + 3];
        if (over!=null && (over ? alpha<cutoff : alpha>=cutoff)){
          hits.push({x:x,y:y});
        }
        var e2 = 2*e;
        if (e2 > -dy){ e-=dy; x+=sx }
        if (e2 <  dx){ e+=dx; y+=sy  }
        over = alpha>=cutoff;
      }
      return hits;
    }

    function randomDiamond(ctx,color){
      var x = Math.round(Math.random()*(ctx.canvas.width  - 100) + 50),
          y = Math.round(Math.random()*(ctx.canvas.height - 100) + 50);
      ctx.save();
      ctx.fillStyle = color;
      ctx.translate(x,y);
      ctx.rotate(Math.random() * Math.PI);
      var scale = Math.random()*0.8 + 0.4;
      ctx.scale(scale,scale);
      ctx.lineWidth = 5/scale;
      ctx.fillRect(-50,-50,100,100);
      ctx.strokeRect(-50,-50,100,100);
      ctx.restore();
      return {x:x,y:y};
    }

    function randomCircle(ctx,color){
      ctx.save();
      ctx.beginPath();
      ctx.arc(
        Math.round(Math.random()*(ctx.canvas.width  - 100) + 50),
        Math.round(Math.random()*(ctx.canvas.height - 100) + 50),
        Math.random()*20 + 10,
        0, Math.PI * 2, false
      );
      ctx.fillStyle = color;
      ctx.fill();
      ctx.lineWidth = 2;
      ctx.stroke();
      ctx.restore();
    }

  </script>
</body></html>