diff --git a/03_voila/05_Advantages_and_Disadvantages_of_Voila.ipynb b/03_voila/05_Advantages_and_Disadvantages_of_Voila.ipynb index 581e4f860453b1ca474adcbd9fea46e74c263ead..026cb375c05a12b9d4ae719e8d2bcb542e22ab6a 100644 --- a/03_voila/05_Advantages_and_Disadvantages_of_Voila.ipynb +++ b/03_voila/05_Advantages_and_Disadvantages_of_Voila.ipynb @@ -2,7 +2,7 @@ "cells": [ { "cell_type": "markdown", - "id": "unable-wisconsin", + "id": "binary-seafood", "metadata": {}, "source": [ "# Advantages and Disadvantages of Voilà\n", @@ -12,7 +12,7 @@ }, { "cell_type": "markdown", - "id": "unable-prefix", + "id": "pharmaceutical-campus", "metadata": {}, "source": [ "# Use Voilà when...\n", @@ -34,7 +34,7 @@ }, { "cell_type": "markdown", - "id": "transparent-payroll", + "id": "prerequisite-cartridge", "metadata": {}, "source": [ "## Excursion: JupyterHub\n", @@ -61,12 +61,15 @@ "\n", "Instead of launching a single-user notebook server, configure JupyterHub to directly launch a voila server instead. That way, the user never sees the Jupyter interface and does not get any access to the system. Remember: Voilà runs the notebook once and disallows all further execute requests. \n", "\n", - "You can configure your dashboard to only run on certain systems. For example, your Voilà dashboard is allowed to run on JUWELS, but not JURECA. If you launch Voilà dashboard on the Cloud, even users without access to the HPC systems can run your dashboard." + "You can configure your dashboard to only run on certain systems. For example, your Voilà dashboard is allowed to run on JUWELS, but not JURECA. If you launch Voilà dashboard on the Cloud, even users without access to the HPC systems can run your dashboard.\n", + "\n", + "Another possible way, with which I however don't have personal experience, seems to be [ContainDS Dashboards for JupyterHub](https://github.com/ideonate/cdsdashboards):\n", + "> Run a private on-premise or cloud-based JupyterHub with extensions to instantly publish apps and notebooks as user-friendly interactive dashboards to share with non-technical colleagues." ] }, { "cell_type": "markdown", - "id": "sexual-serum", + "id": "alone-december", "metadata": {}, "source": [ "# Disadvantages of Voilà\n", @@ -88,10 +91,10 @@ }, { "cell_type": "markdown", - "id": "defensive-discharge", + "id": "separate-controversy", "metadata": {}, "source": [ - "# Alternatives?\n", + "# Closest Alternative?\n", "\n", "[Panel](https://panel.holoviz.org/index.html) is similar to Voilà and very well integrated with Jupyter Notebooks, although you don't need to use Notebooks at all. Panel is built on top of Bokeh widgets and server. You only need Python to get started (in fact, Panel only supports Python), although HTML, CSS and JavaScript knowledge is helpful for styling your dashboard. It supports all Python plotting libraries and has enhanced support for Bokeh and Plotly.\n", "\n", @@ -106,13 +109,18 @@ "**When to use Panel:** \n", " * You can use Panel if you already use Jupyter Notebooks and want more flexibility than what is offered by Voila.\n", " * With Panel, it is easy to create dashboards which are not restricted to a single GUI.\n", - " * Panel ist best suited for dealing with gridded and geospatial data." + " * Panel ist best suited for dealing with gridded and geospatial data.\n", + " \n", + " \n", + "**Panel vs Voilà:** \n", + "* Voila - Rapid prototyping in Jupyter notebooks. Quickly and reliably report data insights across an organisation.\n", + "* Panel - Creating dashboard applications not restricted to a single GUI. Working with geospacial data." ] }, { "cell_type": "code", "execution_count": null, - "id": "shared-aspect", + "id": "threatened-carroll", "metadata": {}, "outputs": [], "source": [] diff --git a/04_plotly_dash/07_Multi-Page_Apps.ipynb b/04_plotly_dash/07_Multi-Page_Apps.ipynb index 70c02e5064fdf678f0c839cd7f16f26e0181ec34..51e732936a2273bab5f6b7b3cca61ab5eb4015f1 100644 --- a/04_plotly_dash/07_Multi-Page_Apps.ipynb +++ b/04_plotly_dash/07_Multi-Page_Apps.ipynb @@ -1,12 +1,14 @@ { "cells": [ { - "cell_type": "code", - "execution_count": null, - "id": "leading-montana", + "cell_type": "markdown", + "id": "molecular-function", "metadata": {}, - "outputs": [], - "source": [] + "source": [ + "# Multi-Page Apps and URL Support\n", + "\n", + "See https://dash.plotly.com/urls." + ] } ], "metadata": { diff --git a/04_plotly_dash/08_Deploying_Dash.ipynb b/04_plotly_dash/08_Deploying_Dash.ipynb index 7d60e078f07210b4755bdad49e4ee3534d951c13..c6283cce884e80293c41c83d6e3ec2509e9a8138 100644 --- a/04_plotly_dash/08_Deploying_Dash.ipynb +++ b/04_plotly_dash/08_Deploying_Dash.ipynb @@ -2,26 +2,34 @@ "cells": [ { "cell_type": "markdown", - "id": "comfortable-inclusion", + "id": "elect-touch", "metadata": {}, "source": [ - "https://datasciencecampus.github.io/deploy-dash-with-gcp/\n", + "# Deploying your Dash app\n", "\n", - "https://austinlasseter.medium.com/deploying-a-dash-app-with-elastic-beanstalk-console-27a834ebe91d\n", + "Here is the official documentation on deploying Dash: https://dash.plotly.com/deployment\n", + "It's, again, very heavy on Dash Enterprise, but does contain a section on how to share your Dash app on Heroku.\n", "\n", - "kubernetes\n", - "https://ldnicolasmay.medium.com/deploying-a-free-dash-open-source-app-from-a-docker-container-with-gunicorn-3f426b5fd5df\n", + "Dash apps can be deployed on any servers which support Flask app deployment. Here are some more tutorials on how to deploy dash on other platforms:\n", "\n", - "https://towardsdatascience.com/build-a-highly-scalable-dashboard-that-runs-on-kubernetes-fa2bc6271f1d" + "* [Heroku](https://towardsdatascience.com/deploying-your-dash-app-to-heroku-the-magical-guide-39bd6a0c586c)\n", + "* [Google Cloud Platform](https://datasciencecampus.github.io/deploy-dash-with-gcp/)\n", + "* [Google App Engine](https://www.phillipsj.net/posts/deploying-dash-to-google-app-engine/)\n", + "* [AWS Elastic Beanstalk](https://austinlasseter.medium.com/deploying-a-dash-app-with-elastic-beanstalk-console-27a834ebe91d)\n", + "* [Azure](https://www.phillipsj.net/posts/deploying-dash-to-azure-without-using-docker/)\n", + "* [PythonAnywhere](https://towardsdatascience.com/the-easiest-way-to-deploy-your-dash-app-for-free-f92c575bb69e)\n", + "* Kubernetes\n", + " * [Docker container and gunicorn](https://ldnicolasmay.medium.com/deploying-a-free-dash-open-source-app-from-a-docker-container-with-gunicorn-3f426b5fd5df)\n", + " * [Using Python, Docker and Google Cloud Platform](https://towardsdatascience.com/build-a-highly-scalable-dashboard-that-runs-on-kubernetes-fa2bc6271f1d)" ] }, { - "cell_type": "markdown", - "id": "acknowledged-genealogy", + "cell_type": "code", + "execution_count": null, + "id": "hearing-sunrise", "metadata": {}, - "source": [ - "https://appsilon.com/overview-of-dash-python-framework-from-plotly-for-building-dashboards/" - ] + "outputs": [], + "source": [] } ], "metadata": { diff --git a/04_plotly_dash/examples/multi_page_apps/dynamic_layout.py b/04_plotly_dash/examples/multi_page_apps/dynamic_layout.py new file mode 100644 index 0000000000000000000000000000000000000000..f72ec3daf7bbc62a7990bb1953702e2e0aca5c88 --- /dev/null +++ b/04_plotly_dash/examples/multi_page_apps/dynamic_layout.py @@ -0,0 +1,91 @@ +import dash +import dash_core_components as dcc +import dash_html_components as html +from dash.dependencies import Input, Output, State + +import flask + +app = dash.Dash(__name__) + +url_bar_and_content_div = html.Div([ + dcc.Location(id='url', refresh=False), + html.Div(id='page-content') +]) + +layout_index = html.Div([ + dcc.Link('Navigate to "/page-1"', href='/page-1'), + html.Br(), + dcc.Link('Navigate to "/page-2"', href='/page-2'), +]) + +layout_page_1 = html.Div([ + html.H2('Page 1'), + dcc.Input(id='input-1-state', type='text', value='Montreal'), + dcc.Input(id='input-2-state', type='text', value='Canada'), + html.Button(id='submit-button', n_clicks=0, children='Submit'), + html.Div(id='output-state'), + html.Br(), + dcc.Link('Navigate to "/"', href='/'), + html.Br(), + dcc.Link('Navigate to "/page-2"', href='/page-2'), +]) + +layout_page_2 = html.Div([ + html.H2('Page 2'), + dcc.Dropdown( + id='page-2-dropdown', + options=[{'label': i, 'value': i} for i in ['LA', 'NYC', 'MTL']], + value='LA' + ), + html.Div(id='page-2-display-value'), + html.Br(), + dcc.Link('Navigate to "/"', href='/'), + html.Br(), + dcc.Link('Navigate to "/page-1"', href='/page-1'), +]) + +# index layout +app.layout = url_bar_and_content_div + +# "complete" layout +app.validation_layout = html.Div([ + url_bar_and_content_div, + layout_index, + layout_page_1, + layout_page_2, +]) + + +# Index callbacks +@app.callback(Output('page-content', 'children'), + Input('url', 'pathname')) +def display_page(pathname): + if pathname == "/page-1": + return layout_page_1 + elif pathname == "/page-2": + return layout_page_2 + else: + return layout_index + + +# Page 1 callbacks +@app.callback(Output('output-state', 'children'), + Input('submit-button', 'n_clicks'), + State('input-1-state', 'value'), + State('input-2-state', 'value')) +def update_output(n_clicks, input1, input2): + return ('The Button has been pressed {} times,' + 'Input 1 is "{}",' + 'and Input 2 is "{}"').format(n_clicks, input1, input2) + + +# Page 2 callbacks +@app.callback(Output('page-2-display-value', 'children'), + Input('page-2-dropdown', 'value')) +def display_value(value): + print('display_value') + return 'You have selected "{}"'.format(value) + + +if __name__ == '__main__': + app.run_server(debug=True) \ No newline at end of file diff --git a/04_plotly_dash/examples/multi_page_apps/page_based_on_URL.py b/04_plotly_dash/examples/multi_page_apps/page_based_on_URL.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/04_plotly_dash/examples/multi_page_apps/simple_example.py b/04_plotly_dash/examples/multi_page_apps/simple_example.py new file mode 100644 index 0000000000000000000000000000000000000000..a9d73bc98dbfcb8f20f62bd02f04b49d8557879a --- /dev/null +++ b/04_plotly_dash/examples/multi_page_apps/simple_example.py @@ -0,0 +1,78 @@ +import dash +import dash_core_components as dcc +import dash_html_components as html + +# Since we're adding callbacks to elements that don't exist in the app.layout, +# Dash will raise an exception to warn us that we might be +# doing something wrong. +# In this case, we're adding the elements through a callback, so we can ignore +# the exception. +app = dash.Dash(__name__, suppress_callback_exceptions=True) + +app.layout = html.Div([ + dcc.Location(id='url', refresh=False), + html.Div(id='page-content') +]) + + +index_page = html.Div([ + dcc.Link('Go to Page 1', href='/page-1'), + html.Br(), + dcc.Link('Go to Page 2', href='/page-2'), +]) + +page_1_layout = html.Div([ + html.H1('Page 1'), + dcc.Dropdown( + id='page-1-dropdown', + options=[{'label': i, 'value': i} for i in ['LA', 'NYC', 'MTL']], + value='LA' + ), + html.Div(id='page-1-content'), + html.Br(), + dcc.Link('Go to Page 2', href='/page-2'), + html.Br(), + dcc.Link('Go back to home', href='/'), +]) + +@app.callback(dash.dependencies.Output('page-1-content', 'children'), + [dash.dependencies.Input('page-1-dropdown', 'value')]) +def page_1_dropdown(value): + return 'You have selected "{}"'.format(value) + + +page_2_layout = html.Div([ + html.H1('Page 2'), + dcc.RadioItems( + id='page-2-radios', + options=[{'label': i, 'value': i} for i in ['Orange', 'Blue', 'Red']], + value='Orange' + ), + html.Div(id='page-2-content'), + html.Br(), + dcc.Link('Go to Page 1', href='/page-1'), + html.Br(), + dcc.Link('Go back to home', href='/') +]) + +@app.callback(dash.dependencies.Output('page-2-content', 'children'), + [dash.dependencies.Input('page-2-radios', 'value')]) +def page_2_radios(value): + return 'You have selected "{}"'.format(value) + + +# Update the index +@app.callback(dash.dependencies.Output('page-content', 'children'), + [dash.dependencies.Input('url', 'pathname')]) +def display_page(pathname): + if pathname == '/page-1': + return page_1_layout + elif pathname == '/page-2': + return page_2_layout + else: + return index_page + # You could also return a 404 "URL not found" page here + + +if __name__ == '__main__': + app.run_server(debug=True) \ No newline at end of file diff --git a/04_plotly_dash/img/comparison.jpeg b/04_plotly_dash/img/comparison.jpeg new file mode 100644 index 0000000000000000000000000000000000000000..32f214165671ff5a2e44ebace9c85f2a818e470d Binary files /dev/null and b/04_plotly_dash/img/comparison.jpeg differ