Библиотека Qwt: как построить график функции на плоскости?

    Скриншот простейшей программы, демонстрирующей использование виджетов Qwt
    Уже пять лет я не пишу приложения с GUI, потому предложение зав. кафедрой подготовить для его лекции программу, строящую некие графики, поначалу меня несколько расстроило.

    Как настоящий программист я решил найти самый легкий (и полезный для саморазвития) путь.
    А именно, это задание оказалось хорошим поводом для того, чтобы познакомиться с Qt и библиотекой Qwt. Заодно я узнал, что приложения с GUI, оказываются, могут быть кроссплатформенными, а их код не менее элегантным, чем у приложений с «интерфейсом в стиле Unix™».

    Итак, Qwt — библиотека виджетов для программирования приложений, имеющих техническую направленность. Она содержит набор виджетов, представляющих собой всевозможные слайдеры и дисковые «номеронабиратели», виджеты для построение гистограмм. Но здесь я расскажу, как Qwt применить для построения самых обычных двумерных графиков функций вида y = f(x).

    Дальше в данной заметке
    • приводится ссылка на исходный код моего простейшего примера (+ исполняемые файлы) и инструкция по его компиляции;
    • рассказывается, как же в этом примере всё устроено;
    • для новичков описывается компиляция и установка Qwt.


    Простейший пример и его компиляция


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

    По ссылке для скачивания (308 КБ) можно получить tgz-архив с
    • исходными кодами,
    • исполняемым файлом для Windows (собиралось в WinXP Pro SP3 32-bit);
    • исполняемым файлом для Linux (собиралось в OpenSUSE 11.2 x86-64).

    Собрать же эти бинарники из исходников, можно следующими командами:
    • qmake

    Linux:
    • make release

    Windows:
    • mingw32-make release

    Обратите внимание, что в каталоге с исходниками лежит файл qwt.prf из оригинальной поставки. Так как я использую статическую версию библиотеки, то переменной QwtBuild присваивается пустое значение. Кроме того, в разделе win32 значение переменной QwtBase изменено на путь к каталогу, в который у меня установлена библиотека.

    Пройдемся по исходникам


    В Qwt всё просто. Есть виджет «область рисования» — QwtPlot. С ним можно связать несколько кривых — объекты QwtPlotCurve. Собственно, вот и всё.

    Описание класса (файл QwtBeginner.h)

    В моем примере окно приложения — экземпляр класса QwtBeginner, который является потомком класса QWidget.

    Он включает в себя две области рисования QwtPlot: на funPlot будут отображаться графики функций, на derPlot — графики их производных.

    Всего будет построено четыре кривых: функция синуса и кубическая функция(sinFunCurve, cubFunCurve) и их производные (sinDerCurve, cubDerCurve).

    Включать и отключать отображение соответствующих графиков будут кнопки sinButton и cubButton, к сигналам которых будут привязаны слоты sinToggled() и cubToggled().
    class QwtBeginner : public QWidget<br>{<br>    Q_OBJECT<br><br>public:<br>    QwtBeginner(QWidget *parent = 0);<br><br>private:<br>    QwtPlot *funPlot, *derPlot;<br>    QwtPlotCurve *sinFunCurve, *sinDerCurve;<br>    QwtPlotCurve *cubFunCurve, *cubDerCurve;<br>    QPushButton *sinButton, *cubButton;<br><br>private slots:<br>    void sinToggled(bool checked);<br>    void cubToggled(bool checked);<br>};


    Реализация класса (файл QwtBeginner.cpp)

    В конструкторе класса
    QwtBeginner::QwtBeginner(QWidget *parent)<br>    : QWidget(parent)<br>{

    создаются области рисования и задаются их заголовки:
        // Create plots<br>    funPlot = new QwtPlot;<br>    derPlot = new QwtPlot;<br><br>    funPlot->setTitle("Function");<br>    derPlot->setTitle("Derivative");

    Зададим «перышки», которыми будем рисовать кривые:
        // Create curves and attach them to plots<br>    QPen sinPen = QPen(Qt::red);<br>    QPen cubPen = QPen(Qt::blue);

    Создадим кривые, потребуем, чтобы они были сглаженными (RenderAntialiased), укажем «перышки» (setPen()), и привяжем кривые к соответствующим областям рисования (attach()).
        sinFunCurve = new QwtPlotCurve;<br>    sinFunCurve->setRenderHint(QwtPlotItem::RenderAntialiased);<br>    sinFunCurve->setPen(sinPen);<br>    sinFunCurve->attach(funPlot);<br><br>    sinDerCurve = new QwtPlotCurve;<br>    sinDerCurve->setRenderHint(QwtPlotItem::RenderAntialiased);<br>    sinDerCurve->setPen(sinPen);<br>    sinDerCurve->attach(derPlot);<br><br>    cubFunCurve = new QwtPlotCurve;<br>    cubFunCurve->setRenderHint(QwtPlotItem::RenderAntialiased);<br>    cubFunCurve->setPen(cubPen);<br>    cubFunCurve->attach(funPlot);<br><br>    cubDerCurve = new QwtPlotCurve;<br>    cubDerCurve->setRenderHint(QwtPlotItem::RenderAntialiased);<br>    cubDerCurve->setPen(cubPen);<br>    cubDerCurve->attach(derPlot);

    Заполним массивы данных, которые мы хотим отобразить:
        // Set data<br>    const int N = 20;<br>    double x[N+1];<br>    double sinFunData[N+1], sinDerData[N+1];<br>    double cubFunData[N+1], cubDerData[N+1];<br>    for (int i = 0; i <= N; ++i)<br>    {<br>        const double pi = 4.0 * atan(1.0);<br>        double L = 2;<br>        double h = L / N;<br><br>        x[i] = -L/2 + i * h;<br>        sinFunData[i] = sin(x[i] * pi);<br>        sinDerData[i] = cos(x[i] * pi) * pi;<br>        cubFunData[i] = x[i] * x[i] * x[i];<br>        cubDerData[i] = 3 * x[i] * x[i];<br>    }

    и свяжем эти данные с кривыми:
        sinFunCurve->setData(x, sinFunData, N+1);<br>    sinDerCurve->setData(x, sinDerData, N+1);<br>    cubFunCurve->setData(x, cubFunData, N+1);<br>    cubDerCurve->setData(x, cubDerData, N+1);

    Для начала спрячем кривые:
        // Hide curves<br>    sinFunCurve->setVisible(false);<br>    sinDerCurve->setVisible(false);<br><br>    cubFunCurve->setVisible(false);<br>    cubDerCurve->setVisible(false);

    Создадим кнопки и свяжем сигналы со слотами:
        // Create buttons<br>    sinButton = new QPushButton("Sinus"),<br>    cubButton = new QPushButton("Cubic function"),<br><br>    sinButton->setCheckable(true);<br>    cubButton->setCheckable(true);<br><br>    // Connect signals<br>    connect(sinButton, SIGNAL(toggled(bool)), this, SLOT(sinToggled(bool)));<br>    connect(cubButton, SIGNAL(toggled(bool)), this, SLOT(cubToggled(bool)));

    Разместим виджеты на форме и раскроем окно на весь экран:
        // Set layouts<br>    QHBoxLayout *plotsLayout = new QHBoxLayout;<br>    plotsLayout->setSpacing(10);<br>    plotsLayout->addWidget(funPlot);<br>    plotsLayout->addWidget(derPlot);<br><br>    QHBoxLayout *buttonsLayout = new QHBoxLayout ;<br>    buttonsLayout->addWidget(sinButton);<br>    buttonsLayout->addWidget(cubButton);<br><br>    QVBoxLayout *widgetLayout = new QVBoxLayout;<br>    widgetLayout->addLayout(plotsLayout);<br>    widgetLayout->addLayout(buttonsLayout);<br><br>    setLayout(widgetLayout);<br>    showMaximized();<br>}

    Наконец, реализация слотов:
    void QwtBeginner::sinToggled(bool checked)<br>{<br>    sinFunCurve->setVisible(checked);<br>    sinDerCurve->setVisible(checked);<br>    funPlot->replot();<br>    derPlot->replot();<br>} <br><br>void QwtBeginner::cubToggled(bool checked)<br>{<br>    cubFunCurve->setVisible(checked);<br>    cubDerCurve->setVisible(checked);<br>    funPlot->replot();<br>    derPlot->replot();<br>}

    Обратите внимание, что мы не только должны изменить признак видимости кривых (setVisible()), но и перерисовать области рисования (replot()).

    Функция main() (файл main.cpp)

    Здесь всё традиционно:
    int main(int argc, char *argv[])<br>{<br>    QApplication app(argc, argv);<br>    QwtBeginner *wnd = new QwtBeginner;<br>    wnd->show();<br>    return app.exec();<br>} <br><br>* This source code was highlighted with Source Code Highlighter.


    Для новичков: компиляция и установка Qwt


    С официального сайта для скачивания доступны архивы tar.bz2 и zip. На момент написания этой заметки последней версией является 5.2.0.

    Итак, скачиваем и распаковываем архив в какую-нибудь временную директорию.

    В файл qwtconfig.pri у меня внесены следующие изменения.
    • Закомментирована строчка "CONFIG += QwtDll" (по каким-то причинам в Windows DLL у меня не собирается).
    • Строчка "CONFIG += QwtExamples" раскомментирована.
    • Прописан другой каталог установки "INSTALLBASE".

    Теперь можно приступать к сборке и установке.

    Windows

    • qmake qwt.pro
    • mingw32-make
    • mingw32-make install

    Linux

    • qmake qwt.pro
    • make
    • sudo make install

    Заключение


    Qwt — удобный кроссплатформенный инструмент для построения графиков функций одной переменной. Предоставляет простой интерфейс и избавляет от необходимости заботиться о деталях, типа масштабирования и меток на осях.

    Необходимо еще отметить, что подкаталоге examples/ исходной поставки лежит еще 15 примеров, среди которых и examples/simple_plot, послуживший основой для моей программки.

    Буду признателен за критику, комментарии и дополнения, так как пока я не являюсь большим специалистом по Qt.

    Спасибо за внимание!

    P.S.

    Похожие инструменты.
    • Пакет PLplot: поддержка C++, Python (и нескольких других языков), встраивание в Qt, wxWidgets, Gtk+ (и некоторые другие среды) — спасибо gribozavr.
    • Библиотека MathGL: поддержка C++, Python, встраивание в Qt, см. тж. программу UDAV в качестве примера — спасибо toshnya.
    Поделиться публикацией
    Реклама помогает поддерживать и развивать наши сервисы

    Подробнее
    Реклама
    Комментарии 41
    • +1
      Согласен QT — это здорово и просто. Сам никогда не писал ГУИ-приложения под линукс, а тут торкнуло написать(курсовую по распределенным вычислениям) на питоне, да и с гуем, написал за несколько часов, всё крайне юзабельно.
      • 0
        Честно говоря не очень нравится этот Qwt, во первых он часто не собирается с новыми версиями Qt, у меня такое было один раз под Windows, а во вторых API осталось ещё со времен Qt3 и в целом по подходу устарело.
        • 0
          А не знаете ли каких-нибудь альтернатив связке Qt+Qwt для решения задач, подобных моей?

          К слову, Qwt у меня успешно собрался и под Windows, и под Linux с Qt 4.6.0 и Qt 4.6.1.
          Правда в Windows (как я упомянул в тексте) пришлось собирать статическую библиотеку, а не DLL.
          • +1
            Для построения графиков попробуйте биндинги plplot к Qt.
            • 0
              А как де gnuplot? Ничего лучше для построения графиков я не видел. Многие программы используют его.
              • +2
                Qwt попроще будет. Сел и поехал. +разработан поверх Qt.
                • 0
                  gnuplot — это мощная stand-alone программа, которая, правда, для моей задачи не очень подходит.

                  У меня речь идет о построении графика в рамках приложения с GUI: нажал кнопку — построился некоторый предопределенный график, нажал другую — построился другой график.

                  Конкретнее, задача была такая. На лекции рассматривается численный метод для решения краевой задачи для ОДУ 1-го порядка. Нужно изобразить численное решение и погрешность для разных значений сеток. На форме приложения две области рисования и несколько кнопок, отвечающих разным сеткам. Демонстратор нажимает кнопки в том порядке, в котором просит лектор :-)
                  • 0
                    Я про gnuplot вспомнил потому что насколько знаю он используется в maxima и octave,
                • 0
                  >А не знаете ли каких-нибудь альтернатив связке Qt+Qwt для решения задач, подобных моей?

                  QGraphicsScene? Но ежели честно, то других либ специально для рисования графиков то и нет на Qt.
                  • 0
                    Хм… У QGraphicsScene, кажется, несколько иное предназначение — ведь не addLine() же в самом деле использовать!

                    А если говорить не только о Qt, то чем можно воспользоваться, чтобы писать кроссплатформенные приложения, основной задачей которых является отображение простеньких графиков?
                • 0
                  Ошибка сборки под версию 4.6 там по вине Qt, а не Qwt. В общем-то правится одной строкой, там один каст убрать надо.

                  А с подходом не совсем понятно. Что именно устарело?
                  • 0
                    Да просто, выглядит он часто не слишком презентабельно, будто бы в Qt4 вставили кусок из Qt3, да и для моих целей это «забивание гвоздей микроскопом». Я вообще иногда подумываю лично для себя сделать простенькую либу для создания графиков.
                  • 0
                    Ну подождите QML.
                    • 0
                      Я лишь в самых общих чертах представляю, что такое QML.
                      Потому не очень понимаю, почему его нужно подождать? :-)
                      • +1
                        Вот когда там будет нечто вроде canvas'а тогда можно будет сделать и на QML рисовалку графиков
                        qt.nokia.com/doc/qml-snapshot/qmlelements.html
                        А пока QML скорее занимается управлением уже готовыми элементами.
                    • +1
                      Я сам когда начал изучать Qt, то первое для чего он был мне нужен — построения вот таких простых графиков. Как и вы, изначально, я начал искать готовые решения, и конечно же нашел QWT, но чето я тогда отказался ставить qwt и решил для опыта программирования на qt написать свою небольшую програмку.
                      Так вот, прочитал вашу статью, и появилась идея все-таки глянуть как же реализовано все тут) спасибо.

                      И да, мое маленькое пожелание по коду, так это то, что кусок:
                      const double pi = 4.0 * atan(1.0);
                      double L = 2;
                      double h = L / N;
                      можно вынести за цикл :)
                      • 0
                        Спасибо за комментарий. Конечно, вы правы — упомянутый кусок следует вынести за цикл. Mea culpa.
                        • 0
                          > const double pi = 4.0 * atan(1.0);
                          по моему лучше использовать M_PI из :)

                          да и смысл писать L/2, если L==2 как-то не очевиден, быстрее же будет работать просто написание 1.0
                          • 0
                            Опыт показывает, что константа M_PI доступна не на всех системах.

                            Что касается L/2, то так писать мне кажется более логичным.
                            Традиционно задачи решаются на отрезке длины L: либо на (-L/2, +L/2), либо на (0, L).
                            Кроме того, компиляторы сейчас достаточно умны, чтобы самостоятельно пронести все константы на этапе компиляции.
                            Наконец, появление в коде магического числа 1.0 не самым лучшим образом скажется на его читабельности и возможности поддержки.
                            • 0
                              как показывает любой текстовый редактор, то <QtCore/qmath.h> доопределяет ее, если это не сделали хидеры :) но тогда да, надо инклудить его вместо насчет L понял свой промах
                      • 0
                        Хочется заметить изрядную корявость в алгоритмах шкалирования и центровки. Автоматических разумеется.
                        • 0
                          уточню: спектрограмм.
                          • 0
                            Можно написать свою реализацию QwtScaleEngine.
                            • 0
                              Это сравнимо с заменой qwt на plplot, скажем.
                          • +1
                            Юзаю qwt в своих приложениях. Есть ряд претензий, но все же очень доволен (тем более что лучшего-то и нет). Жалко что проект не развивает в данный момент…
                            • +1
                              Вы очень кстати с этой статьей=) Я как раз недавно собирался использовать Qwt для своей работы курсовой в вузе… Но столкнулся тогда с проблемой в виде зависимости Qwt исходников от хидерфайла, которого не нашел ни в самом Qt ни в Qwt… Тогда решил отложить и пока не возвращался =) а щас прочту статейку на досуге и посмотрю где недосмотрел и где затупил=)
                              Спасиб… плюс к статье и карме=)
                              • 0
                                Подождите, так эта библиотека не парсит функции, а просто строит по готовому массиву данных? Я думал, что пост про разбор функций будет в том числе. Это в софте, строящем графики — самое главное, сложное и вкусное. :) Мне эта тема с графиками самому очень нравится. Где-то год назад я создал приложение для соц. сети на букву «В» (не буду называть полностью, а то НЛО автоматически заминусует). Оно умеет по-умному разбирать введённую формулу и строить (правда, пока что только одну функцию за раз). Этой весной очень многое там поменяю, сделаю отдельный веб-сервис, а потом, может быть, тоже напишу чего-нибудь про это. :)
                                • 0
                                  Нет, не парсит.

                                  На самом деле, построение графиков функций, заданных таблично, — это тоже важная и интересная задача. Особенно для тех, кто как и я, занимается численными методами.
                                  • 0
                                    Можно прикрутить Qtscript и не надо будет парсить. Да + в объеме правда )
                                  • 0
                                    Для построения различных графиков и диаграмм в программах на Qt хорошо подходит библиотека MathGL. В ней реализован виджет для Qt. В качестве примера использования этой библиотеки в приложении на Qt можно посмотреть программу UDAV.
                                    • НЛО прилетело и опубликовало эту надпись здесь
                                    • 0
                                      Сам тоже пользовался Qwt во время учебы в универе.
                                      Кроме того, есть либа QwtPlot3d — для пространственных графиков.
                                      • 0
                                        спасибо за статью, есть такой вопрос: у меня стоит задача динамической отрисовки графиков, т.е. не сразу отрисовать весь график, а как бы постепенно, как такое сделать в QWT? Перегенерировать массив точек и перерисовывать весь график? Имхо — достаточно неэффективно. Может быть есть какие-то готовые решения моей проблемы, полезные ссылки?

                                        Заранее спасибо за ответы
                                        • 0
                                          Боюсь, ничего не могу посоветовать сверх того, что предлагаете Вы.
                                          Возможно, Вам имеет смысл посмотреть примеры, которые поставляются вместе с QwtPlot, например, data_plot.
                                        • 0
                                          Не подскажите новичку: сделал всё как описано(установка qwt) в итоге получаю папку c:\qwt-5.2.1, при попытке собрать ваш проект — не находит qwt`шные библиотеки.
                                          Если добавить их все непосредственно в папку с проектом, заменив в программе на "". Все библиотеки читает, но в итоге выдаёт
                                          c:/qt/2010.02.1/mingw/bin/../lib/gcc/mingw32/4.4.0/../../../../mingw32/bin/ld.exe: cannot find -lqwt
                                          collect2: ld returned 1 exit status
                                          mingw32-make[1]: *** [debug\QwtBeginner.exe] Error 1
                                          mingw32-make: *** [debug] Error 2
                                          Очень расчитываю на вашу помощь. Весь день провозился в попытках связать Qt и Qwt.
                                          • 0
                                            «заменив в программе на „“ „
                                            имел ввиду, что скобки, которые отправляют на поиски библиотек в стандартных библиотечных папках, заменил на “» посылающие компилятор за библиотеками в папку с самим проектом.
                                            • 0
                                              в .pro путь к библиотеке qwt переписал как положено
                                              • 0
                                                вобщем поправил все пути, проблема с библиотеками решилась, но ошибка осталась
                                                c:/qt/2010.02.1/mingw/bin/../lib/gcc/mingw32/4.4.0/../../../../mingw32/bin/ld.exe: cannot find -lqwt
                                                collect2: ld returned 1 exit status
                                                mingw32-make[1]: *** [debug\QwtBeginner.exe] Error 1
                                                mingw32-make: *** [debug] Error 2
                                                • 0
                                                  решилось заменой -lqwt на -l Qwt5
                                                  но запускать полученный екзешник не хочет

                                                  Запускается C:\QwtBeginner\release\QwtBeginner.exe…
                                                  C:\QwtBeginner\release\QwtBeginner.exe завершился с кодом -1073741515

                                                  Если запускать из папки, то говорит, что qtcore4.dll не может найти точку входа в процедуру
                                                  • 0
                                                    Проблему решил, спасибо за внимание :)
                                                    Начал писать от безысходности — весь день провозился, ничего не получалось. Сейчас заново начал перебирать все нагугленные ссылки, и, наконец, получилось.

                                                    Как бы то ни было, ваша заметка по установке и запуску qwt для новичков подходит с трудом) не всё так просто как оказалось
                                                    • 0
                                                      Рад, что у Вас получилось.

                                                      Я пытался придумать минимальный содержательный пример использования Qwt.
                                                      Жаль, что не всё в моей инструкции гладко.

                                                      Будет здорово, если Вы расскажете о своем опыте — вдруг он для кого-нибудь окажется полезным!

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