- •О.С. Зеленський
- •Розділ 1. Загальні відомості створення додатку windows
- •1.1. Створення додатку Windows за допомогою майстра AppWizard
- •1.2. Варіанти майстрів для різних проектів
- •1.3. Короткий опис sdi програми
- •1.4. Короткий опис mdi програми
- •1.5. Короткий опис простого діалогового додатку
- •Контрольні питання
- •Розділ 2. Повідомлення і команди
- •2.1. Обробка повідомлень
- •2.2. Цикли обробки повідомлень
- •2.3. Карти повідомлень
- •Контрольні питання
- •Розділ 3. Документи та види
- •3.1. Клас додатку
- •3.2. Клас головного вікна
- •3.3. Клас документа
- •3.4. Класи виду
- •Контрольні питання
- •Розділ 4. Робота з клавіатурою, мишею і меню
- •4.1. Робота з клавіатурою
- •4.2. Робота з мишею
- •4.3. Робота з меню
- •Контрольні питання
- •Розділ 5. Виведення на екран
- •5.1. Класи графічних об'єктів
- •5.2. Робота зі шрифтами
- •5.3. Робота з пензликами та малювання графічних фігур
- •5.4. Робота з пензликом
- •5.5. Робота зі скролінгом
- •5.6. Приклад роботи з таблицями
- •5.7. Малювання на екрані маніпулятором "миша"
- •5.8. Завантаження та виведення на екран бітових зображень
- •5.9. Копіювання бітових образів
- •5.10. Малювання графічних об'єктів з використанням резинових контурів та метафайлів
- •5.11. Виділення графічних об'єктів у прямокутній області
- •5.12. Універсальний приклад роботи з двовимірною графікою з використанням резинового контуру
- •5.13. Запис на диск та зчитування з диску графічних об'єктів
- •5.14. Побудова кругових діаграм і гістограм
- •5.15. Користувацький режим роботи з графікою на прикладі малювання годинника Clock
- •Контрольні питання
- •Завдання
- •Розділ 6. Друк і попередній перегляд документів
- •6.1. Вибір і налаштування параметрів друку
- •6.2. Створення контекста пристрою
- •6.3. Друк документів і бібліотека mfc
- •6.4. Масштабування
- •6.5. Друк багатосторінкового документа
- •Контрольні питання
- •Розділ 7. Робота з файлами
- •7.1. Приклад роботи з файлами на основі класів cFile, cStdioFile та потоку fstream
- •7.1.1. Робота з класом cFile
- •7.1.2. Робота з потоком fstream
- •Можливі режими доступу
- •7.1.3. Робота з класом cStdioFile
- •7.2. Серіалізація даних, клас cArchive
- •7.3. Використання реєстру в додатках
- •Контрольні питання
- •Завдання
- •Розділ 8. Діалогові вікна
- •8.1. Створення діалогового вікна та простіші елементи керування
- •8.2. Робота зі списками і комбінованими полями
- •8.3. Ускладнений приклад зі списками
- •8.4. Робота з повзунками
- •8.5. Виведення бітових матриць в діалозі та у вікні виду
- •8.6. Лінійний регулятор, лінійний індикатор, інкриментний регулятор
- •8.7. Стандартні діалоги вибору файлів, шрифтів та кольору
- •8.8. Взаємоз'вязок діалога, документа та виду при розробці додатку
- •8.8.1. Клас cDialDoc
- •8.8.2. Клас cDialView
- •8.8.3. Клас Cdlg
- •8.9. Формування вхідного документа на основі діалогу
- •Контрольні питання
- •Завдання Робота з типовими елементами керування
- •Робота зі списками і комбінованими полями
- •Список літератури
5.14. Побудова кругових діаграм і гістограм
Приклад знаходиться у папці DISK\GDI\GDI12.
Створимо однодокументну програму (на основі SDI) з назвою GDIDiagramm. В клас документа додамо наступні змінні:
class CDiagramDoc : public CDocument
{
.............................................................
public:
double mas[20];
int n;
int kod_graf;
.............................................................
};
Змінна mas буде містити дані масиву, за яким будуть будуватись діаграми. Кількість елементів масиву буде задаватись у змінній n (із 20 елементів масиву для побудови діаграми будуть використані перші n елементів). Якщо змінна kod_graf дорівнюватиме 1 то будемо виводити на екран гістограму, інакше при kod_graf == 2 – кругову діаграму.
У конструкторі класу документа зробимо першу ініціалізацію значень масиву.
CDiagramDoc::CDiagramDoc()
{
kod_graf = 0;
n = 7;
mas[0] = 10.53;
mas[1] = 8.23;
mas[2] = 2.78;
mas[3] = 5.22;
mas[4] = 12.00;
mas[5] = 50.00;
mas[6] = 6.13;
}
Додамо у клас виду CDiagramView масив colorArray для зберігання кольорів, а також 2 функції для малювання діаграм:
class CDiagramView : public CView
{
.............................................................
public:
long colorArray[17];
void Bar(double* mas, int n, CRect& rect, CDC*dc,
int kod=0, int dec=1, int kl=0, long* mascolor=0);
void Sector(double* mas, int n, CRect& rect, CDC*cdc,
long* mascolor=0);
.............................................................
};
Функція для малювання гістограми має наступний вид:
//Гістограма
void CDiagramView::
Bar(double* mas,int n, CRect& rect,CDC*dc, int kod,
int dec,int kl,long *mascolor)
{
int i,kol_sim,h;
double proc,sum,max;
double otstup,ch_st,dop;
double k_ed_h,delta;
int nad,proc_i,proc_imax;
//proc_imax - верхня ціна ділення для цілих чисел
CString dd,str;
CSize pram,pram1,vrr;
CPoint p2;
CRect rr1;
CBrush br;
CBrush* br_st;
sum = 0;
max = mas[0];
//Пошук суми елементів та максимуму
for (i = 0;i < n;i++)
{
if (mas[i] > max)max = mas[i];
sum += mas[i];
}
if(sum == 0)return;
if(kl==1)max=100.*max/sum;
//Формування шаблону відносно кількості цифр
dd.Format("%c0.%df",'%',dec);
str.Format("%0.0f",max);
kol_sim = str.GetLength();
pram = dc->GetTextExtent(str);
otstup = pram.cx+2.*pram.cx/kol_sim;
ch_st = (rect.Width()-otstup)/(2.0*n+1);
srand(48);
h = rect.Height();
//Визначення максимуму типу int для вісі OY
delta = (kl==0)?sum - max:100.-max;
proc_i = (dec==0)?(int)max:(int)ceil(max);
proc_imax = proc_i;
if (fabs(delta) > 0.00001&&proc_i%9)
proc_imax=(proc_i/9+1)*9;
//Кількість пікселів на висоту
k_ed_h = 0.9*h/proc_imax;
dc->SetBkMode(TRANSPARENT);
pram1 = dc->GetTextExtent((LPSTR)"9",1);
br.CreateSolidBrush(RGB(0,0,0));
br_st = dc->SelectObject(&br);
for (i = 0; i < n; i++)
{
proc = (kl==1)?100.0*mas[i]/sum:mas[i];
dop = (proc < max/100.0)? max/100.0 : proc;
CPoint p1((int)(otstup+ ch_st*(2*i+1)+ rect.left),
(int)(0.95*h - dop*k_ed_h + rect.top));
p2 = p1;
p2.Offset((int)ch_st,(int)(dop*k_ed_h));
br.DeleteObject();
if (!mascolor)
br.CreateSolidBrush(RGB(rand()%256,
rand()%256,rand()%256));
else
br.CreateSolidBrush(mascolor[i]);
dc->SelectObject(&br);
rr1.SetRect(p1.x,p1.y,p2.x,p2.y);
dc->Rectangle(&rr1);
str.Format(dd, proc);
vrr = dc->GetTextExtent(str);
nad = vrr.cx+4;
if (nad <= 2* ch_st )//
{
if(p2.y-p1.y>=pram1.cy*1.2&&ch_st>=nad &&kod==0)
dc->DrawText(str,&rr1,
DT_CENTER | DT_VCENTER |DT_SINGLELINE);
else
if(p1.y-rect.top >= 1.2*pram1.cy &&kod==0)
{
rr1.SetRect((int)(p1.x-0.5*ch_st),
(int)(p1.y - 1.2*pram1.cy),
(int)(p2.x+0.5*ch_st),p1.y);
dc->DrawText(str,&rr1,
DT_CENTER | DT_VCENTER |DT_SINGLELINE);
}
} // if
} // for
dc->MoveTo((int)(otstup + rect.left),
(int)(0.05*h + rect.top));
dc->LineTo((int)(otstup + rect.left),
(int)(0.95*h + rect.top));
dc->LineTo((int)(otstup + rect.left +
ch_st*(2*n+1)),(int)(0.95*h + rect.top));
if (fabs(delta) < 0.00001)
{
str.Format("%d", proc_imax);
dc->TextOut(rect.left+ pram.cx/kol_sim,
(int)(rect.top+0.05*h-pram.cy/2),str);
for(i = 1;i <=9; i++)
{
dc->MoveTo((int)(rect.left + otstup),
(int)(0.1*h*(i-1) + rect.top+0.05*h));
dc->LineTo((int)(rect.left+otstup+
0.3*ch_st),(int)(0.1*h*(i-1) + rect.top+0.05*h));
}//end for
}
else
{
for(i = 1;i <=9; i++)
{
dc->MoveTo((int)(rect.left + otstup),
(int)(0.1*h*(i-1) + rect.top+0.05*h));
dc->LineTo((int)(rect.left + otstup+0.3*ch_st),
(int)(0.1*h*(i-1) + rect.top+0.05*h));
if(i==1&&kl==1&&proc_imax>100)continue;
str.Format("%0.0f",proc_imax*
(1.0 -(i-1)*1.0/9));
if((((0.1*h<1.05*pram.cy)+1)&1)||(i&1))
dc->TextOut(rect.left+ pram.cx/kol_sim,
(int)(rect.top+0.1*h*(i-1)+0.05*h-pram.cy/2),str,
strlen(str));
}//end for
}//end if
dc->SelectObject(br_st);
}
Вхідні параметри функції:
double* mas – масив з вхідними даними;
int n – кількість чисел у масиві;
CRect& rect – прямокутна область, де буде відбуватись малювання діаграми;
CDC* dc – контекст вікна для малювання;
int kod – відповідає за виведення підписів: 0 – виведення підписів (за умовчанням); 1 – відсутність виведення підписів;
int dec – кількість знаків у підписах після коми (за умовчанням 0);
int kl – ключ виведення інформації: 0 – виведення значень масиву (за умовчанням); 1 – виведення частки у відсотках (%).
long* mascolor – масив кольорів (за умовчанням 0 – кольори вибираються випадковим чином).
Розглянемо механізм побудови гістограми більш детально. Малювання гістограми відбувається у заданій прямокутній області rect.
На першому етапі треба знайти максимальний елемент масиву, відносно якого буде малюватися гістограма. Крім того, треба знайти суму елементів масиву для виведення частки у відсотках, %. Це робиться у наступному фрагменті коду
sum = 0;
max = mas[0];
//Пошук суми елементів та максимуму
for (i = 0;i < n;i++)
{
if (mas[i] > max)max = mas[i];
sum += mas[i];
}
Далі у змінній dd типу CString формується шаблон для виведення інформації відносно заданої кількості цифр dec.
//Формування шаблону відносно кількості цифр
dd.Format("%c0.%df",'%',dec);
Важливою змінною за допомогої якої розраховується висота стовпчиків є змінна k_ed_h, що характеризує кількість пікселів на одиницю максимального значення.
//Кількість пікселів на висоту
k_ed_h = 0.9*h/proc_imax;
Таким чином максимальний стовпчик буде дорівнювати 0,9*h. Зверху та знизу заданої прямокутної області rect буде відступ 0,05*h для виведення додаткової інформації.
Ширина стовпчика ch_st розраховується за наступною формулою:
ch_st = (rect.Width()-otstup)/(2.0*n+1);
де rect.Width() – ширина клієнтської області вікна; otstup – відступ зліва для виведення вісі OY та відповідної шкали; n – кількість елементів масиву (стовпчиків).
Таким чином, після кожного стовпчика буде відступ, рівний його ширині. Висота стовпця буде розраховуватись наступним чином: mas[i]*k_ed_h. Знаючи висоту та ширину кожного стовпця, не представляє складності побудувати гістограму.
Результат виведення гістограми приведено на рис. 5.31.
а)
б)
Рис. 5.31. Гістограма: а) – підписи не умістились всередині, б) – підписи всередині стовпчиків
Функція для малювання кругової діаграми має вид:
//Кругова діаграма
void CDiagramView::
Sector(double* mas,int n, CRect& rect,CDC*cdc,long*mascolor)
{
int i;
double r,r1,r2,xc,yc,sum,sum_n;
CBrush br;
CBrush *br_st;
srand(48);
//Отримання квадрату з прямокутника
r1=rect.Width()/2.;
r2=rect.Height()/2.;
r = r1 < r2 ? r1 : r2;
rect.InflateRect((int)(r-r1),(int)(r-r2));
cdc->Rectangle(&rect);
////////////////////////////////////////
sum = sum_n = 0;
br.CreateSolidBrush(RGB(0,0,0));
br_st = cdc->SelectObject(&br);
for (i = 0;i < n;i++)
sum += mas[i];
xc = (rect.left+rect.right)/2.0;
yc = (rect.top +rect.bottom)/2.0;
CPoint point1((int)(xc + r),(int)yc);
CPoint point2;
for (i = 0; i < n;i++)
{
sum_n += mas[i];
point2.x = (int)(xc + r*cos(2.*M_PI*sum_n/sum));
point2.y = (int)(yc + r*sin(2.*M_PI*sum_n/sum));
br.DeleteObject();
if (!mascolor)
br.CreateSolidBrush(RGB(rand()%256,
rand()%256,rand()%256));
else
br.CreateSolidBrush(mascolor[i]);
cdc->SelectObject(&br);
if((sum-mas[i])>0.001)
if(point1.x==point2.x||point1.y==point2.y)
{point1.x--;point1.y--;}
if(mas[i]!=0.0)
cdc->Pie(&rect,point2,point1);
point1 = point2;
}
cdc->SelectObject(br_st);
}
Вхідні параметри функції такі як і у попередній функції, але їх менше тому що тут не передбачено підписів значень:
double* mas – масив з вхідними даними;
int n – кількість чисел у масиві;
CRect& rect – прямокутна область, де буде відбуватись малювання діаграми;
CDC* сdc – контекст вікна для малювання;
long* mascolor – масив кольорів (за умовчанням 0 – кольори вибираються випадковим чином).
На першому етапі треба отримати з визначеного прямокутнику квадрат, в який буде вписано кругову діаграму, а також радіус кругу r.
Відомо, що при роботі з GDI у Visual C++ для малювання еліпсу необхідно задати прямокутник, у який він буде вписаний. Отже, для отримання двох радіусів еліпсу відносно заданого прямокутнику напишемо наступні рядки коду:
r1=rect.Width()/2.;
r2=rect.Height()/2.;
Далі необхідно обчислити мінімальний радіус еліпсу, та зменшити відповідну прямокутну область:
r = r1 < r2 ? r1 : r2;
rect.InflateRect((int)(r-r1),(int)(r-r2));
Так за допомогою функції InflateRect у прямокутнику буде зменшено значення більшої сторони до меншої. Якщо r дорівнює r1 – ширина прямокутника не буде змінена, а зміниться тільки висота на різницю r – r2 та навпаки.
Таким чином, отримуємо квадрат, половина сторони якого (радіус вписаного кола) дорівнює r.
Змінні xc, yc будуть координатами центру нашої діаграми. Оскільки у круговій діаграмі необхідно обчислювати відносну долю кожного із значень то нам потрібно буде визначити загальну суму всіх значень ряду. Для знаходження суми цих значень введемо змінну sum. При малюванні секторів попередні значення ряду будуть впливати на розміщення нових секторів. В залежності від цього необхідно обчислювати новий кут, до якого буде малюватись сектор. Для обчислення цього кута введемо змінну sum_n, значення якої буде накопичуватись. При малюванні останнього сектора змінна sum_n має стати рівною sum. У контексті CDC є функція для малювання секторів вона має назву Pie. Також є подібна функція для малювання дуг з назвою Arc, яку ми не використовуємо у цій програмі. Ці дві функції приймають однакові параметри для малювання. Перший параметр це прямокутник у котрий буде вписано фігуру (сектор або дугу), 2-й та 3-й це координати точок, за котрими буде визначено кути для малювання фігури. Слід зауважити, що малювання сектору або дуги відбувається проти годинникової стрілки.
Обчислення координат точки відбувається за формулами:
point2.x = (int)(xc + r*cos(2.*M_PI*sum_n/sum));
point2.y = (int)(yc + r*sin(2.*M_PI*sum_n/sum));
Тут r – радіус діаграми; cos, sin – функції для знаходження косинуса та синуса розрахованого кута; M_PI – число π; sum_n/sum – співвідношення поточного значення до загальної суми, використовується для обчислення кута.
Результат виведення кругової діаграми приведено на рис. 5.32.
Рис. 5.32. Результат виведення кругової діаграми