В поддержку Qt

статьи в помощь разработчику

   

Главная

Статьи:

Выбор инструментов разработки

Установка MinGW

Установка Qt 4

Установка Qt 5

Сборка Qt 5

Установка Qt Creator

Проблемы Qt 4.8.3

Распараллеливание компиляции

Распараллеливание для Qt 5.0.1

Сборка отладчика GDB

Установка библиотеки Qwt

Плагин Qwt

Установка QwtPolar

Установка QwtPplot3D

Изменение палитры цветов QwtPlot3D

Конфигурация сборки по умолчанию

Сборка Qt Creator из исходников

Пример использования QwtPlot

Масштабирование QwtPlot в стиле TChart

Синхронное масштабирование

Пример использования QwtPolar

Пример использования QwtPlot3D

Редактирование QSplitter

Сборка в Ubuntu для Windows

Установка пакетов без интернета

Установка драйвера NVIDIA

Файлы:

Проект примера

Проект с интерфейсом QwtPlotZoomer

Проект с интерфейсом ScrollZoomer

Проект с интерфейсом QwtChartZoom

Проект Delphi с интерфейсом TChart

 

Главная > Пример использования QwtPlot

 

Пример использования виджета QwtPlot из библиотеки Qwt

Оформление графика

Рассмотрим пример создания и оформления графика с использованием библитеки Qwt. Проект будем разрабатывать с помощью Qt Creator, постараемся обеспечить возможность его сборки как на разных платформах (Ubuntu или Windows), так и с разной версией библиотеки Qwt. Для Вашего удобства готовый проект доступен по ссылке.

Для определения подключаемой к проекту библиотеки используем файл, содержимое которого приведено ниже

файл "qwt.pri"

#-------------------------------------------------
#
# QwtLibrary configuration file
#
#-------------------------------------------------


# QWT_VER = 5.2.3
QWT_VER = 6.0.2

contains(QWT_VER,^5\\..*\\..*) {
  VER_SFX = 5
  UNIX_SFX = -qt4
} else {
  VER_SFX =
  UNIX_SFX =
}

unix {
  QWT_PATH = /usr
  QWT_INC_PATH = $${QWT_PATH}/include/qwt$${UNIX_SFX}
  QWT_LIB = qwt$${UNIX_SFX}
}

win32 {
  win32-x-g++ {
    QWT_PATH = /usr/qwt$${VER_SFX}-win
  } else {
    QWT_PATH = C:/Qt/qwt-$${QWT_VER}
  }
  QWT_INC_PATH = $${QWT_PATH}/include
  CONFIG(debug,debug|release) {
    DEBUG_SFX = d
  } else {
    DEBUG_SFX =
  }
  QWT_LIB = qwt$${DEBUG_SFX}$${VER_SFX}
}

INCLUDEPATH += $${QWT_INC_PATH}
LIBS += -L$${QWT_PATH}/lib -l$${QWT_LIB}

Данный файл обеспечивают включение в проект следующих вариантов директив

# Платформа Windows  [Переключить]
# Версия библиотеки Qwt 6.0.2  [Переключить]
INCLUDEPATH += C:/Qt/qwt-6.0.2/include
# Конфигурация сборки Выпуск (Release)  [Переключить]
LIBS += -LC:/Qt/qwt-6.0.2/lib -lqwt

Замечания. Версии библиотеки актуальны на момент написания статьи. Пути к заголовочным и библиотечным файлам соответствуют рекомендациям по установке библиотеки Qwt, описанным в посвященной этому вопросу статье. Для того чтобы выполнить сборку проекта с другой версией библиотеки, достаточно изменить значение переменной QWT_VER. Файл"qwt.pri" предусматривают и такой экзотический случай, как сборка проекта в Ubuntu для Windows (см. статью), а конкретнее, в этом случае в файл проекта будут включены варианты директив

# Версия библиотеки Qwt 6.x.x  [Переключить]
INCLUDEPATH += /usr/qwt-win/include
# Конфигурация сборки Выпуск (Release)  [Переключить]
LIBS += -L/usr/qwt-win/lib -lqwt

Создадим в Qt Creator проект с именем demo_qwt. В дизайнере откроем форму "mainwindow.ui", удалим из нее главное меню menuBar, панель инструментов mainToolBar и панель состояния statusBar, так как они не будут использоваться в данном приложении. Поместим на форму виджет QwtPlot и присвоим ему имя myPlot. Активируем компоновщик Скомпоновать по сетке, который заставит график занять все свободное пространство формы. Подберем размер формы, подходящий для отображения графика. В результате получим форму, изображенную на рисунке. Элемент управления компоновщиком и индикатор его активного состояния в инспекторе объектов отмечены на рисунке символом "К".

Откроем файл проекта, его содержимое сгенерировано автоматически при создании проекта.

файл "demo_qwt.pro"

#-------------------------------------------------
#
# Project created by QtCreator 2012-07-15T23:40:54
#
#-------------------------------------------------

QT += core gui

greaterThan(QT_MAJOR_VERSION, 4): QT += widgets

TARGET = demo_qwt
TEMPLATE = app


SOURCES += main.cpp\
  mainwindow.cpp

HEADERS += mainwindow.h

FORMS += mainwindow.ui


# Директива, определяющая библиотеку  [Показать]

Поскольку форма нашего проекта содержит виджет QwtPlot, то для успешной сборки в файле проекта следует прописать директивы, определяющие библиотеку Qwt

include(qwt.pri)

Кроме того, в папку с проектом необходимо поместить файл "qwt.pri", содержимое которого приведено выше.

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

Заглянем исходники проекта

файл "mainwindow.h"

#ifndef MAINWINDOW_H
#define MAINWINDOW_H

#include <QMainWindow>

// Дополнительные директивы #include  [Показать]

namespace Ui {
  class MainWindow;
}

class MainWindow : public QMainWindow {
  Q_OBJECT

public:
  MainWindow(QWidget *parent = 0);
  ~MainWindow();

private:
  Ui::MainWindow *ui;

// Указатели на элементы графика  [Показать]
};

#endif // MAINWINDOW_H

файл "mainwindow.cpp"

#include "mainwindow.h"
#include "ui_mainwindow.h"

MainWindow::MainWindow(QWidget *parent) :
  QMainWindow(parent),
  ui(new Ui::MainWindow)
{
  ui->setupUi(this);

// Инструкции оформления графика  [Показать]
}

MainWindow::~MainWindow()
{
// Инструкции удаления элементов графика  [Показать]

  delete ui;
}

Поместим в заголовочный файл "mainwindow.h" директивы #include, которые нам потребуются

#include <qwt_plot_canvas.h>
#include <qwt_legend.h>
#include <qwt_plot_grid.h>
#include <qwt_plot_curve.h>
#include <qwt_symbol.h>

В секции private класса MainWindow объявляем указатели на используемые элементы графика

QwtLegend *leg;
QwtPlotGrid *grid;
QwtPlotCurve *curv1,*curv2;

Построение графика будем производить в кострукторе главного окна приложения (в файле "mainwindow.cpp"). Большинство элементов графика создается командой new. По завершении работы приложения они должны быть удалены командой delete. Поэтому мы и сделали указатели на эти элементы членами класса MainWindow, чтобы иметь доступ кним в деструкторе.

Первым делом устанавливаем заголовок графика (элемент "1" на рисунке)

// Платформа Windows  [Переключить]
ui->myPlot->setTitle(QString::fromLocal8Bit("Зависимость U(t)"));

Создаем легенду – расшифровку отображаемых кривых по цветам (элемент "2" на рисунке), запрещаем редактировать легенду, добавляем ее в верхнюю часть графика.

leg = new QwtLegend();
leg->setItemMode(QwtLegend::ReadOnlyItem);
ui->myPlot->insertLegend(leg,QwtPlot::TopLegend);

Создаем сетку (элемент "3" на рисунке). Разрешаем отображение линий сетки, соответствующих вспомогательным делениям нижней шкалы. Назначаем цвета для линий сетки: черный для основных делений и серый для вспомогательных. Линии сетки будут изображаться пунктирной линией. Связываем созданную сетку с графиком.

grid = new QwtPlotGrid;
grid->enableXMin(true);
grid->setMajPen(QPen(Qt::black,0,Qt::DotLine));
grid->setMinPen(QPen(Qt::gray,0,Qt::DotLine));
grid->attach(ui->myPlot);

Устанавливаем наименование нижней шкалы, минимальную и максимальную границы для нее (элементы "4", "5" на рисунке). Аналогичные установки делаем для левой шкалы (элементы "6", "7" на рисунке).

// Платформа Windows  [Переключить]
ui->myPlot->setAxisTitle(QwtPlot::xBottom,QString::fromLocal8Bit("t, мкс"));
ui->myPlot->setAxisScale(QwtPlot::xBottom,-0.25,8.25);
ui->myPlot->setAxisTitle(QwtPlot::yLeft,QString::fromLocal8Bit("U, В"));
ui->myPlot->setAxisScale(QwtPlot::yLeft,-1.25,1.25);

Создаем первую кривую (элемент "8" на рисунке) с наименованием "U1(t)", разрешаем для нее сглаживание при прорисовке, поскольку предполагается, что точек на этой кривой не будет очень много, назначаем цвет прорисовки – красный.

curv1 = new QwtPlotCurve(QString("U1(t)"));
curv1->setRenderHint(QwtPlotItem::RenderAntialiased);
curv1->setPen(QPen(Qt::red));

На первой кривой каждую точку будем отмечать маркером. Для этого создаем экземпляр класса QwtSymbol, назначаем маркерам стиль – эллипс, цвет прорисовки – черный, размер – 4 и прикрепляем его к кривой.

QwtSymbol *symbol1 = new QwtSymbol();
symbol1->setStyle(QwtSymbol::Ellipse);
symbol1->setPen(QColor(Qt::black));
symbol1->setSize(4);
curv1->setSymbol(symbol1);

Если у Вас установлена библиотека Qwt версии 5.x.x, то приведенный выше код должен выглядеть несколько иначе из-за внесенных разработчиком изменений

QwtSymbol symbol1;
symbol1.setStyle(QwtSymbol::Ellipse);
symbol1.setPen(QColor(Qt::black));
symbol1.setSize(5);
curv1->setSymbol(symbol1);

Создаем вторую кривую (элемент "9" на рисунке) с наименованием "U2(t)". Сглаживание при прорисовке для нее не разрешаем, поскольку предполагается, что количество точек на этой кривой будет большим, и график может "притормаживать". Назначаем цвет прорисовки – темно-зеленый.

curv2 = new QwtPlotCurve(QString("U2(t)"));
curv2->setPen(QPen(Qt::darkGreen));

Опять же, выделять точки на второй кривой не имеет смысла из-за большого их количества.

Теперь займемся подготовкой данных для кривых, изображаемых на графике. Первая кривая будет содержать 128 точек, вторая – 262144. Поскольку частота синусоиды второй кривой больше, чем первой, то и точек на ней должно быть больше.

const int N1 = 128;
const int N2 = 262144;

Объявляем массивы данных: X1 и Y1 для первой кривой, X2 и Y2 для второй. Выделяем блок памяти под размещение данных (общий для всех данных), массивы в нем располагаются последовательно так, как показано на рисунке.

double *X1 = (double *)malloc((2*N1+2*N2)*sizeof(double));
double *Y1 = X1 + N1;
double *X2 = Y1 + N1;
double *Y2 = X2 + N2;

Вычисляем массивы данных для кривых 1 и 2. X принимает значения от 0 до 8, шаг приращения по X зависит от количества точек на кривой. Y(X) – синусоида с заданными амплитудой, частотой и начальной фазой (индивидуальные для каждой кривой).

double h = 8./(N1-1);
for (int k = 0; k < N1; k++)
{
  X1[k] = k*h;
  Y1[k] = cos(M_PI*X1[k]-5*M_PI/12);
}
h = 8./(N2-1);
for (int k = 0; k < N2; k++)
{
  X2[k] = k*h;
  Y2[k] = 0.7 * cos(8*M_PI*X2[k]+M_PI/9);
}

Далее, передаем кривым подготовленные данные

curv1->setSamples(X1,Y1,N1);
curv2->setSamples(X2,Y2,N2);

В библиотеке Qwt версии 5.x.x для этих целей предусмотрена другая функция

curv1->setData(X1,Y1,N1);
curv2->setData(X2,Y2,N2);

Освобождаем выделенную память

free((void *)X1);

Помещаем кривые на график

curv1->attach(ui->myPlot);
curv2->attach(ui->myPlot);

Перестраиваем график

ui->myPlot->replot();

В заключение конструктора назначаем тип курсора для канвы графика (значение по умолчанию Qt::CrossCursor)

ui->myPlot->canvas()->setCursor(Qt::ArrowCursor);

Осталось вставить в деструктор команды удаления объектов, созданных в конструкторе команодой new

delete leg;
delete grid;
delete curv1;
delete curv2;

Наконец, можем выполнить сборку проекта и запустить приложение.

Замечание.

В Ubuntu вместо функции

QString::fromLocal8Bit()

следует использовать функцию

QString::fromUtf8()

Масштабирование и перемещение графика

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

Для того чтобы подключить данный интерфейс к графику из приведенного выше примера, необходимо

1. В заголовочный файл "mainwindow.h" включить дополнительную директиву #include

#include <qwt_plot_zoomer.h>

2. В секции private объявить указатель на менеджер масштабирования графика

QwtPlotZoomer *zoom;

3. В конструкторе главного окна приложения (файл "mainwindow.cpp") создать экземпляр класса QwtPlotZoomer, и, если надо, переопределить цвет каймы выделяемой области, задающей новый масштаб графика

zoom = new QwtPlotZoomer(ui->myPlot->canvas());
zoom->setRubberBandPen(QPen(Qt::white));

4. В деструктор вставить команду удаления еще одного объекта

delete zoom;

Готовый проект доступен для скачивания по ссылке. После сборки проекта и запуска приложения можно наблюдать работу интерфейса в действии.

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

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

Замечание. При использовании Qwt 5.x.x наблюдается различное поведение интерфейса в зависимоти от платформы, для которой собрано приложение. В Ubuntu выделение новых границ графика осуществляется до тех пор, пока нажата левая кнопка мыши. При отпускании кнопки график перерисовывается в новом масштабе. В Windows при нажатии и отпускании левой кнопки мыши включается режим выделения. Изменение размеров выделяемой области продолжается при отпущенной кнопке мыши. Для завершения режима выделения и перерисовки графика в новых границах необходимо кликнуть левую кнопку мыши еще раз.

В стандартном интерфейсе масштабирования QwtPlotZoomer не хватает возможности перемещать график. Бывает, что возникает необходимость перемещаться вдоль кривой графика, просматривая ее отдельные участки в увеличенном масштабе. Для реализации такой возможности можно воспользоваться средствами из примера realtime, поставляемого вместе с библиотекой Qwt.

Рассмотрим подробнее действия, выпоняемые при подключении интерфейса к нашему примеру.

1. Находим в исходниках библиотеки Qwt 6.x.x в папке "examples" проект realtime и копируем в папку со своим проектом файлы

"scrollbar.h"
"scrollzoomer.h"
"scrollbar.cpp"
"scrollzoomer.cpp"

Для того, чтобы проект мог быть собран и с версией 5.x.x библиотеки Qwt, вносим испраления в файл "scrollzoomer.cpp". Находим в нем строки

if ( o == Qt::Horizontal )
  moveTo( QPointF( min, zoomRect().top() ) );
else
  moveTo( QPointF( zoomRect().left(), min ) );

и заменяем их следующими

#if QWT_VERSION < 0x060000
  if ( o == Qt::Horizontal )
    move(min, zoomRect().top());
  else
    move(zoomRect().left(), min);
#else
  if ( o == Qt::Horizontal )
    moveTo( QPointF( min, zoomRect().top() ) );
  else
    moveTo( QPointF( zoomRect().left(), min ) );
#endif

2. Добавляем скопированные файлы в свой проект, а именно, в файле проекта сразу после директивы "include(qwt.pri)" должно быть записано

SOURCES += scrollbar.cpp\
  scrollzoomer.cpp

HEADERS += scrollbar.h\
  scrollzoomer.h

Дальнейшие действия анологичны тем, что выполнялись при подключении стандартного интерфейса масштабирования графика QwtPlotZoomer

3. В заголовочный файл "mainwindow.h" включаем дополнительную директиву #include

#include "scrollzoomer.h"

4. В секции private объявляем указатель на менеджер масштабирования и перемещения графика

ScrollZoomer *zoom;

5. В конструкторе главного окна приложения (файл "mainwindow.cpp") создаем экземпляр класса ScrollZoomer, и, если необходимо, переопределяем цвет каймы выделяемой области, задающей новый масштаб графика

zoom = new ScrollZoomer(ui->myPlot->canvas());
zoom->setRubberBandPen(QPen(Qt::white));

6. В деструктор вставляем команду удаления объекта

delete zoom;

Готовый проект, содержащий подключенный интерфейс ScrollZoomer, можно взять по ссылке. Масштабирование графика ничем не отличается от стандартного интерфейса, но после перерисовки графика в новом масштабе на канве появляются полосы прокрутки, с помощью которых можно двигать график. Результат увеличения масштаба можно увидеть на рисунке. При возвращении графика к исходным размерам нажатием на правую кнопку мыши, полосы прокрутки исчезают.

При использовании Qwt 5.x.x в работе интерфейса можно заметить мелкие огрехи. Например, после перерисовки графика в увеличенном масштабе линии сетки не совпадают с соответствующими делениями шкалы, в чем можно убедиться, взглянув на рисунок. Расхождение исправляется, если подвигать полосы прокрутки или изменить размер окна, содержащего график. Аналогичная рассинхронизация сетки и шкалы наблюдается при возвращении графика к исходному масштабу. Кроме того, область канвы, отведенная под полосы прокрутки, остается незаполненной после их исчезновения.

В наличии недостатков признаются и сами разработчики. В файле "README" проекта с примером realtime написано следующее

"...There are a couple of reasons why the implementation is a hack and therefore the class is not part of the Qwt lib, but it should be working with all types of QwtPlots..."

Т.е. фактически здесь сказано, что в реализации класса имеется дефект, поэтому он не включен в состав библиотеки Qwt, а поставляется в виде файлов из примера.

На мой взгляд, неудачна сама идея двигать график с помощью полос прокрутки, ведь они позволяют делать это только в горизонтальном и вертикальном направлении, а иногда хочется перемещать график по диагонали. На много удобнее интерфейс масштабирования и перемещения графика у компонента TChart из библиотеки компонентов Delphi и C++Builder. Реализация подобного интерфейса описана в статье "Интерфейс масштабирования и перемещения графика QwtPlot в стиле TChart из библиотеки компонентов Delphi", готовый проект рассматриваемого примера с подключенным интерфейсом в стиле TChart доступен по ссылке, а для сравнения результатов приведен проект Delphi с аналогичным графиком. Также может представлять интерес статья на перекликающуюся тему Синхронное масштабирование графиков.

Надеюсь, сведения, изложенные в данной публикации, будут Вам полезны.

 

При подготовке материала данной статьи использовались ресурсы "Библиотека QWT. Простое приложение, использующее QWT. (Урок 1)" и "Библиотека QWT. Добавляем QwtSymbol, QwtPlotPicker, Zoomer. (Урок 2)".