График отображается в такой последовательности.
Сначала рисуется ограничивающий прямоугольник
(рамка), затем дважды вызывается функция Scale,
которая подготавливает данные для разметки осей.
После этого выводятся экстремальные значения
функции. В этот момент в более сложном случае
следует создавать и выводить так называемую
легенду графика — информацию о соответствии
маркеров и стилей линий определенным кривым. Так
как мы изображаем только одну кривую, то эта
часть работы сведена к минимуму. Перед тем как
отобразить координатную сетку, следует создать и
выбрать в контекст другое перо (gridPen). Сама
сетка изображается в двух последовательных
циклах прохода по диапазонам координат,
подготовленных в методе Scale.
В каждом цикле мы сначала нормируем текущую
координату, затем преобразовываем ее в оконную,
вызывая одну из функций типа MapToLog*.
Одновременно с линией сетки выводится цифровая
метка. В ходе процесса нам несколько раз
приходится менять способ выравнивания текста
(см. вызовы SetTextAlign). Подстройка
местоположения текста осуществляется с помощью
переменной m_LH (better Height), значение
которой зависит от выбранного размера шрифта.
После вывода координатной сетки происходит вывод
трех строк текста: метки осей и заголовок
графика. В последнюю очередь происходит вывод
самой кривой графика. В более сложном случае,
который не реализован, мы в цикле проходим по
всем объектам класса MyLine и просим каждую
линию изобразить себя в нашем контексте
устройства. Каждая линия при этом помнит и
использует свой стиль, толщину, цвет и
маркировку:
void
CGraph::Draw(CDC *pDC) {
//====== С помощью контекста устройства
//======
узнаем адрес окна, его использующего
CWnd *pWnd =pDC->GetWindow();
CRect r;
pWnd->GetClientRect(ir);
//======
Уточняем размеры окна
m_Size = r.Size();
m_Center =
CPoint(m_Size.cx/2, m_Size.cy/2);
//======
Сохраняем атрибуты контекста
int
nDC = pDC->SaveDC();
//====== Создаем черное перо для изображения
рамки
CPen pen(PS_SOLID,
О,
COLORREF(0));
pDC->SelectObject(Spen);
//======
Преобразуем координаты рамки
int It = MapToLogX(-0.S),
rt = MapToLogX(0.S),
tp = MapToLogY(0.S),
bm = MapToLogY(-0.S);
pDC->Rectangle (It,
tp, rt, bm);
//====== Задаем цвет и выравнивание текста
pDC->SetTextColor
(0);
pDC->SetTextAlign(TA_LEFT
| TA_BASELINE);
//======
Выбираем шрифт
pDC->SelectObject
(&m_Font);
//======
Вычисляем атрибуты координатных осей
Scale(m_DataX);
Scale(m_DataY);
//====== Выводим экстремумы функции
CString s;
s.Format("Min =
%.3g",m_DataY.Min);
pDC->TextOut(rt+m_LH,
tp+m_LH, s) ;
s.Format("Max =
%.3g",m_DataY.Max);
pDC->TextOut(rt+m_LH,
tp+m_LH+m_LH, s);
//======
Готовимся изображать координатную сетку
CPen
gridPen(PS_SOLID, 0, RGB(92,200, 178));
pDC->SelectObject(SgridPen);
pDC->SetTextAlign(TA_CENTER
| TA_BASELINE);
//======Рисуем
вертикальные линии сетки
for
(double x = m_DataX.Start;
X < m_DataX.End - m_DataX.Step/2.;
x += m_DataX.Step)
{
//====== Нормируем
координату х
double
xn = (x - m_DataX.Start) /
(m_DataX.End - m_DataX.Start) - 0.5;
//======
Вычисляем оконную координату
int
xi = MapToLogX(xn);
//====== Пропускаем крайние линии,
//======
так как они совпатают с рамкой
if
(x > m_DataX.Start && x < m_DataX.End)
{
pDC->MoveTo(xi, bm);
pDC->LineTo(xi, tp); )
//======
Наносим цифровую метку
pDC->TextOut (xi, bm+m_LH, MakeLabel(true, x));
}
//=== Повторяем цикл для горизонтальных линий
сетки
pDC->SetTextAlign(ТА
RIGHT | TA_BASELINE);
for (double
у
= m_DataY.Start;
у
< m_DataY.End - m_DataY.Step/2.;
у
+= m_DataY.Step)
{
double
yn = (y - m_DataY.Start) /
(m_DataY.End - m_DataY.Start) - 0.5;
int
yi = MapToLogY(yn);
if
(y > m_DataY. Start &S,
у
< m_DataY.End)
{
pDC->MoveTo(lt, yi)
;
pDC->LineTo(rt, yi)
;
pDC->TextOut(lt-m_LH/2,yi,MakeLabel(false,
y));
}
}
//======
Вывод меток осей
pDC->TextOut(lt-m_LH/2,
tp - m_LH, m_sY);
pDC->SetTextAlign(TA_LEFT
| TA_BASELINE);
pDC->TextOut(rt-m_LH,
bm + m_LH, m_sX);
//======
Вывод заголовка
if
(ra_sTitle.GetLength() > 40)
m_sTitle.Left(40);
pDC->SelectObject(Sm_TitleFont);
pDC->SetTextAlign(TA_CENTER
| TA_BASELINE);
pDC->TextOut((It+rt)/2,
tp - m_LH, m_sTitle);
//====== Вывод линии графика
DrawLine(pDC);
//======
Восстанавливаем инструменты GDI
pDC->RestoreDC(nDC);
}
Вывод линии графика начинается с создания и
выбора пера. Эти действия можно вынести и
поместить в какой-нибудь диалог по изменению
атрибутов пера, но мы не будем этого делать, так
как данное действие целесообразно только в
случае, когда график состоит из нескольких
линий. Обе координаты каждой точки сначала
нормируются переходом к относительным значениям
в диапазоне (-0.5*0.5), затем приводятся к
оконным. После чего точки графика
последовательно соединяются с помощью
GDI-функции LineTo:
void
CGraph::DrawLine(CDC *pDC) {
//====== Уничтожаем старое перо
if
(m_Pen.m_hObject)
m_Pen.DeleteObject() ; //======
Создаем новое
m_Pen.CreatePen(PS_SOLID, m_Width, m_Clr);
pDC->SelectObject(im_Pen);
double
x0 = m_DataX.dStart,
y0 = m_DataY.dStart,
dx = m_DataX.dEnd -
x0,
dy = m_DataY.dEnd -
y0;
//======
Проход по всем точкам
for
(UINT i=0; i < m_Points.size(); i++) {
//======
Нормируем координаты
double x
= (ra_Points[i].x - xO) / dx - .5,
у
= (m_Points[i].у
- y0) / dy - .5;
//======
Переход к оконным координатам
CPoint pt (MapToLogX(x)
,MapToLogY(y)) ;
//======
Если точка первая, то ставим перо
if
(i==0)
pDC->MoveTo(pt);
else
pDC->LineTo(pt);
}
} |