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

         

Отображение графика


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

}

}




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