Dash Plotly Bootstrap
Pada kesempatan kali ini, saya akan menjelaskan bagaimana styling menggunakan bootstrap css framework pada aplikasi dash yang akan kita buat.
Untuk kesempatan kali ini, saya akan menjelaskan pula mengenai struktur aplikasi dengan konsep mvc
model, view, controller, untuk memudahkan kita dalam memanage dan memaintain aplikasi kita untuk kedepannya.
Prepared Installation Packages
Dilangkah ini kita akan menginstall package/library yang akan kita butuhkan, utamakan menggunakan virtual-env dan diutamakan untuk mengikuti tutorial dash ini dari awal untuk memudahkan management library yang digunakan, silahkan buka link berikut untuk memulai dari awal Dash Plotly solusi Tim Data untuk visualisasi
Dash Bootstrap Components
Pertama-tama kita akan menginstall dash-bootstrap-components yang merupakan sebuah library bootstrap component untuk dash plotly. Dengan langkah sebagai berikut,
pip install dash-bootstrap-components
Flask_caching dan python-dotenv
Karena backend dari Dash terdiri dari flask, kita juga bisa menggunakan library yang diperuntukan kepada flask, misalnya flask-caching yang digunakan untuk menyimpan cache agar aplikasi kita dapat meningkat performancenya. Langkah pertama yaitu dengan menginstall flask caching
dengan menggunkaan perintah berikut :
pip install flask_caching
Untuk memudahkan juga dengan credential variabel, kita juga dapat menginstall [python-dotenv](https:/python-dotenv /github.com/theskumar/python-dotenv) dengan perintah berikut :
pip install python-dotenv
Structure the Apps
Untuk memudahkan dalam management aplikasi dan tentu juga pada aplikasi web umumnya memiliki banyak komponen dan banyak halaman, tentu struktur dari projek akan mempengaruhi kemudahan dalam me-manage aplikasi. Untuk itu, saya merekomendasikan struktur yang nanti kita akan buat pada tutorial ini.
Assets Folder
Layaknya folder asset pada umumnya, folder ini dapat berisikan file js, css ataupun gambar yang akan kita embed langsung kedalam project dash web. Untuk lebih jelasnya bisa dilihat disini Adding CSS & JS and Overriding the Page-Load Template
Cache Directory
Folder ini terbentuk secara otomatis oleh library Flask_caching karena callback pada Dash bersifat fungsional sehingga kita dapat menset memoization cache pada callback tertentu, tujuannya agar hasil nilai kembali dari sebuah fungsi disimpan sehingga kita dapat memanggilnya jika dibutuhkan.
Untuk konfigurasinya terdapat pada file app.py
dengan perintah berikut,
cache = Cache(app.server,
config={
'CACHE_TYPE': 'filesystem',
'CACHE_DIR': 'cache-directory',
'CACHE_THRESHOLD': 5,
})
dan memanggilnya dalam bentuk decorator pada fungsi tertentu, dalam contoh ini saya memanggilnya pada saat kita melakukan pemanggilan data dalam model seperti dibawah ini :
@cache.memoize(timeout=TIMEOUT)
def query_data():
# This could be an expensive data querying step
data_skripsi = pd.read_csv(DATA_PATH.joinpath('judul_skripsi.csv'), sep=';')
return data_skripsi.to_json(date_format='iso', orient='split')
Atau mungkin saat kita melakukan query :
@cache.memoize(timeout=TIMEOUT)
def query_get_all(dbTable=None):
preparedSQL = "SELECT * FROM {}".format(dbTable)
result_data = query_select(preparedSQL, **DB_CONFIG)
return result_data
Components
Pada folder ini, kita dapat mendefinisikan beberapa komponen untuk membantu dalam reuse atau menggunakan kembali komponen yang sama apabila dibutuhkan, dalam hal ini semisal header, dimana header pada aplikasi dapat dipastikan sama antar pages/layout pada aplikasi keseluruhan. Misalnya kita mendifinisikan header :
import dash_html_components as html
import dash_core_components as dcc
import dash_bootstrap_components as dbc
def Header():
return html.Div([get_header(), html.Br([])])
def get_header():
header = html.Div(
[
dbc.NavbarSimple(
children=[
dbc.ButtonGroup(
# Use row and col to control vertical alignment of logo / brand
[
dbc.Button(
"Dash Bootstrap",
href="/dashboard/dash-bootstrap-learn",
className="btn-link",
color="primary",
size="lg",
outline=True,
style={"color": "#fff"},
),
],),
],
brand="Dash Bootstrap Learn",
brand_style={
"font-size": "2.1rem",
"font-weight": "bold",
},
color="#05445E",
dark=True,
fluid=True,
),
],)
return header
config
Pada direktori ini, kita mendefinisikan konfigurasi yang akan kita gunakan dalam aplikasi kita, misalnya konfigurasi dot env ataupun konfigurasi server bahkan konfigurasi database sekalipun.
Dalam konteks tutorial ini, saya mendefinisikan dot env dan juga konfigurasi server yang digunakan seperti berikut :
# config.py
import os
from os.path import join, dirname
from dotenv import load_dotenv
dotenv_path = os.getenv("ENVIRONMENT_FILE")
load_dotenv(dotenv_path=dotenv_path, override=True)
APP_HOST = os.environ.get("HOST")
APP_PORT = os.environ.get("PORT")
APP_DEBUG = os.environ.get("DEBUG")
DEV_TOOLS_PROPS_CHECK = os.environ.get('DEV_TOOLS_PROPS_CHECK')
APP_THREADED = os.environ.get("THREADED")
# DB_HOST = os.environ.get("DB_HOST")
# DB_DATABASE = os.environ.get("DB_DATABASE")
# DB_USER = os.environ.get("DB_USER")
# DB_PASSWORD = os.environ.get("DB_PASSWORD")
layout
Folder Layout berisikan main base design aplikasi yang akan selalu di render, umumnya terdapat beberapa reuse component seperti header, sidebar, footer dsb. Untuk code pada layout sebagai berikut :
import dash_html_components as html
import dash_core_components as dcc
from components.header import Header
content = html.Div(id="page-content")
layout = html.Div([dcc.Location(id="url"), html.Div([Header()]), content])
Pages Folder
Pada aplikasi web yang kompleks, tentu akan terdiri dari banyak pages, dan pada setiap pages, kita membuat folder-folder yang dipisahkan menjadi 3 file, yaitu layout file
, callback file
, model file
, dimana konfigurasi seperti ini bisa disebut sebagai MVC Architectural pattern
. Pada contoh disini saya menggunakan nama bootstrap apps
dimana terdapat 3 file :
- bootstrap_layout.py
file ini berisikan base layout untuk page bootstrap
import dash_core_components as dcc
import dash_html_components as html
from dash_html_components.H4 import H4
from app import app
from pages.bootstrap.bootstrap_model import query_dataframe
import dash_bootstrap_components as dbc
layout = html.Div([
# Start Container
dbc.Container(
[
#
# html.Div(html.Nav(html.H4("hello"))),
html.Div([ # start Row
dbc.Row(
dbc.Col(
html.Div(
[
dbc.Card([
dbc.CardHeader(
html.H4("Simple Graph with Bootstrap"),
className="bg-info",
style={
"text-align": "center",
"color": "white"
}),
dbc.CardBody([
dcc.Graph(
id='graph-with-slider',
config={"displayModeBar": False},
),
]),
dbc.CardFooter(
dcc.Slider(
id='year-slider',
min=query_dataframe()
['Tahun_Lulus'].min(),
max=query_dataframe()
['Tahun_Lulus'].max(),
value=query_dataframe()
['Tahun_Lulus'].min(),
marks={
str(year): str(year)
for year in query_dataframe()
['Tahun_Lulus'].unique()
},
step=None),)
]),
],
style={
"margin-bottom": "35px",
},
),
width={
"size": 6,
"offset": 3
},
),
# End Row
),
# Start Tag Div
html.Div(
[
# Start Row
dbc.Row([
dbc.Col([
dbc.Spinner(
children=html.Div(id='table-data-skripsi'),
color="success",
size="lg",
),
],
md=12,
lg=12,
xl=12,
sm=12,
xs=12,
className="col"),
],
justify="between"),
# End Row
],
style={
"margin-bottom": "35px",
},
),
# End Tag Div
]),
],
# Start Container
fluid=True,
),
])
Seperti yang ditampilkan pada code diatas, component-component pada bootstrap dapat kita gunakan setelah kita mengimport library dash-component-bootstrap
dengan memanggil nama componentnya, semisal
dbc.Row(
[
dbc.Col(
[
html.H1("Hello")
]
),
],
)
code diatas, akan membuat grid pada dash-bootstrap-component. Untuk lebih jelasnya dapat melihat dokumentasi dash-bootstrap-component
- bootstrap_callback.py
File ini berisikan callback yang akan memproses dan mengupdate pages.
from dash.dependencies import Input, Output
from app import app
import dash_bootstrap_components as dbc
import plotly.graph_objs as go
import dash_html_components as html
from pages.bootstrap.bootstrap_model import query_dataframe
@app.callback(Output('graph-with-slider', 'figure'),
Input('year-slider', 'value'))
def update_figure(selected_year):
skripsi_data = query_dataframe()
filtered_df = skripsi_data[skripsi_data['Tahun_Lulus'] == selected_year]
filtered_df = filtered_df.groupby(
'Kategori')["Kategori"].count().reset_index(name='Total')
graph = {
"data": [
go.Bar(
x=filtered_df['Kategori'],
y=filtered_df['Total'],
text=filtered_df['Total'],
textposition='inside',
textfont=dict(size=14,),
marker={
"color": "#75E6DA",
"line": {
"color": "rgb(255, 255, 255)",
"width": 2,
},
},
),
],
"layout":
go.Layout(
autosize=True,
bargap=0.35,
font={
"family": "Raleway",
},
hovermode="closest",
legend={
"x": -0.0228945952895,
"y": -0.189563896463,
"orientation": "h",
"yanchor": "top",
},
margin={
"r": 0,
"t": 20,
"b": 10,
"l": 10,
},
showlegend=True,
xaxis={
# "autorange": True,
"fixedrange": True,
"showline": True,
"title": "Gender",
"type": "category",
"automargin": True,
},
yaxis={
"showgrid": True,
"showline": True,
"title": "Grand Total",
"automargin": True,
"fixedrange": True,
},
),
}
return graph
@app.callback(Output('table-data-skripsi', 'children'),
Input('year-slider', 'value'))
def update_table(selected_year):
skripsi_data = query_dataframe()
filtered_df = skripsi_data[skripsi_data['Tahun_Lulus'] == selected_year]
filtered_df.columns = ['Tahun Lulus', 'Judul', 'Kategori']
top5 = filtered_df.sample(n=10)
filtered_df = filtered_df.groupby(
'Kategori')["Kategori"].count().reset_index(name='Total')
summaryRes = [
dbc.Card([
dbc.CardHeader(html.H4("10 Data Sample"),
className="bg-info",
style={
"text-align": "center",
"color": "white"
}),
dbc.CardBody([
html.H5(className="card-title font-size-custom"),
dbc.Row(
dbc.Col(dbc.Table.from_dataframe(top5,
striped=True,
bordered=True,
hover=True,
className="thead-dark"),
width={
"size": 10,
"offset": 1,
}),
align="center",
),
]),
],)
]
return summaryRes
- bootstrap_model.py
file ini berisikan bagaimana cara kita untuk mendapatkan data, bisa dengan membaca file csv misalnya, atau bahkan query kedalam database. Sebagai contoh saya membuat fungsi untuk membaca file csv
import pandas as pd
from app import cache
from utils.constants import TIMEOUT
import pathlib
# get relative data folder
PATH = pathlib.Path(__file__).parent
DATA_PATH = PATH.joinpath("../../data").resolve()
@cache.memoize(timeout=TIMEOUT)
def query_data():
# This could be an expensive data querying step
data_skripsi = pd.read_csv(DATA_PATH.joinpath('judul_skripsi.csv'), sep=';')
return data_skripsi.to_json(date_format='iso', orient='split')
def query_dataframe():
return pd.read_json(query_data(), orient='split')
- Routing.py
Tentu dalam sebuah aplikasi yang besar dan terdiri dari banyak pages, kita akan membutuhkan routing ke setiap pages-pages tersebut, sehingga kita membuat file routing yang berisikan sebagai berikut :
import dash_bootstrap_components as dbc
import dash_html_components as html
from dash.dependencies import Input, Output
from app import app
from pages.bootstrap import bootstrap_layout
@app.callback(Output('page-content', 'children'), Input('url', 'pathname'))
def display_page(pathname):
if pathname == '/dashboard/dash-bootstrap-learn':
return bootstrap_layout.layout
else:
return bootstrap_layout.layout
- app.py dan index.py
Secara default, instance dari Dash didefinisikan pada file app.py
dan entry point dimulai dari index.py
. Pemisahan ini bertujuan untuk menghindari circular import
file yang berisi definisi callback memerlukan akses ke instance aplikasi Dash namun jika ini diimpor dari index.py , pemanggilan awal index.py pada akhirnya akan mengharuskan me,amggil dirinya sendiri kembali, sehingga akan terjadi circular import
.
Contoh app.py
:
import dash
import dash_bootstrap_components as dbc
from flask_caching import Cache
# from utils.external_assets import FONT_AWSOME, CUSTOM_STYLE
from layout.layout import layout
import flask
server = flask.Flask(__name__) # define flask app.server
FONT_AWESOME = (
"https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.7.0/css/font-awesome.min.css"
)
app = dash.Dash(
__name__,
server=server,
suppress_callback_exceptions=True,
meta_tags=[{
'name': 'viewport',
'content': 'width=device-width, initial-scale=1'
}],
external_stylesheets=[dbc.themes.MINTY, FONT_AWESOME],
)
app.title = "Dash Bootstrap"
cache = Cache(app.server,
config={
'CACHE_TYPE': 'filesystem',
'CACHE_DIR': 'cache-directory',
'CACHE_THRESHOLD': 5,
})
app.layout = layout
server = app.server
pada file ini, kita mendifinisikan beberapa konfigurasi seperti external stylesheet
yang digunakan untuk memanggil bootstrap dan juga fontawesome. Sedangkan untuk file index.py
berisikan sebagai berikut :
from logging import debug
from threading import Thread
import dash_core_components as dcc
import dash_html_components as html
from dash.dependencies import Input, Output
from routes import display_page
from app import app, server
from pages.bootstrap.bootstrap_callbacks import update_figure, update_table
from config.config import APP_DEBUG, APP_HOST, APP_PORT, DEV_TOOLS_PROPS_CHECK, APP_THREADED
if __name__ == '__main__':
# print(
# f' host={APP_HOST}\n port={APP_PORT}\n debug={APP_DEBUG}\n dev_tools_props_check={DEV_TOOLS_PROPS_CHECK}\n Threaded={APP_THREADED}'
# )
app.run_server(
host=APP_HOST,
port=APP_PORT,
debug=APP_DEBUG,
dev_tools_props_check=DEV_TOOLS_PROPS_CHECK,
threaded=APP_THREADED,
dev_tools_ui=True,
)
Seperti dilangkah awal, kita menggunakan file .env
yang kita definisikan pada file config.py
pada folder config
yang kita import disini. Selanjutnya juga, Dan kunci penting pada file ini adalah, kita memanggil semua callback yang ada dalam page disini, untuk memberitahu kepada dash bahwa ada callback yang harus dipanggil. Karena pada dasarnya, callback didefinisikan pada app.py
yang sebenernya tidak ada pada file tersebut, sehingga dash akan memberitahu exception error. Hal ini dapat dicegah denan menambahkan suppress_callback_exception=True
pada file app.py
. Dan untuk menjalankan aplikasi kita dapat menggunakan perintah :
python index.py
Dan voilaaa.
Untuk tutorial selanjutnya, saya akan membahas bagaimana men-dockerize aplikasi dash dan mungkin juga sedikit membahas bagaimana kita membuat model machine learning pada aplikasi dash.
Screenshoot
Homepage graph
Homepage Table
Apabila ada pertanyaan jangan sungkan untuk bertanya di kolom komentar, Stay tune guys …
Untuk source code dapat diunduh pada repository github saya dash learn source code