diff --git a/webviz_subsurface/_abbreviations/abbreviation_data/volume_terminology.json b/webviz_subsurface/_abbreviations/abbreviation_data/volume_terminology.json index a4b62b41b..9461e6d7b 100644 --- a/webviz_subsurface/_abbreviations/abbreviation_data/volume_terminology.json +++ b/webviz_subsurface/_abbreviations/abbreviation_data/volume_terminology.json @@ -1,27 +1,74 @@ { - "BULK_OIL": {"label": "Bulk volume (oil zone)", "unit": "m³"}, - "BULK_GAS": {"label": "Bulk volume (gas zone)", "unit": "m³"}, - "BULK_TOTAL": {"label": "Bulk volume (total)", "unit": "m³"}, - "NET_OIL": {"label": "Net volume (oil zone)", "unit": "m³"}, - "NET_GAS": {"label": "Net volume (gas zone)", "unit": "m³"}, - "NET_TOTAL": {"label": "Net volume (total)", "unit": "m³"}, - "PORV_OIL": {"label": "Pore volume (oil zone)", "unit": "m³"}, - "PORV_GAS": {"label": "Pore volume (gas zone)", "unit": "m³"}, - "PORV_TOTAL": {"label": "Pore volume (total)", "unit": "m³"}, - "PORE_OIL": {"label": "Pore volume (oil zone)", "unit": "m³"}, - "PORE_GAS": {"label": "Pore volume (gas zone)", "unit": "m³"}, - "PORE_TOTAL": {"label": "Pore volume (total)", "unit": "m³"}, - "HCPV_OIL": {"label": "Hydro carbon pore volume (oil zone)", "unit": "m³"}, - "HCPV_GAS": {"label": "Hydro carbon pore volume (gas zone)", "unit": "m³"}, - "HCPV_TOTAL": {"label": "Hydro carbon pore volume (total zone)", "unit": "m³"}, - "STOIIP_OIL": {"label": "Stock tank oil initially in place (oil zone)", "unit": "Sm³", "eclsum": ["FOIPL", "ROIPL"]}, - "STOIIP_GAS": {"label": "Stock tank oil initially in place (gas zone)", "unit": "Sm³", "eclsum": ["FOIPG", "ROIPG"]}, - "STOIIP_TOTAL": {"label": "Stock tank oil initially in place (total)", "unit": "Sm³", "eclsum": ["FOIP", "ROIP"]}, - "GIIP_OIL": {"label": "Gas initially in place (oil zone)", "unit": "Sm³", "eclsum": ["FGIPL", "RGIPL"]}, - "GIIP_GAS": {"label": "Gas initially in place (gas zone)", "unit": "Sm³", "eclsum": ["FGIPG", "RGIPG"]}, - "GIIP_TOTAL": {"label": "Gas initially in place (total)", "unit": "Sm³", "eclsum": ["FGIP", "RGIP"]}, - "RECOVERABLE_OIL": {"label": "Recoverable volume (oil zone)", "unit": "Sm³"}, - "RECOVERABLE_GAS": {"label": "Recoverable volume (gas zone)", "unit": "Sm³"}, - "RECOVERABLE_TOTAL": {"label": "Recoverable volume (total)", "unit": "Sm³"} + "BULK_OIL": { + "description": "Bulk volume (oil zone)" + }, + "BULK_GAS": { + "description": "Bulk volume (gas zone)" + }, + "BULK_TOTAL": { + "description": "Bulk volume (total)" + }, + "NET_OIL": { + "description": "Net volume (oil zone)" + }, + "NET_GAS": { + "description": "Net volume (gas zone)" + }, + "NET_TOTAL": { + "description": "Net volume (total)" + }, + "PORV_OIL": { + "description": "Pore volume (oil zone)" + }, + "PORV_GAS": { + "description": "Pore volume (gas zone)" + }, + "PORV_TOTAL": { + "description": "Pore volume (total)" + }, + "PORE_OIL": { + "description": "Pore volume (oil zone)" + }, + "PORE_GAS": { + "description": "Pore volume (gas zone)" + }, + "PORE_TOTAL": { + "description": "Pore volume (total)" + }, + "HCPV_OIL": { + "description": "Hydro carbon pore volume (oil zone)" + }, + "HCPV_GAS": { + "description": "Hydro carbon pore volume (gas zone)" + }, + "HCPV_TOTAL": { + "description": "Hydro carbon pore volume (total zone)" + }, + "STOIIP_OIL": { + "description": "Stock tank oil initially in place (oil zone)" + }, + "STOIIP_GAS": { + "description": "Stock tank oil initially in place (gas zone)" + }, + "STOIIP_TOTAL": { + "description": "Stock tank oil initially in place (total)" + }, + "GIIP_OIL": { + "description": "Gas initially in place (oil zone)" + }, + "GIIP_GAS": { + "description": "Gas initially in place (gas zone)" + }, + "GIIP_TOTAL": { + "description": "Gas initially in place (total)" + }, + "RECOVERABLE_OIL": { + "description": "Recoverable volume (oil zone)" + }, + "RECOVERABLE_GAS": { + "description": "Recoverable volume (gas zone)" + }, + "RECOVERABLE_TOTAL": { + "description": "Recoverable volume (total)" + } } - diff --git a/webviz_subsurface/_abbreviations/abbreviation_data/volume_terminology_metric.json b/webviz_subsurface/_abbreviations/abbreviation_data/volume_terminology_metric.json new file mode 100644 index 000000000..4bf914391 --- /dev/null +++ b/webviz_subsurface/_abbreviations/abbreviation_data/volume_terminology_metric.json @@ -0,0 +1,26 @@ +{ + "BULK_OIL": {"description": "Bulk volume (oil zone)", "unit": "m³"}, + "BULK_GAS": {"description": "Bulk volume (gas zone)", "unit": "m³"}, + "BULK_TOTAL": {"description": "Bulk volume (total)", "unit": "m³"}, + "NET_OIL": {"description": "Net volume (oil zone)", "unit": "m³"}, + "NET_GAS": {"description": "Net volume (gas zone)", "unit": "m³"}, + "NET_TOTAL": {"description": "Net volume (total)", "unit": "m³"}, + "PORV_OIL": {"description": "Pore volume (oil zone)", "unit": "m³"}, + "PORV_GAS": {"description": "Pore volume (gas zone)", "unit": "m³"}, + "PORV_TOTAL": {"description": "Pore volume (total)", "unit": "m³"}, + "PORE_OIL": {"description": "Pore volume (oil zone)", "unit": "m³"}, + "PORE_GAS": {"description": "Pore volume (gas zone)", "unit": "m³"}, + "PORE_TOTAL": {"description": "Pore volume (total)", "unit": "m³"}, + "HCPV_OIL": {"description": "Hydro carbon pore volume (oil zone)", "unit": "m³"}, + "HCPV_GAS": {"description": "Hydro carbon pore volume (gas zone)", "unit": "m³"}, + "HCPV_TOTAL": {"description": "Hydro carbon pore volume (total zone)", "unit": "m³"}, + "STOIIP_OIL": {"description": "Stock tank oil initially in place (oil zone)", "unit": "Sm³"}, + "STOIIP_GAS": {"description": "Stock tank oil initially in place (gas zone)", "unit": "Sm³"}, + "STOIIP_TOTAL": {"description": "Stock tank oil initially in place (total)", "unit": "Sm³"}, + "GIIP_OIL": {"description": "Gas initially in place (oil zone)", "unit": "Sm³"}, + "GIIP_GAS": {"description": "Gas initially in place (gas zone)", "unit": "Sm³"}, + "GIIP_TOTAL": {"description": "Gas initially in place (total)", "unit": "Sm³"}, + "RECOVERABLE_OIL": {"description": "Recoverable volume (oil zone)", "unit": "Sm³"}, + "RECOVERABLE_GAS": {"description": "Recoverable volume (gas zone)", "unit": "Sm³"}, + "RECOVERABLE_TOTAL": {"description": "Recoverable volume (total)", "unit": "Sm³"} +} diff --git a/webviz_subsurface/_abbreviations/volume_terminology.py b/webviz_subsurface/_abbreviations/volume_terminology.py index bea36dfdf..d7a261091 100644 --- a/webviz_subsurface/_abbreviations/volume_terminology.py +++ b/webviz_subsurface/_abbreviations/volume_terminology.py @@ -1,36 +1,45 @@ import json import pathlib - +from typing import Optional, Union _DATA_PATH = pathlib.Path(__file__).parent.absolute() / "abbreviation_data" VOLUME_TERMINOLOGY = json.loads((_DATA_PATH / "volume_terminology.json").read_text()) +VOLUME_TERMINOLOGY_METRIC = json.loads( + (_DATA_PATH / "volume_terminology_metric.json").read_text() +) -def volume_description(column: str): +def volume_description(column: str, metadata: Optional[Union[dict, str]] = None): """Return description for the column if defined""" + if metadata is not None: + try: + if isinstance(metadata, dict): + return metadata[column]["description"] + if isinstance(metadata, str) and metadata.lower() == "metric": + return VOLUME_TERMINOLOGY_METRIC[column]["description"] + except KeyError: + pass try: - label = VOLUME_TERMINOLOGY[column]["label"] + description = VOLUME_TERMINOLOGY[column]["description"] except KeyError: - label = column - return label + description = column + return description -def volume_unit(column: str): +def volume_unit(column: str, metadata: Optional[Union[dict, str]] = None): """Return unit for the column if defined""" - try: - unit = VOLUME_TERMINOLOGY[column]["unit"] - except KeyError: - unit = "" - return unit - - -def volume_simulation_vector_match(column: str): - """Return a list of simulation vectors that match the column - Useful to verify/propose data to compare. - """ - try: - vectors = VOLUME_TERMINOLOGY[column]["eclsum"] - except KeyError: - vectors = [] - return vectors if isinstance(vectors, list) else [vectors] + if metadata is not None: + try: + if isinstance(metadata, dict): + return metadata[column]["unit"] + if isinstance(metadata, str) and metadata.lower() == "metric": + return VOLUME_TERMINOLOGY_METRIC[column]["unit"] + except KeyError: + pass + return "" + + +def column_title(response: str, metadata: Union[dict, str]): + unit = volume_unit(response, metadata) + return f"{volume_description(response, metadata)}" + (f" [{unit}]" if unit else "") diff --git a/webviz_subsurface/_datainput/inplace_volumes.py b/webviz_subsurface/_datainput/inplace_volumes.py index bf549d6f7..c6c3f4d1e 100644 --- a/webviz_subsurface/_datainput/inplace_volumes.py +++ b/webviz_subsurface/_datainput/inplace_volumes.py @@ -1,4 +1,7 @@ import os +from pathlib import Path +from io import BytesIO +import json import pandas as pd import fmu.ensemble @@ -40,3 +43,14 @@ def extract_volumes(ensemble_paths, volfolder, volfiles) -> pd.DataFrame: f"Ensure that the files are present in relative folder {volfolder}" ) return pd.concat(dfs) + + +@CACHE.memoize(timeout=CACHE.TIMEOUT) +@webvizstore +def get_metadata(metadata: Path) -> BytesIO: + """Returns a json formatted dict stored in a BytesIO object""" + metadict = json.loads(metadata.read_text()) + bytesio = BytesIO() + bytesio.write(json.dumps(metadict).encode()) + bytesio.seek(0) + return bytesio diff --git a/webviz_subsurface/plugins/_inplace_volumes.py b/webviz_subsurface/plugins/_inplace_volumes.py index 2dd533325..62bf7a83e 100644 --- a/webviz_subsurface/plugins/_inplace_volumes.py +++ b/webviz_subsurface/plugins/_inplace_volumes.py @@ -1,5 +1,6 @@ -from uuid import uuid4 from pathlib import Path +import json +from typing import Optional import numpy as np import pandas as pd @@ -12,8 +13,11 @@ from webviz_config.webviz_store import webvizstore from webviz_config import WebvizPluginABC -from .._datainput.inplace_volumes import extract_volumes -from .._abbreviations.volume_terminology import volume_description, volume_unit +from .._datainput.inplace_volumes import extract_volumes, get_metadata +from .._abbreviations.volume_terminology import ( + volume_description, + column_title, +) from .._abbreviations.number_formatting import table_statistics_base @@ -38,7 +42,8 @@ class InplaceVolumes(WebvizPluginABC): **Common settings for both input options** * **`response`:** Optional volume response to visualize initially. - +* **`metadata`:** Optional path to volume response metadata stored in a json-file. +Supports descriptions and units. --- ?> The input files must follow FMU standards. @@ -50,6 +55,8 @@ class InplaceVolumes(WebvizPluginABC): (https://github.com/equinor/webviz-subsurface-testdata/blob/master/reek_history_match/\ realization-0/iter-0/share/results/volumes/geogrid--oil.csv). +* ADD PATH TO METADATAFILE + **The following columns will be used as available filters, if present:** * `ZONE` @@ -62,7 +69,8 @@ class InplaceVolumes(WebvizPluginABC): **Remaining columns are seen as volumetric responses.** All names are allowed (except those mentioned above, in addition to `REAL` and `ENSEMBLE`), \ -but the following responses are given more descriptive names automatically: +but the following responses are given more descriptive names automatically, unless another \ +description is given in `metadata`: * `BULK_OIL`: Bulk Volume (Oil) * `NET_OIL`: Net Volume (Oil) @@ -84,11 +92,12 @@ class InplaceVolumes(WebvizPluginABC): def __init__( self, app, - csvfile: Path = None, - ensembles: list = None, - volfiles: dict = None, + csvfile: Optional[Path] = None, + ensembles: Optional[list] = None, + volfiles: Optional[dict] = None, volfolder: str = "share/results/volumes", response: str = "STOIIP_OIL", + metadata: Optional[str] = None, ): super().__init__() @@ -118,8 +127,17 @@ def __init__( ) self.initial_response = response - self.uid = uuid4() - self.selectors_id = {x: str(uuid4()) for x in self.selectors} + if isinstance(metadata, str) and metadata.lower() == "metric": + self.metadata_path = None + self.metadata = metadata + else: + self.metadata_path = None if metadata is None else Path(metadata) + self.metadata = ( + self.metadata_path + if self.metadata_path is None + else json.load(get_metadata(self.metadata_path)) + ) + self.selectors_id = {x: self.uuid(x) for x in self.selectors} self.plotly_theme = app.webviz_settings["theme"].plotly_theme if len(self.volumes["ENSEMBLE"].unique()) > 1: self.initial_plot = "Box plot" @@ -129,26 +147,22 @@ def __init__( self.initial_group = None self.set_callbacks(app) - def ids(self, element): - """Generate unique id for dom element""" - return f"{element}-id-{self.uid}" - @property def tour_steps(self): return [ { - "id": self.ids("layout"), + "id": self.uuid("layout"), "content": ("Dashboard displaying in place volumetric results. "), }, { - "id": self.ids("graph"), + "id": self.uuid("graph"), "content": ( "Chart showing results for the current selection. " "Different charts and options can be selected from the menu above." ), }, { - "id": self.ids("table"), + "id": self.uuid("table"), "content": ( "The table shows statistics for the current active selection. " "Rows can be filtered by searching, and sorted by " @@ -156,11 +170,11 @@ def tour_steps(self): ), }, { - "id": self.ids("response"), + "id": self.uuid("response"), "content": "Select the volumetric calculation to display.", }, { - "id": self.ids("plot-type"), + "id": self.uuid("plot-type"), "content": ( "Controls the type of the visualized chart. " "Per realization shows bars per realization, " @@ -168,11 +182,11 @@ def tour_steps(self): ), }, { - "id": self.ids("group"), + "id": self.uuid("group"), "content": ("Allows grouping of results on a given category."), }, { - "id": self.ids("filters"), + "id": self.uuid("filters"), "content": ( "Filter on different combinations of e.g. zones, facies and regions " "(The options will vary dependent on what was included " @@ -182,10 +196,11 @@ def tour_steps(self): ] def add_webvizstore(self): - return ( - [(read_csv, [{"csv_file": self.csvfile}])] - if self.csvfile - else [ + functions = [] + if self.csvfile: + functions.append((read_csv, [{"csv_file": self.csvfile}])) + else: + functions.append( ( extract_volumes, [ @@ -196,8 +211,10 @@ def add_webvizstore(self): } ], ) - ] - ) + ) + if self.metadata_path is not None: + functions.append((get_metadata, [{"metadata": self.metadata_path}])) + return functions @property def vol_columns(self): @@ -231,9 +248,9 @@ def vol_callback_inputs(self): selector columns in the volumes dataframe """ inputs = [] - inputs.append(Input(self.ids("response"), "value")) - inputs.append(Input(self.ids("plot-type"), "value")) - inputs.append(Input(self.ids("group"), "value")) + inputs.append(Input(self.uuid("response"), "value")) + inputs.append(Input(self.uuid("plot-type"), "value")) + inputs.append(Input(self.uuid("group"), "value")) for selector in self.selectors: inputs.append(Input(self.selectors_id[selector], "value")) return inputs @@ -287,9 +304,12 @@ def plot_options_layout(self): children=[ html.Span("Response:", style={"font-weight": "bold"}), dcc.Dropdown( - id=self.ids("response"), + id=self.uuid("response"), options=[ - {"label": volume_description(i), "value": i} + { + "label": volume_description(i, self.metadata), + "value": i, + } for i in self.responses ], value=self.initial_response @@ -305,7 +325,7 @@ def plot_options_layout(self): children=[ html.Span("Plot type:", style={"font-weight": "bold"}), dcc.Dropdown( - id=self.ids("plot-type"), + id=self.uuid("plot-type"), options=[ {"label": i, "value": i} for i in self.plot_types ], @@ -320,7 +340,7 @@ def plot_options_layout(self): children=[ html.Span("Group by:", style={"font-weight": "bold"}), dcc.Dropdown( - id=self.ids("group"), + id=self.uuid("group"), options=[ {"label": i.lower().capitalize(), "value": i} for i in self.selectors @@ -340,14 +360,14 @@ def layout(self): return html.Div( children=[ wcc.FlexBox( - id=self.ids("layout"), + id=self.uuid("layout"), children=[ html.Div( style={"flex": 1}, children=[ html.Span("Filters:", style={"font-weight": "bold"}), html.Div( - id=self.ids("filters"), + id=self.uuid("filters"), children=self.selector_dropdowns, ), ], @@ -358,17 +378,17 @@ def layout(self): self.plot_options_layout, html.Div( style={"height": 400}, - children=wcc.Graph(id=self.ids("graph")), + children=wcc.Graph(id=self.uuid("graph")), ), html.Div( children=[ html.Div( - id=self.ids("table_title"), + id=self.uuid("table_title"), style={"textAlign": "center"}, children="", ), DataTable( - id=self.ids("table"), + id=self.uuid("table"), sort_action="native", filter_action="native", page_action="native", @@ -386,10 +406,10 @@ def layout(self): def set_callbacks(self, app): @app.callback( [ - Output(self.ids("graph"), "figure"), - Output(self.ids("table"), "data"), - Output(self.ids("table"), "columns"), - Output(self.ids("table_title"), "children"), + Output(self.uuid("graph"), "figure"), + Output(self.uuid("table"), "data"), + Output(self.uuid("table"), "columns"), + Output(self.uuid("table_title"), "children"), ], self.vol_callback_inputs, ) @@ -436,11 +456,16 @@ def _render_vol_chart(*args): return ( { "data": plot_traces, - "layout": plot_layout(plot_type, response, theme=self.plotly_theme), + "layout": plot_layout( + plot_type, + response, + theme=self.plotly_theme, + metadata=self.metadata, + ), }, table, columns, - f"{volume_description(response)} [{volume_unit(response)}]", + column_title(response, self.metadata), ) @app.callback( @@ -449,7 +474,7 @@ def _render_vol_chart(*args): Output(self.selectors_id["ENSEMBLE"], "value"), Output(self.selectors_id["ENSEMBLE"], "size"), ], - [Input(self.ids("group"), "value")], + [Input(self.uuid("group"), "value")], ) def _set_iteration_selector(group_by): """If iteration is selected as group by set the iteration @@ -469,7 +494,7 @@ def _set_iteration_selector(group_by): Output(self.selectors_id["SOURCE"], "value"), Output(self.selectors_id["SOURCE"], "size"), ], - [Input(self.ids("group"), "value")], + [Input(self.uuid("group"), "value")], ) def _set_source_selector(group_by): """If iteration is selected as group by set the iteration @@ -520,7 +545,7 @@ def plot_table(dframe, response, name): @CACHE.memoize(timeout=CACHE.TIMEOUT) -def plot_layout(plot_type, response, theme): +def plot_layout(plot_type, response, theme, metadata): layout = {} layout.update(theme["layout"]) layout["height"] = 400 @@ -530,27 +555,17 @@ def plot_layout(plot_type, response, theme): "barmode": "overlay", "bargap": 0.01, "bargroupgap": 0.2, - "xaxis": { - "title": f"{volume_description(response)} [{volume_unit(response)}]" - }, + "xaxis": {"title": column_title(response, metadata)}, "yaxis": {"title": "Count"}, } ) elif plot_type == "Box plot": - layout.update( - { - "yaxis": { - "title": f"{volume_description(response)} [{volume_unit(response)}]" - } - } - ) + layout.update({"yaxis": {"title": column_title(response, metadata)}}) else: layout.update( { "margin": {"l": 60, "r": 40, "b": 30, "t": 10}, - "yaxis": { - "title": f"{volume_description(response)} [{volume_unit(response)}]" - }, + "yaxis": {"title": column_title(response, metadata)}, "xaxis": {"title": "Realization"}, } ) diff --git a/webviz_subsurface/plugins/_inplace_volumes_onebyone.py b/webviz_subsurface/plugins/_inplace_volumes_onebyone.py index 19a5c30e6..594dd5ef8 100644 --- a/webviz_subsurface/plugins/_inplace_volumes_onebyone.py +++ b/webviz_subsurface/plugins/_inplace_volumes_onebyone.py @@ -1,5 +1,4 @@ import json -from uuid import uuid4 from pathlib import Path import numpy as np @@ -16,9 +15,13 @@ from webviz_config.webviz_store import webvizstore from .._private_plugins.tornado_plot import TornadoPlot -from .._datainput.inplace_volumes import extract_volumes +from .._datainput.inplace_volumes import extract_volumes, get_metadata from .._datainput.fmu_input import get_realizations, find_sens_type -from .._abbreviations.volume_terminology import volume_description, volume_unit +from .._abbreviations.volume_terminology import ( + volume_description, + volume_unit, + column_title, +) from .._abbreviations.number_formatting import table_statistics_base @@ -41,6 +44,7 @@ class InplaceVolumesOneByOne(WebvizPluginABC): E.g. `{geogrid: geogrid--oil.csv}`. * **`volfolder`:** Optional local folder for the `volfiles`. * **`response`:** Optional volume response to visualize initially. +* **`metadata`:** Optional volume response metadata. Supports descriptions and units. --- ?> The input files must follow FMU standards. @@ -55,6 +59,8 @@ class InplaceVolumesOneByOne(WebvizPluginABC): (https://github.com/equinor/webviz-subsurface-testdata/blob/master/reek_history_match/\ realization-0/iter-0/share/results/volumes/geogrid--oil.csv). +* ADD PATH TO METADATAFILE + The following columns will be used as available filters, if present: * `ZONE` @@ -67,7 +73,8 @@ class InplaceVolumesOneByOne(WebvizPluginABC): Remaining columns are seen as volumetric responses. All names are allowed (except those mentioned above, in addition to `REAL` and `ENSEMBLE`), \ -but the following responses are given more descriptive names automatically: +but the following responses are given more descriptive names automatically, unless another \ +description is given in `metadata`: * `BULK_OIL`: Bulk Volume (Oil) * `NET_OIL`: Net Volume (Oil) @@ -110,12 +117,13 @@ class InplaceVolumesOneByOne(WebvizPluginABC): def __init__( self, app, - csvfile_vol: Path = None, - csvfile_parameters: Path = None, - ensembles: list = None, - volfiles: dict = None, + csvfile_vol: Optional[Path] = None, + csvfile_parameters: Optional[Path] = None, + ensembles: Optional[list] = None, + volfiles: Optional[dict] = None, volfolder: str = "share/results/volumes", response: str = "STOIIP_OIL", + metadata: Optional[str] = None, ): super().__init__() @@ -155,20 +163,28 @@ def __init__( '"ensembles" and "volfiles"' ) self.initial_response = response - + if isinstance(metadata, str) and metadata.lower() == "metric": + self.metadata_path = None + self.metadata = metadata + else: + self.metadata_path = None if metadata is None else Path(metadata) + self.metadata = ( + self.metadata_path + if self.metadata_path is None + else json.load(get_metadata(self.metadata_path)) + ) # Merge into one dataframe # (TODO: Should raise error if not all ensembles have sensitivity data) self.volumes = pd.merge(volumes, parameters, on=["ENSEMBLE", "REAL"]) # Initialize a tornado plot. Data is added in callback self.tornadoplot = TornadoPlot(app, parameters, allow_click=True) - self.uid = uuid4() self.selectors_id = {x: self.uuid(x) for x in self.selectors} self.theme = app.webviz_settings["theme"] self.set_callbacks(app) def add_webvizstore(self): - return ( + functions = ( [ ( read_csv, @@ -201,6 +217,9 @@ def add_webvizstore(self): ), ] ) + if self.metadata is not None: + functions.append((get_metadata, [{"metadata": self.metadata_path}])) + return functions def selector(self, label, id_name, column): return html.Div( @@ -488,7 +507,7 @@ def _render_table_and_tornado(ensemble, response, source, *filters): .reset_index()[["REAL", response]] .values.tolist(), "number_format": "#.4g", - "unit": volume_unit(response), + "unit": volume_unit(response, self.metadata), } ) return tornado, table, columns @@ -556,7 +575,7 @@ def _render_chart( ) # Volume title: - volume_title = f"{volume_description(response)} [{volume_unit(response)}]" + volume_title = column_title(response, self.metadata) # Make Plotly figure layout = {}