[dearpygui] drag&dropでプロットする

# やりたいこと

ドキュメントのギャラリーにあるような、データをドラッグしてグラフを表示してみたい

<img width=500 alt='gallery.gif' src='https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/779817/351f2bb9-125e-b3ae-a8d7-8b5745f27659.gif'>

https://dearpygui.readthedocs.io/en/latest/documentation/plots.html?highlight=drag#gallery

demoを参考にしながら方法を調べてみました

https://github.com/hoffstadt/DearPyGui/blob/master/DearPyGui/dearpygui/demo.py


<img width=700 alt='dd_plot_2.gif' src='https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/779817/fe544e2d-4167-5439-7fa4-f1f3ea31da5e.gif'>


# 環境
Mac OS
Python 3.10.1

dearpygui 1.3.1
numpy 1.22.1

`pip install dearpygui numpy`
# 詳細
## ドラッグ&ドロップの実装
まずは簡単なドラッグの実装から

### ドラッグ側
ドラッグしたいアイテムに`dpg.add_drag_payload()`を設定
引数は
- `parent` : ドラッグしたいアイテムのタグ
- `drag_data` : ドラッグして渡したいデータ
- `payload_type` :  ドラッグ元とドラッグ先の合言葉?のようなもの. 双方に同じ文字列を入力

### ドロップ側
アイテム作成時に`drop_callback`, `payload_type`引数を入力. それぞれ
- `drop_callback` : ドロップされたときに実行する関数
- `payload_type` : ドラッグ元と同じ文字列

下は10と表示してあるボタンをドラッグして、`input_int`アイテムに値を入れています

<img width=500 alt='input_10.gif' src='https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/779817/1649f930-2f31-2b8b-2878-e6df01769f7c.gif'>

```python
from typing import Any, Union

import dearpygui.dearpygui as dpg


# ドラッグされたときに呼び出す関数
def input_drop_int(sender: Union[int, str], app_data: Any, user_data) -> None:
    """ドロップされた整数を入力する

    Parameters
    -----
    sender: Union[int, str]
        送信元のタグ
    app_data: Any
        ドロップされたデータ
    user_data
    """

    dpg.set_value(sender, app_data)


# ##################### 描画設定 #####################
dpg.create_context()
dpg.create_viewport(title='Plot view port', width=800, height=500)

with dpg.window(label='Drag&Drop Window', width=250, height=50):
    with dpg.group(horizontal=True):
        button = dpg.add_button(label=str(10))
        # drag_data: ドラッグ先に渡すデータ, payload_type: ドラッグ元とドラッグ先の合言葉?のようなもの, strを指定
        dpg.add_drag_payload(parent=button, drag_data=10, payload_type='Drag&Drop')

        input_int = dpg.add_input_int(label='drop', drop_callback=input_drop_int, payload_type='Drag&Drop')

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

```

`with dpg.drag_payload()`だとドラッグ中に表示さあれるウィンドウの編集ができます。

<img width=600 alt='input_10_2.gif' src='https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/779817/8ab8789a-afb8-d893-a4d2-4aeb0eaa6e3d.gif'>

```python
with dpg.window(label='Drag&Drop Window', width=250, height=50):
    with dpg.group(horizontal=True):
        button = dpg.add_button(label=str(10))
        # drag_data: ドラッグ先に渡すデータ, payload_type: ドラッグ元とドラッグ先の合言葉?のようなもの, strを指定
        with dpg.drag_payload(parent=button, drag_data=10, payload_type='Drag&Drop'):
            dpg.add_text(default_value='value:10')

        input_int = dpg.add_input_int(label='drop', drop_callback=input_drop_int, payload_type='Drag&Drop')

```

## グラフでやってみる

<img width=600 alt='dd_plot.gif' src='https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/779817/3ee19f05-de25-6eea-ca42-8669ee33a844.gif'>


```python
"""Drag&Dropでグラフをかく
参考
https://github.com/hoffstadt/DearPyGui/blob/master/DearPyGui/dearpygui/demo.py
2205 ~ 2245行目
"""

from typing import Any, Union

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)
sin_data = np.sin(x_data)


# ドラッグされたときに呼び出す関数
def plot_drag_data(sender: Union[int, str], app_data: Any, user_data) -> None:
    """ドラックされたデータをプロットする

    Parameters
    -----
    sender: Union[int, str]
        送信元のタグ
    app_data: Any
        ドラッグされたデータ
    user_data
    """

    dpg.add_line_series(app_data[0], app_data[1], label=app_data[2], parent=sender)
    # 軸の範囲をデータに合わせる
    dpg.fit_axis_data('xaxis')
    dpg.fit_axis_data(sender)

    # 凡例を右クリックすると追加した線を削除できるボタンを追加
    dpg.add_button(label='delete', user_data=dpg.last_item(), parent=dpg.last_item(),
                   callback=lambda s, a, u: dpg.delete_item(u))


# ##################### 描画設定 #####################
dpg.create_context()
dpg.create_viewport(title='Plot view port', width=800, height=500)

with dpg.window(label='Drag&Drop Window', width=-1, height=-1):
    # グループ内のアイテムを横に並べる
    with dpg.group(horizontal=True):
        # グループ内のアイテムを縦に並べる
        with dpg.group():
            dpg.add_text(default_value='sources:')

            # button追加&ボタンをドラッグしたときの情報追加
            dpg.add_button(label='sin', tag='sin_src')
            # drag_data: ドラッグ先に渡すデータ, payload_type: ドラッグ元とドラッグ先の合言葉?のようなもの, strを指定
            with dpg.drag_payload(parent='sin_src', drag_data=(x_data, sin_data, 'sin'), payload_type='plotting'):
                dpg.add_text('sin')
                dpg.add_simple_plot(default_value=sin_data)

        with dpg.plot(label='Drag&Drop Plot', width=700, height=440):
            # 凡例, x軸
            dpg.add_plot_legend()
            dpg.add_plot_axis(dpg.mvXAxis, label='x', tag='xaxis')
            # y軸作成、dropされた時のコールバックを設定、payload_typeはドラッグ元と同じ文字列を指定
            dpg.add_plot_axis(dpg.mvYAxis, label='y1', drop_callback=plot_drag_data, payload_type='plotting')

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

```

y2, y3, plot, legendにもドラッグできるように変更
(`Demo.py`を参考にしています)

<img width=700 alt='dd_plot_2.gif' src='https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/779817/fe544e2d-4167-5439-7fa4-f1f3ea31da5e.gif'>


```python
"""
参考
https://github.com/hoffstadt/DearPyGui/blob/master/DearPyGui/dearpygui/demo.py
2205 ~ 2245行目
"""

from typing import Any, Union

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)
sin_data = np.sin(x_data)


def make_y_data(wave):
    freq = dpg.get_value('freq')
    if wave == 'sin':
        y_data = np.sin(freq * x_data)
    else:
        y_data = np.cos(freq * x_data)
    return y_data, freq


def dropped_axis(sender: Union[int, str], app_data: Any, user_data) -> None:
    """"y軸にドロップされたデータを表示する

    Parameters
    -----
    sender: Union[int, str]
        送信元のタグ
    app_data: Any
        ドラッグされたデータ
    user_data
    """

    y_data, freq = make_y_data(app_data[-1])
    dpg.add_line_series(app_data[0], y_data, label=f"{app_data[-1]}({freq}x)", parent=sender)
    dpg.fit_axis_data('xaxis')
    dpg.fit_axis_data(sender)

    # 凡例を右クリックすると追加した線を削除できるボタンを追加
    dpg.add_button(label='delete', user_data=dpg.last_item(), parent=dpg.last_item(),
                   callback=lambda s, a, u: dpg.delete_item(u))


def dropped_legend(sender, app_data, user_data):
    """legendにドロップした時の動作"""

    y_data, freq = make_y_data(app_data[-1])

    parent = dpg.get_item_info(sender)["parent"]
    yaxis2 = dpg.get_item_info(parent)["children"][1][2]
    dpg.add_line_series(app_data[0], y_data, label=f"{app_data[-1]}({freq}x)", parent=yaxis2)
    dpg.fit_axis_data('xaxis')
    dpg.fit_axis_data(yaxis2)

    dpg.add_button(label="Delete Series", user_data=dpg.last_item(), parent=dpg.last_item(),
                   callback=lambda s, a, u: dpg.delete_item(u))


def dropped_plot(sender, app_data, user_data):
    """プロットにドロップした時の動作"""
    y_data, freq = make_y_data(app_data[-1])

    yaxis1 = dpg.get_item_info(sender)["children"][1][0]
    dpg.add_line_series(app_data[0], y_data, label=f"{app_data[-1]}({freq}x)", parent=yaxis1)
    dpg.fit_axis_data('xaxis')
    dpg.fit_axis_data(yaxis1)

    dpg.add_button(label="Delete Series", user_data=dpg.last_item(), parent=dpg.last_item(),
                   callback=lambda s, a, u: dpg.delete_item(u))


dpg.create_context()
dpg.create_viewport(title='DD', width=800, height=500)

with dpg.window(label='Drag&Drop Window', width=-1, height=-1):
    # グループ内のアイテムを横に並べる
    with dpg.group(horizontal=True):
        # グループ内のアイテムを縦に並べる
        with dpg.group():
            dpg.add_text(default_value='sources:')
            dpg.add_input_int(label='freq', width=80, default_value=1, tag='freq')

            # button追加&ボタンをドラッグしたときの情報追加
            dpg.add_button(label='sin', tag='sin_src')
            with dpg.drag_payload(parent='sin_src', drag_data=(x_data, 'sin'), payload_type='plotting'):
                dpg.add_text('sin')

            dpg.add_button(label='cos', tag='cos_src')
            with dpg.drag_payload(parent='cos_src', drag_data=(x_data, 'cos'), payload_type='plotting'):
                dpg.add_text('cos')

        with dpg.plot(label='Drag&Drop Plot', width=600, height=375, drop_callback=dropped_plot,
                      payload_type='plotting'):
            # 凡例, x軸
            dpg.add_plot_legend(drop_callback=dropped_legend, payload_type='plotting')
            dpg.add_plot_axis(dpg.mvXAxis, label='x', tag='xaxis')
            # y軸作成、dropされた時のコールバックを設定、payload_typeはドラッグ元と同じ文字列を指定
            dpg.add_plot_axis(dpg.mvYAxis, label='y1', drop_callback=dropped_axis, payload_type='plotting')
            dpg.add_plot_axis(dpg.mvYAxis, label='y2', drop_callback=dropped_axis, payload_type='plotting')
            dpg.add_plot_axis(dpg.mvYAxis, label='y3', drop_callback=dropped_axis, payload_type='plotting')

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

```

# 参考
[DearPyGui/demo.py at master · hoffstadt/DearPyGui](https://github.com/hoffstadt/DearPyGui/blob/master/DearPyGui/dearpygui/demo.py)