Figure geometriche

Un applet che disegna delle linee

Vediamo ora un semplice uso degli applet, ossia quello di fare dei disegni geometrici. Il punto fondamentale è che ogni finestra è composta da un numero finito di punti, detti pixel. Per poter fare riferimento a un punto (per esempio per poter disegnare una linea che parte da un pixel e arriva a un altro) esiste un sistema di coordinate in Java. Una finestra viene vista come un piano cartesiano, in cui l'origine si trova in alto a destra, e le coordinate Y procedono scendendo invece di salire.

Nell'esempio, è stato evidenziato il pixel che ha coordinate (6,5). Il pixel di coordinate (0,0) è quello più in alto e più a sinistra della finestra. La coordinata X aumenta muovendosi verso destra, mentre la coordinata Y aumenta andando verso il basso. Questo secondo punto va ricordato, dal momento che nel normale piano cartesiano la coordinata Y aumenta nell'altra direzione.

Il sistema di coordinate permette di specificare delle posizioni nella finestra, e quindi permette di fare dei disegni. Per disegnare una linea fra due punti di coordinate (a,b) e (c,d), in Java si usa la funzione drawLine(a,b,c,d). Per poter chiamare questa funzione, occorre prependere l'argomento della funzione paint, ossia quello che sta fra parentesi dopo il Graphics, in questo caso g (ma si può usare un qualsiasi altro nome). Se usiamo lo schema visto sopra, per disegnare una linea si fa g.drawLine(a,b,c,d).

L'applet DueLinee.java disegna due linee.

/*
  Un semplice applet: due linee in diagonale.
*/

import java.awt.*;

public class DueLinee extends java.applet.Applet {
  public void paint(Graphics g) {
    g.drawLine(0, 0, 100, 100);
    g.drawLine(0, 100, 100, 0);
  }
}

La prima istruzione è g.drawLine(0, 0, 100, 100). Questa istruzione disegna una linea fra i due punti di coordinate (0, 0) e (100, 100).

La seconda istruzione è g.drawLine(0, 100, 100, 0), che disegna una nuova linea. I due estremi hanno coordinate (0, 100) e (100, 0).

Le due linee sono quindi in diagonale. L'esecuzione di questo applet produce una finestra che contiene il seguente disegno.

Disegna una griglia

Si vuole disegnare una griglia, ossia un quadrato suddiviso in altri quadrati più piccoli, tutti della stessa dimensione.

La istruzione di stampa della linea in questo caso è sufficente. Si tratta infatti di disegnare una serie di linee verticali e una serie di linee orizzontali. Prendiamo per esempio le linee verticali. Supponiamo che la dimensione (larghezza e altezza) totale della griglia sia 200, e che i quadratini devono avere lato 10. Allora, le linee verticali sono fatte come nel disegno, in cui sono riportate anche le coordinate dei punti estremi di ogni linea.

A parole: la prima linea va da (0,0) a (0,200). La seconda linea va da (10,0) a (10,200), la terza da (20,0) a (20,200). A questo punto è chiaro che occorre disegnare tutte le linee da (x,0) a (x,200), in cui x parte da 0 e arriva a 200, con un incremento di 10 a ogni passo.

Il programma completo Griglia.java viene realizzato usando un ciclo per disegnare le linee verticali, e un altro ciclo per disegnare le linee orizzontali.

/*
  Disegna una griglia.
*/

import java.awt.*;

public class Griglia extends java.applet.Applet {
  public void paint(Graphics g) {
    int x,y;

    /* disegna le linee verticali */
    for(x=0; x<=200; x=x+10) {
      g.drawLine(x, 0, x, 200);
    }

    /* disegna le linee orizzontali */
    for(y=0; y<=200; y=y+10) {
      g.drawLine(0, y, 200, y);
    }
  }
}

Quando si esegue il programma, si ottiene il seguente grafico.

Rettangoli

Si supponga di voler disegnare un rettangolo, i cui lati orizzontali abbiano coordinate 50 e 200, e i cui lati verticali abbiano coordinate 20 e 80. Questo è già possibile utilizzando la funzione di disegno della linea, usando quattro istruzioni separate. I vertici del rettangolo hanno coordinate (20, 50), (20, 200), (80, 200), (80, 50), quindi è necessario disegnare quattro linee fra questi punti, come viene fatto nel programma Rettangolo.java.

/*
  Disegna un rettangolo usando quattro linee.
*/

import java.awt.*;

public class Rettangolo extends java.applet.Applet {
  public void paint(Graphics g) {
    g.drawLine(20, 50, 20, 200);
    g.drawLine(20, 200, 80, 200);
    g.drawLine(80, 200, 80, 50);
    g.drawLine(80, 50, 20, 50);
  }
}

È possibile disegnare un rettangolo usando una sola istruzione. L'istruzione drawRect(x, y, w, h) disegna un rettangolo in cui x e y sono le coordinate del vertice in alto a sinistra del rettangolo, mentre i valori di w e h sono la larghezza e l'altezza del rettangolo. È chiaro che questi valori sono sufficienti a determinare completamente la forma e la posizione del rettangolo. Per esempio, per disegnare un rettangolo i cui estremi opposti abbiano coordinate (x1, y1) e (x2, y2), la istruzione che occorre eseguire è drawRect(x1, y1, x2-x1, y2-y1), dal momento che x2-x1 è la larghezza del rettangolo mentre y2-y1 è la sua altezza. Il programma RettUno.java disegna lo stesso rettangolo del programma di sopra, questa volta usando la istruzione drawRect.

/*
  Disegna un rettangolo usando quattro linee.
*/

import java.awt.*;

public class RettUno extends java.applet.Applet {
  public void paint(Graphics g) {
    g.drawRect(20, 50, 60, 150);
  }
}

Si ricorda che le istruzioni grafiche come la drawLine e drawRect vanno usate mettendo sempre prima l'argomento di paint, ossia la stringa fra parentesi dopo il Graphics (in questo caso, g).

L'esecuzione produce il seguente disegno:

Grafico elementi di un vettore

Si supponga dato un vettore di interi vett. Si vuole stampare la sua rappresentazione grafica con rettangoli. In altre parole, si vogliono tanti rettangoli l'uno accanto all'altro quanti sono gli elementi del vettore, e ogni rettangolo deve essere di altezza proporzionale al contenuto dell'elemento del vettore (si veda più sotto per degli esempi).

Dal momento che occorre un rettangolo per ogni elemento del vettore, è necessario un ciclo in cui una variabile i assume tutti i valori da 0 fino al numero di elementi del vettore meno 1. Ad ogni passo, è necessario disegnare un rettangolo. Occorre decidere come deve esseere fatto questo rettangolo. Sappiamo che la sua altezza deve essere proporzionale a vett[i]. Per quello che riguarda la specifica dell'altezza siamo quindi a posto. Per la larghezza, possiamo assumere che tutti i rettangoli siano larghi 10.

Ci mancano quindi solo la coordinate dell'angolo in alto a sinistra del rettangolo. Per quello che riguarda la coordinata y, è facile: da momento che tutti i rettangoli devono partire dallo stesso piano, possiamo mettere per esempio y=100 per tutti i rettangoli.

La coordinata x si determina sapendo che i rettangoli vanno messi l'uno dietro l'altro. Il rettangolo che corrisponde a vett[0], per esempio, può essere compreso fra le coordinate 0 e 10, mentre quello di vett[1] sta fra 10 e 20, ecc. Non è difficile capire che il rettangolo che corrisponde a vett[i] si trova tra le coordinate x di valore i*10 e (i+1)*10. La coordinata x dell'angolo in alto a sinistra è quindi data da i*10.

Il programma GraficoRect.java realizza il grafico usando esattamente questo tipo di coordinate.

/*
  Grafico dei valori di un vettore usando rettangoli.
*/

import java.awt.*;

public class GraficoRect extends java.applet.Applet {
  public void paint(Graphics g) {
    int vett[]={3, 4, -5, -6, 2, 4, 4, -2, 0, 2, -9, 2, 9, 4, 5};
    int i;

    for(i=0; i<=vett.length-1; i=i+1) {
      g.drawRect(10*i, 100, 10, vett[i]*10);
    }
  }
}

L'output prodotto dal programma è il seguente:

Naturalmente, non è esattamente quello che si voleva ottenere. Per prima cosa, i valori negativi non sono stati disegnati. Secondo, i valori positivi corrispondono a rettangoli che ``scendono'' anzichè salire. Il primo problema si risolve con una istruzione condizionale che controlla se il contenuto di un elemento del vettore è positivo oppure negativo. Per quello che riguard il secondo problema, occorre cambiare gli argomenti della istruzione di stampa del rettangolo. Non si entra qui nel dettaglio di come questa cosa vada fatta. Il programma completo è GraficoRectDue.java:

/*
  Grafico dei valori di un vettore usando rettangoli:
  versione corretta che ammette valori negativi.
*/

import java.awt.*;

public class GraficoRectDue extends java.applet.Applet {
  public void paint(Graphics g) {
    int vett[]={3, 4, -5, -6, 2, 4, 4, -2, 0, 2, -9, 2, 9, 4, 5};
    int i;

    for(i=0; i<=vett.length-1; i=i+1) {
      if( vett[i]<0 ) {
        g.drawRect(10*i, 100, 10, -vett[i]*10);
      }
      else {
        g.drawRect(10*i, 100-vett[i]*10, 10, vett[i]*10);
      }
    }
  }
}

Il grafico che viene prodotto è:

Poligoni

Un poligono è una sequenza di punti, collgati fra loro con dei segmenti. Ogni punto è collegato al seguente, e l'ultimo è collegato con il primo.

Per disegnare un poligono in Java, si può chiaramente usare la funzione drawLine, ma esiste anche un meccanismo che rende le cose più facili. Il procedimento consiste in tre passi:

  1. dichiarazione di una variabile di tipo poligono
  2. assegnazione di valori a questa variabile
  3. disegno del poligono
Esistono due modi per effettuare i primi due passi.

Definizione di poligoni con vettori

Si definiscono due vettori di interi, che contengono le coordinate x e y dei punti del poligono. Poi si dichiara una variabile di tipo poligono:
    int x={....};
    int y={....};
    Polygon pol=new Polygon(x, y, x.lenght);
Le prime due istruzioni creano due vettori, che devono avere la stessa dimensione. L'idea è che il primo punto avrà coordinate x[0],y[0], il secondo punto avrà coordinate x[1],y[1], e così via. La terza istruzione dichiara il poligono: in questo modo, viene creata una nuova variabile pol, ossia viene creata una zona di memoria in cui viene memorizzato un poligono. In particolare, il poligono è formato da x.lenght punti (numero di elementi del vettore x), e le coordinate dei suoi punti sono determinate dai valori memorizzati nei vettori x e y.

Aggiunta di punti a un poligono

Il secondo modo per creare un poligono è quello di partire da un poligono vuoto, e usare una funzione per aggiungere ogni volta un nuovo punto. Per creare un poligono vuoto, si usa la dichiarazione seguente.

    Polygon nomepoligono=new Polygon();
Questa dichiarazione crea un poligono che non contiene punti. Per poter definire il poligono che serve a noi, occorre aggiungere dei punti al poligono. La istruzione che aggiunge un punto a un poligono è:

    nomepoligono.addPoint(x,y);
Questa istruzione aggiunge il punto di coordinate x,y al poligono che è stato definito con nome nomepoligono. Partendo da un poligono vuoto e aggiungendo uno per volta i punti necessari, si può creare qualsiasi poligono.

Disegno del poligono

La creazione di un poligono (sia nel primo che nel secondo modo) non produce nessun risultato visibile nella finestra. In altre parole, dichiarare una variabile di tipo poligono, o aggiungere ad essa dei punti, non fa sí che il poligono venga disegnato. Per disegnare un poligono occorre mettere una istruzione drawPolygon. Il programma Poligoni.java disegna due poligoni pol e p, il primo dei quali viene definito usando i vettori di interi, mentre il secondo è creato vuoto e gli vengono poi aggiunti dei punti.
/*
  Disegna due poligoni.
*/

import java.awt.*;

public class Poligoni extends java.applet.Applet {
  public void paint(Graphics g) {
    /* metodo 1: definizione con due vettori */
    int x[]={10, 20, 30, 40};
    int y[]={5, 30, 5, 21};
    Polygon pol=new Polygon(x, y, x.length);


    /* metodo 2: si parte dal poligono vuoto e si aggiungono
       ogni volta dei punti */
    Polygon p=new Polygon();

    p.addPoint(100,100);
    p.addPoint(100,180);
    p.addPoint(180,140);
    p.addPoint(160,110);


    /* disegna i poligoni */
    g.drawPolygon(pol);
    g.drawPolygon(p);
  }
}

Le due istruzioni finali del programma sono quelle che disegnano effettivamente i due poligoni. L'esecuzione del programma produce il seguente risultato.

Poligoni regolari

Il seguente esercizio può essere risolto facilmente usando i poligoni e le operazioni trigonometriche. In particolare, ci occorre il valore di Pi greco, che è rappresentato dalla variabile Math.PI, e le funzioni seno e coseno, che sono realizzate in Java come Math.sin e Math.cos.

Quello che si vuole fare è disegnare un poligono regolare con n lati.

L'esercizio si risolve tenendo presente che ogni poligono regolare si può sempre inscrivere in un cerchio. Il problema si sposta quindi dal disegno del poligono alla individuazione di n punti sulla circonferenza. Il medodo che usiamo è questo: l'angolo giro vale 2*Math.PI, e questo va diviso esattamente in n parti. Quindi, possiamo fare un ciclo, in cui una variabile i va da 0 a n. L'angolo che consideriamo ogni volta vale 2*Math.PI*i/n.

Una volta noto l'angolo corrispondente ai punti della circonferenza che ci servono, occorre calcolare le loro coordinate. Se per esempio assumiamo che il centro del cerchio sia posizionato in (xcentro,ycentro), e che il suo raggio sia memorizzato nella variabile raggio, allora le coordinate di un punto si ottengono usando le note formule trigonometriche:

      x=xcentro+raggio*Math.cos(2*Math.PI*i/n);
      y=ycentro+raggio*Math.sin(2*Math.PI*i/n);
Una volta note le coordinate di un punto, è sufficiente aggiungere il punto al poligono che è stato inizialmente dichiarato vuoto. Il programma completo Regolari.java viene riportato qui sotto.

/*
  Disegna un poligono regolare con n lati
*/

import java.awt.*;

public class Regolari extends java.applet.Applet {
  public void paint(Graphics g) {
    int n=8;
    Polygon reg=new Polygon();
    int i;
    double x,y;
    double xcentro=100, ycentro=100, raggio=100;

    for(i=0; i<=n-1; i=i+1) {
      x=xcentro+raggio*Math.cos(2*Math.PI*i/n);
      y=ycentro+raggio*Math.sin(2*Math.PI*i/n);

      reg.addPoint( Math.round((float) x), Math.round((float) y) );
    }

    g.drawPolygon(reg);
  }
}

L'esecuzione di questo programma, in cui il centro è stato posto in (100,100), il raggio è 100, ed n vale 8, produce il disegno seguente:

Cerchi e ovali

In Java, non esiste una istruzione specifica per disegnare un cerchio. Esiste però una istruzione per disegnare un ovale, e questa può venire usata anche per disegnare cerchi.

La istruzione per gli ovali si chiama drawOval(x,y,w,h), e ha quattro argomenti, che rappresentano posizione e dimensione del rettangolo circoscritto all'ovale. Il modo più facile per capire come funziona il disegno di ovali è di pensare che x,y,w,h rappresentano un rettangolo, e che l'ovale che viene disegnato è quello circoscritto nel rettangolo.

Per esempio, dal punto di vista concettuale, possiamo pensare prima al rettangolo che verrebbe disegnato usando gli stessi quattro numeri:

L'ovale che viene realmente disegnato è quello inscritto in questo rettangolo (in altre parole, il rettangolo non viene disegnato):

Il programma Ovali.java disegna due ovali e un cerchio. In particolare, il secondo ovale viene disegnato con la istruzione g.drawOval(100,50,50,120), e per confronto si fa vedere cosa succede se si disegna un rettangolo usando gli stessi parametri, ossia si fa anche g.drawRect(100,50,50,120).

/*
  Disegna due ovali e un cerchio
*/

import java.awt.Graphics;

public class Ovali extends java.applet.Applet {
  public void paint(Graphics g) {
    /* disegna un ovale */
    g.drawOval(0,0,100,50);

    /* un altro ovale, e il rettangolo con le stesse dimensioni */
    g.drawOval(100,50,50,120);
    g.drawRect(100,50,50,120);

    /* un cerchio: e' un ovale con larghezza=altezza */
    g.drawOval(0,100,50,50);
  }
}

Il programma produce il seguente disegno:

La coppia ovale-rettangolo fa capire in che modo viene disegnato l'ovale: quando si esegue la istruzione g.drawOval(x,y,w,h), è come se venisse localizzato il rettangolo prodotto da g.drawRect(x,y,w,h), ma quello che si disegna in effetti è l'ovale inscritto. Nel nostro caso, abbiamo poi disegnato anche il rettangolo.

L'ultima istruzione dell'applet disegna un cerchio: questo si ottiene semplicemente usando lo stesso numero come altezza e larghezza del rettangolo circoscritto: in questo modo il rettangolo diventa un quadrato, e quindi l'ovale all'interno è un cerchio.

Simbolo delle olimpiadi

Lo scopo di questo esercizio è quello di scrivere un applet che disegna il simbolo delle olimpiadi.

Usando la istruzione drawOval la cosa risulta particolarmente facile: i tre cerchi in alto sono messi l'uno accanto all'altro, per cui sono circoscritti da tre quadrati messi l'uno a fianco dell'altro.

I due cerchi di sotto si trovano spostati sia a destra che in basso di esattamente il raggio dei cerchi. Il raggio è la metà dal lato del quadrato circoscritto. Questo permette di disegnare gli altri due cerchi.

Il programma completo è Olimpiadi.java, di cui si riporta qui sotto il codice:

/*
  Disegna il simbolo delle olimpiadi.
*/

import java.awt.*;

public class Olimpiadi extends java.applet.Applet {
  public void paint(Graphics g) {
    /* i tre cerchi di sopra */
    g.drawOval(0, 0, 20, 20);
    g.drawOval(20, 0, 20, 20);
    g.drawOval(40, 0, 20, 20);

    /* i tre cerchi di sotto */
    g.drawOval(10, 10, 20, 20);
    g.drawOval(30, 10, 20, 20);
  }
}


Questo programma produce il seguente risultato:

Archi

Java mette a disposizione una istruzione per disegnare archi di cerchio e di ovale. L'istruzione si chiama drawArc, e ha i seguenti argomenti:

drawArc(x,y,w,h,s,n);

Un arco è semplicemente una parte di un cerchio o di una ellisse. Il modo più semplice di dire come deve essere fatto un arco è quello di specificare come è fatta l'ellisse completa, e poi di dare l'indicazione del punto in cui l'arco comincia e quello in cui finisce. In Java, l'ellisse si specifica esattamente in questo modo: i primi tre numeri x,y,w,h sono esattamente la specifica della ellisse ``completa'', di cui l'arco fa parte; il numero s è il numero di gradi del punto in cui l'arco inizia; n è invece la ``lunghezza'' dell'arco in gradi.

Consideriamo di nuovo il disegno dell'ovale:

Quello che dobbiamo fare è segnare l'angolo che corrisponde a s, e da qui partiamo con un angolo ampio n.

La parte di ovale che viene realmente disegnata è solo quella compresa nell'angolo ampio n.

Non è difficile rendersi conto che questi sei numeri che vanno messi nella istruzione drawArc sono in grado di caratterizzare completamente l'arco: infatti, una volta specificato come è fatta l'ellisse completa, e i punti in cui inizia e finisce l'arco, questo è completamente specificato.

Il programma Archi.java disegna alcuni archi nella finestra.

/*
  Disegna degli archi.
*/

import java.awt.*;

public class Archi extends java.applet.Applet {
  public void paint(Graphics g) {
    g.drawArc(0,0,100,100,45,360-90);

    g.drawArc(100,0,100,100,-45,90);
  }
}

Il risultato dell'esecuzione di questo programma è riportato sotto.

Una figura composta

Si vuole disegnare la figura seguente:

Come si può facilemente notare, questa figura è in effetti composta da due segmenti e da due archi di cerchio. I due segmenti non presentano nessuna difficoltà: il segmento orizzontale si trova tra le coordinate (200,100) e le coordinate (250,100), mentre il secondo si trova tra i due punti (300,150) e (300,200).

Per quello che riguarda gli archi, occorre per prima cosa individuare le coordinate dell'ovale di cui fanno parte. In questo caso, si tratta di archi di cerchio. Il cerchio piccolo è inscritto nel quadrato che ha l'angolo in alto in (200,100) e ha lato 100. Inoltre, l'arco parte da 0 gradi e si estende per 90 gradi. Quindi, l'istruzione necessaria per disegnare questo arco è:

    g.drawArc(200,100,100,100,0,90);
Per capire quali sono le coordinate dell'arco grande usiamo lo stesso metodo: il quadrato circosritto ha l'angolo in alto a sinitra nelle coordinate (100,100), e il lato è 200. Inoltre, l'arco parte da 90 gradi e si estende fino ai 360, ossia ha ampiezza 360-90. L'istruzione per disegnare questo arco è quindi:

    g.drawArc(100,100,200,200,90,360-90);
Il programma completo Composta.java è riportato qui sotto.

/*
  Disegna una figura fatta di segmenti e archi
*/

import java.awt.*;

public class Composta extends java.applet.Applet {
  public void paint(Graphics g) {

	/* i due segmenti */
    g.drawLine(200,100,250,100);
    g.drawLine(300,150,300,200);

	/* l'arco piccolo */
    g.drawArc(200,100,100,100,0,90);

	/* l'arco grande */
    g.drawArc(100,100,200,200,90,360-90);
  }
}

Il risultato dell'esecuzione di questo applet è esattamente il disegno che ci aspettavamo.