Los gráficos en Java permite realizar cualquier tipo de dibujo con base en figuras básicas como rectángulos, arcos, imágenes, textos, óvalos, polígonos y poli líneas.
Un grafico se puede realizar en cualquier contenedor, sin embargo, la mejor practica es realzarlo sobre un JPanel. El JPanel contiene un método sobre escribible denominado paint que recibe un parámetro Graphics. En este método se debe realizar toda la implementación de dibujo de la aplicación.
Para asegurar el bajo acoplamiento, se recomienda aplicar una arquitectura en donde en su capa de presentación se cree una clase que herede de JPanel en donde se implementen los diferentes algoritmos de dibujo. En este caso, se debe incluir un JFrame y en él incluir una referencia del JPanel que se agregaría al JFrame. Para agregar el JPanel al JFrame, se hace necesario especificar un Layout al JFrame. Si no se le asigna Layout, por defecto tendrá null, el cual permitiría agregar el JPanel con un tamaño y posición fija.
La implementación de esta opción es la siguiente.
Clase PDibujo
package Presentacion; import java.awt.Graphics; /** * @author Hector Florez */ public class PDibujo extends javax.swing.JPanel { public PDibujo() { } public void paint(Graphics g){ } }
Clase FPrincipal
Clase FPrincipal package Presentacion; import javax.swing.WindowConstants; import javax.swing.SwingUtilities; /** * @author Hector Florez */ public class FPrincipal extends javax.swing.JFrame { private PDibujo pDibujo; public static void main(String[] args) { SwingUtilities.invokeLater(new Runnable() { public void run() { FPrincipal inst = new FPrincipal(); inst.setLocationRelativeTo(null); inst.setVisible(true); } }); } public FPrincipal() { super(); initGUI(); } private void initGUI() { try { setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE); pack(); getContentPane().add(this.pDibujo); PContenedorDibujo.setBounds(15, 15, 360, 240); setSize(400, 300); } catch (Exception e) { e.printStackTrace(); } } }
Lo más apropiado es agregar al JFrame Border Layout, de esa forma, el JPanel quedará con tamaño dinámico, pero el dibujo tendrá que ser también dinámico.
Con el fin de facilitar el uso de eventos sobre el JPanel que requieran el mouse, se recomienda, crear una instancia aparte de la clase JPanel y a él agregar la instancia de PDibujo. La instancia del JPanel en cualquier caso debería tener Border Layout
La implementación del JFrame es la siguiente.
Clase FPrincipal
Clase FPrincipal package Presentacion; import javax.swing.WindowConstants; import javax.swing.SwingUtilities; /** * @author Hector Florez */ public class FPrincipal extends javax.swing.JFrame { private PDibujo pDibujo; public static void main(String[] args) { SwingUtilities.invokeLater(new Runnable() { public void run() { FPrincipal inst = new FPrincipal(); inst.setLocationRelativeTo(null); inst.setVisible(true); } }); } public FPrincipal() { super(); initGUI(); } private void initGUI() { try { setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE); pack(); getContentPane().add(this.pDibujo); PContenedorDibujo.setBounds(15, 15, 360, 240); setSize(400, 300); } catch (Exception e) { e.printStackTrace(); } } }
Clase Graphics
La clase Graphics, permite realizar dibujos sobre el JPanel a través del método paint. Esta clase está directamente relacionada con la clase Color, ya que a través de ésta, se puede definir un color con el cual la instancia de Graphics puede pintar. La clase Color, proporciona colores del formato RGB, los cuales pueden ser incluidos en el constructor.
Para graficar es importante tener en cuenta las coordenadas que se utilizan en computación. Estas coordenadas inician el (0,0) en la esquina superior izquierda y terminan en (x,y) en la esquina inferior derecha en donde x,y equivalen al ancho y alto del panel respectivamente. La siguiente figura ilustra el manejo de coordenadas.
Formas de Graphics
A través de la clase Graphics es posible dibujar una gran cantidad de formas como rectángulos, arcos, imágenes, textos, óvalos, polígonos y poli líneas.
Linea
Una línea se puede obtener mediante el siguiente método:
drawLine(int x1, int y1, int x2, int y2)
Este método dibuja una línea con un punto en la en la coordenada (x1,y1) y el otro punto en la coordenada (x2,y2).
Rectángulo
Un rectángulo se puede obtener mediante diferentes métodos como:
drawRect(int x, int y, int width, int height)
Este método dibuja un rectángulo con la esquina superior izquierda en la coordenada (x,y). Tiene un ancho y alto dados por los parámetros width y height. Esto indica que la coordenada de la esquina inferior derecha es (x+width,y+height).
drawRoundRect(int x, int y, int width, int height, int arcWidth, int arcHeight)
Este método dibuja un rectángulo con las mismas características de posición y tamaño del método drawRect. Adicionalmente dibuja las esquinas redondeadas con base en los parámetros arcWidth y arcHeight.
fillRect(int x, int y, int width, int height)
Este método dibuja un rectángulo con las mismas características de posición y tamaño del método drawRect. Adicionalmente, llena de color el rectángulo.
fillRoundRect(int x, int y, int width, int height, int arcWidth, int arcHeight)
Este método dibuja un rectángulo con las mismas características de posición, tamaño y esquinas del método drawRoundRect. Adicionalmente, llena de color el rectángulo.
Ovalo
Un óvalo se puede obtener mediante diferentes métodos como:
drawOval(int x, int y, int width, int height)
Este método dibuja un ovalo con la esquina superior izquierda en la coordenada (x,y). Tiene un ancho y alto dados por los parámetros width y heigh. Esto indica que la coordenada de la esquina inferior derecha es (x+width,y+height).
fillOval(int x, int y, int width, int height)
Este método dibuja un ovalo con las mismas características de posición y tamaño del método drawOval. Adicionalmente, llena de color el óvalo.
Polígono
Un polígono se puede obtener mediante diferentes métodos como:
drawPolygon(int[] xPoints, int[] yPoints, int nPoints)
Este método dibuja un polígono con puntos definidos por los arreglos xPoints y yPoints. La cantidad de puntos se incluyen en el parámetro nPoints.
fillPolygon(int[] xPoints, int[] yPoints, int nPoints)
Este método dibuja un polígono con las mismas características de posición del método drawPoligon. Adicionalmente, llena de color el polígono.
Cadenas de caracteres
Una línea se puede obtener mediante el siguiente metodo:
drawString(String str, int x, int y)
Este método dibuja la cadena del parámetro str con la esquina inferior izquierda en la coordenada (x,y).
Considerando un JPanel, se pueden realizar diferentes formas desde el método paint que recibe por parámetro un objeto Graphics.
package Graficas; import java.awt.Color; import java.awt.Font; import java.awt.Graphics; /** * @author Hector Florez */ public class PDibujo extends javax.swing.JPanel { private int ancho; private int alto; public PDibujo() { } public void paint(Graphics g){ this.ancho=(int)this.getSize().getWidth(); this.alto=(int)this.getSize().getHeight(); Color c; c=new Color(0,0,255); g.setColor(c); g.drawLine(10, 10, 10, 100); c=new Color(0,255,0); g.setColor(c); g.drawRect(20, 10, 50, 100); g.fillRect(80, 10, 50, 100); g.drawRoundRect(140, 10, 50, 100, 20, 20); g.fillRoundRect(200, 10, 50, 100, 20, 20); c=new Color(255,0,0); g.setColor(c); g.drawOval(260, 10, 50, 100); g.fillOval(320, 10, 50, 100); c=new Color(255,0,255); g.setColor(c); int []x={10,20,30,40,50}; int []y={120,180,150,180,120}; g.drawPolygon(x, y, 5); int []x2={60,70,80,90,100}; int []y2={120,180,150,180,120}; g.fillPolygon(x2, y2, 5); c=new Color(100,100,100); g.setColor(c); Font f = new Font("Tahoma",10,25); g.setFont(f); g.drawString("Hola mundo", 120, 150); } }
En esta aplicación dibuja las siguientes formas:
- Una línea de color azul con el primer punto en las coordenadas (10,10) y el segundo punto en las coordenadas (10,100).
- Un rectángulo sin relleno de color verde con el punto superior izquierdo en las coordenadas (20,10) y el punto inferior derecho en las coordenadas (50,100)
- Un rectángulo con relleno de color verde con el punto superior izquierdo en las coordenadas (80,10) y el punto inferior derecho en las coordenadas (50,100)
- Un rectángulo sin relleno de color verde con el punto superior izquierdo en las coordenadas (140,10) y el punto inferior derecho en las coordenadas (50,100). Además tiene bordes redondeados con un alto y ancho de 20 pixeles, es decir, desde la esquina hasta donde inicial la curva de redondeo hay 20 pixeles tanto en el eje X como en Y
- Un rectángulo con relleno de color verde con el punto superior izquierdo en las coordenadas (200,10) y el punto inferior derecho en las coordenadas (50,100). Además tiene bordes redondeados con un alto y ancho de 20 pixeles, es decir, desde la esquina hasta donde inicia la curva de redondeo hay 20 pixeles tanto en el eje X como en Y
- Un óvalo sin relleno de color rojo con el punto superior izquierdo en las coordenadas (260,10) y el punto inferior derecho en las coordenadas (50,100). Esto indica que el óvalo tiene su punto máximo superior en la coordenada Y=10, su punto máximo a la izquierda en la coordenada X=260, su punto máximo inferior en la coordenada Y=10+100=110 y su punto máximo a la derecha en la coordenada Y=260+50=310.
- Un ovalo con relleno de color rojo con el punto superior izquierdo en las coordenadas (320,10) y el punto inferior derecho en las coordenadas (50,100).
- Un polígono con 5 puntos sin relleno de color violeta.
- Un polígono con 5 puntos con relleno de color violeta.
- La palabra "Hola mundo" de color gris, con fuente Tahoma y tamaño 25 pixeles en la coordenada inferior izquierda (120,150)
El resultado es como se presenta en la siguiente figura.
Paneles Estáticos y Dinámicos
Si se desea pintar una cuadricula de color gris colocando de fondo color blanco, se podría realizar la siguiente implementación en la clase PDibujo con base en que la resolución de este panel es de 360 pixeles de ancho por 240 pixeles de alto como se definió en el método setBounds. Esta implementación utiliza el método fillRect para pintar un cuadrado blanco del tamaño del panel y el método drawLine para pintar cada línea en el panel.
Clase PDibujo
package Presentacion; import java.awt.Color; import java.awt.Graphics; /** * @author Hector Florez */ public class PDibujo extends javax.swing.JPanel { public PDibujo() { } public void paint(Graphics g){ Color c; c=new Color(255,255,255); g.setColor(c); g.fillRect(0, 0, 360, 240); c=new Color(180,180,180); g.setColor(c); int i; for(i=36; i<360; i+=36){ g.drawLine(i, 0, i, 240); } for(i=24; i<240; i+=24){ g.drawLine(0, i, 360, i); } } }
El resultado es como se muestra en la siguiente figura.
En esta implementación, la cuadrícula es estática debido a que el tamaño del panel también lo es. El tamaño de cada celda entonces es de 36 x 24 pixeles para lograr una cuadricula de 10 filas por 10 columnas. Esta implementación genera un defecto que consiste en que al maximizar o cambiar de tamaño el JFrame, la grafica no se adapta al nuevo tamaño.
Modificando el Layout del JFrame a Border Layout, se obtiene un panel dinámico y su cuadrícula también lo sería. La implementación de las dos clases es la siguiente.
Clase FPrincipal
package Presentacion; import java.awt.BorderLayout; import javax.swing.JPanel; import javax.swing.WindowConstants; import javax.swing.SwingUtilities; /** * @author Hector Florez */ public class FPrincipal extends javax.swing.JFrame { private PDibujo pDibujo; private JPanel PContenedorDibujo; public static void main(String[] args) { SwingUtilities.invokeLater(new Runnable() { public void run() { FPrincipal inst = new FPrincipal(); inst.setLocationRelativeTo(null); inst.setVisible(true); } }); } public FPrincipal() { super(); initGUI(); } private void initGUI() { try { setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE); BorderLayout thisLayout = new BorderLayout(); getContentPane().setLayout(thisLayout); this.setTitle("Graficacion"); { PContenedorDibujo = new JPanel(); BorderLayout PContenedorDibujoLayout = new BorderLayout(); PContenedorDibujo.setLayout(PContenedorDibujoLayout); getContentPane().add(PContenedorDibujo, BorderLayout.CENTER); this.pDibujo = new PDibujo(); PContenedorDibujo.add(this.pDibujo); } pack(); setSize(400, 300); } catch (Exception e) { e.printStackTrace(); } } }
Clase PDibujo
package Presentacion; import java.awt.Color; import java.awt.Graphics; /** * @author Hector Florez */ public class PDibujo extends javax.swing.JPanel { private int ancho; private int alto; public PDibujo() { } public void paint(Graphics g){ this.ancho=(int)this.getSize().getWidth(); this.alto=(int)this.getSize().getHeight(); int anchoCelda=(int)(this.getSize().getWidth()/10); int altoCelda=(int)(this.getSize().getHeight()/10); Color c; c=new Color(255,255,255); g.setColor(c); g.fillRect(0, 0, this.ancho, this.alto); c=new Color(180,180,180); g.setColor(c); int i; for(i=anchoCelda; i <this.ancho-anchoCelda; i+=anchoCelda){ g.drawLine(i, 0, i, this.alto); } for(i=altoCelda; i<this.alto-altoCelda; i+=altoCelda){ g.drawLine(0, i, this.ancho, i); } } }
En este caso, se debe calcular el ancho y alto del panel a través de los métodos this.getSize().getWidth() y this.getSize().getHeight(). Así mismo el alto y ancho de cada celda el cual corresponde al ancho y alto del panel dividido en 10, ya que ese es el número de celdas deseadas. Se debe hacer casting a int, debido a que el método drawLine recibe en sus parámetros este tipo de dato.
El resultado es como se muestra en la siguiente figura.
Graficas de Señales
Para graficar señales como seno y coseno, lo más apropiado es crear una capa de lógica de negocio que contenga las clases seno y coseno. Sin embargo para asegurar escalabilidad al proyecto, se debería crear una clase abstracta llamada “Senal” que herede atributos y métodos necesarios para el dibujo. La implementación es la siguiente.
Clase Senal
package Logica; public abstract class Senal { protected int amplitud; protected int frecuencia; protected int offset; public Senal(int a, int f,int o){ this.amplitud=a; this.frecuencia=f; this.offset=o; } public int getAmplitud() { return amplitud; } public void setAmplitud(int amplitud) { this.amplitud = amplitud; } public int getFrecuencia() { return frecuencia; } public void setFrecuencia(int frecuencia) { this.frecuencia = frecuencia; } public int getOffset() { return offset; } public void setOffset(int offset) { this.offset = offset; } public abstract int[] calcular(int ancho, int alto); }
Clase Seno
package Logica; public class Seno extends Senal { public Seno(int a, int f,int o){ super(a,f,o); } public int[] calcular(int ancho, int alto){ int [] puntos = new int[ancho]; for(int i=0;i<ancho;i++){ puntos[i]=(this.offset*alto/10)+(int)((alto/10)*this.amplitud* Math.sin(Math.PI/180*i*this.frecuencia)); } return puntos; } }
Clase Coseno
package Logica; public class Coseno extends Senal { public Coseno(int a, int f,int o){ super(a,f,o); } public int[] calcular(int ancho, int alto){ int [] puntos = new int[ancho]; for(int i=0;i<ancho;i++){ puntos[i]=(this.offset*alto/10)+(int)((alto/10)*this.amplitud* Math.cos(Math.PI/180*i*this.frecuencia)); } return puntos; } }
Las clases Seno y Coseno implementan un método llamado calcular que retorna un arreglo de puntos. Estos puntos son los valores de la señal con la amplitud, frecuencia y offset (desfase en el eje Y) asignados a través del constructor. Los parámetros ancho y alto, indican la cantidad de pixeles del panel, debido a que este es dinámico.
Los puntos de la señal se calculan, haciendo un proceso iterativo que depende del número de pixeles en el eje X. Los puntos dependen del nivel de offset que se multiplican por el alto del panel y divide en 10 debido a que la cuadricula posee 10 filas. Al nivel offset se le suma la señal cuya amplitud se multiplica por el atributo correspondiente por el alto dividido 10, con el fin de que la señal ocupe en el eje Y una amplitud proporcional a las filas de la cuadricula. Las señales seno y coseno, reciben por parámetro el ángulo en radianes, por lo que se debe realizar la conversión a frecuencia multiplicando por pi y dividiendo por 180.
El JFrame, entonces debe cambiar para ofrecer un mecanismo que asigne valores a las señales. En este caso, se propone tres cuadros de texto para los valores un botón para dibujar la señal seno y un botón para dibujar la señal coseno.
La implementación es la siguiente.
Clase FPrincipal
package Presentacion; import java.awt.BorderLayout; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import javax.swing.Jbutton; import javax.swing.Jlabel; import javax.swing.Jpanel; import javax.swing.JtextField; import javax.swing.SwingConstants; import javax.swing.WindowConstants; import javax.swing.SwingUtilities; import Logica.Coseno; import Logica.Senal; import Logica.Seno; /** * @author Hector Florez */ public class FPrincipal extends javax.swing.Jframe { private Pdibujo pDibujo; private Jlabel Lamplitud; private Jbutton Bcoseno; private Jbutton Bseno; private JtextField Toffset; private Jlabel Loffset; private JtextField Tfrecuencia; private Jlabel Lfrecuencia; private JtextField Tamplitud; private Jpanel Pcomponentes; private Jlabel Ltitulo; private Jpanel PcontenedorDibujo; private Senal señal; public static void main(String[] args) { SwingUtilities.invokeLater(new Runnable() { public void run() { FPrincipal inst = new FPrincipal(); inst.setLocationRelativeTo(null); inst.setVisible(true); } }); } public FPrincipal() { super(); initGUI(); } private void initGUI() { try { setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE); BorderLayout thisLayout = new BorderLayout(); getContentPane().setLayout(thisLayout); this.setTitle(“Senales Trigonometricas”); { PcontenedorDibujo = new Jpanel(); BorderLayout PcontenedorDibujoLayout = new BorderLayout(); PcontenedorDibujo.setLayout(PcontenedorDibujoLayout); getContentPane().add(PcontenedorDibujo, BorderLayout.CENTER); this.pDibujo = new Pdibujo(); PcontenedorDibujo.add(this.pDibujo); } { Ltitulo = new Jlabel(); getContentPane().add(Ltitulo, BorderLayout.NORTH); Ltitulo.setText(“SENALES TRIGONOMETRICAS”); Ltitulo.setFont(new java.awt.Font(“Tahoma”,1,16)); Ltitulo.setHorizontalAlignment(SwingConstants.CENTER); } { Pcomponentes = new Jpanel(); getContentPane().add(Pcomponentes, BorderLayout.SOUTH); { Lamplitud = new Jlabel(); Pcomponentes.add(Lamplitud); Lamplitud.setText(“Amplitud”); } { Tamplitud = new JtextField(); Pcomponentes.add(Tamplitud); Tamplitud.setText(“1”); Tamplitud.setSize(40, 20); Tamplitud.setPreferredSize(new java.awt.Dimension(20, 20)); } { Lfrecuencia = new Jlabel(); Pcomponentes.add(Lfrecuencia); Lfrecuencia.setText(“Frecuencia”); } { Tfrecuencia = new JtextField(); Pcomponentes.add(Tfrecuencia); Tfrecuencia.setText(“10”); Tfrecuencia.setSize(10, 20); Tfrecuencia.setPreferredSize(new java.awt.Dimension(20, 20)); } { Loffset = new Jlabel(); Pcomponentes.add(Loffset); Loffset.setText(“Offset”); } { Toffset = new JtextField(); Pcomponentes.add(Toffset); Toffset.setText(“0”); Toffset.setSize(10, 20); Toffset.setPreferredSize(new java.awt.Dimension(20, 20)); } { Bseno = new Jbutton(); Pcomponentes.add(Bseno); Bseno.setText(“Seno”); Bseno.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent evt) { BsenoActionPerformed(evt); } }); } { Bcoseno = new Jbutton(); Pcomponentes.add(Bcoseno); Bcoseno.setText(“Coseno”); Bcoseno.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent evt) { BcosenoActionPerformed(evt); } }); } } pack(); setSize(400, 300); } catch (Exception e) { e.printStackTrace(); } } private void BsenoActionPerformed(ActionEvent evt) { this.senal=new Seno(Integer.parseInt(this.Tamplitud.getText()), Integer.parseInt(this.Tfrecuencia.getText()), Integer.parseInt(this.Toffset.getText())); this.pDibujo.actualizar(this.senal); } private void BcosenoActionPerformed(ActionEvent evt) { this.senal=new Coseno(Integer.parseInt(this.Tamplitud.getText()), Integer.parseInt(this.Tfrecuencia.getText()), Integer.parseInt(this.Toffset.getText())); this.pDibujo.actualizar(this.senal); } }
La anterior clase presenta las siguientes características:
- Posee un panel en el centro con la gráfica.
- Posee un panel en el sur con los componentes necesarios para enviar los parámetros de amplitud, frecuencia y offset de las señales.
- Posee un método para un botón Seno que crea un objeto instancia de la clase Seno en la capa de lógica, al que se le envía por el constructor los valores de amplitud, frecuencia y offset. Este método envía el objeto al objeto pDibujo a través del método actualizar.
- Posee un método para un botón Coseno que crea un objeto instancia de la clase Coseno en la capa de lógica, al que se le envía por el constructor los valores de amplitud, frecuencia y offset. Este método envía el objeto al objeto pDibujo a través del método actualizar.
Clase PDibujo
package Presentacion; import java.awt.Color; import java.awt.Graphics; import Logica.Senal; /** * @author Hector Florez */ public class PDibujo extends javax.swing.JPanel { private int ancho; private int alto; private Senal senal; public PDibujo() { } public void actualizar(Senal senal){ this.senal=senal; this.repaint(); } public void paint(Graphics g){ this.ancho=(int)this.getSize().getWidth(); this.alto=(int)this.getSize().getHeight(); int anchoCelda=(int)(this.getSize().getWidth()/10); int altoCelda=(int)(this.getSize().getHeight()/10); Color c; c=new Color(255,255,255); g.setColor(c); g.fillRect(0, 0, this.ancho, this.alto); c=new Color(180,180,180); g.setColor(c); int i; for(i=anchoCelda; i<this.ancho-(anchoCelda/2); i+=anchoCelda){ g.drawLine(i, 0, i, this.alto); } for(i=altoCelda; i<this.alto-(altoCelda/2); i+=altoCelda){ g.drawLine(0, i, this.ancho, i); } c=new Color(0,0,255); g.setColor(c); if(this.senal!=null){ int []puntos = this.senal.calcular(this.ancho,this.alto); for(i=0;i<this.ancho-1; i++){ g.drawLine(i, (this.alto/2)-puntos[i], i+1, (this.alto/2)-puntos[i+1]); } } } }
La anterior clase presenta las siguientes características:
- Posee un método actualizar que asigna el objeto senal.
- Posee un método paint que coloca el fondo blanco al panel, la cuadrícula y dibuja la senal basado en los puntos retornados por Seno y Coseno. Pintar la gráfica consiste en pintar un conjunto de líneas desde un punto hasta el punto siguiente. El método drawLine, permite dibujar las líneas en mención. Los parámetros del eje Y, tienen un valor que depende del alto del panel, con el fin de ubicar la senal en la mitad.
Los resultados se presentan en las siguientes figuras.
No hay comentarios:
Publicar un comentario