from pathlib import Path
import dash
import dash_bootstrap_components as dbc
import dash_core_components as dcc
import dash_html_components as html
import pandas as pd
import plotly.express as px
import plotly.graph_objects as go
from dash.dependencies import Input, Output, State
from plotly.graph_objs import Layout
from ..generator import Generator
from . import functions
from .utils import get_functions
[docs]_EXTERNAL_STYLESHEETS = ['https://codepen.io/chriddyp/pen/bWLwgP.css']
[docs]def _create_layout(app, dest_folder: 'Path', function_UIs: dict):
"""Prepares the UI layout.
:param app: the current Dash application
:type app: dash.Dash
:param dest_folder: the destination folder for the datasets
:type dest_folder: PurePath
:param function_UIs: dataset generator UIs
:type function_UIs: dict
:return: the updated Dash application
:rtype: dash.Dash
"""
app.layout = html.Div(children=[
dcc.Interval(id='progress-interval', n_intervals=0, interval=750),
# For empty output callbacks
html.Div(id='hidden-div', style={'display': "none"}),
html.Div(children=[
dbc.Toast(
"",
id="dataset-prepare-info-alert",
header="Dataset prepare",
icon="info",
is_open=False,
duration=2000,
style={"position": "fixed", "top": 8,
"right": 8, "width": 480},
),
dbc.Toast(
"",
id="dataset-inspect-info-alert",
header="Dataset inpsect",
icon="success",
is_open=False,
duration=2000,
style={"position": "fixed", "top": 8,
"right": 8, "width": 480},
),
dbc.Toast(
"",
id="dataset-generator-info-alert",
header="Dataset save",
icon="success",
is_open=False,
duration=2000,
style={"position": "fixed", "top": 8,
"right": 8, "width": 480},
),
]),
html.H2(children='Dataset Generator'),
html.Hr(),
dbc.Row([
dbc.Col(
html.H3(children="Output folder"),
width={'size': "auto", 'offset': 1}
),
dbc.Col(dcc.Input(
id='dest-folder',
type="text",
placeholder="destination folder name",
value=dest_folder.name,
), width="auto"),
dbc.Col(
dbc.Button(
'Load files',
id='load-files'
),
width={'size': "auto"}
),
dbc.Col(
dbc.Spinner(
html.H5(
id='dest-folder-load',
children=""
)),
width={'size': "auto"}
),
]),
html.Hr(),
html.H3(children="Generator Parameters"),
html.Hr(),
dbc.Row([
dbc.Col(
html.H5(id='seed-val', children="Seed: "),
width={'size': 3, 'offset': 1}
),
dbc.Col(dcc.Input(
id='seed',
type="number",
placeholder="random seed value",
value=_DEFAULT_SEED,
), width="auto")
], style={'padding-bottom': "1em"}),
dbc.Row([
dbc.Col(
html.H5(id='num-day-val', children="Num. Days: "),
width={'size': 3, 'offset': 1}
),
dbc.Col(dcc.Slider(
id='num-days',
min=1,
max=365,
step=1,
value=7,
marks={
0: {'label': '0'},
7: {'label': '7'},
30: {'label': '30'},
60: {'label': '60'},
90: {'label': '90'},
120: {'label': '120'},
365: {'label': '365'}
},
), width=7)
]),
dbc.Row([
dbc.Col(html.H5(id='num-req-x-day-val', children="Num. Req. x Day: "),
width={'size': 3, 'offset': 1}
),
dbc.Col(dcc.Slider(
id='num-req-x-day',
min=1,
max=100000,
step=1,
value=1000,
marks={
1000: {'label': '1000'},
10000: {'label': '10000'},
20000: {'label': '20000'},
30000: {'label': '30000'},
50000: {'label': '50000'},
100000: {'label': '100000'},
},
), width=7)
]),
html.Hr(),
html.H4(children="Function Selection"),
dbc.Row([
dbc.Col(
dbc.Spinner(
dcc.Dropdown(
id='functions',
options=get_functions(),
value='None'
)),
width={'size': 7, 'offset': 1}
),
dbc.Col(
dbc.Button("Reload functions",
id='reload-functions', color="primary",
),
),
]),
html.Hr(),
html.H4(children="Function Parameters"),
html.Div(id='function-parameters'),
html.Hr(),
dbc.Button("Prepare", id='prepare-dataset',
color="warning", block=True),
dbc.Button("Inpsect", id='inspect-dataset',
color="info", block=True),
dbc.Button("Save", id='save-dataset',
color="success", block=True),
html.Hr(),
dbc.Progress(
id='create-dataset-progress', value=_PROGRESS,
className="mb-3"
),
html.Hr(),
dbc.Spinner(html.Div(id='inspect-output')),
], style={'padding': "1em"})
return app
[docs]def _prepare_callbacks(app, generator, dest_folder, function_UIs: dict):
"""Function to prepare the UI callbacks.
In this function are called also all the personalized UI callbacks present
in the function generator UIs.
:param app: the current Dash application
:type app: dash.Dash
:param generator: the generator object
:type generator: Generator
:param dest_folder: the destination folder for the datasets
:type dest_folder: PurePath
:param function_UIs: the dataset generator UIs
:type function_UIs: dict
:return: the Dash app with the updated callbacks
:rtype: dash.Dash
"""
for elm in function_UIs.values():
elm.callbacks()
@app.callback([Output('dest-folder', 'value'),
Output('dest-folder-load', 'children')],
[Input('load-files', 'n_clicks')],)
def open_folder(n_clicks):
if n_clicks:
generator.open_data(generator.dest_folder.name)
return generator.dest_folder.name, "All data loaded..."
return generator.dest_folder.name, "No data loaded..."
@app.callback(
[Output("functions", "options"), Output("functions", "value")],
[Input("reload-functions", "n_clicks")],
)
def reload_functions(n_clicks):
return get_functions(), ""
@app.callback(
Output('function-parameters', 'children'),
[Input('functions', 'value')],
)
def update_function_ui(value):
if value in function_UIs:
return function_UIs[value].elements()
else:
return "Function has no parameters"
@app.callback(
Output("hidden-div", "children"),
[Input("dest-folder", "value")],
)
def change_dest_folder(new_dest_folder):
generator.dest_folder = Path(
dest_folder).parent.joinpath(new_dest_folder)
return ""
@app.callback(
Output("seed-val", "children"),
[Input("seed", "value")],
)
def change_seed(value):
generator.seed = value
return f"Seed: {value}"
@app.callback(
Output('num-day-val', 'children'),
[Input('num-days', 'value')])
def update_num_days(value):
generator.clean()
generator.num_days = value
return f"Num. Days: {value}"
@app.callback(
Output('num-req-x-day-val', 'children'),
[Input('num-req-x-day', 'value')])
def update_num_req_x_day(value):
generator.num_req_x_day = value
return f"Num. Req. x Day: {value}"
@app.callback(
[Output('dataset-prepare-info-alert', 'is_open'),
Output('dataset-prepare-info-alert', 'icon'),
Output('dataset-prepare-info-alert', 'children')],
[Input('prepare-dataset', 'n_clicks')],
[State("functions", "value")]
)
def prepare_dataset(n_clicks, selected_function):
if n_clicks:
global _PROGRESS
_PROGRESS = 0
try:
fun_kwargs = function_UIs[selected_function].to_dict()
except KeyError:
return True, "danger", "Impossible to get function parameters..."
if selected_function and fun_kwargs:
generator.clean()
for day in generator.prepare(
selected_function, fun_kwargs
):
_PROGRESS = day
return True, "success", "Done"
else:
return True, "warning", "Nothing to do..."
else:
return False, "primary", "No message from prepare dataset..."
@app.callback(
[Output('inspect-output', 'children'),
Output('dataset-inspect-info-alert', 'is_open'),
Output('dataset-inspect-info-alert', 'children')],
[Input('inspect-dataset', 'n_clicks')]
)
def inspect_dataset(n_clicks):
if n_clicks:
(df, file_frequencies, all_day_file_size, file_sizes,
num_files, num_req) = generator.df_stats
layout = Layout(
paper_bgcolor='rgb(255,255,255)',
plot_bgcolor='rgb(255,255,255)',
yaxis={'gridcolor': 'black'},
xaxis={'gridcolor': 'black'},
)
daily_stats = go.Figure(data=[
go.Bar(name='Files', x=num_files.day,
y=num_files.numFiles),
go.Bar(name='Requests', x=num_req.day,
y=num_req.numReq),
], layout=layout)
daily_stats.update_layout(title="# files and requests x day")
day_file_size = px.bar(
all_day_file_size, x="day",
y="Size", title="Day size"
)
file_freq_fig = px.bar(
file_frequencies, x="Filename",
y="# requests", title="File requests"
)
file_size_fig = px.bar(
file_sizes, x="Filename",
y="Size", title="File sizes"
)
size_hist_fig = px.histogram(
df, x="Size", title="Size distribution"
)
size_scatter_fit = px.scatter(
df, y='Size', size='Size',
title="Sizes during days"
)
filename_scatter_fig = px.scatter(
df, y='Filename', color="Filename",
title="Files during days"
)
file_freq_fig.update_layout(layout)
file_size_fig.update_layout(layout)
size_hist_fig.update_layout(layout)
size_scatter_fit.update_layout(layout)
filename_scatter_fig.update_layout(layout)
return [
dcc.Graph(figure=daily_stats),
dcc.Graph(figure=day_file_size),
dcc.Graph(figure=file_freq_fig),
dcc.Graph(figure=file_size_fig),
dcc.Graph(figure=size_hist_fig),
dcc.Graph(figure=size_scatter_fit),
dcc.Graph(figure=filename_scatter_fig),
], True, "Inspection done. Result plots are ready!"
return "", False, ""
@app.callback(
[Output('dataset-generator-info-alert', 'is_open'),
Output('dataset-generator-info-alert', 'children')],
[Input('save-dataset', 'n_clicks')]
)
def save_dataset(n_clicks):
if n_clicks:
global _PROGRESS
_PROGRESS = 0
for day in generator.save():
_PROGRESS = day
return True, "Done! Dataset saved in the output folder..."
else:
return False, "No message from save dataset..."
@app.callback(
Output("create-dataset-progress", "value"),
[Input("progress-interval", "n_intervals")],
)
def update_progress(n):
global _PROGRESS
return _PROGRESS
return app
[docs]def start_app(debug: bool = True, dest_folder: 'Path' = Path(__file__).parent):
"""Start the generator UI app.
:param debug: if start in debug mode or not, defaults to True
:type debug: bool, optional
:param dest_folder: the destination folder for the dataset generator, defaults to Path(__file__).parent
:type dest_folder: PurePath, optional
"""
generator = Generator(
dest_folder=dest_folder,
)
app = dash.Dash(__name__, external_stylesheets=[
_EXTERNAL_STYLESHEETS, dbc.themes.BOOTSTRAP
], suppress_callback_exceptions=True)
function_UIs = {}
for elm in dir(functions):
cur_elm = getattr(functions, elm)
if type(cur_elm) == type and \
cur_elm is not functions.FunctionUI and \
issubclass(cur_elm, functions.FunctionUI):
function_UIs[elm] = cur_elm(app)
app = _create_layout(app, dest_folder, function_UIs)
app = _prepare_callbacks(app, generator, dest_folder, function_UIs)
app.run_server(debug=debug)