Suele ser habitual querer hacer un gráfico en una zona de pantalla. Lo normal es que los puntos de nuestro gráfico tengas unas unidades o rangos de valores propias, mientras que las unidades en la zona de pantalla son los pixels.
Por ejemplo, queremos pintar una función y = sin (x). La x va desde 0 hasta 2*PI, mientras que la y varía de -1 a 1. La zona de pantalla en la que queremos dibujar eso puede ocupar, por ejemplo, 300 pixels de ancho y 200 de alto. La coordenada superior izquierda de esa zona suele ser la 0,0. Vamos, que tenemos lo del siguiente gráfico.
El sentido de las flechas indica hacia que lado crecen las y. Es decir,en la zona de pixels, según se va haciendo la y más grande, nos vamos hacia abajo en el dibujo, mientras que en el gráfico de la función según se hace más grande la y, vamos hacia arriba.
Veamos cuales son las cuentas que hay que hacer para poder pintar nuestra
función en la zona de pantalla y que quede bien. Si no tienes ganas
de seguir las cuentas, al final hay una clase en
C++ que las hecha por tí. Sólo tienes que descargarla e incluirla
en tus proyectos.
En nuestro caso tenemos una x que va desde 0 hasta 2*PI y queremos transformala en algo que va entre 0 y 300. Para que las cuentas sean más generales, vamos a un caso más general. Supongamos que la x de nuestro gráfico va entre Xmin y Xmax (0 y 2*PI en nuestro caso) y que queremos transformalo en algo que va entre 0 y Ancho (Ancho es el ancho en pixels de nuestra zona de dibujo, 300 en nuestro caso).
Lo primero que hay que hacer es que nuestro rango [Xmin, Xmax] se transfome en un rango desde 0 hasta otro valor (el que sea). Esto se consigue si al valor de x le restamos Xmin. Haciendo esta resta, la Xmin quedaría 0, mientras que en el caso de Xmax quedaría Xmax-Xmin. Los valores intermedios se moverán de acuerdo a esto. El rango queda ahora entonces entre [0, Xmax-Xmin] y la x concreta que queramos dibujar quedará como
x = x - Xmin;
Ahora podemos transformar nuestro rango a un rango entre 0 y 1. Para ello basta dividir la x entre (Xmax-Xmin). Si en el rango [0,Xmax-Xmin] dividimos ambos números entre (Xmax-Xmin) quedará [0,1] y una x concreta, quedaría como
x = (x-Xmin) / (Xmax-Xmin);
Ahora podemos transformar nuestro rango entre [0,1] en un rango justo en nuestra zona de dibujo, es decir, entre 0 y Ancho. Para ello, si multiplicamos por Ancho el 0 y el 1, nuestro rango quedará [0, Ancho]. Una x concreta que queramos dibujar, quedará como
x = (x-Xmin) / (Xmax-Xmin) * Ancho;
Ya tenemos lo que qeríamos. Hemos transformado una x que
va de Xmin a Xmax en una x que va de 0 a Ancho.
Este valor obtenido se puede dibujar directamente en nuestra zona de pantalla.
El eje de las y sería exactamente igual, si no fuera porque en ambos gráficos (el de verdad y el de pixels) la y crece en sentido contrario. Si aplicamos la misma cuenta
y = (y-Ymin) / (Ymax-Ymin) * Alto;
tenemos una y que va de 0 a Alto, pero si la dibujamos tal cual, nuestro gráfico quedará invertido, ya que los pixels crecen hacia abajo. La solución está en restar esta y de Alto
y = Alto - (y-Ymin) / (Ymax-Ymin) * Alto;
¿Qué hemos hecho con esto?. Cuando queremos dibujar una y = 0, nos queda como Alto - 0 = Alto, es decir, se dibuja en la parte de abajo de nuestra zona de pixels. Si queremos dibujar una y = Alto, nos queda como Alto - Alto = 0, es decir, se dibuja en la parte alta de nuestra zona gráfica. Todo arreglado
El resto ya no está metido en la clase de C++ que hay al final,
pero se podría meter sin mucho esfuerzo. Lo siento, he sido vago
para hacerlo yo mismo.
Supongamos que no queremos ajustar nuestro gráfico justo hasta los bordes de la zona de dibujo. Imaginemos que queremos dejar un márgen de pixels por los cuatro lados, por ejemplo, para poner unos valores, unas etiquetas o lo que sea.
En este caso, nuestro Alto y Ancho serían el Alto y Ancho del área en la que queremos dibujar, es decir, el Alto y Ancho del área total de dibujo menos los márgenes que queramos mantener. Es decir
Alto = Alto - margenSuperior - margenInferior;
Ancho = Ancho - margenIzquierdo - margenDerecho;
Si nuestros márgenes izquierdo y superior son margenIzquierdo e margenSuperior, bastará con sumar estos valores a las transformaciones anteriores.
x = margenIzquierdo + (x-Xmin) / (Xmax-Xmin) * Ancho;
y = margenDerecho + Alto - (y-Ymin) / (Ymax-Ymin) * Alto;
Si vamos a pintar muchos puntos, vamos a calcular muchas veces estos factores
.. / (Xmax-Xmin) * Ancho
.. / (Ymax-Ymin) * Alto
que aparecen en las cuentas anteriores y que son valores fijos para todos los puntos de nuestro dibujo. Por eficiencia, sobre todo si tenemos muchos que dibujar, conviene guardarse el resultado en una variable y utilizar dicha variable directamente. Ahorramos unas restas y divisiones por cada punto que tengamos que dibujar.
Antes de dibujar una x y una y debemos hacer las siguientes cuentas
Xpixels = (x-Xmin) / (Xmax-Xmin) * Ancho;
Ypixels = Alto - (y-Ymin) / (Ymax-Ymin) * Alto;
EscalaC.h y EscalaC.cc es una clase C++ que ayuda a hacer estas cuentas. Te los puedes descargar, quitarles la extensión .txt y usarlos en tus proyectos. Únicamente hay que instanciar la clase, pasarle con tomaExtremos() los valores máximos y mínimos de x e y, pasarle con tomaAreaGrafica() el ancho y alto de tu zona de dibujo. Luego puedes llarmar al dameX() o dameY() para obtener el valor de los pixels a dibujar a partir de un x,y de tu gráfico.
En Seno tienes un ejemplo de cómo se utiliza para X11 y linux.