Qt: строим графики без QWT
Базовый комплект библиотек QT не содержит удобных средств для построения графиков и диаграмм, похожих на Chart из Studio. Тем не менее, для решения задачи вывода графика в QT можно использовать, как минимум, 3 подхода:
1. Написать всё самому, это не так сложно, как кажется. Если сделать класс-потомок QWidget
, включить в его заголовочный файл widget.h
прототип обработчика события paintEvent
protected: void paintEvent(QPaintEvent *);
и вверху widget.h
заинклудить включаемый файл QPainter
,
#include <QWidget> #include <QPainter>
то в коде файла widget.cpp
можно добавить свой обработчик события перерисовки окна виджета, например, такой:
void Widget::paintEvent(QPaintEvent *) { QPainter p(this); // Создаём новый объект рисовальщика p.setPen(QPen(Qt::red,1,Qt::SolidLine)); // Настройки рисования p.drawLine(0,0,width(),height()); // Рисование линии }
Здесь мы просто рисуем красную линию из левого верхнего угла окна в правый нижний. Добавьте сотню-другую строк с расчётами - и получите график :)
Скачать архив .zip с этим примером (проект QT5) (2 Кб)
2. Можно скачать и настроить QWT - дополнительную библиотеку для графического представления числовых данных. Тогда задачу можно решить на её основе, правда, опять повозитесь с путями :)
3. Наконец, мы можем использовать альтернативные решения, которые не потребуют работы с настройками qwt или кропотливого "ручного" программирования классов. Поможет нам маленький open-source класс QCustomPlot.
Скачав исходники по ссылке (QCustomPlot-source.tar.gz
), разверните архив, из него нужны всего 2 файла: qcustomplot.h
и qcustomplot.cpp
. Проще всего создать новый проект-виджет на основе базового класса QMainWindow
и добавить туда оба файла (проверьте, что они появились в списках "Заголовочные" и "Исходники" диспетчера проекта).
Теперь идем в настройки проекта (файл .pro
). Там просто нужно добавить строку
QT += printsupport
Теперь самое интересное. Если мы создали приложение на основе базового класса QMainWindow
, у нас уже есть главное окно приложения (файлы mainwindow.*
) со встроенным меню, кнопочным тулбаром, строкой статуса и основной областью окна, представляющей собой объект класса QWidget
с именем centralWidget
. Всё это можно увидеть в списке объектов и классов, переключившись в режим дизайна формы (откройте список Формы, файл mainwindow.ui
). Поэтому мы не будем делать новый виджет, в котором отобразится график, а используем для этого centralWidget
. Щёлкнув правой кнопкой мыши на объекте centralWidget
, поменяем ему тип на QCustomPlot
:
Смена класса виджета в QT5, шаг 1
Смена класса виджета в QT5, шаг 2
На втором рисунке не забудьте нажать "Добавить" и "Преобразовать", проверьте в списке с первого рисунка, что класс виждета действительно изменился. Приложение можно собрать, при запуске мы увидим сетку и оси координат будущего графика.
Теперь нарисуем что-нибудь реальное, например, синусоиду.
В mainwindow.h
добавим в описание класса меню и пункт для него, а также слот для обработки события "щелчок по пункту меню":
private: QMenu * fileMenu; QAction * newAct; private slots: void newFile(void);
В mainwindow.cpp
подключим файл math.h
, чтобы получить доступ к функции синуса, а также в конструкторе окна создадим элементы интерфейса и подключим слот, начало файла получится таким:
#include <math.h> #include "mainwindow.h" #include "ui_mainwindow.h" MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent), ui(new Ui::MainWindow) { ui->setupUi(this); newAct = new QAction(tr("&Новый"), this); fileMenu = this->menuBar()->addMenu(tr("&Файл")); fileMenu->addAction(newAct); connect(newAct, SIGNAL(triggered()), this, SLOT(newFile())); }
Всё остальное будем делать по выбору пункта меню Файл - Новый, то есть, внутри добавленной в класс функции:
void MainWindow::newFile() { //...вот здесь }
Всю эту работу по созданию пункта меню можно было сделать и в режиме дизайна: откройте редактор действий и правой кнопкой мыши выберите "Перейти к слоту..." - triggered(), в файле mainwindow.cpp создастся соответствующий обработчик события:void MainWindow::on_action_triggered() { }в котором и пишется дальнейший код.
Код ниже показывает, как легко составить таблицу данных и добавить её на график:
//1. Подготовить данные x,y: const int size = 360; QVector <double> x(size),y(size); for (int i=0; i<size; i++) { x[i] = i; y[i] = sin(qDegreesToRadians(x[i])); } //2. Создать график и добавить данные: ui->centralWidget->addGraph(); ui->centralWidget->graph(0)->setData(x,y); //3. Настроить оси и подписи: ui->centralWidget->xAxis->setLabel("Ось x"); ui->centralWidget->xAxis->setRange(0,size); //1 ui->centralWidget->yAxis->setLabel("f(x)"); ui->centralWidget->yAxis->setRange(-1,1); //4. Показать всё это: ui->centralWidget->replot();
Любой из шагов можно улучшить, например, сделать подписи оси x с нужным нам шагом (код после оператора //1
):
QVector<double> Ticks; // вектор с шагом в 30 градусов for (int i=0; i<=size; i+=30) Ticks << i; ui->centralWidget->xAxis->setAutoTicks(false); // выключаем автоматические отсчеты ui->centralWidget->xAxis->setTickVector(Ticks); // задаем созданный нами вектор
или даже в долях числа Пи (предполагается, что кодировка проекта - Юникод):
//Оси в долях числа Пи QVector <QString> Labels; for (int i=0; i<=size; i+=size/9) Labels << ClipFraction(i,size/2) + QChar(960); ui->centralWidget->xAxis->setAutoTickLabels(false); ui->centralWidget->xAxis->setTickVectorLabels(Labels);
Здесь служебная функция ClipFraction
занимается сокращением дроби и конвертированием её в QString
:
int GetNod(int ch,int zn) { //Найти НОД return (ch ? GetNod(zn%ch,ch) : zn); } QString ClipFraction (int ch,int zn) { //Сократить, если есть НОД>1 int nod = GetNod(ch,zn); if (nod>1) { ch/=nod; zn/=nod; } QString result; if (ch==0) result.setNum(0); else if (ch==zn) result.setNum(1); else if (zn==1) result.setNum(ch); else result = QString("%1/%2").arg(ch).arg(zn); return result; }
Вот что получилось, при этом график нормально перерисовывается при масштабировании окна (так как класс контролирует обработку события перерисовки):
Окно виджета построения графика на QT без QWT
Скачать этот проект QT5 в архиве .zip (187 Кб)
15.05.2014, 23:14 [37411 просмотров]