14. Developing Interactive Dashboards with Voilà and Solara#
14.1. Introduction#
14.1.1. Understanding Voilà#
14.1.2. Understanding Solara#
14.1.3. Integration with the Geospatial Python Ecosystem#
14.1.4. Choosing the Right Framework#
14.1.5. Why Not Streamlit or Gradio?#
14.1.6. What You’ll Learn#
14.2. Learning Objectives#
14.3. Installing Voilà and Solara#
# %pip install voila solara leafmap duckdb
import voila
import solara
14.4. Introduction to Hugging Face Spaces#
14.4.1. Creating a Hugging Face Account#
14.4.2. Installing the Hugging Face CLI#
# %pip install -U "huggingface_hub"
14.4.3. Logging in to Hugging Face#
hf auth login
14.5. Creating a Basic Voilà Application#
14.5.1. Creating a new Hugging Face Space#
14.5.2. Embedding the Hugging Face Space in Your Website#
14.5.3. Running the Voilà Application#
14.5.4. Understanding the Application Code#
14.5.4.1. Setting Up the Map and Imports#
import json
import duckdb
import ipywidgets as widgets
import leafmap.maplibregl as leafmap
import matplotlib.pyplot as plt
m = leafmap.Map(sidebar_visible=True, layer_manager_expanded=False, height="800px")
m.add_basemap("Esri.WorldImagery", before_id=m.first_symbol_layer_id, visible=False)
m.add_draw_control(controls=["polygon", "trash"])
m
14.5.4.2. Initializing the DuckDB Connection#
con = duckdb.connect()
con.install_extension("httpfs")
con.install_extension("spatial")
con.install_extension("h3", repository="community")
con.load_extension("httpfs")
con.load_extension("spatial")
con.load_extension("h3")
14.5.4.3. Loading the Building Data#
url = "https://data.gishub.org/duckdb/h3_res4_geo.parquet"
con.sql(
f"CREATE TABLE IF NOT EXISTS h3_res4_geo AS SELECT * FROM read_parquet('{url}');"
)
14.5.4.4. Creating the User Interface Controls#
colormaps = sorted(plt.colormaps())
checkbox = widgets.Checkbox(
description="3D Map",
value=True,
style={"description_width": "initial"},
layout=widgets.Layout(width="initial"),
)
outline_chk = widgets.Checkbox(
description="Add Hexagon Outline",
value=False,
style={"description_width": "initial"},
layout=widgets.Layout(width="initial"),
)
colormap_dropdown = widgets.Dropdown(
options=colormaps,
description="Colormap:",
value="inferno",
style={"description_width": "initial"},
)
class_slider = widgets.IntSlider(
description="Class:",
min=1,
max=10,
step=1,
value=5,
style={"description_width": "initial"},
)
apply_btn = widgets.Button(description="Apply")
close_btn = widgets.Button(description="Close")
output_widget = widgets.Output()
with output_widget:
print("Draw a polygon on the map. Then, \nclick on the 'Apply' button")
14.5.4.5. Implementing the Query Logic#
def on_apply_btn_click(change):
with output_widget:
try:
output_widget.clear_output()
if len(m.draw_features_selected) > 0:
geojson = m.draw_features_selected[0]["geometry"]
df = con.sql(
f"""
SELECT * EXCLUDE (geometry), ST_AsText(geometry) AS geometry FROM h3_res4_geo
WHERE ST_Intersects(geometry, ST_GeomFromGeoJSON('{json.dumps(geojson)}'));
"""
).df()
gdf = leafmap.df_to_gdf(df)
if "H3 Hexagon" in m.layer_dict:
m.remove_layer("H3 Hexagon")
if outline_chk.value:
outline_color = "rgba(255, 255, 255, 255)"
else:
outline_color = "rgba(255, 255, 255, 0)"
if checkbox.value:
m.add_data(
gdf,
column="building_count",
scheme="JenksCaspall",
cmap=colormap_dropdown.value,
k=class_slider.value,
outline_color=outline_color,
name="H3 Hexagon",
before_id=m.first_symbol_layer_id,
extrude=True,
fit_bounds=False,
add_legend=False,
)
else:
m.add_data(
gdf,
column="building_count",
scheme="JenksCaspall",
cmap=colormap_dropdown.value,
k=class_slider.value,
outline_color=outline_color,
name="H3 Hexagon",
before_id=m.first_symbol_layer_id,
fit_bounds=False,
add_legend=False,
)
m.remove_from_sidebar(name="Legend")
m.add_legend_to_sidebar(
title="Building Count",
legend_dict=m.legend_dict,
)
except Exception as e:
with output_widget:
print(e)
def on_close_btn_click(change):
m.remove_from_sidebar(name="H3 Hexagonal Grid")
14.5.4.6. Wiring Up the Interface#
apply_btn.on_click(on_apply_btn_click)
close_btn.on_click(on_close_btn_click)
widget = widgets.VBox(
[
widgets.HBox([checkbox, outline_chk]),
colormap_dropdown,
class_slider,
widgets.HBox([apply_btn, close_btn]),
output_widget,
]
)
m.add_to_sidebar(widget, label="H3 Hexagonal Grid", widget_icon="mdi-hexagon-multiple")
14.5.4.7. How Voilà Transforms This Notebook#
14.5.5. Exploring the File Structure of the Space#
14.5.6. Updating the Hugging Face Space#
14.5.6.1. Updating the Space from the Hugging Face Website#
14.5.6.2. Updating the Space from the Command Line#
git clone https://huggingface.co/spaces/YOUR-USERNAME/duckdb-voila
cd duckdb-voila
voila notebooks/
voila notebooks/ --strip_sources=False
14.6. Creating an Advanced Web Application with Solara#
14.6.1. Understanding Solara’s Architecture#
14.6.2. Development and Deployment Flexibility#
14.6.3. Reactive Programming Model#
14.6.4. Native ipywidgets Integration#
14.6.5. Using a Solara Template for DuckDB#
14.6.6. Exploring the File Structure of the Solara Web App#
14.6.7. Introduction to Solara Components#
14.6.7.1. Creating Your First Solara Component#
import solara
import leafmap.maplibregl as leafmap
def create_map():
m = leafmap.Map(
style="liberty",
projection="globe",
height="750px",
zoom=2.5,
add_sidebar=True,
sidebar_visible=True,
)
return m
@solara.component
def Page():
m = create_map()
return m.to_solara()
Page()
14.6.7.2. Understanding the Component Structure#
14.6.7.3. The Solara Development Pattern#
14.6.8. Creating a New Page#
14.6.8.1. Page Naming Convention#
14.6.8.2. Creating a Building Density Visualization Page#
import solara
import leafmap.maplibregl as leafmap
def create_map():
m = leafmap.Map(
style="dark-matter",
projection="globe",
height="750px",
zoom=2.5,
add_sidebar=True,
sidebar_visible=True,
)
m.add_basemap("Esri.WorldImagery", before_id=m.first_symbol_layer_id, visible=False)
url = "https://data.gishub.org/duckdb/h3_res4_geo.parquet"
gdf = leafmap.read_vector(url)
m.add_data(
gdf,
column="building_count",
scheme="JenksCaspall",
cmap="inferno",
outline_color="rgba(255, 255, 255, 0)",
name="H3 Hexagon",
before_id=m.first_symbol_layer_id,
)
return m
@solara.component
def Page():
m = create_map()
return m.to_solara()
14.6.8.3. Understanding the Buildings Page#
14.6.9. Running the Solara Web App Locally#
cd duckdb-solara
solara run pages/
14.6.10. Deploying to Hugging Face Spaces#
14.6.10.1. Making Changes and Deploying#
git add .
git commit -m "Add buildings visualization page"
git push