@opengeoweb/soundings 19.3.0 → 19.4.0

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.
package/dist/index.esm.js CHANGED
@@ -3,8 +3,8 @@ import { Stack, Box, Tooltip, Typography, styled, Card, IconButton, Menu, ListSu
3
3
  import { range, isEqual } from 'lodash';
4
4
  import React, { useMemo, useRef, useEffect, useId, useState, Fragment as Fragment$1, useCallback } from 'react';
5
5
  import { useDispatch, useSelector, shallowEqual } from 'react-redux';
6
- import { dateUtils, referenceTimeUtils, safelyInitI18nForTestsAndStories, sharedTranslations, SHARED_NAMESPACE, SwitchButton, CustomToggleButton, CustomSlider, AlertBanner, ToolContainerDraggable, TooltipSelect, AlertIcon, useResizeObserver, SearchField, CustomAccordion } from '@opengeoweb/shared';
7
- import { selectorMemoizationOptions, uiSelectors, uiTypes, uiActions, genericActions, useRegisterFeatureSyncSource, genericSelectors, useSyncgroupTimeState, mapSelectors, mapActions, useUpdateFeatureSyncLayers, layerActions, useSetupDialog, storeReducerMap, storeMiddlewares } from '@opengeoweb/store';
6
+ import { dateUtils, referenceTimeUtils, safelyInitI18nForTestsAndStories, sharedTranslations, SHARED_NAMESPACE, SwitchButton, CustomToggleButton, LegendToggleButton, CustomSlider, AlertBanner, ToolContainerDraggable, TooltipSelect, AlertIcon, useResizeObserver, SearchField, CustomAccordion } from '@opengeoweb/shared';
7
+ import { selectorMemoizationOptions, uiSelectors, uiTypes, uiActions, genericActions, useSyncgroupTimeState, useRegisterFeatureSyncSource, genericSelectors, mapSelectors, mapActions, useUpdateFeatureSyncLayers, layerActions, useSetupDialog, storeReducerMap, storeMiddlewares } from '@opengeoweb/store';
8
8
  import { Layers, Sources, Location, Equalizer, ArrowDropDownDown, DragHandle, Close, Visibility, VisibilityOff } from '@opengeoweb/theme';
9
9
  import { ParseISOTimeRangeDuration, parseISO8601IntervalToDateInterval, handleDateUtilsISOString, webmapUtils } from '@opengeoweb/webmap';
10
10
  import { MapFeatureClass } from '@opengeoweb/webmap-react';
@@ -35,6 +35,9 @@ var en = {
35
35
  "soundings-time-mismatch-title": "Selected time in the sounding view does not match map view time.",
36
36
  "soundings-toggle-location-markers-label": "Markers",
37
37
  "soundings-toggle-location-markers-tooltip": "Show/hide location markers",
38
+ "soundings-legend": "Legend",
39
+ "soundings-legend-tooltip-show": "Show legend",
40
+ "soundings-legend-tooltip-hide": "Hide legend",
38
41
  "no-sounding-data-available": "No sounding data available"
39
42
  };
40
43
  var no = {
@@ -52,6 +55,9 @@ var no = {
52
55
  "soundings-time-mismatch-title": "Selected time in the sounding view does not match map view time.",
53
56
  "soundings-toggle-location-markers-label": "Lokasjoner",
54
57
  "soundings-toggle-location-markers-tooltip": "Vis/skjul lokasjoner",
58
+ "soundings-legend": "Forklaring",
59
+ "soundings-legend-tooltip-show": "Vis forklaring",
60
+ "soundings-legend-tooltip-hide": "Skjul forklaring",
55
61
  "no-sounding-data-available": "No sounding data available"
56
62
  };
57
63
  var nl = {
@@ -69,6 +75,9 @@ var nl = {
69
75
  "soundings-time-mismatch-title": "Selected time in the sounding view does not match map view time.",
70
76
  "soundings-toggle-location-markers-label": "Kaart locaties",
71
77
  "soundings-toggle-location-markers-tooltip": "Toon/Verberg locatie punten",
78
+ "soundings-legend": "Legenda",
79
+ "soundings-legend-tooltip-show": "Toon legenda",
80
+ "soundings-legend-tooltip-hide": "Verberg legenda",
72
81
  "no-sounding-data-available": "No sounding data available"
73
82
  };
74
83
  var fi = {
@@ -86,6 +95,9 @@ var fi = {
86
95
  "soundings-time-mismatch-title": "Valittu aika luotausnäytöllä ei vastaa karttanäytön aikaa",
87
96
  "soundings-toggle-location-markers-label": "Kohdemerkit",
88
97
  "soundings-toggle-location-markers-tooltip": "Näytä/piilota kohdemerkit",
98
+ "soundings-legend": "Selite",
99
+ "soundings-legend-tooltip-show": "Näytä selite",
100
+ "soundings-legend-tooltip-hide": "Piilota selite",
89
101
  "no-sounding-data-available": "Ei luotausdataa saatavilla"
90
102
  };
91
103
  var soundingsTranslations = {
@@ -139,7 +151,7 @@ var REFERENCE_PRESSURE = 1000 * 100; // 1000hPa = 100000Pa
139
151
  var EARTH_MEAN_RADIUS = 6371; // [km]
140
152
  var MAX_PROXIMITY_RADIUS = 100; // [km]
141
153
  var SAME_POINT_DISTANCE_KM = 0.05;
142
- var SOUNDINGS_CONTAINER_BREAKPOINT_PX = '600px';
154
+ var SOUNDINGS_CONTAINER_BREAKPOINT_PX = '720px';
143
155
  var SOUNDINGS_CONTAINER_NAME = 'soundings-container';
144
156
 
145
157
  /* *
@@ -310,6 +322,14 @@ var initializeSvg$1 = function initializeSvg(_ref) {
310
322
  // Ensure clipPath uses same coordinate system as the translated group
311
323
  // so width/height starting at x=0,y=0 align with axis origin.
312
324
  .attr('clipPathUnits', 'userSpaceOnUse').append('rect').attr('width', width).attr('height', height).attr('x', 0).attr('y', 0);
325
+ svg.append('clipPath').attr('id', "clip-" + d3GraphId + "-" + diagramType + "x-axis").attr('clipPathUnits', 'userSpaceOnUse').append('rect')
326
+ // Keep x-axis labels within the horizontal chart band while allowing them
327
+ // to use the reserved left and bottom margin.
328
+ .attr('width', width + margin.left).attr('height', height + margin.bottom).attr('x', -margin.left).attr('y', 0);
329
+ svg.append('clipPath').attr('id', "clip-" + d3GraphId + "-" + diagramType + "y-axis").attr('clipPathUnits', 'userSpaceOnUse').append('rect')
330
+ // Keep y-axis labels within the vertical chart band while allowing them
331
+ // to use the reserved left margin.
332
+ .attr('width', width + margin.left).attr('height', height).attr('x', -margin.left).attr('y', 0);
313
333
  svg.append('rect').attr('height', height).attr('width', width).attr('fill', '#ffffff00').attr('opacity', 1).attr('id', d3GraphId + "-" + diagramType + "d3").attr('clip-path', "url(#clip-" + d3GraphId + "-" + diagramType + "d3)");
314
334
  return svg;
315
335
  };
@@ -438,6 +458,7 @@ var plotSounding = function plotSounding(_ref7) {
438
458
  pressureValues = _ref7.pressureValues,
439
459
  color = _ref7.color,
440
460
  lineStyle = _ref7.lineStyle,
461
+ d3GraphId = _ref7.d3GraphId,
441
462
  xScale = _ref7.xScale,
442
463
  yScale = _ref7.yScale,
443
464
  height = _ref7.height,
@@ -450,7 +471,7 @@ var plotSounding = function plotSounding(_ref7) {
450
471
  var Px = mapToPlot(diagramType, filteredPressureValues, filteredTemperatureValues, xScale, yScale, height);
451
472
  var lineGenerator = d3.line();
452
473
  var pathString = lineGenerator(Px);
453
- svg.append('path').attr('d', pathString).attr('id', 'dataline').attr('x', 0).attr('y', 0).style('fill', 'none').style('stroke-width', 2).style('stroke', color).style('stroke-opacity', 1).style('stroke-dasharray', lineStyle).attr('clip-path', "url(#clip-" + diagramType + "d3)");
474
+ svg.append('path').attr('d', pathString).attr('id', 'dataline').attr('x', 0).attr('y', 0).style('fill', 'none').style('stroke-width', 2).style('stroke', color).style('stroke-opacity', 1).style('stroke-dasharray', lineStyle).attr('clip-path', "url(#clip-" + d3GraphId + "-" + diagramType + "d3)");
454
475
  };
455
476
  // Use this function to filter the pressure and parameter array so no NaN or null values are included
456
477
  var filterDomainRangeForParameter = function filterDomainRangeForParameter(pressureDomain, parameterDomain) {
@@ -463,8 +484,16 @@ var filterDomainRangeForParameter = function filterDomainRangeForParameter(press
463
484
  return [filteredPressureDomain, filteredParameterDomain];
464
485
  };
465
486
 
487
+ var defaultVerticalZoom = {
488
+ scale: 1,
489
+ y: 0,
490
+ sourceId: null
491
+ };
466
492
  var MouseYContext = /*#__PURE__*/React.createContext({
467
- clientY: null
493
+ clientY: null,
494
+ verticalZoom: defaultVerticalZoom,
495
+ publishVerticalZoom: function publishVerticalZoom() {},
496
+ resetVerticalZoom: function resetVerticalZoom() {}
468
497
  });
469
498
  var useSharedMouseY = function useSharedMouseY() {
470
499
  return React.useContext(MouseYContext);
@@ -479,6 +508,31 @@ var SharedMouseYProvider = function SharedMouseYProvider(_ref) {
479
508
  var _React$useState = React.useState(null),
480
509
  clientY = _React$useState[0],
481
510
  setClientY = _React$useState[1];
511
+ var _React$useState2 = React.useState(defaultVerticalZoom),
512
+ verticalZoom = _React$useState2[0],
513
+ setVerticalZoom = _React$useState2[1];
514
+ var publishVerticalZoom = React.useCallback(function (sourceId, scale, y) {
515
+ setVerticalZoom(function (previous) {
516
+ var hasSameValues = previous.sourceId === sourceId && Math.abs(previous.scale - scale) < 1e-6 && Math.abs(previous.y - y) < 1e-3;
517
+ if (hasSameValues) {
518
+ return previous;
519
+ }
520
+ return {
521
+ scale: scale,
522
+ y: y,
523
+ sourceId: sourceId
524
+ };
525
+ });
526
+ }, []);
527
+ var resetVerticalZoom = React.useCallback(function (sourceId) {
528
+ setVerticalZoom(function () {
529
+ return {
530
+ scale: 1,
531
+ y: 0,
532
+ sourceId: sourceId
533
+ };
534
+ });
535
+ }, []);
482
536
  React.useEffect(function () {
483
537
  var el = wrapperRef.current;
484
538
  if (!el) {
@@ -502,9 +556,12 @@ var SharedMouseYProvider = function SharedMouseYProvider(_ref) {
502
556
  }, []);
503
557
  var contextValue = React.useMemo(function () {
504
558
  return {
505
- clientY: clientY
559
+ clientY: clientY,
560
+ verticalZoom: verticalZoom,
561
+ publishVerticalZoom: publishVerticalZoom,
562
+ resetVerticalZoom: resetVerticalZoom
506
563
  };
507
- }, [clientY]);
564
+ }, [clientY, verticalZoom, publishVerticalZoom, resetVerticalZoom]);
508
565
  return jsx("div", {
509
566
  "data-testid": "shared-mouse-y-wrapper",
510
567
  ref: wrapperRef,
@@ -1702,6 +1759,7 @@ var useResolvedSourceSelections = function useResolvedSourceSelections(sources)
1702
1759
  }, [sources, instancesBySource]);
1703
1760
  };
1704
1761
  var useTimesteps = function useTimesteps(sourceSelections) {
1762
+ var queryClient = useQueryClient();
1705
1763
  var queries = React.useMemo(function () {
1706
1764
  return sourceSelections.map(function (_ref3) {
1707
1765
  var collectionId = _ref3.collectionId,
@@ -1711,12 +1769,12 @@ var useTimesteps = function useTimesteps(sourceSelections) {
1711
1769
  queryKey: ['edr-soundings-instance', collectionId, instanceId || ''],
1712
1770
  queryFn: function () {
1713
1771
  var _queryFn3 = _asyncToGenerator(/*#__PURE__*/_regenerator().m(function _callee3() {
1714
- var _instanceData$extent, instanceData, timeExtentValues, allInstancesData, collectionData;
1772
+ var _instanceData$extent, instanceData, timeExtentValues, collectionData;
1715
1773
  return _regenerator().w(function (_context3) {
1716
1774
  while (1) switch (_context3.n) {
1717
1775
  case 0:
1718
1776
  if (!(instanceId && instanceId !== '')) {
1719
- _context3.n = 4;
1777
+ _context3.n = 3;
1720
1778
  break;
1721
1779
  }
1722
1780
  _context3.n = 1;
@@ -1725,20 +1783,16 @@ var useTimesteps = function useTimesteps(sourceSelections) {
1725
1783
  instanceData = _context3.v;
1726
1784
  timeExtentValues = instanceData == null || (_instanceData$extent = instanceData.extent) == null || (_instanceData$extent = _instanceData$extent.temporal) == null ? void 0 : _instanceData$extent.values;
1727
1785
  if (timeExtentValues) {
1728
- _context3.n = 3;
1786
+ _context3.n = 2;
1729
1787
  break;
1730
1788
  }
1731
- _context3.n = 2;
1732
- return getCollectionInstances(collectionId, url);
1789
+ return _context3.a(2, queryClient.fetchQuery(soundingsQueryOptions.getCollectionInstances(collectionId, url)));
1733
1790
  case 2:
1734
- allInstancesData = _context3.v;
1735
- return _context3.a(2, allInstancesData);
1736
- case 3:
1737
1791
  return _context3.a(2, instanceData);
1738
- case 4:
1739
- _context3.n = 5;
1792
+ case 3:
1793
+ _context3.n = 4;
1740
1794
  return getCollectionDetails(collectionId, url);
1741
- case 5:
1795
+ case 4:
1742
1796
  collectionData = _context3.v;
1743
1797
  return _context3.a(2, collectionData);
1744
1798
  }
@@ -1762,7 +1816,7 @@ var useTimesteps = function useTimesteps(sourceSelections) {
1762
1816
  refetchInterval: 5 * 60 * 1000
1763
1817
  };
1764
1818
  });
1765
- }, [sourceSelections]);
1819
+ }, [sourceSelections, queryClient]);
1766
1820
  var results = useQueries({
1767
1821
  queries: queries,
1768
1822
  combine: function combine(queryResults) {
@@ -1834,10 +1888,13 @@ var useGetCollectionsInstances = function useGetCollectionsInstances(collections
1834
1888
  var _queryResults$find3;
1835
1889
  var data = queryResults.map(function (result) {
1836
1890
  var _result$data2;
1837
- return ((_result$data2 = result.data) == null || (_result$data2 = _result$data2.data_queries) == null ? void 0 : _result$data2.instances) !== undefined;
1891
+ return !!((_result$data2 = result.data) != null && (_result$data2 = _result$data2.data_queries) != null && _result$data2.instances);
1838
1892
  });
1839
- var isPending = queryResults.some(function (result) {
1840
- return result.isPending;
1893
+ // Only enabled queries should contribute to isPending to prevent a
1894
+ // loading indicator that never clears
1895
+ var isPending = queryResults.some(function (result, index) {
1896
+ var _queriesCollection$in;
1897
+ return !!((_queriesCollection$in = queriesCollection[index]) != null && _queriesCollection$in.enabled) && result.isPending;
1841
1898
  });
1842
1899
  var error = (_queryResults$find3 = queryResults.find(function (result) {
1843
1900
  return result == null ? void 0 : result.error;
@@ -1854,52 +1911,28 @@ var useGetCollectionsInstances = function useGetCollectionsInstances(collections
1854
1911
  return collectionIds.map(function (_ref5, index) {
1855
1912
  var id = _ref5.id,
1856
1913
  url = _ref5.url;
1857
- return {
1858
- queryKey: ['edr-soundings-instances', id, resultsCollection.data],
1859
- queryFn: function () {
1860
- var _queryFn5 = _asyncToGenerator(/*#__PURE__*/_regenerator().m(function _callee5() {
1861
- var data;
1862
- return _regenerator().w(function (_context5) {
1863
- while (1) switch (_context5.n) {
1864
- case 0:
1865
- if (!resultsCollection.data[index]) {
1866
- _context5.n = 2;
1867
- break;
1868
- }
1869
- _context5.n = 1;
1870
- return getCollectionInstances(id, url);
1871
- case 1:
1872
- data = _context5.v;
1873
- return _context5.a(2, data);
1874
- case 2:
1875
- return _context5.a(2, {
1876
- instances: []
1877
- });
1878
- }
1879
- }, _callee5);
1880
- }));
1881
- function queryFn() {
1882
- return _queryFn5.apply(this, arguments);
1883
- }
1884
- return queryFn;
1885
- }(),
1886
- enabled: isDialogOpen && resultsCollection.data !== undefined
1887
- };
1914
+ var hasInstances = !!resultsCollection.data[index];
1915
+ return Object.assign({}, soundingsQueryOptions.getCollectionInstances(id, url), {
1916
+ enabled: !!isDialogOpen && hasInstances
1917
+ });
1888
1918
  });
1889
- }, [collectionIds, isDialogOpen, resultsCollection]);
1919
+ }, [collectionIds, isDialogOpen, resultsCollection.data]);
1890
1920
  var results = useQueries({
1891
1921
  queries: queries,
1892
1922
  combine: function combine(queryResults) {
1893
1923
  var _queryResults$find4;
1894
1924
  var data = queryResults.map(function (result) {
1895
- var _result$data3;
1896
- var instances = ((_result$data3 = result.data) == null ? void 0 : _result$data3.instances.sort(function (featureA, featureB) {
1925
+ var _result$data$instance, _result$data3;
1926
+ var instances = [].concat((_result$data$instance = (_result$data3 = result.data) == null ? void 0 : _result$data3.instances) != null ? _result$data$instance : []).sort(function (featureA, featureB) {
1897
1927
  return featureB.id.localeCompare(featureA.id);
1898
- })) || []; // Sort to get latest first
1928
+ }); // Sort to get latest first
1899
1929
  return instances;
1900
1930
  });
1901
- var isPending = queryResults.some(function (result) {
1902
- return result.isPending;
1931
+ // Mirror the collection-details behavior: only enabled instance queries
1932
+ // participate in the loading state.
1933
+ var isPending = queryResults.some(function (result, index) {
1934
+ var _queries$index;
1935
+ return !!((_queries$index = queries[index]) != null && _queries$index.enabled) && result.isPending;
1903
1936
  });
1904
1937
  var error = (_queryResults$find4 = queryResults.find(function (result) {
1905
1938
  return result == null ? void 0 : result.error;
@@ -1932,61 +1965,61 @@ var useGetLocations = function useGetLocations(collections, enabled) {
1932
1965
  return {
1933
1966
  queryKey: ['edr-locations', collection.id, collection.url, (_collection$instanceI = collection.instanceId) != null ? _collection$instanceI : ''],
1934
1967
  queryFn: function () {
1935
- var _queryFn6 = _asyncToGenerator(/*#__PURE__*/_regenerator().m(function _callee6() {
1968
+ var _queryFn5 = _asyncToGenerator(/*#__PURE__*/_regenerator().m(function _callee5() {
1936
1969
  var resolvedInstanceId, instances, resolved, locationsExplicitlyUnsupported, _locations, locations;
1937
- return _regenerator().w(function (_context6) {
1938
- while (1) switch (_context6.p = _context6.n) {
1970
+ return _regenerator().w(function (_context5) {
1971
+ while (1) switch (_context5.p = _context5.n) {
1939
1972
  case 0:
1940
1973
  resolvedInstanceId = collection.instanceId;
1941
1974
  if (!(resolvedInstanceId && referenceTimeUtils.isRelativeReferenceTime(resolvedInstanceId))) {
1942
- _context6.n = 4;
1975
+ _context5.n = 4;
1943
1976
  break;
1944
1977
  }
1945
- _context6.p = 1;
1946
- _context6.n = 2;
1978
+ _context5.p = 1;
1979
+ _context5.n = 2;
1947
1980
  return queryClient.fetchQuery(soundingsQueryOptions.getCollectionInstances(collection.id, collection.url));
1948
1981
  case 2:
1949
- instances = _context6.v;
1982
+ instances = _context5.v;
1950
1983
  resolved = referenceTimeUtils.resolveReferenceTimeInstance(instances.instances, resolvedInstanceId); // Skip instance-level only when data_queries is present but explicitly excludes locations — if data_queries is absent, try instance-level anyway
1951
1984
  locationsExplicitlyUnsupported = (resolved == null ? void 0 : resolved.data_queries) !== undefined && !resolved.data_queries.locations;
1952
1985
  resolvedInstanceId = locationsExplicitlyUnsupported ? undefined : resolved == null ? void 0 : resolved.id;
1953
- _context6.n = 4;
1986
+ _context5.n = 4;
1954
1987
  break;
1955
1988
  case 3:
1956
- _context6.p = 3;
1957
- _context6.v;
1989
+ _context5.p = 3;
1990
+ _context5.v;
1958
1991
  resolvedInstanceId = undefined;
1959
1992
  case 4:
1960
1993
  if (!resolvedInstanceId) {
1961
- _context6.n = 8;
1994
+ _context5.n = 8;
1962
1995
  break;
1963
1996
  }
1964
- _context6.p = 5;
1965
- _context6.n = 6;
1997
+ _context5.p = 5;
1998
+ _context5.n = 6;
1966
1999
  return getInstanceLocations(collection.id, resolvedInstanceId, collection.url);
1967
2000
  case 6:
1968
- _locations = _context6.v;
1969
- return _context6.a(2, Object.assign({}, _locations, {
2001
+ _locations = _context5.v;
2002
+ return _context5.a(2, Object.assign({}, _locations, {
1970
2003
  id: collection.id,
1971
2004
  name: collection.name
1972
2005
  }));
1973
2006
  case 7:
1974
- _context6.p = 7;
1975
- _context6.v;
2007
+ _context5.p = 7;
2008
+ _context5.v;
1976
2009
  case 8:
1977
- _context6.n = 9;
2010
+ _context5.n = 9;
1978
2011
  return getCollectionLocations(collection.id, collection.url);
1979
2012
  case 9:
1980
- locations = _context6.v;
1981
- return _context6.a(2, Object.assign({}, locations, {
2013
+ locations = _context5.v;
2014
+ return _context5.a(2, Object.assign({}, locations, {
1982
2015
  id: collection.id,
1983
2016
  name: collection.name
1984
2017
  }));
1985
2018
  }
1986
- }, _callee6, null, [[5, 7], [1, 3]]);
2019
+ }, _callee5, null, [[5, 7], [1, 3]]);
1987
2020
  }));
1988
2021
  function queryFn() {
1989
- return _queryFn6.apply(this, arguments);
2022
+ return _queryFn5.apply(this, arguments);
1990
2023
  }
1991
2024
  return queryFn;
1992
2025
  }(),
@@ -2027,6 +2060,7 @@ var useGetLocations = function useGetLocations(collections, enabled) {
2027
2060
  var useCheckPositionQuerySupportBySource = function useCheckPositionQuerySupportBySource(sources) {
2028
2061
  // Ref to stabilize data object reference when values haven't changed
2029
2062
  var dataRef = React.useRef({});
2063
+ var queryClient = useQueryClient();
2030
2064
  var queries = useMemo(function () {
2031
2065
  return sources.map(function (_ref6) {
2032
2066
  var collectionId = _ref6.collectionId,
@@ -2035,55 +2069,61 @@ var useCheckPositionQuerySupportBySource = function useCheckPositionQuerySupport
2035
2069
  return {
2036
2070
  queryKey: ['edr-position-support', collectionId, instanceId || ''],
2037
2071
  queryFn: function () {
2038
- var _queryFn7 = _asyncToGenerator(/*#__PURE__*/_regenerator().m(function _callee7() {
2072
+ var _queryFn6 = _asyncToGenerator(/*#__PURE__*/_regenerator().m(function _callee6() {
2039
2073
  var _details$data_queries, _instance$data_querie, instances, instance, details;
2040
- return _regenerator().w(function (_context7) {
2041
- while (1) switch (_context7.p = _context7.n) {
2074
+ return _regenerator().w(function (_context6) {
2075
+ while (1) switch (_context6.p = _context6.n) {
2042
2076
  case 0:
2043
- _context7.p = 0;
2077
+ _context6.p = 0;
2044
2078
  if (!instanceId) {
2045
- _context7.n = 2;
2079
+ _context6.n = 2;
2046
2080
  break;
2047
2081
  }
2048
- _context7.n = 1;
2049
- return getCollectionInstances(collectionId, url);
2082
+ _context6.n = 1;
2083
+ return queryClient.fetchQuery(soundingsQueryOptions.getCollectionInstances(collectionId, url));
2050
2084
  case 1:
2051
- instances = _context7.v;
2085
+ instances = _context6.v;
2052
2086
  instance = instances.instances.find(function (i) {
2053
2087
  return i.id === instanceId;
2054
2088
  });
2055
- return _context7.a(2, {
2089
+ return _context6.a(2, {
2056
2090
  collectionId: collectionId,
2057
2091
  supported: !!(instance != null && (_instance$data_querie = instance.data_queries) != null && _instance$data_querie.position)
2058
2092
  });
2059
2093
  case 2:
2060
- _context7.n = 3;
2094
+ _context6.n = 3;
2061
2095
  return getCollectionDetails(collectionId, url);
2062
2096
  case 3:
2063
- details = _context7.v;
2064
- return _context7.a(2, {
2097
+ details = _context6.v;
2098
+ return _context6.a(2, {
2065
2099
  collectionId: collectionId,
2066
2100
  supported: !!(details != null && (_details$data_queries = details.data_queries) != null && _details$data_queries.position)
2067
2101
  });
2068
2102
  case 4:
2069
- _context7.p = 4;
2070
- _context7.v;
2071
- return _context7.a(2, {
2103
+ _context6.p = 4;
2104
+ _context6.v;
2105
+ return _context6.a(2, {
2072
2106
  collectionId: collectionId,
2073
2107
  supported: false
2074
2108
  });
2075
2109
  }
2076
- }, _callee7, null, [[0, 4]]);
2110
+ }, _callee6, null, [[0, 4]]);
2077
2111
  }));
2078
2112
  function queryFn() {
2079
- return _queryFn7.apply(this, arguments);
2113
+ return _queryFn6.apply(this, arguments);
2080
2114
  }
2081
2115
  return queryFn;
2082
2116
  }(),
2083
- staleTime: 5 * 60 * 1000 // Cache for 5 minutes
2117
+ enabled: !referenceTimeUtils.isRelativeReferenceTime(instanceId),
2118
+ staleTime: 5 * 60 * 1000,
2119
+ // Cache for 5 minutes
2120
+ // Preserve previous result while a new instance check is in-flight to avoid spurious location updates.
2121
+ placeholderData: function placeholderData(prev) {
2122
+ return prev;
2123
+ }
2084
2124
  };
2085
2125
  });
2086
- }, [sources]);
2126
+ }, [sources, queryClient]);
2087
2127
  return useQueries({
2088
2128
  queries: queries,
2089
2129
  combine: function combine(queryResults) {
@@ -2117,6 +2157,7 @@ var useCheckPositionQuerySupportBySource = function useCheckPositionQuerySupport
2117
2157
  var useCheckRadiusQuerySupportBySource = function useCheckRadiusQuerySupportBySource(sources) {
2118
2158
  // Ref to stabilize data object reference when values haven't changed
2119
2159
  var dataRef = React.useRef({});
2160
+ var queryClient = useQueryClient();
2120
2161
  var queries = useMemo(function () {
2121
2162
  return sources.map(function (_ref7) {
2122
2163
  var collectionId = _ref7.collectionId,
@@ -2125,55 +2166,60 @@ var useCheckRadiusQuerySupportBySource = function useCheckRadiusQuerySupportBySo
2125
2166
  return {
2126
2167
  queryKey: ['edr-radius-support', collectionId, instanceId || ''],
2127
2168
  queryFn: function () {
2128
- var _queryFn8 = _asyncToGenerator(/*#__PURE__*/_regenerator().m(function _callee8() {
2169
+ var _queryFn7 = _asyncToGenerator(/*#__PURE__*/_regenerator().m(function _callee7() {
2129
2170
  var _details$data_queries2, _instance$data_querie2, instances, instance, details;
2130
- return _regenerator().w(function (_context8) {
2131
- while (1) switch (_context8.p = _context8.n) {
2171
+ return _regenerator().w(function (_context7) {
2172
+ while (1) switch (_context7.p = _context7.n) {
2132
2173
  case 0:
2133
- _context8.p = 0;
2174
+ _context7.p = 0;
2134
2175
  if (!instanceId) {
2135
- _context8.n = 2;
2176
+ _context7.n = 2;
2136
2177
  break;
2137
2178
  }
2138
- _context8.n = 1;
2139
- return getCollectionInstances(collectionId, url);
2179
+ _context7.n = 1;
2180
+ return queryClient.fetchQuery(soundingsQueryOptions.getCollectionInstances(collectionId, url));
2140
2181
  case 1:
2141
- instances = _context8.v;
2182
+ instances = _context7.v;
2142
2183
  instance = instances.instances.find(function (i) {
2143
2184
  return i.id === instanceId;
2144
2185
  });
2145
- return _context8.a(2, {
2186
+ return _context7.a(2, {
2146
2187
  collectionId: collectionId,
2147
2188
  supported: !!(instance != null && (_instance$data_querie2 = instance.data_queries) != null && _instance$data_querie2.radius)
2148
2189
  });
2149
2190
  case 2:
2150
- _context8.n = 3;
2191
+ _context7.n = 3;
2151
2192
  return getCollectionDetails(collectionId, url);
2152
2193
  case 3:
2153
- details = _context8.v;
2154
- return _context8.a(2, {
2194
+ details = _context7.v;
2195
+ return _context7.a(2, {
2155
2196
  collectionId: collectionId,
2156
2197
  supported: !!(details != null && (_details$data_queries2 = details.data_queries) != null && _details$data_queries2.radius)
2157
2198
  });
2158
2199
  case 4:
2159
- _context8.p = 4;
2160
- _context8.v;
2161
- return _context8.a(2, {
2200
+ _context7.p = 4;
2201
+ _context7.v;
2202
+ return _context7.a(2, {
2162
2203
  collectionId: collectionId,
2163
2204
  supported: false
2164
2205
  });
2165
2206
  }
2166
- }, _callee8, null, [[0, 4]]);
2207
+ }, _callee7, null, [[0, 4]]);
2167
2208
  }));
2168
2209
  function queryFn() {
2169
- return _queryFn8.apply(this, arguments);
2210
+ return _queryFn7.apply(this, arguments);
2170
2211
  }
2171
2212
  return queryFn;
2172
2213
  }(),
2173
- staleTime: 5 * 60 * 1000 // Cache for 5 minutes
2214
+ enabled: !referenceTimeUtils.isRelativeReferenceTime(instanceId),
2215
+ staleTime: 5 * 60 * 1000,
2216
+ // Cache for 5 minutes
2217
+ placeholderData: function placeholderData(prev) {
2218
+ return prev;
2219
+ }
2174
2220
  };
2175
2221
  });
2176
- }, [sources]);
2222
+ }, [sources, queryClient]);
2177
2223
  return useQueries({
2178
2224
  queries: queries,
2179
2225
  combine: function combine(queryResults) {
@@ -2223,20 +2269,20 @@ var useGridPointsFromRadius = function useGridPointsFromRadius(sources, radiusSu
2223
2269
  return {
2224
2270
  queryKey: ['edr-grid-points-disabled', collectionId],
2225
2271
  queryFn: function () {
2226
- var _queryFn9 = _asyncToGenerator(/*#__PURE__*/_regenerator().m(function _callee9() {
2227
- return _regenerator().w(function (_context9) {
2228
- while (1) switch (_context9.n) {
2272
+ var _queryFn8 = _asyncToGenerator(/*#__PURE__*/_regenerator().m(function _callee8() {
2273
+ return _regenerator().w(function (_context8) {
2274
+ while (1) switch (_context8.n) {
2229
2275
  case 0:
2230
- return _context9.a(2, {
2276
+ return _context8.a(2, {
2231
2277
  sourceId: collectionId,
2232
2278
  gridPoint: undefined,
2233
2279
  allGridPoints: []
2234
2280
  });
2235
2281
  }
2236
- }, _callee9);
2282
+ }, _callee8);
2237
2283
  }));
2238
2284
  function queryFn() {
2239
- return _queryFn9.apply(this, arguments);
2285
+ return _queryFn8.apply(this, arguments);
2240
2286
  }
2241
2287
  return queryFn;
2242
2288
  }(),
@@ -2261,22 +2307,22 @@ var useGridPointsFromRadius = function useGridPointsFromRadius(sources, radiusSu
2261
2307
  return {
2262
2308
  queryKey: ['edr-grid-points-radius', collectionId, instanceId || '', centerCoordinates.join(','), radiusKm.toString(), datetime],
2263
2309
  queryFn: function () {
2264
- var _queryFn0 = _asyncToGenerator(/*#__PURE__*/_regenerator().m(function _callee0() {
2310
+ var _queryFn9 = _asyncToGenerator(/*#__PURE__*/_regenerator().m(function _callee9() {
2265
2311
  var minimalParameters, minimalVerticalRange, rawData, allGridPoints, nearestPoint, minSquaredDistance;
2266
- return _regenerator().w(function (_context0) {
2267
- while (1) switch (_context0.p = _context0.n) {
2312
+ return _regenerator().w(function (_context9) {
2313
+ while (1) switch (_context9.p = _context9.n) {
2268
2314
  case 0:
2269
2315
  if (supportsRadius) {
2270
- _context0.n = 1;
2316
+ _context9.n = 1;
2271
2317
  break;
2272
2318
  }
2273
- return _context0.a(2, {
2319
+ return _context9.a(2, {
2274
2320
  sourceId: collectionId,
2275
2321
  gridPoint: undefined,
2276
2322
  allGridPoints: []
2277
2323
  });
2278
2324
  case 1:
2279
- _context0.p = 1;
2325
+ _context9.p = 1;
2280
2326
  // Optimization: Only request a single parameter to minimize data transfer
2281
2327
  // We only need the grid point coordinates, not the actual sounding data
2282
2328
  minimalParameters = parameters.slice(0, 1); // Just one parameter
@@ -2284,10 +2330,10 @@ var useGridPointsFromRadius = function useGridPointsFromRadius(sources, radiusSu
2284
2330
  // (Don't hardcode '1000' as that only works for pressure-level sources,
2285
2331
  // not for hybrid/model-level sources which use levels 1-65)
2286
2332
  minimalVerticalRange = '';
2287
- _context0.n = 2;
2333
+ _context9.n = 2;
2288
2334
  return getEdrCollectionForRadius(collectionId, url, instanceId, centerCoordinates, radiusKm, minimalParameters, datetime, minimalVerticalRange);
2289
2335
  case 2:
2290
- rawData = _context0.v;
2336
+ rawData = _context9.v;
2291
2337
  allGridPoints = extractGridPointsFromCoverage(rawData); // Find the nearest grid point to the center coordinates
2292
2338
  minSquaredDistance = Infinity;
2293
2339
  allGridPoints.forEach(function (point) {
@@ -2302,24 +2348,24 @@ var useGridPointsFromRadius = function useGridPointsFromRadius(sources, radiusSu
2302
2348
  nearestPoint = point;
2303
2349
  }
2304
2350
  });
2305
- return _context0.a(2, {
2351
+ return _context9.a(2, {
2306
2352
  sourceId: collectionId,
2307
2353
  gridPoint: nearestPoint,
2308
2354
  allGridPoints: allGridPoints
2309
2355
  });
2310
2356
  case 3:
2311
- _context0.p = 3;
2312
- _context0.v;
2313
- return _context0.a(2, {
2357
+ _context9.p = 3;
2358
+ _context9.v;
2359
+ return _context9.a(2, {
2314
2360
  sourceId: collectionId,
2315
2361
  gridPoint: undefined,
2316
2362
  allGridPoints: []
2317
2363
  });
2318
2364
  }
2319
- }, _callee0, null, [[1, 3]]);
2365
+ }, _callee9, null, [[1, 3]]);
2320
2366
  }));
2321
2367
  function queryFn() {
2322
- return _queryFn0.apply(this, arguments);
2368
+ return _queryFn9.apply(this, arguments);
2323
2369
  }
2324
2370
  return queryFn;
2325
2371
  }(),
@@ -2627,6 +2673,7 @@ var initialSoundingsViewState = {
2627
2673
  },
2628
2674
  shouldHideLocationMarkers: false,
2629
2675
  isTimeSliderVisible: true,
2676
+ isLegendVisible: true,
2630
2677
  profileDataPreferences: DEFAULT_PROFILE_DATA_PREFERENCES,
2631
2678
  syncGroupsIds: []
2632
2679
  };
@@ -2696,7 +2743,7 @@ var slice = createSlice({
2696
2743
  }
2697
2744
  var preset = payloadAction.payload.preset;
2698
2745
  if (preset) {
2699
- var _preset$selectedSourc, _preset$selectedLocat, _preset$selectedTime, _preset$diagramType, _preset$shouldHideLoc, _preset$isTimeSliderV, _preset$profileDataPr;
2746
+ var _preset$selectedSourc, _preset$selectedLocat, _preset$selectedTime, _preset$diagramType, _preset$shouldHideLoc, _preset$isTimeSliderV, _preset$isLegendVisib, _preset$profileDataPr;
2700
2747
  panel.selectedSources = (_preset$selectedSourc = preset.selectedSources) != null ? _preset$selectedSourc : {};
2701
2748
  panel.selectedLocations = (_preset$selectedLocat = preset.selectedLocations) != null ? _preset$selectedLocat : {};
2702
2749
  panel.selectedTime = (_preset$selectedTime = preset.selectedTime) != null ? _preset$selectedTime : new Date().toISOString();
@@ -2718,6 +2765,7 @@ var slice = createSlice({
2718
2765
  }
2719
2766
  panel.shouldHideLocationMarkers = (_preset$shouldHideLoc = preset.shouldHideLocationMarkers) != null ? _preset$shouldHideLoc : false;
2720
2767
  panel.isTimeSliderVisible = (_preset$isTimeSliderV = preset.isTimeSliderVisible) != null ? _preset$isTimeSliderV : true;
2768
+ panel.isLegendVisible = (_preset$isLegendVisib = preset.isLegendVisible) != null ? _preset$isLegendVisib : true;
2721
2769
  panel.profileDataPreferences = (_preset$profileDataPr = preset.profileDataPreferences) != null ? _preset$profileDataPr : DEFAULT_PROFILE_DATA_PREFERENCES;
2722
2770
  }
2723
2771
  },
@@ -2776,6 +2824,13 @@ var slice = createSlice({
2776
2824
  }
2777
2825
  panel.isTimeSliderVisible = payloadAction.payload.isTimeSliderVisible;
2778
2826
  },
2827
+ toggleLegendVisibility: function toggleLegendVisibility(draft, payloadAction) {
2828
+ var panel = resolveSoundingsView(draft, payloadAction);
2829
+ if (!panel) {
2830
+ return;
2831
+ }
2832
+ panel.isLegendVisible = payloadAction.payload.isLegendVisible;
2833
+ },
2779
2834
  updateProfileDataPreferences: function updateProfileDataPreferences(draft, payloadAction) {
2780
2835
  var panel = resolveSoundingsView(draft, payloadAction);
2781
2836
  if (!panel) {
@@ -2954,6 +3009,10 @@ var getIsTimeSliderVisible = createSelector(getSoundingsViewState, function (sto
2954
3009
  var _store$isTimeSliderVi;
2955
3010
  return (_store$isTimeSliderVi = store.isTimeSliderVisible) != null ? _store$isTimeSliderVi : true;
2956
3011
  }, selectorMemoizationOptions);
3012
+ var getIsLegendVisible = createSelector(getSoundingsViewState, function (store) {
3013
+ var _store$isLegendVisibl;
3014
+ return (_store$isLegendVisibl = store.isLegendVisible) != null ? _store$isLegendVisibl : true;
3015
+ }, selectorMemoizationOptions);
2957
3016
  var getProfileDataPreferences = createSelector(getSoundingsViewState, function (store) {
2958
3017
  var _store$profileDataPre;
2959
3018
  return (_store$profileDataPre = store.profileDataPreferences) != null ? _store$profileDataPre : DEFAULT_PROFILE_DATA_PREFERENCES;
@@ -3008,6 +3067,7 @@ var getSoundingPanelsInOrder = createSelector(getSoundingsViewState, function (s
3008
3067
  var selectors = /*#__PURE__*/Object.freeze({
3009
3068
  __proto__: null,
3010
3069
  getDiagramType: getDiagramType,
3070
+ getIsLegendVisible: getIsLegendVisible,
3011
3071
  getIsTimeSliderVisible: getIsTimeSliderVisible,
3012
3072
  getLocationSelections: getLocationSelections,
3013
3073
  getPanelCustomWidth: getPanelCustomWidth,
@@ -3199,13 +3259,15 @@ var MenuOptions;
3199
3259
  MenuOptions["profileData"] = "profileData";
3200
3260
  })(MenuOptions || (MenuOptions = {}));
3201
3261
  var SoundingsMenuBar = function SoundingsMenuBar(_ref) {
3202
- var _sx;
3262
+ var _sx, _sx2;
3203
3263
  var handleClick = _ref.handleClick,
3204
3264
  isOpen = _ref.isOpen,
3205
3265
  _ref$selectedSourcesA = _ref.selectedSourcesAmount,
3206
3266
  selectedSourcesAmount = _ref$selectedSourcesA === void 0 ? 0 : _ref$selectedSourcesA,
3207
3267
  shouldHideMarkers = _ref.shouldHideMarkers,
3208
- toggleMarkers = _ref.toggleMarkers;
3268
+ toggleMarkers = _ref.toggleMarkers,
3269
+ isLegendVisible = _ref.isLegendVisible,
3270
+ toggleLegend = _ref.toggleLegend;
3209
3271
  var _useSoundingsTranslat = useSoundingsTranslation(),
3210
3272
  t = _useSoundingsTranslat.t;
3211
3273
  return jsxs(Stack, {
@@ -3260,6 +3322,23 @@ var SoundingsMenuBar = function SoundingsMenuBar(_ref) {
3260
3322
  }), jsx(SoundingsMarkersSwitch, {
3261
3323
  shouldHideMarkers: shouldHideMarkers,
3262
3324
  toggleMarkers: toggleMarkers
3325
+ }), jsx(LegendToggleButton, {
3326
+ hideTooltipText: t('soundings-legend-tooltip-hide'),
3327
+ isLegendVisible: isLegendVisible,
3328
+ legendLabel: t('soundings-legend'),
3329
+ onToggle: toggleLegend,
3330
+ showTooltipText: t('soundings-legend-tooltip-show'),
3331
+ sx: (_sx2 = {}, _sx2["@container " + SOUNDINGS_CONTAINER_NAME + " (width < " + SOUNDINGS_CONTAINER_BREAKPOINT_PX + ")"] = {
3332
+ maxWidth: '90px',
3333
+ '& .longText': {
3334
+ display: 'none'
3335
+ },
3336
+ '& svg': {
3337
+ marginRight: '0 !important'
3338
+ }
3339
+ }, _sx2["@container " + SOUNDINGS_CONTAINER_NAME + " (min-width: " + SOUNDINGS_CONTAINER_BREAKPOINT_PX + ")"] = {
3340
+ width: '130px'
3341
+ }, _sx2)
3263
3342
  })]
3264
3343
  });
3265
3344
  };
@@ -3304,6 +3383,16 @@ var MenuBarConnect = function MenuBarConnect(_ref) {
3304
3383
  var shouldHideLocationMarkers = useSelector(function (state) {
3305
3384
  return getShouldHideLocationMarkers(state, viewId);
3306
3385
  });
3386
+ var isLegendVisible = useSelector(function (state) {
3387
+ return getIsLegendVisible(state, viewId);
3388
+ });
3389
+ var toggleLegend = React.useCallback(function () {
3390
+ dispatch(soundingsActions.toggleLegendVisibility({
3391
+ isLegendVisible: !isLegendVisible,
3392
+ viewId: viewId,
3393
+ origin: SoundingsActionOrigin.soundingsComponent
3394
+ }));
3395
+ }, [dispatch, isLegendVisible, viewId]);
3307
3396
  var toggleLocationMarkers = React.useCallback(function (shouldHide) {
3308
3397
  dispatch(soundingsActions.toggleLocationMarkers({
3309
3398
  shouldHideLocationMarkers: shouldHide,
@@ -3338,9 +3427,11 @@ var MenuBarConnect = function MenuBarConnect(_ref) {
3338
3427
  var isOpen = (_isOpen = {}, _isOpen[MenuOptions.sources] = currentActiveMapIdSources === viewId && isSourcesOpenInStore, _isOpen[MenuOptions.locations] = currentActiveMapIdLocations === viewId && isLocationsOpenInStore, _isOpen[MenuOptions.soundingsManager] = currentActiveMapIdsoundingsManager === viewId && isSoundingsManagerOpenInStore, _isOpen[MenuOptions.profileData] = currentActiveMapIdProfileData === viewId && isProfileDataOpenInStore, _isOpen);
3339
3428
  return jsx(SoundingsMenuBar, {
3340
3429
  handleClick: handleClick,
3430
+ isLegendVisible: isLegendVisible,
3341
3431
  isOpen: isOpen,
3342
3432
  selectedSourcesAmount: sourcesAmount,
3343
3433
  shouldHideMarkers: shouldHideLocationMarkers,
3434
+ toggleLegend: toggleLegend,
3344
3435
  toggleMarkers: toggleLocationMarkers
3345
3436
  });
3346
3437
  };
@@ -3956,7 +4047,7 @@ var Hodograph = function Hodograph(_ref) {
3956
4047
  });
3957
4048
  });
3958
4049
  // Allow zooming and panning
3959
- var zoomBehavior = d3.zoom().scaleExtent([1, 7]).translateExtent([[-0.2 * widthHeight, -0.2 * widthHeight], [1.2 * widthHeight, 1.2 * widthHeight]]).on('zoom', function (event) {
4050
+ var zoomBehavior = d3.zoom().scaleExtent([1, 7]).translateExtent([[0, 0], [widthHeight, widthHeight]]).on('zoom', function (event) {
3960
4051
  svg.select('g.hodograph').attr('transform', event.transform);
3961
4052
  });
3962
4053
  svg.call(zoomBehavior);
@@ -4310,23 +4401,21 @@ var extractPressureFromY = function extractPressureFromY(diagramType, y, yScale)
4310
4401
  };
4311
4402
  var drawSoundingsTooltip = function drawSoundingsTooltip(_ref) {
4312
4403
  var event = _ref.event,
4313
- yScale = _ref.yScale,
4314
4404
  svgRectangle = _ref.svgRectangle,
4405
+ hoveredPressure = _ref.hoveredPressure,
4315
4406
  pressureRefd3 = _ref.pressureRefd3,
4316
4407
  tempRefd3 = _ref.tempRefd3,
4317
4408
  dewpointRefd3 = _ref.dewpointRefd3,
4318
4409
  windRefd3 = _ref.windRefd3,
4319
4410
  margin = _ref.margin,
4320
4411
  width = _ref.width,
4321
- tooltipData = _ref.tooltipData,
4322
- diagramType = _ref.diagramType;
4412
+ tooltipData = _ref.tooltipData;
4413
+ _ref.diagramType;
4323
4414
  if (tooltipData.length === 0) {
4324
4415
  return;
4325
4416
  }
4326
- var _d3$pointer = d3.pointer(event),
4327
- y = _d3$pointer[1];
4328
4417
  var pressureY = event.clientY - svgRectangle.top - 10;
4329
- var pressure = extractPressureFromY(diagramType, y, yScale);
4418
+ var pressure = hoveredPressure;
4330
4419
  var interpolatedData = tooltipData.map(function (dataSet) {
4331
4420
  return {
4332
4421
  hoveredPressure: dataSet.interpolatePressure(pressure),
@@ -4525,6 +4614,7 @@ var drawIsothermsBijvoet = function drawIsothermsBijvoet(_ref2) {
4525
4614
  xScale = _ref2.xScale,
4526
4615
  yScale = _ref2.yScale,
4527
4616
  svg = _ref2.svg,
4617
+ labelSvg = _ref2.labelSvg,
4528
4618
  height = _ref2.height,
4529
4619
  d3GraphId = _ref2.d3GraphId;
4530
4620
  // lines of constant temperature (2 degree interval from -150...+40C)
@@ -4542,7 +4632,7 @@ var drawIsothermsBijvoet = function drawIsothermsBijvoet(_ref2) {
4542
4632
  var strID = temp + "-isotherm-BIJVOET";
4543
4633
  svg.append('path').attr('id', strID).attr('d', pathString).style('fill', 'none').style('stroke-width', temp % 10 === 0 ? 2 : 1).style('stroke', temp === 0 ? '#D27D2D' : 'orange').style('stroke-opacity', 0.5).attr('clip-path', "url(#clip-" + d3GraphId + "-BIJVOETd3)");
4544
4634
  if (temp % 10 === 0 && ((_points$ = points[0]) == null ? void 0 : _points$[1]) === height) {
4545
- svg.append('text').text(temp).attr('text-anchor', 'middle').attr('x', points[0][0] + "px").attr('y', height + 12 + "px").attr('fill', temp === 0 ? '#D27D2D' : 'orange').attr('font-size', '10px');
4635
+ (labelSvg != null ? labelSvg : svg).append('text').text(temp).attr('text-anchor', 'middle').attr('x', points[0][0] + "px").attr('y', height + 12 + "px").attr('fill', temp === 0 ? '#D27D2D' : 'orange').attr('font-size', '10px');
4546
4636
  }
4547
4637
  }
4548
4638
  };
@@ -4685,6 +4775,7 @@ var drawStaticLines = function drawStaticLines(_ref) {
4685
4775
  minPressure = _ref.minPressure,
4686
4776
  maxPressure = _ref.maxPressure,
4687
4777
  svg = _ref.svg,
4778
+ bijvoetLabelSvg = _ref.bijvoetLabelSvg,
4688
4779
  d3GraphId = _ref.d3GraphId;
4689
4780
  drawIsobars({
4690
4781
  diagramType: diagramType,
@@ -4708,6 +4799,7 @@ var drawStaticLines = function drawStaticLines(_ref) {
4708
4799
  xScale: xScale,
4709
4800
  yScale: yScale,
4710
4801
  svg: svg,
4802
+ labelSvg: bijvoetLabelSvg,
4711
4803
  height: height,
4712
4804
  d3GraphId: d3GraphId
4713
4805
  });
@@ -4796,6 +4888,11 @@ var soundingsStyle$1 = {
4796
4888
  fontSize: '12px',
4797
4889
  fontWeight: 'bold'
4798
4890
  }};
4891
+ var updateTransformToViewport = function updateTransformToViewport(transform, width, height) {
4892
+ var minX = width * (1 - transform.k);
4893
+ var minY = height * (1 - transform.k);
4894
+ return d3.zoomIdentity.translate(Math.min(0, Math.max(minX, transform.x)), Math.min(0, Math.max(minY, transform.y))).scale(transform.k);
4895
+ };
4799
4896
  var Soundings = function Soundings(_ref) {
4800
4897
  var d3GraphId = _ref.d3GraphId,
4801
4898
  data = _ref.data,
@@ -4817,8 +4914,21 @@ var Soundings = function Soundings(_ref) {
4817
4914
  var widthRef = useRef(0);
4818
4915
  var svgRectRef = useRef(null);
4819
4916
  var tooltipDataRef = useRef(null);
4917
+ var zoomBehaviorRef = useRef(null);
4918
+ // Keep zoom target on the inner translated <g>; Soundings and WindBarbs must
4919
+ // transform the same coordinate space to stay aligned.
4920
+ var zoomTargetRef = useRef(null);
4921
+ var isApplyingSharedZoomRef = useRef(false);
4922
+ // Keep the last applied d3 zoom transform across SVG redraws.
4923
+ // Width/height changes recreate the chart, so this ref prevents zoom snapping
4924
+ // back to identity when panels are closed/resized.
4925
+ var persistedZoomTransformRef = useRef(d3.zoomIdentity);
4926
+ var previousDiagramTypeRef = useRef(null);
4820
4927
  var _useSharedMouseY = useSharedMouseY(),
4821
- clientY = _useSharedMouseY.clientY;
4928
+ clientY = _useSharedMouseY.clientY,
4929
+ publishVerticalZoom = _useSharedMouseY.publishVerticalZoom,
4930
+ verticalZoom = _useSharedMouseY.verticalZoom,
4931
+ resetVerticalZoom = _useSharedMouseY.resetVerticalZoom;
4822
4932
  // Set SVG dimensions (required props; no optional chaining needed)
4823
4933
  var baseWidth = svgDimensions.width,
4824
4934
  baseHeight = svgDimensions.height,
@@ -4826,9 +4936,16 @@ var Soundings = function Soundings(_ref) {
4826
4936
  var width = baseWidth - (margin.left + margin.right);
4827
4937
  var height = baseHeight - (margin.top + margin.bottom);
4828
4938
  useEffect(function () {
4829
- if (!svgRef.current) {
4939
+ if (!svgRef.current || width <= 0 || height <= 0) {
4830
4940
  return;
4831
4941
  }
4942
+ var isDiagramTypeChange = previousDiagramTypeRef.current !== null && previousDiagramTypeRef.current !== diagramType;
4943
+ if (isDiagramTypeChange) {
4944
+ persistedZoomTransformRef.current = d3.zoomIdentity;
4945
+ resetVerticalZoom(scopedGraphId);
4946
+ }
4947
+ var root = d3.select(svgRef.current);
4948
+ root.selectAll('*').remove();
4832
4949
  var svg = initializeSvg$1({
4833
4950
  svgRef: svgRef,
4834
4951
  width: width,
@@ -4837,6 +4954,13 @@ var Soundings = function Soundings(_ref) {
4837
4954
  diagramType: diagramType,
4838
4955
  d3GraphId: scopedGraphId
4839
4956
  });
4957
+ // Create a separate layer for axes so they can stay visible above the zoomed content.
4958
+ var axisLayer = svg.append('g').attr('data-layer', 'axis-layer');
4959
+ // Keep a static viewport group for clipping, and transform only the inner
4960
+ // content group during zoom so the visible area stays fixed.
4961
+ var zoomViewport = svg.append('g').attr('data-layer', 'zoom-viewport').attr('clip-path', "url(#clip-" + scopedGraphId + "-" + diagramType + "d3)");
4962
+ var zoomContent = zoomViewport.append('g').attr('data-layer', 'zoom-content');
4963
+ var bijvoetLabelLayer = svg.append('g').attr('data-layer', 'bijvoet-label-layer').style('pointer-events', 'none');
4840
4964
  var svgRectangle = svgRef.current.getBoundingClientRect();
4841
4965
  svgRectRef.current = svgRectangle;
4842
4966
  marginRef.current = margin;
@@ -4847,13 +4971,19 @@ var Soundings = function Soundings(_ref) {
4847
4971
  xScale = _createScales[0],
4848
4972
  yScale = _createScales[1];
4849
4973
  yScaleRef.current = yScale;
4974
+ var applyAxisClips = function applyAxisClips() {
4975
+ axisLayer.select('g[type="x-axis"] path.domain').attr('d', "M0.5,0.5H" + (width + 0.5));
4976
+ axisLayer.select('g[type="x-axis"]').attr('clip-path', "url(#clip-" + scopedGraphId + "-" + diagramType + "x-axis)");
4977
+ axisLayer.select('g[type="y-axis"]').attr('clip-path', "url(#clip-" + scopedGraphId + "-" + diagramType + "y-axis)");
4978
+ };
4850
4979
  drawAxes$1({
4851
- svg: svg,
4980
+ svg: axisLayer,
4852
4981
  xScale: xScale,
4853
4982
  yScale: yScale,
4854
4983
  height: height,
4855
4984
  diagramType: diagramType
4856
4985
  });
4986
+ applyAxisClips();
4857
4987
  drawStaticLines({
4858
4988
  diagramType: diagramType,
4859
4989
  dp: dp,
@@ -4866,7 +4996,8 @@ var Soundings = function Soundings(_ref) {
4866
4996
  yScale: yScale,
4867
4997
  minPressure: minPressure,
4868
4998
  maxPressure: maxPressure,
4869
- svg: svg,
4999
+ svg: zoomContent,
5000
+ bijvoetLabelSvg: bijvoetLabelLayer,
4870
5001
  d3GraphId: scopedGraphId
4871
5002
  });
4872
5003
  data.forEach(function (features, index) {
@@ -4882,10 +5013,11 @@ var Soundings = function Soundings(_ref) {
4882
5013
  pressureValues: pressureValues,
4883
5014
  color: color,
4884
5015
  lineStyle: 'none',
5016
+ d3GraphId: scopedGraphId,
4885
5017
  xScale: xScale,
4886
5018
  yScale: yScale,
4887
5019
  height: height,
4888
- svg: svg,
5020
+ svg: zoomContent,
4889
5021
  diagramType: diagramType
4890
5022
  });
4891
5023
  var dewpointValues = features.map(function (feature) {
@@ -4896,20 +5028,89 @@ var Soundings = function Soundings(_ref) {
4896
5028
  pressureValues: pressureValues,
4897
5029
  color: color,
4898
5030
  lineStyle: '8, 2',
5031
+ d3GraphId: scopedGraphId,
4899
5032
  xScale: xScale,
4900
5033
  yScale: yScale,
4901
5034
  height: height,
4902
- svg: svg,
5035
+ svg: zoomContent,
4903
5036
  diagramType: diagramType
4904
5037
  });
4905
5038
  });
4906
5039
  tooltipDataRef.current = constructTooltipData(data);
4907
- var svgRefCur = svgRef.current;
4908
- // eslint-disable-next-line consistent-return, @typescript-eslint/explicit-function-return-type
5040
+ var zoomTarget = zoomContent.node();
5041
+ if (!zoomTarget) {
5042
+ return;
5043
+ }
5044
+ zoomTargetRef.current = zoomTarget;
5045
+ var bijvoetLabelTarget = bijvoetLabelLayer.node();
5046
+ if (!bijvoetLabelTarget) {
5047
+ return;
5048
+ }
5049
+ var applyZoomTransform = function applyZoomTransform(transform) {
5050
+ d3.select(zoomTarget).attr('transform', transform.toString());
5051
+ d3.select(bijvoetLabelTarget).attr('transform', transform.toString());
5052
+ };
5053
+ // Allow zooming and panning
5054
+ var zoomBehavior = d3.zoom().scaleExtent([1, 7]).extent([[0, 0], [width, height]]).translateExtent([[0, 0], [width, height]]).on('zoom', function (event) {
5055
+ // Persist every interactive zoom/pan event so the transform can be
5056
+ // restored after dimension-driven rerenders.
5057
+ persistedZoomTransformRef.current = event.transform;
5058
+ applyZoomTransform(event.transform);
5059
+ var zoomedXScale = event.transform.rescaleX(xScale);
5060
+ var zoomedYScale = event.transform.rescaleY(yScale);
5061
+ var axisXScale = diagramType === 'SKEWT' ? xScale.copy().range(xScale.range().map(function (xValue) {
5062
+ // Skew-T X uses x + (height - y). To keep tick labels tied to
5063
+ // isotherm intersections on the bottom axis, apply the same
5064
+ // transformed bottom-edge offset to the axis scale.
5065
+ var skewBottomOffset = (event.transform.k - 1) * height + event.transform.y;
5066
+ return event.transform.k * xValue + event.transform.x + skewBottomOffset;
5067
+ })) : zoomedXScale;
5068
+ var axisYScale = diagramType === 'BIJVOET' ? yScale.copy().range(yScale.range().map(function (yValue) {
5069
+ return event.transform.k * yValue + event.transform.y;
5070
+ })) : zoomedYScale;
5071
+ axisLayer.selectAll('*').remove();
5072
+ drawAxes$1({
5073
+ svg: axisLayer,
5074
+ xScale: axisXScale,
5075
+ yScale: axisYScale,
5076
+ height: height,
5077
+ diagramType: diagramType
5078
+ });
5079
+ applyAxisClips();
5080
+ if (!isApplyingSharedZoomRef.current) {
5081
+ publishVerticalZoom(scopedGraphId, event.transform.k, event.transform.y);
5082
+ }
5083
+ });
5084
+ zoomBehaviorRef.current = zoomBehavior;
5085
+ svg.call(zoomBehavior);
5086
+ var previousTransform = updateTransformToViewport(persistedZoomTransformRef.current, width, height);
5087
+ var hasPersistedTransform = previousTransform.k > 1 || Math.abs(previousTransform.x) > 1e-3 || Math.abs(previousTransform.y) > 1e-3;
5088
+ // Re-apply the previous transform after rebuilding the SVG so resize
5089
+ // operations preserve the current zoom level/pan offset.
5090
+ if (hasPersistedTransform) {
5091
+ isApplyingSharedZoomRef.current = true;
5092
+ svg.call(zoomBehavior.transform, previousTransform);
5093
+ isApplyingSharedZoomRef.current = false;
5094
+ }
5095
+ // Keep shared zoom state aligned with the effective transform that was
5096
+ // restored after resize so companion panels (WindBarbs) do not wait for the
5097
+ // next interactive zoom event.
5098
+ publishVerticalZoom(scopedGraphId, previousTransform.k, previousTransform.y);
5099
+ previousDiagramTypeRef.current = diagramType;
5100
+ // eslint-disable-next-line consistent-return
4909
5101
  return function () {
4910
- d3.select(svgRefCur).selectAll('*').remove();
5102
+ // Read the transform directly from d3's internal zoom state before this
5103
+ // chart instance is torn down, then reuse it on the next mount pass.
5104
+ var zoomState = zoomTarget.__zoom;
5105
+ if (zoomState) {
5106
+ persistedZoomTransformRef.current = zoomState;
5107
+ }
5108
+ svg.on('zoom', null);
5109
+ zoomBehaviorRef.current = null;
5110
+ zoomTargetRef.current = null;
5111
+ root.selectAll('*').remove();
4911
5112
  };
4912
- }, [data, diagramType, width, height, scopedGraphId, margin]);
5113
+ }, [data, diagramType, width, height, scopedGraphId, margin, publishVerticalZoom, resetVerticalZoom]);
4913
5114
  useEffect(function () {
4914
5115
  var _svgRectRef$current;
4915
5116
  if (!showTooltip || !svgRef.current) {
@@ -4943,19 +5144,22 @@ var Soundings = function Soundings(_ref) {
4943
5144
  var maxT = maxTemperature;
4944
5145
  var _createScales2 = createScales$1(diagramType, width, height, minPressure, maxPressure, minT, maxT),
4945
5146
  yScale = _createScales2[1];
5147
+ var pointerY = clientY - rect.top - marginRef.current.top;
5148
+ var unscaledY = (pointerY - verticalZoom.y) / verticalZoom.scale;
5149
+ var hoveredPressure = extractPressureFromY(diagramType, unscaledY, yScale);
4946
5150
  drawSoundingsTooltip({
4947
5151
  event: synthetic,
4948
- yScale: yScale,
4949
5152
  svgRectangle: rect,
4950
5153
  margin: marginRef.current,
4951
5154
  width: widthRef.current,
5155
+ hoveredPressure: hoveredPressure,
4952
5156
  pressureRefd3: pressureRefd3,
4953
5157
  tempRefd3: tempRefd3,
4954
5158
  dewpointRefd3: dewpointRefd3,
4955
5159
  tooltipData: tooltipDataRef.current,
4956
5160
  diagramType: diagramType
4957
5161
  });
4958
- }, [clientY, showTooltip, diagramType, data, width, height]);
5162
+ }, [clientY, showTooltip, diagramType, data, width, height, verticalZoom.scale, verticalZoom.y]);
4959
5163
  return jsxs(Box, {
4960
5164
  ref: canvasRef,
4961
5165
  sx: {
@@ -5165,7 +5369,10 @@ var SoundingsTimeSlider = function SoundingsTimeSlider(_ref) {
5165
5369
  * Copyright 2025 - The Norwegian Meteorological Institute (MET Norway)
5166
5370
  * */
5167
5371
  var OFFSET_HEIGHT = 100; // Space reserved in layout
5168
- var useSoundingsDiagramSize = function useSoundingsDiagramSize(ref) {
5372
+ var useSoundingsDiagramSize = function useSoundingsDiagramSize(ref, useWidth) {
5373
+ if (useWidth === void 0) {
5374
+ useWidth = true;
5375
+ }
5169
5376
  var _React$useState = React.useState(0),
5170
5377
  size = _React$useState[0],
5171
5378
  setSize = _React$useState[1];
@@ -5182,14 +5389,14 @@ var useSoundingsDiagramSize = function useSoundingsDiagramSize(ref) {
5182
5389
  width = _entries$0$contentRec.width,
5183
5390
  height = _entries$0$contentRec.height;
5184
5391
  var availableHeight = height - TIMESLIDER_HEIGHT + OFFSET_HEIGHT;
5185
- var nextSize = Math.floor(Math.min(width, availableHeight));
5392
+ var nextSize = useWidth ? Math.floor(Math.min(width, availableHeight)) : Math.floor(availableHeight);
5186
5393
  setSize(nextSize > 0 ? nextSize : 0);
5187
5394
  });
5188
5395
  resizeObserver.observe(container);
5189
5396
  return function () {
5190
5397
  resizeObserver.disconnect();
5191
5398
  };
5192
- }, [ref]);
5399
+ }, [ref, useWidth]);
5193
5400
  return size;
5194
5401
  };
5195
5402
 
@@ -5444,7 +5651,9 @@ var drawWindBarbs = function drawWindBarbs(_ref) {
5444
5651
  _ref$barbColor = _ref.barbColor,
5445
5652
  barbColor = _ref$barbColor === void 0 ? 'black' : _ref$barbColor,
5446
5653
  _ref$columnOffsetX = _ref.columnOffsetX,
5447
- columnOffsetX = _ref$columnOffsetX === void 0 ? 0 : _ref$columnOffsetX;
5654
+ columnOffsetX = _ref$columnOffsetX === void 0 ? 0 : _ref$columnOffsetX,
5655
+ _ref$verticalScale = _ref.verticalScale,
5656
+ verticalScale = _ref$verticalScale === void 0 ? 1 : _ref$verticalScale;
5448
5657
  var barbsSize = 40;
5449
5658
  var scalingFactor = 0.5;
5450
5659
  var horizontalBarbMargin = barbsSize * scalingFactor + 5;
@@ -5458,14 +5667,14 @@ var drawWindBarbs = function drawWindBarbs(_ref) {
5458
5667
  if (!currentWidth) {
5459
5668
  return;
5460
5669
  }
5461
- // Add extra width to all isobars needed for the windbarbs
5462
- d3.selectAll('path[type="isobar"]').each(function (_d, i, nodes) {
5670
+ // Add extra width to isobars in this SVG only.
5671
+ rootSvg.selectAll('path[type="isobar"]').each(function (_d, i, nodes) {
5463
5672
  var path = d3.select(nodes[i]).attr('d');
5464
5673
  var newD = extendIsoBar(path, horizontalBarbMargin);
5465
5674
  d3.select(nodes[i]).attr('d', newD).attr('clip-path', null);
5466
5675
  });
5467
- // Add extra width to x-axis needed for the windbarbs
5468
- d3.select('g[type="x-axis"] path.domain').each(function (_d, i, nodes) {
5676
+ // Add extra width to x-axis in this SVG only.
5677
+ rootSvg.select('g[type="x-axis"] path.domain').each(function (_d, i, nodes) {
5469
5678
  var path = d3.select(nodes[i]).attr('d');
5470
5679
  var newD = extendXAxis(path, horizontalBarbMargin);
5471
5680
  d3.select(nodes[i]).attr('d', newD);
@@ -5475,10 +5684,10 @@ var drawWindBarbs = function drawWindBarbs(_ref) {
5475
5684
  // Place the barbs just to the right of the plotting area.
5476
5685
  // plotGroup's origin is already translated to (margin.left, margin.top),
5477
5686
  // and its width equals `width`.
5478
- var windBarbColumnX = width + 5 + columnOffsetX;
5479
- var barbGroup = plotGroup.append('g').attr('class', 'wind-group').attr('transform', // This translation should be revisited when implementing
5480
- // the plotting of several datasets at once
5481
- "translate(" + windBarbColumnX + ", 0)");
5687
+ var windBarbColumnX = width + 5 + columnOffsetX + horizontalBarbMargin;
5688
+ var barbGroup = plotGroup.append('g').attr('class', 'wind-group').attr('transform', // Apply translation and counter-scale to maintain aspect ratio during vertical zoom.
5689
+ // The counter-scale (1, 1/verticalScale) undoes the parent's vertical scaling.
5690
+ "translate(" + windBarbColumnX + ", 0) scale(1, " + 1 / verticalScale + ")");
5482
5691
  barbGroup.selectAll('g').data(visibleItems).enter().append('g').attr('id', 'wind-barb').each(function handleBarb(data) {
5483
5692
  var group = d3.select(this);
5484
5693
  var windSymbol = getWindSymbol(data.windSpeed);
@@ -5504,9 +5713,10 @@ var drawWindBarbs = function drawWindBarbs(_ref) {
5504
5713
  var cy = bbox.y + bbox.height / 2;
5505
5714
  var rotation = 180 + data.windDirection;
5506
5715
  var y = yScale(data.pressure);
5507
- // Place items in the correct vertical position
5508
- // Correcting for barbSize
5509
- group.attr('transform', "translate(0, " + (y - barbsSize * scalingFactor / 8) + ")");
5716
+ // Place items in the correct vertical position.
5717
+ // Apply verticalScale to compensate for the parent's counter-scale,
5718
+ // ensuring barbs appear at the correct zoomed Y position.
5719
+ group.attr('transform', "translate(0, " + (y - barbsSize * scalingFactor / 8) * verticalScale + ")");
5510
5720
  // Rotate accordingly, and scale
5511
5721
  innerGroup.attr('transform', "rotate(" + rotation + ") scale(" + scalingFactor + ") translate(" + -cx + ", " + -cy + ")");
5512
5722
  }
@@ -5562,6 +5772,10 @@ var soundingsStyle = {
5562
5772
  }
5563
5773
  };
5564
5774
  var WIND_BARB_OFFSET_X = 35;
5775
+ var getVerticalOnlyTransform = function getVerticalOnlyTransform(scale, yOffset) {
5776
+ // Keep x-axis unchanged, apply shared zoom on y-axis only.
5777
+ return "matrix(1, 0, 0, " + scale + ", 0, " + yOffset + ")";
5778
+ };
5565
5779
  /**
5566
5780
  * Check if the sounding data is effectively empty.
5567
5781
  * Data can be empty in two ways:
@@ -5597,8 +5811,12 @@ var WindBarbs = function WindBarbs(_ref) {
5597
5811
  var widthRef = useRef(0);
5598
5812
  var svgRectRef = useRef(null);
5599
5813
  var tooltipDataRef = useRef(null);
5814
+ // Must point to the inner translated <g> to match Soundings zoom coordinate space.
5815
+ var zoomTargetRef = useRef(null);
5600
5816
  var _useSharedMouseY = useSharedMouseY(),
5601
- clientY = _useSharedMouseY.clientY;
5817
+ clientY = _useSharedMouseY.clientY,
5818
+ verticalZoom = _useSharedMouseY.verticalZoom,
5819
+ resetVerticalZoom = _useSharedMouseY.resetVerticalZoom;
5602
5820
  // Set SVG dimensions (required props; no optional chaining needed)
5603
5821
  var baseWidth = svgDimensions.width,
5604
5822
  baseHeight = svgDimensions.height,
@@ -5606,7 +5824,7 @@ var WindBarbs = function WindBarbs(_ref) {
5606
5824
  var width = baseWidth - (margin.left + margin.right);
5607
5825
  var height = baseHeight - (margin.top + margin.bottom);
5608
5826
  useEffect(function () {
5609
- if (!svgRef.current) {
5827
+ if (!svgRef.current || width <= 0 || height <= 0) {
5610
5828
  return;
5611
5829
  }
5612
5830
  var root = d3.select(svgRef.current);
@@ -5619,8 +5837,9 @@ var WindBarbs = function WindBarbs(_ref) {
5619
5837
  diagramType: diagramType,
5620
5838
  d3GraphId: d3GraphId
5621
5839
  });
5622
- // When Soundings is dragged close, animate the Wind Barbs to resize back to the height where dragging started
5840
+ // When Soundings is dragged closed, reset the zoom level and animate the Wind Barbs to resize back to the height where dragging started
5623
5841
  if (animateResize) {
5842
+ resetVerticalZoom(d3GraphId);
5624
5843
  animateResizeTransform(svg, margin, height, prevInnerHeightRef);
5625
5844
  }
5626
5845
  var svgRectangle = svgRef.current.getBoundingClientRect();
@@ -5658,18 +5877,33 @@ var WindBarbs = function WindBarbs(_ref) {
5658
5877
  width: width,
5659
5878
  margin: margin,
5660
5879
  barbColor: color,
5661
- columnOffsetX: index * WIND_BARB_OFFSET_X
5880
+ columnOffsetX: index * WIND_BARB_OFFSET_X,
5881
+ verticalScale: verticalZoom.scale
5662
5882
  });
5663
5883
  });
5664
5884
  // Clear or update tooltip data based on whether we have actual data
5665
5885
  tooltipDataRef.current = isDataEmpty(data) ? null : constructTooltipData(data);
5666
5886
  // Update previous inner height after drawing so animation uses the prior value
5667
5887
  prevInnerHeightRef.current = height;
5888
+ var zoomTarget = svg.node();
5889
+ if (!zoomTarget) {
5890
+ return;
5891
+ }
5892
+ zoomTargetRef.current = zoomTarget;
5893
+ // Initialize with shared zoom state from Soundings.
5894
+ d3.select(zoomTarget).attr('transform', getVerticalOnlyTransform(verticalZoom.scale, verticalZoom.y));
5668
5895
  // eslint-disable-next-line consistent-return, @typescript-eslint/explicit-function-return-type
5669
5896
  return function () {
5897
+ zoomTargetRef.current = null;
5670
5898
  root.selectAll('*').remove();
5671
5899
  };
5672
- }, [data, diagramType, width, height, d3GraphId, margin, animateResize]);
5900
+ }, [data, diagramType, width, height, d3GraphId, margin, animateResize, verticalZoom.scale, verticalZoom.y, resetVerticalZoom]);
5901
+ useEffect(function () {
5902
+ if (!zoomTargetRef.current) {
5903
+ return;
5904
+ }
5905
+ d3.select(zoomTargetRef.current).attr('transform', getVerticalOnlyTransform(verticalZoom.scale, verticalZoom.y));
5906
+ }, [verticalZoom]);
5673
5907
  useEffect(function () {
5674
5908
  var _svgRectRef$current;
5675
5909
  if (!showTooltip || !svgRef.current || !windRef.current) {
@@ -5697,18 +5931,27 @@ var WindBarbs = function WindBarbs(_ref) {
5697
5931
  target: svgRef.current,
5698
5932
  currentTarget: svgRef.current
5699
5933
  };
5934
+ var pointerY = clientY - rect.top - marginRef.current.top;
5935
+ var unscaledY = (pointerY - verticalZoom.y) / verticalZoom.scale;
5936
+ var hoveredPressure = extractPressureFromY(diagramType, unscaledY, yScaleRef.current);
5937
+ var transformedY = yScaleRef.current(hoveredPressure) * verticalZoom.scale + verticalZoom.y;
5938
+ var pressureLineY = transformedY + marginRef.current.top;
5700
5939
  var windRefd3 = d3.select(windRef.current);
5701
5940
  drawSoundingsTooltip({
5702
5941
  event: synthetic,
5703
- yScale: yScaleRef.current,
5704
5942
  svgRectangle: rect,
5705
5943
  margin: marginRef.current,
5706
5944
  width: widthRef.current,
5945
+ hoveredPressure: hoveredPressure,
5707
5946
  windRefd3: windRefd3,
5708
5947
  tooltipData: tooltipDataRef.current,
5709
5948
  diagramType: diagramType
5710
5949
  });
5711
- }, [clientY, showTooltip, diagramType, data]);
5950
+ var tooltipHeight = windRef.current.getBoundingClientRect().height;
5951
+ var centeredTop = pressureLineY - tooltipHeight / 2;
5952
+ var clampedTop = Math.max(0, Math.min(centeredTop, rect.height - tooltipHeight));
5953
+ windRef.current.style.top = clampedTop + "px";
5954
+ }, [clientY, showTooltip, diagramType, data, verticalZoom.scale, verticalZoom.y]);
5712
5955
  return jsxs(Box, {
5713
5956
  ref: canvasRef,
5714
5957
  sx: {
@@ -5972,32 +6215,42 @@ var SoundingsPanels = function SoundingsPanels(_ref) {
5972
6215
  panelComponent = _ref.panelComponent,
5973
6216
  profileDataPreferences = _ref.profileDataPreferences;
5974
6217
  var soundingsContentRef = React.useRef(null);
6218
+ var windBarbsContentRef = React.useRef(null);
5975
6219
  var diagramSize = useSoundingsDiagramSize(soundingsContentRef);
5976
- var _React$useState = React.useState(false),
5977
- soundingsIsClosed = _React$useState[0],
5978
- setSoundingsIsClosed = _React$useState[1];
5979
- var _React$useState2 = React.useState(null),
5980
- previousHeight = _React$useState2[0],
5981
- setPreviousHeight = _React$useState2[1];
6220
+ var windBarbsDiagramSize = useSoundingsDiagramSize(windBarbsContentRef, false);
6221
+ var soundingsIsOpen = React.useMemo(function () {
6222
+ var _soundingPanelsInOrde;
6223
+ return ((_soundingPanelsInOrde = soundingPanelsInOrder.find(function (panel) {
6224
+ return panel.id === 'soundings';
6225
+ })) == null ? void 0 : _soundingPanelsInOrde.isOpen) || false;
6226
+ }, [soundingPanelsInOrder]);
6227
+ var _React$useState = React.useState(null),
6228
+ previousHeight = _React$useState[0],
6229
+ setPreviousHeight = _React$useState[1];
5982
6230
  var firstSelectedSourceData = React.useMemo(function () {
5983
6231
  var _data$;
5984
6232
  return (_data$ = data[0]) != null ? _data$ : [];
5985
6233
  }, [data]);
6234
+ var windBarbsHeight = React.useMemo(function () {
6235
+ if (soundingsIsOpen) {
6236
+ return diagramSize;
6237
+ }
6238
+ return previousHeight != null ? previousHeight : windBarbsDiagramSize;
6239
+ }, [diagramSize, previousHeight, soundingsIsOpen, windBarbsDiagramSize]);
5986
6240
  var sizeRef = React.useRef(diagramSize);
5987
6241
  var isResizingRef = React.useRef(false);
5988
6242
  React.useEffect(function () {
5989
6243
  sizeRef.current = diagramSize;
5990
6244
  }, [diagramSize]);
5991
6245
  React.useEffect(function () {
5992
- if (previousHeight == null && diagramSize > 0) {
6246
+ if (previousHeight == null && diagramSize > 24) {
5993
6247
  setPreviousHeight(diagramSize);
5994
6248
  }
5995
6249
  }, [diagramSize, previousHeight]);
5996
6250
  var preserveWindBarbsGrowBackHeight = React.useCallback(function () {
5997
- if (sizeRef.current > 0) {
5998
- setPreviousHeight(sizeRef.current);
5999
- }
6000
- }, []);
6251
+ var nextHeight = sizeRef.current > 24 ? sizeRef.current : windBarbsDiagramSize;
6252
+ setPreviousHeight(nextHeight);
6253
+ }, [windBarbsDiagramSize]);
6001
6254
  var handleSoundingsResizeStart = React.useCallback(function () {
6002
6255
  isResizingRef.current = true;
6003
6256
  preserveWindBarbsGrowBackHeight();
@@ -6010,7 +6263,6 @@ var SoundingsPanels = function SoundingsPanels(_ref) {
6010
6263
  if (isClosingViaClick) {
6011
6264
  preserveWindBarbsGrowBackHeight();
6012
6265
  }
6013
- setSoundingsIsClosed(!isOpen);
6014
6266
  }, [preserveWindBarbsGrowBackHeight]);
6015
6267
  var panels = soundingPanelsInOrder.flatMap(function (_ref2) {
6016
6268
  var id = _ref2.id,
@@ -6054,9 +6306,10 @@ var SoundingsPanels = function SoundingsPanels(_ref) {
6054
6306
  name: 'Wind Barbs',
6055
6307
  isDisplayed: isDisplayed,
6056
6308
  maxWidth: 200,
6309
+ panelRef: windBarbsContentRef,
6057
6310
  render: function render() {
6058
6311
  return jsx(WindBarbs, {
6059
- animateResize: Boolean(soundingsIsClosed && previousHeight),
6312
+ animateResize: Boolean(!soundingsIsOpen && previousHeight),
6060
6313
  d3GraphId: normalizedId,
6061
6314
  data: data,
6062
6315
  diagramType: chosenDiagramType,
@@ -6068,7 +6321,7 @@ var SoundingsPanels = function SoundingsPanels(_ref) {
6068
6321
  right: 25,
6069
6322
  top: 0
6070
6323
  },
6071
- height: soundingsIsClosed ? previousHeight != null ? previousHeight : diagramSize : diagramSize
6324
+ height: windBarbsHeight
6072
6325
  }
6073
6326
  });
6074
6327
  }
@@ -6261,6 +6514,26 @@ var SoundingsConnect = function SoundingsConnect(_ref) {
6261
6514
  previousLocationsRef.current = {};
6262
6515
  }
6263
6516
  }, [currentSoundingsPreset, dispatch, soundingsPreset, viewId]);
6517
+ var syncgroupTimeState = useSyncgroupTimeState(viewId, 0);
6518
+ var syncgroupSelectedTime = useMemo(function () {
6519
+ if ((syncgroupTimeState == null ? void 0 : syncgroupTimeState.current) === undefined || !Number.isFinite(syncgroupTimeState.current)) {
6520
+ return undefined;
6521
+ }
6522
+ return dateUtils.fromUnix(syncgroupTimeState.current).toISOString();
6523
+ }, [syncgroupTimeState]);
6524
+ var isTimeSynced = !!syncgroupSelectedTime;
6525
+ React.useEffect(function () {
6526
+ if (soundingsPreset != null && soundingsPreset.selectedTime && isTimeSynced) {
6527
+ // trigger sync of time to map when preset has selectedTime
6528
+ dispatch(genericActions.setTime({
6529
+ sourceId: viewId,
6530
+ origin: 'SoundingsConnect',
6531
+ value: {
6532
+ currentTime: handleDateUtilsISOString(soundingsPreset.selectedTime)
6533
+ }
6534
+ }));
6535
+ }
6536
+ }, [dispatch, isTimeSynced, soundingsPreset == null ? void 0 : soundingsPreset.selectedTime, viewId]);
6264
6537
  useRegisterFeatureSyncSource(viewId);
6265
6538
  var selectedTime = useSelector(function (state) {
6266
6539
  return getSelectedTime(state, viewId);
@@ -6293,6 +6566,9 @@ var SoundingsConnect = function SoundingsConnect(_ref) {
6293
6566
  var profileDataPreferences = useSelector(function (state) {
6294
6567
  return getProfileDataPreferences(state, viewId);
6295
6568
  });
6569
+ var isLegendVisible = useSelector(function (state) {
6570
+ return getIsLegendVisible(state, viewId);
6571
+ });
6296
6572
  var selectedMapIds = useSelector(function (state) {
6297
6573
  return genericSelectors.getLinkedTargetIds(state, viewId);
6298
6574
  }, shallowEqual);
@@ -6307,14 +6583,6 @@ var SoundingsConnect = function SoundingsConnect(_ref) {
6307
6583
  var firstLinkedMapId = selectedMapIds[0];
6308
6584
  var _useTimesteps = useTimesteps(resolvedSourceSelections),
6309
6585
  sourceTimesteps = _useTimesteps.data;
6310
- var syncgroupTimeState = useSyncgroupTimeState(viewId, 0);
6311
- var syncgroupSelectedTime = useMemo(function () {
6312
- if ((syncgroupTimeState == null ? void 0 : syncgroupTimeState.current) === undefined || !Number.isFinite(syncgroupTimeState.current)) {
6313
- return undefined;
6314
- }
6315
- return dateUtils.fromUnix(syncgroupTimeState.current).toISOString();
6316
- }, [syncgroupTimeState]);
6317
- var isTimeSynced = !!syncgroupSelectedTime;
6318
6586
  var selectedTimeInUse = syncgroupSelectedTime || selectedTime;
6319
6587
  var timeMismatchInfo = useMemo(function () {
6320
6588
  return getTimeMismatchInfo(isTimeSynced, selectedTimeInUse, sourceTimesteps);
@@ -6973,7 +7241,7 @@ var SoundingsConnect = function SoundingsConnect(_ref) {
6973
7241
  fontSize: "small"
6974
7242
  })
6975
7243
  })]
6976
- }), jsx(Box, {
7244
+ }), isLegendVisible ? jsx(Box, {
6977
7245
  sx: {
6978
7246
  display: 'flex',
6979
7247
  justifyContent: 'center',
@@ -6989,7 +7257,7 @@ var SoundingsConnect = function SoundingsConnect(_ref) {
6989
7257
  sourceResolvedInstanceIds: sourceResolvedInstanceIds,
6990
7258
  sourceSelections: sourceSelections
6991
7259
  })
6992
- }), jsx(Box, {
7260
+ }) : null, jsx(Box, {
6993
7261
  sx: {
6994
7262
  flex: 1,
6995
7263
  minHeight: 0,
@@ -7272,6 +7540,28 @@ var SoundingsSourceDialogConnect = function SoundingsSourceDialogConnect(_ref) {
7272
7540
  data = _useGetCollectionsIns.data,
7273
7541
  isPending = _useGetCollectionsIns.isPending,
7274
7542
  error = _useGetCollectionsIns.error;
7543
+ // Update checked sources without an instance once instances finish loading.
7544
+ React.useEffect(function () {
7545
+ data == null || data.forEach(function (collection) {
7546
+ var currentSelection = sourceSelections[collection.id];
7547
+ if (!(currentSelection != null && currentSelection.isChecked) || currentSelection.instanceId || collection.instances.length === 0) {
7548
+ return;
7549
+ }
7550
+ var resolvedInstance = referenceTimeUtils.resolveReferenceTimeInstance(collection.instances, defaultInstanceId);
7551
+ if (!resolvedInstance) {
7552
+ return;
7553
+ }
7554
+ dispatch(soundingsActions.updateSelectedSource({
7555
+ sourceId: collection.id,
7556
+ value: {
7557
+ instanceId: defaultInstanceId,
7558
+ verticalRange: getVerticalRangeFromInstance(resolvedInstance)
7559
+ },
7560
+ viewId: viewId,
7561
+ origin: SoundingsActionOrigin.soundingsManager
7562
+ }));
7563
+ });
7564
+ }, [data, dispatch, sourceSelections, viewId]);
7275
7565
  return jsx(SoundingsSourceMenu, {
7276
7566
  error: error,
7277
7567
  handleClose: onCloseDialog,