webviz-subsurface 0.2.40__py3-none-any.whl → 0.2.42__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 (35) hide show
  1. webviz_subsurface/_providers/ensemble_surface_provider/surface_array_server.py +2 -6
  2. webviz_subsurface/_version.py +2 -2
  3. webviz_subsurface/plugins/_co2_migration/__init__.py +1 -0
  4. webviz_subsurface/plugins/{_co2_leakage → _co2_migration}/_plugin.py +86 -46
  5. webviz_subsurface/plugins/{_co2_leakage → _co2_migration}/_utilities/callbacks.py +53 -30
  6. webviz_subsurface/plugins/{_co2_leakage → _co2_migration}/_utilities/co2volume.py +282 -39
  7. webviz_subsurface/plugins/{_co2_leakage → _co2_migration}/_utilities/color_tables.py +1 -1
  8. webviz_subsurface/plugins/{_co2_leakage → _co2_migration}/_utilities/containment_data_provider.py +6 -4
  9. webviz_subsurface/plugins/{_co2_leakage → _co2_migration}/_utilities/containment_info.py +6 -0
  10. webviz_subsurface/plugins/{_co2_leakage → _co2_migration}/_utilities/ensemble_well_picks.py +1 -1
  11. webviz_subsurface/plugins/{_co2_leakage → _co2_migration}/_utilities/generic.py +59 -6
  12. webviz_subsurface/plugins/{_co2_leakage → _co2_migration}/_utilities/initialization.py +73 -10
  13. webviz_subsurface/plugins/{_co2_leakage → _co2_migration}/_utilities/polygon_handler.py +1 -1
  14. webviz_subsurface/plugins/{_co2_leakage → _co2_migration}/_utilities/summary_graphs.py +20 -18
  15. webviz_subsurface/plugins/{_co2_leakage → _co2_migration}/_utilities/surface_publishing.py +18 -20
  16. webviz_subsurface/plugins/{_co2_leakage → _co2_migration}/_utilities/unsmry_data_provider.py +8 -8
  17. webviz_subsurface/plugins/{_co2_leakage → _co2_migration}/views/mainview/mainview.py +98 -44
  18. webviz_subsurface/plugins/{_co2_leakage → _co2_migration}/views/mainview/settings.py +7 -5
  19. webviz_subsurface/plugins/_map_viewer_fmu/layout.py +2 -1
  20. {webviz_subsurface-0.2.40.dist-info → webviz_subsurface-0.2.42.dist-info}/METADATA +3 -3
  21. {webviz_subsurface-0.2.40.dist-info → webviz_subsurface-0.2.42.dist-info}/RECORD +34 -34
  22. {webviz_subsurface-0.2.40.dist-info → webviz_subsurface-0.2.42.dist-info}/entry_points.txt +1 -1
  23. webviz_subsurface/plugins/_co2_leakage/__init__.py +0 -1
  24. /webviz_subsurface/plugins/{_co2_leakage → _co2_migration}/_error.py +0 -0
  25. /webviz_subsurface/plugins/{_co2_leakage → _co2_migration}/_types.py +0 -0
  26. /webviz_subsurface/plugins/{_co2_leakage → _co2_migration}/_utilities/__init__.py +0 -0
  27. /webviz_subsurface/plugins/{_co2_leakage → _co2_migration}/_utilities/_misc.py +0 -0
  28. /webviz_subsurface/plugins/{_co2_leakage → _co2_migration}/_utilities/fault_polygons_handler.py +0 -0
  29. /webviz_subsurface/plugins/{_co2_leakage → _co2_migration}/_utilities/plume_extent.py +0 -0
  30. /webviz_subsurface/plugins/{_co2_leakage → _co2_migration}/views/__init__.py +0 -0
  31. /webviz_subsurface/plugins/{_co2_leakage → _co2_migration}/views/mainview/__init__.py +0 -0
  32. {webviz_subsurface-0.2.40.dist-info → webviz_subsurface-0.2.42.dist-info}/WHEEL +0 -0
  33. {webviz_subsurface-0.2.40.dist-info → webviz_subsurface-0.2.42.dist-info}/licenses/LICENSE +0 -0
  34. {webviz_subsurface-0.2.40.dist-info → webviz_subsurface-0.2.42.dist-info}/licenses/LICENSE.chromedriver +0 -0
  35. {webviz_subsurface-0.2.40.dist-info → webviz_subsurface-0.2.42.dist-info}/top_level.txt +0 -0
@@ -3,16 +3,15 @@ import io
3
3
  import json
4
4
  import logging
5
5
  import math
6
+ import tempfile
6
7
  from dataclasses import asdict, dataclass
7
8
  from typing import List, Optional, Tuple, Union
8
9
  from urllib.parse import quote
9
- from uuid import uuid4
10
10
 
11
11
  import flask
12
12
  import flask_caching
13
13
  import xtgeo
14
14
  from dash import Dash
15
- from webviz_config.webviz_instance_info import WEBVIZ_INSTANCE_INFO
16
15
 
17
16
  from webviz_subsurface._utils.perf_timer import PerfTimer
18
17
 
@@ -51,10 +50,7 @@ class SurfaceArrayMeta:
51
50
 
52
51
  class SurfaceArrayServer:
53
52
  def __init__(self, app: Dash) -> None:
54
- cache_dir = (
55
- WEBVIZ_INSTANCE_INFO.storage_folder
56
- / f"SurfaceArrayServer_filecache_{uuid4()}"
57
- )
53
+ cache_dir = tempfile.mkdtemp()
58
54
  LOGGER.debug(f"Setting up file cache in: {cache_dir}")
59
55
  self._array_cache = flask_caching.Cache(
60
56
  config={
@@ -28,7 +28,7 @@ version_tuple: VERSION_TUPLE
28
28
  commit_id: COMMIT_ID
29
29
  __commit_id__: COMMIT_ID
30
30
 
31
- __version__ = version = '0.2.40'
32
- __version_tuple__ = version_tuple = (0, 2, 40)
31
+ __version__ = version = '0.2.42'
32
+ __version_tuple__ = version_tuple = (0, 2, 42)
33
33
 
34
34
  __commit_id__ = commit_id = None
@@ -0,0 +1 @@
1
+ from ._plugin import CO2Migration
@@ -1,4 +1,5 @@
1
1
  import logging
2
+ from datetime import datetime
2
3
  from typing import Any, Dict, List, Optional, Tuple, Union
3
4
 
4
5
  import plotly.graph_objects as go
@@ -7,14 +8,15 @@ from dash.exceptions import PreventUpdate
7
8
  from webviz_config import WebvizPluginABC, WebvizSettings
8
9
  from webviz_config.utils import StrEnum, callback_typecheck
9
10
 
10
- from webviz_subsurface._providers import FaultPolygonsServer, SurfaceImageServer
11
+ from webviz_subsurface._providers import FaultPolygonsServer, SurfaceArrayServer
11
12
  from webviz_subsurface._providers.ensemble_polygon_provider import PolygonServer
12
- from webviz_subsurface.plugins._co2_leakage._utilities.callbacks import (
13
+ from webviz_subsurface.plugins._co2_migration._utilities.callbacks import (
13
14
  SurfaceData,
14
15
  create_map_annotations,
15
16
  create_map_layers,
16
17
  create_map_viewports,
17
18
  derive_surface_address,
19
+ export_figure_data_to_csv,
18
20
  extract_legendonly,
19
21
  generate_containment_figures,
20
22
  generate_unsmry_figures,
@@ -27,10 +29,10 @@ from webviz_subsurface.plugins._co2_leakage._utilities.callbacks import (
27
29
  readable_name,
28
30
  set_plot_ids,
29
31
  )
30
- from webviz_subsurface.plugins._co2_leakage._utilities.fault_polygons_handler import (
32
+ from webviz_subsurface.plugins._co2_migration._utilities.fault_polygons_handler import (
31
33
  FaultPolygonsHandler,
32
34
  )
33
- from webviz_subsurface.plugins._co2_leakage._utilities.generic import (
35
+ from webviz_subsurface.plugins._co2_migration._utilities.generic import (
34
36
  BoundarySettings,
35
37
  Co2MassScale,
36
38
  Co2VolumeScale,
@@ -39,8 +41,9 @@ from webviz_subsurface.plugins._co2_leakage._utilities.generic import (
39
41
  MapThresholds,
40
42
  MapType,
41
43
  )
42
- from webviz_subsurface.plugins._co2_leakage._utilities.initialization import (
44
+ from webviz_subsurface.plugins._co2_migration._utilities.initialization import (
43
45
  init_containment_data_providers,
46
+ init_dates_per_ensemble,
44
47
  init_dictionary_of_content,
45
48
  init_map_attribute_names,
46
49
  init_menu_options,
@@ -50,24 +53,26 @@ from webviz_subsurface.plugins._co2_leakage._utilities.initialization import (
50
53
  init_unsmry_data_providers,
51
54
  init_well_pick_provider,
52
55
  )
53
- from webviz_subsurface.plugins._co2_leakage.views.mainview.mainview import (
56
+ from webviz_subsurface.plugins._co2_migration.views.mainview.mainview import (
54
57
  MainView,
55
58
  MapViewElement,
56
59
  )
57
- from webviz_subsurface.plugins._co2_leakage.views.mainview.settings import ViewSettings
60
+ from webviz_subsurface.plugins._co2_migration.views.mainview.settings import (
61
+ ViewSettings,
62
+ )
58
63
 
59
64
  from . import _error
60
65
  from ._types import LegendData
61
- from ._utilities.color_tables import co2leakage_color_tables
62
- from ._utilities.containment_info import StatisticsTabOption
66
+ from ._utilities.color_tables import co2migration_color_tables
67
+ from ._utilities.containment_info import MainTabOption, StatisticsTabOption
63
68
 
64
69
  LOGGER = logging.getLogger(__name__)
65
70
  TABLES_PATH = "share/results/tables"
66
71
 
67
72
 
68
73
  # pylint: disable=too-many-instance-attributes
69
- class CO2Leakage(WebvizPluginABC):
70
- """Plugin for analyzing CO2 leakage potential across multiple realizations in an
74
+ class CO2Migration(WebvizPluginABC):
75
+ """Plugin for analyzing CO2 migration across multiple realizations in an
71
76
  FMU ensemble
72
77
 
73
78
  ---
@@ -121,7 +126,7 @@ class CO2Leakage(WebvizPluginABC):
121
126
 
122
127
  The following keys are allowed: `MIGRATION_TIME_SGAS, MIGRATION_TIME_AMFG,
123
128
  MIGRATION_TIME_XMF2, MAX_SGAS, MAX_AMFG, MAX_XMF2, MAX_SGSTRAND, MAX_SGTRH, MASS,
124
- DISSOLVED, FREE, FREE_GAS, TRAPPED_GAS`
129
+ DISSOLVED_WATER, DISSOLVED_OIL, FREE, FREE_GAS, TRAPPED_GAS`
125
130
 
126
131
  Well pick files and fault polygons might name surfaces differently than the ones
127
132
  generated by the ccs-scripts workflow. The options
@@ -184,7 +189,7 @@ class CO2Leakage(WebvizPluginABC):
184
189
  for ensemble_name in ensembles
185
190
  }
186
191
  self._realizations_per_ensemble = init_realizations(ensemble_paths)
187
- self._surface_server = SurfaceImageServer.instance(app)
192
+ self._surface_server = SurfaceArrayServer.instance(app)
188
193
  self._polygons_server = FaultPolygonsServer.instance(app)
189
194
  self._map_attribute_names = init_map_attribute_names(
190
195
  webviz_settings, ensembles, map_attribute_names
@@ -195,6 +200,10 @@ class CO2Leakage(WebvizPluginABC):
195
200
  self._ensemble_surface_providers = init_surface_providers(
196
201
  webviz_settings, ensembles
197
202
  )
203
+ # Dates
204
+ self._ensemble_dates = init_dates_per_ensemble(
205
+ ensembles, self._map_attribute_names, self._ensemble_surface_providers
206
+ )
198
207
  # Polygons
199
208
  self._fault_polygon_handlers = {
200
209
  ens: FaultPolygonsHandler(
@@ -251,7 +260,7 @@ class CO2Leakage(WebvizPluginABC):
251
260
  "change": False,
252
261
  "unit": "tons",
253
262
  }
254
- self._color_tables = co2leakage_color_tables()
263
+ self._color_tables = co2migration_color_tables()
255
264
  self._well_pick_names: Dict[str, List[str]] = {
256
265
  ens: (
257
266
  self._well_pick_provider[ens].well_names
@@ -296,30 +305,6 @@ class CO2Leakage(WebvizPluginABC):
296
305
  .to_string()
297
306
  )
298
307
 
299
- def _ensemble_dates(self, ens: str) -> List[str]:
300
- surface_provider = self._ensemble_surface_providers[ens]
301
- date_map_attribute = next(
302
- (
303
- k
304
- for k in self._map_attribute_names.filtered_values
305
- if MapType[k.name].value != "MIGRATION_TIME"
306
- ),
307
- None,
308
- )
309
- att_name = (
310
- self._map_attribute_names[date_map_attribute]
311
- if date_map_attribute is not None
312
- else None
313
- )
314
- dates = (
315
- None
316
- if att_name is None
317
- else surface_provider.surface_dates_for_attribute(att_name)
318
- )
319
- if dates is None:
320
- raise ValueError(f"Failed to fetch dates for attribute '{att_name}'")
321
- return dates
322
-
323
308
  # Might want to do some refactoring if this gets too big
324
309
  def _set_callbacks(self) -> None:
325
310
  if self._content["any_table"]:
@@ -327,6 +312,7 @@ class CO2Leakage(WebvizPluginABC):
327
312
  self._add_legend_change_callback()
328
313
  self._add_set_unit_list_callback()
329
314
  self._add_time_plot_visibility_callback()
315
+ self._add_csv_export_callback()
330
316
 
331
317
  if self._content["maps"]:
332
318
  self._add_set_dates_callback()
@@ -515,17 +501,17 @@ class CO2Leakage(WebvizPluginABC):
515
501
  current_thresholds,
516
502
  mass_unit,
517
503
  self._visualization_info,
518
- self._surface_server._image_cache,
504
+ self._surface_server._array_cache,
519
505
  )
520
506
  if self._visualization_info["change"]:
521
507
  return [], None, no_update
522
508
  attribute = MapAttribute(attribute)
523
509
  if len(realization) == 0 or ensemble is None:
524
510
  raise PreventUpdate
525
- if isinstance(date, int):
526
- datestr = self._ensemble_dates(ensemble)[date]
527
- elif date is None:
511
+ if MapType[MapAttribute(attribute).name].value == "MIGRATION_TIME":
528
512
  datestr = None
513
+ else:
514
+ datestr = self._ensemble_dates[ensemble][date]
529
515
  # Contour data
530
516
  contour_data = None
531
517
  if MapType[MapAttribute(attribute).name].value == "PLUME":
@@ -657,13 +643,12 @@ class CO2Leakage(WebvizPluginABC):
657
643
  if ensemble is None:
658
644
  return {}, None
659
645
  # Dates
660
- date_list = self._ensemble_dates(ensemble)
661
646
  dates = {
662
647
  i: {
663
648
  "label": f"{d[:4]}",
664
649
  "style": {"writingMode": "vertical-rl"},
665
650
  }
666
- for i, d in enumerate(date_list)
651
+ for i, d in enumerate(self._ensemble_dates[ensemble])
667
652
  }
668
653
  if len(dates.keys()) > 0:
669
654
  return dates, max(dates.keys())
@@ -741,6 +726,7 @@ class CO2Leakage(WebvizPluginABC):
741
726
  Input(self._settings_component(ViewSettings.Ids.BOX_SHOW_POINTS), "value"),
742
727
  )
743
728
  @callback_typecheck
729
+ # pylint: disable=too-many-locals
744
730
  def update_graphs(
745
731
  legend_data: LegendData,
746
732
  ensemble: str,
@@ -763,8 +749,10 @@ class CO2Leakage(WebvizPluginABC):
763
749
  date_option: str,
764
750
  statistics_tab_option: StatisticsTabOption,
765
751
  box_show_points: str,
766
- ) -> Tuple[Dict, go.Figure, go.Figure, go.Figure]:
767
- # pylint: disable=too-many-locals
752
+ ) -> Tuple[go.Figure, go.Figure, go.Figure]:
753
+ if len(realizations) == 0:
754
+ return go.Figure(), go.Figure(), go.Figure()
755
+
768
756
  figs = [no_update] * 3
769
757
  cont_info = process_containment_info(
770
758
  zone,
@@ -892,3 +880,55 @@ class CO2Leakage(WebvizPluginABC):
892
880
  if event is None or not isinstance(event, list):
893
881
  return False
894
882
  return any("visible" in e for e in event if isinstance(e, dict))
883
+
884
+ def _add_csv_export_callback(self) -> None:
885
+ @callback(
886
+ Output(self._view_component(MapViewElement.Ids.DOWNLOAD_CSV), "data"),
887
+ Input(
888
+ self._view_component(MapViewElement.Ids.CSV_EXPORT_BUTTON), "n_clicks"
889
+ ),
890
+ State(self._view_component(MapViewElement.Ids.SUMMARY_TABS), "value"),
891
+ State(self._view_component(MapViewElement.Ids.BAR_PLOT), "figure"),
892
+ State(self._view_component(MapViewElement.Ids.TIME_PLOT), "figure"),
893
+ State(self._view_component(MapViewElement.Ids.STATISTICS_PLOT), "figure"),
894
+ State(
895
+ self._settings_component(ViewSettings.Ids.STATISTICS_TAB_OPTION),
896
+ "value",
897
+ ),
898
+ prevent_initial_call=True,
899
+ )
900
+ def export_data(
901
+ _n_clicks: int,
902
+ active_tab: str,
903
+ bar_figure: go.Figure,
904
+ time_figure: go.Figure,
905
+ stats_figure: go.Figure,
906
+ statistics_tab_option: StatisticsTabOption,
907
+ ) -> Dict[str, Any]:
908
+ file_name = "co2_migration"
909
+ if active_tab == MainTabOption.CONTAINMENT_STATE:
910
+ current_figure = bar_figure
911
+ file_name += "_state_plot"
912
+ plot_choice = "containment_state"
913
+ elif active_tab == MainTabOption.CONTAINMENT_OVER_TIME:
914
+ current_figure = time_figure
915
+ file_name += "_time_plot"
916
+ plot_choice = "containment_time"
917
+ elif active_tab == MainTabOption.STATISTICS:
918
+ current_figure = stats_figure
919
+ if statistics_tab_option == StatisticsTabOption.PROBABILITY_PLOT:
920
+ file_name += "_prob_plot"
921
+ plot_choice = "probability"
922
+ else: # => StatisticsTabOption.BOX_PLOT:
923
+ file_name += "_box_plot"
924
+ plot_choice = "box"
925
+ else:
926
+ raise PreventUpdate # Should not happen
927
+
928
+ date_and_time = datetime.now().strftime("%y%m%d_%H%M%S")
929
+ file_name += f"_{date_and_time}.csv"
930
+ result = export_figure_data_to_csv(current_figure, file_name, plot_choice)
931
+
932
+ if result is None:
933
+ raise PreventUpdate
934
+ return result
@@ -1,3 +1,4 @@
1
+ import logging
1
2
  import warnings
2
3
  from dataclasses import dataclass
3
4
  from typing import Any, Dict, List, Optional, Tuple, Union
@@ -6,7 +7,7 @@ import geojson
6
7
  import numpy as np
7
8
  import plotly.graph_objects as go
8
9
  import webviz_subsurface_components as wsc
9
- from dash import no_update
10
+ from dash import dcc, no_update
10
11
  from flask_caching import Cache
11
12
 
12
13
  from webviz_subsurface._providers import (
@@ -14,32 +15,33 @@ from webviz_subsurface._providers import (
14
15
  SimulatedSurfaceAddress,
15
16
  StatisticalSurfaceAddress,
16
17
  SurfaceAddress,
17
- SurfaceImageMeta,
18
- SurfaceImageServer,
18
+ SurfaceArrayMeta,
19
+ SurfaceArrayServer,
19
20
  )
20
21
  from webviz_subsurface._providers.ensemble_surface_provider.ensemble_surface_provider import (
21
22
  SurfaceStatistic,
22
23
  )
23
- from webviz_subsurface.plugins._co2_leakage._types import LegendData
24
- from webviz_subsurface.plugins._co2_leakage._utilities import plume_extent
25
- from webviz_subsurface.plugins._co2_leakage._utilities.co2volume import (
24
+ from webviz_subsurface.plugins._co2_migration._types import LegendData
25
+ from webviz_subsurface.plugins._co2_migration._utilities import plume_extent
26
+ from webviz_subsurface.plugins._co2_migration._utilities.co2volume import (
27
+ extract_df_from_fig,
26
28
  generate_co2_box_plot_figure,
27
29
  generate_co2_statistics_figure,
28
30
  generate_co2_time_containment_figure,
29
31
  generate_co2_time_containment_one_realization_figure,
30
32
  generate_co2_volume_figure,
31
33
  )
32
- from webviz_subsurface.plugins._co2_leakage._utilities.containment_data_provider import (
34
+ from webviz_subsurface.plugins._co2_migration._utilities.containment_data_provider import (
33
35
  ContainmentDataProvider,
34
36
  )
35
- from webviz_subsurface.plugins._co2_leakage._utilities.containment_info import (
37
+ from webviz_subsurface.plugins._co2_migration._utilities.containment_info import (
36
38
  ContainmentInfo,
37
39
  StatisticsTabOption,
38
40
  )
39
- from webviz_subsurface.plugins._co2_leakage._utilities.ensemble_well_picks import (
41
+ from webviz_subsurface.plugins._co2_migration._utilities.ensemble_well_picks import (
40
42
  EnsembleWellPicks,
41
43
  )
42
- from webviz_subsurface.plugins._co2_leakage._utilities.generic import (
44
+ from webviz_subsurface.plugins._co2_migration._utilities.generic import (
43
45
  Co2MassScale,
44
46
  Co2VolumeScale,
45
47
  FilteredMapAttribute,
@@ -50,17 +52,19 @@ from webviz_subsurface.plugins._co2_leakage._utilities.generic import (
50
52
  MapType,
51
53
  MenuOptions,
52
54
  )
53
- from webviz_subsurface.plugins._co2_leakage._utilities.summary_graphs import (
55
+ from webviz_subsurface.plugins._co2_migration._utilities.summary_graphs import (
54
56
  generate_summary_figure,
55
57
  )
56
- from webviz_subsurface.plugins._co2_leakage._utilities.surface_publishing import (
58
+ from webviz_subsurface.plugins._co2_migration._utilities.surface_publishing import (
57
59
  TruncatedSurfaceAddress,
58
60
  publish_and_get_surface_metadata,
59
61
  )
60
- from webviz_subsurface.plugins._co2_leakage._utilities.unsmry_data_provider import (
62
+ from webviz_subsurface.plugins._co2_migration._utilities.unsmry_data_provider import (
61
63
  UnsmryDataProvider,
62
64
  )
63
65
 
66
+ LOGGER = logging.getLogger(__name__)
67
+
64
68
 
65
69
  def property_origin(
66
70
  attribute: MapAttribute, map_attribute_names: FilteredMapAttribute
@@ -81,12 +85,12 @@ class SurfaceData:
81
85
  color_map_range: Tuple[Optional[float], Optional[float]]
82
86
  color_map_name: str
83
87
  value_range: Tuple[float, float]
84
- meta_data: SurfaceImageMeta
88
+ meta_data: SurfaceArrayMeta
85
89
  img_url: str
86
90
 
87
91
  @staticmethod
88
92
  def from_server(
89
- server: SurfaceImageServer,
93
+ server: SurfaceArrayServer,
90
94
  provider: EnsembleSurfaceProvider,
91
95
  address: Union[SurfaceAddress, TruncatedSurfaceAddress],
92
96
  color_map_range: Tuple[Optional[float], Optional[float]],
@@ -158,11 +162,6 @@ def derive_surface_address(
158
162
  threshold=contour_data["threshold"] if contour_data else 0.0,
159
163
  smoothing=contour_data["smoothing"] if contour_data else 0.0,
160
164
  )
161
- date = (
162
- None
163
- if MapType[MapAttribute(attribute).name].value == "MIGRATION_TIME"
164
- else date
165
- )
166
165
  if len(realization) == 1:
167
166
  return SimulatedSurfaceAddress(
168
167
  attribute=map_attribute_names[attribute],
@@ -192,7 +191,7 @@ def get_plume_polygon(
192
191
  surface_provider: EnsembleSurfaceProvider,
193
192
  realizations: List[int],
194
193
  surface_name: str,
195
- datestr: str,
194
+ datestr: Optional[str],
196
195
  contour_data: Dict[str, Any],
197
196
  ) -> Optional[geojson.FeatureCollection]:
198
197
  surface_attribute = contour_data["property"]
@@ -312,17 +311,22 @@ def create_map_layers(
312
311
  layers = []
313
312
  if surface_data is not None:
314
313
  # Update ColormapLayer
314
+ meta = surface_data.meta_data
315
315
  layers.append(
316
316
  {
317
- "@@type": "ColormapLayer",
318
- "name": surface_data.readable_name,
317
+ "@@type": "MapLayer",
319
318
  "id": "colormap-layer",
320
- "image": surface_data.img_url,
321
- "bounds": surface_data.meta_data.deckgl_bounds,
322
- "valueRange": surface_data.value_range,
323
- "colorMapRange": surface_data.color_map_range,
319
+ "name": surface_data.readable_name,
320
+ "meshUrl": surface_data.img_url,
321
+ "frame": {
322
+ "origin": [meta.x_ori, meta.y_ori],
323
+ "count": [meta.x_count, meta.y_count],
324
+ "increment": [meta.x_inc, meta.y_inc],
325
+ "rotDeg": meta.rot_deg,
326
+ },
324
327
  "colorMapName": surface_data.color_map_name,
325
- "rotDeg": surface_data.meta_data.deckgl_rot_deg,
328
+ "colorMapRange": surface_data.color_map_range,
329
+ "material": False,
326
330
  }
327
331
  )
328
332
 
@@ -408,7 +412,7 @@ def generate_containment_figures(
408
412
  try:
409
413
  fig0 = generate_co2_volume_figure(
410
414
  table_provider,
411
- table_provider.realizations,
415
+ realizations,
412
416
  co2_scale,
413
417
  containment_info,
414
418
  legenddata["bar_legendonly"],
@@ -624,7 +628,7 @@ def set_plot_ids(
624
628
  def process_summed_mass(
625
629
  formation: str,
626
630
  realization: List[int],
627
- datestr: str,
631
+ datestr: Optional[str],
628
632
  attribute: MapAttribute,
629
633
  summed_mass: Optional[float],
630
634
  surf_data: Optional[SurfaceData],
@@ -641,3 +645,22 @@ def process_summed_mass(
641
645
  f" ({unit}) (Total: {summed_co2[summed_co2_key]:.2E}): "
642
646
  )
643
647
  return surf_data, summed_co2
648
+
649
+
650
+ def export_figure_data_to_csv(
651
+ figure: Dict, file_name: str, plot_choice: str
652
+ ) -> Optional[Dict[str, Any]]:
653
+ """Export visible figure data to CSV file"""
654
+ try:
655
+ figure_go = go.Figure(figure) # Dash State returns dict
656
+ df = extract_df_from_fig(figure_go.data, plot_choice)
657
+ if df.empty:
658
+ LOGGER.warning("No plot data to export to CSV file.")
659
+ return None
660
+
661
+ result = dcc.send_data_frame(df.to_csv, filename=file_name, index=False)
662
+ return result
663
+
664
+ except (ValueError, KeyError, AttributeError, TypeError) as e:
665
+ LOGGER.warning(f"Failed to export plot data to CSV file: {e}")
666
+ return None