Класс
графика
С помощью Studio.Net
введите в состав проекта новый generic-класс CGraph, не указывая имени базового
класса и не включая флажок Virtual destructor. В файл декларации нового класса
введите вручную вспомогательный класс CDPoint, необходимость в котором мы обсуждали
ранее. Затем добавьте объявление структуры TData, которая собирает воедино все
данные, используемые при построении графика. Начальная буква Т в имени класса
осталась со времен работы в среде Borland. Там принято все классы именовать
начиная с буквы Т (Туре), означающей создание нового типа данных. Но в отличие
от старой реализации графика, которая, возможно, знакома читателю по книге «Технологии
программирования на языке C++» (Издательство СПбГТУ, 1997), мы введем в класс
CGraph некоторые новые возможности:
#
pragma
once
class
CDPoint
{
public:
//===
Две вещественные координаты точки на плоскости
double
x, у;
//=======
Стандартный набор конструкторов и операций
CDPoint
() {
х=0.;
у=0.;
}
CDPoint(
double
xx
, double yy)
{
х=хх;
У=УУ;
}
CDPoints
operator=(const CDPointi pt) {
x
= pt.x;
У
= pt.y; return *this;
}
CDPoint(
const
CDPointS pt) {
*this
- pt; } };
//=====
Вспомогательные данные, характеризующие
//==
последовательность координат вдоль одной из осей
struct
TData (
//=====
Порядок в нормализованном представлении числа
int
Power; //===== Флаг оси X
bool
bХ;
double
//=======
Экстремумы
Min,
Max,
//=======
Множитель -(10 в степени Power)
{
Factor,
//=======
Шаг вдоль оси (мантисса)
Step,
//=======
Реальный шаг
dStep,
//====
Первая и последняя координаты (мантиссы)
Start,
End,
//
======= Первая и последняя координаты
dStart,
dEnd; };
//=====
Класс, реализующий функции плоского графика
class
CGraph {
public:
//=====
Данные, характеризующие данные вдоль осей
TData
m_DataX, m_DataY;
//=====
Контейнер точек графика
vector
<CDPoint>& m_Points;
//=====
Текущие размеры окна графика
CSize
m_Size;
//=====
Экранные координаты центра окна
CPoint
m_Center;
//=====
Заголовок и наименования осей
CString
m_sTitle, m_sX, m_sY;
//=====
Перо для рисования
CPen
m_Pen;
//=====
Два типа шрифтов
CFont
m_TitleFont, m_Font;
//=====
Высота буквы (зависит от шрифта)
int
m_LH,
//=====
Толщина пера
m_Width;
//=====
Цвет пера COLORREF m_Clr;
//=======
Методы для управления графиком
CGraph(vector<CDPoint>&
pt, CString sTitle, CString sX, CString sY) ;
virtual
-CGraph();
//=====
Заполнение TData для любой из осей
void
Scale(TDataS data);
//=====
Переход к логическим координатам точек
int
MapToLogX
(double d);
int
MapToLogY
(double d);
//=====
Изображение в заданном контексте
void
Draw (CDC *pDC);
//=====
Изображение одной линии
void
DrawLine(CDC *pDC) ;
//=====
Подготовка цифровой метки на оси
CString
MakeLabel(bool bx,
doubles d);
};
Класс CGraph
сделан с учетом возможности развития его функциональности, так чтобы вы могли
добавить в него нечто и он мог бы справиться с несколькими кривыми одновременно.
Фактически он представляет собой упрощенную версию того класса, которым мы пользуемся
для отображения результатов расчета поля в двухмерной постановке. Отметьте,
что структура TData используется как для последовательности абсцисс, так и ординат.
Алгоритм нормирования
абсцисс и ординат проще создать, чем кратко и понятно описать. Тем не менее
попробуем дать ключ к тому, что происходит. Мы хотим, чтобы размеры графика
отслеживали размеры окна, а числа, используемые для разметки осей, из любого
разумного диапазона, как можно дольше оставались читабельными. Задача трудновыполнимая,
если динамически не изменять шрифт. В данной реализации мы не будем подбирать,
а используем только два фиксированных шрифта: для оцифровки осей и для вывода
заголовка графика. Обычно при построении графиков числа, используемые для оцифровки
осей (мантиссы), укладываются в некоторый разумный диапазон и принадлежат множеству
чисел, кратных по модулю 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+10
4),
и это дает максимально удобный для восприятия формат, повышая читабельность
графика:
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;
}
Содержание раздела
Forekc.ru
Рефераты, дипломы, курсовые, выпускные и квалификационные работы, диссертации, учебники, учебные пособия, лекции, методические пособия и рекомендации, программы и курсы обучения, публикации из профильных изданий
|