webviz-subsurface 0.2.30__py3-none-any.whl → 0.2.31__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.
Files changed (19) hide show
  1. webviz_subsurface/_components/tornado/_tornado_data.py +3 -0
  2. webviz_subsurface/_providers/ensemble_surface_provider/surface_array_server.py +0 -1
  3. webviz_subsurface/_providers/ensemble_surface_provider/surface_image_server.py +0 -1
  4. webviz_subsurface/plugins/_co2_leakage/_plugin.py +79 -37
  5. webviz_subsurface/plugins/_co2_leakage/_utilities/callbacks.py +99 -38
  6. webviz_subsurface/plugins/_co2_leakage/_utilities/co2volume.py +417 -355
  7. webviz_subsurface/plugins/_co2_leakage/_utilities/generic.py +2 -7
  8. webviz_subsurface/plugins/_co2_leakage/_utilities/initialization.py +15 -11
  9. webviz_subsurface/plugins/_co2_leakage/_utilities/surface_publishing.py +13 -4
  10. webviz_subsurface/plugins/_co2_leakage/views/mainview/mainview.py +93 -33
  11. webviz_subsurface/plugins/_co2_leakage/views/mainview/settings.py +301 -116
  12. webviz_subsurface/plugins/_volumetric_analysis/controllers/tornado_controllers.py +5 -1
  13. {webviz_subsurface-0.2.30.dist-info → webviz_subsurface-0.2.31.dist-info}/METADATA +34 -34
  14. {webviz_subsurface-0.2.30.dist-info → webviz_subsurface-0.2.31.dist-info}/RECORD +19 -19
  15. {webviz_subsurface-0.2.30.dist-info → webviz_subsurface-0.2.31.dist-info}/WHEEL +1 -1
  16. {webviz_subsurface-0.2.30.dist-info → webviz_subsurface-0.2.31.dist-info}/LICENSE +0 -0
  17. {webviz_subsurface-0.2.30.dist-info → webviz_subsurface-0.2.31.dist-info}/LICENSE.chromedriver +0 -0
  18. {webviz_subsurface-0.2.30.dist-info → webviz_subsurface-0.2.31.dist-info}/entry_points.txt +0 -0
  19. {webviz_subsurface-0.2.30.dist-info → webviz_subsurface-0.2.31.dist-info}/top_level.txt +0 -0
@@ -1,3 +1,4 @@
1
+ # pylint: disable=too-many-lines
1
2
  import warnings
2
3
  from typing import Any, Dict, List, Optional, Tuple, Union
3
4
 
@@ -14,7 +15,6 @@ from webviz_subsurface._providers.ensemble_surface_provider.ensemble_surface_pro
14
15
  from webviz_subsurface.plugins._co2_leakage._utilities.callbacks import property_origin
15
16
  from webviz_subsurface.plugins._co2_leakage._utilities.generic import (
16
17
  Co2MassScale,
17
- ContainmentViews,
18
18
  GraphSource,
19
19
  LayoutLabels,
20
20
  LayoutStyle,
@@ -48,9 +48,18 @@ class ViewSettings(SettingsGroupABC):
48
48
  Y_MAX_GRAPH = "y-max-graph"
49
49
  Y_MIN_AUTO_GRAPH = "y-min-auto-graph"
50
50
  Y_MAX_AUTO_GRAPH = "y-max-auto-graph"
51
+ COLOR_BY = "color-by"
52
+ MARK_BY = "mark-by"
53
+ SORT_PLOT = "sort-plot"
51
54
  ZONE = "zone"
55
+ ZONE_COL = "zone-column"
56
+ REGION_COL = "region-column"
57
+ ZONE_REGION = "zone-and-region"
52
58
  REGION = "region"
53
- CONTAINMENT_VIEW = "containment_view"
59
+ PHASE = "phase"
60
+ PHASE_MENU = "phase-menu"
61
+ CONTAINMENT = "containment"
62
+ CONTAINMENT_MENU = "containment-menu"
54
63
 
55
64
  PLUME_THRESHOLD = "plume-threshold"
56
65
  PLUME_SMOOTHING = "plume-smoothing"
@@ -70,7 +79,7 @@ class ViewSettings(SettingsGroupABC):
70
79
  map_attribute_names: Dict[MapAttribute, str],
71
80
  color_scale_names: List[str],
72
81
  well_names_dict: Dict[str, List[str]],
73
- zone_and_region_options: Dict[str, Dict[str, Dict[str, List[str]]]],
82
+ menu_options: Dict[str, Dict[str, Dict[str, List[str]]]],
74
83
  ):
75
84
  super().__init__("Settings")
76
85
  self._ensemble_paths = ensemble_paths
@@ -79,15 +88,15 @@ class ViewSettings(SettingsGroupABC):
79
88
  self._color_scale_names = color_scale_names
80
89
  self._initial_surface = initial_surface
81
90
  self._well_names_dict = well_names_dict
82
- self._zone_and_region_options = zone_and_region_options
91
+ self._menu_options = menu_options
83
92
  self._has_zones = max(
84
93
  len(inner_dict["zones"]) > 0
85
- for outer_dict in zone_and_region_options.values()
94
+ for outer_dict in menu_options.values()
86
95
  for inner_dict in outer_dict.values()
87
96
  )
88
97
  self._has_regions = max(
89
98
  len(inner_dict["regions"]) > 0
90
- for outer_dict in zone_and_region_options.values()
99
+ for outer_dict in menu_options.values()
91
100
  for inner_dict in outer_dict.values()
92
101
  )
93
102
 
@@ -126,9 +135,18 @@ class ViewSettings(SettingsGroupABC):
126
135
  self.register_component_unique_id(self.Ids.Y_MAX_AUTO_GRAPH),
127
136
  ],
128
137
  [
138
+ self.register_component_unique_id(self.Ids.COLOR_BY),
139
+ self.register_component_unique_id(self.Ids.MARK_BY),
140
+ self.register_component_unique_id(self.Ids.SORT_PLOT),
129
141
  self.register_component_unique_id(self.Ids.ZONE),
142
+ self.register_component_unique_id(self.Ids.ZONE_COL),
130
143
  self.register_component_unique_id(self.Ids.REGION),
131
- self.register_component_unique_id(self.Ids.CONTAINMENT_VIEW),
144
+ self.register_component_unique_id(self.Ids.REGION_COL),
145
+ self.register_component_unique_id(self.Ids.ZONE_REGION),
146
+ self.register_component_unique_id(self.Ids.PHASE),
147
+ self.register_component_unique_id(self.Ids.PHASE_MENU),
148
+ self.register_component_unique_id(self.Ids.CONTAINMENT),
149
+ self.register_component_unique_id(self.Ids.CONTAINMENT_MENU),
132
150
  ],
133
151
  self._has_zones,
134
152
  self._has_regions,
@@ -173,10 +191,14 @@ class ViewSettings(SettingsGroupABC):
173
191
  prop_name = property_origin(MapAttribute(prop), self._map_attribute_names)
174
192
  surfaces = surface_provider.surface_names_for_attribute(prop_name)
175
193
  if len(surfaces) == 0:
176
- warnings.warn(
177
- f"Surface not found for property: {prop}.\n"
178
- f"Expected name: <formation>--{prop_name}--<date>.gri"
179
- )
194
+ warning = f"Surface not found for property: {prop}.\n"
195
+ warning += f"Expected name: <formation>--{prop_name}"
196
+ if MapAttribute(prop) not in [
197
+ MapAttribute.MIGRATION_TIME_SGAS,
198
+ MapAttribute.MIGRATION_TIME_AMFG,
199
+ ]:
200
+ warning += "--<date>"
201
+ warnings.warn(warning + ".gri")
180
202
  # Formation names
181
203
  formations = [{"label": v.title(), "value": v} for v in surfaces]
182
204
  picked_formation = None
@@ -229,7 +251,10 @@ class ViewSettings(SettingsGroupABC):
229
251
  Input(self.component_unique_id(self.Ids.PROPERTY).to_string(), "value"),
230
252
  )
231
253
  def set_visualization_threshold(attribute: str) -> bool:
232
- return MapAttribute(attribute) == MapAttribute.MIGRATION_TIME
254
+ return MapAttribute(attribute) in [
255
+ MapAttribute.MIGRATION_TIME_SGAS,
256
+ MapAttribute.MIGRATION_TIME_AMFG,
257
+ ]
233
258
 
234
259
  @callback(
235
260
  Output(
@@ -250,6 +275,24 @@ class ViewSettings(SettingsGroupABC):
250
275
  ) -> Tuple[bool, bool]:
251
276
  return len(min_auto) == 1, len(max_auto) == 1
252
277
 
278
+ @callback(
279
+ Output(self.component_unique_id(self.Ids.PHASE).to_string(), "options"),
280
+ Output(self.component_unique_id(self.Ids.PHASE).to_string(), "value"),
281
+ Input(self.component_unique_id(self.Ids.GRAPH_SOURCE).to_string(), "value"),
282
+ Input(self.component_unique_id(self.Ids.ENSEMBLE).to_string(), "value"),
283
+ State(self.component_unique_id(self.Ids.PHASE).to_string(), "value"),
284
+ )
285
+ def set_phases(
286
+ source: GraphSource,
287
+ ensemble: str,
288
+ current_value: str,
289
+ ) -> Tuple[List[Dict[str, str]], Union[Any, str]]:
290
+ if ensemble is not None:
291
+ phases = self._menu_options[ensemble][source]["phases"]
292
+ options = [{"label": phase.title(), "value": phase} for phase in phases]
293
+ return options, no_update if current_value in phases else "total"
294
+ return [], "total"
295
+
253
296
  @callback(
254
297
  Output(self.component_unique_id(self.Ids.ZONE).to_string(), "options"),
255
298
  Output(self.component_unique_id(self.Ids.ZONE).to_string(), "value"),
@@ -263,26 +306,11 @@ class ViewSettings(SettingsGroupABC):
263
306
  current_value: str,
264
307
  ) -> Tuple[List[Dict[str, str]], Union[Any, str]]:
265
308
  if ensemble is not None:
266
- zones = self._zone_and_region_options[ensemble][source]["zones"]
309
+ zones = self._menu_options[ensemble][source]["zones"]
267
310
  if len(zones) > 0:
268
311
  options = [{"label": zone.title(), "value": zone} for zone in zones]
269
312
  return options, no_update if current_value in zones else "all"
270
- return [], None
271
-
272
- @callback(
273
- Output(self.component_unique_id(self.Ids.ZONE).to_string(), "disabled"),
274
- Input(self.component_unique_id(self.Ids.ZONE).to_string(), "value"),
275
- Input(self.component_unique_id(self.Ids.REGION).to_string(), "value"),
276
- Input(
277
- self.component_unique_id(self.Ids.CONTAINMENT_VIEW).to_string(), "value"
278
- ),
279
- )
280
- def disable_zone(zone: str, region: str, containment_view: str) -> bool:
281
- return (
282
- zone is None
283
- or containment_view != ContainmentViews.CONTAINMENTSPLIT
284
- or (region is not None and region != "all")
285
- )
313
+ return [], "all"
286
314
 
287
315
  @callback(
288
316
  Output(self.component_unique_id(self.Ids.REGION).to_string(), "options"),
@@ -297,26 +325,11 @@ class ViewSettings(SettingsGroupABC):
297
325
  current_value: str,
298
326
  ) -> Tuple[List[Dict[str, str]], Union[Any, str]]:
299
327
  if ensemble is not None:
300
- regions = self._zone_and_region_options[ensemble][source]["regions"]
328
+ regions = self._menu_options[ensemble][source]["regions"]
301
329
  if len(regions) > 0:
302
330
  options = [{"label": reg.title(), "value": reg} for reg in regions]
303
331
  return options, no_update if current_value in regions else "all"
304
- return [], None
305
-
306
- @callback(
307
- Output(self.component_unique_id(self.Ids.REGION).to_string(), "disabled"),
308
- Input(self.component_unique_id(self.Ids.REGION).to_string(), "value"),
309
- Input(self.component_unique_id(self.Ids.ZONE).to_string(), "value"),
310
- Input(
311
- self.component_unique_id(self.Ids.CONTAINMENT_VIEW).to_string(), "value"
312
- ),
313
- )
314
- def disable_region(region: str, zone: str, containment_view: str) -> bool:
315
- return (
316
- region is None
317
- or containment_view != ContainmentViews.CONTAINMENTSPLIT
318
- or (zone is not None and zone != "all")
319
- )
332
+ return [], "all"
320
333
 
321
334
  @callback(
322
335
  Output(
@@ -334,34 +347,49 @@ class ViewSettings(SettingsGroupABC):
334
347
  return False
335
348
 
336
349
  @callback(
337
- Output("zone_col", "style"),
338
- Output("region_col", "style"),
339
- Output("both_col", "style"),
340
- Output("zone_region_header", "style"),
341
- Input(
342
- self.component_unique_id(self.Ids.CONTAINMENT_VIEW).to_string(), "value"
350
+ Output(self.component_unique_id(self.Ids.MARK_BY).to_string(), "options"),
351
+ Output(self.component_unique_id(self.Ids.MARK_BY).to_string(), "value"),
352
+ Output(self.component_unique_id(self.Ids.ZONE_COL).to_string(), "style"),
353
+ Output(self.component_unique_id(self.Ids.REGION_COL).to_string(), "style"),
354
+ Output(self.component_unique_id(self.Ids.PHASE_MENU).to_string(), "style"),
355
+ Output(
356
+ self.component_unique_id(self.Ids.CONTAINMENT_MENU).to_string(),
357
+ "style",
343
358
  ),
359
+ Input(self.component_unique_id(self.Ids.COLOR_BY).to_string(), "value"),
360
+ Input(self.component_unique_id(self.Ids.MARK_BY).to_string(), "value"),
344
361
  )
345
- def hide_dropdowns(view: str) -> List[Dict[str, str]]:
346
- if view != ContainmentViews.CONTAINMENTSPLIT:
347
- return [{"display": "none"}] * 4
348
- disp_zone = "flex" if self._has_zones else "none"
349
- disp_region = "flex" if self._has_regions else "none"
350
- disp_either = "flex" if self._has_zones or self._has_regions else "none"
351
- return [
352
- {
353
- "width": "50%" if self._has_regions else "100%",
354
- "display": disp_zone,
355
- "flex-direction": "column",
356
- },
357
- {
358
- "width": "50%" if self._has_zones else "100%",
359
- "display": disp_region,
360
- "flex-direction": "column",
361
- },
362
- {"display": disp_either},
363
- {"display": disp_either},
362
+ def organize_color_and_mark_menus(
363
+ color_choice: str,
364
+ mark_choice: str,
365
+ ) -> Tuple[List[Dict], str, Dict, Dict, Dict, Dict]:
366
+ mark_options = [
367
+ {"label": "Phase", "value": "phase"},
368
+ {"label": "None", "value": "none"},
364
369
  ]
370
+ if self._has_zones and color_choice == "containment":
371
+ mark_options.append({"label": "Zone", "value": "zone"})
372
+ if self._has_regions and color_choice == "containment":
373
+ mark_options.append({"label": "Region", "value": "region"})
374
+ if color_choice in ["zone", "region"]:
375
+ mark_options.append({"label": "Containment", "value": "containment"})
376
+ if mark_choice is None or mark_choice == color_choice:
377
+ mark_choice = "phase"
378
+ if mark_choice in ["zone", "region"] and color_choice in ["zone", "region"]:
379
+ mark_choice = "phase"
380
+ zone, region, phase, containment = _make_styles(
381
+ color_choice, mark_choice, self._has_zones, self._has_regions
382
+ )
383
+ return mark_options, mark_choice, zone, region, phase, containment
384
+
385
+ @callback(
386
+ Output(self.component_unique_id(self.Ids.ZONE).to_string(), "disabled"),
387
+ Output(self.component_unique_id(self.Ids.REGION).to_string(), "disabled"),
388
+ Input(self.component_unique_id(self.Ids.ZONE).to_string(), "value"),
389
+ Input(self.component_unique_id(self.Ids.REGION).to_string(), "value"),
390
+ )
391
+ def disable_zone_or_region(zone: str, region: str) -> Tuple[bool, bool]:
392
+ return region != "all", zone != "all"
365
393
 
366
394
 
367
395
  class OpenDialogButton(html.Button):
@@ -492,7 +520,7 @@ class MapSelectorLayout(wcc.Selectors):
492
520
  wcc.Dropdown(
493
521
  id=property_id,
494
522
  options=_compile_property_options(),
495
- value=MapAttribute.MIGRATION_TIME.value,
523
+ value=MapAttribute.MIGRATION_TIME_SGAS.value,
496
524
  clearable=False,
497
525
  ),
498
526
  "Statistic",
@@ -588,35 +616,25 @@ class GraphSelectorsLayout(wcc.Selectors):
588
616
  has_zones: bool,
589
617
  has_regions: bool,
590
618
  ):
591
- disp = "flex" if has_zones or has_regions else "none"
592
619
  disp_zone = "flex" if has_zones else "none"
593
620
  disp_region = "flex" if has_regions else "none"
594
- only_zone = has_zones and not has_regions
595
- only_region = has_regions and not has_zones
596
621
  header = "Containment for specific"
597
- if only_zone:
622
+ if has_zones and not has_regions:
598
623
  header += " zone"
599
- elif only_region:
624
+ elif has_regions and not has_zones:
600
625
  header += " region"
601
- options = [ContainmentViews.CONTAINMENTSPLIT]
626
+ color_options = [{"label": "Containment (standard)", "value": "containment"}]
627
+ mark_options = [{"label": "Phase", "value": "phase"}]
602
628
  if has_zones:
603
- options.append(ContainmentViews.ZONESPLIT)
629
+ color_options.append({"label": "Zone", "value": "zone"})
630
+ mark_options.append({"label": "Zone", "value": "zone"})
604
631
  if has_regions:
605
- options.append(ContainmentViews.REGIONSPLIT)
632
+ color_options.append({"label": "Region", "value": "region"})
633
+ mark_options.append({"label": "Region", "value": "region"})
606
634
  super().__init__(
607
635
  label="Graph Settings",
608
636
  open_details=False,
609
637
  children=[
610
- html.Div(
611
- [
612
- dcc.RadioItems(
613
- options,
614
- ContainmentViews.CONTAINMENTSPLIT,
615
- id=containment_ids[2],
616
- ),
617
- ],
618
- style={"display": disp, "flex-direction": "column"},
619
- ),
620
638
  "Source",
621
639
  wcc.Dropdown(
622
640
  id=graph_source_id,
@@ -624,51 +642,141 @@ class GraphSelectorsLayout(wcc.Selectors):
624
642
  value=GraphSource.CONTAINMENT_MASS,
625
643
  clearable=False,
626
644
  ),
627
- html.Div(
628
- header,
629
- id="zone_region_header",
630
- style={"display": disp},
645
+ "Unit",
646
+ wcc.Dropdown(
647
+ id=co2_scale_id,
648
+ options=list(Co2MassScale),
649
+ value=Co2MassScale.MTONS,
650
+ clearable=False,
631
651
  ),
632
652
  html.Div(
633
653
  [
634
654
  html.Div(
635
- ([] if only_zone else ["zone"])
636
- + [
655
+ [
656
+ "Color by",
637
657
  wcc.Dropdown(
658
+ options=color_options,
659
+ value="containment",
638
660
  id=containment_ids[0],
639
661
  clearable=False,
640
662
  ),
641
663
  ],
642
- id="zone_col",
664
+ style={
665
+ "width": "50%",
666
+ "flex-direction": "column",
667
+ },
668
+ ),
669
+ html.Div(
670
+ [
671
+ "Mark by",
672
+ wcc.Dropdown(
673
+ options=mark_options,
674
+ value="phase",
675
+ id=containment_ids[1],
676
+ clearable=False,
677
+ ),
678
+ ],
679
+ style={
680
+ "width": "50%",
681
+ "flex-direction": "column",
682
+ },
683
+ ),
684
+ ],
685
+ style={
686
+ "display": "flex", # disp,
687
+ "flex-direction": "row",
688
+ "margin-top": "10px",
689
+ "margin-bottom": "1px",
690
+ },
691
+ ),
692
+ html.Div(
693
+ [
694
+ "Sort by",
695
+ dcc.RadioItems(
696
+ options=["color", "marking"],
697
+ value="color",
698
+ id=containment_ids[2],
699
+ inline=True,
700
+ ),
701
+ ],
702
+ style={
703
+ "display": "flex",
704
+ "flex-direction": "row",
705
+ "margin-top": "5px",
706
+ "margin-bottom": "1px",
707
+ },
708
+ ),
709
+ html.Div(
710
+ [
711
+ html.Div(
712
+ [
713
+ "Zone",
714
+ wcc.Dropdown(
715
+ value="all",
716
+ id=containment_ids[3],
717
+ clearable=False,
718
+ ),
719
+ ],
720
+ id=containment_ids[4],
643
721
  style={
644
722
  "width": "50%" if has_regions else "100%",
645
723
  "display": disp_zone,
724
+ "flex-direction": "column",
646
725
  },
647
726
  ),
648
727
  html.Div(
649
- ([] if only_region else ["region"])
650
- + [
728
+ [
729
+ "Region",
651
730
  wcc.Dropdown(
652
- id=containment_ids[1],
731
+ value="all",
732
+ id=containment_ids[5],
653
733
  clearable=False,
654
734
  ),
655
735
  ],
656
- id="region_col",
736
+ id=containment_ids[6],
657
737
  style={
658
738
  "width": "50%" if has_zones else "100%",
659
739
  "display": disp_region,
740
+ "flex-direction": "column",
660
741
  },
661
742
  ),
743
+ html.Div(
744
+ [
745
+ "Phase",
746
+ wcc.Dropdown(
747
+ value="total",
748
+ clearable=False,
749
+ id=containment_ids[8],
750
+ ),
751
+ ],
752
+ id=containment_ids[9],
753
+ style={"display": "none"},
754
+ ),
755
+ html.Div(
756
+ [
757
+ "Containment",
758
+ wcc.Dropdown(
759
+ options=[
760
+ {"label": "Total", "value": "total"},
761
+ {"label": "Contained", "value": "contained"},
762
+ {"label": "Outside", "value": "outside"},
763
+ {"label": "Hazardous", "value": "hazardous"},
764
+ ],
765
+ value="total",
766
+ clearable=False,
767
+ id=containment_ids[10],
768
+ ),
769
+ ],
770
+ id=containment_ids[11],
771
+ style={"display": "none"},
772
+ ),
662
773
  ],
663
- id="both_col",
664
- style={"display": disp},
774
+ id=containment_ids[7],
775
+ style={"display": "flex"},
665
776
  ),
666
- "Unit",
667
- wcc.Dropdown(
668
- id=co2_scale_id,
669
- options=list(Co2MassScale),
670
- value=Co2MassScale.MTONS,
671
- clearable=False,
777
+ html.Div(
778
+ "Fix y-limits in third plot:",
779
+ style={"margin-top": "10px"},
672
780
  ),
673
781
  "Minimum",
674
782
  html.Div(
@@ -770,8 +878,8 @@ def _compile_property_options() -> List[Dict[str, Any]]:
770
878
  "disabled": True,
771
879
  },
772
880
  {
773
- "label": MapAttribute.MIGRATION_TIME.value,
774
- "value": MapAttribute.MIGRATION_TIME.value,
881
+ "label": MapAttribute.MIGRATION_TIME_SGAS.value,
882
+ "value": MapAttribute.MIGRATION_TIME_SGAS.value,
775
883
  },
776
884
  {"label": MapAttribute.MAX_SGAS.value, "value": MapAttribute.MAX_SGAS.value},
777
885
  {
@@ -783,6 +891,10 @@ def _compile_property_options() -> List[Dict[str, Any]]:
783
891
  "value": "",
784
892
  "disabled": True,
785
893
  },
894
+ {
895
+ "label": MapAttribute.MIGRATION_TIME_AMFG.value,
896
+ "value": MapAttribute.MIGRATION_TIME_AMFG.value,
897
+ },
786
898
  {"label": MapAttribute.MAX_AMFG.value, "value": MapAttribute.MAX_AMFG.value},
787
899
  {
788
900
  "label": MapAttribute.AMFG_PLUME.value,
@@ -800,7 +912,7 @@ def _compile_property_options() -> List[Dict[str, Any]]:
800
912
 
801
913
 
802
914
  class FeedbackLayout(wcc.Dialog):
803
- """Layout for the options dialog"""
915
+ """Layout for the feedback button"""
804
916
 
805
917
  def __init__(
806
918
  self,
@@ -812,9 +924,16 @@ class FeedbackLayout(wcc.Dialog):
812
924
  open=False,
813
925
  children=[
814
926
  dcc.Markdown(
815
- """If you have any feedback regarding the CO2-leakage application,
816
- please contact XXX@XX.X."""
817
- )
927
+ """If you have any feedback regarding the CO2-Leakage application,
928
+ don't hesitate to"""
929
+ ),
930
+ dcc.Link(
931
+ ["send an email!"],
932
+ href=f"mailto:{get_emails()}&subject=Feedback regarding the "
933
+ f"CO2-Leakage application",
934
+ target="_blank",
935
+ style={"float": "left"},
936
+ ),
818
937
  ],
819
938
  )
820
939
 
@@ -822,10 +941,76 @@ class FeedbackLayout(wcc.Dialog):
822
941
  class FeedbackButton(html.Button):
823
942
  def __init__(self) -> None:
824
943
  style = LayoutStyle.FEEDBACK_BUTTON
825
- style["display"] = "none"
826
944
  super().__init__(
827
945
  LayoutLabels.FEEDBACK,
828
946
  id=ViewSettings.Ids.FEEDBACK_BUTTON,
829
947
  style=style,
830
948
  n_clicks=0,
831
949
  )
950
+
951
+
952
+ def decrypt_email(encrypted_email: str, key: int) -> str:
953
+ decrypted_email = []
954
+ for char in encrypted_email:
955
+ decrypted_email.append(chr(ord(char) ^ key))
956
+ return "".join(decrypted_email)
957
+
958
+
959
+ def get_emails() -> str:
960
+ emails = [
961
+ decrypt_email(m, i + 1)
962
+ for i, m in enumerate(
963
+ [
964
+ "GLLNAdpthons/bnl",
965
+ "OLCIKBgswklmp,amo",
966
+ "pfhCmq-ml",
967
+ "bjarnajDjv*jk",
968
+ "vlfdfmdEkw+kj",
969
+ ]
970
+ )
971
+ ]
972
+ return ";".join(emails[:2]) + "?cc=" + ";".join(emails[2:])
973
+
974
+
975
+ def _make_styles(
976
+ color_choice: str,
977
+ mark_choice: str,
978
+ has_zones: bool,
979
+ has_regions: bool,
980
+ ) -> List[Dict[str, str]]:
981
+ zone = {"display": "none", "flex-direction": "column", "width": "100%"}
982
+ region = {"display": "none", "flex-direction": "column", "width": "100%"}
983
+ phase = {"display": "none", "flex-direction": "column", "width": "100%"}
984
+ containment = {"display": "none", "flex-direction": "column", "width": "100%"}
985
+ if color_choice == "containment":
986
+ if mark_choice == "phase":
987
+ zone["width"] = "50%" if has_regions else "100%"
988
+ zone["display"] = "flex" if has_zones else "none"
989
+ region["width"] = "50%" if has_zones else "100%"
990
+ region["display"] = "flex" if has_regions else "none"
991
+ elif mark_choice == "none":
992
+ zone["width"] = "33%" if has_regions else "50%"
993
+ zone["display"] = "flex" if has_zones else "none"
994
+ region["width"] = "33%" if has_zones else "50%"
995
+ region["display"] = "flex" if has_regions else "none"
996
+ phase["width"] = (
997
+ "33%"
998
+ if has_zones and has_regions
999
+ else "100%"
1000
+ if not has_regions and not has_zones
1001
+ else "50%"
1002
+ )
1003
+ phase["display"] = "flex"
1004
+ else: # mark_choice == "zone" / "region"
1005
+ phase["display"] = "flex"
1006
+ else: # color_choice == "zone" / "region"
1007
+ if mark_choice == "phase":
1008
+ containment["display"] = "flex"
1009
+ elif mark_choice == "none":
1010
+ containment["width"] = "50%"
1011
+ containment["display"] = "flex"
1012
+ phase["width"] = "50%"
1013
+ phase["display"] = "flex"
1014
+ else: # mark == "containment"
1015
+ phase["display"] = "flex"
1016
+ return [zone, region, phase, containment]
@@ -359,12 +359,16 @@ def create_tornado_table(
359
359
  use_si_format=use_si_format,
360
360
  precision=4 if use_si_format else 3,
361
361
  )
362
+ mean_col = "Mean (mc)"
362
363
  table_data = tornado_table.as_plotly_table
363
364
  for data in table_data:
364
- data["Reference"] = tornado_data.reference_average
365
+ data[mean_col] = tornado_data.mean_per_mc_sens.get(data["Sensitivity"])
365
366
  if group is not None:
366
367
  data[subplots] = group
367
368
 
368
369
  columns = create_table_columns(columns=[subplots]) if subplots is not None else []
369
370
  columns.extend(tornado_table.columns)
371
+ columns.insert(
372
+ -3, create_table_columns(columns=[mean_col], use_si_format=[mean_col])[0]
373
+ )
370
374
  return table_data, columns