Làm cách nào để vẽ đường cong mượt mà qua N điểm bằng cách sử dụng canvas HTML5 javascript?


133

Đối với một ứng dụng vẽ, tôi đang lưu tọa độ di chuyển chuột vào một mảng rồi vẽ chúng bằng lineTo. Dòng kết quả không được trơn tru. Làm cách nào tôi có thể tạo một đường cong duy nhất giữa tất cả các điểm đã thu thập?

Tôi đã googled nhưng tôi chỉ tìm thấy 3 chức năng để vẽ đường: Đối với 2 điểm mẫu, chỉ cần sử dụng lineTo. Đối với 3 điểm mẫu quadraticCurveTo, cho 4 điểm mẫu , bezierCurveTo.

(Tôi đã thử vẽ a bezierCurveTocho mỗi 4 điểm trong mảng, nhưng điều này dẫn đến việc cứ 4 điểm mẫu thay vì một đường cong trơn tru liên tục.)

Làm cách nào để viết một hàm để vẽ một đường cong trơn tru với 5 điểm mẫu và hơn thế nữa?


5
Bạn có ý nghĩa gì bởi "trơn tru"? Khác biệt vô cùng? Hai lần khác nhau? Các khối vuông ("đường cong Bezier") có nhiều đặc tính tốt và có thể phân biệt hai lần, và đủ dễ để tính toán.
Kerrek SB

7
@Kerrek SB, bằng cách "trơn tru" Ý tôi là trực quan không thể phát hiện bất kỳ góc / nút nào, v.v.
Homan

@sketchfemme, bạn đang kết xuất các dòng trong thời gian thực hay trì hoãn kết xuất cho đến khi thu thập được một loạt các điểm?
Crashalot

@Crashalot Tôi đang thu thập các điểm thành một mảng. Bạn cần ít nhất 4 điểm để sử dụng thuật toán này. Sau đó, bạn có thể kết xuất trong thời gian thực trên một khung vẽ bằng cách xóa màn hình trên mỗi cuộc gọi của mouseMove
Homan

1
@sketchfemme: Đừng quên chấp nhận câu trả lời. Sẽ tốt thôi nếu đó là của riêng bạn .
TJ Crowder

Câu trả lời:


130

Vấn đề với việc nối các điểm mẫu tiếp theo cùng với các hàm loại "đường cong" khác nhau, là nơi các đường cong gặp nhau không trơn tru. Điều này là do hai đường cong chia sẻ một điểm kết thúc nhưng bị ảnh hưởng bởi các điểm kiểm soát hoàn toàn rời rạc. Một giải pháp là "đường cong tới" điểm giữa giữa 2 điểm mẫu tiếp theo. Việc nối các đường cong bằng cách sử dụng các điểm được nội suy mới này sẽ tạo ra sự chuyển tiếp suôn sẻ tại các điểm cuối (điểm cuối của một lần lặp trở thành điểm kiểm soát cho lần lặp tiếp theo.) Nói cách khác, hai đường cong tách rời này có nhiều điểm chung hơn bây giờ.

Giải pháp này đã được trích xuất ra khỏi cuốn sách "Foundation ActionScript 3.0 Animation: Làm cho mọi thứ chuyển động". p.95 - kỹ thuật kết xuất: tạo nhiều đường cong.

Lưu ý: giải pháp này không thực sự rút ra từng điểm, đó là tiêu đề của câu hỏi của tôi (thay vào đó là xấp xỉ đường cong qua các điểm mẫu nhưng không bao giờ đi qua các điểm mẫu), nhưng cho mục đích của tôi (một ứng dụng vẽ), nó đủ tốt cho tôi và trực quan bạn không thể phân biệt được. Có một giải pháp để đi qua tất cả các điểm mẫu, nhưng nó được nhiều hơn nữa phức tạp (xem http://www.cartogrammar.com/blog/actionscript-curves-update/ )

Đây là mã bản vẽ cho phương pháp gần đúng:

// move to the first point
   ctx.moveTo(points[0].x, points[0].y);


   for (i = 1; i < points.length - 2; i ++)
   {
      var xc = (points[i].x + points[i + 1].x) / 2;
      var yc = (points[i].y + points[i + 1].y) / 2;
      ctx.quadraticCurveTo(points[i].x, points[i].y, xc, yc);
   }
 // curve through the last two points
 ctx.quadraticCurveTo(points[i].x, points[i].y, points[i+1].x,points[i+1].y);

+1 Điều này hoạt động rất tốt cho một dự án JavaScript / canvas mà tôi đang làm việc
Matt

1
Vinh dự khi được giup bạn. FYI, tôi đã bắt đầu một bản vẽ canvas html5 mã nguồn mở là một plugin jQuery. Nó nên là một điểm khởi đầu hữu ích. github.com/homanchou/sketchyPad
Homan

4
Điều đó tốt, nhưng làm thế nào bạn có thể tạo đường cong để nó đi qua tất cả các điểm?
Richard

Với thuật toán này là mỗi đường cong liên tiếp có nghĩa là bắt đầu từ điểm cuối đường cong trước?
Lee Brindley

Cảm ơn bạn rất nhiều Homan! Nó hoạt động! Tôi đã dành rất nhiều ngày để giải quyết nó. Và xin chào từ cộng đồng Delphi Android / iOS!
alitrun

104

Một chút muộn, nhưng cho hồ sơ.

Bạn có thể đạt được các đường trơn bằng cách sử dụng các spline hồng y (còn gọi là spline chính tắc) để vẽ các đường cong trơn tru đi qua các điểm.

Tôi đã tạo chức năng này cho canvas - nó được chia thành ba chức năng để tăng tính linh hoạt. Hàm bao bọc chính trông như thế này:

function drawCurve(ctx, ptsa, tension, isClosed, numOfSegments, showPoints) {

    showPoints  = showPoints ? showPoints : false;

    ctx.beginPath();

    drawLines(ctx, getCurvePoints(ptsa, tension, isClosed, numOfSegments));

    if (showPoints) {
        ctx.stroke();
        ctx.beginPath();
        for(var i=0;i<ptsa.length-1;i+=2) 
                ctx.rect(ptsa[i] - 2, ptsa[i+1] - 2, 4, 4);
    }
}

Để vẽ một đường cong có một mảng với các điểm x, y theo thứ tự : x1,y1, x2,y2, ...xn,yn.

Sử dụng nó như thế này:

var myPoints = [10,10, 40,30, 100,10]; //minimum two points
var tension = 1;

drawCurve(ctx, myPoints); //default tension=0.5
drawCurve(ctx, myPoints, tension);

Hàm trên gọi hai hàm phụ, một để tính các điểm được làm mịn. Điều này trả về một mảng với các điểm mới - đây là hàm cốt lõi tính toán các điểm được làm mịn:

function getCurvePoints(pts, tension, isClosed, numOfSegments) {

    // use input value if provided, or use a default value   
    tension = (typeof tension != 'undefined') ? tension : 0.5;
    isClosed = isClosed ? isClosed : false;
    numOfSegments = numOfSegments ? numOfSegments : 16;

    var _pts = [], res = [],    // clone array
        x, y,           // our x,y coords
        t1x, t2x, t1y, t2y, // tension vectors
        c1, c2, c3, c4,     // cardinal points
        st, t, i;       // steps based on num. of segments

    // clone array so we don't change the original
    //
    _pts = pts.slice(0);

    // The algorithm require a previous and next point to the actual point array.
    // Check if we will draw closed or open curve.
    // If closed, copy end points to beginning and first points to end
    // If open, duplicate first points to befinning, end points to end
    if (isClosed) {
        _pts.unshift(pts[pts.length - 1]);
        _pts.unshift(pts[pts.length - 2]);
        _pts.unshift(pts[pts.length - 1]);
        _pts.unshift(pts[pts.length - 2]);
        _pts.push(pts[0]);
        _pts.push(pts[1]);
    }
    else {
        _pts.unshift(pts[1]);   //copy 1. point and insert at beginning
        _pts.unshift(pts[0]);
        _pts.push(pts[pts.length - 2]); //copy last point and append
        _pts.push(pts[pts.length - 1]);
    }

    // ok, lets start..

    // 1. loop goes through point array
    // 2. loop goes through each segment between the 2 pts + 1e point before and after
    for (i=2; i < (_pts.length - 4); i+=2) {
        for (t=0; t <= numOfSegments; t++) {

            // calc tension vectors
            t1x = (_pts[i+2] - _pts[i-2]) * tension;
            t2x = (_pts[i+4] - _pts[i]) * tension;

            t1y = (_pts[i+3] - _pts[i-1]) * tension;
            t2y = (_pts[i+5] - _pts[i+1]) * tension;

            // calc step
            st = t / numOfSegments;

            // calc cardinals
            c1 =   2 * Math.pow(st, 3)  - 3 * Math.pow(st, 2) + 1; 
            c2 = -(2 * Math.pow(st, 3)) + 3 * Math.pow(st, 2); 
            c3 =       Math.pow(st, 3)  - 2 * Math.pow(st, 2) + st; 
            c4 =       Math.pow(st, 3)  -     Math.pow(st, 2);

            // calc x and y cords with common control vectors
            x = c1 * _pts[i]    + c2 * _pts[i+2] + c3 * t1x + c4 * t2x;
            y = c1 * _pts[i+1]  + c2 * _pts[i+3] + c3 * t1y + c4 * t2y;

            //store points in array
            res.push(x);
            res.push(y);

        }
    }

    return res;
}

Và để thực sự vẽ các điểm dưới dạng một đường cong được làm mịn (hoặc bất kỳ đường được phân đoạn nào khác miễn là bạn có một mảng x, y):

function drawLines(ctx, pts) {
    ctx.moveTo(pts[0], pts[1]);
    for(i=2;i<pts.length-1;i+=2) ctx.lineTo(pts[i], pts[i+1]);
}

Điều này dẫn đến kết quả này:

Ví dụ pix

Bạn có thể dễ dàng mở rộng khung vẽ để thay vào đó bạn có thể gọi nó như thế này:

ctx.drawCurve(myPoints);

Thêm phần sau vào javascript:

if (CanvasRenderingContext2D != 'undefined') {
    CanvasRenderingContext2D.prototype.drawCurve = 
        function(pts, tension, isClosed, numOfSegments, showPoints) {
       drawCurve(this, pts, tension, isClosed, numOfSegments, showPoints)}
}

Bạn có thể tìm thấy phiên bản tối ưu hơn này trên NPM ( npm i cardinal-spline-js) hoặc trên GitLab .


3
Trước hết: Điều này là tuyệt đẹp. :-) Nhưng nhìn vào hình ảnh đó, không phải nó mang lại ấn tượng (sai lệch) rằng các giá trị thực sự đã đi dưới giá trị # 10 trên đường giữa # 9 và # 10? (Tôi đang đếm từ các chấm thực tế mà tôi có thể thấy, vì vậy # 1 sẽ là điểm gần đỉnh của quỹ đạo đi xuống ban đầu, # 2 là điểm ở dưới cùng [điểm thấp nhất trong biểu đồ], v.v. )
TJ Crowder

6
Chỉ muốn nói rằng sau nhiều ngày tìm kiếm, đây là công cụ duy nhất thực sự hoạt động đúng như tôi muốn. Cảm ơn rất nhiều
cnp

4
CÓ CÓ CÓ Cảm ơn bạn! Tôi nhảy lên và nhảy múa trong niềm vui.
Jeffrey Sun

1
Có một lỗi loại trong mã của bạn. Thông số ptsanên được pts, nếu không nó sẽ ném erros.
gfacless

2
Từ lâu bạn đã đăng giải pháp này và hôm nay bạn đã giúp tôi giải quyết một vấn đề lớn. Cảm ơn rât nhiều!
ÂlexBay

19

Câu trả lời đầu tiên sẽ không vượt qua tất cả các điểm. Biểu đồ này sẽ chính xác đi qua tất cả các điểm và sẽ là một đường cong hoàn hảo với các điểm là [{x :, y:}] n các điểm như vậy.

var points = [{x:1,y:1},{x:2,y:3},{x:3,y:4},{x:4,y:2},{x:5,y:6}] //took 5 example points
ctx.moveTo((points[0].x), points[0].y);

for(var i = 0; i < points.length-1; i ++)
{

  var x_mid = (points[i].x + points[i+1].x) / 2;
  var y_mid = (points[i].y + points[i+1].y) / 2;
  var cp_x1 = (x_mid + points[i].x) / 2;
  var cp_x2 = (x_mid + points[i+1].x) / 2;
  ctx.quadraticCurveTo(cp_x1,points[i].y ,x_mid, y_mid);
  ctx.quadraticCurveTo(cp_x2,points[i+1].y ,points[i+1].x,points[i+1].y);
}

1
Đây là cách tiếp cận đơn giản và chính xác nhất.
haymez

10

Như Daniel Howard chỉ ra , Rob Spencer mô tả những gì bạn muốn tại http://scaledinnovation.com/analytics/splines/aboutSplines.html .

Đây là bản demo tương tác: http://jsbin.com/ApitIxo/2/

Đây là một đoạn trong trường hợp jsbin bị hỏng.

<!DOCTYPE html>
    <html>
      <head>
        <meta charset=utf-8 />
        <title>Demo smooth connection</title>
      </head>
      <body>
        <div id="display">
          Click to build a smooth path. 
          (See Rob Spencer's <a href="http://scaledinnovation.com/analytics/splines/aboutSplines.html">article</a>)
          <br><label><input type="checkbox" id="showPoints" checked> Show points</label>
          <br><label><input type="checkbox" id="showControlLines" checked> Show control lines</label>
          <br>
          <label>
            <input type="range" id="tension" min="-1" max="2" step=".1" value=".5" > Tension <span id="tensionvalue">(0.5)</span>
          </label>
        <div id="mouse"></div>
        </div>
        <canvas id="canvas"></canvas>
        <style>
          html { position: relative; height: 100%; width: 100%; }
          body { position: absolute; left: 0; right: 0; top: 0; bottom: 0; } 
          canvas { outline: 1px solid red; }
          #display { position: fixed; margin: 8px; background: white; z-index: 1; }
        </style>
        <script>
          function update() {
            $("tensionvalue").innerHTML="("+$("tension").value+")";
            drawSplines();
          }
          $("showPoints").onchange = $("showControlLines").onchange = $("tension").onchange = update;
      
          // utility function
          function $(id){ return document.getElementById(id); }
          var canvas=$("canvas"), ctx=canvas.getContext("2d");

          function setCanvasSize() {
            canvas.width = parseInt(window.getComputedStyle(document.body).width);
            canvas.height = parseInt(window.getComputedStyle(document.body).height);
          }
          window.onload = window.onresize = setCanvasSize();
      
          function mousePositionOnCanvas(e) {
            var el=e.target, c=el;
            var scaleX = c.width/c.offsetWidth || 1;
            var scaleY = c.height/c.offsetHeight || 1;
          
            if (!isNaN(e.offsetX)) 
              return { x:e.offsetX*scaleX, y:e.offsetY*scaleY };
          
            var x=e.pageX, y=e.pageY;
            do {
              x -= el.offsetLeft;
              y -= el.offsetTop;
              el = el.offsetParent;
            } while (el);
            return { x: x*scaleX, y: y*scaleY };
          }
      
          canvas.onclick = function(e){
            var p = mousePositionOnCanvas(e);
            addSplinePoint(p.x, p.y);
          };
      
          function drawPoint(x,y,color){
            ctx.save();
            ctx.fillStyle=color;
            ctx.beginPath();
            ctx.arc(x,y,3,0,2*Math.PI);
            ctx.fill()
            ctx.restore();
          }
          canvas.onmousemove = function(e) {
            var p = mousePositionOnCanvas(e);
            $("mouse").innerHTML = p.x+","+p.y;
          };
      
          var pts=[]; // a list of x and ys

          // given an array of x,y's, return distance between any two,
          // note that i and j are indexes to the points, not directly into the array.
          function dista(arr, i, j) {
            return Math.sqrt(Math.pow(arr[2*i]-arr[2*j], 2) + Math.pow(arr[2*i+1]-arr[2*j+1], 2));
          }

          // return vector from i to j where i and j are indexes pointing into an array of points.
          function va(arr, i, j){
            return [arr[2*j]-arr[2*i], arr[2*j+1]-arr[2*i+1]]
          }
      
          function ctlpts(x1,y1,x2,y2,x3,y3) {
            var t = $("tension").value;
            var v = va(arguments, 0, 2);
            var d01 = dista(arguments, 0, 1);
            var d12 = dista(arguments, 1, 2);
            var d012 = d01 + d12;
            return [x2 - v[0] * t * d01 / d012, y2 - v[1] * t * d01 / d012,
                    x2 + v[0] * t * d12 / d012, y2 + v[1] * t * d12 / d012 ];
          }

          function addSplinePoint(x, y){
            pts.push(x); pts.push(y);
            drawSplines();
          }
          function drawSplines() {
            clear();
            cps = []; // There will be two control points for each "middle" point, 1 ... len-2e
            for (var i = 0; i < pts.length - 2; i += 1) {
              cps = cps.concat(ctlpts(pts[2*i], pts[2*i+1], 
                                      pts[2*i+2], pts[2*i+3], 
                                      pts[2*i+4], pts[2*i+5]));
            }
            if ($("showControlLines").checked) drawControlPoints(cps);
            if ($("showPoints").checked) drawPoints(pts);
    
            drawCurvedPath(cps, pts);
 
          }
          function drawControlPoints(cps) {
            for (var i = 0; i < cps.length; i += 4) {
              showPt(cps[i], cps[i+1], "pink");
              showPt(cps[i+2], cps[i+3], "pink");
              drawLine(cps[i], cps[i+1], cps[i+2], cps[i+3], "pink");
            } 
          }
      
          function drawPoints(pts) {
            for (var i = 0; i < pts.length; i += 2) {
              showPt(pts[i], pts[i+1], "black");
            } 
          }
      
          function drawCurvedPath(cps, pts){
            var len = pts.length / 2; // number of points
            if (len < 2) return;
            if (len == 2) {
              ctx.beginPath();
              ctx.moveTo(pts[0], pts[1]);
              ctx.lineTo(pts[2], pts[3]);
              ctx.stroke();
            }
            else {
              ctx.beginPath();
              ctx.moveTo(pts[0], pts[1]);
              // from point 0 to point 1 is a quadratic
              ctx.quadraticCurveTo(cps[0], cps[1], pts[2], pts[3]);
              // for all middle points, connect with bezier
              for (var i = 2; i < len-1; i += 1) {
                // console.log("to", pts[2*i], pts[2*i+1]);
                ctx.bezierCurveTo(
                  cps[(2*(i-1)-1)*2], cps[(2*(i-1)-1)*2+1],
                  cps[(2*(i-1))*2], cps[(2*(i-1))*2+1],
                  pts[i*2], pts[i*2+1]);
              }
              ctx.quadraticCurveTo(
                cps[(2*(i-1)-1)*2], cps[(2*(i-1)-1)*2+1],
                pts[i*2], pts[i*2+1]);
              ctx.stroke();
            }
          }
          function clear() {
            ctx.save();
            // use alpha to fade out
            ctx.fillStyle = "rgba(255,255,255,.7)"; // clear screen
            ctx.fillRect(0,0,canvas.width,canvas.height);
            ctx.restore();
          }
      
          function showPt(x,y,fillStyle) {
            ctx.save();
            ctx.beginPath();
            if (fillStyle) {
              ctx.fillStyle = fillStyle;
            }
            ctx.arc(x, y, 5, 0, 2*Math.PI);
            ctx.fill();
            ctx.restore();
          }

          function drawLine(x1, y1, x2, y2, strokeStyle){
            ctx.beginPath();
            ctx.moveTo(x1, y1);
            ctx.lineTo(x2, y2);
            if (strokeStyle) {
              ctx.save();
              ctx.strokeStyle = strokeStyle;
              ctx.stroke();
              ctx.restore();
            }
            else {
              ctx.save();
              ctx.strokeStyle = "pink";
              ctx.stroke();
              ctx.restore();
            }
          }

        </script>


      </body>
    </html>


7

Tôi thấy điều này để làm việc tốt

function drawCurve(points, tension) {
    ctx.beginPath();
    ctx.moveTo(points[0].x, points[0].y);

    var t = (tension != null) ? tension : 1;
    for (var i = 0; i < points.length - 1; i++) {
        var p0 = (i > 0) ? points[i - 1] : points[0];
        var p1 = points[i];
        var p2 = points[i + 1];
        var p3 = (i != points.length - 2) ? points[i + 2] : p2;

        var cp1x = p1.x + (p2.x - p0.x) / 6 * t;
        var cp1y = p1.y + (p2.y - p0.y) / 6 * t;

        var cp2x = p2.x - (p3.x - p1.x) / 6 * t;
        var cp2y = p2.y - (p3.y - p1.y) / 6 * t;

        ctx.bezierCurveTo(cp1x, cp1y, cp2x, cp2y, p2.x, p2.y);
    }
    ctx.stroke();
}

6

Tôi quyết định thêm vào, thay vì đăng giải pháp của tôi lên một bài đăng khác. Dưới đây là giải pháp mà tôi xây dựng, có thể không hoàn hảo, nhưng cho đến nay đầu ra vẫn tốt.

Quan trọng: nó sẽ đi qua tất cả các điểm!

Nếu bạn có bất kỳ ý tưởng, để làm cho nó tốt hơn , xin vui lòng chia sẻ với tôi. Cảm ơn.

Dưới đây là so sánh trước sau:

nhập mô tả hình ảnh ở đây

Lưu mã này vào HTML để kiểm tra.

    <!DOCTYPE html>
    <html>
    <body>
    	<canvas id="myCanvas" width="1200" height="700" style="border:1px solid #d3d3d3;">Your browser does not support the HTML5 canvas tag.</canvas>
    	<script>
    		var cv = document.getElementById("myCanvas");
    		var ctx = cv.getContext("2d");
    
    		function gradient(a, b) {
    			return (b.y-a.y)/(b.x-a.x);
    		}
    
    		function bzCurve(points, f, t) {
    			//f = 0, will be straight line
    			//t suppose to be 1, but changing the value can control the smoothness too
    			if (typeof(f) == 'undefined') f = 0.3;
    			if (typeof(t) == 'undefined') t = 0.6;
    
    			ctx.beginPath();
    			ctx.moveTo(points[0].x, points[0].y);
    
    			var m = 0;
    			var dx1 = 0;
    			var dy1 = 0;
    
    			var preP = points[0];
    			for (var i = 1; i < points.length; i++) {
    				var curP = points[i];
    				nexP = points[i + 1];
    				if (nexP) {
    					m = gradient(preP, nexP);
    					dx2 = (nexP.x - curP.x) * -f;
    					dy2 = dx2 * m * t;
    				} else {
    					dx2 = 0;
    					dy2 = 0;
    				}
    				ctx.bezierCurveTo(preP.x - dx1, preP.y - dy1, curP.x + dx2, curP.y + dy2, curP.x, curP.y);
    				dx1 = dx2;
    				dy1 = dy2;
    				preP = curP;
    			}
    			ctx.stroke();
    		}
    
    		// Generate random data
    		var lines = [];
    		var X = 10;
    		var t = 40; //to control width of X
    		for (var i = 0; i < 100; i++ ) {
    			Y = Math.floor((Math.random() * 300) + 50);
    			p = { x: X, y: Y };
    			lines.push(p);
    			X = X + t;
    		}
    
    		//draw straight line
    		ctx.beginPath();
    		ctx.setLineDash([5]);
    		ctx.lineWidth = 1;
    		bzCurve(lines, 0, 1);
    
    		//draw smooth line
    		ctx.setLineDash([0]);
    		ctx.lineWidth = 2;
    		ctx.strokeStyle = "blue";
    		bzCurve(lines, 0.3, 1);
    	</script>
    </body>
    </html>


5

Hãy thử KineticJS - bạn có thể xác định Spline với một loạt các điểm. Đây là một ví dụ:

Url cũ: http://www.html5canvastutorials.com/kineticjs/html5-canvas-kineticjs-spline-tutorial/

Xem url lưu trữ: https://web.archive.org/web/20141204030628/http://www.html5canvastutorials.com/kineticjs/html5-canvas-kineticjs-spline-tutorial/


Lib tuyệt vời! Một trong những tốt nhất cho nhiệm vụ!
Dziad Borowy

Đúng!! Tôi cần hàm blob () để tạo một hình khép kín đi qua tất cả các điểm.
Đánh thức

7
Lôi 404 Không Tim Được Trang.
ăn kiêng 7/2/2016

Original link - 404 dnot tìm thấy - xem web.archive.org/web/20141204030628/http://...
satels

1

Vô cùng muộn màng nhưng được truyền cảm hứng từ câu trả lời đơn giản tuyệt vời của Homan, cho phép tôi đăng một giải pháp tổng quát hơn (nói chung theo nghĩa là giải pháp của Homan gặp sự cố với các mảng có ít hơn 3 đỉnh):

function smooth(ctx, points)
{
    if(points == undefined || points.length == 0)
    {
        return true;
    }
    if(points.length == 1)
    {
        ctx.moveTo(points[0].x, points[0].y);
        ctx.lineTo(points[0].x, points[0].y);
        return true;
    }
    if(points.length == 2)
    {
        ctx.moveTo(points[0].x, points[0].y);
        ctx.lineTo(points[1].x, points[1].y);
        return true;
    }
    ctx.moveTo(points[0].x, points[0].y);
    for (var i = 1; i < points.length - 2; i ++)
    {
        var xc = (points[i].x + points[i + 1].x) / 2;
        var yc = (points[i].y + points[i + 1].y) / 2;
        ctx.quadraticCurveTo(points[i].x, points[i].y, xc, yc);
    }
    ctx.quadraticCurveTo(points[i].x, points[i].y, points[i+1].x, points[i+1].y);
}

0

Để thêm vào phương pháp spline hồng y của K3N và có lẽ giải quyết mối quan tâm của TJ Crowder về các đường cong 'nhúng' ở những vị trí sai lệch, tôi đã chèn đoạn mã sau vào getCurvePoints()hàm, ngay trước đóres.push(x);

if ((y < _pts[i+1] && y < _pts[i+3]) || (y > _pts[i+1] && y > _pts[i+3])) {
    y = (_pts[i+1] + _pts[i+3]) / 2;
}
if ((x < _pts[i] && x < _pts[i+2]) || (x > _pts[i] && x > _pts[i+2])) {
    x = (_pts[i] + _pts[i+2]) / 2;
}

Điều này thực sự tạo ra một hộp giới hạn (vô hình) giữa mỗi cặp điểm liên tiếp và đảm bảo đường cong nằm trong hộp giới hạn này - tức là. nếu một điểm trên đường cong nằm trên / dưới / trái / phải của cả hai điểm, nó sẽ thay đổi vị trí của nó nằm trong hộp. Ở đây điểm giữa được sử dụng, nhưng điều này có thể được cải thiện, có lẽ sử dụng phép nội suy tuyến tính.


0

Nếu bạn muốn xác định phương trình của đường cong qua n điểm thì đoạn mã sau sẽ cung cấp cho bạn các hệ số của đa thức bậc n-1 và lưu các hệ số này vào coefficients[]mảng (bắt đầu từ số hạng không đổi). Các tọa độ x không phải theo thứ tự. Đây là một ví dụ về đa thức Lagrange .

var xPoints=[2,4,3,6,7,10]; //example coordinates
var yPoints=[2,5,-2,0,2,8];
var coefficients=[];
for (var m=0; m<xPoints.length; m++) coefficients[m]=0;
    for (var m=0; m<xPoints.length; m++) {
        var newCoefficients=[];
        for (var nc=0; nc<xPoints.length; nc++) newCoefficients[nc]=0;
        if (m>0) {
            newCoefficients[0]=-xPoints[0]/(xPoints[m]-xPoints[0]);
            newCoefficients[1]=1/(xPoints[m]-xPoints[0]);
    } else {
        newCoefficients[0]=-xPoints[1]/(xPoints[m]-xPoints[1]);
        newCoefficients[1]=1/(xPoints[m]-xPoints[1]);
    }
    var startIndex=1; 
    if (m==0) startIndex=2; 
    for (var n=startIndex; n<xPoints.length; n++) {
        if (m==n) continue;
        for (var nc=xPoints.length-1; nc>=1; nc--) {
        newCoefficients[nc]=newCoefficients[nc]*(-xPoints[n]/(xPoints[m]-xPoints[n]))+newCoefficients[nc-1]/(xPoints[m]-xPoints[n]);
        }
        newCoefficients[0]=newCoefficients[0]*(-xPoints[n]/(xPoints[m]-xPoints[n]));
    }    
    for (var nc=0; nc<xPoints.length; nc++) coefficients[nc]+=yPoints[m]*newCoefficients[nc];
}
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.