Difference between revisions of "Dash - Plotly"

From Sinfronteras
Jump to: navigation, search
(Deploying a Gunicorn server)
 
(48 intermediate revisions by the same user not shown)
Line 1: Line 1:
==Word cloud==
 
https://github.com/amueller/word_cloud
 
  
  
In Dash:
+
<br />
* https://community.plot.ly/t/wordcloud-in-dash/11407/4
+
==Examples==
:: https://community.plot.ly/t/show-and-tell-wordcloudworld-com/15649
+
* '''Dash App Gallery:''' https://dash-gallery.plotly.host/Portal/
::: https://github.com/mikesmith1611/word-cloud-world
+
: GitHub repository: https://github.com/plotly/dash-sample-apps
:::: http://www.wordcloudworld.com/
+
 
 +
:* This one is with a sidebar: https://dash-gallery.plotly.host/dash-svm/
 +
::* https://github.com/plotly/dash-sample-apps/tree/master/apps/dash-svm
 +
:* https://dash-gallery.plotly.host/dash-oil-and-gas/
 +
:* https://dash-gallery.plotly.host/dash-web-trader/
  
  
* https://community.plot.ly/t/solved-is-it-possible-to-make-a-wordcloud-in-dash/4565
+
'''Dash Core Components Gallery:''' https://dash.plot.ly/dash-core-components
  
  
 
<br />
 
<br />
===Installation===
+
===Hello world example===
Using pip:
+
app.py
pip install wordcloud
+
<syntaxhighlight lang="python">
 +
import dash
 +
import dash_core_components as dcc
 +
import dash_html_components as html
  
  
Using conda:
+
app = dash.Dash(__name__)
  
https://anaconda.org/conda-forge/wordcloud
+
app.layout = html.Div(children=[
 +
    html.H1(children='Hello Dash'),
 +
    html.Div(children='Dash: A web application framework for Python')
 +
])
  
conda install -c conda-forge wordcloud
 
  
 +
if __name__ == '__main__':
 +
    app.run_server(debug=True, port=8551)
 +
</syntaxhighlight>
  
'''Installation notes:'''
 
  
<code>wordcloud</code> depends on <code>numpy</code> and <code>pillow</code>.
+
'''To run the app:'''
 
+
python app.ph
 
+
<span style="color:#FF0000">Es importante utilizar un port que no esté ocupado por otro proceso.</span>
To save the <code>wordcloud</code> into a file, <code>matplotlib</code> can also be installed.
 
  
  
 
<br />
 
<br />
===Minimal example===
+
===A nice example===
Can be run in jupyter-notebook:
+
app.py
<syntaxhighlight lang="python3">
+
<syntaxhighlight lang="python">
"""
+
import dash
Minimal Example
+
import dash_core_components as dcc
===============
+
import dash_html_components as html
Generating a square wordcloud from the US constitution using default arguments.
+
import pandas as pd
"""
+
import plotly.graph_objs as go
  
import os
+
external_stylesheets = ['https://codepen.io/chriddyp/pen/bWLwgP.css']
  
from os import path
+
app = dash.Dash(__name__, external_stylesheets=external_stylesheets)
from wordcloud import WordCloud
 
  
# get data directory (using getcwd() is needed to support running example in generated IPython notebook)
+
df = pd.read_csv(
d = path.dirname(__file__) if "__file__" in locals() else os.getcwd()
+
    'https://gist.githubusercontent.com/chriddyp/'
 +
    'cb5392c35661370d95f300086accea51/raw/'
 +
    '8e0768211f6b747c0db42a9ce9a0937dafcbd8b2/'
 +
    'indicators.csv')
  
# Read the whole text.
+
available_indicators = df['Indicator Name'].unique()
text = open(path.join(d, 'constitution.txt')).read()
 
  
# Generate a word cloud image
+
app.layout = html.Div([
wordcloud = WordCloud().generate(text)
+
    html.Div([
  
# Display the generated image:
+
        html.Div([
# the matplotlib way:
+
            dcc.Dropdown(
import matplotlib.pyplot as plt
+
                id='crossfilter-xaxis-column',
plt.imshow(wordcloud, interpolation='bilinear')
+
                options=[{'label': i, 'value': i} for i in available_indicators],
plt.axis("off")
+
                value='Fertility rate, total (births per woman)'
 +
            ),
 +
            dcc.RadioItems(
 +
                id='crossfilter-xaxis-type',
 +
                options=[{'label': i, 'value': i} for i in ['Linear', 'Log']],
 +
                value='Linear',
 +
                labelStyle={'display': 'inline-block'}
 +
            )
 +
        ],
 +
        style={'width': '49%', 'display': 'inline-block'}),
  
# lower max_font_size
+
        html.Div([
wordcloud = WordCloud(max_font_size=40).generate(text)
+
            dcc.Dropdown(
plt.figure()
+
                id='crossfilter-yaxis-column',
plt.imshow(wordcloud, interpolation="bilinear")
+
                options=[{'label': i, 'value': i} for i in available_indicators],
plt.axis("off")
+
                value='Life expectancy at birth, total (years)'
plt.show()
+
            ),
 +
            dcc.RadioItems(
 +
                id='crossfilter-yaxis-type',
 +
                options=[{'label': i, 'value': i} for i in ['Linear', 'Log']],
 +
                value='Linear',
 +
                labelStyle={'display': 'inline-block'}
 +
            )
 +
        ], style={'width': '49%', 'float': 'right', 'display': 'inline-block'})
 +
    ], style={
 +
        'borderBottom': 'thin lightgrey solid',
 +
        'backgroundColor': 'rgb(250, 250, 250)',
 +
        'padding': '10px 5px'
 +
    }),
  
# The pil way (if you don't have matplotlib)
+
    html.Div([
# image = wordcloud.to_image()
+
        dcc.Graph(
# image.show()
+
            id='crossfilter-indicator-scatter',
</syntaxhighlight>
+
            hoverData={'points': [{'customdata': 'Japan'}]}
 +
        )
 +
    ], style={'width': '49%', 'display': 'inline-block', 'padding': '0 20'}),
 +
    html.Div([
 +
        dcc.Graph(id='x-time-series'),
 +
        dcc.Graph(id='y-time-series'),
 +
    ], style={'display': 'inline-block', 'width': '49%'}),
  
 +
    html.Div(dcc.Slider(
 +
        id='crossfilter-year--slider',
 +
        min=df['Year'].min(),
 +
        max=df['Year'].max(),
 +
        value=df['Year'].max(),
 +
        marks={str(year): str(year) for year in df['Year'].unique()}
 +
    ), style={'width': '49%', 'padding': '0px 20px 20px 20px'})
 +
])
  
<br />
 
  
==Dash==
+
@app.callback(
https://plot.ly/dash/
+
    dash.dependencies.Output('crossfilter-indicator-scatter', 'figure'),
 +
    [dash.dependencies.Input('crossfilter-xaxis-column', 'value'),
 +
    dash.dependencies.Input('crossfilter-yaxis-column', 'value'),
 +
    dash.dependencies.Input('crossfilter-xaxis-type', 'value'),
 +
    dash.dependencies.Input('crossfilter-yaxis-type', 'value'),
 +
    dash.dependencies.Input('crossfilter-year--slider', 'value')])
 +
def update_graph(xaxis_column_name, yaxis_column_name,
 +
                xaxis_type, yaxis_type,
 +
                year_value):
 +
    dff = df[df['Year'] == year_value]
  
https://dash.plot.ly/?_ga=2.87536863.631018149.1572391590-265914126.1570370926
+
    return {
 +
        'data': [go.Scatter(
 +
            x=dff[dff['Indicator Name'] == xaxis_column_name]['Value'],
 +
            y=dff[dff['Indicator Name'] == yaxis_column_name]['Value'],
 +
            text=dff[dff['Indicator Name'] == yaxis_column_name]['Country Name'],
 +
            customdata=dff[dff['Indicator Name'] == yaxis_column_name]['Country Name'],
 +
            mode='markers',
 +
            marker={
 +
                'size': 15,
 +
                'opacity': 0.5,
 +
                'line': {'width': 0.5, 'color': 'white'}
 +
            }
 +
        )],
 +
        'layout': go.Layout(
 +
            xaxis={
 +
                'title': xaxis_column_name,
 +
                'type': 'linear' if xaxis_type == 'Linear' else 'log'
 +
            },
 +
            yaxis={
 +
                'title': yaxis_column_name,
 +
                'type': 'linear' if yaxis_type == 'Linear' else 'log'
 +
            },
 +
            margin={'l': 40, 'b': 30, 't': 10, 'r': 0},
 +
            height=450,
 +
            hovermode='closest'
 +
        )
 +
    }
  
  
'''Material from the Udemy's course I'm doing:''' https://www.udemy.com/course/interactive-python-dashboards-with-plotly-and-dash/
+
def create_time_series(dff, axis_type, title):
 +
    return {
 +
        'data': [go.Scatter(
 +
            x=dff['Year'],
 +
            y=dff['Value'],
 +
            mode='lines+markers'
 +
        )],
 +
        'layout': {
 +
            'height': 225,
 +
            'margin': {'l': 20, 'b': 30, 'r': 10, 't': 10},
 +
            'annotations': [{
 +
                'x': 0, 'y': 0.85, 'xanchor': 'left', 'yanchor': 'bottom',
 +
                'xref': 'paper', 'yref': 'paper', 'showarrow': False,
 +
                'align': 'left', 'bgcolor': 'rgba(255, 255, 255, 0.5)',
 +
                'text': title
 +
            }],
 +
            'yaxis': {'type': 'linear' if axis_type == 'Linear' else 'log'},
 +
            'xaxis': {'showgrid': False}
 +
        }
 +
    }
  
*https://docs.google.com/document/d/1DjWL2DxLiRaBrlD3ELyQlCBRu7UQuuWfgjv9LncNp_M/edit#heading=h.6kzspbaklmdx
 
*https://docs.google.com/document/d/1vI84_EpRTh4xfcFkTunFzZT0RWMcRSqdkPueVNBcLx8/edit
 
*https://github.com/Pierian-Data/Plotly-Dashboards-with-Dash
 
  
 +
@app.callback(
 +
    dash.dependencies.Output('x-time-series', 'figure'),
 +
    [dash.dependencies.Input('crossfilter-indicator-scatter', 'hoverData'),
 +
    dash.dependencies.Input('crossfilter-xaxis-column', 'value'),
 +
    dash.dependencies.Input('crossfilter-xaxis-type', 'value')])
 +
def update_y_timeseries(hoverData, xaxis_column_name, axis_type):
 +
    country_name = hoverData['points'][0]['customdata']
 +
    dff = df[df['Country Name'] == country_name]
 +
    dff = dff[dff['Indicator Name'] == xaxis_column_name]
 +
    title = '<b>{}</b><br>{}'.format(country_name, xaxis_column_name)
 +
    return create_time_series(dff, axis_type, title)
  
Dash apps consist of a Flask server that communicates with front-end React components using JSON packets over HTTP requests. https://www.tutorialspoint.com/python_web_development_libraries/python_web_development_libraries_dash_framework.htm
 
  
 +
@app.callback(
 +
    dash.dependencies.Output('y-time-series', 'figure'),
 +
    [dash.dependencies.Input('crossfilter-indicator-scatter', 'hoverData'),
 +
    dash.dependencies.Input('crossfilter-yaxis-column', 'value'),
 +
    dash.dependencies.Input('crossfilter-yaxis-type', 'value')])
 +
def update_x_timeseries(hoverData, yaxis_column_name, axis_type):
 +
    dff = df[df['Country Name'] == hoverData['points'][0]['customdata']]
 +
    dff = dff[dff['Indicator Name'] == yaxis_column_name]
 +
    return create_time_series(dff, axis_type, yaxis_column_name)
  
<br />
 
===Installation===
 
https://dash.plot.ly/installation
 
  
<syntaxhighlight lang="python3">
+
if __name__ == '__main__':
pip install dash==1.4.1  # The core dash backend
+
    app.run_server(debug=True, port=8051)
pip install dash-daq==0.2.1  # DAQ components (newly open-sourced!)
 
 
 
# Note: starting with dash 0.37.0, dash automatically installs dash-renderer, dash-core-components, dash-html-components, and dash-table, using known-compatible versions of each. You need not and should not install these separately any longer, only dash itself.
 
 
</syntaxhighlight>
 
</syntaxhighlight>
  
  
A quick note on checking your versions and on upgrading. These docs are run using the versions listed above and these versions should be the latest versions available. To check which version that you have installed, you can run e.g:
+
[[File:Dash example2.png|center|900x900px|thumb]]
<syntaxhighlight lang="python3">
 
>>> import dash_core_components
 
>>> print(dash_core_components.__version__)
 
</syntaxhighlight>
 
  
  
To see the latest changes of any package, check the GitHub repo's CHANGELOG.md file:
+
<br />
 
 
*[https://github.com/plotly/dash/blob/master/CHANGELOG.md dash & dash-renderer changelog]
 
**<code>dash-renderer</code> is a separate package installed automatically with dash but its updates are included in the main dash changelog. These docs are using dash-renderer==1.1.2.
 
*[https://github.com/plotly/dash-core-components/blob/master/CHANGELOG.md dash-core-components changelog]
 
*[https://github.com/plotly/dash-html-components/blob/master/CHANGELOG.md dash-html-components changelog]
 
*[https://github.com/plotly/dash-table/blob/master/CHANGELOG.md dash-table changelog]
 
*[https://github.com/plotly/plotly.py/blob/master/CHANGELOG.md plotly changelog]
 
**the <code>plotly</code> package is also installed automatically with dash. It is the Python interface to the plotly.js graphing library, so is mainly used by dash-core-components, but it's also used by dash itself. These docs are using plotly==3.3.0.
 
 
 
All of these packages adhere to [https://semver.org/ semver].
 
  
 
+
==Deploying Dash Apps==
<br />
 
===Deploying Dash Apps===
 
 
https://dash.plot.ly/deployment
 
https://dash.plot.ly/deployment
  
Line 139: Line 230:
  
 
<br />
 
<br />
====Flask Deployment====
+
===Flask Deployment===
 
https://flask.palletsprojects.com/en/1.1.x/deploying/
 
https://flask.palletsprojects.com/en/1.1.x/deploying/
 +
 +
<span style="color:#0000FF; background:#F0E68C">'''Es este archivo está paso por paso el procedimiento que realicé la última vez to deploy my Dash Application en AWS: '''</span> [[File:Deploying_a_Dash_App_in_AWS.zip]]
  
  
 
<br />
 
<br />
=====Gunicorn=====
+
====Gunicorn====
 
https://flask.palletsprojects.com/en/1.1.x/deploying/wsgi-standalone/#gunicorn
 
https://flask.palletsprojects.com/en/1.1.x/deploying/wsgi-standalone/#gunicorn
  
Line 155: Line 248:
  
 
https://anaconda.org/conda-forge/gunicorn
 
https://anaconda.org/conda-forge/gunicorn
<syntaxhighlight>
+
<syntaxhighlight lang="shell">
 
conda install -c conda-forge gunicorn
 
conda install -c conda-forge gunicorn
 
</syntaxhighlight>
 
</syntaxhighlight>
Line 161: Line 254:
 
or
 
or
  
<syntaxhighlight>
+
<syntaxhighlight lang="shell">
 
pip install gunicorn
 
pip install gunicorn
 
</syntaxhighlight>
 
</syntaxhighlight>
Line 185: Line 278:
 
<br />
 
<br />
 
'''First example:'''
 
'''First example:'''
<syntaxhighlight lang="python3">
+
<syntaxhighlight lang="python">
 
def app(environ, start_response):
 
def app(environ, start_response):
 
         data = b"Hello, World!\n"
 
         data = b"Hello, World!\n"
Line 202: Line 295:
  
 
<br />
 
<br />
======Deploying a Gunicorn server======
+
=====Deploying a Gunicorn server=====
 
This is the official page. It doesn't explain well how to do it:
 
This is the official page. It doesn't explain well how to do it:
 
: http://docs.gunicorn.org/en/latest/deploy.html
 
: http://docs.gunicorn.org/en/latest/deploy.html
Line 221: Line 314:
  
 
* '''Create and activate a Python Virtual Environment :'''
 
* '''Create and activate a Python Virtual Environment :'''
::<syntaxhighlight lang="shell">
+
: <span style="color:red">See this source to understand how to create a virtualenv for an specific python version</span>: https://help.dreamhost.com/hc/en-us/articles/115000695551-Installing-and-using-virtualenv-with-Python-3
 +
 
 +
::<syntaxhighlight lang="bash">
 
sudo pip3 install virtualenv
 
sudo pip3 install virtualenv
  
Line 234: Line 329:
  
 
:: Your prompt will change to indicate that you are now operating within the virtual environment. It will look something like this:
 
:: Your prompt will change to indicate that you are now operating within the virtual environment. It will look something like this:
:: <syntaxhighlight>
+
:: <syntaxhighlight lang="shell">
 
(myprojectenv)user@host:~/myproject$.
 
(myprojectenv)user@host:~/myproject$.
 
</syntaxhighlight>
 
</syntaxhighlight>
Line 241: Line 336:
  
 
* '''Install Flask, Dash and Gunicorn inside the virtual environment:'''
 
* '''Install Flask, Dash and Gunicorn inside the virtual environment:'''
:: <syntaxhighlight>
+
:: <syntaxhighlight lang="shell">
 
pip install gunicorn flask
 
pip install gunicorn flask
ñl ñl
+
 
 +
ver «Dash» installation
 +
ver «gunicorn» installation
 
</syntaxhighlight>
 
</syntaxhighlight>
  
Line 249: Line 346:
  
 
* '''Create a Sample App:'''
 
* '''Create a Sample App:'''
:: <syntaxhighlight lang="python3">
+
:: <syntaxhighlight lang="python">
 
import os
 
import os
 
import dash
 
import dash
Line 291: Line 388:
  
 
* '''Now, you can test your Dash app by typing:'''
 
* '''Now, you can test your Dash app by typing:'''
:: <syntaxhighlight>
+
:: <syntaxhighlight lang="shell">
 
(myprojectenv)$ python myproject.py
 
(myprojectenv)$ python myproject.py
 
</syntaxhighlight>
 
</syntaxhighlight>
Line 300: Line 397:
  
 
* '''Create the WSGI Entry Point:''' We'll create a file that will serve as the entry point for our application. This will tell our Gunicorn server how to interact with the application:
 
* '''Create the WSGI Entry Point:''' We'll create a file that will serve as the entry point for our application. This will tell our Gunicorn server how to interact with the application:
:: <syntaxhighlight>
+
:: <syntaxhighlight lang="shell">
 
(myprojectenv)$ vi ~/myproject/wsgi.py
 
(myprojectenv)$ vi ~/myproject/wsgi.py
 
</syntaxhighlight>
 
</syntaxhighlight>
  
:: <syntaxhighlight lang="python3">
+
:: <syntaxhighlight lang="python">
 
from myproject import server
 
from myproject import server
  
Line 313: Line 410:
 
:: <span style="color:#FF0000">Notice that we have import the variable <code>server</code> from <code>myproject.py</code></span>
 
:: <span style="color:#FF0000">Notice that we have import the variable <code>server</code> from <code>myproject.py</code></span>
 
:: <span style="color:#FF0000">This is the different with respect to a pure Flask application, where you would import <code>App</code> instead of <code>server</code>. In Dash, we require <code>app.server</code>, which is in the <code>server</code> variable we have created. So if we were deploying a pure flak App, it would be:</span>
 
:: <span style="color:#FF0000">This is the different with respect to a pure Flask application, where you would import <code>App</code> instead of <code>server</code>. In Dash, we require <code>app.server</code>, which is in the <code>server</code> variable we have created. So if we were deploying a pure flak App, it would be:</span>
::: <syntaxhighlight>
+
::: <syntaxhighlight lang="shell">
 
from myproject import app
 
from myproject import app
  
Line 323: Line 420:
  
 
* '''Testing Gunicorn's Ability to Serve the Project:'''
 
* '''Testing Gunicorn's Ability to Serve the Project:'''
:: <syntaxhighlight>
+
:: <syntaxhighlight lang="shell">
 
(myprojectenv)$ cd ~/myproject
 
(myprojectenv)$ cd ~/myproject
 
(myprojectenv)$ gunicorn --bind 0.0.0.0:5000 wsgi:server
 
(myprojectenv)$ gunicorn --bind 0.0.0.0:5000 wsgi:server
Line 334: Line 431:
  
 
* '''We're now done with our virtual environment, so we can deactivate it:'''
 
* '''We're now done with our virtual environment, so we can deactivate it:'''
:: <syntaxhighlight>
+
:: <syntaxhighlight lang="shell">
 
(myprojectenv)$ deactivate
 
(myprojectenv)$ deactivate
 
</syntaxhighlight>
 
</syntaxhighlight>
Line 342: Line 439:
  
 
* '''Create a <code>systemd Unit</code> File:'''
 
* '''Create a <code>systemd Unit</code> File:'''
:: <syntaxhighlight>
+
:: <syntaxhighlight lang="shell">
 
$ vi /etc/systemd/system/myproject.service
 
$ vi /etc/systemd/system/myproject.service
 
</syntaxhighlight>
 
</syntaxhighlight>
Line 365: Line 462:
  
 
* '''We can now start the Gunicorn service we created and enable it so that it starts at boot:'''
 
* '''We can now start the Gunicorn service we created and enable it so that it starts at boot:'''
:: <syntaxhighlight>
+
:: <syntaxhighlight lang="shell">
 
$ sudo systemctl start myproject
 
$ sudo systemctl start myproject
 
$ sudo systemctl enable myproject
 
$ sudo systemctl enable myproject
Line 373: Line 470:
  
 
* '''Configuring Nginx to Proxy Requests:'''
 
* '''Configuring Nginx to Proxy Requests:'''
:: <syntaxhighlight>
+
:: <syntaxhighlight lang="shell">
 
$ vi /etc/nginx/sites-available/default
 
$ vi /etc/nginx/sites-available/default
 
</syntaxhighlight>
 
</syntaxhighlight>
 +
  
  
 
:: <syntaxhighlight lang="bash">
 
:: <syntaxhighlight lang="bash">
 +
# Esta es la configuración por defecto (eliminando lo que en el archive original está comentado para simplificarlo aquí)
 
server {
 
server {
    listen 80;
+
listen 80 default_server;
    server_name gofaaaz.sinfronteras.ws;
+
listen [::]:80 default_server;
 
 
    location / {
 
        include proxy_params;
 
        proxy_pass http://unix:/root/myproject/myproject.sock;
 
    }
 
}
 
</syntaxhighlight>
 
  
 +
index index.html index.htm index.nginx-debian.html;
  
 +
server_name _;
  
* '''Finally, we restart the Nginx process:'''
+
location / {
:: <syntaxhighlight>
+
                include proxy_params;
sudo systemctl restart nginx.service
+
                proxy_pass http://unix:/home/ubuntu/SADashboard/index.sock;
</syntaxhighlight>
+
        }
:: You should now be able to go to your server's domain name or IP address in your web browser and see your App.
 
  
 +
}
  
<br />
 
  
===Dash Layout===
+
# Aquí estamos realizando la configuración
https://dash.plot.ly/getting-started
+
server {
 +
    listen 80;
 +
    server_name awsdashboard.sinfronteras.ws;
  
Dash apps are composed of two parts:
+
    location / {
 
+
        include proxy_params;
*'''The first part''' is' the "layout" of the app and it describes what the application looks like.
+
        proxy_pass http://unix:/home/ubuntu/SADashboard/index.sock;
*'''The second part''' describes the interactivity of the application and will be covered in the next chapter.
+
     }
 
+
}
Dash provides Python classes for all of the visual components of the application. We maintain a set of components in the <code>dash_core_components</code> and the <code>dash_html_components</code> library but <span style="color:#FF0000">you can also [https://github.com/plotly/dash-component-boilerplate build your own] with <code>JavaScript</code> and <code>React.js</code>.</span>
 
 
 
To get started, create a file named `app.py` with the following code:<syntaxhighlight lang="python3">
 
# -*- coding: utf-8 -*-
 
import dash
 
import dash_core_components as dcc
 
import dash_html_components as html
 
 
 
external_stylesheets = ['https://codepen.io/chriddyp/pen/bWLwgP.css']
 
 
 
app = dash.Dash(__name__, external_stylesheets=external_stylesheets)
 
 
 
app.layout = html.Div(children=[
 
    html.H1(children='Hello Dash'),
 
 
 
    html.Div(children='''
 
        Dash: A web application framework for Python.
 
     '''),
 
 
 
    dcc.Graph(
 
        id='example-graph',
 
        figure={
 
            'data': [
 
                {'x': [1, 2, 3], 'y': [4, 1, 2], 'type': 'bar', 'name': 'SF'},
 
                {'x': [1, 2, 3], 'y': [2, 4, 5], 'type': 'bar', 'name': u'Montréal'},
 
            ],
 
            'layout': {
 
                'title': 'Dash Data Visualization'
 
            }
 
        }
 
    )
 
])
 
 
 
if __name__ == '__main__':
 
    app.run_server(debug=True, port=8051)
 
 
</syntaxhighlight>
 
</syntaxhighlight>
 +
:: <span style="color:#FF0000">ES EXTREMADAMENTE IMPORTANTE NOTAR QUE «gofaaaz.sinfronteras.ws» no puede ser reemplazado por la IP del server. La última vez perdí muchísimo tiempo porque intenté hacerlo con la IP y no funcionaba; pues la IP va hacial el «default_server;» y buscá el directorio root de nginx. Tampoco funciona si no se hace esta modificación en  Nginx y se trata de acceder sólo con la IP:PORT en donde hemos iniciado la Dash applicatioin. Lo que tuve que hacer para que funcionara fue crear un subdominio y agregar el subdominio en vez de la IP como se muestra a continuación. </span>
  
<span style="color:#FF0000">Es importante utilizar un port que no esté ocupado por otro proceso.</span>
 
  
 +
:: <span style="color:#FF0000">Ahora, si queremos ingresar a la aplicación Dash utilizando al IP del server, podemo realizar la configuración de la siguiente forma. Note que en «location» hemos configurado la ruta hacia el «index.sock» en donde está corriendo la Dash Application</span>
 +
:: <syntaxhighlight lang="bash">
 +
server {
 +
listen 80 default_server;
 +
listen [::]:80 default_server;
  
<br />
+
index index.html index.htm index.nginx-debian.html;
  
===Examples===
+
server_name _;
  
 
+
location / {
<br />
+
                include proxy_params;
====Example 2====
+
                proxy_pass http://unix:/home/ubuntu/SADashboard/index.sock;
<syntaxhighlight lang="python3">
 
import dash
 
import dash_core_components as dcc
 
import dash_html_components as html
 
import pandas as pd
 
import plotly.graph_objs as go
 
 
 
external_stylesheets = ['https://codepen.io/chriddyp/pen/bWLwgP.css']
 
 
 
app = dash.Dash(__name__, external_stylesheets=external_stylesheets)
 
 
 
df = pd.read_csv(
 
    'https://gist.githubusercontent.com/chriddyp/'
 
    'cb5392c35661370d95f300086accea51/raw/'
 
    '8e0768211f6b747c0db42a9ce9a0937dafcbd8b2/'
 
    'indicators.csv')
 
 
 
available_indicators = df['Indicator Name'].unique()
 
 
 
app.layout = html.Div([
 
    html.Div([
 
 
 
        html.Div([
 
            dcc.Dropdown(
 
                id='crossfilter-xaxis-column',
 
                options=[{'label': i, 'value': i} for i in available_indicators],
 
                value='Fertility rate, total (births per woman)'
 
            ),
 
            dcc.RadioItems(
 
                id='crossfilter-xaxis-type',
 
                options=[{'label': i, 'value': i} for i in ['Linear', 'Log']],
 
                value='Linear',
 
                labelStyle={'display': 'inline-block'}
 
            )
 
        ],
 
        style={'width': '49%', 'display': 'inline-block'}),
 
 
 
        html.Div([
 
            dcc.Dropdown(
 
                id='crossfilter-yaxis-column',
 
                options=[{'label': i, 'value': i} for i in available_indicators],
 
                value='Life expectancy at birth, total (years)'
 
            ),
 
            dcc.RadioItems(
 
                id='crossfilter-yaxis-type',
 
                options=[{'label': i, 'value': i} for i in ['Linear', 'Log']],
 
                value='Linear',
 
                labelStyle={'display': 'inline-block'}
 
            )
 
        ], style={'width': '49%', 'float': 'right', 'display': 'inline-block'})
 
    ], style={
 
        'borderBottom': 'thin lightgrey solid',
 
        'backgroundColor': 'rgb(250, 250, 250)',
 
        'padding': '10px 5px'
 
    }),
 
 
 
    html.Div([
 
        dcc.Graph(
 
            id='crossfilter-indicator-scatter',
 
            hoverData={'points': [{'customdata': 'Japan'}]}
 
        )
 
    ], style={'width': '49%', 'display': 'inline-block', 'padding': '0 20'}),
 
    html.Div([
 
        dcc.Graph(id='x-time-series'),
 
        dcc.Graph(id='y-time-series'),
 
    ], style={'display': 'inline-block', 'width': '49%'}),
 
 
 
    html.Div(dcc.Slider(
 
        id='crossfilter-year--slider',
 
        min=df['Year'].min(),
 
        max=df['Year'].max(),
 
        value=df['Year'].max(),
 
        marks={str(year): str(year) for year in df['Year'].unique()}
 
    ), style={'width': '49%', 'padding': '0px 20px 20px 20px'})
 
])
 
 
 
 
 
@app.callback(
 
    dash.dependencies.Output('crossfilter-indicator-scatter', 'figure'),
 
    [dash.dependencies.Input('crossfilter-xaxis-column', 'value'),
 
    dash.dependencies.Input('crossfilter-yaxis-column', 'value'),
 
    dash.dependencies.Input('crossfilter-xaxis-type', 'value'),
 
    dash.dependencies.Input('crossfilter-yaxis-type', 'value'),
 
    dash.dependencies.Input('crossfilter-year--slider', 'value')])
 
def update_graph(xaxis_column_name, yaxis_column_name,
 
                xaxis_type, yaxis_type,
 
                year_value):
 
    dff = df[df['Year'] == year_value]
 
 
 
    return {
 
        'data': [go.Scatter(
 
            x=dff[dff['Indicator Name'] == xaxis_column_name]['Value'],
 
            y=dff[dff['Indicator Name'] == yaxis_column_name]['Value'],
 
            text=dff[dff['Indicator Name'] == yaxis_column_name]['Country Name'],
 
            customdata=dff[dff['Indicator Name'] == yaxis_column_name]['Country Name'],
 
            mode='markers',
 
            marker={
 
                'size': 15,
 
                'opacity': 0.5,
 
                'line': {'width': 0.5, 'color': 'white'}
 
            }
 
        )],
 
        'layout': go.Layout(
 
            xaxis={
 
                'title': xaxis_column_name,
 
                'type': 'linear' if xaxis_type == 'Linear' else 'log'
 
            },
 
            yaxis={
 
                'title': yaxis_column_name,
 
                'type': 'linear' if yaxis_type == 'Linear' else 'log'
 
            },
 
            margin={'l': 40, 'b': 30, 't': 10, 'r': 0},
 
            height=450,
 
            hovermode='closest'
 
        )
 
    }
 
 
 
 
 
def create_time_series(dff, axis_type, title):
 
    return {
 
        'data': [go.Scatter(
 
            x=dff['Year'],
 
            y=dff['Value'],
 
            mode='lines+markers'
 
        )],
 
        'layout': {
 
            'height': 225,
 
            'margin': {'l': 20, 'b': 30, 'r': 10, 't': 10},
 
            'annotations': [{
 
                'x': 0, 'y': 0.85, 'xanchor': 'left', 'yanchor': 'bottom',
 
                'xref': 'paper', 'yref': 'paper', 'showarrow': False,
 
                'align': 'left', 'bgcolor': 'rgba(255, 255, 255, 0.5)',
 
                'text': title
 
            }],
 
            'yaxis': {'type': 'linear' if axis_type == 'Linear' else 'log'},
 
            'xaxis': {'showgrid': False}
 
 
         }
 
         }
    }
 
  
 
+
}
@app.callback(
 
    dash.dependencies.Output('x-time-series', 'figure'),
 
    [dash.dependencies.Input('crossfilter-indicator-scatter', 'hoverData'),
 
    dash.dependencies.Input('crossfilter-xaxis-column', 'value'),
 
    dash.dependencies.Input('crossfilter-xaxis-type', 'value')])
 
def update_y_timeseries(hoverData, xaxis_column_name, axis_type):
 
    country_name = hoverData['points'][0]['customdata']
 
    dff = df[df['Country Name'] == country_name]
 
    dff = dff[dff['Indicator Name'] == xaxis_column_name]
 
    title = '<b>{}</b><br>{}'.format(country_name, xaxis_column_name)
 
    return create_time_series(dff, axis_type, title)
 
  
  
@app.callback(
+
# server {
     dash.dependencies.Output('y-time-series', 'figure'),
+
#     listen 80;
     [dash.dependencies.Input('crossfilter-indicator-scatter', 'hoverData'),
+
#     server_name awsdashboard.sinfronteras.ws;
    dash.dependencies.Input('crossfilter-yaxis-column', 'value'),
+
#
    dash.dependencies.Input('crossfilter-yaxis-type', 'value')])
+
#    location / {
def update_x_timeseries(hoverData, yaxis_column_name, axis_type):
+
#        include proxy_params;
     dff = df[df['Country Name'] == hoverData['points'][0]['customdata']]
+
#        proxy_pass http://unix:/home/ubuntu/SADashboard/index.sock;
    dff = dff[dff['Indicator Name'] == yaxis_column_name]
+
#     }
    return create_time_series(dff, axis_type, yaxis_column_name)
+
# }
 +
</syntaxhighlight>
  
  
if __name__ == '__main__':
+
* '''Finally, we restart the Nginx process:'''
    app.run_server(debug=True, port=8051)
+
:: <syntaxhighlight lang="bash">
 +
sudo systemctl restart nginx.service
 
</syntaxhighlight>
 
</syntaxhighlight>
 
+
:: You should now be able to go to your server's domain name or IP address in your web browser and see your App.
 
 
[[File:Dash example2.png|center|900x900px|thumb]]
 
  
  
 
<br />
 
<br />

Latest revision as of 14:43, 11 September 2024



Examples

GitHub repository: https://github.com/plotly/dash-sample-apps


Dash Core Components Gallery: https://dash.plot.ly/dash-core-components



Hello world example

app.py
import dash
import dash_core_components as dcc
import dash_html_components as html


app = dash.Dash(__name__)

app.layout = html.Div(children=[
    html.H1(children='Hello Dash'),
    html.Div(children='Dash: A web application framework for Python')
])


if __name__ == '__main__':
    app.run_server(debug=True, port=8551)


To run the app:

python app.ph

Es importante utilizar un port que no esté ocupado por otro proceso.



A nice example

app.py
import dash
import dash_core_components as dcc
import dash_html_components as html
import pandas as pd
import plotly.graph_objs as go

external_stylesheets = ['https://codepen.io/chriddyp/pen/bWLwgP.css']

app = dash.Dash(__name__, external_stylesheets=external_stylesheets)

df = pd.read_csv(
    'https://gist.githubusercontent.com/chriddyp/'
    'cb5392c35661370d95f300086accea51/raw/'
    '8e0768211f6b747c0db42a9ce9a0937dafcbd8b2/'
    'indicators.csv')

available_indicators = df['Indicator Name'].unique()

app.layout = html.Div([
    html.Div([

        html.Div([
            dcc.Dropdown(
                id='crossfilter-xaxis-column',
                options=[{'label': i, 'value': i} for i in available_indicators],
                value='Fertility rate, total (births per woman)'
            ),
            dcc.RadioItems(
                id='crossfilter-xaxis-type',
                options=[{'label': i, 'value': i} for i in ['Linear', 'Log']],
                value='Linear',
                labelStyle={'display': 'inline-block'}
            )
        ],
        style={'width': '49%', 'display': 'inline-block'}),

        html.Div([
            dcc.Dropdown(
                id='crossfilter-yaxis-column',
                options=[{'label': i, 'value': i} for i in available_indicators],
                value='Life expectancy at birth, total (years)'
            ),
            dcc.RadioItems(
                id='crossfilter-yaxis-type',
                options=[{'label': i, 'value': i} for i in ['Linear', 'Log']],
                value='Linear',
                labelStyle={'display': 'inline-block'}
            )
        ], style={'width': '49%', 'float': 'right', 'display': 'inline-block'})
    ], style={
        'borderBottom': 'thin lightgrey solid',
        'backgroundColor': 'rgb(250, 250, 250)',
        'padding': '10px 5px'
    }),

    html.Div([
        dcc.Graph(
            id='crossfilter-indicator-scatter',
            hoverData={'points': [{'customdata': 'Japan'}]}
        )
    ], style={'width': '49%', 'display': 'inline-block', 'padding': '0 20'}),
    html.Div([
        dcc.Graph(id='x-time-series'),
        dcc.Graph(id='y-time-series'),
    ], style={'display': 'inline-block', 'width': '49%'}),

    html.Div(dcc.Slider(
        id='crossfilter-year--slider',
        min=df['Year'].min(),
        max=df['Year'].max(),
        value=df['Year'].max(),
        marks={str(year): str(year) for year in df['Year'].unique()}
    ), style={'width': '49%', 'padding': '0px 20px 20px 20px'})
])


@app.callback(
    dash.dependencies.Output('crossfilter-indicator-scatter', 'figure'),
    [dash.dependencies.Input('crossfilter-xaxis-column', 'value'),
     dash.dependencies.Input('crossfilter-yaxis-column', 'value'),
     dash.dependencies.Input('crossfilter-xaxis-type', 'value'),
     dash.dependencies.Input('crossfilter-yaxis-type', 'value'),
     dash.dependencies.Input('crossfilter-year--slider', 'value')])
def update_graph(xaxis_column_name, yaxis_column_name,
                 xaxis_type, yaxis_type,
                 year_value):
    dff = df[df['Year'] == year_value]

    return {
        'data': [go.Scatter(
            x=dff[dff['Indicator Name'] == xaxis_column_name]['Value'],
            y=dff[dff['Indicator Name'] == yaxis_column_name]['Value'],
            text=dff[dff['Indicator Name'] == yaxis_column_name]['Country Name'],
            customdata=dff[dff['Indicator Name'] == yaxis_column_name]['Country Name'],
            mode='markers',
            marker={
                'size': 15,
                'opacity': 0.5,
                'line': {'width': 0.5, 'color': 'white'}
            }
        )],
        'layout': go.Layout(
            xaxis={
                'title': xaxis_column_name,
                'type': 'linear' if xaxis_type == 'Linear' else 'log'
            },
            yaxis={
                'title': yaxis_column_name,
                'type': 'linear' if yaxis_type == 'Linear' else 'log'
            },
            margin={'l': 40, 'b': 30, 't': 10, 'r': 0},
            height=450,
            hovermode='closest'
        )
    }


def create_time_series(dff, axis_type, title):
    return {
        'data': [go.Scatter(
            x=dff['Year'],
            y=dff['Value'],
            mode='lines+markers'
        )],
        'layout': {
            'height': 225,
            'margin': {'l': 20, 'b': 30, 'r': 10, 't': 10},
            'annotations': [{
                'x': 0, 'y': 0.85, 'xanchor': 'left', 'yanchor': 'bottom',
                'xref': 'paper', 'yref': 'paper', 'showarrow': False,
                'align': 'left', 'bgcolor': 'rgba(255, 255, 255, 0.5)',
                'text': title
            }],
            'yaxis': {'type': 'linear' if axis_type == 'Linear' else 'log'},
            'xaxis': {'showgrid': False}
        }
    }


@app.callback(
    dash.dependencies.Output('x-time-series', 'figure'),
    [dash.dependencies.Input('crossfilter-indicator-scatter', 'hoverData'),
     dash.dependencies.Input('crossfilter-xaxis-column', 'value'),
     dash.dependencies.Input('crossfilter-xaxis-type', 'value')])
def update_y_timeseries(hoverData, xaxis_column_name, axis_type):
    country_name = hoverData['points'][0]['customdata']
    dff = df[df['Country Name'] == country_name]
    dff = dff[dff['Indicator Name'] == xaxis_column_name]
    title = '<b>{}</b><br>{}'.format(country_name, xaxis_column_name)
    return create_time_series(dff, axis_type, title)


@app.callback(
    dash.dependencies.Output('y-time-series', 'figure'),
    [dash.dependencies.Input('crossfilter-indicator-scatter', 'hoverData'),
     dash.dependencies.Input('crossfilter-yaxis-column', 'value'),
     dash.dependencies.Input('crossfilter-yaxis-type', 'value')])
def update_x_timeseries(hoverData, yaxis_column_name, axis_type):
    dff = df[df['Country Name'] == hoverData['points'][0]['customdata']]
    dff = dff[dff['Indicator Name'] == yaxis_column_name]
    return create_time_series(dff, axis_type, yaxis_column_name)


if __name__ == '__main__':
    app.run_server(debug=True, port=8051)


Dash example2.png



Deploying Dash Apps

https://dash.plot.ly/deployment


Dash uses Flask under the hood. This makes deployment easy: you can deploy a Dash app just like you would deploy a Flask app. Almost every cloud server provider has a guide for deploying Flask apps. There is also a Dash Deployment Server, but is not free (commercial).

  • Flask Deployment
  • Dash Deployment Server (commercial)



Flask Deployment

https://flask.palletsprojects.com/en/1.1.x/deploying/

Es este archivo está paso por paso el procedimiento que realicé la última vez to deploy my Dash Application en AWS: File:Deploying a Dash App in AWS.zip



Gunicorn

https://flask.palletsprojects.com/en/1.1.x/deploying/wsgi-standalone/#gunicorn

https://gunicorn.org/



Installation:

https://anaconda.org/conda-forge/gunicorn

conda install -c conda-forge gunicorn

or

pip install gunicorn



Gunicorn «Green Unicorn» is a WSGI HTTP Server for UNIX. It's a pre-fork worker model ported from Ruby's Unicorn project. It supports both eventlet and greenlet. Running a Flask application on this server is quite simple:

gunicorn myproject:app


Gunicorn provides many command-line options (see gunicorn -h). For example, to run a Flask application with 4 worker processes (-w 4) binding to localhost port 4000 (-b 127.0.0.1:4000):

gunicorn -w 4 -b 127.0.0.1:4000 myproject:app


The gunicorn command expects the names of your application module or package and the application instance within the module. If you use the application factory pattern, you can pass a call to that:

gunicorn "myproject:create_app()"



First example:

def app(environ, start_response):
        data = b"Hello, World!\n"
        start_response("200 OK", [
            ("Content-Type", "text/plain"),
            ("Content-Length", str(len(data)))
        ])
        return iter([data])

To run the server:

gunicorn -w 4 myapp:app

Executing the above command will only run the development server. In the next section we will explain how to deploy a Gunicorn



Deploying a Gunicorn server

This is the official page. It doesn't explain well how to do it:

http://docs.gunicorn.org/en/latest/deploy.html


This tutorial explain well hot to do deploy a Flask Applications with Gunicorn and Nginx:

https://www.digitalocean.com/community/tutorials/how-to-serve-flask-applications-with-gunicorn-and-nginx-on-ubuntu-16-04


Now, when using Dash, we have to make a few changes with respect to the above tutorial. The following posts helped me to find the solution:

https://community.plot.ly/t/error-with-gunicorn/8247
https://community.plot.ly/t/failed-to-find-application-object-server-in-app/13723



Example - Deploying a Dash aplications with Gunicorn and Nginx on Ubuntu 16.04 (based on https://www.digitalocean.com/community/tutorials/how-to-serve-flask-applications-with-gunicorn-and-nginx-on-ubuntu-16-04)


  • Create and activate a Python Virtual Environment :
See this source to understand how to create a virtualenv for an specific python version: https://help.dreamhost.com/hc/en-us/articles/115000695551-Installing-and-using-virtualenv-with-Python-3
sudo pip3 install virtualenv

mkdir ~/myproject
cd ~/myproject

virtualenv myprojectenv  # This will install a local copy of Python and pip into a directory called myprojectenv

source myprojectenv/bin/activate


Your prompt will change to indicate that you are now operating within the virtual environment. It will look something like this:
(myprojectenv)user@host:~/myproject$.


  • Install Flask, Dash and Gunicorn inside the virtual environment:
pip install gunicorn flask

ver «Dash» installation
ver «gunicorn» installation


  • Create a Sample App:
import os
import dash
import dash_core_components as dcc
import dash_html_components as html

external_stylesheets = ['https://codepen.io/chriddyp/pen/bWLwgP.css']

app = dash.Dash(__name__, external_stylesheets=external_stylesheets)

server = app.server

app.layout = html.Div(children=[
    html.H1(children='Hello Dash'),

    html.Div(children='''
        Dash: A web application framework for Python.
    '''),

    dcc.Graph(
        id='example-graph',
        figure={
            'data': [
                {'x': [1, 2, 3], 'y': [4, 1, 2], 'type': 'bar', 'name': 'SF'},
                {'x': [1, 2, 3], 'y': [2, 4, 5], 'type': 'bar', 'name': u'Montréal'},
            ],
            'layout': {
                'title': 'Dash Data Visualization'
            }
        }
    )
])

if __name__ == '__main__':
    app.run_server(debug=True, host='0.0.0.0')
Notice that we have included: server = app.server.


  • Now, you can test your Dash app by typing:
(myprojectenv)$ python myproject.py
Visit your server's domain name or IP address followed by :port in your web browser to verify your App is working.


  • Create the WSGI Entry Point: We'll create a file that will serve as the entry point for our application. This will tell our Gunicorn server how to interact with the application:
(myprojectenv)$ vi ~/myproject/wsgi.py
from myproject import server

if __name__ == "__main__":
    server.run()
Notice that we have import the variable server from myproject.py
This is the different with respect to a pure Flask application, where you would import App instead of server. In Dash, we require app.server, which is in the server variable we have created. So if we were deploying a pure flak App, it would be:
from myproject import app

if __name__ == "__main__":
    app.run()


  • Testing Gunicorn's Ability to Serve the Project:
(myprojectenv)$ cd ~/myproject
(myprojectenv)$ gunicorn --bind 0.0.0.0:5000 wsgi:server
For a pure Flask application, would be wsgi:App.
Visit your server's domain name or IP address with :port appended to the end in your web browser again.


  • We're now done with our virtual environment, so we can deactivate it:
(myprojectenv)$ deactivate
Any Python commands will now use the system’s Python environment again.


  • Create a systemd Unit File:
$ vi /etc/systemd/system/myproject.service
[Unit]
Description=Gunicorn instance to serve myproject
After=network.target

[Service]
User=root
Group=www-data
WorkingDirectory=/root/myproject
Environment="PATH=/root/myproject/myprojectenv/bin"
ExecStart=/root/myproject/myprojectenv/bin/gunicorn --workers 3 --bind unix:myproject.sock -m 007 wsgi:server

[Install]
WantedBy=multi-user.target


  • We can now start the Gunicorn service we created and enable it so that it starts at boot:
$ sudo systemctl start myproject
$ sudo systemctl enable myproject


  • Configuring Nginx to Proxy Requests:
$ vi /etc/nginx/sites-available/default


# Esta es la configuración por defecto (eliminando lo que en el archive original está comentado para simplificarlo aquí)
server {
	listen 80 default_server;
	listen [::]:80 default_server;

	index index.html index.htm index.nginx-debian.html;

	server_name _;

	 location / {
                include proxy_params;
                proxy_pass http://unix:/home/ubuntu/SADashboard/index.sock;
        }

}


# Aquí estamos realizando la configuración 
server {
    listen 80;
    server_name awsdashboard.sinfronteras.ws;

    location / {
        include proxy_params;
        proxy_pass http://unix:/home/ubuntu/SADashboard/index.sock;
    }
}
ES EXTREMADAMENTE IMPORTANTE NOTAR QUE «gofaaaz.sinfronteras.ws» no puede ser reemplazado por la IP del server. La última vez perdí muchísimo tiempo porque intenté hacerlo con la IP y no funcionaba; pues la IP va hacial el «default_server;» y buscá el directorio root de nginx. Tampoco funciona si no se hace esta modificación en Nginx y se trata de acceder sólo con la IP:PORT en donde hemos iniciado la Dash applicatioin. Lo que tuve que hacer para que funcionara fue crear un subdominio y agregar el subdominio en vez de la IP como se muestra a continuación.


Ahora, si queremos ingresar a la aplicación Dash utilizando al IP del server, podemo realizar la configuración de la siguiente forma. Note que en «location» hemos configurado la ruta hacia el «index.sock» en donde está corriendo la Dash Application
server {
	listen 80 default_server;
	listen [::]:80 default_server;

	index index.html index.htm index.nginx-debian.html;

	server_name _;

	 location / {
                include proxy_params;
                proxy_pass http://unix:/home/ubuntu/SADashboard/index.sock;
        }

}


# server {
#     listen 80;
#     server_name awsdashboard.sinfronteras.ws;
# 
#     location / {
#         include proxy_params;
#         proxy_pass http://unix:/home/ubuntu/SADashboard/index.sock;
#     }
# }


  • Finally, we restart the Nginx process:
sudo systemctl restart nginx.service
You should now be able to go to your server's domain name or IP address in your web browser and see your App.