Developing Interactive Dashboards with Voilà and Solara

Contents

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

14.6.10.2. Monitoring the Deployment#

14.6.10.3. Sharing Your Application#

14.7. Key Takeaways#

14.8. Exercises#

14.8.1. Exercise 1: Create a World Population Voilà Dashboard#

14.8.2. Exercise 2: Build a Multi-Dataset Solara Application#

14.8.3. Exercise 3: Interactive DuckDB Query Dashboard (Advanced)#