当长度超过时,将直线更改为曲线

时间:2015-06-02 12:59:51

标签: javascript html canvas

我想在画布中将几条腿显示为矩形。 基于一个将我的腿的英里数组合在一起的阵列,我已经使算法成比例地在给定的画布上代表它们。



var c = document.getElementById("myCanvas");
var ctx = c.getContext("2d");

var width = c.width;
var somme = 0;
var prevValue = 0;
var recapProp = [];

function drawArrow(fromx, fromy, tox, toy){
    //variables to be used when creating the arrow
    
    var headlen = 5;
    
    var angle = Math.atan2(toy-fromy,tox-fromx);
    
    //starting path of the arrow from the start square to the end square and drawing the stroke
    ctx.beginPath();
    ctx.moveTo(fromx, fromy);
    ctx.lineTo(tox, toy);
    ctx.strokeStyle = "blue";
    ctx.lineWidth = 2;
    ctx.stroke();
    
    //starting a new path from the head of the arrow to one of the sides of the point
    ctx.beginPath();
    ctx.moveTo(tox, toy);
    ctx.lineTo(tox-headlen*Math.cos(angle-Math.PI/7),toy-headlen*Math.sin(angle-Math.PI/7));
    
    //path from the side point of the arrow, to the other side point
    ctx.lineTo(tox-headlen*Math.cos(angle+Math.PI/7),toy-headlen*Math.sin(angle+Math.PI/7));
    
    //path from the side point back to the tip of the arrow, and then again to the opposite side point
    ctx.lineTo(tox, toy);
    ctx.lineTo(tox-headlen*Math.cos(angle-Math.PI/7),toy-headlen*Math.sin(angle-Math.PI/7));
    
    //draws the paths created above
    ctx.strokeStyle = "blue";
    ctx.lineWidth = 2;
    ctx.stroke();
    ctx.fillStyle = "blue";
    ctx.fill();
}

function drawCircle(centerXFrom, centerYFrom){   
    var radius = 3;
    
    ctx.beginPath();
    ctx.arc(centerXFrom, centerYFrom, radius, 0, 2 * Math.PI, false);
    ctx.fillStyle = 'green';
    ctx.fill();
    ctx.lineWidth = 1;
    ctx.strokeStyle = '#003300';
    ctx.stroke();
    ctx.beginPath();
    
}

function sumTab(tabTT){

    for (var i = 0; i < tabTT.length; i++){
         somme += tabTT[i];
    }
    return somme;
}

function findProportion(tabTT){
    var tailleMax = tabTT.length;
    sumTab(tabTT);
    for(var i = 0; i < tabTT.length; i++){
        var percentLeg = (tabTT[i]/somme)*100;
        var tailleLeg = ((width- 20)*percentLeg)/100 ;
        recapProp.push(tailleLeg);
    }
    for(var i = 0; i <= recapProp.length; ++i){
        console.log(prevValue);
        drawCircle(prevValue +5, 5);
        drawArrow(prevValue + 7, 5, prevValue+recapProp[i],5);
        prevValue += recapProp[i];
    }
        
}

var tabTT = [0,5,1,8,2];
findProportion(tabTT);
&#13;
<canvas id="myCanvas" height="200" width="500"></canvas>
&#13;
&#13;
&#13;

然后,我想以矩形形式显示,以形成一个循环(下面不是矩形,但它可以帮助你理解):

enter image description here

我试图操纵quadracticCurveTo(),但这并不是真正的结论......

&#13;
&#13;
var c=document.getElementById("myCanvas");
var ctx=c.getContext("2d");

function drawArrow(fromx, fromy, tox, toy, radius){
    //variables to be used when creating the arrow    
    var headlen = 5;  
    var r = fromx + tox;
    var b = fromy + toy;
    var angle = Math.atan2(r,b);
    
    
    //starting path of the arrow from the start square to the end square and drawing the stroke
    ctx.beginPath();
    ctx.moveTo(fromx+radius, fromy);
    ctx.lineTo(r-radius, fromy);
    ctx.quadraticCurveTo(r, fromy, r, fromy+radius);
    ctx.lineWidth = "2";
    ctx.strokeStyle = '#ff0000';
    ctx.stroke();
    
    //starting a new path from the head of the arrow to one of the sides of the point
    ctx.beginPath();
    ctx.moveTo(r, b);
    ctx.lineTo(r-headlen*Math.cos(angle-Math.PI/7),b-headlen*Math.sin(angle-Math.PI/7));
    
    //path from the side point of the arrow, to the other side point
    ctx.lineTo(r-headlen*Math.cos(angle+Math.PI/7),b-headlen*Math.sin(angle+Math.PI/7));
    
    //path from the side point back to the tip of the arrow, and then again to the opposite side point
    ctx.lineTo(r, b);
    ctx.lineTo(r-headlen*Math.cos(angle-Math.PI/7),b-headlen*Math.sin(angle-Math.PI/7));
    
    //draws the paths created above
    ctx.strokeStyle = "blue";
    ctx.lineWidth = 2;
    ctx.stroke();
    ctx.fillStyle = "blue";
    ctx.fill();
}

drawArrow(50,5, 80,25, 25);
&#13;
<canvas id="myCanvas" height="2000" width="2000"></canvas>
&#13;
&#13;
&#13;

最后,当我知道如何曲线并保持其长度时,我已经创建了我需要的片段。。我计算了画布表面的周长,以便重新计算我腿部的比例。

&#13;
&#13;
var c = document.getElementById("myCanvas");
var ctx = c.getContext("2d");

var width = c.width;
var height = c.height;
var perimetre = (width*2 + height*2);

var up = 0;
var right = 0;
var left = 0;
var bot = 0;

var somme = 0;
var prevValue = 0;
var recapProp = [];

/**********************************/
/*****<<Straight>> Arrows*********/
/********************************/
function drawArrow(fromx, fromy, tox, toy){
    var headlen = 5;    
    var angle = Math.atan2(toy-fromy,tox-fromx);    
    ctx.beginPath();
    ctx.moveTo(fromx, fromy);
    ctx.lineTo(tox, toy);
    ctx.strokeStyle = "blue";
    ctx.lineWidth = 2;
    ctx.stroke();    
    ctx.beginPath();
    ctx.moveTo(tox, toy);
    ctx.lineTo(tox-headlen*Math.cos(angle-Math.PI/7),toy-headlen*Math.sin(angle-Math.PI/7));    
    ctx.lineTo(tox-headlen*Math.cos(angle+Math.PI/7),toy-headlen*Math.sin(angle+Math.PI/7));    
    ctx.lineTo(tox, toy);
    ctx.lineTo(tox-headlen*Math.cos(angle-Math.PI/7),toy-headlen*Math.sin(angle-Math.PI/7));   
    ctx.strokeStyle = "blue";
    ctx.lineWidth = 2;
    ctx.stroke();
    ctx.fillStyle = "blue";
    ctx.fill();
}


/**********************************/
/************Points***************/
/********************************/
function drawCircle(centerXFrom, centerYFrom){   
    var radius = 3;    
    ctx.beginPath();
    ctx.arc(centerXFrom, centerYFrom, radius, 0, 2 * Math.PI, false);
    ctx.fillStyle = 'green';
    ctx.fill();
    ctx.lineWidth = 1;
    ctx.strokeStyle = '#003300';
    ctx.stroke();
    ctx.beginPath();    
}


function sumTab(tabTT){
    for (var i = 0; i < tabTT.length; i++){
         somme += tabTT[i];
    }
    return somme;
}

/***************************************************/
/************Get length for each leg***************/
/*************************************************/
function findProportion(tabTT){
    var tailleMax = tabTT.length;
    sumTab(tabTT);
    
    for(var i = 0; i < tabTT.length; i++){
        var percentLeg = (tabTT[i]/somme)*100;
        var tailleLeg = ((perimetre - 20)*percentLeg)/100 ;
        recapProp.push(tailleLeg);
    }
    
    /* For each leg I draw the circle and the arrow, due to the length calculated previously. If the length > the width of the canva, the arrow has to be curved */
    for(var i = 0; i <= recapProp.length; ++i){
        if(prevValue > width && top == 1){
            drawCircle(prevValue +5, 5);
            drawArrowBot(prevValue + 7, 5, prevValue+recapProp[i],5);   
            right = 1;
            top = 0;
        }       
        else if(prevValue > height && right == 1){
            drawCircle(prevValue +5, 5);
            drawArrowLeft(prevValue + 7, 5, prevValue+recapProp[i],5);   
            bot = 1;
            right = 0;
        }
        else if (prevValue > width && bot == 1){
            drawCircle(prevValue +5, 5);
            drawArrowTop(prevValue + 7, 5, prevValue+recapProp[i],5);   
            bot = 0;
            left = 0;   
        }
        else {
            drawCircle(prevValue +5, 5);
            drawArrow(prevValue + 7, 5, prevValue+recapProp[i],5);             
        }
       
        prevValue += recapProp[i];
    }
        
}

var tabTT = [0,5,1,8,2];
findProportion(tabTT);
&#13;
<canvas id="myCanvas" height="200" width="500"  style="border:1px solid #000000;"></canvas>
&#13;
&#13;
&#13;

我已经对我的所有代码进行了评论,以帮助您了解逻辑和我想要的内容。

那么,是否可以以通用方式对线进行曲线化?

3 个答案:

答案 0 :(得分:2)

我可能会这样做:

  • 根据分辨率
  • 定义包含条目数的保留数组
  • 将线条映射到该数组设置1,这将是一个线条范围,0为间隙。
  • 定义一个目标形状,例如椭圆形(可以是任何形状!),它由与阵列分辨率相同的许多部分组成。将每个零件及其坐标存储在一个数组中(与行数组相同)。
  • 使用形状数组和线数组之间的插值来变形每个部分

现在,你可以将线条生成几乎任何形状和形状的你想要的。

提示:您当然可以通过第一次直接映射来跳过一个形状 提示2:可以在标准化坐标中定义形状,这样可以更容易地进行平移和缩放。

实施例

这里我们定义一个圆角正方形和圆形,然后将线条映射到任一个,我们可以在形状之间进行变换以找到我们喜欢的组合并使用它(注意:因为此示例中的方形在“右上角”开始角落而不是圆圈的位置是0°,也会有一个小的旋转,这可以作为练习单独处理。

圆角正方形可能是一个兔子(对于更“紧”的圆角正方形,你可以使用 cubic Bezier而不是像这里的二次方)。关键点在于形状可以独立于线条本身来定义。这可能是矫枉过正,但它不是那么复杂,而且它是多功能的,即。通用的。

有关向线条添加箭头的一种方法,请参阅this answer

var ctx = document.querySelector("canvas").getContext("2d"),
    resolution = 2000,
    raster = new Uint8Array(resolution),      // line raster array
    shape = new Float32Array(resolution * 2), // target shape array (x2 for x/y)
    shape2 = new Float32Array(resolution * 2),// target shape array 2
    lines = [100, 70, 180, 35],               // lines, lengths only
    tLen = 0,                                 // total length of lines + gaps
    gap = 20,                                 // gap in pixels
    gapNorm,                                  // normalized gap value for mapping
    p = 0,                                    // position in lines array
    radius = 100,                             // target circle radius
    angleStep = Math.PI * 2 / resolution,     // angle step to reach circle / res.
    cx = 150, cy = 150,                       // circle center
    interpolation = 0.5,                      // t for interpolation
    i;

// get total length of lines + gaps so we can normalize
for(i = 0; i < lines.length; i++) tLen += lines[i];
tLen += (lines.length - 2) * gap;
gapNorm = gap / tLen * 0.5;

// convert line and gap ranges to "on" in the lines array
for(i = 0; i < lines.length; i++) {
  var sx = p,                                 // start position in lines array
      ex = p + ((lines[i] / tLen) * resolution)|0; // end position in lines array (int)
  
  // fill array
  while(sx <= ex) raster[sx++] = 1;

  // update arrqay pointer incl. gap
  p = ex + ((gapNorm * resolution)|0);
}

// Create a circle target shape split into same amount of segments as lines array:
p = 0;                                        // reset pointer for shape array
for(var angle = 0; angle < Math.PI*2; angle += angleStep) {
  shape[p++] = cx + radius * Math.cos(angle);
  shape[p++] = cy + radius * Math.sin(angle);
}

// create a rounded rectangle
p = i = 0;
var corners = [
    {x1: 250, y1: 150, cx: 250, cy: 250, x2: 150, y2: 250}, // bottom-right
    {x1: 150, y1: 250, cx: 50, cy: 250, x2: 50, y2: 150},   // bottom-left
    {x1: 50, y1: 150, cx: 50, cy: 50, x2: 150, y2: 50},     // upper-left
    {x1: 150, y1: 50, cx: 250, cy: 50, x2: 250, y2: 150}    // upper-right
  ],
   c, cres = resolution * 0.25;
while(c = corners[i++]) {
  for(var t = 0; t < cres; t++) {
    var pos = getQuadraticPoint(c.x1, c.y1, c.cx, c.cy, c.x2, c.y2, t / cres);
    shape2[p++] = pos.x;
    shape2[p++] = pos.y;
  }
}


// now we can map the lines array onto our shape depending on the values
// interpolation. Make it a reusable function so we can regulate the "morph"
function map(raster, shape, shape2, t) {

  ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height);
  ctx.beginPath();
  
  for(var i = 0, x, y, x1, y1, x2, y2, prev = 0; i < resolution; i++) {

    x1 = shape[i*2];
    y1 = shape[i*2 + 1];
    x2 = shape2[i*2];
    y2 = shape2[i*2 + 1];
    x = x1 + (x2 - x1) * t;
    y = y1 + (y2 - y1) * t;
    
    // do we have a change?
    if (prev !== raster[i]) {
      if (raster[i]) {  // it's on, was off. create sub-path
        ctx.moveTo(x, y);
      }
      else {           // it's off, was on, render and reset path
        ctx.stroke();
        ctx.beginPath();

        // create "arrow"
        ctx.moveTo(x + 3, y);
        ctx.arc(x, y, 3, 0, 6.28);
        ctx.fill();
        ctx.beginPath();
      }
    }
    
    // add segment if on
    else if (raster[i]) {
      ctx.lineTo(x, y);
    }
    
    prev = raster[i];
  }
}
ctx.fillStyle = "red";
map(raster, shape, shape2, interpolation);

document.querySelector("input").onchange = function() {
  map(raster, shape, shape2, +this.value / 100);
};

function getQuadraticPoint(z0x, z0y, cx, cy, z1x, z1y, t) {

  var t1 = (1 - t),       // (1 - t)
      t12 = t1 * t1,      // (1 - t) ^ 2
      t2 = t * t,         // t ^ 2
      t21tt = 2 * t1 * t; // 2(1-t)t

  return {
    x: t12 * z0x + t21tt * cx + t2 * z1x,
    y: t12 * z0y + t21tt * cy + t2 * z1y
  }
}
<script src="https://cdn.rawgit.com/epistemex/slider-feedback/master/sliderfeedback.min.js"></script>

<label>Interpolation: <input type="range" min=0 max=400 value=50></label><br>
<canvas width=400 height=400></canvas>

答案 1 :(得分:2)

计算使二次贝塞尔曲线成为指定长度的中间控制点。

enter image description here

<强>假设:

  • p0p2:QCurves的起点和终点。
  • length:二次贝塞尔曲线的理想弧长。

您可以计算使QCurve的总弧长等于length的控制点:

  1. 计算p0和amp;之间的中点。 P2
  2. 计算p0和amp;之间的角度。 P2
  3. 计算与指定距离处的中点垂直的点(p1)。这是一个可能的控制点。垂直角度是从步骤#2减去90度的计算角度。
  4. 使用p0,p1&amp;计算QCurve的弧长。 p2(calculatedLength)。
  5. 如果calculatedLength等于所需的length,则您拥有正确的中间控制点。
  6. 以下是示例代码和演示:

    var canvas=document.getElementById("canvas");
    var ctx=canvas.getContext("2d");
    var cw=canvas.width;
    var ch=canvas.height;
    function reOffset(){
      var BB=canvas.getBoundingClientRect();
      offsetX=BB.left;
      offsetY=BB.top;        
    }
    var offsetX,offsetY;
    reOffset();
    window.onscroll=function(e){ reOffset(); }
    
    
    var $length=$('#length');
    var PI2=Math.PI*2;
    var radius=5+1; // 5==fill, 1=added stroke
    var p0={x:50,y:100,color:'red'};
    var p2={x:175,y:150,color:'gold'};
    var p1={x:0,y:0,color:'green'};
    var midpoint={x:0,y:0,color:'purple'};
    var perpendicularPoint={x:0,y:0,color:'cyan'};
    //var points=[p0,p1,p2];
    //var draggingPoint=-1;
    
    setQLength(p0,p2,150,1);
    
    draw();
    
    
    
    function draw(){
      ctx.clearRect(0,0,cw,ch);
      ctx.beginPath();
      ctx.moveTo(p0.x,p0.y);
      ctx.quadraticCurveTo(p1.x,p1.y,p2.x,p2.y);
      ctx.strokeStyle='blue';
      ctx.lineWidth=3;
      ctx.stroke();
      dot(p0);
      dot(p1);
      dot(p2);
      dot(midpoint);
      dot(perpendicularPoint)
      $length.text('Curve length: '+parseInt(QCurveLength(p0,p1,p2)))
    }
    //
    function dot(p){
      ctx.beginPath();
      ctx.arc(p.x,p.y,radius,0,PI2);
      ctx.closePath();
      ctx.fillStyle=p.color;
      ctx.fill();
      ctx.lineWidth=1;
      ctx.strokeStyle='black';
      ctx.stroke();
    }
    
    function setQLength(p0,p2,length,tolerance){
      var dx=p2.x-p0.x;
      var dy=p2.y-p0.y;
      var alength=Math.sqrt(dx*dx+dy*dy);
    
      // impossible to fit
      if(alength>length){
        alert('The points are too far apart to have length='+length);
        return;
      }
    
      // fit
      for(var distance=0;distance<200;distance++){
        // calc the point perpendicular to midpoint at specified distance
        var p=pointPerpendicularToMidpoint(p0,p2,distance);
        p1.x=p.x;
        p1.y=p.y;
        // calc the result qCurve length
        qlength=QCurveLength(p0,p1,p2);
        // draw the curve
        draw();
        // break if qCurve's length is within tolerance
        if(Math.abs(length-qlength)<tolerance){
          break;
        }
      }
      return(p1);
    }
    
    
    function pointPerpendicularToMidpoint(p0,p2,distance){
      var dx=p2.x-p0.x;
      var dy=p2.y-p0.y;
      var perpAngle=Math.atan2(dy,dx)-Math.PI/2;
      midpoint={ x:p0.x+dx*0.50, y:p0.y+dy*0.50, color:'purple' };
      perpendicularPoint={
        x: midpoint.x+distance*Math.cos(perpAngle),
        y: midpoint.y+distance*Math.sin(perpAngle),
        color:'cyan'        
      };
      return(perpendicularPoint);
    }
    
    // Attribution: Mateusz Matczak
    // http://www.malczak.linuxpl.com/blog/quadratic-bezier-curve-length/
    function QCurveLength(p0,p1,p2){
      var a={x: p0.x-2*p1.x+p2.x, y: p0.y-2*p1.y+p2.y}
      var b={x:2*p1.x-2*p0.x, y:2*p1.y-2*p0.y}
      var A=4*(a.x*a.x+a.y*a.y);
      var B=4*(a.x*b.x+a.y*b.y);
      var C=b.x*b.x+b.y*b.y;
      var Sabc=2*Math.sqrt(A+B+C);
      var A2=Math.sqrt(A);
      var A32=2*A*A2;
      var C2=2*Math.sqrt(C);
      var BA=B/A2;
      if(A2==0 || BA+C2==0){
        var dx=p2.x-p0.x;
        var dy=p2.y-p0.y;
        var length=Math.sqrt(dx*dx+dy*dy);
      }else{
        var length=(A32*Sabc+A2*B*(Sabc-C2)+(4*C*A-B*B)*Math.log((2*A2+BA+Sabc)/(BA+C2)))/(4*A32)
        }
      return(length);
    };
    body{ background-color: ivory; }
    #canvas{border:1px solid red; margin:0 auto; }
    <script src="https://ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js"></script>
    <h4 id=length>Curve length:</h4>
    <h4>Red,Gold == start and end points<br>Purple == midpoint between start & end<br>Cyan == middle control point.</h4>
    <canvas id="canvas" width=300 height=300></canvas>

答案 2 :(得分:0)

bidAmount