Работа с Visual Studio.Net

         

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


С помощью 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+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;

}

Содержание раздела