LoginSignup
0
0

More than 1 year has passed since last update.

Streamlit / Cytoscape.js 連携

Last updated at Posted at 2022-08-20

学びたいこと

下記のデータ連携方法について学ぶこと

  • python → react.js
  • react.js → python

Streamlitのテンプレート

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

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

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

React無しパターン

参考にしたリポジトリ

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

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を賢く使うことが、要求される時代になったのではないかと思います。 

ポイント

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" がコンソールの出力される

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連携だけで連携できるじゃないですか・・・

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0