{
"cells": [
{
"cell_type": "markdown",
"metadata": {},
"source": [
"# Goal: Recreate Gapminder's Bubble Chart*"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"![Gapminder](Gapminder.png)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"*As much as possible..."
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Import libraries"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"import pandas as pd\n",
"import plotly.express as px\n",
"import plotly.graph_objs as go"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Get the data"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"df = pd.read_csv('Gapminder-data.csv', sep=',')\n",
"df.head()"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"df_info = pd.read_csv('Gapminder-info.csv', sep=',', index_col=0)\n",
"df_info"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Create the bubble chart"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"scrolled": false
},
"outputs": [],
"source": [
"df = df.sort_values(['Year', 'Population'], ascending=[True, False])"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"color_map = {\n",
" 'Asia': '#ff798e', \n",
" 'Europe': '#ffeb33', \n",
" 'Africa': '#33dded',\n",
" 'Americas': '#98ef33'\n",
"}"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"fig = px.scatter(\n",
" df.query('Year==2020'), \n",
" x='Income', \n",
" y='Life expectancy', \n",
" color='Region',\n",
" size='Population', \n",
" size_max=60,\n",
" log_x=True, \n",
" hover_name='Country', \n",
" hover_data={c: False for c in df.columns},\n",
" color_discrete_map=color_map,\n",
" title='Gapminder
Data from gapminder.org, CC-BY license'\n",
")"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"# Adjust markers\n",
"fig.update_traces(marker=dict(line=dict(color='black', width=0.8), opacity=1))\n",
"# Adjust figure layout\n",
"fig.update_layout(plot_bgcolor='white', \n",
" font=dict(color='dimgray'), \n",
" hoverlabel=dict(font_size=16)) #bgcolor='white', \n",
"# Adjust axes and grid\n",
"fig.update_xaxes(showline=True, linewidth=1, linecolor='dimgray', gridcolor='lightgray', \n",
" showspikes=True, spikecolor='dimgray', spikethickness=1)\n",
"fig.update_yaxes(showline=True, linewidth=1, linecolor='dimgray', gridcolor='lightgray', \n",
" showspikes=True, spikecolor='dimgray', spikethickness=1)\n",
"fig.show()"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Add animation"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"scrolled": false
},
"outputs": [],
"source": [
"def gapminder_fig(xaxis='Income', yaxis='Life expectancy'):\n",
" \n",
" def background_year(year):\n",
" \"\"\"Return the dictionary containing a single position for the text that shows the year\"\"\"\n",
" return dict(\n",
" x=[df_info.loc[xaxis, 'Mid']],\n",
" y=[df_info.loc[yaxis, 'Mid']],\n",
" mode=\"text\",\n",
" showlegend=False,\n",
" text=[\"{}\".format(year)],\n",
" textposition=\"middle center\",\n",
" textfont=dict(size=200, color=\"lightgray\")\n",
" )\n",
" \n",
" # Define the colors for the continents\n",
" color_map = {\n",
" 'Asia': '#ff798e', \n",
" 'Europe': '#ffeb33', \n",
" 'Africa': '#33dded',\n",
" 'Americas': '#98ef33'\n",
" }\n",
" \n",
" # Create the figure\n",
" fig = px.scatter(\n",
" df.query('Year>=2000'), \n",
" x=xaxis, \n",
" y=yaxis, \n",
" color='Region',\n",
" size='Population', \n",
" size_max=60,\n",
" log_x=df_info.loc[xaxis, 'LogScale'], \n",
" log_y=df_info.loc[yaxis, 'LogScale'], \n",
" hover_name='Country', \n",
" hover_data={c: False for c in df.columns},\n",
" color_discrete_map=color_map,\n",
" title='Gapminder
Data from gapminder.org, CC-BY license',\n",
" animation_frame='Year',\n",
" animation_group='Country', \n",
" range_x=[df_info.loc[xaxis, 'Min'], df_info.loc[xaxis, 'Max']], \n",
" range_y=[df_info.loc[yaxis, 'Min'], df_info.loc[yaxis, 'Max']]\n",
" )\n",
" # Adjust markers\n",
" fig.update_traces(marker=dict(line=dict(color='black', width=0.8), opacity=1))\n",
" # Adjust figure layout\n",
" fig.update_layout(plot_bgcolor='white', yaxis=dict(rangemode='tozero'), font=dict(color='dimgray'), \n",
" hoverlabel=dict(font_size=16)) \n",
" fig.update_layout(autosize=False, width=800, height=600)\n",
" \n",
" # Adjust axes and grid\n",
" fig.update_xaxes(showline=True, linewidth=1, linecolor='dimgray', gridcolor='lightgray', \n",
" showspikes=True, spikecolor='dimgray', spikethickness=1)\n",
" fig.update_yaxes(showline=True, linewidth=1, linecolor='dimgray', gridcolor='lightgray', \n",
" showspikes=True, spikecolor='dimgray', spikethickness=1)\n",
" \n",
" # Show the year in the background\n",
" frame_year = fig.frames[0].name\n",
" fig.add_trace(go.Scatter(background_year(frame_year)))\n",
" fig.data = (fig.data[-1], ) + fig.data[:-1]\n",
" for frame in fig.frames:\n",
" frame.data = (background_year(frame.name), ) + frame.data\n",
" # Remove year from the slider\n",
" fig.layout.sliders[0].currentvalue.update({'visible': False})\n",
" \n",
" # Add annotations to the axes\n",
" fig.add_annotation(xref='x domain', yref='y domain', x=1, y=0, \n",
" text=df_info.loc[xaxis, 'Meaning'], showarrow=False, align='right')\n",
" fig.add_annotation(xref='x domain', yref='y domain', x=0, y=1, \n",
" text=df_info.loc[yaxis, 'Meaning'], showarrow=False, valign='top', textangle=-90)\n",
" \n",
" # Emphasize the selected point (hold shift for selecting multiple countries)\n",
" fig.update_layout(clickmode='event+select')\n",
" \n",
" return fig\n",
"fig = gapminder_fig()\n",
"fig.show()"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Make axes choice interactive"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"import numpy as np\n",
"from jupyter_dash import JupyterDash\n",
"from dash import dcc, html\n",
"from dash.dependencies import Input, Output\n",
"import json"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"- Dash html components: https://dash.plotly.com/dash-html-components\n",
"- Dash core components: https://dash.plotly.com/dash-core-components"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"external_stylesheets = ['https://codepen.io/chriddyp/pen/bWLwgP.css']\n",
"app = JupyterDash(__name__, external_stylesheets=external_stylesheets)\n",
"attributes = ['Income', 'Life expectancy', 'Fertility', 'Child mortality']\n",
"app.layout = html.Div([\n",
" html.H1(\n",
" 'Interactive data visualization', \n",
" style={'fontSize': 50}#, 'text-align': 'center'}\n",
" ),\n",
" dcc.Graph(\n",
" id='plot', \n",
" figure=gapminder_fig()\n",
" ),\n",
" html.Div([\n",
" html.P('X axis'),\n",
" dcc.Dropdown(\n",
" id='dropdown_x',\n",
" options=[{'label': a, 'value': a} for a in attributes],\n",
" value='Income',\n",
" clearable=False\n",
" )],\n",
" style={'width': '200px', 'display': 'inline-block', 'padding-right': '10px'}\n",
" ),\n",
" html.Div([\n",
" html.P('Y axis'),\n",
" dcc.Dropdown(\n",
" id='dropdown_y',\n",
" options=[{'label': a, 'value': a} for a in attributes],\n",
" value='Life expectancy',\n",
" clearable=False\n",
" )],\n",
" style={'width': '200px', 'display': 'inline-block'}\n",
" ),\n",
"],\n",
" style={'width': '800px', 'margin': '0 auto'}\n",
")\n",
"\n",
"@app.callback(\n",
" Output('plot', 'figure'), \n",
" [Input('dropdown_x', 'value'), Input('dropdown_y', 'value')])\n",
"def update_plot(value_x, value_y):\n",
" return gapminder_fig(xaxis=value_x, yaxis=value_y)"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"app.run_server(debug=True, mode='inline')"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Retreive information from selected points"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"external_stylesheets = ['https://codepen.io/chriddyp/pen/bWLwgP.css']\n",
"app = JupyterDash(__name__, external_stylesheets=external_stylesheets)\n",
"attributes = ['Income', 'Life expectancy', 'Fertility', 'Child mortality']\n",
"app.layout = html.Div([\n",
" html.H1(\n",
" 'Interactive data visualization', \n",
" style={'fontSize': 50}#, 'text-align': 'center'}\n",
" ),\n",
" dcc.Graph(\n",
" id='plot', \n",
" figure=gapminder_fig()\n",
" ),\n",
" html.Div([\n",
" html.P('X axis'),\n",
" dcc.Dropdown(\n",
" id='dropdown_x',\n",
" options=[{'label': a, 'value': a} for a in attributes],\n",
" value='Income',\n",
" clearable=False\n",
" )],\n",
" style={'width': '200px', 'display': 'inline-block', 'padding-right': '10px'}\n",
" ),\n",
" html.Div([\n",
" html.P('Y axis'),\n",
" dcc.Dropdown(\n",
" id='dropdown_y',\n",
" options=[{'label': a, 'value': a} for a in attributes],\n",
" value='Life expectancy',\n",
" clearable=False\n",
" )],\n",
" style={'width': '200px', 'display': 'inline-block'}\n",
" ),\n",
" html.Div([\n",
" html.P('Selected countries'),\n",
" dcc.Textarea(\n",
" id='text',\n",
" value='',\n",
" contentEditable=False,\n",
" draggable=False,\n",
" style={'width': '100%', 'height': '400px'}\n",
" )],\n",
" style={'padding-top': '10px'}\n",
" ),\n",
"],\n",
" style={'width': '800px', 'margin': '0 auto'}\n",
")\n",
"\n",
"@app.callback(\n",
" Output('plot', 'figure'), \n",
" [Input('dropdown_x', 'value'), Input('dropdown_y', 'value')])\n",
"def update_plot(value_x, value_y):\n",
" return gapminder_fig(xaxis=value_x, yaxis=value_y)\n",
"\n",
"@app.callback(\n",
" Output('text', 'value'),\n",
" Input('plot', 'selectedData'))\n",
"def print_value(selected_data):\n",
" return json.dumps(selected_data, indent=2) "
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"app.run_server(debug=True)"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": []
}
],
"metadata": {
"kernelspec": {
"display_name": "Python 3 (ipykernel)",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.9.7"
}
},
"nbformat": 4,
"nbformat_minor": 4
}