# load tutorial data
from tutorial_data import data
# activate notebook output
from bokeh.io import show, output_notebook
output_notebook()
In this chapter, you will combine all the concepts you have learned in the previous chapters to build the dashboard you saw in the introduction.
Each of the following sections contains links to previous chapters. These links take you to where these concepts were first introduced. If you are not familiar with a concept or if you need a refresher, you should click those links and read those chapters first.
The first element of the demo dashboard is the header.
The header is a Div widget. It contains the title and some basic information about the data in the dashboard.
The head Div element uses the following two parameters:
text
: A HTML string with the text to display in the Div element.sizing_mode
: The sizing mode of the Div element. In this case, it is set to
stretch_width
to make the Div element stretch to the full width of the
dashboard.This is the code to create the header Div element:
from bokeh.models import Div
header_div = Div(
text="""
<h1>US domestic air carriers</h1>
<p>Data: Bureau of Transportation Statistics, <a href="https://transtats.bts.gov/DL_SelectFields.asp?gnoyr_VQ=FIL&QO_fu146_anzr=Nv4%20Pn44vr45" target="_blank">Air Carriers: T-100 Domestic Market (U.S. Carriers)</a></p target="_blank">
""",
sizing_mode="stretch_width",
)
show(header_div)
The first plot in the dashboard is a bar chart. The bar chart shows the biggest carriers by number of passengers transported. The bar chart has three interactive features:
Before diving into the code, let's take a look at the underlying DataFrame:
df = data.get_biggest_airlines_by_passengers()
df.head()
The data contains the following columns:
unique_carrier_name
: The full name of the carrier.unique_carrier
: The IATA code of the carrier.passengers
: The total number of passengers transported by the carrier in 2021
(domestic flights)position
: The ranking of the carrier by number of passengers transported.The code below is the complete code to create the "Top carriers by passengers" plot. It consists of the following elements:
bokeh.models
module:from bokeh.models import ColumnDataSource, NumeralTickFormatter, OpenURL, TapTool, Slider, CustomJS
from bokeh.layouts import column
from bokeh.plotting import figure
# define the initial number of carriers to display
initial_carriers = 10
data
).# read date from the demo data set
source = ColumnDataSource(data.get_biggest_airlines_by_passengers())
TOOLTIPS
list when creating the figure.# set up tooltips
TOOLTIPS = [
("Position", "@position"),
("Carrier", "@unique_carrier_name"),
("Passengers", "@passengers{(0,0)}"),
]
largest_carriers_plot
. This includes the following
parameters:x_range
, you use the carrier names from theunique_carrier_name
column. The ColumnDataSource contains more than 10 carriers,
so you limit the x_range
to the first 10 carriers.title
for the plot. This title includes the initial_carriers
variablestretch_width
. This means that the
figure will stretch to the full width it has available in the layout.None
. This makes the toolbar invisible.# set up the figure
largest_carriers_plot = figure(
x_range=source.data["unique_carrier_name"][:initial_carriers], # initially, display the top 10 carriers as the plot's x_range
title=f"Top {initial_carriers} carriers by passengers (domestic routes)", # initially, display 10 as the number of carriers in the title
height=300,
sizing_mode="stretch_width",
tooltips=TOOLTIPS,
toolbar_location=None,
)
vbar
method to define the bars.
This includes the following parameters:x
coordinates of the bars are the carrier names from the unique_carrier_name
column.top
values are the number of passengers from the passengers
column.nonselection_alpha=0.8
is relevant when a user clicks on a bar to open
the IATA website. This parameter makes all non-clicked (i.e. non-selected) bars
semi-transparent. This way, a user can easily see which bar was clicked.source
for this renderer is the ColumnDataSource object called source
.legend_label
to "Passengers". This way, Bokeh will automatically generate
a legend for the plot.width
of 0.6.# add a vbar renderer
carriers_vbar = largest_carriers_plot.vbar(
x="unique_carrier_name",
top="passengers",
nonselection_alpha=0.8,
source=source,
legend_label="Passengers",
width=0.6,
)
# customize plot appearance
largest_carriers_plot.xgrid.grid_line_color = None # remove grid lines
largest_carriers_plot.yaxis.formatter = NumeralTickFormatter(format="0,0") # format y-axis ticks
largest_carriers_plot.xaxis.major_label_orientation = 0.8 # rotate labels by roughly pi/4
Add a TapTool to look up airline IATA code. Combined with an OpenURL callback, a user can click on any of the bars in the plot. This opens the IATA website with information about the selected carrier.
The url for each carrier is constructed with the IATA code from the unique_carrier
column.
Use an OpenURL callback to open the IATA
website.
# Add TapTool to look up airline IATA code
url = "https://www.iata.org/en/publications/directories/code-search/?airline.search=@unique_carrier"
taptool = largest_carriers_plot.select(type=TapTool)
taptool.callback = OpenURL(url=url)
number_slider
.
This includes the following parameters:start
value is 1. This means that the lowest possible value for
the slider is 1.end
value is 25. This is the maximum number of carriers a user
can display.value
is the initial number of carriers to display. This value
is read from len(largest_carriers_plot.x_range.factors)
. This means the slider
uses the number of carriers in the largest_carriers_plot x_range
as its
initial value. This value is set to 10 in the code above.title
is "Number of airlines to consider".width
is 400.stretch_width
. This makes the slider
stretch to the maximum available width.# Set up Slider widget
number_slider = Slider(
start=1,
end=25,
value=len(largest_carriers_plot.x_range.factors),
title="Number of airlines to consider",
sizing_mode="stretch_width",
)
args
parameter makes the following elements accessible from the JavaScript
code:largest_carriers_plot
, the bar chart plot object.source.data["unique_carrier_name"]
, the carrier names from the ColumnDataSource
object.code
parameter contains the custom JavaScript code. This code updates
the bar chart based on the slider's value.# Set up CustomJS callback
custom_js = CustomJS(
args={ # the args parameter is a dictionary of the variables that will be accessible in the JavaScript code
"largest_carriers_plot": largest_carriers_plot, # the first variable will be called "largest_carriers_plot" and links to the largest_carriers_plot Python object
"carriers": source.data["unique_carrier_name"], # the second variable will be called "carriers" and links to list of carrier names in the source ColumnDataSource
},
code="""
largest_carriers_plot.title.text = "Top " + this.value + " carriers by passenger (domestic routes)" // update the plot title using the slider's value (this.value)
largest_carriers_plot.x_range.factors = carriers.slice(0,this.value) // update the plot's x_range using data from the list of carrier names and the slider's value (this.value)
""",
)
on_change
event:# Add callback to slider widget
number_slider.js_on_change("value", custom_js)
number_slider
) and the plot(largest_carriers_plot
) into a layout:
# assemble the layout
largest_carriers_layout = column([number_slider, largest_carriers_plot], sizing_mode="stretch_width")
Now, you are able to display the full plot with its interactive features:
show(largest_carriers_layout)
This is the full code for the "Top carriers by passengers" plot:
from bokeh.models import ColumnDataSource, NumeralTickFormatter, OpenURL, TapTool, Slider, CustomJS
from bokeh.layouts import column
from bokeh.plotting import figure
# define the initial number of carriers to display
initial_carriers = 10
# read date from the demo data set
source = ColumnDataSource(data.get_biggest_airlines_by_passengers())
# set up tooltips
TOOLTIPS = [
("Position", "@position"),
("Carrier", "@unique_carrier_name"),
("Passengers", "@passengers{(0,0)}"),
]
# set up the figure
largest_carriers_plot = figure(
x_range=source.data["unique_carrier_name"][:initial_carriers], # initially, display the top 10 carriers as the plot's x_range
title=f"Top {initial_carriers} carriers by passengers (domestic routes)", # initially, display 10 as the number of carriers in the title
height=300,
sizing_mode="stretch_width",
tooltips=TOOLTIPS,
toolbar_location=None,
)
# add a vbar renderer
carriers_vbar = largest_carriers_plot.vbar(
x="unique_carrier_name",
top="passengers",
nonselection_alpha=0.8,
source=source,
legend_label="Passengers",
width=0.6,
)
# customize plot appearance
largest_carriers_plot.xgrid.grid_line_color = None # remove grid lines
largest_carriers_plot.yaxis.formatter = NumeralTickFormatter(format="0,0") # format y-axis ticks
largest_carriers_plot.xaxis.major_label_orientation = 0.8 # rotate labels by roughly pi/4
# Add TapTool to look up airline IATA code
url = "https://www.iata.org/en/publications/directories/code-search/?airline.search=@unique_carrier"
taptool = largest_carriers_plot.select(type=TapTool)
taptool.callback = OpenURL(url=url)
# Set up Slider widget
number_slider = Slider(
start=1,
end=25,
value=len(largest_carriers_plot.x_range.factors),
title="Number of airlines to consider",
sizing_mode="stretch_width",
)
# Set up CustomJS callback
custom_js = CustomJS(
args={ # the args parameter is a dictionary of the variables that will be accessible in the JavaScript code
"largest_carriers_plot": largest_carriers_plot, # the first variable will be called "largest_carriers_plot" and links to the largest_carriers_plot Python object
"carriers": source.data["unique_carrier_name"], # the second variable will be called "carriers" and links to list of carrier names in the source ColumnDataSource
},
code="""
largest_carriers_plot.title.text = "Top " + this.value + " carriers by passenger (domestic routes)" // update the plot title using the slider's value (this.value)
largest_carriers_plot.x_range.factors = carriers.slice(0,this.value) // update the plot's x_range using data from the list of carrier names and the slider's value (this.value)
""",
)
# Add callback to slider widget
number_slider.js_on_change("value", custom_js)
# assemble the layout
largest_carriers_layout = column([number_slider, largest_carriers_plot], sizing_mode="stretch_width")
show(largest_carriers_layout)
The second plot in the dashboard is a line chart. It visualizes how the total number of passengers, freight, and mail has developed throughout 2021. This chart considers all domestic flight data for the top 10 carriers.
The line chart has two interactive features:
This is the underlying DataFrame for this plot:
df = data.get_monthly_values()
df.head(3)
The DataFrame contains the following columns:
passengers
: The total number of passengers transported in that monthfreight
: The total amount of freight transported in that monthmail
: The total amount of mail transported in that monthmonth_name
: The month nameThis plot also uses a list of all available metrics. This list is also available in the
demo data set (data
):
data.metrics
The code to create the "Development of passengers, freight, and mail" plot consists of the following elements:
bokeh.models
module. In this case,
you import the Category10 palette.
You will use this palette to generate colors for the lines.from bokeh.palettes import Category10
data
).# read date from the demo data set
source = ColumnDataSource(data.get_monthly_values())
TOOLTIPS
list when creating the figure. This tooltip uses fields that are replaced
with data in the tooltip:$name
is replaced with the name of the line (e.g. "Passengers") that the
user hovers over.$y{(0,0)
is replaced with the y value of the point that the user hovers over.
The 0,0
part of the expression means that the value is displayed with a
comma as the thousands separator.# set up tooltips
TOOLTIPS = "$name: $y{(0,0)}"
largest_carriers_development_plot
.
This includes the following parameters:month_name
column.stretch_width
.# set up the figure
largest_carriers_development_plot = figure(
title="Domestic passengers, freight, and mail (top 10 carriers)",
x_range=source.data["month_name"],
height=300,
sizing_mode="stretch_width",
tooltips=TOOLTIPS,
)
mode
to vline
, you make the tooltips stick to the plot's vertical
grid lines. In this case, there is one vertical line per month.# configure HoverTool
largest_carriers_development_plot.hover.mode = "vline"
Set up the three line renderers for passengers, freight, and mail.
Since all three line renderers are very similar, you can use a for
loop to create
them.
You loop over the three metrics defined in
data.metrics
(['passengers', 'freight', 'mail']
).
The only other difference between the three line renderer is the
color assigned to the line.
To make sure all three lines use a different color, use an incremental counter that
picks a different color from the Category10
palette for each line.
# set up three line renderers
color = 0
for metric in data.metrics:
largest_carriers_development_plot.line(
x="month_name", # use the month_name column as the x-axis
y=metric, # use the metric column as the y-axis
legend_label=metric.capitalize(), # use the current metric as the legend label
source=source,
width=2,
color=Category10[3][color], # use the `color` variable to pick a different color for each iteration
alpha=1,
muted_alpha=0.2, # make lines transparent when muted
name=metric,
)
color += 1
click_policy
to "hide"
. This way, the corresponding line glyph
will disappear when a user clicks on a legend entry.# customize plot appearance
largest_carriers_development_plot.yaxis.formatter = NumeralTickFormatter(format="0,0")
largest_carriers_development_plot.xaxis.axis_label = "Month"
largest_carriers_development_plot.xaxis.major_label_orientation = 0.8 # rotate labels by roughly pi/4
largest_carriers_development_plot.legend.click_policy = "mute"
Now, you are able to display the full plot with its interactive features:
show(largest_carriers_development_plot)
This is the full code for the "Development of passengers, freight, and mail" plot:
from bokeh.palettes import Category10
# read date from the demo data set
source = ColumnDataSource(data.get_monthly_values())
# set up tooltips
TOOLTIPS = "$name: $y{(0,0)}"
# set up the figure
largest_carriers_development_plot = figure(
title="Domestic passengers, freight, and mail (top 10 carriers)",
x_range=source.data["month_name"],
height=300,
sizing_mode="stretch_width",
tooltips=TOOLTIPS,
)
# configure HoverTool
largest_carriers_development_plot.hover.mode = "vline"
# set up three line renderers
color = 0
for metric in data.metrics:
largest_carriers_development_plot.line(
x="month_name",
y=metric,
legend_label=metric.capitalize(),
source=source,
width=2,
color=Category10[3][color],
alpha=1,
muted_alpha=0.2,
name=metric,
)
color += 1
# customize plot appearance
largest_carriers_development_plot.yaxis.formatter = NumeralTickFormatter(format="0,0")
largest_carriers_development_plot.xaxis.axis_label = "Month"
largest_carriers_development_plot.xaxis.major_label_orientation = 0.8 # rotate labels by roughly pi/4
largest_carriers_development_plot.legend.click_policy = "mute"
show(largest_carriers_development_plot)
The third plot in the dashboard is a scatter plot. It visualizes all available data in the data set.
The scatter plot has two interactive features:
This is the underlying DataFrame for this plot:
df = data.get_distance_df()
df.head(3)
This plot also uses a list of all available metrics. This list is also available in the
demo data set (data
):
data.metrics
The code to create the "Distance flown" plot consists of the following elements:
# define a list of markers to use for the scatter plot
MARKERS = ["circle", "square", "triangle"]
data
).# read date from the demo data set
source = ColumnDataSource(data.get_distance_df())
# set up the tooltips
TOOLTIPS = [
("Distance", "@distance{(0,0)} miles"),
("Route", "@origin, @dest"),
("Amount", "$y{(0,0)}"),
]
distance_plot
.
This includes the following parameters:stretch_width
.box_zoom
the default zoom tool.# set up the figure
distance_plot = figure(
title="Distance flown vs. number of passengers, freight, and mail",
height=300,
sizing_mode="stretch_width", # use the full width of the parent element
tooltips=TOOLTIPS,
output_backend="webgl", # use webgl to speed up rendering
tools="pan,box_zoom,reset,save",
active_drag="box_zoom", # enable box zoom by default
)
This works similarly to the previous plot. In addition to assigning different colors in each iteration, you also assign a different marker types.
# loop through the three metrics ("passengers", "freight", "mail") and plot them
i = 0
for metric in data.metrics:
distance_plot.scatter(
"distance",
metric,
source=source,
legend_label=metric.capitalize(),
color=Category10[3][i], # assign a different color to each metric
marker=MARKERS[i], # assign a different marker to each metric
alpha=0.5,
)
i += 1
click_policy
to "hide"
. This way, the corresponding
scatter glyphs will disappear
when a user clicks on a legend entry.# customize plot appearance
distance_plot.yaxis.formatter = NumeralTickFormatter(format="0,0")
distance_plot.xaxis.axis_label = "Distance (miles)"
distance_plot.legend.click_policy = "hide" # set the legend click policy to hide
show(distance_plot)
# define a list of markers to use for the scatter plot
MARKERS = ["circle", "square", "triangle"]
source = ColumnDataSource(data.get_distance_df())
# set up the tooltips
TOOLTIPS = [
("Distance", "@distance{(0,0)} miles"),
("Route", "@origin, @dest"),
("Amount", "$y{(0,0)}"),
]
# set up the figure
distance_plot = figure(
title="Distance flown vs. number of passengers, freight, and mail",
height=300,
sizing_mode="stretch_width", # use the full width of the parent element
tooltips=TOOLTIPS,
output_backend="webgl", # use webgl to speed up rendering
tools="pan,box_zoom,reset,save",
active_drag="box_zoom", # enable box zoom by default
)
# loop through the three metrics ("passengers", "freight", "mail") and plot them
i = 0
for metric in data.metrics:
distance_plot.scatter( # use the scatter method to use different markers
"distance",
metric,
source=source,
legend_label=metric.capitalize(),
color=Category10[3][i], # assign a different color to each metric
marker=MARKERS[i], # assign a different marker to each metric
alpha=0.5,
)
i += 1
# customize plot appearance
distance_plot.yaxis.formatter = NumeralTickFormatter(format="0,0")
distance_plot.xaxis.axis_label = "Distance (miles)"
distance_plot.legend.click_policy = "hide" # set the legend click policy to hide
show(distance_plot)
This map is based on two data sets:
import geopandas as gpd
states_gdf = gpd.read_file("../data/us-states.geojson")
states_gdf.plot() # use geopandas to plot the state shapes
data
):data.get_states_routes_df().head(2)
The GeoJSONDataSource for this plot is
the result of a join
operation. The join operation creates a DataFrame with both the state shapes and the number of routes:
states_gdf = states_gdf.join(data.get_states_routes_df(), on=states_gdf["Name"])
states_gdf.head(2)
In this DataFrame, you have the following columns that you'll use in the map plot:
Name
: The name of the stateorigin
: The number of domestic routes beginning in that stategeometry
: The shape of the stateimport geopandas as gpd
from bokeh.models import GeoJSONDataSource
from bokeh.palettes import Cividis
from bokeh.transform import linear_cmap
states
DataFrame.
As described above, this is based on a joined DataFrame.
The joined DataFrame combines data from the GeoJSON file with data from the demo
data set.# read the geojson file containing the state shapes
states_gdf = gpd.read_file("../data/us-states.geojson")
# read the pre-processed data frame from the demo data set and join it to the state shapes
states_gdf = states_gdf.join(data.get_states_routes_df(), on=states_gdf["Name"])
# create the GeoJSONDataSource
geo_source = GeoJSONDataSource(geojson=states_gdf.to_json())
# set up the tooltips
TOOLTIPS = [
("State", "@Name"),
("# of routes departing from here", "@origin{(0,0)}"),
]
map_plot
.
This includes the following parameters:width
and height
for the plot. This way, you define the
aspect ratio of the map.
Defining the aspect ratio is important to make sure that the map
doesn't get distorted. This could happen when the user resizes the browser
window, for example.scale_width
. This way, the plot will always fill the
available width in the browser window. At the same time, it will keep the
aspect ratio defined by the width
and height
parameters.TOOLTIPS
list to the figure.title
for the plot.grid_line_color
to None
to remove the grid lines.# set up the figure
map_plot = figure(
height=200, # set a width and height to define the aspect ratio
width=300,
sizing_mode="scale_width",
tooltips=TOOLTIPS,
title="Number of routes with a state as its origin (all domestic carriers)",
x_axis_location=None, # deactivate x-axis
y_axis_location=None, # deactivate y-axis
toolbar_location=None, # deactivate toolbar
)
map_plot.grid.grid_line_color = None # make grid lines invisible
geometry
column in the
states
DataFrame. Bokeh automatically converts these geometries into
xs
and ys
columns. These are the columns that Bokeh uses to draw the
polygons.fill_color
to each state polygon. The color is based on the number of
routes departing from that state. Bokeh distributes the available colors from
the palette based on the min and max values in the origin
column.us = map_plot.patches( # use the patches method to draw the polygons of all states
xs="xs",
ys="ys",
fill_color=linear_cmap(field_name="origin", palette=Cividis[256], low=states_gdf["origin"].min(), high=states_gdf["origin"].max()), # color the states by mapping the number of routes to color values from a palette
source=geo_source,
line_color="darkgrey",
line_width=1,
)
construct_color_bar()
method. This uses the
following parameters:formatter
: How to format the numbers in the color bar. Use a
NumeralTickFormatter
with the format string 0,0
. This means that
Bokeh uses a comma as a thousands separator.height
: The height of the color bar in pixels.add_layout
method to
add the object and place it below the map plot.# add the color bar
color_bar = us.construct_color_bar(formatter=NumeralTickFormatter(format="0,0"), height=10)
map_plot.add_layout(obj=color_bar, place="below")
show(map_plot)
import geopandas as gpd
from bokeh.models import GeoJSONDataSource
from bokeh.palettes import Cividis
from bokeh.transform import linear_cmap
# read the geojson file containing the state shapes
states_gdf = gpd.read_file("../data/us-states.geojson")
# read the pre-processed data frame from the demo data set and join it to the state shapes
states_gdf = states_gdf.join(data.get_states_routes_df(), on=states_gdf["Name"])
# create the GeoJSONDataSource
geo_source = GeoJSONDataSource(geojson=states_gdf.to_json())
# set up the tooltips
TOOLTIPS = [
("State", "@Name"),
("# of routes departing from here", "@origin{(0,0)}"),
]
# set up the figure
map_plot = figure(
height=200, # set a width and height to define the aspect ratio
width=300,
sizing_mode="scale_width",
tooltips=TOOLTIPS,
title="Number of routes with a state as its origin (all domestic carriers)",
x_axis_location=None, # deactivate x-axis
y_axis_location=None, # deactivate y-axis
toolbar_location=None, # deactivate toolbar
)
map_plot.grid.grid_line_color = None # make grid lines invisible
# draw the state polygons
us = map_plot.patches( # use the patches method to draw the polygons of all states
xs="xs",
ys="ys",
fill_color=linear_cmap(field_name="origin", palette=Cividis[256], low=states_gdf["origin"].min(), high=states_gdf["origin"].max()), # color the states by mapping the number of routes to color values from a palette
source=geo_source,
line_color="darkgrey",
line_width=1,
)
# add color bar
color_bar = us.construct_color_bar(formatter=NumeralTickFormatter(format="0,0"), height=10)
map_plot.add_layout(obj=color_bar, place="below")
show(map_plot)
The fifth plot in the dashboard is a tabbed donut chart. It visualizes the shares of the top 10 carriers in each of the three metrics: passengers, freight, and mail.
This plot has two interactive features:
This plot uses three data sets: One for passengers, one for freight, and one for mail.
All three are available in the demo data set (data
). You can access any of these
three using the metric parameter. This is the DataFrame for the passenger data:
df = data.get_top_carriers_by_metrics("passengers")
df.head(3)
This DataFrame has the following columns:
unique_carrier_name
: The name of the carrierpassengers
: The number of passengers carried by that carrierangle
: pre-computed values for the angle of each wedgecolor
: pre-computed values for the color of each wedgeFor details about how to compute the angles, see the detailed example in the
Wedge plots section.
For the specific code generating these DataFrames, see the function
get_top_carriers_by_metrics
in carriers_data.py.
This plot also uses a list of all available metrics. This list is also available in the
demo data set (data
):
data.metrics
TabPanel
and Tabs
objects from bokeh.models.widgets. You'll also need the
cumsum
function from bokeh.transform
:from bokeh.models import TabPanel, Tabs
from bokeh.transform import cumsum
This plot uses Bokeh's Tabs widget to create a tabbed interface.
The code contains a function called create_annular_wedge
.
This function creates the donut chart for a specific metric.
This way, you only have to write the code once.
It is then run three times, once for every metric.
Let's take a closer look at the function:
metric
. This is a string defining the
metric that the function should use. The function uses this parameter to select
the correct data set from the data
object. For example, if metric
is
"passengers"
, the function builds a donut chart with the passenger data.def create_annular_wedge(metric):
# load data for current metric from demo data set
source = ColumnDataSource(data.get_top_carriers_by_metrics(metric))
# set up the tooltips
TOOLTIPS = [
("Carrier", "@unique_carrier_name"),
(metric.capitalize(), f"@{metric}{{(0,0)}}"),
]
# set up the figure for the current metric
annular_plot = figure(
height=200, # set a width and height to define the aspect ratio
width=300,
sizing_mode="scale_width",
toolbar_location=None,
outline_line_color=None,
name="region",
x_range=(-0.66, 1),
title=f"Top ten carriers by {metric}",
tooltips=TOOLTIPS,
)
# draw the annular wedges for the current metric
annular_plot.annular_wedge(
x=0,
y=0,
inner_radius=0.2,
outer_radius=0.4,
start_angle=cumsum("angle", include_zero=True),
end_angle=cumsum("angle"),
line_color="white",
fill_color="color",
legend_field="unique_carrier_name",
source=source,
)
# customize plot appearance
annular_plot.axis.visible = False
annular_plot.grid.grid_line_color = None
annular_plot.legend.spacing = 1
annular_plot.legend.label_text_font_size = "0.8em"
return annular_plot
data
):# get list of metrics to consider from demo data set
metrics = data.metrics
TabPanel
objects. To create each object, use a for
loop.
This loop iterates over the list of metrics. Each iteration creates a single
TabPanel
object with a different donut chart. The TabPanel
object takes the
following parameters:child
: The plot to display in the tab. In this case, use the create_annular_wedge
function to create the plot. Pass the metric name as a parameter.title
: The title of the tab. This is where you call the create_annular_wedge
function from above. Pass the metric name as a parameter.# call create_annular_wedge to create tabs with annular wedges for each metric
tabs = []
for metric in metrics:
tabs.append(TabPanel(child=create_annular_wedge(metric), title=metric.capitalize()))
Tabs
object takes the following parameters:tabs
: The list of TabPanel
objects that you created above.sizing_mode
: The sizing mode for the tabs, set to "stretch_width"
.# display all plots as tabs
annular_wedge_tabs = Tabs(tabs=tabs, sizing_mode="scale_width")
Display the full plot:
show(annular_wedge_tabs)
from bokeh.models import TabPanel, Tabs
# Function to create annular wedge plots for all metrics (passengers, freight, or mail)
def create_annular_wedge(metric):
# load data for current metric from demo data set
source = ColumnDataSource(data.get_top_carriers_by_metrics(metric))
# set up the tooltips
TOOLTIPS = [
("Carrier", "@unique_carrier_name"),
(metric.capitalize(), f"@{metric}{{(0,0)}}"),
]
# set up the figure for the current metric
annular_plot = figure(
height=200, # set a width and height to define the aspect ratio
width=300,
sizing_mode="scale_width",
toolbar_location=None,
outline_line_color=None,
name="region",
x_range=(-0.66, 1),
title=f"Top ten carriers by {metric}",
tooltips=TOOLTIPS,
)
# draw the annular wedges for the current metric
annular_plot.annular_wedge(
x=0,
y=0,
inner_radius=0.2,
outer_radius=0.4,
start_angle=cumsum("angle", include_zero=True),
end_angle=cumsum("angle"),
line_color="white",
fill_color="color",
legend_field="unique_carrier_name",
source=source,
)
# customize plot appearance
annular_plot.axis.visible = False
annular_plot.grid.grid_line_color = None
annular_plot.legend.spacing = 1
annular_plot.legend.label_text_font_size = "0.8em"
return annular_plot
# get list of metrics to consider from demo data set
metrics = data.metrics
# call create_annular_wedge to create tabs with annular wedges for each metric
tabs = []
for metric in metrics:
tabs.append(TabPanel(child=create_annular_wedge(metric), title=metric.capitalize()))
# display all plots as tabs
annular_wedge_tabs = Tabs(tabs=tabs, sizing_mode="scale_width")
show(annular_wedge_tabs)
The last step is to add all the plots to a single layout:
from bokeh.layouts import layout
layout = layout(
[
[header_div],
[largest_carriers_layout, largest_carriers_development_plot],
[distance_plot],
[map_plot, annular_wedge_tabs],
],
sizing_mode="stretch_width",
)
show(layout)