Zeichnen Sie Ihren Weg zu benutzerdefinierten Diagrammkomponenten

Unsere benutzerdefinierten Diagrammkomponenten erfordern manuelles Zeichnen, daher müssen wir eine Unterklasse erstellen Canvas, die die Standardkomponente für die direkte Grafikmanipulation darstellt. Die Technik, die wir verwenden werden, besteht darin, die paintMethode Canvasmit der benutzerdefinierten Zeichnung zu überschreiben, die wir benötigen. Wir werden das GraphicsObjekt verwenden, das automatisch an die paintMethode aller Komponenten übergeben wird, um auf Farben und Zeichenmethoden zuzugreifen.

Wir erstellen zwei benutzerdefinierte Grafikkomponenten: ein Balkendiagramm und ein Liniendiagramm. Wir beginnen mit der Erstellung einer allgemeinen Framework-Klasse für die beiden Diagramme, die einige Basiselemente gemeinsam haben.

Erstellen eines generischen Diagramm-Frameworks

Das Liniendiagramm und das Balkendiagramm, die wir erstellen werden, sind ähnlich genug, um ein generisches Diagramm zu erstellen

Graph

Klasse, um einige der mühsamen Layout-Arbeiten durchzuführen. Sobald dies erledigt ist, können wir die Klasse für die bestimmte Art von Diagramm erweitern, die wir benötigen.

Das erste, was Sie tun müssen, wenn Sie benutzerdefinierte Grafikkomponenten entwerfen, ist, Stift auf Papier zu bringen und ein Bild von dem zu zeichnen, was Sie benötigen. Da wir Pixel zählen, ist es leicht, die Platzierung der Elemente zu verwechseln. Wenn Sie sich Gedanken über die Benennung und Positionierung von Elementen machen, können Sie den Code sauberer und später leichter lesbar halten.

Das Liniendiagramm und das Balkendiagramm verwenden dasselbe Layout für Titel und Linien. Daher erstellen wir zunächst ein generisches Diagramm mit diesen beiden Funktionen. Das Layout, das wir erstellen werden, ist in der folgenden Abbildung dargestellt.

Um die generische GraphKlasse zu erstellen , werden wir eine Unterklasse erstellen Canvas. Im mittleren Bereich werden die tatsächlichen Diagrammdaten angezeigt. Wir überlassen dies einer Erweiterung von Graphzu implementieren. Wir implementieren die anderen Elemente - eine Titelleiste, eine vertikale Linie links, eine horizontale Linie unten und Werte für den Bereich - in der Basisklasse. Wir könnten eine Schriftart angeben und die Pixelmaße fest codieren, aber der Benutzer könnte die Größe des Diagramms nicht ändern. Ein besserer Ansatz besteht darin, die Elemente an der aktuellen Größe der Komponente zu messen , sodass die Größenänderung der Anwendung zu einer korrekten Größenänderung des Diagramms führt.

Hier ist unser Plan: Wir nehmen einen StringTitel, einen intminimalen Wert und einen intmaximalen Wert im Konstruktor. Diese geben uns alle Informationen, die wir benötigen, um den Rahmen festzulegen. Wir werden in den Unterklassen vier Variablen für die Verwendung halten - die top, bottom, leftund rightWerte für die Grenzen der Graphen Zeichnungsregion. Wir werden diese Variablen später verwenden, um die Positionierung von Diagrammelementen zu berechnen. Beginnen wir mit einem kurzen Blick auf die GraphKlassendeklaration.

import java.awt. *; import java.util. *; public class Graph erweitert Canvas {// benötigte Variablen public int top; public int bottom; public int left; öffentliches Recht; int titleHeight; int labelWidth; FontMetrics fm; int padding = 4; String title; int min; int max; Vektorelemente;

Um die korrekte Platzierung von Diagrammelementen zu berechnen, müssen wir zuerst die Bereiche in unserem generischen Diagrammlayout berechnen, aus denen das Framework besteht. Um das Erscheinungsbild der Komponente zu verbessern, fügen wir den Außenkanten eine 4-Pixel-Polsterung hinzu. Wir fügen den oben zentrierten Titel unter Berücksichtigung des Auffüllbereichs hinzu. Um sicherzustellen, dass das Diagramm nicht über dem Text gezeichnet wird, müssen wir die Höhe des Texts vom Titelbereich abziehen. Wir müssen dasselbe für die Beschriftungen minund den maxWertebereich tun . Die Breite dieses Textes wird in der Variablen gespeichert labelWidth. Wir müssen einen Verweis auf die Schriftmetriken behalten, um die Messungen durchführen zu können. DasitemsDer Vektor wird verwendet, um alle einzelnen Elemente zu verfolgen, die der Diagrammkomponente hinzugefügt wurden. Eine Klasse, die Variablen für Diagrammelemente enthält, wird nach der GraphKlasse eingefügt (und erläutert) , die als Nächstes angezeigt wird.

öffentlicher Graph (Stringtitel, int min, int max) {this.title = title; this.min = min; this.max = max; items = new Vector (); } // Konstruktor beenden

Der Konstruktor übernimmt den Diagrammtitel und den Wertebereich und erstellt einen leeren Vektor für die einzelnen Diagrammelemente.

öffentliche Leerenumformung (int x, int y, int Breite, int Höhe) {super.reshape (x, y, Breite, Höhe); fm = getFontMetrics (getFont ()); titleHeight = fm.getHeight (); labelWidth = Math.max (fm.stringWidth (neue Ganzzahl (min) .toString ()), fm.stringWidth (neue Ganzzahl (max) .toString ()) + 2; top = padding + titleHeight; unten = Größe (). Höhe - Polsterung; left = padding + labelWidth; rechts = Größe (). Breite - Polsterung; } // Umformung beenden

Hinweis: In JDK 1.1 wird die reshapeMethode durch ersetzt public void setBounds(Rectangle r). Weitere Informationen finden Sie in der API-Dokumentation.

Wir überschreiben die reshapeMethode, die in der gesamten Kette von der ComponentKlasse geerbt wird . Die reshapeMethode wird aufgerufen, wenn die Größe der Komponente geändert wird und wenn sie zum ersten Mal angelegt wird. Wir verwenden diese Methode, um Messungen zu erfassen, damit sie immer aktualisiert werden, wenn die Größe der Komponente geändert wird. Wir erhalten die Schriftmetriken für die aktuelle Schrift und weisen der titleHeightVariablen die maximale Höhe dieser Schrift zu. Wir erhalten die maximale Breite der Etiketten, testen, welche größer ist, und verwenden dann diese. Die top, bottom, leftund rightVariablen werden von den anderen Variablen berechnet und die Grenzen der mittleren Graph Zeichnungsregion darstellen. Wir werden diese Variablen in den Unterklassen von verwenden Graph. Beachten Sie, dass alle Messungen einen Strom berücksichtigenGröße der Komponente, sodass das Neuzeichnen bei jeder Größe oder jedem Aspekt korrekt ist. Wenn wir fest codierte Werte verwenden, kann die Größe der Komponente nicht geändert werden.

Als nächstes zeichnen wir das Framework für das Diagramm.

public void paint (Grafik g) {// zeichne den Titel fm = getFontMetrics (getFont ()); g.drawString (Titel, (Größe (). Breite - fm.stringWidth (Titel)) / 2, oben); // zeichne die Max- und Min-Werte g.drawString (neue Ganzzahl (min) .toString (), padding, bottom); g.drawString (neue Ganzzahl (max) .toString (), Auffüllen, oben + Titelhöhe); // zeichne die vertikalen und horizontalen Linien g.drawLine (links, oben, links, unten); g.drawLine (links, unten, rechts, unten); } // Farbe beenden

Das Framework wird in der paintMethode gezeichnet . Wir zeichnen den Titel und die Etiketten an den entsprechenden Stellen. Wir zeichnen eine vertikale Linie am linken Rand des Diagrammzeichnungsbereichs und eine horizontale Linie am unteren Rand.

In diesem nächsten Snippet legen wir die bevorzugte Größe für die Komponente fest, indem wir die preferredSizeMethode überschreiben . Die preferredSizeMethode wird auch von der ComponentKlasse geerbt . Komponenten können eine bevorzugte Größe und eine Mindestgröße angeben. Ich habe eine bevorzugte Breite von 300 und eine bevorzugte Höhe von 200 gewählt. Der Layout-Manager ruft diese Methode auf, wenn er die Komponente auslegt.

public Dimension PreferredSize () {return (neue Dimension (300, 200)); }} // Graph beenden

Hinweis: In JDK 1.1 wird die preferredSizeMethode durch ersetzt public Dimension getPreferredSize().

Als nächstes benötigen wir eine Funktion zum Hinzufügen und Entfernen der zu grafischen Elemente.

public void addItem (Stringname, int-Wert, Farbe col) {items.addElement (neues GraphItem (Name, Wert, col)); } // end addItem public void addItem (Stringname, int-Wert) {items.addElement (neues GraphItem (Name, Wert, Color.black)); } // end addItem public void removeItem (String name) {für (int i = 0; i <items.size (); i ++) {if (((GraphItem) items.elementAt (i)). title.equals (name )) items.removeElementAt (i); }} // end removeItem} // end Graph

Ich habe die addItemund removeItem-Methoden nach ähnlichen Methoden in der ChoiceKlasse modelliert , damit der Code ein vertrautes Gefühl hat. Beachten Sie, dass wir addItemhier zwei Methoden verwenden. Wir brauchen eine Möglichkeit, Elemente mit oder ohne Farbe hinzuzufügen. Wenn ein Element hinzugefügt wird, wird ein neues GraphItemObjekt erstellt und dem Elementvektor hinzugefügt. Wenn ein Element entfernt wird, wird das erste Element im Vektor mit diesem Namen entfernt. Die GraphItemKlasse ist sehr einfach; Hier ist der Code:

import java.awt. *; Klasse GraphItem {String title; int value; Farbe Farbe; public GraphItem (String title, int value, Color color) {this.title = title; this.value = value; this.color = color; } // Konstruktor beenden} // GraphItem beenden

Die GraphItemKlasse fungiert als Halter für die Variablen, die sich auf Diagrammelemente beziehen. Ich habe Colorhier aufgenommen, falls es in einer Unterklasse von verwendet wird Graph.

Mit diesem Framework können wir Erweiterungen für jeden Diagrammtyp erstellen. Diese Strategie ist sehr praktisch; Wir müssen uns nicht die Mühe machen, die Pixel für das Framework erneut zu messen, und wir können Unterklassen erstellen, um uns auf das Ausfüllen des Diagrammzeichnungsbereichs zu konzentrieren.

Erstellen des Balkendiagramms

Nachdem wir nun ein Grafik-Framework haben, können wir es durch Erweitern anpassen

Graph

und Implementieren von benutzerdefinierten Zeichnungen. Wir beginnen mit einem einfachen Balkendiagramm, das wir wie jede andere Komponente verwenden können. Ein typisches Balkendiagramm ist unten dargestellt. Wir füllen den Diagrammzeichnungsbereich aus, indem wir das überschreiben

paint

Methode zum Aufrufen der Oberklasse

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.

Um sicherzustellen, dass die vertikale Darstellung immer korrekt ist, müssen wir die Diagrammelementwerte mit den tatsächlichen Pixelplatzierungen abbilden. Es gibt eine Komplikation: Die Werte maxund minsollten für die Position des Diagrammelementwerts von Bedeutung sein. Mit anderen Worten, wenn der Graph bei 150 beginnt und bis 200 geht, sollte ein Element mit einem Wert von 175 auf halber Höhe der vertikalen Achse erscheinen. Um dies zu erreichen, ermitteln wir den Prozentsatz des Diagrammbereichs, den das Element darstellt, und multiplizieren ihn mit dem tatsächlichen Pixelbereich. Da unser Diagramm vom Koordinatensystem des Grafikkontexts verkehrt herum ist, subtrahieren wir diese Zahl von bottom, um den richtigen Plotpunkt zu finden. Denken Sie daran, dass der Ursprung (0,0) für den Code in der oberen linken Ecke liegt, für den von uns erstellten Diagrammstil jedoch in der unteren linken Ecke.