Difference between revisions of "Dash - Plotly"
Adelo Vieira (talk | contribs) |
Adelo Vieira (talk | contribs) |
||
Line 539: | Line 539: | ||
* '''Finally, we restart the Nginx process:''' | * '''Finally, we restart the Nginx process:''' | ||
− | :: <syntaxhighlight> | + | :: <syntaxhighlight lang="bash"> |
sudo systemctl restart nginx.service | sudo systemctl restart nginx.service | ||
</syntaxhighlight> | </syntaxhighlight> |
Revision as of 14:41, 11 September 2024
Contents
Examples
- Dash App Gallery: https://dash-gallery.plotly.host/Portal/
- GitHub repository: https://github.com/plotly/dash-sample-apps
- This one is with a sidebar: https://dash-gallery.plotly.host/dash-svm/
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)
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
Installation:
https://anaconda.org/conda-forge/gunicorn
conda install -c conda-forge gunicornor
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:
This tutorial explain well hot to do deploy a Flask Applications with Gunicorn
and Nginx
:
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
.
- Notice that we have included:
- 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.
- Visit your server's domain name or IP address followed by :
- 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
frommyproject.py
- This is the different with respect to a pure Flask application, where you would import
App
instead ofserver
. In Dash, we requireapp.server
, which is in theserver
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()
- Notice that we have import the variable
- 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
.
- For a pure Flask application, would be
- Visit your server's domain name or IP address with :
port
appended to the end in your web browser again.
- Visit your server's domain name or IP address with :
- 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.