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 paint
Methode Canvas
mit der benutzerdefinierten Zeichnung zu überschreiben, die wir benötigen. Wir werden das Graphics
Objekt verwenden, das automatisch an die paint
Methode 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 Graph
Klasse zu erstellen , werden wir eine Unterklasse erstellen Canvas
. Im mittleren Bereich werden die tatsächlichen Diagrammdaten angezeigt. Wir überlassen dies einer Erweiterung von Graph
zu 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 String
Titel, einen int
minimalen Wert und einen int
maximalen 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
, left
und right
Werte 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 Graph
Klassendeklaration.
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 min
und den max
Wertebereich 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. Dasitems
Der 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 Graph
Klasse 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 reshape
Methode durch ersetzt public void setBounds(Rectangle r)
. Weitere Informationen finden Sie in der API-Dokumentation.
Wir überschreiben die reshape
Methode, die in der gesamten Kette von der Component
Klasse geerbt wird . Die reshape
Methode 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 titleHeight
Variablen 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
, left
und right
Variablen 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 paint
Methode 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 preferredSize
Methode überschreiben . Die preferredSize
Methode wird auch von der Component
Klasse 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 preferredSize
Methode 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 addItem
und removeItem
-Methoden nach ähnlichen Methoden in der Choice
Klasse modelliert , damit der Code ein vertrautes Gefühl hat. Beachten Sie, dass wir addItem
hier zwei Methoden verwenden. Wir brauchen eine Möglichkeit, Elemente mit oder ohne Farbe hinzuzufügen. Wenn ein Element hinzugefügt wird, wird ein neues GraphItem
Objekt erstellt und dem Elementvektor hinzugefügt. Wenn ein Element entfernt wird, wird das erste Element im Vektor mit diesem Namen entfernt. Die GraphItem
Klasse 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 GraphItem
Klasse fungiert als Halter für die Variablen, die sich auf Diagrammelemente beziehen. Ich habe Color
hier 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 max
und min
sollten 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.