Paola Magillo, Univestita' di Genova,
Corso di Interfacce Utente per Informatica, a.a. 2007-2008.
LABORATORIO - INTEGRAZIONE E AGGIUNTA ALL'ESERCIZIO SUL GRAFO
Integrazione: Zoom e traslazione del grafo
Nell'integrazione precedente abbiamo
visto come trasportare il dominio del grafo nella finestra di
visualizzazione (con alcune varianti).
Secondo la variante scelta, siano
- x_device = f1(x)
- y_device = f2(Y)
le equazioni di questra trasformazione (dove x,y sono le coordinate
di un nodo nel dominio e x_device,y_device le coordinate dello
stesso nodo in pixel).
Zoom
Il testo chiede inoltre di eseguire lo zoom
(scalatura) del grafo tenendo fermo il baricentro del grafo.
Se dovessimo eseguire solo questa trasformazione (dimenticando
che dobbiamo anche trasportare in pixel), le equazioni sarebbero:
- x_zoom = (x-xC)*zoom + xC
- y_zoom = (y-yC)*zoom + yC
dove xC = x0+0.5*deltaX e yC = y0+0.5*deltaY sono le coordinate
del baricentro del dominio e zoom e' il fattore di zoom corrente.
Dobbiamo combinare le due trasformazioni:
- prima eseguire lo zoom
- poi portare tutto in pixel
Percio'
- x_device = f1(x_zoom)
- y_device = f1(y_zoom)
Si consiglia di organizzare il codice in maniera
modulare con due funzioni distinte, corrispondenti ai due
passaggi qui descritti.
Traslazione
La traslazione invece sposta l'immagine disegnata di un
certo numero di pixel nella direzione prescelta.
Va fatta per ultima, sulle coordinate del nodo gia' tradotte
in pixel, cioe'
- x_traslato = x_device + trasla_x = f1(x_zoom) + trasla_x
- y_traslato = y_device + trasla_y = f1(y_zoom) + trasla_y
Aggiunta: archi con spessore
L'obiettivo e' modificare
l'esercizio sul grafo
disegnando gli archi come linee o come poligoni con spessore,
a seconda dello zoom.
Procederemo a tappe.
Tappa 1: leggere e memorizzare gli spessori
Nel nostro grafo ora ogni arco ha uno spessore.
Lo spessore e' un numero intero che si suppone espresso
nell'unita' di misura dello stesso sistema di riferimento
del dominio del grafo.
Questo significa che lo spessore e' compatibile con le posizioni
dei nodi e le lunghezze degli archi, non succedera' mai che
due archi spessi si intersechino, se non nei pressi dell'eventuale
estremo in comune.
Figura: grafo e lo stesso grafo dove gli archi sono disegnati con
spessore. Gli archi verde e magenta hanno un tipo di intersezione
non permessa nei nostri grafi.
Questo significa che:
- nel file di input oltre agli indici dei due estremi dell'arco
c'e' un numero double che rappresenta lo spessore.
- nella vostra struttura dati dovete aggiungere un array per
gli spessori degli archi
- nel disegnare, dovete tener conto dello spessore,
lo faremo nella prossima tappa...
Esempi di grafi (gli stessi che nel
testo originale) con spessore:
- punti02sp grafo con 2 vertici e un arco che
li collega, spesso 0.4
- punti03sp grafo con 3 vertici e un arco che
collega i primi due (il terzo vertice non ha archi)
- punti10sp grafo con 10 vertici
Tappa 2: disegnare con spessore
Un arco di estremi p1, p2 con spessore
e' disegnato come un quadrilatero avente:
-
due lati della stessa lunghezza del segmento p1-p2,
paralleli a p1-p2 e ciascuno situato a distanza pari a meta' spessore
da p1-p2, uno a destra e uno a sinistra
-
due lati perpendicolari al segmento p1-p2,
di lunghezza pari allo spessore,
e passanti uno per p1 e uno per p2
Per poter disegnare questo quadrilatero, dobbiamo calcolare
le coordinate dei suoi 4 vertici.
I due vertici del segmento corto tangente a p1 [p2]
sono dati da p1 [p2] traslato di piu o meno
meta' spessore nella direzione della normale al segmento p1-p2.
La normale n = (nx,ny) al segmento p1-p2 si trova secondo la formula
geometrtica:
- nx = y1-y2 / L
- ny = x2-x1 / L
- dove L = radice quadrata di ( (x1-x2)^2 + (y1-y2)^2 )
Percio' i 4 vertici del quadrilatero sono:
- p1 - 0.5 * spessore * n
- p1 + 0.5 * spessore * n
- p2 + 0.5 * spessore * n
- p2 - 0.5 * spessore * n
Note utili
Il quadrilatero cosi' ottenuto (ovvero i suoi 4 vertici) e' espresso
nel sistema di riferimento del dominio e dovra'
essere poi trasformato nel sistema di riferimento della finestra grafica.
Il quadrilatero non ha i lati paralleti agli assi coordinati per
cui non si puo' disegnare usando funzioni come drawRect.
Se fate le trasformazioni a mano, dopo aver ottenute le coordinate
intere finali potete usare la funzione drawPolygon di Graphics
Se usate le trasformazioni affini di Graphics2D,
dovete creare con i 4 punti un oggetto di classe
GeneralPath (che implementa Shape) e poi disegnarlo
con la funzione draw.
Un general path gp funziona come una penna scrivente che si muove
con comandi: muove la penna nel primo punto gp.lineto(x1,y1);
traccia linea ai 3 punti seguenti gp.lineto(x2,y2); gp.lineto(x3,y3);
gp.lineto(x4,y4); infine chiude ricollegando line al primo punto
gp.closePath().
Tappa 2: adattare il disegno allo zoom corrente
Quando il grafo e' disegnato con un fattore di zoom molto
piccolo, gli archi disegnati spessi possono
intersecarsi in modo anomalo o comunque risultare
troppo fitti e impedire di leggere il disegno.
Quando il fattore di zoom e' troppo piccolo, non disegnamo
l'arco con spessore (quadrilatero) ma disegnamo l'arco senza
spessore (segmento di retta).
Come decidere quando lo zoom e' troppo piccolo?
Un possibile modo e' questo.
-
Calcolare i due estremi a,b di uno dei lati corti del quatrilatero
-
Tradurre a,b in coordinate della finestra (che sono espresse in pixel)
-
Guardare quanto sono distanti fra loro a,b in pixel
-
Se sono piu' distanti di una soglia (es. 3 pixel)
allora disegnare l'arco come quadrilatero, altrimenti disegnarlo
come segmento di retta
Aggiornamento del disegno
Il disegno deve cambiare, per esempio, quando faccio zoom o
traslazione. Intervengono due funzioni:
- la funzione di disegno paint / paintComponent
del pannello che ospita il grafo
- la funzione del listener associato al dispositivo
che esegue l'operazione di zoom / traslazione
La regola generale e':
-
La funzione del listener non contiene chiamate a
funzioni inerenti al disegno (comprese trasformazioni), ma
contiene solo riassegnamenti di variabili globali
(es. la variabile che tiene il fattore di zoom corrente)
e la chiamata finale alla funzione
repaint sul pannello di disegno
-
Tutti i comandi inerenti al disegno (comprese trasformazioni)
vanno messe nella funzione di disegno.
Tale funzione disegna il grafo usando i valori correnti delle
variabili globali (che le sono stati appena cambiati dal
listener).
-
Qui per variabile "globale" si intende una variabile definita
come attributo (in opposizione a variabile "locale"
della funzione di listener o di quella di disegno).