2024-08-18

動的に複数のチャートを表示する

PySide (Qt for Python) は、Qt(キュート)の Python バインディングで、GUI などを構築するためのクロスプラットフォームなライブラリです。Linux/X11, macOS および Microsoft Windows をサポートしています。配布ライセンスは LGPL で公開されています。

mplfinance のチャートを PySide6 のウィジェットに埋め込んだ時に、表示するチャートを動的に増やしたり減らしたりしたかったので、FigureCanvas インスタンスを変えずに、表示するチャートの数を増減するサンプルを作ってみました。

下記の OS 環境で動作確認をしています。

RHEL 9.4 x86_64
Python 3.12.1
PySide6 6.7.2
mplfinance 0.12.10b0
yfinance 0.2.41

サンプルを以下に示しました。サンプルでは、日経平均株価の過去3ヶ月の日足データを yfinance で取得しています。

qt_mpl_finance_3.py
import mplfinance as mpf
import sys
import yfinance as yf
from matplotlib.backends.backend_qtagg import (
FigureCanvasQTAgg as FigureCanvas,
NavigationToolbar2QT as NavigationToolbar,
)
from matplotlib.figure import Figure
from PySide6.QtCore import Qt, Signal
from PySide6.QtWidgets import (
QApplication,
QCheckBox,
QMainWindow,
QToolBar,
)
class MyChart(FigureCanvas):
def __init__(self):
self.fig = Figure()
super().__init__(self.fig)
self.fig.subplots_adjust(
left=0.12,
right=0.87,
top=0.9,
bottom=0.05,
)
self.ax = dict()
def initAxes(self, ax, n: int):
if n > 1:
gs = self.fig.add_gridspec(
n, 1,
wspace=0.0, hspace=0.0,
height_ratios=[3 if i == 0 else 1 for i in range(n)]
)
for i, axis in enumerate(gs.subplots(sharex='col')):
ax[i] = axis
ax[i].grid()
else:
ax[0] = self.fig.add_subplot()
ax[0].grid()
def initChart(self, n: int):
self.removeAxes()
self.initAxes(self.ax, n)
def clearAxes(self):
axs = self.fig.axes
for ax in axs:
ax.cla()
def refreshDraw(self):
self.fig.canvas.draw()
def removeAxes(self):
axs = self.fig.axes
for ax in axs:
ax.remove()
self.ax = dict()
def setTitle(self, title: str):
self.ax[0].set_title(title)
class MyToolBar(QToolBar):
volumeCheckChanged = Signal()
def __init__(self):
super().__init__()
self.chk_volume = chk_volume = QCheckBox('Volume')
chk_volume.checkStateChanged.connect(self.volume_check_changed)
self.addWidget(chk_volume)
def isVolumeChecked(self) -> bool:
return self.chk_volume.isChecked()
def volume_check_changed(self):
self.volumeCheckChanged.emit()
class Example(QMainWindow):
def __init__(self):
super().__init__()
self.setWindowTitle('Dynamic charts')
self.toolbar = toolbar = MyToolBar()
toolbar.volumeCheckChanged.connect(self.draw)
self.addToolBar(Qt.ToolBarArea.TopToolBarArea, toolbar)
self.chart = chart = MyChart()
self.setCentralWidget(chart)
self.navigation = navigation = NavigationToolbar(chart)
self.addToolBar(Qt.ToolBarArea.BottomToolBarArea, navigation)
self.symbol = symbol = '^N225'
ticker = yf.Ticker(symbol)
self.df = ticker.history(period='3mo')
self.draw()
def draw(self):
if self.toolbar.isVolumeChecked():
n = 2
else:
n = 1
self.chart.initChart(n)
param = dict(
data=self.df,
style='yahoo',
type='candle',
datetime_format='%m/%d',
xrotation=0,
ax=self.chart.ax[0],
)
if self.toolbar.isVolumeChecked():
param['volume'] = self.chart.ax[1]
mpf.plot(**param)
self.chart.setTitle(self.symbol)
self.chart.refreshDraw()
self.navigation.update()
def main():
app = QApplication(sys.argv)
ex = Example()
ex.show()
sys.exit(app.exec())
if __name__ == '__main__':
main()
qt_mpl_finance_3.py の実行例

ツールバーの Volume チャックボックスにチェックを入れると、出来高の棒グラフが下に追加されます。プロットは切り替える度に再描画しています。

qt_mpl_finance_3.py の実行例(ツールバーの Volume チャックボックスをチェック)

FigureCanvas クラスを継承した MyChart クラスで、軸数の設定や再描画の処理をしています。今回の用途では縦方向にチャートを増減させるだけですが、メインのチャートを常に大きく表示したかったので、処理を簡単にするために GridSpec で複数チャートの表示レイアウトを決めています。

class MyChart(FigureCanvas):
    def __init__(self):
        self.fig = Figure()
        super().__init__(self.fig)
        self.fig.subplots_adjust(
            left=0.12,
            right=0.87,
            top=0.9,
            bottom=0.05,
        )
        self.ax = dict()
 
    def initAxes(self, ax, n: int):
        if n > 1:
            gs = self.fig.add_gridspec(
                n, 1,
                wspace=0.0, hspace=0.0,
                height_ratios=[3 if i == 0 else 1 for i in range(n)]
            )
            for i, axis in enumerate(gs.subplots(sharex='col')):
                ax[i] = axis
                ax[i].grid()
        else:
            ax[0] = self.fig.add_subplot()
            ax[0].grid()
 
    def initChart(self, n: int):
        self.removeAxes()
        self.initAxes(self.ax, n)
 
    def clearAxes(self):
        axs = self.fig.axes
        for ax in axs:
            ax.cla()
 
    def refreshDraw(self):
        self.fig.canvas.draw()
 
    def removeAxes(self):
        axs = self.fig.axes
        for ax in axs:
            ax.remove()
        self.ax = dict()
 
    def setTitle(self, title: str):
        self.ax[0].set_title(title)

MyChart の使い方は(mplfinance には依存せず)以下のような流れになります。

# MyChart クラスのインスタンスを配置
chart = MyChart()
self.setCentralWidget(chart)
# チャート用のナビゲーションツールバーを配置
navigation = NavigationToolbar(chart)
self.addToolBar(navigation)
...
...
# 表示するチャート数を n で指定して初期化
chart.initChart(n)
 
# チャートを描画
# 軸は chart.ax[0], chart.ax[1], ..., chart.ax[n-1] で指定
...
...
 
# チャートの表示を更新
chart.refreshDraw()
# ナビゲーションツールバーの更新
navigation.update()

参考サイト

  1. matplotlib.gridspec.GridSpec — Matplotlib documentation

 

ブログランキング・にほんブログ村へ bitWalk's - にほんブログ村 にほんブログ村 IT技術ブログ オープンソースへ
にほんブログ村

オープンソース - ブログ村ハッシュタグ
#オープンソース



0 件のコメント: