Добрый день, хаброжители!

Введение


В свободное от работы время увлекся написанием приложений на PyQt5. И свой давний проект по ведению домашней бухгалтерии MyWallet решил в конце мая переписать с плюсов на Python, так как в предыдущей версии были допущены ряд архитектурных ошибок, которые на хотелось исправлять. Поэтому собрав PyQt5 из исходников под Fedora 21, где-то за две недели реализовал весь функционал, который был ранее. И теперь встает вопрос в визуализации данных по расходам/доходам помесячно. Так как имел опыт визуализации данных с помощью QCustomPlot , хотел визуализацию сделать с помощью этой либы. Но к огорчению, не нашел биндов.

Сборка


После просмотра исходников PyQt5 было выяснено, что генерация биндов реализована с помощью SIP). SIP принимает на вход что-то вроде урезанного заголовка методов класса (естественно, со своими так называемыми аннотациями), а на выходе генерирует C++ код для создания готового модуля python.

Итак, для сборки модуля QCustomPlot для Python нам понадобится:
  1. Qt 5.x.
  2. SIP наиболее свежей версии.
  3. PyQt 5.x.
  4. Собранная в виде динамически подключаемой библиотеки qcustomplot, собранной под Qt 5.x.
  5. Файл специального вида с описанием интерфейса классов библиотеки.


Покопавшись по github'у в поисках готового файла интерфейса для этой либы, наткнулся на репозиторий qcustomplot-python, владелец которого собрал бинды, правда для PyQt4. Действуя по аналогии, получаем файл интерфейса либы qcustomplot.sip.

В этом же репозитории можно найти и configure.py, который, как известно, необходим для сборки и установки модулей Python. Данный файл пришлось адаптировать к новой версии PyQt.

Ну, а далее стандартно:

$ python3 configure.py build
$ make
$ sudo make install 


Удостоверимся, что у нас все получилось, запуститим IPy:
$ python3
>>> import qcustomplot
>>> dir(qcustomplot)
['QCP', 'QCPAbstractItem', 'QCPAbstractLegendItem', 'QCPAbstractPlottable', 'QCPAxis', 'QCPAxisRect', 'QCPBarData', 'QCPBars', 'QCPBarsGroup', 'QCPColorGradient', 'QCPColorMap', 'QCPColorMapData', 'QCPColorScale', 'QCPColorScaleAxisRectPrivate', 'QCPCurve', 'QCPCurveData', 'QCPData', 'QCPFinancial', 'QCPFinancialData', 'QCPGraph', 'QCPGrid', 'QCPItemAnchor', 'QCPItemBracket', 'QCPItemCurve', 'QCPItemEllipse', 'QCPItemLine', 'QCPItemPixmap', 'QCPItemPosition', 'QCPItemRect', 'QCPItemStraightLine', 'QCPItemText', 'QCPItemTracer', 'QCPLayer', 'QCPLayerable', 'QCPLayout', 'QCPLayoutElement', 'QCPLayoutGrid', 'QCPLayoutInset', 'QCPLegend', 'QCPLineEnding', 'QCPMarginGroup', 'QCPPainter', 'QCPPlotTitle', 'QCPPlottableLegendItem', 'QCPRange', 'QCPScatterStyle', 'QCPStatisticalBox', 'QCustomPlot', '__doc__', '__file__', '__loader__', '__name__', '__package__', '__spec__']
>>> 


Ну, а чтобы совсем было красиво, привожу код одного из примеров BarsDemo:
Код примера
import sys
from PyQt5.QtCore import Qt
from PyQt5.QtWidgets import QApplication
from PyQt5.QtGui import QColor, QPen
from qcustomplot import QCustomPlot, QCPBars, QCP

if __name__ == '__main__':

    app = QApplication(sys.argv)

    w = QCustomPlot()
    regen = QCPBars(w.xAxis, w.yAxis)
    nuclear = QCPBars(w.xAxis, w.yAxis)
    fossil = QCPBars(w.xAxis, w.yAxis)

    w.addPlottable(regen)
    w.addPlottable(nuclear)
    w.addPlottable(fossil)

    pen = QPen()
    pen.setWidthF(1.2)
    fossil.setName('Fossil fuels')
    pen.setColor(QColor(255, 131, 0))
    fossil.setPen(pen)
    fossil.setBrush(QColor(255, 131, 0, 50))
    nuclear.setName('Nuclear')
    pen.setColor(QColor(1, 92, 192))
    nuclear.setPen(pen)
    nuclear.setBrush(QColor(1, 92, 191, 50))
    regen.setName('Regenerative')
    pen.setColor(QColor(150, 222, 0))
    regen.setPen(pen)
    regen.setBrush(QColor(150, 222, 0, 70))
    nuclear.moveAbove(fossil)
    regen.moveAbove(nuclear)

    ticks = [1, 2, 3, 4, 5, 6, 7]
    labels = ['USA', 'Japan', 'Germany', 'France', 'UK', 'Italy', 'Canada']
    w.xAxis.setAutoTicks(False)
    w.xAxis.setAutoTickLabels(False)
    w.xAxis.setTickVector(ticks)
    w.xAxis.setTickVectorLabels(labels)
    w.xAxis.setTickLabelRotation(60)
    w.xAxis.setSubTickCount(0)
    w.xAxis.grid().setVisible(True)
    w.xAxis.setRange(0, 8)

    w.yAxis.setRange(0, 12.1)
    w.yAxis.setPadding(5)
    w.yAxis.setLabel('Power Consumption in\nKilowatts per Capita (2007)')
    w.yAxis.grid().setSubGridVisible(True)

    grid_pen = QPen()
    grid_pen.setStyle(Qt.SolidLine)
    grid_pen.setColor(QColor(0, 0, 0, 25))
    w.yAxis.grid().setSubGridPen(grid_pen)

    fossil_data = [0.86 * 10.5, 0.83 * 5.5, 0.84 * 5.5, 0.52 * 5.8, 0.89 * 5.2, 0.90 * 4.2, 0.67 * 11.2]
    nuclear_data = [0.08 * 10.5, 0.12 * 5.5, 0.12 * 5.5, 0.40 * 5.8, 0.09 * 5.2, 0.00 * 4.2, 0.07 * 11.2]
    regen_data = [0.06 * 10.5, 0.05 * 5.5, 0.04 * 5.5, 0.06 * 5.8, 0.02 * 5.2, 0.07 * 4.2, 0.25 * 11.2]
    fossil.setData(ticks, fossil_data)
    nuclear.setData(ticks, nuclear_data)
    regen.setData(ticks, regen_data)

    w.legend.setVisible(True)
    w.axisRect().insetLayout().setInsetAlignment(0, Qt.AlignTop|Qt.AlignHCenter)
    w.legend.setBrush(QColor(255, 255, 255, 200))
    legendPen = QPen()
    legendPen.setColor(QColor(130, 130, 130, 200))
    w.legend.setBorderPen(legendPen)
    w.setInteractions(QCP.iRangeDrag or QCP.iRangeZoom)

    w.show()

    sys.exit(app.exec())


Вот, что получилось:
Результат
image


P.S.


Ссылка на репозиторий с исходниками: QCustomPlot-PyQt5. В репозитории в каталоге RPMS находятся SRPM и RPM для Fedora21 (PyQt5, qcustomplot 1.3.1 и python3-qcustomplot).

Все комментарии и пожелания приветствуются. Надеюсь, этот модуль вам пригодится. Спасибо за внимание!

Комментарии (3)


  1. iroln
    20.06.2015 19:55

    А можно сделать универсальный байндинг-пакет, который можно было бы собрать для PyQt4 и для PyQt5? Тогда его можно было бы выложить на тот же PyPI в виде wheels под разные платформы для PyQt4 и PyQt5.

    $ python3 configure.py build
    $ make
    $ sudo make install 
    Зачем же так сразу захламлять систему через sudo make install? Можно же, наверное, соответствующий setup.py написать и через тот же pip собрать и установить пакет? Во-первых, систему не захламляет, а во-вторых, кроссплатформенно и уже почти стандарт в мире Python.


    1. dimv36 Автор
      20.06.2015 20:29

      Это будет следующая итерация. Опять же, делал по примеру с PyQt5, там никаких wheels нет.


    1. dimv36 Автор
      20.06.2015 20:34

      Да, а чтобы не засорять систему на Linux'e можно запаковать (в rpm или deb архивы).