简体   繁体   English

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

[英]Draw an arrow on HTML5 Canvas between two objects

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. 我希望在链接和右方的交叉点绘制箭头。

I've created an example that does this. 我已经创建了一个这样做的例子。 I use Bresenham's Line Algorithm to walk the line of whole canvas pixels and check the alpha at each point; 我使用Bresenham的线算法来遍历整个画布像素的线并检查每个点的alpha; 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). 然后我使用第一个和最后一个这样的点来绘制箭头(带有正确旋转的箭头)。

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>

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

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