diff --git a/share/lustrev-plot.py b/share/lustrev-plot.py new file mode 100644 index 0000000000000000000000000000000000000000..72a5bf9651a050b1679b498f52208d9e49bd41e4 --- /dev/null +++ b/share/lustrev-plot.py @@ -0,0 +1,287 @@ +import base64 +import datetime +import io +import argparse + + +import dash +import dash_core_components as dcc +import dash_html_components as html +import dash_table +from dash.dependencies import Input, Output, State + +import plotly.express as px +import plotly.graph_objects as go +from plotly.subplots import make_subplots + +import pandas as pd + + +parser = argparse.ArgumentParser(description='Launch dash app to display tubes.') +parser.add_argument('datacsv', metavar='data.csv', type=str, + help='a csv file with bounds on signals') +args = parser.parse_args() + +# initial load +init_df = pd.read_csv(args.datacsv,index_col=False) +init_df_json=init_df.to_json(date_format='iso', orient='split') +init_types=init_df['type'].unique() +init_typesel_options=[{'label': t, 'value': t} for t in init_types] +init_signals = init_df['varid'].unique() +init_signals_option = [{'label': i, 'value': i} for i in init_signals] + +# TODO: extraire les suffixes de contexte booleens pour construire, pour chaque signal, la liste des signaux de partitionnement + + +app = dash.Dash(__name__) + +app.layout = html.Div([ + dcc.Upload( + id='upload-data', + children=html.Div([ + 'Drag and Drop or ', + html.A('Select Files') + ]), + style={ + 'width': '100%', + 'height': '60px', + 'lineHeight': '60px', + 'borderWidth': '1px', + 'borderStyle': 'dashed', + 'borderRadius': '5px', + 'textAlign': 'center', + 'margin': '10px' + }, + # Allow multiple files to be uploaded + multiple=True + ), + dcc.Dropdown( + id="signal_sel", + options=[], + multi=True, + value = [] + ), + dcc.Checklist( + id="sameplotcheck", + options=[ + {'label': 'Plot on same graph', 'value': 'samegraph'}, + ], + value=[] + ), + dcc.Checklist( + id="typesel", + options=[], + value=[] + ), + dcc.Graph(id="graph"), + #html.Button("Switch Min/Max", id='btn', n_clicks=0), + html.Div(id='selected-data'), + html.Div(id='output-data-upload'), + # Hidden div inside the app that stores the intermediate value + #html.Div(children='Dash: A web application framework for Python.', style={ + # 'textAlign': 'center', + # 'color': colors['text'] + #}), + html.Div(id='global_df', style={'display': 'none'}, + children=init_df_json + ) +]) + +# # @app.callback( +# # Output("graph", "figure"), +# # [Input("btn", "n_clicks")]) +# # def display_graph(n_clicks): +# # if n_clicks % 2 == 0: +# # x, y = 'timestep', ' __lego_anti_windup_real_10_min' +# # else: +# # x, y = 'timestep', ' __lego_anti_windup_real_10_max' + +# # fig = px.line(df, x=x, y=y) +# # return fig + + +def onesigfig(fig, signal_df, name,row,col): + color='rgba(0,250,0,0.4)' + x, y_min, y_max = 'timestep', 'min', 'max' + + fig.add_traces( + go.Scatter( + x = signal_df[x], + y = signal_df[y_min], + line = dict(color='rgba(0,0,0,0)',shape='hv'), + name=None), + rows=row,cols=col) + fig.add_traces( + go.Scatter( + x = signal_df[x], + y = signal_df[y_max], + fill='tonexty', + line = dict(shape='hv'), + name=name, + fillcolor =color), + rows=row,cols=col) + fig.add_traces( + go.Scatter( + x=signal_df[x], + y = signal_df[y_max], + line = dict(color = 'black', width=1,shape='hv'), + name=name+'_max'), + rows=row,cols=col) + fig.add_traces( + go.Scatter( + x=signal_df[x], + y = signal_df[y_min], + mode='lines', + line = dict(color = 'black', width=1,shape='hv'), + name=name+'_min'), + rows=row, + cols=col) +# fig.update_yaxes(type='linear') + return fig + # fig.add_traces(go.Scatter(x = signal_df[x], y = signal_df[y_min], + # line = dict(color='rgba(0,0,0,0)'))) + # fig.add_traces(go.Scatter(x = signal_df[x], y = signal_df[y_max], + # fill='tonexty', + # fillcolor =color)) + # fig.add_traces(go.Scatter(x=signal_df[x], y = signal_df[y_max], + # line = dict(color = 'black', width=1))) + # fig.add_traces(go.Scatter(x=signal_df[x], y = signal_df[y_min], + # line = dict(color = 'black', width=1))) + + #fig = px.line(signal_df, x=x, y=[y_min,y_max],line_shape='hv') + #, fill='tonexty') + # fig.add_scatter(df, x=x, y=y_max, mode='lines') + +def extract_selection(df): + children = html.Div([ + html.Hr(), # horizontal line + dash_table.DataTable( + data=df.to_dict('records'), + columns=[{'name': i, 'id': i} for i in df.columns] + ), + html.Hr(), # horizontal line + ]) + return children + + +# Changing dropdown menu or same/split view updates the figure +@app.callback( + Output("graph", "figure"), + [Input("signal_sel", "value"), + Input("sameplotcheck", "value"), + Input("global_df", 'children') + ]) +def display_graph_signal(selection,checkval,jsonified): + if 'samegraph' in checkval: + plot_on_same_graph = True + else: + plot_on_same_graph = False + color='rgba(0,250,0,0.4)' + x, y_min, y_max = 'timestep', 'min', 'max' + if selection is None or selection == [] or jsonified == None: + return {} + #selection=signals + else: + df=pd.read_json(jsonified, orient='split') + if plot_on_same_graph: + nb_rows=1 + fig = make_subplots(rows=nb_rows, + cols=1, + ) + else: + nb_rows=len(selection) + fig = make_subplots(rows=nb_rows, + cols=1, + subplot_titles=selection) + for idx, sig in enumerate(selection): + signal_df = df.query("varid=='" + sig + "'") + if plot_on_same_graph: + row_id=1 + else: + row_id=idx+1 + onesigfig(fig,signal_df,sig,row_id,1) + fig.update_layout(height=300 * nb_rows) + return fig + +# Changing global_df or choice of types updates dropdown menu +@app.callback(Output("signal_sel", "options"), + Output("signal_sel", "value"), + Output('selected-data', 'children'), + Input("global_df", 'children'), + Input('typesel', 'value') + ) +def update_available_signals(jsonified, typesel): + if jsonified == None: + return [],[] + else: + df=pd.read_json(jsonified, orient='split') + signals_data=df.query('type in ' +str(typesel)) + signals=signals_data['varid'].unique() + signals_options=[{'label': i, 'value': i} for i in signals] + data=extract_selection(signals_data) + return signals_options, signals, data + + +def parse_contents(contents, filename, date): + content_type, content_string = contents.split(',') + decoded = base64.b64decode(content_string) + try: + if 'csv' in filename: + # Assume that the user uploaded a CSV file + #print(decoded) + df = pd.read_csv( + io.StringIO(decoded.decode('utf-8')), + index_col=False + ) + #types=df['type'].unique() + #signals = df['varid'].unique() + + except Exception as e: + print(e) + return None, [], [], html.Div([ + 'There was an error processing this file.' + ]) + + return df, html.Div([ + html.H5(filename), + html.H6(datetime.datetime.fromtimestamp(date)), + + dash_table.DataTable( + data=df.to_dict('records'), + columns=[{'name': i, 'id': i} for i in df.columns] + ), + + html.Hr(), # horizontal line + + # For debugging, display the raw contents provided by the web browser + html.Div('Raw Content'), + html.Pre(contents[0:200] + '...', style={ + 'whiteSpace': 'pre-wrap', + 'wordBreak': 'break-all' + }) + ]) + +# Loading new csv, updates types and global_df +@app.callback(Output("typesel", 'options'), + Output("typesel", 'value'), + Output("global_df", 'children'), + Output('output-data-upload', 'children'), + Input('upload-data', 'contents'), + State('upload-data', 'filename'), + State('upload-data', 'last_modified') + ) +def update_df_and_outputlog(list_of_contents, list_of_names, list_of_dates): + if list_of_contents is not None: + #args= zip(list_of_contents, list_of_names, list_of_dates) + #c,n,d= args[0] + df, children = parse_contents(list_of_contents[0], list_of_names[0], list_of_dates[0]) + # for c, n, d in + + df_json=df.to_json(date_format='iso', orient='split') + types=df['type'].unique() + typesel_options = [{'label': t, 'value': t} for t in types] + return typesel_options, types, df_json, children + else: + return init_typesel_options, init_types, init_df_json, None + +app.run_server(debug=True)