2021-07-27

Qt for Python によるチャート (9)

Python で GUI アプリを作成するときに Qt の Python 用バインディングである PySide (Qt for Python) を使用することが多くなりました。散布図などのチャート作成には、もっぱら matplotlib を使っていますが、他の選択肢も検討しようと、QtCharts というチャート作成用ライブラリの使い方をまとめました。

当初、PySide 用の QtCharts のサンプルが見つからず、C++ 用のサンプル [1] を PySide 用に書き直していましたが、よく探してみると PySide 用サンプルもありました [2]。ここでは、勉強がてら C++ 用のサンプルを書き直したものを紹介していきます。

本記事では、下記の OS 環境を使用しています。

Fedora 34 Workstation x86_64
- Python 3.9.6
- PySide6 6.1.2 (venv)
- IDE: PyCharm 2021.1.3 (Community Edition)

InteractScatterChart(やりとりできる散布図)

ScatterChart(散布図)をインタラクティブに変化させるサンプルです。

qtcharts_interactscatterchart.py
#!/usr/bin/env python
# coding: utf-8
# Reference
# https://doc.qt.io/qt-6/qtcharts-scatterinteractions-example.html
import math
import sys
from PySide6.QtCharts import (
QChart,
QChartView,
QScatterSeries,
)
from PySide6.QtCore import (
QPointF,
Qt,
)
from PySide6.QtGui import QPainter
from PySide6.QtWidgets import (
QApplication,
QMainWindow,
)
class InteractScatterChart(QChartView):
def __init__(self):
super().__init__()
chart = self.init_ui()
self.setChart(chart)
self.setRenderHint(QPainter.Antialiasing)
def init_ui(self):
series_a = QScatterSeries()
series_a.setName('scatter1')
series_a.setColor('cyan')
for i in range(8):
x = 0.5 * (i + 1)
for j in range(8):
y = 0.5 * (j + 1)
series_a << QPointF(x, y)
series_b = QScatterSeries()
series_b.setName('scatter2')
series_b.setColor('magenta')
series_a.clicked.connect(lambda point: self.handleClickedPoint(point, series_a, series_b))
series_b.clicked.connect(lambda point: self.handleClickedPoint(point, series_b, series_a))
chart = QChart()
chart.legend().hide()
chart.addSeries(series_a)
chart.addSeries(series_b)
chart.setTitle('Click to interact with scatter points')
chart.createDefaultAxes()
chart.axes(Qt.Horizontal)[0].setRange(0, 4.5)
chart.axes(Qt.Vertical)[0].setRange(0, 4.5)
return chart
def handleClickedPoint(self, clickedPoint: QPointF, series_old: QScatterSeries, series_new: QScatterSeries):
# Find the closest point from series 1
INT_MAX = 100000
closest = QPointF(INT_MAX, INT_MAX)
distance = float(INT_MAX)
points = series_old.points()
for currentPoint in points:
currentDistance = math.sqrt((currentPoint.x() - clickedPoint.x())
* (currentPoint.x() - clickedPoint.x())
+ (currentPoint.y() - clickedPoint.y())
* (currentPoint.y() - clickedPoint.y()))
if currentDistance < distance:
distance = currentDistance
closest = currentPoint
# Remove the closes point from series 1 and append it to series 2
series_old.remove(closest)
series_new.append(closest)
class Example(QMainWindow):
def __init__(self):
super().__init__()
scatter = InteractScatterChart()
self.setCentralWidget(scatter)
self.resize(500, 300)
self.setWindowTitle('InteractScatterChart')
def main():
app = QApplication(sys.argv)
ex = Example()
ex.show()
sys.exit(app.exec())
if __name__ == '__main__':
main()

実行例を下記に示しました。マウスでプロット点をクリックすると水色から赤紫色(あるいはその逆)に変化します。

qtcharts_interactscatterchart.py の実行例

データ点クリック時のイベントで何か処理をするには、データ列(このサンプルの場合、series_a と series_b )にバインディングを指定します。

        series_a.clicked.connect(lambda point: self.handleClickedPoint(point, series_a, series_b))
        series_b.clicked.connect(lambda point: self.handleClickedPoint(point, series_b, series_a))
    def handleClickedPoint(self, clickedPoint: QPointF, series_old: QScatterSeries, series_new: QScatterSeries):        ...
        ...
        ...

データ点をクリックするイベントから簡単に処理へバインディングできるのは、データ解析用のアプリを作る場合に魅力的な機能ですが、大量のデータを扱ったときにリソースをどれだけ消費することになるかが気になるところです。別途、調べてみる予定です。

参考サイト

  1. Qt Charts Examples | Qt Charts 6.1.2
  2. Qt for Python Examples — Qt for Python

 

ブログランキング・にほんブログ村へ bitWalk's - にほんブログ村 にほんブログ村 IT技術ブログ Linuxへ
にほんブログ村

0 件のコメント: