Crea la tua strada per componenti grafici personalizzati

I nostri componenti grafici personalizzati richiedono il disegno manuale, quindi avremo bisogno di una sottoclasse Canvas, che è il componente standard fornito per la manipolazione grafica diretta. La tecnica che useremo sarà quella di sostituire il paintmetodo Canvascon il disegno personalizzato di cui abbiamo bisogno. Useremo l' Graphicsoggetto, che viene automaticamente passato nel paintmetodo di tutti i componenti, per accedere ai colori e ai metodi di disegno.

Creeremo due componenti grafici personalizzati: un grafico a barre e un grafico a linee. Inizieremo costruendo una classe framework generale per i due grafici, che condividono alcuni elementi di base.

Costruire un framework grafico generico

Il grafico a linee e il grafico a barre che creeremo sono abbastanza simili da poter creare un generico

Graph

classe per eseguire alcuni dei noiosi lavori di layout. Fatto ciò, possiamo estendere la classe per il particolare tipo di grafico di cui abbiamo bisogno.

La prima cosa da fare quando si progettano componenti grafici personalizzati è mettere la penna su carta e disegnare un'immagine di ciò di cui si ha bisogno. Poiché stiamo contando i pixel, è facile confondersi sul posizionamento degli elementi. Pensare alla denominazione e al posizionamento degli elementi ti aiuterà a mantenere il codice più pulito e più facile da leggere in seguito.

Il grafico a linee e il grafico a barre utilizzano lo stesso layout per il titolo e le linee, quindi inizieremo creando un grafico generico contenente queste due caratteristiche. Il layout che creeremo è mostrato nella figura sotto.

Per creare la Graphclasse generica , faremo una sottoclasse Canvas. La regione centrale è dove verranno visualizzati i dati effettivi del grafico; lasceremo questo a un'estensione di Graphda implementare. Implementeremo gli altri elementi - una barra del titolo, una linea verticale a sinistra, una linea orizzontale in basso e i valori per l'intervallo - nella classe base. Potremmo specificare un carattere e codificare in modo rigido le misurazioni dei pixel, ma l'utente non sarebbe in grado di ridimensionare il grafico. Un approccio migliore consiste nel misurare gli elementi rispetto alla dimensione corrente del componente, in modo che il ridimensionamento dell'applicazione comporti un corretto ridimensionamento del grafico.

Ecco il nostro piano: prenderemo un Stringtitolo, un intvalore minimo e un intvalore massimo nel costruttore. Questi ci danno tutte le informazioni di cui abbiamo bisogno per definire la struttura. Terremo quattro variabili da utilizzare in sottoclassi - i top, bottom, left, e rightvalori per i confini della regione disegno grafico. In seguito utilizzeremo queste variabili per calcolare il posizionamento degli elementi del grafico. Cominciamo con una rapida occhiata alla Graphdichiarazione di classe.

import java.awt. *; import java.util. *; public class Graph estende Canvas {// variabili necessarie public int top; public int bottom; public int a sinistra; public int right; int titleHeight; int labelWidth; FontMetrics fm; int padding = 4; Titolo della stringa; int min; int max; Articoli vettoriali;

Per calcolare il corretto posizionamento degli elementi del grafico, dobbiamo prima calcolare le regioni nel nostro layout grafico generico che compongono il framework. Per migliorare l'aspetto del componente, aggiungiamo un riempimento di 4 pixel ai bordi esterni. Aggiungeremo il titolo centrato in alto, tenendo conto dell'area di riempimento. Per assicurarci che il grafico non venga disegnato sopra il testo, dobbiamo sottrarre l'altezza del testo dalla regione del titolo. Dobbiamo fare la stessa cosa per le etichette dell'intervallo di valori mine max. La larghezza di questo testo è memorizzata nella variabile labelWidth. Dobbiamo mantenere un riferimento alle metriche dei caratteri per poter eseguire le misurazioni. Ilitemsvettore viene utilizzato per tenere traccia di tutti i singoli elementi che sono stati aggiunti al componente Grafico. Una classe utilizzata per contenere le variabili relative agli elementi del grafico è inclusa (e spiegata) dopo la Graphclasse, che viene mostrata di seguito.

public Graph (String title, int min, int max) {this.title = title; this.min = min; this.max = max; elementi = nuovo vettore (); } // end costruttore

Il costruttore prende il titolo del grafico e l'intervallo di valori e creiamo un vettore vuoto per i singoli elementi del grafico.

public void reshape (int x, int y, int larghezza, int altezza) {super.reshape (x, y, larghezza, altezza); fm = getFontMetrics (getFont ()); titleHeight = fm.getHeight (); labelWidth = Math.max (fm.stringWidth (new Integer (min) .toString ()), fm.stringWidth (new Integer (max) .toString ())) + 2; top = padding + titleHeight; fondo = dimensione (). altezza - imbottitura; sinistra = padding + labelWidth; destra = dimensione (). larghezza - imbottitura; } // end reshape

Nota: in JDK 1.1, il reshapemetodo è sostituito con public void setBounds(Rectangle r). Consulta la documentazione API per i dettagli.

Eseguiamo l'override del reshapemetodo, che viene ereditato lungo la catena dalla Componentclasse. Il reshapemetodo viene chiamato quando il componente viene ridimensionato e quando viene disposto per la prima volta. Utilizziamo questo metodo per raccogliere le misurazioni, in modo che vengano sempre aggiornate se il componente viene ridimensionato. Otteniamo le metriche del font per il font corrente e assegniamo alla titleHeightvariabile l'altezza massima di quel font. Otteniamo la larghezza massima delle etichette, testando per vedere quale è più grande e quindi usando quella. La top, bottom, left, e rightvariabili sono calcolati dalle altre variabili e rappresentano i confini della regione disegno grafico centro. Useremo queste variabili nelle sottoclassi di Graph. Notare che tutte le misurazioni tengono conto di una correntedimensione del componente in modo che il ridisegno sia corretto con qualsiasi dimensione o aspetto. Se usassimo valori hardcoded, il componente non potrebbe essere ridimensionato.

Successivamente, disegneremo la struttura per il grafico.

public void paint (Graphics g) {// disegna il titolo fm = getFontMetrics (getFont ()); g.drawString (title, (size (). width - fm.stringWidth (title)) / 2, top); // disegna i valori max e min g.drawString (new Integer (min) .toString (), padding, bottom); g.drawString (new Integer (max) .toString (), padding, top + titleHeight); // disegna le linee verticali e orizzontali g.drawLine (sinistra, in alto, a sinistra, in basso); g.drawLine (sinistra, in basso, a destra, in basso); } // termina la pittura

La struttura è disegnata nel paintmetodo. Disegniamo il titolo e le etichette nelle posizioni appropriate. Tracciamo una linea verticale sul bordo sinistro dell'area di disegno del grafico e una linea orizzontale sul bordo inferiore.

In questo frammento successivo impostiamo la dimensione preferita per il componente sovrascrivendo il preferredSizemetodo. Anche il preferredSizemetodo viene ereditato dalla Componentclasse. I componenti possono specificare una dimensione preferita e una dimensione minima. Ho scelto una larghezza preferita di 300 e un'altezza preferita di 200. Il layout manager chiamerà questo metodo quando disporrà il componente.

public Dimension preferredSize () {return (new Dimension (300, 200)); }} // end Graph

Nota: in JDK 1.1, il preferredSizemetodo è sostituito con public Dimension getPreferredSize().

Successivamente, abbiamo bisogno di una funzione per aggiungere e rimuovere gli elementi da rappresentare graficamente.

public void addItem (String name, int value, Color col) {items.addElement (new GraphItem (name, value, col)); } // end addItem public void addItem (String name, int value) {items.addElement (new GraphItem (name, value, Color.black)); } // end addItem public void removeItem (String name) {for (int i = 0; i <items.size (); i ++) {if (((GraphItem) items.elementAt (i)). title.equals (name )) items.removeElementAt (i); }} // end removeItem} // end Graph

Ho modellato i metodi addIteme removeItemdopo metodi simili nella Choiceclasse, quindi il codice avrà un aspetto familiare. Notare che qui utilizziamo due addItemmetodi; abbiamo bisogno di un modo per aggiungere elementi con o senza un colore. Quando viene aggiunto un elemento, GraphItemviene creato un nuovo oggetto e aggiunto al vettore degli elementi. Quando un elemento viene rimosso, il primo nel vettore con quel nome verrà rimosso. La GraphItemlezione è molto semplice; ecco il codice:

import java.awt. *; class GraphItem {String title; valore int; Colore colore; public GraphItem (String title, int value, Color color) {this.title = title; this.value = valore; this.color = color; } // end constructor} // end GraphItem

La GraphItemclasse funge da detentore per le variabili relative agli elementi del grafico. Ho incluso Colorqui nel caso in cui verrà utilizzato in una sottoclasse di Graph.

Con questo framework in atto, possiamo creare estensioni per gestire ogni tipo di grafico. Questa strategia è abbastanza conveniente; non dobbiamo prenderci la briga di misurare nuovamente i pixel per il framework e possiamo creare sottoclassi per concentrarci sul riempimento della regione di disegno del grafico.

Costruire il grafico a barre

Ora che abbiamo un framework grafico, possiamo personalizzarlo estendendolo

Graph

e implementare il disegno personalizzato. Inizieremo con un semplice grafico a barre, che possiamo usare proprio come qualsiasi altro componente. Di seguito è illustrato un tipico grafico a barre. Riempiremo l'area di disegno del grafico sovrascrivendo il

paint

metodo per chiamare la superclasse

paint

method (to draw the framework), then we'll perform the custom drawing needed for this type of graph.

import java.awt.*; public class BarChart extends Graph { int position; int increment; public BarChart(String title, int min, int max) { super(title, min, max); } // end constructor 

To space the items evenly, we keep an increment variable to indicate the amount we will shift to the right for each item. The position variable is the current position, and the increment value is added to it each time. The constructor simply takes in values for the super constructor (Graph), which we call explicitly.

Now we can get down to some actual drawing.

 public void paint(Graphics g) { super.paint(g); increment = (right - left)/(items.size()); position = left; Color temp = g.getColor(); for (int i = 0; i < items.size(); i++) { GraphItem item = (GraphItem)items.elementAt(i); int adjustedValue = bottom - (((item.value - min)*(bottom - top)) /(max - min)); g.drawString(item.title, position + (increment - fm.stringWidth(item.title))/2, adjustedValue - 2); g.setColor(item.color); g.fillRect(position, adjustedValue, increment, bottom - adjustedValue); position+=increment; g.setColor(temp); } } // end paint } // end BarChart 

Let's take a close look at what's happening here. In the paint method, we call the superclass paint method to draw the graph framework. We then find the increment by subtracting the right edge from the left edge, and then dividing the result by the number of items. This value is the distance between the left edges of the graph items. Because we want the graph to be resizable, we base these values on the current value of the left and right variables inherited from Graph. Recall that the left, right, top, and bottom values are the current actual pixel measurements of the graph drawing region taken in the reshape method of Graph, and therefore available for our use. If we did not base our measurements on these values, the graph would not be resizable.

We'll draw the rectangles in the color specified by the GraphItem. To allow us to go back to the original color, we set a temporary color variable to hold the current value before we change it. We cycle through the vector of graph items, calculating an adjusted vertical value for each one, drawing the title of the item and a filled rectangle representing its value. The increment is added to the x position variable each time through the loop.

The adjusted vertical value ensures that if the component is stretched vertically, the graph will still remain true to its plotted values. To do this properly, we need to take the percentage of the range the item represents and multiply that value by the actual pixel range of the graph drawing region. We then subtract the result from the bottom value to correctly plot the point.

As you can see from the following diagram, the total horizontal pixel size is represented by right - left and the total vertical size is represented by bottom - top.

We take care of the horizontal stretching by initializing the position variable to the left edge and increasing it by the increment variable for each item. Because the position and increment variables are dependent on the actual current pixel values, the component is always resized correctly in the horizontal direction.

Per garantire che il tracciato verticale sia sempre corretto, dobbiamo mappare i valori dell'elemento grafico con i posizionamenti dei pixel effettivi. C'è una complicazione: i valori maxe mindovrebbero essere significativi per la posizione del valore dell'elemento grafico. In altre parole, se il grafico parte da 150 e arriva a 200, un elemento con un valore di 175 dovrebbe apparire a metà dell'asse verticale. Per ottenere ciò, troviamo la percentuale dell'intervallo del grafico che l'elemento rappresenta e la moltiplichiamo per l'intervallo di pixel effettivo. Poiché il nostro grafico è capovolto dal sistema di coordinate del contesto grafico, sottraiamo questo numero da bottomper trovare il punto corretto del grafico . Ricorda, l'origine (0,0) è nell'angolo in alto a sinistra per il codice, ma nell'angolo in basso a sinistra per lo stile del grafico che stiamo creando.