Streamlit aggridで表のカスタマイズ rev1 ~ボタンを付けたり色々~

会社技術

はじめに

社内のDBから情報を抜き取り表示することを検討している。設計データはスカラー以外にもベクトルやマップがあり、このデータを一つの表で表すには、次元に応じた表示が必要だと考える。スカラーは重要な要素なので、こちらをベースに次元数が上がるパラメータはクリックして別のところに表示をと思い、表の中にボタンを作ることを考えた。

課題

streamlitはpythonで出来るのが持ち味だが、aggをカスタマイズするのにJava Scriptの知識が必要になる。大変だ。。。

結果

取り合えず動画をみていただくと、出来栄えが分かるだろう。

ポイント

  • ヘッダーのグループ化:ヘッダーとchildrenの組み合わせで作り込み可能
  • ボタン:いくつかイベントにバリエーションを持たせてみた。特にボタンを押したときのセルへの値反映が苦労
  • レンダーの切り替え:セルの値に応じてレンダーを切り替える部分だが、cellrendereselectorが上手く機能しなかったので、クラス内でparam.valueの値に応じた切り替えを実装

ちなみにこの動画を参考

コード

とりあえず色々書いてあるので、試してみてください。
いまだに分からないところが、cellrendererに関数、クラスを渡すことができるが、両者の違いは何か?関数だとHTMLが上手く渡せなかった。クラスだとできている。。。

import streamlit as st
import pandas as pd
from st_aggrid import AgGrid,JsCode

##########設定###########################
df = pd.read_excel("tmp.xlsx")
df=df.dropna()

st.set_page_config(
    page_title="agg cheat", 
    layout="wide", )

### 全部hello ###
SimpleComp=JsCode('''
function(){
    return 'hello'}
''')

### 元の値を返す ###
SimpleComp1=JsCode('''
function(p){
    return p.value}
''')

ClassComp = JsCode('''
class ClassComp {
    init(params) {
        this.params=params;
        this.eGui = document.createElement('div');
        this.eGui.innerHTML = `
                   <button class="btn-dollar">$</button>
                   <button class="btn-at">@</button>
                   <span>${params.value}</span>
      `;
        this.btnDollar=this.eGui.querySelector('.btn-dollar');
        this.btnAt=this.eGui.querySelector('.btn-at');

        this.btnDollar.onclick=()=>alert('$$$ '+params.value);           
        this.btnAt.onclick=()=>alert('@@@ '+params.value);
    }

    getGui() {
        return this.eGui;
    }

    refresh() {
        return false;
    }

    destroy() { }
};
''')

ClassComp1 = JsCode('''
class ClassComp1 {
    init(params) {
        this.params=params;
        this.eGui = document.createElement('div');
        this.eGui.innerHTML = `
                   <button class="btn-dollar">${params.btnText}</button>
                   <span>${params.value}</span>
      `;
        this.btnDollar=this.eGui.querySelector('.btn-dollar');
        this.btnDollar.onclick=()=>alert('$$$ '+params.value);           
    }

    getGui() {
        return this.eGui;
    }

    refresh() {
        return false;
    }

    destroy() { }
};
''')

### セレクタ込み ###
### 上手くいかない ###
Select=JsCode('''
function(p){
    if (p.value==="C"){
        return '<a href="https://www.google.com">p.value</a>'

        }else{
            return p.value
        }}''')

Select1=JsCode('''
class ClassComp1k {
    init(params) {
        this.params=params;
        if (this.params.value.match("C")){
            this.eGui = document.createElement('div');
            this.eGui.innerHTML = `
                   <button class="btn-dollar">${params.value}</button>
                   <span>${params.value}</span>`;
            this.btnDollar=this.eGui.querySelector('.btn-dollar');
            this.btnDollar.onclick=()=>{
                alert('$$$ '+params.value);
                if(this.params.getValue() .match('clicked')) {
                    this.params.setValue('C');
                } else {
                    this.params.setValue('C clicked');
                } }       
        }else{
            this.eGui = document.createElement('div');
            this.eGui.innerHTML = `
                   <span>${params.value}</span>`;}
    }

    getGui() {
        return this.eGui;
    }

    refresh() {
        return false;
    }

    destroy() { }
};
''')

#########################################

st.markdown("test")

gridOP={
    "defaultColDef": {
    "sortable": "true",
    "resizable": "true",
    "filter": "true",
    "editable":True,},    
    "columnDefs":[
    {"headerName":"",
    "children":[
        {"field":"index",
         "pinned":"left"}]},
    {"headerName":"Gr1",
    "children":[
        {"field":"data1","cellRenderer":SimpleComp
         },{"field":"data2","checkboxSelection":True,},
        {"headerName":"Gr2",
        "children":[{"field":"data3",
                     "cellRenderer":Select1
                     }]}]},
    {"headerName":"Gr3",
    "children":[
        {"field":"data4","cellRenderer":ClassComp},
        {"field":"data5","cellRenderer":ClassComp1,"cellRendererParams":{"btnText":"Amazing"}},
        {"field":"data6","cellRenderer":JsCode('''function(p){
                                               return 'Age is ' + p.value
        }''')
        }]}]}

tmp=AgGrid(df,gridOptions=gridOP,
       layout="wide",
       key="jitan",
       allow_unsafe_jscode = True
       )

tmp['data']
tmp["selected_rows"]

##### 仮置き ###########

Select2=JsCode('''
class BtnCellRenderer {
    init(params) {
        this.params = params;
        if (this.params.value==="C"){

            this.eGui = document.createElement('div');
            this.eGui.innerHTML = `
            <span>
                <button id='click-button' 
                    class='btn-simple' 
                    style='color: ${this.params.color}; background-color: ${this.params.background_color}'>Click!</button>
            </span>
        `;

            this.eButton = this.eGui.querySelector('#click-button');

            this.btnClickedHandler = this.btnClickedHandler.bind(this);
            this.eButton.addEventListener('click', this.btnClickedHandler);
        }else{
            this.eGui = document.createElement('div');
            this.eGui.innerHTML = `
                   <span>${params.value}</span>`;}                    
    }

    getGui() {
        return this.eGui;
    }

    refresh() {
        return true;
    }

    destroy() {
        if (this.eButton) {
            this.eGui.removeEventListener('click', this.btnClickedHandler);
        }
    }

    btnClickedHandler(event) {
        if (confirm('Are you sure you want to CLICK?') == true) {
            if(this.params.getValue() == 'clicked') {
                this.refreshTable('');
            } else {
                this.refreshTable('clicked');
            }
                console.log(this.params);
                console.log(this.params.getValue());
            }
        }

    refreshTable(value) {
        this.params.setValue(value);
    }
};
''')

ちなみにエクセルに書かれている情報は以下のとおり

前回記事のようにヘッダー名をデータ中に書く必要はない。。

おしまい

この記事が参考になったら幸いです。ぜひHP内のアフィを活用ください。。。(お金に余裕あれば)

コメント