Beautiful and fast dashboards with h2o Wave
Data dashboards are often used as a tool for data storytelling, which is the process of using data and visualization to communicate a message or tell a story. Dashboards can be particularly effective for data storytelling because they provide an interactive platform for exploring and understanding data.
When used effectively, data dashboards can help users identify patterns, trends, and relationships within data that might not be immediately apparent. They can also be used to highlight key insights and findings, helping users to understand the implications of the data and how it can be used to inform decisions and solve problems.
Data dashboards can be tailored to meet the specific needs and goals of an organization or individual. They can be used to present data in a clear and concise manner, making it easier for users to understand and interpret the data.
This article will delve into the advantages and capabilities of using H2O Wave to craft compelling and efficient dashboards for data storytelling.
H2o wave
H2O Wave is a powerful tool for AI apps, data visualization and dashboard creation, and we will be showcasing how it can be used to create visually appealing and functional dashboards that can help you better understand and analyze your data. So if you’re looking to enhance your dashboard game, keep reading to learn more about H2O Wave and how it can help you create beautiful and performant dashboards.
Setup
To use H2O Wave, we must first install it by creating a virtual environment using the following command:
python -m venv .myenv
This will create a new virtual environment called “.myenv” in the current directory.
Next, we need to install the dependency we will be using. This can be done by running the following command:
pip install h20_wave plotly siuba
`h2o_wave` is the package that will be used to build the dashboard, while `plotly` is a tool for creating interactive visualizations. `Siuba` is a data manipulation library that follows the philosophy of R’s `dplyr` and `tidyr` packages. In a future article, we will delve deeper into `siuba`.
The project should now look like this:
.myenv
music_mental_survey_clean.csv
├── requirements.txt
└── src
├── app.py
├── charts.py
Planning the dashboard
Planning the dashboard is an essential step in the process of creating a dashboard that meets the needs of your organization. A dashboard is a visual representation of data and information, typically used to monitor key performance indicators (KPIs) and other important metrics. It can be a powerful tool for making informed decisions, but only if it is well-designed and provides the right information in an easily digestible format.
Before you start building your dashboard, it is important to take the time to plan and define the goals and objectives of the dashboard. Consider what information is most important to your organization and how you want to use the dashboard. This will help you determine what data needs to be included and how it should be displayed.
let’s now plan for the dashboard. We will use the `charts.py` module to hold our `plotly` visualizations. This will be functions that we can import into our `app.py`
NB: The data and visualization for this project can be found in the project directory. The dashboard is part of an analysis examining the relationship between music and mental health. The Jupyter notebook contains more information about the analysis. The entire project can be found here
The dashboard will have 3 columns on the first row and 2 on the second row. The first card will display the dataset. The final dashboard will look like this:
Building the dashboard
We will start by creating our visualization functions in `chart.js` using the `plotly` package.
# charts.py
from h2o_wave import Q, ui
from siuba import *
from siuba.siu import call
import plotly.express as px
import plotly.io as pio
from plotly import graph_objects as go
pio.templates.default = "plotly_dark"
def update_chart(fig):
return fig.update(
layout=go.Layout(margin=dict(t=40, r=0, b=40, l=0), legend=dict(yanchor="top", y=0.95, xanchor="left", x=0.01))
)
# show table of data
async def show_table(df):
return ui.table(
name="table",
height="410px",
# Add pagination attribute to make your table paginated.
pagination=ui.table_pagination(total_rows=100, rows_per_page=5),
events=["page_change"],
columns=[ui.table_column(name=x, label=x) for x in df.columns.tolist()],
rows=[ui.table_row(name=str(i), cells=list(map(str, df.values.tolist()[i]))) for i in df.index[0:100]],
)
async def age_hist(df):
fig = df >> call(px.histogram, x="age", data_frame=_, labels={"age": "Age"})
update_chart(fig)
html = pio.to_html(fig, config=None, auto_play=True, include_plotlyjs="cdn")
return html
async def streaming_service(df):
fig = df >> call(
px.histogram,
x="primary_streaming_service",
data_frame=_,
labels={"primary_streaming_service": "Primary Streaming Service"},
)
fig.update_xaxes(categoryorder="total descending")
update_chart(fig)
html = pio.to_html(fig, config=None, auto_play=True, include_plotlyjs="cdn")
return html
async def fav_age_effect(df):
fig = df >> call(
px.histogram,
x="fav_genre",
y="age",
color="music_effects",
data_frame=_,
text_auto=True,
histfunc="avg",
barmode="group",
title="Music Effect on Listeners - Age",
labels={"fav_genre": "Fav Genre", "age": "Age"},
)
update_chart(fig)
html = pio.to_html(fig, config=None, auto_play=True, include_plotlyjs="cdn")
return html
async def fav_depression(df):
fig = df >> call(
px.histogram,
x="fav_genre",
y="depression",
color="music_effects",
data_frame=_,
text_auto=True,
histfunc="avg",
barmode="group",
title="Music Effect on Listeners - Depression",
labels={"fav_genre": "Fav Genre", "depression": "Depression"},
)
fig.update_yaxes(title="Depression", range=[0, 10])
update_chart(fig)
html = pio.to_html(fig, config=None, auto_play=True, include_plotlyjs="cdn")
return html
async def fav_insomnia(df):
fig = df >> call(
px.histogram,
y="insomnia",
x="fav_genre",
color="music_effects",
data_frame=_,
text_auto=True,
histfunc="avg",
barmode="group",
title="Music Effect on Listeners - Insomnia",
labels={"fav_genre": "Fav Genre", "insomnia": "Insomnia"},
)
fig.update_yaxes(title="Insomnia", range=[0, 10])
update_chart(fig)
html = pio.to_html(fig, config=None, auto_play=True, include_plotlyjs="cdn")
return html
The first function, `update_chart`, takes a figure object as input and updates the layout of the figure. The layout of the figure is modified to include reduce the margin on the side.
The `show_table` function displays a table with rows and columns. The table is paginated and has a height of “410px”. The columns of the table are defined by the columns of the input data frame, and the rows are defined by the rows of the data frame.
The remaining functions, age_hist, streaming_service, `fav_age_effect`, `fav_depression`, and `fav_insomnia`, all create visualizations using the plotly library. They take a data frame as input and use the `px.histogram` function to create histogram plots. The histogram function has various parameters, such as x, y, and color, that allow the user to specify which data columns to use for the histogram’s x-axis, y-axis, and color coding. The `barmode` parameter specifies the style of the histogram bars, and the `histfunc` parameter specifies the function to use for aggregating the data. The `update_chart` function is called on the figure object to update its layout, and the figure is then converted to HTML using the `pio.to_html` function. The resulting HTML is returned by the function.
NB: The `.histogram` method is used to generate histograms, which are represented using bars. Even though the function is called `histogram`, the resulting visualization will consist of bars.
Now, we will import these charts into our `src/app.py` file and create our dashboard. This will be the entry point to our dashboard. The `src/app.py` file will now look like this:
from h2o_wave import Q, ui, main, app
import pandas as pd
from siuba import *
from siuba.siu import call
from charts import show_table, streaming_service, fav_age_effect, fav_depression, fav_insomnia
# read the data anc cover
music = pd.read_csv("../music_mental_survey_clean.csv")
music = music >> select(~_["Unnamed: 0"]) # remove unwanted column
@app("/")
async def serve(q: Q):
q.page["meta"] = ui.meta_card(
box="",
themes=[
ui.theme(
name="cool7",
primary="#ffffff",
text="#ffffff",
card="#111111",
page="#ffffff",
)
],
theme="cool7",
)
q.page["header"] = ui.header_card(
box=("1 1 10 1"),
icon="HealthRefresh",
color="card",
title="Music's effect on Mental Health",
subtitle="Can music impact mental health? Let's find out",
)
q.page["table"] = ui.form_card(
box=("1 2 3 5"),
title="Music and Mental Condition Dataset",
items=[await show_table(df=music)],
)
q.page["streaming_service"] = ui.frame_card(
box=("4 2 3 5"),
title="Most popular streaming service",
content=await streaming_service(df=music),
)
q.page["fav_depression"] = ui.frame_card(
box=("7 2 4 5"),
title="",
content=await fav_depression(df=music),
)
q.page["fav_age_effect"] = ui.frame_card(
box=("1 7 5 5"),
title="",
content=await fav_age_effect(df=music),
)
q.page["fav_insomnia"] = ui.frame_card(
box=("6 7 5 5"),
title="",
content=await fav_insomnia(df=music),
)
q.page["foot"] = ui.markdown_card(
box="1 12 10 1",
title="",
content="""<br/>**[Daniel Boadzie](https://www.linkedin.com/in/boadzie/) -
The dataset for this project was obtained from [kaggle](https://www.kaggle.com/code/totoro29/music-and-mental-condition/notebook)**
""",
)
await q.page.save()
The code above first imports a number of libraries, including `ui` from `h2o_wave`, `siuba`, and `plotly`. It also imports several functions from a charts module, which are the functions that create the visualizations.
The code reads in a data set from a CSV file using pandas, and then removes an unwanted column using the `siuba` library. The resulting data frame is stored in a variable called music.
The code then defines an `async` function called `serve` that serves as the entry point for the dashboard. This function is decorated with the `app` decorator, which specifies that it should be accessed at the root URL of the application. The function takes an object called `q` as input, which is an instance of the `Q` class from `h2o_wave`.
The function sets a number of properties of the `q.page` object, which represents the page of the dashboard. Each property is an instance of a `ui` class, which defines a particular element of the dashboard such as a header, form, or frame. The box property of each element specifies its position and size in the grid layout of the dashboard.
`h2o_wave` has a flexible grid system that allows you to easily layout your dashboard elements. The position of the card on the grid is specified by the column and row, which are the first two values in the box. The size of the card is determined by the width and height, which are given in terms of the number of columns or rows that the card spans(i.e. the last two values). It’s important to note that the column and row numbering starts at 1, rather than 0. There is also a `flex` layout system for advanced responsive design.
The `serve` function calls the chart functions imported from the charts module to generate the visualizations and sets the resulting HTML as the content of each element. Finally, the function saves the page using the save method.
It’s worth noting that we have applied a custom theme to the dashboard. While `h20_wave` comes with a variety of attractive themes, it is also possible to create your own custom theme.”
Run the app
To start the app, go to the `src` folder and run the command
wave run app.py
Now point your browser to `http://localhost:10101` to view the app. If all work well, you should see the following:
Conclusion
In conclusion, the use of h2o_wave has allowed us to create a beautiful and fast dashboard for our needs. The wide range of widgets provided by h2o_wave made it easy to add components to the dashboard, and the performance of the dashboard has been consistently smooth and efficient. Overall, h2o_wave has proved to be an invaluable tool in the creation of our dashboard. There are more widgets and other cool components available, and I highly recommend checking the documentation for more information.
The project can be found here