Глава 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;
Поскольку в году 12 месяцев, закономерно применять для месяцев индексы от 1 до 12. Мы объявим массив длиной 13, в котором элемент с индексом 0 не употребляется. Теперь в результате ввода rainTable[4] мы сможем определить уровень осадков за все годы, вошедшие в рассматриваемый диапазон. На рис 6.12 данные за май месяц выделены серым цветом.
Разработка программы. В рассматриваемом случае мы имеем дело с классической программой типа считывание/процесс/вывод, где при выполнении процедур ввода/вывода применяются файлы. Поэтому мы создадим один класс для выполнения всех действий и вызовем его из главной программы.
Наряду с печатью данных и результатов мы бы хотели начертить график, показывающий среднее значение уровней осадков за различные месяцы, а так же соответствующие стандартные отклонения. Поскольку при вызове метода add класса Graph точки добавляются только к одной строящейся линии, мы сохраним значения стандартных отклонений в массиве и в конце программы выполним еще один цикл для создания второго графика.
В процессе обработки значений будут использоваться методы, предназначенные для вычисления среднего значения и стандартного отклонения, которые хранятся в классе Stats пакета myutilities. Процедура вызова этих методов, описанная в примере 6.3, является довольно простой.
Программа. В программе предусмотрено получение данных из конкретного файла rain.data. Можно сделать так, чтобы имя файла являлось переменной и могло быть введено. Поскольку результаты работы программы занимают значительный объем, мы не задавали вывод на экран. Результаты появятся в консольном окне, но могут так же быть отправлены в файл. Для этого следует выполнить перенаправление потока вывода, как показано в следующей строке:
java Wether>weather.out
Ниже дан полный текст программы.
Вверх
class Weather
{
/* The Weather program by J M Bishop Jan 1997
static class RainBase
{
int base = 1950;
// all arrays declared length 13 so months go from 1 to 12
Graph g = new Graph("Rainfall", "month", "cm");
void readIn () throws IOException
{
// The actual years are read in and might not be sorted
try
{
void showResults ()
{
for (int m =1; m<=12; m++)
{
g.nextGraph();
public static void main (String args [])throws IOException
{
Рис 6.6а Промежуточный результат программы Weather
Рис 6.6 График из программы Weather
И вновь мы выбрали реальные данные, хотя это затрудняет проверку программы и нам сложнее убедиться в том, что получены правильные результаты. Но поскольку начерчен график, не трудно удостовериться, что значения корректны. Кроме того, доподлинно известно, что методы, определяющие среднее значение и стандартное отклонение, работают корректно, поскольку их проверка уже производилась. Следует подчеркнуть, что это преимущество мы получаем при работе с библиотеками.
Вверх
Анализ программы:
class Weather
{
static class RainBase
{
//Сначала определим переменные и массивы.
int base = 1950;
// all arrays declared length 13 so months go from 1 to 12
//Затем объявим и инициируем объект g класса Graph:
Graph g = new Graph("Rainfall", "month", "cm");
//В параметрах мы определили название графика, и названия шкалы X и Y.
//Метод readIn() который считывает данные из файла,
void readIn () throws IOException
{
Stream fin = new Stream("Rain.dat",Stream.READ);
// годы могут быть неотсортированы и неупорядочены.
try
{
//Считываем из текстового файла первую цифру - год,
actualYear = fin.readInt();
if (yearIndex == 0)
{
//Далее в цикле считываем пословно двеннадцать цифр (до конца строки),
for (int m = 1; m<=12; m++)
{
//Инкременируем переменную yearIndex, и цикл while
System.out.println();
Такой способ заполнения двумерного массива, который мы избрали, приведет к тому, что
в первом измерении массива у нас будут месяцы, а во втором измерении - годы. Это для нас очень удобно, потому-что выбирая в первом измерении какой-либо один месяц, мы сможем "пробежаться" в цикле по второму измерению массива по всем годам для этого месяца, получая данные по осадкам за все годы в этот конкретный месяц, что нам и было нужно.
//Обнаружен конец файла:
catch (EOFException e)
{
void showResults ()
{
//Заголовок для вывода результата на консоль:
System.out.println("Rainfall statistics for " +
int nyears = endyear-startyear+1;
for (int m =1; m<=12; m++)
{
System.out.println(Stream.format(m,2)+
Начертим еще один график Graph, для стандартного отклонения:
g.nextGraph();
for (int m = 1; m <= 12; m++)
{
Далее главная программа, с которой все ясно:
public static void main (String args [])throws IOException
{
RainBase rain = new RainBase();
rain.readIn ();
rain.showResults ();
В функции showResults () поначалу возникло два вопроса.
Первый вопрос по следующей инструкции:
Здесь
То есть эта функция пробегается по всему одномерному массиву, складывает значения всех элементов и делит на количество элементов, то есть находит среднее значение, которое и возвращает. Значит функция пробежится по всей строке двумерного массива (фактически по одномерному массиву) адрес которой мы передали в параметре.
Остается запомнить это среднее значение в элементе другого одномерного массива, который хранит средние значения одноименных месяцев за все годы которые мы исследуем.
Второе. Надо проштудировать в применении класса Graph. Вот выделим отдельно его инструкции:
Graph g = new Graph("Rainfall", "month", "cm");
...
g.nextGraph();
for (int m = 1; m <= 12; m++)
{
----------------------------------------
Graph g = new Graph("Rainfall", "month", "cm");
...
Назад |
Начало урока |
Вверх |
Вперед
double rainTable [] = new double [13][maxyear];
import java.io.*;
import javagently.*;
import myutilities.*;
* =================== 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.
*/
int startyear, endyear = 0; // range from 1950 upwards
double rainTable [] [] = new double [13] [70];
double averagetable [] = new double [13];
double stddevTable [] = new double [13];
int actualYear = 0; // e.g. 1989
int yearIndex = 0; // e.g. 0
// or contiguous. The yearIndex starts at 0 and is
// used to store the data in an orderly manner.
System.out.print(actualYear+" ");
if (yearIndex == 0)
{
for (int m = 1; m<=12; m++)
{
System.out.print(
Stream.format(rainTable[m][yearIndex],6,1));
System.out.println();
yearIndex++;
catch (EOFException e)
{
endyear = actualYear;
System.out.println("Data read for "+startyear+" to "+
endyear+"\n\n");
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);
stddevTable[m] = Stats.stddev(rainTable[m], nyears, averagetable[m]);
System.out.println(Stream.format(m,2)+
Stream.format(stddevTable[m],12,4));
g.setColor(g.blue);
g.setSymbol(true);
g.setTitle("Standard Deviation");
for (int m = 1; m <= 12; m++)
{
g.showGraph();
RainBase rain = new RainBase();
rain.readIn ();
rain.showResults ();
import java.io.*;
Данные считаны из текстового файла, выведены на экран. И этими данными инициирован двумерный массив rainTable. Теперь с эти двумерным массивом можно работать. Кроме того определены начальный и конечный годы выборки. Они нужны для построения графика.
import javagently.*;
import myutilities.*;
int startyear, endyear = 0; // range from 1950 upwards
double rainTable [] [] = new double [13] [70];
double averagetable [] = new double [13];
double stddevTable [] = new double [13];
//пока не обнаружит конец файла,
//обнаружив который выбросит исключение:
int actualYear = 0; // e.g. 1989
int yearIndex = 0; // e.g. 0
// переменная yearIndex начинается с 0 и используется
// для упорядочения и хранения данных
//и сразу выводим ее на экран:
System.out.print(actualYear+" ");
//и при считывании каждого слова сразу выводим его на экран.
//Считанными данными заполняем двумерный массив rainTable:
System.out.print(
Stream.format(rainTable[m][yearIndex],6,1));
//повторяется для чтения следующей строки,
//пока не будет обнаружен конец файла:
yearIndex++;
endyear = actualYear;
System.out.println("Data read for "+startyear+" to "+
endyear+"\n\n");
startyear + " to " + endyear);
System.out.println("========================" +
"============\n");
System.out.println("Month\tMean\tStd Deviation");
double a;
g.setTitle("Mean");
g.setSymbol(true);
находится данные о среднем уровне осадков и стандартном отклонении соответственно, в
каждом элементе массива - средний уровень осадков за один месяц и соостветственно среднее
отклонение за один месяц. По мере инициализации массива выводим данные из него на
консоль, а так же на график класса Graph:
stddevTable[m] = Stats.stddev
(rainTable[m], nyears, averagetable[m]);
Stream.format(stddevTable[m],12,4));
g.add(m,averagetable[m]);
g.setColor(g.blue);
g.setSymbol(true);
g.setTitle("Standard Deviation");
averagetable[m] = Stats.mean (rainTable[m], nyears);
rainTable[m]
- адрес строки двумерного массива, а что такое строка двумерного массива? Это одномерный массив! Значит мы передали адрес одномерного массива. А во втором параметре мы передали число элементов, этого одномерного массива.
А как мы помним функция mean() работает так:
public static double mean (double a[], int n)
{
for (int i = 1; i<=n; i++)
g.setTitle("Mean");
g.setSymbol(true);
...
for (int m =1; m<=12; m++)
{
g.setColor(g.blue);
g.setSymbol(true);
g.setTitle("Standard Deviation");
g.setTitle("Mean");
g.setSymbol(true);
...
for (int m =1; m<=12; m++)
{
g.showGraph();
Содержание