[pyqtgraph] 左側に複数y軸を（違和感がないように）作成する

# はじめに
pyqtgraphで複数のy軸を左側に表示したかった時にハマったので記事にしました。

単純に左側に表示するだけならすぐにできるのですが、その場合もともとあるy軸と追加した軸の大きさが異なってしまいます。
<img width=300 alt="miss.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/779817/2ae7f480-6875-15c9-2fa4-8c92378520ad.png">
↑ 左側2軸の方が長い

試行錯誤して、（完全ではないですが）違和感の無い程度に揃えることができました。

<img width=300 alt="result.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/779817/db111b93-5b8c-88ae-8a01-f0195991cbdc.png">



# 環境
Windows10
Python 3.9.5

numpy 1.20.3
PySide6 6.1.0
pyqtgraph 0.12.1

`pip install numpy PySide6 pyqtgraph`

# 原因・対策
下記URLのように、`PlotItem`の中にある`AxisItem`と`AxisItem`単体を並べてしまうと大きさに差異が発生してしまいます。


<img width=300 alt="原因.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/779817/970373cd-cc8f-9c67-da96-5bd50c505c69.png">

[python - How can I have multiple left axisItems with the same alignment/position using pyqtgraph? - Stack Overflow](https://stackoverflow.com/questions/42931474/how-can-i-have-multiple-left-axisitems-with-the-same-alignment-position-using-py)
<br>
追加する`AxisItem`を別ウィジットに入れ、下にスペーサー（空白の文字列等）で高さ調節して上手くいきました。
<img width=300 alt="対策.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/779817/d8e9f33c-2731-1806-9be8-f7f168c050f8.png">

# 全文

```python
import sys
from typing import List, Sequence

import numpy as np
from PySide6 import QtWidgets
import pyqtgraph as pg


class LeftYAxisWidget(QtWidgets.QGraphicsWidget):
    """追加するY軸用のウィジット"""

    def __init__(self, ylabels: List[str], *args, **kwargs) -> None:
        """初期化処理

        Parameters
        -----
        ylabels: List[str]
            生成するY軸のラベル
        """

        super(LeftYAxisWidget, self).__init__(*args, **kwargs)
        
        # レイアウト
        layout = QtWidgets.QGraphicsGridLayout()
        layout.setContentsMargins(1, 1, 1, 1)
        layout.setSpacing(0)
        layout.setHorizontalSpacing(10)
        layout.setVerticalSpacing(0)
        layout.setRowStretchFactor(0, 10)
        layout.setRowStretchFactor(1, 0)
        self.setLayout(layout)

        self.yaxes: List[pg.AxisItem] = []

        for i, label in enumerate(ylabels):
            yaxis = pg.AxisItem(orientation='left')
            yaxis.setLabel(label)
            self.yaxes.append(yaxis)

            layout.addItem(yaxis, 0, i)
        layout.addItem(pg.LabelItem(' '), 1, 0)


class MultiAxisWidget(pg.GraphicsLayoutWidget):
    """複数y軸グラフ"""
    def __init__(self, ylabels: List[str], *args, **kwargs) -> None:
        """初期化処理

        Parameters
        -----
        ylabels: List[str]
            生成するY軸のラベル
        """

        super(MultiAxisWidget, self).__init__(*args, **kwargs)

        self.plot = pg.PlotItem()
        self.plot.hideAxis('left')  # 元々のy軸は隠す
        self.yaxes = LeftYAxisWidget(ylabels)

        self.addItem(self.yaxes, 0, 0)  # 追加Y軸ウィジットをレイアウトに追加(0行0列目)
        self.addItem(self.plot, 0, 1)  # plotItemをレイアウトに追加(0行1列目)

        self.view_boxes: List[pg.ViewBox] = []
        self.data: List[pg.PlotDataItem] = []

        for i, yaxis in enumerate(self.yaxes.yaxes):
            view_box = pg.ViewBox()
            view_box.setXLink(self.plot)  # x軸をリンク
            self.plot.scene().addItem(view_box)  # plotItem内にviewBoxを追加

            yaxis.linkToView(view_box)  # 追加するy軸とviewBoxをリンク

            data = pg.PlotDataItem()
            view_box.addItem(data)

            self.view_boxes.append(view_box)
            self.data.append(data)

        self.plot.autoBtn.clicked.connect(self.reset_range)  # plotItemのautoBtnを押したときにself.reset_rageも実行
        self.plot.vb.sigResized.connect(self.update_views)  # plotItemのサイズが変わった時にviewBox達をそれに合わせる

        self.update_views()
        self.reset_range()

    def set_multi_value(self, values: List[Sequence]) -> None:
        """複数データ入力

        Parameters
        ----------
        values: List[Sequence]
            入力するデータ
        """

        for data, value in zip(self.data, values):
            data.clear()
            data.setData(value)

    def reset_range(self) -> None:
        """表示範囲をデータに合わせる"""

        for vb in self.view_boxes:
            vb.enableAutoRange(axis=pg.ViewBox.XYAxes, enable=True)

    def update_views(self) -> None:
        """self.plot.scene()のサイズが変わったとき,view_boxもその大きさに合わせる"""

        plot_vb = self.plot.vb
        for vb in self.view_boxes:
            vb.setGeometry(plot_vb.sceneBoundingRect())


if __name__ == '__main__':
    app = QtWidgets.QApplication(sys.argv)
    window = MultiAxisWidget([f"y{i}" for i in range(3, 0, -1)])

    data1 = np.random.randint(0, 10, 10)
    data2 = np.random.randint(0, 10, 10)
    data3 = np.random.randint(0, 10, 10)

    window.set_multi_value([data1, data2, data3])

    for i, color in enumerate(['b', 'g', 'r']):
        window.yaxes.yaxes[i].setPen(pg.mkPen(color))
        window.data[i].setPen(pg.mkPen(color))

    window.show()
    sys.exit(app.exec())

```

# 詳細

`LeftYAxisWidget`は追加Y軸用ウィジットです。2行目に空の文字列を入れて高さ調節しています。

`MultiAxisWidget`のfor文内で軸とグラフをリンクしています。

```python

        for i, yaxis in enumerate(self.yaxes.yaxes):
            view_box = pg.ViewBox()
            view_box.setXLink(self.plot)  # x軸をリンク挿せる
            self.plot.scene().addItem(view_box)  # plotItem内にviewBoxを追加

            yaxis.linkToView(view_box)  # 追加するy軸とviewBoxをリンクさせる

            data = pg.PlotDataItem()
            view_box.addItem(data)
```


# 参考
[python - How can I have multiple left axisItems with the same alignment/position using pyqtgraph? - Stack Overflow](https://stackoverflow.com/questions/42931474/how-can-i-have-multiple-left-axisitems-with-the-same-alignment-position-using-py)
[python - pyqtgraph: Multiple y-axis on left side? - Stack Overflow](https://stackoverflow.com/questions/29473757/pyqtgraph-multiple-y-axis-on-left-side)
[PyQtGraphでグラフを描写する その5 - Y軸を増やす - Qiita](https://qiita.com/nebula121/items/004087c2d2902eba9e47)