Gráficas de funciones matemáticas con puntos HTML y Javascript (o CANVAS)

Trazador de gráficas (con canvas)

Aplicación

Trazando...
x=0
y=0
1.234
 Función Opciones I/O HTML/IMG
Modo:
Modo trazado
Medidas plano
Zoom
Mover plano
Color de la gráfica
Ejes y subejes
Graduaciones
Títulos ejes
Título gráfica
Otras opciones Los nodos e iteraciones máximas se multiplican por 10 cuando se use canvas.
 
Con canvas obtenemos una imagen y los datos de la misma serializados. Con HTML obtenemos el literal de los nodos que forman la gráfica.


Introducción

Un trazador de gráficas de funciones matemáticas online no es algo nuevo, por supuesto. Mi propuesta es un trazador que usa puntos confeccionados con elementos HTML e insertados en el DOM de la página con Javascript. Y esto tampoco sé si existe aunque supongo que sí, pero en lo indagado hasta ahora no lo he encontrado. Funciona totalmente en el lado del cliente y el navegador no tiene necesidad de usar ningún tipo de complemento como hacen otros trazadores (Java o Flash por ejemplo).

Aunque en este sitio he adquirido el compromiso de explicar las partes del diseño de cada documento, en este caso se trata de una versión en pruebas y, al mismo tiempo, una simple curiosidad que no está directamente relacionada con el desarrollo web. Por lo tanto sólo me limitaré por el momento a esta única página explicando como usarlo y algunos detalles sobre el objeto Math de Javascript.

El trazador lo he probado con las versiones que tengo actualizadas en Agosto 2011 de Explorer 8.0 (IE), Google Chrome 12.0 (CH), Firefox 5.0 (FF), Safari 5.0 (SA) y Opera 11.5 (OP). En versiones anteriores no sé que pasará, pues en general no testeo las páginas de este sitio con versiones antiguas de exploradores.

Ayuda para usar el trazador de gráficas

En el área de texto podemos escribir una o más funciones separadas por el caracter "#". Si la opción de usar un color para cada gráfica está activada, se aplicarán colores diferentes. Una función puede ser declarada en coordenadas cartesianas, como por ejemplo y=3*sin(x) o incluso especificando un rango en el eje X como x=0..PI/2; y=3*sin(x). Otro ejemplo, la ecuación de la circunferencia en cartesianas necesitará dos funciones para trazar ambos lados de la función raíz cuadrada:

y=sqrt(1-pow(x,2)) # y=-sqrt(1-pow(x,2))

Pero también puede expresarse en coordenadas polares, que tiene un mejor rendimiento en el trazado:

t=-PI..PI; r=1; x=r*cos(t); y=r*sin(t)

Hay unos limitadores para evitar el número excesivo de nodos o de iteraciones en el muestreo automático. Se fijan en 3000 nodos y 5000 iteraciones. Si se llega a alguno de estos límites nos preguntará si seguimos trazando. En cuanto a las iteraciones no tiene más carga que la del tiempo. Si estamos dispuestos a esperar para ver una gráfica más precisa entonces podemos ir aceptando los avisos. Pero en cuanto al número de nodos hay que entender que 3000 es una cantidad que soporta bien mi ordenador con Windows XP y 1GB de RAM. Con otros recursos se podrán variar estos limitadores.

El trazador puede saltarse de forma automática las discontinuidades de las funciones, pero genera más nodos. Por ejemplo, la función y = sin(1/x) es tal que cuando x tiende a cero la función no tiene un límite pues será cualquier valor entre 1 y -1. Esto hace que la gráfica vaya "apretándose" a medida que se acerca al origen. El sistema de muestreo automático activado con la casilla de remarcar hace que se busquen los puntos de X tal que dos puntos consecutivos de Y resulten adyacentes. Si la aplicamos a una función con esos puntos especiales, el número de nodos puede resultar muy alto. En estos casos conviene separar la gráfica en dos partes para reducir el número de nodos

x=-3.8..0;y=sin(10/x) # x=0..3.8;y=sin(10/x)

La precisión es un número real no negativo y está relacionada con el grosor del punto (valor fijo de 2px). Esto nos dará el paso de muestra inicial de los valores del eje X:

pasoMuestraInicial = gruesoPunto/(1+precision)

Si el plano está configurado con las dimensiones 400x400 píxeles y sin la opción de remarcado y con precisión cero, entonces el paso de muestra inicial es de 2/(1+0) = 2px. Es decir, en este caso el paso de muestra es el grosor del punto. Por lo tanto para un ancho del plano de 400px tendremos 200 muestras, por lo que cualquier función no tendrá más de 200 puntos. En definitiva, si no se activa la casilla de remarcar y se pone una precisión de cero, entonces se trazarán puntos sueltos. El remarcado hace que se active el muestreo automático que dije antes, aunque a veces es suficiente con sólo incrementar la precisión pues produce un paso de muestra más pequeño. En algunos casos hacen falta ambas opciones lo que puede llegar a incrementar notablemente el número de nodos o de iteraciones de muestreo automático.

Para algunas gráficas complejas hay que buscar los valores adecuados con el zoom y actuando sobre el marcado y la precisión. Este trazador lo he probado con los navegadores señalados más arriba con resultado satisfactorio, aunque el grosor del punto de 1 píxel sólo funcionaba en Explorer y Firefox, pues en Safari, Chrome y Opera no se veía el trazado. Es algo que intenté arreglar, pues con 1px se consigue un detalle más fino en el trazado. La razón de esto creo que se debe a que el punto lo dibujo usando un elemento sin contenido y con un color de fondo, pero dándole un {padding} (o relleno) para el grosor. Así un grosor 2 equivale a un padding de 1px, pero con un grosor de 1px significa que ese relleno debe ser de 0.5px y sólo funciona como dije en Explorer y Firefox. Al final en una segunda versión decidí dejar un único grosor de 2px fijo para evitar estas diferencias.

Otras utilidades y cuestiones de seguridad

En la pestaña I/O (abreviando input/output) podemos exportar la función y todas las opciones a una cadena de texto para guardarla manualmente en algún soporte de texto. Si en un momento posterior copiamos ese texto y lo pegamos en el cuadro, podemos importar esa función con todas sus opciones.

La pestaña HTML/IMG nos sirve para extraer todos los nodos de la gráfica como un literal HTML o como una imagen si estamos usando canvas. En este caso también podemos obtener la imagen en base64. Con el menú del navegador podemos descargar esa imagen para otros usos. O bien usar el valor del data:image/png;base64 que contiene la imagen serializada.

Sobre el tema de la seguridad hay que tener en cuenta que toda la ejecución se encarga al módulo Javascript y, por lo tanto, se ejecuta en el navegador. El script hace uso de la función eval() para poder obtener el resultado de las funciones matemáticas del objeto Math. He aplicado algunas limitaciones para impedir poner cualquier cosa en las funciones. Sólo se permite declarar variables de una sóla letra del conjunto de letras [a-z]. Por otro lado las únicas funciones permitidas serán las del objeto Math de Javascript, incluidas sus constantes así como las funciones complementarias, es decir, todo lo que hay en los dos cuadros desplegables para insertar funciones.

En toda función tiene que haber al menos la variable independiente x que siempre se declara aunque el usuario no lo haga expresamente. La variable y es la que se calcula en el script y puede ser función de x como en las coordenadas cartesianas o de cualesquiera otras variables, como cuando usamos coordenadas polares. En todo caso podemos usar cuantas variables intermedias necesitemos. Al final toda esa expresión separada por puntos y comas no es otra cosa que instrucciones de código Javascript para obtener el punto (x,y).

El objeto Math de Javascript

Hay tres desplegables para insertar funciones. El primero contiene las constantes y funciones básicas del objeto Math de Javascript. El segundo contiene algunas funciones complementarias basadas en las anteriores. El último desplegable nos sirve para presentar algunos ejemplos de funciones que extraje con la utilidad de exportación.

El objeto Math tiene constantes en mayúsculas y funciones:

  • Constantes:
    • E = e = 2.718281..., tal que exp(1) = E
    • LN10 = log(10) = 2.302585.... En general suele usarse ln(x) para el logaritmo natural (o neperiano) y log(x) para el de base 10. Pero en Javascript sólo hay logaritmos naturales designados con la función log(x) y de esta forma también los designaremos aquí. Para otras bases pondríamos logB(x) siendo B la base. Así para la conversión de unas bases a otras necesitamos dividir por el logaritmo de la base. En este caso la constante LN10 = log(10), es decir, es el logaritmo natural de 10 y nos sirve para cambiar de base log10(x) = log(x)/LN10.
    • LN2 = log(2) = 0.693147..., por lo que log2(x) = log(x)/LN2
    • LOG2E = log2(E) = log(E)/LN2 = 1/LN2 = 1.442695...
    • LOG10E = log10(E) = log(E)/LN10 = 1/LN10 = 0.434294...
    • PI = π = 3.141592...
    • SQRT1_2 = 1/21/2
    • SQRT2 = 21/2
  • Funciones:
    • y = abs(x): es la función valor absoluto.
    • y = floor(x): redondea al número entero inferior de x. Por ejemplo, floor(2.0000...) hasta floor(2.9999...) devuelve 2. Es una funcion escalonada.
    • y = ceil(x): redondea al número entero superior de x, el contrario de la anterior. Esta y la anterior función deben usarse sin la opción de remarcado y con un zoom proporcional al tamaño del plano. Por ejemplo, con un plano de 400x400 y un zoom de 40x40 entonces dos valores consecutivos en cada eje estarán separados 10 puntos de muestra. Así entre x y x+1 habrán 10 puntos y podemos probar adecuadamente estas funciones escalonadas.
    • y = round(x): redondea al entero más próximo, de tal forma que para una fracción mayor o igual que 0.5 redondea al entero superior y el resto al entero inferior. También se trata de una función escalonada.
    • y = max(x1, x2, ...), y = min(x1, x2, ...): devuelve el valor máximo o mínimo de la lista de valores. Puede usarse con funciones en los argumentos como max(sin(x), cos(x)).
    • y = pow(a, b): la función potencia a de b, es decir, y=ab siendo a y b reales. Como el exponente puede ser un entero negativo, algo como pow(x, -2) es realmente 1 / x2. Pero además puede ser un número no entero como pow(x, 1/2) que es realmente la raíz cuadrada √x, es decir, x1/2. Y también nos sirve como exponenciación y=2x o y=ex. En este caso y=pow(E,x) sería lo mismo que y=exp(x).
    • y = sqrt(x): la raíz cuadrada de un número.
    • Funciones trigonométricas: sin(x), cos(x), tan(x). Son las funciones seno, coseno y tangente.
    • Funciones trigonométricas inversas: y = asin(x), y = acos(x), y = atan(x). Son las funciones arcoseno, arcocoseno y arcotangente. Hay otra función atan2(y,x) que devuelve el ángulo en radianes de ese punto (x,y) del plano. Por ejemplo, atan2(1,1) devuelve π/4, por lo que veremos una recta a esa altura (0.785398... en el eje Y).
    • y = exp(x): es la función exponencial y=ex.
    • y = log(x): es la función logaritmo natural o neperiano (es la inversa de la exponencial). En matemáticas suele designarse ln(x). La base de este logaritmo es la constante e (E en Javascript) de tal forma que log(E) nos dará una recta a la altura 1 en el eje Y. Se puede cambiar de base con logb(x) = logc(x) / logc(b). Para esto podemos usar las constantes que mencionamos más arriba.
    • y = random(): genera un número aleatorio en el rango [0..1), sin incluir el 1.
  • Otras funciones: Podemos agregar más funciones a nuestro código componiéndolas con las básicas anteriores. Las que he puesto son estas:
    • y = log10(x), logaritmo decimal y = log10(x).
    • y = log2(x), logaritmo base dos y = log2(x).
    • y = loga(a,x), logaritmo cualquier base y = loga(x).
    • y = sec(x), secante y = sec(x) = 1/cos(x)
    • y = cosec(x), cosecante y = cosec(x) = 1/sin(x)
    • y = cotan(x), cotangente y = cotan(x) = 1/tan(x)
    • y = sinh(x), seno hiperbólico y = sinh(x) = (ex - e-x)/2
    • y = cosh(x), coseno hiperbólico y = cosh(x) = (ex + e-x)/2
    • y = tanh(x), tangente hiperbólica y = tanh(x) = (ex - e-x)/(ex + e-x)

Operadores Javascript

A continuación relaciono los operadores de Javascript que pueden usarse:

  • Operadores aritméticos
    • Básicos: + suma, - resta o negación de una expresión, * multiplicación, / división.
    • Incremento y decremento: ++, --.
    • Módulo: %, es el resto de la división entera.
  • Operadores lógicos
    • Booleanos: && conjuntiva (AND), || disyuntiva (OR), ! negativa (NOT). Es posible usarlos para delimitar los rangos de ejecución de una función, como en el ejemplo Operadores booleanos que he puesto en el desplegable:
      y=cos(x)*(x<-2*PI)||cos(x)*(x>2*PI)||pow(x/(2*PI),2)
      En este caso para valores menores que se dibuja la función coseno, pues el resultado de x<2π será true pero que se toma como un 1. cuando sea false el valor será cero. Así esta función dibuja el coseno para valores mayores de y una parábola en el centro.
    • Comparativos: < menor que, > mayor que, <= menor o igual que, >= mayor o igual que, == igual que, != distinto de.
  • Operadores a nivel de bit: ~ NOT, & AND, | OR, ^ XOR, <<, >>, >>> desplazamiento de bits izquierda, derecha o derecha sin signo. Observe que el signo ^ a veces se usa para presentar la potencia, es decir y=x^2 para y=x2, pero con Javascript esto sería y=pow(x,2) pues y=x^2 es la operación y = x XOR 2. Un uso de estos operadores es aplicarlo con y=x&1 que nos daría la función de un generador de bits (ver ese ejemplo Generador de bits).

En cuanto a los recursos para controlar el flujo de las instrucciones, tenemos los siguientes:

  • Variables: Se declaran explícitamente las variables a, b, c, ..., z inicialmente con un valor cero. El punto en el plano corresponde a las variables x e y. Podemos redefinir el tipo de una variable como una array haciendo a=[0,1,2,3,4] por ejemplo (ver también el ejemplo Puntos sueltos con contorno).
  • Separador de funciones: el caracter # nos permite separar las funciones (esto no es un operador de Javascript, sino sólo a efectos de esta aplicación).
  • Separador de instrucciones: ; pues las instrucciones se separan en Javascript con un punto y coma.
  • Separador de expresiones: , pues la coma separa expresiones en Javascript. Por ejemplo A,B hace que se opere ambas expresiones en el orden izquierda a derecha y se devuelva la de la derecha. En Javascript suele usarse dentro de la declaración de un bucle for para arrays de dos dimensiones por ejemplo, pero aquí no pueden incluirse bucles. Lo siguiente también está entre los ejemplos presentados Puntos sueltos con contorno y es una forma de poner un bucle para iterar por un array:
    a=[0,3,4,-2,3]; b=[0,-3,0,-2,3]; x=(i++, a[i]); y=b[i]
    En este caso tenemos dos arrays e iteramos por ellos para mostrar esos puntos en pantalla. Se usa la opción de contorno del punto. Es importante observar que dado que la variable i está inicializada con un valor cero, entonces el primer punto a[0] = 0 y b[0] = 0 no se mostrara en pantalla porque se incrementa i antes. Es decir, en este caso los arrays trabajan con base 1. Veáse el operador coma x=(i++, a[i]), donde se incrementa i y se devuelve la posición de ese array.
  • Condicional ternario: A?B:C. Se trata de un operador de Javascript que nos permite seleccionar que se ejecute B o C según sea A cierto o no. Hay un ejemplo de Comparativos y condicionales que tiene esta función y=((x<-PI)||(x>PI))?sin(x):sqrt(pow(PI,2)-pow(x,2)). En este ejemplo la función seno se traza para valores de x menores que y mayores que π. En el centro se traza una circunferencia de radio π.

Referencias de trazadores gráficos y otros recursos

Hay numerosos sitios que ofrecen trazadores de gráficas online. Con sólo mirar un par de las primeras páginas de los resultados de consulta en Google nos aparece:

Las versiones que usan complementos requieren que estos estén instalados y actualizados en el navegador. Las que se confeccionan en el servidor y se envían al navegador como imágenes son más lentas y con menor poder de posterior manipulación por el usuario. Hasta el momento no he visto ninguna aplicación basada en puntos HTML sin requerir ningún otro complemento, pero sólo he podido indagar en unas pocas.

Para aprender más sobre gráficas de funciones y buscar ejemplos:

Y por supuesto en Wikipedia, por ejemplo en la parte de Commons.wikemedia.org encontraremos otras tantas.