{ "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 }