From 9272987191096174917b0426835f21a5dbd776b0 Mon Sep 17 00:00:00 2001 From: Riley Thai Date: Fri, 7 Feb 2025 18:22:18 +1100 Subject: [PATCH 1/8] feat: bokeh support --- solara/lab/components/__init__.py | 1 + solara/lab/components/bokehloaded.vue | 27 ++++ solara/lab/components/figurebokeh.py | 73 ++++++++++ solara/website/pages/apps/scatter-bokeh.py | 129 ++++++++++++++++++ .../examples/fullscreen/scatter_bokeh.py | 3 + .../examples/visualization/bokeh.py | 48 +++++++ 6 files changed, 281 insertions(+) create mode 100644 solara/lab/components/bokehloaded.vue create mode 100644 solara/lab/components/figurebokeh.py create mode 100644 solara/website/pages/apps/scatter-bokeh.py create mode 100644 solara/website/pages/documentation/examples/fullscreen/scatter_bokeh.py create mode 100644 solara/website/pages/documentation/examples/visualization/bokeh.py diff --git a/solara/lab/components/__init__.py b/solara/lab/components/__init__.py index 30808840f..7a90c6371 100644 --- a/solara/lab/components/__init__.py +++ b/solara/lab/components/__init__.py @@ -1,5 +1,6 @@ from .chat import ChatBox, ChatInput, ChatMessage # noqa: F401 from .confirmation_dialog import ConfirmationDialog # noqa: F401 +from .figurebokeh import FigureBokeh # noqa: F401 from .input_date import InputDate, InputDateRange # noqa: F401 from .input_time import InputTime as InputTime from .menu import ClickMenu, ContextMenu, Menu # noqa: F401 F403 diff --git a/solara/lab/components/bokehloaded.vue b/solara/lab/components/bokehloaded.vue new file mode 100644 index 000000000..643c48494 --- /dev/null +++ b/solara/lab/components/bokehloaded.vue @@ -0,0 +1,27 @@ + + + + + diff --git a/solara/lab/components/figurebokeh.py b/solara/lab/components/figurebokeh.py new file mode 100644 index 000000000..b97a7dcce --- /dev/null +++ b/solara/lab/components/figurebokeh.py @@ -0,0 +1,73 @@ +from typing import Callable + +import solara +from solara.components.component_vue import component_vue +from bokeh.io import output_notebook +from bokeh.models import Plot +from bokeh.plotting import figure +from bokeh.themes import Theme +from jupyter_bokeh import BokehModel + + +@component_vue("bokehloaded.vue") +def BokehLoaded(loaded: bool, on_loaded: Callable[[bool], None]): + pass + + +def FigureBokeh( + fig, + dependencies=None, + light_theme: str | Theme = "light_minimal", + dark_theme: str | Theme = "dark_minimal", +): + # NOTE: no docstring because not a component. + loaded = solara.use_reactive(False) + dark = solara.lab.use_dark_effective() + fig_key = solara.use_uuid4([]) + output_notebook(hide_banner=True) + BokehLoaded(loaded=loaded.value, on_loaded=loaded.set) + if loaded.value: + # TODO: there's an error with deletion on the doc. do we need to modify the underlying class? + fig_element = BokehModel.element(model=fig).key(fig_key) + + def update_data(): + fig_widget: BokehModel = solara.get_widget(fig_element) + fig_model: Plot | figure = fig_widget._model # base class for figure + if fig != fig_model: # don't run through on first startup + # pause until all updates complete + fig_model.hold_render = True + + # extend renderer set and cull previous + length = len(fig_model.renderers) + fig_model.renderers.extend(fig.renderers) + fig_model.renderers = fig_model.renderers[length:] + + # similarly update plot layout properties + places = ["above", "below", "center", "left", "right"] + for place in places: + attr = getattr(fig_model, place) + newattr = getattr(fig, place) + length = len(attr) + attr.extend(newattr) + if place == "right": + fig_model.hold_render = False + setattr(fig_model, place, attr[length:]) + return + + def update_theme(): + # NOTE: using bokeh.io.curdoc and this _document prop will point to the same object + fig_widget: BokehModel = solara.get_widget(fig_element) + if dark: + fig_widget._document.theme = dark_theme + else: + fig_widget._document.theme = light_theme + + solara.use_effect(update_data, dependencies or fig) + solara.use_effect(update_theme, [dark, loaded.value]) + return fig_element + else: + # NOTE: we don't return this as to not break effect callbacks outside this function + with solara.Card(margin=0, elevation=0): + # the card expands to fit space + with solara.Row(justify="center"): + solara.SpinnerSolara(size="200px") diff --git a/solara/website/pages/apps/scatter-bokeh.py b/solara/website/pages/apps/scatter-bokeh.py new file mode 100644 index 000000000..2ed4f276f --- /dev/null +++ b/solara/website/pages/apps/scatter-bokeh.py @@ -0,0 +1,129 @@ +import pathlib +import sys + +from typing import Optional, cast + +import vaex +import vaex.datasets + +import solara +import solara.lab +from bokeh.models import ColumnDataSource +from bokeh.plotting import figure +from bokeh.transform import linear_cmap, factor_cmap + +github_url = solara.util.github_url(__file__) +if sys.platform != "emscripten": + pycafe_url = solara.util.pycafe_url(path=pathlib.Path(__file__), requirements=["vaex", "bokeh"]) +else: + pycafe_url = None + +df_sample = vaex.datasets.titanic() + + +class State: + color = solara.reactive(cast(Optional[str], None)) + x = solara.reactive(cast(Optional[str], None)) + y = solara.reactive(cast(Optional[str], None)) + df = solara.reactive(cast(Optional[vaex.DataFrame], None)) + + @staticmethod + def load_sample(): + State.x.value = "age" + State.y.value = "fare" + State.color.value = "body" + State.df.value = df_sample + + @staticmethod + def reset(): + State.df.value = None + + +@solara.component +def Page(): + df = State.df.value + selected, on_selected = solara.use_state({"x": [0, 0]}) # noqa: SH101 + + # the PivotTable will set this cross filter + filter, _ = solara.use_cross_filter(id(df), name="scatter") + + # only apply the filter if the filter or dataframe changes + def filter_df(): + if (filter is not None) and (df is not None): + return df[filter] + else: + return df + + dff = solara.use_memo(filter_df, dependencies=[df, filter]) + + with solara.AppBar(): + solara.lab.ThemeToggle() + with solara.Sidebar(): + with solara.Card("Controls", margin=0, elevation=0): + with solara.Column(): + with solara.Row(): + solara.Button("Sample dataset", color="primary", text=True, outlined=True, on_click=State.load_sample, disabled=df is not None) + solara.Button("Clear dataset", color="primary", text=True, outlined=True, on_click=State.reset) + + if df is not None: + columns = df.get_column_names() + solara.Select("Column x", values=columns, value=State.x) + solara.Select("Column y", values=columns, value=State.y) + solara.Select("Color", values=columns, value=State.color) + + solara.provide_cross_filter() + solara.PivotTable(df, ["pclass"], ["sex"], selected=selected, on_selected=on_selected) + + if dff is not None: + source = ColumnDataSource( + data={ + "x": dff[State.x.value].values, + "y": dff[State.y.value].values, + "z": dff[State.color.value].values, + } + ) + if State.x.value and State.y.value: + p = figure(x_axis_label=State.x.value, y_axis_label=State.y.value, width_policy="max", height=700) + + # add a scatter, colorbar, and mapper + color_expr = dff[State.color.value] + if (color_expr.dtype == "string") or (color_expr.dtype == "bool"): + mapper = factor_cmap + factors = color_expr.unique() + try: + factors.remove(None) + except ValueError: + pass + args = dict(palette=f"Viridis{min(11, max(3, color_expr.nunique()))}", factors=factors) + else: + mapper = linear_cmap + args = dict(palette="Viridis256", low=color_expr.min()[()], high=color_expr.max()[()]) + + s = p.scatter(source=source, x="x", y="y", size=12, fill_color=mapper(field_name="z", **args)) + p.add_layout(s.construct_color_bar(title=State.color.value, label_standoff=6, padding=5, border_line_color=None), "right") + + solara.lab.FigureBokeh(p, dark_theme="carbon") + + else: + solara.Warning("Select x and y columns") + + else: + solara.Info("No data loaded, click on the sample dataset button to load a sample dataset, or upload a file.") + + with solara.Column(style={"max-width": "400px"}): + solara.Button(label="View source", icon_name="mdi-github-circle", attributes={"href": github_url, "target": "_blank"}, text=True, outlined=True) + if sys.platform != "emscripten": + solara.Button( + label="Edit this example live on py.cafe", + icon_name="mdi-coffee-to-go-outline", + attributes={"href": pycafe_url, "target": "_blank"}, + text=True, + outlined=True, + ) + + +@solara.component +def Layout(children): + route, routes = solara.use_route() + dark_effective = solara.lab.use_dark_effective() + return solara.AppLayout(children=children, toolbar_dark=dark_effective, color=None) # if dark_effective else "primary") diff --git a/solara/website/pages/documentation/examples/fullscreen/scatter_bokeh.py b/solara/website/pages/documentation/examples/fullscreen/scatter_bokeh.py new file mode 100644 index 000000000..da94f5e66 --- /dev/null +++ b/solara/website/pages/documentation/examples/fullscreen/scatter_bokeh.py @@ -0,0 +1,3 @@ +redirect = "/apps/scatter-bokeh" + +Page = True diff --git a/solara/website/pages/documentation/examples/visualization/bokeh.py b/solara/website/pages/documentation/examples/visualization/bokeh.py new file mode 100644 index 000000000..b58c6c28f --- /dev/null +++ b/solara/website/pages/documentation/examples/visualization/bokeh.py @@ -0,0 +1,48 @@ +"""# Scatter plot using Bokeh + +This example shows how to use Bokeh to create a scatter plot and a select box to do some filtering. + +Inspired by the bokeh documentation. +""" + +from bokeh.models import ColorBar, DataRange1d, LinearColorMapper + +from bokeh.plotting import figure, ColumnDataSource +from bokeh.sampledata import penguins + +import solara + +title = "Scatter plot using Bokeh" + +df = penguins.data + + +@solara.component +def Page(): + all_species = df["species"].unique().tolist() + species = solara.use_reactive(all_species[0]) + with solara.Div() as main: + solara.Select(label="Species", value=species, values=all_species) + dff = df[df["species"] == species.value] + + source = ColumnDataSource( + data={ + "x": dff["bill_length_mm"].values, + "y": dff["bill_depth_mm"].values, + "z": dff["body_mass_g"].values, + } + ) + + # make a figure + p = figure( + x_range=DataRange1d(), y_range=DataRange1d(), x_axis_label="Bill length [mm]", y_axis_label="Bill depth [mm]", width_policy="max", height=400 + ) + + # add a scatter, colorbar, and mapper + mapper = LinearColorMapper(palette="Viridis256", low=dff["body_mass_g"].min(), high=dff["body_mass_g"].max()) + cb = ColorBar(color_mapper=mapper, title="Body mass [g]") + p.scatter(source=source, x="x", y="y", marker="circle", size=8, fill_color={"field": "z", "transform": mapper}) + p.add_layout(cb, "right") + + solara.lab.FigureBokeh(p, dark_theme="carbon", dependencies=[species]) + return main From cb23a9cb12406a894f4d9bc0d9d87b82dc4b7886 Mon Sep 17 00:00:00 2001 From: riley <127359621+rileythai@users.noreply.github.com> Date: Tue, 11 Feb 2025 10:47:06 +1100 Subject: [PATCH 2/8] fix: no main return in example Co-authored-by: Maarten Breddels --- .../website/pages/documentation/examples/visualization/bokeh.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/solara/website/pages/documentation/examples/visualization/bokeh.py b/solara/website/pages/documentation/examples/visualization/bokeh.py index b58c6c28f..d755c27b8 100644 --- a/solara/website/pages/documentation/examples/visualization/bokeh.py +++ b/solara/website/pages/documentation/examples/visualization/bokeh.py @@ -21,7 +21,7 @@ def Page(): all_species = df["species"].unique().tolist() species = solara.use_reactive(all_species[0]) - with solara.Div() as main: + with solara.Div(): solara.Select(label="Species", value=species, values=all_species) dff = df[df["species"] == species.value] From 3efdbe5bafb40b86077f370b784e8a11f92f47ef Mon Sep 17 00:00:00 2001 From: riley <127359621+rileythai@users.noreply.github.com> Date: Tue, 11 Feb 2025 10:47:20 +1100 Subject: [PATCH 3/8] fix: no main return in example ((part II) Co-authored-by: Maarten Breddels --- .../website/pages/documentation/examples/visualization/bokeh.py | 1 - 1 file changed, 1 deletion(-) diff --git a/solara/website/pages/documentation/examples/visualization/bokeh.py b/solara/website/pages/documentation/examples/visualization/bokeh.py index d755c27b8..392607c65 100644 --- a/solara/website/pages/documentation/examples/visualization/bokeh.py +++ b/solara/website/pages/documentation/examples/visualization/bokeh.py @@ -45,4 +45,3 @@ def Page(): p.add_layout(cb, "right") solara.lab.FigureBokeh(p, dark_theme="carbon", dependencies=[species]) - return main From f3437418cd94b0e636973d4c386010ac59492ad4 Mon Sep 17 00:00:00 2001 From: Riley Thai Date: Tue, 11 Feb 2025 15:14:36 +1100 Subject: [PATCH 4/8] fix: FigureBokeh as component - figurebokeh is now a component --- solara/lab/components/figurebokeh.py | 79 ++++++++++++++++++---------- 1 file changed, 52 insertions(+), 27 deletions(-) diff --git a/solara/lab/components/figurebokeh.py b/solara/lab/components/figurebokeh.py index b97a7dcce..e55ec9dd3 100644 --- a/solara/lab/components/figurebokeh.py +++ b/solara/lab/components/figurebokeh.py @@ -14,29 +14,55 @@ def BokehLoaded(loaded: bool, on_loaded: Callable[[bool], None]): pass +@solara.component def FigureBokeh( fig, dependencies=None, light_theme: str | Theme = "light_minimal", dark_theme: str | Theme = "dark_minimal", ): - # NOTE: no docstring because not a component. + """ + Display a Bokeh figure or Plot. + + ## Example + + ```solara + import solara + import solara.lab + from bokeh.plotting import figure + + @solara.component + def Page(): + p = figure(width=600, height=400) + p.line(x=[1, 2, 3, 4, 5], y=[2, 4, 2, 7, 9]) + + return solara.lab.FigureBokeh(p) + ``` + + For performance reasons, you might want to pass in a list of dependencies that indicate when + the figure changed, to avoid re-rendering it on every render. + + ## Arguments + + * fig: `Plot` or `figure` object to display. + * dependencies: List of dependencies to watch for changes, if None, will rerender when `fig` is changed. + * light_theme: The name or `bokeh.themes.Theme` object to use for light mode. Defaults to `"light_minimal"`. + * dark_theme: The name or `bokeh.themes.Theme` object to use for dark mode. Defaults to `"dark_minimal"`. + """ loaded = solara.use_reactive(False) dark = solara.lab.use_dark_effective() - fig_key = solara.use_uuid4([]) output_notebook(hide_banner=True) BokehLoaded(loaded=loaded.value, on_loaded=loaded.set) - if loaded.value: - # TODO: there's an error with deletion on the doc. do we need to modify the underlying class? - fig_element = BokehModel.element(model=fig).key(fig_key) - def update_data(): - fig_widget: BokehModel = solara.get_widget(fig_element) - fig_model: Plot | figure = fig_widget._model # base class for figure - if fig != fig_model: # don't run through on first startup - # pause until all updates complete - fig_model.hold_render = True + # TODO: there's an error with deletion on the doc. do we need to modify the underlying class? + fig_element = BokehModel.element(model=fig) + def update_data(): + fig_widget: BokehModel = solara.get_widget(fig_element) + fig_model: Plot | figure = fig_widget._model # base class for figure + if fig != fig_model: # don't run through on first startup + # pause until all updates complete + with fig_model.hold(render=True): # extend renderer set and cull previous length = len(fig_model.renderers) fig_model.renderers.extend(fig.renderers) @@ -49,25 +75,24 @@ def update_data(): newattr = getattr(fig, place) length = len(attr) attr.extend(newattr) - if place == "right": - fig_model.hold_render = False setattr(fig_model, place, attr[length:]) - return - - def update_theme(): - # NOTE: using bokeh.io.curdoc and this _document prop will point to the same object - fig_widget: BokehModel = solara.get_widget(fig_element) - if dark: - fig_widget._document.theme = dark_theme - else: - fig_widget._document.theme = light_theme - - solara.use_effect(update_data, dependencies or fig) - solara.use_effect(update_theme, [dark, loaded.value]) + + def update_theme(): + # NOTE: using bokeh.io.curdoc and this _document prop will point to the same object + fig_widget: BokehModel = solara.get_widget(fig_element) + if dark: + fig_widget._document.theme = dark_theme + else: + fig_widget._document.theme = light_theme + + solara.use_effect(update_data, dependencies or fig) + solara.use_effect(update_theme, [dark, loaded.value]) + + if loaded.value: return fig_element else: - # NOTE: we don't return this as to not break effect callbacks outside this function + # NOTE: the returned object will be a v.Sheet until Bokeh is loaded + # BUG: this will show the JS error temporarily before loading with solara.Card(margin=0, elevation=0): - # the card expands to fit space with solara.Row(justify="center"): solara.SpinnerSolara(size="200px") From ebff57d8952f9db501f9d13e9fae65cbfaf08bdf Mon Sep 17 00:00:00 2001 From: Riley Thai Date: Tue, 11 Feb 2025 15:14:47 +1100 Subject: [PATCH 5/8] feat: FigureBokeh docs page - now a page in lab --- .../documentation/components/lab/figurebokeh.py | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) create mode 100644 solara/website/pages/documentation/components/lab/figurebokeh.py diff --git a/solara/website/pages/documentation/components/lab/figurebokeh.py b/solara/website/pages/documentation/components/lab/figurebokeh.py new file mode 100644 index 000000000..f2a00c07f --- /dev/null +++ b/solara/website/pages/documentation/components/lab/figurebokeh.py @@ -0,0 +1,17 @@ +""" +# FigureBokeh + +Display a Bokeh figure. + +""" + +import solara +from solara.website.components import NoPage +from solara.website.utils import apidoc + +title = "FigureBokeh" + +__doc__ += apidoc( + solara.lab.components.figurebokeh.FigureBokeh.f) # type: ignore + +Page = NoPage From 47f5221ac44386a9de75da47131867e76fb6c972 Mon Sep 17 00:00:00 2001 From: Riley Thai Date: Tue, 11 Feb 2025 15:29:45 +1100 Subject: [PATCH 6/8] fix: conditional hook (provide) in scatter-bokeh example --- solara/website/pages/apps/scatter-bokeh.py | 74 +++++++++++++++++----- 1 file changed, 57 insertions(+), 17 deletions(-) diff --git a/solara/website/pages/apps/scatter-bokeh.py b/solara/website/pages/apps/scatter-bokeh.py index 2ed4f276f..704373f1d 100644 --- a/solara/website/pages/apps/scatter-bokeh.py +++ b/solara/website/pages/apps/scatter-bokeh.py @@ -14,7 +14,8 @@ github_url = solara.util.github_url(__file__) if sys.platform != "emscripten": - pycafe_url = solara.util.pycafe_url(path=pathlib.Path(__file__), requirements=["vaex", "bokeh"]) + pycafe_url = solara.util.pycafe_url(path=pathlib.Path(__file__), + requirements=["vaex", "bokeh"]) else: pycafe_url = None @@ -43,6 +44,7 @@ def reset(): def Page(): df = State.df.value selected, on_selected = solara.use_state({"x": [0, 0]}) # noqa: SH101 + solara.provide_cross_filter() # the PivotTable will set this cross filter filter, _ = solara.use_cross_filter(id(df), name="scatter") @@ -62,8 +64,17 @@ def filter_df(): with solara.Card("Controls", margin=0, elevation=0): with solara.Column(): with solara.Row(): - solara.Button("Sample dataset", color="primary", text=True, outlined=True, on_click=State.load_sample, disabled=df is not None) - solara.Button("Clear dataset", color="primary", text=True, outlined=True, on_click=State.reset) + solara.Button("Sample dataset", + color="primary", + text=True, + outlined=True, + on_click=State.load_sample, + disabled=df is not None) + solara.Button("Clear dataset", + color="primary", + text=True, + outlined=True, + on_click=State.reset) if df is not None: columns = df.get_column_names() @@ -71,8 +82,9 @@ def filter_df(): solara.Select("Column y", values=columns, value=State.y) solara.Select("Color", values=columns, value=State.color) - solara.provide_cross_filter() - solara.PivotTable(df, ["pclass"], ["sex"], selected=selected, on_selected=on_selected) + solara.PivotTable(df, ["pclass"], ["sex"], + selected=selected, + on_selected=on_selected) if dff is not None: source = ColumnDataSource( @@ -80,10 +92,12 @@ def filter_df(): "x": dff[State.x.value].values, "y": dff[State.y.value].values, "z": dff[State.color.value].values, - } - ) + }) if State.x.value and State.y.value: - p = figure(x_axis_label=State.x.value, y_axis_label=State.y.value, width_policy="max", height=700) + p = figure(x_axis_label=State.x.value, + y_axis_label=State.y.value, + width_policy="max", + height=700) # add a scatter, colorbar, and mapper color_expr = dff[State.color.value] @@ -94,13 +108,25 @@ def filter_df(): factors.remove(None) except ValueError: pass - args = dict(palette=f"Viridis{min(11, max(3, color_expr.nunique()))}", factors=factors) + args = dict( + palette=f"Viridis{min(11, max(3, color_expr.nunique()))}", + factors=factors) else: mapper = linear_cmap - args = dict(palette="Viridis256", low=color_expr.min()[()], high=color_expr.max()[()]) - - s = p.scatter(source=source, x="x", y="y", size=12, fill_color=mapper(field_name="z", **args)) - p.add_layout(s.construct_color_bar(title=State.color.value, label_standoff=6, padding=5, border_line_color=None), "right") + args = dict(palette="Viridis256", + low=color_expr.min()[()], + high=color_expr.max()[()]) + + s = p.scatter(source=source, + x="x", + y="y", + size=12, + fill_color=mapper(field_name="z", **args)) + p.add_layout( + s.construct_color_bar(title=State.color.value, + label_standoff=6, + padding=5, + border_line_color=None), "right") solara.lab.FigureBokeh(p, dark_theme="carbon") @@ -108,15 +134,27 @@ def filter_df(): solara.Warning("Select x and y columns") else: - solara.Info("No data loaded, click on the sample dataset button to load a sample dataset, or upload a file.") + solara.Info( + "No data loaded, click on the sample dataset button to load a sample dataset, or upload a file." + ) with solara.Column(style={"max-width": "400px"}): - solara.Button(label="View source", icon_name="mdi-github-circle", attributes={"href": github_url, "target": "_blank"}, text=True, outlined=True) + solara.Button(label="View source", + icon_name="mdi-github-circle", + attributes={ + "href": github_url, + "target": "_blank" + }, + text=True, + outlined=True) if sys.platform != "emscripten": solara.Button( label="Edit this example live on py.cafe", icon_name="mdi-coffee-to-go-outline", - attributes={"href": pycafe_url, "target": "_blank"}, + attributes={ + "href": pycafe_url, + "target": "_blank" + }, text=True, outlined=True, ) @@ -126,4 +164,6 @@ def filter_df(): def Layout(children): route, routes = solara.use_route() dark_effective = solara.lab.use_dark_effective() - return solara.AppLayout(children=children, toolbar_dark=dark_effective, color=None) # if dark_effective else "primary") + return solara.AppLayout(children=children, + toolbar_dark=dark_effective, + color=None) # if dark_effective else "primary") From f050a501322533b8c2e94430b5d6305967c4ddc7 Mon Sep 17 00:00:00 2001 From: Riley Thai Date: Tue, 11 Feb 2025 15:42:32 +1100 Subject: [PATCH 7/8] fix: routing for scatter-bokeh example --- solara/website/pages/__init__.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/solara/website/pages/__init__.py b/solara/website/pages/__init__.py index fc1b0377d..4cf27f460 100644 --- a/solara/website/pages/__init__.py +++ b/solara/website/pages/__init__.py @@ -11,7 +11,6 @@ route_order = ["/", "showcase", "documentation", "apps", "contact", "changelog", "roadmap", "pricing", "our_team", "careers", "about", "scale_ipywidgets"] - _redirects = { "/docs": "/documentation/getting_started/introduction", "/docs/installing": "/documentation/getting_started/installing", @@ -118,6 +117,8 @@ "/documentation/examples/fullscreen/multipage": "/apps/multipage", "/examples/fullscreen/scatter": "apps/scatter", "/documentation/examples/fullscreen/scatter": "/apps/scatter", + "/examples/fullscreen/scatter-bokeh": "/apps/scatter-bokeh", + "/documentation/examples/fullscreen/scatter_bokeh": "/apps/scatter-bokeh", "/examples/fullscreen/scrolling": "/apps/scrolling", "/documentation/examples/fullscreen/scrolling": "/apps/scrolling", "/examples/fullscreen/tutorial-streamlit": "/apps/tutorial-streamlit", @@ -211,7 +212,6 @@ "/api/title": "/documentation/components/page/title", } - server._redirects = _redirects autorouting._redirects = _redirects From 33ffa52ca4870a3d0d8560450721c5cf1ade18a2 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 11 Feb 2025 04:42:59 +0000 Subject: [PATCH 8/8] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- solara/website/pages/apps/scatter-bokeh.py | 72 +++++-------------- .../components/lab/figurebokeh.py | 3 +- 2 files changed, 17 insertions(+), 58 deletions(-) diff --git a/solara/website/pages/apps/scatter-bokeh.py b/solara/website/pages/apps/scatter-bokeh.py index 704373f1d..5adbb40a1 100644 --- a/solara/website/pages/apps/scatter-bokeh.py +++ b/solara/website/pages/apps/scatter-bokeh.py @@ -14,8 +14,7 @@ github_url = solara.util.github_url(__file__) if sys.platform != "emscripten": - pycafe_url = solara.util.pycafe_url(path=pathlib.Path(__file__), - requirements=["vaex", "bokeh"]) + pycafe_url = solara.util.pycafe_url(path=pathlib.Path(__file__), requirements=["vaex", "bokeh"]) else: pycafe_url = None @@ -64,17 +63,8 @@ def filter_df(): with solara.Card("Controls", margin=0, elevation=0): with solara.Column(): with solara.Row(): - solara.Button("Sample dataset", - color="primary", - text=True, - outlined=True, - on_click=State.load_sample, - disabled=df is not None) - solara.Button("Clear dataset", - color="primary", - text=True, - outlined=True, - on_click=State.reset) + solara.Button("Sample dataset", color="primary", text=True, outlined=True, on_click=State.load_sample, disabled=df is not None) + solara.Button("Clear dataset", color="primary", text=True, outlined=True, on_click=State.reset) if df is not None: columns = df.get_column_names() @@ -82,9 +72,7 @@ def filter_df(): solara.Select("Column y", values=columns, value=State.y) solara.Select("Color", values=columns, value=State.color) - solara.PivotTable(df, ["pclass"], ["sex"], - selected=selected, - on_selected=on_selected) + solara.PivotTable(df, ["pclass"], ["sex"], selected=selected, on_selected=on_selected) if dff is not None: source = ColumnDataSource( @@ -92,12 +80,10 @@ def filter_df(): "x": dff[State.x.value].values, "y": dff[State.y.value].values, "z": dff[State.color.value].values, - }) + } + ) if State.x.value and State.y.value: - p = figure(x_axis_label=State.x.value, - y_axis_label=State.y.value, - width_policy="max", - height=700) + p = figure(x_axis_label=State.x.value, y_axis_label=State.y.value, width_policy="max", height=700) # add a scatter, colorbar, and mapper color_expr = dff[State.color.value] @@ -108,25 +94,13 @@ def filter_df(): factors.remove(None) except ValueError: pass - args = dict( - palette=f"Viridis{min(11, max(3, color_expr.nunique()))}", - factors=factors) + args = dict(palette=f"Viridis{min(11, max(3, color_expr.nunique()))}", factors=factors) else: mapper = linear_cmap - args = dict(palette="Viridis256", - low=color_expr.min()[()], - high=color_expr.max()[()]) - - s = p.scatter(source=source, - x="x", - y="y", - size=12, - fill_color=mapper(field_name="z", **args)) - p.add_layout( - s.construct_color_bar(title=State.color.value, - label_standoff=6, - padding=5, - border_line_color=None), "right") + args = dict(palette="Viridis256", low=color_expr.min()[()], high=color_expr.max()[()]) + + s = p.scatter(source=source, x="x", y="y", size=12, fill_color=mapper(field_name="z", **args)) + p.add_layout(s.construct_color_bar(title=State.color.value, label_standoff=6, padding=5, border_line_color=None), "right") solara.lab.FigureBokeh(p, dark_theme="carbon") @@ -134,27 +108,15 @@ def filter_df(): solara.Warning("Select x and y columns") else: - solara.Info( - "No data loaded, click on the sample dataset button to load a sample dataset, or upload a file." - ) + solara.Info("No data loaded, click on the sample dataset button to load a sample dataset, or upload a file.") with solara.Column(style={"max-width": "400px"}): - solara.Button(label="View source", - icon_name="mdi-github-circle", - attributes={ - "href": github_url, - "target": "_blank" - }, - text=True, - outlined=True) + solara.Button(label="View source", icon_name="mdi-github-circle", attributes={"href": github_url, "target": "_blank"}, text=True, outlined=True) if sys.platform != "emscripten": solara.Button( label="Edit this example live on py.cafe", icon_name="mdi-coffee-to-go-outline", - attributes={ - "href": pycafe_url, - "target": "_blank" - }, + attributes={"href": pycafe_url, "target": "_blank"}, text=True, outlined=True, ) @@ -164,6 +126,4 @@ def filter_df(): def Layout(children): route, routes = solara.use_route() dark_effective = solara.lab.use_dark_effective() - return solara.AppLayout(children=children, - toolbar_dark=dark_effective, - color=None) # if dark_effective else "primary") + return solara.AppLayout(children=children, toolbar_dark=dark_effective, color=None) # if dark_effective else "primary") diff --git a/solara/website/pages/documentation/components/lab/figurebokeh.py b/solara/website/pages/documentation/components/lab/figurebokeh.py index f2a00c07f..d489ee331 100644 --- a/solara/website/pages/documentation/components/lab/figurebokeh.py +++ b/solara/website/pages/documentation/components/lab/figurebokeh.py @@ -11,7 +11,6 @@ title = "FigureBokeh" -__doc__ += apidoc( - solara.lab.components.figurebokeh.FigureBokeh.f) # type: ignore +__doc__ += apidoc(solara.lab.components.figurebokeh.FigureBokeh.f) # type: ignore Page = NoPage