diff --git a/eit_dash/callbacks/load_callbacks.py b/eit_dash/callbacks/load_callbacks.py index 53b84d6..05b10dd 100644 --- a/eit_dash/callbacks/load_callbacks.py +++ b/eit_dash/callbacks/load_callbacks.py @@ -91,6 +91,7 @@ def load_selected_data( @callback( Output(ids.DATA_SELECTOR_OPTIONS, "hidden"), Output(ids.CHECKBOX_SIGNALS, "options"), + Output(ids.CHECKBOX_SIGNALS, "value"), Output(ids.FILE_LENGTH_SLIDER, "figure"), Input(ids.NFILES_PLACEHOLDER, "children"), Input(ids.LOAD_CANCEL_BUTTON, "n_clicks"), @@ -109,16 +110,18 @@ def open_data_selector(data, cancel_load, sig, file_type, fig): if trigger == ids.LOAD_CANCEL_BUTTON: data = None file_data = None + options = ticked = [] if not data: # this is needed, because a figure object must be returned for the graph, even if empty figure = go.Figure() - return True, [], figure + return True, [], [], figure if trigger in [ids.NFILES_PLACEHOLDER, ids.CHECKBOX_SIGNALS]: if trigger == ids.CHECKBOX_SIGNALS: options = get_signal_options(file_data) figure = fig + ticked = sig else: path = Path(data) file_data = load_eit_data( @@ -134,18 +137,25 @@ def open_data_selector(data, cancel_load, sig, file_type, fig): continuous_data=list(file_data.continuous_data), clickable_legend=True, ) + ticked = [s["value"] for s in options] ok = [RAW_EIT_LABEL] - if sig: - ok += [options[s]["label"] for s in sig] + if ticked: + ok += [options[s]["label"] for s in ticked] for s in figure["data"]: if s["name"] in ok: - s["visible"] = True + # raw signal visible + if s["name"] == RAW_EIT_LABEL: + s["visible"] = True + else: + # other selected signals are included but toggled off + # (the legend item has to be clicked to make the trace visible) + s["visible"] = "legendonly" else: s["visible"] = False - return False, options, figure + return False, options, ticked, figure @callback( diff --git a/eit_dash/callbacks/preprocessing_callbacks.py b/eit_dash/callbacks/preprocessing_callbacks.py index 11c4e65..79ef8bd 100644 --- a/eit_dash/callbacks/preprocessing_callbacks.py +++ b/eit_dash/callbacks/preprocessing_callbacks.py @@ -212,7 +212,7 @@ def open_periods_modal(open_click, confirm_click) -> bool: @callback( Output(ids.PERIODS_SELECTION_SELECT_DATASET, "children"), Input(ids.PERIODS_METHOD_SELECTOR, "value"), - prevent_initial_call=True, + prevent_initial_call=False, ) def populate_periods_selection_modal(method): """Populate modal body according to the selected method for stable periods selection.""" @@ -222,43 +222,24 @@ def populate_periods_selection_modal(method): signals = data_object.get_all_sequences() options = [{"label": sequence.label, "value": index} for index, sequence in enumerate(signals)] - body = [ - html.H6("Select one dataset"), - dbc.Select( - id=ids.PREPROCESING_DATASET_SELECT, - options=options, - ), - ] + body = ( + [ + html.H6("Select one dataset"), + dbc.Select( + id=ids.PREPROCESING_DATASET_SELECT, + options=options, + value=str(options[0]["value"]), + ), + ] + if options + else [] + ) else: body = [] return body -@callback( - Output(ids.PREPROCESING_SIGNALS_CHECKBOX_ROW, "children"), - Input(ids.PREPROCESING_DATASET_SELECT, "value"), - prevent_initial_call=True, -) -def populate_periods_selection_datasets(dataset): - """Activated when a dataset is selected. Populates signals selection in the manual selection case.""" - if dataset: - options = get_signal_options( - data_object.get_sequence_at(int(dataset)), - show_eit=True, - ) - return [ - html.H6("Select the signals to be displayed"), - dcc.Checklist( - id=ids.PREPROCESING_SIGNALS_CHECKBOX, - inputStyle=styles.CHECKBOX_INPUT, - options=options, - ), - ] - - return [] - - @callback( Output(ids.PERIODS_SELECTION_DIV, "hidden"), Input(ids.PREPROCESING_SIGNALS_CHECKBOX, "value"), @@ -276,6 +257,7 @@ def show_selection_div(signals): [ Output(ids.PREPROCESING_PERIODS_GRAPH, "figure", allow_duplicate=True), Output(ids.PREPROCESING_PERIODS_GRAPH, "style", allow_duplicate=True), + Output(ids.PREPROCESING_SIGNALS_CHECKBOX_ROW, "children"), ], [ Input(ids.PREPROCESING_DATASET_SELECT, "value"), @@ -285,7 +267,7 @@ def show_selection_div(signals): def initialize_figure( dataset, ): - """When the dataset is selected, the figure is initialized.""" + """When the dataset is selected, the figure and the checkbox are initialized.""" # the callback is run also when populating the dataset options. # In this case we don't want to run it if not dataset: @@ -304,10 +286,24 @@ def initialize_figure( if saved_periods := data_object.get_dataset_stable_periods(int(dataset)): current_figure = mark_selected_periods(current_figure, saved_periods) + options = get_signal_options( + data_object.get_sequence_at(int(dataset)), + show_eit=True, + ) + + signals_checkbox = [ + html.H6("Select the signals to be displayed"), + dcc.Checklist( + id=ids.PREPROCESING_SIGNALS_CHECKBOX, + inputStyle=styles.CHECKBOX_INPUT, + options=options, + ), + ] + # THIS IS A TEMPORARY PATCH time.sleep(2) - return current_figure, style + return current_figure, style, signals_checkbox @callback( diff --git a/eit_dash/definitions/element_ids.py b/eit_dash/definitions/element_ids.py index 36955fe..b34e973 100644 --- a/eit_dash/definitions/element_ids.py +++ b/eit_dash/definitions/element_ids.py @@ -1,3 +1,16 @@ +NEXT_PAGE_BUTTON_LOAD = "next-page-button-load" +NEXT_PAGE_LINK_LOAD = "next-page-link-load" +NEXT_PAGE_BUTTON_PREP = "next-page-button-prep" +NEXT_PAGE_LINK_PREP = "next-page-link-prep" +NEXT_PAGE_BUTTON_ANALYZE = "next-page-button-analyze" +NEXT_PAGE_LINK_ANALYZE = "next-page-link-analyze" +PREV_PAGE_BUTTON_LOAD = "next-page-button-load" +PREV_PAGE_LINK_LOAD = "prev-page-link-load" +PREV_PAGE_BUTTON_PREP = "prev-page-button-prep" +PREV_PAGE_LINK_PREP = "prev-page-link-prep" +PREV_PAGE_BUTTON_ANALYZE = "prev-page-button-analyze" +PREV_PAGE_LINK_ANALYZE = "prev-page-link-analyze" + # load page ADD_DATA_BUTTON = "add-data-button" ALERT_LOAD = "alert-load" @@ -14,6 +27,7 @@ DATASET_CONTAINER = "dataset-container" LOAD_CONFIRM_BUTTON = "load-confirm-button" LOAD_CANCEL_BUTTON = "load-cancel-button" +LOAD_RESULTS_TITLE = "load-results-title" PARENT_DIR = "parent-dir" STORED_CWD = "stored-cwd" SELECT_CONFIRM_BUTTON = "select-confirm-button" diff --git a/eit_dash/definitions/layout_styles.py b/eit_dash/definitions/layout_styles.py index cadd320..8e225e9 100644 --- a/eit_dash/definitions/layout_styles.py +++ b/eit_dash/definitions/layout_styles.py @@ -7,6 +7,40 @@ GRAPH = {} INFO_CARD = {"margin-left": 10} LOAD_RESULTS = {"textAlign": "center"} +NEXT_PAGE_BUTTON = { + "position": "fixed", + "bottom": "35px", + "right": "35px", + "display": "flex", + "align-items": "center", + "background": "transparent", + "color": "blue", + "border": "none", + "font-size": "70px", +} +NEXT_PAGE_SECTION = { + "position": "fixed", + "bottom": "7px", + "right": "35px", + "display": "flex", +} +PREV_PAGE_BUTTON = { + "position": "fixed", + "bottom": "35px", + "left": "35px", + "display": "flex", + "align-items": "center", + "background": "transparent", + "color": "blue", + "border": "none", + "font-size": "70px", +} +PREV_PAGE_SECTION = { + "position": "fixed", + "bottom": "7px", + "left": "35px", + "display": "flex", +} PAGES_LINK = {"fontWeight": "bold", "color": "blue"} SUMMARY_ELEMENT = {"textAlign": "center"} SECTION_TITLE = {"margin-top": "50px"} diff --git a/eit_dash/pages/analyze.py b/eit_dash/pages/analyze.py index 9df3284..1928e99 100644 --- a/eit_dash/pages/analyze.py +++ b/eit_dash/pages/analyze.py @@ -33,6 +33,7 @@ html.P(), html.Div( [ + dbc.Row(html.H6("Select a period to view the results")), dbc.Row(dbc.Select(id=ids.ANALYZE_SELECT_PERIOD_VIEW)), dbc.Row( dcc.Graph(id=ids.EELI_RESULTS_GRAPH, style=styles.EMPTY_ELEMENT), @@ -51,5 +52,23 @@ summary, actions, results, + html.Div( + [ + dbc.NavLink( + dbc.Button( + className="fa fa-arrow-circle-left", + id=ids.PREV_PAGE_BUTTON_ANALYZE, + style=styles.PREV_PAGE_BUTTON, + disabled=False, + ), + href="/preprocessing", + id=ids.PREV_PAGE_LINK_ANALYZE, + ), + html.Div( + "PREVIOUS PAGE", + style=styles.PREV_PAGE_SECTION, + ), + ], + ), ], ) diff --git a/eit_dash/pages/load.py b/eit_dash/pages/load.py index f9dcf91..eacdc23 100644 --- a/eit_dash/pages/load.py +++ b/eit_dash/pages/load.py @@ -13,7 +13,7 @@ results = dbc.Col( [ - html.H2("Results", style=styles.COLUMN_TITLE), + html.H2("Results", style=styles.COLUMN_TITLE, id=ids.LOAD_RESULTS_TITLE), html.Div(id=ids.DATASET_CONTAINER, style=styles.LOAD_RESULTS), ], ) @@ -160,5 +160,23 @@ results, placeholder_nfiles, modal_dialog, + # TODO: the following is duplicated in multiple pages. To be refactored + html.Div( + [ + dbc.NavLink( + dbc.Button( + className="fa fa-arrow-circle-right", + id=ids.NEXT_PAGE_BUTTON_LOAD, + style=styles.NEXT_PAGE_BUTTON, + ), + href="/preprocessing", + id=ids.NEXT_PAGE_LINK_LOAD, + ), + html.Div( + "NEXT PAGE", + style=styles.NEXT_PAGE_SECTION, + ), + ], + ), ], ) diff --git a/eit_dash/pages/preprocessing.py b/eit_dash/pages/preprocessing.py index 7539767..d8b4df9 100644 --- a/eit_dash/pages/preprocessing.py +++ b/eit_dash/pages/preprocessing.py @@ -68,7 +68,7 @@ html.P(), dbc.Row( dbc.Button( - "Select stable periods", + "Select periods", id=ids.OPEN_SELECT_PERIODS_BUTTON, disabled=False, ), @@ -176,6 +176,7 @@ dbc.Select( id=ids.PERIODS_METHOD_SELECTOR, options=[{"label": method.name, "value": method.value} for method in PeriodsSelectMethods], + value=str(PeriodsSelectMethods.Manual.value), ), modal_selection_body, ], @@ -336,5 +337,44 @@ modal_synchronization, modal_selection, modal_filtering, + html.Div( + [ + dbc.NavLink( + html.Div( + [ + dbc.Button( + className="fa fa-arrow-circle-right", + id=ids.NEXT_PAGE_BUTTON_PREP, + style=styles.NEXT_PAGE_BUTTON, + ), + ], + ), + href="/analyze", + id=ids.NEXT_PAGE_LINK_PREP, + ), + html.Div( + "NEXT PAGE", + style=styles.NEXT_PAGE_SECTION, + ), + ], + ), + html.Div( + [ + dbc.NavLink( + dbc.Button( + className="fa fa-arrow-circle-left", + id=ids.PREV_PAGE_BUTTON_PREP, + style=styles.PREV_PAGE_BUTTON, + disabled=False, + ), + href="/", + id=ids.PREV_PAGE_LINK_PREP, + ), + html.Div( + "PREVIOUS PAGE", + style=styles.PREV_PAGE_SECTION, + ), + ], + ), ], ) diff --git a/eit_dash/utils/common.py b/eit_dash/utils/common.py index 4183d2e..580ecf4 100644 --- a/eit_dash/utils/common.py +++ b/eit_dash/utils/common.py @@ -3,6 +3,7 @@ from typing import TYPE_CHECKING import dash_bootstrap_components as dbc +import plotly.colors import plotly.graph_objects as go from dash import html @@ -130,8 +131,13 @@ def create_slider_figure( x=dataset.continuous_data[RAW_EIT_LABEL].time, y=dataset.continuous_data[RAW_EIT_LABEL].values, name=RAW_EIT_LABEL, + line={"color": plotly.colors.DEFAULT_PLOTLY_COLORS[0]}, ), ) + figure.update_yaxes( + color=plotly.colors.DEFAULT_PLOTLY_COLORS[0], + title=f"{RAW_EIT_LABEL} {dataset.continuous_data[RAW_EIT_LABEL].unit}", + ) for n, cont_signal in enumerate(continuous_data): if cont_signal != RAW_EIT_LABEL: @@ -140,6 +146,7 @@ def create_slider_figure( x=dataset.continuous_data[cont_signal].time, y=dataset.continuous_data[cont_signal].values, name=cont_signal, + line={"color": plotly.colors.DEFAULT_PLOTLY_COLORS[n + 1]}, opacity=0.5, yaxis=f"y{n + 2}", ), @@ -149,11 +156,12 @@ def create_slider_figure( y_position += 0.1 new_y = { - "title": cont_signal, + "title": f"{cont_signal} {dataset.continuous_data[cont_signal].unit}", "anchor": "free", "overlaying": "y", "side": side, "autoshift": True, + "color": plotly.colors.DEFAULT_PLOTLY_COLORS[n + 1], } # layout parameters for multiple y axis