{ "cells": [ { "cell_type": "code", "execution_count": null, "metadata": { "scrolled": true }, "outputs": [], "source": [ "import os, numpy as np, pandas as pd, cartopy.crs as ccrs, bokeh\n", "import holoviews as hv, geoviews as gv, datashader as ds, panel as pn\n", "from holoviews.operation.datashader import rasterize, datashade\n", "from colorcet import bmy, bgyw, isolum\n", "from holoviews.util import Dynamic\n", "from bokeh.models import HoverTool, CustomJSHover\n", "hv.extension('bokeh', width=100)\n", "pn.extension()" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "from version import __version__" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Read the data " ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "data = pd.read_hdf('./data/rgi62_era5_itmix_df.h5', 'df')\n", "data = data.rename(columns={'Area':'area'})\n", "data['latdeg'] = data.CenLat\n", "data['vol_asl_m3'] = data.vol_itmix_m3 - data.vol_bsl_itmix_m3\n", "data['vol_itmix_km3'] = data.vol_itmix_m3 * 1e-9\n", "data.tail()" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "total_area = data.area.sum()\n", "total_vol = data.vol_itmix_km3.sum()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Convert to HoloViews dataset" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "data = gv.Points(data, [('CenLon', 'Longitude'), ('CenLat', 'Latitude')],\n", " [('era5_avg_pcp', 'Annual Precipitation (mm/yr)'),\n", " ('area', 'Area'), ('latdeg', 'Latitude (deg)'),\n", " ('era5_avg_temp_at_zmed', 'Annual Temperature at avg. altitude (°C)'), \n", " ('Zmed', 'Mean elevation of the glacier (m a.s.l.)'), ('vol_asl_m3', 'Volume asl'),\n", " ('vol_bsl_itmix_m3', 'Volume bsl'),\n", " ('vol_itmix_km3', 'Volume km3'),\n", " ('era5_trend', 'Temperature trend 1979-2018 (°C per decade)')])\n", "data = gv.Dataset(gv.operation.project_points(data))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Sea-level equivalent computation" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "def compute_slr(ice_vol_m3):\n", " \"\"\"ice_vol in m³ gives slr in mm\"\"\"\n", " rho = 900\n", " rho_oc = 1028\n", " A_oc = 362.5 * 1e9\n", " return ice_vol_m3 * rho / (A_oc * rho_oc)\n", "\n", "total_slr = compute_slr(data['vol_asl_m3']).sum()\n", "np.testing.assert_allclose(total_slr, 324.3, atol=0.5) # somethings slightly different..." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Plots kwargs " ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# Datashader map\n", "geo_kw = dict(aggregator=ds.sum('area'), x_sampling=1000, y_sampling=1000)\n", "# Elev vs Lat scatter\n", "elev_kw = dict(cmap='#7d3c98')\n", "# Histograms\n", "temp_kw = dict(num_bins=50, adjoin=False, normed=False, bin_range=data.range('era5_avg_temp_at_zmed'))\n", "prcp_kw = dict(num_bins=50, adjoin=False, normed=False, bin_range=(0, 6000)) # for precip we crop large values\n", "tren_kw = dict(num_bins=50, adjoin=False, normed=False, bin_range=data.range('era5_trend'))" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "size_opts_map = dict(width=715, height=520)\n", "size_opts_his = dict(height=180, width=350)\n", "size_opts_bar = dict(height=45, width=250)\n", "size_opts_slr = dict(height=300, width=120)\n", "size_text_bar = dict(height=20)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "geo_opts = dict(size_opts_map, cmap=bmy, global_extent=True, logz=True, colorbar=True, colorbar_opts={'title':'Area (km²)'},\n", " toolbar='above', projection=ccrs.GOOGLE_MERCATOR)\n", "elev_opts = dict(size_opts_his, show_grid=True)\n", "temp_opts = dict(size_opts_his, fill_color='#f1948a', default_tools=[], toolbar=None, ylabel='', alpha=1.0)\n", "prcp_opts = dict(size_opts_his, fill_color='#85c1e9', default_tools=[], toolbar=None, ylabel='', alpha=1.0)\n", "tren_opts = dict(size_opts_his, fill_color='#f4d34e', default_tools=[], toolbar=None, alpha=1.0)\n", "slr_opts = dict(size_opts_slr, color='orange', default_tools=[], toolbar=None, xlabel='', show_legend=False, yticks=[0, 50, 100, 150,200,250,300,350,390], shared_axes=False)\n", "glno_opts = dict(size_opts_bar, color='#326a86', default_tools=[], toolbar=None, alpha=0.8, invert_axes=True, show_legend=False, xaxis=None, yaxis=None, shared_axes=False)\n", "area_opts = dict(size_opts_bar, color='#326a86', default_tools=[], toolbar=None, alpha=0.8, invert_axes=True, show_legend=False, xaxis=None, yaxis=None, shared_axes=False)\n", "vol_opts = dict(size_opts_bar, color='#326a86', default_tools=[], toolbar=None, alpha=0.8, invert_axes=True, show_legend=False, xaxis=None, yaxis=None, shared_axes=False)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Language settings " ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "from international import trads, supported_languages\n", "language = 'en'" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Plots " ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "def base_slr(data):\n", " return hv.Bars([('asl', compute_slr(np.sum(data['vol_asl_m3']))), ('bsl', compute_slr(np.sum(data['vol_bsl_itmix_m3'])))])" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "static_slr = base_slr(data)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "gl_number = len(data['area'])\n", "\n", "def geo(data):\n", " return gv.Points(data, crs=ccrs.PlateCarree).options(alpha=1)\n", "\n", "def elev(data):\n", " return data.to(hv.Scatter, 'Zmed', 'latdeg', [])\n", "\n", "def temp(data):\n", " return data.hist('era5_avg_temp_at_zmed', **temp_kw).options(**temp_opts)\n", "\n", "def prcp(data):\n", " return data.hist('era5_avg_pcp', **prcp_kw).options(**prcp_opts)\n", "\n", "def tren(data):\n", " return data.hist('era5_trend', **tren_kw).options(**tren_opts)\n", "\n", "def slr(data):\n", " slr_opts['ylabel'] = trads['bar_sealevel_y'][language]\n", " return static_slr.opts(**slr_opts, alpha=0.1) * base_slr(data).opts(**slr_opts)\n", "\n", "def gl_no(data):\n", " return hv.Bars(('', len(data))).opts(**glno_opts)\n", "def area(data):\n", " return hv.Bars(('', np.sum(data['area']))).opts(**area_opts)\n", "def vol(data):\n", " return hv.Bars(('', np.sum(data['vol_itmix_km3']))).opts(**vol_opts)\n", "\n", "def count1(data): \n", " legend = trads['bar_glaciers_selected'][language]\n", " text = '
{}'\n", " v1, v2 = len(data), gl_number\n", " if trads['is_rtl'].get(language, False):\n", " return hv.Div(text.format(legend).format(v2, v1)).options(**size_text_bar)\n", " else:\n", " return hv.Div(text.format(legend).format(v1, v2)).options(**size_text_bar)\n", "\n", "def count2(data): \n", " legend = trads['bar_area'][language]\n", " v1, v2 = np.sum(data['area']), np.sum(data['area']) / total_area * 100\n", " if trads['is_rtl'].get(language, False):\n", " text = '
({:.1f}%) {:.0f} km² :{}
'\n", " return hv.Div(text.format(v2, v1, legend)).options(**size_text_bar)\n", " else:\n", " text = '{}: {:.0f} km² ({:.1f}%)
'\n", " return hv.Div(text.format(legend, v1, v2)).options(**size_text_bar)\n", "\n", "def count3(data):\n", " legend = trads['bar_volume'][language]\n", " v1, v2 = np.sum(data['vol_itmix_km3']), np.sum(data['vol_itmix_km3']) / total_vol * 100\n", " if trads['is_rtl'].get(language, False):\n", " text = '({:.1f}%) {:.0f} km³ :{}
'\n", " return hv.Div(text.format(v2, v1, legend)).options(**size_text_bar)\n", " else:\n", " text = '{}: {:.0f} km³ ({:.1f}%)
'\n", " return hv.Div(text.format(legend, v1, v2)).options(**size_text_bar)\n", "\n", "def slr_text(data):\n", " legend = trads['bar_sealevel_text'][language]\n", " v1, v2 = compute_slr(np.sum(data['vol_asl_m3'])), compute_slr(np.sum(data['vol_asl_m3'])) / total_slr * 100\n", " if trads['is_rtl'].get(language, False):\n", " text = '({:.1f}%) {:.1f} mm {}
'\n", " return hv.Div(text.format(v2, v1, legend)).options(**size_text_bar)\n", " else:\n", " text = '{}{:.1f} mm ({:.1f}%)
'\n", " return hv.Div(text.format(legend, v1, v2)).options(**size_text_bar)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Static " ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "static_geo = rasterize(geo(data), **geo_kw).options(alpha=0.1, tools=['hover', 'box_select'], active_tools=['box_select'], **geo_opts) \n", "static_elev = datashade(elev(data), **elev_kw).options(alpha=0.1, tools=[ 'box_select'], active_tools=['box_select'], toolbar=None, **elev_opts)\n", " \n", "static_gl_no= gl_no(data).options(alpha=0.1)\n", "static_area = area(data).options(alpha=0.1)\n", "static_vol = vol(data).options(alpha=0.1)\n", "\n", "static_tren = tren(data).options(alpha=0.1)\n", "static_temp = temp(data).options(alpha=0.1)\n", "static_prcp = prcp(data).options(alpha=0.1)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Dynamic selection " ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "def combine_selections(**kwargs):\n", " \"\"\"\n", " Combines selections on all available plots into a single selection by index.\n", " \"\"\"\n", " if all(not v for v in kwargs.values()):\n", " return slice(None)\n", " selection = {}\n", " for key, bounds in kwargs.items():\n", " if bounds is None:\n", " continue\n", " elif len(bounds) == 2:\n", " selection[key] = bounds\n", " else:\n", " xbound, ybound = key.split('__')\n", " selection[xbound] = bounds[0], bounds[2]\n", " selection[ybound] = bounds[1], bounds[3]\n", " return sorted(set(data.select(**selection).data.index))\n", "\n", "def select_data(**kwargs):\n", " return data.iloc[combine_selections(**kwargs)] if kwargs else data\n", "\n", "def clear_selections(arg=None):\n", " geo_bounds.update(bounds=None)\n", " elev_bounds.update(bounds=None)\n", " temp_bounds.update(boundsx=None)\n", " prcp_bounds.update(boundsx=None)\n", " tren_bounds.update(boundsx=None)\n", " Stream.trigger(selections)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Dynamic plots " ] }, { "cell_type": "code", "execution_count": null, "metadata": { "scrolled": true }, "outputs": [], "source": [ "from holoviews.streams import Stream, BoundsXY, BoundsX\n", "\n", "geo_bounds = BoundsXY(source=static_geo, rename= {'bounds': 'CenLon__CenLat'})\n", "elev_bounds = BoundsXY(source=static_elev, rename= {'bounds': 'Zmed__latdeg'})\n", "temp_bounds = BoundsX( source=static_temp, rename= {'boundsx': 'era5_avg_temp_at_zmed'})\n", "prcp_bounds = BoundsX( source=static_prcp, rename= {'boundsx': 'era5_avg_pcp'})\n", "tren_bounds = BoundsX( source= static_tren, rename= {'boundsx': 'era5_trend'})\n", "\n", "selections = [geo_bounds, elev_bounds, temp_bounds, prcp_bounds, tren_bounds]\n", "\n", "dyn_data = hv.DynamicMap(select_data, streams=selections)\n", "\n", "dyn_geo = rasterize(dyn_data.apply(geo), **geo_kw).options( **geo_opts)\n", "dyn_elev = datashade(dyn_data.apply(elev), **elev_kw).options(**elev_opts)\n", "dyn_temp = dyn_data.apply(temp)\n", "dyn_prcp = dyn_data.apply(prcp)\n", "dyn_count1= dyn_data.apply(count1)\n", "dyn_count2= dyn_data.apply(count2)\n", "dyn_count3= dyn_data.apply(count3)\n", "dyn_slr_text = dyn_data.apply(slr_text)\n", "dyn_tren = dyn_data.apply(tren)\n", "dyn_slr = dyn_data.apply(slr)\n", "dyn_gl_no = dyn_data.apply(gl_no)\n", "dyn_area = dyn_data.apply(area)\n", "dyn_vol = dyn_data.apply(vol)\n", "\n", "geo_bg = gv.tile_sources.EsriImagery.options(alpha=1.0, bgcolor=\"white\")\n", "\n", "geomap = geo_bg * static_geo * dyn_geo\n", "elevation = static_elev * dyn_elev\n", "temperature = static_temp * dyn_temp\n", "precipitation = static_prcp * dyn_prcp\n", "trends = static_tren * dyn_tren\n", "sealevelrise = dyn_slr\n", "gl_num = static_gl_no * dyn_gl_no\n", "area_bar = static_area * dyn_area\n", "vol_bar = static_vol * dyn_vol" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# set range\n", "elevation = elevation.redim(latdeg=dict(range=(-90, 90)), Zmed=dict(range=(0, 8000)))" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "def set_button_name_language():\n", " clear_button.name = trads['clear_button'][language]\n", " \n", "clear_button = pn.widgets.Button(name='', width=170)\n", "\n", "clear_button.param.watch(clear_selections, 'clicks')\n", "set_button_name_language()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Language selector " ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "def language_selector_function(arg=None):\n", " global language\n", " \n", " # Change back display to selector\n", " language = language_selector.value\n", " for k, v in trads['lang_display'].items():\n", " language = language.replace(v, k)\n", " \n", " change_language()\n", "\n", "\n", "def change_hist_label_language():\n", " temp_opts['xlabel'] = trads['temp_plot_x'][language]\n", " prcp_opts['xlabel'] = trads['precip_plot_x'][language]\n", " tren_opts['xlabel'] = trads['trend_plot_x'][language]\n", " tren_opts['ylabel'] = trads['trend_plot_y'][language]\n", "\n", "\n", "def change_language(arg=None):\n", " set_instr_text()\n", " set_explanation_text()\n", " change_hist_label_language()\n", " clear_selections()\n", " set_button_name_language()\n", " \n", "def replace_lan(l):\n", " # Change selector to display characters \n", " for k, v in trads['lang_display'].items():\n", " l = l.replace(k, v)\n", " return l \n", "\n", "language_selector = pn.widgets.RadioBoxGroup(name='Select your language',\n", " options=[replace_lan(l) for l in supported_languages],\n", " inline=True,\n", " margin=(0, 0),\n", " width=540,\n", "# background='red'\n", " )\n", "language_selector.param.watch(language_selector_function, 'value');" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "#### change width of language_selector when adding new languages" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Here you find a short introduction how to adapt the width of the language_selector if a new language is added. This is needed because the the RadioBosGroup is not adapting the width automatically to the content (see https://github.com/holoviz/panel/issues/3533).\n", "\n", "- uncomment `background='red'` in the cell above and execute cell\n", "- adapt `width` so that all languages are on the red background (include a little extra space to the right)\n", "- comment `# background='red'`\n", "- execute all cells and look at final result (in the last cell you can use `app.show()` to open in new tab)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## functions to change language with datashader" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "def elev_language(plot, element):\n", " plot.handles['xaxis'].axis_label = trads['elev_plot_x'][language]\n", " plot.handles['yaxis'].axis_label = trads['elev_plot_y'][language]\n", "\n", "\n", "def geo_language(plot, element):\n", " plot.handles['xaxis'].axis_label = trads['map_plot_x'][language]\n", " plot.handles['yaxis'].axis_label = trads['map_plot_y'][language]\n", " # changing the title of the colorbar, caution if one day the colorbar is not located on the right side anymore\n", " plot.state.right[0].title = trads['bar_area'][language] + ' (km²)'" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Put everything together " ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "oggm_logo = ''\n", "fk_logo = ''\n", "unib_logo = ''\n", "pn_logo = ''\n", "holo_logo = ''\n", "dasha_logo = ''\n", "\n", "logos = pn.Row(pn.layout.Spacer(width=10), pn_logo, holo_logo, dasha_logo)\n", "\n", "logos_height_spacer = 25\n", "left = pn.Column(pn.Pane(oggm_logo, margin=(0, 0), align='center'), pn.layout.Spacer(height=logos_height_spacer, margin=(0, 0)),\n", " pn.Pane(logos, margin=(0, 0), align='center'), pn.layout.Spacer(height=logos_height_spacer, margin=(0, 0)),\n", " pn.Pane(fk_logo, margin=(0, 0), align='center'), pn.layout.Spacer(height=logos_height_spacer, margin=(0, 0)),\n", " pn.Pane(unib_logo, margin=(0, 0), align='center', height=50), pn.layout.Spacer(height=logos_height_spacer, margin=(0, 0)),\n", " clear_button)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "bars = pn.Row(pn.Column(pn.layout.Spacer(height=1, margin=(0, 0)), \n", " pn.Pane(dyn_count1), pn.layout.Spacer(height=0, margin=(0, 0)),\n", " pn.Pane(gl_num, linked_axes=False, margin=(0, 0)), pn.layout.Spacer(sizing_mode='stretch_height', margin=(0, 0)),\n", " pn.Pane(dyn_count2), pn.layout.Spacer(height=0, margin=(0, 0)),\n", " pn.Pane(area_bar, linked_axes=False, margin=(0, 0)), pn.layout.Spacer(sizing_mode='stretch_height', margin=(0, 0)),\n", " pn.Pane(dyn_count3), pn.layout.Spacer(height=0, margin=(0, 0)),\n", " pn.Pane(vol_bar, linked_axes=False, margin=(0, 0)), pn.layout.Spacer(sizing_mode='stretch_height', margin=(0, 0)),\n", " pn.Pane(dyn_slr_text), pn.layout.Spacer(height=3, margin=(0, 0)),\n", " ), \n", " pn.Column(pn.layout.Spacer(height=5, margin=(0, 0)),\n", " pn.Pane(sealevelrise, linked_axes=False, margin=(0, 0)),\n", " pn.layout.Spacer(height=5, margin=(0, 0)))\n", " )" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "title = '' + trads['instructions'][language] + '
'\n", "set_instr_text()\n", "\n", "explanation = pn.pane.Markdown(sizing_mode='stretch_width', margin=(0, 0), height=60)\n", "def set_explanation_text():\n", " explanation.object = '' + trads['abbreviations'][language] + '
'\n", "set_explanation_text()\n", "\n", "overview = pn.Column(pn.Pane(title, width=400, margin=(0, 0)), \n", " pn.layout.Spacer(height=1, margin=(0, 0)),\n", " pn.Pane(instruction, width=470, margin=(0, 0)),\n", " pn.layout.Spacer(height=10, margin=(0, 0)),\n", " bars)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "plots = pn.Row(pn.layout.Spacer(width=10, margin=(0, 0)),\n", " trends, pn.layout.Spacer(sizing_mode='stretch_width', margin=(0, 0)),\n", " temperature, pn.layout.Spacer(sizing_mode='stretch_width', margin=(0, 0)),\n", " precipitation, pn.layout.Spacer(sizing_mode='stretch_width', margin=(0, 0)), elevation.options(hooks=[elev_language]),\n", " pn.layout.Spacer(width=10, margin=(0, 0)),\n", " sizing_mode='stretch_width')" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "top = pn.Row(left, pn.Spacer(width=60, margin=(0, 0)),\n", " overview, pn.Spacer(width=60, margin=(0, 0)), \n", " geomap.options(hooks=[geo_language]),\n", " margin=(0, 0))" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "app = pn.Column(pn.Row(pn.Spacer(sizing_mode='stretch_width',\n", " margin=(0, 0)),\n", " language_selector,\n", " sizing_mode='stretch_width'),\n", " top,\n", " plots,\n", " explanation,\n", " width=1465,\n", " )" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "app.servable(title='World glaciers explorer ' + __version__)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [] } ], "metadata": { "hide_input": false, "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.8.12" }, "latex_envs": { "LaTeX_envs_menu_present": true, "autoclose": false, "autocomplete": true, "bibliofile": "biblio.bib", "cite_by": "apalike", "current_citInitial": 1, "eqLabelWithNumbers": true, "eqNumInitial": 1, "hotkeys": { "equation": "Ctrl-E", "itemize": "Ctrl-I" }, "labels_anchors": false, "latex_user_defs": false, "report_style_numbering": false, "user_envs_cfg": false }, "nbTranslate": { "displayLangs": [ "*" ], "hotkey": "alt-t", "langInMainMenu": true, "sourceLang": "en", "targetLang": "fr", "useGoogleTranslate": true }, "toc": { "base_numbering": 1, "nav_menu": {}, "number_sections": false, "sideBar": true, "skip_h1_title": true, "title_cell": "Table of Contents", "title_sidebar": "Contents", "toc_cell": false, "toc_position": {}, "toc_section_display": true, "toc_window_display": false } }, "nbformat": 4, "nbformat_minor": 2 }