{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The [Panel](http://panel.pyviz.org) library from [PyViz](http://pyviz.org) lets you make widget-controlled apps and dashboards from a wide variety of [plotting libraries and data types](https://github.com/pyviz/panel/issues/2). Here let's set up four different plotting libraries controlled by a couple of widgets, for Hans Rosling's [gapminder](https://demo.bokehplots.com/apps/gapminder) example." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "import param\n", "import numpy as np \n", "import pandas as pd\n", "import panel as pn\n", "\n", "import altair as alt\n", "import plotly.graph_objs as go\n", "import matplotlib.pyplot as plt\n", "import hvplot.pandas\n", "\n", "pn.extension('vega', 'plotly')" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "First, we'll get the data into a Pandas dataframe:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "url = 'https://raw.githubusercontent.com/plotly/datasets/master/gapminderDataFiveYear.csv'\n", "dataset = pd.read_csv(url)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "dataset.sample(10)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Now let's define a couple of user-settable parameters (the year and whether to show a legend), then write methods to plot this data with Matplotlib, Plotly, Altair (using Vega), and hvPlot (using HoloViews and Bokeh):" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "class Gapminder(param.Parameterized):\n", " year = param.ObjectSelector(default=1952, objects=list(dataset.year.unique()))\n", " show_legend = param.Boolean(default=True)\n", " \n", " title = 'Life expectancy vs. GDP, %s'\n", " xlabel = 'GDP per capita (2000 dollars)'\n", " ylabel = 'Life expectancy (years)'\n", " ylim = (20, 90)\n", " \n", "\n", " def get_data(self):\n", " df = dataset[(dataset.year==self.year) & (dataset.gdpPercap < 10000)].copy()\n", " df['size'] = np.sqrt(df['pop']*2.666051223553066e-05)\n", " self.xlim = (df['gdpPercap'].min()-100,df['gdpPercap'].max()+1000)\n", " return df\n", " \n", " def mpl_view(self):\n", " data = self.get_data()\n", " title = \"Matplotlib: \" + (self.title % self.year)\n", " \n", " plot = plt.figure(figsize=(7, 6))\n", " ax = plot.add_subplot(111)\n", " ax.set_xscale(\"log\")\n", " ax.set_title(title)\n", " ax.set_xlabel(self.xlabel)\n", " ax.set_ylabel(self.ylabel)\n", " ax.set_ylim(self.ylim)\n", " ax.set_xlim(self.xlim)\n", "\n", " for continent, df in data.groupby('continent'):\n", " ax.scatter(df.gdpPercap, y=df.lifeExp, s=df['size']*5,\n", " edgecolor='black', label=continent)\n", " \n", " if self.show_legend:\n", " ax.legend(loc=4)\n", " \n", " plt.close(plot)\n", " return plot\n", " \n", " def plotly_view(self):\n", " data = self.get_data()\n", " title = 'Plotly: ' + (self.title % self.year)\n", "\n", " traces = []\n", " for continent, df in data.groupby('continent'):\n", " marker=dict(symbol='circle', sizemode='area', sizeref=0.1, size=df['size'], line=dict(width=2))\n", " traces.append(go.Scatter(x=df.gdpPercap, y=df.lifeExp, mode='markers', marker=marker, name=continent, text=df.country))\n", " \n", " axis_opts = dict(gridcolor='rgb(255, 255, 255)', zerolinewidth=1, ticklen=5, gridwidth=2)\n", " layout = go.Layout(title=title, showlegend=self.show_legend, width=550,\n", " xaxis=dict(title=self.xlabel, type='log', **axis_opts),\n", " yaxis=dict( title=self.ylabel, **axis_opts))\n", " \n", " return go.Figure(data=traces, layout=layout)\n", " \n", " def altair_view(self):\n", " data = self.get_data()\n", " title = \"Altair/Vega: \" + (self.title % self.year)\n", " legend= ({} if self.show_legend else {'legend': None})\n", "\n", " plot = alt.Chart(data).mark_circle().encode(\n", " alt.X('gdpPercap:Q', scale=alt.Scale(type='log'), axis=alt.Axis(title=self.xlabel)),\n", " alt.Y('lifeExp:Q', scale=alt.Scale(zero=False, domain=self.ylim), axis=alt.Axis(title=self.ylabel)),\n", " size=alt.Size('pop:Q', scale=alt.Scale(type=\"log\"), legend=None),\n", " color=alt.Color('continent', scale=alt.Scale(scheme=\"category10\"), **legend),\n", " tooltip=['continent','country'])\\\n", " .properties(title=title).configure_axis(grid=False)\n", " return plot.interactive()\n", " \n", " def hvplot_view(self):\n", " data = self.get_data()\n", " title = \"hvPlot/Bokeh: \" + (self.title % self.year)\n", " \n", " plot = data.hvplot.scatter('gdpPercap', 'lifeExp', by='continent', s='size',\n", " logx=True, title=title, width=500, height=400, legend=self.show_legend, hover_cols=['country'])\n", " plot = plot.options(legend_position='bottom_right', xticks=[500, 1000, 2000, 5000, 10000])\n", " plot = plot.redim.label(gdpPercap=self.xlabel, lifeExp=self.ylabel)\n", " plot = plot.redim.range(lifeExp=self.ylim, gdpPercap=(200, 12000))\n", " return plot\n", "\n", "gm = Gapminder(name='')" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "We can now make a panel showing the parameters of this `gm` object along with the result of calling four different `_view()` methods:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "pn.Column(gm.param,\n", " pn.Row(gm.hvplot_view, gm.altair_view),\n", " pn.Row(gm.mpl_view, gm.plotly_view))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Here the Matplotlib output is being rendered as PNG, but the rest are interactive, with hovering, zooming, and similar features (if allowed by the notebook viewer being used). Note that due to interactions between the various JS libraries used here, the Plotly output can get mixed up when resizing or reloading the page, but it should be reset if you adjust the year slider once you are done.\n", "\n", "We can also make these plots available as a standalone server, if we add `.servable()` to the panel object and call this notebook as `bokeh serve --show Panel_Gapminders.ipynb`. Before doing that, let's make another panel where we can add a logo and a title, so that it makes a nicer page layout when served separately. We'll also add an R ggplot2-based plot to fill out the page:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "logo = \"\"\"\n", " \"\"\"\n", "title = '

Plotting library comparison

'\n", "\n", "desc = pn.pane.HTML(\"\"\"\n", " The
Panel library from PyViz \n", " lets you make widget-controlled apps and dashboards from a wide variety of \n", " plotting libraries and data types. Here you can try out five different plotting libraries\n", " controlled by a couple of widgets, for Hans Rosling's \n", " gapminder example.\"\"\", width=450)\n", "\n", "pn.Row(pn.Column(logo, title, desc, pn.panel(gm.param, widgets={'year': pn.widgets.DiscreteSlider})),\n", " pn.Column(pn.Row(gm.hvplot_view, gm.altair_view),\n", " pn.Row(gm.mpl_view, gm.plotly_view))).servable()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Once you run Bokeh Server on this notebook, you should get a panel like the following in your web browser that you can explore or share with other users of your machine:" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "" ] } ], "metadata": { "kernelspec": { "display_name": "Python 3", "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.7.3" } }, "nbformat": 4, "nbformat_minor": 2 }