webviz-subsurface 0.2.30__py3-none-any.whl → 0.2.32__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/_components/tornado/_tornado_data.py +3 -0
- webviz_subsurface/_providers/ensemble_surface_provider/surface_array_server.py +0 -1
- webviz_subsurface/_providers/ensemble_surface_provider/surface_image_server.py +2 -7
- webviz_subsurface/plugins/_co2_leakage/_plugin.py +79 -37
- webviz_subsurface/plugins/_co2_leakage/_utilities/callbacks.py +99 -38
- webviz_subsurface/plugins/_co2_leakage/_utilities/co2volume.py +417 -355
- webviz_subsurface/plugins/_co2_leakage/_utilities/generic.py +2 -7
- webviz_subsurface/plugins/_co2_leakage/_utilities/initialization.py +15 -11
- webviz_subsurface/plugins/_co2_leakage/_utilities/surface_publishing.py +13 -4
- webviz_subsurface/plugins/_co2_leakage/views/mainview/mainview.py +93 -33
- webviz_subsurface/plugins/_co2_leakage/views/mainview/settings.py +301 -116
- webviz_subsurface/plugins/_volumetric_analysis/controllers/tornado_controllers.py +5 -1
- {webviz_subsurface-0.2.30.dist-info → webviz_subsurface-0.2.32.dist-info}/METADATA +34 -34
- {webviz_subsurface-0.2.30.dist-info → webviz_subsurface-0.2.32.dist-info}/RECORD +19 -19
- {webviz_subsurface-0.2.30.dist-info → webviz_subsurface-0.2.32.dist-info}/WHEEL +1 -1
- {webviz_subsurface-0.2.30.dist-info → webviz_subsurface-0.2.32.dist-info}/LICENSE +0 -0
- {webviz_subsurface-0.2.30.dist-info → webviz_subsurface-0.2.32.dist-info}/LICENSE.chromedriver +0 -0
- {webviz_subsurface-0.2.30.dist-info → webviz_subsurface-0.2.32.dist-info}/entry_points.txt +0 -0
- {webviz_subsurface-0.2.30.dist-info → webviz_subsurface-0.2.32.dist-info}/top_level.txt +0 -0
|
@@ -1,7 +1,9 @@
|
|
|
1
|
+
import warnings
|
|
2
|
+
from datetime import datetime as dt
|
|
1
3
|
from typing import Any, Dict, List, Optional, Tuple, Union
|
|
2
4
|
|
|
3
5
|
import numpy as np
|
|
4
|
-
import pandas
|
|
6
|
+
import pandas as pd
|
|
5
7
|
import plotly.express as px
|
|
6
8
|
import plotly.graph_objects as go
|
|
7
9
|
|
|
@@ -10,7 +12,6 @@ from webviz_subsurface._utils.enum_shim import StrEnum
|
|
|
10
12
|
from webviz_subsurface.plugins._co2_leakage._utilities.generic import (
|
|
11
13
|
Co2MassScale,
|
|
12
14
|
Co2VolumeScale,
|
|
13
|
-
ContainmentViews,
|
|
14
15
|
)
|
|
15
16
|
|
|
16
17
|
|
|
@@ -45,100 +46,51 @@ def _read_dataframe(
|
|
|
45
46
|
table_provider: EnsembleTableProvider,
|
|
46
47
|
realization: int,
|
|
47
48
|
scale_factor: float,
|
|
48
|
-
|
|
49
|
-
) -> pandas.DataFrame:
|
|
49
|
+
) -> pd.DataFrame:
|
|
50
50
|
df = table_provider.get_column_data(table_provider.column_names(), [realization])
|
|
51
|
-
if any(split in list(df.columns) for split in ["zone", "region"]):
|
|
52
|
-
df = _process_containment_information(df, containment_info)
|
|
53
|
-
if containment_info["containment_view"] != ContainmentViews.CONTAINMENTSPLIT:
|
|
54
|
-
df["aqueous"] = (
|
|
55
|
-
df["aqueous_contained"]
|
|
56
|
-
+ df["aqueous_outside"]
|
|
57
|
-
+ df["aqueous_hazardous"]
|
|
58
|
-
)
|
|
59
|
-
df["gas"] = df["gas_contained"] + df["gas_outside"] + df["gas_hazardous"]
|
|
60
|
-
df = df.drop(
|
|
61
|
-
columns=[
|
|
62
|
-
"aqueous_contained",
|
|
63
|
-
"aqueous_outside",
|
|
64
|
-
"aqueous_hazardous",
|
|
65
|
-
"gas_contained",
|
|
66
|
-
"gas_outside",
|
|
67
|
-
"gas_hazardous",
|
|
68
|
-
]
|
|
69
|
-
)
|
|
70
51
|
if scale_factor == 1.0:
|
|
71
52
|
return df
|
|
72
|
-
|
|
73
|
-
if col not in ["date", "zone", "region"]:
|
|
74
|
-
df[col] /= scale_factor
|
|
53
|
+
df["amount"] /= scale_factor
|
|
75
54
|
return df
|
|
76
55
|
|
|
77
56
|
|
|
78
|
-
def
|
|
57
|
+
def read_menu_options(
|
|
79
58
|
table_provider: EnsembleTableProvider,
|
|
80
59
|
realization: int,
|
|
60
|
+
relpath: str,
|
|
81
61
|
) -> Dict[str, List[str]]:
|
|
82
|
-
|
|
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
|
+
)
|
|
83
72
|
zones = ["all"]
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
zones.append(zone)
|
|
73
|
+
for zone in list(df["zone"]):
|
|
74
|
+
if zone not in zones:
|
|
75
|
+
zones.append(zone)
|
|
88
76
|
regions = ["all"]
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
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"]
|
|
93
84
|
return {
|
|
94
85
|
"zones": zones if len(zones) > 1 else [],
|
|
95
86
|
"regions": regions if len(regions) > 1 else [],
|
|
87
|
+
"phases": phases,
|
|
96
88
|
}
|
|
97
89
|
|
|
98
90
|
|
|
99
|
-
def
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
) -> pandas.DataFrame:
|
|
103
|
-
view = containment_info["containment_view"]
|
|
104
|
-
if view == ContainmentViews.ZONESPLIT:
|
|
105
|
-
return (
|
|
106
|
-
df[df["zone"] != "all"]
|
|
107
|
-
.drop(columns="region", errors="ignore")
|
|
108
|
-
.reset_index(drop=True)
|
|
109
|
-
)
|
|
110
|
-
if view == ContainmentViews.REGIONSPLIT:
|
|
111
|
-
return (
|
|
112
|
-
df[df["region"] != "all"]
|
|
113
|
-
.drop(columns="zone", errors="ignore")
|
|
114
|
-
.reset_index(drop=True)
|
|
115
|
-
)
|
|
116
|
-
zone = containment_info["zone"]
|
|
117
|
-
region = containment_info["region"]
|
|
118
|
-
if zone not in ["all", None]:
|
|
119
|
-
if zone in list(df["zone"]):
|
|
120
|
-
return df[df["zone"] == zone].drop(
|
|
121
|
-
columns=["zone", "region"], errors="ignore"
|
|
122
|
-
)
|
|
123
|
-
print(f"Zone {zone} not found, using sum for each unique date.")
|
|
124
|
-
elif region not in ["all", None]:
|
|
125
|
-
if region in list(df["region"]):
|
|
126
|
-
return df[df["region"] == region].drop(
|
|
127
|
-
columns=["zone", "region"], errors="ignore"
|
|
128
|
-
)
|
|
129
|
-
print(f"Region {region} not found, using sum for each unique date.")
|
|
130
|
-
if "zone" in list(df.columns):
|
|
131
|
-
if "region" in list(df.columns):
|
|
132
|
-
return df[
|
|
133
|
-
[a and b for a, b in zip(df["zone"] == "all", df["region"] == "all")]
|
|
134
|
-
].drop(columns=["zone", "region"])
|
|
135
|
-
df = df[df["zone"] == "all"].drop(columns=["zone"])
|
|
136
|
-
elif "region" in list(df.columns):
|
|
137
|
-
df = df[df["region"] == "all"].drop(columns=["region"])
|
|
138
|
-
return df
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
def _split_colors(num_cols: int, split: str = "zone") -> List[str]:
|
|
91
|
+
def _get_colors(num_cols: int = 3, split: str = "zone") -> List[str]:
|
|
92
|
+
if split == "containment":
|
|
93
|
+
return [_COLOR_HAZARDOUS, _COLOR_OUTSIDE, _COLOR_CONTAINED]
|
|
142
94
|
options = list(_COLOR_ZONES)
|
|
143
95
|
if split == "region":
|
|
144
96
|
options.reverse()
|
|
@@ -149,6 +101,130 @@ def _split_colors(num_cols: int, split: str = "zone") -> List[str]:
|
|
|
149
101
|
return new_cols[:num_cols]
|
|
150
102
|
|
|
151
103
|
|
|
104
|
+
def _get_marks(num_marks: int, mark_choice: str) -> List[str]:
|
|
105
|
+
if mark_choice == "none":
|
|
106
|
+
return [""] * num_marks
|
|
107
|
+
if mark_choice == "containment":
|
|
108
|
+
return ["x", "/", ""]
|
|
109
|
+
if mark_choice in ["zone", "region"]:
|
|
110
|
+
base_pattern = ["", "/", "x", "-", "\\", "+", "|", "."]
|
|
111
|
+
if num_marks > len(base_pattern):
|
|
112
|
+
base_pattern *= int(np.ceil(num_marks / len(base_pattern)))
|
|
113
|
+
warnings.warn(
|
|
114
|
+
f"More {mark_choice}s than pattern options. "
|
|
115
|
+
f"Some {mark_choice}s will share pattern."
|
|
116
|
+
)
|
|
117
|
+
return base_pattern[:num_marks]
|
|
118
|
+
return ["", "/"] if num_marks == 2 else ["", ".", "/"]
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
def _get_line_types(mark_options: List[str], mark_choice: str) -> List[str]:
|
|
122
|
+
if mark_choice == "none":
|
|
123
|
+
return ["solid"]
|
|
124
|
+
if mark_choice == "containment":
|
|
125
|
+
return ["dash", "dot", "solid"]
|
|
126
|
+
if mark_choice in ["zone", "region"]:
|
|
127
|
+
if len(mark_options) > 8:
|
|
128
|
+
warnings.warn(
|
|
129
|
+
f"Large number of {mark_choice}s might make it hard "
|
|
130
|
+
f"to distinguish different dashed lines."
|
|
131
|
+
)
|
|
132
|
+
return [
|
|
133
|
+
f"{round(i / len(mark_options) * 25)}px" for i in range(len(mark_options))
|
|
134
|
+
]
|
|
135
|
+
return ["dot", "dash"] if "gas" in mark_options else ["dot", "dashdot", "dash"]
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
def _prepare_pattern_and_color_options(
|
|
139
|
+
df: pd.DataFrame,
|
|
140
|
+
containment_info: Dict,
|
|
141
|
+
color_choice: str,
|
|
142
|
+
mark_choice: str,
|
|
143
|
+
) -> Tuple[Dict, List, List]:
|
|
144
|
+
mark_options = [] if mark_choice == "none" else containment_info[f"{mark_choice}s"]
|
|
145
|
+
color_options = containment_info[f"{color_choice}s"]
|
|
146
|
+
num_colors = len(color_options)
|
|
147
|
+
num_marks = num_colors if mark_choice == "none" else len(mark_options)
|
|
148
|
+
marks = _get_marks(num_marks, mark_choice)
|
|
149
|
+
colors = _get_colors(num_colors, color_choice)
|
|
150
|
+
if mark_choice == "none":
|
|
151
|
+
cat_ord = {"type": color_options}
|
|
152
|
+
df["type"] = df[color_choice]
|
|
153
|
+
return cat_ord, colors, marks
|
|
154
|
+
df["type"] = [", ".join((c, m)) for c, m in zip(df[color_choice], df[mark_choice])]
|
|
155
|
+
if containment_info["sorting"] == "color":
|
|
156
|
+
cat_ord = {
|
|
157
|
+
"type": [", ".join((c, m)) for c in color_options for m in mark_options],
|
|
158
|
+
}
|
|
159
|
+
colors = [c for c in colors for _ in range(num_marks)]
|
|
160
|
+
marks = marks * num_colors
|
|
161
|
+
else:
|
|
162
|
+
cat_ord = {
|
|
163
|
+
"type": [", ".join((c, m)) for m in mark_options for c in color_options],
|
|
164
|
+
}
|
|
165
|
+
colors = colors * num_marks
|
|
166
|
+
marks = [m for m in marks for _ in range(num_colors)]
|
|
167
|
+
return cat_ord, colors, marks
|
|
168
|
+
|
|
169
|
+
|
|
170
|
+
def _prepare_line_type_and_color_options(
|
|
171
|
+
df: pd.DataFrame,
|
|
172
|
+
containment_info: Dict,
|
|
173
|
+
color_choice: str,
|
|
174
|
+
mark_choice: str,
|
|
175
|
+
) -> pd.DataFrame:
|
|
176
|
+
mark_options = []
|
|
177
|
+
if mark_choice != "none":
|
|
178
|
+
mark_options = list(containment_info[f"{mark_choice}s"])
|
|
179
|
+
color_options = list(containment_info[f"{color_choice}s"])
|
|
180
|
+
num_colors = len(color_options)
|
|
181
|
+
line_types = _get_line_types(mark_options, mark_choice)
|
|
182
|
+
colors = _get_colors(num_colors, color_choice)
|
|
183
|
+
filter_mark = True
|
|
184
|
+
if mark_choice == "phase":
|
|
185
|
+
mark_options = ["total"] + mark_options
|
|
186
|
+
line_types = ["solid"] + line_types
|
|
187
|
+
filter_mark = False
|
|
188
|
+
if color_choice == "containment":
|
|
189
|
+
color_options = ["total"] + color_options
|
|
190
|
+
colors = ["black"] + colors
|
|
191
|
+
else:
|
|
192
|
+
_filter_rows(df, color_choice, mark_choice, filter_mark)
|
|
193
|
+
if mark_choice == "none":
|
|
194
|
+
df["name"] = df[color_choice]
|
|
195
|
+
return pd.DataFrame(
|
|
196
|
+
{
|
|
197
|
+
"name": color_options,
|
|
198
|
+
"color": colors,
|
|
199
|
+
"line_type": line_types * len(colors),
|
|
200
|
+
}
|
|
201
|
+
)
|
|
202
|
+
df["name"] = [", ".join((c, m)) for c, m in zip(df[color_choice], df[mark_choice])]
|
|
203
|
+
_change_names(df, color_options, mark_options)
|
|
204
|
+
if containment_info["sorting"] == "color":
|
|
205
|
+
options = pd.DataFrame(
|
|
206
|
+
{
|
|
207
|
+
"name": [
|
|
208
|
+
", ".join((c, m)) for c in color_options for m in mark_options
|
|
209
|
+
],
|
|
210
|
+
"color": [c for c in colors for _ in mark_options],
|
|
211
|
+
"line_type": [l for _ in colors for l in line_types],
|
|
212
|
+
}
|
|
213
|
+
)
|
|
214
|
+
else:
|
|
215
|
+
options = pd.DataFrame(
|
|
216
|
+
{
|
|
217
|
+
"name": [
|
|
218
|
+
", ".join((c, m)) for m in mark_options for c in color_options
|
|
219
|
+
],
|
|
220
|
+
"color": [c for _ in mark_options for c in colors],
|
|
221
|
+
"line_type": [l for l in line_types for _ in colors],
|
|
222
|
+
}
|
|
223
|
+
)
|
|
224
|
+
_change_names(options, color_options, mark_options)
|
|
225
|
+
return options
|
|
226
|
+
|
|
227
|
+
|
|
152
228
|
def _find_scale_factor(
|
|
153
229
|
table_provider: EnsembleTableProvider,
|
|
154
230
|
scale: Union[Co2MassScale, Co2VolumeScale],
|
|
@@ -168,110 +244,161 @@ def _read_terminal_co2_volumes(
|
|
|
168
244
|
realizations: List[int],
|
|
169
245
|
scale: Union[Co2MassScale, Co2VolumeScale],
|
|
170
246
|
containment_info: Dict[str, Union[str, None, List[str]]],
|
|
171
|
-
) ->
|
|
172
|
-
view = containment_info["containment_view"]
|
|
247
|
+
) -> pd.DataFrame:
|
|
173
248
|
records: Dict[str, List[Any]] = {
|
|
174
249
|
"real": [],
|
|
175
250
|
"amount": [],
|
|
176
|
-
"phase": [],
|
|
177
251
|
"sort_key": [],
|
|
178
252
|
"sort_key_secondary": [],
|
|
179
253
|
}
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
254
|
+
color_choice = containment_info["color_choice"]
|
|
255
|
+
mark_choice = containment_info["mark_choice"]
|
|
256
|
+
assert isinstance(color_choice, str)
|
|
257
|
+
assert isinstance(mark_choice, str)
|
|
258
|
+
records[color_choice] = []
|
|
259
|
+
if mark_choice != "none":
|
|
260
|
+
records[mark_choice] = []
|
|
186
261
|
scale_factor = _find_scale_factor(table_provider, scale)
|
|
262
|
+
data_frame = None
|
|
187
263
|
for real in realizations:
|
|
188
|
-
df = _read_dataframe(table_provider, real, scale_factor
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
records["real"] += [label] * 2
|
|
197
|
-
records["amount"] += [
|
|
198
|
-
last["aqueous"],
|
|
199
|
-
last["gas"],
|
|
200
|
-
]
|
|
201
|
-
records["phase"] += ["aqueous", "gas"]
|
|
202
|
-
records[split] += [last[split]] * 2
|
|
203
|
-
records["sort_key"] += [label] * 2
|
|
204
|
-
records["sort_key_secondary"] += [last[split]] * 2
|
|
264
|
+
df = _read_dataframe(table_provider, real, scale_factor)
|
|
265
|
+
df = df[df["date"] == np.max(df["date"])]
|
|
266
|
+
_add_sort_key_and_real(df, str(real), containment_info)
|
|
267
|
+
_filter_columns(df, color_choice, mark_choice, containment_info)
|
|
268
|
+
_filter_rows(df, color_choice, mark_choice)
|
|
269
|
+
if data_frame is None:
|
|
270
|
+
data_frame = df
|
|
205
271
|
else:
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
records["real"] += [label] * 6
|
|
210
|
-
records["amount"] += [
|
|
211
|
-
last["aqueous_contained"],
|
|
212
|
-
last["gas_contained"],
|
|
213
|
-
last["aqueous_outside"],
|
|
214
|
-
last["gas_outside"],
|
|
215
|
-
last["aqueous_hazardous"],
|
|
216
|
-
last["gas_hazardous"],
|
|
217
|
-
]
|
|
218
|
-
records["containment"] += [
|
|
219
|
-
"contained",
|
|
220
|
-
"contained",
|
|
221
|
-
"outside",
|
|
222
|
-
"outside",
|
|
223
|
-
"hazardous",
|
|
224
|
-
"hazardous",
|
|
225
|
-
]
|
|
226
|
-
records["phase"] += ["aqueous", "gas", "aqueous", "gas", "aqueous", "gas"]
|
|
227
|
-
records["sort_key"] += [last["gas_hazardous"]] * 6
|
|
228
|
-
records["sort_key_secondary"] += [last["gas_outside"]] * 6
|
|
229
|
-
df = pandas.DataFrame.from_dict(records)
|
|
230
|
-
df.sort_values(
|
|
272
|
+
data_frame = pd.concat([data_frame, df])
|
|
273
|
+
assert data_frame is not None
|
|
274
|
+
data_frame.sort_values(
|
|
231
275
|
["sort_key", "sort_key_secondary"], inplace=True, ascending=[True, True]
|
|
232
276
|
)
|
|
233
|
-
return
|
|
277
|
+
return data_frame
|
|
278
|
+
|
|
279
|
+
|
|
280
|
+
def _filter_columns(
|
|
281
|
+
df: pd.DataFrame,
|
|
282
|
+
color_choice: str,
|
|
283
|
+
mark_choice: str,
|
|
284
|
+
containment_info: Dict,
|
|
285
|
+
) -> None:
|
|
286
|
+
filter_columns = [
|
|
287
|
+
col
|
|
288
|
+
for col in ["phase", "containment", "zone", "region"]
|
|
289
|
+
if col not in [mark_choice, color_choice]
|
|
290
|
+
]
|
|
291
|
+
for col in filter_columns:
|
|
292
|
+
df.query(f'{col} == "{containment_info[col]}"', inplace=True)
|
|
293
|
+
df.drop(columns=filter_columns, inplace=True)
|
|
294
|
+
|
|
295
|
+
|
|
296
|
+
def _filter_rows(
|
|
297
|
+
df: pd.DataFrame,
|
|
298
|
+
color_choice: str,
|
|
299
|
+
mark_choice: str,
|
|
300
|
+
filter_mark: bool = True,
|
|
301
|
+
) -> None:
|
|
302
|
+
df.query(f'{color_choice} not in ["total", "all"]', inplace=True)
|
|
303
|
+
if mark_choice != "none" and filter_mark:
|
|
304
|
+
df.query(f'{mark_choice} not in ["total", "all"]', inplace=True)
|
|
305
|
+
|
|
306
|
+
|
|
307
|
+
def _add_sort_key_and_real(
|
|
308
|
+
df: pd.DataFrame,
|
|
309
|
+
label: str,
|
|
310
|
+
containment_info: Dict,
|
|
311
|
+
) -> None:
|
|
312
|
+
sort_value = np.sum(
|
|
313
|
+
df[
|
|
314
|
+
(df["phase"] == "total")
|
|
315
|
+
& (df["containment"] == "hazardous")
|
|
316
|
+
& (df["zone"] == containment_info["zone"])
|
|
317
|
+
& (df["region"] == containment_info["region"])
|
|
318
|
+
]["amount"]
|
|
319
|
+
)
|
|
320
|
+
sort_value_secondary = np.sum(
|
|
321
|
+
df[
|
|
322
|
+
(df["phase"] == "total")
|
|
323
|
+
& (df["containment"] == "outside")
|
|
324
|
+
& (df["zone"] == containment_info["zone"])
|
|
325
|
+
& (df["region"] == containment_info["region"])
|
|
326
|
+
]["amount"]
|
|
327
|
+
)
|
|
328
|
+
df["real"] = [label] * df.shape[0]
|
|
329
|
+
df["sort_key"] = [sort_value] * df.shape[0]
|
|
330
|
+
df["sort_key_secondary"] = [sort_value_secondary] * df.shape[0]
|
|
234
331
|
|
|
235
332
|
|
|
236
333
|
def _read_co2_volumes(
|
|
237
334
|
table_provider: EnsembleTableProvider,
|
|
238
335
|
realizations: List[int],
|
|
239
336
|
scale: Union[Co2MassScale, Co2VolumeScale],
|
|
240
|
-
|
|
241
|
-
) -> pandas.DataFrame:
|
|
337
|
+
) -> pd.DataFrame:
|
|
242
338
|
scale_factor = _find_scale_factor(table_provider, scale)
|
|
243
|
-
return
|
|
339
|
+
return pd.concat(
|
|
244
340
|
[
|
|
245
|
-
_read_dataframe(
|
|
246
|
-
|
|
247
|
-
).assign(realization=real)
|
|
248
|
-
for real in realizations
|
|
341
|
+
_read_dataframe(table_provider, r, scale_factor).assign(realization=r)
|
|
342
|
+
for r in realizations
|
|
249
343
|
]
|
|
250
344
|
)
|
|
251
345
|
|
|
252
346
|
|
|
253
|
-
def
|
|
254
|
-
df
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
df["type"] = df["type"].replace("aqueous_outside", "Outside aqueous")
|
|
265
|
-
df["type"] = df["type"].replace("aqueous_hazardous", "Hazardous aqueous")
|
|
347
|
+
def _change_names(
|
|
348
|
+
df: pd.DataFrame,
|
|
349
|
+
color_options: List[str],
|
|
350
|
+
mark_options: List[str],
|
|
351
|
+
) -> None:
|
|
352
|
+
for m in mark_options + ["total", "all"]:
|
|
353
|
+
df["name"] = df["name"].replace(f"total, {m}", m)
|
|
354
|
+
df["name"] = df["name"].replace(f"all, {m}", m)
|
|
355
|
+
for m in color_options:
|
|
356
|
+
df["name"] = df["name"].replace(f"{m}, total", m)
|
|
357
|
+
df["name"] = df["name"].replace(f"{m}, all", m)
|
|
266
358
|
|
|
267
359
|
|
|
268
360
|
def _adjust_figure(fig: go.Figure) -> None:
|
|
361
|
+
fig.layout.legend.orientation = "v"
|
|
362
|
+
fig.layout.legend.title.text = ""
|
|
363
|
+
fig.layout.legend.itemwidth = 40
|
|
364
|
+
fig.layout.xaxis.exponentformat = "power"
|
|
269
365
|
fig.layout.title.x = 0.5
|
|
270
366
|
fig.layout.paper_bgcolor = "rgba(0,0,0,0)"
|
|
271
367
|
fig.layout.margin.b = 6
|
|
272
|
-
fig.layout.margin.t =
|
|
368
|
+
fig.layout.margin.t = 15
|
|
273
369
|
fig.layout.margin.l = 10
|
|
274
370
|
fig.layout.margin.r = 10
|
|
371
|
+
fig.update_layout(
|
|
372
|
+
legend={
|
|
373
|
+
"x": 1.05,
|
|
374
|
+
"xanchor": "left",
|
|
375
|
+
}
|
|
376
|
+
)
|
|
377
|
+
|
|
378
|
+
|
|
379
|
+
def _add_prop_to_df(
|
|
380
|
+
df: pd.DataFrame,
|
|
381
|
+
list_to_iterate: Union[List, np.ndarray],
|
|
382
|
+
column: str,
|
|
383
|
+
filter_columns: Optional[List[str]] = None,
|
|
384
|
+
) -> None:
|
|
385
|
+
prop = np.zeros(df.shape[0])
|
|
386
|
+
for element in list_to_iterate:
|
|
387
|
+
if filter_columns is None:
|
|
388
|
+
summed_amount = np.sum(df.loc[df[column] == element]["amount"])
|
|
389
|
+
else:
|
|
390
|
+
filter_for_sum = df[column] == element
|
|
391
|
+
for col in filter_columns:
|
|
392
|
+
if col in df.columns:
|
|
393
|
+
filter_for_sum &= ~df[col].isin(["total", "all"])
|
|
394
|
+
summed_amount = np.sum(df.loc[filter_for_sum]["amount"])
|
|
395
|
+
prop[np.where(df[column] == element)[0]] = summed_amount
|
|
396
|
+
nonzero = np.where(prop > 0)[0]
|
|
397
|
+
prop[nonzero] = (
|
|
398
|
+
np.round(np.array(df["amount"])[nonzero] / prop[nonzero] * 1000) / 10
|
|
399
|
+
)
|
|
400
|
+
df["prop"] = prop
|
|
401
|
+
df["prop"] = df["prop"].map(lambda p: str(p) + "%")
|
|
275
402
|
|
|
276
403
|
|
|
277
404
|
def generate_co2_volume_figure(
|
|
@@ -283,43 +410,34 @@ def generate_co2_volume_figure(
|
|
|
283
410
|
df = _read_terminal_co2_volumes(
|
|
284
411
|
table_provider, realizations, scale, containment_info
|
|
285
412
|
)
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
color = "containment"
|
|
296
|
-
cat_ord = {
|
|
297
|
-
"containment": ["hazardous", "outside", "contained"],
|
|
298
|
-
"phase": ["gas", "aqueous"],
|
|
299
|
-
}
|
|
300
|
-
colors = [_COLOR_HAZARDOUS, _COLOR_OUTSIDE, _COLOR_CONTAINED]
|
|
413
|
+
color_choice = containment_info["color_choice"]
|
|
414
|
+
mark_choice = containment_info["mark_choice"]
|
|
415
|
+
_add_prop_to_df(df, [str(r) for r in realizations], "real")
|
|
416
|
+
cat_ord, colors, marks = _prepare_pattern_and_color_options(
|
|
417
|
+
df,
|
|
418
|
+
containment_info,
|
|
419
|
+
color_choice,
|
|
420
|
+
mark_choice,
|
|
421
|
+
)
|
|
301
422
|
fig = px.bar(
|
|
302
423
|
df,
|
|
303
424
|
y="real",
|
|
304
425
|
x="amount",
|
|
305
|
-
color=
|
|
306
|
-
|
|
307
|
-
|
|
426
|
+
color="type",
|
|
427
|
+
color_discrete_sequence=colors,
|
|
428
|
+
pattern_shape="type" if mark_choice != "none" else None,
|
|
429
|
+
pattern_shape_sequence=marks,
|
|
308
430
|
orientation="h",
|
|
309
431
|
category_orders=cat_ord,
|
|
310
|
-
|
|
432
|
+
hover_data={"prop": True, "real": False},
|
|
311
433
|
)
|
|
312
|
-
fig.layout.legend.title.text = ""
|
|
313
|
-
fig.layout.legend.orientation = "h"
|
|
314
|
-
fig.layout.legend.y = -0.3
|
|
315
|
-
fig.layout.legend.font = {"size": 8}
|
|
316
434
|
fig.layout.yaxis.title = "Realization"
|
|
317
|
-
fig.layout.xaxis.exponentformat = "power"
|
|
318
435
|
fig.layout.xaxis.title = scale.value
|
|
319
436
|
_adjust_figure(fig)
|
|
320
437
|
return fig
|
|
321
438
|
|
|
322
439
|
|
|
440
|
+
# pylint: disable=too-many-locals
|
|
323
441
|
def generate_co2_time_containment_one_realization_figure(
|
|
324
442
|
table_provider: EnsembleTableProvider,
|
|
325
443
|
scale: Union[Co2MassScale, Co2VolumeScale],
|
|
@@ -327,232 +445,176 @@ def generate_co2_time_containment_one_realization_figure(
|
|
|
327
445
|
y_limits: List[Optional[float]],
|
|
328
446
|
containment_info: Dict[str, Any],
|
|
329
447
|
) -> go.Figure:
|
|
330
|
-
df = _read_co2_volumes(
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
df
|
|
334
|
-
df
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
"REAL",
|
|
338
|
-
"total",
|
|
339
|
-
"total_contained",
|
|
340
|
-
"total_outside",
|
|
341
|
-
"total_hazardous",
|
|
342
|
-
"total_gas",
|
|
343
|
-
"total_aqueous",
|
|
344
|
-
]
|
|
345
|
-
)
|
|
346
|
-
if containment_info["containment_view"] == ContainmentViews.ZONESPLIT:
|
|
347
|
-
df = pandas.melt(df, id_vars=["date", "zone"])
|
|
348
|
-
df["variable"] = df["zone"] + ", " + df["variable"]
|
|
349
|
-
df = df.drop(columns=["zone"])
|
|
350
|
-
cat_ord = {
|
|
351
|
-
"type": [
|
|
352
|
-
zone_name + ", " + phase
|
|
353
|
-
for zone_name in containment_info["zones"]
|
|
354
|
-
for phase in ["gas", "aqueous"]
|
|
355
|
-
]
|
|
356
|
-
}
|
|
357
|
-
pattern = ["", "/"] * len(containment_info["zones"])
|
|
358
|
-
colors = [
|
|
359
|
-
col
|
|
360
|
-
for col in _split_colors(len(containment_info["zones"]))
|
|
361
|
-
for i in range(2)
|
|
362
|
-
]
|
|
363
|
-
elif containment_info["containment_view"] == ContainmentViews.REGIONSPLIT:
|
|
364
|
-
df = pandas.melt(df, id_vars=["date", "region"])
|
|
365
|
-
df["variable"] = df["region"] + ", " + df["variable"]
|
|
366
|
-
df = df.drop(columns=["region"])
|
|
367
|
-
cat_ord = {
|
|
368
|
-
"type": [
|
|
369
|
-
region_name + ", " + phase
|
|
370
|
-
for region_name in containment_info["regions"]
|
|
371
|
-
for phase in ["gas", "aqueous"]
|
|
372
|
-
]
|
|
373
|
-
}
|
|
374
|
-
pattern = ["", "/"] * len(containment_info["regions"])
|
|
375
|
-
colors = [
|
|
376
|
-
col
|
|
377
|
-
for col in _split_colors(len(containment_info["regions"]), "region")
|
|
378
|
-
for i in range(2)
|
|
379
|
-
]
|
|
448
|
+
df = _read_co2_volumes(table_provider, [time_series_realization], scale)
|
|
449
|
+
color_choice = containment_info["color_choice"]
|
|
450
|
+
mark_choice = containment_info["mark_choice"]
|
|
451
|
+
_filter_columns(df, color_choice, mark_choice, containment_info)
|
|
452
|
+
_filter_rows(df, color_choice, mark_choice)
|
|
453
|
+
if containment_info["sorting"] == "marking" and mark_choice != "none":
|
|
454
|
+
sort_order = ["date", mark_choice]
|
|
380
455
|
else:
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
"type": [
|
|
384
|
-
"Hazardous mobile gas",
|
|
385
|
-
"Hazardous aqueous",
|
|
386
|
-
"Outside mobile gas",
|
|
387
|
-
"Outside aqueous",
|
|
388
|
-
"Contained mobile gas",
|
|
389
|
-
"Contained aqueous",
|
|
390
|
-
]
|
|
391
|
-
}
|
|
392
|
-
pattern = ["", "/"] * 3
|
|
393
|
-
colors = [
|
|
394
|
-
_COLOR_HAZARDOUS,
|
|
395
|
-
_COLOR_HAZARDOUS,
|
|
396
|
-
_COLOR_OUTSIDE,
|
|
397
|
-
_COLOR_OUTSIDE,
|
|
398
|
-
_COLOR_CONTAINED,
|
|
399
|
-
_COLOR_CONTAINED,
|
|
400
|
-
]
|
|
401
|
-
df = df.rename(columns={"value": "mass", "variable": "type"})
|
|
402
|
-
df.sort_values(by="date", inplace=True)
|
|
403
|
-
_change_type_names(df)
|
|
456
|
+
sort_order = ["date", color_choice]
|
|
457
|
+
df.sort_values(by=sort_order, inplace=True)
|
|
404
458
|
if y_limits[0] is None and y_limits[1] is not None:
|
|
405
459
|
y_limits[0] = 0.0
|
|
406
460
|
elif y_limits[1] is None and y_limits[0] is not None:
|
|
407
|
-
y_limits[1] = max(df.groupby("date")["
|
|
461
|
+
y_limits[1] = max(df.groupby("date")["amount"].sum()) * 1.05
|
|
462
|
+
|
|
463
|
+
_add_prop_to_df(df, np.unique(df["date"]), "date")
|
|
464
|
+
cat_ord, colors, marks = _prepare_pattern_and_color_options(
|
|
465
|
+
df,
|
|
466
|
+
containment_info,
|
|
467
|
+
color_choice,
|
|
468
|
+
mark_choice,
|
|
469
|
+
)
|
|
408
470
|
fig = px.area(
|
|
409
471
|
df,
|
|
410
472
|
x="date",
|
|
411
|
-
y="
|
|
473
|
+
y="amount",
|
|
412
474
|
color="type",
|
|
413
|
-
category_orders=cat_ord,
|
|
414
475
|
color_discrete_sequence=colors,
|
|
415
|
-
pattern_shape="type",
|
|
416
|
-
pattern_shape_sequence=
|
|
476
|
+
pattern_shape="type" if mark_choice != "none" else None,
|
|
477
|
+
pattern_shape_sequence=marks,
|
|
478
|
+
category_orders=cat_ord,
|
|
417
479
|
range_y=y_limits,
|
|
480
|
+
hover_data={
|
|
481
|
+
"prop": True,
|
|
482
|
+
"amount": ":.3f",
|
|
483
|
+
},
|
|
418
484
|
)
|
|
485
|
+
_add_hover_info_in_field(fig, df, cat_ord, colors)
|
|
419
486
|
fig.layout.yaxis.range = y_limits
|
|
420
|
-
fig.layout.legend.orientation = "h"
|
|
421
|
-
fig.layout.legend.title.text = ""
|
|
422
|
-
fig.layout.legend.y = -0.3
|
|
423
|
-
fig.layout.legend.font = {"size": 8}
|
|
424
|
-
fig.layout.title = "CO<sub>2</sub> containment for realization: " + str(
|
|
425
|
-
time_series_realization
|
|
426
|
-
)
|
|
427
487
|
fig.layout.xaxis.title = "Time"
|
|
428
488
|
fig.layout.yaxis.title = scale.value
|
|
429
|
-
fig.layout.yaxis.exponentformat = "power"
|
|
430
489
|
_adjust_figure(fig)
|
|
431
490
|
return fig
|
|
432
491
|
|
|
433
492
|
|
|
434
|
-
def
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
else
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
493
|
+
def spaced_dates(dates: List[str], num_between: int) -> Dict[str, List[str]]:
|
|
494
|
+
dates_list = [dt.strptime(date, "%Y-%m-%d") for date in dates]
|
|
495
|
+
date_dict: Dict[str, List[str]] = {date: [] for date in dates}
|
|
496
|
+
for i in range(len(dates_list) - 1):
|
|
497
|
+
date_dict[dates[i]].append(dates[i])
|
|
498
|
+
delta = (dates_list[i + 1] - dates_list[i]) / (num_between + 1)
|
|
499
|
+
for j in range(1, num_between + 1):
|
|
500
|
+
new_date = dates_list[i] + delta * j
|
|
501
|
+
if j <= num_between / 2:
|
|
502
|
+
date_dict[dates[i]].append(new_date.strftime("%Y-%m-%d"))
|
|
503
|
+
else:
|
|
504
|
+
date_dict[dates[i + 1]].append(new_date.strftime("%Y-%m-%d"))
|
|
505
|
+
date_dict[dates[-1]].append(dates[-1])
|
|
506
|
+
return date_dict
|
|
507
|
+
|
|
508
|
+
|
|
509
|
+
def _add_hover_info_in_field(
|
|
510
|
+
fig: go.Figure,
|
|
511
|
+
df: pd.DataFrame,
|
|
512
|
+
cat_ord: Dict,
|
|
513
|
+
colors: List,
|
|
514
|
+
) -> None:
|
|
515
|
+
"""
|
|
516
|
+
Plots additional, invisible points in the middle of each field in the third plot,
|
|
517
|
+
solely to display hover information inside the fields
|
|
518
|
+
(which is not possible directly with plotly.express.area)
|
|
519
|
+
"""
|
|
520
|
+
months = ["Jan", "Feb", "Mar", "Apr", "May", "Jun"]
|
|
521
|
+
months += ["Jul", "Aug", "Sep", "Oct", "Nov", "Dec"]
|
|
522
|
+
dates = np.unique(df["date"])
|
|
523
|
+
date_strings = {
|
|
524
|
+
date: f"{months[int(date.split('-')[1]) - 1]} {date.split('-')[0]}"
|
|
525
|
+
for date in dates
|
|
526
|
+
}
|
|
527
|
+
prev_vals = {date: 0 for date in dates}
|
|
528
|
+
date_dict = spaced_dates(dates, 4)
|
|
529
|
+
for name, color in zip(cat_ord["type"], colors):
|
|
530
|
+
sub_df = df[df["type"] == name]
|
|
531
|
+
for date in dates:
|
|
532
|
+
amount = sub_df[sub_df["date"] == date]["amount"].item()
|
|
533
|
+
prop = sub_df[sub_df["date"] == date]["prop"].item()
|
|
534
|
+
prev_val = prev_vals[date]
|
|
535
|
+
p15 = prev_val + 0.15 * amount
|
|
536
|
+
p85 = prev_val + 0.85 * amount
|
|
537
|
+
y_vals = np.linspace(p15, p85, 8).tolist() * len(date_dict[date])
|
|
538
|
+
y_vals.sort()
|
|
539
|
+
fig.add_trace(
|
|
540
|
+
go.Scatter(
|
|
541
|
+
x=date_dict[date] * 8,
|
|
542
|
+
y=y_vals,
|
|
543
|
+
mode="lines",
|
|
544
|
+
line=go.scatter.Line(color=color),
|
|
545
|
+
text=f"type={name}<br>date={date_strings[date]}<br>"
|
|
546
|
+
f"amount={amount:.3f}<br>prop={prop}",
|
|
547
|
+
opacity=0,
|
|
548
|
+
hoverinfo="text",
|
|
549
|
+
hoveron="points",
|
|
550
|
+
showlegend=False,
|
|
482
551
|
)
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
else:
|
|
486
|
-
df.sort_values(by="date", inplace=True)
|
|
487
|
-
cols_to_plot = {
|
|
488
|
-
"Total": ("total", "solid", _COLOR_TOTAL),
|
|
489
|
-
"Contained": ("total_contained", "solid", _COLOR_CONTAINED),
|
|
490
|
-
"Outside": ("total_outside", "solid", _COLOR_OUTSIDE),
|
|
491
|
-
"Hazardous": ("total_hazardous", "solid", _COLOR_HAZARDOUS),
|
|
492
|
-
"Gas": ("total_gas", "dot", _COLOR_TOTAL),
|
|
493
|
-
"Aqueous": ("total_aqueous", "dash", _COLOR_TOTAL),
|
|
494
|
-
"Contained mobile gas": ("gas_contained", "dot", _COLOR_CONTAINED),
|
|
495
|
-
"Outside mobile gas": ("gas_outside", "dot", _COLOR_OUTSIDE),
|
|
496
|
-
"Hazardous mobile gas": ("gas_hazardous", "dot", _COLOR_HAZARDOUS),
|
|
497
|
-
"Contained aqueous": ("aqueous_contained", "dash", _COLOR_CONTAINED),
|
|
498
|
-
"Outside aqueous": ("aqueous_outside", "dash", _COLOR_OUTSIDE),
|
|
499
|
-
"Hazardous aqueous": ("aqueous_hazardous", "dash", _COLOR_HAZARDOUS),
|
|
500
|
-
}
|
|
501
|
-
active_cols_at_startup = ["Total", "Outside", "Hazardous"]
|
|
502
|
-
return df, cols_to_plot, active_cols_at_startup
|
|
552
|
+
)
|
|
553
|
+
prev_vals[date] = prev_val + amount
|
|
503
554
|
|
|
504
555
|
|
|
556
|
+
# pylint: disable=too-many-locals
|
|
505
557
|
def generate_co2_time_containment_figure(
|
|
506
558
|
table_provider: EnsembleTableProvider,
|
|
507
559
|
realizations: List[int],
|
|
508
560
|
scale: Union[Co2MassScale, Co2VolumeScale],
|
|
509
561
|
containment_info: Dict[str, Any],
|
|
510
562
|
) -> go.Figure:
|
|
511
|
-
df = _read_co2_volumes(table_provider, realizations, scale
|
|
512
|
-
|
|
513
|
-
|
|
563
|
+
df = _read_co2_volumes(table_provider, realizations, scale)
|
|
564
|
+
color_choice = containment_info["color_choice"]
|
|
565
|
+
mark_choice = containment_info["mark_choice"]
|
|
566
|
+
_filter_columns(df, color_choice, mark_choice, containment_info)
|
|
567
|
+
options = _prepare_line_type_and_color_options(
|
|
568
|
+
df, containment_info, color_choice, mark_choice
|
|
569
|
+
)
|
|
570
|
+
active_cols_at_startup = list(
|
|
571
|
+
options[options["line_type"].isin(["solid", "0px"])]["name"]
|
|
514
572
|
)
|
|
515
573
|
fig = go.Figure()
|
|
516
574
|
# Generate dummy scatters for legend entries
|
|
517
575
|
dummy_args = {"x": df["date"], "mode": "lines", "hoverinfo": "none"}
|
|
518
|
-
for
|
|
576
|
+
for name, color, line_type in zip(
|
|
577
|
+
options["name"], options["color"], options["line_type"]
|
|
578
|
+
):
|
|
519
579
|
args = {
|
|
520
|
-
"line_dash":
|
|
521
|
-
"marker_color":
|
|
522
|
-
"legendgroup":
|
|
523
|
-
"name":
|
|
580
|
+
"line_dash": line_type,
|
|
581
|
+
"marker_color": color,
|
|
582
|
+
"legendgroup": name,
|
|
583
|
+
"name": name,
|
|
524
584
|
}
|
|
525
|
-
if
|
|
585
|
+
if name not in active_cols_at_startup:
|
|
526
586
|
args["visible"] = "legendonly"
|
|
527
587
|
fig.add_scatter(y=[0.0], **dummy_args, **args)
|
|
528
588
|
for rlz in realizations:
|
|
529
|
-
sub_df = df[df["realization"] == rlz]
|
|
589
|
+
sub_df = df[df["realization"] == rlz].copy().reset_index()
|
|
590
|
+
_add_prop_to_df(
|
|
591
|
+
sub_df, np.unique(df["date"]), "date", [color_choice, mark_choice]
|
|
592
|
+
)
|
|
530
593
|
common_args = {
|
|
531
594
|
"x": sub_df["date"],
|
|
532
|
-
"hovertemplate": "%{x}: %{y}<br>Realization: %{meta[0]}",
|
|
595
|
+
"hovertemplate": "%{x}: %{y}<br>Realization: %{meta[0]}<br>Prop: %{customdata}%",
|
|
533
596
|
"meta": [rlz],
|
|
534
597
|
"showlegend": False,
|
|
535
598
|
}
|
|
536
|
-
for
|
|
599
|
+
for name, color, line_type in zip(
|
|
600
|
+
options["name"], options["color"], options["line_type"]
|
|
601
|
+
):
|
|
602
|
+
# NBNB-AS: Check this, mypy complains:
|
|
537
603
|
args = {
|
|
538
|
-
"line_dash":
|
|
539
|
-
"marker_color":
|
|
540
|
-
"legendgroup":
|
|
541
|
-
"name":
|
|
604
|
+
"line_dash": line_type,
|
|
605
|
+
"marker_color": color,
|
|
606
|
+
"legendgroup": name,
|
|
607
|
+
"name": name,
|
|
608
|
+
"customdata": sub_df[sub_df["name"] == name]["prop"], # type: ignore
|
|
542
609
|
}
|
|
543
|
-
if
|
|
610
|
+
if name not in active_cols_at_startup:
|
|
544
611
|
args["visible"] = "legendonly"
|
|
545
|
-
fig.add_scatter(
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
fig.layout.legend.y = -0.3
|
|
549
|
-
fig.layout.legend.font = {"size": 8}
|
|
612
|
+
fig.add_scatter(
|
|
613
|
+
y=sub_df[sub_df["name"] == name]["amount"], **args, **common_args
|
|
614
|
+
)
|
|
550
615
|
fig.layout.legend.tracegroupgap = 0
|
|
551
|
-
fig.layout.title = "CO<sub>2</sub> containment (all realizations)"
|
|
552
616
|
fig.layout.xaxis.title = "Time"
|
|
553
617
|
fig.layout.yaxis.title = scale.value
|
|
554
|
-
fig.layout.yaxis.exponentformat = "power"
|
|
555
618
|
fig.layout.yaxis.autorange = True
|
|
556
619
|
_adjust_figure(fig)
|
|
557
|
-
# fig.update_layout(legend=dict(font=dict(size=8)), legend_tracegroupgap=0)
|
|
558
620
|
return fig
|