График отображается в такой последовательности. Сначала рисуется ограничивающий прямоугольник (рамка), затем дважды вызывается функция 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);
}
}