В поддержку 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

Файлы:

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

Проект без массивов

 

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

 

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

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

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

файл "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}

файл "qwtpolar.pri"

#-------------------------------------------------
#
# QwtPolarLibrary configuration file
#
#-------------------------------------------------


POLAR_VER = 1.0.1

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

unix {
  POLAR_PATH = /usr
  POLAR_INC_PATH = $${POLAR_PATH}/include/qwtpolar$${UNIX_SFX}
  POLAR_LIB = qwtpolar$${UNIX_SFX}
}

win32 {
  win32-x-g++ {
    POLAR_PATH = /usr/qwtpolar$${VER_SFX}-win
  } else {
    POLAR_PATH = C:/Qt/qwtpolar-$${POLAR_VER}
  }
  POLAR_INC_PATH = $${POLAR_PATH}/include
  CONFIG(debug,debug|release) {
    DEBUG_SFX = d
  } else {
    DEBUG_SFX =
  }
  POLAR_LIB = qwtpolar$${DEBUG_SFX}$${VER_SFX}
}

INCLUDEPATH += $${POLAR_INC_PATH}
LIBS += -L$${POLAR_PATH}/lib -l$${POLAR_LIB}

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

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

Замечания. Версии библиотек актуальны на момент написания статьи. Пути к заголовочным и библиотечным файлам соответствуют рекомендациям по установке библиотек, описанным в посвященных этому вопросу статьях (здесь и здесь). С Qwt 5.x.x совместима только QwtPolar 0.1.0, а с Qwt 6.x.x – QwtPolar 1.0.1 и предположительно последующие версии. Для того чтобы выполнить сборку проекта с другими версиями библиотек, достаточно изменить значения переменных QWT_VER и POLAR_VER. Файлы "qwt.pri" и "qwtpolar.pri" предусматривают и такой редкий случай, как сборка проекта в Ubuntu для Windows (см. статью), а конкретнее, в этом случае в файл проекта будут включены варианты директив

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

Создадим в Qt Creator проект с именем demo_qwtpolar. В дизайнере откроем форму "mainwindow.ui", удалим из нее главное меню menuBar, панель инструментов mainToolBar и панель состояния statusBar, так как они не будут использоваться в данном приложении. Установим размеры формы 480 x 480. Поместим на нее виджет QwtPolarPlot и присвом ему имя myPlot. Активируем для центрального виджета компоновщик Скомпоновать по сетке, который заставит график занять все свободное пространство формы. Установим равными "3" свойства компоновщика layoutLeftMargin, layoutTopMargin, layoutRightMargin и layoutBottomMargin. В результате получим форму, изображенную на рисунке. Элемент управления компоновщиком и индикатор его активного состояния в инспекторе объектов отмечены на рисунке символом "К".

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

файл "demo_qwtpolar.pro"

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

QT += core gui

greaterThan(QT_MAJOR_VERSION, 4): QT += widgets

TARGET = demo_qwtpolar
TEMPLATE = app


SOURCES += main.cpp\
  mainwindow.cpp

HEADERS += mainwindow.h

FORMS += mainwindow.ui


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


# Включение класса QData (QData5)  [Показать]

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

include(qwt.pri)
include(qwtpolar.pri)

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

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

Это диаграммы направленности двух антенн. Одна из них широкая и направлена вдоль азимута 0°. Вторая узкая и отклонена на 30°. Диаграммы направленности идеальные и аппроксимированы зависимостью типа sin(kx)/kx, где параметр k определеят ширину.

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

Если Вы используетет библиотеку Qwt 6.x.x, то дополнительный класс должен наследовать свойства класса QwtSeriesData<QwtPointPolar>. Помимо конструктора, обеспечивающего прием данных, в нем должны быть реализованы несколько обязательных функций:
size_t size() – возвращает количество точек в серии;
QwtPointPolar sample(size_t) – по заданному индексу возвращает пару значений – азимут и радиус;
QRectF boundingRect() – возвращает рабочую (ограничивающую) область для кривой.

В случае с библиотекой Qwt 5.x.x дополнительный класс должен наследовать свойства класса QwtData. В нем должны быть реализованы следующие функциии:
size_t size() – возвращает количество точек в серии;
double x(size_t) – по заданному индексу возвращает азимут;
double у(size_t) – по заданному индексу возвращает радиус;
QwtData *copy() – создает новый экземпляр класса и возвращает указатель на него.

Чтобы не путать, дадим наименование QData классу, который будет использоваться с Qwt 6.x.x, и QData5 – классу, предназначенному для Qwt 5.x.x. Файлы того или другого класса должны быть добавлены в перечни файлов реализации и заголовочных файлов проекта. Сделать это можно так:

contains(QWT_VER,^5\\..*\\..*) {
  SOURCES += qdata5.cpp
  HEADERS += qdata5.h
} else {
  SOURCES += qdata.cpp
  HEADERS += qdata.h
}

Эти инструкции следует поместить в файле проекта сразу после директив #include, определяющих библиотеки. Ниже приведено содержимое файлов классов QData и QData5.

файл "qdata.h"

#ifndef QDATA_H
#define QDATA_H

#include <qwt_series_data.h>

class QData : public QwtSeriesData<QwtPointPolar>
{
public:
  QData(double *,double *,size_t);
  ~QData();

  virtual size_t size() const;
  virtual QwtPointPolar sample(size_t) const;
  virtual QRectF boundingRect() const;

protected:
  double *azimuth;
  double *radial;
  size_t d_size;
};

#endif // QDATA_H

файл "qdata.cpp"

#include "qdata.h"

QData::QData(double *az,double *rad,size_t sz)
{
  azimuth = (double *)malloc(2*sz*sizeof(double));
  radial = azimuth + sz;
  for (int k=0; k < (int)sz; k++)
  {
    azimuth[k] = az[k];
    radial[k] = rad[k];
  }
  d_size = sz;
}

QData::~QData() {
  free((void *)azimuth);
}

size_t QData::size() const {
  return d_size;
}

QwtPointPolar QData::sample(size_t i) const {
  return QwtPointPolar(azimuth[i],radial[i]);
}

QRectF QData::boundingRect() const
{
  if (d_boundingRect.width() < 0)
    d_boundingRect = qwtBoundingRect(*this);

  return d_boundingRect;
}

файл "qdata5.h"

#ifndef QDATA5_H
#define QDATA5_H

#include <qwt_data.h>

class QData5 : public QwtData
{
public:
  QData5(double *,double *,size_t);
  ~QData5();

  virtual size_t size() const;
  virtual double x(size_t) const;
  virtual double y(size_t) const;
  virtual QwtData *copy() const;

protected:
  double *azimuth;
  double *radial;
  size_t d_size;
};

#endif // QDATA5_H

файл "qdata5.cpp"

#include "qdata5.h"

QData5::QData5(double *az,double *rad,size_t sz)
{
  azimuth = (double *)malloc(2*sz*sizeof(double));
  radial = azimuth + sz;
  for (int k=0; k < (int)sz; k++)
  {
    azimuth[k] = az[k];
    radial[k] = rad[k];
  }
  d_size = sz;
}

QData5::~QData5() {
  free((void *)azimuth);
}

size_t QData5::size() const {
  return d_size;
}

double QData5::x(size_t i) const {
  return azimuth[i];
}

double QData5::y(size_t i) const {
  return radial[i];
}

QwtData *QData5::copy() const {
  return new QData5(azimuth,radial,d_size);
}

Оба класса и QData, и QData5 в конструкторе получают указатели на два массива с данными и количество элементов в массивах. Для хранения данных выделяется блок памяти нужного размера, в который данные и копируются. В деструкторе выделенный блок памяти освобождается. Файлы этих классов нужно создать и поместить их в папку с проектом.

Теперь можно заняться непосредственно оформлением графика. Порядок действий очень напоминает аналогичный при создании графика QwtPlot из библиотеки Qwt (см. соответствующую статью). Заглянем в файлы главного окна приложения.

файл "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"

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

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

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

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

  delete ui;
}

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

#include <qwt_legend.h>
#include <qwt_polar_grid.h>
#include <qwt_polar_curve.h>
#include <qwt_polar_panner.h>
#include <qwt_polar_magnifier.h>

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

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

QwtPolarPanner *pan;
QwtPolarMagnifier *zoom;

В файле реализации главного окна приложения "mainwindow.cpp" добавляем еще директивы #include

#include <QPen>

#if QWT_VERSION < 0x060000
#include "qdata5.h"
#else
#include "qdata.h"
#endif

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

Итак, устанавливаем заголовок графика

ui->myPlot->setTitle(QString("QwtPolarPlot Demo"));

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

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

Создаем координатную сетку. Для всех шкал разрешаем отображение линий сетки, соответствующих вспомогательным делениям нижней шкалы. Назначаем цвет для линий сетки – светло-серый. Вспомогательные линии сетки будут изображаться пунктирной линией.

grid = new QwtPolarGrid;
for (int sc=0; sc < QwtPolar::ScaleCount; sc++)
{
  grid->showMinorGrid(sc);
  grid->setMajorGridPen(sc,QPen(Qt::lightGray));
  grid->setMinorGridPen(sc,QPen(Qt::lightGray,0,Qt::DotLine));
}

Запрещаем отображение радиальной шкалы справа и разрешаем сверху

ui->myPlot->showAxis(QwtPolar::AxisRight,false);
ui->myPlot->showAxis(QwtPolar::AxisTop,true);

Связываем созданную сетку с графиком

grid->attach(ui->myPlot);

Устанавливаем наименование нижней шкалы, минимальную и максимальную границы для нее. Аналогичные установки делаем для левой шкалы.

ui->myPlot->setScale(QwtPlot::Radius,0,1,0.2);
ui->myPlot->setScale(QwtPlot::Azimuth,0,360,30);

Создаем первую кривую с наименованием "DNA 1", назначаем ей толщину 2 и цвет прорисовки – темно-зеленый

curv1 = new QwtPlotCurve(QString("DNA 1"));
curv1->setPen(QPen(Qt::darkGreen),2);

Создаем вторую кривую с наименованием "DNA 2", назначаем ей цвет прорисовки – красный

curv2 = new QwtPlotCurve(QString("DNA 2"));
curv2->setPen(QPen(Qt::red));

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

const int N1 = 361;
const int N2 = 1441;

Объявляем массивы данных: 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 до 360, шаг приращения по углу зависит от количества точек на кривой.

double d = 40;         // ширина ДНА
double kd = 2.7831 / d;
double h = 360./(N1-1);
for (int k = 0; k < N1; k++)
{
  double fi = k*h;
  X1[k] = fi;
  double x = fi;
  while (x >= 180) x -= 360;
  double y = 1;
  if (x != 0) y = qAbs(sin(kd*x)/(kd*x));
  Y1[k] = y;
}
const double fi0 = 30; // смещение
d = 6;                 // ширина ДНА
kd = 2.7831 / d;
h = 8./(N2-1);
for (int k = 0; k < N2; k++)
{
  double fi = k*h;
  X2[k] = fi;
  double x = fi - fi0;
  while (x >= 180) x -= 360;
  double y = 1;
  if (x != 0) y = qAbs(sin(kd*x)/(kd*x));
  Y2[k] = y;
}

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

#if QWT_VERSION < 0x060000
  curv1->setData(QData5(X1,Y1,N1));
  curv2->setData(QData5(X2,Y2,N2));
#else
  curv1->setData(new QData(X1,Y1,N1));
  curv2->setData(new QData(X2,Y2,N2));
#endif

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

free((void *)X1);

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

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

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

pan = new QwtPolarPanner(ui->myPlot->canvas());
zoom = new QwtPolarMagnifier(ui->myPlot->canvas());

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

delete pan;
delete zoom;

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

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

 

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

Существует другой подход, реализованный в примере polardemo, поставляемом вместе с библиотекой QwtPolar. В нем на графике изображаются две кривые, для генерации данных которых предназначены два класса SpiralData и RoseData, созданные по тем же правилам, что и класс QData (или QData5) из нашего примера. Обратите внимание, что для каждой кривой предназначен свой класс. Данные в эти классы не передаются, они вычисляются непосредственно при вызове функции sample() или соответственно функций x() и y(). Такой подход имеет право на существование, но не является универсальным – зависимость, изображаемая на графике, не всегда может быть выражена аналитически, или требует большого объема вычислений (например, при моделировании). Вам предлагается самомтоятельно посмотреть, как можно построить диаграммы направленности из нашего примера с применением такого подхода, альтернативный проект доступен по ссылке.