2015-06-02 36 views
3

我想在画布中将多条腿显示为矩形。 基于一个排列我的腿的数组,我已经使算法在给定的画布上按比例表示它们。长度超过时将直线变为曲线

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);
<canvas id="myCanvas" height="200" width="500"></canvas>

然后,我想在一个长方形的形式来显示,然后,做一个循环(以下不是矩形,但它可以帮助你理解):

enter image description here

我试图操纵quadracticCurveTo(),但那不是真的确凿..

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);
<canvas id="myCanvas" height="2000" width="2000"></canvas>

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

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);
<canvas id="myCanvas" height="200" width="500" style="border:1px solid #000000;"></canvas>

我的评论我的代码,以帮助您了解的逻辑和我想要的。

那么,是否有可能以一种通用的方式来曲线?

回答

3

我可能会做这样的事情:

  • 定义与基于分辨率
  • 地图行成阵列设置1的很会有一个行范围,0的条目数保持阵列为差距。
  • 定义目标形状,如椭圆形(可以是任何真正的形状!),它由与数组分辨率相同的许多部分组成。存储每个零件并将其坐标存储在一个数组中(与线数组长度相同)。
  • 顶点变形使用形状阵列和线阵

之间的内插现在可以生产线到几乎任何形状和形式,你的愿望每个部分。

提示:您当然可以在第一次直接映射时跳过一个形状。提示2:可以在标准化坐标中定义形状,这样可以更轻松地翻译和缩放它们。

这里我们定义一个圆角正方形和圆圈,则线映射到或者,我们可以在形状之间变形找到我们喜欢的组合,并使用该(注:如在这个例子中,平方开始在“右上角”而不是在圆圈有0°的地方,也会有小的旋转,这可以作为练习单独处理)。

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

请参阅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

哇感谢您的解释,这是令人难以置信!直到下个星期一我都无法工作,但它肯定会帮助我。我已经在今天早些时候处理过一个“预定义”的圆角矩形,并且在它上面添加阴影显然更加简单,正如您提到的那样:-) – So4ne

2

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

enter image description here

考虑:

  • p0p2:在QCurves出发点和落脚点。
  • length:二次贝塞尔曲线的期望弧长。

可以计算出控制点,使QCurve总弧长等于length

  1. 计算P0 & P2之间的中点。
  2. 计算p0 & p2之间的夹角。
  3. 计算与指定距离处的中点垂直的点(p1)。这是一个可能的控制点。垂直角度是从步骤#2减去90度计算出的角度。
  4. 使用p0,p1 & p2(calculatedLength)计算QCurve的弧长。
  5. 如果calculatedLength等于想要的length,那么您已经拥有了正确的中间控制点。

这里的示例代码和演示:

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>