Лет пятнадцать назад потребовалось мне в программе для диплома отобразить график. Была бы программа на Builder или Delphi, всё было бы ничего, но только писал я для Windows на MFC, а там с классами графиков как-то не очень. И написал я тогда собственный модуль построения графиков. Три пятилетки прошло, а модуль остался, был переработан и я его иногда использую в своих поделках в QNX, Linux и Windows. Быть может, он пригодится чем-либо и вам.

Представляемый модуль рисования графиков позволяет отображать произвольное количество цветных графиков с легендой. Количество точек в графиках может составлять десятки миллионов и больше (лишь бы памяти хватало) без особого снижения быстродействия на перерисовку, так как при каждом изменении масштаба производится построение набора точек, отображаемого на экране с учётом масштаба. Таким образом, количество точек влияет только на время пересчёта этих отображаемых точек при изменении масштаба.

Функции растеризации вынесены в подключаемые классы. Всего возможно на текущий момент три варианта: рисование штатными функциями Windows-GUI из MFC (класс CVideo_Windows), рисование штатными функциями Qt (класс CVideo_Qt) и программная растеризация (класс CVideo_Software — с доработкой этот модуль можно использовать на микроконтроллерах). Перекодировку символов в требуемый для классов растеризации формат осуществляет класс CTranslator.

В отдельные классы вынесены типы линий (если тип поддерживается классом растеризации), цвета, точки, прямоугольники.

Зажав левую кнопку мышки можно перемещаться по полю графиков. Правой кнопкой можно задать выделенную область, сделав щелчок на которой правой кнопкой мышки вы вернётесь в заданный изначальный масштаб, а после щелчка левой кнопкой выполнится операция увеличения.

Выглядят нарисованные графики вот так:



Создание класса графика, например, для Windows в MFC выполняется следующим образом:

 CWnd_Graphics cWnd_Graphics;//класс графиков
 CGrData *cGrData_SinPtr;//указатель на данные графика синуса
 CGrData *cGrData_CosPtr;//указатель на данные графика косинуса

//создаём график
CRect cRect_Client;
((CStatic*)GetDlgItem(IDC_STATIC_MAIN_MAP))->GetClientRect(&cRect_Client);
cWnd_Graphics.Create(WS_VISIBLE,cRect_Client,(CStatic*)GetDlgItem(IDC_STATIC_MAIN_MAP));
 	
 //настраиваем график
 cWnd_Graphics.GetIGraphicsPtr()->SetBackGroundColor(CGrColor(192,192,192));
 cWnd_Graphics.GetIGraphicsPtr()->SetLegendBackGroundColor(CGrColor(230,230,230));
 cWnd_Graphics.GetIGraphicsPtr()->SetAxisColor(CGrColor(0,0,0),CGrColor(0,0,0));
 cWnd_Graphics.GetIGraphicsPtr()->SetTextValueColor(CGrColor(0,0,0),CGrColor(0,0,0),CGrColor(0,0,0));
 cWnd_Graphics.GetIGraphicsPtr()->SetSelectRectangleColor(CGrColor(0,0,255)); 
 cWnd_Graphics.GetIGraphicsPtr()->SetName("Графики функций");
 //создаём графики
 cGrData_SinPtr=cWnd_Graphics.GetIGraphicsPtr()->AddNewGraphic();
 cGrData_SinPtr->SetEnable(true);
 cGrData_SinPtr->SetGrColor(CGrColor(255,0,0));
 cGrData_SinPtr->SetGrLineStyle(CGrLineStyle(IVideo::LINE_TYPE_SOLID,1,false,false));
 cGrData_SinPtr->SetName("График синуса");
 
 for(size_t n=0;n<1024;n++)
 {
  double x=n;
  double y=sin(x*0.01);
  cGrData_SinPtr->AddPoint(x,y);
 }

 cGrData_CosPtr=cWnd_Graphics.GetIGraphicsPtr()->AddNewGraphic();
 cGrData_CosPtr->SetEnable(true);
 cGrData_CosPtr->SetGrColor(CGrColor(0,0,255));
 cGrData_CosPtr->SetGrLineStyle(CGrLineStyle(IVideo::LINE_TYPE_SOLID,3,false,false));
 cGrData_CosPtr->SetName("График косинуса");

 for(size_t n=0;n<1024;n++)
 {
  double x=n;
  double y=cos(x*0.01);
  cGrData_CosPtr->AddPoint(x,y);
 }

 //приводим масштаб, чтобы отображались все графики
 CGrRect cGrRect;
 cWnd_Graphics.GetIGraphicsPtr()->FindViewRectangle(cGrRect);
 cWnd_Graphics.GetIGraphicsPtr()->SetRectangle(cGrRect);
 cWnd_Graphics.GetIGraphicsPtr()->OnMagnify();
 cWnd_Graphics.GetIGraphicsPtr()->GetRectangle(cGrRect);
 cWnd_Graphics.GetIGraphicsPtr()->SetOriginalScale(cGrRect);

Здесь класс cWnd_Graphics обеспечивает связку класса графиков CGraphics с Windows, перенаправляя в класс CGraphics события, возникающие в Windows и обеспечивая отображение графика в событии перерисовки ON_WM_PAINT. Для других ОС эту связку потребуется переписать, с учётом используемой ОС. В данном примере через cWnd_Graphics.GetIGraphicsPtr() можно непосредственно обратиться к классу графиков CGraphics и настроить параметры отображения графиков, а так же попросить класс графиков создать новый график и вернуть на него указатель AddNewGraphic (будет получен указатель на класс CGrData). Удалять этот указатель самостоятельно нельзя – график можно удалить только через функцию DeleteGraphic. В дальнейшем работа с графиком выполняется через полученный указатель.

Всего доступны следующие функции управления графиками:

CGrData* AddNewGraphic(void);//добавить график и получить указатель на созданный график
  void DeleteAllGraphics(void);//удалить все графики из памяти
  void DeleteGraphic(CGrData *cGrDataPtr);//удалить график из памяти
  void FindRectangle(CGrRect &cGrRect) const;//найти описывающий прямоугольник всех активных графиков
  void FindRectangleOfEndPoints(CGrRect &cGrRect,size_t points) const;//найти описывающий прямоугольник всех активных графиков за последние points точек
  void FindRectangleOfEndTime(CGrRect &cGrRect,long double time) const;//найти описывающий прямоугольник всех активных графиков за последние time время
  void FindViewRectangle(CGrRect &cGrRect) const;//найти описывающий прямоугольник всех активных графиков с запасом по высоте
  void FindViewRectangleOfEndPoints(CGrRect &cGrRect,size_t points) const;//найти описывающий прямоугольник всех активных графиков с запасом по высоте за последние points точек
  void FindViewRectangleOfEndTime(CGrRect &cGrRect,long double time) const;//найти описывающий прямоугольник всех активных графиков с запасом по высоте за последнее time время
  void SetTimeDivider(double value);//установить временной делитель
  double GetTimeDivider(void) const;//получить временной делитель
  //функции отображения
  void CancelSelect(void);//убрать выделение
  void Redraw(void);//перерисовать изображение
  void RedrawAll(void);//перерисовать всё
  void OnOriginalScale(void);//перейти в режим оригинального масштаба
  //функции цветовых настроек
  void SetBackGroundColor(const CGrColor &cGrColor);//задать цвет фона
  void SetLegendBackGroundColor(const CGrColor &cGrColor);//задать цвет фона легенды
  void SetAxisColor(const CGrColor &cGrColor_AxisX,const CGrColor &cGrColor_AxisY);//задать цвет осей
  void SetGridColor(const CGrColor &cGrColor_GridX,const CGrColor &cGrColor_GridY);//задать цвет сетки
  void SetSelectRectangleColor(const CGrColor &cGrColor);//задать цвет прямоугольника выделения
  void SetTextValueColor(const CGrColor &cGrColor_TextX,const CGrColor &cGrColor_TextY,const CGrColor &cGrColor_TextLegend);//задать цвет текста
  //функции настройки стиля линий
  void SetAxisLineStyle(const CGrLineStyle &cGrLineStyle_AxisX,const CGrLineStyle &cGrLineStyle_AxisY);//задать стиль осей
  void SetGridLineStyle(const CGrLineStyle &cGrLineStyle_GridX,const CGrLineStyle &cGrLineStyle_GridY);//задать стиль сетки
  //функции настройки системы координат
  void SetRectangle(const CGrRect &cGrRect);//задать прямоугольник системы координат
  void SetGridStep(long double step_x,long double step_y);//задать шаг сетки
  void GetRectangle(CGrRect &cGrRect) const;//получить прямоугольник системы координат
  void GetGridSize(long double &step_x,long double &step_y) const;//получить оптимальный шаг сетки
  //функции дополнительных настроек
  void SetEnableMagnify(bool enable);//разрешить увеличение
  void SetEnableValue(bool x_value,bool y_value);//разрешить отображение значений
  void SetOriginalScale(const CGrRect &cGrRect);//задать оригинальный масштаб
  void SetMoveMode(bool inversion);//задать режим инверсии мышки
  bool GetSelectedRectangle(CGrRect &cGrRect) const;//вернуть прямоугольник выделения
  void GetClientRectangle(CGrRect &cGrRect) const;//вернуть прямоугольник графика
  void SetName(const std::string &name);//установить название графика
  bool GetUserMode(void) const;//получить режим управления пользователем
  void SetUserMode(bool state);//установить режим управления пользователем

В принципе, настроить отображение можно достаточно гибко.

Режим управления пользователем, указанный в последних двух функциях, используется при выводе данных с обновлением (скажем, прибор добавляет точки с некоторой частотой). В этом случае при попытке перемещения/увеличения графика включается режим управления пользователем, а при возврате в исходный масштаб этот режим отключается. Это позволяет остановить изменение масштаба во время добавления данных (если режим включён, то просто не надо вызывать пересчёт масштаба, как в примере выше).

Пример программы, использующей этот модуль (там и исходники модуля).
От проекта там 4 файла — main.cpp, cdialog_main.h, cdialog_main.cpp и stdafx.h. В этих четырёх файлах и находится пример подключения модуля построения графиков. Все остальные файлы исходников и есть части модуля рисования графиков.

Ну вот, собственно, и всё, что можно сказать об этой примитивной поделке.
P.S. Мне тут подсказали, что я неудачно назвал функции (Graph -график, а Graphic — графика). Извиняюсь, я немецкий язык учил и счёл, что по-английски будет вот так. :)