Начертайте пътя си към персонализирани компоненти на графиката

Нашите персонализирани графични компоненти изискват ръчно чертане, така че ще трябва да подкласираме Canvas, което е стандартният компонент, предвиден за директна манипулация на графиката. Техниката, която ще използваме, ще бъде да заменим paintметода Canvasс персонализирания чертеж, от който се нуждаем. Ще използваме Graphicsобекта, който автоматично се предава в paintметода на всички компоненти, за достъп до цветове и методи за рисуване.

Ще създадем два персонализирани компонента за графики: стълбовидна диаграма и линейна графика. Ще започнем с изграждането на общ рамков клас за двете графики, които споделят някои базови елементи.

Изграждане на обща рамка на графика

Линейната графика и стълбовата диаграма, които ще изградим, са достатъчно сходни, за да можем да създадем родово

Graph

клас, за да извършите някои от досадната работа по оформлението. След като приключим, можем да разширим класа за конкретния вид графика, от която се нуждаем.

Първото нещо, което трябва да направите, когато проектирате персонализирани графични компоненти, е да поставите писалка върху хартия и да нарисувате картина на това, от което се нуждаете. Тъй като броим пиксели, е лесно да се объркаме относно разположението на елементите. Влагането на някои мисли в именуването и позиционирането на елементи ще ви помогне да поддържате кода по-чист и по-лесен за четене по-късно.

Линейната графика и стълбовидната диаграма използват едно и също оформление за заглавието и редовете, така че ще започнем със създаването на обща графика, съдържаща тези две характеристики. Оформлението, което ще създадем, е показано на фигурата по-долу.

За да създадем родовия Graphклас, ще подкласираме Canvas. Централният регион е мястото, където ще се показват действителните графични данни; ще оставим това за разширение на Graphза изпълнение. Ще приложим останалите елементи - заглавна лента, вертикална линия вляво, хоризонтална линия отдолу и стойности за диапазона - в основния клас. Можем да посочим шрифт и твърд код за измерване на пикселите, но потребителят няма да може да преоразмери графиката. По-добър подход е измерването на елементите спрямо текущия размер на компонента, така че преоразмеряването на приложението да доведе до правилно преоразмеряване на графиката.

Ето нашия план: Ще вземем Stringзаглавие, intминимална стойност и intмаксимална стойност в конструктора. Те ни дават цялата информация, от която се нуждаем, за да изложим рамката. Ще продължим четири променливи, използвани в подкласове - на top, bottom, left, както и rightстойностите на границите на изготвяне региона на графика. Ще използваме тези променливи, за да изчислим позиционирането на графични елементи по-късно. Нека започнем с бърз поглед към Graphдекларацията на класа.

импортиране на java.awt. *; импортиране на java.util. *; публичен клас Graph разширява Canvas {// променливи, необходими public int top; публичен int дъно; публичен int вляво; публично международно право; int titleHeight; int labelWidth; FontMetrics fm; int padding = 4; Заглавие на низ; int min; int max; Векторни елементи;

За да изчислим правилното разположение на графичните елементи, първо трябва да изчислим регионите в нашето общо оформление на графиката, които съставят рамката. За да подобрим външния вид на компонента, добавяме 4-пикселна подложка към външните ръбове. Ще добавим заглавието, центрирано в горната част, като вземем предвид областта на подплънките. За да сме сигурни, че графиката не е нарисувана отгоре на текста, трябва да извадим височината на текста от заглавната област. Трябва да направим същото за етикетите minи maxдиапазона на стойност. Ширината на този текст се съхранява в променливата labelWidth. Трябва да запазим препратка към показателите на шрифта, за да направим измерванията. Theitemsвектор се използва за проследяване на всички отделни елементи, които са добавени към компонента Графика. Клас, използван за съхранение на променливи, свързани с елементите на графиката, е включен (и обяснен) след Graphкласа, който е показан по-нататък.

публична графика (заглавие на низ, int min, int max) {this.title = заглавие; this.min = мин; this.max = max; артикули = нов вектор (); } // краен конструктор

Конструкторът взема заглавието на графиката и диапазона от стойности и ние създаваме празен вектор за отделните елементи на графиката.

публична промяна на празнотата (int x, int y, int width, int height) {super.reshape (x, y, width, height); fm = getFontMetrics (getFont ()); titleHeight = fm.getHeight (); labelWidth = Math.max (fm.stringWidth (ново цяло число (min) .toString ()), fm.stringWidth (ново цяло число (max) .toString ())) + 2; отгоре = подложка + titleHeight; отдолу = размер (). височина - подложка; вляво = подложка + labelWidth; вдясно = размер (). ширина - подложка; } // край на преоформянето

Забележка: В JDK 1.1 reshapeметодът се заменя с public void setBounds(Rectangle r). Вижте документацията за API за подробности.

Ние заместваме reshapeметода, който се наследява по веригата от Componentкласа. В reshapeметод се нарича, когато компонентът е оразмерен и когато тя е изложена за първи път. Използваме този метод за събиране на измервания, така че те винаги да бъдат актуализирани, ако компонентът бъде преоразмерен. Получаваме метриките на шрифта за текущия шрифт и присвояваме на titleHeightпроменливата максималната височина на този шрифт. Получаваме максималната ширина на етикетите, тестваме кой е по-голям и след това използваме този. Най- top, bottom, left, и rightпроменливи се изчислява от други променливи и представляват границите на чертеж регион център графика. Ще използваме тези променливи в подкласовете на Graph. Имайте предвид, че всички измервания отчитат токразмер на компонента, така че преначертаването ще бъде правилно при всякакъв размер или аспект. Ако използвахме твърдо кодирани стойности, компонентът не можеше да бъде преоразмерен.

След това ще нарисуваме рамката за графиката.

публична невалидна боя (Графика g) {// изчертаване на заглавието fm = getFontMetrics (getFont ()); g.drawString (заглавие, (размер (). ширина - fm.stringWidth (заглавие)) / 2, отгоре); // изчертаваме максималните и минималните стойности g.drawString (ново цяло число (min) .toString (), подложка, отдолу); g.drawString (ново цяло число (max) .toString (), подложка, top + titleHeight); // начертайте вертикалните и хоризонталните линии g.drawLine (вляво, отгоре, отляво, отдолу); g.drawLine (вляво, отдолу, отдясно, отдолу); } // крайна боя

Рамката е изчертана в paintметода. Изчертаваме заглавието и етикетите на съответните им места. Изчертаваме вертикална линия в лявата граница на областта за рисуване на графика и хоризонтална линия в долната граница.

В този следващ фрагмент задаваме предпочитания размер за компонента, като заместваме preferredSizeметода. В preferredSizeметод също се наследява от Componentкласа. Компонентите могат да определят предпочитан размер и минимален размер. Избрах предпочитана ширина 300 и предпочитана височина 200. Мениджърът на оформлението ще извика този метод, когато изложи компонента.

публично измерение preferSize () {return (ново измерение (300, 200)); }} // крайна графика

Забележка: В JDK 1.1 preferredSizeметодът се заменя с public Dimension getPreferredSize().

След това се нуждаем от съоръжение за добавяне и премахване на елементите, които ще бъдат изобразени.

публична невалидна addItem (име на низ, стойност на int, цветна колона) {items.addElement (нова GraphItem (име, стойност, col)); } // край на addItem public void addItem (Име на низ, стойност int) {items.addElement (нов GraphItem (име, стойност, Color.black)); } // край на 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); }} // край на removeItem} // край на графика

Моделирах методите addItemи removeItemметодите след подобни методи в Choiceкласа, така че кодът ще има познато усещане. Забележете, че addItemтук използваме два метода; имаме нужда от начин за добавяне на елементи със или без цвят. Когато се добавя елемент, се създава нов GraphItemобект и се добавя към вектора на елементите. Когато елемент бъде премахнат, първият във вектора с това име ще бъде премахнат. Най- GraphItemкласа е много проста; ето кода:

импортиране на java.awt. *; клас GraphItem {Заглавие на низ; стойност int; Цветен цвят; публичен GraphItem (заглавие на низ, стойност на int, цвят на цвета) {this.title = заглавие; this.value = стойност; this.color = цвят; } // краен конструктор} // краен GraphItem

В GraphItemкласа действа като титуляр за променливите, свързани с графика предмети. Включих Colorтук, в случай че ще бъде използван в подклас на Graph.

С тази рамка на място можем да създадем разширения за обработка на всеки тип графика. Тази стратегия е доста удобна; не е нужно да се затрудняваме отново да измерваме пикселите за рамката и можем да създадем подкласове, за да се съсредоточим върху попълването на областта за рисуване на графика.

Изграждане на стълбовидната диаграма

Сега, когато имаме графична рамка, можем да я персонализираме чрез разширяване

Graph

и внедряване на персонализирано рисуване. Ще започнем с обикновена диаграма, която можем да използваме точно както всеки друг компонент. Типична диаграма е показана по-долу. Ще попълним региона за рисуване на графики, като заменим

paint

метод за извикване на суперклас

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.

За да сме сигурни, че вертикалното нанасяне винаги е правилно, трябва да картографираме стойностите на елементите на графиката с действителни пикселни разположения. Има едно усложнение: Стойностите maxи minтрябва да имат значение за позицията на стойността на елемента на графиката. С други думи, ако графиката започва от 150 и отива до 200, елемент със стойност 175 трябва да се появи по средата на вертикалната ос. За да постигнем това, намираме процента от обхвата на графиката, който елементът представлява, и го умножаваме по действителния обхват на пикселите. Тъй като нашата графика е обърната от координатната система на графичния контекст, изваждаме това число, за bottomда намерим правилната точка на графиката. Не забравяйте, че началото (0,0) е в горния ляв ъгъл за кода, но долният ляв ъгъл за стила на графиката, който създаваме.