Skip to content

Commit

Permalink
fix: redesigning shiny and reusing _create_html_doc for iframe genera…
Browse files Browse the repository at this point in the history
…tion
  • Loading branch information
krishnanand5 committed Aug 22, 2024
1 parent 3f718a7 commit ad37451
Show file tree
Hide file tree
Showing 5 changed files with 141 additions and 32 deletions.
52 changes: 52 additions & 0 deletions example/py-shiny/example_pyshiny_reactive_scatter.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import matplotlib.pyplot as plt
import seaborn as sns
from shiny import App, ui

from maidr.widget.shiny import render_maidr

# Load the dataset
iris = sns.load_dataset("iris")

# Define the UI components for the Shiny application
app_ui = ui.page_fluid(
ui.row(
ui.column(
3,
ui.input_select(
"x_var",
"Select X variable:",
choices=iris.select_dtypes(include=["float64"]).columns.tolist(),
selected="sepal_length",
),
ui.input_select(
"y_var",
"Select Y variable:",
choices=iris.select_dtypes(include=["float64"]).columns.tolist(),
selected="sepal_width",
),
),
ui.column(9, ui.output_ui("create_reactivebarplot")),
)
)


# Define the server
def server(input, output, session):
def create_reactivebarplot():
fig, ax = plt.subplots(figsize=(10, 6))
s_plot = sns.scatterplot(
data=iris, x=input.x_var(), y=input.y_var(), hue="species", ax=ax
)
ax.set_title(f"Iris {input.y_var()} vs {input.x_var()}")
ax.set_xlabel(input.x_var().replace("_", " ").title())
ax.set_ylabel(input.y_var().replace("_", " ").title()) # Hello
return s_plot
output.reactivebarplot = render_maidr(create_reactivebarplot)


# Create the app
app = App(app_ui, server)

# Run the app
if __name__ == "__main__":
app.run()
3 changes: 2 additions & 1 deletion maidr/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,11 @@
lineplot,
scatterplot,
)
from .api import close, save_html, show, stacked
from .api import close, render, save_html, show, stacked

__all__ = [
"close",
"render",
"save_html",
"show",
"stacked",
Expand Down
9 changes: 9 additions & 0 deletions maidr/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

from typing import Literal, Any

from htmltools import Tag
from matplotlib.axes import Axes
from matplotlib.container import BarContainer

Expand All @@ -10,6 +11,14 @@
from maidr.core.figure_manager import FigureManager


def render(
plot: Any, *, lib_prefix: str | None = "lib", include_version: bool = True
) -> Tag:
ax = FigureManager.get_axes(plot)
maidr = FigureManager.get_maidr(ax.get_figure())
return maidr.render()


def show(plot: Any, renderer: Literal["auto", "ipython", "browser"] = "auto") -> object:
ax = FigureManager.get_axes(plot)
maidr = FigureManager.get_maidr(ax.get_figure())
Expand Down
51 changes: 20 additions & 31 deletions maidr/core/maidr.py
Original file line number Diff line number Diff line change
Expand Up @@ -52,21 +52,9 @@ def plots(self) -> list[MaidrPlot]:
"""Return the list of plots extracted from the ``fig``."""
return self._plots

def render(
self, *, lib_prefix: str | None = "lib", include_version: bool = True
) -> RenderedHTML:
"""
Render the document.
Parameters
----------
lib_prefix : str, default="lib"
A prefix to add to relative paths to dependency files.
include_version : bool, default=True
Whether to include the version number in the dependency's folder name.
"""
html = self._create_html_doc()
return html.render(lib_prefix=lib_prefix, include_version=include_version)
def render(self) -> Tag:
"""Return the maidr plot inside an iframe."""
return self._create_html_tag()

def save_html(
self, file: str, *, lib_dir: str | None = "lib", include_version: bool = True
Expand All @@ -79,7 +67,8 @@ def save_html(
file : str
The file to save to.
lib_dir : str, default="lib"
The directory to save the dependencies to (relative to the file's directory).
The directory to save the dependencies to
(relative to the file's directory).
include_version : bool, default=True
Whether to include the version number in the dependency folder name.
"""
Expand Down Expand Up @@ -183,20 +172,20 @@ def _inject_plot(plot: HTML, maidr: str) -> Tag:
tags.script(maidr),
)

if Environment.is_interactive_shell():
# If running in an interactive environment (e.g., Jupyter Notebook),
# display the HTML content using an iframe to ensure proper rendering
# and interactivity. The iframe's height is dynamically adjusted
base_html = tags.iframe(
srcdoc=str(base_html.get_html_string()),
width="100%",
height="100%",
scrolling="auto",
style="background-color: #fff",
frameBorder=0,
onload="""
this.style.height = this.contentWindow.document.body.scrollHeight + 100 + 'px';
""",
)
# If running in an interactive environment (e.g., Jupyter Notebook),
# display the HTML content using an iframe to ensure proper rendering
# and interactivity. The iframe's height is dynamically adjusted
base_html = tags.iframe(
srcdoc=str(base_html.get_html_string()),
width="100%",
height="100%",
scrolling="auto",
style="background-color: #fff",
frameBorder=0,
onload="""
this.style.height = this.contentWindow.document.body.scrollHeight +
100 + 'px';
""",
)

return base_html
58 changes: 58 additions & 0 deletions maidr/widget/shiny.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
from __future__ import annotations

from typing import Any, Callable, Coroutine, Optional

from shiny.render import ui
from shiny.types import Jsonifiable

import maidr


class render_maidr(ui):
"""
A custom UI rendering class for Maidr objects in Shiny applications.
This class extends the Shiny UI rendering functionality to handle Maidr objects.
Attributes
----------
fn : Callable[[], Coroutine[Any, Any, Optional[Any]]]
An asynchronous function that returns the value to be rendered.
Methods
-------
render()
Asynchronously renders the Maidr object.
"""

def __init__(self, fn: Callable[[], Coroutine[Any, Any, Optional[Any]]]):
"""
Initialize the render_maidr instance.
Parameters
----------
fn : Callable[[], Coroutine[Any, Any, Optional[Any]]]
An asynchronous function that returns the value to be rendered.
"""
super().__init__(fn)

async def render(self) -> Optional[Jsonifiable]:
"""
Asynchronously render the Maidr object.
This method calls the provided function, renders the result using Maidr,
and transforms it into a Jsonifiable format.
Returns
-------
Optional[Jsonifiable]
The rendered and transformed Maidr object, or
None if the initial value is None.
"""
initial_value: Optional[Any] = await self.fn()
if initial_value is None:
return None

maidr_rendered: Any = maidr.render(initial_value)
transformed: Jsonifiable = await self.transform(maidr_rendered)
return transformed

0 comments on commit ad37451

Please sign in to comment.