From 6c86f96ad3c1bc5ebc4f024ef1ac80413a081666 Mon Sep 17 00:00:00 2001 From: edsaa Date: Thu, 16 Nov 2023 19:34:46 -0600 Subject: [PATCH] Add use_container_width option --- stpyvista/CHANGELOG.md | 13 +++- stpyvista/pyproject.toml | 9 +-- stpyvista/requirements.txt | 1 - stpyvista/src/stpyvista/__init__.py | 95 +++++++++++++----------- stpyvista/src/stpyvista/__main__.py | 4 +- stpyvista/src/stpyvista/frontend/main.js | 29 ++++++-- stpyvista/test/cube.py | 18 ++++- 7 files changed, 105 insertions(+), 64 deletions(-) diff --git a/stpyvista/CHANGELOG.md b/stpyvista/CHANGELOG.md index 884907b..9cc39e1 100644 --- a/stpyvista/CHANGELOG.md +++ b/stpyvista/CHANGELOG.md @@ -1,26 +1,31 @@ # Changelog +## [v 0.0.10] - 2023-11-16 +- Add `use_container_width` option to `stpyvista`. Defaults to True +- Changed bokeh.resources to import CDN instead of INLINE to html generation. +- Drop ipython from the dependencies. + ## [v 0.0.9] - 2023-09-05 - Use hatchling for building package. Remove setup.py support. - Remove `panel` as the jupyter_notebook backend. Not necessary if reading from panel. - Move changelog from README.md to CHANGELOG.md -# [v 0.0.8] +## [v 0.0.8] - Remove excessive whitespace below the rendered component. - Can pass additional kwargs to panel.pane.vtk, e.g. setting an orientation_widget. Check panel-vtk for details on valid kwargs. -# [v 0.0.6] +## [v 0.0.6] - Replaced `pythreejs` backend for `panel` backend. This is a temporary solution as pyvista will remove panel support in favor of trame. -# [v 0.0.5] +## [v 0.0.5] - Support transparent backgrounds to blend with streamlit's web app theme. - Add a control to spin along a certain axis the first mesh passed to the plotter. -# [v 0.0.4] +## [v 0.0.4] - Pass a key to the stpyvista component to avoid re-rendering at every streamlit interaction - Using ipywidgets `embed_minimal_html` directly instead of pyvista `export_html`. diff --git a/stpyvista/pyproject.toml b/stpyvista/pyproject.toml index 0486e64..0533176 100644 --- a/stpyvista/pyproject.toml +++ b/stpyvista/pyproject.toml @@ -4,13 +4,13 @@ build-backend = "hatchling.build" [project] name = "stpyvista" -version = "0.0.9" +version = "0.0.10" authors = [ - { name="Edwin S", email="esaavedrac@u.northwestern.edu" }, + { name="Edwin Saavedra C.", email="esaavedrac@u.northwestern.edu" }, ] description = "Streamlit component to render pyvista 3D visualizations" readme = "README.md" -requires-python = ">=3.8" +requires-python = ">=3.9" classifiers = [ "Programming Language :: Python :: 3", "License :: OSI Approved :: MIT License", @@ -21,8 +21,7 @@ dependencies = [ "streamlit", "pyvista", "bokeh", - "panel", - "ipython" + "panel" ] [project.urls] diff --git a/stpyvista/requirements.txt b/stpyvista/requirements.txt index 0e14fa3..96d743d 100644 --- a/stpyvista/requirements.txt +++ b/stpyvista/requirements.txt @@ -1,5 +1,4 @@ streamlit pyvista -ipython bokeh panel \ No newline at end of file diff --git a/stpyvista/src/stpyvista/__init__.py b/stpyvista/src/stpyvista/__init__.py index 91b6b8d..ee1c80a 100644 --- a/stpyvista/src/stpyvista/__init__.py +++ b/stpyvista/src/stpyvista/__init__.py @@ -1,52 +1,55 @@ # __init__.py +from io import BytesIO from pathlib import Path -from typing import Optional, Union - -import streamlit as st +from typing import Optional, Literal import streamlit.components.v1 as components - import pyvista as pv -pv.set_jupyter_backend('static') - -#from tempfile import NamedTemporaryFile -from io import BytesIO - import panel as pn -pn.extension('vtk') -from bokeh.resources import INLINE +from bokeh.resources import CDN + +pv.set_jupyter_backend("static") +pn.extension("vtk", sizing_mode="stretch_width") # Tell streamlit that there is a component called stpyvista, # and that the code to display that component is in the "frontend" folder frontend_dir = (Path(__file__).parent / "frontend").absolute() -_component_func = components.declare_component( - "stpyvista", - path=str(frontend_dir) -) +_component_func = components.declare_component("stpyvista", path=str(frontend_dir)) + class stpyvistaTypeError(TypeError): - """ Unsupported format for input""" + """Unsupported format for input""" + pass + # Create the python function that will be called from the front end -def stpyvista( - plotter : pv.Plotter, - horizontal_align : str = "center", - panel_kwargs = None, - key: Optional[str] = None - ) -> None: +HA_MODES = Literal["left", "center", "right"] + +def stpyvista( + plotter: pv.Plotter, + use_container_width: bool = True, + horizontal_align: HA_MODES = "center", + panel_kwargs=None, + key: Optional[str] = None, +) -> None: """ - Renders a HTML_stpyvista as a threejs model. + Renders an interactive pyvisya Plotter in streamlit. Parameters ---------- input: Union[pv.Plotter, HTML_stpyvista] Plotter to render + use_container_width : bool = True + If True, set the dataframe width to the width of the parent container. \ + This takes precedence over the `horizontal_align` argument. \ + Defaults to True + horizontal_align: str = "center" - Either "center", "left" or "right" + Either "center", "left" or "right". Defaults to "center". panel_kwargs: dict | None Optional keyword parameters to pass to pn.panel() Check: @@ -56,7 +59,6 @@ def stpyvista( orientation_widget: bool Show the xyz axis indicator - key: str|None An optional key that uniquely identifies this component. If this is None, and the component's arguments are changed, the component will @@ -67,39 +69,44 @@ def stpyvista( None """ - if isinstance(plotter, pv.Plotter): - + if isinstance(plotter, pv.Plotter): if panel_kwargs is None: panel_kwargs = dict() - + width, height = plotter.window_size - geo_pan_pv = pn.panel( - plotter.ren_win, - width = width, - height = height, - **panel_kwargs) - + + if use_container_width: + geo_pan_pv = pn.panel(plotter.ren_win, height=height, **panel_kwargs) + else: + geo_pan_pv = pn.panel( + plotter.ren_win, height=height, width=width, **panel_kwargs + ) + # Create HTML file model_bytes = BytesIO() - geo_pan_pv.save(model_bytes, resources=INLINE) - panel_html = model_bytes.getvalue().decode('utf-8') + geo_pan_pv.save(model_bytes, resources=CDN) + panel_html = model_bytes.getvalue().decode("utf-8") model_bytes.close() component_value = _component_func( - panel_html = panel_html, - width = width, - height = height, - horizontal_align = horizontal_align, - key = key, - default = 0) + panel_html=panel_html, + height=height, + width=width, + horizontal_align=horizontal_align, + use_container_width=1 if use_container_width else 0, + key=key, + default=0, + ) return component_value - else: - raise(stpyvistaTypeError) + else: + raise (stpyvistaTypeError) + def main(): pass + if __name__ == "__main__": main() diff --git a/stpyvista/src/stpyvista/__main__.py b/stpyvista/src/stpyvista/__main__.py index 811ce08..5afa6d1 100644 --- a/stpyvista/src/stpyvista/__main__.py +++ b/stpyvista/src/stpyvista/__main__.py @@ -1,7 +1,9 @@ # __main__.py + def main(): pass + if __name__ == "__main__": - main() \ No newline at end of file + main() diff --git a/stpyvista/src/stpyvista/frontend/main.js b/stpyvista/src/stpyvista/frontend/main.js index 88eefb9..0dcf217 100644 --- a/stpyvista/src/stpyvista/frontend/main.js +++ b/stpyvista/src/stpyvista/frontend/main.js @@ -18,22 +18,37 @@ function onRender(event) { if (!window.rendered) { // You most likely want to get the data passed in like this - const {panel_html, width, height, horizontal_align, key} = event.detail.args; + const {panel_html, height, width, horizontal_align, use_container_width, key} = event.detail.args; + const stpyvistadiv = document.getElementById("stpyvistadiv"); const stpyvistaframe = document.getElementById("stpyvistaframe"); + + // Style the wrapping div for the iframe + stpyvistadiv.style.textAlign = horizontal_align; + // Overwrite default iframe dimensions with the container width + if (Boolean(use_container_width)){ + + stpyvistaframe.width = document.body.offsetWidth; + + // Listen to resize changes. If any, panel takes care of resizing + function updateFrameWidth() { + stpyvistaframe.width = document.body.offsetWidth; + } + + window.onresize = function(event) { + updateFrameWidth(); + } + } else { + stpyvistaframe.width = width + 24; + } - // Overwrite default iframe dimensions and put model in the iframe - // just CSS styling does not apply to the iframe stpyvistaframe.srcdoc = panel_html; - stpyvistaframe.width = width + 24; stpyvistaframe.height = height + 20; stpyvistaframe.scrolling = "yes"; - // console.log("WIDTH", width) - // Style the wrapping div for the iframe + // stpyvistadiv.style.width = stpyvistaframe.width + 10; - stpyvistadiv.style.textAlign = horizontal_align; // console.log("HEIGHT", height) Streamlit.setFrameHeight(height + 50); diff --git a/stpyvista/test/cube.py b/stpyvista/test/cube.py index 8cad83d..31e3ffc 100644 --- a/stpyvista/test/cube.py +++ b/stpyvista/test/cube.py @@ -26,8 +26,22 @@ plotter.view_isometric() plotter.background_color = 'lightgray' +chk = st.sidebar.checkbox("use_container_width", False) +align = st.sidebar.select_slider("align", ["left", "center", "right"]) + ## Pass a key to avoid re-rendering at each time something changes in the page -stpyvista(plotter, panel_kwargs=dict(orientation_widget = True)) +stpyvista( + plotter, + use_container_width=chk, + panel_kwargs=dict(orientation_widget = True), + horizontal_align=align +) ## Add something else below -st.text("Jello there"*50) \ No newline at end of file +st.markdown("Jello there"*50) + +cols = st.columns([1,2,1], gap="small") + +for col in cols: + with col: + stpyvista(plotter, use_container_width=chk) \ No newline at end of file