код является результатом непосредственного воплощения идей, изложенных в главе 10. Если какие-то места этого кода вам не ясны, пожалуйста, перечитайте эту главу. Мы не обязаны определять тип
Distribution
и оператор
>>
. Однако он упрощает код по сравнению с методом грубой силы, основанным на принципе “просто прочитать данные и построить график”. Наше использование класса
Distribution
разделяет код на логические части, что облегчает его анализ и отладку. Не бойтесь вводить типы просто для того, чтобы упростить код. Мы определяем классы, чтобы программа точнее соответствовала нашему представлению об основных понятиях предметной области. В этом случае даже “небольшие” понятия, использованные локально, например линия, представляющая распределение возрастов по годам, могут оказаться полезными. Имея тип
Distribution
, можем записать цикл чтения данных следующим образом.
string file_name = "japanese-age-data.txt";
ifstream ifs(file_name.c_str);
if (!ifs) error("Невозможно открыть файл ",file_name);
// ...
Distribution d;
while (ifs>>d) {
if (d.year<base_year || end_year<d.year)
error("год не попадает в диапазон");
if (d.young+d.middle+d.old != 100)
error("Проценты не согласованы");
// ...
}
Иначе говоря, мы пытаемся открыть файл
japanese-age-data.txt
и выйти из программы, если его нет. Идея не указывать явно имя файла в программе часто оказывается удачной, но в данном случае мы пишем простой пример и не хотим прилагать лишние усилия. С другой стороны, мы присваиваем имя файла
japanese-age-data.txt
именованной переменной типа
string
, поэтому при необходимости его легко изменить.
Цикл чтения проверяет диапазон чисел и согласованность данных. Это основные правила проверки таких данных. Поскольку оператор
>>
сам проверяет формат каждого элемента данных, в цикле чтения больше нет никаких проверок.
15.6.2. Общая схема
Что мы хотим увидеть на экране? Этот ответ можно найти в начале раздела 15.6. На первый взгляд, для изображения данных нужны три объекта класса
Open_polyline
— по одному на каждую возрастную группу. Каждый график должен быть помечен. Для этого мы решили в левой части окна записать “название” каждой линии. Этот выбор кажется удачнее, чем обычная альтернатива
clearer
, — поместить метку где-то на самой линии. Кроме того, для того чтобы отличать графики друг от друга, мы используем разные цвета и связываем их с метками.
Мы
хотим пометить ось x, указав годы. Вертикальная линия, проходящая через отметку 2008, означает год, после которого данные являются результатом экстраполяции.
В качестве названия изображения мы решили просто использовать метку окна.
Сделать так, чтобы графический код был правильным и красиво выглядел, — довольно сложная задача. Основная причина заключается в том, что нам придется выполнить множество кропотливых вычислений, связанных с определением размеров и смещений. Для их упрощения мы начали с определения символических констант, определяющих способ использования экрана.
const int xmax = 600; // размер окна
const int ymax = 400;
const int xoffset = 100;// расстояние от левого края окна до оси y
const int yoffset = 60; // расстояние от нижнего края окна до оси х
const int xspace = 40; // пространство между осями
const int yspace = 40;
const int xlength = xmax–xoffset–xspace; // длина осей
const int ylength = ymax–yoffset–yspace;
В принципе эти инструкции определяют прямоугольную область (окно) и вложенный в него прямоугольник (определенный осями).
Без такого схематического представления о размещении элементов экрана в нашем окне с помощью символических констант код был бы безнадежно запутанным.
15.6.3. Масштабирование данных
Теперь мы должны определить, как изобразить данные в описанной области. Для этого масштабируем данные так, чтобы они помещались в прямоугольнике, определенном осями координат. Масштабирование осуществляется с помощью масштабных множителей, представляющих собой отношение диапазона изменения данных и меток на осях.
) как числа с плавающей точкой — иначе в наших вычислениях возникли бы серьезные ошибки, связанные с округлением. Для того чтобы избежать целочисленного деления, перед делением преобразовываем наши длины в тип
double
(см. раздел 4.3.3).
Теперь можно поместить точки на ось x, вычитая их базовое значение (
1960
), масштабируя с помощью множителя
xscale
и добавляя смещение
xoffset
. Значение y обрабатывается аналогично. Эти операции тривиальны, но кропотливы и скучны. Для того чтобы упростить код и минимизировать вероятность ошибок (а также, чтобы не приходить в отчаяние), мы определили небольшой класс, в который включили эти вычисления.
class Scale { // класс для преобразования координат