Skip to content

Commit 3c70c51

Browse files
committed
Implement dataview for HTML representations
1 parent 8deca2a commit 3c70c51

File tree

4 files changed

+140
-3
lines changed

4 files changed

+140
-3
lines changed

stpyvista/CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
# Changelog
22

3+
## [v 0.0.18] - 2024-05-17
4+
- Introduce `dataview` to display HTML representation of pyvista data
5+
36
## [v 0.0.17] - 2024-04-11
47
- Rewrite buffer using context manager
58
- Introduce and experimental viewer based on trame and vanilla vtk-js

stpyvista/pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
44

55
[project]
66
name = "stpyvista"
7-
version = "0.0.17"
7+
version = "0.0.18"
88
authors = [
99
{ name="Edwin Saavedra C.", email="esaavedrac@u.northwestern.edu" },
1010
]

stpyvista/src/stpyvista/__init__.py

Lines changed: 78 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,15 @@
44
from pathlib import Path
55
from typing import Optional, Literal
66
import base64
7+
import xml.dom.minidom
78

9+
import streamlit as st
810
import streamlit.components.v1 as components
11+
912
from pyvista.plotting import Plotter
13+
from pyvista import DataSet
1014

1115
import panel as pn
12-
1316
from bokeh.resources import CDN, INLINE
1417

1518
pn.extension("vtk", sizing_mode="stretch_both")
@@ -32,7 +35,9 @@ class stpyvistaValueError(ValueError):
3235
)
3336

3437

35-
def experimental_vtkjs(vtksz_data: bytes, height: int = 400, key: Optional[str] = None) -> dict:
38+
def experimental_vtkjs(
39+
vtksz_data: bytes, height: int = 400, key: Optional[str] = None
40+
) -> dict:
3641
"""
3742
Renders an interactive Pyvista Plotter in streamlit.
3843
@@ -149,6 +154,77 @@ def stpyvista(
149154
raise stpyvistaTypeError(f"{plotter} is not a `pyvista.Plotter` instance. ")
150155

151156

157+
def dataview(obj: DataSet):
158+
"""
159+
Renders the HTML representation of a Pyvista/VTK dataset.
160+
161+
Parameters
162+
----------
163+
element: pv.DataSet
164+
Pyvista element with some data to show.
165+
166+
Returns
167+
-------
168+
None
169+
"""
170+
171+
def assemble_details(title: str, table: str) -> str:
172+
return f"<details open><summary><em>{title}</em></summary>{table}</details>"
173+
174+
try:
175+
# Look up an HTML representation and arange in details tags
176+
177+
dom = xml.dom.minidom.parseString(obj._repr_html_())
178+
tables = dom.getElementsByTagName("table")
179+
180+
css = """
181+
<style>
182+
details {
183+
padding: 6px;
184+
margin-bottom: 5px;
185+
border: 1px solid #eeeeee;
186+
background-color: transparent;
187+
border-radius: 10px;
188+
}
189+
summary {
190+
background-color: transparent;
191+
opacity: 60%;
192+
padding: 5px;
193+
}
194+
summary::marker {
195+
color: purple;
196+
font-size: 1.1em;
197+
}
198+
</style>
199+
"""
200+
201+
html = StringIO("""<div class="stpv-dataview">""")
202+
203+
if len(tables) == 1:
204+
html.write(assemble_details("Header", tables[0].toprettyxml()))
205+
206+
else:
207+
headers = (
208+
dom.getElementsByTagName("table")[0]
209+
.getElementsByTagName("tr")[0]
210+
.getElementsByTagName("th")
211+
)
212+
213+
for title, table in zip(headers, tables[1:]):
214+
html.write(
215+
assemble_details(title.firstChild.nodeValue, table.toprettyxml())
216+
)
217+
218+
html.write(css + "</div>")
219+
220+
# Render in streamlit
221+
st.html(html.getvalue())
222+
223+
except AttributeError:
224+
# Defaults to streamlit's write function
225+
st.write(obj)
226+
227+
152228
def main():
153229
pass
154230

stpyvista/test/dataview.py

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
import streamlit as st
2+
import pyvista as pv
3+
import numpy as np
4+
from stpyvista import stpyvista, dataview
5+
6+
def put_in_plotter(actor: pv.DataSet):
7+
plotter = pv.Plotter()
8+
plotter.window_size = (300, 300)
9+
plotter.add_mesh(actor, color='purple', line_width=10)
10+
plotter.view_isometric()
11+
return plotter
12+
13+
def sphere():
14+
return pv.Sphere(radius=1.0, center=(0, 0, 0))
15+
16+
def spline():
17+
theta = np.linspace(-1 * np.pi, 1 * np.pi, 100)
18+
z = np.linspace(2, -2, 100)
19+
r = z**2 + 1
20+
x = r * np.sin(theta)
21+
y = r * np.cos(theta)
22+
points = np.column_stack((x, y, z))
23+
return pv.Spline(points, 1000)
24+
25+
def surface():
26+
x = np.arange(-10, 10, 0.5)
27+
y = np.arange(-10, 10, 0.5)
28+
x, y = np.meshgrid(x, y)
29+
z = np.sin(np.sqrt(x**2 + y**2))
30+
surf = pv.StructuredGrid(x, y, z)
31+
x, y, z = surf.cell_centers().points.T
32+
surf["my_scalar"] = x * y * z
33+
34+
return surf
35+
36+
37+
def main():
38+
39+
st.title("Testing `dataview`")
40+
41+
datasets = [
42+
sphere(),
43+
spline(),
44+
surface()
45+
]
46+
47+
for obj in datasets:
48+
cols = st.columns([1, 1.5])
49+
50+
with cols[0]:
51+
stpyvista(put_in_plotter(obj))
52+
with cols[1]:
53+
dataview(obj)
54+
55+
dataview(pv.MultiBlock(datasets))
56+
57+
if __name__ == "__main__":
58+
main()

0 commit comments

Comments
 (0)