Назад | Начало урока | Вперед
Содержание

Глава 06

6.3 Класс Graph

Java представляет эффективное средство для черчения на уровне точек и линий. Но конструирование графиков, которые к тому же дополнены осями из таких примитивных компонентов представляет собой довольно длительный процесс. Чтобы осовбодить программиста от монотонной работы, мы создали очень легкий в использовании класс Graph. Как и два остальных класса, Display и Stream, класс Graph будет использоваться далее в практикуме.

Методы класса Graph

Изучая класс Graph, мы сделали акцент на десяти методах и четырех константах, описание которых приведено ниже.

Класс Graph
//Основные методы
new Graph создание экземпляра класса
add(x, y); добавляет точку к списку; ожидается, что точки будут указываться в порядке, соответствующем направлению оси x.
showGraph() вызов этого метода является обязательным и приводит к фактическому черчению графика, а так же делает окно видимым
//Дополнительные методы
new Graph(graphTotle, xAxisTitle, yAxisTitle) версия создания экземпляра, позволяющая вывести метки рядом с осями. Если не все заголовки используются, применяйте пустые строки.
nextGraph() строит новый график, используя те же оси. Для всех графиков применяется единственный вызов showGraph
setColor(int 0 to 3) служит для выбора цвета; возможные варианты - black, magenta, blue и red (вместо цифр можно указывать константы).
setSymbol(int 0 to 3) предназначен для черчения круга, перевернутого треугольника, треугольника и квадрата (используется если цвета не назначены).
setline(boolean) по умолчанию имеет состояние on(включен), можно отключить (off). Применяется если линии между точками не нужны.
setTitle(String) используется для вывода под графиком текстовой строки
black, magenta,
blue, red
константы для назначения цвета. Эквивалентны цифрам 0, 1, 2, 3.

Чтобы использовать класс Graph, мы должны объявить объект Graph:

Graph = new Graph();

Кроме того, мы можем указать в качестве параметров названия графика и осей, как объяснялось выше. После этого все, что нам нужно будет сделать - это добавить к графику каждую точку (x, y) и вывести его на экран. Итак, для построения простейшего графика нам нужны только три метода.

Принципы работы класса Graph

Класс Graph принимает точку, предоставленную ему и добавлет ее в список точек. Когда вызывается метод showGraph, при определении масштаба для выведения осей на экран используются минимальные и максимальные значения x и y существующих точек. Заранее установленных осей, по которым можно было бы ориентироваться при вводе точек, не существует, поэтому каждое окно с графиком будут иметь специфический комплект осей.

Начало и конец каждой оси маркируются соответствующим образом. В классе не предпринимаются попытки разместить другие метки вдоль осей, поскольку невозможно предугадать что пользователь захочет изобразить на графике.

Дополнительные возможности

Прежде чем мы перейдем к рассмотрению дополнительных методов, предоставляемых классом Graph, мы изучим возможность построения нескольких графиков. Поскольку Graph - это класс, мы можем создать несколько объектов Graph, каждый из которых будет иметь собственное окно. Программа последовательно сможет добавлять точки к различным окнам. Эта технология удобна в тех случаях, когда необходимо, чтобы оси различались.

Если же требуется сравнить различные графики на одних и тех же осях, следует применять метод nextGraph. Этот метод создает для нового графика набор точек и переустанавливает параметры следующим образом:

d.plotType = black;
d.symbolRequired = false;
d.colorRequired = false;
d.titleRequired = false;
d.lineRequired = true;

С помощью рассматривавшихся методов указанные параметры могут быть изменены программой. Полный комплекс изменений представлен ниже:

g.setColor(blue);
g.setSymbol(true);
g.setTitle("Осадки");
g.setLine(false);

Существует взаимосвязь между цветом и символом: им соответствуют значения от 0 до 3. При отсутствии инструкций, в которых указывались бы иные значения (другой цвет или символ), число, используемое для указания символа, применяется и для задания цвета. Таким образом круги строятся из черных линий, треугольники из голубых и тд. Для получения кругов голубого цвета следует в приведенный выше комплект инструкций добавить такую строку:

g.setSymbol(0);

В графиках, предназначенных для печати на принтере рекомендуется использовать символы, так как они лучше смотрятся. В нижней части графика принято раскрывать значение каждого символа. Аналогичные пояснения для цветов не приводятся.

Вверх

Пример 6.6 Определение уровня осадков

Задача Гидрометцентр Саванны хранит данные о ежемесячном уровне осадков за последние 20 лет. Необходимо вычислить следующее:


Решение таблица уровней осадков любезно предоставленная нам клерком управления метеорологии выглядит так:

Подсказка


Данные должны быть сохранены в файле именно в таком виде (только без названий годов и месяцев). По мере ввода значения сохраняются в матрице, которая проиндексирована по годам и месяцам. Поскольку далее в наших вычислениях ключевыми будут значения уровней осадков за разные месяцы, матрицу нужно организовать так, чтобы обеспечить обработку и перемещение колонок значений целиком. Для этого мы укажем в качестве первого индекса месяцы, а в качестве второго - годы (рис 6.12).

Подсказка

Рис 6.12 Часть матрицы для хранения данных по уровням осадков

Вот как будут выглядеть необходимые объявления для нашей таблицы:

static final int maxyear = 70;
double rainTable [] = new double [13][maxyear];

Поскольку в году 12 месяцев, закономерно применять для месяцев индексы от 1 до 12. Мы объявим массив длиной 13, в котором элемент с индексом 0 не употребляется. Теперь в результате ввода rainTable[4] мы сможем определить уровень осадков за все годы, вошедшие в рассматриваемый диапазон. На рис 6.12 данные за май месяц выделены серым цветом.

Разработка программы. В рассматриваемом случае мы имеем дело с классической программой типа считывание/процесс/вывод, где при выполнении процедур ввода/вывода применяются файлы. Поэтому мы создадим один класс для выполнения всех действий и вызовем его из главной программы.

Наряду с печатью данных и результатов мы бы хотели начертить график, показывающий среднее значение уровней осадков за различные месяцы, а так же соответствующие стандартные отклонения. Поскольку при вызове метода add класса Graph точки добавляются только к одной строящейся линии, мы сохраним значения стандартных отклонений в массиве и в конце программы выполним еще один цикл для создания второго графика.

В процессе обработки значений будут использоваться методы, предназначенные для вычисления среднего значения и стандартного отклонения, которые хранятся в классе Stats пакета myutilities. Процедура вызова этих методов, описанная в примере 6.3, является довольно простой.

Программа. В программе предусмотрено получение данных из конкретного файла rain.data. Можно сделать так, чтобы имя файла являлось переменной и могло быть введено. Поскольку результаты работы программы занимают значительный объем, мы не задавали вывод на экран. Результаты появятся в консольном окне, но могут так же быть отправлены в файл. Для этого следует выполнить перенаправление потока вывода, как показано в следующей строке:

java Wether>weather.out

Ниже дан полный текст программы.

Вверх


import java.io.*;
import javagently.*;
import myutilities.*;

class Weather {

/* The Weather program by J M Bishop Jan 1997
* =================== Graphics July 1999
* updated June 2000
*
* Calculates mean and standard deviation of rainfall
* for each month over the number of years provided.
*
* Illustrates handling of a matrices and passing columns
* as parameters.
* The data must be in a file in the form:
* year followed by the 12 rainfall figures for
* the months of that year.
*/

static class RainBase {

int base = 1950;
int startyear, endyear = 0; // range from 1950 upwards

// all arrays declared length 13 so months go from 1 to 12
double rainTable [] [] = new double [13] [70];
double averagetable [] = new double [13];
double stddevTable [] = new double [13];

Graph g = new Graph("Rainfall", "month", "cm");

void readIn () throws IOException {

Stream fin = new Stream("Rain.dat",Stream.READ);
int actualYear = 0; // e.g. 1989
int yearIndex = 0; // e.g. 0

// The actual years are read in and might not be sorted
// or contiguous. The yearIndex starts at 0 and is
// used to store the data in an orderly manner.

try {

while (true) {
actualYear = fin.readInt();
System.out.print(actualYear+" ");
if (yearIndex == 0) {
startyear = actualYear;
}
for (int m = 1; m<=12; m++) {
rainTable[m][yearIndex] = fin.readDouble();
System.out.print(
Stream.format(rainTable[m][yearIndex],6,1));
}
System.out.println();
yearIndex++;
}
}
catch (EOFException e) {
// Pick up the last year of data read in.
endyear = actualYear;
System.out.println("Data read for "+startyear+" to "+
endyear+"\n\n");
}
}

void showResults () {

System.out.println("Rainfall statistics for " +
startyear + " to " + endyear);
System.out.println("========================" +
"============\n");
System.out.println("Month\tMean\tStd Deviation");
int nyears = endyear-startyear+1;
double a;
g.setTitle("Mean");
g.setSymbol(true);

for (int m =1; m<=12; m++) {

averagetable[m] = Stats.mean (rainTable[m], nyears);
stddevTable[m] = Stats.stddev(rainTable[m], nyears, averagetable[m]);
System.out.println(Stream.format(m,2)+
Stream.format(averagetable[m],12,2)+
Stream.format(stddevTable[m],12,4));
g.add(m,averagetable[m]);
}

g.nextGraph();
g.setColor(g.blue);
g.setSymbol(true);
g.setTitle("Standard Deviation");
for (int m = 1; m <= 12; m++) {

g.add (m, stddevTable[m]);
}
g.showGraph();
}
}

public static void main (String args [])throws IOException {


RainBase rain = new RainBase();
rain.readIn ();
rain.showResults ();
}

}

Подсказка

Рис 6.6а Промежуточный результат программы Weather

Подсказка

Рис 6.6 График из программы Weather

И вновь мы выбрали реальные данные, хотя это затрудняет проверку программы и нам сложнее убедиться в том, что получены правильные результаты. Но поскольку начерчен график, не трудно удостовериться, что значения корректны. Кроме того, доподлинно известно, что методы, определяющие среднее значение и стандартное отклонение, работают корректно, поскольку их проверка уже производилась. Следует подчеркнуть, что это преимущество мы получаем при работе с библиотеками.

Вверх

Анализ программы:

import java.io.*;
import javagently.*;
import myutilities.*;

class Weather {

static class RainBase {

//Сначала определим переменные и массивы.

int base = 1950;
int startyear, endyear = 0; // range from 1950 upwards

// all arrays declared length 13 so months go from 1 to 12
double rainTable [] [] = new double [13] [70];
double averagetable [] = new double [13];
double stddevTable [] = new double [13];

//Затем объявим и инициируем объект g класса Graph:

Graph g = new Graph("Rainfall", "month", "cm");

//В параметрах мы определили название графика, и названия шкалы X и Y.

//Метод readIn() который считывает данные из файла,
//пока не обнаружит конец файла,
//обнаружив который выбросит исключение:

void readIn () throws IOException {

Stream fin = new Stream("Rain.dat",Stream.READ);
int actualYear = 0; // e.g. 1989
int yearIndex = 0; // e.g. 0

// годы могут быть неотсортированы и неупорядочены.
// переменная yearIndex начинается с 0 и используется
// для упорядочения и хранения данных

try {

while (true) {

//Считываем из текстового файла первую цифру - год,
//и сразу выводим ее на экран:

actualYear = fin.readInt();
System.out.print(actualYear+" ");

if (yearIndex == 0) {

startyear = actualYear;
}

//Далее в цикле считываем пословно двеннадцать цифр (до конца строки),
//и при считывании каждого слова сразу выводим его на экран.
//Считанными данными заполняем двумерный массив rainTable:

for (int m = 1; m<=12; m++) {

rainTable[m][yearIndex] = fin.readDouble();
System.out.print( Stream.format(rainTable[m][yearIndex],6,1));
}

//Инкременируем переменную yearIndex, и цикл while
//повторяется для чтения следующей строки,
//пока не будет обнаружен конец файла:

System.out.println();
yearIndex++;

}

}

Такой способ заполнения двумерного массива, который мы избрали, приведет к тому, что в первом измерении массива у нас будут месяцы, а во втором измерении - годы. Это для нас очень удобно, потому-что выбирая в первом измерении какой-либо один месяц, мы сможем "пробежаться" в цикле по второму измерению массива по всем годам для этого месяца, получая данные по осадкам за все годы в этот конкретный месяц, что нам и было нужно.

//Обнаружен конец файла:

catch (EOFException e) {

// Pick up the last year of data read in.
endyear = actualYear;
System.out.println("Data read for "+startyear+" to "+
endyear+"\n\n");
}
}

Данные считаны из текстового файла, выведены на экран. И этими данными инициирован двумерный массив rainTable. Теперь с эти двумерным массивом можно работать. Кроме того определены начальный и конечный годы выборки. Они нужны для построения графика.

void showResults () {

//Заголовок для вывода результата на консоль:

System.out.println("Rainfall statistics for " +
startyear + " to " + endyear);
System.out.println("========================" +
"============\n");
System.out.println("Month\tMean\tStd Deviation");

int nyears = endyear-startyear+1;
double a;
g.setTitle("Mean");
g.setSymbol(true);

В нижеприведенном цикле инициируем данными массивы averagetable и stddevTable, в которых будут
находится данные о среднем уровне осадков и стандартном отклонении соответственно, в
каждом элементе массива - средний уровень осадков за один месяц и соостветственно среднее
отклонение за один месяц. По мере инициализации массива выводим данные из него на
консоль, а так же на график класса Graph:


for (int m =1; m<=12; m++) {

averagetable[m] = Stats.mean (rainTable[m], nyears);
stddevTable[m] = Stats.stddev (rainTable[m], nyears, averagetable[m]);

System.out.println(Stream.format(m,2)+

Stream.format(averagetable[m],12,2)+
Stream.format(stddevTable[m],12,4));

g.add(m,averagetable[m]);
}

Начертим еще один график Graph, для стандартного отклонения:

g.nextGraph();
g.setColor(g.blue);
g.setSymbol(true);
g.setTitle("Standard Deviation");

for (int m = 1; m <= 12; m++) {

g.add (m, stddevTable[m]);
} g.showGraph();
}
}

Далее главная программа, с которой все ясно:

public static void main (String args [])throws IOException {

RainBase rain = new RainBase();

rain.readIn (); rain.showResults ();

}

}


В функции showResults () поначалу возникло два вопроса.

Первый вопрос по следующей инструкции:

averagetable[m] = Stats.mean (rainTable[m], nyears);

Здесь rainTable[m] - адрес строки двумерного массива, а что такое строка двумерного массива? Это одномерный массив! Значит мы передали адрес одномерного массива. А во втором параметре мы передали число элементов, этого одномерного массива. А как мы помним функция mean() работает так:

public static double mean (double a[], int n) {

double sum = 0;
for (int i = 1; i<=n; i++)
sum += a[i];
return sum / n;
}

То есть эта функция пробегается по всему одномерному массиву, складывает значения всех элементов и делит на количество элементов, то есть находит среднее значение, которое и возвращает. Значит функция пробежится по всей строке двумерного массива (фактически по одномерному массиву) адрес которой мы передали в параметре.

Остается запомнить это среднее значение в элементе другого одномерного массива, который хранит средние значения одноименных месяцев за все годы которые мы исследуем.

Второе. Надо проштудировать в применении класса Graph. Вот выделим отдельно его инструкции:

Graph g = new Graph("Rainfall", "month", "cm");

...
g.setTitle("Mean");
g.setSymbol(true);
...
for (int m =1; m<=12; m++) {

... g.add(m,averagetable[m]);

}

g.nextGraph();
g.setColor(g.blue);
g.setSymbol(true);
g.setTitle("Standard Deviation");

for (int m = 1; m <= 12; m++) {

g.add (m, stddevTable[m]);
} g.showGraph();

----------------------------------------

Graph g = new Graph("Rainfall", "month", "cm");

...
g.setTitle("Mean");
g.setSymbol(true);
...
for (int m =1; m<=12; m++) {

... g.add(m,averagetable[m]);

}
g.showGraph();


Назад | Начало урока | Вверх | Вперед
Содержание

Hosted by uCoz