webviz-subsurface 0.2.37__py3-none-any.whl → 0.2.39__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.
- tests/unit_tests/plugin_tests/test_tornado_data.py +10 -1
- webviz_subsurface/_components/tornado/_tornado_bar_chart.py +31 -11
- webviz_subsurface/_components/tornado/_tornado_data.py +20 -2
- webviz_subsurface/_datainput/well_completions.py +2 -1
- webviz_subsurface/_providers/ensemble_table_provider/ensemble_table_provider_factory.py +4 -0
- webviz_subsurface/_utils/design_matrix.py +36 -0
- webviz_subsurface/plugins/_co2_leakage/_plugin.py +623 -493
- webviz_subsurface/plugins/_co2_leakage/_types.py +7 -0
- webviz_subsurface/plugins/_co2_leakage/_utilities/callbacks.py +96 -52
- webviz_subsurface/plugins/_co2_leakage/_utilities/co2volume.py +300 -82
- webviz_subsurface/plugins/_co2_leakage/_utilities/containment_info.py +31 -0
- webviz_subsurface/plugins/_co2_leakage/_utilities/initialization.py +16 -7
- webviz_subsurface/plugins/_co2_leakage/_utilities/surface_publishing.py +102 -9
- webviz_subsurface/plugins/_co2_leakage/views/mainview/mainview.py +14 -1
- webviz_subsurface/plugins/_co2_leakage/views/mainview/settings.py +181 -58
- webviz_subsurface/plugins/_parameter_analysis/_types.py +1 -0
- webviz_subsurface/plugins/_parameter_analysis/_utils/_parameters_model.py +10 -2
- webviz_subsurface/plugins/_parameter_analysis/_views/_parameter_distributions_view/_settings/_visualization_type.py +2 -1
- {webviz_subsurface-0.2.37.dist-info → webviz_subsurface-0.2.39.dist-info}/METADATA +1 -1
- {webviz_subsurface-0.2.37.dist-info → webviz_subsurface-0.2.39.dist-info}/RECORD +25 -22
- {webviz_subsurface-0.2.37.dist-info → webviz_subsurface-0.2.39.dist-info}/LICENSE +0 -0
- {webviz_subsurface-0.2.37.dist-info → webviz_subsurface-0.2.39.dist-info}/LICENSE.chromedriver +0 -0
- {webviz_subsurface-0.2.37.dist-info → webviz_subsurface-0.2.39.dist-info}/WHEEL +0 -0
- {webviz_subsurface-0.2.37.dist-info → webviz_subsurface-0.2.39.dist-info}/entry_points.txt +0 -0
- {webviz_subsurface-0.2.37.dist-info → webviz_subsurface-0.2.39.dist-info}/top_level.txt +0 -0
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
# pylint: disable=too-many-lines
|
|
2
|
+
# NBNB-AS: We should address this pylint message soon
|
|
1
3
|
import warnings
|
|
2
4
|
from datetime import datetime as dt
|
|
3
5
|
from typing import Any, Dict, List, Optional, Tuple, Union
|
|
@@ -12,6 +14,9 @@ from webviz_subsurface._utils.enum_shim import StrEnum
|
|
|
12
14
|
from webviz_subsurface.plugins._co2_leakage._utilities.containment_data_provider import (
|
|
13
15
|
ContainmentDataProvider,
|
|
14
16
|
)
|
|
17
|
+
from webviz_subsurface.plugins._co2_leakage._utilities.containment_info import (
|
|
18
|
+
ContainmentInfo,
|
|
19
|
+
)
|
|
15
20
|
from webviz_subsurface.plugins._co2_leakage._utilities.generic import (
|
|
16
21
|
Co2MassScale,
|
|
17
22
|
Co2VolumeScale,
|
|
@@ -29,6 +34,10 @@ _COLOR_TOTAL = "#222222"
|
|
|
29
34
|
_COLOR_CONTAINED = "#00aa00"
|
|
30
35
|
_COLOR_OUTSIDE = "#006ddd"
|
|
31
36
|
_COLOR_HAZARDOUS = "#dd4300"
|
|
37
|
+
_COLOR_DISSOLVED = "#208eb7"
|
|
38
|
+
_COLOR_GAS = "#C41E3A"
|
|
39
|
+
_COLOR_FREE = "#FF2400"
|
|
40
|
+
_COLOR_TRAPPED = "#880808"
|
|
32
41
|
_COLOR_ZONES = [
|
|
33
42
|
"#e91451",
|
|
34
43
|
"#daa218",
|
|
@@ -62,6 +71,9 @@ _LIGHTER_COLORS = {
|
|
|
62
71
|
"#1357ca": "#7ba7f3",
|
|
63
72
|
"#f75ef0": "#fbaef7",
|
|
64
73
|
"#34b36f": "#93e0b7",
|
|
74
|
+
"#C41E3A": "#E42E5A",
|
|
75
|
+
"#FF2400": "#FF7430",
|
|
76
|
+
"#880808": "#C84848",
|
|
65
77
|
}
|
|
66
78
|
|
|
67
79
|
|
|
@@ -80,6 +92,10 @@ def _read_dataframe(
|
|
|
80
92
|
def _get_colors(num_cols: int = 3, split: str = "zone") -> List[str]:
|
|
81
93
|
if split == "containment":
|
|
82
94
|
return [_COLOR_HAZARDOUS, _COLOR_OUTSIDE, _COLOR_CONTAINED]
|
|
95
|
+
if split == "phase":
|
|
96
|
+
if num_cols == 2:
|
|
97
|
+
return [_COLOR_GAS, _COLOR_DISSOLVED]
|
|
98
|
+
return [_COLOR_FREE, _COLOR_TRAPPED, _COLOR_DISSOLVED]
|
|
83
99
|
options = list(_COLOR_ZONES)
|
|
84
100
|
if split == "region":
|
|
85
101
|
options.reverse()
|
|
@@ -104,6 +120,7 @@ def _get_marks(num_marks: int, mark_choice: str) -> List[str]:
|
|
|
104
120
|
f"Some {mark_choice}s will share pattern."
|
|
105
121
|
)
|
|
106
122
|
return base_pattern[:num_marks]
|
|
123
|
+
# mark_choice == "phase":
|
|
107
124
|
return ["", "/"] if num_marks == 2 else ["", ".", "/"]
|
|
108
125
|
|
|
109
126
|
|
|
@@ -113,35 +130,36 @@ def _get_line_types(mark_options: List[str], mark_choice: str) -> List[str]:
|
|
|
113
130
|
if mark_choice == "containment":
|
|
114
131
|
return ["dash", "dot", "solid"]
|
|
115
132
|
if mark_choice in ["zone", "region", "plume_group"]:
|
|
116
|
-
|
|
133
|
+
options = ["solid", "dash", "dot", "dashdot", "longdash", "longdashdot"]
|
|
134
|
+
if len(mark_options) > 6:
|
|
117
135
|
warnings.warn(
|
|
118
136
|
f"Large number of {mark_choice}s might make it hard "
|
|
119
137
|
f"to distinguish different dashed lines."
|
|
120
138
|
)
|
|
121
|
-
return [
|
|
122
|
-
|
|
123
|
-
]
|
|
139
|
+
return [options[i % 6] for i in range(len(mark_options))]
|
|
140
|
+
# mark_choice == "phase":
|
|
124
141
|
return ["dot", "dash"] if "gas" in mark_options else ["dot", "dashdot", "dash"]
|
|
125
142
|
|
|
126
143
|
|
|
127
144
|
def _prepare_pattern_and_color_options(
|
|
128
145
|
df: pd.DataFrame,
|
|
129
|
-
containment_info:
|
|
146
|
+
containment_info: ContainmentInfo,
|
|
130
147
|
color_choice: str,
|
|
131
148
|
mark_choice: str,
|
|
132
149
|
) -> Tuple[Dict, List, List]:
|
|
133
|
-
|
|
134
|
-
|
|
150
|
+
no_mark = mark_choice == "none"
|
|
151
|
+
mark_options = [] if no_mark else getattr(containment_info, f"{mark_choice}s")
|
|
152
|
+
color_options = getattr(containment_info, f"{color_choice}s")
|
|
135
153
|
num_colors = len(color_options)
|
|
136
|
-
num_marks = num_colors if
|
|
154
|
+
num_marks = num_colors if no_mark else len(mark_options)
|
|
137
155
|
marks = _get_marks(num_marks, mark_choice)
|
|
138
156
|
colors = _get_colors(num_colors, color_choice)
|
|
139
|
-
if
|
|
157
|
+
if no_mark:
|
|
140
158
|
cat_ord = {"type": color_options}
|
|
141
159
|
df["type"] = df[color_choice]
|
|
142
160
|
return cat_ord, colors, marks
|
|
143
161
|
df["type"] = [", ".join((c, m)) for c, m in zip(df[color_choice], df[mark_choice])]
|
|
144
|
-
if containment_info
|
|
162
|
+
if containment_info.sorting == "color":
|
|
145
163
|
cat_ord = {
|
|
146
164
|
"type": [", ".join((c, m)) for c in color_options for m in mark_options],
|
|
147
165
|
}
|
|
@@ -158,14 +176,15 @@ def _prepare_pattern_and_color_options(
|
|
|
158
176
|
|
|
159
177
|
def _prepare_pattern_and_color_options_statistics_plot(
|
|
160
178
|
df: pd.DataFrame,
|
|
161
|
-
containment_info:
|
|
179
|
+
containment_info: ContainmentInfo,
|
|
162
180
|
color_choice: str,
|
|
163
181
|
mark_choice: str,
|
|
164
182
|
) -> Tuple[Dict, List, List]:
|
|
165
|
-
|
|
166
|
-
|
|
183
|
+
no_mark = mark_choice == "none"
|
|
184
|
+
mark_options = [] if no_mark else getattr(containment_info, f"{mark_choice}s")
|
|
185
|
+
color_options = getattr(containment_info, f"{color_choice}s")
|
|
167
186
|
num_colors = len(color_options)
|
|
168
|
-
num_marks = num_colors if
|
|
187
|
+
num_marks = num_colors if no_mark else len(mark_options)
|
|
169
188
|
line_types = _get_line_types(mark_options, mark_choice)
|
|
170
189
|
colors = _get_colors(num_colors, color_choice)
|
|
171
190
|
|
|
@@ -173,7 +192,7 @@ def _prepare_pattern_and_color_options_statistics_plot(
|
|
|
173
192
|
mark_options = ["total"] + mark_options
|
|
174
193
|
line_types = ["solid"] + line_types
|
|
175
194
|
num_marks += 1
|
|
176
|
-
if color_choice
|
|
195
|
+
if color_choice in ["containment", "phase"]:
|
|
177
196
|
color_options = ["total"] + color_options
|
|
178
197
|
colors = ["black"] + colors
|
|
179
198
|
num_colors += 1
|
|
@@ -182,13 +201,13 @@ def _prepare_pattern_and_color_options_statistics_plot(
|
|
|
182
201
|
filter_color = color_choice not in ["phase", "containment"]
|
|
183
202
|
_filter_rows(df, color_choice, mark_choice, filter_mark, filter_color)
|
|
184
203
|
|
|
185
|
-
if
|
|
204
|
+
if no_mark:
|
|
186
205
|
cat_ord = {"type": color_options}
|
|
187
206
|
df["type"] = df[color_choice]
|
|
188
207
|
return cat_ord, colors, line_types
|
|
189
208
|
df["type"] = [", ".join((c, m)) for c, m in zip(df[color_choice], df[mark_choice])]
|
|
190
209
|
|
|
191
|
-
if containment_info
|
|
210
|
+
if containment_info.sorting == "color":
|
|
192
211
|
cat_ord = {
|
|
193
212
|
"type": [", ".join((c, m)) for c in color_options for m in mark_options],
|
|
194
213
|
}
|
|
@@ -227,9 +246,7 @@ def _prepare_pattern_and_color_options_statistics_plot(
|
|
|
227
246
|
return cat_ord, colors, line_types
|
|
228
247
|
|
|
229
248
|
|
|
230
|
-
def
|
|
231
|
-
df: pd.DataFrame, categories: List[str]
|
|
232
|
-
) -> str:
|
|
249
|
+
def _find_default_legendonly(df: pd.DataFrame, categories: List[str]) -> List[str]:
|
|
233
250
|
if "hazardous" in categories:
|
|
234
251
|
default_option = "hazardous"
|
|
235
252
|
else:
|
|
@@ -240,29 +257,32 @@ def _find_default_option_statistics_figure(
|
|
|
240
257
|
if df_filtered["amount"].max() > max_value:
|
|
241
258
|
max_value = df_filtered["amount"].max()
|
|
242
259
|
default_option = category
|
|
243
|
-
|
|
260
|
+
|
|
261
|
+
# The default list should contain all categories HIDDEN in the legend, so we need
|
|
262
|
+
# to create a copy of the list with default_option excluded instead.
|
|
263
|
+
return [c for c in categories if c != default_option]
|
|
244
264
|
|
|
245
265
|
|
|
246
266
|
def _prepare_line_type_and_color_options(
|
|
247
267
|
df: pd.DataFrame,
|
|
248
|
-
containment_info:
|
|
268
|
+
containment_info: ContainmentInfo,
|
|
249
269
|
color_choice: str,
|
|
250
270
|
mark_choice: str,
|
|
251
271
|
) -> pd.DataFrame:
|
|
252
272
|
mark_options = []
|
|
253
273
|
if mark_choice != "none":
|
|
254
|
-
mark_options = list(containment_info
|
|
255
|
-
color_options = list(containment_info
|
|
274
|
+
mark_options = list(getattr(containment_info, f"{mark_choice}s"))
|
|
275
|
+
color_options = list(getattr(containment_info, f"{color_choice}s"))
|
|
256
276
|
num_colors = len(color_options)
|
|
257
277
|
line_types = _get_line_types(mark_options, mark_choice)
|
|
258
278
|
colors = _get_colors(num_colors, color_choice)
|
|
259
279
|
|
|
260
280
|
filter_mark = True
|
|
261
|
-
if mark_choice
|
|
281
|
+
if mark_choice in ["containment", "phase"]:
|
|
262
282
|
mark_options = ["total"] + mark_options
|
|
263
283
|
line_types = ["solid"] + line_types
|
|
264
284
|
filter_mark = False
|
|
265
|
-
if color_choice
|
|
285
|
+
if color_choice in ["containment", "phase"]:
|
|
266
286
|
color_options = ["total"] + color_options
|
|
267
287
|
colors = ["black"] + colors
|
|
268
288
|
else:
|
|
@@ -278,7 +298,7 @@ def _prepare_line_type_and_color_options(
|
|
|
278
298
|
)
|
|
279
299
|
df["name"] = [", ".join((c, m)) for c, m in zip(df[color_choice], df[mark_choice])]
|
|
280
300
|
_change_names(df, color_options, mark_options)
|
|
281
|
-
if containment_info
|
|
301
|
+
if containment_info.sorting == "color":
|
|
282
302
|
options = pd.DataFrame(
|
|
283
303
|
{
|
|
284
304
|
"name": [
|
|
@@ -306,7 +326,7 @@ def _read_terminal_co2_volumes(
|
|
|
306
326
|
table_provider: ContainmentDataProvider,
|
|
307
327
|
realizations: List[int],
|
|
308
328
|
scale: Union[Co2MassScale, Co2VolumeScale],
|
|
309
|
-
containment_info:
|
|
329
|
+
containment_info: ContainmentInfo,
|
|
310
330
|
) -> pd.DataFrame:
|
|
311
331
|
records: Dict[str, List[Any]] = {
|
|
312
332
|
"real": [],
|
|
@@ -314,8 +334,8 @@ def _read_terminal_co2_volumes(
|
|
|
314
334
|
"sort_key": [],
|
|
315
335
|
"sort_key_secondary": [],
|
|
316
336
|
}
|
|
317
|
-
color_choice = containment_info
|
|
318
|
-
mark_choice = containment_info
|
|
337
|
+
color_choice = containment_info.color_choice
|
|
338
|
+
mark_choice = containment_info.mark_choice
|
|
319
339
|
assert isinstance(color_choice, str)
|
|
320
340
|
assert isinstance(mark_choice, str)
|
|
321
341
|
records[color_choice] = []
|
|
@@ -324,7 +344,7 @@ def _read_terminal_co2_volumes(
|
|
|
324
344
|
data_frame = None
|
|
325
345
|
for real in realizations:
|
|
326
346
|
df = table_provider.extract_dataframe(real, scale)
|
|
327
|
-
df = df[df["date"] == containment_info
|
|
347
|
+
df = df[df["date"] == containment_info.date_option]
|
|
328
348
|
_add_sort_key_and_real(df, str(real), containment_info)
|
|
329
349
|
_filter_columns(df, color_choice, mark_choice, containment_info)
|
|
330
350
|
_filter_rows(df, color_choice, mark_choice)
|
|
@@ -343,7 +363,7 @@ def _filter_columns(
|
|
|
343
363
|
df: pd.DataFrame,
|
|
344
364
|
color_choice: str,
|
|
345
365
|
mark_choice: str,
|
|
346
|
-
containment_info:
|
|
366
|
+
containment_info: ContainmentInfo,
|
|
347
367
|
) -> None:
|
|
348
368
|
filter_columns = [
|
|
349
369
|
col
|
|
@@ -351,7 +371,7 @@ def _filter_columns(
|
|
|
351
371
|
if col not in [mark_choice, color_choice]
|
|
352
372
|
]
|
|
353
373
|
for col in filter_columns:
|
|
354
|
-
df.query(f'{col} == "{containment_info
|
|
374
|
+
df.query(f'{col} == "{getattr(containment_info, col)}"', inplace=True)
|
|
355
375
|
df.drop(columns=filter_columns, inplace=True)
|
|
356
376
|
|
|
357
377
|
|
|
@@ -371,24 +391,24 @@ def _filter_rows(
|
|
|
371
391
|
def _add_sort_key_and_real(
|
|
372
392
|
df: pd.DataFrame,
|
|
373
393
|
label: str,
|
|
374
|
-
containment_info:
|
|
394
|
+
containment_info: ContainmentInfo,
|
|
375
395
|
) -> None:
|
|
376
396
|
sort_value = np.sum(
|
|
377
397
|
df[
|
|
378
398
|
(df["phase"] == "total")
|
|
379
399
|
& (df["containment"] == "hazardous")
|
|
380
|
-
& (df["zone"] == containment_info
|
|
381
|
-
& (df["region"] == containment_info
|
|
382
|
-
& (df["plume_group"] == containment_info
|
|
400
|
+
& (df["zone"] == containment_info.zone)
|
|
401
|
+
& (df["region"] == containment_info.region)
|
|
402
|
+
& (df["plume_group"] == containment_info.plume_group)
|
|
383
403
|
]["amount"]
|
|
384
404
|
)
|
|
385
405
|
sort_value_secondary = np.sum(
|
|
386
406
|
df[
|
|
387
407
|
(df["phase"] == "total")
|
|
388
408
|
& (df["containment"] == "outside")
|
|
389
|
-
& (df["zone"] == containment_info
|
|
390
|
-
& (df["region"] == containment_info
|
|
391
|
-
& (df["plume_group"] == containment_info
|
|
409
|
+
& (df["zone"] == containment_info.zone)
|
|
410
|
+
& (df["region"] == containment_info.region)
|
|
411
|
+
& (df["plume_group"] == containment_info.plume_group)
|
|
392
412
|
]["amount"]
|
|
393
413
|
)
|
|
394
414
|
df["real"] = [label] * df.shape[0]
|
|
@@ -422,19 +442,18 @@ def _change_names(
|
|
|
422
442
|
df["name"] = df["name"].replace(f"{m}, all", m)
|
|
423
443
|
|
|
424
444
|
|
|
425
|
-
def _adjust_figure(fig: go.Figure, plot_title:
|
|
445
|
+
def _adjust_figure(fig: go.Figure, plot_title: str) -> None:
|
|
426
446
|
fig.layout.legend.orientation = "v"
|
|
427
447
|
fig.layout.legend.title.text = ""
|
|
428
448
|
fig.layout.legend.itemwidth = 40
|
|
429
449
|
fig.layout.xaxis.exponentformat = "power"
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
else:
|
|
436
|
-
fig.layout.margin.t = 15
|
|
450
|
+
|
|
451
|
+
fig.layout.title.text = plot_title
|
|
452
|
+
fig.layout.title.font = {"size": 14}
|
|
453
|
+
fig.layout.margin.t = 40
|
|
454
|
+
fig.layout.title.y = 0.95
|
|
437
455
|
fig.layout.title.x = 0.4
|
|
456
|
+
|
|
438
457
|
fig.layout.paper_bgcolor = "rgba(0,0,0,0)"
|
|
439
458
|
fig.layout.margin.b = 6
|
|
440
459
|
fig.layout.margin.l = 10
|
|
@@ -476,13 +495,14 @@ def generate_co2_volume_figure(
|
|
|
476
495
|
table_provider: ContainmentDataProvider,
|
|
477
496
|
realizations: List[int],
|
|
478
497
|
scale: Union[Co2MassScale, Co2VolumeScale],
|
|
479
|
-
containment_info:
|
|
498
|
+
containment_info: ContainmentInfo,
|
|
499
|
+
legendonly_traces: Optional[List[str]],
|
|
480
500
|
) -> go.Figure:
|
|
481
501
|
df = _read_terminal_co2_volumes(
|
|
482
502
|
table_provider, realizations, scale, containment_info
|
|
483
503
|
)
|
|
484
|
-
color_choice = containment_info
|
|
485
|
-
mark_choice = containment_info
|
|
504
|
+
color_choice = containment_info.color_choice
|
|
505
|
+
mark_choice = containment_info.mark_choice
|
|
486
506
|
_add_prop_to_df(df, [str(r) for r in realizations], "real")
|
|
487
507
|
cat_ord, colors, marks = _prepare_pattern_and_color_options(
|
|
488
508
|
df,
|
|
@@ -506,9 +526,11 @@ def generate_co2_volume_figure(
|
|
|
506
526
|
hovertemplate="Type: %{customdata[0]}<br>Amount: %{x:.3f}<br>"
|
|
507
527
|
"Realization: %{y}<br>Proportion: %{customdata[1]}<extra></extra>",
|
|
508
528
|
)
|
|
529
|
+
if legendonly_traces is not None:
|
|
530
|
+
_toggle_trace_visibility(fig.data, legendonly_traces)
|
|
509
531
|
fig.layout.yaxis.title = "Realization"
|
|
510
532
|
fig.layout.xaxis.title = scale.value
|
|
511
|
-
_adjust_figure(fig, plot_title=containment_info
|
|
533
|
+
_adjust_figure(fig, plot_title=_make_title(containment_info))
|
|
512
534
|
return fig
|
|
513
535
|
|
|
514
536
|
|
|
@@ -518,14 +540,14 @@ def generate_co2_time_containment_one_realization_figure(
|
|
|
518
540
|
scale: Union[Co2MassScale, Co2VolumeScale],
|
|
519
541
|
time_series_realization: int,
|
|
520
542
|
y_limits: List[Optional[float]],
|
|
521
|
-
containment_info:
|
|
543
|
+
containment_info: ContainmentInfo,
|
|
522
544
|
) -> go.Figure:
|
|
523
545
|
df = _read_co2_volumes(table_provider, [time_series_realization], scale)
|
|
524
|
-
color_choice = containment_info
|
|
525
|
-
mark_choice = containment_info
|
|
546
|
+
color_choice = containment_info.color_choice
|
|
547
|
+
mark_choice = containment_info.mark_choice
|
|
526
548
|
_filter_columns(df, color_choice, mark_choice, containment_info)
|
|
527
549
|
_filter_rows(df, color_choice, mark_choice)
|
|
528
|
-
if containment_info
|
|
550
|
+
if containment_info.sorting == "marking" and mark_choice != "none":
|
|
529
551
|
sort_order = ["date", mark_choice]
|
|
530
552
|
else:
|
|
531
553
|
sort_order = ["date", color_choice]
|
|
@@ -562,7 +584,7 @@ def generate_co2_time_containment_one_realization_figure(
|
|
|
562
584
|
fig.layout.yaxis.range = y_limits
|
|
563
585
|
fig.layout.xaxis.title = "Time"
|
|
564
586
|
fig.layout.yaxis.title = scale.value
|
|
565
|
-
_adjust_figure(fig)
|
|
587
|
+
_adjust_figure(fig, plot_title=_make_title(containment_info, include_date=False))
|
|
566
588
|
return fig
|
|
567
589
|
|
|
568
590
|
|
|
@@ -702,23 +724,27 @@ def _connect_plume_groups(
|
|
|
702
724
|
df.drop(columns="is_merged", inplace=True)
|
|
703
725
|
|
|
704
726
|
|
|
705
|
-
# pylint: disable=too-many-locals
|
|
727
|
+
# pylint: disable=too-many-locals, too-many-statements
|
|
706
728
|
def generate_co2_time_containment_figure(
|
|
707
729
|
table_provider: ContainmentDataProvider,
|
|
708
730
|
realizations: List[int],
|
|
709
731
|
scale: Union[Co2MassScale, Co2VolumeScale],
|
|
710
|
-
containment_info:
|
|
732
|
+
containment_info: ContainmentInfo,
|
|
733
|
+
legendonly_traces: Optional[List[str]],
|
|
711
734
|
) -> go.Figure:
|
|
712
735
|
df = _read_co2_volumes(table_provider, realizations, scale)
|
|
713
|
-
color_choice = containment_info
|
|
714
|
-
mark_choice = containment_info
|
|
736
|
+
color_choice = containment_info.color_choice
|
|
737
|
+
mark_choice = containment_info.mark_choice
|
|
715
738
|
_filter_columns(df, color_choice, mark_choice, containment_info)
|
|
716
739
|
options = _prepare_line_type_and_color_options(
|
|
717
740
|
df, containment_info, color_choice, mark_choice
|
|
718
741
|
)
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
742
|
+
if legendonly_traces is None:
|
|
743
|
+
inactive_cols_at_startup = list(
|
|
744
|
+
options[~(options["line_type"].isin(["solid", "0px"]))]["name"]
|
|
745
|
+
)
|
|
746
|
+
else:
|
|
747
|
+
inactive_cols_at_startup = legendonly_traces
|
|
722
748
|
if "plume_group" in df:
|
|
723
749
|
try:
|
|
724
750
|
_connect_plume_groups(df, color_choice, mark_choice)
|
|
@@ -737,7 +763,7 @@ def generate_co2_time_containment_figure(
|
|
|
737
763
|
"legendgroup": name,
|
|
738
764
|
"name": name,
|
|
739
765
|
}
|
|
740
|
-
if name
|
|
766
|
+
if name in inactive_cols_at_startup:
|
|
741
767
|
args["visible"] = "legendonly"
|
|
742
768
|
fig.add_scatter(y=[0.0], **dummy_args, **args)
|
|
743
769
|
|
|
@@ -746,7 +772,7 @@ def generate_co2_time_containment_figure(
|
|
|
746
772
|
"Realization: %{meta[0]}<br>Proportion: %{customdata}"
|
|
747
773
|
)
|
|
748
774
|
|
|
749
|
-
if containment_info
|
|
775
|
+
if containment_info.use_stats:
|
|
750
776
|
df_no_real = df.drop(columns=["REAL", "realization"]).reset_index(drop=True)
|
|
751
777
|
if mark_choice == "none":
|
|
752
778
|
df_grouped = df_no_real.groupby(
|
|
@@ -756,11 +782,11 @@ def generate_co2_time_containment_figure(
|
|
|
756
782
|
df_grouped = df_no_real.groupby(
|
|
757
783
|
["date", "name", color_choice, mark_choice], as_index=False
|
|
758
784
|
)
|
|
759
|
-
df_mean = df_grouped.agg(
|
|
785
|
+
df_mean = df_grouped.agg("mean")
|
|
760
786
|
df_mean["realization"] = ["mean"] * df_mean.shape[0]
|
|
761
|
-
df_p10 = df_grouped.agg(lambda x: np.quantile(x, 0.
|
|
787
|
+
df_p10 = df_grouped.agg(lambda x: np.quantile(x, 0.9))
|
|
762
788
|
df_p10["realization"] = ["p10"] * df_p10.shape[0]
|
|
763
|
-
df_p90 = df_grouped.agg(lambda x: np.quantile(x, 0.
|
|
789
|
+
df_p90 = df_grouped.agg(lambda x: np.quantile(x, 0.1))
|
|
764
790
|
df_p90["realization"] = ["p90"] * df_p90.shape[0]
|
|
765
791
|
df = (
|
|
766
792
|
pd.concat([df_mean, df_p10, df_p90])
|
|
@@ -775,7 +801,7 @@ def generate_co2_time_containment_figure(
|
|
|
775
801
|
for rlz in realizations:
|
|
776
802
|
lwd = 1.5 if rlz in ["p10", "p90"] else 2.5
|
|
777
803
|
sub_df = df[df["realization"] == rlz].copy().reset_index(drop=True)
|
|
778
|
-
if not containment_info
|
|
804
|
+
if not containment_info.use_stats:
|
|
779
805
|
_add_prop_to_df(
|
|
780
806
|
sub_df, np.unique(df["date"]), "date", [color_choice, mark_choice]
|
|
781
807
|
)
|
|
@@ -797,9 +823,9 @@ def generate_co2_time_containment_figure(
|
|
|
797
823
|
"meta": [rlz, name],
|
|
798
824
|
"hovertemplate": hover_template,
|
|
799
825
|
}
|
|
800
|
-
if not containment_info
|
|
826
|
+
if not containment_info.use_stats:
|
|
801
827
|
args["customdata"] = sub_df[sub_df["name"] == name]["prop"]
|
|
802
|
-
if name
|
|
828
|
+
if name in inactive_cols_at_startup:
|
|
803
829
|
args["visible"] = "legendonly"
|
|
804
830
|
fig.add_scatter(
|
|
805
831
|
y=sub_df[sub_df["name"] == name]["amount"], **args, **common_args
|
|
@@ -808,7 +834,7 @@ def generate_co2_time_containment_figure(
|
|
|
808
834
|
fig.layout.xaxis.title = "Time"
|
|
809
835
|
fig.layout.yaxis.title = scale.value
|
|
810
836
|
fig.layout.yaxis.autorange = True
|
|
811
|
-
_adjust_figure(fig)
|
|
837
|
+
_adjust_figure(fig, plot_title=_make_title(containment_info, include_date=False))
|
|
812
838
|
return fig
|
|
813
839
|
|
|
814
840
|
|
|
@@ -816,14 +842,15 @@ def generate_co2_statistics_figure(
|
|
|
816
842
|
table_provider: ContainmentDataProvider,
|
|
817
843
|
realizations: List[int],
|
|
818
844
|
scale: Union[Co2MassScale, Co2VolumeScale],
|
|
819
|
-
containment_info:
|
|
845
|
+
containment_info: ContainmentInfo,
|
|
846
|
+
legend_only_traces: Optional[List[str]],
|
|
820
847
|
) -> go.Figure:
|
|
821
|
-
date_option = containment_info
|
|
848
|
+
date_option = containment_info.date_option
|
|
822
849
|
df = _read_co2_volumes(table_provider, realizations, scale)
|
|
823
850
|
df = df[df["date"] == date_option]
|
|
824
851
|
df = df.drop(columns=["date"]).reset_index(drop=True)
|
|
825
|
-
color_choice = containment_info
|
|
826
|
-
mark_choice = containment_info
|
|
852
|
+
color_choice = containment_info.color_choice
|
|
853
|
+
mark_choice = containment_info.mark_choice
|
|
827
854
|
_filter_columns(df, color_choice, mark_choice, containment_info)
|
|
828
855
|
cat_ord, colors, line_types = _prepare_pattern_and_color_options_statistics_plot(
|
|
829
856
|
df,
|
|
@@ -847,10 +874,11 @@ def generate_co2_statistics_figure(
|
|
|
847
874
|
category_orders=cat_ord,
|
|
848
875
|
)
|
|
849
876
|
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
877
|
+
if legend_only_traces is None:
|
|
878
|
+
default_option = _find_default_legendonly(df, cat_ord["type"])
|
|
879
|
+
_toggle_trace_visibility(fig.data, default_option)
|
|
880
|
+
else:
|
|
881
|
+
_toggle_trace_visibility(fig.data, legend_only_traces)
|
|
854
882
|
|
|
855
883
|
fig.update_traces(
|
|
856
884
|
hovertemplate="Type: %{data.name}<br>Amount: %{x:.3f}<br>"
|
|
@@ -860,6 +888,196 @@ def generate_co2_statistics_figure(
|
|
|
860
888
|
fig.layout.legend.tracegroupgap = 0
|
|
861
889
|
fig.layout.xaxis.title = scale.value
|
|
862
890
|
fig.layout.yaxis.title = "Probability"
|
|
863
|
-
_adjust_figure(fig, plot_title=containment_info
|
|
891
|
+
_adjust_figure(fig, plot_title=_make_title(containment_info))
|
|
892
|
+
|
|
893
|
+
return fig
|
|
894
|
+
|
|
895
|
+
|
|
896
|
+
def generate_co2_box_plot_figure(
|
|
897
|
+
table_provider: ContainmentDataProvider,
|
|
898
|
+
realizations: List[int],
|
|
899
|
+
scale: Union[Co2MassScale, Co2VolumeScale],
|
|
900
|
+
containment_info: ContainmentInfo,
|
|
901
|
+
legendonly_traces: Optional[List[str]],
|
|
902
|
+
) -> go.Figure:
|
|
903
|
+
eps = 0.00001
|
|
904
|
+
date_option = containment_info.date_option
|
|
905
|
+
df = _read_co2_volumes(table_provider, realizations, scale)
|
|
906
|
+
df = df[df["date"] == date_option]
|
|
907
|
+
df = df.drop(columns=["date"]).reset_index(drop=True)
|
|
908
|
+
|
|
909
|
+
color_choice = containment_info.color_choice
|
|
910
|
+
mark_choice = containment_info.mark_choice
|
|
911
|
+
_filter_columns(df, color_choice, mark_choice, containment_info)
|
|
912
|
+
cat_ord, colors, _ = _prepare_pattern_and_color_options_statistics_plot(
|
|
913
|
+
df,
|
|
914
|
+
containment_info,
|
|
915
|
+
color_choice,
|
|
916
|
+
mark_choice,
|
|
917
|
+
)
|
|
918
|
+
|
|
919
|
+
fig = go.Figure()
|
|
920
|
+
for count, type_val in enumerate(cat_ord["type"], 0):
|
|
921
|
+
df_sub = df[df["type"] == type_val]
|
|
922
|
+
if df_sub.size == 0:
|
|
923
|
+
continue
|
|
924
|
+
|
|
925
|
+
values = df_sub["amount"].to_numpy()
|
|
926
|
+
real = df_sub["realization"].to_numpy()
|
|
927
|
+
|
|
928
|
+
median_val = df_sub["amount"].median()
|
|
929
|
+
q1 = _calculate_plotly_quantiles(values, 0.25)
|
|
930
|
+
q3 = _calculate_plotly_quantiles(values, 0.75)
|
|
931
|
+
p10 = np.percentile(values, 90)
|
|
932
|
+
p90 = np.percentile(values, 10)
|
|
933
|
+
min_fence, max_fence = _calculate_plotly_whiskers(values, q1, q3)
|
|
934
|
+
|
|
935
|
+
fig.add_trace(
|
|
936
|
+
go.Box(
|
|
937
|
+
x=[count] * len(values),
|
|
938
|
+
y=values,
|
|
939
|
+
name=type_val,
|
|
940
|
+
marker_color=colors[count],
|
|
941
|
+
boxpoints="all"
|
|
942
|
+
if containment_info.box_show_points == "all_points"
|
|
943
|
+
else "outliers",
|
|
944
|
+
customdata=real,
|
|
945
|
+
hovertemplate="<span style='font-family:Courier New;'>"
|
|
946
|
+
"Type : %{data.name}<br>Amount : %{y:.3f}<br>"
|
|
947
|
+
"Realization: %{customdata}"
|
|
948
|
+
"</span><extra></extra>",
|
|
949
|
+
legendgroup=type_val,
|
|
950
|
+
width=0.55,
|
|
951
|
+
)
|
|
952
|
+
)
|
|
953
|
+
|
|
954
|
+
fig.add_trace(
|
|
955
|
+
go.Bar(
|
|
956
|
+
x=[count],
|
|
957
|
+
y=[values.max() - values.min() + 2 * eps],
|
|
958
|
+
base=[values.min() - eps],
|
|
959
|
+
opacity=0.0,
|
|
960
|
+
hoverinfo="none",
|
|
961
|
+
hovertemplate=(
|
|
962
|
+
"<span style='font-family:Courier New;'>"
|
|
963
|
+
f"Type : {type_val}<br>"
|
|
964
|
+
f"Max : {values.max():.3f}<br>"
|
|
965
|
+
f"Top whisker : {max_fence:.3f}<br>"
|
|
966
|
+
f"p10 (not shown): {p10:.3f}<br>"
|
|
967
|
+
f"Q3 : {q3:.3f}<br>"
|
|
968
|
+
f"Median : {median_val:.3f}<br>"
|
|
969
|
+
f"Q1 : {q1:.3f}<br>"
|
|
970
|
+
f"p90 (not shown): {p90:.3f}<br>"
|
|
971
|
+
f"Lower whisker : {min_fence:.3f}<br>"
|
|
972
|
+
f"Min : {values.min():.3f}"
|
|
973
|
+
"</span><extra></extra>"
|
|
974
|
+
),
|
|
975
|
+
showlegend=False,
|
|
976
|
+
legendgroup=type_val,
|
|
977
|
+
name=type_val,
|
|
978
|
+
marker_color=colors[count],
|
|
979
|
+
width=0.56,
|
|
980
|
+
)
|
|
981
|
+
)
|
|
982
|
+
|
|
983
|
+
fig.update_layout(
|
|
984
|
+
xaxis={
|
|
985
|
+
"tickmode": "array",
|
|
986
|
+
"tickvals": list(range(len(cat_ord["type"]))),
|
|
987
|
+
"ticktext": cat_ord["type"],
|
|
988
|
+
}
|
|
989
|
+
)
|
|
990
|
+
|
|
991
|
+
if len(cat_ord["type"]) > 20 or legendonly_traces is None:
|
|
992
|
+
default_option = _find_default_legendonly(df, cat_ord["type"])
|
|
993
|
+
_toggle_trace_visibility(fig.data, default_option)
|
|
994
|
+
else:
|
|
995
|
+
_toggle_trace_visibility(fig.data, legendonly_traces)
|
|
996
|
+
|
|
997
|
+
fig.layout.yaxis.autorange = True
|
|
998
|
+
fig.layout.legend.tracegroupgap = 0
|
|
999
|
+
fig.layout.yaxis.title = scale.value
|
|
1000
|
+
_adjust_figure(fig, plot_title=_make_title(containment_info))
|
|
864
1001
|
|
|
865
1002
|
return fig
|
|
1003
|
+
|
|
1004
|
+
|
|
1005
|
+
# pylint: disable=too-many-branches
|
|
1006
|
+
def _make_title(c_info: ContainmentInfo, include_date: bool = True) -> str:
|
|
1007
|
+
components = []
|
|
1008
|
+
if include_date:
|
|
1009
|
+
components.append(c_info.date_option)
|
|
1010
|
+
if len(c_info.phases) > 0 and "phase" not in [
|
|
1011
|
+
c_info.color_choice,
|
|
1012
|
+
c_info.mark_choice,
|
|
1013
|
+
]:
|
|
1014
|
+
if c_info.phase is not None and c_info.phase != "total":
|
|
1015
|
+
components.append(c_info.phase.capitalize())
|
|
1016
|
+
else:
|
|
1017
|
+
components.append("Phase: Total")
|
|
1018
|
+
if len(c_info.containments) > 0 and "containment" not in [
|
|
1019
|
+
c_info.color_choice,
|
|
1020
|
+
c_info.mark_choice,
|
|
1021
|
+
]:
|
|
1022
|
+
if c_info.containment is not None and c_info.containment != "total":
|
|
1023
|
+
components.append(c_info.containment.capitalize())
|
|
1024
|
+
else:
|
|
1025
|
+
components.append("All containments areas")
|
|
1026
|
+
if len(c_info.zones) > 0 and "zone" not in [
|
|
1027
|
+
c_info.color_choice,
|
|
1028
|
+
c_info.mark_choice,
|
|
1029
|
+
]:
|
|
1030
|
+
if c_info.zone is not None and c_info.zone != "all":
|
|
1031
|
+
components.append(c_info.zone)
|
|
1032
|
+
else:
|
|
1033
|
+
components.append("All zones")
|
|
1034
|
+
if (
|
|
1035
|
+
c_info.regions is not None
|
|
1036
|
+
and len(c_info.regions) > 0
|
|
1037
|
+
and "region"
|
|
1038
|
+
not in [
|
|
1039
|
+
c_info.color_choice,
|
|
1040
|
+
c_info.mark_choice,
|
|
1041
|
+
]
|
|
1042
|
+
):
|
|
1043
|
+
if c_info.region is not None and c_info.region != "all":
|
|
1044
|
+
components.append(c_info.region)
|
|
1045
|
+
else:
|
|
1046
|
+
components.append("All regions")
|
|
1047
|
+
if len(c_info.plume_groups) > 0 and "plume_group" not in [
|
|
1048
|
+
c_info.color_choice,
|
|
1049
|
+
c_info.mark_choice,
|
|
1050
|
+
]:
|
|
1051
|
+
if c_info.plume_group is not None and c_info.plume_group != "all":
|
|
1052
|
+
components.append(c_info.plume_group)
|
|
1053
|
+
else:
|
|
1054
|
+
components.append("All plume groups")
|
|
1055
|
+
return " - ".join(components)
|
|
1056
|
+
|
|
1057
|
+
|
|
1058
|
+
def _calculate_plotly_quantiles(values: np.ndarray, percentile: float) -> float:
|
|
1059
|
+
values_sorted = values.copy()
|
|
1060
|
+
values_sorted.sort()
|
|
1061
|
+
n_val = len(values_sorted)
|
|
1062
|
+
a = n_val * percentile - 0.5
|
|
1063
|
+
if a.is_integer():
|
|
1064
|
+
return float(values_sorted[int(a)])
|
|
1065
|
+
return float(np.interp(a, list(range(0, n_val)), values_sorted))
|
|
1066
|
+
|
|
1067
|
+
|
|
1068
|
+
def _calculate_plotly_whiskers(
|
|
1069
|
+
values: np.ndarray, q1: float, q3: float
|
|
1070
|
+
) -> Tuple[float, float]:
|
|
1071
|
+
values_sorted = values.copy()
|
|
1072
|
+
values_sorted.sort()
|
|
1073
|
+
a = q1 - 1.5 * (q3 - q1)
|
|
1074
|
+
b = q3 + 1.5 * (q3 - q1)
|
|
1075
|
+
return values[values >= a].min(), values[values <= b].max()
|
|
1076
|
+
|
|
1077
|
+
|
|
1078
|
+
def _toggle_trace_visibility(traces: List, legendonly_names: List[str]) -> None:
|
|
1079
|
+
for t in traces:
|
|
1080
|
+
if t.name in legendonly_names:
|
|
1081
|
+
t.visible = "legendonly"
|
|
1082
|
+
else:
|
|
1083
|
+
t.visible = True
|