Bagan jalan Anda ke komponen grafik khusus

Komponen grafik kustom kami memerlukan gambar manual, jadi kami perlu subclass Canvas, yang merupakan komponen standar yang disediakan untuk manipulasi grafik langsung. Teknik yang akan kita gunakan akan mengganti paintmetode Canvasdengan gambar kustom yang kita butuhkan. Kami akan menggunakan Graphicsobjek, yang secara otomatis diteruskan ke paintmetode semua komponen, untuk mengakses warna dan metode menggambar.

Kami akan membuat dua komponen grafik kustom: diagram batang dan grafik garis. Kami akan mulai dengan membangun kelas kerangka umum untuk dua grafik, yang berbagi beberapa elemen dasar.

Membangun kerangka grafik generik

Grafik garis dan diagram batang yang akan kita buat cukup mirip sehingga kita dapat membuat yang umum

Graph

kelas untuk melakukan beberapa pekerjaan tata letak yang membosankan. Setelah itu selesai, kami kemudian dapat memperluas kelas untuk jenis grafik tertentu yang kami butuhkan.

Hal pertama yang harus dilakukan saat Anda mendesain komponen grafis khusus adalah meletakkan pena di atas kertas dan menggambar apa yang Anda butuhkan. Karena kita menghitung piksel, mudah tercampur aduk tentang penempatan elemen. Memikirkan penamaan dan pemosisian elemen akan membantu Anda menjaga kode lebih bersih dan lebih mudah dibaca nanti.

Grafik garis dan diagram batang menggunakan tata letak yang sama untuk judul dan garis, jadi kita akan mulai dengan membuat grafik umum yang berisi kedua fitur ini. Tata letak yang akan kita buat ditunjukkan pada gambar di bawah ini.

Untuk membuat Graphkelas generik , kita akan membuat subkelas Canvas. Wilayah tengah adalah tempat data grafik aktual akan ditampilkan; kita akan menyerahkan ini pada perpanjangan Graphuntuk diterapkan. Kami akan menerapkan elemen lainnya - bilah judul, garis vertikal ke kiri, garis horizontal di bawah, dan nilai untuk rentang - di kelas dasar. Kita dapat menentukan font dan hard-code pengukuran piksel, tetapi pengguna tidak dapat mengubah ukuran grafik. Pendekatan yang lebih baik adalah untuk mengukur elemen terhadap arus ukuran komponen, sehingga mengubah ukuran aplikasi akan menghasilkan mengubah ukuran yang benar dari grafik.

Inilah rencana kami: Kami akan mengambil Stringjudul, intnilai minimum, dan intnilai maksimum dalam konstruktor. Ini memberi kami semua informasi yang kami butuhkan untuk menyusun kerangka kerja. Kami akan terus empat variabel untuk digunakan dalam subclass - yang top, bottom, left, dan rightnilai-nilai untuk perbatasan grafik menggambar wilayah. Kami akan menggunakan variabel ini untuk menghitung posisi item grafik nanti. Mari kita mulai dengan melihat sekilas Graphdeklarasi kelas.

import java.awt. *; import java.util. *; public class Graph extends Canvas {// variabel diperlukan public int top; publik int bawah; public int left; hak int publik; int titleHeight; int labelWidth; FontMetrics fm; int padding = 4; Judul string; int min; int max; Item vektor;

Untuk menghitung penempatan elemen grafik yang benar, pertama-tama kita perlu menghitung wilayah dalam tata letak grafik umum yang menyusun kerangka kerja. Untuk meningkatkan penampilan komponen, kami menambahkan bantalan 4-piksel ke tepi luar. Kami akan menambahkan judul yang berpusat di atas, dengan mempertimbangkan area paddding. Untuk memastikan bahwa grafik tidak digambar di atas teks, kita perlu mengurangi tinggi teks dari wilayah judul. Kita perlu melakukan hal yang sama untuk label rentang nilai mindan max. Lebar teks ini disimpan dalam variabel labelWidth. Kita perlu menyimpan referensi ke metrik font untuk melakukan pengukuran. Ituitemsvektor digunakan untuk melacak semua item individual yang telah ditambahkan ke komponen Grafik. Kelas yang digunakan untuk menampung variabel yang terkait dengan item grafik dimasukkan (dan dijelaskan) setelah Graphkelas, yang ditampilkan berikutnya.

Grafik publik (Judul string, int min, int max) {this.title = judul; this.min = min; this.max = max; item = new Vector (); } // mengakhiri konstruktor

Konstruktor mengambil judul grafik dan kisaran nilai, dan kami membuat vektor kosong untuk item grafik individual.

public void membentuk kembali (int x, int y, lebar int, tinggi int) {super.reshape (x, y, lebar, tinggi); fm = getFontMetrics (getFont ()); titleHeight = fm.getHeight (); labelWidth = Math.max (fm.stringWidth (Integer baru (min) .toString ()), fm.stringWidth (Integer baru (max) .toString ())) + 2; atas = padding + titleHeight; bottom = size (). height - padding; kiri = padding + labelWidth; right = size (). width - padding; } // akhiri pembentukan kembali

Catatan: Di JDK 1.1, reshapemetode ini diganti dengan public void setBounds(Rectangle r). Lihat dokumentasi API untuk detailnya.

Kami mengganti reshapemetode, yang diwarisi dari rantai Componentkelas. The reshapemetode ini disebut ketika komponen diubah ukurannya dan ketika itu diletakkan pertama kalinya. Kami menggunakan metode ini untuk mengumpulkan pengukuran, sehingga akan selalu diperbarui jika komponen diubah ukurannya. Kami mendapatkan metrik font untuk font saat ini dan menetapkan titleHeighttinggi maksimum untuk variabel tersebut. Kami mendapatkan lebar maksimum label, menguji untuk melihat mana yang lebih besar dan kemudian menggunakan yang itu. The top, bottom, left, dan rightvariabel dihitung dari variabel lain dan mewakili perbatasan grafik pusat wilayah menggambar. Kami akan menggunakan variabel ini di subclass dari Graph. Perhatikan bahwa semua pengukuran memperhitungkan arusukuran komponen sehingga penggambaran ulang akan benar di semua ukuran atau aspek. Jika kami menggunakan nilai hard-code, komponen tidak dapat diubah ukurannya.

Selanjutnya, kami akan menggambar kerangka untuk grafik.

public void paint (Grafik g) {// gambarlah judul fm = getFontMetrics (getFont ()); g.drawString (judul, (size (). width - fm.stringWidth (judul)) / 2, atas); // gambar nilai maks dan min g.drawString (new Integer (min) .toString (), padding, bottom); g.drawString (new Integer (max) .toString (), padding, top + titleHeight); // gambar garis vertikal dan horizontal g.drawLine (kiri, atas, kiri, bawah); g.drawLine (kiri, bawah, kanan, bawah); } // akhiri cat

Kerangka digambar dalam paintmetode. Kami menggambar judul dan label di tempat yang sesuai. Kami menggambar garis vertikal di perbatasan kiri dari wilayah gambar grafik, dan garis horizontal di perbatasan bawah.

Dalam potongan berikutnya kami menetapkan ukuran yang diinginkan untuk komponen dengan mengganti preferredSizemetode. The preferredSizeMetode ini juga diwarisi dari Componentkelas. Komponen dapat menentukan ukuran yang disukai dan ukuran minimum. Saya telah memilih lebar yang disukai 300 dan tinggi yang disukai 200. Manajer tata letak akan memanggil metode ini saat meletakkan komponen.

public Dimension preferSize () {return (new Dimension (300, 200)); }} // Grafik akhir

Catatan: Di JDK 1.1, preferredSizemetode ini diganti dengan public Dimension getPreferredSize().

Selanjutnya, kita membutuhkan fasilitas untuk menambah dan menghapus item yang akan dibuat grafiknya.

public void addItem (String name, int value, Color col) {items.addElement (new GraphItem (name, value, col)); } // akhiri addItem public void addItem (String name, int value) {items.addElement (new GraphItem (name, value, Color.black)); } // akhiri addItem public void removeItem (Nama string) {untuk (int i = 0; i <items.size (); i ++) {if (((GraphItem) items.elementAt (i)). title.equals (nama )) items.removeElementAt (i); }} // akhiri removeItem} // akhir Grafik

Saya telah memodelkan addItemdan removeItemmetode setelah metode serupa di Choicekelas, sehingga kode akan terasa familiar. Perhatikan bahwa kami menggunakan dua addItemmetode di sini; kita membutuhkan cara untuk menambahkan item dengan atau tanpa warna. Ketika sebuah item ditambahkan, sebuah GraphItemobjek baru dibuat dan ditambahkan ke vektor item. Saat sebuah item dihapus, yang pertama dalam vektor dengan nama itu akan dihapus. The GraphItemkelas sangat sederhana; ini kodenya:

import java.awt. *; kelas GraphItem {String title; nilai int; Warna warna; public GraphItem (Judul string, nilai int, Warna warna) {this.title = title; this.value = value; this.color = color; } // mengakhiri konstruktor} // mengakhiri GraphItem

The GraphItemkelas bertindak sebagai pemegang untuk variabel yang berkaitan dengan item grafik. Saya telah menyertakan Colordi sini seandainya itu akan digunakan dalam subkelas Graph.

Dengan kerangka kerja ini, kita dapat membuat ekstensi untuk menangani setiap jenis grafik. Strategi ini cukup nyaman; kita tidak perlu bersusah payah mengukur piksel untuk kerangka kerja lagi, dan kita dapat membuat subclass untuk fokus mengisi wilayah gambar grafik.

Membangun diagram batang

Sekarang setelah kita memiliki kerangka kerja grafik, kita dapat menyesuaikannya dengan memperluas

Graph

dan menerapkan gambar kustom. Kami akan mulai dengan diagram batang sederhana, yang dapat kami gunakan seperti komponen lainnya. Grafik batang khas diilustrasikan di bawah ini. Kami akan mengisi wilayah gambar grafik dengan menimpa

paint

metode untuk memanggil superclass tersebut

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.

Untuk memastikan bahwa penggambaran vertikal selalu benar, kita harus memetakan nilai item grafik dengan penempatan piksel aktual. Ada satu komplikasi: Nilai maxdan minharus bermakna bagi posisi nilai item grafik. Dengan kata lain, jika grafik dimulai dari 150 dan berlanjut ke 200, sebuah item dengan nilai 175 akan muncul di tengah sumbu vertikal. Untuk mencapai ini, kami menemukan persentase rentang grafik yang diwakili oleh item dan mengalikannya dengan rentang piksel aktual. Karena grafik kami terbalik dari sistem koordinat konteks grafik, kami mengurangi angka ini bottomuntuk menemukan titik plot yang benar. Ingat, origin (0,0) ada di pojok kiri atas kode, tapi pojok kiri bawah untuk style grafik yang kita buat.