【问题标题】:Draw an arrow on HTML5 Canvas between two objects在 HTML5 Canvas 上的两个对象之间绘制一个箭头
【发布时间】:2011-11-21 12:24:58
【问题描述】:

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

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

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

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

【问题讨论】:

    标签: javascript html canvas


    【解决方案1】:

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

    示例如下: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>
    

    【讨论】:

    • 链接到我的工作:jsfiddle.net/emYhJ 你能重构它以适应我的代码吗?
    • 还是谢谢..想通了:)
    • @allwyn.menezes 为什么要通过第二个立方体绘制它?你能更新你的小提琴吗?
    • @Phrogz 嗨,我在带有纯绿色矩形的白色画布上尝试了这种方法。我将这些矩形的原点(左上角 + 5,绘制矩形)点传递给箭头函数,但没有得到任何回击。也许我需要发送不同的点?也许我应该开始一个新问题..
    • 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);
    猜你喜欢
    • 2013-06-29
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-11-30
    相关资源
    最近更新 更多