jak narysować gładką krzywą przez N punktów za pomocą płótna javascript HTML5?

135

W przypadku aplikacji do rysowania zapisuję współrzędne ruchu myszy w tablicy, a następnie rysuję je za pomocą lineTo. Wynikowa linia nie jest gładka. Jak mogę utworzyć pojedynczą krzywą między wszystkimi zebranymi punktami?

Wyszukałem w Google, ale znalazłem tylko 3 funkcje do rysowania linii: W przypadku 2 punktów próbkowania po prostu użyj lineTo. Do 3 punktów próbki quadraticCurveTo, z 4 punktów na próbki bezierCurveTo.

(Próbowałem narysować a bezierCurveTodla każdych 4 punktów w tablicy, ale prowadzi to do załamań co 4 punkty próbkowania, zamiast ciągłej gładkiej krzywej.)

Jak napisać funkcję rysującą gładką krzywą z 5 punktami próbkowania i więcej?

Homan
źródło
5
Co masz na myśli mówiąc „gładko”? Nieskończenie różniczkowalne? Dwukrotnie różniczkowalna? Krzywe sześcienne („krzywe Beziera”) mają wiele dobrych właściwości, są dwukrotnie różniczkowalne i dość łatwe do obliczenia.
Kerrek SB
7
@Kerrek SB, by „wygładzić” Znaczy wizualnie nie można wykryć żadnych narożników / guzki itp
Homan
@sketchfemme, czy renderujesz linie w czasie rzeczywistym, czy opóźniasz renderowanie do momentu zebrania kilku punktów?
Crashalot
@ Crashalot Zbieram punkty do tablicy. Aby korzystać z tego algorytmu, potrzebujesz co najmniej 4 punktów. Następnie możesz renderować w czasie rzeczywistym na płótnie, czyszcząc ekran przy każdym wywołaniu mouseMove
Homan Kwietnia
1
@sketchfemme: Nie zapomnij przyjąć odpowiedzi. W porządku, jeśli to twój własny .
TJ Crowder

Odpowiedzi:

132

Problem z łączeniem kolejnych punktów próbkowania wraz z rozłącznymi funkcjami typu „curveTo” polega na tym, że miejsce, w którym spotykają się krzywe, nie jest gładkie. Dzieje się tak, ponieważ dwie krzywe mają wspólny punkt końcowy, ale wpływają na nie całkowicie rozłączne punkty kontrolne. Jednym z rozwiązań jest „zakrzywienie” do punktów środkowych między następnymi 2 kolejnymi punktami próbkowania. Łączenie krzywych za pomocą tych nowych interpolowanych punktów zapewnia płynne przejście w punktach końcowych (punkt końcowy dla jednej iteracji staje się punktem kontrolnym dla następnej iteracji). Innymi słowy, dwie rozłączne krzywe mają teraz znacznie więcej wspólnego.

To rozwiązanie zostało wyodrębnione z książki „Foundation ActionScript 3.0 Animation: Making Things Move”. s.95 - techniki renderowania: tworzenie wielu krzywych.

Uwaga: to rozwiązanie właściwie nie rysuje przez każdy z punktów, co było tytułem mojego pytania (raczej aproksymuje krzywą przez punkty próbkowania, ale nigdy nie przechodzi przez punkty próbkowania), ale dla moich celów (aplikacja do rysowania), to jest dla mnie wystarczająco dobre i wizualnie nie możesz odróżnić. Jest to rozwiązanie, aby przejść przez wszystkie punkty próbkowania, ale jest o wiele bardziej skomplikowane (patrz http://www.cartogrammar.com/blog/actionscript-curves-update/ )

Oto kod rysowania metody aproksymacji:

// 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);
Homan
źródło
+1 To działało świetnie w przypadku projektu JavaScript / płótna, nad którym pracuję
Matt
1
Cieszę się, że mogłem pomóc. FYI, uruchomiłem platformę do rysowania na płótnie html5 typu open source, która jest wtyczką jQuery. Powinien być przydatnym punktem wyjścia. github.com/homanchou/sketchyPad
Homan,
4
To dobrze, ale jak zrobić krzywą, żeby przechodziła przez wszystkie punkty?
Richard
Czy w przypadku tego algorytmu każda kolejna krzywa ma zaczynać się od punktu końcowego poprzedniej krzywej?
Lee Brindley
Dziękuję bardzo Homan! To działa! Spędziłem tyle dni, aby go rozwiązać. Cześć od społeczności Delphi Android / iOS!
alitrun
109

Trochę późno, ale dla przypomnienia.

Możesz uzyskać gładkie linie, używając splajnów kardynalnych (inaczej splajnów kanonicznych) do rysowania gładkich krzywych przechodzących przez punkty.

Zrobiłem tę funkcję dla płótna - jest podzielona na trzy funkcje, aby zwiększyć wszechstronność. Główna funkcja opakowania wygląda następująco:

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);
    }
}

Zwrócić krzywą mieć tablicę X, Y punktów w kolejności: x1,y1, x2,y2, ...xn,yn.

Użyj tego w ten sposób:

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);

Powyższa funkcja wywołuje dwie podfunkcje, jedną do obliczenia wygładzonych punktów. Zwraca tablicę z nowymi punktami - to jest główna funkcja, która oblicza wygładzone punkty:

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;
}

Aby faktycznie narysować punkty jako wygładzoną krzywą (lub dowolną inną segmentowaną linię, o ile masz tablicę 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]);
}

Skutkuje to:

Przykład pix

Możesz łatwo rozszerzyć płótno, aby zamiast tego nazwać je w ten sposób:

ctx.drawCurve(myPoints);

Dodaj do javascript:

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

Możesz znaleźć bardziej zoptymalizowaną wersję tego w NPM ( npm i cardinal-spline-js) lub w GitLab .


źródło
3
Po pierwsze: to jest wspaniałe. :-) Ale patrząc na ten obrazek, czy nie daje to (mylącego) wrażenia, że ​​wartości faktycznie spadły poniżej wartości nr 10 na trasie pomiędzy nr 9 a 10? (Liczę na podstawie rzeczywistych kropek, które widzę, więc numer 1 byłby tym w pobliżu szczytu początkowej trajektorii w dół, # 2 tym na samym dole [najniższy punkt na wykresie] i tak dalej ... )
TJ Crowder
6
Chcę tylko powiedzieć, że po wielu dniach poszukiwań było to jedyne narzędzie, które faktycznie działało dokładnie tak , jak chciałem.
Wielkie
4
TAK TAK TAK Dziękuję! Podskoczyłem i zatańczyłem z radości.
Jeffrey Sun
1
@TJCrowder (przepraszam za trochę (?!) Późną obserwację :)) Spadek jest wynikiem obliczenia napięcia. Aby "trafić" w następny punkt pod odpowiednim kątem / kierunku, napięcie zmusza krzywą do opadania, aby mogła kontynuować pod odpowiednim kątem dla następnego segmentu (kąt prawdopodobnie nie jest tutaj dobrym słowem, mój angielski nie ma ... .). Naprężenie jest obliczane na podstawie dwóch poprzednich i dwóch następnych punktów. Krótko mówiąc: nie, nie przedstawia żadnych rzeczywistych danych, a jedynie obliczenia napięcia.
3
Dawno temu opublikowałeś to rozwiązanie i dzisiaj pomogłeś mi rozwiązać duży problem. Dziękuję Ci bardzo!
ÂlexBay
20

Pierwsza odpowiedź nie przejdzie przez wszystkie punkty. Ten wykres przejdzie dokładnie przez wszystkie punkty i będzie doskonałą krzywą z punktami jako [{x:, y:}] n takich punktów.

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);
}
Abhishek Kanthed
źródło
1
Jest to zdecydowanie najprostsze i najbardziej poprawne podejście.
haymez
11

Jak zauważa Daniel Howard , Rob Spencer opisuje, czego chcesz, pod adresem http://scaledinnovation.com/analytics/splines/aboutSplines.html .

Oto interaktywne demo: http://jsbin.com/ApitIxo/2/

Tutaj jest to fragment kodu na wypadek, gdyby jsbin nie działał.

<!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>

Daniel Patru
źródło
7

Okazało się, że to działa ładnie

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();
}
Roy Aarts
źródło
6

Postanawiam dodać, zamiast publikować moje rozwiązanie w innym poście. Poniżej przedstawiam rozwiązanie, które buduję, może nie jest idealne, ale jak na razie wyjście jest dobre.

Ważne: przejdzie przez wszystkie punkty!

Jeśli masz jakiś pomysł, aby to ulepszyć , podziel się ze mną. Dzięki.

Oto porównanie przed po:

wprowadź opis obrazu tutaj

Zapisz ten kod w formacie HTML, aby go przetestować.

    <!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>

Eric K.
źródło
5

Wypróbuj KineticJS - możesz zdefiniować splajn za pomocą tablicy punktów. Oto przykład:

Stary adres URL: http://www.html5canvastutorials.com/kineticjs/html5-canvas-kineticjs-spline-tutorial/

Zobacz adres URL archiwum: https://web.archive.org/web/20141204030628/http://www.html5canvastutorials.com/kineticjs/html5-canvas-kineticjs-spline-tutorial/

Eric Rowell
źródło
Niesamowita lib! Najlepszy do zadania!
Dziad Borowy
tak!! Potrzebowałem funkcji blob (), aby utworzyć zamknięty kształt, który przechodzi przez wszystkie punkty.
AwokeKnowing
7
404. Nie znaleziono strony.
dieter
Oryginalny link - 404 d nie znaleziono - patrz web.archive.org/web/20141204030628/http://…
satels
2

Niezwykle późno, ale zainspirowana genialnie prostą odpowiedzią Homana, pozwólcie mi opublikować bardziej ogólne rozwiązanie (ogólne w tym sensie, że rozwiązanie Homana rozbija się na tablicach punktów z mniej niż 3 wierzchołkami):

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);
}
mxl
źródło
0

Aby dodać do kardynalnej metody splajnów K3N i być może rozwiązać obawy TJ Crowdera dotyczące „zanurzania” krzywych w mylących miejscach, wstawiłem następujący kod do getCurvePoints()funkcji, tuż przedres.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;
}

To skutecznie tworzy (niewidoczną) ramkę graniczną między każdą parą kolejnych punktów i zapewnia, że ​​krzywa pozostaje w tej ramce ograniczającej - tj. jeśli punkt na krzywej znajduje się powyżej / poniżej / po lewej / prawej stronie obu punktów, zmienia swoje położenie tak, aby znalazł się w ramce. Tutaj używany jest punkt środkowy, ale można to poprawić, być może stosując interpolację liniową.

James Pearce
źródło
0

Jeśli chcesz wyznaczyć równanie krzywej przez n punktów, to poniższy kod poda współczynniki wielomianu stopnia n-1 i zapisze te współczynniki do coefficients[]tablicy (zaczynając od stałego członu). Współrzędne x nie muszą być w porządku. To jest przykład wielomianu Lagrange'a .

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];
}
Kevin Bertman
źródło
0

Ten kod jest dla mnie idealny:

this.context.beginPath();
this.context.moveTo(data[0].x, data[0].y);
for (let i = 1; i < data.length; i++) {
  this.context.bezierCurveTo(
    data[i - 1].x + (data[i].x - data[i - 1].x) / 2,
    data[i - 1].y,
    data[i - 1].x + (data[i].x - data[i - 1].x) / 2,
    data[i].y,
    data[i].x,
    data[i].y);
}

masz poprawną gładką linię i prawidłowe punkty końcowe UWAGA! (y = "wysokość płótna" - y);

beka shkubuliani
źródło