@opengeoweb/soundings 19.3.0 → 19.5.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
  /* *
@@ -200,8 +212,27 @@ var dTdp = function dTdp(pressure, temperature) {
200
212
  var dTdp2 = dTdp1 / (1.0 + Rs / (7.0 * EPSILON) + Math.pow(Lv / temperature, 2) * Rs * EPSILON / (R_AIR * HEATCP));
201
213
  return dTdp2;
202
214
  };
215
+ var MAX_THETA_CACHE_SIZE = 50000;
216
+ var thetaFromPressTempCache = new Map();
217
+ var buildThetaCacheKey = function buildThetaCacheKey(pressure, temperature, basePressure) {
218
+ return pressure.toFixed(6) + "|" + temperature.toFixed(6) + "|" + basePressure.toFixed(6);
219
+ };
220
+ var setThetaCacheValue = function setThetaCacheValue(key, value) {
221
+ if (thetaFromPressTempCache.size >= MAX_THETA_CACHE_SIZE) {
222
+ var oldestKey = thetaFromPressTempCache.keys().next().value;
223
+ if (oldestKey !== undefined) {
224
+ thetaFromPressTempCache["delete"](oldestKey);
225
+ }
226
+ }
227
+ thetaFromPressTempCache.set(key, value);
228
+ };
203
229
  var calculateThetaFromPressTemp = function calculateThetaFromPressTemp(pressure, temperature, basePressure) {
204
230
  var base = basePressure || REFERENCE_PRESSURE;
231
+ var cacheKey = buildThetaCacheKey(pressure, temperature, base);
232
+ var cachedValue = thetaFromPressTempCache.get(cacheKey);
233
+ if (cachedValue !== undefined) {
234
+ return cachedValue;
235
+ }
205
236
  var dpPascal = pressure > base ? pressToPascal(-10) : pressToPascal(10); // hPa steps to Pa steps
206
237
  var numberOfSteps = Math.round((base - pressure) / dpPascal) < 1 ? 1 : Math.round((base - pressure) / dpPascal);
207
238
  var pstep = (base - pressure) / numberOfSteps;
@@ -215,6 +246,7 @@ var calculateThetaFromPressTemp = function calculateThetaFromPressTemp(pressure,
215
246
  tempN += k1 / 6.0 + k2 / 3.0 + k3 / 3.0 + k4 / 6.0;
216
247
  pressureN += pstep;
217
248
  }
249
+ setThetaCacheValue(cacheKey, tempN);
218
250
  return tempN;
219
251
  };
220
252
 
@@ -310,6 +342,14 @@ var initializeSvg$1 = function initializeSvg(_ref) {
310
342
  // Ensure clipPath uses same coordinate system as the translated group
311
343
  // so width/height starting at x=0,y=0 align with axis origin.
312
344
  .attr('clipPathUnits', 'userSpaceOnUse').append('rect').attr('width', width).attr('height', height).attr('x', 0).attr('y', 0);
345
+ svg.append('clipPath').attr('id', "clip-" + d3GraphId + "-" + diagramType + "x-axis").attr('clipPathUnits', 'userSpaceOnUse').append('rect')
346
+ // Keep x-axis labels within the horizontal chart band while allowing them
347
+ // to use the reserved left and bottom margin.
348
+ .attr('width', width + margin.left).attr('height', height + margin.bottom).attr('x', -margin.left).attr('y', 0);
349
+ svg.append('clipPath').attr('id', "clip-" + d3GraphId + "-" + diagramType + "y-axis").attr('clipPathUnits', 'userSpaceOnUse').append('rect')
350
+ // Keep y-axis labels within the vertical chart band while allowing them
351
+ // to use the reserved left margin.
352
+ .attr('width', width + margin.left).attr('height', height).attr('x', -margin.left).attr('y', 0);
313
353
  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
354
  return svg;
315
355
  };
@@ -438,6 +478,7 @@ var plotSounding = function plotSounding(_ref7) {
438
478
  pressureValues = _ref7.pressureValues,
439
479
  color = _ref7.color,
440
480
  lineStyle = _ref7.lineStyle,
481
+ d3GraphId = _ref7.d3GraphId,
441
482
  xScale = _ref7.xScale,
442
483
  yScale = _ref7.yScale,
443
484
  height = _ref7.height,
@@ -450,7 +491,7 @@ var plotSounding = function plotSounding(_ref7) {
450
491
  var Px = mapToPlot(diagramType, filteredPressureValues, filteredTemperatureValues, xScale, yScale, height);
451
492
  var lineGenerator = d3.line();
452
493
  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)");
494
+ 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
495
  };
455
496
  // Use this function to filter the pressure and parameter array so no NaN or null values are included
456
497
  var filterDomainRangeForParameter = function filterDomainRangeForParameter(pressureDomain, parameterDomain) {
@@ -463,8 +504,16 @@ var filterDomainRangeForParameter = function filterDomainRangeForParameter(press
463
504
  return [filteredPressureDomain, filteredParameterDomain];
464
505
  };
465
506
 
507
+ var defaultVerticalZoom = {
508
+ scale: 1,
509
+ y: 0,
510
+ sourceId: null
511
+ };
466
512
  var MouseYContext = /*#__PURE__*/React.createContext({
467
- clientY: null
513
+ clientY: null,
514
+ verticalZoom: defaultVerticalZoom,
515
+ publishVerticalZoom: function publishVerticalZoom() {},
516
+ resetVerticalZoom: function resetVerticalZoom() {}
468
517
  });
469
518
  var useSharedMouseY = function useSharedMouseY() {
470
519
  return React.useContext(MouseYContext);
@@ -479,6 +528,31 @@ var SharedMouseYProvider = function SharedMouseYProvider(_ref) {
479
528
  var _React$useState = React.useState(null),
480
529
  clientY = _React$useState[0],
481
530
  setClientY = _React$useState[1];
531
+ var _React$useState2 = React.useState(defaultVerticalZoom),
532
+ verticalZoom = _React$useState2[0],
533
+ setVerticalZoom = _React$useState2[1];
534
+ var publishVerticalZoom = React.useCallback(function (sourceId, scale, y) {
535
+ setVerticalZoom(function (previous) {
536
+ var hasSameValues = previous.sourceId === sourceId && Math.abs(previous.scale - scale) < 1e-6 && Math.abs(previous.y - y) < 1e-3;
537
+ if (hasSameValues) {
538
+ return previous;
539
+ }
540
+ return {
541
+ scale: scale,
542
+ y: y,
543
+ sourceId: sourceId
544
+ };
545
+ });
546
+ }, []);
547
+ var resetVerticalZoom = React.useCallback(function (sourceId) {
548
+ setVerticalZoom(function () {
549
+ return {
550
+ scale: 1,
551
+ y: 0,
552
+ sourceId: sourceId
553
+ };
554
+ });
555
+ }, []);
482
556
  React.useEffect(function () {
483
557
  var el = wrapperRef.current;
484
558
  if (!el) {
@@ -502,9 +576,12 @@ var SharedMouseYProvider = function SharedMouseYProvider(_ref) {
502
576
  }, []);
503
577
  var contextValue = React.useMemo(function () {
504
578
  return {
505
- clientY: clientY
579
+ clientY: clientY,
580
+ verticalZoom: verticalZoom,
581
+ publishVerticalZoom: publishVerticalZoom,
582
+ resetVerticalZoom: resetVerticalZoom
506
583
  };
507
- }, [clientY]);
584
+ }, [clientY, verticalZoom, publishVerticalZoom, resetVerticalZoom]);
508
585
  return jsx("div", {
509
586
  "data-testid": "shared-mouse-y-wrapper",
510
587
  ref: wrapperRef,
@@ -1145,6 +1222,25 @@ var soundingsQueryOptions = {
1145
1222
  }(),
1146
1223
  refetchInterval: 5 * 60 * 1000
1147
1224
  });
1225
+ },
1226
+ getCollectionDetails: function getCollectionDetails$1(collectionId, url) {
1227
+ return queryOptions({
1228
+ queryKey: ['edr-soundings-collection-details', collectionId, url],
1229
+ queryFn: function () {
1230
+ var _queryFn2 = _asyncToGenerator(/*#__PURE__*/_regenerator().m(function _callee2() {
1231
+ return _regenerator().w(function (_context2) {
1232
+ while (1) switch (_context2.n) {
1233
+ case 0:
1234
+ return _context2.a(2, getCollectionDetails(collectionId, url));
1235
+ }
1236
+ }, _callee2);
1237
+ }));
1238
+ function queryFn() {
1239
+ return _queryFn2.apply(this, arguments);
1240
+ }
1241
+ return queryFn;
1242
+ }()
1243
+ });
1148
1244
  }
1149
1245
  };
1150
1246
 
@@ -1702,6 +1798,7 @@ var useResolvedSourceSelections = function useResolvedSourceSelections(sources)
1702
1798
  }, [sources, instancesBySource]);
1703
1799
  };
1704
1800
  var useTimesteps = function useTimesteps(sourceSelections) {
1801
+ var queryClient = useQueryClient();
1705
1802
  var queries = React.useMemo(function () {
1706
1803
  return sourceSelections.map(function (_ref3) {
1707
1804
  var collectionId = _ref3.collectionId,
@@ -1711,12 +1808,12 @@ var useTimesteps = function useTimesteps(sourceSelections) {
1711
1808
  queryKey: ['edr-soundings-instance', collectionId, instanceId || ''],
1712
1809
  queryFn: function () {
1713
1810
  var _queryFn3 = _asyncToGenerator(/*#__PURE__*/_regenerator().m(function _callee3() {
1714
- var _instanceData$extent, instanceData, timeExtentValues, allInstancesData, collectionData;
1811
+ var _instanceData$extent, instanceData, timeExtentValues, collectionData;
1715
1812
  return _regenerator().w(function (_context3) {
1716
1813
  while (1) switch (_context3.n) {
1717
1814
  case 0:
1718
1815
  if (!(instanceId && instanceId !== '')) {
1719
- _context3.n = 4;
1816
+ _context3.n = 3;
1720
1817
  break;
1721
1818
  }
1722
1819
  _context3.n = 1;
@@ -1725,20 +1822,16 @@ var useTimesteps = function useTimesteps(sourceSelections) {
1725
1822
  instanceData = _context3.v;
1726
1823
  timeExtentValues = instanceData == null || (_instanceData$extent = instanceData.extent) == null || (_instanceData$extent = _instanceData$extent.temporal) == null ? void 0 : _instanceData$extent.values;
1727
1824
  if (timeExtentValues) {
1728
- _context3.n = 3;
1825
+ _context3.n = 2;
1729
1826
  break;
1730
1827
  }
1731
- _context3.n = 2;
1732
- return getCollectionInstances(collectionId, url);
1828
+ return _context3.a(2, queryClient.fetchQuery(soundingsQueryOptions.getCollectionInstances(collectionId, url)));
1733
1829
  case 2:
1734
- allInstancesData = _context3.v;
1735
- return _context3.a(2, allInstancesData);
1736
- case 3:
1737
1830
  return _context3.a(2, instanceData);
1831
+ case 3:
1832
+ _context3.n = 4;
1833
+ return queryClient.fetchQuery(soundingsQueryOptions.getCollectionDetails(collectionId, url));
1738
1834
  case 4:
1739
- _context3.n = 5;
1740
- return getCollectionDetails(collectionId, url);
1741
- case 5:
1742
1835
  collectionData = _context3.v;
1743
1836
  return _context3.a(2, collectionData);
1744
1837
  }
@@ -1762,7 +1855,7 @@ var useTimesteps = function useTimesteps(sourceSelections) {
1762
1855
  refetchInterval: 5 * 60 * 1000
1763
1856
  };
1764
1857
  });
1765
- }, [sourceSelections]);
1858
+ }, [sourceSelections, queryClient]);
1766
1859
  var results = useQueries({
1767
1860
  queries: queries,
1768
1861
  combine: function combine(queryResults) {
@@ -1803,29 +1896,9 @@ var useGetCollectionsInstances = function useGetCollectionsInstances(collections
1803
1896
  return collectionIds.map(function (_ref4) {
1804
1897
  var id = _ref4.id,
1805
1898
  url = _ref4.url;
1806
- return {
1807
- queryKey: ['edr-soundings-collection-details', id, url],
1808
- queryFn: function () {
1809
- var _queryFn4 = _asyncToGenerator(/*#__PURE__*/_regenerator().m(function _callee4() {
1810
- var data;
1811
- return _regenerator().w(function (_context4) {
1812
- while (1) switch (_context4.n) {
1813
- case 0:
1814
- _context4.n = 1;
1815
- return getCollectionDetails(id, url);
1816
- case 1:
1817
- data = _context4.v;
1818
- return _context4.a(2, data);
1819
- }
1820
- }, _callee4);
1821
- }));
1822
- function queryFn() {
1823
- return _queryFn4.apply(this, arguments);
1824
- }
1825
- return queryFn;
1826
- }(),
1899
+ return Object.assign({}, soundingsQueryOptions.getCollectionDetails(id, url), {
1827
1900
  enabled: isDialogOpen
1828
- };
1901
+ });
1829
1902
  });
1830
1903
  }, [collectionIds, isDialogOpen]);
1831
1904
  var resultsCollection = useQueries({
@@ -1834,10 +1907,13 @@ var useGetCollectionsInstances = function useGetCollectionsInstances(collections
1834
1907
  var _queryResults$find3;
1835
1908
  var data = queryResults.map(function (result) {
1836
1909
  var _result$data2;
1837
- return ((_result$data2 = result.data) == null || (_result$data2 = _result$data2.data_queries) == null ? void 0 : _result$data2.instances) !== undefined;
1910
+ return !!((_result$data2 = result.data) != null && (_result$data2 = _result$data2.data_queries) != null && _result$data2.instances);
1838
1911
  });
1839
- var isPending = queryResults.some(function (result) {
1840
- return result.isPending;
1912
+ // Only enabled queries should contribute to isPending to prevent a
1913
+ // loading indicator that never clears
1914
+ var isPending = queryResults.some(function (result, index) {
1915
+ var _queriesCollection$in;
1916
+ return !!((_queriesCollection$in = queriesCollection[index]) != null && _queriesCollection$in.enabled) && result.isPending;
1841
1917
  });
1842
1918
  var error = (_queryResults$find3 = queryResults.find(function (result) {
1843
1919
  return result == null ? void 0 : result.error;
@@ -1854,52 +1930,28 @@ var useGetCollectionsInstances = function useGetCollectionsInstances(collections
1854
1930
  return collectionIds.map(function (_ref5, index) {
1855
1931
  var id = _ref5.id,
1856
1932
  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
- };
1933
+ var hasInstances = !!resultsCollection.data[index];
1934
+ return Object.assign({}, soundingsQueryOptions.getCollectionInstances(id, url), {
1935
+ enabled: !!isDialogOpen && hasInstances
1936
+ });
1888
1937
  });
1889
- }, [collectionIds, isDialogOpen, resultsCollection]);
1938
+ }, [collectionIds, isDialogOpen, resultsCollection.data]);
1890
1939
  var results = useQueries({
1891
1940
  queries: queries,
1892
1941
  combine: function combine(queryResults) {
1893
1942
  var _queryResults$find4;
1894
1943
  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) {
1944
+ var _result$data$instance, _result$data3;
1945
+ 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
1946
  return featureB.id.localeCompare(featureA.id);
1898
- })) || []; // Sort to get latest first
1947
+ }); // Sort to get latest first
1899
1948
  return instances;
1900
1949
  });
1901
- var isPending = queryResults.some(function (result) {
1902
- return result.isPending;
1950
+ // Mirror the collection-details behavior: only enabled instance queries
1951
+ // participate in the loading state.
1952
+ var isPending = queryResults.some(function (result, index) {
1953
+ var _queries$index;
1954
+ return !!((_queries$index = queries[index]) != null && _queries$index.enabled) && result.isPending;
1903
1955
  });
1904
1956
  var error = (_queryResults$find4 = queryResults.find(function (result) {
1905
1957
  return result == null ? void 0 : result.error;
@@ -1932,61 +1984,61 @@ var useGetLocations = function useGetLocations(collections, enabled) {
1932
1984
  return {
1933
1985
  queryKey: ['edr-locations', collection.id, collection.url, (_collection$instanceI = collection.instanceId) != null ? _collection$instanceI : ''],
1934
1986
  queryFn: function () {
1935
- var _queryFn6 = _asyncToGenerator(/*#__PURE__*/_regenerator().m(function _callee6() {
1987
+ var _queryFn4 = _asyncToGenerator(/*#__PURE__*/_regenerator().m(function _callee4() {
1936
1988
  var resolvedInstanceId, instances, resolved, locationsExplicitlyUnsupported, _locations, locations;
1937
- return _regenerator().w(function (_context6) {
1938
- while (1) switch (_context6.p = _context6.n) {
1989
+ return _regenerator().w(function (_context4) {
1990
+ while (1) switch (_context4.p = _context4.n) {
1939
1991
  case 0:
1940
1992
  resolvedInstanceId = collection.instanceId;
1941
1993
  if (!(resolvedInstanceId && referenceTimeUtils.isRelativeReferenceTime(resolvedInstanceId))) {
1942
- _context6.n = 4;
1994
+ _context4.n = 4;
1943
1995
  break;
1944
1996
  }
1945
- _context6.p = 1;
1946
- _context6.n = 2;
1997
+ _context4.p = 1;
1998
+ _context4.n = 2;
1947
1999
  return queryClient.fetchQuery(soundingsQueryOptions.getCollectionInstances(collection.id, collection.url));
1948
2000
  case 2:
1949
- instances = _context6.v;
2001
+ instances = _context4.v;
1950
2002
  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
2003
  locationsExplicitlyUnsupported = (resolved == null ? void 0 : resolved.data_queries) !== undefined && !resolved.data_queries.locations;
1952
2004
  resolvedInstanceId = locationsExplicitlyUnsupported ? undefined : resolved == null ? void 0 : resolved.id;
1953
- _context6.n = 4;
2005
+ _context4.n = 4;
1954
2006
  break;
1955
2007
  case 3:
1956
- _context6.p = 3;
1957
- _context6.v;
2008
+ _context4.p = 3;
2009
+ _context4.v;
1958
2010
  resolvedInstanceId = undefined;
1959
2011
  case 4:
1960
2012
  if (!resolvedInstanceId) {
1961
- _context6.n = 8;
2013
+ _context4.n = 8;
1962
2014
  break;
1963
2015
  }
1964
- _context6.p = 5;
1965
- _context6.n = 6;
2016
+ _context4.p = 5;
2017
+ _context4.n = 6;
1966
2018
  return getInstanceLocations(collection.id, resolvedInstanceId, collection.url);
1967
2019
  case 6:
1968
- _locations = _context6.v;
1969
- return _context6.a(2, Object.assign({}, _locations, {
2020
+ _locations = _context4.v;
2021
+ return _context4.a(2, Object.assign({}, _locations, {
1970
2022
  id: collection.id,
1971
2023
  name: collection.name
1972
2024
  }));
1973
2025
  case 7:
1974
- _context6.p = 7;
1975
- _context6.v;
2026
+ _context4.p = 7;
2027
+ _context4.v;
1976
2028
  case 8:
1977
- _context6.n = 9;
2029
+ _context4.n = 9;
1978
2030
  return getCollectionLocations(collection.id, collection.url);
1979
2031
  case 9:
1980
- locations = _context6.v;
1981
- return _context6.a(2, Object.assign({}, locations, {
2032
+ locations = _context4.v;
2033
+ return _context4.a(2, Object.assign({}, locations, {
1982
2034
  id: collection.id,
1983
2035
  name: collection.name
1984
2036
  }));
1985
2037
  }
1986
- }, _callee6, null, [[5, 7], [1, 3]]);
2038
+ }, _callee4, null, [[5, 7], [1, 3]]);
1987
2039
  }));
1988
2040
  function queryFn() {
1989
- return _queryFn6.apply(this, arguments);
2041
+ return _queryFn4.apply(this, arguments);
1990
2042
  }
1991
2043
  return queryFn;
1992
2044
  }(),
@@ -2027,6 +2079,7 @@ var useGetLocations = function useGetLocations(collections, enabled) {
2027
2079
  var useCheckPositionQuerySupportBySource = function useCheckPositionQuerySupportBySource(sources) {
2028
2080
  // Ref to stabilize data object reference when values haven't changed
2029
2081
  var dataRef = React.useRef({});
2082
+ var queryClient = useQueryClient();
2030
2083
  var queries = useMemo(function () {
2031
2084
  return sources.map(function (_ref6) {
2032
2085
  var collectionId = _ref6.collectionId,
@@ -2035,55 +2088,61 @@ var useCheckPositionQuerySupportBySource = function useCheckPositionQuerySupport
2035
2088
  return {
2036
2089
  queryKey: ['edr-position-support', collectionId, instanceId || ''],
2037
2090
  queryFn: function () {
2038
- var _queryFn7 = _asyncToGenerator(/*#__PURE__*/_regenerator().m(function _callee7() {
2091
+ var _queryFn5 = _asyncToGenerator(/*#__PURE__*/_regenerator().m(function _callee5() {
2039
2092
  var _details$data_queries, _instance$data_querie, instances, instance, details;
2040
- return _regenerator().w(function (_context7) {
2041
- while (1) switch (_context7.p = _context7.n) {
2093
+ return _regenerator().w(function (_context5) {
2094
+ while (1) switch (_context5.p = _context5.n) {
2042
2095
  case 0:
2043
- _context7.p = 0;
2096
+ _context5.p = 0;
2044
2097
  if (!instanceId) {
2045
- _context7.n = 2;
2098
+ _context5.n = 2;
2046
2099
  break;
2047
2100
  }
2048
- _context7.n = 1;
2049
- return getCollectionInstances(collectionId, url);
2101
+ _context5.n = 1;
2102
+ return queryClient.fetchQuery(soundingsQueryOptions.getCollectionInstances(collectionId, url));
2050
2103
  case 1:
2051
- instances = _context7.v;
2104
+ instances = _context5.v;
2052
2105
  instance = instances.instances.find(function (i) {
2053
2106
  return i.id === instanceId;
2054
2107
  });
2055
- return _context7.a(2, {
2108
+ return _context5.a(2, {
2056
2109
  collectionId: collectionId,
2057
2110
  supported: !!(instance != null && (_instance$data_querie = instance.data_queries) != null && _instance$data_querie.position)
2058
2111
  });
2059
2112
  case 2:
2060
- _context7.n = 3;
2061
- return getCollectionDetails(collectionId, url);
2113
+ _context5.n = 3;
2114
+ return queryClient.fetchQuery(soundingsQueryOptions.getCollectionDetails(collectionId, url));
2062
2115
  case 3:
2063
- details = _context7.v;
2064
- return _context7.a(2, {
2116
+ details = _context5.v;
2117
+ return _context5.a(2, {
2065
2118
  collectionId: collectionId,
2066
2119
  supported: !!(details != null && (_details$data_queries = details.data_queries) != null && _details$data_queries.position)
2067
2120
  });
2068
2121
  case 4:
2069
- _context7.p = 4;
2070
- _context7.v;
2071
- return _context7.a(2, {
2122
+ _context5.p = 4;
2123
+ _context5.v;
2124
+ return _context5.a(2, {
2072
2125
  collectionId: collectionId,
2073
2126
  supported: false
2074
2127
  });
2075
2128
  }
2076
- }, _callee7, null, [[0, 4]]);
2129
+ }, _callee5, null, [[0, 4]]);
2077
2130
  }));
2078
2131
  function queryFn() {
2079
- return _queryFn7.apply(this, arguments);
2132
+ return _queryFn5.apply(this, arguments);
2080
2133
  }
2081
2134
  return queryFn;
2082
2135
  }(),
2083
- staleTime: 5 * 60 * 1000 // Cache for 5 minutes
2136
+ enabled: !referenceTimeUtils.isRelativeReferenceTime(instanceId),
2137
+ staleTime: 5 * 60 * 1000,
2138
+ // Cache for 5 minutes
2139
+ // Preserve previous result while a new instance check is in-flight to avoid spurious location updates.
2140
+ placeholderData: function placeholderData(prev) {
2141
+ return prev;
2142
+ }
2084
2143
  };
2085
2144
  });
2086
- }, [sources]);
2145
+ }, [sources, queryClient]);
2087
2146
  return useQueries({
2088
2147
  queries: queries,
2089
2148
  combine: function combine(queryResults) {
@@ -2117,6 +2176,7 @@ var useCheckPositionQuerySupportBySource = function useCheckPositionQuerySupport
2117
2176
  var useCheckRadiusQuerySupportBySource = function useCheckRadiusQuerySupportBySource(sources) {
2118
2177
  // Ref to stabilize data object reference when values haven't changed
2119
2178
  var dataRef = React.useRef({});
2179
+ var queryClient = useQueryClient();
2120
2180
  var queries = useMemo(function () {
2121
2181
  return sources.map(function (_ref7) {
2122
2182
  var collectionId = _ref7.collectionId,
@@ -2125,55 +2185,60 @@ var useCheckRadiusQuerySupportBySource = function useCheckRadiusQuerySupportBySo
2125
2185
  return {
2126
2186
  queryKey: ['edr-radius-support', collectionId, instanceId || ''],
2127
2187
  queryFn: function () {
2128
- var _queryFn8 = _asyncToGenerator(/*#__PURE__*/_regenerator().m(function _callee8() {
2188
+ var _queryFn6 = _asyncToGenerator(/*#__PURE__*/_regenerator().m(function _callee6() {
2129
2189
  var _details$data_queries2, _instance$data_querie2, instances, instance, details;
2130
- return _regenerator().w(function (_context8) {
2131
- while (1) switch (_context8.p = _context8.n) {
2190
+ return _regenerator().w(function (_context6) {
2191
+ while (1) switch (_context6.p = _context6.n) {
2132
2192
  case 0:
2133
- _context8.p = 0;
2193
+ _context6.p = 0;
2134
2194
  if (!instanceId) {
2135
- _context8.n = 2;
2195
+ _context6.n = 2;
2136
2196
  break;
2137
2197
  }
2138
- _context8.n = 1;
2139
- return getCollectionInstances(collectionId, url);
2198
+ _context6.n = 1;
2199
+ return queryClient.fetchQuery(soundingsQueryOptions.getCollectionInstances(collectionId, url));
2140
2200
  case 1:
2141
- instances = _context8.v;
2201
+ instances = _context6.v;
2142
2202
  instance = instances.instances.find(function (i) {
2143
2203
  return i.id === instanceId;
2144
2204
  });
2145
- return _context8.a(2, {
2205
+ return _context6.a(2, {
2146
2206
  collectionId: collectionId,
2147
2207
  supported: !!(instance != null && (_instance$data_querie2 = instance.data_queries) != null && _instance$data_querie2.radius)
2148
2208
  });
2149
2209
  case 2:
2150
- _context8.n = 3;
2151
- return getCollectionDetails(collectionId, url);
2210
+ _context6.n = 3;
2211
+ return queryClient.fetchQuery(soundingsQueryOptions.getCollectionDetails(collectionId, url));
2152
2212
  case 3:
2153
- details = _context8.v;
2154
- return _context8.a(2, {
2213
+ details = _context6.v;
2214
+ return _context6.a(2, {
2155
2215
  collectionId: collectionId,
2156
2216
  supported: !!(details != null && (_details$data_queries2 = details.data_queries) != null && _details$data_queries2.radius)
2157
2217
  });
2158
2218
  case 4:
2159
- _context8.p = 4;
2160
- _context8.v;
2161
- return _context8.a(2, {
2219
+ _context6.p = 4;
2220
+ _context6.v;
2221
+ return _context6.a(2, {
2162
2222
  collectionId: collectionId,
2163
2223
  supported: false
2164
2224
  });
2165
2225
  }
2166
- }, _callee8, null, [[0, 4]]);
2226
+ }, _callee6, null, [[0, 4]]);
2167
2227
  }));
2168
2228
  function queryFn() {
2169
- return _queryFn8.apply(this, arguments);
2229
+ return _queryFn6.apply(this, arguments);
2170
2230
  }
2171
2231
  return queryFn;
2172
2232
  }(),
2173
- staleTime: 5 * 60 * 1000 // Cache for 5 minutes
2233
+ enabled: !referenceTimeUtils.isRelativeReferenceTime(instanceId),
2234
+ staleTime: 5 * 60 * 1000,
2235
+ // Cache for 5 minutes
2236
+ placeholderData: function placeholderData(prev) {
2237
+ return prev;
2238
+ }
2174
2239
  };
2175
2240
  });
2176
- }, [sources]);
2241
+ }, [sources, queryClient]);
2177
2242
  return useQueries({
2178
2243
  queries: queries,
2179
2244
  combine: function combine(queryResults) {
@@ -2223,20 +2288,20 @@ var useGridPointsFromRadius = function useGridPointsFromRadius(sources, radiusSu
2223
2288
  return {
2224
2289
  queryKey: ['edr-grid-points-disabled', collectionId],
2225
2290
  queryFn: function () {
2226
- var _queryFn9 = _asyncToGenerator(/*#__PURE__*/_regenerator().m(function _callee9() {
2227
- return _regenerator().w(function (_context9) {
2228
- while (1) switch (_context9.n) {
2291
+ var _queryFn7 = _asyncToGenerator(/*#__PURE__*/_regenerator().m(function _callee7() {
2292
+ return _regenerator().w(function (_context7) {
2293
+ while (1) switch (_context7.n) {
2229
2294
  case 0:
2230
- return _context9.a(2, {
2295
+ return _context7.a(2, {
2231
2296
  sourceId: collectionId,
2232
2297
  gridPoint: undefined,
2233
2298
  allGridPoints: []
2234
2299
  });
2235
2300
  }
2236
- }, _callee9);
2301
+ }, _callee7);
2237
2302
  }));
2238
2303
  function queryFn() {
2239
- return _queryFn9.apply(this, arguments);
2304
+ return _queryFn7.apply(this, arguments);
2240
2305
  }
2241
2306
  return queryFn;
2242
2307
  }(),
@@ -2261,22 +2326,22 @@ var useGridPointsFromRadius = function useGridPointsFromRadius(sources, radiusSu
2261
2326
  return {
2262
2327
  queryKey: ['edr-grid-points-radius', collectionId, instanceId || '', centerCoordinates.join(','), radiusKm.toString(), datetime],
2263
2328
  queryFn: function () {
2264
- var _queryFn0 = _asyncToGenerator(/*#__PURE__*/_regenerator().m(function _callee0() {
2329
+ var _queryFn8 = _asyncToGenerator(/*#__PURE__*/_regenerator().m(function _callee8() {
2265
2330
  var minimalParameters, minimalVerticalRange, rawData, allGridPoints, nearestPoint, minSquaredDistance;
2266
- return _regenerator().w(function (_context0) {
2267
- while (1) switch (_context0.p = _context0.n) {
2331
+ return _regenerator().w(function (_context8) {
2332
+ while (1) switch (_context8.p = _context8.n) {
2268
2333
  case 0:
2269
2334
  if (supportsRadius) {
2270
- _context0.n = 1;
2335
+ _context8.n = 1;
2271
2336
  break;
2272
2337
  }
2273
- return _context0.a(2, {
2338
+ return _context8.a(2, {
2274
2339
  sourceId: collectionId,
2275
2340
  gridPoint: undefined,
2276
2341
  allGridPoints: []
2277
2342
  });
2278
2343
  case 1:
2279
- _context0.p = 1;
2344
+ _context8.p = 1;
2280
2345
  // Optimization: Only request a single parameter to minimize data transfer
2281
2346
  // We only need the grid point coordinates, not the actual sounding data
2282
2347
  minimalParameters = parameters.slice(0, 1); // Just one parameter
@@ -2284,10 +2349,10 @@ var useGridPointsFromRadius = function useGridPointsFromRadius(sources, radiusSu
2284
2349
  // (Don't hardcode '1000' as that only works for pressure-level sources,
2285
2350
  // not for hybrid/model-level sources which use levels 1-65)
2286
2351
  minimalVerticalRange = '';
2287
- _context0.n = 2;
2352
+ _context8.n = 2;
2288
2353
  return getEdrCollectionForRadius(collectionId, url, instanceId, centerCoordinates, radiusKm, minimalParameters, datetime, minimalVerticalRange);
2289
2354
  case 2:
2290
- rawData = _context0.v;
2355
+ rawData = _context8.v;
2291
2356
  allGridPoints = extractGridPointsFromCoverage(rawData); // Find the nearest grid point to the center coordinates
2292
2357
  minSquaredDistance = Infinity;
2293
2358
  allGridPoints.forEach(function (point) {
@@ -2302,24 +2367,24 @@ var useGridPointsFromRadius = function useGridPointsFromRadius(sources, radiusSu
2302
2367
  nearestPoint = point;
2303
2368
  }
2304
2369
  });
2305
- return _context0.a(2, {
2370
+ return _context8.a(2, {
2306
2371
  sourceId: collectionId,
2307
2372
  gridPoint: nearestPoint,
2308
2373
  allGridPoints: allGridPoints
2309
2374
  });
2310
2375
  case 3:
2311
- _context0.p = 3;
2312
- _context0.v;
2313
- return _context0.a(2, {
2376
+ _context8.p = 3;
2377
+ _context8.v;
2378
+ return _context8.a(2, {
2314
2379
  sourceId: collectionId,
2315
2380
  gridPoint: undefined,
2316
2381
  allGridPoints: []
2317
2382
  });
2318
2383
  }
2319
- }, _callee0, null, [[1, 3]]);
2384
+ }, _callee8, null, [[1, 3]]);
2320
2385
  }));
2321
2386
  function queryFn() {
2322
- return _queryFn0.apply(this, arguments);
2387
+ return _queryFn8.apply(this, arguments);
2323
2388
  }
2324
2389
  return queryFn;
2325
2390
  }(),
@@ -2627,6 +2692,7 @@ var initialSoundingsViewState = {
2627
2692
  },
2628
2693
  shouldHideLocationMarkers: false,
2629
2694
  isTimeSliderVisible: true,
2695
+ isLegendVisible: true,
2630
2696
  profileDataPreferences: DEFAULT_PROFILE_DATA_PREFERENCES,
2631
2697
  syncGroupsIds: []
2632
2698
  };
@@ -2696,7 +2762,7 @@ var slice = createSlice({
2696
2762
  }
2697
2763
  var preset = payloadAction.payload.preset;
2698
2764
  if (preset) {
2699
- var _preset$selectedSourc, _preset$selectedLocat, _preset$selectedTime, _preset$diagramType, _preset$shouldHideLoc, _preset$isTimeSliderV, _preset$profileDataPr;
2765
+ var _preset$selectedSourc, _preset$selectedLocat, _preset$selectedTime, _preset$diagramType, _preset$shouldHideLoc, _preset$isTimeSliderV, _preset$isLegendVisib, _preset$profileDataPr;
2700
2766
  panel.selectedSources = (_preset$selectedSourc = preset.selectedSources) != null ? _preset$selectedSourc : {};
2701
2767
  panel.selectedLocations = (_preset$selectedLocat = preset.selectedLocations) != null ? _preset$selectedLocat : {};
2702
2768
  panel.selectedTime = (_preset$selectedTime = preset.selectedTime) != null ? _preset$selectedTime : new Date().toISOString();
@@ -2718,7 +2784,11 @@ var slice = createSlice({
2718
2784
  }
2719
2785
  panel.shouldHideLocationMarkers = (_preset$shouldHideLoc = preset.shouldHideLocationMarkers) != null ? _preset$shouldHideLoc : false;
2720
2786
  panel.isTimeSliderVisible = (_preset$isTimeSliderV = preset.isTimeSliderVisible) != null ? _preset$isTimeSliderV : true;
2787
+ panel.isLegendVisible = (_preset$isLegendVisib = preset.isLegendVisible) != null ? _preset$isLegendVisib : true;
2721
2788
  panel.profileDataPreferences = (_preset$profileDataPr = preset.profileDataPreferences) != null ? _preset$profileDataPr : DEFAULT_PROFILE_DATA_PREFERENCES;
2789
+ panel.locationMenuSize = preset.locationMenuSize;
2790
+ panel.locationMenuPosition = preset.locationMenuPosition;
2791
+ panel.isLocationsDialogOpen = preset.isLocationsDialogOpen;
2722
2792
  }
2723
2793
  },
2724
2794
  setSoundingsCollections: function setSoundingsCollections(draft, payloadAction) {
@@ -2776,6 +2846,13 @@ var slice = createSlice({
2776
2846
  }
2777
2847
  panel.isTimeSliderVisible = payloadAction.payload.isTimeSliderVisible;
2778
2848
  },
2849
+ toggleLegendVisibility: function toggleLegendVisibility(draft, payloadAction) {
2850
+ var panel = resolveSoundingsView(draft, payloadAction);
2851
+ if (!panel) {
2852
+ return;
2853
+ }
2854
+ panel.isLegendVisible = payloadAction.payload.isLegendVisible;
2855
+ },
2779
2856
  updateProfileDataPreferences: function updateProfileDataPreferences(draft, payloadAction) {
2780
2857
  var panel = resolveSoundingsView(draft, payloadAction);
2781
2858
  if (!panel) {
@@ -2842,6 +2919,23 @@ var slice = createSlice({
2842
2919
  customWidth: customWidth
2843
2920
  });
2844
2921
  },
2922
+ setLocationMenuSizeAndPosition: function setLocationMenuSizeAndPosition(draft, payloadAction) {
2923
+ var panel = resolveSoundingsView(draft, payloadAction);
2924
+ if (!panel) {
2925
+ return;
2926
+ }
2927
+ panel.locationMenuSize = payloadAction.payload.size;
2928
+ if (payloadAction.payload.position !== undefined) {
2929
+ panel.locationMenuPosition = payloadAction.payload.position;
2930
+ }
2931
+ },
2932
+ setLocationsMenuOpen: function setLocationsMenuOpen(draft, payloadAction) {
2933
+ var panel = resolveSoundingsView(draft, payloadAction);
2934
+ if (!panel) {
2935
+ return;
2936
+ }
2937
+ panel.isLocationsDialogOpen = payloadAction.payload.isLocationsDialogOpen;
2938
+ },
2845
2939
  notifyViewClosed: function notifyViewClosed(draft) {
2846
2940
  var _draft$resizeOnViewCl;
2847
2941
  draft.resizeOnViewCloseSignal = ((_draft$resizeOnViewCl = draft.resizeOnViewCloseSignal) != null ? _draft$resizeOnViewCl : 0) + 1;
@@ -2954,6 +3048,10 @@ var getIsTimeSliderVisible = createSelector(getSoundingsViewState, function (sto
2954
3048
  var _store$isTimeSliderVi;
2955
3049
  return (_store$isTimeSliderVi = store.isTimeSliderVisible) != null ? _store$isTimeSliderVi : true;
2956
3050
  }, selectorMemoizationOptions);
3051
+ var getIsLegendVisible = createSelector(getSoundingsViewState, function (store) {
3052
+ var _store$isLegendVisibl;
3053
+ return (_store$isLegendVisibl = store.isLegendVisible) != null ? _store$isLegendVisibl : true;
3054
+ }, selectorMemoizationOptions);
2957
3055
  var getProfileDataPreferences = createSelector(getSoundingsViewState, function (store) {
2958
3056
  var _store$profileDataPre;
2959
3057
  return (_store$profileDataPre = store.profileDataPreferences) != null ? _store$profileDataPre : DEFAULT_PROFILE_DATA_PREFERENCES;
@@ -2987,6 +3085,12 @@ var getSelectedSourcesIds = createSelector(getSelectedSources, function (activeS
2987
3085
  return source.collectionId;
2988
3086
  });
2989
3087
  }, selectorMemoizationOptions);
3088
+ var getLocationMenuSize = createSelector(getSoundingsViewState, function (store) {
3089
+ return store.locationMenuSize;
3090
+ }, selectorMemoizationOptions);
3091
+ var getLocationMenuPosition = createSelector(getSoundingsViewState, function (store) {
3092
+ return store.locationMenuPosition;
3093
+ }, selectorMemoizationOptions);
2990
3094
  var getSoundingsPreset = createSelector(getSoundingsViewState, function (soundingsStore) {
2991
3095
  return Object.assign({}, soundingsStore);
2992
3096
  }, selectorMemoizationOptions);
@@ -3008,7 +3112,10 @@ var getSoundingPanelsInOrder = createSelector(getSoundingsViewState, function (s
3008
3112
  var selectors = /*#__PURE__*/Object.freeze({
3009
3113
  __proto__: null,
3010
3114
  getDiagramType: getDiagramType,
3115
+ getIsLegendVisible: getIsLegendVisible,
3011
3116
  getIsTimeSliderVisible: getIsTimeSliderVisible,
3117
+ getLocationMenuPosition: getLocationMenuPosition,
3118
+ getLocationMenuSize: getLocationMenuSize,
3012
3119
  getLocationSelections: getLocationSelections,
3013
3120
  getPanelCustomWidth: getPanelCustomWidth,
3014
3121
  getPanelIsOpen: getPanelIsOpen,
@@ -3199,13 +3306,15 @@ var MenuOptions;
3199
3306
  MenuOptions["profileData"] = "profileData";
3200
3307
  })(MenuOptions || (MenuOptions = {}));
3201
3308
  var SoundingsMenuBar = function SoundingsMenuBar(_ref) {
3202
- var _sx;
3309
+ var _sx, _sx2;
3203
3310
  var handleClick = _ref.handleClick,
3204
3311
  isOpen = _ref.isOpen,
3205
3312
  _ref$selectedSourcesA = _ref.selectedSourcesAmount,
3206
3313
  selectedSourcesAmount = _ref$selectedSourcesA === void 0 ? 0 : _ref$selectedSourcesA,
3207
3314
  shouldHideMarkers = _ref.shouldHideMarkers,
3208
- toggleMarkers = _ref.toggleMarkers;
3315
+ toggleMarkers = _ref.toggleMarkers,
3316
+ isLegendVisible = _ref.isLegendVisible,
3317
+ toggleLegend = _ref.toggleLegend;
3209
3318
  var _useSoundingsTranslat = useSoundingsTranslation(),
3210
3319
  t = _useSoundingsTranslat.t;
3211
3320
  return jsxs(Stack, {
@@ -3260,6 +3369,23 @@ var SoundingsMenuBar = function SoundingsMenuBar(_ref) {
3260
3369
  }), jsx(SoundingsMarkersSwitch, {
3261
3370
  shouldHideMarkers: shouldHideMarkers,
3262
3371
  toggleMarkers: toggleMarkers
3372
+ }), jsx(LegendToggleButton, {
3373
+ hideTooltipText: t('soundings-legend-tooltip-hide'),
3374
+ isLegendVisible: isLegendVisible,
3375
+ legendLabel: t('soundings-legend'),
3376
+ onToggle: toggleLegend,
3377
+ showTooltipText: t('soundings-legend-tooltip-show'),
3378
+ sx: (_sx2 = {}, _sx2["@container " + SOUNDINGS_CONTAINER_NAME + " (width < " + SOUNDINGS_CONTAINER_BREAKPOINT_PX + ")"] = {
3379
+ maxWidth: '90px',
3380
+ '& .longText': {
3381
+ display: 'none'
3382
+ },
3383
+ '& svg': {
3384
+ marginRight: '0 !important'
3385
+ }
3386
+ }, _sx2["@container " + SOUNDINGS_CONTAINER_NAME + " (min-width: " + SOUNDINGS_CONTAINER_BREAKPOINT_PX + ")"] = {
3387
+ width: '130px'
3388
+ }, _sx2)
3263
3389
  })]
3264
3390
  });
3265
3391
  };
@@ -3304,6 +3430,16 @@ var MenuBarConnect = function MenuBarConnect(_ref) {
3304
3430
  var shouldHideLocationMarkers = useSelector(function (state) {
3305
3431
  return getShouldHideLocationMarkers(state, viewId);
3306
3432
  });
3433
+ var isLegendVisible = useSelector(function (state) {
3434
+ return getIsLegendVisible(state, viewId);
3435
+ });
3436
+ var toggleLegend = React.useCallback(function () {
3437
+ dispatch(soundingsActions.toggleLegendVisibility({
3438
+ isLegendVisible: !isLegendVisible,
3439
+ viewId: viewId,
3440
+ origin: SoundingsActionOrigin.soundingsComponent
3441
+ }));
3442
+ }, [dispatch, isLegendVisible, viewId]);
3307
3443
  var toggleLocationMarkers = React.useCallback(function (shouldHide) {
3308
3444
  dispatch(soundingsActions.toggleLocationMarkers({
3309
3445
  shouldHideLocationMarkers: shouldHide,
@@ -3328,19 +3464,29 @@ var MenuBarConnect = function MenuBarConnect(_ref) {
3328
3464
  dispatchActiveMapId(uiTypes.DialogTypes.SoundingsSources, isSourcesOpenInStore, currentActiveMapIdSources);
3329
3465
  break;
3330
3466
  case MenuOptions.locations:
3331
- dispatchActiveMapId(uiTypes.DialogTypes.SoundingsLocations, isLocationsOpenInStore, currentActiveMapIdLocations);
3332
- break;
3467
+ {
3468
+ var newIsLocationsOpen = currentActiveMapIdLocations !== viewId ? true : !isLocationsOpenInStore;
3469
+ dispatchActiveMapId(uiTypes.DialogTypes.SoundingsLocations, isLocationsOpenInStore, currentActiveMapIdLocations);
3470
+ dispatch(soundingsActions.setLocationsMenuOpen({
3471
+ viewId: viewId,
3472
+ isLocationsDialogOpen: newIsLocationsOpen,
3473
+ origin: SoundingsActionOrigin.soundingsComponent
3474
+ }));
3475
+ break;
3476
+ }
3333
3477
  case MenuOptions.profileData:
3334
3478
  dispatchActiveMapId(uiTypes.DialogTypes.SoundingsProfileData, isProfileDataOpenInStore, currentActiveMapIdProfileData);
3335
3479
  break;
3336
3480
  }
3337
- }, [dispatchActiveMapId, isSoundingsManagerOpenInStore, currentActiveMapIdsoundingsManager, isSourcesOpenInStore, currentActiveMapIdSources, isLocationsOpenInStore, currentActiveMapIdLocations, isProfileDataOpenInStore, currentActiveMapIdProfileData]);
3481
+ }, [dispatch, dispatchActiveMapId, viewId, isSoundingsManagerOpenInStore, currentActiveMapIdsoundingsManager, isSourcesOpenInStore, currentActiveMapIdSources, isLocationsOpenInStore, currentActiveMapIdLocations, isProfileDataOpenInStore, currentActiveMapIdProfileData]);
3338
3482
  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
3483
  return jsx(SoundingsMenuBar, {
3340
3484
  handleClick: handleClick,
3485
+ isLegendVisible: isLegendVisible,
3341
3486
  isOpen: isOpen,
3342
3487
  selectedSourcesAmount: sourcesAmount,
3343
3488
  shouldHideMarkers: shouldHideLocationMarkers,
3489
+ toggleLegend: toggleLegend,
3344
3490
  toggleMarkers: toggleLocationMarkers
3345
3491
  });
3346
3492
  };
@@ -3956,7 +4102,7 @@ var Hodograph = function Hodograph(_ref) {
3956
4102
  });
3957
4103
  });
3958
4104
  // 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) {
4105
+ var zoomBehavior = d3.zoom().scaleExtent([1, 7]).translateExtent([[0, 0], [widthHeight, widthHeight]]).on('zoom', function (event) {
3960
4106
  svg.select('g.hodograph').attr('transform', event.transform);
3961
4107
  });
3962
4108
  svg.call(zoomBehavior);
@@ -4310,23 +4456,21 @@ var extractPressureFromY = function extractPressureFromY(diagramType, y, yScale)
4310
4456
  };
4311
4457
  var drawSoundingsTooltip = function drawSoundingsTooltip(_ref) {
4312
4458
  var event = _ref.event,
4313
- yScale = _ref.yScale,
4314
4459
  svgRectangle = _ref.svgRectangle,
4460
+ hoveredPressure = _ref.hoveredPressure,
4315
4461
  pressureRefd3 = _ref.pressureRefd3,
4316
4462
  tempRefd3 = _ref.tempRefd3,
4317
4463
  dewpointRefd3 = _ref.dewpointRefd3,
4318
4464
  windRefd3 = _ref.windRefd3,
4319
4465
  margin = _ref.margin,
4320
4466
  width = _ref.width,
4321
- tooltipData = _ref.tooltipData,
4322
- diagramType = _ref.diagramType;
4467
+ tooltipData = _ref.tooltipData;
4468
+ _ref.diagramType;
4323
4469
  if (tooltipData.length === 0) {
4324
4470
  return;
4325
4471
  }
4326
- var _d3$pointer = d3.pointer(event),
4327
- y = _d3$pointer[1];
4328
4472
  var pressureY = event.clientY - svgRectangle.top - 10;
4329
- var pressure = extractPressureFromY(diagramType, y, yScale);
4473
+ var pressure = hoveredPressure;
4330
4474
  var interpolatedData = tooltipData.map(function (dataSet) {
4331
4475
  return {
4332
4476
  hoveredPressure: dataSet.interpolatePressure(pressure),
@@ -4413,6 +4557,7 @@ var drawDryAdiabatsBijvoet = function drawDryAdiabatsBijvoet(_ref2) {
4413
4557
  yScale = _ref2.yScale,
4414
4558
  svg = _ref2.svg,
4415
4559
  d3GraphId = _ref2.d3GraphId;
4560
+ var lineGenerator = d3.line();
4416
4561
  for (var Tbase = -20; Tbase < 40; Tbase += 5) {
4417
4562
  var Px = [];
4418
4563
  var tempKelvin = tempToKelvin(Tbase);
@@ -4426,7 +4571,6 @@ var drawDryAdiabatsBijvoet = function drawDryAdiabatsBijvoet(_ref2) {
4426
4571
  var Tpx = xScale(theta);
4427
4572
  Px.push([Tpx, Ppx]);
4428
4573
  }
4429
- var lineGenerator = d3.line();
4430
4574
  var daPathString = lineGenerator(Px);
4431
4575
  var strID = Tbase + "-dryadiabat";
4432
4576
  svg.append('path').attr('id', strID).attr('d', daPathString).style('fill', 'none').style('stroke-width', 1).style('stroke', 'green').style('opacity', 0.3).attr('clip-path', "url(#clip-" + d3GraphId + "-BIJVOETd3)");
@@ -4525,8 +4669,10 @@ var drawIsothermsBijvoet = function drawIsothermsBijvoet(_ref2) {
4525
4669
  xScale = _ref2.xScale,
4526
4670
  yScale = _ref2.yScale,
4527
4671
  svg = _ref2.svg,
4672
+ labelSvg = _ref2.labelSvg,
4528
4673
  height = _ref2.height,
4529
4674
  d3GraphId = _ref2.d3GraphId;
4675
+ var lineGenerator = d3.line();
4530
4676
  // lines of constant temperature (2 degree interval from -150...+40C)
4531
4677
  for (var temp = -150; temp <= 40; temp += 2) {
4532
4678
  var _points$;
@@ -4537,12 +4683,11 @@ var drawIsothermsBijvoet = function drawIsothermsBijvoet(_ref2) {
4537
4683
  var theta = calculateThetaFromPressTemp(pressToPascal(pressure), Tiso, REFERENCE_PRESSURE);
4538
4684
  points.push([xScale(theta), yScale(pressure)]);
4539
4685
  }
4540
- var lineGenerator = d3.line();
4541
4686
  var pathString = lineGenerator(points);
4542
4687
  var strID = temp + "-isotherm-BIJVOET";
4543
4688
  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
4689
  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');
4690
+ (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
4691
  }
4547
4692
  }
4548
4693
  };
@@ -4685,6 +4830,7 @@ var drawStaticLines = function drawStaticLines(_ref) {
4685
4830
  minPressure = _ref.minPressure,
4686
4831
  maxPressure = _ref.maxPressure,
4687
4832
  svg = _ref.svg,
4833
+ bijvoetLabelSvg = _ref.bijvoetLabelSvg,
4688
4834
  d3GraphId = _ref.d3GraphId;
4689
4835
  drawIsobars({
4690
4836
  diagramType: diagramType,
@@ -4708,6 +4854,7 @@ var drawStaticLines = function drawStaticLines(_ref) {
4708
4854
  xScale: xScale,
4709
4855
  yScale: yScale,
4710
4856
  svg: svg,
4857
+ labelSvg: bijvoetLabelSvg,
4711
4858
  height: height,
4712
4859
  d3GraphId: d3GraphId
4713
4860
  });
@@ -4773,105 +4920,503 @@ var drawStaticLines = function drawStaticLines(_ref) {
4773
4920
  }
4774
4921
  };
4775
4922
 
4776
- var shadow$1 = '0 1px 5px 0 rgba(0, 0, 0, 0.2), 0 3px 4px 0 rgba(0, 0, 0, 0.12), 0 2px 4px 0 rgba(0, 0, 0, 0.14)';
4777
- var soundingsStyle$1 = {
4778
- tooltip: {
4779
- display: 'block',
4780
- position: 'absolute',
4781
- borderRadius: '1.5px',
4782
- backgroundColor: 'geowebColors.soundings.tooltip.background',
4783
- boxShadow: shadow$1,
4784
- padding: '2px 4px',
4785
- pointerEvents: 'none',
4786
- zIndex: 500
4787
- },
4788
- pressure: {
4789
- fontSize: '10px'
4790
- },
4791
- temperature: {
4792
- fontSize: '12px',
4793
- fontWeight: 'bold'
4794
- },
4795
- dewPoint: {
4796
- fontSize: '12px',
4797
- fontWeight: 'bold'
4798
- }};
4799
- var Soundings = function Soundings(_ref) {
4800
- var d3GraphId = _ref.d3GraphId,
4801
- data = _ref.data,
4802
- svgDimensions = _ref.svgDimensions,
4803
- _ref$showTooltip = _ref.showTooltip,
4804
- showTooltip = _ref$showTooltip === void 0 ? true : _ref$showTooltip,
4805
- _ref$diagramType = _ref.diagramType,
4806
- diagramType = _ref$diagramType === void 0 ? 'SKEWT' : _ref$diagramType;
4807
- var instanceId = useId().replace(/:/g, '_');
4808
- var scopedGraphId = d3GraphId + "-" + instanceId;
4809
- var svgRef = useRef(null);
4810
- var canvasRef = useRef(null);
4811
- var pressureRef = useRef(null);
4812
- var tempRef = useRef(null);
4813
- var dewpointRef = useRef(null);
4814
- // Refs to store current scale/data for shared mouse effect
4815
- var yScaleRef = useRef(null);
4816
- var marginRef = useRef(null);
4817
- var widthRef = useRef(0);
4818
- var svgRectRef = useRef(null);
4819
- var tooltipDataRef = useRef(null);
4820
- var _useSharedMouseY = useSharedMouseY(),
4821
- clientY = _useSharedMouseY.clientY;
4822
- // Set SVG dimensions (required props; no optional chaining needed)
4823
- var baseWidth = svgDimensions.width,
4824
- baseHeight = svgDimensions.height,
4825
- margin = svgDimensions.margin;
4826
- var width = baseWidth - (margin.left + margin.right);
4827
- var height = baseHeight - (margin.top + margin.bottom);
4828
- useEffect(function () {
4829
- if (!svgRef.current) {
4830
- return;
4831
- }
4832
- var svg = initializeSvg$1({
4833
- svgRef: svgRef,
4834
- width: width,
4835
- height: height,
4836
- margin: margin,
4837
- diagramType: diagramType,
4838
- d3GraphId: scopedGraphId
4839
- });
4840
- var svgRectangle = svgRef.current.getBoundingClientRect();
4841
- svgRectRef.current = svgRectangle;
4842
- marginRef.current = margin;
4843
- widthRef.current = width;
4844
- var minT = diagramType === 'EMAGRAM' ? minEmagramTemperature : minTemperature;
4845
- var maxT = maxTemperature;
4846
- var _createScales = createScales$1(diagramType, width, height, minPressure, maxPressure, minT, maxT),
4847
- xScale = _createScales[0],
4848
- yScale = _createScales[1];
4849
- yScaleRef.current = yScale;
4850
- drawAxes$1({
4851
- svg: svg,
4852
- xScale: xScale,
4853
- yScale: yScale,
4854
- height: height,
4855
- diagramType: diagramType
4856
- });
4857
- drawStaticLines({
4858
- diagramType: diagramType,
4859
- dp: dp,
4860
- staticPressureArray: staticPressureArray,
4861
- minTemperature: minT,
4862
- maxTemperature: maxT,
4863
- height: height,
4864
- width: width,
4865
- xScale: xScale,
4866
- yScale: yScale,
4867
- minPressure: minPressure,
4868
- maxPressure: maxPressure,
4869
- svg: svg,
4870
- d3GraphId: scopedGraphId
4871
- });
4872
- data.forEach(function (features, index) {
4873
- var color = SOUNDING_COLORS[index] || SOUNDING_COLORS[0];
4874
- var temperatureValues = features.map(function (feature) {
4923
+ /* *
4924
+ * Licensed under the Apache License, Version 2.0 (the "License");
4925
+ * you may not use this file except in compliance with the License.
4926
+ * You may obtain a copy of the License at
4927
+ *
4928
+ * http://www.apache.org/licenses/LICENSE-2.0
4929
+ *
4930
+ * Unless required by applicable law or agreed to in writing, software
4931
+ * distributed under the License is distributed on an "AS IS" BASIS,
4932
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
4933
+ * See the License for the specific language governing permissions and
4934
+ * limitations under the License.
4935
+ *
4936
+ * Copyright 2025 - Koninklijk Nederlands Meteorologisch Instituut (KNMI)
4937
+ * Copyright 2025 - Finnish Meteorological Institute (FMI)
4938
+ * Copyright 2025 - The Norwegian Meteorological Institute (MET Norway)
4939
+ * */
4940
+ var windSymbolDefinitions = {
4941
+ calm: "<svg width=\"50\" height=\"50\" viewBox=\"0 0 50 50\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\">\n<path d=\"M25 45C36.0456 45 45 36.0456 45 25C45 13.9543 36.0456 5 25 5C13.9543 5 5 13.9543 5 25C5 36.0456 13.9543 45 25 45Z\" stroke=\"black\" stroke-width=\"1.8\"/>\n</svg>\n",
4942
+ lightAir: "<svg width=\"14\" height=\"48\" viewBox=\"0 0 14 48\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\">\n<path d=\"M7.22656 4.89307V48.0001M7.22656 43.0262V44.6842\" stroke=\"black\" stroke-width=\"1.8\"/>\n</svg>\n",
4943
+ fiveKnots: "<svg width=\"15\" height=\"49\" viewBox=\"0 0 15 49\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\">\n<path d=\"M8.26953 5.57324V48.608M8.26953 40.5757L0.269531 42.2309\" stroke=\"black\" stroke-width=\"1.8\"/>\n</svg>",
4944
+ tenKnots: "<svg width=\"24\" height=\"49\" viewBox=\"0 0 24 49\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\">\n<path d=\"M17.0781 4.64502V44.9031L1.07812 47.9999\" stroke=\"black\" stroke-width=\"1.8\"/>\n</svg>",
4945
+ fifteenKnots: "<svg width=\"23\" height=\"50\" viewBox=\"0 0 23 50\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\">\n<path d=\"M16.4727 5.55469V45.8128L0.472656 48.9095M16.4727 38.0708L9.61551 39.6192\" stroke=\"black\" stroke-width=\"1.8\"/>\n</svg>\n",
4946
+ twentyKnots: "<svg width=\"24\" height=\"49\" viewBox=\"0 0 24 49\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\">\n<path d=\"M16.8555 4.71924V44.9773L0.855469 48.0741M16.8555 38.4093L0.855469 41.5061\" stroke=\"black\" stroke-width=\"1.8\"/>\n</svg>",
4947
+ twentyFiveKnots: "<svg width=\"23\" height=\"51\" viewBox=\"0 0 23 51\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\">\n<path d=\"M16.2383 4.94434V46.1541L0.238281 49.3241M16.2383 40.3787L0.238281 43.5486M16.2383 33.9801L9.38114 35.5651\" stroke=\"black\" stroke-width=\"1.8\"/>\n</svg>",
4948
+ thirtyKnots: "<svg width=\"23\" height=\"50\" viewBox=\"0 0 23 50\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\">\n<path d=\"M16.1914 5.64502V45.9031L0.191406 48.9999M16.1914 40.3459L0.191406 43.4427M16.1914 34.9407L0.191406 38.0375\" stroke=\"black\" stroke-width=\"1.8\"/>\n</svg>",
4949
+ thirtyFiveKnots: "<svg width=\"19\" height=\"50\" viewBox=\"0 0 19 50\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\">\n<path d=\"M12.0028 4.96729V45.2253L1.16406 48.3221M12.0028 39.6443L1.16406 42.7411M12.0028 34.4793L1.16406 37.576M12.0028 29.2735L7.35761 30.8219\" stroke=\"black\" stroke-width=\"1.8\"/>\n</svg>",
4950
+ fortyKnots: "<svg width=\"24\" height=\"50\" viewBox=\"0 0 24 50\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\">\n<path d=\"M16.5391 5.40311V45.6612L0.539062 48.7579M16.5391 40.4525L0.539062 43.5493M16.5391 35.0449L0.539062 38.1417M16.5391 30.2009L0.539062 33.2976\" stroke=\"black\" stroke-width=\"1.8\"/>\n</svg>",
4951
+ fortyFiveKnots: "<svg width=\"23\" height=\"50\" viewBox=\"0 0 23 50\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\">\n<path d=\"M16.4492 5.4043V45.6624L0.449219 48.7591M16.4492 40.1513L0.449219 43.2481M16.4492 34.7124L0.449219 37.8091M16.4492 29.2013L0.449219 32.2981M16.4492 24.3066L9.59208 25.855\" stroke=\"black\" stroke-width=\"1.8\"/>\n</svg>",
4952
+ fiftyKnots: "<svg width=\"24\" height=\"49\" viewBox=\"0 0 24 49\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\">\n<path d=\"M16.9062 5.27002V45.5281L0.90625 48.6249L16.9062 37.7861\" fill=\"black\"/>\n<path d=\"M16.9053 5.27002V45.5281L4.35132 47.2337L16.9053 37.7861\" stroke=\"black\" stroke-width=\"1.8\"/>\n</svg>\n",
4953
+ fiftyFiveKnots: "<svg width=\"24\" height=\"49\" viewBox=\"0 0 24 49\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\">\n<path d=\"M16.728 5.55371V45.8118L0.894531 48.9085L16.728 38.0698\" fill=\"black\"/>\n<path d=\"M16.73 5.55371V45.8118L4.33984 47.5245L16.73 38.0698\" stroke=\"black\" stroke-width=\"1.8\"/>\n<path d=\"M16.7292 32.7358C13.6376 33.3406 8.8125 34.2842 8.8125 34.2842\" stroke=\"black\" stroke-width=\"1.8\" stroke-miterlimit=\"16\"/>\n</svg>",
4954
+ sixtyKnots: "<svg width=\"23\" height=\"49\" viewBox=\"0 0 23 49\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\">\n<path d=\"M16.2891 5.27051V45.5286L0.289062 48.6253L16.2891 37.7866\" fill=\"black\"/>\n<path d=\"M16.2897 5.27051V45.5286L3.7422 47.2478L16.2897 37.7866\" stroke=\"black\" stroke-width=\"1.8\"/>\n<path d=\"M16.2891 34.6899L0.289062 37.7867\" stroke=\"black\" stroke-width=\"1.8\"/>\n</svg>",
4955
+ sixtyFiveKnots: "<svg width=\"24\" height=\"47\" viewBox=\"0 0 24 47\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\">\n<path d=\"M17 3.26807V43.5261L1 46.6229L17 35.7842\" fill=\"black\"/>\n<path d=\"M16.9984 3.26807V43.5261L4.35938 45.2985L16.9984 35.7842\" stroke=\"black\" stroke-width=\"1.8\"/>\n<path d=\"M17 32.0788L1 35.1756M17 26.8862L9.5 28.5\" stroke=\"black\" stroke-width=\"1.8\"/>\n</svg>",
4956
+ seventyKnots: "<svg width=\"24\" height=\"47\" viewBox=\"0 0 24 47\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\">\n<path d=\"M17 3.26807V43.5261L1 46.6229L17 35.7842\" fill=\"black\"/>\n<path d=\"M16.9984 3.26807V43.5261L4.35938 45.2985L16.9984 35.7842\" stroke=\"black\" stroke-width=\"1.8\"/>\n<path d=\"M17 32.0788L1 35.1756M17 26.8862L1 29.983\" stroke=\"black\" stroke-width=\"1.8\"/>\n</svg>",
4957
+ seventyFiveKnots: "<svg width=\"24\" height=\"47\" viewBox=\"0 0 24 47\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\">\n<path d=\"M17 3.26807V43.5261L1 46.6229L17 35.7842\" fill=\"black\"/>\n<path d=\"M16.9984 3.26807V43.5261L4.35938 45.2985L16.9984 35.7842\" stroke=\"black\" stroke-width=\"1.8\"/>\n<path d=\"M17 32.0788L1 35.1756M17 26.8862L1 29.983\" stroke=\"black\" stroke-width=\"1.8\"/>\n<path d=\"M16.9167 22C13.8251 22.6048 9 23.5484 9 23.5484\" stroke=\"black\" stroke-width=\"1.8\" stroke-miterlimit=\"16\"/>\n</svg>",
4958
+ eightyKnots: "<svg width=\"24\" height=\"47\" viewBox=\"0 0 24 47\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\">\n<path d=\"M17 3.26807V43.5261L1 46.6229L17 35.7842\" fill=\"black\"/>\n<path d=\"M16.9984 3.26807V43.5261L4.35938 45.2985L16.9984 35.7842\" stroke=\"black\" stroke-width=\"1.8\"/>\n<path d=\"M17 32.0788L1 35.1756M17 26.8862L1 29.983\" stroke=\"black\" stroke-width=\"1.8\"/>\n<path d=\"M17 22C10.7518 23.1718 1 25 1 25\" stroke=\"black\" stroke-width=\"1.8\" stroke-miterlimit=\"16\"/>\n</svg>",
4959
+ eightyFiveKnots: "<svg width=\"24\" height=\"47\" viewBox=\"0 0 24 47\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\">\n<path d=\"M17 3.26807V43.5261L1 46.6229L17 35.7842\" fill=\"black\"/>\n<path d=\"M16.9984 3.26807V43.5261L4.35938 45.2985L16.9984 35.7842\" stroke=\"black\" stroke-width=\"1.8\"/>\n<path d=\"M16.9167 17C13.8251 17.6048 9 18.5484 9 18.5484\" stroke=\"black\" stroke-width=\"1.8\" stroke-miterlimit=\"16\"/>\n<path d=\"M17 32.0788L1 35.1756M17 26.8862L1 29.983\" stroke=\"black\" stroke-width=\"1.8\"/>\n<path d=\"M17 22C10.7518 23.1718 1 25 1 25\" stroke=\"black\" stroke-width=\"1.8\" stroke-miterlimit=\"16\"/>\n</svg>\n",
4960
+ ninetyKnots: "<svg width=\"24\" height=\"47\" viewBox=\"0 0 24 47\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\">\n<path d=\"M17 3.26807V43.5261L1 46.6229L17 35.7842\" fill=\"black\"/>\n<path d=\"M16.9984 3.26807V43.5261L4.35938 45.2985L16.9984 35.7842\" stroke=\"black\" stroke-width=\"1.8\"/>\n<path d=\"M17 32.0788L1 35.1756M17 26.8862L1 29.983\" stroke=\"black\" stroke-width=\"1.8\"/>\n<path d=\"M17 22C10.7518 23.1718 1 25 1 25\" stroke=\"black\" stroke-width=\"1.8\" stroke-miterlimit=\"16\"/>\n<path d=\"M17 17C10.7518 18.1718 1 20 1 20\" stroke=\"black\" stroke-width=\"1.8\" stroke-miterlimit=\"16\"/>\n</svg>\n",
4961
+ ninetyFiveKnots: "<svg width=\"24\" height=\"47\" viewBox=\"0 0 24 47\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\">\n<path d=\"M17 3.26807V43.5261L1 46.6229L17 35.7842\" fill=\"black\"/>\n<path d=\"M16.9984 3.26807V43.5261L4.35938 45.2985L16.9984 35.7842\" stroke=\"black\" stroke-width=\"1.8\"/>\n<path d=\"M17 32.0788L1 35.1756M17 26.8862L1 29.983\" stroke=\"black\" stroke-width=\"1.8\"/>\n<path d=\"M17 22C10.7518 23.1718 1 25 1 25\" stroke=\"black\" stroke-width=\"1.8\" stroke-miterlimit=\"16\"/>\n<path d=\"M17 17C10.7518 18.1718 1 20 1 20\" stroke=\"black\" stroke-width=\"1.8\" stroke-miterlimit=\"16\"/>\n<path d=\"M16.9167 12C13.8251 12.6048 9 13.5484 9 13.5484\" stroke=\"black\" stroke-width=\"1.8\" stroke-miterlimit=\"16\"/>\n</svg>",
4962
+ hundredKnots: "<svg width=\"24\" height=\"47\" viewBox=\"0 0 24 47\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\">\n<path d=\"M17 3.26807V23.3971V43.5261L1 46.6229L17 35.7842\" fill=\"black\"/>\n<path d=\"M16.9984 3.26807V28M16.9984 35.7842L4.35938 45.2985L16.9984 43.5261V35.5M16.9984 28C16.9984 28 5.67406 35.7842 4 37M16.9984 28V29.5M4 37C9.07621 36.4142 16.9984 35.5 16.9984 35.5M4 37C9.07621 34.0711 16.9984 29.5 16.9984 29.5M4 37C9.07682 34.6569 17 31 17 31M4 37C9.07621 35.2426 16.9984 32.5 16.9984 32.5M4 37C9.07621 35.8284 16.9984 34 16.9984 34M16.9984 35.5V34M16.9984 29.5V32.5M16.9984 32.5V34\" stroke=\"black\" stroke-width=\"1.8\"/>\n</svg>\n",
4963
+ hundredFiveKnots: "<svg width=\"24\" height=\"47\" viewBox=\"0 0 24 47\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\">\n<path d=\"M17 3.26807V23.3971V43.5261L1 46.6229L17 35.7842\" fill=\"black\"/>\n<path d=\"M16.9984 3.26807V28M16.9984 35.7842L4.35938 45.2985L16.9984 43.5261V35.5M16.9984 28C16.9984 28 5.67406 35.7842 4 37M16.9984 28V29.5M4 37C9.07621 36.4142 16.9984 35.5 16.9984 35.5M4 37C9.07621 34.0711 16.9984 29.5 16.9984 29.5M4 37C9.07682 34.6569 17 31 17 31M4 37C9.07621 35.2426 16.9984 32.5 16.9984 32.5M4 37C9.07621 35.8284 16.9984 34 16.9984 34M16.9984 35.5V34M16.9984 29.5V32.5M16.9984 32.5V34\" stroke=\"black\" stroke-width=\"1.8\"/>\n<path d=\"M16.9167 25C13.8251 25.6048 9 26.5484 9 26.5484\" stroke=\"black\" stroke-width=\"1.8\" stroke-miterlimit=\"16\"/>\n</svg>\n",
4964
+ hundredTenKnots: "<svg width=\"24\" height=\"47\" viewBox=\"0 0 24 47\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\">\n<path d=\"M17 3.26807V23.3971V43.5261L1 46.6229L17 35.7842\" fill=\"black\"/>\n<path d=\"M16.9984 3.26807V28M16.9984 35.7842L4.35938 45.2985L16.9984 43.5261V35.5M16.9984 28C16.9984 28 5.67406 35.7842 4 37M16.9984 28V29.5M4 37C9.07621 36.4142 16.9984 35.5 16.9984 35.5M4 37C9.07621 34.0711 16.9984 29.5 16.9984 29.5M4 37C9.07682 34.6569 17 31 17 31M4 37C9.07621 35.2426 16.9984 32.5 16.9984 32.5M4 37C9.07621 35.8284 16.9984 34 16.9984 34M16.9984 35.5V34M16.9984 29.5V32.5M16.9984 32.5V34\" stroke=\"black\" stroke-width=\"1.8\"/>\n<path d=\"M17 25C10.7518 26.1718 1 28 1 28\" stroke=\"black\" stroke-width=\"1.8\" stroke-miterlimit=\"16\"/>\n</svg>",
4965
+ hundredFifteenKnots: "<svg width=\"24\" height=\"47\" viewBox=\"0 0 24 47\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\">\n<path d=\"M17 3.26807V23.3971V43.5261L1 46.6229L17 35.7842\" fill=\"black\"/>\n<path d=\"M16.9984 3.26807V28M16.9984 35.7842L4.35938 45.2985L16.9984 43.5261V35.5M16.9984 28C16.9984 28 5.67406 35.7842 4 37M16.9984 28V29.5M4 37C9.07621 36.4142 16.9984 35.5 16.9984 35.5M4 37C9.07621 34.0711 16.9984 29.5 16.9984 29.5M4 37C9.07682 34.6569 17 31 17 31M4 37C9.07621 35.2426 16.9984 32.5 16.9984 32.5M4 37C9.07621 35.8284 16.9984 34 16.9984 34M16.9984 35.5V34M16.9984 29.5V32.5M16.9984 32.5V34\" stroke=\"black\" stroke-width=\"1.8\"/>\n<path d=\"M17 25C10.7518 26.1718 1 28 1 28\" stroke=\"black\" stroke-width=\"1.8\" stroke-miterlimit=\"16\"/>\n<path d=\"M16.9167 21C13.8251 21.6048 9 22.5484 9 22.5484\" stroke=\"black\" stroke-width=\"1.8\" stroke-miterlimit=\"16\"/>\n</svg>",
4966
+ hundredTwentyKnots: "<svg width=\"24\" height=\"47\" viewBox=\"0 0 24 47\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\">\n<path d=\"M17 3.26807V23.3971V43.5261L1 46.6229L17 35.7842\" fill=\"black\"/>\n<path d=\"M16.9984 3.26807V28M16.9984 35.7842L4.35938 45.2985L16.9984 43.5261V35.5M16.9984 28C16.9984 28 5.67406 35.7842 4 37M16.9984 28V29.5M4 37C9.07621 36.4142 16.9984 35.5 16.9984 35.5M4 37C9.07621 34.0711 16.9984 29.5 16.9984 29.5M4 37C9.07682 34.6569 17 31 17 31M4 37C9.07621 35.2426 16.9984 32.5 16.9984 32.5M4 37C9.07621 35.8284 16.9984 34 16.9984 34M16.9984 35.5V34M16.9984 29.5V32.5M16.9984 32.5V34\" stroke=\"black\" stroke-width=\"1.8\"/>\n<path d=\"M17 25C10.7518 26.1718 1 28 1 28\" stroke=\"black\" stroke-width=\"1.8\" stroke-miterlimit=\"16\"/>\n<path d=\"M17 21C10.7518 22.1718 1 24 1 24\" stroke=\"black\" stroke-width=\"1.8\" stroke-miterlimit=\"16\"/>\n</svg>",
4967
+ hundredTwentyFiveKnots: "<svg width=\"24\" height=\"47\" viewBox=\"0 0 24 47\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\">\n<path d=\"M17 3.26807V23.3971V43.5261L1 46.6229L17 35.7842\" fill=\"black\"/>\n<path d=\"M16.9984 3.26807V28M16.9984 35.7842L4.35938 45.2985L16.9984 43.5261V35.5M16.9984 28C16.9984 28 5.67406 35.7842 4 37M16.9984 28V29.5M4 37C9.07621 36.4142 16.9984 35.5 16.9984 35.5M4 37C9.07621 34.0711 16.9984 29.5 16.9984 29.5M4 37C9.07682 34.6569 17 31 17 31M4 37C9.07621 35.2426 16.9984 32.5 16.9984 32.5M4 37C9.07621 35.8284 16.9984 34 16.9984 34M16.9984 35.5V34M16.9984 29.5V32.5M16.9984 32.5V34\" stroke=\"black\" stroke-width=\"1.8\"/>\n<path d=\"M17 25C10.7518 26.1718 1 28 1 28\" stroke=\"black\" stroke-width=\"1.8\" stroke-miterlimit=\"16\"/>\n<path d=\"M17 21C10.7518 22.1718 1 24 1 24\" stroke=\"black\" stroke-width=\"1.8\" stroke-miterlimit=\"16\"/>\n<path d=\"M16.9167 17C13.8251 17.6048 9 18.5484 9 18.5484\" stroke=\"black\" stroke-width=\"1.8\" stroke-miterlimit=\"16\"/>\n</svg>\n",
4968
+ hundredThirtyKnots: "<svg width=\"24\" height=\"47\" viewBox=\"0 0 24 47\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\">\n<path d=\"M17 3.26807V23.3971V43.5261L1 46.6229L17 35.7842\" fill=\"black\"/>\n<path d=\"M16.9984 3.26807V28M16.9984 35.7842L4.35938 45.2985L16.9984 43.5261V35.5M16.9984 28C16.9984 28 5.67406 35.7842 4 37M16.9984 28V29.5M4 37C9.07621 36.4142 16.9984 35.5 16.9984 35.5M4 37C9.07621 34.0711 16.9984 29.5 16.9984 29.5M4 37C9.07682 34.6569 17 31 17 31M4 37C9.07621 35.2426 16.9984 32.5 16.9984 32.5M4 37C9.07621 35.8284 16.9984 34 16.9984 34M16.9984 35.5V34M16.9984 29.5V32.5M16.9984 32.5V34\" stroke=\"black\" stroke-width=\"1.8\"/>\n<path d=\"M17 25C10.7518 26.1718 1 28 1 28\" stroke=\"black\" stroke-width=\"1.8\" stroke-miterlimit=\"16\"/>\n<path d=\"M17 21C10.7518 22.1718 1 24 1 24\" stroke=\"black\" stroke-width=\"1.8\" stroke-miterlimit=\"16\"/>\n<path d=\"M17 17C10.7518 18.1718 1 20 1 20\" stroke=\"black\" stroke-width=\"1.8\" stroke-miterlimit=\"16\"/>\n</svg>",
4969
+ hundredThirtyFiveKnots: "<svg width=\"24\" height=\"47\" viewBox=\"0 0 24 47\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\">\n<path d=\"M17 3.26807V23.3971V43.5261L1 46.6229L17 35.7842\" fill=\"black\"/>\n<path d=\"M16.9984 3.26807V28M16.9984 35.7842L4.35938 45.2985L16.9984 43.5261V35.5M16.9984 28C16.9984 28 5.67406 35.7842 4 37M16.9984 28V29.5M4 37C9.07621 36.4142 16.9984 35.5 16.9984 35.5M4 37C9.07621 34.0711 16.9984 29.5 16.9984 29.5M4 37C9.07682 34.6569 17 31 17 31M4 37C9.07621 35.2426 16.9984 32.5 16.9984 32.5M4 37C9.07621 35.8284 16.9984 34 16.9984 34M16.9984 35.5V34M16.9984 29.5V32.5M16.9984 32.5V34\" stroke=\"black\" stroke-width=\"1.8\"/>\n<path d=\"M17 25C10.7518 26.1718 1 28 1 28\" stroke=\"black\" stroke-width=\"1.8\" stroke-miterlimit=\"16\"/>\n<path d=\"M17 21C10.7518 22.1718 1 24 1 24\" stroke=\"black\" stroke-width=\"1.8\" stroke-miterlimit=\"16\"/>\n<path d=\"M17 17C10.7518 18.1718 1 20 1 20\" stroke=\"black\" stroke-width=\"1.8\" stroke-miterlimit=\"16\"/>\n<path d=\"M16.9167 13C13.8251 13.6048 9 14.5484 9 14.5484\" stroke=\"black\" stroke-width=\"1.8\" stroke-miterlimit=\"16\"/>\n</svg>",
4970
+ hundredFortyKnots: "<svg width=\"24\" height=\"47\" viewBox=\"0 0 24 47\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\">\n<path d=\"M17 3.26807V23.3971V43.5261L1 46.6229L17 35.7842\" fill=\"black\"/>\n<path d=\"M16.9984 3.26807V28M16.9984 35.7842L4.35938 45.2985L16.9984 43.5261V35.5M16.9984 28C16.9984 28 5.67406 35.7842 4 37M16.9984 28V29.5M4 37C9.07621 36.4142 16.9984 35.5 16.9984 35.5M4 37C9.07621 34.0711 16.9984 29.5 16.9984 29.5M4 37C9.07682 34.6569 17 31 17 31M4 37C9.07621 35.2426 16.9984 32.5 16.9984 32.5M4 37C9.07621 35.8284 16.9984 34 16.9984 34M16.9984 35.5V34M16.9984 29.5V32.5M16.9984 32.5V34\" stroke=\"black\" stroke-width=\"1.8\"/>\n<path d=\"M17 25C10.7518 26.1718 1 28 1 28\" stroke=\"black\" stroke-width=\"1.8\" stroke-miterlimit=\"16\"/>\n<path d=\"M17 21C10.7518 22.1718 1 24 1 24\" stroke=\"black\" stroke-width=\"1.8\" stroke-miterlimit=\"16\"/>\n<path d=\"M17 17C10.7518 18.1718 1 20 1 20\" stroke=\"black\" stroke-width=\"1.8\" stroke-miterlimit=\"16\"/>\n<path d=\"M17 13C10.7518 14.1718 1 16 1 16\" stroke=\"black\" stroke-width=\"1.8\" stroke-miterlimit=\"16\"/>\n</svg>",
4971
+ hundredFortyFiveKnots: "<svg width=\"24\" height=\"49\" viewBox=\"0 0 24 49\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\">\n<path d=\"M17 5.26807V25.3971V45.5261L1 48.6229L17 37.7842\" fill=\"black\"/>\n<path d=\"M16.9984 5.26807V30M16.9984 37.7842L4.35938 47.2985L16.9984 45.5261V37.5M16.9984 30C16.9984 30 5.67406 37.7842 4 39M16.9984 30V31.5M4 39C9.07621 38.4142 16.9984 37.5 16.9984 37.5M4 39C9.07621 36.0711 16.9984 31.5 16.9984 31.5M4 39C9.07682 36.6569 17 33 17 33M4 39C9.07621 37.2426 16.9984 34.5 16.9984 34.5M4 39C9.07621 37.8284 16.9984 36 16.9984 36M16.9984 37.5V36M16.9984 31.5V34.5M16.9984 34.5V36\" stroke=\"black\" stroke-width=\"1.8\"/>\n<path d=\"M16.9167 12C13.8251 12.6048 9 13.5484 9 13.5484\" stroke=\"black\" stroke-width=\"1.8\" stroke-miterlimit=\"16\"/>\n<path d=\"M17 27C10.7518 28.1718 1 30 1 30\" stroke=\"black\" stroke-width=\"1.8\" stroke-miterlimit=\"16\"/>\n<path d=\"M17 23C10.7518 24.1718 1 26 1 26\" stroke=\"black\" stroke-width=\"1.8\" stroke-miterlimit=\"16\"/>\n<path d=\"M17 19C10.7518 20.1718 1 22 1 22\" stroke=\"black\" stroke-width=\"1.8\" stroke-miterlimit=\"16\"/>\n<path d=\"M17 15C10.7518 16.1718 1 18 1 18\" stroke=\"black\" stroke-width=\"1.8\" stroke-miterlimit=\"16\"/>\n</svg>",
4972
+ hundredFiftyKnots: "<svg width=\"24\" height=\"47\" viewBox=\"0 0 24 47\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\">\n<path d=\"M17 3.26807V23.3971V43.5261L1 46.6229L17 35.7842\" fill=\"black\"/>\n<path d=\"M16.9984 3.26807V19M16.9984 35.7842L4.35938 45.2985L16.9984 43.5261V35.5M16.9984 28C16.9984 28 5.67406 35.7842 4 37M16.9984 28V29.5M16.9984 28V26.5M4 37C9.07621 36.4142 16.9984 35.5 16.9984 35.5M4 37C9.07621 34.0711 16.9984 29.5 16.9984 29.5M4 37C9.07682 34.6569 17 31 17 31M4 37C9.07621 35.2426 16.9984 32.5 16.9984 32.5M4 37C9.07621 35.8284 16.9984 34 16.9984 34M16.9984 35.5V34M16.9984 29.5V32.5M16.9984 32.5V34M16.9984 19C16.9984 19 8 25.5 4 29M16.9984 19V20.5M4 29C9.07621 28.0237 16.9984 26.5 16.9984 26.5M4 29C9.07621 25.6805 16.9984 20.5 16.9984 20.5M4 29C9.07621 26.4616 16.9984 22.5 16.9984 22.5M4 29C9.07621 27.0474 16.9984 24 16.9984 24M4 29C9.07621 27.5355 16.9984 25.25 16.9984 25.25M16.9984 26.5V25.25M16.9984 20.5V22.5M16.9984 22.5V24M16.9984 24V25.25\" stroke=\"black\" stroke-width=\"1.8\"/>\n</svg>"
4973
+ };
4974
+ var windSpeedRanges = [{
4975
+ min: 0,
4976
+ max: 1,
4977
+ symbol: windSymbolDefinitions.calm
4978
+ }, {
4979
+ min: 1,
4980
+ max: 4.5,
4981
+ symbol: windSymbolDefinitions.lightAir
4982
+ }, {
4983
+ min: 4.5,
4984
+ max: 7.5,
4985
+ symbol: windSymbolDefinitions.fiveKnots
4986
+ }, {
4987
+ min: 7.5,
4988
+ max: 12.5,
4989
+ symbol: windSymbolDefinitions.tenKnots
4990
+ }, {
4991
+ min: 12.5,
4992
+ max: 17.5,
4993
+ symbol: windSymbolDefinitions.fifteenKnots
4994
+ }, {
4995
+ min: 17.5,
4996
+ max: 22.5,
4997
+ symbol: windSymbolDefinitions.twentyKnots
4998
+ }, {
4999
+ min: 22.5,
5000
+ max: 27.5,
5001
+ symbol: windSymbolDefinitions.twentyFiveKnots
5002
+ }, {
5003
+ min: 27.5,
5004
+ max: 32.5,
5005
+ symbol: windSymbolDefinitions.thirtyKnots
5006
+ }, {
5007
+ min: 32.5,
5008
+ max: 37.5,
5009
+ symbol: windSymbolDefinitions.thirtyFiveKnots
5010
+ }, {
5011
+ min: 37.5,
5012
+ max: 42.5,
5013
+ symbol: windSymbolDefinitions.fortyKnots
5014
+ }, {
5015
+ min: 42.5,
5016
+ max: 47.5,
5017
+ symbol: windSymbolDefinitions.fortyFiveKnots
5018
+ }, {
5019
+ min: 47.5,
5020
+ max: 52.5,
5021
+ symbol: windSymbolDefinitions.fiftyKnots
5022
+ }, {
5023
+ min: 52.5,
5024
+ max: 57.5,
5025
+ symbol: windSymbolDefinitions.fiftyFiveKnots
5026
+ }, {
5027
+ min: 57.5,
5028
+ max: 62.5,
5029
+ symbol: windSymbolDefinitions.sixtyKnots
5030
+ }, {
5031
+ min: 62.5,
5032
+ max: 67.5,
5033
+ symbol: windSymbolDefinitions.sixtyFiveKnots
5034
+ }, {
5035
+ min: 67.5,
5036
+ max: 72.5,
5037
+ symbol: windSymbolDefinitions.seventyKnots
5038
+ }, {
5039
+ min: 72.5,
5040
+ max: 77.5,
5041
+ symbol: windSymbolDefinitions.seventyFiveKnots
5042
+ }, {
5043
+ min: 77.5,
5044
+ max: 82.5,
5045
+ symbol: windSymbolDefinitions.eightyKnots
5046
+ }, {
5047
+ min: 82.5,
5048
+ max: 87.5,
5049
+ symbol: windSymbolDefinitions.eightyFiveKnots
5050
+ }, {
5051
+ min: 87.5,
5052
+ max: 92.5,
5053
+ symbol: windSymbolDefinitions.ninetyKnots
5054
+ }, {
5055
+ min: 92.5,
5056
+ max: 97.5,
5057
+ symbol: windSymbolDefinitions.ninetyFiveKnots
5058
+ }, {
5059
+ min: 97.5,
5060
+ max: 102.5,
5061
+ symbol: windSymbolDefinitions.hundredKnots
5062
+ }, {
5063
+ min: 102.5,
5064
+ max: 107.5,
5065
+ symbol: windSymbolDefinitions.hundredFiveKnots
5066
+ }, {
5067
+ min: 107.5,
5068
+ max: 112.5,
5069
+ symbol: windSymbolDefinitions.hundredTenKnots
5070
+ }, {
5071
+ min: 112.5,
5072
+ max: 117.5,
5073
+ symbol: windSymbolDefinitions.hundredFifteenKnots
5074
+ }, {
5075
+ min: 117.5,
5076
+ max: 122.5,
5077
+ symbol: windSymbolDefinitions.hundredTwentyKnots
5078
+ }, {
5079
+ min: 122.5,
5080
+ max: 127.5,
5081
+ symbol: windSymbolDefinitions.hundredTwentyFiveKnots
5082
+ }, {
5083
+ min: 127.5,
5084
+ max: 132.5,
5085
+ symbol: windSymbolDefinitions.hundredThirtyKnots
5086
+ }, {
5087
+ min: 132.5,
5088
+ max: 137.5,
5089
+ symbol: windSymbolDefinitions.hundredThirtyFiveKnots
5090
+ }, {
5091
+ min: 137.5,
5092
+ max: 142.5,
5093
+ symbol: windSymbolDefinitions.hundredFortyKnots
5094
+ }, {
5095
+ min: 142.5,
5096
+ max: 147.5,
5097
+ symbol: windSymbolDefinitions.hundredFortyFiveKnots
5098
+ }, {
5099
+ min: 147.5,
5100
+ max: 152,
5101
+ symbol: windSymbolDefinitions.hundredFiftyKnots
5102
+ }];
5103
+
5104
+ /* *
5105
+ * Licensed under the Apache License, Version 2.0 (the "License");
5106
+ * you may not use this file except in compliance with the License.
5107
+ * You may obtain a copy of the License at
5108
+ *
5109
+ * http://www.apache.org/licenses/LICENSE-2.0
5110
+ *
5111
+ * Unless required by applicable law or agreed to in writing, software
5112
+ * distributed under the License is distributed on an "AS IS" BASIS,
5113
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
5114
+ * See the License for the specific language governing permissions and
5115
+ * limitations under the License.
5116
+ *
5117
+ * Copyright 2025 - Koninklijk Nederlands Meteorologisch Instituut (KNMI)
5118
+ * Copyright 2025 - Finnish Meteorological Institute (FMI)
5119
+ * Copyright 2025 - The Norwegian Meteorological Institute (MET Norway)
5120
+ * */
5121
+ var MIN_VERTICAL_SCALE = 1;
5122
+ var MAX_VERTICAL_SCALE = 7;
5123
+ var DEFAULT_THINNING_BASE = 1.15;
5124
+ var MIN_THINNING_BASE = 1.08;
5125
+ var EXTRA_REVEAL_EXPONENT = 0.5; // Decrease this number to reveal more barbs earlier as you zoom in
5126
+ var extendIsoBar = function extendIsoBar(path, horizontalBarbMargin) {
5127
+ var match = /^M([\d.-]+),([\d.-]+)\s*L([\d.-]+),([\d.-]+)/.exec(path);
5128
+ if (match) {
5129
+ var startX = parseFloat(match[1]);
5130
+ var startY = parseFloat(match[2]);
5131
+ var endX = parseFloat(match[3]);
5132
+ var endY = parseFloat(match[4]);
5133
+ return "M" + startX + "," + startY + "L" + (endX + horizontalBarbMargin * 3) + "," + endY;
5134
+ }
5135
+ return path;
5136
+ };
5137
+ var extendXAxis = function extendXAxis(path, horizontalBarbMargin) {
5138
+ var match = /^M([\d.-]+),([\d.-]+)H([\d.-]+)/.exec(path);
5139
+ if (match) {
5140
+ var endX = parseFloat(match[3]);
5141
+ return path.replace(/H[\d.-]+/, "H" + (endX + horizontalBarbMargin * 3));
5142
+ }
5143
+ return path;
5144
+ };
5145
+ var drawWindBarbs = function drawWindBarbs(_ref) {
5146
+ var _rootSvg$node;
5147
+ var svgRef = _ref.svgRef,
5148
+ plotGroup = _ref.plotGroup,
5149
+ yScale = _ref.yScale,
5150
+ data = _ref.data,
5151
+ width = _ref.width,
5152
+ margin = _ref.margin,
5153
+ _ref$barbColor = _ref.barbColor,
5154
+ barbColor = _ref$barbColor === void 0 ? 'black' : _ref$barbColor,
5155
+ _ref$columnOffsetX = _ref.columnOffsetX,
5156
+ columnOffsetX = _ref$columnOffsetX === void 0 ? 0 : _ref$columnOffsetX,
5157
+ _ref$verticalScale = _ref.verticalScale,
5158
+ verticalScale = _ref$verticalScale === void 0 ? 1 : _ref$verticalScale;
5159
+ var barbsSize = 40;
5160
+ var scalingFactor = 0.5;
5161
+ var horizontalBarbMargin = barbsSize * scalingFactor + 5;
5162
+ var visibleItems = getVisibleWindItemsForVerticalScale(data, verticalScale).filter(function (item) {
5163
+ return item.pressure >= 150;
5164
+ });
5165
+ // Measure width and height and make extra room for one column
5166
+ // of windbarbs
5167
+ var rootSvg = d3.select(svgRef.current);
5168
+ var currentWidth = (_rootSvg$node = rootSvg.node()) == null ? void 0 : _rootSvg$node.getBoundingClientRect().width;
5169
+ if (!currentWidth) {
5170
+ return;
5171
+ }
5172
+ // Add extra width to isobars in this SVG only.
5173
+ rootSvg.selectAll('path[type="isobar"]').each(function (_d, i, nodes) {
5174
+ var path = d3.select(nodes[i]).attr('d');
5175
+ var newD = extendIsoBar(path, horizontalBarbMargin);
5176
+ d3.select(nodes[i]).attr('d', newD).attr('clip-path', null);
5177
+ });
5178
+ // Add extra width to x-axis in this SVG only.
5179
+ rootSvg.select('g[type="x-axis"] path.domain').each(function (_d, i, nodes) {
5180
+ var path = d3.select(nodes[i]).attr('d');
5181
+ var newD = extendXAxis(path, horizontalBarbMargin);
5182
+ d3.select(nodes[i]).attr('d', newD);
5183
+ });
5184
+ // Expand root SVG width to make room for barbs column at the right
5185
+ rootSvg.attr('width', currentWidth + margin.left + horizontalBarbMargin);
5186
+ // Place the barbs just to the right of the plotting area.
5187
+ // plotGroup's origin is already translated to (margin.left, margin.top),
5188
+ // and its width equals `width`.
5189
+ var windBarbColumnX = width + 5 + columnOffsetX + horizontalBarbMargin;
5190
+ var barbGroup = plotGroup.append('g').attr('class', 'wind-group').attr('transform', // Apply translation and counter-scale to maintain aspect ratio during vertical zoom.
5191
+ // The counter-scale (1, 1/verticalScale) undoes the parent's vertical scaling.
5192
+ "translate(" + windBarbColumnX + ", 0) scale(1, " + 1 / verticalScale + ")");
5193
+ barbGroup.selectAll('g').data(visibleItems).enter().append('g').attr('id', 'wind-barb').each(function handleBarb(data) {
5194
+ var group = d3.select(this);
5195
+ var windSymbol = getWindSymbol(data.windSpeed);
5196
+ if (windSymbol) {
5197
+ var _innerGroup$node;
5198
+ // Inner group will have rotation and scaling applied to it
5199
+ var innerGroup = group.append('g');
5200
+ (_innerGroup$node = innerGroup.node()) == null || _innerGroup$node.appendChild(windSymbol);
5201
+ // Update stroke color
5202
+ innerGroup.selectAll('*').attr('stroke', barbColor);
5203
+ // Only update the fill for the paths that already used a fill
5204
+ // in their original svg form.
5205
+ innerGroup.selectAll('*').each(function (_d, i, nodes) {
5206
+ var el = d3.select(nodes[i]);
5207
+ var currentFill = el.attr('fill');
5208
+ if (currentFill && currentFill !== 'none') {
5209
+ el.attr('fill', barbColor);
5210
+ }
5211
+ });
5212
+ var bbox = windSymbol.getBBox();
5213
+ // Central point
5214
+ var cx = bbox.x + bbox.width / 2;
5215
+ var cy = bbox.y + bbox.height / 2;
5216
+ var rotation = 180 + data.windDirection;
5217
+ var y = yScale(data.pressure);
5218
+ // Place items in the correct vertical position.
5219
+ // Apply verticalScale to compensate for the parent's counter-scale,
5220
+ // ensuring barbs appear at the correct zoomed Y position.
5221
+ group.attr('transform', "translate(0, " + (y - barbsSize * scalingFactor / 8) * verticalScale + ")");
5222
+ // Rotate accordingly, and scale
5223
+ innerGroup.attr('transform', "rotate(" + rotation + ") scale(" + scalingFactor + ") translate(" + -cx + ", " + -cy + ")");
5224
+ }
5225
+ });
5226
+ };
5227
+ var removeItemsExponential = function removeItemsExponential(array, base) {
5228
+ if (base === void 0) {
5229
+ base = DEFAULT_THINNING_BASE;
5230
+ }
5231
+ var indicesToKeep = [];
5232
+ var i = 0;
5233
+ var nextIndex = 0;
5234
+ while (nextIndex < array.length) {
5235
+ nextIndex = Math.floor(Math.pow(base, i));
5236
+ indicesToKeep.push(nextIndex);
5237
+ i += 1;
5238
+ }
5239
+ return array.filter(function (_, index) {
5240
+ return indicesToKeep.includes(index);
5241
+ });
5242
+ };
5243
+ var getVisibleWindItemsForVerticalScale = function getVisibleWindItemsForVerticalScale(data, verticalScale) {
5244
+ var reversedData = [].concat(data).reverse();
5245
+ var baselineItems = removeItemsExponential(reversedData);
5246
+ var maxDetailItems = removeItemsExponential(reversedData, MIN_THINNING_BASE);
5247
+ var baselineSet = new Set(baselineItems);
5248
+ var maxDetailSet = new Set(maxDetailItems);
5249
+ // Candidate additions are fixed and ordered; zooming in reveals a prefix only.
5250
+ var extraCandidates = reversedData.filter(function (item) {
5251
+ return maxDetailSet.has(item) && !baselineSet.has(item);
5252
+ });
5253
+ var clampedScale = Math.max(MIN_VERTICAL_SCALE, Math.min(MAX_VERTICAL_SCALE, verticalScale));
5254
+ var normalizedScale = (clampedScale - MIN_VERTICAL_SCALE) / (MAX_VERTICAL_SCALE - MIN_VERTICAL_SCALE);
5255
+ // Use a front-loaded curve so extra barbs appear earlier when zooming in,
5256
+ // while still keeping zoom level 1 unchanged and monotonic.
5257
+ var revealProgress = Math.pow(normalizedScale, EXTRA_REVEAL_EXPONENT);
5258
+ var extraCount = Math.floor(revealProgress * extraCandidates.length);
5259
+ var extraSet = new Set(extraCandidates.slice(0, extraCount));
5260
+ return reversedData.filter(function (item) {
5261
+ return baselineSet.has(item) || extraSet.has(item);
5262
+ });
5263
+ };
5264
+ var getWindSymbol = function getWindSymbol(value) {
5265
+ var matchedRange = windSpeedRanges.find(function (_ref2) {
5266
+ var min = _ref2.min,
5267
+ max = _ref2.max;
5268
+ return value >= min && value < max;
5269
+ });
5270
+ // Out of range
5271
+ if (!matchedRange) {
5272
+ return null;
5273
+ }
5274
+ // SVG string to actual SVG element
5275
+ var parser = new DOMParser();
5276
+ var svgDoc = parser.parseFromString(matchedRange.symbol, 'image/svg+xml');
5277
+ var svgNode = svgDoc.documentElement;
5278
+ // Return the parsed SVG node
5279
+ return svgNode;
5280
+ };
5281
+
5282
+ var shadow$1 = '0 1px 5px 0 rgba(0, 0, 0, 0.2), 0 3px 4px 0 rgba(0, 0, 0, 0.12), 0 2px 4px 0 rgba(0, 0, 0, 0.14)';
5283
+ var soundingsStyle$1 = {
5284
+ tooltip: {
5285
+ display: 'block',
5286
+ position: 'absolute',
5287
+ borderRadius: '1.5px',
5288
+ backgroundColor: 'geowebColors.soundings.tooltip.background',
5289
+ boxShadow: shadow$1,
5290
+ padding: '2px 4px',
5291
+ pointerEvents: 'none',
5292
+ zIndex: 500
5293
+ },
5294
+ pressure: {
5295
+ fontSize: '10px'
5296
+ },
5297
+ temperature: {
5298
+ fontSize: '12px',
5299
+ fontWeight: 'bold'
5300
+ },
5301
+ dewPoint: {
5302
+ fontSize: '12px',
5303
+ fontWeight: 'bold'
5304
+ }};
5305
+ var updateTransformToViewport = function updateTransformToViewport(transform, width, height) {
5306
+ var minX = width * (1 - transform.k);
5307
+ var minY = height * (1 - transform.k);
5308
+ return d3.zoomIdentity.translate(Math.min(0, Math.max(minX, transform.x)), Math.min(0, Math.max(minY, transform.y))).scale(transform.k);
5309
+ };
5310
+ var Soundings = function Soundings(_ref) {
5311
+ var d3GraphId = _ref.d3GraphId,
5312
+ data = _ref.data,
5313
+ svgDimensions = _ref.svgDimensions,
5314
+ _ref$showTooltip = _ref.showTooltip,
5315
+ showTooltip = _ref$showTooltip === void 0 ? true : _ref$showTooltip,
5316
+ _ref$diagramType = _ref.diagramType,
5317
+ diagramType = _ref$diagramType === void 0 ? 'SKEWT' : _ref$diagramType;
5318
+ var instanceId = useId().replace(/:/g, '_');
5319
+ var scopedGraphId = d3GraphId + "-" + instanceId;
5320
+ var svgRef = useRef(null);
5321
+ var canvasRef = useRef(null);
5322
+ var pressureRef = useRef(null);
5323
+ var tempRef = useRef(null);
5324
+ var dewpointRef = useRef(null);
5325
+ // Refs to store current scale/data for shared mouse effect
5326
+ var yScaleRef = useRef(null);
5327
+ var marginRef = useRef(null);
5328
+ var widthRef = useRef(0);
5329
+ var svgRectRef = useRef(null);
5330
+ var tooltipDataRef = useRef(null);
5331
+ var zoomBehaviorRef = useRef(null);
5332
+ // Keep zoom target on the inner translated <g>; Soundings and WindBarbs must
5333
+ // transform the same coordinate space to stay aligned.
5334
+ var zoomTargetRef = useRef(null);
5335
+ var isApplyingSharedZoomRef = useRef(false);
5336
+ // Keep the last applied d3 zoom transform across SVG redraws.
5337
+ // Width/height changes recreate the chart, so this ref prevents zoom snapping
5338
+ // back to identity when panels are closed/resized.
5339
+ var persistedZoomTransformRef = useRef(d3.zoomIdentity);
5340
+ var previousDiagramTypeRef = useRef(null);
5341
+ var _useSharedMouseY = useSharedMouseY(),
5342
+ clientY = _useSharedMouseY.clientY,
5343
+ publishVerticalZoom = _useSharedMouseY.publishVerticalZoom,
5344
+ verticalZoom = _useSharedMouseY.verticalZoom,
5345
+ resetVerticalZoom = _useSharedMouseY.resetVerticalZoom;
5346
+ // Set SVG dimensions (required props; no optional chaining needed)
5347
+ var baseWidth = svgDimensions.width,
5348
+ baseHeight = svgDimensions.height,
5349
+ margin = svgDimensions.margin;
5350
+ var width = baseWidth - (margin.left + margin.right);
5351
+ var height = baseHeight - (margin.top + margin.bottom);
5352
+ useEffect(function () {
5353
+ if (!svgRef.current || width <= 0 || height <= 0) {
5354
+ return;
5355
+ }
5356
+ var isDiagramTypeChange = previousDiagramTypeRef.current !== null && previousDiagramTypeRef.current !== diagramType;
5357
+ if (isDiagramTypeChange) {
5358
+ persistedZoomTransformRef.current = d3.zoomIdentity;
5359
+ resetVerticalZoom(scopedGraphId);
5360
+ }
5361
+ var root = d3.select(svgRef.current);
5362
+ root.selectAll('*').remove();
5363
+ var svg = initializeSvg$1({
5364
+ svgRef: svgRef,
5365
+ width: width,
5366
+ height: height,
5367
+ margin: margin,
5368
+ diagramType: diagramType,
5369
+ d3GraphId: scopedGraphId
5370
+ });
5371
+ // Create a separate layer for axes so they can stay visible above the zoomed content.
5372
+ var axisLayer = svg.append('g').attr('data-layer', 'axis-layer');
5373
+ // Keep a static viewport group for clipping, and transform only the inner
5374
+ // content group during zoom so the visible area stays fixed.
5375
+ var zoomViewport = svg.append('g').attr('data-layer', 'zoom-viewport').attr('clip-path', "url(#clip-" + scopedGraphId + "-" + diagramType + "d3)");
5376
+ var zoomContent = zoomViewport.append('g').attr('data-layer', 'zoom-content');
5377
+ var bijvoetLabelLayer = svg.append('g').attr('data-layer', 'bijvoet-label-layer').style('pointer-events', 'none');
5378
+ var svgRectangle = svgRef.current.getBoundingClientRect();
5379
+ svgRectRef.current = svgRectangle;
5380
+ marginRef.current = margin;
5381
+ widthRef.current = width;
5382
+ var minT = diagramType === 'EMAGRAM' ? minEmagramTemperature : minTemperature;
5383
+ var maxT = maxTemperature;
5384
+ var _createScales = createScales$1(diagramType, width, height, minPressure, maxPressure, minT, maxT),
5385
+ xScale = _createScales[0],
5386
+ yScale = _createScales[1];
5387
+ yScaleRef.current = yScale;
5388
+ var applyAxisClips = function applyAxisClips() {
5389
+ axisLayer.select('g[type="x-axis"] path.domain').attr('d', "M0.5,0.5H" + (width + 0.5));
5390
+ axisLayer.select('g[type="x-axis"]').attr('clip-path', "url(#clip-" + scopedGraphId + "-" + diagramType + "x-axis)");
5391
+ axisLayer.select('g[type="y-axis"]').attr('clip-path', "url(#clip-" + scopedGraphId + "-" + diagramType + "y-axis)");
5392
+ };
5393
+ drawAxes$1({
5394
+ svg: axisLayer,
5395
+ xScale: xScale,
5396
+ yScale: yScale,
5397
+ height: height,
5398
+ diagramType: diagramType
5399
+ });
5400
+ applyAxisClips();
5401
+ drawStaticLines({
5402
+ diagramType: diagramType,
5403
+ dp: dp,
5404
+ staticPressureArray: staticPressureArray,
5405
+ minTemperature: minT,
5406
+ maxTemperature: maxT,
5407
+ height: height,
5408
+ width: width,
5409
+ xScale: xScale,
5410
+ yScale: yScale,
5411
+ minPressure: minPressure,
5412
+ maxPressure: maxPressure,
5413
+ svg: zoomContent,
5414
+ bijvoetLabelSvg: bijvoetLabelLayer,
5415
+ d3GraphId: scopedGraphId
5416
+ });
5417
+ data.forEach(function (features, index) {
5418
+ var color = SOUNDING_COLORS[index] || SOUNDING_COLORS[0];
5419
+ var temperatureValues = features.map(function (feature) {
4875
5420
  return feature.properties.temp;
4876
5421
  });
4877
5422
  var pressureValues = features.map(function (feature) {
@@ -4882,10 +5427,11 @@ var Soundings = function Soundings(_ref) {
4882
5427
  pressureValues: pressureValues,
4883
5428
  color: color,
4884
5429
  lineStyle: 'none',
5430
+ d3GraphId: scopedGraphId,
4885
5431
  xScale: xScale,
4886
5432
  yScale: yScale,
4887
5433
  height: height,
4888
- svg: svg,
5434
+ svg: zoomContent,
4889
5435
  diagramType: diagramType
4890
5436
  });
4891
5437
  var dewpointValues = features.map(function (feature) {
@@ -4896,20 +5442,89 @@ var Soundings = function Soundings(_ref) {
4896
5442
  pressureValues: pressureValues,
4897
5443
  color: color,
4898
5444
  lineStyle: '8, 2',
5445
+ d3GraphId: scopedGraphId,
4899
5446
  xScale: xScale,
4900
5447
  yScale: yScale,
4901
5448
  height: height,
4902
- svg: svg,
5449
+ svg: zoomContent,
4903
5450
  diagramType: diagramType
4904
5451
  });
4905
5452
  });
4906
5453
  tooltipDataRef.current = constructTooltipData(data);
4907
- var svgRefCur = svgRef.current;
4908
- // eslint-disable-next-line consistent-return, @typescript-eslint/explicit-function-return-type
5454
+ var zoomTarget = zoomContent.node();
5455
+ if (!zoomTarget) {
5456
+ return;
5457
+ }
5458
+ zoomTargetRef.current = zoomTarget;
5459
+ var bijvoetLabelTarget = bijvoetLabelLayer.node();
5460
+ if (!bijvoetLabelTarget) {
5461
+ return;
5462
+ }
5463
+ var applyZoomTransform = function applyZoomTransform(transform) {
5464
+ d3.select(zoomTarget).attr('transform', transform.toString());
5465
+ d3.select(bijvoetLabelTarget).attr('transform', transform.toString());
5466
+ };
5467
+ // Allow zooming and panning
5468
+ var zoomBehavior = d3.zoom().scaleExtent([MIN_VERTICAL_SCALE, MAX_VERTICAL_SCALE]).extent([[0, 0], [width, height]]).translateExtent([[0, 0], [width, height]]).on('zoom', function (event) {
5469
+ // Persist every interactive zoom/pan event so the transform can be
5470
+ // restored after dimension-driven rerenders.
5471
+ persistedZoomTransformRef.current = event.transform;
5472
+ applyZoomTransform(event.transform);
5473
+ var zoomedXScale = event.transform.rescaleX(xScale);
5474
+ var zoomedYScale = event.transform.rescaleY(yScale);
5475
+ var axisXScale = diagramType === 'SKEWT' ? xScale.copy().range(xScale.range().map(function (xValue) {
5476
+ // Skew-T X uses x + (height - y). To keep tick labels tied to
5477
+ // isotherm intersections on the bottom axis, apply the same
5478
+ // transformed bottom-edge offset to the axis scale.
5479
+ var skewBottomOffset = (event.transform.k - 1) * height + event.transform.y;
5480
+ return event.transform.k * xValue + event.transform.x + skewBottomOffset;
5481
+ })) : zoomedXScale;
5482
+ var axisYScale = diagramType === 'BIJVOET' ? yScale.copy().range(yScale.range().map(function (yValue) {
5483
+ return event.transform.k * yValue + event.transform.y;
5484
+ })) : zoomedYScale;
5485
+ axisLayer.selectAll('*').remove();
5486
+ drawAxes$1({
5487
+ svg: axisLayer,
5488
+ xScale: axisXScale,
5489
+ yScale: axisYScale,
5490
+ height: height,
5491
+ diagramType: diagramType
5492
+ });
5493
+ applyAxisClips();
5494
+ if (!isApplyingSharedZoomRef.current) {
5495
+ publishVerticalZoom(scopedGraphId, event.transform.k, event.transform.y);
5496
+ }
5497
+ });
5498
+ zoomBehaviorRef.current = zoomBehavior;
5499
+ svg.call(zoomBehavior);
5500
+ var previousTransform = updateTransformToViewport(persistedZoomTransformRef.current, width, height);
5501
+ var hasPersistedTransform = previousTransform.k > 1 || Math.abs(previousTransform.x) > 1e-3 || Math.abs(previousTransform.y) > 1e-3;
5502
+ // Re-apply the previous transform after rebuilding the SVG so resize
5503
+ // operations preserve the current zoom level/pan offset.
5504
+ if (hasPersistedTransform) {
5505
+ isApplyingSharedZoomRef.current = true;
5506
+ svg.call(zoomBehavior.transform, previousTransform);
5507
+ isApplyingSharedZoomRef.current = false;
5508
+ }
5509
+ // Keep shared zoom state aligned with the effective transform that was
5510
+ // restored after resize so companion panels (WindBarbs) do not wait for the
5511
+ // next interactive zoom event.
5512
+ publishVerticalZoom(scopedGraphId, previousTransform.k, previousTransform.y);
5513
+ previousDiagramTypeRef.current = diagramType;
5514
+ // eslint-disable-next-line consistent-return
4909
5515
  return function () {
4910
- d3.select(svgRefCur).selectAll('*').remove();
5516
+ // Read the transform directly from d3's internal zoom state before this
5517
+ // chart instance is torn down, then reuse it on the next mount pass.
5518
+ var zoomState = zoomTarget.__zoom;
5519
+ if (zoomState) {
5520
+ persistedZoomTransformRef.current = zoomState;
5521
+ }
5522
+ svg.on('zoom', null);
5523
+ zoomBehaviorRef.current = null;
5524
+ zoomTargetRef.current = null;
5525
+ root.selectAll('*').remove();
4911
5526
  };
4912
- }, [data, diagramType, width, height, scopedGraphId, margin]);
5527
+ }, [data, diagramType, width, height, scopedGraphId, margin, publishVerticalZoom, resetVerticalZoom]);
4913
5528
  useEffect(function () {
4914
5529
  var _svgRectRef$current;
4915
5530
  if (!showTooltip || !svgRef.current) {
@@ -4925,477 +5540,230 @@ var Soundings = function Soundings(_ref) {
4925
5540
  dewpointRef.current && (dewpointRef.current.style.display = 'none');
4926
5541
  return;
4927
5542
  }
4928
- if (!yScaleRef.current || !marginRef.current || !tooltipDataRef.current) {
4929
- return;
4930
- }
4931
- // Synthetic event with shared clientY
4932
- var synthetic = {
4933
- clientX: rect.left + rect.width / 2,
4934
- clientY: clientY,
4935
- pageY: clientY + window.scrollY,
4936
- target: svgRef.current,
4937
- currentTarget: svgRef.current
4938
- };
4939
- var pressureRefd3 = d3.select(pressureRef.current);
4940
- var tempRefd3 = d3.select(tempRef.current);
4941
- var dewpointRefd3 = d3.select(dewpointRef.current);
4942
- var minT = diagramType === 'EMAGRAM' ? minEmagramTemperature : minTemperature;
4943
- var maxT = maxTemperature;
4944
- var _createScales2 = createScales$1(diagramType, width, height, minPressure, maxPressure, minT, maxT),
4945
- yScale = _createScales2[1];
4946
- drawSoundingsTooltip({
4947
- event: synthetic,
4948
- yScale: yScale,
4949
- svgRectangle: rect,
4950
- margin: marginRef.current,
4951
- width: widthRef.current,
4952
- pressureRefd3: pressureRefd3,
4953
- tempRefd3: tempRefd3,
4954
- dewpointRefd3: dewpointRefd3,
4955
- tooltipData: tooltipDataRef.current,
4956
- diagramType: diagramType
4957
- });
4958
- }, [clientY, showTooltip, diagramType, data, width, height]);
4959
- return jsxs(Box, {
4960
- ref: canvasRef,
4961
- sx: {
4962
- position: 'relative'
4963
- },
4964
- children: [jsx("svg", {
4965
- ref: svgRef
4966
- }), jsx(Box, {
4967
- "data-testid": "pressure-tooltip",
4968
- ref: pressureRef,
4969
- sx: Object.assign({}, soundingsStyle$1.tooltip, soundingsStyle$1.pressure)
4970
- }), jsx(Box, {
4971
- "data-testid": "temperature-tooltip",
4972
- ref: tempRef,
4973
- sx: Object.assign({}, soundingsStyle$1.tooltip, soundingsStyle$1.temperature)
4974
- }), jsx(Box, {
4975
- "data-testid": "dewpoint-tooltip",
4976
- ref: dewpointRef,
4977
- sx: Object.assign({}, soundingsStyle$1.tooltip, soundingsStyle$1.dewPoint)
4978
- })]
4979
- });
4980
- };
4981
-
4982
- var TIMESLIDER_HEIGHT = 135;
4983
- var LABEL_MIN_WIDTH = 48;
4984
- var useMaxLabelCount = function useMaxLabelCount(ref) {
4985
- var _React$useState = React.useState(0),
4986
- width = _React$useState[0],
4987
- setWidth = _React$useState[1];
4988
- React.useLayoutEffect(function () {
4989
- var resizeObserver;
4990
- if (ref.current) {
4991
- resizeObserver = new ResizeObserver(function (entry) {
4992
- var _entry$0$target$getBo = entry[0].target.getBoundingClientRect(),
4993
- width = _entry$0$target$getBo.width;
4994
- setWidth(width);
4995
- });
4996
- resizeObserver.observe(ref.current);
4997
- }
4998
- return function () {
4999
- if (resizeObserver) {
5000
- resizeObserver.disconnect();
5001
- }
5002
- };
5003
- }, [ref]);
5004
- var maxLabelCount = Math.max(1, Math.floor(width / LABEL_MIN_WIDTH));
5005
- return maxLabelCount;
5006
- };
5007
- var shouldHideLabel = function shouldHideLabel(nItems, index, maxLabelCount) {
5008
- var nthLabelsToShow = Math.ceil(nItems / maxLabelCount);
5009
- return nItems > maxLabelCount &&
5010
- // hide labels if too many items
5011
- index !== nItems - 1 && (
5012
- // not last item (always shown)
5013
- // only show every nth label, skip label within last n/2 items so end doesn't get cluttered
5014
- index % nthLabelsToShow !== 0 || nItems - 1 - index < nthLabelsToShow / 2);
5015
- };
5016
- var SoundingsTimeSlider = function SoundingsTimeSlider(_ref) {
5017
- var timesteps = _ref.timesteps,
5018
- selectedTime = _ref.selectedTime,
5019
- updateSelectedTime = _ref.updateSelectedTime;
5020
- var sliderRef = React.useRef(null);
5021
- var maxLabelCount = useMaxLabelCount(sliderRef);
5022
- var _useSoundingsTranslat = useSoundingsTranslation(),
5023
- i18n = _useSoundingsTranslat.i18n;
5024
- var marks = timesteps.filter(function (date) {
5025
- return dateUtils.isValidIsoDateString(date);
5026
- }).map(function (date, index, arr) {
5027
- return {
5028
- value: dateUtils.unix(dateUtils.getUtcFromString(date)),
5029
- label: shouldHideLabel(arr.length, index, maxLabelCount) ? '' : dateUtils.dateToString(dateUtils.getUtcFromString(date), 'iii d. HH:mm', true, i18n.language)
5030
- };
5031
- });
5032
- var currentTimeIndex = React.useMemo(function () {
5033
- if (timesteps.length === 0) {
5034
- return -1;
5035
- }
5036
- var selectedUtc = dateUtils.getUtcFromString(selectedTime);
5037
- if (!selectedUtc) {
5038
- return 0;
5039
- }
5040
- var selectedUnix = dateUtils.unix(selectedUtc);
5041
- var indexByExactTimestamp = timesteps.findIndex(function (time) {
5042
- var utcTime = dateUtils.getUtcFromString(time);
5043
- if (!utcTime) {
5044
- return false;
5045
- }
5046
- return dateUtils.unix(utcTime) === selectedUnix;
5047
- });
5048
- if (indexByExactTimestamp !== -1) {
5049
- return indexByExactTimestamp;
5050
- }
5051
- var indexByExactString = timesteps.findIndex(function (time) {
5052
- return time === selectedTime;
5053
- });
5054
- if (indexByExactString !== -1) {
5055
- return indexByExactString;
5056
- }
5057
- // If selected time is not a sounding timestep, anchor on the nearest timestep
5058
- // so forward/backward navigation continues from the expected point in time.
5059
- return timesteps.reduce(function (nearestIndex, time, index) {
5060
- var utcTime = dateUtils.getUtcFromString(time);
5061
- if (!utcTime) {
5062
- return nearestIndex;
5063
- }
5064
- var currentDiff = Math.abs(dateUtils.unix(utcTime) - selectedUnix);
5065
- var nearestUtc = dateUtils.getUtcFromString(timesteps[nearestIndex]);
5066
- if (!nearestUtc) {
5067
- return index;
5068
- }
5069
- var nearestDiff = Math.abs(dateUtils.unix(nearestUtc) - selectedUnix);
5070
- return currentDiff < nearestDiff ? index : nearestIndex;
5071
- }, 0);
5072
- }, [selectedTime, timesteps]);
5073
- var onStepBackward = React.useCallback(function () {
5074
- var precedingIndex = Math.max(currentTimeIndex - 1, 0);
5075
- updateSelectedTime(timesteps[precedingIndex]);
5076
- }, [currentTimeIndex, updateSelectedTime, timesteps]);
5077
- var onStepForward = React.useCallback(function () {
5078
- var followingIndex = Math.min(currentTimeIndex + 1, timesteps.length - 1);
5079
- updateSelectedTime(timesteps[followingIndex]);
5080
- }, [currentTimeIndex, updateSelectedTime, timesteps]);
5081
- return jsxs("div", {
5082
- ref: sliderRef,
5083
- style: {
5084
- display: 'flex',
5085
- paddingTop: '32px',
5086
- marginRight: '80px',
5087
- marginLeft: '24px',
5088
- height: TIMESLIDER_HEIGHT + "px"
5543
+ if (!yScaleRef.current || !marginRef.current || !tooltipDataRef.current) {
5544
+ return;
5545
+ }
5546
+ // Synthetic event with shared clientY
5547
+ var synthetic = {
5548
+ clientX: rect.left + rect.width / 2,
5549
+ clientY: clientY,
5550
+ pageY: clientY + window.scrollY,
5551
+ target: svgRef.current,
5552
+ currentTarget: svgRef.current
5553
+ };
5554
+ var pressureRefd3 = d3.select(pressureRef.current);
5555
+ var tempRefd3 = d3.select(tempRef.current);
5556
+ var dewpointRefd3 = d3.select(dewpointRef.current);
5557
+ var minT = diagramType === 'EMAGRAM' ? minEmagramTemperature : minTemperature;
5558
+ var maxT = maxTemperature;
5559
+ var _createScales2 = createScales$1(diagramType, width, height, minPressure, maxPressure, minT, maxT),
5560
+ yScale = _createScales2[1];
5561
+ var pointerY = clientY - rect.top - marginRef.current.top;
5562
+ var unscaledY = (pointerY - verticalZoom.y) / verticalZoom.scale;
5563
+ var hoveredPressure = extractPressureFromY(diagramType, unscaledY, yScale);
5564
+ drawSoundingsTooltip({
5565
+ event: synthetic,
5566
+ svgRectangle: rect,
5567
+ margin: marginRef.current,
5568
+ width: widthRef.current,
5569
+ hoveredPressure: hoveredPressure,
5570
+ pressureRefd3: pressureRefd3,
5571
+ tempRefd3: tempRefd3,
5572
+ dewpointRefd3: dewpointRefd3,
5573
+ tooltipData: tooltipDataRef.current,
5574
+ diagramType: diagramType
5575
+ });
5576
+ }, [clientY, showTooltip, diagramType, data, width, height, verticalZoom.scale, verticalZoom.y]);
5577
+ return jsxs(Box, {
5578
+ ref: canvasRef,
5579
+ sx: {
5580
+ position: 'relative'
5089
5581
  },
5090
- children: [jsxs("div", {
5091
- style: {
5092
- minWidth: '60px',
5093
- marginTop: '2px',
5094
- display: 'flex',
5095
- gap: '4px'
5096
- },
5097
- children: [jsx(BackwardForwardStepButton, {
5098
- isDisabled: marks.length === 0,
5099
- isForwardStep: false,
5100
- onClickBFButton: onStepBackward
5101
- }), jsx(BackwardForwardStepButton, {
5102
- isDisabled: marks.length === 0,
5103
- isForwardStep: true,
5104
- onClickBFButton: onStepForward
5105
- })]
5106
- }), marks.length === 0 ? jsx(CustomSlider, {
5107
- disabled: true,
5108
- max: 1,
5109
- min: 0,
5110
- orientation: "horizontal",
5111
- value: 0.5
5112
- }) : jsx(CustomSlider, {
5113
- extraStyles: {
5114
- margin: '0px 20px 0px 20px',
5115
- '.MuiSlider-markLabel': {
5116
- transform: 'translate(-90%, 20px) rotate(-45deg)',
5117
- transformOrigin: 'top center'
5118
- }
5119
- },
5120
- marks: marks,
5121
- max: marks[marks.length - 1].value,
5122
- min: marks[0].value,
5123
- onChange: function onChange(_, value) {
5124
- updateSelectedTime(dateUtils.fromUnix(value).toISOString());
5125
- },
5126
- orientation: "horizontal",
5127
- slotProps: {
5128
- // Workaround for MUI bug where labels are just draggable area and clicking on one
5129
- // does not necessarily select the described timestep
5130
- markLabel: {
5131
- onPointerDown: function onPointerDown(event) {
5132
- event.preventDefault();
5133
- var markIndex = event.currentTarget.getAttribute('data-index');
5134
- var mark = marks[Number(markIndex)];
5135
- var value = mark.value;
5136
- updateSelectedTime(dateUtils.fromUnix(value).toISOString());
5137
- }
5138
- }
5139
- },
5140
- step: null,
5141
- value: dateUtils.unix(dateUtils.getUtcFromString(selectedTime)),
5142
- valueLabelDisplay: "on",
5143
- valueLabelFormat: function valueLabelFormat(value) {
5144
- return dateUtils.dateToString(dateUtils.fromUnix(value), "yyyy iii dd MMM HH:mm 'UTC'", true, i18n.language);
5145
- }
5582
+ children: [jsx("svg", {
5583
+ ref: svgRef
5584
+ }), jsx(Box, {
5585
+ "data-testid": "pressure-tooltip",
5586
+ ref: pressureRef,
5587
+ sx: Object.assign({}, soundingsStyle$1.tooltip, soundingsStyle$1.pressure)
5588
+ }), jsx(Box, {
5589
+ "data-testid": "temperature-tooltip",
5590
+ ref: tempRef,
5591
+ sx: Object.assign({}, soundingsStyle$1.tooltip, soundingsStyle$1.temperature)
5592
+ }), jsx(Box, {
5593
+ "data-testid": "dewpoint-tooltip",
5594
+ ref: dewpointRef,
5595
+ sx: Object.assign({}, soundingsStyle$1.tooltip, soundingsStyle$1.dewPoint)
5146
5596
  })]
5147
5597
  });
5148
5598
  };
5149
5599
 
5150
- /* *
5151
- * Licensed under the Apache License, Version 2.0 (the "License");
5152
- * you may not use this file except in compliance with the License.
5153
- * You may obtain a copy of the License at
5154
- *
5155
- * http://www.apache.org/licenses/LICENSE-2.0
5156
- *
5157
- * Unless required by applicable law or agreed to in writing, software
5158
- * distributed under the License is distributed on an "AS IS" BASIS,
5159
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
5160
- * See the License for the specific language governing permissions and
5161
- * limitations under the License.
5162
- *
5163
- * Copyright 2025 - Koninklijk Nederlands Meteorologisch Instituut (KNMI)
5164
- * Copyright 2025 - Finnish Meteorological Institute (FMI)
5165
- * Copyright 2025 - The Norwegian Meteorological Institute (MET Norway)
5166
- * */
5167
- var OFFSET_HEIGHT = 100; // Space reserved in layout
5168
- var useSoundingsDiagramSize = function useSoundingsDiagramSize(ref) {
5600
+ var TIMESLIDER_HEIGHT = 135;
5601
+ var LABEL_MIN_WIDTH = 48;
5602
+ var useMaxLabelCount = function useMaxLabelCount(ref) {
5169
5603
  var _React$useState = React.useState(0),
5170
- size = _React$useState[0],
5171
- setSize = _React$useState[1];
5604
+ width = _React$useState[0],
5605
+ setWidth = _React$useState[1];
5172
5606
  React.useLayoutEffect(function () {
5173
- var container = ref.current;
5174
- if (!container) {
5175
- return undefined;
5176
- }
5177
- var resizeObserver = new ResizeObserver(function (entries) {
5178
- if (!entries.length) {
5179
- return;
5180
- }
5181
- var _entries$0$contentRec = entries[0].contentRect,
5182
- width = _entries$0$contentRec.width,
5183
- height = _entries$0$contentRec.height;
5184
- var availableHeight = height - TIMESLIDER_HEIGHT + OFFSET_HEIGHT;
5185
- var nextSize = Math.floor(Math.min(width, availableHeight));
5186
- setSize(nextSize > 0 ? nextSize : 0);
5187
- });
5188
- resizeObserver.observe(container);
5189
- return function () {
5190
- resizeObserver.disconnect();
5191
- };
5192
- }, [ref]);
5193
- return size;
5194
- };
5195
-
5196
- /* *
5197
- * Licensed under the Apache License, Version 2.0 (the "License");
5198
- * you may not use this file except in compliance with the License.
5199
- * You may obtain a copy of the License at
5200
- *
5201
- * http://www.apache.org/licenses/LICENSE-2.0
5202
- *
5203
- * Unless required by applicable law or agreed to in writing, software
5204
- * distributed under the License is distributed on an "AS IS" BASIS,
5205
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
5206
- * See the License for the specific language governing permissions and
5207
- * limitations under the License.
5208
- *
5209
- * Copyright 2025 - Koninklijk Nederlands Meteorologisch Instituut (KNMI)
5210
- * Copyright 2025 - Finnish Meteorological Institute (FMI)
5211
- * Copyright 2025 - The Norwegian Meteorological Institute (MET Norway)
5212
- * */
5213
- var animateResizeTransform = function animateResizeTransform(svg, marginLocal, innerHeight, prevInnerHeightRef) {
5214
- var prevInnerHeight = typeof prevInnerHeightRef.current === 'number' ? prevInnerHeightRef.current : 0;
5215
- var scaleYStart = prevInnerHeight > 0 && innerHeight > 0 ? prevInnerHeight / innerHeight : 0;
5216
- svg.attr('transform', "translate(" + marginLocal.left + "," + marginLocal.top + ") scale(1," + scaleYStart + ")").transition().duration(300).attr('transform', "translate(" + marginLocal.left + "," + marginLocal.top + ") scale(1,1)");
5217
- };
5218
-
5219
- /* *
5220
- * Licensed under the Apache License, Version 2.0 (the "License");
5221
- * you may not use this file except in compliance with the License.
5222
- * You may obtain a copy of the License at
5223
- *
5224
- * http://www.apache.org/licenses/LICENSE-2.0
5225
- *
5226
- * Unless required by applicable law or agreed to in writing, software
5227
- * distributed under the License is distributed on an "AS IS" BASIS,
5228
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
5229
- * See the License for the specific language governing permissions and
5230
- * limitations under the License.
5231
- *
5232
- * Copyright 2025 - Koninklijk Nederlands Meteorologisch Instituut (KNMI)
5233
- * Copyright 2025 - Finnish Meteorological Institute (FMI)
5234
- * Copyright 2025 - The Norwegian Meteorological Institute (MET Norway)
5235
- * */
5236
- var windSymbolDefinitions = {
5237
- calm: "<svg width=\"50\" height=\"50\" viewBox=\"0 0 50 50\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\">\n<path d=\"M25 45C36.0456 45 45 36.0456 45 25C45 13.9543 36.0456 5 25 5C13.9543 5 5 13.9543 5 25C5 36.0456 13.9543 45 25 45Z\" stroke=\"black\" stroke-width=\"1.8\"/>\n</svg>\n",
5238
- lightAir: "<svg width=\"14\" height=\"48\" viewBox=\"0 0 14 48\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\">\n<path d=\"M7.22656 4.89307V48.0001M7.22656 43.0262V44.6842\" stroke=\"black\" stroke-width=\"1.8\"/>\n</svg>\n",
5239
- fiveKnots: "<svg width=\"15\" height=\"49\" viewBox=\"0 0 15 49\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\">\n<path d=\"M8.26953 5.57324V48.608M8.26953 40.5757L0.269531 42.2309\" stroke=\"black\" stroke-width=\"1.8\"/>\n</svg>",
5240
- tenKnots: "<svg width=\"24\" height=\"49\" viewBox=\"0 0 24 49\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\">\n<path d=\"M17.0781 4.64502V44.9031L1.07812 47.9999\" stroke=\"black\" stroke-width=\"1.8\"/>\n</svg>",
5241
- fifteenKnots: "<svg width=\"23\" height=\"50\" viewBox=\"0 0 23 50\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\">\n<path d=\"M16.4727 5.55469V45.8128L0.472656 48.9095M16.4727 38.0708L9.61551 39.6192\" stroke=\"black\" stroke-width=\"1.8\"/>\n</svg>\n",
5242
- twentyKnots: "<svg width=\"24\" height=\"49\" viewBox=\"0 0 24 49\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\">\n<path d=\"M16.8555 4.71924V44.9773L0.855469 48.0741M16.8555 38.4093L0.855469 41.5061\" stroke=\"black\" stroke-width=\"1.8\"/>\n</svg>",
5243
- twentyFiveKnots: "<svg width=\"23\" height=\"51\" viewBox=\"0 0 23 51\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\">\n<path d=\"M16.2383 4.94434V46.1541L0.238281 49.3241M16.2383 40.3787L0.238281 43.5486M16.2383 33.9801L9.38114 35.5651\" stroke=\"black\" stroke-width=\"1.8\"/>\n</svg>",
5244
- thirtyKnots: "<svg width=\"23\" height=\"50\" viewBox=\"0 0 23 50\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\">\n<path d=\"M16.1914 5.64502V45.9031L0.191406 48.9999M16.1914 40.3459L0.191406 43.4427M16.1914 34.9407L0.191406 38.0375\" stroke=\"black\" stroke-width=\"1.8\"/>\n</svg>",
5245
- thirtyFiveKnots: "<svg width=\"19\" height=\"50\" viewBox=\"0 0 19 50\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\">\n<path d=\"M12.0028 4.96729V45.2253L1.16406 48.3221M12.0028 39.6443L1.16406 42.7411M12.0028 34.4793L1.16406 37.576M12.0028 29.2735L7.35761 30.8219\" stroke=\"black\" stroke-width=\"1.8\"/>\n</svg>",
5246
- fortyKnots: "<svg width=\"24\" height=\"50\" viewBox=\"0 0 24 50\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\">\n<path d=\"M16.5391 5.40311V45.6612L0.539062 48.7579M16.5391 40.4525L0.539062 43.5493M16.5391 35.0449L0.539062 38.1417M16.5391 30.2009L0.539062 33.2976\" stroke=\"black\" stroke-width=\"1.8\"/>\n</svg>",
5247
- fortyFiveKnots: "<svg width=\"23\" height=\"50\" viewBox=\"0 0 23 50\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\">\n<path d=\"M16.4492 5.4043V45.6624L0.449219 48.7591M16.4492 40.1513L0.449219 43.2481M16.4492 34.7124L0.449219 37.8091M16.4492 29.2013L0.449219 32.2981M16.4492 24.3066L9.59208 25.855\" stroke=\"black\" stroke-width=\"1.8\"/>\n</svg>",
5248
- fiftyKnots: "<svg width=\"24\" height=\"49\" viewBox=\"0 0 24 49\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\">\n<path d=\"M16.9062 5.27002V45.5281L0.90625 48.6249L16.9062 37.7861\" fill=\"black\"/>\n<path d=\"M16.9053 5.27002V45.5281L4.35132 47.2337L16.9053 37.7861\" stroke=\"black\" stroke-width=\"1.8\"/>\n</svg>\n",
5249
- fiftyFiveKnots: "<svg width=\"24\" height=\"49\" viewBox=\"0 0 24 49\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\">\n<path d=\"M16.728 5.55371V45.8118L0.894531 48.9085L16.728 38.0698\" fill=\"black\"/>\n<path d=\"M16.73 5.55371V45.8118L4.33984 47.5245L16.73 38.0698\" stroke=\"black\" stroke-width=\"1.8\"/>\n<path d=\"M16.7292 32.7358C13.6376 33.3406 8.8125 34.2842 8.8125 34.2842\" stroke=\"black\" stroke-width=\"1.8\" stroke-miterlimit=\"16\"/>\n</svg>",
5250
- sixtyKnots: "<svg width=\"23\" height=\"49\" viewBox=\"0 0 23 49\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\">\n<path d=\"M16.2891 5.27051V45.5286L0.289062 48.6253L16.2891 37.7866\" fill=\"black\"/>\n<path d=\"M16.2897 5.27051V45.5286L3.7422 47.2478L16.2897 37.7866\" stroke=\"black\" stroke-width=\"1.8\"/>\n<path d=\"M16.2891 34.6899L0.289062 37.7867\" stroke=\"black\" stroke-width=\"1.8\"/>\n</svg>",
5251
- sixtyFiveKnots: "<svg width=\"24\" height=\"47\" viewBox=\"0 0 24 47\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\">\n<path d=\"M17 3.26807V43.5261L1 46.6229L17 35.7842\" fill=\"black\"/>\n<path d=\"M16.9984 3.26807V43.5261L4.35938 45.2985L16.9984 35.7842\" stroke=\"black\" stroke-width=\"1.8\"/>\n<path d=\"M17 32.0788L1 35.1756M17 26.8862L9.5 28.5\" stroke=\"black\" stroke-width=\"1.8\"/>\n</svg>",
5252
- seventyKnots: "<svg width=\"24\" height=\"47\" viewBox=\"0 0 24 47\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\">\n<path d=\"M17 3.26807V43.5261L1 46.6229L17 35.7842\" fill=\"black\"/>\n<path d=\"M16.9984 3.26807V43.5261L4.35938 45.2985L16.9984 35.7842\" stroke=\"black\" stroke-width=\"1.8\"/>\n<path d=\"M17 32.0788L1 35.1756M17 26.8862L1 29.983\" stroke=\"black\" stroke-width=\"1.8\"/>\n</svg>",
5253
- seventyFiveKnots: "<svg width=\"24\" height=\"47\" viewBox=\"0 0 24 47\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\">\n<path d=\"M17 3.26807V43.5261L1 46.6229L17 35.7842\" fill=\"black\"/>\n<path d=\"M16.9984 3.26807V43.5261L4.35938 45.2985L16.9984 35.7842\" stroke=\"black\" stroke-width=\"1.8\"/>\n<path d=\"M17 32.0788L1 35.1756M17 26.8862L1 29.983\" stroke=\"black\" stroke-width=\"1.8\"/>\n<path d=\"M16.9167 22C13.8251 22.6048 9 23.5484 9 23.5484\" stroke=\"black\" stroke-width=\"1.8\" stroke-miterlimit=\"16\"/>\n</svg>",
5254
- eightyKnots: "<svg width=\"24\" height=\"47\" viewBox=\"0 0 24 47\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\">\n<path d=\"M17 3.26807V43.5261L1 46.6229L17 35.7842\" fill=\"black\"/>\n<path d=\"M16.9984 3.26807V43.5261L4.35938 45.2985L16.9984 35.7842\" stroke=\"black\" stroke-width=\"1.8\"/>\n<path d=\"M17 32.0788L1 35.1756M17 26.8862L1 29.983\" stroke=\"black\" stroke-width=\"1.8\"/>\n<path d=\"M17 22C10.7518 23.1718 1 25 1 25\" stroke=\"black\" stroke-width=\"1.8\" stroke-miterlimit=\"16\"/>\n</svg>",
5255
- eightyFiveKnots: "<svg width=\"24\" height=\"47\" viewBox=\"0 0 24 47\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\">\n<path d=\"M17 3.26807V43.5261L1 46.6229L17 35.7842\" fill=\"black\"/>\n<path d=\"M16.9984 3.26807V43.5261L4.35938 45.2985L16.9984 35.7842\" stroke=\"black\" stroke-width=\"1.8\"/>\n<path d=\"M16.9167 17C13.8251 17.6048 9 18.5484 9 18.5484\" stroke=\"black\" stroke-width=\"1.8\" stroke-miterlimit=\"16\"/>\n<path d=\"M17 32.0788L1 35.1756M17 26.8862L1 29.983\" stroke=\"black\" stroke-width=\"1.8\"/>\n<path d=\"M17 22C10.7518 23.1718 1 25 1 25\" stroke=\"black\" stroke-width=\"1.8\" stroke-miterlimit=\"16\"/>\n</svg>\n",
5256
- ninetyKnots: "<svg width=\"24\" height=\"47\" viewBox=\"0 0 24 47\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\">\n<path d=\"M17 3.26807V43.5261L1 46.6229L17 35.7842\" fill=\"black\"/>\n<path d=\"M16.9984 3.26807V43.5261L4.35938 45.2985L16.9984 35.7842\" stroke=\"black\" stroke-width=\"1.8\"/>\n<path d=\"M17 32.0788L1 35.1756M17 26.8862L1 29.983\" stroke=\"black\" stroke-width=\"1.8\"/>\n<path d=\"M17 22C10.7518 23.1718 1 25 1 25\" stroke=\"black\" stroke-width=\"1.8\" stroke-miterlimit=\"16\"/>\n<path d=\"M17 17C10.7518 18.1718 1 20 1 20\" stroke=\"black\" stroke-width=\"1.8\" stroke-miterlimit=\"16\"/>\n</svg>\n",
5257
- ninetyFiveKnots: "<svg width=\"24\" height=\"47\" viewBox=\"0 0 24 47\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\">\n<path d=\"M17 3.26807V43.5261L1 46.6229L17 35.7842\" fill=\"black\"/>\n<path d=\"M16.9984 3.26807V43.5261L4.35938 45.2985L16.9984 35.7842\" stroke=\"black\" stroke-width=\"1.8\"/>\n<path d=\"M17 32.0788L1 35.1756M17 26.8862L1 29.983\" stroke=\"black\" stroke-width=\"1.8\"/>\n<path d=\"M17 22C10.7518 23.1718 1 25 1 25\" stroke=\"black\" stroke-width=\"1.8\" stroke-miterlimit=\"16\"/>\n<path d=\"M17 17C10.7518 18.1718 1 20 1 20\" stroke=\"black\" stroke-width=\"1.8\" stroke-miterlimit=\"16\"/>\n<path d=\"M16.9167 12C13.8251 12.6048 9 13.5484 9 13.5484\" stroke=\"black\" stroke-width=\"1.8\" stroke-miterlimit=\"16\"/>\n</svg>",
5258
- hundredKnots: "<svg width=\"24\" height=\"47\" viewBox=\"0 0 24 47\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\">\n<path d=\"M17 3.26807V23.3971V43.5261L1 46.6229L17 35.7842\" fill=\"black\"/>\n<path d=\"M16.9984 3.26807V28M16.9984 35.7842L4.35938 45.2985L16.9984 43.5261V35.5M16.9984 28C16.9984 28 5.67406 35.7842 4 37M16.9984 28V29.5M4 37C9.07621 36.4142 16.9984 35.5 16.9984 35.5M4 37C9.07621 34.0711 16.9984 29.5 16.9984 29.5M4 37C9.07682 34.6569 17 31 17 31M4 37C9.07621 35.2426 16.9984 32.5 16.9984 32.5M4 37C9.07621 35.8284 16.9984 34 16.9984 34M16.9984 35.5V34M16.9984 29.5V32.5M16.9984 32.5V34\" stroke=\"black\" stroke-width=\"1.8\"/>\n</svg>\n",
5259
- hundredFiveKnots: "<svg width=\"24\" height=\"47\" viewBox=\"0 0 24 47\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\">\n<path d=\"M17 3.26807V23.3971V43.5261L1 46.6229L17 35.7842\" fill=\"black\"/>\n<path d=\"M16.9984 3.26807V28M16.9984 35.7842L4.35938 45.2985L16.9984 43.5261V35.5M16.9984 28C16.9984 28 5.67406 35.7842 4 37M16.9984 28V29.5M4 37C9.07621 36.4142 16.9984 35.5 16.9984 35.5M4 37C9.07621 34.0711 16.9984 29.5 16.9984 29.5M4 37C9.07682 34.6569 17 31 17 31M4 37C9.07621 35.2426 16.9984 32.5 16.9984 32.5M4 37C9.07621 35.8284 16.9984 34 16.9984 34M16.9984 35.5V34M16.9984 29.5V32.5M16.9984 32.5V34\" stroke=\"black\" stroke-width=\"1.8\"/>\n<path d=\"M16.9167 25C13.8251 25.6048 9 26.5484 9 26.5484\" stroke=\"black\" stroke-width=\"1.8\" stroke-miterlimit=\"16\"/>\n</svg>\n",
5260
- hundredTenKnots: "<svg width=\"24\" height=\"47\" viewBox=\"0 0 24 47\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\">\n<path d=\"M17 3.26807V23.3971V43.5261L1 46.6229L17 35.7842\" fill=\"black\"/>\n<path d=\"M16.9984 3.26807V28M16.9984 35.7842L4.35938 45.2985L16.9984 43.5261V35.5M16.9984 28C16.9984 28 5.67406 35.7842 4 37M16.9984 28V29.5M4 37C9.07621 36.4142 16.9984 35.5 16.9984 35.5M4 37C9.07621 34.0711 16.9984 29.5 16.9984 29.5M4 37C9.07682 34.6569 17 31 17 31M4 37C9.07621 35.2426 16.9984 32.5 16.9984 32.5M4 37C9.07621 35.8284 16.9984 34 16.9984 34M16.9984 35.5V34M16.9984 29.5V32.5M16.9984 32.5V34\" stroke=\"black\" stroke-width=\"1.8\"/>\n<path d=\"M17 25C10.7518 26.1718 1 28 1 28\" stroke=\"black\" stroke-width=\"1.8\" stroke-miterlimit=\"16\"/>\n</svg>",
5261
- hundredFifteenKnots: "<svg width=\"24\" height=\"47\" viewBox=\"0 0 24 47\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\">\n<path d=\"M17 3.26807V23.3971V43.5261L1 46.6229L17 35.7842\" fill=\"black\"/>\n<path d=\"M16.9984 3.26807V28M16.9984 35.7842L4.35938 45.2985L16.9984 43.5261V35.5M16.9984 28C16.9984 28 5.67406 35.7842 4 37M16.9984 28V29.5M4 37C9.07621 36.4142 16.9984 35.5 16.9984 35.5M4 37C9.07621 34.0711 16.9984 29.5 16.9984 29.5M4 37C9.07682 34.6569 17 31 17 31M4 37C9.07621 35.2426 16.9984 32.5 16.9984 32.5M4 37C9.07621 35.8284 16.9984 34 16.9984 34M16.9984 35.5V34M16.9984 29.5V32.5M16.9984 32.5V34\" stroke=\"black\" stroke-width=\"1.8\"/>\n<path d=\"M17 25C10.7518 26.1718 1 28 1 28\" stroke=\"black\" stroke-width=\"1.8\" stroke-miterlimit=\"16\"/>\n<path d=\"M16.9167 21C13.8251 21.6048 9 22.5484 9 22.5484\" stroke=\"black\" stroke-width=\"1.8\" stroke-miterlimit=\"16\"/>\n</svg>",
5262
- hundredTwentyKnots: "<svg width=\"24\" height=\"47\" viewBox=\"0 0 24 47\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\">\n<path d=\"M17 3.26807V23.3971V43.5261L1 46.6229L17 35.7842\" fill=\"black\"/>\n<path d=\"M16.9984 3.26807V28M16.9984 35.7842L4.35938 45.2985L16.9984 43.5261V35.5M16.9984 28C16.9984 28 5.67406 35.7842 4 37M16.9984 28V29.5M4 37C9.07621 36.4142 16.9984 35.5 16.9984 35.5M4 37C9.07621 34.0711 16.9984 29.5 16.9984 29.5M4 37C9.07682 34.6569 17 31 17 31M4 37C9.07621 35.2426 16.9984 32.5 16.9984 32.5M4 37C9.07621 35.8284 16.9984 34 16.9984 34M16.9984 35.5V34M16.9984 29.5V32.5M16.9984 32.5V34\" stroke=\"black\" stroke-width=\"1.8\"/>\n<path d=\"M17 25C10.7518 26.1718 1 28 1 28\" stroke=\"black\" stroke-width=\"1.8\" stroke-miterlimit=\"16\"/>\n<path d=\"M17 21C10.7518 22.1718 1 24 1 24\" stroke=\"black\" stroke-width=\"1.8\" stroke-miterlimit=\"16\"/>\n</svg>",
5263
- hundredTwentyFiveKnots: "<svg width=\"24\" height=\"47\" viewBox=\"0 0 24 47\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\">\n<path d=\"M17 3.26807V23.3971V43.5261L1 46.6229L17 35.7842\" fill=\"black\"/>\n<path d=\"M16.9984 3.26807V28M16.9984 35.7842L4.35938 45.2985L16.9984 43.5261V35.5M16.9984 28C16.9984 28 5.67406 35.7842 4 37M16.9984 28V29.5M4 37C9.07621 36.4142 16.9984 35.5 16.9984 35.5M4 37C9.07621 34.0711 16.9984 29.5 16.9984 29.5M4 37C9.07682 34.6569 17 31 17 31M4 37C9.07621 35.2426 16.9984 32.5 16.9984 32.5M4 37C9.07621 35.8284 16.9984 34 16.9984 34M16.9984 35.5V34M16.9984 29.5V32.5M16.9984 32.5V34\" stroke=\"black\" stroke-width=\"1.8\"/>\n<path d=\"M17 25C10.7518 26.1718 1 28 1 28\" stroke=\"black\" stroke-width=\"1.8\" stroke-miterlimit=\"16\"/>\n<path d=\"M17 21C10.7518 22.1718 1 24 1 24\" stroke=\"black\" stroke-width=\"1.8\" stroke-miterlimit=\"16\"/>\n<path d=\"M16.9167 17C13.8251 17.6048 9 18.5484 9 18.5484\" stroke=\"black\" stroke-width=\"1.8\" stroke-miterlimit=\"16\"/>\n</svg>\n",
5264
- hundredThirtyKnots: "<svg width=\"24\" height=\"47\" viewBox=\"0 0 24 47\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\">\n<path d=\"M17 3.26807V23.3971V43.5261L1 46.6229L17 35.7842\" fill=\"black\"/>\n<path d=\"M16.9984 3.26807V28M16.9984 35.7842L4.35938 45.2985L16.9984 43.5261V35.5M16.9984 28C16.9984 28 5.67406 35.7842 4 37M16.9984 28V29.5M4 37C9.07621 36.4142 16.9984 35.5 16.9984 35.5M4 37C9.07621 34.0711 16.9984 29.5 16.9984 29.5M4 37C9.07682 34.6569 17 31 17 31M4 37C9.07621 35.2426 16.9984 32.5 16.9984 32.5M4 37C9.07621 35.8284 16.9984 34 16.9984 34M16.9984 35.5V34M16.9984 29.5V32.5M16.9984 32.5V34\" stroke=\"black\" stroke-width=\"1.8\"/>\n<path d=\"M17 25C10.7518 26.1718 1 28 1 28\" stroke=\"black\" stroke-width=\"1.8\" stroke-miterlimit=\"16\"/>\n<path d=\"M17 21C10.7518 22.1718 1 24 1 24\" stroke=\"black\" stroke-width=\"1.8\" stroke-miterlimit=\"16\"/>\n<path d=\"M17 17C10.7518 18.1718 1 20 1 20\" stroke=\"black\" stroke-width=\"1.8\" stroke-miterlimit=\"16\"/>\n</svg>",
5265
- hundredThirtyFiveKnots: "<svg width=\"24\" height=\"47\" viewBox=\"0 0 24 47\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\">\n<path d=\"M17 3.26807V23.3971V43.5261L1 46.6229L17 35.7842\" fill=\"black\"/>\n<path d=\"M16.9984 3.26807V28M16.9984 35.7842L4.35938 45.2985L16.9984 43.5261V35.5M16.9984 28C16.9984 28 5.67406 35.7842 4 37M16.9984 28V29.5M4 37C9.07621 36.4142 16.9984 35.5 16.9984 35.5M4 37C9.07621 34.0711 16.9984 29.5 16.9984 29.5M4 37C9.07682 34.6569 17 31 17 31M4 37C9.07621 35.2426 16.9984 32.5 16.9984 32.5M4 37C9.07621 35.8284 16.9984 34 16.9984 34M16.9984 35.5V34M16.9984 29.5V32.5M16.9984 32.5V34\" stroke=\"black\" stroke-width=\"1.8\"/>\n<path d=\"M17 25C10.7518 26.1718 1 28 1 28\" stroke=\"black\" stroke-width=\"1.8\" stroke-miterlimit=\"16\"/>\n<path d=\"M17 21C10.7518 22.1718 1 24 1 24\" stroke=\"black\" stroke-width=\"1.8\" stroke-miterlimit=\"16\"/>\n<path d=\"M17 17C10.7518 18.1718 1 20 1 20\" stroke=\"black\" stroke-width=\"1.8\" stroke-miterlimit=\"16\"/>\n<path d=\"M16.9167 13C13.8251 13.6048 9 14.5484 9 14.5484\" stroke=\"black\" stroke-width=\"1.8\" stroke-miterlimit=\"16\"/>\n</svg>",
5266
- hundredFortyKnots: "<svg width=\"24\" height=\"47\" viewBox=\"0 0 24 47\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\">\n<path d=\"M17 3.26807V23.3971V43.5261L1 46.6229L17 35.7842\" fill=\"black\"/>\n<path d=\"M16.9984 3.26807V28M16.9984 35.7842L4.35938 45.2985L16.9984 43.5261V35.5M16.9984 28C16.9984 28 5.67406 35.7842 4 37M16.9984 28V29.5M4 37C9.07621 36.4142 16.9984 35.5 16.9984 35.5M4 37C9.07621 34.0711 16.9984 29.5 16.9984 29.5M4 37C9.07682 34.6569 17 31 17 31M4 37C9.07621 35.2426 16.9984 32.5 16.9984 32.5M4 37C9.07621 35.8284 16.9984 34 16.9984 34M16.9984 35.5V34M16.9984 29.5V32.5M16.9984 32.5V34\" stroke=\"black\" stroke-width=\"1.8\"/>\n<path d=\"M17 25C10.7518 26.1718 1 28 1 28\" stroke=\"black\" stroke-width=\"1.8\" stroke-miterlimit=\"16\"/>\n<path d=\"M17 21C10.7518 22.1718 1 24 1 24\" stroke=\"black\" stroke-width=\"1.8\" stroke-miterlimit=\"16\"/>\n<path d=\"M17 17C10.7518 18.1718 1 20 1 20\" stroke=\"black\" stroke-width=\"1.8\" stroke-miterlimit=\"16\"/>\n<path d=\"M17 13C10.7518 14.1718 1 16 1 16\" stroke=\"black\" stroke-width=\"1.8\" stroke-miterlimit=\"16\"/>\n</svg>",
5267
- hundredFortyFiveKnots: "<svg width=\"24\" height=\"49\" viewBox=\"0 0 24 49\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\">\n<path d=\"M17 5.26807V25.3971V45.5261L1 48.6229L17 37.7842\" fill=\"black\"/>\n<path d=\"M16.9984 5.26807V30M16.9984 37.7842L4.35938 47.2985L16.9984 45.5261V37.5M16.9984 30C16.9984 30 5.67406 37.7842 4 39M16.9984 30V31.5M4 39C9.07621 38.4142 16.9984 37.5 16.9984 37.5M4 39C9.07621 36.0711 16.9984 31.5 16.9984 31.5M4 39C9.07682 36.6569 17 33 17 33M4 39C9.07621 37.2426 16.9984 34.5 16.9984 34.5M4 39C9.07621 37.8284 16.9984 36 16.9984 36M16.9984 37.5V36M16.9984 31.5V34.5M16.9984 34.5V36\" stroke=\"black\" stroke-width=\"1.8\"/>\n<path d=\"M16.9167 12C13.8251 12.6048 9 13.5484 9 13.5484\" stroke=\"black\" stroke-width=\"1.8\" stroke-miterlimit=\"16\"/>\n<path d=\"M17 27C10.7518 28.1718 1 30 1 30\" stroke=\"black\" stroke-width=\"1.8\" stroke-miterlimit=\"16\"/>\n<path d=\"M17 23C10.7518 24.1718 1 26 1 26\" stroke=\"black\" stroke-width=\"1.8\" stroke-miterlimit=\"16\"/>\n<path d=\"M17 19C10.7518 20.1718 1 22 1 22\" stroke=\"black\" stroke-width=\"1.8\" stroke-miterlimit=\"16\"/>\n<path d=\"M17 15C10.7518 16.1718 1 18 1 18\" stroke=\"black\" stroke-width=\"1.8\" stroke-miterlimit=\"16\"/>\n</svg>",
5268
- hundredFiftyKnots: "<svg width=\"24\" height=\"47\" viewBox=\"0 0 24 47\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\">\n<path d=\"M17 3.26807V23.3971V43.5261L1 46.6229L17 35.7842\" fill=\"black\"/>\n<path d=\"M16.9984 3.26807V19M16.9984 35.7842L4.35938 45.2985L16.9984 43.5261V35.5M16.9984 28C16.9984 28 5.67406 35.7842 4 37M16.9984 28V29.5M16.9984 28V26.5M4 37C9.07621 36.4142 16.9984 35.5 16.9984 35.5M4 37C9.07621 34.0711 16.9984 29.5 16.9984 29.5M4 37C9.07682 34.6569 17 31 17 31M4 37C9.07621 35.2426 16.9984 32.5 16.9984 32.5M4 37C9.07621 35.8284 16.9984 34 16.9984 34M16.9984 35.5V34M16.9984 29.5V32.5M16.9984 32.5V34M16.9984 19C16.9984 19 8 25.5 4 29M16.9984 19V20.5M4 29C9.07621 28.0237 16.9984 26.5 16.9984 26.5M4 29C9.07621 25.6805 16.9984 20.5 16.9984 20.5M4 29C9.07621 26.4616 16.9984 22.5 16.9984 22.5M4 29C9.07621 27.0474 16.9984 24 16.9984 24M4 29C9.07621 27.5355 16.9984 25.25 16.9984 25.25M16.9984 26.5V25.25M16.9984 20.5V22.5M16.9984 22.5V24M16.9984 24V25.25\" stroke=\"black\" stroke-width=\"1.8\"/>\n</svg>"
5607
+ var resizeObserver;
5608
+ if (ref.current) {
5609
+ resizeObserver = new ResizeObserver(function (entry) {
5610
+ var _entry$0$target$getBo = entry[0].target.getBoundingClientRect(),
5611
+ width = _entry$0$target$getBo.width;
5612
+ setWidth(width);
5613
+ });
5614
+ resizeObserver.observe(ref.current);
5615
+ }
5616
+ return function () {
5617
+ if (resizeObserver) {
5618
+ resizeObserver.disconnect();
5619
+ }
5620
+ };
5621
+ }, [ref]);
5622
+ var maxLabelCount = Math.max(1, Math.floor(width / LABEL_MIN_WIDTH));
5623
+ return maxLabelCount;
5624
+ };
5625
+ var shouldHideLabel = function shouldHideLabel(nItems, index, maxLabelCount) {
5626
+ var nthLabelsToShow = Math.ceil(nItems / maxLabelCount);
5627
+ return nItems > maxLabelCount &&
5628
+ // hide labels if too many items
5629
+ index !== nItems - 1 && (
5630
+ // not last item (always shown)
5631
+ // only show every nth label, skip label within last n/2 items so end doesn't get cluttered
5632
+ index % nthLabelsToShow !== 0 || nItems - 1 - index < nthLabelsToShow / 2);
5633
+ };
5634
+ var SoundingsTimeSlider = function SoundingsTimeSlider(_ref) {
5635
+ var timesteps = _ref.timesteps,
5636
+ selectedTime = _ref.selectedTime,
5637
+ updateSelectedTime = _ref.updateSelectedTime;
5638
+ var sliderRef = React.useRef(null);
5639
+ var maxLabelCount = useMaxLabelCount(sliderRef);
5640
+ var _useSoundingsTranslat = useSoundingsTranslation(),
5641
+ i18n = _useSoundingsTranslat.i18n;
5642
+ var marks = timesteps.filter(function (date) {
5643
+ return dateUtils.isValidIsoDateString(date);
5644
+ }).map(function (date, index, arr) {
5645
+ return {
5646
+ value: dateUtils.unix(dateUtils.getUtcFromString(date)),
5647
+ label: shouldHideLabel(arr.length, index, maxLabelCount) ? '' : dateUtils.dateToString(dateUtils.getUtcFromString(date), 'iii d. HH:mm', true, i18n.language)
5648
+ };
5649
+ });
5650
+ var currentTimeIndex = React.useMemo(function () {
5651
+ if (timesteps.length === 0) {
5652
+ return -1;
5653
+ }
5654
+ var selectedUtc = dateUtils.getUtcFromString(selectedTime);
5655
+ if (!selectedUtc) {
5656
+ return 0;
5657
+ }
5658
+ var selectedUnix = dateUtils.unix(selectedUtc);
5659
+ var indexByExactTimestamp = timesteps.findIndex(function (time) {
5660
+ var utcTime = dateUtils.getUtcFromString(time);
5661
+ if (!utcTime) {
5662
+ return false;
5663
+ }
5664
+ return dateUtils.unix(utcTime) === selectedUnix;
5665
+ });
5666
+ if (indexByExactTimestamp !== -1) {
5667
+ return indexByExactTimestamp;
5668
+ }
5669
+ var indexByExactString = timesteps.findIndex(function (time) {
5670
+ return time === selectedTime;
5671
+ });
5672
+ if (indexByExactString !== -1) {
5673
+ return indexByExactString;
5674
+ }
5675
+ // If selected time is not a sounding timestep, anchor on the nearest timestep
5676
+ // so forward/backward navigation continues from the expected point in time.
5677
+ return timesteps.reduce(function (nearestIndex, time, index) {
5678
+ var utcTime = dateUtils.getUtcFromString(time);
5679
+ if (!utcTime) {
5680
+ return nearestIndex;
5681
+ }
5682
+ var currentDiff = Math.abs(dateUtils.unix(utcTime) - selectedUnix);
5683
+ var nearestUtc = dateUtils.getUtcFromString(timesteps[nearestIndex]);
5684
+ if (!nearestUtc) {
5685
+ return index;
5686
+ }
5687
+ var nearestDiff = Math.abs(dateUtils.unix(nearestUtc) - selectedUnix);
5688
+ return currentDiff < nearestDiff ? index : nearestIndex;
5689
+ }, 0);
5690
+ }, [selectedTime, timesteps]);
5691
+ var onStepBackward = React.useCallback(function () {
5692
+ var precedingIndex = Math.max(currentTimeIndex - 1, 0);
5693
+ updateSelectedTime(timesteps[precedingIndex]);
5694
+ }, [currentTimeIndex, updateSelectedTime, timesteps]);
5695
+ var onStepForward = React.useCallback(function () {
5696
+ var followingIndex = Math.min(currentTimeIndex + 1, timesteps.length - 1);
5697
+ updateSelectedTime(timesteps[followingIndex]);
5698
+ }, [currentTimeIndex, updateSelectedTime, timesteps]);
5699
+ return jsxs("div", {
5700
+ ref: sliderRef,
5701
+ style: {
5702
+ display: 'flex',
5703
+ paddingTop: '32px',
5704
+ marginRight: '80px',
5705
+ marginLeft: '24px',
5706
+ height: TIMESLIDER_HEIGHT + "px"
5707
+ },
5708
+ children: [jsxs("div", {
5709
+ style: {
5710
+ minWidth: '60px',
5711
+ marginTop: '2px',
5712
+ display: 'flex',
5713
+ gap: '4px'
5714
+ },
5715
+ children: [jsx(BackwardForwardStepButton, {
5716
+ isDisabled: marks.length === 0,
5717
+ isForwardStep: false,
5718
+ onClickBFButton: onStepBackward
5719
+ }), jsx(BackwardForwardStepButton, {
5720
+ isDisabled: marks.length === 0,
5721
+ isForwardStep: true,
5722
+ onClickBFButton: onStepForward
5723
+ })]
5724
+ }), marks.length === 0 ? jsx(CustomSlider, {
5725
+ disabled: true,
5726
+ max: 1,
5727
+ min: 0,
5728
+ orientation: "horizontal",
5729
+ value: 0.5
5730
+ }) : jsx(CustomSlider, {
5731
+ extraStyles: {
5732
+ margin: '0px 20px 0px 20px',
5733
+ '.MuiSlider-markLabel': {
5734
+ transform: 'translate(-90%, 20px) rotate(-45deg)',
5735
+ transformOrigin: 'top center'
5736
+ }
5737
+ },
5738
+ marks: marks,
5739
+ max: marks[marks.length - 1].value,
5740
+ min: marks[0].value,
5741
+ onChange: function onChange(_, value) {
5742
+ updateSelectedTime(dateUtils.fromUnix(value).toISOString());
5743
+ },
5744
+ orientation: "horizontal",
5745
+ slotProps: {
5746
+ // Workaround for MUI bug where labels are just draggable area and clicking on one
5747
+ // does not necessarily select the described timestep
5748
+ markLabel: {
5749
+ onPointerDown: function onPointerDown(event) {
5750
+ event.preventDefault();
5751
+ var markIndex = event.currentTarget.getAttribute('data-index');
5752
+ var mark = marks[Number(markIndex)];
5753
+ var value = mark.value;
5754
+ updateSelectedTime(dateUtils.fromUnix(value).toISOString());
5755
+ }
5756
+ }
5757
+ },
5758
+ step: null,
5759
+ value: dateUtils.unix(dateUtils.getUtcFromString(selectedTime)),
5760
+ valueLabelDisplay: "on",
5761
+ valueLabelFormat: function valueLabelFormat(value) {
5762
+ return dateUtils.dateToString(dateUtils.fromUnix(value), "yyyy iii dd MMM HH:mm 'UTC'", true, i18n.language);
5763
+ }
5764
+ })]
5765
+ });
5269
5766
  };
5270
- var windSpeedRanges = [{
5271
- min: 0,
5272
- max: 1,
5273
- symbol: windSymbolDefinitions.calm
5274
- }, {
5275
- min: 1,
5276
- max: 4.5,
5277
- symbol: windSymbolDefinitions.lightAir
5278
- }, {
5279
- min: 4.5,
5280
- max: 7.5,
5281
- symbol: windSymbolDefinitions.fiveKnots
5282
- }, {
5283
- min: 7.5,
5284
- max: 12.5,
5285
- symbol: windSymbolDefinitions.tenKnots
5286
- }, {
5287
- min: 12.5,
5288
- max: 17.5,
5289
- symbol: windSymbolDefinitions.fifteenKnots
5290
- }, {
5291
- min: 17.5,
5292
- max: 22.5,
5293
- symbol: windSymbolDefinitions.twentyKnots
5294
- }, {
5295
- min: 22.5,
5296
- max: 27.5,
5297
- symbol: windSymbolDefinitions.twentyFiveKnots
5298
- }, {
5299
- min: 27.5,
5300
- max: 32.5,
5301
- symbol: windSymbolDefinitions.thirtyKnots
5302
- }, {
5303
- min: 32.5,
5304
- max: 37.5,
5305
- symbol: windSymbolDefinitions.thirtyFiveKnots
5306
- }, {
5307
- min: 37.5,
5308
- max: 42.5,
5309
- symbol: windSymbolDefinitions.fortyKnots
5310
- }, {
5311
- min: 42.5,
5312
- max: 47.5,
5313
- symbol: windSymbolDefinitions.fortyFiveKnots
5314
- }, {
5315
- min: 47.5,
5316
- max: 52.5,
5317
- symbol: windSymbolDefinitions.fiftyKnots
5318
- }, {
5319
- min: 52.5,
5320
- max: 57.5,
5321
- symbol: windSymbolDefinitions.fiftyFiveKnots
5322
- }, {
5323
- min: 57.5,
5324
- max: 62.5,
5325
- symbol: windSymbolDefinitions.sixtyKnots
5326
- }, {
5327
- min: 62.5,
5328
- max: 67.5,
5329
- symbol: windSymbolDefinitions.sixtyFiveKnots
5330
- }, {
5331
- min: 67.5,
5332
- max: 72.5,
5333
- symbol: windSymbolDefinitions.seventyKnots
5334
- }, {
5335
- min: 72.5,
5336
- max: 77.5,
5337
- symbol: windSymbolDefinitions.seventyFiveKnots
5338
- }, {
5339
- min: 77.5,
5340
- max: 82.5,
5341
- symbol: windSymbolDefinitions.eightyKnots
5342
- }, {
5343
- min: 82.5,
5344
- max: 87.5,
5345
- symbol: windSymbolDefinitions.eightyFiveKnots
5346
- }, {
5347
- min: 87.5,
5348
- max: 92.5,
5349
- symbol: windSymbolDefinitions.ninetyKnots
5350
- }, {
5351
- min: 92.5,
5352
- max: 97.5,
5353
- symbol: windSymbolDefinitions.ninetyFiveKnots
5354
- }, {
5355
- min: 97.5,
5356
- max: 102.5,
5357
- symbol: windSymbolDefinitions.hundredKnots
5358
- }, {
5359
- min: 102.5,
5360
- max: 107.5,
5361
- symbol: windSymbolDefinitions.hundredFiveKnots
5362
- }, {
5363
- min: 107.5,
5364
- max: 112.5,
5365
- symbol: windSymbolDefinitions.hundredTenKnots
5366
- }, {
5367
- min: 112.5,
5368
- max: 117.5,
5369
- symbol: windSymbolDefinitions.hundredFifteenKnots
5370
- }, {
5371
- min: 117.5,
5372
- max: 122.5,
5373
- symbol: windSymbolDefinitions.hundredTwentyKnots
5374
- }, {
5375
- min: 122.5,
5376
- max: 127.5,
5377
- symbol: windSymbolDefinitions.hundredTwentyFiveKnots
5378
- }, {
5379
- min: 127.5,
5380
- max: 132.5,
5381
- symbol: windSymbolDefinitions.hundredThirtyKnots
5382
- }, {
5383
- min: 132.5,
5384
- max: 137.5,
5385
- symbol: windSymbolDefinitions.hundredThirtyFiveKnots
5386
- }, {
5387
- min: 137.5,
5388
- max: 142.5,
5389
- symbol: windSymbolDefinitions.hundredFortyKnots
5390
- }, {
5391
- min: 142.5,
5392
- max: 147.5,
5393
- symbol: windSymbolDefinitions.hundredFortyFiveKnots
5394
- }, {
5395
- min: 147.5,
5396
- max: 152,
5397
- symbol: windSymbolDefinitions.hundredFiftyKnots
5398
- }];
5399
5767
 
5400
5768
  /* *
5401
5769
  * Licensed under the Apache License, Version 2.0 (the "License");
@@ -5414,134 +5782,59 @@ var windSpeedRanges = [{
5414
5782
  * Copyright 2025 - Finnish Meteorological Institute (FMI)
5415
5783
  * Copyright 2025 - The Norwegian Meteorological Institute (MET Norway)
5416
5784
  * */
5417
- var extendIsoBar = function extendIsoBar(path, horizontalBarbMargin) {
5418
- var match = /^M([\d.-]+),([\d.-]+)\s*L([\d.-]+),([\d.-]+)/.exec(path);
5419
- if (match) {
5420
- var startX = parseFloat(match[1]);
5421
- var startY = parseFloat(match[2]);
5422
- var endX = parseFloat(match[3]);
5423
- var endY = parseFloat(match[4]);
5424
- return "M" + startX + "," + startY + "L" + (endX + horizontalBarbMargin * 3) + "," + endY;
5425
- }
5426
- return path;
5427
- };
5428
- var extendXAxis = function extendXAxis(path, horizontalBarbMargin) {
5429
- var match = /^M([\d.-]+),([\d.-]+)H([\d.-]+)/.exec(path);
5430
- if (match) {
5431
- var endX = parseFloat(match[3]);
5432
- return path.replace(/H[\d.-]+/, "H" + (endX + horizontalBarbMargin * 3));
5433
- }
5434
- return path;
5435
- };
5436
- var drawWindBarbs = function drawWindBarbs(_ref) {
5437
- var _rootSvg$node;
5438
- var svgRef = _ref.svgRef,
5439
- plotGroup = _ref.plotGroup,
5440
- yScale = _ref.yScale,
5441
- data = _ref.data,
5442
- width = _ref.width,
5443
- margin = _ref.margin,
5444
- _ref$barbColor = _ref.barbColor,
5445
- barbColor = _ref$barbColor === void 0 ? 'black' : _ref$barbColor,
5446
- _ref$columnOffsetX = _ref.columnOffsetX,
5447
- columnOffsetX = _ref$columnOffsetX === void 0 ? 0 : _ref$columnOffsetX;
5448
- var barbsSize = 40;
5449
- var scalingFactor = 0.5;
5450
- var horizontalBarbMargin = barbsSize * scalingFactor + 5;
5451
- var visibleItems = removeItemsExponential(data.reverse()).filter(function (item) {
5452
- return item.pressure >= 150;
5453
- });
5454
- // Measure width and height and make extra room for one column
5455
- // of windbarbs
5456
- var rootSvg = d3.select(svgRef.current);
5457
- var currentWidth = (_rootSvg$node = rootSvg.node()) == null ? void 0 : _rootSvg$node.getBoundingClientRect().width;
5458
- if (!currentWidth) {
5459
- return;
5785
+ var OFFSET_HEIGHT = 100; // Space reserved in layout
5786
+ var useSoundingsDiagramSize = function useSoundingsDiagramSize(ref, useWidth) {
5787
+ if (useWidth === void 0) {
5788
+ useWidth = true;
5460
5789
  }
5461
- // Add extra width to all isobars needed for the windbarbs
5462
- d3.selectAll('path[type="isobar"]').each(function (_d, i, nodes) {
5463
- var path = d3.select(nodes[i]).attr('d');
5464
- var newD = extendIsoBar(path, horizontalBarbMargin);
5465
- d3.select(nodes[i]).attr('d', newD).attr('clip-path', null);
5466
- });
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) {
5469
- var path = d3.select(nodes[i]).attr('d');
5470
- var newD = extendXAxis(path, horizontalBarbMargin);
5471
- d3.select(nodes[i]).attr('d', newD);
5472
- });
5473
- // Expand root SVG width to make room for barbs column at the right
5474
- rootSvg.attr('width', currentWidth + margin.left + horizontalBarbMargin);
5475
- // Place the barbs just to the right of the plotting area.
5476
- // plotGroup's origin is already translated to (margin.left, margin.top),
5477
- // 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)");
5482
- barbGroup.selectAll('g').data(visibleItems).enter().append('g').attr('id', 'wind-barb').each(function handleBarb(data) {
5483
- var group = d3.select(this);
5484
- var windSymbol = getWindSymbol(data.windSpeed);
5485
- if (windSymbol) {
5486
- var _innerGroup$node;
5487
- // Inner group will have rotation and scaling applied to it
5488
- var innerGroup = group.append('g');
5489
- (_innerGroup$node = innerGroup.node()) == null || _innerGroup$node.appendChild(windSymbol);
5490
- // Update stroke color
5491
- innerGroup.selectAll('*').attr('stroke', barbColor);
5492
- // Only update the fill for the paths that already used a fill
5493
- // in their original svg form.
5494
- innerGroup.selectAll('*').each(function (_d, i, nodes) {
5495
- var el = d3.select(nodes[i]);
5496
- var currentFill = el.attr('fill');
5497
- if (currentFill && currentFill !== 'none') {
5498
- el.attr('fill', barbColor);
5499
- }
5500
- });
5501
- var bbox = windSymbol.getBBox();
5502
- // Central point
5503
- var cx = bbox.x + bbox.width / 2;
5504
- var cy = bbox.y + bbox.height / 2;
5505
- var rotation = 180 + data.windDirection;
5506
- 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) + ")");
5510
- // Rotate accordingly, and scale
5511
- innerGroup.attr('transform', "rotate(" + rotation + ") scale(" + scalingFactor + ") translate(" + -cx + ", " + -cy + ")");
5790
+ var _React$useState = React.useState(0),
5791
+ size = _React$useState[0],
5792
+ setSize = _React$useState[1];
5793
+ React.useLayoutEffect(function () {
5794
+ var container = ref.current;
5795
+ if (!container) {
5796
+ return undefined;
5512
5797
  }
5513
- });
5514
- };
5515
- var removeItemsExponential = function removeItemsExponential(array) {
5516
- var base = 1.15;
5517
- var indicesToKeep = [];
5518
- var i = 0;
5519
- var nextIndex = 0;
5520
- while (nextIndex < array.length) {
5521
- nextIndex = Math.floor(Math.pow(base, i));
5522
- indicesToKeep.push(nextIndex);
5523
- i += 1;
5524
- }
5525
- return array.filter(function (_, index) {
5526
- return indicesToKeep.includes(index);
5527
- });
5798
+ var resizeObserver = new ResizeObserver(function (entries) {
5799
+ if (!entries.length) {
5800
+ return;
5801
+ }
5802
+ var _entries$0$contentRec = entries[0].contentRect,
5803
+ width = _entries$0$contentRec.width,
5804
+ height = _entries$0$contentRec.height;
5805
+ var availableHeight = height - TIMESLIDER_HEIGHT + OFFSET_HEIGHT;
5806
+ var nextSize = useWidth ? Math.floor(Math.min(width, availableHeight)) : Math.floor(availableHeight);
5807
+ setSize(nextSize > 0 ? nextSize : 0);
5808
+ });
5809
+ resizeObserver.observe(container);
5810
+ return function () {
5811
+ resizeObserver.disconnect();
5812
+ };
5813
+ }, [ref, useWidth]);
5814
+ return size;
5528
5815
  };
5529
- var getWindSymbol = function getWindSymbol(value) {
5530
- var matchedRange = windSpeedRanges.find(function (_ref2) {
5531
- var min = _ref2.min,
5532
- max = _ref2.max;
5533
- return value >= min && value < max;
5534
- });
5535
- // Out of range
5536
- if (!matchedRange) {
5537
- return null;
5538
- }
5539
- // SVG string to actual SVG element
5540
- var parser = new DOMParser();
5541
- var svgDoc = parser.parseFromString(matchedRange.symbol, 'image/svg+xml');
5542
- var svgNode = svgDoc.documentElement;
5543
- // Return the parsed SVG node
5544
- return svgNode;
5816
+
5817
+ /* *
5818
+ * Licensed under the Apache License, Version 2.0 (the "License");
5819
+ * you may not use this file except in compliance with the License.
5820
+ * You may obtain a copy of the License at
5821
+ *
5822
+ * http://www.apache.org/licenses/LICENSE-2.0
5823
+ *
5824
+ * Unless required by applicable law or agreed to in writing, software
5825
+ * distributed under the License is distributed on an "AS IS" BASIS,
5826
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
5827
+ * See the License for the specific language governing permissions and
5828
+ * limitations under the License.
5829
+ *
5830
+ * Copyright 2025 - Koninklijk Nederlands Meteorologisch Instituut (KNMI)
5831
+ * Copyright 2025 - Finnish Meteorological Institute (FMI)
5832
+ * Copyright 2025 - The Norwegian Meteorological Institute (MET Norway)
5833
+ * */
5834
+ var animateResizeTransform = function animateResizeTransform(svg, marginLocal, innerHeight, prevInnerHeightRef) {
5835
+ var prevInnerHeight = typeof prevInnerHeightRef.current === 'number' ? prevInnerHeightRef.current : 0;
5836
+ var scaleYStart = prevInnerHeight > 0 && innerHeight > 0 ? prevInnerHeight / innerHeight : 0;
5837
+ svg.attr('transform', "translate(" + marginLocal.left + "," + marginLocal.top + ") scale(1," + scaleYStart + ")").transition().duration(300).attr('transform', "translate(" + marginLocal.left + "," + marginLocal.top + ") scale(1,1)");
5545
5838
  };
5546
5839
 
5547
5840
  var shadow = '0 1px 5px 0 rgba(0, 0, 0, 0.2), 0 3px 4px 0 rgba(0, 0, 0, 0.12), 0 2px 4px 0 rgba(0, 0, 0, 0.14)';
@@ -5562,6 +5855,10 @@ var soundingsStyle = {
5562
5855
  }
5563
5856
  };
5564
5857
  var WIND_BARB_OFFSET_X = 35;
5858
+ var getVerticalOnlyTransform = function getVerticalOnlyTransform(scale, yOffset) {
5859
+ // Keep x-axis unchanged, apply shared zoom on y-axis only.
5860
+ return "matrix(1, 0, 0, " + scale + ", 0, " + yOffset + ")";
5861
+ };
5565
5862
  /**
5566
5863
  * Check if the sounding data is effectively empty.
5567
5864
  * Data can be empty in two ways:
@@ -5597,8 +5894,12 @@ var WindBarbs = function WindBarbs(_ref) {
5597
5894
  var widthRef = useRef(0);
5598
5895
  var svgRectRef = useRef(null);
5599
5896
  var tooltipDataRef = useRef(null);
5897
+ // Must point to the inner translated <g> to match Soundings zoom coordinate space.
5898
+ var zoomTargetRef = useRef(null);
5600
5899
  var _useSharedMouseY = useSharedMouseY(),
5601
- clientY = _useSharedMouseY.clientY;
5900
+ clientY = _useSharedMouseY.clientY,
5901
+ verticalZoom = _useSharedMouseY.verticalZoom,
5902
+ resetVerticalZoom = _useSharedMouseY.resetVerticalZoom;
5602
5903
  // Set SVG dimensions (required props; no optional chaining needed)
5603
5904
  var baseWidth = svgDimensions.width,
5604
5905
  baseHeight = svgDimensions.height,
@@ -5606,7 +5907,7 @@ var WindBarbs = function WindBarbs(_ref) {
5606
5907
  var width = baseWidth - (margin.left + margin.right);
5607
5908
  var height = baseHeight - (margin.top + margin.bottom);
5608
5909
  useEffect(function () {
5609
- if (!svgRef.current) {
5910
+ if (!svgRef.current || width <= 0 || height <= 0) {
5610
5911
  return;
5611
5912
  }
5612
5913
  var root = d3.select(svgRef.current);
@@ -5619,8 +5920,9 @@ var WindBarbs = function WindBarbs(_ref) {
5619
5920
  diagramType: diagramType,
5620
5921
  d3GraphId: d3GraphId
5621
5922
  });
5622
- // When Soundings is dragged close, animate the Wind Barbs to resize back to the height where dragging started
5923
+ // When Soundings is dragged closed, reset the zoom level and animate the Wind Barbs to resize back to the height where dragging started
5623
5924
  if (animateResize) {
5925
+ resetVerticalZoom(d3GraphId);
5624
5926
  animateResizeTransform(svg, margin, height, prevInnerHeightRef);
5625
5927
  }
5626
5928
  var svgRectangle = svgRef.current.getBoundingClientRect();
@@ -5658,18 +5960,33 @@ var WindBarbs = function WindBarbs(_ref) {
5658
5960
  width: width,
5659
5961
  margin: margin,
5660
5962
  barbColor: color,
5661
- columnOffsetX: index * WIND_BARB_OFFSET_X
5963
+ columnOffsetX: index * WIND_BARB_OFFSET_X,
5964
+ verticalScale: verticalZoom.scale
5662
5965
  });
5663
5966
  });
5664
5967
  // Clear or update tooltip data based on whether we have actual data
5665
5968
  tooltipDataRef.current = isDataEmpty(data) ? null : constructTooltipData(data);
5666
5969
  // Update previous inner height after drawing so animation uses the prior value
5667
5970
  prevInnerHeightRef.current = height;
5971
+ var zoomTarget = svg.node();
5972
+ if (!zoomTarget) {
5973
+ return;
5974
+ }
5975
+ zoomTargetRef.current = zoomTarget;
5976
+ // Initialize with shared zoom state from Soundings.
5977
+ d3.select(zoomTarget).attr('transform', getVerticalOnlyTransform(verticalZoom.scale, verticalZoom.y));
5668
5978
  // eslint-disable-next-line consistent-return, @typescript-eslint/explicit-function-return-type
5669
5979
  return function () {
5980
+ zoomTargetRef.current = null;
5670
5981
  root.selectAll('*').remove();
5671
5982
  };
5672
- }, [data, diagramType, width, height, d3GraphId, margin, animateResize]);
5983
+ }, [data, diagramType, width, height, d3GraphId, margin, animateResize, verticalZoom.scale, verticalZoom.y, resetVerticalZoom]);
5984
+ useEffect(function () {
5985
+ if (!zoomTargetRef.current) {
5986
+ return;
5987
+ }
5988
+ d3.select(zoomTargetRef.current).attr('transform', getVerticalOnlyTransform(verticalZoom.scale, verticalZoom.y));
5989
+ }, [verticalZoom]);
5673
5990
  useEffect(function () {
5674
5991
  var _svgRectRef$current;
5675
5992
  if (!showTooltip || !svgRef.current || !windRef.current) {
@@ -5697,18 +6014,27 @@ var WindBarbs = function WindBarbs(_ref) {
5697
6014
  target: svgRef.current,
5698
6015
  currentTarget: svgRef.current
5699
6016
  };
6017
+ var pointerY = clientY - rect.top - marginRef.current.top;
6018
+ var unscaledY = (pointerY - verticalZoom.y) / verticalZoom.scale;
6019
+ var hoveredPressure = extractPressureFromY(diagramType, unscaledY, yScaleRef.current);
6020
+ var transformedY = yScaleRef.current(hoveredPressure) * verticalZoom.scale + verticalZoom.y;
6021
+ var pressureLineY = transformedY + marginRef.current.top;
5700
6022
  var windRefd3 = d3.select(windRef.current);
5701
6023
  drawSoundingsTooltip({
5702
6024
  event: synthetic,
5703
- yScale: yScaleRef.current,
5704
6025
  svgRectangle: rect,
5705
6026
  margin: marginRef.current,
5706
6027
  width: widthRef.current,
6028
+ hoveredPressure: hoveredPressure,
5707
6029
  windRefd3: windRefd3,
5708
6030
  tooltipData: tooltipDataRef.current,
5709
6031
  diagramType: diagramType
5710
6032
  });
5711
- }, [clientY, showTooltip, diagramType, data]);
6033
+ var tooltipHeight = windRef.current.getBoundingClientRect().height;
6034
+ var centeredTop = pressureLineY - tooltipHeight / 2;
6035
+ var clampedTop = Math.max(0, Math.min(centeredTop, rect.height - tooltipHeight));
6036
+ windRef.current.style.top = clampedTop + "px";
6037
+ }, [clientY, showTooltip, diagramType, data, verticalZoom.scale, verticalZoom.y]);
5712
6038
  return jsxs(Box, {
5713
6039
  ref: canvasRef,
5714
6040
  sx: {
@@ -5972,32 +6298,42 @@ var SoundingsPanels = function SoundingsPanels(_ref) {
5972
6298
  panelComponent = _ref.panelComponent,
5973
6299
  profileDataPreferences = _ref.profileDataPreferences;
5974
6300
  var soundingsContentRef = React.useRef(null);
6301
+ var windBarbsContentRef = React.useRef(null);
5975
6302
  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];
6303
+ var windBarbsDiagramSize = useSoundingsDiagramSize(windBarbsContentRef, false);
6304
+ var soundingsIsOpen = React.useMemo(function () {
6305
+ var _soundingPanelsInOrde;
6306
+ return ((_soundingPanelsInOrde = soundingPanelsInOrder.find(function (panel) {
6307
+ return panel.id === 'soundings';
6308
+ })) == null ? void 0 : _soundingPanelsInOrde.isOpen) || false;
6309
+ }, [soundingPanelsInOrder]);
6310
+ var _React$useState = React.useState(null),
6311
+ previousHeight = _React$useState[0],
6312
+ setPreviousHeight = _React$useState[1];
5982
6313
  var firstSelectedSourceData = React.useMemo(function () {
5983
6314
  var _data$;
5984
6315
  return (_data$ = data[0]) != null ? _data$ : [];
5985
6316
  }, [data]);
6317
+ var windBarbsHeight = React.useMemo(function () {
6318
+ if (soundingsIsOpen) {
6319
+ return diagramSize;
6320
+ }
6321
+ return previousHeight != null ? previousHeight : windBarbsDiagramSize;
6322
+ }, [diagramSize, previousHeight, soundingsIsOpen, windBarbsDiagramSize]);
5986
6323
  var sizeRef = React.useRef(diagramSize);
5987
6324
  var isResizingRef = React.useRef(false);
5988
6325
  React.useEffect(function () {
5989
6326
  sizeRef.current = diagramSize;
5990
6327
  }, [diagramSize]);
5991
6328
  React.useEffect(function () {
5992
- if (previousHeight == null && diagramSize > 0) {
6329
+ if (previousHeight == null && diagramSize > 24) {
5993
6330
  setPreviousHeight(diagramSize);
5994
6331
  }
5995
6332
  }, [diagramSize, previousHeight]);
5996
6333
  var preserveWindBarbsGrowBackHeight = React.useCallback(function () {
5997
- if (sizeRef.current > 0) {
5998
- setPreviousHeight(sizeRef.current);
5999
- }
6000
- }, []);
6334
+ var nextHeight = sizeRef.current > 24 ? sizeRef.current : windBarbsDiagramSize;
6335
+ setPreviousHeight(nextHeight);
6336
+ }, [windBarbsDiagramSize]);
6001
6337
  var handleSoundingsResizeStart = React.useCallback(function () {
6002
6338
  isResizingRef.current = true;
6003
6339
  preserveWindBarbsGrowBackHeight();
@@ -6010,7 +6346,6 @@ var SoundingsPanels = function SoundingsPanels(_ref) {
6010
6346
  if (isClosingViaClick) {
6011
6347
  preserveWindBarbsGrowBackHeight();
6012
6348
  }
6013
- setSoundingsIsClosed(!isOpen);
6014
6349
  }, [preserveWindBarbsGrowBackHeight]);
6015
6350
  var panels = soundingPanelsInOrder.flatMap(function (_ref2) {
6016
6351
  var id = _ref2.id,
@@ -6054,9 +6389,10 @@ var SoundingsPanels = function SoundingsPanels(_ref) {
6054
6389
  name: 'Wind Barbs',
6055
6390
  isDisplayed: isDisplayed,
6056
6391
  maxWidth: 200,
6392
+ panelRef: windBarbsContentRef,
6057
6393
  render: function render() {
6058
6394
  return jsx(WindBarbs, {
6059
- animateResize: Boolean(soundingsIsClosed && previousHeight),
6395
+ animateResize: Boolean(!soundingsIsOpen && previousHeight),
6060
6396
  d3GraphId: normalizedId,
6061
6397
  data: data,
6062
6398
  diagramType: chosenDiagramType,
@@ -6068,7 +6404,7 @@ var SoundingsPanels = function SoundingsPanels(_ref) {
6068
6404
  right: 25,
6069
6405
  top: 0
6070
6406
  },
6071
- height: soundingsIsClosed ? previousHeight != null ? previousHeight : diagramSize : diagramSize
6407
+ height: windBarbsHeight
6072
6408
  }
6073
6409
  });
6074
6410
  }
@@ -6243,6 +6579,7 @@ var SoundingsConnect = function SoundingsConnect(_ref) {
6243
6579
  // Skip re-registering if the incoming preset already matches the current
6244
6580
  // soundings store state (e.g. workspace initialProps updated after a save)
6245
6581
  if (!isEqual(soundingsPreset, currentSoundingsPreset)) {
6582
+ var _soundingsPreset$isLo;
6246
6583
  presetJustLoadedRef.current = true;
6247
6584
  initialLocationsRef.current = soundingsPreset.selectedLocations || {};
6248
6585
  previousLocationsRef.current = {};
@@ -6250,6 +6587,11 @@ var SoundingsConnect = function SoundingsConnect(_ref) {
6250
6587
  viewId: viewId,
6251
6588
  preset: soundingsPreset
6252
6589
  }));
6590
+ dispatch(uiActions.setActiveMapIdForDialog({
6591
+ type: uiTypes.DialogTypes.SoundingsLocations,
6592
+ mapId: viewId,
6593
+ setOpen: (_soundingsPreset$isLo = soundingsPreset.isLocationsDialogOpen) != null ? _soundingsPreset$isLo : false
6594
+ }));
6253
6595
  setTimeout(function () {
6254
6596
  presetJustLoadedRef.current = false;
6255
6597
  initialLocationsRef.current = {};
@@ -6261,6 +6603,26 @@ var SoundingsConnect = function SoundingsConnect(_ref) {
6261
6603
  previousLocationsRef.current = {};
6262
6604
  }
6263
6605
  }, [currentSoundingsPreset, dispatch, soundingsPreset, viewId]);
6606
+ var syncgroupTimeState = useSyncgroupTimeState(viewId, 0);
6607
+ var syncgroupSelectedTime = useMemo(function () {
6608
+ if ((syncgroupTimeState == null ? void 0 : syncgroupTimeState.current) === undefined || !Number.isFinite(syncgroupTimeState.current)) {
6609
+ return undefined;
6610
+ }
6611
+ return dateUtils.fromUnix(syncgroupTimeState.current).toISOString();
6612
+ }, [syncgroupTimeState]);
6613
+ var isTimeSynced = !!syncgroupSelectedTime;
6614
+ React.useEffect(function () {
6615
+ if (soundingsPreset != null && soundingsPreset.selectedTime && isTimeSynced) {
6616
+ // trigger sync of time to map when preset has selectedTime
6617
+ dispatch(genericActions.setTime({
6618
+ sourceId: viewId,
6619
+ origin: 'SoundingsConnect',
6620
+ value: {
6621
+ currentTime: handleDateUtilsISOString(soundingsPreset.selectedTime)
6622
+ }
6623
+ }));
6624
+ }
6625
+ }, [dispatch, isTimeSynced, soundingsPreset == null ? void 0 : soundingsPreset.selectedTime, viewId]);
6264
6626
  useRegisterFeatureSyncSource(viewId);
6265
6627
  var selectedTime = useSelector(function (state) {
6266
6628
  return getSelectedTime(state, viewId);
@@ -6293,6 +6655,9 @@ var SoundingsConnect = function SoundingsConnect(_ref) {
6293
6655
  var profileDataPreferences = useSelector(function (state) {
6294
6656
  return getProfileDataPreferences(state, viewId);
6295
6657
  });
6658
+ var isLegendVisible = useSelector(function (state) {
6659
+ return getIsLegendVisible(state, viewId);
6660
+ });
6296
6661
  var selectedMapIds = useSelector(function (state) {
6297
6662
  return genericSelectors.getLinkedTargetIds(state, viewId);
6298
6663
  }, shallowEqual);
@@ -6307,14 +6672,6 @@ var SoundingsConnect = function SoundingsConnect(_ref) {
6307
6672
  var firstLinkedMapId = selectedMapIds[0];
6308
6673
  var _useTimesteps = useTimesteps(resolvedSourceSelections),
6309
6674
  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
6675
  var selectedTimeInUse = syncgroupSelectedTime || selectedTime;
6319
6676
  var timeMismatchInfo = useMemo(function () {
6320
6677
  return getTimeMismatchInfo(isTimeSynced, selectedTimeInUse, sourceTimesteps);
@@ -6973,7 +7330,7 @@ var SoundingsConnect = function SoundingsConnect(_ref) {
6973
7330
  fontSize: "small"
6974
7331
  })
6975
7332
  })]
6976
- }), jsx(Box, {
7333
+ }), isLegendVisible ? jsx(Box, {
6977
7334
  sx: {
6978
7335
  display: 'flex',
6979
7336
  justifyContent: 'center',
@@ -6989,7 +7346,7 @@ var SoundingsConnect = function SoundingsConnect(_ref) {
6989
7346
  sourceResolvedInstanceIds: sourceResolvedInstanceIds,
6990
7347
  sourceSelections: sourceSelections
6991
7348
  })
6992
- }), jsx(Box, {
7349
+ }) : null, jsx(Box, {
6993
7350
  sx: {
6994
7351
  flex: 1,
6995
7352
  minHeight: 0,
@@ -7272,6 +7629,28 @@ var SoundingsSourceDialogConnect = function SoundingsSourceDialogConnect(_ref) {
7272
7629
  data = _useGetCollectionsIns.data,
7273
7630
  isPending = _useGetCollectionsIns.isPending,
7274
7631
  error = _useGetCollectionsIns.error;
7632
+ // Update checked sources without an instance once instances finish loading.
7633
+ React.useEffect(function () {
7634
+ data == null || data.forEach(function (collection) {
7635
+ var currentSelection = sourceSelections[collection.id];
7636
+ if (!(currentSelection != null && currentSelection.isChecked) || currentSelection.instanceId || collection.instances.length === 0) {
7637
+ return;
7638
+ }
7639
+ var resolvedInstance = referenceTimeUtils.resolveReferenceTimeInstance(collection.instances, defaultInstanceId);
7640
+ if (!resolvedInstance) {
7641
+ return;
7642
+ }
7643
+ dispatch(soundingsActions.updateSelectedSource({
7644
+ sourceId: collection.id,
7645
+ value: {
7646
+ instanceId: defaultInstanceId,
7647
+ verticalRange: getVerticalRangeFromInstance(resolvedInstance)
7648
+ },
7649
+ viewId: viewId,
7650
+ origin: SoundingsActionOrigin.soundingsManager
7651
+ }));
7652
+ });
7653
+ }, [data, dispatch, sourceSelections, viewId]);
7275
7654
  return jsx(SoundingsSourceMenu, {
7276
7655
  error: error,
7277
7656
  handleClose: onCloseDialog,
@@ -7334,7 +7713,10 @@ var LocationsMenu = function LocationsMenu(_ref2) {
7334
7713
  _ref2$order = _ref2.order,
7335
7714
  order = _ref2$order === void 0 ? 0 : _ref2$order,
7336
7715
  isLoading = _ref2.isLoading,
7337
- error = _ref2.error;
7716
+ error = _ref2.error,
7717
+ locationMenuSize = _ref2.locationMenuSize,
7718
+ onSizeChange = _ref2.onSizeChange,
7719
+ locationMenuPosition = _ref2.locationMenuPosition;
7338
7720
  var _useState = useState(''),
7339
7721
  searchFilter = _useState[0],
7340
7722
  setSearchFilter = _useState[1];
@@ -7361,13 +7743,26 @@ var LocationsMenu = function LocationsMenu(_ref2) {
7361
7743
  isOpen: isOpen,
7362
7744
  minWidth: 264,
7363
7745
  onClose: onClose,
7746
+ onDragEnd: function onDragEnd(position, size) {
7747
+ if (typeof size.width === 'number' && typeof size.height === 'number') {
7748
+ onSizeChange == null || onSizeChange({
7749
+ width: size.width,
7750
+ height: size.height
7751
+ }, position);
7752
+ }
7753
+ },
7364
7754
  onMouseDown: onMouseDown,
7365
7755
  order: order,
7756
+ resetPositionOnOpen: true,
7757
+ startDraggablePosition: locationMenuPosition != null ? locationMenuPosition : {
7758
+ x: 0,
7759
+ y: 0
7760
+ },
7366
7761
  startPosition: {
7367
7762
  top: 60,
7368
7763
  left: 200
7369
7764
  },
7370
- startSize: {
7765
+ startSize: locationMenuSize != null ? locationMenuSize : {
7371
7766
  width: 320,
7372
7767
  height: 500
7373
7768
  },
@@ -7483,6 +7878,12 @@ var LocationsMenuConnect = function LocationsMenuConnect(_ref) {
7483
7878
  dispatch(soundingsActions.setSoundingsCollections(soundingsCollections));
7484
7879
  }
7485
7880
  }, [dispatch, soundingsCollections]);
7881
+ var locationMenuSize = useSelector(function (state) {
7882
+ return getLocationMenuSize(state, viewId);
7883
+ });
7884
+ var locationMenuPosition = useSelector(function (state) {
7885
+ return getLocationMenuPosition(state, viewId);
7886
+ });
7486
7887
  var _useGetLocations = useGetLocations(collections, isDialogOpen),
7487
7888
  data = _useGetLocations.data,
7488
7889
  isPending = _useGetLocations.isPending,
@@ -7556,12 +7957,31 @@ var LocationsMenuConnect = function LocationsMenuConnect(_ref) {
7556
7957
  }
7557
7958
  }
7558
7959
  }, [dispatch, viewId, services, selectedSourceIds, locationSelections, selectedMapIds, ownedFeatureLayerIds]);
7960
+ var handleSizeChange = useCallback(function (size, position) {
7961
+ dispatch(soundingsActions.setLocationMenuSizeAndPosition({
7962
+ size: size,
7963
+ position: position,
7964
+ viewId: viewId,
7965
+ origin: SoundingsActionOrigin.soundingsComponent
7966
+ }));
7967
+ }, [dispatch, viewId]);
7968
+ var handleClose = useCallback(function () {
7969
+ onCloseDialog();
7970
+ dispatch(soundingsActions.setLocationsMenuOpen({
7971
+ viewId: viewId,
7972
+ isLocationsDialogOpen: false,
7973
+ origin: SoundingsActionOrigin.soundingsComponent
7974
+ }));
7975
+ }, [dispatch, onCloseDialog, viewId]);
7559
7976
  return jsx(LocationsMenu, {
7560
7977
  error: error,
7561
7978
  isLoading: isPending,
7562
7979
  isOpen: isDialogOpen,
7563
- onClose: onCloseDialog,
7980
+ locationMenuPosition: locationMenuPosition,
7981
+ locationMenuSize: locationMenuSize,
7982
+ onClose: handleClose,
7564
7983
  onMouseDown: setDialogOrder,
7984
+ onSizeChange: handleSizeChange,
7565
7985
  order: dialogOrder,
7566
7986
  selectedLocations: locationSelections,
7567
7987
  selectedSourcesAmount: sourcesAmount,