学びたいこと
下記のデータ連携方法について学ぶこと
- python → react.js
- react.js → python
Streamlitのテンプレート
これを見ていると、そもそもStreamlitに追加できるコンポーネントには2つあるようだ。
- REACTを使う場合
- REACTを使わない場合
現在、検討しているアプリケーションはCytoscape.JsとVis.Jsを表示させるものであるから、そもそもREACTを使う必要が無い。
加えて言えば、StreamlitがReact作成を代行するものであり、Reactを挫折した自分にしてみれば、StreamlitのコンポーネントをREACT経由にすること自体が間違っているのである。
React無しパターン
参考にしたリポジトリ
やりたいことに特化したリポジトリを発見しました。
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を賢く使うことが、要求される時代になったのではないかと思います。
ポイント
JavaScriptの根幹にかかわる部分を少し勉強しました。
https://developer.mozilla.org/ja/docs/Web/API/CustomEvent/CustomEvent
つまりカスタムイベントを使って、detailにデータを載せればいいのですね。
とりあえず PYTHON→ JavaScript の部分は、だいたい理解できました。
// カスタムイベントを作成
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" がコンソールの出力される
streamlitでの受け取り
これは簡単でした。
// 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連携だけで連携できるじゃないですか・・・