webviz-subsurface 0.2.35__py3-none-any.whl → 0.2.37__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/_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/_providers/ensemble_summary_provider/_provider_impl_arrow_lazy.py +1 -1
- webviz_subsurface/plugins/_co2_leakage/_plugin.py +531 -377
- webviz_subsurface/plugins/_co2_leakage/_utilities/_misc.py +9 -0
- webviz_subsurface/plugins/_co2_leakage/_utilities/callbacks.py +169 -173
- webviz_subsurface/plugins/_co2_leakage/_utilities/co2volume.py +329 -84
- webviz_subsurface/plugins/_co2_leakage/_utilities/containment_data_provider.py +147 -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 +189 -96
- 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 +29 -21
- webviz_subsurface/plugins/_co2_leakage/_utilities/unsmry_data_provider.py +108 -0
- webviz_subsurface/plugins/_co2_leakage/views/mainview/mainview.py +30 -18
- webviz_subsurface/plugins/_co2_leakage/views/mainview/settings.py +805 -343
- webviz_subsurface/plugins/_relative_permeability.py +1 -1
- {webviz_subsurface-0.2.35.dist-info → webviz_subsurface-0.2.37.dist-info}/METADATA +2 -2
- {webviz_subsurface-0.2.35.dist-info → webviz_subsurface-0.2.37.dist-info}/RECORD +32 -21
- {webviz_subsurface-0.2.35.dist-info → webviz_subsurface-0.2.37.dist-info}/WHEEL +1 -1
- /webviz_subsurface/plugins/_co2_leakage/_utilities/{fault_polygons.py → fault_polygons_handler.py} +0 -0
- {webviz_subsurface-0.2.35.dist-info → webviz_subsurface-0.2.37.dist-info}/LICENSE +0 -0
- {webviz_subsurface-0.2.35.dist-info → webviz_subsurface-0.2.37.dist-info}/LICENSE.chromedriver +0 -0
- {webviz_subsurface-0.2.35.dist-info → webviz_subsurface-0.2.37.dist-info}/entry_points.txt +0 -0
- {webviz_subsurface-0.2.35.dist-info → webviz_subsurface-0.2.37.dist-info}/top_level.txt +0 -0
|
@@ -9,6 +9,9 @@ import plotly.graph_objects as go
|
|
|
9
9
|
|
|
10
10
|
from webviz_subsurface._providers import EnsembleTableProvider
|
|
11
11
|
from webviz_subsurface._utils.enum_shim import StrEnum
|
|
12
|
+
from webviz_subsurface.plugins._co2_leakage._utilities.containment_data_provider import (
|
|
13
|
+
ContainmentDataProvider,
|
|
14
|
+
)
|
|
12
15
|
from webviz_subsurface.plugins._co2_leakage._utilities.generic import (
|
|
13
16
|
Co2MassScale,
|
|
14
17
|
Co2VolumeScale,
|
|
@@ -41,6 +44,26 @@ _COLOR_ZONES = [
|
|
|
41
44
|
"#34b36f",
|
|
42
45
|
]
|
|
43
46
|
|
|
47
|
+
_LIGHTER_COLORS = {
|
|
48
|
+
"black": "#909090",
|
|
49
|
+
"#222222": "#909090",
|
|
50
|
+
"#00aa00": "#55ff55",
|
|
51
|
+
"#006ddd": "#6eb6ff",
|
|
52
|
+
"#dd4300": "#ff9a6e",
|
|
53
|
+
"#e91451": "#f589a8",
|
|
54
|
+
"#daa218": "#f2d386",
|
|
55
|
+
"#208eb7": "#81cde9",
|
|
56
|
+
"#84bc04": "#cdfc63",
|
|
57
|
+
"#b74532": "#e19e92",
|
|
58
|
+
"#9a89b4": "#ccc4d9",
|
|
59
|
+
"#8d30ba": "#c891e3",
|
|
60
|
+
"#256b33": "#77d089",
|
|
61
|
+
"#95704d": "#cfb7a1",
|
|
62
|
+
"#1357ca": "#7ba7f3",
|
|
63
|
+
"#f75ef0": "#fbaef7",
|
|
64
|
+
"#34b36f": "#93e0b7",
|
|
65
|
+
}
|
|
66
|
+
|
|
44
67
|
|
|
45
68
|
def _read_dataframe(
|
|
46
69
|
table_provider: EnsembleTableProvider,
|
|
@@ -54,40 +77,6 @@ def _read_dataframe(
|
|
|
54
77
|
return df
|
|
55
78
|
|
|
56
79
|
|
|
57
|
-
def read_menu_options(
|
|
58
|
-
table_provider: EnsembleTableProvider,
|
|
59
|
-
realization: int,
|
|
60
|
-
relpath: str,
|
|
61
|
-
) -> Dict[str, List[str]]:
|
|
62
|
-
col_names = table_provider.column_names()
|
|
63
|
-
df = table_provider.get_column_data(col_names, [realization])
|
|
64
|
-
required_columns = ["date", "amount", "phase", "containment", "zone", "region"]
|
|
65
|
-
missing_columns = [col for col in required_columns if col not in col_names]
|
|
66
|
-
if len(missing_columns) > 0:
|
|
67
|
-
raise KeyError(
|
|
68
|
-
f"Missing expected columns {', '.join(missing_columns)} in {relpath}"
|
|
69
|
-
f" in realization {realization} (and possibly other csv-files). "
|
|
70
|
-
f"Provided files are likely from an old version of ccs-scripts."
|
|
71
|
-
)
|
|
72
|
-
zones = ["all"]
|
|
73
|
-
for zone in list(df["zone"]):
|
|
74
|
-
if zone not in zones:
|
|
75
|
-
zones.append(zone)
|
|
76
|
-
regions = ["all"]
|
|
77
|
-
for region in list(df["region"]):
|
|
78
|
-
if region not in regions:
|
|
79
|
-
regions.append(region)
|
|
80
|
-
if "free_gas" in list(df["phase"]):
|
|
81
|
-
phases = ["total", "free_gas", "trapped_gas", "aqueous"]
|
|
82
|
-
else:
|
|
83
|
-
phases = ["total", "gas", "aqueous"]
|
|
84
|
-
return {
|
|
85
|
-
"zones": zones if len(zones) > 1 else [],
|
|
86
|
-
"regions": regions if len(regions) > 1 else [],
|
|
87
|
-
"phases": phases,
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
|
|
91
80
|
def _get_colors(num_cols: int = 3, split: str = "zone") -> List[str]:
|
|
92
81
|
if split == "containment":
|
|
93
82
|
return [_COLOR_HAZARDOUS, _COLOR_OUTSIDE, _COLOR_CONTAINED]
|
|
@@ -106,7 +95,7 @@ def _get_marks(num_marks: int, mark_choice: str) -> List[str]:
|
|
|
106
95
|
return [""] * num_marks
|
|
107
96
|
if mark_choice == "containment":
|
|
108
97
|
return ["x", "/", ""]
|
|
109
|
-
if mark_choice in ["zone", "region"]:
|
|
98
|
+
if mark_choice in ["zone", "region", "plume_group"]:
|
|
110
99
|
base_pattern = ["", "/", "x", "-", "\\", "+", "|", "."]
|
|
111
100
|
if num_marks > len(base_pattern):
|
|
112
101
|
base_pattern *= int(np.ceil(num_marks / len(base_pattern)))
|
|
@@ -123,7 +112,7 @@ def _get_line_types(mark_options: List[str], mark_choice: str) -> List[str]:
|
|
|
123
112
|
return ["solid"]
|
|
124
113
|
if mark_choice == "containment":
|
|
125
114
|
return ["dash", "dot", "solid"]
|
|
126
|
-
if mark_choice in ["zone", "region"]:
|
|
115
|
+
if mark_choice in ["zone", "region", "plume_group"]:
|
|
127
116
|
if len(mark_options) > 8:
|
|
128
117
|
warnings.warn(
|
|
129
118
|
f"Large number of {mark_choice}s might make it hard "
|
|
@@ -167,6 +156,93 @@ def _prepare_pattern_and_color_options(
|
|
|
167
156
|
return cat_ord, colors, marks
|
|
168
157
|
|
|
169
158
|
|
|
159
|
+
def _prepare_pattern_and_color_options_statistics_plot(
|
|
160
|
+
df: pd.DataFrame,
|
|
161
|
+
containment_info: Dict,
|
|
162
|
+
color_choice: str,
|
|
163
|
+
mark_choice: str,
|
|
164
|
+
) -> Tuple[Dict, List, List]:
|
|
165
|
+
mark_options = [] if mark_choice == "none" else containment_info[f"{mark_choice}s"]
|
|
166
|
+
color_options = containment_info[f"{color_choice}s"]
|
|
167
|
+
num_colors = len(color_options)
|
|
168
|
+
num_marks = num_colors if mark_choice == "none" else len(mark_options)
|
|
169
|
+
line_types = _get_line_types(mark_options, mark_choice)
|
|
170
|
+
colors = _get_colors(num_colors, color_choice)
|
|
171
|
+
|
|
172
|
+
if mark_choice == "phase":
|
|
173
|
+
mark_options = ["total"] + mark_options
|
|
174
|
+
line_types = ["solid"] + line_types
|
|
175
|
+
num_marks += 1
|
|
176
|
+
if color_choice == "containment":
|
|
177
|
+
color_options = ["total"] + color_options
|
|
178
|
+
colors = ["black"] + colors
|
|
179
|
+
num_colors += 1
|
|
180
|
+
|
|
181
|
+
filter_mark = mark_choice != "phase"
|
|
182
|
+
filter_color = color_choice not in ["phase", "containment"]
|
|
183
|
+
_filter_rows(df, color_choice, mark_choice, filter_mark, filter_color)
|
|
184
|
+
|
|
185
|
+
if mark_choice == "none":
|
|
186
|
+
cat_ord = {"type": color_options}
|
|
187
|
+
df["type"] = df[color_choice]
|
|
188
|
+
return cat_ord, colors, line_types
|
|
189
|
+
df["type"] = [", ".join((c, m)) for c, m in zip(df[color_choice], df[mark_choice])]
|
|
190
|
+
|
|
191
|
+
if containment_info["sorting"] == "color":
|
|
192
|
+
cat_ord = {
|
|
193
|
+
"type": [", ".join((c, m)) for c in color_options for m in mark_options],
|
|
194
|
+
}
|
|
195
|
+
colors = [c for c in colors for _ in range(num_marks)]
|
|
196
|
+
line_types = line_types * num_colors
|
|
197
|
+
else:
|
|
198
|
+
cat_ord = {
|
|
199
|
+
"type": [", ".join((c, m)) for m in mark_options for c in color_options],
|
|
200
|
+
}
|
|
201
|
+
colors = colors * num_marks
|
|
202
|
+
line_types = [m for m in line_types for _ in range(num_colors)]
|
|
203
|
+
|
|
204
|
+
for m in mark_options + ["total", "all"]:
|
|
205
|
+
df["type"] = df["type"].replace(f"total, {m}", m)
|
|
206
|
+
df["type"] = df["type"].replace(f"all, {m}", m)
|
|
207
|
+
for m in color_options:
|
|
208
|
+
df["type"] = df["type"].replace(f"{m}, total", m)
|
|
209
|
+
df["type"] = df["type"].replace(f"{m}, all", m)
|
|
210
|
+
cat_ord["type"] = [
|
|
211
|
+
label.replace("total, ", "") if "total, " in label else label
|
|
212
|
+
for label in cat_ord["type"]
|
|
213
|
+
]
|
|
214
|
+
cat_ord["type"] = [
|
|
215
|
+
label.replace("all, ", "") if "all, " in label else label
|
|
216
|
+
for label in cat_ord["type"]
|
|
217
|
+
]
|
|
218
|
+
cat_ord["type"] = [
|
|
219
|
+
label.replace(", total", "") if ", total" in label else label
|
|
220
|
+
for label in cat_ord["type"]
|
|
221
|
+
]
|
|
222
|
+
cat_ord["type"] = [
|
|
223
|
+
label.replace(", all", "") if ", all" in label else label
|
|
224
|
+
for label in cat_ord["type"]
|
|
225
|
+
]
|
|
226
|
+
|
|
227
|
+
return cat_ord, colors, line_types
|
|
228
|
+
|
|
229
|
+
|
|
230
|
+
def _find_default_option_statistics_figure(
|
|
231
|
+
df: pd.DataFrame, categories: List[str]
|
|
232
|
+
) -> str:
|
|
233
|
+
if "hazardous" in categories:
|
|
234
|
+
default_option = "hazardous"
|
|
235
|
+
else:
|
|
236
|
+
max_value = -999.9
|
|
237
|
+
default_option = categories[0]
|
|
238
|
+
for category in categories:
|
|
239
|
+
df_filtered = df[df["type"] == category]
|
|
240
|
+
if df_filtered["amount"].max() > max_value:
|
|
241
|
+
max_value = df_filtered["amount"].max()
|
|
242
|
+
default_option = category
|
|
243
|
+
return default_option
|
|
244
|
+
|
|
245
|
+
|
|
170
246
|
def _prepare_line_type_and_color_options(
|
|
171
247
|
df: pd.DataFrame,
|
|
172
248
|
containment_info: Dict,
|
|
@@ -180,6 +256,7 @@ def _prepare_line_type_and_color_options(
|
|
|
180
256
|
num_colors = len(color_options)
|
|
181
257
|
line_types = _get_line_types(mark_options, mark_choice)
|
|
182
258
|
colors = _get_colors(num_colors, color_choice)
|
|
259
|
+
|
|
183
260
|
filter_mark = True
|
|
184
261
|
if mark_choice == "phase":
|
|
185
262
|
mark_options = ["total"] + mark_options
|
|
@@ -225,22 +302,8 @@ def _prepare_line_type_and_color_options(
|
|
|
225
302
|
return options
|
|
226
303
|
|
|
227
304
|
|
|
228
|
-
def _find_scale_factor(
|
|
229
|
-
table_provider: EnsembleTableProvider,
|
|
230
|
-
scale: Union[Co2MassScale, Co2VolumeScale],
|
|
231
|
-
) -> float:
|
|
232
|
-
if scale in (Co2MassScale.KG, Co2VolumeScale.CUBIC_METERS):
|
|
233
|
-
return 1.0
|
|
234
|
-
if scale in (Co2MassScale.MTONS, Co2VolumeScale.BILLION_CUBIC_METERS):
|
|
235
|
-
return 1e9
|
|
236
|
-
if scale in (Co2MassScale.NORMALIZE, Co2VolumeScale.NORMALIZE):
|
|
237
|
-
df = table_provider.get_column_data(table_provider.column_names())
|
|
238
|
-
return df["total"].max()
|
|
239
|
-
return 1.0
|
|
240
|
-
|
|
241
|
-
|
|
242
305
|
def _read_terminal_co2_volumes(
|
|
243
|
-
table_provider:
|
|
306
|
+
table_provider: ContainmentDataProvider,
|
|
244
307
|
realizations: List[int],
|
|
245
308
|
scale: Union[Co2MassScale, Co2VolumeScale],
|
|
246
309
|
containment_info: Dict[str, Union[str, None, List[str]]],
|
|
@@ -258,11 +321,10 @@ def _read_terminal_co2_volumes(
|
|
|
258
321
|
records[color_choice] = []
|
|
259
322
|
if mark_choice != "none":
|
|
260
323
|
records[mark_choice] = []
|
|
261
|
-
scale_factor = _find_scale_factor(table_provider, scale)
|
|
262
324
|
data_frame = None
|
|
263
325
|
for real in realizations:
|
|
264
|
-
df =
|
|
265
|
-
df = df[df["date"] ==
|
|
326
|
+
df = table_provider.extract_dataframe(real, scale)
|
|
327
|
+
df = df[df["date"] == containment_info["date_option"]]
|
|
266
328
|
_add_sort_key_and_real(df, str(real), containment_info)
|
|
267
329
|
_filter_columns(df, color_choice, mark_choice, containment_info)
|
|
268
330
|
_filter_rows(df, color_choice, mark_choice)
|
|
@@ -285,7 +347,7 @@ def _filter_columns(
|
|
|
285
347
|
) -> None:
|
|
286
348
|
filter_columns = [
|
|
287
349
|
col
|
|
288
|
-
for col in ["phase", "containment", "zone", "region"]
|
|
350
|
+
for col in ["phase", "containment", "zone", "region", "plume_group"]
|
|
289
351
|
if col not in [mark_choice, color_choice]
|
|
290
352
|
]
|
|
291
353
|
for col in filter_columns:
|
|
@@ -298,8 +360,10 @@ def _filter_rows(
|
|
|
298
360
|
color_choice: str,
|
|
299
361
|
mark_choice: str,
|
|
300
362
|
filter_mark: bool = True,
|
|
363
|
+
filter_color: bool = True,
|
|
301
364
|
) -> None:
|
|
302
|
-
|
|
365
|
+
if filter_color:
|
|
366
|
+
df.query(f'{color_choice} not in ["total", "all"]', inplace=True)
|
|
303
367
|
if mark_choice != "none" and filter_mark:
|
|
304
368
|
df.query(f'{mark_choice} not in ["total", "all"]', inplace=True)
|
|
305
369
|
|
|
@@ -315,6 +379,7 @@ def _add_sort_key_and_real(
|
|
|
315
379
|
& (df["containment"] == "hazardous")
|
|
316
380
|
& (df["zone"] == containment_info["zone"])
|
|
317
381
|
& (df["region"] == containment_info["region"])
|
|
382
|
+
& (df["plume_group"] == containment_info["plume_group"])
|
|
318
383
|
]["amount"]
|
|
319
384
|
)
|
|
320
385
|
sort_value_secondary = np.sum(
|
|
@@ -323,6 +388,7 @@ def _add_sort_key_and_real(
|
|
|
323
388
|
& (df["containment"] == "outside")
|
|
324
389
|
& (df["zone"] == containment_info["zone"])
|
|
325
390
|
& (df["region"] == containment_info["region"])
|
|
391
|
+
& (df["plume_group"] == containment_info["plume_group"])
|
|
326
392
|
]["amount"]
|
|
327
393
|
)
|
|
328
394
|
df["real"] = [label] * df.shape[0]
|
|
@@ -331,14 +397,13 @@ def _add_sort_key_and_real(
|
|
|
331
397
|
|
|
332
398
|
|
|
333
399
|
def _read_co2_volumes(
|
|
334
|
-
table_provider:
|
|
400
|
+
table_provider: ContainmentDataProvider,
|
|
335
401
|
realizations: List[int],
|
|
336
402
|
scale: Union[Co2MassScale, Co2VolumeScale],
|
|
337
403
|
) -> pd.DataFrame:
|
|
338
|
-
scale_factor = _find_scale_factor(table_provider, scale)
|
|
339
404
|
return pd.concat(
|
|
340
405
|
[
|
|
341
|
-
|
|
406
|
+
table_provider.extract_dataframe(r, scale).assign(realization=r)
|
|
342
407
|
for r in realizations
|
|
343
408
|
]
|
|
344
409
|
)
|
|
@@ -357,15 +422,21 @@ def _change_names(
|
|
|
357
422
|
df["name"] = df["name"].replace(f"{m}, all", m)
|
|
358
423
|
|
|
359
424
|
|
|
360
|
-
def _adjust_figure(fig: go.Figure) -> None:
|
|
425
|
+
def _adjust_figure(fig: go.Figure, plot_title: Optional[str] = None) -> None:
|
|
361
426
|
fig.layout.legend.orientation = "v"
|
|
362
427
|
fig.layout.legend.title.text = ""
|
|
363
428
|
fig.layout.legend.itemwidth = 40
|
|
364
429
|
fig.layout.xaxis.exponentformat = "power"
|
|
365
|
-
|
|
430
|
+
if plot_title is not None:
|
|
431
|
+
fig.layout.title.text = plot_title
|
|
432
|
+
fig.layout.title.font = {"size": 14}
|
|
433
|
+
fig.layout.margin.t = 40
|
|
434
|
+
fig.layout.title.y = 0.95
|
|
435
|
+
else:
|
|
436
|
+
fig.layout.margin.t = 15
|
|
437
|
+
fig.layout.title.x = 0.4
|
|
366
438
|
fig.layout.paper_bgcolor = "rgba(0,0,0,0)"
|
|
367
439
|
fig.layout.margin.b = 6
|
|
368
|
-
fig.layout.margin.t = 15
|
|
369
440
|
fig.layout.margin.l = 10
|
|
370
441
|
fig.layout.margin.r = 10
|
|
371
442
|
fig.update_layout(
|
|
@@ -402,7 +473,7 @@ def _add_prop_to_df(
|
|
|
402
473
|
|
|
403
474
|
|
|
404
475
|
def generate_co2_volume_figure(
|
|
405
|
-
table_provider:
|
|
476
|
+
table_provider: ContainmentDataProvider,
|
|
406
477
|
realizations: List[int],
|
|
407
478
|
scale: Union[Co2MassScale, Co2VolumeScale],
|
|
408
479
|
containment_info: Dict[str, Any],
|
|
@@ -429,17 +500,21 @@ def generate_co2_volume_figure(
|
|
|
429
500
|
pattern_shape_sequence=marks,
|
|
430
501
|
orientation="h",
|
|
431
502
|
category_orders=cat_ord,
|
|
432
|
-
|
|
503
|
+
custom_data=["type", "prop"],
|
|
504
|
+
)
|
|
505
|
+
fig.update_traces(
|
|
506
|
+
hovertemplate="Type: %{customdata[0]}<br>Amount: %{x:.3f}<br>"
|
|
507
|
+
"Realization: %{y}<br>Proportion: %{customdata[1]}<extra></extra>",
|
|
433
508
|
)
|
|
434
509
|
fig.layout.yaxis.title = "Realization"
|
|
435
510
|
fig.layout.xaxis.title = scale.value
|
|
436
|
-
_adjust_figure(fig)
|
|
511
|
+
_adjust_figure(fig, plot_title=containment_info["date_option"])
|
|
437
512
|
return fig
|
|
438
513
|
|
|
439
514
|
|
|
440
515
|
# pylint: disable=too-many-locals
|
|
441
516
|
def generate_co2_time_containment_one_realization_figure(
|
|
442
|
-
table_provider:
|
|
517
|
+
table_provider: ContainmentDataProvider,
|
|
443
518
|
scale: Union[Co2MassScale, Co2VolumeScale],
|
|
444
519
|
time_series_realization: int,
|
|
445
520
|
y_limits: List[Optional[float]],
|
|
@@ -477,10 +552,11 @@ def generate_co2_time_containment_one_realization_figure(
|
|
|
477
552
|
pattern_shape_sequence=marks,
|
|
478
553
|
category_orders=cat_ord,
|
|
479
554
|
range_y=y_limits,
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
}
|
|
555
|
+
custom_data=["type", "prop"],
|
|
556
|
+
)
|
|
557
|
+
fig.update_traces(
|
|
558
|
+
hovertemplate="Type: %{customdata[0]}<br>Date: %{x}<br>"
|
|
559
|
+
"Amount: %{y:.3f}<br>Proportion: %{customdata[1]}<extra></extra>",
|
|
484
560
|
)
|
|
485
561
|
_add_hover_info_in_field(fig, df, cat_ord, colors)
|
|
486
562
|
fig.layout.yaxis.range = y_limits
|
|
@@ -535,15 +611,15 @@ def _add_hover_info_in_field(
|
|
|
535
611
|
p15 = prev_val + 0.15 * amount
|
|
536
612
|
p85 = prev_val + 0.85 * amount
|
|
537
613
|
y_vals = np.linspace(p15, p85, 8).tolist() * len(date_dict[date])
|
|
538
|
-
y_vals.sort()
|
|
614
|
+
y_vals.sort() # type: ignore[attr-defined]
|
|
539
615
|
fig.add_trace(
|
|
540
616
|
go.Scatter(
|
|
541
617
|
x=date_dict[date] * 8,
|
|
542
618
|
y=y_vals,
|
|
543
619
|
mode="lines",
|
|
544
620
|
line=go.scatter.Line(color=color),
|
|
545
|
-
text=f"
|
|
546
|
-
f"
|
|
621
|
+
text=f"Type: {name}<br>Date: {date_strings[date]}<br>"
|
|
622
|
+
f"Amount: {amount:.3f}<br>Proportion: {prop}",
|
|
547
623
|
opacity=0,
|
|
548
624
|
hoverinfo="text",
|
|
549
625
|
hoveron="points",
|
|
@@ -553,9 +629,82 @@ def _add_hover_info_in_field(
|
|
|
553
629
|
prev_vals[date] = prev_val + amount
|
|
554
630
|
|
|
555
631
|
|
|
632
|
+
def _connect_plume_groups(
|
|
633
|
+
df: pd.DataFrame,
|
|
634
|
+
color_choice: str,
|
|
635
|
+
mark_choice: str,
|
|
636
|
+
) -> None:
|
|
637
|
+
col_list = ["realization"]
|
|
638
|
+
if color_choice == "plume_group" and mark_choice != "none":
|
|
639
|
+
col_list.append(mark_choice)
|
|
640
|
+
elif mark_choice == "plume_group":
|
|
641
|
+
col_list.append(color_choice)
|
|
642
|
+
|
|
643
|
+
cols: Union[List[str], str] = col_list
|
|
644
|
+
if len(col_list) == 1:
|
|
645
|
+
cols = col_list[0]
|
|
646
|
+
# Find points where plumes start or end, to connect the lines
|
|
647
|
+
end_points = []
|
|
648
|
+
start_points = []
|
|
649
|
+
for plume_name, df_sub in df.groupby("plume_group"):
|
|
650
|
+
if plume_name == "undetermined":
|
|
651
|
+
continue
|
|
652
|
+
for _, df_sub2 in df_sub.groupby(cols):
|
|
653
|
+
# Assumes the data frame is sorted on date
|
|
654
|
+
mask_end = (
|
|
655
|
+
(df_sub2["amount"] == 0.0)
|
|
656
|
+
& (df_sub2["amount"].shift(1) > 0.0)
|
|
657
|
+
& (df_sub2.index > 0)
|
|
658
|
+
)
|
|
659
|
+
mask_start = (
|
|
660
|
+
(df_sub2["amount"] > 0.0)
|
|
661
|
+
& (df_sub2["amount"].shift(1) == 0.0)
|
|
662
|
+
& (df_sub2.index > 0)
|
|
663
|
+
)
|
|
664
|
+
first_index_end = mask_end.idxmax() if mask_end.any() else None
|
|
665
|
+
first_index_start = mask_start.idxmax() if mask_start.any() else None
|
|
666
|
+
transition_row_end = (
|
|
667
|
+
df_sub2.loc[first_index_end] if first_index_end is not None else None
|
|
668
|
+
)
|
|
669
|
+
transition_row_start = (
|
|
670
|
+
df_sub2.loc[first_index_start]
|
|
671
|
+
if first_index_start is not None
|
|
672
|
+
else None
|
|
673
|
+
)
|
|
674
|
+
if transition_row_end is not None:
|
|
675
|
+
end_points.append(transition_row_end)
|
|
676
|
+
# Replace 0 with np.nan for all dates after this
|
|
677
|
+
date = str(transition_row_end["date"])
|
|
678
|
+
df.loc[
|
|
679
|
+
(df["plume_group"] == plume_name)
|
|
680
|
+
& (df["amount"] == 0.0)
|
|
681
|
+
& (df["date"] > date),
|
|
682
|
+
"amount",
|
|
683
|
+
] = np.nan
|
|
684
|
+
if transition_row_start is not None:
|
|
685
|
+
start_points.append(transition_row_start)
|
|
686
|
+
for end_point in end_points:
|
|
687
|
+
plume1 = end_point["plume_group"]
|
|
688
|
+
row1 = end_point.drop(["amount", "plume_group", "name"])
|
|
689
|
+
for start_point in start_points:
|
|
690
|
+
plume2 = start_point["plume_group"]
|
|
691
|
+
if plume1 in plume2 and len(plume1) < len(plume2):
|
|
692
|
+
row2 = start_point.drop(["amount", "plume_group", "name"])
|
|
693
|
+
if row1.equals(row2):
|
|
694
|
+
row_to_change = df.eq(end_point).all(axis=1)
|
|
695
|
+
if sum(row_to_change) == 1:
|
|
696
|
+
df.loc[row_to_change, "amount"] = start_point["amount"]
|
|
697
|
+
df["is_merged"] = ["+" in x for x in df["plume_group"].values]
|
|
698
|
+
df.loc[
|
|
699
|
+
(df["plume_group"] != "all") & (df["is_merged"]) & (df["amount"] == 0.0),
|
|
700
|
+
"amount",
|
|
701
|
+
] = np.nan
|
|
702
|
+
df.drop(columns="is_merged", inplace=True)
|
|
703
|
+
|
|
704
|
+
|
|
556
705
|
# pylint: disable=too-many-locals
|
|
557
706
|
def generate_co2_time_containment_figure(
|
|
558
|
-
table_provider:
|
|
707
|
+
table_provider: ContainmentDataProvider,
|
|
559
708
|
realizations: List[int],
|
|
560
709
|
scale: Union[Co2MassScale, Co2VolumeScale],
|
|
561
710
|
containment_info: Dict[str, Any],
|
|
@@ -570,6 +719,12 @@ def generate_co2_time_containment_figure(
|
|
|
570
719
|
active_cols_at_startup = list(
|
|
571
720
|
options[options["line_type"].isin(["solid", "0px"])]["name"]
|
|
572
721
|
)
|
|
722
|
+
if "plume_group" in df:
|
|
723
|
+
try:
|
|
724
|
+
_connect_plume_groups(df, color_choice, mark_choice)
|
|
725
|
+
except ValueError:
|
|
726
|
+
pass
|
|
727
|
+
|
|
573
728
|
fig = go.Figure()
|
|
574
729
|
# Generate dummy scatters for legend entries
|
|
575
730
|
dummy_args = {"x": df["date"], "mode": "lines", "hoverinfo": "none"}
|
|
@@ -585,28 +740,65 @@ def generate_co2_time_containment_figure(
|
|
|
585
740
|
if name not in active_cols_at_startup:
|
|
586
741
|
args["visible"] = "legendonly"
|
|
587
742
|
fig.add_scatter(y=[0.0], **dummy_args, **args)
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
743
|
+
|
|
744
|
+
hover_template = (
|
|
745
|
+
"Type: %{meta[1]}<br>Date: %{x}<br>Amount: %{y:.3f}<br>"
|
|
746
|
+
"Realization: %{meta[0]}<br>Proportion: %{customdata}"
|
|
747
|
+
)
|
|
748
|
+
|
|
749
|
+
if containment_info["use_stats"]:
|
|
750
|
+
df_no_real = df.drop(columns=["REAL", "realization"]).reset_index(drop=True)
|
|
751
|
+
if mark_choice == "none":
|
|
752
|
+
df_grouped = df_no_real.groupby(
|
|
753
|
+
["date", "name", color_choice], as_index=False
|
|
754
|
+
)
|
|
755
|
+
else:
|
|
756
|
+
df_grouped = df_no_real.groupby(
|
|
757
|
+
["date", "name", color_choice, mark_choice], as_index=False
|
|
758
|
+
)
|
|
759
|
+
df_mean = df_grouped.agg(np.mean)
|
|
760
|
+
df_mean["realization"] = ["mean"] * df_mean.shape[0]
|
|
761
|
+
df_p10 = df_grouped.agg(lambda x: np.quantile(x, 0.1))
|
|
762
|
+
df_p10["realization"] = ["p10"] * df_p10.shape[0]
|
|
763
|
+
df_p90 = df_grouped.agg(lambda x: np.quantile(x, 0.9))
|
|
764
|
+
df_p90["realization"] = ["p90"] * df_p90.shape[0]
|
|
765
|
+
df = (
|
|
766
|
+
pd.concat([df_mean, df_p10, df_p90])
|
|
767
|
+
.sort_values(["name", "date"])
|
|
768
|
+
.reset_index(drop=True)
|
|
769
|
+
)
|
|
770
|
+
realizations = ["p10", "mean", "p90"] # type: ignore
|
|
771
|
+
hover_template = (
|
|
772
|
+
"Type: %{meta[1]}<br>Date: %{x}<br>Amount: %{y:.3f}<br>"
|
|
773
|
+
"Statistic: %{meta[0]}"
|
|
592
774
|
)
|
|
775
|
+
for rlz in realizations:
|
|
776
|
+
lwd = 1.5 if rlz in ["p10", "p90"] else 2.5
|
|
777
|
+
sub_df = df[df["realization"] == rlz].copy().reset_index(drop=True)
|
|
778
|
+
if not containment_info["use_stats"]:
|
|
779
|
+
_add_prop_to_df(
|
|
780
|
+
sub_df, np.unique(df["date"]), "date", [color_choice, mark_choice]
|
|
781
|
+
)
|
|
593
782
|
common_args = {
|
|
594
783
|
"x": sub_df["date"],
|
|
595
|
-
"hovertemplate": "%{x}: %{y}<br>Realization: %{meta[0]}<br>Prop: %{customdata}%",
|
|
596
|
-
"meta": [rlz],
|
|
597
784
|
"showlegend": False,
|
|
598
785
|
}
|
|
599
786
|
for name, color, line_type in zip(
|
|
600
787
|
options["name"], options["color"], options["line_type"]
|
|
601
788
|
):
|
|
602
|
-
# NBNB-AS: Check this, mypy complains:
|
|
603
789
|
args = {
|
|
604
790
|
"line_dash": line_type,
|
|
605
|
-
"
|
|
791
|
+
"line_width": lwd,
|
|
792
|
+
"marker_color": (
|
|
793
|
+
_LIGHTER_COLORS[color] if rlz in ["p10", "p90"] else color
|
|
794
|
+
),
|
|
606
795
|
"legendgroup": name,
|
|
607
|
-
"name":
|
|
608
|
-
"
|
|
796
|
+
"name": "",
|
|
797
|
+
"meta": [rlz, name],
|
|
798
|
+
"hovertemplate": hover_template,
|
|
609
799
|
}
|
|
800
|
+
if not containment_info["use_stats"]:
|
|
801
|
+
args["customdata"] = sub_df[sub_df["name"] == name]["prop"]
|
|
610
802
|
if name not in active_cols_at_startup:
|
|
611
803
|
args["visible"] = "legendonly"
|
|
612
804
|
fig.add_scatter(
|
|
@@ -618,3 +810,56 @@ def generate_co2_time_containment_figure(
|
|
|
618
810
|
fig.layout.yaxis.autorange = True
|
|
619
811
|
_adjust_figure(fig)
|
|
620
812
|
return fig
|
|
813
|
+
|
|
814
|
+
|
|
815
|
+
def generate_co2_statistics_figure(
|
|
816
|
+
table_provider: ContainmentDataProvider,
|
|
817
|
+
realizations: List[int],
|
|
818
|
+
scale: Union[Co2MassScale, Co2VolumeScale],
|
|
819
|
+
containment_info: Dict[str, Any],
|
|
820
|
+
) -> go.Figure:
|
|
821
|
+
date_option = containment_info["date_option"]
|
|
822
|
+
df = _read_co2_volumes(table_provider, realizations, scale)
|
|
823
|
+
df = df[df["date"] == date_option]
|
|
824
|
+
df = df.drop(columns=["date"]).reset_index(drop=True)
|
|
825
|
+
color_choice = containment_info["color_choice"]
|
|
826
|
+
mark_choice = containment_info["mark_choice"]
|
|
827
|
+
_filter_columns(df, color_choice, mark_choice, containment_info)
|
|
828
|
+
cat_ord, colors, line_types = _prepare_pattern_and_color_options_statistics_plot(
|
|
829
|
+
df,
|
|
830
|
+
containment_info,
|
|
831
|
+
color_choice,
|
|
832
|
+
mark_choice,
|
|
833
|
+
)
|
|
834
|
+
|
|
835
|
+
# Remove if we want realization as label?
|
|
836
|
+
df = df.drop(columns=["REAL", "realization"]).reset_index(drop=True)
|
|
837
|
+
fig = px.ecdf(
|
|
838
|
+
df,
|
|
839
|
+
x="amount",
|
|
840
|
+
ecdfmode="reversed",
|
|
841
|
+
ecdfnorm="probability",
|
|
842
|
+
markers=True,
|
|
843
|
+
color="type",
|
|
844
|
+
color_discrete_sequence=colors,
|
|
845
|
+
line_dash="type" if mark_choice != "none" else None,
|
|
846
|
+
line_dash_sequence=line_types,
|
|
847
|
+
category_orders=cat_ord,
|
|
848
|
+
)
|
|
849
|
+
|
|
850
|
+
default_option = _find_default_option_statistics_figure(df, cat_ord["type"])
|
|
851
|
+
for trace in fig.data:
|
|
852
|
+
if trace.name != default_option:
|
|
853
|
+
trace.visible = "legendonly"
|
|
854
|
+
|
|
855
|
+
fig.update_traces(
|
|
856
|
+
hovertemplate="Type: %{data.name}<br>Amount: %{x:.3f}<br>"
|
|
857
|
+
"Probability: %{y:.3f}<extra></extra>",
|
|
858
|
+
)
|
|
859
|
+
fig.layout.yaxis.range = [-0.02, 1.02]
|
|
860
|
+
fig.layout.legend.tracegroupgap = 0
|
|
861
|
+
fig.layout.xaxis.title = scale.value
|
|
862
|
+
fig.layout.yaxis.title = "Probability"
|
|
863
|
+
_adjust_figure(fig, plot_title=containment_info["date_option"])
|
|
864
|
+
|
|
865
|
+
return fig
|