如何将箭头添加到使用画布绘制的线条?

时间:2015-12-31 01:43:29

标签: javascript canvas

我有一个在坐标网格上绘制线性图的函数,如下面的JsFiddle所示:https://jsfiddle.net/zje14n92/1/

绘制线性图(而不是轴)的代码组件如下:

if (canvas1.getContext) {
canvas1.width  = x_axis * 2;
canvas1.height = y_axis * 2;
var ctx1 = canvas1.getContext("2d");

ctx1.font = "10px sans-serif";
ctx1.strokeText('   ', x_axis+50, 50);

ctx1.lineWidth = 1;

ctx1.beginPath();

ctx1.strokeStyle = 'black';

x = -x_max;
y = 4*x + 5; // FIRST THING TO CHANGE TO CHANGE EQUATION (MUST USE * FOR MULTIPLICATION)
ctx1.moveTo(offsetX(x), offsetY(y));

while (x < x_max) { // INCLUDE CODE FOR BROKEN LINE IN HERE
    x += 0.1;
    y = 4*x+5; // SECOND THING TO CHANGE TO CHANGE EQUATION (MUST USE * FOR MULTIPLICATION)
    ctx1.lineTo(offsetX(x), offsetY(y));
}
ctx1.stroke();

ctx1.closePath();

我希望线性图(我已经计算出轴)以箭头结束,但我无法弄清楚如何做到这一点。我在下面的JsFiddle(http://jsfiddle.net/m1erickson/Sg7EZ/)中找到了下面的代码,但是我无法弄清楚如何将它合并到我现有的代码中......或者如果这确实是我应该怎么做的。

var startRadians=Math.atan((this.y2-this.y1)/(this.x2-this.x1));
startRadians+=((this.x2>this.x1)?-90:90)*Math.PI/180;
        this.drawArrowhead(ctx,this.x1,this.y1,startRadians);
        // draw the ending arrowhead
        var endRadians=Math.atan((this.y2-this.y1)/(this.x2-this.x1));
        endRadians+=((this.x2>this.x1)?90:-90)*Math.PI/180;
        this.drawArrowhead(ctx,this.x2,this.y2,endRadians);

    }

    Line.prototype.drawArrowhead=function(ctx,x,y,radians){
        ctx.save();
        ctx.beginPath();
        ctx.translate(x,y);
        ctx.rotate(radians);
        ctx.moveTo(0,0);
        ctx.lineTo(5,20);
        ctx.lineTo(-5,20);
        ctx.closePath();
        ctx.restore();
        ctx.fill();
}

是否有某种简单的方法将线的端点转换为箭头?谢谢!

2 个答案:

答案 0 :(得分:1)

您必须找到(无限)绘图线退出画布的任何点(矩形)。

它可能会在0,1或2点退出画布。如果绘图线在0或1点退出画布矩形,则不需要箭头。如果绘图线在2点退出画布矩形,则使用箭头小提琴在两个出口点放置箭头。

要测试出口点,您可以将画布矩形视为形成矩形的4条线。然后测试绘图线和4个矩形线中的每一个之间的交点。

此脚本返回2行的任意交集:

var exitTop=line2lineIntersection(
    {x:0,y:0}, { x:canvas.width, y:0},
    yourLinePoint0, yourLinePoint1
);

var exitRight=line2lineIntersection(
    {x:canvas.width,y:0}, { x:canvas.width, y:canvas.height},
    yourLinePoint0, yourLinePoint1
);

var exitBottom=line2lineIntersection(
    {x:0,y:canvas.height}, { x:canvas.width, y:canvas.height},
    yourLinePoint0, yourLinePoint1
);

var exitLeft=line2lineIntersection(
    {x:0,y:0}, { x:0, y:canvas.height},
    yourLinePoint0, yourLinePoint1
);

var intersections=[];
if(exitTop){ intersections.push(exitTop); }
if(exitRight){ intersections.push(exitRight); }
if(exitBottom){ intersections.push(exitBottom); }
if(exitLeft){ intersections.push(exitLeft); }

if(intersections.length==2){
    // feed your 2 exit points into your arrow drawing script 
}

查找绘图线和4个画布矩形线的任何交点:

// create a new line object
var line=new Line(
    intersections[0].x, intersections[0].y,
    intersections[1].x, intersections[1].y
);

// draw the line
line.drawWithArrowheads(context);

然后将2个退出点输入箭头绘图脚本:

var canvas=document.getElementById("canvas");
var ctx=canvas.getContext("2d");
var cw=canvas.width;
var ch=canvas.height;

function Line(x1,y1,x2,y2){
  this.x1=x1;
  this.y1=y1;
  this.x2=x2;
  this.y2=y2;
}
Line.prototype.drawWithArrowheads=function(){

  // arbitrary styling
  ctx.strokeStyle="blue";
  ctx.fillStyle="blue";
  ctx.lineWidth=1;

  // draw the line
  ctx.beginPath();
  ctx.moveTo(this.x1,this.y1);
  ctx.lineTo(this.x2,this.y2);
  ctx.stroke();

  // draw the starting arrowhead
  var startRadians=Math.atan((this.y2-this.y1)/(this.x2-this.x1));
  startRadians+=((this.x2>this.x1)?-90:90)*Math.PI/180;
  this.drawArrowhead(ctx,this.x1,this.y1,startRadians);
  // draw the ending arrowhead
  var endRadians=Math.atan((this.y2-this.y1)/(this.x2-this.x1));
  endRadians+=((this.x2>this.x1)?90:-90)*Math.PI/180;
  this.drawArrowhead(ctx,this.x2,this.y2,endRadians);

}
Line.prototype.drawArrowhead=function(ctx,x,y,radians){
  ctx.save();
  ctx.beginPath();
  ctx.translate(x,y);
  ctx.rotate(radians);
  ctx.moveTo(0,0);
  ctx.lineTo(5,20);
  ctx.lineTo(-5,20);
  ctx.closePath();
  ctx.restore();
  ctx.fill();
}

////////////////////////////////////

var x0=22.375;
var y0=678.625;
var x1=330.8125;
var y1=-555.125;

var exitTop=line2lineIntersection(
  {x:0,y:0}, { x:canvas.width, y:0},
  {x:x0,y:y0},{x:x1,y:y1}
);

var exitRight=line2lineIntersection(
  {x:canvas.width,y:0}, { x:canvas.width, y:canvas.height},
  {x:x0,y:y0},{x:x1,y:y1}
);

var exitBottom=line2lineIntersection(
  {x:0,y:canvas.height}, { x:canvas.width, y:canvas.height},
  {x:x0,y:y0},{x:x1,y:y1}
);

var exitLeft=line2lineIntersection(
  {x:0,y:0}, { x:0, y:canvas.height},
  {x:x0,y:y0},{x:x1,y:y1}
);

var intersections=[];
if(exitTop){ intersections.push(exitTop); }
if(exitRight){ intersections.push(exitRight); }
if(exitBottom){ intersections.push(exitBottom); }
if(exitLeft){ intersections.push(exitLeft); }

if(intersections.length==2){
  // feed your 2 exit points into your arrow drawing script
  for(var i=0;i<intersections.length;i++){
    ctx.beginPath();
    ctx.arc(intersections[i].x,intersections[i].y,3,0,Math.PI*2);
    //ctx.fill();
  } 
}


// create a new line object
var line=new Line(
  intersections[0].x, intersections[0].y,
  intersections[1].x, intersections[1].y
);

// draw the line
line.drawWithArrowheads();



// Attribution: http://paulbourke.net/geometry/pointlineplane/
function line2lineIntersection(p0,p1,p2,p3) {

  var unknownA = (p3.x-p2.x) * (p0.y-p2.y) - (p3.y-p2.y) * (p0.x-p2.x);
  var unknownB = (p1.x-p0.x) * (p0.y-p2.y) - (p1.y-p0.y) * (p0.x-p2.x);
  var denominator  = (p3.y-p2.y) * (p1.x-p0.x) - (p3.x-p2.x) * (p1.y-p0.y);        

  // Test if Coincident
  // If the denominator and numerator for the ua and ub are 0
  //    then the two lines are coincident.    
  if(unknownA==0 && unknownB==0 && denominator==0){return(null);}

  // Test if Parallel 
  // If the denominator for the equations for ua and ub is 0
  //     then the two lines are parallel. 
  if (denominator == 0) return null;

  // If the intersection of line segments is required 
  // then it is only necessary to test if ua and ub lie between 0 and 1.
  // Whichever one lies within that range then the corresponding
  // line segment contains the intersection point. 
  // If both lie within the range of 0 to 1 then 
  // the intersection point is within both line segments. 
  unknownA /= denominator;
  unknownB /= denominator;

  var isIntersecting=(unknownA>=0 && unknownA<=1 && unknownB>=0 && unknownB<=1)

  if(!isIntersecting){return(null);}

  return({
    x: p0.x + unknownA * (p1.x-p0.x),
    y: p0.y + unknownA * (p1.y-p0.y)
  });
}

示例代码和演示

&#13;
&#13;
body{ background-color: ivory; }
#canvas{border:1px solid red; margin:0 auto; }
&#13;
<canvas id="canvas" width=300 height=300></canvas>
&#13;
/////////////
//
function Line(x1,y1,x2,y2){
    this.x1=x1;
    this.y1=y1;
    this.x2=x2;
    this.y2=y2;
}
Line.prototype.drawWithArrowheads=function(color){
    // arbitrary styling
    ctx.strokeStyle=color || "black";
    ctx.fillStyle=color || "black";
    ctx.lineWidth=1;   
    // draw the line
    ctx.beginPath();
    ctx.moveTo(this.x1,this.y1);
    ctx.lineTo(this.x2,this.y2);
    ctx.stroke();
    // draw the starting arrowhead
    var startRadians=Math.atan((this.y2-this.y1)/(this.x2-this.x1));
    startRadians+=((this.x2>this.x1)?-90:90)*Math.PI/180;
    this.drawArrowhead(ctx,this.x1,this.y1,startRadians);
    // draw the ending arrowhead
    var endRadians=Math.atan((this.y2-this.y1)/(this.x2-this.x1));
    endRadians+=((this.x2>this.x1)?90:-90)*Math.PI/180;
    this.drawArrowhead(ctx,this.x2,this.y2,endRadians);
}
//
Line.prototype.drawArrowhead=function(ctx,x,y,radians){
    ctx.save();
    ctx.beginPath();
    ctx.translate(x,y);
    ctx.rotate(radians);
    ctx.moveTo(0,0);
    ctx.lineTo(5,20);
    ctx.lineTo(-5,20);
    ctx.closePath();
    ctx.restore();
    ctx.fill();
}
/////////////

var x_axis = 175;
var y_axis = 175;

var x_max = 7;  // THIS CHANGES RANGE OF X-AXIS
var y_max = 7; // THIS CHANGES RANGE OF Y-AXIS

var x_scale = x_axis / (x_max + 1);
var y_scale = y_axis / (y_max + 1);

var x_offset = x_axis + 0.5; // location on canvas
var y_offset = y_axis + 0.5; // of graph's origin


var canvas1 = document.getElementById("plot");
var ctx1 = canvas1.getContext("2d");

var canvas = document.getElementById("axes");
var ctx = canvas.getContext("2d");

canvas.width = canvas1.width  = x_axis * 2;
canvas.height = canvas1.height = y_axis * 2;

drawAxes(ctx);

plotline(-x_max,function(x){return(4*x+5);},'purple');
plotline(-x_max,function(x){return(3*x-1);},'blue');


function drawAxes(ctx) {
    ctx.font = "20px";
    
    ctx.font = "14px";
    ctx.strokeText('Y', x_axis - 25, 10);   //CHANGES LABEL OF Y-AXIS
    ctx.strokeText('X', x_axis * 2 - 10, y_axis + 20); //CHANGES LABEL OF X-AXIS

    ctx.font = "10px sans-serif";
    ctx.lineWidth = 1;

    // draw x-axis
    ctx.beginPath();
    ctx.moveTo(0, y_offset);
    ctx.lineTo(x_axis*2, y_offset);
    ctx.stroke();
    ctx.closePath();

    // draw arrow
    ctx.beginPath();
    ctx.moveTo(10 - 8, y_axis+0.5);
    ctx.lineTo(10, y_axis+0.5 - 3);
    ctx.lineTo(10, y_axis+0.5 + 3);
    ctx.stroke();
    ctx.closePath();
    ctx.fill();

    ctx.beginPath();
    ctx.moveTo(x_axis*2+0.5, y_axis+0.5);
    ctx.lineTo(x_axis*2+0.5 - 8, y_axis+0.5 - 3);
    ctx.lineTo(x_axis*2+0.5 - 8, y_axis+0.5 + 3);
    ctx.stroke();
    ctx.closePath();
    ctx.fill();

    // draw x values
    j = -x_max;
    while (j <= x_max) {
      x = j * x_scale;
      ctx.strokeStyle = '#aaa';
      ctx.beginPath();
      ctx.moveTo(x + x_offset, y_offset);
      ctx.lineTo(x + x_offset, y_offset + 10);
      ctx.stroke();
      ctx.closePath();

      ctx.strokeStyle = '#666';
      ctx.strokeText(j, x + x_offset - 10, y_offset + 20);
    
      j++;
      if (j == 0) { j++; }
    }

    // draw y-axis
    ctx.beginPath();
    ctx.moveTo(x_offset, 0);
    ctx.lineTo(x_offset, y_axis*2);
    ctx.stroke();
    ctx.closePath();

    // draw arrow
    ctx.beginPath();
    ctx.moveTo(x_axis+0.5, 0.5);
    ctx.lineTo(x_axis+0.5 - 3, 0.5 + 8);
    ctx.lineTo(x_axis+0.5 + 3, 0.5 + 8);
    ctx.stroke();
    ctx.closePath();
    ctx.fill();

    ctx.beginPath();
    ctx.moveTo(x_axis+0.5, y_axis*2);
    ctx.lineTo(x_axis+0.5 - 3, y_axis*2 -8);
    ctx.lineTo(x_axis+0.5 + 3, y_axis*2 - 8);
    ctx.stroke();
    ctx.closePath();
    ctx.fill();

    // draw y values
    j = -y_max;
    while (j <= y_max) {
      y = j * y_scale;
      ctx.strokeStyle = '#aaa';
      ctx.beginPath();
      ctx.moveTo(x_offset, y + y_offset);
      ctx.lineTo(x_offset - 10, y + y_offset);
      ctx.stroke();
      ctx.closePath();

      ctx.strokeStyle = '#666';
      ctx.strokeText(-j, x_offset - 25, y + y_offset + 5);

      j++;
      if (j == 0) { j++; }
    }
}

function offsetX(v) {
	return x_offset + (v * x_scale);
}

function offsetY(v) {
	return y_offset - (v * y_scale);
}

/////////////////////////

function plotline(x,yFn,strokecolor){
    ctx1.font = "10px sans-serif";
    ctx1.strokeText('   ', x_axis+50, 50);
    ctx1.lineWidth = 1;
    ctx1.strokeStyle=strokecolor;
    
    drawLineWithArrowheads(
        offsetX(x),
        offsetY(yFn(x)),
        offsetX(x_max),
        offsetY(yFn(x_max)),
        strokecolor
    );
}
//
function drawLineWithArrowheads(x0,y0,x1,y1,color){
    var exitTop=line2lineIntersection(
        {x:0,y:0}, { x:canvas.width, y:0},
        {x:x0,y:y0},{x:x1,y:y1}
    );
    var exitRight=line2lineIntersection(
        {x:canvas.width,y:0}, { x:canvas.width, y:canvas.height},
        {x:x0,y:y0},{x:x1,y:y1}
    );
    var exitBottom=line2lineIntersection(
        {x:0,y:canvas.height}, { x:canvas.width, y:canvas.height},
        {x:x0,y:y0},{x:x1,y:y1}
    );
    var exitLeft=line2lineIntersection(
        {x:0,y:0}, { x:0, y:canvas.height},
        {x:x0,y:y0},{x:x1,y:y1}
    );
    //
    var intersections=[];
    if(exitTop){ intersections.push(exitTop); }
    if(exitRight){ intersections.push(exitRight); }
    if(exitBottom){ intersections.push(exitBottom); }
    if(exitLeft){ intersections.push(exitLeft); }
    //
    if(intersections.length==2){
        // create a new line object
        var line=new Line(
            intersections[0].x, intersections[0].y,
            intersections[1].x, intersections[1].y
        );
        // draw the line
        line.drawWithArrowheads(color);
    }
}


// Attribution: http://paulbourke.net/geometry/pointlineplane/
function line2lineIntersection(p0,p1,p2,p3) {
    var unknownA = (p3.x-p2.x) * (p0.y-p2.y) - (p3.y-p2.y) * (p0.x-p2.x);
    var unknownB = (p1.x-p0.x) * (p0.y-p2.y) - (p1.y-p0.y) * (p0.x-p2.x);
    var denominator  = (p3.y-p2.y) * (p1.x-p0.x) - (p3.x-p2.x) * (p1.y-p0.y);        
    // Test if Coincident
    // If the denominator and numerator for the ua and ub are 0
    //    then the two lines are coincident.    
    if(unknownA==0 && unknownB==0 && denominator==0){return(null);}
    // Test if Parallel 
    // If the denominator for the equations for ua and ub is 0
    //     then the two lines are parallel. 
    if (denominator == 0) return null;
    // If the intersection of line segments is required 
    // then it is only necessary to test if ua and ub lie between 0 and 1.
    // Whichever one lies within that range then the corresponding
    // line segment contains the intersection point. 
    // If both lie within the range of 0 to 1 then 
    // the intersection point is within both line segments. 
    unknownA /= denominator;
    unknownB /= denominator;
    var isIntersecting=(unknownA>=0 && unknownA<=1 && unknownB>=0 && unknownB<=1)
    if(!isIntersecting){return(null);}
    return({
        x: p0.x + unknownA * (p1.x-p0.x),
        y: p0.y + unknownA * (p1.y-p0.y)
    });
}
&#13;
&#13;
&#13;

[添加:将答案代码添加到提问者的代码中]

enter image description here

&#13;
&#13;
body{ background-color: ivory; }
canvas{border:1px solid red; }
canvas{left:0;	position: absolute;	top: 0; }
&#13;
<div class="relative">
  <canvas id="axes"></canvas>
  <canvas id="plot"></canvas>
&#13;
//set up your Bitmap and WritableBitmap as you see fit
Bitmap colorBitmap = new Bitmap(..);
WriteableBitmap colorWB = new WriteableBitmap(..);

//choose appropriate bytes as per your pixel format, I'll cheat here an just pick 4
int bytesPerPixel = 4;

//rectangles will be used to identify what bits change
Rectangle colorBitmapRectangle = new Rectangle(0, 0, colorBitmap.Width, colorBitmap.Height);
Int32Rect colorBitmapInt32Rect = new Int32Rect(0, 0, colorWB.PixelWidth, colorWB.PixelHeight);
&#13;
&#13;
&#13;

答案 1 :(得分:0)

jsFiddle的最后4行显示用法:

    // create a new line object
    var line=new Line(50,50,250,275);
    // draw the line
    line.drawWithArrowheads(context);

(但您还需要jsFiddle顶部的Line定义。)

无论如何,代码所做的是弄清线条前进的角度,然后在该角度绘制一个三角形。

  • 它保存状态,然后
  • 它使用translate移动到行尾,然后
  • 它使用旋转来定向箭头,然后
  • 它形成箭头路径,然后
  • 它填补了路径,最后
  • 恢复状态

到目前为止最棘手的一点是找到定向箭头的角度。 由于你有一个多点线,大概你只能使用最后两点。