Build a Dashboard#

Welcome to our tutorial on building an interactive dashboard showcasing essential metrics from wind turbine manufacturers.

We’ll demonstrate how to leverage a variety of components such as sliders, dropdowns, plots, indicators, tables and layouts to craft a visually stunning and functional application.

By following along, you’ll gain insights into how these components can be seamlessly combined to present data in a meaningful way.

Our objective is to empower you to replicate this process for your own datasets and use cases, enabling you to create impactful dashboards tailored to your specific needs.

Click the dropdowns below to see the requirements or the full code.

Requirements
pip install hvplot pandas panel
conda install -y -c conda-forge hvplot pandas panel
Code
import hvplot.pandas
import pandas as pd
import panel as pn

pn.extension("tabulator", sizing_mode="stretch_width", throttled=True)

ACCENT = "#008080"  # teal

styles = {
    "box-shadow": "rgba(50, 50, 93, 0.25) 0px 6px 12px -2px, rgba(0, 0, 0, 0.3) 0px 3px 7px -3px",
    "border-radius": "4px",
    "padding": "10px",
}

# Extract Data

@pn.cache  # only download data once
def get_data():
    return pd.read_csv("https://assets.holoviz.org/panel/tutorials/turbines.csv.gz")

source_data = get_data()

# Transform Data

min_year = int(source_data["p_year"].min())
max_year = int(source_data["p_year"].max())
top_manufacturers = (
    source_data.groupby("t_manu").p_cap.sum().sort_values().iloc[-10:].index.to_list()
)

def filter_data(t_manu, year):
    data = source_data[(source_data.t_manu == t_manu) & (source_data.p_year <= year)]
    return data

# Filters

t_manu = pn.ui.Select(
    label="Manufacturer",
    value="Vestas",
    options=sorted(top_manufacturers),
    description="The name of the manufacturer",
)
p_year = pn.ui.IntSlider(
    label="Year", value=max_year, start=min_year, end=max_year, format="0"
)

# Transform Data 2

df = pn.rx(filter_data)(t_manu=t_manu, year=p_year)
count = df.rx.len()
total_capacity = df.t_cap.sum()
avg_capacity = df.t_cap.mean()
avg_rotor_diameter = df.t_rd.mean()

# Plot Data

fig = (
    df[["p_year", "t_cap"]].groupby("p_year").sum() / 10**6
).hvplot.bar(
    title="Capacity Change",
    rot=90,
    ylabel="Capacity (GW)",
    xlabel="Year",
    xlim=(min_year, max_year),
    color=ACCENT,
    responsive=True,
)

# Display Data

image = pn.ui.PNG("https://assets.holoviz.org/panel/tutorials/wind_turbines_sunset.png")

indicators = pn.ui.FlexBox(
    pn.ui.Number(
        value=count,
        label="Count",
        format="{value:,.0f}",
        font_size="28pt",
        styles=styles,
    ),
    pn.ui.Number(
        value=total_capacity / 1e6,
        label="Total Capacity (GW)",
        format="{value:,.1f}",
        font_size="28pt",
        styles=styles,
    ),
    pn.ui.Number(
        value=avg_capacity/1e3,
        label="Avg. Capacity (MW)",
        format="{value:,.1f}",
        font_size="28pt",
        styles=styles,
    ),
    pn.ui.Number(
        value=avg_rotor_diameter,
        label="Avg. Rotor Diameter (m)",
        format="{value:,.1f}",
        font_size="28pt",
        styles=styles,
    ),
)

plot = pn.ui.HoloViews(fig, sizing_mode="stretch_both")
table = pn.ui.Tabulator(df, sizing_mode="stretch_both")

# Layout Data

tabs = pn.ui.Tabs(("Plot", plot), ("Table", table), styles=styles, height=500, margin=10)

pn.ui.Page(
    title="Wind Turbine Dashboard",
    sidebar=[image, t_manu, p_year],
    main=[indicators, tabs],
    theme_config={"palette": {"primary": {"main": ACCENT}}},
).servable()

Explanation#

Importing Libraries#

import hvplot.pandas
import pandas as pd
import panel as pn
  • hvplot.pandas: This library provides a high-level plotting interface for Pandas DataFrames using HoloViews.

  • pandas: This library is used for data manipulation and analysis.

  • panel: This is the Panel library used for creating interactive dashboards and apps.

Extension#

pn.extension("tabulator", sizing_mode="stretch_width", throttled=True)
  • pn.extension("tabulator", ...): This line loads the JavaScript for the Tabulator widget, which provides a high-performance interactive table. sizing_mode="stretch_width" makes components fill the available width by default, and throttled=True makes the year slider only trigger updates when it is released rather than on every step of a drag.

Styles#

ACCENT = "#008080"  # teal

styles = {
    "box-shadow": "rgba(50, 50, 93, 0.25) 0px 6px 12px -2px, rgba(0, 0, 0, 0.3) 0px 3px 7px -3px",
    "border-radius": "4px",
    "padding": "10px",
}
  • ACCENT: This variable stores the hex code of the color teal, which will be used as the accent color throughout the dashboard. The theme of the Page requires a hex (or rgb(...)) color rather than a CSS color name.

  • styles: This dictionary contains CSS styles to be applied to various components in the dashboard for consistent styling, including box shadow, border radius, and padding.

Data Extraction#

@pn.cache
def get_data():
    return pd.read_csv("https://assets.holoviz.org/panel/tutorials/turbines.csv.gz")

source_data = get_data()
  • get_data(): This function is decorated with @pn.cache, which ensures that the data is only downloaded once, improving performance by caching the result.

  • source_data: This variable stores the downloaded CSV data as a Pandas DataFrame.

Data Transformation#

min_year = int(source_data["p_year"].min())
max_year = int(source_data["p_year"].max())
top_manufacturers = (
    source_data.groupby("t_manu").p_cap.sum().sort_values().iloc[-10:].index.to_list()
)

def filter_data(t_manu, year):
    data = source_data[(source_data.t_manu == t_manu) & (source_data.p_year <= year)]
    return data
  • min_year, max_year: These variables store the minimum and maximum values of the “p_year” column in the DataFrame, representing the earliest and latest years.

  • top_manufacturers: This variable stores the top 10 manufacturers based on the sum of their turbine capacities.

  • filter_data(): This function filters the source data based on the selected manufacturer and year.

Widgets#

t_manu = pn.ui.Select(
    label="Manufacturer",
    value="Vestas",
    options=sorted(top_manufacturers),
    description="The name of the manufacturer",
)
p_year = pn.ui.IntSlider(
    label="Year", value=max_year, start=min_year, end=max_year, format="0"
)
  • t_manu: This widget is a select dropdown for choosing the manufacturer.

  • p_year: This widget is an integer slider for selecting the year. format="0" displays the year without a thousands separator.

Data Transformation 2#

df = pn.rx(filter_data)(t_manu=t_manu, year=p_year)
count = df.rx.len()
total_capacity = df.t_cap.sum()
avg_capacity = df.t_cap.mean()
avg_rotor_diameter = df.t_rd.mean()
  • df: This variable stores the filtered DataFrame based on the selected manufacturer and year.

  • count, total_capacity, avg_capacity, avg_rotor_diameter: These variables calculate various statistics from the filtered DataFrame, such as count, total capacity, average capacity, and average rotor diameter.

Plotting Data#

fig = (
    df[["p_year", "t_cap"]].groupby("p_year").sum() / 10**6
).hvplot.bar(
    title="Capacity Change",
    rot=90,
    ylabel="Capacity (GW)",
    xlabel="Year",
    xlim=(min_year, max_year),
    color=ACCENT,
    responsive=True,
)
  • fig: This variable stores a bar plot of the total turbine capacity over the years. responsive=True makes the plot fill the space of the tab it is displayed in.

Displaying Data#

image = pn.ui.PNG("https://assets.holoviz.org/panel/tutorials/wind_turbines_sunset.png")

indicators = pn.ui.FlexBox(
    pn.ui.Number(
        value=count,
        label="Count",
        format="{value:,.0f}",
        font_size="28pt",
        styles=styles,
    ),
    pn.ui.Number(
        value=total_capacity / 1e6,
        label="Total Capacity (GW)",
        format="{value:,.1f}",
        font_size="28pt",
        styles=styles,
    ),
    pn.ui.Number(
        value=avg_capacity/1e3,
        label="Avg. Capacity (MW)",
        format="{value:,.1f}",
        font_size="28pt",
        styles=styles,
    ),
    pn.ui.Number(
        value=avg_rotor_diameter,
        label="Avg. Rotor Diameter (m)",
        format="{value:,.1f}",
        font_size="28pt",
        styles=styles,
    ),
)
  • image: This variable stores an image pane displaying a background image for the dashboard.

  • indicators: This variable stores a collection of Number indicators displaying various statistics. We reduce their font_size from the default of 54pt so all four fit side by side.

Creating Components#

plot = pn.ui.HoloViews(fig, sizing_mode="stretch_both")
table = pn.ui.Tabulator(df, sizing_mode="stretch_both")
  • plot: This variable stores a HoloViews plot pane displaying the bar plot.

  • table: This variable stores a Tabulator widget displaying the filtered DataFrame as a table.

Layout#

tabs = pn.ui.Tabs(("Plot", plot), ("Table", table), styles=styles, height=500, margin=10)
  • tabs: This variable stores a Tabs layout containing the plot and table, allowing users to switch between them. Each (title, component) tuple sets the title of a tab.

Creating the Dashboard#

pn.ui.Page(
    title="Wind Turbine Dashboard",
    sidebar=[image, t_manu, p_year],
    main=[indicators, tabs],
    theme_config={"palette": {"primary": {"main": ACCENT}}},
).servable()
  • This code creates a Page with a sidebar containing the image, manufacturer select dropdown, and year slider, and a main section containing the indicators and tabs. The theme_config sets the primary color of the theme to our ACCENT color.

Conclusion#

This code sets up a dynamic dashboard for exploring wind turbine data, including filtering, data transformation, visualization, and statistics display. It leverages various Panel widgets, HoloViews plots, and CSS styling to create an interactive and informative dashboard.