【PyScript】PyScript使ってめちゃ適当にデータの可視化してみる【BI】

なんかPyScriptとかいうのが出たらしいですね。
ちょうどデータの可視化云々をやる機会があったので、CSVアップロードしてそのデータの中身を可視化するアプリ的なのを作ってみます。

なお、めちゃ適当にするので散布図だけでいったんやってみます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/395106/9fbe59c8-9839-2a3f-eb76-322abf6792bc.png)



# やること
csvの受け取りや要素の追加(カラムの選択とかしたいので)等はJSで行い、PyScriptでは可視化だけやってみたいとおもいます。


# HTML要素
利用するHTML要素です。
ファイルの受け取り、データを置いておくダミー要素、プルダウン、グラフの表示部分、表示ボタンを用意してます。

```html:index.html
<!DOCTYPE html>
<html lang="ja">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <link rel="stylesheet" href="https://pyscript.net/alpha/pyscript.css" />
    <link rel="stylesheet" href="test.css">
    <script defer src="https://pyscript.net/alpha/pyscript.js"></script>
    <py-env>
      - pandas
      - matplotlib
    </py-env>
  </head>

  <body>
    <div id="drop-zone" style="border: 1px solid; padding: 30px;">
      <p>ファイルをドラッグ＆ドロップもしくは</p>
      <input type="file" name="file" id="file-input">
  </div>

  <div id="data"></div>
  <div id="col"></div>
  <py-button id="create_graph" label="グラフ表示"></py-button>
  <select id="col1"></select>
  <select id="col2"></select>

  <h2>グラフ</h2>
  <div id="graph_view"></div>

  <py-script>~~~</py-script>
  <script src="test.js"></script>
  </body>

</html>
```
PyScriptの実行には
`<script defer src="https://pyscript.net/alpha/pyscript.js"></script>`
`<link rel="stylesheet" href="https://pyscript.net/alpha/pyscript.css" />`
の二つを用意し、スクリプトを`py-script`内に記述することで動作させることができます。

また、インストールが必要なライブラリは`py-env`内に記載しましょう」。

# csv受け取り部分のJS
データをドラッグ&ドロップで受け取り、データとカラムを適当な要素にぶち込んでPyScriptで読み取れるようにしておきます。
また、カラムを取得してプルダウンを2つ用意して、散布図のX軸とY軸のデータを選択できるようにします。

```js:test.js

    var dropZone = document.getElementById('drop-zone');
    var preview = document.getElementById('preview');
    var fileInput = document.getElementById('file-input');

    dropZone.addEventListener('dragover', function(e) {
        e.stopPropagation();
        e.preventDefault();
        this.style.background = '#e1e7f0';
    }, false);

    dropZone.addEventListener('dragleave', function(e) {
        e.stopPropagation();
        e.preventDefault();
        this.style.background = '#ffffff';
    }, false);

    fileInput.addEventListener('change', function () {
        dataGet(this.files[0]);
    });

    dropZone.addEventListener('drop', function(e) {
        e.stopPropagation();
        e.preventDefault();
        this.style.background = '#ffffff'; //背景色を白に戻す
        var files = e.dataTransfer.files; //ドロップしたファイルを取得
        if (files.length > 1) return alert('アップロードできるファイルは1つだけです。');
        fileInput.files = files; //inputのvalueをドラッグしたファイルに置き換える。
        dataGet(files[0]);
    }, false);

// データとカラムの配置、プルダウンの作成
async function dataGet(file){
    let text = await file.text();
    document.getElementById('data').textContent = text;
    let col = text.split("\n")[0]
    document.getElementById('col').textContent = col;
    select1 = document.getElementById("col1")
    select2 = document.getElementById("col2")
    col.split(",").forEach(e => {
        let tmp1 = document.createElement('option');
        let tmp2 = document.createElement('option');
        tmp1.value = e
        tmp1.textContent = e
        tmp2.value = e
        tmp2.textContent = e
        select1.appendChild(tmp1)
        select2.appendChild(tmp2)
    });
}
```

本題ではないので適当に流します。


# PyScriptの中身
では本題のPyScriptの中身を見ていきましょう。

```python:py-script
import pandas as pd
import matplotlib.pyplot as plt
import io

def setData(*ags, **kwgs):
  df = pd.read_csv(io.StringIO(Element('data').element.innerText))
  x=df[Element('col1').element.value]
  y=df[Element('col2').element.value]
  fig = plt.figure()
  ax = fig.add_subplot(1,1,1)
  ax.scatter(x,y)
  ax.set_title('first scatter plot')
  ax.set_xlabel('x')
  ax.set_ylabel('y')
  pyscript.write('graph_view', fig)

Element('create_graph').element.onclick = setData
```

まずは関数内から説明していきます。
`Element('data')`は要素内のidがdataの要素を取得してきます。
その要素のelement.innerTextを取得しています。今回はここにcsvデータを仮置きしているのでcsvデータを取得していることになります。

続いて`pd.read_csv(io.StringIO(~~~))`です。
pd.read_csvでは引数のパスのcsvをDataFrameとして読み込んでくれるpandasの関数です。
本来はcsvへのパスを引数に入れるのですが、io.StirngIO(文字列)として挙げることで、csv形式の文字列をそのままDataFrameに変換してくれます。

次に`Element('col1').element.value`によってプルダウンで設定したカラムの名前を取得してきます。
その後そのカラムの情報からデータを取得し、xとyを設定しグラフを作成します。
作成したグラフは`pyscript.write(id, fig)`とすることで第一引数のidを持つ要素にグラフを表示させることができます。

ここがすっごい便利なので今回使ってみたかったんですよね。

関数内部の説明は以上です。
最後の`Element('create_graph').element.onclick = setData`です。
`Element('create_graph')`は先にも言ったようにcreate_graphというidを持つ要素を取得します。
その要素のクリックイベントを`element.onclick = setData`によって設定しており、実行したい関数を入れることで要素をクリックした時に特定の関数を実行させることができます。

コードは以上です。
実行結果は最初に張り付けているので割愛しますね。

# まとめ
matplotlibで可視化した内容を簡単に埋め込めるのは便利ですね。
作り方次第では汎用ツールとして作り込むこともできると思います。
またサーバーを立てておく必要もないのが便利ですね。
ただ、ページの読み込み時間が長かったり、要素の扱いがまだ怪しいのでjsとの併用になってしまう点が難点ですね。
たとえばアップロードデータの取得やプルダウンの作成等もPyScript上でできるようになればかなり便利だと思います。

また、外部に書いたpythonscriptをうまく読み込めなかったためファイルを分けられない点が少し面倒かなと思います。
モジュールとして管理することはできる様ですが、まだAPIでもいいかな？と思う部分が多いですね。

それでもmatplotlibで作成したグラフを表示できる点が強いので可視化ツールとして利用していくのはいいのかもしれないですね。
もうちょい作り込んでみようかなと思います。