webviz-subsurface 0.2.36__py3-none-any.whl → 0.2.38__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- webviz_subsurface/__init__.py +1 -1
- webviz_subsurface/_components/color_picker.py +1 -1
- webviz_subsurface/_datainput/well_completions.py +2 -1
- webviz_subsurface/_providers/ensemble_polygon_provider/__init__.py +3 -0
- webviz_subsurface/_providers/ensemble_polygon_provider/_polygon_discovery.py +97 -0
- webviz_subsurface/_providers/ensemble_polygon_provider/_provider_impl_file.py +226 -0
- webviz_subsurface/_providers/ensemble_polygon_provider/ensemble_polygon_provider.py +53 -0
- webviz_subsurface/_providers/ensemble_polygon_provider/ensemble_polygon_provider_factory.py +99 -0
- webviz_subsurface/_providers/ensemble_polygon_provider/polygon_server.py +125 -0
- webviz_subsurface/plugins/_co2_leakage/_plugin.py +577 -293
- webviz_subsurface/plugins/_co2_leakage/_types.py +7 -0
- webviz_subsurface/plugins/_co2_leakage/_utilities/_misc.py +9 -0
- webviz_subsurface/plugins/_co2_leakage/_utilities/callbacks.py +226 -186
- webviz_subsurface/plugins/_co2_leakage/_utilities/co2volume.py +591 -128
- webviz_subsurface/plugins/_co2_leakage/_utilities/containment_data_provider.py +147 -0
- webviz_subsurface/plugins/_co2_leakage/_utilities/containment_info.py +31 -0
- webviz_subsurface/plugins/_co2_leakage/_utilities/ensemble_well_picks.py +105 -0
- webviz_subsurface/plugins/_co2_leakage/_utilities/generic.py +170 -2
- webviz_subsurface/plugins/_co2_leakage/_utilities/initialization.py +199 -97
- webviz_subsurface/plugins/_co2_leakage/_utilities/polygon_handler.py +60 -0
- webviz_subsurface/plugins/_co2_leakage/_utilities/summary_graphs.py +77 -173
- webviz_subsurface/plugins/_co2_leakage/_utilities/surface_publishing.py +122 -21
- webviz_subsurface/plugins/_co2_leakage/_utilities/unsmry_data_provider.py +108 -0
- webviz_subsurface/plugins/_co2_leakage/views/mainview/mainview.py +44 -19
- webviz_subsurface/plugins/_co2_leakage/views/mainview/settings.py +944 -359
- {webviz_subsurface-0.2.36.dist-info → webviz_subsurface-0.2.38.dist-info}/METADATA +2 -2
- {webviz_subsurface-0.2.36.dist-info → webviz_subsurface-0.2.38.dist-info}/RECORD +33 -20
- {webviz_subsurface-0.2.36.dist-info → webviz_subsurface-0.2.38.dist-info}/WHEEL +1 -1
- /webviz_subsurface/plugins/_co2_leakage/_utilities/{fault_polygons.py → fault_polygons_handler.py} +0 -0
- {webviz_subsurface-0.2.36.dist-info → webviz_subsurface-0.2.38.dist-info}/LICENSE +0 -0
- {webviz_subsurface-0.2.36.dist-info → webviz_subsurface-0.2.38.dist-info}/LICENSE.chromedriver +0 -0
- {webviz_subsurface-0.2.36.dist-info → webviz_subsurface-0.2.38.dist-info}/entry_points.txt +0 -0
- {webviz_subsurface-0.2.36.dist-info → webviz_subsurface-0.2.38.dist-info}/top_level.txt +0 -0
|
@@ -2,21 +2,24 @@ import logging
|
|
|
2
2
|
from typing import Any, Dict, List, Optional, Tuple, Union
|
|
3
3
|
|
|
4
4
|
import plotly.graph_objects as go
|
|
5
|
-
from dash import Dash, Input, Output, State, callback, html, no_update
|
|
5
|
+
from dash import Dash, Input, Output, Patch, State, callback, ctx, html, no_update
|
|
6
6
|
from dash.exceptions import PreventUpdate
|
|
7
7
|
from webviz_config import WebvizPluginABC, WebvizSettings
|
|
8
8
|
from webviz_config.utils import StrEnum, callback_typecheck
|
|
9
9
|
|
|
10
10
|
from webviz_subsurface._providers import FaultPolygonsServer, SurfaceImageServer
|
|
11
|
+
from webviz_subsurface._providers.ensemble_polygon_provider import PolygonServer
|
|
11
12
|
from webviz_subsurface.plugins._co2_leakage._utilities.callbacks import (
|
|
12
13
|
SurfaceData,
|
|
13
14
|
create_map_annotations,
|
|
14
15
|
create_map_layers,
|
|
15
16
|
create_map_viewports,
|
|
16
17
|
derive_surface_address,
|
|
18
|
+
extract_legendonly,
|
|
17
19
|
generate_containment_figures,
|
|
18
20
|
generate_unsmry_figures,
|
|
19
21
|
get_plume_polygon,
|
|
22
|
+
make_plot_ids,
|
|
20
23
|
process_containment_info,
|
|
21
24
|
process_summed_mass,
|
|
22
25
|
process_visualization_info,
|
|
@@ -24,22 +27,28 @@ from webviz_subsurface.plugins._co2_leakage._utilities.callbacks import (
|
|
|
24
27
|
readable_name,
|
|
25
28
|
set_plot_ids,
|
|
26
29
|
)
|
|
27
|
-
from webviz_subsurface.plugins._co2_leakage._utilities.
|
|
30
|
+
from webviz_subsurface.plugins._co2_leakage._utilities.fault_polygons_handler import (
|
|
28
31
|
FaultPolygonsHandler,
|
|
29
32
|
)
|
|
30
33
|
from webviz_subsurface.plugins._co2_leakage._utilities.generic import (
|
|
34
|
+
BoundarySettings,
|
|
31
35
|
Co2MassScale,
|
|
32
36
|
Co2VolumeScale,
|
|
33
37
|
GraphSource,
|
|
34
38
|
MapAttribute,
|
|
39
|
+
MapThresholds,
|
|
40
|
+
MapType,
|
|
35
41
|
)
|
|
36
42
|
from webviz_subsurface.plugins._co2_leakage._utilities.initialization import (
|
|
43
|
+
init_containment_data_providers,
|
|
44
|
+
init_dictionary_of_content,
|
|
37
45
|
init_map_attribute_names,
|
|
38
46
|
init_menu_options,
|
|
47
|
+
init_polygon_provider_handlers,
|
|
48
|
+
init_realizations,
|
|
39
49
|
init_surface_providers,
|
|
40
|
-
|
|
50
|
+
init_unsmry_data_providers,
|
|
41
51
|
init_well_pick_provider,
|
|
42
|
-
process_files,
|
|
43
52
|
)
|
|
44
53
|
from webviz_subsurface.plugins._co2_leakage.views.mainview.mainview import (
|
|
45
54
|
MainView,
|
|
@@ -48,7 +57,9 @@ from webviz_subsurface.plugins._co2_leakage.views.mainview.mainview import (
|
|
|
48
57
|
from webviz_subsurface.plugins._co2_leakage.views.mainview.settings import ViewSettings
|
|
49
58
|
|
|
50
59
|
from . import _error
|
|
60
|
+
from ._types import LegendData
|
|
51
61
|
from ._utilities.color_tables import co2leakage_color_tables
|
|
62
|
+
from ._utilities.containment_info import StatisticsTabOption
|
|
52
63
|
|
|
53
64
|
LOGGER = logging.getLogger(__name__)
|
|
54
65
|
TABLES_PATH = "share/results/tables"
|
|
@@ -56,31 +67,89 @@ TABLES_PATH = "share/results/tables"
|
|
|
56
67
|
|
|
57
68
|
# pylint: disable=too-many-instance-attributes
|
|
58
69
|
class CO2Leakage(WebvizPluginABC):
|
|
59
|
-
"""
|
|
60
|
-
|
|
61
|
-
|
|
70
|
+
"""Plugin for analyzing CO2 leakage potential across multiple realizations in an
|
|
71
|
+
FMU ensemble
|
|
72
|
+
|
|
73
|
+
---
|
|
62
74
|
|
|
63
75
|
* **`ensembles`:** Which ensembles in `shared_settings` to visualize.
|
|
64
|
-
* **`file_containment_boundary`:** Path to a polygon representing the containment area
|
|
65
|
-
* **`file_hazardous_boundary`:** Path to a polygon representing the hazardous area
|
|
66
76
|
* **`well_pick_file`:** Path to a file containing well picks
|
|
67
|
-
* **`plume_mass_relpath`:** Path to a table of co2
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
* **`
|
|
73
|
-
* **`
|
|
74
|
-
polygons
|
|
75
|
-
* **`map_attribute_names`:** Dictionary for overriding the default mapping between
|
|
77
|
+
* **`plume_mass_relpath`:** Path to a table of co2 _containment data_ for co2 mass
|
|
78
|
+
* **`plume_actual_volume_relpath`:** Path to a table of co2 _containment data_ for co2
|
|
79
|
+
volume of type "actual"
|
|
80
|
+
* **`unsmry_relpath`:** Path to a csv/arrow version of a Cirrus/Eclipse unified summary
|
|
81
|
+
file
|
|
82
|
+
* **`fault_polygon_attribute`:** Polygons with this attribute are used as fault polygons
|
|
83
|
+
* **`map_attribute_names`:** Key-value pairs for overriding the default mapping between
|
|
76
84
|
attributes visualized by the plugin and attributes names used by
|
|
77
85
|
EnsembleSurfaceProvider
|
|
78
86
|
* **`initial_surface`:** Name of the surface/formation to show when the plugin is
|
|
79
|
-
launched. If
|
|
80
|
-
* **`map_surface_names_to_well_pick_names`:**
|
|
81
|
-
|
|
82
|
-
* **`map_surface_names_to_fault_polygons`:**
|
|
83
|
-
|
|
87
|
+
launched. If not provided, the first alphabetical surface is shown.
|
|
88
|
+
* **`map_surface_names_to_well_pick_names`:** Mapping between surface map names and
|
|
89
|
+
surface names used in the well pick file
|
|
90
|
+
* **`map_surface_names_to_fault_polygons`:** Mapping between surface map names and
|
|
91
|
+
surface names used by the fault polygons
|
|
92
|
+
* **`boundary_settings`:** Settings for polygons representing the containment and
|
|
93
|
+
hazardous areas
|
|
94
|
+
---
|
|
95
|
+
|
|
96
|
+
This plugin is tightly linked to the FMU CCS post-process available in the ccs-scripts
|
|
97
|
+
repository. If the ccs-scripts workflow is executed without alterations, there is no
|
|
98
|
+
need for any configuration except the `ensembles` keyword. If any steps of the
|
|
99
|
+
post-process are skipped, the plugin will exclude the respective results and
|
|
100
|
+
functionality.
|
|
101
|
+
|
|
102
|
+
Even though the workflow is standardized, it is sometimes necessary to override
|
|
103
|
+
specific settings. For all path settings, these are interpreted relative to the
|
|
104
|
+
realization root, but can also be absolute paths. Their default values are
|
|
105
|
+
- `well_pick_file`: `share/results/well_picks.csv`
|
|
106
|
+
- `plume_mass_relpath`: `share/results/tables/plume_mass.csv`
|
|
107
|
+
- `plume_actual_volume_relpath`: No value
|
|
108
|
+
- `unsmry_relpath`: No value
|
|
109
|
+
|
|
110
|
+
Fault polygons are assumed to be stored in the `share/results/polygons` folder, and
|
|
111
|
+
with a `dl_extracted_faultlines` attribute. This attribute can be overridden with
|
|
112
|
+
`fault_polygon_attribute`, but the relative path to the polygons cannot.
|
|
113
|
+
|
|
114
|
+
`map_attribute_names` can be used to override how attributes are mapped to specific
|
|
115
|
+
features of the plugin. For instance, the attribute `migration_time_sgas` is mapped to
|
|
116
|
+
the Migration Time (SGAS) visualization, but this can be overridden by specifying
|
|
117
|
+
```
|
|
118
|
+
map_attribute_names:
|
|
119
|
+
MIGRATION_TIME_SGAS: mig_time
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
The following keys are allowed: `MIGRATION_TIME_SGAS, MIGRATION_TIME_AMFG,
|
|
123
|
+
MIGRATION_TIME_XMF2, MAX_SGAS, MAX_AMFG, MAX_XMF2, MAX_SGSTRAND, MAX_SGTRH, MASS,
|
|
124
|
+
DISSOLVED, FREE, FREE_GAS, TRAPPED_GAS`
|
|
125
|
+
|
|
126
|
+
Well pick files and fault polygons might name surfaces differently than the ones
|
|
127
|
+
generated by the ccs-scripts workflow. The options
|
|
128
|
+
`map_surface_names_to_well_pick_names` and `map_surface_names_to_fault_polygons` can
|
|
129
|
+
be used to specify this mapping explicitly. For instance, if the well pick file
|
|
130
|
+
contains a surface called `top_sgas`, but the ccs-scripts workflow generated a
|
|
131
|
+
surface called `top_sgas_2`, this can be specified with
|
|
132
|
+
```
|
|
133
|
+
map_surface_names_to_well_pick_names:
|
|
134
|
+
top_sgas_2: top_sgas
|
|
135
|
+
```
|
|
136
|
+
Similar for `map_surface_names_to_fault_polygons`.
|
|
137
|
+
|
|
138
|
+
`boundary_settings` is the final override option, and it can be used to specify
|
|
139
|
+
polygons representing the containment and hazardous areas. By default, the polygons are
|
|
140
|
+
expected to be named:
|
|
141
|
+
- `share/results/polygons/containment--boundary.csv`
|
|
142
|
+
- `share/results/polygons/hazarduous--boundary.csv`
|
|
143
|
+
|
|
144
|
+
This corresponds to the following input:
|
|
145
|
+
```
|
|
146
|
+
boundary_settings:
|
|
147
|
+
polygon_file_pattern: share/results/polygons/*.csv
|
|
148
|
+
attribute: boundary
|
|
149
|
+
hazardous_name: hazardous
|
|
150
|
+
containment_name: containment
|
|
151
|
+
```
|
|
152
|
+
All four settings are optional, and if not specified, the default values are used.
|
|
84
153
|
"""
|
|
85
154
|
|
|
86
155
|
class Ids(StrEnum):
|
|
@@ -94,17 +163,16 @@ class CO2Leakage(WebvizPluginABC):
|
|
|
94
163
|
app: Dash,
|
|
95
164
|
webviz_settings: WebvizSettings,
|
|
96
165
|
ensembles: List[str],
|
|
97
|
-
file_containment_boundary: Optional[str] = None,
|
|
98
|
-
file_hazardous_boundary: Optional[str] = None,
|
|
99
166
|
well_pick_file: Optional[str] = None,
|
|
100
167
|
plume_mass_relpath: str = TABLES_PATH + "/plume_mass.csv",
|
|
101
|
-
plume_actual_volume_relpath: str =
|
|
168
|
+
plume_actual_volume_relpath: Optional[str] = None,
|
|
102
169
|
unsmry_relpath: Optional[str] = None,
|
|
103
170
|
fault_polygon_attribute: str = "dl_extracted_faultlines",
|
|
104
171
|
initial_surface: Optional[str] = None,
|
|
105
172
|
map_attribute_names: Optional[Dict[str, str]] = None,
|
|
106
173
|
map_surface_names_to_well_pick_names: Optional[Dict[str, str]] = None,
|
|
107
174
|
map_surface_names_to_fault_polygons: Optional[Dict[str, str]] = None,
|
|
175
|
+
boundary_settings: Optional[BoundarySettings] = None,
|
|
108
176
|
):
|
|
109
177
|
super().__init__()
|
|
110
178
|
self._error_message = ""
|
|
@@ -115,21 +183,14 @@ class CO2Leakage(WebvizPluginABC):
|
|
|
115
183
|
]
|
|
116
184
|
for ensemble_name in ensembles
|
|
117
185
|
}
|
|
118
|
-
(
|
|
119
|
-
containment_poly_dict,
|
|
120
|
-
hazardous_poly_dict,
|
|
121
|
-
well_pick_dict,
|
|
122
|
-
) = process_files(
|
|
123
|
-
file_containment_boundary,
|
|
124
|
-
file_hazardous_boundary,
|
|
125
|
-
well_pick_file,
|
|
126
|
-
ensemble_paths,
|
|
127
|
-
)
|
|
128
|
-
self._polygon_files = [containment_poly_dict, hazardous_poly_dict]
|
|
186
|
+
self._realizations_per_ensemble = init_realizations(ensemble_paths)
|
|
129
187
|
self._surface_server = SurfaceImageServer.instance(app)
|
|
130
188
|
self._polygons_server = FaultPolygonsServer.instance(app)
|
|
131
|
-
|
|
132
|
-
|
|
189
|
+
self._map_attribute_names = init_map_attribute_names(
|
|
190
|
+
webviz_settings, ensembles, map_attribute_names
|
|
191
|
+
)
|
|
192
|
+
self._map_thresholds = MapThresholds(self._map_attribute_names)
|
|
193
|
+
self._threshold_ids = list(self._map_thresholds.standard_thresholds.keys())
|
|
133
194
|
# Surfaces
|
|
134
195
|
self._ensemble_surface_providers = init_surface_providers(
|
|
135
196
|
webviz_settings, ensembles
|
|
@@ -145,25 +206,27 @@ class CO2Leakage(WebvizPluginABC):
|
|
|
145
206
|
for ens in ensembles
|
|
146
207
|
}
|
|
147
208
|
# CO2 containment
|
|
148
|
-
self._co2_table_providers =
|
|
209
|
+
self._co2_table_providers = init_containment_data_providers(
|
|
149
210
|
ensemble_paths,
|
|
150
211
|
plume_mass_relpath,
|
|
151
212
|
)
|
|
152
|
-
self._co2_actual_volume_table_providers =
|
|
213
|
+
self._co2_actual_volume_table_providers = init_containment_data_providers(
|
|
153
214
|
ensemble_paths,
|
|
154
215
|
plume_actual_volume_relpath,
|
|
155
216
|
)
|
|
156
|
-
self._unsmry_providers = (
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
217
|
+
self._unsmry_providers = init_unsmry_data_providers(
|
|
218
|
+
ensemble_paths,
|
|
219
|
+
unsmry_relpath,
|
|
220
|
+
)
|
|
221
|
+
self._polygon_handlers = init_polygon_provider_handlers(
|
|
222
|
+
PolygonServer.instance(app),
|
|
223
|
+
ensemble_paths,
|
|
224
|
+
boundary_settings,
|
|
163
225
|
)
|
|
164
226
|
# Well picks
|
|
165
227
|
self._well_pick_provider = init_well_pick_provider(
|
|
166
|
-
|
|
228
|
+
ensemble_paths,
|
|
229
|
+
well_pick_file,
|
|
167
230
|
map_surface_names_to_well_pick_names,
|
|
168
231
|
)
|
|
169
232
|
# Phase (in case of residual trapping), zone and region options
|
|
@@ -171,8 +234,11 @@ class CO2Leakage(WebvizPluginABC):
|
|
|
171
234
|
ensemble_paths,
|
|
172
235
|
self._co2_table_providers,
|
|
173
236
|
self._co2_actual_volume_table_providers,
|
|
174
|
-
|
|
175
|
-
|
|
237
|
+
self._unsmry_providers,
|
|
238
|
+
)
|
|
239
|
+
self._content = init_dictionary_of_content(
|
|
240
|
+
self._menu_options,
|
|
241
|
+
len(self._map_attribute_names.mapping) > 0,
|
|
176
242
|
)
|
|
177
243
|
except Exception as err:
|
|
178
244
|
self._error_message = f"Plugin initialization failed: {err}"
|
|
@@ -180,29 +246,36 @@ class CO2Leakage(WebvizPluginABC):
|
|
|
180
246
|
|
|
181
247
|
self._summed_co2: Dict[str, Any] = {}
|
|
182
248
|
self._visualization_info = {
|
|
183
|
-
"
|
|
249
|
+
"thresholds": self._map_thresholds.standard_thresholds,
|
|
184
250
|
"n_clicks": 0,
|
|
185
251
|
"change": False,
|
|
186
|
-
"unit": "
|
|
252
|
+
"unit": "tons",
|
|
187
253
|
}
|
|
188
254
|
self._color_tables = co2leakage_color_tables()
|
|
189
|
-
self._well_pick_names = {
|
|
190
|
-
ens:
|
|
191
|
-
|
|
255
|
+
self._well_pick_names: Dict[str, List[str]] = {
|
|
256
|
+
ens: (
|
|
257
|
+
self._well_pick_provider[ens].well_names
|
|
258
|
+
if ens in self._well_pick_provider
|
|
259
|
+
else []
|
|
260
|
+
)
|
|
261
|
+
for ens in ensembles
|
|
192
262
|
}
|
|
193
263
|
self.add_shared_settings_group(
|
|
194
264
|
ViewSettings(
|
|
195
265
|
ensemble_paths,
|
|
266
|
+
self._realizations_per_ensemble,
|
|
196
267
|
self._ensemble_surface_providers,
|
|
197
268
|
initial_surface,
|
|
198
269
|
self._map_attribute_names,
|
|
270
|
+
self._map_thresholds,
|
|
199
271
|
[c["name"] for c in self._color_tables], # type: ignore
|
|
200
272
|
self._well_pick_names,
|
|
201
273
|
self._menu_options,
|
|
274
|
+
self._content,
|
|
202
275
|
),
|
|
203
276
|
self.Ids.MAIN_SETTINGS,
|
|
204
277
|
)
|
|
205
|
-
self.add_view(MainView(self._color_tables), self.Ids.MAIN_VIEW)
|
|
278
|
+
self.add_view(MainView(self._color_tables, self._content), self.Ids.MAIN_VIEW)
|
|
206
279
|
|
|
207
280
|
@property
|
|
208
281
|
def layout(self) -> html.Div:
|
|
@@ -225,184 +298,119 @@ class CO2Leakage(WebvizPluginABC):
|
|
|
225
298
|
|
|
226
299
|
def _ensemble_dates(self, ens: str) -> List[str]:
|
|
227
300
|
surface_provider = self._ensemble_surface_providers[ens]
|
|
228
|
-
|
|
229
|
-
|
|
301
|
+
date_map_attribute = next(
|
|
302
|
+
(
|
|
303
|
+
k
|
|
304
|
+
for k in self._map_attribute_names.filtered_values
|
|
305
|
+
if MapType[k.name].value != "MIGRATION_TIME"
|
|
306
|
+
),
|
|
307
|
+
None,
|
|
308
|
+
)
|
|
309
|
+
att_name = (
|
|
310
|
+
self._map_attribute_names[date_map_attribute]
|
|
311
|
+
if date_map_attribute is not None
|
|
312
|
+
else None
|
|
313
|
+
)
|
|
314
|
+
dates = (
|
|
315
|
+
None
|
|
316
|
+
if att_name is None
|
|
317
|
+
else surface_provider.surface_dates_for_attribute(att_name)
|
|
318
|
+
)
|
|
230
319
|
if dates is None:
|
|
231
320
|
raise ValueError(f"Failed to fetch dates for attribute '{att_name}'")
|
|
232
321
|
return dates
|
|
233
322
|
|
|
234
323
|
# Might want to do some refactoring if this gets too big
|
|
235
|
-
# pylint: disable=too-many-statements
|
|
236
324
|
def _set_callbacks(self) -> None:
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
325
|
+
if self._content["any_table"]:
|
|
326
|
+
self._add_graph_callback()
|
|
327
|
+
self._add_legend_change_callback()
|
|
328
|
+
self._add_set_unit_list_callback()
|
|
329
|
+
self._add_time_plot_visibility_callback()
|
|
330
|
+
|
|
331
|
+
if self._content["maps"]:
|
|
332
|
+
self._add_set_dates_callback()
|
|
333
|
+
self._add_date_slider_visibility_callback()
|
|
334
|
+
self._add_set_well_options_callback()
|
|
335
|
+
self._add_create_map_callback()
|
|
336
|
+
self._add_options_dialog_callback()
|
|
337
|
+
self._add_thresholds_dialog_callback()
|
|
338
|
+
|
|
339
|
+
self._add_feedback_dialog_callback()
|
|
340
|
+
|
|
341
|
+
if self._content["maps"] and self._content["any_table"]:
|
|
342
|
+
self._add_resize_plot_callback()
|
|
343
|
+
|
|
344
|
+
def _add_resize_plot_callback(self) -> None:
|
|
240
345
|
@callback(
|
|
241
|
-
Output(self._view_component(MapViewElement.Ids.
|
|
242
|
-
Output(self._view_component(MapViewElement.Ids.
|
|
243
|
-
Output(
|
|
244
|
-
|
|
245
|
-
),
|
|
346
|
+
Output(self._view_component(MapViewElement.Ids.TOP_ELEMENT), "style"),
|
|
347
|
+
Output(self._view_component(MapViewElement.Ids.BOTTOM_ELEMENT), "style"),
|
|
348
|
+
Output(self._view_component(MapViewElement.Ids.BAR_PLOT), "style"),
|
|
349
|
+
Output(self._view_component(MapViewElement.Ids.TIME_PLOT), "style"),
|
|
350
|
+
Output(self._view_component(MapViewElement.Ids.STATISTICS_PLOT), "style"),
|
|
246
351
|
Input(self._settings_component(ViewSettings.Ids.ENSEMBLE), "value"),
|
|
352
|
+
Input(self._view_component(MapViewElement.Ids.SIZE_SLIDER), "value"),
|
|
353
|
+
State(self._view_component(MapViewElement.Ids.TOP_ELEMENT), "style"),
|
|
354
|
+
State(self._view_component(MapViewElement.Ids.BOTTOM_ELEMENT), "style"),
|
|
247
355
|
Input(self._settings_component(ViewSettings.Ids.GRAPH_SOURCE), "value"),
|
|
248
|
-
Input(self._settings_component(ViewSettings.Ids.CO2_SCALE), "value"),
|
|
249
|
-
Input(self._settings_component(ViewSettings.Ids.REALIZATION), "value"),
|
|
250
|
-
Input(self._settings_component(ViewSettings.Ids.Y_MIN_AUTO_GRAPH), "value"),
|
|
251
|
-
Input(self._settings_component(ViewSettings.Ids.Y_MIN_GRAPH), "value"),
|
|
252
|
-
Input(self._settings_component(ViewSettings.Ids.Y_MAX_AUTO_GRAPH), "value"),
|
|
253
|
-
Input(self._settings_component(ViewSettings.Ids.Y_MAX_GRAPH), "value"),
|
|
254
|
-
Input(self._settings_component(ViewSettings.Ids.ZONE), "value"),
|
|
255
|
-
Input(self._settings_component(ViewSettings.Ids.REGION), "value"),
|
|
256
|
-
Input(self._settings_component(ViewSettings.Ids.PHASE), "value"),
|
|
257
|
-
Input(self._settings_component(ViewSettings.Ids.CONTAINMENT), "value"),
|
|
258
|
-
Input(self._settings_component(ViewSettings.Ids.COLOR_BY), "value"),
|
|
259
|
-
Input(self._settings_component(ViewSettings.Ids.MARK_BY), "value"),
|
|
260
|
-
Input(self._settings_component(ViewSettings.Ids.SORT_PLOT), "value"),
|
|
261
356
|
)
|
|
262
|
-
|
|
263
|
-
def update_graphs(
|
|
357
|
+
def resize_plots(
|
|
264
358
|
ensemble: str,
|
|
359
|
+
slider_value: float,
|
|
360
|
+
top_style: Dict,
|
|
361
|
+
bottom_style: Dict,
|
|
265
362
|
source: GraphSource,
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
y_min_val: Optional[float],
|
|
270
|
-
y_max_auto: List[str],
|
|
271
|
-
y_max_val: Optional[float],
|
|
272
|
-
zone: Optional[str],
|
|
273
|
-
region: Optional[str],
|
|
274
|
-
phase: str,
|
|
275
|
-
containment: str,
|
|
276
|
-
color_choice: str,
|
|
277
|
-
mark_choice: Optional[str],
|
|
278
|
-
sorting: str,
|
|
279
|
-
) -> Tuple[Dict, go.Figure, go.Figure, go.Figure]:
|
|
280
|
-
# pylint: disable=too-many-locals
|
|
281
|
-
figs = [no_update] * 3
|
|
282
|
-
cont_info = process_containment_info(
|
|
283
|
-
zone,
|
|
284
|
-
region,
|
|
285
|
-
phase,
|
|
286
|
-
containment,
|
|
287
|
-
color_choice,
|
|
288
|
-
mark_choice,
|
|
289
|
-
sorting,
|
|
290
|
-
self._menu_options[ensemble][source],
|
|
291
|
-
)
|
|
292
|
-
if source in [
|
|
293
|
-
GraphSource.CONTAINMENT_MASS,
|
|
294
|
-
GraphSource.CONTAINMENT_ACTUAL_VOLUME,
|
|
295
|
-
]:
|
|
296
|
-
y_limits = [
|
|
297
|
-
y_min_val if len(y_min_auto) == 0 else None,
|
|
298
|
-
y_max_val if len(y_max_auto) == 0 else None,
|
|
299
|
-
]
|
|
300
|
-
if (
|
|
301
|
-
source == GraphSource.CONTAINMENT_MASS
|
|
302
|
-
and ensemble in self._co2_table_providers
|
|
303
|
-
):
|
|
304
|
-
figs[: len(figs)] = generate_containment_figures(
|
|
305
|
-
self._co2_table_providers[ensemble],
|
|
306
|
-
co2_scale,
|
|
307
|
-
realizations[0],
|
|
308
|
-
y_limits,
|
|
309
|
-
cont_info,
|
|
310
|
-
)
|
|
311
|
-
elif (
|
|
312
|
-
source == GraphSource.CONTAINMENT_ACTUAL_VOLUME
|
|
313
|
-
and ensemble in self._co2_actual_volume_table_providers
|
|
314
|
-
):
|
|
315
|
-
figs[: len(figs)] = generate_containment_figures(
|
|
316
|
-
self._co2_actual_volume_table_providers[ensemble],
|
|
317
|
-
co2_scale,
|
|
318
|
-
realizations[0],
|
|
319
|
-
y_limits,
|
|
320
|
-
cont_info,
|
|
321
|
-
)
|
|
322
|
-
set_plot_ids(figs, source, co2_scale, cont_info, realizations)
|
|
323
|
-
elif source == GraphSource.UNSMRY:
|
|
324
|
-
if self._unsmry_providers is not None:
|
|
325
|
-
if ensemble in self._unsmry_providers:
|
|
326
|
-
u_figs = generate_unsmry_figures(
|
|
327
|
-
self._unsmry_providers[ensemble],
|
|
328
|
-
co2_scale,
|
|
329
|
-
self._co2_table_providers[ensemble],
|
|
330
|
-
)
|
|
331
|
-
figs = list(u_figs)
|
|
332
|
-
else:
|
|
333
|
-
LOGGER.warning(
|
|
334
|
-
"""UNSMRY file has not been specified as input.
|
|
335
|
-
Please use unsmry_relpath in the configuration."""
|
|
336
|
-
)
|
|
337
|
-
return figs # type: ignore
|
|
363
|
+
) -> List[Dict]:
|
|
364
|
+
bottom_style["height"] = f"{slider_value}vh"
|
|
365
|
+
top_style["height"] = f"{80 - slider_value}vh"
|
|
338
366
|
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
for i, d in enumerate(date_list)
|
|
355
|
-
}
|
|
356
|
-
return dates, max(dates.keys())
|
|
367
|
+
styles = [{"height": f"{slider_value * 0.9 - 4}vh", "width": "90%"}] * 3
|
|
368
|
+
if source == GraphSource.UNSMRY and self._unsmry_providers is None:
|
|
369
|
+
styles = [{"display": "none"}] * 3
|
|
370
|
+
elif (
|
|
371
|
+
source == GraphSource.CONTAINMENT_MASS
|
|
372
|
+
and ensemble not in self._co2_table_providers
|
|
373
|
+
):
|
|
374
|
+
styles = [{"display": "none"}] * 3
|
|
375
|
+
elif (
|
|
376
|
+
source == GraphSource.CONTAINMENT_ACTUAL_VOLUME
|
|
377
|
+
and ensemble not in self._co2_actual_volume_table_providers
|
|
378
|
+
):
|
|
379
|
+
styles = [{"display": "none"}] * 3
|
|
380
|
+
|
|
381
|
+
return [top_style, bottom_style] + styles
|
|
357
382
|
|
|
383
|
+
def _add_feedback_dialog_callback(self) -> None:
|
|
358
384
|
@callback(
|
|
359
|
-
Output(
|
|
360
|
-
Input(
|
|
385
|
+
Output(ViewSettings.Ids.FEEDBACK, "open"),
|
|
386
|
+
Input(ViewSettings.Ids.FEEDBACK_BUTTON, "n_clicks"),
|
|
361
387
|
)
|
|
362
|
-
def
|
|
363
|
-
if
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
]:
|
|
367
|
-
return {"display": "none"}
|
|
368
|
-
return {}
|
|
388
|
+
def open_close_feedback(_n_clicks: Optional[int]) -> bool:
|
|
389
|
+
if _n_clicks is not None:
|
|
390
|
+
return _n_clicks > 0
|
|
391
|
+
raise PreventUpdate
|
|
369
392
|
|
|
393
|
+
def _add_thresholds_dialog_callback(self) -> None:
|
|
370
394
|
@callback(
|
|
371
|
-
Output(
|
|
372
|
-
|
|
373
|
-
Input(self._settings_component(ViewSettings.Ids.GRAPH_SOURCE), "value"),
|
|
395
|
+
Output(ViewSettings.Ids.VISUALIZATION_THRESHOLD_DIALOG, "open"),
|
|
396
|
+
Input(ViewSettings.Ids.VISUALIZATION_THRESHOLD_BUTTON, "n_clicks"),
|
|
374
397
|
)
|
|
375
|
-
def
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
return list(Co2VolumeScale), Co2VolumeScale.BILLION_CUBIC_METERS
|
|
380
|
-
return list(Co2MassScale), Co2MassScale.MTONS
|
|
398
|
+
def open_close_thresholds(_n_clicks: Optional[int]) -> bool:
|
|
399
|
+
if _n_clicks is not None:
|
|
400
|
+
return _n_clicks > 0
|
|
401
|
+
raise PreventUpdate
|
|
381
402
|
|
|
403
|
+
def _add_options_dialog_callback(self) -> None:
|
|
382
404
|
@callback(
|
|
383
|
-
Output(ViewSettings.Ids.
|
|
384
|
-
|
|
385
|
-
Output(ViewSettings.Ids.OPTIONS_DIALOG_WELL_FILTER, "style"),
|
|
386
|
-
Output(ViewSettings.Ids.WELL_FILTER_HEADER, "style"),
|
|
387
|
-
Input(self._settings_component(ViewSettings.Ids.ENSEMBLE), "value"),
|
|
405
|
+
Output(ViewSettings.Ids.OPTIONS_DIALOG, "open"),
|
|
406
|
+
Input(ViewSettings.Ids.OPTIONS_DIALOG_BUTTON, "n_clicks"),
|
|
388
407
|
)
|
|
389
|
-
def
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
[{"label": i, "value": i} for i in self._well_pick_names[ensemble]],
|
|
394
|
-
self._well_pick_names[ensemble],
|
|
395
|
-
{
|
|
396
|
-
"display": "block" if self._well_pick_names[ensemble] else "none",
|
|
397
|
-
"height": f"{len(self._well_pick_names[ensemble]) * 22}px",
|
|
398
|
-
},
|
|
399
|
-
{
|
|
400
|
-
"flex": 3,
|
|
401
|
-
"minWidth": "20px",
|
|
402
|
-
"display": "block" if self._well_pick_names[ensemble] else "none",
|
|
403
|
-
},
|
|
404
|
-
)
|
|
408
|
+
def open_close_options_dialog(_n_clicks: Optional[int]) -> bool:
|
|
409
|
+
if _n_clicks is not None:
|
|
410
|
+
return _n_clicks > 0
|
|
411
|
+
raise PreventUpdate
|
|
405
412
|
|
|
413
|
+
def _add_create_map_callback(self) -> None:
|
|
406
414
|
# Cannot avoid many arguments and/or locals since all layers of the DeckGL map
|
|
407
415
|
# need to be updated simultaneously
|
|
408
416
|
# pylint: disable=too-many-arguments,too-many-locals
|
|
@@ -410,31 +418,70 @@ class CO2Leakage(WebvizPluginABC):
|
|
|
410
418
|
Output(self._view_component(MapViewElement.Ids.DECKGL_MAP), "layers"),
|
|
411
419
|
Output(self._view_component(MapViewElement.Ids.DECKGL_MAP), "children"),
|
|
412
420
|
Output(self._view_component(MapViewElement.Ids.DECKGL_MAP), "views"),
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
421
|
+
inputs={
|
|
422
|
+
"attribute": Input(
|
|
423
|
+
self._settings_component(ViewSettings.Ids.PROPERTY), "value"
|
|
424
|
+
),
|
|
425
|
+
"date": Input(
|
|
426
|
+
self._view_component(MapViewElement.Ids.DATE_SLIDER), "value"
|
|
427
|
+
),
|
|
428
|
+
"formation": Input(
|
|
429
|
+
self._settings_component(ViewSettings.Ids.FORMATION), "value"
|
|
430
|
+
),
|
|
431
|
+
"realization": Input(
|
|
432
|
+
self._settings_component(ViewSettings.Ids.REALIZATION), "value"
|
|
433
|
+
),
|
|
434
|
+
"statistic": Input(
|
|
435
|
+
self._settings_component(ViewSettings.Ids.STATISTIC), "value"
|
|
436
|
+
),
|
|
437
|
+
"color_map_name": Input(
|
|
438
|
+
self._settings_component(ViewSettings.Ids.COLOR_SCALE), "value"
|
|
439
|
+
),
|
|
440
|
+
"cm_min_auto": Input(
|
|
441
|
+
self._settings_component(ViewSettings.Ids.CM_MIN_AUTO), "value"
|
|
442
|
+
),
|
|
443
|
+
"cm_min_val": Input(
|
|
444
|
+
self._settings_component(ViewSettings.Ids.CM_MIN), "value"
|
|
445
|
+
),
|
|
446
|
+
"cm_max_auto": Input(
|
|
447
|
+
self._settings_component(ViewSettings.Ids.CM_MAX_AUTO), "value"
|
|
448
|
+
),
|
|
449
|
+
"cm_max_val": Input(
|
|
450
|
+
self._settings_component(ViewSettings.Ids.CM_MAX), "value"
|
|
451
|
+
),
|
|
452
|
+
"plume_threshold": Input(
|
|
453
|
+
self._settings_component(ViewSettings.Ids.PLUME_THRESHOLD),
|
|
454
|
+
"value",
|
|
455
|
+
),
|
|
456
|
+
"plume_smoothing": Input(
|
|
457
|
+
self._settings_component(ViewSettings.Ids.PLUME_SMOOTHING),
|
|
458
|
+
"value",
|
|
459
|
+
),
|
|
460
|
+
"visualization_update": Input(
|
|
461
|
+
self._settings_component(ViewSettings.Ids.VISUALIZATION_UPDATE),
|
|
462
|
+
"n_clicks",
|
|
463
|
+
),
|
|
464
|
+
"mass_unit": Input(
|
|
465
|
+
self._settings_component(ViewSettings.Ids.MASS_UNIT), "value"
|
|
466
|
+
),
|
|
467
|
+
"mass_unit_update": Input(
|
|
468
|
+
self._settings_component(ViewSettings.Ids.MASS_UNIT_UPDATE),
|
|
469
|
+
"n_clicks",
|
|
470
|
+
),
|
|
471
|
+
"options_dialog_options": Input(
|
|
472
|
+
ViewSettings.Ids.OPTIONS_DIALOG_OPTIONS, "value"
|
|
473
|
+
),
|
|
474
|
+
"selected_wells": Input(
|
|
475
|
+
ViewSettings.Ids.OPTIONS_DIALOG_WELL_FILTER, "value"
|
|
476
|
+
),
|
|
477
|
+
"ensemble": Input(
|
|
478
|
+
self._settings_component(ViewSettings.Ids.ENSEMBLE), "value"
|
|
479
|
+
),
|
|
480
|
+
"current_views": State(
|
|
481
|
+
self._view_component(MapViewElement.Ids.DECKGL_MAP), "views"
|
|
482
|
+
),
|
|
483
|
+
"thresholds": [Input(id, "value") for id in self._threshold_ids],
|
|
484
|
+
},
|
|
438
485
|
)
|
|
439
486
|
def update_map_attribute(
|
|
440
487
|
attribute: MapAttribute,
|
|
@@ -449,32 +496,39 @@ class CO2Leakage(WebvizPluginABC):
|
|
|
449
496
|
cm_max_val: Optional[float],
|
|
450
497
|
plume_threshold: Optional[float],
|
|
451
498
|
plume_smoothing: Optional[float],
|
|
452
|
-
visualization_threshold: Optional[float],
|
|
453
499
|
visualization_update: int,
|
|
454
500
|
mass_unit: str,
|
|
501
|
+
mass_unit_update: int,
|
|
455
502
|
options_dialog_options: List[int],
|
|
456
503
|
selected_wells: List[str],
|
|
457
504
|
ensemble: str,
|
|
458
505
|
current_views: List[Any],
|
|
459
|
-
|
|
506
|
+
thresholds: List[float],
|
|
507
|
+
) -> Tuple[List[Dict[Any, Any]], Optional[List[Any]], Dict[Any, Any]]:
|
|
460
508
|
# Unable to clear cache (when needed) without the protected member
|
|
461
509
|
# pylint: disable=protected-access
|
|
510
|
+
current_thresholds = dict(zip(self._threshold_ids, thresholds))
|
|
511
|
+
assert visualization_update >= 0 # Need the input to trigger callback
|
|
512
|
+
assert mass_unit_update >= 0 # These are just to silence pylint
|
|
462
513
|
self._visualization_info = process_visualization_info(
|
|
463
|
-
|
|
464
|
-
|
|
514
|
+
attribute,
|
|
515
|
+
current_thresholds,
|
|
465
516
|
mass_unit,
|
|
466
517
|
self._visualization_info,
|
|
467
518
|
self._surface_server._image_cache,
|
|
468
519
|
)
|
|
469
520
|
if self._visualization_info["change"]:
|
|
470
|
-
return [],
|
|
521
|
+
return [], None, no_update
|
|
471
522
|
attribute = MapAttribute(attribute)
|
|
472
523
|
if len(realization) == 0 or ensemble is None:
|
|
473
524
|
raise PreventUpdate
|
|
474
|
-
|
|
525
|
+
if isinstance(date, int):
|
|
526
|
+
datestr = self._ensemble_dates(ensemble)[date]
|
|
527
|
+
elif date is None:
|
|
528
|
+
datestr = None
|
|
475
529
|
# Contour data
|
|
476
530
|
contour_data = None
|
|
477
|
-
if attribute
|
|
531
|
+
if MapType[MapAttribute(attribute).name].value == "PLUME":
|
|
478
532
|
contour_data = {
|
|
479
533
|
"property": property_origin(attribute, self._map_attribute_names),
|
|
480
534
|
"threshold": plume_threshold,
|
|
@@ -525,18 +579,23 @@ class CO2Leakage(WebvizPluginABC):
|
|
|
525
579
|
contour_data,
|
|
526
580
|
)
|
|
527
581
|
# Create layers and view bounds
|
|
582
|
+
fault_polygon_url = self._fault_polygon_handlers[
|
|
583
|
+
ensemble
|
|
584
|
+
].extract_fault_polygon_url(formation, realization)
|
|
585
|
+
hazardous_polygon_url = self._polygon_handlers[
|
|
586
|
+
ensemble
|
|
587
|
+
].extract_hazardous_poly_url(realization)
|
|
588
|
+
containment_polygon_url = self._polygon_handlers[
|
|
589
|
+
ensemble
|
|
590
|
+
].extract_containment_poly_url(realization)
|
|
528
591
|
layers = create_map_layers(
|
|
592
|
+
realizations=realization,
|
|
529
593
|
formation=formation,
|
|
530
594
|
surface_data=surf_data,
|
|
531
|
-
fault_polygon_url=
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
)
|
|
536
|
-
),
|
|
537
|
-
file_containment_boundary=self._polygon_files[0][ensemble],
|
|
538
|
-
file_hazardous_boundary=self._polygon_files[1][ensemble],
|
|
539
|
-
well_pick_provider=self._well_pick_provider[ensemble],
|
|
595
|
+
fault_polygon_url=fault_polygon_url,
|
|
596
|
+
containment_bounds_url=containment_polygon_url,
|
|
597
|
+
haz_bounds_url=hazardous_polygon_url,
|
|
598
|
+
well_pick_provider=self._well_pick_provider.get(ensemble, None),
|
|
540
599
|
plume_extent_data=plume_polygon,
|
|
541
600
|
options_dialog_options=options_dialog_options,
|
|
542
601
|
selected_wells=selected_wells,
|
|
@@ -551,60 +610,285 @@ class CO2Leakage(WebvizPluginABC):
|
|
|
551
610
|
viewports = no_update if current_views else create_map_viewports()
|
|
552
611
|
return layers, annotations, viewports
|
|
553
612
|
|
|
613
|
+
def _add_set_well_options_callback(self) -> None:
|
|
554
614
|
@callback(
|
|
555
|
-
Output(ViewSettings.Ids.
|
|
556
|
-
|
|
615
|
+
Output(ViewSettings.Ids.OPTIONS_DIALOG_WELL_FILTER, "options"),
|
|
616
|
+
Output(ViewSettings.Ids.OPTIONS_DIALOG_WELL_FILTER, "value"),
|
|
617
|
+
Output(ViewSettings.Ids.OPTIONS_DIALOG_WELL_FILTER, "style"),
|
|
618
|
+
Output(ViewSettings.Ids.WELL_FILTER_HEADER, "style"),
|
|
619
|
+
Input(self._settings_component(ViewSettings.Ids.ENSEMBLE), "value"),
|
|
557
620
|
)
|
|
558
|
-
def
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
621
|
+
def set_well_options(
|
|
622
|
+
ensemble: str,
|
|
623
|
+
) -> Tuple[List[Any], List[str], Dict[Any, Any], Dict[Any, Any]]:
|
|
624
|
+
return (
|
|
625
|
+
[{"label": i, "value": i} for i in self._well_pick_names[ensemble]],
|
|
626
|
+
self._well_pick_names[ensemble],
|
|
627
|
+
{
|
|
628
|
+
"display": ("block" if self._well_pick_names[ensemble] else "none"),
|
|
629
|
+
"height": f"{len(self._well_pick_names[ensemble]) * 22}px",
|
|
630
|
+
},
|
|
631
|
+
{
|
|
632
|
+
"flex": 3,
|
|
633
|
+
"minWidth": "20px",
|
|
634
|
+
"display": ("block" if self._well_pick_names[ensemble] else "none"),
|
|
635
|
+
},
|
|
636
|
+
)
|
|
562
637
|
|
|
638
|
+
def _add_date_slider_visibility_callback(self) -> None:
|
|
563
639
|
@callback(
|
|
564
|
-
Output(
|
|
565
|
-
Input(ViewSettings.Ids.
|
|
640
|
+
Output(self._view_component(MapViewElement.Ids.DATE_WRAPPER), "style"),
|
|
641
|
+
Input(self._settings_component(ViewSettings.Ids.PROPERTY), "value"),
|
|
566
642
|
)
|
|
567
|
-
def
|
|
568
|
-
if
|
|
569
|
-
return
|
|
570
|
-
|
|
643
|
+
def toggle_date_slider(attribute: str) -> Dict[str, str]:
|
|
644
|
+
if MapType[MapAttribute(attribute).name].value == "MIGRATION_TIME":
|
|
645
|
+
return {"display": "none"}
|
|
646
|
+
return {}
|
|
571
647
|
|
|
648
|
+
def _add_set_dates_callback(self) -> None:
|
|
572
649
|
@callback(
|
|
573
|
-
Output(self._view_component(MapViewElement.Ids.
|
|
574
|
-
Output(self._view_component(MapViewElement.Ids.
|
|
575
|
-
|
|
576
|
-
|
|
650
|
+
Output(self._view_component(MapViewElement.Ids.DATE_SLIDER), "marks"),
|
|
651
|
+
Output(self._view_component(MapViewElement.Ids.DATE_SLIDER), "value"),
|
|
652
|
+
Input(self._settings_component(ViewSettings.Ids.ENSEMBLE), "value"),
|
|
653
|
+
)
|
|
654
|
+
def set_dates(
|
|
655
|
+
ensemble: str,
|
|
656
|
+
) -> Tuple[Dict[int, Dict[str, Any]], Optional[int]]:
|
|
657
|
+
if ensemble is None:
|
|
658
|
+
return {}, None
|
|
659
|
+
# Dates
|
|
660
|
+
date_list = self._ensemble_dates(ensemble)
|
|
661
|
+
dates = {
|
|
662
|
+
i: {
|
|
663
|
+
"label": f"{d[:4]}",
|
|
664
|
+
"style": {"writingMode": "vertical-rl"},
|
|
665
|
+
}
|
|
666
|
+
for i, d in enumerate(date_list)
|
|
667
|
+
}
|
|
668
|
+
if len(dates.keys()) > 0:
|
|
669
|
+
return dates, max(dates.keys())
|
|
670
|
+
return dates, None
|
|
671
|
+
|
|
672
|
+
def _add_time_plot_visibility_callback(self) -> None:
|
|
673
|
+
@callback(
|
|
674
|
+
Output(self._settings_component(ViewSettings.Ids.REAL_OR_STAT), "style"),
|
|
675
|
+
Output(self._settings_component(ViewSettings.Ids.Y_LIM_OPTIONS), "style"),
|
|
676
|
+
Input(self._settings_component(ViewSettings.Ids.REALIZATION), "value"),
|
|
677
|
+
)
|
|
678
|
+
def toggle_time_plot_options_visibility(
|
|
679
|
+
realizations: List[int],
|
|
680
|
+
) -> Tuple[Dict[str, str], Dict[str, str]]:
|
|
681
|
+
if len(realizations) == 1:
|
|
682
|
+
return (
|
|
683
|
+
{"display": "none"},
|
|
684
|
+
{"display": "flex", "flex-direction": "column"},
|
|
685
|
+
)
|
|
686
|
+
return (
|
|
687
|
+
{"display": "flex", "flex-direction": "row"},
|
|
688
|
+
{"display": "none"},
|
|
689
|
+
)
|
|
690
|
+
|
|
691
|
+
def _add_set_unit_list_callback(self) -> None:
|
|
692
|
+
@callback(
|
|
693
|
+
Output(self._settings_component(ViewSettings.Ids.CO2_SCALE), "options"),
|
|
694
|
+
Output(self._settings_component(ViewSettings.Ids.CO2_SCALE), "value"),
|
|
695
|
+
Input(self._settings_component(ViewSettings.Ids.GRAPH_SOURCE), "value"),
|
|
696
|
+
)
|
|
697
|
+
def make_unit_list(
|
|
698
|
+
attribute: str,
|
|
699
|
+
) -> Union[Tuple[List[Any], Co2MassScale], Tuple[List[Any], Co2VolumeScale],]:
|
|
700
|
+
if attribute == GraphSource.CONTAINMENT_ACTUAL_VOLUME:
|
|
701
|
+
return list(Co2VolumeScale), Co2VolumeScale.BILLION_CUBIC_METERS
|
|
702
|
+
return list(Co2MassScale), Co2MassScale.MTONS
|
|
703
|
+
|
|
704
|
+
def _add_graph_callback(self) -> None:
|
|
705
|
+
# Cannot avoid many arguments since all the parameters are needed
|
|
706
|
+
# to determine what to plot
|
|
707
|
+
# pylint: disable=too-many-arguments
|
|
708
|
+
@callback(
|
|
709
|
+
Output(self._view_component(MapViewElement.Ids.BAR_PLOT), "figure"),
|
|
710
|
+
Output(self._view_component(MapViewElement.Ids.TIME_PLOT), "figure"),
|
|
577
711
|
Output(
|
|
578
|
-
self._view_component(MapViewElement.Ids.
|
|
712
|
+
self._view_component(MapViewElement.Ids.STATISTICS_PLOT),
|
|
713
|
+
"figure",
|
|
579
714
|
),
|
|
715
|
+
# LEGEND_DATA_STORE is updated whenever the legend is clicked. However,
|
|
716
|
+
# there is not need to update the plots based on this change, since that
|
|
717
|
+
# is done by plotly internally. We therefore use State instead of Input
|
|
718
|
+
State(self._view_component(MapViewElement.Ids.LEGEND_DATA_STORE), "data"),
|
|
580
719
|
Input(self._settings_component(ViewSettings.Ids.ENSEMBLE), "value"),
|
|
581
|
-
Input(self._view_component(MapViewElement.Ids.SIZE_SLIDER), "value"),
|
|
582
|
-
State(self._view_component(MapViewElement.Ids.TOP_ELEMENT), "style"),
|
|
583
|
-
State(self._view_component(MapViewElement.Ids.BOTTOM_ELEMENT), "style"),
|
|
584
720
|
Input(self._settings_component(ViewSettings.Ids.GRAPH_SOURCE), "value"),
|
|
721
|
+
Input(self._settings_component(ViewSettings.Ids.CO2_SCALE), "value"),
|
|
722
|
+
Input(self._settings_component(ViewSettings.Ids.REALIZATION), "value"),
|
|
723
|
+
Input(self._settings_component(ViewSettings.Ids.Y_MIN_AUTO_GRAPH), "value"),
|
|
724
|
+
Input(self._settings_component(ViewSettings.Ids.Y_MIN_GRAPH), "value"),
|
|
725
|
+
Input(self._settings_component(ViewSettings.Ids.Y_MAX_AUTO_GRAPH), "value"),
|
|
726
|
+
Input(self._settings_component(ViewSettings.Ids.Y_MAX_GRAPH), "value"),
|
|
727
|
+
Input(self._settings_component(ViewSettings.Ids.ZONE), "value"),
|
|
728
|
+
Input(self._settings_component(ViewSettings.Ids.REGION), "value"),
|
|
729
|
+
Input(self._settings_component(ViewSettings.Ids.PHASE), "value"),
|
|
730
|
+
Input(self._settings_component(ViewSettings.Ids.CONTAINMENT), "value"),
|
|
731
|
+
Input(self._settings_component(ViewSettings.Ids.PLUME_GROUP), "value"),
|
|
732
|
+
Input(self._settings_component(ViewSettings.Ids.COLOR_BY), "value"),
|
|
733
|
+
Input(self._settings_component(ViewSettings.Ids.MARK_BY), "value"),
|
|
734
|
+
Input(self._settings_component(ViewSettings.Ids.SORT_PLOT), "value"),
|
|
735
|
+
Input(self._settings_component(ViewSettings.Ids.REAL_OR_STAT), "value"),
|
|
736
|
+
Input(self._settings_component(ViewSettings.Ids.DATE_OPTION), "value"),
|
|
737
|
+
Input(
|
|
738
|
+
self._settings_component(ViewSettings.Ids.STATISTICS_TAB_OPTION),
|
|
739
|
+
"value",
|
|
740
|
+
),
|
|
741
|
+
Input(self._settings_component(ViewSettings.Ids.BOX_SHOW_POINTS), "value"),
|
|
585
742
|
)
|
|
586
|
-
|
|
743
|
+
@callback_typecheck
|
|
744
|
+
def update_graphs(
|
|
745
|
+
legend_data: LegendData,
|
|
587
746
|
ensemble: str,
|
|
588
|
-
slider_value: float,
|
|
589
|
-
top_style: Dict,
|
|
590
|
-
bottom_style: Dict,
|
|
591
747
|
source: GraphSource,
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
748
|
+
co2_scale: Union[Co2MassScale, Co2VolumeScale],
|
|
749
|
+
realizations: List[int],
|
|
750
|
+
y_min_auto: List[str],
|
|
751
|
+
y_min_val: Optional[float],
|
|
752
|
+
y_max_auto: List[str],
|
|
753
|
+
y_max_val: Optional[float],
|
|
754
|
+
zone: Optional[str],
|
|
755
|
+
region: Optional[str],
|
|
756
|
+
phase: str,
|
|
757
|
+
containment: str,
|
|
758
|
+
plume_group: str,
|
|
759
|
+
color_choice: str,
|
|
760
|
+
mark_choice: Optional[str],
|
|
761
|
+
sorting: str,
|
|
762
|
+
lines_to_show: str,
|
|
763
|
+
date_option: str,
|
|
764
|
+
statistics_tab_option: StatisticsTabOption,
|
|
765
|
+
box_show_points: str,
|
|
766
|
+
) -> Tuple[Dict, go.Figure, go.Figure, go.Figure]:
|
|
767
|
+
# pylint: disable=too-many-locals
|
|
768
|
+
figs = [no_update] * 3
|
|
769
|
+
cont_info = process_containment_info(
|
|
770
|
+
zone,
|
|
771
|
+
region,
|
|
772
|
+
phase,
|
|
773
|
+
containment,
|
|
774
|
+
plume_group,
|
|
775
|
+
color_choice,
|
|
776
|
+
mark_choice,
|
|
777
|
+
sorting,
|
|
778
|
+
lines_to_show,
|
|
779
|
+
date_option,
|
|
780
|
+
statistics_tab_option,
|
|
781
|
+
box_show_points,
|
|
782
|
+
self._menu_options[ensemble][source],
|
|
783
|
+
)
|
|
784
|
+
if source in [
|
|
785
|
+
GraphSource.CONTAINMENT_MASS,
|
|
786
|
+
GraphSource.CONTAINMENT_ACTUAL_VOLUME,
|
|
787
|
+
]:
|
|
788
|
+
plot_ids = make_plot_ids(
|
|
789
|
+
ensemble,
|
|
790
|
+
source,
|
|
791
|
+
co2_scale,
|
|
792
|
+
cont_info,
|
|
793
|
+
realizations,
|
|
794
|
+
len(figs),
|
|
795
|
+
)
|
|
796
|
+
y_limits = [
|
|
797
|
+
y_min_val if len(y_min_auto) == 0 else None,
|
|
798
|
+
y_max_val if len(y_max_auto) == 0 else None,
|
|
799
|
+
]
|
|
800
|
+
if (
|
|
801
|
+
source == GraphSource.CONTAINMENT_MASS
|
|
802
|
+
and ensemble in self._co2_table_providers
|
|
803
|
+
):
|
|
804
|
+
figs[: len(figs)] = generate_containment_figures(
|
|
805
|
+
self._co2_table_providers[ensemble],
|
|
806
|
+
co2_scale,
|
|
807
|
+
realizations,
|
|
808
|
+
y_limits,
|
|
809
|
+
cont_info,
|
|
810
|
+
legend_data,
|
|
811
|
+
)
|
|
812
|
+
elif (
|
|
813
|
+
source == GraphSource.CONTAINMENT_ACTUAL_VOLUME
|
|
814
|
+
and ensemble in self._co2_actual_volume_table_providers
|
|
815
|
+
):
|
|
816
|
+
figs[: len(figs)] = generate_containment_figures(
|
|
817
|
+
self._co2_actual_volume_table_providers[ensemble],
|
|
818
|
+
co2_scale,
|
|
819
|
+
realizations,
|
|
820
|
+
y_limits,
|
|
821
|
+
cont_info,
|
|
822
|
+
legend_data,
|
|
823
|
+
)
|
|
824
|
+
set_plot_ids(figs, plot_ids)
|
|
825
|
+
elif source == GraphSource.UNSMRY:
|
|
826
|
+
if self._unsmry_providers is not None:
|
|
827
|
+
if ensemble in self._unsmry_providers:
|
|
828
|
+
figs[0] = go.Figure()
|
|
829
|
+
figs[1] = generate_unsmry_figures(
|
|
830
|
+
self._unsmry_providers[ensemble],
|
|
831
|
+
co2_scale,
|
|
832
|
+
self._co2_table_providers[ensemble],
|
|
833
|
+
)
|
|
834
|
+
figs[2] = go.Figure()
|
|
835
|
+
else:
|
|
836
|
+
LOGGER.warning(
|
|
837
|
+
"""UNSMRY file has not been specified as input.
|
|
838
|
+
Please use unsmry_relpath in the configuration."""
|
|
839
|
+
)
|
|
840
|
+
return figs # type: ignore
|
|
595
841
|
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
)
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
842
|
+
def _add_legend_change_callback(self) -> None:
|
|
843
|
+
@callback(
|
|
844
|
+
Output(self._view_component(MapViewElement.Ids.LEGEND_DATA_STORE), "data"),
|
|
845
|
+
Input(self._view_component(MapViewElement.Ids.BAR_PLOT), "restyleData"),
|
|
846
|
+
State(self._view_component(MapViewElement.Ids.BAR_PLOT), "figure"),
|
|
847
|
+
Input(self._view_component(MapViewElement.Ids.TIME_PLOT), "restyleData"),
|
|
848
|
+
State(self._view_component(MapViewElement.Ids.TIME_PLOT), "figure"),
|
|
849
|
+
Input(
|
|
850
|
+
self._view_component(MapViewElement.Ids.STATISTICS_PLOT), "restyleData"
|
|
851
|
+
),
|
|
852
|
+
State(self._view_component(MapViewElement.Ids.STATISTICS_PLOT), "figure"),
|
|
853
|
+
Input(
|
|
854
|
+
self._settings_component(ViewSettings.Ids.STATISTICS_TAB_OPTION),
|
|
855
|
+
"value",
|
|
856
|
+
),
|
|
857
|
+
)
|
|
858
|
+
def on_bar_legend_update(
|
|
859
|
+
bar_event: List[Any],
|
|
860
|
+
bar_figure: go.Figure,
|
|
861
|
+
time_event: List[Any],
|
|
862
|
+
time_figure: go.Figure,
|
|
863
|
+
stats_event: List[Any],
|
|
864
|
+
stats_figure: go.Figure,
|
|
865
|
+
_: StatisticsTabOption,
|
|
866
|
+
) -> Patch:
|
|
867
|
+
# We cannot subscribe to a legend click event directly, but we can subscribe
|
|
868
|
+
# to the more general "restyleData" event, and then try to identify if this
|
|
869
|
+
# was a click event or not. If yes, we update the appropriate store component
|
|
870
|
+
p = Patch()
|
|
871
|
+
_id = ctx.triggered_id
|
|
872
|
+
if _id is None:
|
|
873
|
+
return p
|
|
874
|
+
|
|
875
|
+
if _id == self._view_component(MapViewElement.Ids.BAR_PLOT):
|
|
876
|
+
if self._is_legend_click_event(bar_event):
|
|
877
|
+
p["bar_legendonly"] = extract_legendonly(bar_figure)
|
|
878
|
+
elif _id == self._view_component(MapViewElement.Ids.TIME_PLOT):
|
|
879
|
+
if self._is_legend_click_event(time_event):
|
|
880
|
+
p["time_legendonly"] = extract_legendonly(time_figure)
|
|
881
|
+
elif _id in (
|
|
882
|
+
self._view_component(MapViewElement.Ids.STATISTICS_PLOT),
|
|
883
|
+
self._settings_component(ViewSettings.Ids.STATISTICS_TAB_OPTION),
|
|
607
884
|
):
|
|
608
|
-
|
|
885
|
+
if self._is_legend_click_event(stats_event):
|
|
886
|
+
p["stats_legendonly"] = extract_legendonly(stats_figure)
|
|
887
|
+
return p
|
|
609
888
|
|
|
610
|
-
|
|
889
|
+
@staticmethod
|
|
890
|
+
def _is_legend_click_event(event: List[Any]) -> bool:
|
|
891
|
+
# A typical legend click event would be: [{'visible': ['legendonly']}, [1]]
|
|
892
|
+
if event is None or not isinstance(event, list):
|
|
893
|
+
return False
|
|
894
|
+
return any("visible" in e for e in event if isinstance(e, dict))
|