Diagramas

prev.gif (997 bytes)chapter.gif (1105 bytes)home.gif (1232 bytes)next.gif (998 bytes)

Ejemplo completos

Diseño del applet

La jerarquía de clases

La clase que describe el canvas

El código fuente


Este ejemplo es muy instructivo, ya que se incluyen muchos aspectos que hemos estudiado: establecer una jerarquía de clases, el enlace dinámico, la respuesta a las acciones del usuario sobre los controles, el control canvas, la clase StringTokenizer que nos permite dividir un string en substrings, el diseño del applet empleando la técnica de paneles anidados. Como en otros ejemplos que hemos comentado es importante para el mantenimiento del código identificar las distintas partes que entran en el proyecto.

 

Diseño del applet

El applet está diseñado de modo que en la parte izquierda se introducen los datos en un control área de texto. Se introduce un dato, se pulsa la tecla Retrono o Enter, luego se introduce otro dato y así sucesivamente. Cada dato está en una fila y todos ellos en una única una columna.

En la parte derecha, se sitúa un canvas en el que tiene lugar la representación gráfica de los datos, bien sea en forma de tarta o en forma de barras.

Debajo del control área de texto, se sitúan dos botones uno titulado Gráfica para procesar los datos introducidos y otro Borrar, para eliminar los datos del control área de texto, y prepararlo para  introducir otros nuevos.

Debajo de estos botones, se sitúan un grupo de dos botones de radio que permite seleccionar el tipo de gráfico: diagrama de barras o gráfico en forma de tarta.

Para disponer los distintos controles se emplea la aproximación de paneles anidados tal como puee verse en la figura. Sobre el applet se sitúa el Panel3 y el canvas. Sobre el Panel3 se sitúa el control área de texto y el Panel2. Sobre el Panel2 el Panel5 y el Panel6. Se aplica el gestor de diseño BorderLayout. Sobre el Panel5 se colocan dos botones titulados Gráfica y Borrar, y sobre el Panel6 los botones de radio titulados Barras y Tarta. Se disponen los controles sobre sus respectivos paneles aplicando el gestor de diseño FlowLayout.

Todas estas operaciones se realizan con suma facilidad en el modo de diseño del IDE de JBuilder, si antes tenemos una idea previa de como va a quedar el applet, o disponemos de un esquema similar al de la figura adjunta.

grafico2.gif (5221 bytes)

Respuesta a las acciones del usuario sobre los controles

En modo diseño, se hace doble-clic sobre los botones Gráfica y Borrar. El IDE asocia cada botón a un objeto de una clase anónima que implementa el interface ActionListener. El programador solamente necesita escribir el código entre las llaves de apertura y cierre de la función respuesta cuyo nombre ha generado JBuilder.

En la función respuesta btnGrafica_actionPerformed a la pulsación sobre el botón titulado Gráfica, se obtiene el texto del control área de texto mediante getText, y se guarda en el string local texto. Se obtiene también, el botón de radio que está activado mediante la función getSelectedCheckbox de la clase CheckboxGroup. Finalmente, le pasamos al canvas los datos introducidos en el área de texto en forma de un string único, y el tipo de gráfico que se va a representar, un entero cuyo valor es cero o la constante Grafico.BARRAS o un uno, o la constante Grafico.TARTA. Ambas constantes son miembros estáticos públicos de la clase Grafico que describiremos más adelante.

  void btnGrafica_actionPerformed(ActionEvent e) {
     String texto=tDatos.getText();
     Checkbox radio=chkGrupo.getSelectedCheckbox();
     int tipo=(radio.equals(chkBarra)) ? Grafico.BARRAS : Grafico.TARTA;
     canvas.setDatos(texto, tipo);
  }

La definición de la función respuesta btnBorrar_actionPerformed a la acción de pulsar sobre el botón titulado Borrar es muy simple, borra el texto del área de texto.

  void btnBorrar_actionPerformed(ActionEvent e) {
     tDatos.setText("");
  }

 

La jerarquía de clases

Podíamos diseñar una clase independiente que describiese cada uno de los tipos de gráficos. Pero es mucho más elegante establecer una jerarquía de clases, ya que las clases GraficoTarta y GarficoBarras comparten los mismos datos y solamente se diferencian en el modo en el que presentan dichos datos. Por lo tanto, definimos una clase base abstracta denominada Grafico, que describe las características comunes a GraficoTarta y GraficoBarras, y declara una función abstracta dibuja que se define en cada una de las clases derivadas.

La clase base Grafico declara una serie de miembros dato, el más importante es el array valores que guarda los datos introducidos en el control área de texto, y el número n de datos. Para representar gráficamente los datos es necesario definir el área de la representación gráfica: un origen x e y, y unas dimensiones ancho y alto. Alternativamente, podemos encapsular estos cuatro datos en un objeto de la clase Rectangle.

Otros miembros de esta clase son constantes, se refieren al tipo de gráfico TARTA y BARRAS, al número máximo de datos MAXDATOS que es igual al máximo número de colores disponibles (esta restricción puede ser fácilmente levantada para representar cualquier número de datos). Finalmente, un array con los colores disponibles para representar los datos.

En el constructor de la clase Grafico se inicializan los miembros dato no estáticos. Se halla el valor máximo de los datos introducidos y se dividen entre el valor máximo. Se transforma así un conjunto de datos en otro equivalente cuyos valores están comprendidos entre 0.0 y 1.0.

public abstract class Grafico {
    public static final int TARTA=1;
    public static final int BARRAS=0;
    protected int x, y;                     //posición del origen
    protected int ancho, alto;              //dimensiones del gráfico
    public static final int MAXDATOS=10;
    protected double[] valores=new double[MAXDATOS];
    protected int n;                         //número de valores
 //colores del gráfico
    protected static final Color colores[]={new Color(255,0,0), new Color(0, 255,0), new Color(0, 0, 255),
          new Color(0, 255, 255), new Color(255, 0, 255), new Color(255, 255, 0),
          new Color(128, 0, 0), new Color(255, 128, 0),
          new Color(128, 128, 255), new Color(255, 0, 128)};

    public Grafico(int x, int y, int ancho, int alto, double[] valores) {
        this.x=x;
        this.y=y;
        this.ancho=ancho;
        this.alto=alto;
        this.valores=valores;
        n=valores.length;
        double maximo=0.0;
        for (int i=0; i<n; i++) {
            if (Math.abs(valores[i])>maximo){
                  maximo = Math.abs(valores[i]);
            }
        }
      for (int i=0; i<n; i+=1) {
            valores[i] = valores[i]/maximo;
      }
  }
  public abstract void dibuja(Graphics g);
}

Las clases derivadas de Grafico definen la función dibuja declarada abstracta en la clase base. Las clases derivadas no declaran nuevos miembros dato, por lo que el constructor de la clase derivada se limita a llamar al constructor de la clase base.

 

Diagrama de barras

La clase derivada GarficoBarras hereda los miembros dato de la clase base y define un constructor, que se limita a llamar al constructor de la clase base.

La función miembro dibuja, divide el ancho del área de la representación gráfica entre los n datos, y lo guarda en la variable local dx, pero dibuja una barra cuya anchura anchoBarra que es las tres cuartas partes de dx. La altura de la barra i es porpocional a valores[i], la altura de la barra que corresponde al valor máximo es igual al alto del área de la representación gráfica. Para dibujar las barras en el contexto gráfico g con apariencia plana se llama a la función fillRect, para que tenga un aspecto tridimensional, más estético, se llama a fill3DRect.

class GraficoBarras extends Grafico {
   public GraficoBarras(int x, int y, int ancho, int alto, double[] valores) {
      super (x, y, ancho, alto, valores);
    }
   public void dibuja (Graphics g) {
      int anchoBarra=3*ancho/(4*n);
      int dx = ancho/n;
      int h;
       for(int i=0; i<n; i++) {
            g.setColor (colores[i]);
            h=(int)(valores[i]*alto);
            g.fill3DRect (x+dx*i, y-h, anchoBarra, h, true);
      }
   }
}

 

Diagrama de tarta

La clase derivada GraficoTarta es similar a GraficoBarras solamente se diferencia en la definición de la función dibuja.

Para dibujar un diagrama de tarta, necesitamos conocer la suma de todos los valores que guarda el array valores. Para hallar el ángulo de cada porción de la tarta se hace una regla de tres: si al valor total le correponden 360º a un valor concreto de índice i, valores[i], le corresponde el valor que se guarda en la variable local incAngulo. Cada porción de la tarta se dibuja en el contexto gráfico g llamando a la función fillArc

class GraficoTarta extends Grafico {
   public GraficoTarta(int x, int y, int ancho, int alto, double[] valores) {
      super (x, y, ancho, alto, valores);
   }
   public void dibuja (Graphics g) {
        double total = 0.0;
        for (int i=0; i<n; i+=1) {
            total += valores[i];
        }
        int angulo = 0, incAngulo;
        for(int i=0; i<n; i++) {
            incAngulo=(int)(360.0*valores[i]/total);
            g.setColor (colores[i]);
            g.fillArc (x, y-alto, ancho, alto, angulo, incAngulo);
            angulo+=incAngulo;
      }
  }
}

 

La clase que describe el canvas

Definimos una clase denominada MiCanvas derivada de Canvas, y creamos un objeto canvas de dicha clase en la función init de la clase que describe el applet. El canvas se añade al applet y se sitúa en la parte dercha del mismo.

    this.add(Panel3, BorderLayout.WEST);
    this.add(canvas, BorderLayout.CENTER);

En la función repuesta a la pulsación sobre el botón titulado Gráfica se pasa al canvas, por medio de la función miembro setDatos, información sobre los datos introducidos en un único string, y el tipo de gráfica (tarta o barras) para representar dichos datos.

La primera tarea de la función miembro setDatos, consistirá en romper el string texto, en un número de substrings igual al número de datos introducidos, y convertir los strings resultantes en números del tipo double. Para este propósito empleamos la clase StringTokenizer en la forma explicada en la página dedicada al estudio de esta clase.

El segundo paso, consiste en crear un objeto gráfico, cuya conducta sea la de representarse en el canvas. Dependiendo del tipo de gráfico elegido, se crea un objeto de la clase GraficoBarras o GarficoTarta. El valor devuelto por new al crear un objeto de la clase derivada se guarda en la variable grafico de la clase base Grafico.

     if(tipo==Grafico.BARRAS){
        grafico=new GraficoBarras(anchoCanvas/8, 7*altoCanvas/8, 
		3*anchoCanvas/4, 3*altoCanvas/4, datos);
      }else{
        grafico=new GraficoTarta(anchoCanvas/8, 7*altoCanvas/8, 
		3*anchoCanvas/4, 3*altoCanvas/4, datos);
      } 

Finalmente, se llama a paint para dibujar los gráficos. Ahora bien, hemos de tener cuidado, paint se llama cuando se crea el applet y se muestra en una ventana del navegador, cuando aún no hemos introducido ningún dato, ni hemos creado un objeto grafico, en esta circustancia si escribimos

  public void paint(Graphics g){
        grafico.dibuja(g);
  }

El miembro dato grafico no ha sido aún inicializado, obteniendo un error cuando llama a la función dibuja. En la consola vemos el nombre de la excepción NullPointerException que ha sido lanzada. Para evitar este error definimos paint de la siguiente forma.

  public void paint(Graphics g){
	if(grafico!=null){     
		grafico.dibuja(g);
	}
  }

El código completo de la clase MiCanvas que describe el control canvas es, el siguiente

public class MiCanvas extends Canvas {
      Grafico grafico;      //objeto gráfico

  public MiCanvas() {
     setBackground(Color.white);
  }
  void setDatos(String texto, int tipo){
      StringTokenizer t=new StringTokenizer(texto, "\n");
      int n=t.countTokens();
      double[] datos=new double[n];
      int i=0;
      while(t.hasMoreTokens()){
          String subTexto=(String)t.nextToken();
          if(i>=Grafico.MAXDATOS) break;
          datos[i]=Double.valueOf(subTexto.trim()).doubleValue();
          i++;
      }
//anchura y altura del canvas
      int anchoCanvas=getSize().width;
      int altoCanvas=getSize().height;
//objeto gráfico
      if(tipo==Grafico.BARRAS){
        grafico=new GraficoBarras(anchoCanvas/8, 7*altoCanvas/8, 3*anchoCanvas/4, 3*altoCanvas/4, datos);
      }else{
        grafico=new GraficoTarta(anchoCanvas/8, 7*altoCanvas/8, 3*anchoCanvas/4, 3*altoCanvas/4, datos);
      }
      repaint();
   }

  public void paint(Graphics g){
    if(grafico==null){
        Font font = new Font("TimesRoman", Font.BOLD, 16);
        g.setFont(font);
        FontMetrics fm = g.getFontMetrics(font);
         String texto="Introducir los datos";
        g.drawString(texto, (getSize().width - fm.stringWidth(texto)) / 2, ((getSize().height - fm.getHeight()) / 2) + fm.getAscent());
      }else{
        grafico.dibuja(g);
      }
  }
}

 

El código fuente

disco.gif (1035 bytes)grafico1: Grafico.java, GraficoApplet1.java MiCanvas.java