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)