如何使用“画布”使线条图居中对齐?

时间:2019-03-16 23:13:06

标签: javascript html5 canvas html5-canvas

问题

当我尝试将 moveTo(100,400)用作x轴时,我试图将此线图放在canvas的中心。将水平起始位置更改为100。如果我在y轴上尝试相同的操作,它将使该线沿x轴移动。

在沿着y轴垂直绘制y轴编号1-9时,我也需要帮助,它似乎只能水平对齐。 编辑!:我已经手动在y轴上画了每个点,所以上面有数字,现在我只想知道如何将图形移到中心!

脚本

var c = document.getElementById("myCanvas");
        var ctx = c.getContext("2d");
        
        ctx.linecap = 'round';
        // draw a scale with the numbers on it
        ctx.lineWidth = 2;
            
        ctx.strokeStyle = '#FF9900';
        ctx.fillStyle = 'blue';
        ctx.beginPath();
        ctx.moveTo(100, 400);             
        for (i = 0; i <= 6; i+=1) {
            
             //put a stroke mark
             ctx.lineTo(100*i,400);
             ctx.lineTo(100*i,405); //markers
             ctx.lineTo(100*i,400);
             
             // write the number 10px below
             ctx.strokeStyle = '#000000';
             // default size is 10px
             ctx.strokeText(i, 100*i, 415);
             ctx.strokeStyle = '#FF9900';
        }    
        // draw a vertical scale with lines on it
        ctx.moveTo(0, -100);
        for (b = 0; b <= 9; b+=1) {
            
            //put a stroke mark
            ctx.lineTo(0,44.5*b);
            ctx.lineTo(5,44.5*b);
            ctx.lineTo(0,44.5*b);
            
            // write the number 10px below
            ctx.strokeStyle = '#000000';
            // default size is 10px                  
       }  
       ctx.strokeStyle = '#000000'
       ctx.strokeText(1, 8, 365);
       ctx.strokeText(2, 8, 320.5);
       ctx.strokeText(3, 8, 276);
       ctx.strokeText(4, 8, 231.5);
       ctx.strokeText(5, 8, 187);
       ctx.strokeText(6, 8, 142.5);
       ctx.strokeText(7, 8, 98);
       ctx.strokeText(8, 8, 53.5);
       ctx.strokeText(9, 8, 9);
       ctx.strokeStyle = '#FF9900';
        ctx.stroke();
<!DOCTYPE html>
<html>
   <head>
      <title>Canvas Axis calibration</title>
       <link rel="stylesheet" type="text/css" href="base.css"/> 
        
   </head>
   <body>
    <canvas id="myCanvas" width="1600" height="500"style="border:1px solid #c3c3c3;">
       Canvas is not playing!
    </canvas>
 

</body>
</html>

3 个答案:

答案 0 :(得分:1)

moveTo()只是设置线条的起点,而不是绘制实际线条。使用 lineTo()绘制实际线条。因此, moveTo()是从开始开始的地方,而 lineTo()是您开始的地方。因此,x轴的起点必须为 moveTo(800,0)

var c = document.getElementById("myCanvas"),
    ctx = c.getContext("2d"),
    lineWidth = 2,
    xNumber = 6,
    yNumber = 9,
    xCenter = c.width / 2,
    yCenter = 44.5 * yNumber + 44.5

ctx.linecap = 'round';
// draw a scale with the numbers on it
ctx.lineWidth = lineWidth;
ctx.strokeStyle = '#FF9900';
ctx.fillStyle = 'blue';

ctx.beginPath();
ctx.moveTo(xCenter, yCenter);

for (i = 0; i <= xNumber; ++i) {
    //put a stroke mark
    ctx.lineTo((xCenter + (100 * i)), yCenter);
    ctx.lineTo((xCenter + (100 * i)), (yCenter + 5)); //markers
    ctx.lineTo((xCenter + (100 * i)), yCenter);
             
    // write the number 10px below
    ctx.strokeStyle = '#000000';
    // default size is 10px
    ctx.strokeText(i, (xCenter + (100 * i)), (yCenter + 15));
}

ctx.strokeStyle = '#FF9900';
ctx.stroke()

// draw a vertical scale with lines on it
ctx.beginPath()
ctx.moveTo(xCenter, yCenter);

for (b = 0; b <= yNumber; ++b) {
    //put a stroke mark
    if(b === 0) continue;

    ctx.lineTo(xCenter, (yCenter - (44.5 * b)));
    ctx.lineTo((xCenter - 5), (yCenter - (44.5 * b)));
    ctx.lineTo(xCenter, (yCenter - (44.5 * b)));  
    ctx.strokeStyle = '#000000';
    ctx.strokeText(b, (xCenter - 15), (yCenter - (44.5 * b)));
}

ctx.strokeStyle = '#FF9900';
ctx.stroke();
<!DOCTYPE html>
<html>
   <head>
      <title>Canvas Axis calibration</title>
       <link rel="stylesheet" type="text/css" href="base.css"/> 
        
   </head>
   <body>
    <canvas id="myCanvas" width="1600" height="500"style="border:1px solid #c3c3c3;">
       Canvas is not playing!
    </canvas>
 

</body>
</html>

答案 1 :(得分:0)

CanvasRenderingContext2D有一种方法:translate()。它只是设置一个坐标平移,该坐标平移将应用于以后绘制的所有内容:

var c = document.getElementById("myCanvas");
var ctx = c.getContext("2d");
        
ctx.linecap = 'round';
ctx.lineWidth = 2;
ctx.fillStyle = 'blue';

ctx.translate((1600-500)/2,0);  //             <-----------

ctx.strokeStyle = '#000000';
ctx.beginPath();
ctx.moveTo(100, 400);             
for (var i = 0; i <= 6; i+=1) {
  ctx.lineTo(100*i,400);
  ctx.lineTo(100*i,405);
  ctx.lineTo(100*i,400);
  ctx.strokeText(i, 100*i, 415);
}    
ctx.moveTo(0, -100);
for (var b = 0; b <= 9; b+=1) {
  ctx.lineTo(0,44.5*b);
  ctx.lineTo(5,44.5*b);
  ctx.lineTo(0,44.5*b);
  if(b<9)
    ctx.strokeText(b+1, 8, 365-44.5*b);
}  
ctx.strokeStyle = '#FF9900';
ctx.stroke();
<!DOCTYPE html>
<html>
  <head>
    <title>Canvas Axis calibration</title>
    <link rel="stylesheet" type="text/css" href="base.css"/> 
  </head>
  <body>
     <canvas id="myCanvas" width="1600" height="500"style="border:1px solid #c3c3c3;">Canvas is not playing!</canvas>
  </body>
</html>

在这里,我假设工程图的宽度为500个单位,这似乎并不完全正确,但是您肯定会看到translate()的结果。可以通过translate()调用来重置setTransform(1, 0, 0, 1, 0, 0)的效果(如果您熟悉齐次坐标和变换矩阵,请注意其顺序已被大量修改,请参阅docs)。实际上,它是一个可以进行各种2D转换(平移,旋转,缩放,倾斜)的矩阵,translate()只是一个便捷函数(等效调用概率为setTransform(1,0,0,1,(1600-500)/2,0),但我还没有尝试过的。)

较小更改:

  • 在循环中添加了var-:否则变量变成全局变量,这对于像i这样的循环变量通常不是问题,但通常被认为是不好的做法
  • 简化为一个stroke()和两个strokeStyle -s。事实是,使用调用stroke()时所设置的设置来绘制线条,圆弧等,这之间的发生无关紧要。因此,在大多数情况下,颜色是黑色的,因为strokeText()是立即变化的,并且颜色变成米色/仅用于stroke()的颜色
  • 将第二组标签移到相应的循环中。我不确定循环是否完全正确,因为可以看到9个标签和9个线段,但绘制了10个线段。

答案 2 :(得分:0)

想想本地起源

当您获得一份建筑计划时,您不会得到一张巨大的纸,因为您要在burbs中进行建筑,所以您想将一些窗户移出夏日的阳光,而您不会用每面墙的新坐标重新绘制平面图。

否,您没有一个适合小图纸的计划,在计划上是一个位置和方向。墙的位置固定在平面图的局部坐标上。

在2D绘图中也是如此。您可以将一个框定义为围绕原点的4个点。 [[-10,-10],[10,-10],[10,10],[-10,10]],并且在绘制时设置其位置和方向,无需将每个点的位置更改为新位置。

通过setTransform在世界上绘制局部坐标

在2D API中,位置和方向是通过转换设置的。

 function drawPath(x,y, points) {  // only position changes
     ctx.setTransform(1,0,0,1,x,y); // set the location
     ctx.beginPath();
     for(const [x,y] of points) {
         ctx.lineTo(x,y);
     }
     ctx.stroke();
 }

 const box = [[-10,-10],[10,-10],[10,10],[-10,10]];
 drawPath(100, 100, box);

并缩放并旋转

 function drawPath(x,y,scale, rotate, points) {
     const xdx = Math.cos(rotate) * scale;
     const xdy = Math.sin(rotate) * scale;
     ctx.setTransform(xdx, xdy, -xdy, xdx, x, y); // set the location
     ctx.beginPath();
     for(const [x,y] of points) {
         ctx.lineTo(x,y);
     }
     ctx.stroke();
 }

  drawPath(100, 100, 2, 0.5, box);

const box = [[-10,-10],[10,-10],[10,10],[-10,10]];
const W = canvas.width;
const H = canvas.height;
const ctx = canvas.getContext("2d");
ctx.font = "2opx arial";
ctx.textAlign = "center";
ctx.textBaseline = "middle";
ctx.fillStyle = "red";
const rand = v => Math.random() * v;
drawRandom();

function drawPath(x, y,scale, rotate, points) {
    const xdx = Math.cos(rotate) * scale;
    const xdy = Math.sin(rotate) * scale;
    ctx.setTransform(xdx, xdy, -xdy, xdx, x, y); // set the location
    ctx.fillText("Hi",0,0);
    ctx.beginPath();
    for(const [x,y] of points) {
        ctx.lineTo(x, y);
    }
    ctx.closePath();
    ctx.setTransform(1, 0, 0, 1, 0, 0); // Resets so line width remains 1 px

    ctx.stroke();
 }


 function drawRandom() {
     drawPath(rand(W), rand(H), rand(2) + 0.5, rand(Math.PI * 2), box);
     setTimeout(drawRandom, 500);
 }
canvas {
   border: 1px solid black;
}
<canvas id="canvas" width ="400" height="400"></canvas>

您需要做的是ctx.setTransform,如果您要制作已绑定的动画,则可能需要ctx.transform。我从不使用ctx.translatectx.scalectx.rotate,因为它们很慢,而且很难描绘出您所在的位置,哦,我是否说它们很慢! >

要重置变换(删除缩放比例,旋转并移回0,0),请调用ctx.resetTransform()ctx.setTransform(1,0,0,1,0,0)

还有更多有关您使用代码的方法。

粒度编码

看起来像要绘制图形。

手动绘制每个刻度线,设置样式以及数十个魔术数字和值不会使它变得很有趣。更糟糕的是,当需要进行更改时,它将永远需要花费时间。

不要重复

您需要像一个懒惰的程序员一样思考。创建函数,这样您就不必一遍又一遍地做同样的事情。

定义样式一次并命名

例如,设置2D上下文样式很麻烦。图纸通常只有几种不同的样式,因此请使用命名样式创建对象

const styles = {
    textHang: {
        textAlign : "center",
        textBaseline : "top",
        fillStyle: "blue",
        font: "16px Arial",
    },
};

还有一个将设置样式的函数

 const setStyle = (style, c = ctx) => Object.assign(c, style);

现在您可以设置样式

 const ctx = myCanvas.getContext("2d");

 setStyle(styles, styles.textHang);
 ctx.fillText("The text", 100, 100);

基本2D点助手

您在2D模式下工作,而2D模式使用很多要点。您将一遍又一遍地添加乘法,复制... 2D点。

仅需7种功能即可减少打字并满足最基本的2D需求

const P2 = (x = 0, y = 0) => ({x,y});
const P2Set = (p, pAs) => (p.x = pAs.x, p.y = pAs.y, p);
const P2Copy = p => P2(p.x, p.y);
const P2Mult = (p, val) => (p.x *= val, p.y *= val, p);
const P2Add = (p, pAdd) => (p.x += pAdd.x, p.y += pAdd.y, p);
const P2Sub = (p, pSub) => (p.x -= pSub.x, p.y -= pSub.y, p);
const P2Dist = (p1, p2) => ((p1.x - p2.x) ** 2 + (p1.y - p2.y) ** 2) ** 0.5;

没有行?二维API

2D API很棒,但是缺少。划一条线太长了,foo bar ....

ctx.linecap = 'round';
ctx.lineWidth = 2;            
ctx.strokeStyle = '#FF9900';
ctx.beginPath();
ctx.moveTo(10, 10);             
ctx.lineTo(410, 410);
ctx.stroke();

没有办法创建函数,使用命名样式,不要输入坐标使用点。

一些常见的2D任务作为函数

const clear = (c = ctx) => (setPos(), c.clearRect(0,0,c.canvas.width,c.canvas.height));
const line = (p1, p2, c = ctx) => (c.moveTo(p1.x, p1.y), c.lineTo(p2.x, p2.y))
const setPos = (p, c = ctx) => p ? c.setTransform(1, 0, 0, 1, p.x, p.y) : c.resetTransform();
const path = (p, path, c = ctx) => {
    c.setTransform(1,0,0,1,p.x,p.y);
    for(const seg of path) {  // each segment
        let first = true;
        for(const p of seg) {  // each point
            first ? (c.moveTo(p.x,p.y), first = false):(c.lineTo(p.x, p.y));
        }
    }
}

示例

以下内容采用以上所有内容并创建2轴。看起来似乎有很多额外的好处,但是随着您增加绘图的复杂性,您很快发现您需要的代码越来越少。

/* Set up the context get common values eg W,H for width and height */
const W = canvas.width;
const H = canvas.height;
const ctx = canvas.getContext("2d");


// Helper functions will use a global ctx, or pass a 2d context as last argument
// P2 is a point. I use p to mean a point
const P2 = (x = 0, y = 0) => ({x,y});
const P2Set = (p, pAs) => (p.x = pAs.x, p.y = pAs.y, p);
const P2Copy = p => P2(p.x, p.y);
const P2Mult = (p, val) => (p.x *= val, p.y *= val, p);
const P2Add = (p, pAdd) => (p.x += pAdd.x, p.y += pAdd.y, p);
const P2Sub = (p, pSub) => (p.x -= pSub.x, p.y -= pSub.y, p);
const P2Dist = (p1, p2) => ((p1.x - p2.x) ** 2 + (p1.y - p2.y) ** 2) ** 0.5;
const setStyle = (style, c = ctx) => Object.assign(c, style);
const clear = (c = ctx) => (setPos(0, c), c.clearRect(0,0,c.canvas.width,c.canvas.height));
const line = (p1, p2, c = ctx) => (c.moveTo(p1.x, p1.y), c.lineTo(p2.x, p2.y))
const setPos = (p, c = ctx) => p ? c.setTransform(1, 0, 0, 1, p.x, p.y) : c.resetTransform();
const path = (p, path, c = ctx) => {
    setPos(p,c);
    for(const seg of path) {  // each segment
        let first = true;
        for(const p of seg) {  // each point
            first ? (c.moveTo(p.x,p.y), first = false):(c.lineTo(p.x, p.y));
        }
    }
}

const styles = { // define any of the 2D context properties you wish to set
    textHang: {textAlign : "center", textBaseline : "top"},
    textLeft: {textAlign : "left", textBaseline : "middle"},
    markTextStyle: {fillStyle: "blue", font: "16px Arial"},
    markStyle: {
        strokeStyle: "black",
        lineCap: "round",
        lineWidth: 2,
    },
};
const paths = {  // Array of arrays of points. each sub array is a line segment
    markLeft: [[P2(-2, 0), P2(5, 0)]],    
    markUp: [[P2(0, 2), P2(0, -5)]],    
}

// Draw an axis from point to point, using mark to mark, lineStyle for the line
// marks is an array of names for each mark, markStyle is the style for the text marks
// markDist is the distance out (90 to the right) to put the text marks
function drawAxis(fromP, toP, mark, lineStyle, marks, markStyle, markDist) {
    const norm = P2Mult(P2Sub(P2Copy(toP), fromP), 1 / P2Dist(fromP, toP));
    const step = P2Mult(P2Sub(P2Copy(toP), fromP), 1 / (marks.length-1));
    const pos = P2Copy(fromP);
    setStyle(lineStyle);
    ctx.beginPath();
    setPos(); // without argument pos is 0,0
    line(fromP, toP);
    for(const m of marks) {
        path(pos, mark);
        P2Add(pos, step);
    }
    ctx.stroke();
    P2Set(pos, fromP);
    setStyle(markStyle);
    for(const m of marks) {
        setPos(pos);
        ctx.fillText(m,-norm.y * markDist, norm.x * markDist)
        P2Add(pos, step)
    }
}

const insetW = W * 0.1; 
const insetH = H * 0.1; 
const axisText =  ["0", "1", "2", "3", "4", "5", "6", "7", "8", "9"];
clear();
drawAxis(
    P2(insetW, H - insetH), P2(insetW, insetH), paths.markLeft,
    styles.markStyle,
    axisText,
    {...styles.textLeft, ...styles.markTextStyle},
    -18
);
drawAxis(
    P2(insetW, H - insetH), P2(W - insetW, H - insetH), paths.markUp,
    styles.markStyle,
    axisText,
    {...styles.textHang, ...styles.markTextStyle},
    6
);
canvas {
   border: 1px solid black;
}
<canvas id="canvas" width ="400" height="400"></canvas>

相关问题