├── .gitignore ├── LICENSE ├── MANIFEST.in ├── README.md ├── assets ├── clipboard.gif ├── color.gif ├── location.gif ├── plots.gif └── table.gif ├── example ├── color_picker.py ├── custom_js.py ├── data_table.py ├── example.py ├── get_clipboard_data.py ├── retain_state.py └── widget_with_plot.py ├── setup.py └── streamlit_bokeh_events ├── __init__.py └── frontend ├── .prettierrc ├── package.json ├── public ├── bootstrap.min.css └── index.html ├── src ├── StreamlitBokehEventsComponent.tsx ├── index.tsx └── react-app-env.d.ts └── tsconfig.json /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | lib/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | wheels/ 23 | share/python-wheels/ 24 | *.egg-info/ 25 | .installed.cfg 26 | *.egg 27 | MANIFEST 28 | 29 | # PyInstaller 30 | # Usually these files are written by a python script from a template 31 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 32 | *.manifest 33 | *.spec 34 | 35 | # Installer logs 36 | pip-log.txt 37 | pip-delete-this-directory.txt 38 | 39 | # Unit test / coverage reports 40 | htmlcov/ 41 | .tox/ 42 | .nox/ 43 | .coverage 44 | .coverage.* 45 | .cache 46 | nosetests.xml 47 | coverage.xml 48 | *.cover 49 | *.py,cover 50 | .hypothesis/ 51 | .pytest_cache/ 52 | cover/ 53 | 54 | # Translations 55 | *.mo 56 | *.pot 57 | 58 | # Django stuff: 59 | *.log 60 | local_settings.py 61 | db.sqlite3 62 | db.sqlite3-journal 63 | 64 | # Flask stuff: 65 | instance/ 66 | .webassets-cache 67 | 68 | # Scrapy stuff: 69 | .scrapy 70 | 71 | # Sphinx documentation 72 | docs/_build/ 73 | 74 | # PyBuilder 75 | .pybuilder/ 76 | target/ 77 | 78 | # Jupyter Notebook 79 | .ipynb_checkpoints 80 | 81 | # IPython 82 | profile_default/ 83 | ipython_config.py 84 | 85 | # pyenv 86 | # For a library or package, you might want to ignore these files since the code is 87 | # intended to run in multiple environments; otherwise, check them in: 88 | # .python-version 89 | 90 | # pipenv 91 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 92 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 93 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 94 | # install all needed dependencies. 95 | #Pipfile.lock 96 | 97 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow 98 | __pypackages__/ 99 | 100 | # Celery stuff 101 | celerybeat-schedule 102 | celerybeat.pid 103 | 104 | # SageMath parsed files 105 | *.sage.py 106 | 107 | # Environments 108 | .env 109 | .venv 110 | env/ 111 | venv/ 112 | ENV/ 113 | env.bak/ 114 | venv.bak/ 115 | 116 | # Spyder project settings 117 | .spyderproject 118 | .spyproject 119 | 120 | # Rope project settings 121 | .ropeproject 122 | 123 | # mkdocs documentation 124 | /site 125 | 126 | # mypy 127 | .mypy_cache/ 128 | .dmypy.json 129 | dmypy.json 130 | 131 | # Pyre type checker 132 | .pyre/ 133 | 134 | # pytype static type analyzer 135 | .pytype/ 136 | 137 | # Cython debug symbols 138 | cython_debug/ 139 | 140 | .DS_STORE 141 | **/node_modules/** 142 | scripts/flow/*/.flowconfig 143 | .flowconfig 144 | *~ 145 | *.pyc 146 | .grunt 147 | _SpecRunner.html 148 | __benchmarks__ 149 | build/ 150 | remote-repo/ 151 | coverage/ 152 | .module-cache 153 | fixtures/dom/public/react-dom.js 154 | fixtures/dom/public/react.js 155 | test/the-files-to-test.generated.js 156 | *.log* 157 | chrome-user-data 158 | *.sublime-project 159 | *.sublime-workspace 160 | .idea 161 | *.iml 162 | .vscode 163 | *.swp 164 | *.swo 165 | 166 | packages/react-devtools-core/dist 167 | packages/react-devtools-extensions/chrome/build 168 | packages/react-devtools-extensions/chrome/*.crx 169 | packages/react-devtools-extensions/chrome/*.pem 170 | packages/react-devtools-extensions/firefox/build 171 | packages/react-devtools-extensions/firefox/*.xpi 172 | packages/react-devtools-extensions/firefox/*.pem 173 | packages/react-devtools-extensions/shared/build 174 | packages/react-devtools-extensions/.tempUserDataDir 175 | packages/react-devtools-inline/dist 176 | packages/react-devtools-shell/dist 177 | 178 | # javascript/typescipt packaging files 179 | package-lock.json 180 | yarn.lock 181 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2018 The Python Packaging Authority 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all 11 | copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | SOFTWARE. 20 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | recursive-include streamlit_bokeh_events/frontend/build * 2 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # NOTE 2 | Due to some personal issues I am unable to maintain the project for quite a while. 3 | I am archiving this project and there wont be further updates in it. 4 | Thanks to those who used the package. 5 | 6 | # Streamlit Bokeh Events 7 | A streamlit component for bi-directional communication with bokeh plots. 8 | 9 | #### Its just a workaround till streamlit team releases support for bi-directional communication with plots. 10 | 11 | ## Demo 12 | 13 | #### Plots 14 |  15 | --- 16 | #### Widgets 17 | 18 | #### Data Tables 19 |  20 | 21 | #### Color Picker 22 |  23 | 24 | #### Run Any Custom JS 25 |  26 | 27 | #### Get data from Clipboard 28 |  29 | 30 | ## Installation 31 | 32 | ```bash 33 | pip install streamlit-bokeh-events 34 | ``` 35 | ## Usage 36 | 37 | ```python 38 | import streamlit as st 39 | from bokeh.plotting import figure 40 | from bokeh.models import ColumnDataSource, CustomJS 41 | 42 | # import function 43 | from streamlit_bokeh_events import streamlit_bokeh_events 44 | 45 | # create plot 46 | p = figure(tools="lasso_select") 47 | cds = ColumnDataSource( 48 | data={ 49 | "x": [1, 2, 3, 4], 50 | "y": [4, 5, 6, 7], 51 | } 52 | ) 53 | p.circle("x", "y", source=cds) 54 | 55 | # define events 56 | cds.selected.js_on_change( 57 | "indices", 58 | CustomJS( 59 | args=dict(source=cds), 60 | code=""" 61 | document.dispatchEvent( 62 | new CustomEvent("YOUR_EVENT_NAME", {detail: {your_data: "goes-here"}}) 63 | ) 64 | """ 65 | ) 66 | ) 67 | 68 | # result will be a dict of {event_name: event.detail} 69 | # events by default is "", in case of more than one events pass it as a comma separated values 70 | # event1,event2 71 | # debounce is in ms 72 | # refresh_on_update should be set to False only if we dont want to update datasource at runtime 73 | # override_height overrides the viewport height 74 | result = streamlit_bokeh_events( 75 | bokeh_plot=p, 76 | events="YOUR_EVENT_NAME", 77 | key="foo", 78 | refresh_on_update=False, 79 | override_height=600, 80 | debounce_time=500) 81 | 82 | # use the result 83 | st.write(result) 84 | ``` 85 | -------------------------------------------------------------------------------- /assets/clipboard.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ash2shukla/streamlit-bokeh-events/7cf423def48cf4611d84d589d5784bbc8c3e89f9/assets/clipboard.gif -------------------------------------------------------------------------------- /assets/color.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ash2shukla/streamlit-bokeh-events/7cf423def48cf4611d84d589d5784bbc8c3e89f9/assets/color.gif -------------------------------------------------------------------------------- /assets/location.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ash2shukla/streamlit-bokeh-events/7cf423def48cf4611d84d589d5784bbc8c3e89f9/assets/location.gif -------------------------------------------------------------------------------- /assets/plots.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ash2shukla/streamlit-bokeh-events/7cf423def48cf4611d84d589d5784bbc8c3e89f9/assets/plots.gif -------------------------------------------------------------------------------- /assets/table.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ash2shukla/streamlit-bokeh-events/7cf423def48cf4611d84d589d5784bbc8c3e89f9/assets/table.gif -------------------------------------------------------------------------------- /example/color_picker.py: -------------------------------------------------------------------------------- 1 | import streamlit as st 2 | from bokeh.models.widgets import ColorPicker 3 | from bokeh.models import CustomJS 4 | from streamlit_bokeh_events import streamlit_bokeh_events 5 | 6 | selected_color = "#ff4466" 7 | color_picker = ColorPicker(color=selected_color, title="Choose color:", width=200) 8 | color_picker.js_on_change("color", CustomJS(code=""" 9 | document.dispatchEvent(new CustomEvent("COLOR_PICKED", {detail: {pickedColor: cb_obj.color}})) 10 | """)) 11 | result = streamlit_bokeh_events( 12 | color_picker, 13 | events="COLOR_PICKED", 14 | key="picker", 15 | refresh_on_update=False, 16 | override_height=75, 17 | debounce_time=0) 18 | 19 | if result: 20 | if "COLOR_PICKED" in result: 21 | selected_color = result.get("COLOR_PICKED")["pickedColor"] 22 | 23 | st.write(selected_color) -------------------------------------------------------------------------------- /example/custom_js.py: -------------------------------------------------------------------------------- 1 | import streamlit as st 2 | from bokeh.models.widgets import Button 3 | from bokeh.models import CustomJS 4 | from streamlit_bokeh_events import streamlit_bokeh_events 5 | 6 | loc_button = Button(label="Get Location") 7 | loc_button.js_on_event("button_click", CustomJS(code=""" 8 | navigator.geolocation.getCurrentPosition( 9 | (loc) => { 10 | document.dispatchEvent(new CustomEvent("GET_LOCATION", {detail: {lat: loc.coords.latitude, lon: loc.coords.longitude}})) 11 | } 12 | ) 13 | """)) 14 | result = streamlit_bokeh_events( 15 | loc_button, 16 | events="GET_LOCATION", 17 | key="get_location", 18 | refresh_on_update=False, 19 | override_height=75, 20 | debounce_time=0) 21 | 22 | if result: 23 | if "GET_LOCATION" in result: 24 | st.write(result.get("GET_LOCATION")) -------------------------------------------------------------------------------- /example/data_table.py: -------------------------------------------------------------------------------- 1 | import streamlit as st 2 | import pandas as pd 3 | from bokeh.plotting import figure 4 | from bokeh.models import ColumnDataSource, CustomJS 5 | from bokeh.models import DataTable, TableColumn 6 | from streamlit_bokeh_events import streamlit_bokeh_events 7 | 8 | df = pd.DataFrame({ 9 | "x": [1, 2, 3, 4], 10 | "y": [4, 5, 6, 7], 11 | }) 12 | # create plot 13 | cds = ColumnDataSource(df) 14 | columns = [ 15 | TableColumn(field="x"), 16 | TableColumn(field="y"), 17 | ] 18 | 19 | # define events 20 | cds.selected.js_on_change( 21 | "indices", 22 | CustomJS( 23 | args=dict(source=cds), 24 | code=""" 25 | document.dispatchEvent( 26 | new CustomEvent("INDEX_SELECT", {detail: {data: source.selected.indices}}) 27 | ) 28 | """ 29 | ) 30 | ) 31 | p = DataTable(source=cds, columns=columns) 32 | result = streamlit_bokeh_events(bokeh_plot=p, events="INDEX_SELECT", key="foo", refresh_on_update=False, debounce_time=0, override_height=100) 33 | if result: 34 | if result.get("INDEX_SELECT"): 35 | st.write(df.iloc[result.get("INDEX_SELECT")["data"]]) 36 | -------------------------------------------------------------------------------- /example/example.py: -------------------------------------------------------------------------------- 1 | import streamlit as st 2 | from bokeh.models import ColumnDataSource, CustomJS 3 | from bokeh.plotting import figure 4 | import pandas as pd 5 | import numpy as np 6 | from streamlit_bokeh_events import streamlit_bokeh_events 7 | 8 | @st.cache 9 | def data(): 10 | df = pd.DataFrame({"x": np.random.rand(500), "y": np.random.rand(500), "size": np.random.rand(500) * 10}) 11 | return df 12 | 13 | df = data() 14 | source = ColumnDataSource(df) 15 | 16 | st.subheader("Select Points From Map") 17 | 18 | plot = figure( tools="lasso_select,reset", width=250, height=250) 19 | plot.circle(x="x", y="y", size="size", source=source, alpha=0.6) 20 | 21 | source.selected.js_on_change( 22 | "indices", 23 | CustomJS( 24 | args=dict(source=source), 25 | code=""" 26 | document.dispatchEvent( 27 | new CustomEvent("TestSelectEvent", {detail: {indices: cb_obj.indices}}) 28 | ) 29 | """, 30 | ), 31 | ) 32 | 33 | event_result = streamlit_bokeh_events( 34 | events="TestSelectEvent", 35 | bokeh_plot=plot, 36 | key="foo", 37 | debounce_time=100, 38 | refresh_on_update=False 39 | ) 40 | 41 | # some event was thrown 42 | if event_result is not None: 43 | # TestSelectEvent was thrown 44 | if "TestSelectEvent" in event_result: 45 | st.subheader("Selected Points' Pandas Stat summary") 46 | indices = event_result["TestSelectEvent"].get("indices", []) 47 | st.table(df.iloc[indices].describe()) 48 | 49 | st.subheader("Raw Event Data") 50 | st.write(event_result) 51 | -------------------------------------------------------------------------------- /example/get_clipboard_data.py: -------------------------------------------------------------------------------- 1 | import streamlit as st 2 | from bokeh.models.widgets import Button 3 | from bokeh.models import CustomJS 4 | from streamlit_bokeh_events import streamlit_bokeh_events 5 | from io import StringIO 6 | import pandas as pd 7 | 8 | 9 | copy_button = Button(label="Get Clipboard Data") 10 | copy_button.js_on_event("button_click", CustomJS(code=""" 11 | navigator.clipboard.readText().then(text => document.dispatchEvent(new CustomEvent("GET_TEXT", {detail: text}))) 12 | """)) 13 | result = streamlit_bokeh_events( 14 | copy_button, 15 | events="GET_TEXT", 16 | key="get_text", 17 | refresh_on_update=False, 18 | override_height=75, 19 | debounce_time=0) 20 | 21 | if result: 22 | if "GET_TEXT" in result: 23 | df = pd.read_csv(StringIO(result.get("GET_TEXT"))) 24 | st.table(df) -------------------------------------------------------------------------------- /example/retain_state.py: -------------------------------------------------------------------------------- 1 | from bokeh.models import ColumnDataSource, CustomJS 2 | from bokeh.palettes import Category20 3 | from bokeh.plotting import figure 4 | from bokeh.transform import linear_cmap 5 | import numpy as np 6 | import pandas as pd 7 | import streamlit as st 8 | from sklearn.datasets import make_blobs 9 | # include state from: https://gist.github.com/ash2shukla/ff180d7fbe8ec3a0240f19f4452acde7 10 | from state import provide_state 11 | st.set_page_config(layout="wide") 12 | 13 | from streamlit_bokeh_events import streamlit_bokeh_events 14 | 15 | @st.cache 16 | def load_data(): 17 | X, y = make_blobs(n_features=2, centers=3) 18 | return pd.DataFrame({"x": X[:, 0], "y": X[:, 1], "label": y}) 19 | 20 | @provide_state 21 | def main(state): 22 | df = load_data() 23 | 24 | st.title("Clustering") 25 | st.subheader("Umap Embedding") 26 | 27 | col1, col2 = st.beta_columns([2,1]) 28 | 29 | with col1: 30 | p1 = figure(sizing_mode="stretch_both", 31 | tools="pan,box_select,lasso_select,wheel_zoom,reset", 32 | active_drag="lasso_select", 33 | active_scroll="wheel_zoom", 34 | **(state.zoom or {})) 35 | 36 | cmap = linear_cmap("label", palette=Category20[20], low=df["label"].min(), high=df["label"].max()) 37 | cds = ColumnDataSource(df) 38 | cds.selected.indices = state.selected_points or [] 39 | p1.circle("x", "y", source=cds, color=cmap, line_color=cmap) 40 | 41 | cds.selected.js_on_change("indices", CustomJS(args=dict(source=cds, plot=p1), 42 | code=""" 43 | document.dispatchEvent(new CustomEvent("select_event", 44 | {detail: { 45 | indices: cb_obj.indices, 46 | zoom: { 47 | y_range: [plot.y_range.start, plot.y_range.end], 48 | x_range: [plot.x_range.start, plot.x_range.end] 49 | } 50 | } 51 | })) 52 | """ 53 | ) 54 | ) 55 | event_result = streamlit_bokeh_events(p1, "select_event", key="select_event") 56 | 57 | with col2: 58 | if event_result is not None: 59 | state.selected_points = event_result["select_event"]["indices"] 60 | state.zoom = event_result["select_event"]["zoom"] 61 | st.dataframe(df.iloc[event_result["select_event"]["indices"]]) 62 | 63 | main() -------------------------------------------------------------------------------- /example/widget_with_plot.py: -------------------------------------------------------------------------------- 1 | import streamlit as st 2 | import pandas as pd 3 | from bokeh.plotting import figure 4 | from bokeh.models import ColumnDataSource, CustomJS 5 | from bokeh.models import DataTable, TableColumn 6 | from bokeh.plotting import figure 7 | from streamlit_bokeh_events import streamlit_bokeh_events 8 | 9 | st.set_page_config(layout="wide") 10 | # import function 11 | # from streamlit_bokeh_events import streamlit_bokeh_events 12 | col1, col2 = st.columns(2) 13 | df = pd.read_csv('https://raw.githubusercontent.com/mwaskom/seaborn-data/master/iris.csv') 14 | # create plot 15 | cds = ColumnDataSource(df) 16 | columns = list(map(lambda colname: TableColumn(field=colname, title=colname), df.columns)) 17 | 18 | # define events 19 | cds.selected.js_on_change( 20 | "indices", 21 | CustomJS( 22 | args=dict(source=cds), 23 | code=""" 24 | document.dispatchEvent( 25 | new CustomEvent("INDEX_SELECT", {detail: {data: source.selected.indices}}) 26 | ) 27 | """ 28 | ) 29 | ) 30 | 31 | table = DataTable(source=cds, columns=columns) 32 | with col1: 33 | result = streamlit_bokeh_events( 34 | bokeh_plot=table, 35 | events="INDEX_SELECT", 36 | key="foo", 37 | refresh_on_update=False, 38 | debounce_time=0, 39 | override_height=500 40 | ) 41 | if result: 42 | if result.get("INDEX_SELECT"): 43 | st.write(df.iloc[result.get("INDEX_SELECT")["data"]]) 44 | 45 | plot = figure(tools="lasso_select,zoom_in") 46 | df["colors"] = df.species.replace({"setosa": "#583d72", "versicolor": "#9f5f80", "virginica": "#ffba93"}) 47 | cds_lasso = ColumnDataSource(df) 48 | cds_lasso.selected.js_on_change( 49 | "indices", 50 | CustomJS( 51 | args=dict(source=cds_lasso), 52 | code=""" 53 | document.dispatchEvent( 54 | new CustomEvent("LASSO_SELECT", {detail: {data: source.selected.indices}}) 55 | ) 56 | """ 57 | ) 58 | ) 59 | 60 | plot.circle("sepal_length", "sepal_width", fill_alpha=0.5, color="colors", size=10, line_color=None, source=cds_lasso) 61 | with col2: 62 | result_lasso = streamlit_bokeh_events( 63 | bokeh_plot=plot, 64 | events="LASSO_SELECT", 65 | key="bar", 66 | refresh_on_update=False, 67 | debounce_time=0) 68 | if result_lasso: 69 | if result_lasso.get("LASSO_SELECT"): 70 | st.write(df.iloc[result_lasso.get("LASSO_SELECT")["data"]]) 71 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | import setuptools 2 | 3 | setuptools.setup( 4 | name="streamlit-bokeh-events", 5 | version="0.1.3", 6 | author="Ashish Shukla", 7 | author_email="ash2shukla@gmail.com", 8 | description="A custom streamlit component to return js event values from bokeh plots to streamlit", 9 | long_description="", 10 | long_description_content_type="text/plain", 11 | url="", 12 | packages=setuptools.find_packages(), 13 | include_package_data=True, 14 | classifiers=[], 15 | python_requires=">=3.6", 16 | install_requires=[ 17 | "bokeh>=2.4.1", 18 | "streamlit >= 0.63", 19 | ], 20 | ) 21 | -------------------------------------------------------------------------------- /streamlit_bokeh_events/__init__.py: -------------------------------------------------------------------------------- 1 | from bokeh.embed import json_item 2 | import json 3 | 4 | import os 5 | from random import choices 6 | from string import ascii_letters 7 | import streamlit.components.v1 as components 8 | 9 | _RELEASE = True 10 | 11 | if not _RELEASE: 12 | _component_func = components.declare_component( 13 | "streamlit_bokeh_events", url="http://localhost:3001", 14 | ) 15 | else: 16 | parent_dir = os.path.dirname(os.path.abspath(__file__)) 17 | build_dir = os.path.join(parent_dir, "frontend/build") 18 | _component_func = components.declare_component("streamlit_bokeh_events", path=build_dir) 19 | 20 | 21 | def streamlit_bokeh_events(bokeh_plot=None, events="", key=None, debounce_time=1000, refresh_on_update=True, override_height=None): 22 | """Returns event dict 23 | 24 | Keyword arguments: 25 | bokeh_plot -- Bokeh figure object (default None) 26 | events -- Comma separated list of events dispatched by bokeh eg. "event1,event2,event3" (default "") 27 | debounce_time -- Time in ms to wait before dispatching latest event (default 1000) 28 | refresh_on_update -- Should the chart be re-rendered on refresh (default False) 29 | : Set to False if you are not updating the datasource at runtime 30 | override_height -- Override plot viewport height 31 | """ 32 | if key is None: 33 | raise ValueError("key can not be None.") 34 | 35 | div_id = "".join(choices(ascii_letters, k=16)) 36 | fig_dict = json_item(bokeh_plot, div_id) 37 | json_figure = json.dumps(fig_dict) 38 | component_value = _component_func( 39 | bokeh_plot=json_figure, 40 | events=events, 41 | key=key, 42 | _id=div_id, 43 | default=None, 44 | debounce_time=debounce_time, 45 | refresh_on_update=refresh_on_update, 46 | override_height=override_height 47 | ) 48 | return component_value 49 | 50 | 51 | if not _RELEASE: 52 | import streamlit as st 53 | import pandas as pd 54 | from bokeh.plotting import figure 55 | from bokeh.models import ColumnDataSource, CustomJS 56 | from bokeh.models import DataTable, TableColumn 57 | from bokeh.plotting import figure 58 | 59 | st.set_page_config(layout="wide") 60 | # import function 61 | # from streamlit_bokeh_events import streamlit_bokeh_events 62 | col1, col2 = st.beta_columns(2) 63 | df = pd.read_csv('https://raw.githubusercontent.com/mwaskom/seaborn-data/master/iris.csv') 64 | # create plot 65 | cds = ColumnDataSource(df) 66 | columns = list(map(lambda colname: TableColumn(field=colname, title=colname), df.columns)) 67 | 68 | # define events 69 | cds.selected.js_on_change( 70 | "indices", 71 | CustomJS( 72 | args=dict(source=cds), 73 | code=""" 74 | document.dispatchEvent( 75 | new CustomEvent("INDEX_SELECT", {detail: {data: source.selected.indices}}) 76 | ) 77 | """ 78 | ) 79 | ) 80 | 81 | table = DataTable(source=cds, columns=columns) 82 | with col1: 83 | result = streamlit_bokeh_events( 84 | bokeh_plot=table, 85 | events="INDEX_SELECT", 86 | key="foo", 87 | refresh_on_update=False, 88 | debounce_time=0, 89 | override_height=500 90 | ) 91 | if result: 92 | if result.get("INDEX_SELECT"): 93 | st.write(df.iloc[result.get("INDEX_SELECT")["data"]]) 94 | 95 | plot = figure(tools="lasso_select,zoom_in") 96 | df["colors"] = df.species.replace({"setosa": "#583d72", "versicolor": "#9f5f80", "virginica": "#ffba93"}) 97 | cds_lasso = ColumnDataSource(df) 98 | cds_lasso.selected.js_on_change( 99 | "indices", 100 | CustomJS( 101 | args=dict(source=cds_lasso), 102 | code=""" 103 | document.dispatchEvent( 104 | new CustomEvent("LASSO_SELECT", {detail: {data: source.selected.indices}}) 105 | ) 106 | """ 107 | ) 108 | ) 109 | 110 | plot.circle("sepal_length", "sepal_width", fill_alpha=0.5, color="colors", size=10, line_color=None, source=cds_lasso) 111 | with col2: 112 | result_lasso = streamlit_bokeh_events( 113 | bokeh_plot=plot, 114 | events="LASSO_SELECT", 115 | key="bar", 116 | refresh_on_update=False, 117 | debounce_time=0) 118 | if result_lasso: 119 | if result_lasso.get("LASSO_SELECT"): 120 | st.write(df.iloc[result_lasso.get("LASSO_SELECT")["data"]]) 121 | -------------------------------------------------------------------------------- /streamlit_bokeh_events/frontend/.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "endOfLine": "lf", 3 | "semi": false, 4 | "trailingComma": "es5" 5 | } 6 | -------------------------------------------------------------------------------- /streamlit_bokeh_events/frontend/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "streamlit_bokeh_events", 3 | "version": "0.1.3", 4 | "private": true, 5 | "dependencies": { 6 | "@bokeh/bokehjs": "^2.4.1", 7 | "@testing-library/jest-dom": "^4.2.4", 8 | "@testing-library/react": "^9.3.2", 9 | "@testing-library/user-event": "^7.1.2", 10 | "@types/jest": "^24.0.0", 11 | "@types/node": "^12.0.0", 12 | "@types/react": "^16.9.0", 13 | "@types/react-dom": "^16.9.0", 14 | "react": "^16.13.1", 15 | "react-dom": "^16.13.1", 16 | "react-scripts": "4.0.3", 17 | "streamlit-component-lib": "^1.3.0", 18 | "typescript": "~3.7.2" 19 | }, 20 | "scripts": { 21 | "start": "react-scripts start", 22 | "build": "react-scripts build", 23 | "test": "react-scripts test", 24 | "eject": "react-scripts eject" 25 | }, 26 | "eslintConfig": { 27 | "extends": "react-app" 28 | }, 29 | "browserslist": [ 30 | ">0.2%", 31 | "not dead", 32 | "not op_mini all" 33 | ], 34 | "homepage": "." 35 | } 36 | -------------------------------------------------------------------------------- /streamlit_bokeh_events/frontend/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 |
4 |