在HTML5画布上绘制两个对象之间的箭头

10
我正在开发一个概念地图应用程序,其中包括一组节点和链接。我已经使用节点中心作为参考将链接连接到节点。由于我的节点具有不同的大小和形状,因此不建议通过指定形状的高度或宽度来绘制链接箭头。我的方法是从一个节点开始,像素点像素点地绘制链接,直到达到下一个节点(这里节点与背景颜色不同),然后通过访问像素值,我希望能够决定链接和节点的交点,这实际上是绘制箭头的坐标。
如果可以的话,希望能得到一些帮助。
示例代码:http://jsfiddle.net/9tUQP/4/ 这里,绿色正方形是节点,从左侧正方形开始并进入右侧正方形的线条是链接。我希望在链接和右侧正方形的交点处绘制箭头。
1个回答

17

我创建了一个例子来实现这个功能。我使用Bresenham's Line Algorithm算法遍历整个画布像素的线,并检查每个点的透明度;每当它越过一个“阈值”点,我就将其记录为候选点。然后,我使用第一个和最后一个这样的点绘制箭头(带有正确旋转的箭头)。

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

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

为了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>

我的工作链接: http://jsfiddle.net/emYhJ/ 你能否重构它以适应我的代码? - allwyn.menezes
谢谢,无论如何...我自己解决了 :) - allwyn.menezes
@allwyn.menezes 为什么你想通过第二个立方体来绘制它?你能更新一下你的fiddle吗? - FutuToad
var p = new Point(boo.position.x + 5,boo.position.y + 5); var p2 = new Point(foo.position.x + 10,foo.position.y + 10); this.arrow(self.context2D,p, p2);变量p = new Point(boo.position.x + 5,boo.position.y + 5); 变量p2 = new Point(foo.position.x + 10,foo.position.y + 10); this.arrow(self.context2D,p,p2); - FutuToad
@Phrogz 好的 :) https://stackoverflow.com/questions/25394358/arrows-not-drawing-between-green-rectangles-in-canvas - FutuToad
显示剩余4条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接