{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "Python has a large collection of plotting libraries and while any content that rendens in a Jupyter Notebooks will render in Jupyter-flex dashboards there are some things to consider for plots to look the best they can.\n", "\n", "## Interactive (JS) libraries\n", "\n", "Since jupyter-flex dashboards have a web frontend, either static `.html` files or a running webserver, in general any library that outputs a web based plot will look better, for example: [Altair](https://altair-viz.github.io/), [plotly](https://plot.ly/python/), [Bokeh](https://docs.bokeh.org/en/latest/index.html) and [bqplot](https://github.com/bloomberg/bqplot)." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Responsive\n", "\n", "For plots to look great in flex dashboards they should be responsive, that means that they should ocupy all the space that the parent html components has instead of having a static width and heigth.\n", "\n", "A responsive behaviour is usually not the default for most plotting libraries but it's very easy to change this even if the way to do this changes from library to library, here are some tips to make this happen in the most popular plotting libraries." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Altair\n", "\n", "Starting from [Altair](https://altair-viz.github.io/) version 4.0 it's possible to make a chart have responsive width and height by setting the `width` and `height` properties to `\"container\"`.\n", "\n", "For example:" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "tags": [] }, "outputs": [], "source": [ "import altair as alt\n", "from vega_datasets import data" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "tags": [] }, "outputs": [], "source": [ "source = data.cars()\n", "\n", "plot = alt.Chart(source).mark_circle(size=60).encode(\n", " x='Horsepower',\n", " y='Miles_per_Gallon',\n", " color='Origin',\n", " tooltip=['Name', 'Origin', 'Horsepower', 'Miles_per_Gallon']\n", ")" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "tags": [] }, "outputs": [], "source": [ "plot" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "If we tag the previous cell with `body` then the size will be static and not responsive, to make it responsive we just add a bit of code:" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "tags": [ "body" ] }, "outputs": [], "source": [ "plot.properties(\n", " width='container',\n", " height='container'\n", ")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "This could could make the plot invisible the Jupyter Lab or Jupyter Classic interface but will look great and expanded in the dashboard. \n", "\n", "It's usually easy to add the call to `property()` once you are done with the Notebook or control this globally using a variable." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "[![](/assets/img/screenshots/jupyter_flex.tests.test_examples/plots_altair-single-reference.png)](/examples/altair-single.html)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Plotly\n", "\n", "[Plotly](https://plot.ly/python/) requires no big changes thanks to the some extra code and styling included in Jupyter-flex.\n", "We can make things look a bit better by changing the margin of the plot.\n", "\n", "For example a simple dashboard that uses `plotly.express`:" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "tags": [] }, "outputs": [], "source": [ "import plotly.express as px\n", "import plotly.graph_objects as go" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "tags": [] }, "outputs": [], "source": [ "margin = go.layout.Margin(l=20, r=20, b=20, t=30)" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "tags": [] }, "outputs": [], "source": [ "df = px.data.iris()\n", "fig = px.scatter(df, x=\"sepal_width\", y=\"sepal_length\")\n", "\n", "fig.update_layout(margin=margin)\n", "None" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "[![](/assets/img/screenshots/jupyter_flex.tests.test_examples/plots_plotly-single-reference.png)](/examples/plotly-single.html)\n", "\n", "

Click on the image to open the dashboard

" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Bokeh\n", "\n", "Using [Bokeh](https://docs.bokeh.org/en/latest/index.html) plots in Jupyter-flex dashboard requires two things:\n", "\n", "1. One `meta` tag in the cell that does `output_notebook()` to embed the bokeh JS code in the notebook. The `meta` tag will add that cell to the dashboard `.html` with the `display: none;` style\n", "2. Add `sizing_mode=\"stretch_both\"` to the Bokeh `figure()` call\n", "\n", "For example:" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "tags": [ "meta" ] }, "outputs": [], "source": [ "import numpy as np\n", "\n", "from bokeh.plotting import figure, show, output_notebook\n", "output_notebook()" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "tags": [] }, "outputs": [], "source": [ "x = np.linspace(0, 4 * np.pi, 100)\n", "y = np.sin(x)" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "tags": [] }, "outputs": [], "source": [ "fig = figure(sizing_mode=\"stretch_both\")\n", "fig.line(x, y)\n", "\n", "# show(fig)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Similar to what happens in Altair we see that the plot might not look its best well in the Jupyter Notebook interface (or these docs) but renders great in the dashboard.\n", "\n", "It's usually easy to add the `sizing_mode=\"stretch_both\"` code once you are done with the Notebook or control this globally using a variable.\n", "\n", "[![](/assets/img/screenshots/jupyter_flex.tests.test_examples/plots_bokeh-single-reference.png)](/examples/bokeh-single.html)\n", "\n", "

Click on the image to open the dashboard

" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### bqplot\n", "\n", "[bqplot](https://github.com/bloomberg/bqplot) is a plotting library that is 100% based on Jupyter widgets and therefore works great with them, it also required no major changes for the plots to look great on Jupyter-flex dashboards.\n", "\n", "Once simple example:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "import numpy as np\n", "from bqplot import *" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "size = 100\n", "np.random.seed(42)\n", "\n", "x_data = range(size)\n", "y_data = np.random.randn(size)\n", "y_data_2 = np.random.randn(size)\n", "y_data_3 = np.cumsum(np.random.randn(size) * 100.)" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "tags": [ "body" ] }, "outputs": [], "source": [ "x_ord = OrdinalScale()\n", "y_sc = LinearScale()\n", "\n", "bar = Bars(x=np.arange(10), y=np.random.rand(10), scales={'x': x_ord, 'y': y_sc})\n", "ax_x = Axis(scale=x_ord)\n", "ax_y = Axis(scale=y_sc, tick_format='0.2f', orientation='vertical')\n", "\n", "Figure(marks=[bar], axes=[ax_x, ax_y], padding_x=0.025, padding_y=0.025)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "[![](/assets/img/screenshots/jupyter_flex.tests.test_examples/plots_bqplot-single-reference.png)](/examples/bqplot-single.html)\n", "\n", "

Click on the image to open the dashboard

" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Updating plots\n", "\n", "When using Voila and IPython widgets to dynamically update the content of plots in the dashboard there are some things to consider:\n", "\n", "1. If the library has native support for IPython Widgets then it's a good idea to use that functionality, this is possible in:\n", " 1. bqplot because the library is designed that way\n", " 2. plotly using [Figure Widget](https://plot.ly/python/figurewidget/)\n", "2. If the library doesn't have native support for Jupyter widgets it's still possible to use it and update the dashboard the using [Output Widgets](https://ipywidgets.readthedocs.io/en/latest/examples/Output%20Widget.html)\n", "\n", "When using Output Widgets remember to call `clear()` before displaying new content, for example:\n", "\n", "```\n", "out = widgets.Output()\n", "\n", "with out:\n", " out.clear_output()\n", " display(...)\n", "```\n", "\n", "It's common to have the `with out: ...` code inside a callback function from a widgets `observe()` method." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Examples\n", "\n", "More examples that show the plotting libraries in action and other examples that show how to have more dyamic dashboards with Jupyter widgets:\n", "\n", "
\n", "\n", "\n", "
\n", " \n", "
card-complete
\n", "
\n", "
\n", "\n", "\n", "
\n", " \n", "
altair-single
\n", "
\n", "
\n", "\n", "\n", "\n", "
\n", " \n", "
altair
\n", "
\n", "
\n", "\n", "\n", "
\n", " \n", "
altair-scroll
\n", "
\n", "
\n", "\n", "\n", "
\n", " \n", "
bokeh-single
\n", "
\n", "
\n", "\n", "\n", "\n", "
\n", " \n", "
bokeh
\n", "
\n", "
\n", "\n", "\n", "
\n", " \n", "
bqplot-single (runs in mybinder.org)
\n", "
\n", "
\n", "\n", "\n", "\n", "
\n", " \n", "
bqplot (runs in mybinder.org)
\n", "
\n", "
\n", "\n", "\n", "
\n", " \n", "
plotly-single
\n", "
\n", "
\n", "\n", "\n", "\n", "
\n", " \n", "
plotly
\n", "
\n", "
\n", "\n", "\n", "
\n", " \n", "
Iris clustering (runs in mybinder.org)
\n", "
\n", "
\n", "\n", "\n", "
\n", " \n", "
Movie Explorer (runs in mybinder.org)
\n", "
\n", "
\n", "\n", "\n", "
\n", " \n", "
Wealth of Nations (runs in mybinder.org)
\n", "
\n", "
\n", "\n", "
" ] } ], "metadata": { "celltoolbar": "Tags", "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" }, "widgets": { "application/vnd.jupyter.widget-state+json": { "state": { "08287f3a41ed446aaaeb97afc7742eaf": { "model_module": "bqplot", "model_module_version": "^0.5.31", "model_name": "FigureModel", "state": { "_dom_classes": [], "_model_module": "bqplot", "_model_module_version": "^0.5.31", "_model_name": "FigureModel", "_view_count": null, "_view_module": "bqplot", "_view_module_version": "^0.5.31", "_view_name": "Figure", "animation_duration": 0, "axes": [ "IPY_MODEL_7e8c99cbcf7b47ba9f1dda4c59ed6370", "IPY_MODEL_63f8e7044b79435983565ee606f0a9be" ], "background_style": {}, "fig_margin": { "bottom": 60, "left": 60, "right": 60, "top": 60 }, "interaction": null, "layout": "IPY_MODEL_67e56df8d42e46bb84aeee84c597773f", "legend_location": "top-right", "legend_style": {}, "legend_text": {}, "marks": [ "IPY_MODEL_83e26db23f7a4f3bbe82550eff2396f6" ], "max_aspect_ratio": 100, "min_aspect_ratio": 0.01, "padding_x": 0.025, "padding_y": 0.025, "pixel_ratio": null, "scale_x": "IPY_MODEL_72ecf30a52894e5e99a0928292656fb8", "scale_y": "IPY_MODEL_e47da3a5495740e0ad9f2a64751c13e8", "theme": "classic", "title": "", "title_style": {} } }, "10f4e043eabd47109706457a731ac4a1": { "model_module": "bqplot", "model_module_version": "^0.5.31", "model_name": "LinearScaleModel", "state": { "_model_module": "bqplot", "_model_module_version": "^0.5.31", "_model_name": "LinearScaleModel", "_view_count": null, "_view_module": "bqplot", "_view_module_version": "^0.5.31", "_view_name": "LinearScale", "allow_padding": true, "max": null, "mid_range": 0.8, "min": null, "min_range": 0.6, "reverse": false, "stabilized": false } }, "63f8e7044b79435983565ee606f0a9be": { "model_module": "bqplot", "model_module_version": "^0.5.31", "model_name": "AxisModel", "state": { "_model_module": "bqplot", "_model_module_version": "^0.5.31", "_model_name": "AxisModel", "_view_count": null, "_view_module": "bqplot", "_view_module_version": "^0.5.31", "_view_name": "Axis", "color": null, "grid_color": null, "grid_lines": "solid", "label": "", "label_color": null, "label_location": "middle", "label_offset": null, "num_ticks": null, "offset": {}, "orientation": "vertical", "scale": "IPY_MODEL_10f4e043eabd47109706457a731ac4a1", "side": null, "tick_format": "0.2f", "tick_rotate": 0, "tick_style": {}, "tick_values": null, "visible": true } }, "67e56df8d42e46bb84aeee84c597773f": { "model_module": "@jupyter-widgets/base", "model_module_version": "1.2.0", "model_name": "LayoutModel", "state": { "_model_module": "@jupyter-widgets/base", "_model_module_version": "1.2.0", "_model_name": "LayoutModel", "_view_count": null, "_view_module": "@jupyter-widgets/base", "_view_module_version": "1.2.0", "_view_name": "LayoutView", "align_content": null, "align_items": null, "align_self": null, "border": null, "bottom": null, "display": null, "flex": null, "flex_flow": null, "grid_area": null, "grid_auto_columns": null, "grid_auto_flow": null, "grid_auto_rows": null, "grid_column": null, "grid_gap": null, "grid_row": null, "grid_template_areas": null, "grid_template_columns": null, "grid_template_rows": null, "height": null, "justify_content": null, "justify_items": null, "left": null, "margin": null, "max_height": null, "max_width": null, "min_height": null, "min_width": null, "object_fit": null, "object_position": null, "order": null, "overflow": null, "overflow_x": null, "overflow_y": null, "padding": null, "right": null, "top": null, "visibility": null, "width": null } }, "72ecf30a52894e5e99a0928292656fb8": { "model_module": "bqplot", "model_module_version": "^0.5.31", "model_name": "LinearScaleModel", "state": { "_model_module": "bqplot", "_model_module_version": "^0.5.31", "_model_name": "LinearScaleModel", "_view_count": null, "_view_module": "bqplot", "_view_module_version": "^0.5.31", "_view_name": "LinearScale", "allow_padding": false, "max": 1, "mid_range": 0.8, "min": 0, "min_range": 0.6, "reverse": false, "stabilized": false } }, "7e8c99cbcf7b47ba9f1dda4c59ed6370": { "model_module": "bqplot", "model_module_version": "^0.5.31", "model_name": "AxisModel", "state": { "_model_module": "bqplot", "_model_module_version": "^0.5.31", "_model_name": "AxisModel", "_view_count": null, "_view_module": "bqplot", "_view_module_version": "^0.5.31", "_view_name": "Axis", "color": null, "grid_color": null, "grid_lines": "solid", "label": "", "label_color": null, "label_location": "middle", "label_offset": null, "num_ticks": null, "offset": {}, "orientation": "horizontal", "scale": "IPY_MODEL_92ee6973606a45599e2ad46b376326a6", "side": null, "tick_format": null, "tick_rotate": 0, "tick_style": {}, "tick_values": null, "visible": true } }, "83e26db23f7a4f3bbe82550eff2396f6": { "buffers": [ { "data": "AAAAAAEAAAACAAAAAwAAAAQAAAAFAAAABgAAAAcAAAAIAAAACQAAAA==", "encoding": "base64", "path": [ "x", "value" ] }, { "data": "FD9ivNoM0j90icbGVrbGPy4qRjoJBeg/K79NFpfR6T8RNe3WN7LvPzCCBflTaNo/nggA8iTP1z8helL+X9joP9Ta76a5z9U/+1k/lsPI7T8=", "encoding": "base64", "path": [ "y", "value" ] } ], "model_module": "bqplot", "model_module_version": "^0.5.31", "model_name": "BarsModel", "state": { "_model_module": "bqplot", "_model_module_version": "^0.5.31", "_model_name": "BarsModel", "_view_count": null, "_view_module": "bqplot", "_view_module_version": "^0.5.31", "_view_name": "Bars", "align": "center", "apply_clip": true, "base": 0, "color": null, "color_mode": "auto", "colors": [ "steelblue" ], "display_legend": false, "enable_hover": true, "fill": true, "interactions": { "hover": "tooltip" }, "label_display": false, "label_display_format": ".2f", "label_display_horizontal_offset": 0, "label_display_vertical_offset": 0, "label_font_style": {}, "labels": [], "opacities": [], "opacity_mode": "auto", "orientation": "vertical", "padding": 0.05, "preserve_domain": {}, "scales": { "x": "IPY_MODEL_92ee6973606a45599e2ad46b376326a6", "y": "IPY_MODEL_10f4e043eabd47109706457a731ac4a1" }, "scales_metadata": { "color": { "dimension": "color" }, "x": { "dimension": "x", "orientation": "horizontal" }, "y": { "dimension": "y", "orientation": "vertical" } }, "selected": null, "selected_style": {}, "stroke": null, "stroke_width": 1, "tooltip": null, "tooltip_location": "mouse", "tooltip_style": { "opacity": 0.9 }, "type": "stacked", "unselected_style": {}, "visible": true, "x": { "dtype": "int32", "shape": [ 10 ], "type": null, "value": {} }, "y": { "dtype": "float64", "shape": [ 10 ], "type": null, "value": {} } } }, "92ee6973606a45599e2ad46b376326a6": { "model_module": "bqplot", "model_module_version": "^0.5.31", "model_name": "OrdinalScaleModel", "state": { "_model_module": "bqplot", "_model_module_version": "^0.5.31", "_model_name": "OrdinalScaleModel", "_view_count": null, "_view_module": "bqplot", "_view_module_version": "^0.5.31", "_view_name": "OrdinalScale", "allow_padding": true, "domain": [], "reverse": false } }, "e47da3a5495740e0ad9f2a64751c13e8": { "model_module": "bqplot", "model_module_version": "^0.5.31", "model_name": "LinearScaleModel", "state": { "_model_module": "bqplot", "_model_module_version": "^0.5.31", "_model_name": "LinearScaleModel", "_view_count": null, "_view_module": "bqplot", "_view_module_version": "^0.5.31", "_view_name": "LinearScale", "allow_padding": false, "max": 1, "mid_range": 0.8, "min": 0, "min_range": 0.6, "reverse": false, "stabilized": false } } }, "version_major": 2, "version_minor": 0 } } }, "nbformat": 4, "nbformat_minor": 4 }