diff --git a/CHANGELOG.md b/CHANGELOG.md index d7d5ee6a7..1f3132fa9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [UNRELEASED] - YYYY-MM-DD ### Changed - [#586](https://github.com/equinor/webviz-subsurface/pull/586) - Added phase ratio vs pressure and density vs pressure plots. Added unit and density functions to PVT library. Refactored code and added checklist for plots to be viewed in PVT plot plugin. Improved the layout. +- [#599](https://github.com/equinor/webviz-subsurface/pull/599) - Fixed an issue in ParameterAnalysis where the plugin did not initialize without FIELD vectors ### Fixed - [#602](https://github.com/equinor/webviz-subsurface/pull/602) - Prevent calculation of data for download at initialisation of ReservoirSimulationTimeSeries. diff --git a/webviz_subsurface/plugins/_parameter_analysis/controllers/parameter_qc_controller.py b/webviz_subsurface/plugins/_parameter_analysis/controllers/parameter_qc_controller.py index ba2a6dfd9..10f187688 100644 --- a/webviz_subsurface/plugins/_parameter_analysis/controllers/parameter_qc_controller.py +++ b/webviz_subsurface/plugins/_parameter_analysis/controllers/parameter_qc_controller.py @@ -1,18 +1,25 @@ +from typing import Callable + +import dash from dash.dependencies import Input, Output, State import dash_table import webviz_core_components as wcc +from ..models import ParametersModel + -def parameter_qc_controller(parent, app): +def parameter_qc_controller( + app: dash.Dash, get_uuid: Callable, parametermodel: ParametersModel +): @app.callback( - Output(parent.uuid("property-qc-wrapper"), "children"), - Input({"id": parent.uuid("ensemble-selector"), "tab": "qc"}, "value"), - Input({"id": parent.uuid("delta-ensemble-selector"), "tab": "qc"}, "value"), + Output(get_uuid("property-qc-wrapper"), "children"), + Input({"id": get_uuid("ensemble-selector"), "tab": "qc"}, "value"), + Input({"id": get_uuid("delta-ensemble-selector"), "tab": "qc"}, "value"), Input( - {"id": parent.uuid("filter-parameter"), "tab": "qc"}, + {"id": get_uuid("filter-parameter"), "tab": "qc"}, "value", ), - Input(parent.uuid("property-qc-plot-type"), "value"), + Input(get_uuid("property-qc-plot-type"), "value"), ) def _update_bars(ensemble, delta_ensemble, parameters, plot_type): """Callback to switch visualization between table and distribution plots""" @@ -20,7 +27,7 @@ def _update_bars(ensemble, delta_ensemble, parameters, plot_type): ensembles = [ensemble, delta_ensemble] if plot_type == "table": - columns, dframe = parent.pmodel.make_statistics_table( + columns, dframe = parametermodel.make_statistics_table( ensembles=ensembles, parameters=parameters ) return dash_table.DataTable( @@ -35,10 +42,10 @@ def _update_bars(ensemble, delta_ensemble, parameters, plot_type): filter_action="native", ) return wcc.Graph( - id=parent.uuid("property-qc-graph"), + id=get_uuid("property-qc-graph"), config={"displayModeBar": False}, style={"height": "75vh"}, - figure=parent.pmodel.make_grouped_plot( + figure=parametermodel.make_grouped_plot( ensembles=ensembles, parameters=parameters, plot_type=plot_type, @@ -47,28 +54,28 @@ def _update_bars(ensemble, delta_ensemble, parameters, plot_type): @app.callback( Output( - {"id": parent.uuid("filter-parameter"), "tab": "qc"}, + {"id": get_uuid("filter-parameter"), "tab": "qc"}, "options", ), Output( - {"id": parent.uuid("filter-parameter"), "tab": "qc"}, + {"id": get_uuid("filter-parameter"), "tab": "qc"}, "value", ), - Input(parent.uuid("delta-sort"), "value"), - Input({"id": parent.uuid("ensemble-selector"), "tab": "qc"}, "value"), - Input({"id": parent.uuid("delta-ensemble-selector"), "tab": "qc"}, "value"), + Input(get_uuid("delta-sort"), "value"), + Input({"id": get_uuid("ensemble-selector"), "tab": "qc"}, "value"), + Input({"id": get_uuid("delta-ensemble-selector"), "tab": "qc"}, "value"), State( - {"id": parent.uuid("filter-parameter"), "tab": "qc"}, + {"id": get_uuid("filter-parameter"), "tab": "qc"}, "value", ), ) def _update_parameters(sortby, ensemble, delta_ensemble, current_params): """Callback to sort parameters based on selection""" - parent.pmodel.sort_parameters( + parametermodel.sort_parameters( ensemble=ensemble, delta_ensemble=delta_ensemble, sortby=sortby, ) return [ - {"label": i, "value": i} for i in parent.pmodel.parameters + {"label": i, "value": i} for i in parametermodel.parameters ], current_params diff --git a/webviz_subsurface/plugins/_parameter_analysis/controllers/parameter_response_controller.py b/webviz_subsurface/plugins/_parameter_analysis/controllers/parameter_response_controller.py index ae5e6e45a..a17dca749 100644 --- a/webviz_subsurface/plugins/_parameter_analysis/controllers/parameter_response_controller.py +++ b/webviz_subsurface/plugins/_parameter_analysis/controllers/parameter_response_controller.py @@ -1,4 +1,4 @@ -from typing import Tuple, Union +from typing import Tuple, Union, Callable from itertools import chain import numpy as np @@ -13,33 +13,38 @@ from ..utils.colors import find_intermediate_color from ..figures.correlation_figure import CorrelationFigure from ..utils.colors import hex_to_rgb, rgb_to_hex - +from ..models import SimulationTimeSeriesModel, ParametersModel # pylint: disable=too-many-statements, -def parameter_response_controller(parent, app): +def parameter_response_controller( + app: dash.Dash, + get_uuid: Callable, + vectormodel: SimulationTimeSeriesModel, + parametermodel: ParametersModel, +): @app.callback( - Output(parent.uuid("vector-vs-time-graph"), "figure"), - Output(parent.uuid("vector-vs-param-scatter"), "figure"), - Output(parent.uuid("vector-corr-graph"), "figure"), - Output(parent.uuid("param-corr-graph"), "figure"), - Input({"id": parent.uuid("ensemble-selector"), "tab": "response"}, "value"), - Input(parent.uuid("vector-select"), "children"), - Input({"id": parent.uuid("parameter-select"), "tab": "response"}, "value"), - Input(parent.uuid("date-selected"), "children"), - Input({"id": parent.uuid("vtype-filter"), "tab": "response"}, "value"), + Output(get_uuid("vector-vs-time-graph"), "figure"), + Output(get_uuid("vector-vs-param-scatter"), "figure"), + Output(get_uuid("vector-corr-graph"), "figure"), + Output(get_uuid("param-corr-graph"), "figure"), + Input({"id": get_uuid("ensemble-selector"), "tab": "response"}, "value"), + Input(get_uuid("vector-select"), "children"), + Input({"id": get_uuid("parameter-select"), "tab": "response"}, "value"), + Input(get_uuid("date-selected"), "children"), + Input({"id": get_uuid("vtype-filter"), "tab": "response"}, "value"), Input( { - "id": parent.uuid("vitem-filter"), + "id": get_uuid("vitem-filter"), "tab": "response", "vtype": ALL, }, "value", ), - Input({"id": parent.uuid("plot-options"), "tab": "response"}, "value"), - State(parent.uuid("vector-vs-time-graph"), "figure"), - State(parent.uuid("param-corr-graph"), "figure"), - State(parent.uuid("vector-corr-graph"), "figure"), - State(parent.uuid("vector-vs-param-scatter"), "figure"), + Input({"id": get_uuid("plot-options"), "tab": "response"}, "value"), + State(get_uuid("vector-vs-time-graph"), "figure"), + State(get_uuid("param-corr-graph"), "figure"), + State(get_uuid("vector-corr-graph"), "figure"), + State(get_uuid("vector-vs-param-scatter"), "figure"), ) # pylint: disable=too-many-locals, too-many-arguments def _update_graphs( @@ -70,34 +75,38 @@ def _update_graphs( initial_run = timeseries_fig is None color = options["color"] if options["color"] is not None else "#007079" - daterange = parent.vmodel.daterange_for_plot(vector=vector) + daterange = vectormodel.daterange_for_plot(vector=vector) # Make timeseries graph - if relevant_ctx(parent, ctx, operation="timeseries_fig") or initial_run: + if relevant_ctx(get_uuid, ctx, operation="timeseries_fig") or initial_run: timeseries_fig = update_timeseries_graph( - parent.vmodel, + vectormodel, ensemble, vector, xaxisrange=[min(daterange[0], date), max(daterange[1], date)], real_filter=None, ) - if parent.uuid("plot-options") not in ctx or initial_run: + if get_uuid("plot-options") not in ctx or initial_run: vectors_filtered = filter_vectors( - parent, vector_type_filter, vector_item_filters + vectormodel, vector_type_filter, vector_item_filters ) if vector not in vectors_filtered: vectors_filtered.append(vector) merged_df = merge_parameter_and_vector_df( - parent, ensemble, vectors_filtered, date + vectormodel=vectormodel, + parametermodel=parametermodel, + ensemble=ensemble, + vectors=vectors_filtered, + date=date, ) # Make correlation figure for vector if options["autocompute_corr"] and ( - relevant_ctx(parent, ctx, operation="vector_correlation") or initial_run + relevant_ctx(get_uuid, ctx, operation="vector_correlation") or initial_run ): corr_v_fig = make_correlation_figure( - merged_df, response=vector, corrwith=parent.pmodel.parameters + merged_df, response=vector, corrwith=parametermodel.parameters ).figure # Get clicked parameter correlation bar or largest bar initially @@ -108,7 +117,8 @@ def _update_graphs( # Make correlation figure for parameter if options["autocompute_corr"] and ( - relevant_ctx(parent, ctx, operation="parameter_correlation") or initial_run + relevant_ctx(get_uuid, ctx, operation="parameter_correlation") + or initial_run ): corr_p_fig = make_correlation_figure( merged_df, response=parameter, corrwith=vectors_filtered @@ -117,16 +127,16 @@ def _update_graphs( corr_p_fig = color_corr_bars(corr_p_fig, vector, color, options["opacity"]) # Create scatter plot of vector vs parameter - if relevant_ctx(parent, ctx, operation="scatter") or initial_run: + if relevant_ctx(get_uuid, ctx, operation="scatter") or initial_run: scatter_fig = update_scatter_graph(merged_df, vector, parameter, color) scatter_fig = scatter_fig_color_update(scatter_fig, color, options["opacity"]) # Order realizations sorted on value of parameter and color traces - df_value_norm = parent.pmodel.get_real_and_value_df( + df_value_norm = parametermodel.get_real_and_value_df( ensemble, parameter=parameter, normalize=True ) - if relevant_ctx(parent, ctx, operation="color_timeseries_fig") or initial_run: + if relevant_ctx(get_uuid, ctx, operation="color_timeseries_fig") or initial_run: timeseries_fig = color_timeseries_graph( timeseries_fig, ensemble, parameter, vector, df_value_norm ) @@ -135,7 +145,7 @@ def _update_graphs( timeseries_fig = add_date_line(timeseries_fig, date, options["show_dateline"]) # Ensure xaxis covers selected date - if parent.uuid("date-selected") in ctx: + if get_uuid("date-selected") in ctx: timeseries_fig["layout"]["xaxis"].update( range=[min(daterange[0], date), max(daterange[1], date)] ) @@ -143,12 +153,12 @@ def _update_graphs( return timeseries_fig, scatter_fig, corr_v_fig, corr_p_fig @app.callback( - Output(parent.uuid("date-slider"), "value"), - Input(parent.uuid("vector-vs-time-graph"), "clickData"), + Output(get_uuid("date-slider"), "value"), + Input(get_uuid("vector-vs-time-graph"), "clickData"), ) def _update_date_from_clickdata(timeseries_clickdata: Union[None, dict]): """Update date-slider from clickdata""" - dates = parent.vmodel.dates + dates = vectormodel.dates return ( dates.index(timeseries_clickdata.get("points", [{}])[0]["x"]) if timeseries_clickdata is not None @@ -156,18 +166,18 @@ def _update_date_from_clickdata(timeseries_clickdata: Union[None, dict]): ) @app.callback( - Output(parent.uuid("date-selected"), "children"), - Input(parent.uuid("date-slider"), "value"), + Output(get_uuid("date-selected"), "children"), + Input(get_uuid("date-slider"), "value"), ) def _update_date(dateidx: int): """Update selected date from date-slider""" - return parent.vmodel.dates[dateidx] + return vectormodel.dates[dateidx] @app.callback( - Output({"id": parent.uuid("plot-options"), "tab": "response"}, "value"), - Input({"id": parent.uuid("checkbox-options"), "tab": "response"}, "value"), - Input({"id": parent.uuid("color-selector"), "tab": "response"}, "clickData"), - Input({"id": parent.uuid("opacity-selector"), "tab": "response"}, "value"), + Output({"id": get_uuid("plot-options"), "tab": "response"}, "value"), + Input({"id": get_uuid("checkbox-options"), "tab": "response"}, "value"), + Input({"id": get_uuid("color-selector"), "tab": "response"}, "clickData"), + Input({"id": get_uuid("opacity-selector"), "tab": "response"}, "value"), ) def _update_plot_options( checkbox_options: list, @@ -190,24 +200,24 @@ def _update_plot_options( ) @app.callback( - Output(parent.uuid("vector-select"), "children"), - Input(parent.uuid("vshort-select"), "value"), - Input({"id": parent.uuid("vitem-select"), "shortname": ALL}, "value"), + Output(get_uuid("vector-select"), "children"), + Input(get_uuid("vshort-select"), "value"), + Input({"id": get_uuid("vitem-select"), "shortname": ALL}, "value"), ) def _combine_substrings_to_vector(shortname: str, item: list): """Combine vector shortname and item to full vector name""" vector = shortname if not item or item[0] is None else f"{shortname}:{item[0]}" - if vector not in parent.vmodel.vectors: + if vector not in vectormodel.vectors: raise PreventUpdate return vector @app.callback( - Output(parent.uuid("vshort-select"), "options"), - Output(parent.uuid("vshort-select"), "value"), - Output(parent.uuid("clickdata-store"), "data"), - Input({"id": parent.uuid("vtype-select"), "state": ALL}, "value"), - Input(parent.uuid("param-corr-graph"), "clickData"), + Output(get_uuid("vshort-select"), "options"), + Output(get_uuid("vshort-select"), "value"), + Output(get_uuid("clickdata-store"), "data"), + Input({"id": get_uuid("vtype-select"), "state": ALL}, "value"), + Input(get_uuid("param-corr-graph"), "clickData"), ) def _update_vectorlist(vtype: list, corr_param_clickdata: dict): """ @@ -215,24 +225,24 @@ def _update_vectorlist(vtype: list, corr_param_clickdata: dict): selected vector type or clickdata """ ctx = dash.callback_context.triggered[0]["prop_id"].split(".")[0] - click_data = parent.uuid("param-corr-graph") in ctx + click_data = get_uuid("param-corr-graph") in ctx vtype = vtype[0] if click_data: vector_selected = corr_param_clickdata.get("points", [{}])[0].get("y") - vtype = find_vector_type(parent, vector_selected) + vtype = find_vector_type(vectormodel, vector_selected) shortname = ( vector_selected.split(":")[0] if click_data - else parent.vmodel.vector_groups[vtype]["shortnames"][0] + else vectormodel.vector_groups[vtype]["shortnames"][0] ) return ( [ {"label": i, "value": i} - for i in parent.vmodel.vector_groups[vtype]["shortnames"] + for i in vectormodel.vector_groups[vtype]["shortnames"] ], shortname, dict( @@ -245,12 +255,12 @@ def _update_vectorlist(vtype: list, corr_param_clickdata: dict): ) @app.callback( - Output(parent.uuid("vitems-container"), "children"), - Output(parent.uuid("vtype-container"), "children"), - Input(parent.uuid("vshort-select"), "value"), - State({"id": parent.uuid("vtype-select"), "state": ALL}, "value"), - State(parent.uuid("clickdata-store"), "data"), - State({"id": parent.uuid("vitem-select"), "shortname": ALL}, "value"), + Output(get_uuid("vitems-container"), "children"), + Output(get_uuid("vtype-container"), "children"), + Input(get_uuid("vshort-select"), "value"), + State({"id": get_uuid("vtype-select"), "state": ALL}, "value"), + State(get_uuid("clickdata-store"), "data"), + State({"id": get_uuid("vitem-select"), "shortname": ALL}, "value"), ) def _update_vector_items( shortname: str, @@ -268,18 +278,21 @@ def _update_vector_items( items = [ v - for v in parent.vmodel.vector_groups[vtype]["items"] - if f"{shortname}:{v}" in parent.vmodel.vectors + for v in vectormodel.vector_groups[vtype]["items"] + if f"{shortname}:{v}" in vectormodel.vectors ] if items and not clickdata_vector: - item = previous_item[0] if previous_item[0] in items else items[0] + if previous_item: + item = previous_item[0] if previous_item[0] in items else items[0] + else: + item = items[0] if items and clickdata_vector: item = clickdata_vector["vector"].replace(f"{shortname}:", "") return ( [ dcc.Dropdown( - id={"id": parent.uuid("vitem-select"), "shortname": shortname}, + id={"id": get_uuid("vitem-select"), "shortname": shortname}, options=[{"label": i, "value": i} for i in items], value=item if items else None, disabled=not items, @@ -291,9 +304,9 @@ def _update_vector_items( ], [ dcc.RadioItems( - id={"id": parent.uuid("vtype-select"), "state": "update"}, + id={"id": get_uuid("vtype-select"), "state": "update"}, options=[ - {"label": i, "value": i} for i in parent.vmodel.vector_groups + {"label": i, "value": i} for i in vectormodel.vector_groups ], value=vtype, labelStyle={"display": "inline-block", "margin-right": "10px"}, @@ -304,8 +317,8 @@ def _update_vector_items( ) @app.callback( - Output({"id": parent.uuid("parameter-select"), "tab": "response"}, "value"), - Input(parent.uuid("vector-corr-graph"), "clickData"), + Output({"id": get_uuid("parameter-select"), "tab": "response"}, "value"), + Input(get_uuid("vector-corr-graph"), "clickData"), ) def _update_parameter_selected( corr_vector_clickdata: Union[None, dict], @@ -317,14 +330,14 @@ def _update_parameter_selected( # pylint: disable=inconsistent-return-statements -def relevant_ctx(parent, ctx: list, operation: str): +def relevant_ctx(get_uuid: Callable, ctx: list, operation: str): """Group relevant uuids for the different plots""" - vector = parent.uuid("vector-select") in ctx - date = parent.uuid("date-selected") in ctx - parameter = parent.uuid("parameter-select") in ctx - ensemble = parent.uuid("ensemble-selector") in ctx + vector = get_uuid("vector-select") in ctx + date = get_uuid("date-selected") in ctx + parameter = get_uuid("parameter-select") in ctx + ensemble = get_uuid("ensemble-selector") in ctx filtered_vectors = ( - parent.uuid("vtype-filter") in ctx or parent.uuid("vitem-filter") in ctx + get_uuid("vtype-filter") in ctx or get_uuid("vitem-filter") in ctx ) if operation == "timeseries_fig": @@ -339,19 +352,21 @@ def relevant_ctx(parent, ctx: list, operation: str): return any([parameter, ensemble, vector]) -def find_vector_type(parent, vector: str): +def find_vector_type(vectormodel: SimulationTimeSeriesModel, vector: str): """Get vector type from vector""" - for vgroup, values in parent.vmodel.vector_groups.items(): + for vgroup, values in vectormodel.vector_groups.items(): if vector in values["vectors"]: return vgroup return None -def filter_vectors(parent, vector_types: list, vector_items: list): +def filter_vectors( + vectormodel: SimulationTimeSeriesModel, vector_types: list, vector_items: list +): """Filter vector list used for correlation""" vectors = list( chain.from_iterable( - [parent.vmodel.vector_groups[vtype]["vectors"] for vtype in vector_types] + [vectormodel.vector_groups[vtype]["vectors"] for vtype in vector_types] ) ) items = list(chain.from_iterable(vector_items)) @@ -461,17 +476,23 @@ def set_real_color(df_norm, real_no: str): return "rgba(220,220,220, 0.2)" -def merge_parameter_and_vector_df(parent, ensemble: str, vectors: list, date: str): +def merge_parameter_and_vector_df( + vectormodel: SimulationTimeSeriesModel, + parametermodel: ParametersModel, + ensemble: str, + vectors: list, + date: str, +): """Merge parameter dataframe with vector dataframe on given date """ # Get dataframe with vector and REAL - vector_df = parent.vmodel.get_ensemble_vectors_for_date( + vector_df = vectormodel.get_ensemble_vectors_for_date( ensemble=ensemble, vectors=vectors, date=date, ).copy() vector_df["REAL"] = vector_df["REAL"].astype(int) # Get dataframe with parameters - param_df = parent.pmodel.dataframe.copy() + param_df = parametermodel.dataframe.copy() param_df = param_df[param_df["ENSEMBLE"] == ensemble] param_df["REAL"] = param_df["REAL"].astype(int) # Return merged dataframe diff --git a/webviz_subsurface/plugins/_parameter_analysis/models/simulation_timeseries_model.py b/webviz_subsurface/plugins/_parameter_analysis/models/simulation_timeseries_model.py index 594289417..1bf5785b8 100644 --- a/webviz_subsurface/plugins/_parameter_analysis/models/simulation_timeseries_model.py +++ b/webviz_subsurface/plugins/_parameter_analysis/models/simulation_timeseries_model.py @@ -99,9 +99,10 @@ def _split_vectors_by_type(vector_names: Iterable[str]) -> Dict[str, dict]: chain.from_iterable([vgroups[vtype]["vectors"] for vtype in vgroups]) ) ] - vgroups["Others"] = dict( - vectors=other_vectors, shortnames=other_vectors, items=[] - ) + if other_vectors: + vgroups["Others"] = dict( + vectors=other_vectors, shortnames=other_vectors, items=[] + ) return vgroups @staticmethod diff --git a/webviz_subsurface/plugins/_parameter_analysis/parameter_analysis.py b/webviz_subsurface/plugins/_parameter_analysis/parameter_analysis.py index 05057bc22..5b76ef92c 100644 --- a/webviz_subsurface/plugins/_parameter_analysis/parameter_analysis.py +++ b/webviz_subsurface/plugins/_parameter_analysis/parameter_analysis.py @@ -136,12 +136,22 @@ def __init__( @property def layout(self) -> dcc.Tabs: - return main_view(parent=self) + return main_view( + get_uuid=self.uuid, + vectormodel=self.vmodel, + parametermodel=self.pmodel, + theme=self.theme, + ) def set_callbacks(self, app) -> None: - parameter_qc_controller(self, app) + parameter_qc_controller(app=app, get_uuid=self.uuid, parametermodel=self.pmodel) if self.vmodel is not None: - parameter_response_controller(self, app) + parameter_response_controller( + app=app, + get_uuid=self.uuid, + parametermodel=self.pmodel, + vectormodel=self.vmodel, + ) def add_webvizstore(self): store = [] diff --git a/webviz_subsurface/plugins/_parameter_analysis/views/main_view.py b/webviz_subsurface/plugins/_parameter_analysis/views/main_view.py index 6ff67eb45..a7732570e 100644 --- a/webviz_subsurface/plugins/_parameter_analysis/views/main_view.py +++ b/webviz_subsurface/plugins/_parameter_analysis/views/main_view.py @@ -1,27 +1,43 @@ +from typing import Callable + import dash_html_components as html import dash_core_components as dcc +from webviz_config import WebvizConfigTheme from .parameter_qc_view import parameter_qc_view from .parameter_response_view import parameter_response_view +from ..models import ParametersModel, SimulationTimeSeriesModel -def main_view(parent) -> dcc.Tabs: +def main_view( + get_uuid: Callable, + vectormodel: SimulationTimeSeriesModel, + parametermodel: ParametersModel, + theme: WebvizConfigTheme, +) -> dcc.Tabs: tabs = [ make_tab( label="Parameter distributions", - children=parameter_qc_view(parent=parent), + children=parameter_qc_view( + get_uuid=get_uuid, parametermodel=parametermodel + ), ) ] - if parent.vmodel is not None: + if vectormodel is not None: tabs.append( make_tab( label="Parameters impact on simulation profiles", - children=parameter_response_view(parent=parent), + children=parameter_response_view( + get_uuid=get_uuid, + parametermodel=parametermodel, + vectormodel=vectormodel, + theme=theme, + ), ) ) return html.Div( - id=parent.uuid("layout"), + id=get_uuid("layout"), children=dcc.Tabs( style={"width": "100%"}, persistence=True, diff --git a/webviz_subsurface/plugins/_parameter_analysis/views/parameter_qc_view.py b/webviz_subsurface/plugins/_parameter_analysis/views/parameter_qc_view.py index d757e0e9b..e1bb2f9e3 100644 --- a/webviz_subsurface/plugins/_parameter_analysis/views/parameter_qc_view.py +++ b/webviz_subsurface/plugins/_parameter_analysis/views/parameter_qc_view.py @@ -1,3 +1,5 @@ +from typing import Callable + import dash_html_components as html import dash_core_components as dcc import webviz_core_components as wcc @@ -9,9 +11,10 @@ html_details, sortby_selector, ) +from ..models import ParametersModel -def selector_view(parent) -> html.Div: +def selector_view(get_uuid: Callable, parametermodel: ParametersModel) -> html.Div: return html.Div( className="framed", style={"height": "80vh", "overflowY": "auto", "font-size": "15px"}, @@ -22,24 +25,27 @@ def selector_view(parent) -> html.Div: summary="Selections", children=[ ensemble_selector( - parent=parent, + get_uuid=get_uuid, + parametermodel=parametermodel, tab="qc", id_string="ensemble-selector", heading="Ensemble A:", - value=parent.pmodel.ensembles[0], + value=parametermodel.ensembles[0], ), ensemble_selector( - parent=parent, + get_uuid=get_uuid, + parametermodel=parametermodel, tab="qc", id_string="delta-ensemble-selector", heading="Ensemble B:", - value=parent.pmodel.ensembles[-1], + value=parametermodel.ensembles[-1], ), - sortby_selector(parent=parent, value="Name"), + sortby_selector(get_uuid=get_uuid, value="Name"), filter_parameter( - parent=parent, + get_uuid=get_uuid, + parametermodel=parametermodel, tab="qc", - value=[parent.pmodel.parameters[0]], + value=[parametermodel.parameters[0]], ), ], open_details=True, @@ -50,11 +56,18 @@ def selector_view(parent) -> html.Div: ) -def parameter_qc_view(parent) -> wcc.FlexBox: +def parameter_qc_view( + get_uuid: Callable, parametermodel: ParametersModel +) -> wcc.FlexBox: return wcc.FlexBox( style={"margin": "20px"}, children=[ - html.Div(style={"flex": 1}, children=selector_view(parent=parent)), + html.Div( + style={"flex": 1}, + children=selector_view( + get_uuid=get_uuid, parametermodel=parametermodel + ), + ), html.Div( style={"flex": 4, "height": "80vh"}, className="framed", @@ -62,7 +75,7 @@ def parameter_qc_view(parent) -> wcc.FlexBox: wcc.FlexBox( children=[ dcc.RadioItems( - id=parent.uuid("property-qc-plot-type"), + id=get_uuid("property-qc-plot-type"), options=[ { "label": "Distribution plots", @@ -79,7 +92,7 @@ def parameter_qc_view(parent) -> wcc.FlexBox: ] ), html.Div( - style={"height": "75vh"}, id=parent.uuid("property-qc-wrapper") + style={"height": "75vh"}, id=get_uuid("property-qc-wrapper") ), ], ), diff --git a/webviz_subsurface/plugins/_parameter_analysis/views/parameter_response_view.py b/webviz_subsurface/plugins/_parameter_analysis/views/parameter_response_view.py index 3b33b38ad..073135ec8 100644 --- a/webviz_subsurface/plugins/_parameter_analysis/views/parameter_response_view.py +++ b/webviz_subsurface/plugins/_parameter_analysis/views/parameter_response_view.py @@ -1,3 +1,6 @@ +from typing import Callable + +from webviz_config import WebvizConfigTheme import dash_html_components as html import webviz_core_components as wcc @@ -12,13 +15,14 @@ color_selector, color_opacity_selector, ) +from ..models import ParametersModel, SimulationTimeSeriesModel -def timeseries_view(parent) -> html.Div: +def timeseries_view(get_uuid: Callable) -> html.Div: return html.Div( children=[ wcc.Graph( - id=parent.uuid("vector-vs-time-graph"), + id=get_uuid("vector-vs-time-graph"), config={"displayModeBar": False}, style={"height": "38vh"}, ), @@ -26,9 +30,14 @@ def timeseries_view(parent) -> html.Div: ) -def selector_view(parent) -> html.Div: +def selector_view( + get_uuid: Callable, + vectormodel: SimulationTimeSeriesModel, + parametermodel: ParametersModel, + theme: WebvizConfigTheme, +) -> html.Div: - theme_colors = parent.theme.plotly_theme.get("layout", {}).get("colorway", []) + theme_colors = theme.plotly_theme.get("layout", {}).get("colorway", []) theme_colors = ( theme_colors[1:12] if theme_colors and len(theme_colors) >= 12 else theme_colors ) @@ -45,15 +54,18 @@ def selector_view(parent) -> html.Div: summary="Selections", children=[ ensemble_selector( - parent=parent, + get_uuid=get_uuid, + parametermodel=parametermodel, tab="response", id_string="ensemble-selector", heading="Ensemble:", - value=parent.pmodel.ensembles[0], + value=parametermodel.ensembles[0], + ), + vector_selector(get_uuid=get_uuid, vectormodel=vectormodel), + date_selector(get_uuid=get_uuid, vectormodel=vectormodel), + parameter_selector( + get_uuid=get_uuid, parametermodel=parametermodel, tab="response" ), - vector_selector(parent=parent), - date_selector(parent=parent), - parameter_selector(parent=parent, tab="response"), ], open_details=True, ), @@ -70,21 +82,27 @@ def selector_view(parent) -> html.Div: }, ), ], - children=[filter_vector_selector(parent=parent, tab="response")], + children=[ + filter_vector_selector( + get_uuid=get_uuid, vectormodel=vectormodel, tab="response" + ) + ], open_details=False, ), html_details( summary="Options", children=[ - plot_options(parent=parent, tab="response"), + plot_options(get_uuid=get_uuid, tab="response"), color_selector( - parent=parent, + get_uuid=get_uuid, tab="response", colors=[theme_colors, "Greys", "BrBG"], bargap=0.2, height=50, ), - color_opacity_selector(parent=parent, tab="response", value=0.5), + color_opacity_selector( + get_uuid=get_uuid, tab="response", value=0.5 + ), ], open_details=False, ), @@ -92,13 +110,23 @@ def selector_view(parent) -> html.Div: ) -def parameter_response_view(parent) -> wcc.FlexBox: +def parameter_response_view( + get_uuid: Callable, + parametermodel: ParametersModel, + vectormodel: SimulationTimeSeriesModel, + theme: WebvizConfigTheme, +) -> wcc.FlexBox: return wcc.FlexBox( style={"margin": "20px"}, children=[ html.Div( style={"flex": 1, "width": "90%"}, - children=selector_view(parent=parent), + children=selector_view( + get_uuid=get_uuid, + parametermodel=parametermodel, + vectormodel=vectormodel, + theme=theme, + ), ), html.Div( style={"flex": 2, "height": "80vh"}, @@ -106,14 +134,14 @@ def parameter_response_view(parent) -> wcc.FlexBox: html.Div( className="framed", style={"height": "37.5vh"}, - children=timeseries_view(parent=parent), + children=timeseries_view(get_uuid=get_uuid), ), html.Div( className="framed", style={"height": "37.5vh"}, children=[ wcc.Graph( - id=parent.uuid("vector-vs-param-scatter"), + id=get_uuid("vector-vs-param-scatter"), config={"displayModeBar": False}, style={"height": "38vh"}, ) @@ -131,7 +159,7 @@ def parameter_response_view(parent) -> wcc.FlexBox: wcc.Graph( config={"displayModeBar": False}, style={"height": "38vh"}, - id=parent.uuid("vector-corr-graph"), + id=get_uuid("vector-corr-graph"), ), ], ), @@ -142,7 +170,7 @@ def parameter_response_view(parent) -> wcc.FlexBox: wcc.Graph( config={"displayModeBar": False}, style={"height": "38vh"}, - id=parent.uuid("param-corr-graph"), + id=get_uuid("param-corr-graph"), ), ], ), diff --git a/webviz_subsurface/plugins/_parameter_analysis/views/selector_view.py b/webviz_subsurface/plugins/_parameter_analysis/views/selector_view.py index c32aefb0b..dc1e03028 100644 --- a/webviz_subsurface/plugins/_parameter_analysis/views/selector_view.py +++ b/webviz_subsurface/plugins/_parameter_analysis/views/selector_view.py @@ -1,14 +1,16 @@ -from typing import Union, Optional +from typing import Union, Optional, Callable import dash_html_components as html import dash_core_components as dcc import webviz_core_components as wcc from ..figures.color_figure import color_figure +from ..models import ParametersModel, SimulationTimeSeriesModel def ensemble_selector( - parent, + get_uuid: Callable, + parametermodel: ParametersModel, tab: str, id_string: str, multi: bool = False, @@ -20,10 +22,10 @@ def ensemble_selector( children.append(html.Span(heading, style={"font-weight": "bold"})) children.append( dcc.Dropdown( - id={"id": parent.uuid(id_string), "tab": tab}, - options=[{"label": ens, "value": ens} for ens in parent.pmodel.ensembles], + id={"id": get_uuid(id_string), "tab": tab}, + options=[{"label": ens, "value": ens} for ens in parametermodel.ensembles], multi=multi, - value=value if value is not None else parent.pmodel.ensembles[0], + value=value if value is not None else parametermodel.ensembles[0], clearable=False, persistence=True, persistence_type="session", @@ -32,21 +34,27 @@ def ensemble_selector( return html.Div(style={"width": "90%"}, children=children) -def vector_selector(parent) -> html.Div: +def vector_selector( + get_uuid: Callable, vectormodel: SimulationTimeSeriesModel +) -> html.Div: + first_vector_group: str = ( + "Field" + if "Field" in list(vectormodel.vector_groups.keys()) + else list(vectormodel.vector_groups.keys())[0] + ) return html.Div( style={"width": "90%", "margin-top": "15px"}, children=[ html.Span("Vector type:", style={"font-weight": "bold"}), html.Div( - id=parent.uuid("vtype-container"), + id=get_uuid("vtype-container"), children=[ dcc.RadioItems( - id={"id": parent.uuid("vtype-select"), "state": "initial"}, + id={"id": get_uuid("vtype-select"), "state": "initial"}, options=[ - {"label": i, "value": i} - for i in parent.vmodel.vector_groups + {"label": i, "value": i} for i in vectormodel.vector_groups ], - value="Field", + value=first_vector_group, labelStyle={"display": "inline-block", "margin-right": "10px"}, ) ], @@ -56,17 +64,17 @@ def vector_selector(parent) -> html.Div: children=[ html.Span("Vector:", style={"font-weight": "bold"}), html.Div( - id=parent.uuid("vshort-container"), + id=get_uuid("vshort-container"), children=[ dcc.Dropdown( - id=parent.uuid("vshort-select"), + id=get_uuid("vshort-select"), options=[ {"label": i, "value": i} - for i in parent.vmodel.vector_groups["Field"][ - "shortnames" - ] + for i in vectormodel.vector_groups[ + first_vector_group + ]["shortnames"] ], - value=parent.vmodel.vector_groups["Field"][ + value=vectormodel.vector_groups[first_vector_group][ "shortnames" ][0], placeholder="Select a vector...", @@ -78,21 +86,23 @@ def vector_selector(parent) -> html.Div: ), ], ), - html.Div(id=parent.uuid("vitems-container"), children=[]), - html.Div(id=parent.uuid("vector-select"), style={"display": "none"}), - html.Div(id=parent.uuid("clickdata-store"), style={"display": "none"}), + html.Div(id=get_uuid("vitems-container"), children=[]), + html.Div(id=get_uuid("vector-select"), style={"display": "none"}), + html.Div(id=get_uuid("clickdata-store"), style={"display": "none"}), ], ) -def parameter_selector(parent, tab: str) -> html.Div: +def parameter_selector( + get_uuid: Callable, parametermodel: ParametersModel, tab: str +) -> html.Div: return html.Div( style={"width": "90%", "margin-top": "15px"}, children=[ html.Span("Parameter:", style={"font-weight": "bold"}), dcc.Dropdown( - id={"id": parent.uuid("parameter-select"), "tab": tab}, - options=[{"label": i, "value": i} for i in parent.pmodel.parameters], + id={"id": get_uuid("parameter-select"), "tab": tab}, + options=[{"label": i, "value": i} for i in parametermodel.parameters], placeholder="Select a parameter...", clearable=False, persistence=True, @@ -103,7 +113,7 @@ def parameter_selector(parent, tab: str) -> html.Div: def sortby_selector( - parent, + get_uuid: Callable, value: str = None, ) -> html.Div: return html.Div( @@ -114,7 +124,7 @@ def sortby_selector( style={"font-weight": "bold"}, ), dcc.RadioItems( - id=parent.uuid("delta-sort"), + id=get_uuid("delta-sort"), options=[ {"label": "Name", "value": "Name"}, { @@ -138,12 +148,12 @@ def sortby_selector( ) -def plot_options(parent, tab: str) -> html.Div: +def plot_options(get_uuid: Callable, tab: str) -> html.Div: return html.Div( style={"width": "90%", "margin-top": "15px"}, children=[ dcc.Checklist( - id={"id": parent.uuid("checkbox-options"), "tab": tab}, + id={"id": get_uuid("checkbox-options"), "tab": tab}, options=[ {"label": "Dateline visible", "value": "DateLine"}, {"label": "Auto compute correlations", "value": "AutoCompute"}, @@ -151,15 +161,17 @@ def plot_options(parent, tab: str) -> html.Div: value=["DateLine", "AutoCompute"], ), html.Div( - id={"id": parent.uuid("plot-options"), "tab": tab}, + id={"id": get_uuid("plot-options"), "tab": tab}, style={"display": "none"}, ), ], ) -def date_selector(parent) -> html.Div: - dates = parent.vmodel.dates +def date_selector( + get_uuid: Callable, vectormodel: SimulationTimeSeriesModel +) -> html.Div: + dates = vectormodel.dates return html.Div( style={"width": "90%", "margin-top": "15px"}, children=[ @@ -169,13 +181,13 @@ def date_selector(parent) -> html.Div: html.Span("Date:", style={"font-weight": "bold"}), html.Span( "date", - id=parent.uuid("date-selected"), + id=get_uuid("date-selected"), style={"margin-left": "10px"}, ), ], ), dcc.Slider( - id=parent.uuid("date-slider"), + id=get_uuid("date-slider"), value=len(dates) - 1, min=0, max=len(dates) - 1, @@ -195,7 +207,8 @@ def date_selector(parent) -> html.Div: def filter_parameter( - parent, + get_uuid: Callable, + parametermodel: ParametersModel, tab: str, multi: bool = True, value: Union[str, float] = None, @@ -205,19 +218,19 @@ def filter_parameter( children=[ html.Span("Parameters:", style={"font-weight": "bold"}), html.Div( - id=parent.uuid("filter-parameter-container"), + id=get_uuid("filter-parameter-container"), children=[ wcc.Select( id={ - "id": parent.uuid("filter-parameter"), + "id": get_uuid("filter-parameter"), "tab": tab, }, options=[ - {"label": i, "value": i} for i in parent.pmodel.parameters + {"label": i, "value": i} for i in parametermodel.parameters ], value=value, multi=multi, - size=min(40, len(parent.pmodel.parameters)), + size=min(40, len(parametermodel.parameters)), persistence=True, persistence_type="session", ), @@ -228,7 +241,7 @@ def filter_parameter( def make_filter( - parent, + get_uuid: Callable, tab: str, vtype: str, column_values: list, @@ -243,7 +256,7 @@ def make_filter( html.Summary(vtype), wcc.Select( id={ - "id": parent.uuid("vitem-filter"), + "id": get_uuid("vitem-filter"), "tab": tab, "vtype": vtype, }, @@ -260,7 +273,8 @@ def make_filter( def filter_vector_selector( - parent, + get_uuid: Callable, + vectormodel: SimulationTimeSeriesModel, tab: str, multi: bool = True, value: Union[str, float] = None, @@ -272,11 +286,11 @@ def filter_vector_selector( html.Span("Vector type:", style={"font-weight": "bold"}), dcc.Dropdown( id={ - "id": parent.uuid("vtype-filter"), + "id": get_uuid("vtype-filter"), "tab": tab, }, - options=[{"label": i, "value": i} for i in parent.vmodel.vector_groups], - value=list(parent.vmodel.vector_groups), + options=[{"label": i, "value": i} for i in vectormodel.vector_groups], + value=list(vectormodel.vector_groups), clearable=False, style={"background-color": "white"}, multi=True, @@ -286,7 +300,7 @@ def filter_vector_selector( html.Div( children=[ make_filter( - parent=parent, + get_uuid=get_uuid, tab=tab, vtype=f"{vtype}s", column_values=vlist["items"], @@ -294,12 +308,12 @@ def filter_vector_selector( value=value, open_details=open_details, ) - for vtype, vlist in parent.vmodel.vector_groups.items() + for vtype, vlist in vectormodel.vector_groups.items() if vlist["items"] ], ), html.Div( - id={"id": parent.uuid("filter-select"), "tab": tab}, + id={"id": get_uuid("filter-select"), "tab": tab}, style={"display": "none"}, ), ], @@ -307,7 +321,7 @@ def filter_vector_selector( def color_selector( - parent, + get_uuid: Callable, tab: str, colors: Optional[list] = None, bargap: Optional[float] = None, @@ -318,7 +332,7 @@ def color_selector( children=[ html.Span("Colors:", style={"font-weight": "bold"}), wcc.Graph( - id={"id": parent.uuid("color-selector"), "tab": tab}, + id={"id": get_uuid("color-selector"), "tab": tab}, config={"displayModeBar": False}, figure=color_figure( colors=colors, @@ -330,13 +344,13 @@ def color_selector( ) -def color_opacity_selector(parent, tab: str, value: float): +def color_opacity_selector(get_uuid: Callable, tab: str, value: float): return html.Div( style={"width": "90%", "margin-top": "5px"}, children=[ "Opacity:", dcc.Input( - id={"id": parent.uuid("opacity-selector"), "tab": tab}, + id={"id": get_uuid("opacity-selector"), "tab": tab}, type="number", min=0, max=1,