Иллюстрированный самоучитель по Visual Studio .NET

Класс графика

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

Обычно при построении графиков числа, используемые для оцифровки осей (мантиссы), укладываются в некоторый разумный диапазон и принадлежат множеству чисел, кратных по модулю 10, стандартным значениям шага мантиссы (2, 2.5, 5 и 10). Операцию выбора шага сетки, удовлетворяющую этим условиям, удобно выполнить в глобально определенной функции, не принадлежащей классу CGraph. Это дает возможность использовать функцию для нужд других алгоритмов и классов. Ниже приведена функция gScale, которая выполняет подбор шага сетки. Мы постепенно дадим содержимое всего файла Graph.срр, поэтому вы можете полностью убрать существующие коды заготовки. Начало файла имеет такой вид:

#include "StdAfx.h"
#include "graph.h"
//===== Доля окна, занимаемая графиком
#define SCAT,F,_X 0. 6
#define SCALE_Y 0.6
//=== Внешняя функция нормировки мантисс шагов сетки
void gScale (double span, doubles step)
{
//== Переменная span определяет диапазон изменения
//== значаний одной из координат точек графика
//== Вычисляем порядок числа, описывающего диапазон
int power = int(floor(loglO(span)));
//===== Множитель (zoom factor)
double factor = pow(10, power);
//===== Мантисса диапазона (теперь 1 < span < 10)
span /= factor;
//===== Выбираем стандартный шаг сетки if (span<1.99)
step=.2;
else if (span<2.49)
step=.25;
else if (span<4.99)
step=.5;
else if (span<10.)
step= 1.;
//===== Возвращаем реальный шаг сетки (step*10~power)
step *= factor;
}

Результатом работы функции gScale является значение мантиссы дискретного шага сетки, которая наносится на график и оцифровывает оду из осей. Самым сложным местом в алгоритме разметки осей является метод CGraph::Scale. Он по очереди работает для обеих осей и поэтому использует параметр с данными типа TData, описывающими конкретную ось. Особенностью алгоритма является реализация идеи, принадлежащей доценту СПбГТУ Александру Калимову и заключающейся в том, чтобы как можно дольше не переходить к экспоненциальной форме записи чисел. Обычно Калимов использует форму с фиксированной запятой в диапазоне 7 порядков изменения чисел (10~3 +104), и это дает максимально удобный для восприятия формат, повышая читабельность графика:

void CGraph::Scale (TDatai data)
{
//===== С пустой последовательностью не работаем
if (m_Points.empty()) return;
//===== Готовимся искать экстремумы
data.Max = data.bX? m_Points [0].х: m_Points [0].у;
data.Min = data.Max;
//===== Поиск экстремумов
for (UINT j=0; j<ra_Point5.size(); j++)
{
double d = data.bX?
m_Points [ j].x
: m_Points [ j] .y;
if (d < data.Min) data.Min = d;
if (d > data.Max) data.Max = d;
}
//===== Максимальная амплитуда двух экстремумов
double ext = max(fabs(data.Min),fabs(data.Max));
//===== Искусственно увеличиваем порядок экстремума
//===== на 3 единицы, так как мы хотим покрыть 7 порядков,
//===== не переходя к экспоненцеальной форме чисел
double power = ext > 0.? loglO(ext) +3.: 0.;
data.Power = int(floor(power/7.));
//===== Если число не укладывается в этот диапазон
if (data.Power!= 0)
//===== то мы восстанавливаем значение порядка
data.Power = int(floor(power)) – 3;
//===== Реальный множитель
data.Factor = pow(10,data.Power);
//===== Диапазон изменения мантиссы
double span = (data.Max – data.Min)/data.Factor;
//===== Если он нулевой, if (span == 0.)
span = 0.5; // то искусственно раздвигаем график
// Подбираем стандартный шаг для координатной сетки
gScale (span, data.Step);
//===== Шаг с учетом искусственных преобразований
data.dStep = data.Step * data.Factor;
//== Начальная линия сетки должна быть кратна шагу
//==== и быть меньше минимума
data.dStart = data.dStep *
int (floor(data.Min/data.dStep));
data.Start = data.dStart/data.Factor;
//===== Вычисляем последнюю линию сетки
for (data.End = data.Start;
data.End < data.Min/data.Factor + span-le-10;
data.End += data.Step)
data.dEnd = data.End*data.Factor;
}
Если Вы заметили ошибку, выделите, пожалуйста, необходимый текст и нажмите CTRL + Enter, чтобы сообщить об этом редактору.