¿Cómo dibujar una curva suave a través de N puntos usando JavaScript HTML5 Canvas?

133

Para una aplicación de dibujo, guardo las coordenadas de movimiento del mouse en una matriz y luego las dibujo con lineTo. La línea resultante no es suave. ¿Cómo puedo producir una sola curva entre todos los puntos reunidos?

Busqué en Google pero solo encontré 3 funciones para dibujar líneas: para 2 puntos de muestra, simplemente use lineTo. Para 3 puntos de muestra quadraticCurveTo, para 4 puntos de muestra, bezierCurveTo.

(Traté de dibujar un bezierCurveTopor cada 4 puntos en la matriz, pero esto conduce a torceduras cada 4 puntos de muestra, en lugar de una curva suave continua).

¿Cómo escribo una función para dibujar una curva suave con 5 puntos de muestra y más?

Homan
fuente
55
¿Qué quieres decir con "suave"? Infinitamente diferenciable? ¿Dos veces diferenciable? Las splines cúbicas ("curvas de Bezier") tienen muchas buenas propiedades y son dos veces diferenciables y fáciles de calcular.
Kerrek SB
77
@Kerrek SB, por "suave" quiero decir visualmente no puedo detectar ninguna esquina / cúspide etc.
Homan
@sketchfemme, ¿estás renderizando las líneas en tiempo real o retrasando el renderizado hasta después de acumular un montón de puntos?
Crashalot
@Crashalot Estoy recopilando los puntos en una matriz. Necesita al menos 4 puntos para utilizar este algoritmo. Después de eso, puede renderizar en tiempo real en un lienzo despejando la pantalla en cada llamada de mouseMove
Homan
1
@sketchfemme: No olvides aceptar una respuesta. Está bien si es tuyo .
TJ Crowder

Respuestas:

130

El problema con la unión de puntos de muestra posteriores junto con funciones de tipo "curveTo" disjuntas, es que donde las curvas se encuentran no es suave. Esto se debe a que las dos curvas comparten un punto final pero están influenciadas por puntos de control completamente disjuntos. Una solución es "curvarse" a los puntos medios entre los siguientes 2 puntos de muestra posteriores. Unir las curvas usando estos nuevos puntos interpolados proporciona una transición suave en los puntos finales (lo que es un punto final para una iteración se convierte en un punto de control para la siguiente iteración). En otras palabras, las dos curvas desunidas tienen mucho más en común ahora.

Esta solución fue extraída del libro "Animación Foundation ActionScript 3.0: Hacer que las cosas se muevan". p.95 - técnicas de renderizado: creación de múltiples curvas.

Nota: esta solución en realidad no atraviesa cada uno de los puntos, que era el título de mi pregunta (más bien se aproxima a la curva a través de los puntos de muestra pero nunca pasa por los puntos de muestra), pero para mis propósitos (una aplicación de dibujo), es lo suficientemente bueno para mí y visualmente no puedes notar la diferencia. No es una solución que pasar por todos los puntos de muestra, pero se complica mucho más (ver http://www.cartogrammar.com/blog/actionscript-curves-update/ )

Aquí está el código de dibujo para el método de aproximación:

// 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
fuente
+1 Esto funcionó muy bien para un proyecto de JavaScript / lienzo en el que estoy trabajando
Matt
1
Me alegra ser de ayuda. Para su información, comencé un panel de dibujo de lienzo html5 de código abierto que es un complemento jQuery. Debería ser un punto de partida útil. github.com/homanchou/sketchyPad
Homan
44
Eso está bien, pero ¿cómo harías la curva para que pase por todos los puntos?
Richard
Con este algoritmo, ¿cada curva sucesiva debe comenzar desde el punto final de las curvas anteriores?
Lee Brindley
Muchas gracias Homan! ¡Funciona! Pasé muchos días para resolverlo. ¡Y hola de la comunidad de Android / iOS de Delphi!
alitrun
104

Un poco tarde, pero para que conste.

Puede lograr líneas suaves utilizando splines cardinales (también conocido como spline canónico) para dibujar curvas suaves que atraviesen los puntos.

Hice esta función para el lienzo: se divide en tres funciones para aumentar la versatilidad. La función de contenedor principal se ve así:

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

Para dibujar una curva de tener una matriz con x, puntos de Y en el orden: x1,y1, x2,y2, ...xn,yn.

Úselo así:

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

La función anterior llama a dos subfunciones, una para calcular los puntos suavizados. Esto devuelve una matriz con nuevos puntos: esta es la función principal que calcula los puntos suavizados:

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

Y para dibujar realmente los puntos como una curva suavizada (o cualquier otra línea segmentada siempre que tenga una matriz 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]);
}

Esto da como resultado esto:

Ejemplo pix

Puede extender fácilmente el lienzo para poder llamarlo así:

ctx.drawCurve(myPoints);

Agregue lo siguiente al javascript:

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

Puede encontrar una versión más optimizada de esto en NPM ( npm i cardinal-spline-js) o en GitLab .


fuente
3
En primer lugar: esto es magnífico. :-) Pero mirando esa imagen, ¿no da la impresión (engañosa) de que los valores realmente fueron inferiores al valor # 10 en la ruta entre # 9 y # 10? (Estoy contando a partir de los puntos reales que puedo ver, por lo que # 1 sería el que está cerca de la parte superior de la trayectoria descendente inicial, # 2 el que está en la parte inferior [punto más bajo en el gráfico], y así sucesivamente ... )
TJ Crowder
66
Solo quiero decir que después de días de búsqueda, esta fue la única utilidad que realmente funcionó exactamente como quería. Muchas gracias
cnp
44
SI SI SI Gracias! Salté y bailé de alegría.
Jeffrey Sun
1
Hay un error de tipo en su código. El parámetro ptsadebería ser pts, o de lo contrario arrojaría errores.
rostro el
2
Hace mucho tiempo publicaste esta solución y me ayudaste hoy a resolver un gran problema. ¡Muchas gracias!
ÂlexBay
19

La primera respuesta no pasará por todos los puntos. Este gráfico pasará exactamente por todos los puntos y será una curva perfecta con los puntos como [{x:, y:}] en dichos puntos.

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
fuente
1
Este es, con mucho, el enfoque más simple y correcto.
haymez
10

Como señala Daniel Howard , Rob Spencer describe lo que quiere en http://scaledinnovation.com/analytics/splines/aboutSplines.html .

Aquí hay una demostración interactiva: http://jsbin.com/ApitIxo/2/

Aquí es como un fragmento en caso de que jsbin esté inactivo.

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

Encontré que esto funciona bien

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

Decido agregar, en lugar de publicar mi solución en otra publicación. A continuación, se muestra la solución que construyo, puede que no sea perfecta, pero hasta ahora la salida es buena.

Importante: ¡ pasará por todos los puntos!

Si tienes alguna idea, para mejorarla , compártela. Gracias.

Aquí está la comparación de antes después:

ingrese la descripción de la imagen aquí

Guarde este código en HTML para probarlo.

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

Pruebe KineticJS: puede definir una Spline con una variedad de puntos. Aquí hay un ejemplo:

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

Consulte la URL del archivo: https://web.archive.org/web/20141204030628/http://www.html5canvastutorials.com/kineticjs/html5-canvas-kineticjs-spline-tutorial/

Eric Rowell
fuente
Increíble lib! ¡El mejor para la tarea!
Dziad Borowy
¡¡si!! Necesitaba la función blob () para hacer una forma cerrada que pase por todos los puntos.
AwokeKnowing
77
404 Pagina no encontrada.
Dieter
Enlace original - 404 no encontrado - ver web.archive.org/web/20141204030628/http://…
satels
1

Increíblemente tarde, pero inspirado por la respuesta brillantemente simple de Homan, me permite publicar una solución más general (general en el sentido de que la solución de Homan se bloquea en conjuntos de puntos con menos de 3 vértices):

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

Para agregar al método de splines cardinales de K3N y tal vez abordar las inquietudes de TJ Crowder sobre las 'inmersiones' de curvas en lugares engañosos, inserté el siguiente código en la getCurvePoints()función, justo antesres.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;
}

Esto crea efectivamente un cuadro delimitador (invisible) entre cada par de puntos sucesivos y garantiza que la curva permanezca dentro de este cuadro delimitador, es decir. Si un punto en la curva está arriba / abajo / izquierda / derecha de ambos puntos, altera su posición para estar dentro del cuadro. Aquí se usa el punto medio, pero esto podría mejorarse, quizás usando interpolación lineal.

James Pearce
fuente
0

Si desea determinar la ecuación de la curva a través de n puntos, el siguiente código le dará los coeficientes del polinomio de grado n-1 y guardará estos coeficientes en la coefficients[]matriz (a partir del término constante). Las coordenadas x no tienen que estar en orden. Este es un ejemplo de un polinomio de 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];
}
Kevin Bertman
fuente