1670915040
Reporting and visualizing data is crucial to businesses of all sizes. Dashboards allow users to efficiently access and use this data for a range of business operations. In this article, Toptal Full-stack Engineer Dylan Golow demonstrates how he created a powerful dashboard for telemedicine using Airtable, Typeform, and React.
Whether a company is a large enterprise or a budding startup, collecting data from users and customers, and reporting on or visualizing that data is crucial to the business.
I recently worked with a telemedicine startup based in Brazil. Its mission is to provide remote care and monitoring by connecting patients to medical professionals and health coaches. The core need was to create an interface for the coaches and health professionals to easily review a patient’s information and most important metrics related to their particular situation: a dashboard.
Enter Typeform and Airtable.
Typeform is one of the go-to data collection tools that enables responsive web experiences for users completing a survey. It also comes with several features that make surveys more intelligent, especially when combined:
Surveys can be shared via URLs that can be pre-seeded with values for the hidden fields, which can then be used to implement logic jumps and alter the behavior of the survey for the user with the link.
Airtable is a spreadsheet-database hybrid and a collaborative cloud platform. Its focus on point and click functionality means that non-technical users can configure it without coding. Airtable has a multitude of use cases in any business or project.
You can use an Airtable Base for:
There are many more potential use cases. You can explore Airtable case studies here.
If you are not familiar with Airtable, the conceptual data model breaks down like this:
Apart from providing a cloud-hosted database with familiar spreadsheet features, here are some of the reasons the platform is so powerful:
For non-technical users, Airtable provides:
For developers, Airtable provides:
You can learn more on Airtable here.
Typeform surveys were already configured by the client, and the next step was to plan how that data would land in Airtable and then be turned into a dashboard. There are many questions to consider when creating dashboards on top of any database: How should we structure the data? What data will need to be processed prior to visualization? Should we sync the Base with Google Sheets and use Google Data Studio? Should we export and find another third-party tool?
Fortunately for developers, not only does Airtable provide automations and scripting to handle the data processing steps, but it has also made it possible to build custom applications and interfaces on top of an Airtable Base with Airtable Apps.
Custom Apps in Airtable have been around since the Airtable Blocks SDK was released at the beginning of 2018, and were recently renamed to Apps. The release of Blocks was huge in that it meant that creators now had the ability to develop, as Airtable puts it, “An infinitely recombinable Lego kit.”
More recently with the change to apps, the Airtable Marketplace made it possible to share apps publicly, as well.
Airtable Apps provide businesses with an infinitely recombinable Lego kit they can tailor to their needs.
In order to build a custom app in Airtable, a JavaScript developer must know how to use React, one of the most popular JavaScript libraries for building user interfaces. Airtable provides a component library of functional React components and hooks, which are a huge help for rapidly building a consistent UI and determining how you will manage state within the app and its components.
Check out Airtable’s Getting Started article for more information and Airtable on GitHub for examples of apps.
After reviewing the dashboard mockups with the client team, the types of data to be used were clear. We would need a series of dashboard components that would display as text on the dashboard and charts of different metrics that could be tracked over time.
Coaches and medical professionals needed to be able to build a custom dashboard for each patient, so we needed a flexible way to add and remove charts. Other static data relative to each patient would be displayed no matter the patient selected.
In this case, the dashboard sections boiled down to:
One way to approach all of the sections except for charts would be to hard-code all the columns for objectives, medicine use, and family history into the dashboard. However, that would not allow the client team to add new questions to a Typeform survey nor add a new column to an Airtable table to present that data on the dashboard without having a developer update the custom app.
A more elegant and extensible solution to this challenge was finding a way to tag columns as relevant to a particular dashboard section and retrieve those columns using the metadata that Airtable exposes when using the Table and Field models.
This was achieved using Field Descriptions as a place to tag a column from the Table as relevant to a dashboard section to be displayed to the user. Then, we could ensure only those with the Creator role (the administrators) for the Base had the ability to modify these Field Descriptions to alter what appears on the dashboard. To illustrate this solution, we will focus mostly on the items in General Information and how to present Charts.
Given the dashboard sections, it made sense to make reusable tags for some sections and specific tags for certain columns. For items like patient name, email, and phone number, #NAME#
, #EMAIL#
, and #PHONE#
were added to each Field’s description, respectively. That would allow that information to be retrieved via the Table metadata like this:
const name = table ? table.fields.filter(field => field.description?.includes("#NAME#"))
For areas of the dashboard that would need to draw from many tagged columns we would have the following tags for each dashboard section:
In addition, it was important to separate the name of a column in a Table from the label it would receive on the dashboard, so anything that received a #TAG#
would also have the ability to receive two #LABEL#
tags in its Field Description. A Field Description would look like this:
In case the #LABEL#
tags are missing, we will display the column name from the Table.
We can parse out the label set in the description with a simple function like this after retrieving the field with the previous code example:
// utils.js
export const setLabel = (field, labelTag = "#LABEL#") => {
const labelTags = (field.description?.match(new RegExp(labelTag, "g")) || []).length;
let label;
if (labelTags === 2) label = field.description?.split(`${labelTag}`)[1];
if (!label || label?.trim() === '') label = field.name;
return {...field, label, name: field.name, description: field.description};
}
With this #TAG#
system, we achieve three main things:
In React, we use state and pass it to components as props in order to re-render that component if its state changes. Normally this is tied to an API call that fuels a dashboard component, but in Airtable we already have all the data and simply need to filter what we are displaying based on which patient we are viewing. In addition, if we use state, it will not persist the data past a refresh in the dashboard itself.
So, how can we persist a value past refresh to keep a dashboard filtered? Fortunately, Airtable provides a hook for this called useGlobalConfig in which it maintains a key-value store for an app installation on a dashboard. We simply need to implement the logic of retrieving values from this key-value store when the app loads to fuel our dashboard components.
What is even more useful about using the useGlobalConfig
hook is that when its values are set, the dashboard component and its child components re-render, so you can use the Global Config like you would use a state variable in a typical React implementation.
Airtable provides examples of charts with its Simple Chart App, which uses React Charts, a React wrapper on Chart.js (chart-ception).
In the Simple Chart App, we have one chart for the whole app, but in our Dashboard App, we need the ability for the user to add and remove their own charts from their own dashboard. What’s more, in discussion with the client team, it seems that certain metrics would be better viewed on the same chart (like readings for diastolic and systolic blood pressure).
With this we have the following items to tackle:
This is where the power of the Global Config comes in handy, as we can use the key-value store to maintain the selected metrics and anything else about our list of charts. As we configure a chart in the UI, the chart component itself will be re-rendered due to updates to the Global Config. For the charting section of the dashboard, here is a gist with the components for reference, focusing on dashboard charts.js and single chart.js.
The table passed to each chart is what is used for its metadata to find the fields, whereas the records passed have already been filtered by the patient selected at the top-level dashboard component that imports dashboard_charts/index.js
.
Note that the fields listed as options in the dropdown for a chart are pulled using the #CHART#
tag we mentioned before, with this line in a useEffect
hook:
// single_chart/index.js
…
useEffect(() => {
(async () => {
...
if (table) {
const tempFieldOptions = table.fields.filter(field =>
field.description?.includes('#CHART#')).map(field => {
return {
...setLabel(field),
value: field.id
}
});
setFieldSelectOptions([...tempFieldOptions]);
}
})();
}, [table, records, fields]);
...
The code above shows how the setLabel
function referenced earlier is used with the #TAG#
to add anything provided in the #LABEL#
tags and display it for the option in the field dropdown.
Our chart component takes advantage of the multi-axis capabilities provided by Chart.js, which is shown with React Charts. We just extended it via the UI with the user’s ability to add a dataset and a chart type (line or bar).
The key to using Global Config, in this case, is to know that each key can only hold a string | boolean | number | null | GlobalConfigArray | GlobalConfigObject (see Global Config Value reference).
We have the following items to maintain per chart:
To manage this, I found it most convenient to stringify this data as an object instead of setting Global Config keys and values all the way down. See the example below (globalConfig.json in the gist), which includes Global Config values to filter records by the patient and some related variables used to support a typeahead filtering component (thanks to react-bootstrap-typeahead):
{
"xCharts": {
"chart-1605425876029": "{\"fields\":[{\"field\":\"fldxLfpjdmYeDOhXT\",\"chartOption\":\"line\",\"color\":\"blueBright\",\"hex\":\"#2d7ff9\"},{\"field\":\"fldqwG8iFazZD5CLH\",\"chartOption\":\"line\",\"color\":\"blueLight1\",\"hex\":\"#9cc7ff\"}],\"chartTitle\":\"Gráfico criado em 11/15/2020, 2:37:56 AM\"}",
"chart-1605425876288": "{\"fields\":[{\"field\":\"fldGJZIdRlq3V3cKu\",\"chartOption\":\"line\",\"color\":\"blue\",\"hex\":\"#1283da\"}],\"chartTitle\":\"Gráfico criado em 11/15/2020, 2:37:56 AM\"}",
"chart-1605425876615": "{\"fields\":[{\"field\":\"fld1AnNcfvXm8DiNs\",\"chartOption\":\"line\",\"color\":\"blueLight1\",\"hex\":\"#9cc7ff\"},{\"field\":\"fldryX5N6vUYWbdzy\",\"chartOption\":\"line\",\"color\":\"blueDark1\",\"hex\":\"#2750ae\"}],\"chartTitle\":\"Gráfico criado em 11/15/2020, 2:37:56 AM\"}",
"chart-1605425994036": "{\"fields\":[{\"field\":\"fld9ak8Ja6DPweMdJ\",\"chartOption\":\"line\",\"color\":\"blueLight2\",\"hex\":\"#cfdfff\"},{\"field\":\"fldxVgXdZSECMVEj6\",\"chartOption\":\"line\",\"color\":\"blue\",\"hex\":\"#1283da\"}],\"chartTitle\":\"Gráfico criado em 11/15/2020, 2:39:54 AM\"}",
"chart-1605430015978": "{\"fields\":[{\"field\":\"fldwdMJkmEGFFSqMy\",\"chartOption\":\"line\",\"color\":\"blue\",\"hex\":\"#1283da\"},{\"field\":\"fldqwG8iFazZD5CLH\",\"chartOption\":\"line\",\"color\":\"blueLight1\",\"hex\":\"#9cc7ff\"}],\"chartTitle\":\"New Chart\"}",
"chart-1605430916029": "{\"fields\":[{\"field\":\"fldCuf3I2V027YAWL\",\"chartOption\":\"line\",\"color\":\"blueLight1\",\"hex\":\"#9cc7ff\"},{\"field\":\"fldBJjtRkWUTuUf60\",\"chartOption\":\"line\",\"color\":\"blueDark1\",\"hex\":\"#2750ae\"}],\"chartTitle\":\"Gráfico criado em 11/15/2020, 4:01:56 AM\"}",
"chart-1605431704374": "{\"fields\":[{\"field\":\"fld7oBtl3iiHNHqoJ\",\"chartOption\":\"line\",\"color\":\"blue\",\"hex\":\"#1283da\"}],\"chartTitle\":\"Gráfico criado em 11/15/2020, 4:15:04 AM\"}"
},
"xPatientEmail": "elle@gmail.com",
"xTypeaheadValue": "Elle Gold (elle@gmail.com)",
"xSelectedValue": "[{\"label\":\"Elle Gold (elle@gmail.com)\",\"id\":\"elle@gmail.com\",\"name\":\"Elle Gold\",\"email\":\"elle@gmail.com\"}]"
}
Note: All data contained above, and the data included in the animations below, are not real patient data.
Here’s a look at the final result:
In order to filter by patient, we needed a way to select a patient and then filter the records based on this patient. In this section, we review how this was achieved.
For the typeahead, react-bootstrap-typeahead was an easy choice, as the only steps left were preparing the options for the typeahead, mixing it with an Airtable input for styling and loading bootstrap, and some other styles for our menu. Dropping components from your favorite component libraries into an Airtable app is not as straightforward as in typical React web development; however, there are only a few extra steps to get everything to look the way you would expect.
Here is the final result:
To render the Airtable input and keep all our styles consistent, react-bootstrap-typeahead comes with a renderInput prop. See more on how to modify the rendering of the component here.
For the bootstrap styles and to override our menu items, the following two utils were used from Airtable:
See frontend.js in the gist for an excerpt of the typeahead implementation.
This line was used to load bootstrap globally:
// frontend/index.js
loadCSSFromURLAsync('https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css');
You will notice some added logic for things like handling style changes on hover or restyling links (<a></a>
) to get the familiar bootstrap look and feel. This also includes the handling of setting the Global Config values for the typeahead and filtering of records so that if a user leaves their dashboard, refreshes their page, or would like to share this dashboard with others, the app persists the selected patient in the Dashboard App. This also allows the users to install multiple copies of this same app side by side in the same Airtable Dashboard with different patients selected or with different charts.
Keep in mind that a dashboard in Airtable is also available to all users of the Base, so these custom app installations on a dashboard will be filtered to the same patients and charts no matter which users are looking at the dashboard at the same time.
Let’s recap what we’ve covered for far:
#TAG#
system can be used to modify the dashboard without requiring code changes by developers. There are better and worse use cases for this. Be sure to limit permissions to the Creator role if using this strategy.loadCSSFromString
and loadCSSFromURLAsync
utils provided by Airtable.Use a more sophisticated middleware
With Typeform and Airtable, it’s easy and cost-effective to configure the mapping of questions to columns.
However, there is one big drawback: If you have a survey of more than 100 questions mapped to Airtable and you need to modify a mapping, you must delete the entire mapping and start again. This is clearly not ideal, but for a free integration, we can deal with this.
Other options would be having a Zapier (or similar) integration manage the data between Typeform and Airtable.Then you could modify the mapping of any question to any column without starting from scratch. This would have its own cost considerations to factor in as well.
Hopefully, some of the lessons learned and communicated here will help others who are looking to build solutions with Airtable.
Finally, you can check out the gist with the files discussed in this article.
Original article source at: https://www.toptal.com/
1656577236
If not long ago the creation of analytical web applications required knowledge of several programming languages, today you can create a data visualization interface in pure Python. One popular tool for this has become Dash, which allows data scientists to display results in interactive web applications.
In this guide, we’ll cover:
What is Dash?
Dash is an open-source framework for building data visualization interfaces. After being released in 2017 as a Python library, Dash was soon extended for R and Julia.
The library was created and maintained by a Canadian company called Plotly. Perhaps you know about her from the popular graphics libraries that bear her name. Plotly opened source Dash and released it under the MIT license, so the library is free to use.
Dash is based on and integrates three frameworks:
You don’t have to worry about these technologies working together. You just need to write the code in Python, R, or Julia and add some CSS.
If you are used to analyzing data using Python, Dash is a useful addition to your toolbox. Here are some practical examples of the library’s capabilities:
Setting up a virtual environment
To develop your application, you need a directory to store your code and data, as well as a clean Python 3 virtual environment. To create them, follow the instructions for your operating system.
Windows. Open a command prompt and run the following commands:
mkdir avocado_analytics && cd avocado_analytics
python -m venv venv venv\Scripts\activate.bat
The first command will create a project directory and change the current working directory. The second command will create the virtual environment, and the last command will activate it. You may need to specify a file path to python.exe
instead of a command python
.
MacOS or Linux. The meaning of the following terminal commands is identical to those for Windows:
mkdir avocado_analytics && cd avocado_analytics
python3 -m venv venv source venv/bin/activate
Next, you need to install the following libraries into the virtual environment:
python -m pip install dash==1.13.3 pandas==1.0.5
The Dash and pandas libraries will be installed in the virtual environment. The virtual environment allows the use of specific versions of the libraries, similar to those used in this tutorial.
Finally, you will need some data that can be downloaded from the accompanying lesson materials.
Save the data file in the root directory of the project. By now, you should have a virtual environment with the required libraries and data in the project root folder. The project structure looks like this: avocado.csv
avocado_analytics/
├── venv/
└── avocado.csv
How to create an app with Dash
Let’s break down the process of creating a Dash application into two steps:
layout
).callbacks
) which parts of the application are interactive and what they react to.Initializing the Dash Application
First, let’s create an empty file called app.py
. Next, we will fill it out step by step and explain what is happening, and at the end of the section, you will find its entire contents.
Here are the first few lines of app.py
:
import dash
import dash_core_components as dcc
import dash_html_components as html
import pandas as pd
data = pd.read_csv("avocado.csv")
data = data.query("type == 'conventional' and region == 'Albany'")
data["Date"] = pd.to_datetime(data["Date"], format="%Y-%m-%d")
data.sort_values("Date", inplace=True)
app = dash.Dash(__name__)
First, we import the required libraries:
dash
will help initialize the applicationdash_core_components
allows you to create interactive components: charts, drop-down lists, date ranges, etc.dash_html_components
allows you to access HTML tagspandas
helps to read and display data in an organized mannerNext, we read the data and process it for use in the control panel. On the last line, we create an instance of the Dash class.
If you’ve used Flask before, then the initialization of the Dash class is already familiar to you. In Flask, we usually initialize a WSGI (short for Web Server Gateway Interface) application with Flask(__name__)
. For Dash apps, we use Dash(__name__)
.
Defining the Dash Application Layout
Now we will define the layout of the application, its appearance. In our case, the layout will consist of a title, description and two diagrams.
app.layout = html.Div(
children=[
html.H1(children="Avocado Analytics",),
html.P(
children="Analyze the behavior of avocado prices"
" and the number of avocados sold in the US"
" between 2015 and 2018",
),
dcc.Graph(
figure={
"data": [
{
"x": data["Date"],
"y": data["AveragePrice"],
"type": "lines",
},
],
"layout": {"title": "Average Price of Avocados"},
},
),
dcc.Graph(
figure={
"data": [
{
"x": data["Date"],
"y": data["Total Volume"],
"type": "lines",
},
],
"layout": {"title": "Avocados Sold"},
},
),
]
)
Well, let’s go step-by-step what’s going on here.
This code defines layout
property of app
object . The appearance of the application is described using a tree structure consisting of Dash components.
We start by defining the parent component html.Div
, then add a heading html.H1
and paragraph html.P
as children. These components are equivalent to HTML tags div
, h1
and p
accordingly. Component arguments are used to change the attributes or content of tags. For example, to indicate what is inside a div
tag , we use in html.Div
an argument children
.
Components also have other arguments, such as style
, className
or id
that refer to attributes of HTML tags. In the next section, we’ll see how to use these properties to style the toolbar.
Thus, the Python code will be converted to the following HTML code:
<div>
<h1>Avocado Analytics</h1>
<p>
Analyze the behavior of avocado prices and the number
of avocados sold in the US between 2015 and 2018
</p>
<!-- The rest of the app -->
</div>
Next two components of dcc.Graph
are described below. The first chart displays the average avocado prices over the study period, and the second shows the number of avocados sold in the US during the same period.
Under the hood, Dash uses Plotly.js to create graphs. The dcc.Graph
components expect a figure object or a Python dictionary containing the graph data and layout
that we are passing in our case.
There are two lines of code left to help launch the application:
if __name__ == "__main__":
app.run_server(debug=True,
host = '127.0.0.1')
These lines allow you to run your Dash application locally using Flask’s built-in server. The debug = True
parameter from app.run_server
allows hot reloads: when we make changes to the application, it automatically reloads without restarting the server.
Finally, the full version of app.py
. You can copy the code to blank app.py
and check the result.
import dash
import dash_core_components as dcc
import dash_html_components as html
import pandas as pd
data = pd.read_csv("avocado.csv")
data = data.query("type == 'conventional' and region == 'Albany'")
data["Date"] = pd.to_datetime(data["Date"], format="%Y-%m-%d")
data.sort_values("Date", inplace=True)
app = dash.Dash(__name__)
app.layout = html.Div(
children=[
html.H1(children="Avocado Analytics",),
html.P(
children="Analyze the behavior of avocado prices"
" and the number of avocados sold in the US"
" between 2015 and 2018",
),
dcc.Graph(
figure={
"data": [
{
"x": data["Date"],
"y": data["AveragePrice"],
"type": "lines",
},
],
"layout": {"title": "Average Price of Avocados"},
},
),
dcc.Graph(
figure={
"data": [
{
"x": data["Date"],
"y": data["Total Volume"],
"type": "lines",
},
],
"layout": {"title": "Avocados Sold"},
},
),
]
)
if __name__ == "__main__":
app.run_server(debug=True,
host = '127.0.0.1')
It’s time to finally ✨launch the application ✨.
Open a terminal in the project root and in the virtual project environment. Run python app.py
, then go to the address http://localhost:8050
in your browser.
The control panel should look something like this:
We now have a working version, but we will improve it further.
🌟Controlling panel appearance
The reason why I liked Dash is that it becomes very flexible in customizing the look and feel of your application. We can use our own CSS or JavaScript files, embed images, and configure additional options.
How to style Dash components
You can style components in two separate ways:
style
argument of distinct components.The style
argument takes a Python dictionary with key-value pairs consisting of the CSS property names and the values we want to set.
When we want to change the size and color of the H1
element in app.py
, we can set the style
argument like this:
html.H1(
children="Avocado Analytics",
style={"fontSize": "48px", "color": "red"},
),
In this case, the title will be formatted in 48 pixels.
The downside to ease of use style
is that code like this will become more difficult to maintain as the codebase grows. If there are several of the same components on the control panel, most of the code will be repeated. You can use a CSS file instead.
If you want to include your own local CSS or JavaScript files, you must create a folder named assets/
in the root directory of the project and save the necessary files in it.
You can then use the className
arguments or id
of components to style them using CSS. When converted to HTML tags, these arguments match the class
and id
attributes .
When we want to customize the font size and text color of an H1
element in app.py
, we can use an className
argument:
html.H1(
children="Avocado Analytics",
className="header-title",
),
Setting the argument specifies the className
class attribute for the H1
element. Then, in the CSS file (style.css
) in the assets/
folder, we specify how we want it to look:
.header-title {
font-size: 48px;
color: red;
}
How to improve the toolbar appearance
Let’s find out how to customize the appearance of the toolbar. Let’s make the following improvements:
favicon
) and title.Adding external resources to the application
Let’s create assets/
folder in the root of the project. Save the favicon.ico icon and the style.css
.
By now, the project structure should look like this:
avocado_analytics/
├── assets/
│ ├── favicon.ico
│ └── style.css
├── venv/
├── app.py
└── avocado.csv
app.py
requires several changes. You need to include an external style sheet, add a title to the toolbar, and style the components using the style.css
file:
external_stylesheets = [
{
"href": "https://fonts.googleapis.com/css2?"
"family=Lato:wght@400;700&display=swap",
"rel": "stylesheet",
},
]
app = dash.Dash(__name__, external_stylesheets=external_stylesheets)
app.title = "Avocado Analytics: Understand Your Avocados!"
Here we specify the CSS file and font family that we want to load into the application. External files are loaded before the application body is loaded. The external_stylesheets
argument is used to add external CSS files, and external_scripts
is used for external JavaScript files such as a Google Analytics script.
Customizing component styles
In the code below, we add className
with the appropriate class selector to each of the components representing the title of the dashboard:
app.layout = html.Div(
children=[
html.Div(
children=[
html.P(children="🥑", className="header-emoji"),
html.H1(
children="Avocado Analytics", className="header-title"
),
html.P(
children="Analyze the behavior of avocado prices"
" and the number of avocados sold in the US"
" between 2015 and 2018",
className="header-description",
),
],
className="header",
),
The header-description
class assigned to the paragraph component has a matching selector in style.css
:
.header-description {
color: #CFCFCF;
margin: 4px auto;
text-align: center;
max-width: 384px;
}
Another significant change is the graphics. New code for the price chart:
html.Div(
children=[
html.Div(
children=dcc.Graph(
id="price-chart",
config={"displayModeBar": False},
figure={
"data": [
{
"x": data["Date"],
"y": data["AveragePrice"],
"type": "lines",
"hovertemplate": "$%{y:.2f}"
"<extra></extra>",
},
],
"layout": {
"title": {
"text": "Average Price of Avocados",
"x": 0.05,
"xanchor": "left",
},
"xaxis": {"fixedrange": True},
"yaxis": {
"tickprefix": "$",
"fixedrange": True,
},
"colorway": ["#17B897"],
},
},
),
className="card",
),
We removed the default bar that appears on the chart and set the hover pattern so that when you hover over the data point, the price is displayed in dollars. So $2.5
will be displayed instead of 2.5
.
We also set up the axis, the color of the picture, the format of the title in the section of the chart layout. We also wrapped the schedule in html.Div
with the class card
class. This will give the graphic a white background and add a little shadow underneath. Similar changes have been made to the sales and volume schedules. Here is the complete code of the updated app.py
:
import dash
import dash_core_components as dcc
import dash_html_components as html
import pandas as pd
data = pd.read_csv("avocado.csv")
data = data.query("type == 'conventional' and region == 'Albany'")
data["Date"] = pd.to_datetime(data["Date"], format="%Y-%m-%d")
data.sort_values("Date", inplace=True)
external_stylesheets = [
{
"href": "https://fonts.googleapis.com/css2?"
"family=Lato:wght@400;700&display=swap",
"rel": "stylesheet",
},
]
app = dash.Dash(__name__, external_stylesheets=external_stylesheets)
app.title = "Avocado Analytics: Understand Your Avocados!"
app.layout = html.Div(
children=[
html.Div(
children=[
html.P(children="🥑", className="header-emoji"),
html.H1(
children="Avocado Analytics", className="header-title"
),
html.P(
children="Analyze the behavior of avocado prices"
" and the number of avocados sold in the US"
" between 2015 and 2018",
className="header-description",
),
],
className="header",
),
html.Div(
children=[
html.Div(
children=dcc.Graph(
id="price-chart",
config={"displayModeBar": False},
figure={
"data": [
{
"x": data["Date"],
"y": data["AveragePrice"],
"type": "lines",
"hovertemplate": "$%{y:.2f}"
"<extra></extra>",
},
],
"layout": {
"title": {
"text": "Average Price of Avocados",
"x": 0.05,
"xanchor": "left",
},
"xaxis": {"fixedrange": True},
"yaxis": {
"tickprefix": "$",
"fixedrange": True,
},
"colorway": ["#17B897"],
},
},
),
className="card",
),
html.Div(
children=dcc.Graph(
id="volume-chart",
config={"displayModeBar": False},
figure={
"data": [
{
"x": data["Date"],
"y": data["Total Volume"],
"type": "lines",
},
],
"layout": {
"title": {
"text": "Avocados Sold",
"x": 0.05,
"xanchor": "left",
},
"xaxis": {"fixedrange": True},
"yaxis": {"fixedrange": True},
"colorway": ["#E12D39"],
},
},
),
className="card",
),
],
className="wrapper",
),
]
)
if __name__ == "__main__":
app.run_server(debug=True)
The panel of the updated version looks like this: app.py
Adding interactive elements to your Dash application
Dash’s interactivity is based on the paradigm of reactive programming. This means that we can link the components and elements of the application that we want to update. If the user interacts with an input component, such as a drop-down list or slider, then the data output object, such as a graph, will automatically respond to input changes.
Let’s make the control panel interactive. The new version will allow the user to interact with the following filters:
Let’s start by replacing the local version of app.py
with the new version:
import dash
import dash_core_components as dcc
import dash_html_components as html
import pandas as pd
import numpy as np
from dash.dependencies import Output, Input
data = pd.read_csv("avocado.csv")
data["Date"] = pd.to_datetime(data["Date"], format="%Y-%m-%d")
data.sort_values("Date", inplace=True)
external_stylesheets = [
{
"href": "https://fonts.googleapis.com/css2?"
"family=Lato:wght@400;700&display=swap",
"rel": "stylesheet",
},
]
app = dash.Dash(__name__, external_stylesheets=external_stylesheets)
app.title = "Avocado Analytics: Understand Your Avocados!"
app.layout = html.Div(
children=[
html.Div(
children=[
html.P(children="🥑", className="header-emoji"),
html.H1(
children="Avocado Analytics", className="header-title"
),
html.P(
children="Analyze the behavior of avocado prices"
" and the number of avocados sold in the US"
" between 2015 and 2018",
className="header-description",
),
],
className="header",
),
html.Div(
children=[
html.Div(
children=[
html.Div(children="Region", className="menu-title"),
dcc.Dropdown(
id="region-filter",
options=[
{"label": region, "value": region}
for region in np.sort(data.region.unique())
],
value="Albany",
clearable=False,
className="dropdown",
),
]
),
html.Div(
children=[
html.Div(children="Type", className="menu-title"),
dcc.Dropdown(
id="type-filter",
options=[
{"label": avocado_type, "value": avocado_type}
for avocado_type in data.type.unique()
],
value="organic",
clearable=False,
searchable=False,
className="dropdown",
),
],
),
html.Div(
children=[
html.Div(
children="Date Range",
className="menu-title"
),
dcc.DatePickerRange(
id="date-range",
min_date_allowed=data.Date.min().date(),
max_date_allowed=data.Date.max().date(),
start_date=data.Date.min().date(),
end_date=data.Date.max().date(),
),
]
),
],
className="menu",
),
html.Div(
children=[
html.Div(
children=dcc.Graph(
id="price-chart", config={"displayModeBar": False},
),
className="card",
),
html.Div(
children=dcc.Graph(
id="volume-chart", config={"displayModeBar": False},
),
className="card",
),
],
className="wrapper",
),
]
)
@app.callback(
[Output("price-chart", "figure"), Output("volume-chart", "figure")],
[
Input("region-filter", "value"),
Input("type-filter", "value"),
Input("date-range", "start_date"),
Input("date-range", "end_date"),
],
)
def update_charts(region, avocado_type, start_date, end_date):
mask = (
(data.region == region)
& (data.type == avocado_type)
& (data.Date >= start_date)
& (data.Date <= end_date)
)
filtered_data = data.loc[mask, :]
price_chart_figure = {
"data": [
{
"x": filtered_data["Date"],
"y": filtered_data["AveragePrice"],
"type": "lines",
"hovertemplate": "$%{y:.2f}<extra></extra>",
},
],
"layout": {
"title": {
"text": "Average Price of Avocados",
"x": 0.05,
"xanchor": "left",
},
"xaxis": {"fixedrange": True},
"yaxis": {"tickprefix": "$", "fixedrange": True},
"colorway": ["#17B897"],
},
}
volume_chart_figure = {
"data": [
{
"x": filtered_data["Date"],
"y": filtered_data["Total Volume"],
"type": "lines",
},
],
"layout": {
"title": {"text": "Avocados Sold", "x": 0.05, "xanchor": "left"},
"xaxis": {"fixedrange": True},
"yaxis": {"fixedrange": True},
"colorway": ["#E12D39"],
},
}
return price_chart_figure, volume_chart_figure
if __name__ == "__main__":
app.run_server(debug=True,
host='127.0.0.1')
Then you need to update style.css
with the following code:
body {
font-family: "Lato", sans-serif;
margin: 0;
background-color: #F7F7F7;
}
.header {
background-color: #222222;
height: 288px;
padding: 16px 0 0 0;
}
.header-emoji {
font-size: 48px;
margin: 0 auto;
text-align: center;
}
.header-title {
color: #FFFFFF;
font-size: 48px;
font-weight: bold;
text-align: center;
margin: 0 auto;
}
.header-description {
color: #CFCFCF;
margin: 4px auto;
text-align: center;
max-width: 384px;
}
.wrapper {
margin-right: auto;
margin-left: auto;
max-width: 1024px;
padding-right: 10px;
padding-left: 10px;
margin-top: 32px;
}
.card {
margin-bottom: 24px;
box-shadow: 0 4px 6px 0 rgba(0, 0, 0, 0.18);
}
.menu {
height: 112px;
width: 912px;
display: flex;
justify-content: space-evenly;
padding-top: 24px;
margin: -80px auto 0 auto;
background-color: #FFFFFF;
box-shadow: 0 4px 6px 0 rgba(0, 0, 0, 0.18);
}
.Select-control {
width: 256px;
height: 48px;
}
.Select--single > .Select-control .Select-value, .Select-placeholder {
line-height: 48px;
}
.Select--multi .Select-value-label {
line-height: 32px;
}
.menu-title {
margin-bottom: 6px;
font-weight: bold;
color: #079A82;
}
How to create interactive components
New html.Div
above charts includes two dropdowns and a date range selector that the user can use to filter data and update charts.
This is how it looks in app.py
:
html.Div(
children=[
html.Div(
children=[
html.Div(children="Region", className="menu-title"),
dcc.Dropdown(
id="region-filter",
options=[
{"label": region, "value": region}
for region in np.sort(data.region.unique())
],
value="Albany",
clearable=False,
className="dropdown",
),
]
),
html.Div(
children=[
html.Div(children="Type", className="menu-title"),
dcc.Dropdown(
id="type-filter",
options=[
{"label": avocado_type, "value": avocado_type}
for avocado_type in data.type.unique()
],
value="organic",
clearable=False,
searchable=False,
className="dropdown",
),
],
),
html.Div(
children=[
html.Div(
children="Date Range",
className="menu-title"
),
dcc.DatePickerRange(
id="date-range",
min_date_allowed=data.Date.min().date(),
max_date_allowed=data.Date.max().date(),
start_date=data.Date.min().date(),
end_date=data.Date.max().date(),
),
]
),
],
className="menu",
),
The drop-down lists and date range selector serve as menus for interacting with the data:
The first component in the menu is the Region dropdown. Component code:
html.Div(
children=[
html.Div(children="Region", className="menu-title"),
dcc.Dropdown(
id="region-filter",
options=[
{"label": region, "value": region}
for region in np.sort(data.region.unique())
],
value="Albany",
clearable=False,
className="dropdown",
),
]
),
Here’s what each parameter means:
id
- element identifier.options
- options displayed when selecting the dropdown list. Waits for a dictionary with labels and values.value
- the default value when loading the page.clearable
- Allows the user to leave the field blank if set True
.className
- class selector used to apply stylesThe Type and Data Range selectors have the same structure as the Region drop-down menu.
Now let’s take a look at the components of dcc.Graphs
:
html.Div(
children=[
html.Div(
children=dcc.Graph(
id="price-chart", config={"displayModeBar": False},
),
className="card",
),
html.Div(
children=dcc.Graph(
id="volume-chart", config={"displayModeBar": False},
),
className="card",
),
],
className="wrapper",
),
Components lack figure
argument compared to the previous version of the figure
toolbar. This is because the argument will now be generated by the callback function using the input that the user sets with the Region, Type, and Data Range selectors.
💬How to define callbacks
We have defined how the user will interact with the application. Now we need to make the application respond to user actions. For this we will use the callback functions ( callbacks
).
Dash callback functions are regular Python functions with a decorator called app.callback
.
When the input changes, a callback function runs, performs predefined operations (such as filtering a dataset), and returns the result to the application. Basically, callbacks bind input and output data in an application.
Here is the callback function used to update the graphs:
@app.callback(
[Output("price-chart", "figure"), Output("volume-chart", "figure")],
[
Input("region-filter", "value"),
Input("type-filter", "value"),
Input("date-range", "start_date"),
Input("date-range", "end_date"),
],
)
def update_charts(region, avocado_type, start_date, end_date):
mask = (
(data.region == region)
& (data.type == avocado_type)
& (data.Date >= start_date)
& (data.Date <= end_date)
)
filtered_data = data.loc[mask, :]
price_chart_figure = {
"data": [
{
"x": filtered_data["Date"],
"y": filtered_data["AveragePrice"],
"type": "lines",
"hovertemplate": "$%{y:.2f}<extra></extra>",
},
],
"layout": {
"title": {
"text": "Average Price of Avocados",
"x": 0.05,
"xanchor": "left",
},
"xaxis": {"fixedrange": True},
"yaxis": {"tickprefix": "$", "fixedrange": True},
"colorway": ["#17B897"],
},
}
volume_chart_figure = {
"data": [
{
"x": filtered_data["Date"],
"y": filtered_data["Total Volume"],
"type": "lines",
},
],
"layout": {
"title": {
"text": "Avocados Sold",
"x": 0.05,
"xanchor": "left"
},
"xaxis": {"fixedrange": True},
"yaxis": {"fixedrange": True},
"colorway": ["#E12D39"],
},
}
return price_chart_figure, volume_chart_figure
First, we define the output using Output
objects. These objects take two arguments:
Output("price-chart", "figure")
update the figure
property of the "price-chart"
item.Then we define the inputs using Input
objects, they also take two arguments:
That is Input("region-filter", "value")
, it will monitor the "region-filter"
element’s changes and will accept its property if the element changes.
Note
The object discussed here is
Input
imported fromdash.dependencies
. Do not confuse it with a component coming fromdash_core_components
. These objects are not interchangeable and have different purposes.In the last lines of the above block, we define the body of the function. In the above example, the function takes input (region, avocado type, and date range), filters it, and generates objects for price and volume charts.
This is the latest version of our toolbar. We made it not only beautiful but also interactive. The only missing step is to make the result shareable with others.
Deploying a Dash application on Heroku
We have finished building the application. We have a beautiful, fully interactive dashboard. Now we will learn how to deploy it.
In fact, Dash apps are the same as Flask apps, so they have the same deployment capabilities. In this section, we will deploy the application to Heroku hosting (with a free plan).
Before starting, make sure you have installed the Heroku Command Line Interface (CLI) and Git. To make sure that both programs are present on the system, run the version check commands in the terminal:
git --version
heroku --version
Next, we need to make a small change to app.py
. After initializing the application, add a variable named server
:
app = dash.Dash(__name__, external_stylesheets=external_stylesheets)
server = app.server
This add-on is required to run the application using a WSGI server. The built-in Flask server is not recommended for use in a production environment as it cannot handle a lot of traffic.
In the root directory of the project, create a file with a name in which we specify the Python version for the Heroku application: runtime.txt
python-3.8.6
When deployed, Heroku will automatically detect that this is a Python application and use the appropriate build package. If you provide the runtime.txt
file as well, the server will determine the Python version that the application will use.
Then, in the root directory of the project, create requirements.txt
file where we list the libraries required to install the Dash application on the web server:
dash==1.13.3
pandas==1.0.5
gunicorn==20.0.4
In the requirements.txt
file there is a gunicorn
package, which we have not mentioned before. Gunicorn is basically a HTTP server that is often used to deploy Flask applications in a production environment.
Now let’s create a file named Procfile
with the following content:
web: gunicorn app:server
This file tells the Heroku application what commands to run to launch our application.
Then you need to initialize the Git repository. To do this, go to the root directory of the project and run the following command:
git init
This command will initiate the creation of a Git repository for avocado_analytics/
. That is, Git will keep track of the changes we make to files in that directory.
However, there are files that are not worth tracking with Git. For example, we usually do not want to track the contents of the virtual environment directory, bytecode files, and metadata files such as .DS_Store
.
Create a file in the root directory named .gitignore
and the following contents:
# Only if you are using macOS
venv *.pyc .DS_Store
This ensures that the repository does not track unnecessary files. Now let’s fix the state of the project:
git add . git commit -m 'Add dashboard files'
Make sure everything is in place before the last step. The project structure should look like this:
avocado_analytics/
├── assets/
│ ├── favicon.ico
│ └── style.css
├── venv/
├── app.py
├── avocado.csv
├── Procfile
├── requirements.txt
└── runtime.txt
Finally, you need to create an application in Heroku, upload your code there using Git, and run the application on one of the free Heroku server options. To do this, run the following commands:
heroku create APP-NAME # Insert your app name instead of APP-NAME
git push heroku master
heroku ps:scale web=1
That’s it. We created and deployed a data dashboard. To access the application, just copy the link: https://APP-NAME.herokuapp.com/ and replace it with the name you defined in the previous step. APP-NAME
Conclusion
Congratulations! You have just created, configured and deployed your dashboard using Dash. We’ve gone from a simple dashboard to fully interactive and deployed on a remote server. With this knowledge, we can use Dash to create analytic applications that can be shared with colleagues and customers.
#webapp #dashboards #python #dash #datavisualizations
1626155160
Data Analysis and Data Dashboards in Excel : Day-3
7 Days Free Bootcamp on Data Analysis and Data Dashboards in Excel in collaboration with Microsoft Learn Student Ambassador Program and Google Developer Groups…
Link to data last class data:
https://drive.google.com/file/d/1HF9_JnWv7yCwcCS-G_R_6wje9wygfIQ0/view?usp=sharing
Link to data visualization data:
https://drive.google.com/file/d/1e6-sOrqAOyh4F5gUi7wZp8x8mmTLaddQ/view?usp=sharing
Link to the notebook:
https://github.com/ShapeAI/Python-and…
Student Influencer Program Application:
https://forms.gle/52RNVYRtM94x4v9B7
#data #analysis #dashboards #excel
1626068700
7 Days Free Bootcamp on Data Analysis and Data Dashboards in Excel in collaboration with Microsoft Learn Student Ambassador Program and Google Developer Groups…
Link to data:
https://drive.google.com/file/d/1HF9_JnWv7yCwcCS-G_R_6wje9wygfIQ0/view?usp=sharing
Link to the notebook:
https://github.com/ShapeAI/Python-and…
Student Influencer Program Application:
https://forms.gle/52RNVYRtM94x4v9B7
#analysis #data #dashboards #excel #google #microsoft
1623835967
Just like monitoring our applications, it is very much necessary to monitor our database system. We want to monitor things substantially more granular than system-level processes — things such as:
And so on.
Hardware metrics are essential, but can only get us so far, and will not help us make concrete decisions — like which queries to optimize and how we are using our database in general. Since PostgreSQL is an open-source database, it offers a lot of insights for free without having to rely on costly tools. Most of the metrics we are going to track are available by default since they are required for the query planner, but there are other critical areas for which we have to enable certain extensions/contrib modules that ship along with PostgreSQL.
Arctype is a very neat database tool that can be used not just as an SQL editor but also as a platform upon which complex dashboards can be both custom-built and shared around with developers/users. To understand its true power, we are going to consider a practical use case — monitoring a PostgreSQL database health.
#database #tutorial #postgresql #schema #dashboards #database tutorial
1590219960
MongoDB Charts on Atlas now supports dashboard-level filters, allowing you to quickly change the context of a single dashboard to better focus your analysis.
#mongodb #dashboards