DearPyGuiでプロットする

# はじめに
以下の記事からDearPyGuiというライブラリを知り, グラフ描画について興味が湧いたので調べてみました。
複数y軸, 時間軸, リアルタイム描画など簡単にできたので紹介します。

https://qiita.com/Ninagawa_Izumi/items/7f905ee5fd4cbf523fec

<img width=300 alt='plot.png' src='https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/779817/8a41c14c-8e0b-c478-eb6a-c1e5078592bd.png'><img width=300 alt='multi_axis.png' src='https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/779817/a5f2ea9b-c814-56b4-d1d8-c79603206ce5.png'>
<img width=300 alt='time_axis.png' src='https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/779817/2fe75ccb-8886-bfc2-fe80-5a532bedc41d.png'><img width=300 alt='real_time.gif' src='https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/779817/0255269e-e644-516a-e04c-f2d050b281a1.gif'>




# 環境
Mac OS
Python 3.10.1

dearpygui 1.3.1
numpy 1.22.1

`pip install dearpygui numpy`


# プロットのデモ
`show_implot_demo()`でプロットのデモが見れます(コードは無いですが、、、)
Helpには以下を参照してとありました。

https://github.com/epezent/implot/blob/4fcc6e01aca406ef17d5a2dabdcbc9e1bd962c0d/implot_demo.cpp

```python
import dearpygui.dearpygui as dpg

dpg.create_context()
dpg.create_viewport(title='Plot demo', width=600, height=600)
# demo
dpg.show_implot_demo()

dpg.setup_dearpygui()
dpg.show_viewport()
dpg.start_dearpygui()
dpg.destroy_context()

```

こちらにもあります。
こっちはコードを確認できます。

```python
import dearpygui.dearpygui as dpg
import dearpygui.demo as demo

dpg.create_context()
dpg.create_viewport(title='Demo', width=600, height=600)

demo.show_demo()

dpg.setup_dearpygui()
dpg.show_viewport()
dpg.start_dearpygui()
dpg.destroy_context()
```


<img width=400 alt='demo2.png' src='https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/779817/57ba8b44-4c84-815e-7a4e-19dfc04ad2a3.png'>



# 単軸, 複数y軸

これらはドキュメントを見る方が確実かな?
y軸は最大3つまで追加出来るようです。
プロット内のズーム、凡例から表示/非表示などできます。

https://dearpygui.readthedocs.io/en/latest/documentation/plots.html#plots


<img width=300 alt='plot.png' src='https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/779817/8a41c14c-8e0b-c478-eb6a-c1e5078592bd.png'><img width=300 alt='multi_axis.png' src='https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/779817/a5f2ea9b-c814-56b4-d1d8-c79603206ce5.png'>

```python
"""dearpyguiのプロット例 (単軸)

参考URL
https://dearpygui.readthedocs.io/en/latest/documentation/plots.html#plots
"""
import dearpygui.dearpygui as dpg
import numpy as np

# ################# プロットデータ作成 #################
data_len: int = 100
x_data: np.ndarray = np.linspace(-np.pi, np.pi, data_len)
y_data = np.sin(x_data)

# ##################### 描画設定 #####################
dpg.create_context()
dpg.create_viewport(title='Plot view port', width=850, height=550)
dpg.setup_dearpygui()
dpg.show_viewport()

with dpg.window(label='Plot window', width=-1, height=-1):
    with dpg.plot(label='Plot', width=800, height=500):
        dpg.add_plot_legend()  # 凡例追加
        # x軸追加
        dpg.add_plot_axis(dpg.mvXAxis, label='x', tag='xaxis')

        # y軸とデータ曲線追加. データ曲線のparent引数は連動させたいy軸のtagを入力
        dpg.add_plot_axis(dpg.mvYAxis, label='y', tag='yaxis')
        # parent=dpg.last_item() は最後に追加したアイテム
        dpg.add_line_series(x_data, y_data, label='sin', parent=dpg.last_item(), tag='line')

        # y軸を追加する場合(最大3軸)はこの下に軸とデータ曲線を追加していく

dpg.start_dearpygui()
dpg.destroy_context()

```

# 時間軸

<img width=300 alt='time_axis.png' src='https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/779817/2fe75ccb-8886-bfc2-fe80-5a532bedc41d.png'>
`time`引数を`True`にすると時間軸にできます。


x軸のデータはunix秒です。
ただしそのままだとUTCの時刻表示になってしまうよう?なので, +9時間して日本時間に調整しています

```python
JST_TZ = 60 * 60 * 9
jst_now = time.time() + JST_TZ
```


```python
dpg.add_plot_axis(dpg.mvXAxis, label='x', tag='xaxis', time=True)
```

```python
"""dearpyguiの時間軸例
x軸を追加する時, time引数をTrueにして (30行目),
データ曲線のx値にunix秒のリストを渡すと時刻列になる

ただしそのままだとUTCの時刻表示になってしまうよう?なので, +9時間して日本時間に調整している(16, 17行目)
"""
import time

import dearpygui.dearpygui as dpg
import numpy as np

# ################# プロットデータ作成 #################
data_len: int = 100
y_data = np.random.randint(0, 100, data_len)
# ####### 時間軸作成 #######
JST_TZ = 60 * 60 * 9  # 日本時間調整用
jst_now = time.time() + JST_TZ
# numpy配列のままだとうまく表示できなかったのでリストに変換
x_data: list = list(np.linspace(jst_now, jst_now + data_len - 1, data_len))

# ##################### 描画設定 #####################
dpg.create_context()
dpg.create_viewport(title='Plot view port', width=850, height=550)
dpg.setup_dearpygui()
dpg.show_viewport()

with dpg.window(label='Plot window', width=-1, height=-1):
    with dpg.plot(label='Time plot', width=800, height=500):
        dpg.add_plot_legend()  # 凡例追加
        # x, y軸追加
        dpg.add_plot_axis(dpg.mvXAxis, label='x', tag='xaxis', time=True)
        dpg.add_plot_axis(dpg.mvYAxis, label='y', tag='yaxis')
        # データ線追加, 親は最後に追加したアイテム (=y軸)
        dpg.add_line_series(x_data, y_data, label='data', parent=dpg.last_item(), tag='line')

dpg.start_dearpygui()
dpg.destroy_context()

```
# リアルタイム

以下を参考にしています

https://dearpygui.readthedocs.io/en/latest/documentation/render-loop.html

https://github.com/hoffstadt/DearPyGui/issues/303


<img width=300 alt='real_time.gif' src='https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/779817/0255269e-e644-516a-e04c-f2d050b281a1.gif'>

画面表示のトータル時間から描画間隔を設定しています。

```python

# ################# 時間経過判定用　#################
elapsed_time: float = 0.0
interval: float = 0.05

# 今までdpg.start_dearpygui()だった箇所を変更
while dpg.is_dearpygui_running():
    total_time = dpg.get_total_time()  # 画面が開いてからのトータル時間 (秒)
    # interval 秒経過したらグラフ更新
    if total_time - elapsed_time >= interval:
        elapsed_time = total_time
        plot_callback()
    dpg.render_dearpygui_frame()
```

`plot_callback()`関数内でデータをプロットしています。

```python
def plot_callback() -> None:
    """データをプロットする"""

    global x_data, y_data
    y_data = update_array(y_data)  # データ更新用関数

    # 第1引数は値を入力したいアイテムのtag (= dpg.add_line_series(tag='line'))
    dpg.set_value('line', [x_data, y_data])
```

```python
"""dearpyguiのリアルタイムプロット例

参考URL
https://dearpygui.readthedocs.io/en/latest/documentation/render-loop.html
https://dearpygui.readthedocs.io/en/latest/documentation/plots.html#updating-series-data
https://github.com/hoffstadt/DearPyGui/issues/303
"""
import dearpygui.dearpygui as dpg
import numpy as np

# ################# プロットデータ作成 #################
data_len: int = 100
x_data: np.ndarray = np.linspace(0, data_len - 1, data_len)
y_data = np.empty(data_len)
y_data[:] = np.nan

# ################# 時間経過判定用　#################
elapsed_time: float = 0.0
interval: float = 0.05


def update_array(data: np.ndarray) -> np.ndarray:
    """np.ndarrayの末尾に新しい値を追加

    Parameters
    ----------
    data: np.ndarray
        データ入力するnp.ndarray

    Returns
    -------
    result: np.ndarray
        追加後のデータ
    """

    new_y = np.random.randint(0, 100)  # 追加する値
    data[:data_len - 1] = data[1:data_len]
    data[data_len - 1] = new_y

    return data


def plot_callback() -> None:
    """データをプロットする"""

    global x_data, y_data
    y_data = update_array(y_data)

    # 第1引数は値を入力したいアイテムのtag (= dpg.add_line_series(tag='line'))
    dpg.set_value('line', [x_data, y_data])


# ##################### 描画設定 #####################
dpg.create_context()
dpg.create_viewport(title='Plot view port', width=420, height=300)
dpg.setup_dearpygui()
dpg.show_viewport()

with dpg.window(label='Plot window', width=-1, height=-1):
    with dpg.plot(label=f"Real time plot \n interval: {interval} sec", width=400, height=250):
        dpg.add_plot_legend()  # 凡例追加
        # x, y軸追加
        dpg.add_plot_axis(dpg.mvXAxis, label='x', tag='xaxis')
        dpg.add_plot_axis(dpg.mvYAxis, label='y', tag='yaxis')
        # データ線追加, 親は最後に追加したアイテム (=y軸)
        dpg.add_line_series(x_data, y_data, label='data', parent=dpg.last_item(), tag='line')

while dpg.is_dearpygui_running():
    total_time = dpg.get_total_time()  # 画面が開いてからのトータル時間 (秒)
    # interval 秒経過したらグラフ更新
    if total_time - elapsed_time >= interval:
        elapsed_time = total_time
        plot_callback()
    dpg.render_dearpygui_frame()

dpg.destroy_context()

```

# その他

`dpg.fit_axis_data(tag)`で軸をデータ範囲に調整できます

```python
    # 毎回範囲内に調整したい時
    dpg.set_value(lineのtag, [x_data, y_data])
    dpg.fit_axis_data(axisのtag)
```

また、プロット内を右クリックすると色々設定できます
<img width=500 alt='right_click.png' src='https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/779817/2a9c16c2-997a-b266-cfd0-e257102812ba.png'>


# 参考
[DearPyGuiをはじめる10行 - Qiita](https://qiita.com/Ninagawa_Izumi/items/7f905ee5fd4cbf523fec)
[Plots — Dear PyGui documentation](https://dearpygui.readthedocs.io/en/latest/documentation/plots.html#plots)
[Render Loop — Dear PyGui documentation](https://dearpygui.readthedocs.io/en/latest/documentation/render-loop.html)
[Add support for timers · Issue #303 · hoffstadt/DearPyGui · GitHub](https://github.com/hoffstadt/DearPyGui/issues/303)