PySide (Qt for Python) は、Qt(キュート)の Python バインディングで、GUI などを構築するためのクロスプラットフォームなライブラリです。Linux/X11, macOS および Microsoft Windows をサポートしています。配布ライセンスは LGPL で公開されています。
データを視覚化したいときには、まず JupyterLab 上で Matplotlib を利用してあれこれチャートを作ります。そして、再利用するニーズがあるチャートについては、PySide6 に埋め込んで簡単な GUI アプリにするのが常です。
GUI 化するときには Matplotlib の NavigatorToolBar を利用して、主にズーム機能を利用しています。
NavigatorToolBar に表示されている虫眼鏡アイコンをクリックして、プロット上の矩形領域をマウスでドラッグして指定すると、簡単に拡大(ズームイン)できるので重宝しているのですが、不満が一つあります。
それは、拡大してもデータ点(マーカー)のサイズが変わらないことです。
同じようなことを不満に思う人はいるだろうなと思って調べたところ、Stack Overflow に、しかもずいぶん前にポストされていました(参考サイト [1])。当然ながら、PySide6 にチャートを埋め込む内容ではないので、参考サイトの回答にあったサンプルをベースにして、PySide6 にチャートを埋め込むサンプルを作ってみました。
下記の OS 環境で動作確認をしています。
![]() |
RHEL 9.4 | x86_64 |
Python | 3.12.1 | |
PySide6 | 6.7.1 | |
matplotlib | 3.9.0 |
このサンプルでは、単純化するために、プロットする path はひとつ、xy 軸は一対のみで、散布図 (scatter) を対象として、データ点の間は線を結ばず、ズーム機能ではマーカーのサイズのみを考慮するものとしています。
# Reference: | |
# https://stackoverflow.com/questions/48474699/marker-size-alpha-scaling-with-window-size-zoom-in-plot-scatter | |
import sys | |
from math import sin | |
from typing import Any | |
from matplotlib.axes import Axes | |
from matplotlib.backends.backend_qtagg import ( | |
FigureCanvasQTAgg as FigureCanvas, | |
NavigationToolbar2QT as NavigationToolbar, | |
) | |
from matplotlib.collections import PathCollection | |
from matplotlib.figure import Figure | |
from PySide6.QtCore import Qt | |
from PySide6.QtWidgets import QMainWindow, QApplication | |
class MyChart(FigureCanvas): | |
def __init__(self): | |
self.fig = Figure() | |
self.fig.canvas.mpl_connect('resize_event', self.onResize) | |
super().__init__(self.fig) | |
self.fig_w = self.fig.get_figwidth() | |
self.fig_h = self.fig.get_figheight() | |
self.ax = self.fig.add_subplot(111) | |
self.ax.callbacks.connect('xlim_changed', self.onLimitChanged) | |
self.ax.callbacks.connect('ylim_changed', self.onLimitChanged) | |
self.xlim = self.ax.get_xlim() | |
self.ylim = self.ax.get_ylim() | |
self.fig_factor = None | |
self.path = None | |
self.sizes = None | |
def getZoomFactor(self, | |
limx: tuple[float, float], | |
limy: tuple[float, float]) -> float: | |
return min( | |
(self.xlim[1] - self.xlim[0]) / (limx[1] - limx[0]), | |
(self.ylim[1] - self.ylim[0]) / (limy[1] - limy[0]) | |
) | |
def onLimitChanged(self, ax: Axes): | |
lx = ax.get_xlim() | |
ly = ax.get_ylim() | |
zfactor = self.getZoomFactor(lx, ly) | |
try: | |
self.path.set_sizes( | |
[s * zfactor * self.fig_factor for s in self.sizes] | |
) | |
except KeyError: | |
pass | |
def onResize(self, event: Any): | |
w = self.fig.get_figwidth() | |
h = self.fig.get_figheight() | |
self.fig_factor = min(w / self.fig_w, h / self.fig_h) | |
def setPath(self, path: PathCollection): | |
self.path = path | |
self.sizes = self.path.get_sizes() | |
def draw_chart(chart: MyChart): | |
list_x = list() | |
list_y = list() | |
for i in range(100): | |
x = i / 10. | |
y = sin(x) | |
list_x.append(x) | |
list_y.append(y) | |
path = chart.ax.scatter(list_x, list_y, s=20, c='blue') | |
chart.setPath(path) | |
class Example(QMainWindow): | |
def __init__(self): | |
super().__init__() | |
chart = MyChart() | |
chart.ax.grid() | |
self.setCentralWidget(chart) | |
navbar = NavigationToolbar(chart, self) | |
self.addToolBar(Qt.ToolBarArea.BottomToolBarArea, navbar) | |
draw_chart(chart) | |
def main(): | |
app = QApplication() | |
ex = Example() | |
ex.show() | |
sys.exit(app.exec()) | |
if __name__ == '__main__': | |
main() |
サンプルの実行例を示しました。
時系列データを扱う場合などで、データ点の間を線で結ぶ場合でも、ズームインした際にマーカーのサイズさえ大きくなれば、線の太さはそのままで個人的には十分だと思っています。それでも、一つのチャートで複数のデータパスを扱うことが多いので、このサンプルで紹介した MyChart クラスを実用的に使えるように機能追加していく必要があります。
参考サイト
- pandas - Marker size/alpha scaling with window size/zoom in plot/scatter - Stack Overflow [2018-01-27]

にほんブログ村
#オープンソース

0 件のコメント:
コメントを投稿