Wie zeichnet man eine glatte Kurve durch N Punkte mit Javascript HTML5 Canvas?

133

Für eine Zeichenanwendung speichere ich die Mausbewegungskoordinaten in einem Array und zeichne sie dann mit lineTo. Die resultierende Linie ist nicht glatt. Wie kann ich eine einzelne Kurve zwischen allen gesammelten Punkten erstellen?

Ich habe gegoogelt, aber ich habe nur 3 Funktionen zum Zeichnen von Linien gefunden: Verwenden Sie für 2 Beispielpunkte einfach lineTo. Für 3 Abtastpunkte quadraticCurveTo, für 4 Abtastpunkte , bezierCurveTo.

(Ich habe versucht, bezierCurveTofür jeweils 4 Punkte im Array ein zu zeichnen , aber dies führt zu Knicken alle 4 Abtastpunkte anstelle einer kontinuierlichen glatten Kurve.)

Wie schreibe ich eine Funktion zum Zeichnen einer glatten Kurve mit 5 Abtastpunkten und darüber hinaus?

Homan
quelle
5
Was meinst du mit "glatt"? Unendlich differenzierbar? Zweimal differenzierbar? Kubische Splines ("Bezier-Kurven") haben viele gute Eigenschaften, sind doppelt differenzierbar und leicht zu berechnen.
Kerrek SB
7
@ Kerrek SB, mit "glatt" meine ich visuell keine Ecken / Höcker usw. erkennen kann
Homan
@sketchfemme, rendern Sie die Linien in Echtzeit oder verzögern Sie das Rendern, bis Sie eine Reihe von Punkten gesammelt haben?
Crashalot
@Crashalot Ich sammle die Punkte in einem Array. Sie benötigen mindestens 4 Punkte, um diesen Algorithmus zu verwenden. Danach können Sie in Echtzeit auf einer Leinwand rendern, indem Sie den Bildschirm bei jedem Aufruf von mouseMove
Homan
1
@sketchfemme: Vergiss nicht, eine Antwort zu akzeptieren. Es ist in Ordnung, wenn es dein eigenes ist .
TJ Crowder

Antworten:

130

Das Problem beim Verbinden nachfolgender Abtastpunkte mit disjunkten Funktionen vom Typ "CurveTo" besteht darin, dass das Zusammentreffen der Kurven nicht glatt ist. Dies liegt daran, dass die beiden Kurven einen Endpunkt gemeinsam haben, jedoch von vollständig getrennten Kontrollpunkten beeinflusst werden. Eine Lösung besteht darin, die Mittelpunkte zwischen den nächsten 2 nachfolgenden Abtastpunkten zu "krümmen". Das Verbinden der Kurven mit diesen neuen interpolierten Punkten ergibt einen reibungslosen Übergang an den Endpunkten (was ein Endpunkt für eine Iteration ist, wird zu einem Kontrollpunkt für die nächste Iteration.) Mit anderen Worten, die beiden nicht zusammenhängenden Kurven haben jetzt viel mehr gemeinsam.

Diese Lösung wurde aus dem Buch "Foundation ActionScript 3.0 Animation: Dinge in Bewegung bringen" extrahiert. S.95 - Rendering-Techniken: Erstellen mehrerer Kurven.

Hinweis: Diese Lösung zeichnet nicht durch jeden der Punkte, wie der Titel meiner Frage lautete (vielmehr nähert sie sich der Kurve durch die Stichprobenpunkte, geht aber nie durch die Stichprobenpunkte), sondern für meine Zwecke (eine Zeichenanwendung). es ist gut genug für mich und visuell kann man den Unterschied nicht erkennen. Es gibt eine Lösung, um alle Beispielpunkte durchzugehen, die jedoch viel komplizierter ist (siehe http://www.cartogrammar.com/blog/actionscript-curves-update/ ).

Hier ist der Zeichnungscode für die Approximationsmethode:

// 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
quelle
+1 Dies funktionierte hervorragend für ein JavaScript / Canvas-Projekt, an dem ich arbeite
Matt
1
Ich bin froh, Ihnen helfen zu können. Zu Ihrer Information, ich habe ein Open Source HTML5 Canvas Zeichenblock gestartet, das ein jQuery Plugin ist. Es sollte ein nützlicher Ausgangspunkt sein. github.com/homanchou/sketchyPad
Homan
4
Das ist gut, aber wie würden Sie die Kurve so gestalten, dass sie alle Punkte durchläuft?
Richard
Soll mit diesem Algorithmus jede aufeinanderfolgende Kurve vom Endpunkt der vorherigen Kurven beginnen?
Lee Brindley
Vielen Dank Homan! Es klappt! Ich habe so viele Tage damit verbracht, es zu lösen. Und hallo von der Delphi Android / iOS Community!
Alitrun
104

Ein bisschen spät, aber fürs Protokoll.

Sie können glatte Linien erzielen, indem Sie Kardinal-Splines verwenden (auch als kanonischer Spline bezeichnet) verwenden, um glatte Kurven zu zeichnen, die durch die Punkte verlaufen.

Ich habe diese Funktion für Leinwand erstellt - sie ist in drei Funktionen unterteilt, um die Vielseitigkeit zu erhöhen. Die Haupt-Wrapper-Funktion sieht folgendermaßen aus:

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

Um eine Kurve zu zeichnen, haben Sie ein Array mit x, y Punkten in der Reihenfolge: x1,y1, x2,y2, ...xn,yn .

Verwenden Sie es so:

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

Die obige Funktion ruft zwei Unterfunktionen auf, eine zur Berechnung der geglätteten Punkte. Dies gibt ein Array mit neuen Punkten zurück - dies ist die Kernfunktion, die die geglätteten Punkte berechnet:

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

Und um die Punkte tatsächlich als geglättete Kurve (oder andere segmentierte Linien, solange Sie ein x, y-Array haben) zu zeichnen:

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

Dies führt zu folgendem:

Beispiel pix

Sie können die Leinwand einfach erweitern, sodass Sie sie stattdessen so nennen können:

ctx.drawCurve(myPoints);

Fügen Sie dem Javascript Folgendes hinzu:

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

Eine optimierte Version finden Sie auf NPM ( npm i cardinal-spline-js) oder auf GitLab .


quelle
3
Zunächst einmal: Das ist wunderschön. :-) Aber wenn man sich dieses Bild ansieht, entsteht dann nicht der (irreführende) Eindruck, dass die Werte auf dem Weg zwischen 9 und 10 tatsächlich unter den Wert Nr. 10 gefallen sind? (Ich zähle von den tatsächlichen Punkten, die ich sehen kann, also wäre # 1 derjenige in der Nähe des oberen Randes der anfänglichen Abwärtsbahn, # 2 derjenige ganz unten [niedrigster Punkt in der Grafik] und so weiter ... )
TJ Crowder
6
Ich möchte nur sagen, dass dies nach Tagen der Suche der einzige Dienstprogramm war, der genau so funktionierte , wie ich es wollte. Vielen Dank
cnp
4
JA JA JA Danke! Ich sprang auf und tanzte vor Freude.
Jeffrey Sun
1
Ihr Code enthält einen Tippfehler. Parameter ptsasollte sein pts, sonst würde es Fehler geben.
gfaceless
2
Vor langer Zeit haben Sie diese Lösung veröffentlicht und mir heute geholfen, ein großes Problem zu lösen. Vielen Dank!
FlexBay
19

Die erste Antwort wird nicht alle Punkte durchlaufen. Dieser Graph durchläuft genau alle Punkte und ist eine perfekte Kurve mit den Punkten als [{x :, y:}] n solchen Punkten.

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
quelle
1
Dies ist bei weitem der einfachste und korrekteste Ansatz.
Haymez
10

Wie Daniel Howard betont , beschreibt Rob Spencer unter http://scaledinnovation.com/analytics/splines/aboutSplines.html, was Sie wollen .

Hier ist eine interaktive Demo: http://jsbin.com/ApitIxo/2/

Hier ist es als Ausschnitt für den Fall, dass jsbin nicht verfügbar ist.

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

Ich fand das gut

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
quelle
6

Ich entscheide mich, etwas hinzuzufügen, anstatt meine Lösung in einem anderen Beitrag zu veröffentlichen. Unten ist die Lösung, die ich baue, vielleicht nicht perfekt, aber bisher ist die Ausgabe gut.

Wichtig: Es werden alle Punkte durchlaufen!

Wenn Sie eine Idee haben, um sie zu verbessern , teilen Sie sie mir bitte mit. Vielen Dank.

Hier ist der Vergleich von vorher nachher:

Geben Sie hier die Bildbeschreibung ein

Speichern Sie diesen Code in HTML, um ihn zu testen.

    <!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.
quelle
5

Probieren Sie KineticJS aus - Sie können einen Spline mit einer Reihe von Punkten definieren. Hier ist ein Beispiel:

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

Siehe Archiv-URL: https://web.archive.org/web/20141204030628/http://www.html5canvastutorials.com/kineticjs/html5-canvas-kineticjs-spline-tutorial/

Eric Rowell
quelle
Erstaunliche Bibliothek! Das Beste für die Aufgabe!
Dziad Borowy
Ja!! Ich brauchte die Funktion blob (), um eine geschlossene Form zu erstellen, die alle Punkte durchläuft.
AwokeKnowing
7
404 Seite nicht gefunden.
Dieter
Ursprünglicher Link - 404 nicht gefunden - siehe web.archive.org/web/20141204030628/http://…
Satels
1

Unglaublich spät, aber inspiriert von Homans genial einfacher Antwort, erlauben Sie mir, eine allgemeinere Lösung zu veröffentlichen (allgemein in dem Sinne, dass Homans Lösung auf Arrays von Punkten mit weniger als 3 Eckpunkten abstürzt):

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
quelle
0

Um die Kardinal-Splines-Methode von K3N zu erweitern und möglicherweise die Bedenken von TJ Crowder bezüglich des Eintauchens von Kurven an irreführenden Stellen auszuräumen, habe ich getCurvePoints()kurz zuvor den folgenden Code in die Funktion eingefügtres.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;
}

Dies erzeugt effektiv einen (unsichtbaren) Begrenzungsrahmen zwischen jedem Paar aufeinanderfolgender Punkte und stellt sicher, dass die Kurve innerhalb dieses Begrenzungsrahmens bleibt - dh. Wenn sich ein Punkt auf der Kurve über / unter / links / rechts von beiden Punkten befindet, ändert sich seine Position so, dass er sich innerhalb des Felds befindet. Hier wird der Mittelpunkt verwendet, dies könnte jedoch verbessert werden, möglicherweise unter Verwendung einer linearen Interpolation.

James Pearce
quelle
0

Wenn Sie die Gleichung der Kurve durch n Punkte bestimmen möchten, gibt Ihnen der folgende Code die Koeffizienten des Polynoms vom Grad n-1 und speichert diese Koeffizienten im coefficients[]Array (beginnend mit dem konstanten Term). Die x-Koordinaten müssen nicht in Ordnung sein. Dies ist ein Beispiel für ein Lagrange-Polynom .

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
quelle