Streamlit / Cytoscape.js 連携

# 学びたいこと
下記のデータ連携方法について学ぶこと
* python　→　react.js
* react.js　→　python

# Streamlitのテンプレート
https://github.com/streamlit/component-template

これを見ていると、そもそもStreamlitに追加できるコンポーネントには２つあるようだ。

* REACTを使う場合
* REACTを使わない場合

 現在、検討しているアプリケーションはCytoscape.JsとVis.Jsを表示させるものであるから、そもそもREACTを使う必要が無い。
加えて言えば、StreamlitがReact作成を代行するものであり、Reactを挫折した自分にしてみれば、StreamlitのコンポーネントをREACT経由にすること自体が間違っているのである。

# React無しパターン
https://github.com/streamlit/component-template/tree/master/template-reactless


# 参考にしたリポジトリ
やりたいことに特化したリポジトリを発見しました。

https://github.com/mateusccoelho/streamlit-bd-cytoscapejs

```streamlit-bd-cytoscapejs.jsx

import { Streamlit, RenderData } from "streamlit-component-lib"
import Cytoscape from "cytoscape"
import deepEqual from "deep-equal"

// Creates a graph instance
const cy_div = document.body.appendChild(document.createElement("div"))
var cy = Cytoscape({
  container: cy_div
});

// Returns the tapped node's id to python
cy.on('tap', function(evt){
  var target = evt.target;
  if(target !== cy) {
    Streamlit.setComponentValue(target.id())
  }
});

var past_elements = {}

function onRender(event: Event): void {
  // Unpacking input from python
  const data = (event as CustomEvent<RenderData>).detail
  let elements = data.args["elements"]
  let stylesheet = data.args["stylesheet"]
  let layout = data.args["layout"]

  console.log(elements)

  // Draw the graph if it's empty or if it changed its elements
  if(cy.nodes().size() === 0 || !deepEqual(past_elements, elements)){
    // Fix the height of the graph
    cy_div.setAttribute("style", "height:400px;")

    cy.elements().remove()
    cy.add(elements)
    past_elements = elements
    cy.style(stylesheet)

    // Set default options if the layout is not specified
    let height = cy_div.offsetHeight
    let width = cy_div.offsetWidth
    let bounding_box = {x1: 0, y1: 0, x2: width, y2: height, w: width, h: height}
    if(Object.keys(layout).length === 0){
      layout = {
        name: "grid",
        boundingBox: bounding_box
      }
    } else {
      layout['boundingBox'] = bounding_box
    }

    cy.layout(layout).run()
  }

  Streamlit.setFrameHeight()
}

// Activating internal Streamlit functions
Streamlit.events.addEventListener(Streamlit.RENDER_EVENT, onRender)
Streamlit.setComponentReady()
Streamlit.setFrameHeight()

```

# HTMLでコンポーネント化
　さらに、きな臭い記事を見つけました。こちらは更に直接的です。
要するに、複雑な入れ子を駆使するぐらいなら、直接書いてしまった方が楽！
今後、Frontendを賢く使うことが、要求される時代になったのではないかと思います。　

https://discuss.streamlit.io/t/code-snippet-create-components-without-any-frontend-tooling-no-react-babel-webpack-etc/13064

# ポイント
 JavaScriptの根幹にかかわる部分を少し勉強しました。
https://developer.mozilla.org/ja/docs/Web/API/CustomEvent/CustomEvent

　つまりカスタムイベントを使って、detailにデータを載せればいいのですね。

　とりあえず PYTHON→　JavaScript の部分は、だいたい理解できました。

```test.js
// カスタムイベントを作成
const catFound = new CustomEvent('animalfound', {
  detail: {
    name: 'cat'
  }
});
const dogFound = new CustomEvent('animalfound', {
  detail: {
    name: 'dog'
  }
});

// 適切なイベントリスナーを追加
obj.addEventListener('animalfound', (e) => console.log(e.detail.name));

// イベントの配信
obj.dispatchEvent(catFound);
obj.dispatchEvent(dogFound);

// "cat" および "dog" がコンソールの出力される
```

https://www.hypertextcandy.com/javascript-custom-events

# streamlitでの受け取り
これは簡単でした。


```test.js
// Add a click handler to our button. It will send data back to Streamlit.
let numClicks = 0
let isFocused = false
button.onclick = function(): void {
  // Increment numClicks, and pass the new value back to
  // Streamlit via `Streamlit.setComponentValue`.
  numClicks += 1
  Streamlit.setComponentValue(numClicks)
}
```

# LocalStrageを使う

しかしながら、もっと簡単にできないのかを検討していて、StreamlitとLocalStrageをつなぐリポジトリを発見
これなら、REACT-COMPONENTはREACT-APPで別サーバーで動かして置き、ストレージをLOCALSTRAGEでやり取りすればいい
疎結合だし、COMPONENTをIFRAMEで持ってくれば、API連携だけで連携できるじゃないですか・・・

https://github.com/gagangoku/streamlit-ws-localstorage