@sybilion/uilib 1.2.26 → 1.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (137) hide show
  1. package/dist/esm/components/ui/Chart/Chart.js +5 -0
  2. package/dist/esm/components/ui/Chart/components/BaseChartWrapper.js +7 -32
  3. package/dist/esm/components/ui/Chart/components/ChartEmptyState/ChartEmptyState.js +21 -0
  4. package/dist/esm/components/ui/Chart/components/ChartEmptyState/ChartEmptyState.styl.js +7 -0
  5. package/dist/esm/components/ui/Chart/lightweight/LightweightForecastChart.js +460 -0
  6. package/dist/esm/components/ui/Chart/lightweight/LightweightForecastChart.styl.js +7 -0
  7. package/dist/esm/components/ui/Chart/lightweight/chartTime.js +16 -0
  8. package/dist/esm/components/ui/Chart/lightweight/lightweightForecastChart.helpers.js +114 -0
  9. package/dist/esm/components/ui/Chart/lightweight/quantileBandCustomSeries.js +147 -0
  10. package/dist/esm/components/ui/Chart/quantileBandConeChartData.js +131 -0
  11. package/dist/esm/components/ui/Chart/tools/chartPlotGeometry.js +65 -0
  12. package/dist/esm/components/ui/ChartAreaInteractive/ChartAreaInteractive.helpers.js +37 -1
  13. package/dist/esm/components/ui/ChartAreaInteractive/ChartAreaInteractive.js +5 -2
  14. package/dist/esm/components/ui/ChartAreaInteractive/TimeRangeBrushLayer.js +205 -0
  15. package/dist/esm/components/ui/ChartAreaInteractive/TimeRangeBrushLayer.styl.js +7 -0
  16. package/dist/esm/components/ui/ChartAreaInteractive/TimeRangeBrushLayout.helpers.js +37 -0
  17. package/dist/esm/components/ui/ChartAreaInteractive/overlays/IntervalsOverlay/IntervalsOverlay.hooks.js +1 -0
  18. package/dist/esm/components/ui/ChartAreaInteractive/overlays/PinOverlay/PinOverlay.js +7 -60
  19. package/dist/esm/components/ui/ChartAreaInteractive/overlays/PinOverlay/PinOverlay.styl.js +2 -2
  20. package/dist/esm/components/ui/ChartAreaInteractive/overlays/ThresholdsOverlay/ThresholdsOverlay.hooks.js +1 -0
  21. package/dist/esm/components/ui/ChartAreaInteractive/overlays/useChartYRange.js +2 -4
  22. package/dist/esm/components/ui/ChartAreaInteractive/overlays/useQuantileBands.js +4 -102
  23. package/dist/esm/components/ui/TimeRangeControls/TimeRangeControls.js +7 -2
  24. package/dist/esm/components/ui/WorldMap/WorldMap.js +11 -0
  25. package/dist/esm/components/ui/WorldMap/WorldMap.styl.js +7 -0
  26. package/dist/esm/components/widgets/DriverCard/DriverCard.js +89 -0
  27. package/dist/esm/components/widgets/DriverCard/DriverCard.styl.js +7 -0
  28. package/dist/esm/components/widgets/DriverCard/DriverPerformanceChart.js +83 -0
  29. package/dist/esm/components/widgets/DriverCard/DriverPerformanceChart.styl.js +7 -0
  30. package/dist/esm/components/widgets/DriverCard/driverPerformanceChartData.js +50 -0
  31. package/dist/esm/components/widgets/DriverMap/DriverMap.js +2 -2
  32. package/dist/esm/components/widgets/DriverMap/DriverMap.styl.js +2 -2
  33. package/dist/esm/index.js +4 -2
  34. package/dist/esm/types/src/components/ui/Chart/Chart.d.ts +2 -0
  35. package/dist/esm/types/src/components/ui/Chart/components/BaseChartWrapper.d.ts +2 -1
  36. package/dist/esm/types/src/components/ui/Chart/components/ChartEmptyState/ChartEmptyState.d.ts +14 -0
  37. package/dist/esm/types/src/components/ui/Chart/lightweight/LightweightForecastChart.d.ts +26 -0
  38. package/dist/esm/types/src/components/ui/Chart/lightweight/chartTime.d.ts +5 -0
  39. package/dist/esm/types/src/components/ui/Chart/lightweight/lightweightForecastChart.helpers.d.ts +13 -0
  40. package/dist/esm/types/src/components/ui/Chart/lightweight/quantileBandCustomSeries.d.ts +24 -0
  41. package/dist/esm/types/src/components/ui/Chart/quantileBandConeChartData.d.ts +7 -0
  42. package/dist/esm/types/src/components/ui/Chart/tools/chartPlotGeometry.d.ts +30 -0
  43. package/dist/esm/types/src/components/ui/ChartAreaInteractive/ChartAreaInteractive.d.ts +1 -1
  44. package/dist/esm/types/src/components/ui/ChartAreaInteractive/ChartAreaInteractive.helpers.d.ts +11 -2
  45. package/dist/esm/types/src/components/ui/ChartAreaInteractive/ChartAreaInteractive.types.d.ts +2 -2
  46. package/dist/esm/types/src/components/ui/ChartAreaInteractive/TimeRangeBrushLayer.d.ts +15 -0
  47. package/dist/esm/types/src/components/ui/ChartAreaInteractive/TimeRangeBrushLayout.helpers.d.ts +14 -0
  48. package/dist/esm/types/src/components/ui/ChartAreaInteractive/overlays/PinOverlay/PinOverlay.d.ts +1 -1
  49. package/dist/esm/types/src/components/ui/Page/PageColumns/PageColumns.d.ts +1 -1
  50. package/dist/esm/types/src/components/ui/TimeRangeControls/TimeRangeControls.d.ts +5 -7
  51. package/dist/esm/types/src/components/ui/WorldMap/WorldMap.d.ts +4 -0
  52. package/dist/esm/types/src/components/ui/WorldMap/index.d.ts +2 -0
  53. package/dist/esm/types/src/components/widgets/DriverCard/DriverCard.d.ts +9 -0
  54. package/dist/esm/types/src/components/widgets/DriverCard/DriverPerformanceChart.d.ts +5 -0
  55. package/dist/esm/types/src/components/widgets/DriverCard/driverPerformanceChartData.d.ts +7 -0
  56. package/dist/esm/types/src/components/widgets/DriverCard/index.d.ts +1 -0
  57. package/dist/esm/types/src/components/widgets/DriverMap/index.d.ts +0 -2
  58. package/dist/esm/types/src/docs/pages/LightweightChartPage.d.ts +1 -0
  59. package/dist/esm/types/src/docs/pages/PageColumnsPage.d.ts +1 -0
  60. package/dist/esm/types/src/docs/pages/WorldMapPage.d.ts +1 -0
  61. package/dist/esm/types/src/index.d.ts +2 -0
  62. package/package.json +3 -2
  63. package/src/components/ui/Chart/Chart.tsx +9 -0
  64. package/src/components/ui/Chart/components/BaseChartWrapper.tsx +8 -41
  65. package/src/components/ui/Chart/components/ChartEmptyState/ChartEmptyState.styl +60 -0
  66. package/src/components/ui/Chart/components/ChartEmptyState/ChartEmptyState.styl.d.ts +15 -0
  67. package/src/components/ui/Chart/components/ChartEmptyState/ChartEmptyState.tsx +66 -0
  68. package/src/components/ui/Chart/lightweight/LightweightForecastChart.styl +25 -0
  69. package/src/components/ui/Chart/lightweight/LightweightForecastChart.styl.d.ts +11 -0
  70. package/src/components/ui/Chart/lightweight/LightweightForecastChart.tsx +721 -0
  71. package/src/components/ui/Chart/lightweight/chartTime.ts +18 -0
  72. package/src/components/ui/Chart/lightweight/lightweightForecastChart.helpers.ts +141 -0
  73. package/src/components/ui/Chart/lightweight/quantileBandCustomSeries.ts +215 -0
  74. package/src/components/ui/Chart/quantileBandConeChartData.ts +171 -0
  75. package/src/components/ui/Chart/tools/chartPlotGeometry.ts +89 -0
  76. package/src/components/ui/ChartAreaInteractive/ChartAreaInteractive.helpers.ts +44 -2
  77. package/src/components/ui/ChartAreaInteractive/ChartAreaInteractive.tsx +14 -1
  78. package/src/components/ui/ChartAreaInteractive/ChartAreaInteractive.types.ts +2 -3
  79. package/src/components/ui/ChartAreaInteractive/TimeRangeBrushLayer.styl +21 -0
  80. package/src/components/{widgets/DriverMap/LoadingSpinner/LoadingSpinner.styl.d.ts → ui/ChartAreaInteractive/TimeRangeBrushLayer.styl.d.ts} +3 -3
  81. package/src/components/ui/ChartAreaInteractive/TimeRangeBrushLayer.tsx +285 -0
  82. package/src/components/ui/ChartAreaInteractive/TimeRangeBrushLayout.helpers.ts +55 -0
  83. package/src/components/ui/ChartAreaInteractive/overlays/IntervalsOverlay/IntervalsOverlay.hooks.ts +1 -0
  84. package/src/components/ui/ChartAreaInteractive/overlays/PinOverlay/PinOverlay.styl +2 -7
  85. package/src/components/ui/ChartAreaInteractive/overlays/PinOverlay/PinOverlay.styl.d.ts +0 -1
  86. package/src/components/ui/ChartAreaInteractive/overlays/PinOverlay/PinOverlay.tsx +7 -71
  87. package/src/components/ui/ChartAreaInteractive/overlays/ThresholdsOverlay/ThresholdsOverlay.hooks.ts +1 -0
  88. package/src/components/ui/ChartAreaInteractive/overlays/useChartYRange.ts +2 -3
  89. package/src/components/ui/ChartAreaInteractive/overlays/useQuantileBands.ts +5 -131
  90. package/src/components/ui/Page/PageColumns/PageColumns.tsx +1 -1
  91. package/src/components/ui/TimeRangeControls/TimeRangeControls.tsx +16 -17
  92. package/src/components/{widgets/DriverMap/MapBackground/MapBackground.styl → ui/WorldMap/WorldMap.styl} +1 -3
  93. package/src/components/{widgets/DriverMap/MapBackground/MapBackground.styl.d.ts → ui/WorldMap/WorldMap.styl.d.ts} +1 -1
  94. package/src/components/ui/WorldMap/WorldMap.tsx +22 -0
  95. package/src/components/ui/WorldMap/index.ts +2 -0
  96. package/src/components/widgets/DriverCard/DriverCard.styl +169 -0
  97. package/src/components/widgets/DriverCard/DriverCard.styl.d.ts +40 -0
  98. package/src/components/widgets/DriverCard/DriverCard.tsx +219 -0
  99. package/src/components/widgets/DriverCard/DriverPerformanceChart.styl +43 -0
  100. package/src/components/widgets/DriverCard/DriverPerformanceChart.styl.d.ts +13 -0
  101. package/src/components/widgets/DriverCard/DriverPerformanceChart.tsx +150 -0
  102. package/src/components/widgets/DriverCard/driverPerformanceChartData.ts +64 -0
  103. package/src/components/widgets/DriverCard/index.ts +1 -0
  104. package/src/components/widgets/DriverMap/DriverIcon/DriverIcon.tsx +0 -2
  105. package/src/components/widgets/DriverMap/DriverMap.styl +6 -1
  106. package/src/components/widgets/DriverMap/DriverMap.styl.d.ts +1 -0
  107. package/src/components/widgets/DriverMap/DriverMap.tsx +2 -4
  108. package/src/components/widgets/DriverMap/driverCategoryIcon.tsx +0 -2
  109. package/src/components/widgets/DriverMap/index.ts +0 -2
  110. package/src/declarations.d.ts +2 -0
  111. package/src/docs/config/webpack.config.js +26 -3
  112. package/src/docs/index.tsx +1 -1
  113. package/src/docs/pages/ChartAreaInteractivePage.tsx +2 -3
  114. package/src/docs/pages/DriverMapPage.tsx +214 -60
  115. package/src/docs/pages/LightweightChartPage.styl +18 -0
  116. package/src/docs/pages/LightweightChartPage.styl.d.ts +10 -0
  117. package/src/docs/pages/LightweightChartPage.tsx +195 -0
  118. package/src/docs/pages/PageColumnsPage.tsx +92 -0
  119. package/src/docs/pages/TimeRangeControlsPage.tsx +2 -3
  120. package/src/docs/pages/WorldMapPage.styl +14 -0
  121. package/src/docs/pages/WorldMapPage.styl.d.ts +8 -0
  122. package/src/docs/pages/WorldMapPage.tsx +26 -0
  123. package/src/docs/registry.ts +19 -1
  124. package/src/index.ts +2 -0
  125. package/dist/esm/components/widgets/DriverMap/LoadingSpinner/LoadingSpinner.js +0 -8
  126. package/dist/esm/components/widgets/DriverMap/LoadingSpinner/LoadingSpinner.styl.js +0 -7
  127. package/dist/esm/components/widgets/DriverMap/MapBackground/MapBackground.js +0 -10
  128. package/dist/esm/components/widgets/DriverMap/MapBackground/MapBackground.styl.js +0 -7
  129. package/dist/esm/types/src/components/widgets/DriverMap/LoadingSpinner/LoadingSpinner.d.ts +0 -1
  130. package/dist/esm/types/src/components/widgets/DriverMap/MapBackground/MapBackground.d.ts +0 -1
  131. package/src/components/widgets/DriverMap/LoadingSpinner/LoadingSpinner.styl +0 -24
  132. package/src/components/widgets/DriverMap/LoadingSpinner/LoadingSpinner.tsx +0 -11
  133. package/src/components/widgets/DriverMap/MapBackground/MapBackground.tsx +0 -18
  134. /package/dist/esm/components/{widgets/DriverMap/MapBackground → ui/WorldMap}/map.svg.js +0 -0
  135. /package/src/components/{widgets/DriverMap/MapBackground → ui/WorldMap}/map.svg +0 -0
  136. /package/src/components/{widgets/DriverMap/MapBackground → ui/WorldMap}/mapAspect.mixin.styl +0 -0
  137. /package/src/components/{widgets/DriverMap/MapBackground → ui/WorldMap}/mapAspect.mixin.styl.d.ts +0 -0
@@ -0,0 +1,205 @@
1
+ import { jsx, Fragment, jsxs } from 'react/jsx-runtime';
2
+ import { useRef, useState, useLayoutEffect } from 'react';
3
+ import debounce from '../../../tools/debounce.js';
4
+ import { CHART_MARGINS } from './ChartAreaInteractive.constants.js';
5
+ import { encodeDragTimeRange } from './ChartAreaInteractive.helpers.js';
6
+ import S from './TimeRangeBrushLayer.styl.js';
7
+ import { BRUSH_LAYOUT_RESIZE_DEBOUNCE_MS, BRUSH_MIN_DRAG_PX, brushClientXToDate, BRUSH_DOUBLE_TAP_MS, BRUSH_DOUBLE_TAP_MAX_DIST_PX, measureBrushPlotLayout, brushPlotLayoutsEqual } from './TimeRangeBrushLayout.helpers.js';
8
+ import { resolveChartMargin } from '../Chart/tools/chartPlotGeometry.js';
9
+
10
+ /**
11
+ * Wraps chart; pointerdown on SVG starts horizontal brush. Plot box tracks the
12
+ * painted grid / Recharts plot (see TimeRangeBrushLayout.helpers).
13
+ */
14
+ function TimeRangeBrushHost({ chartData, onTimeRangeChange, enabled, layoutKey, children, }) {
15
+ const hostRef = useRef(null);
16
+ const plotRef = useRef(null);
17
+ const lastUpRef = useRef(null);
18
+ const dragPointerIdRef = useRef(null);
19
+ const startClientXRef = useRef(0);
20
+ const chartDataRef = useRef(chartData);
21
+ const onTimeRangeChangeRef = useRef(onTimeRangeChange);
22
+ const lastLayoutRef = useRef(null);
23
+ chartDataRef.current = chartData;
24
+ onTimeRangeChangeRef.current = onTimeRangeChange;
25
+ const moveRef = useRef(() => { });
26
+ const upRef = useRef(() => { });
27
+ const onMoveStableRef = useRef((e) => moveRef.current(e));
28
+ const onUpStableRef = useRef((e) => upRef.current(e));
29
+ const [band, setBand] = useState(null);
30
+ const [plotLayout, setPlotLayout] = useState(null);
31
+ useLayoutEffect(() => {
32
+ if (!enabled) {
33
+ lastLayoutRef.current = null;
34
+ setPlotLayout(null);
35
+ return;
36
+ }
37
+ const host = hostRef.current;
38
+ if (!host)
39
+ return;
40
+ const margin = resolveChartMargin(CHART_MARGINS);
41
+ let raf = 0;
42
+ let wrapperRo = null;
43
+ let observedWrapper = null;
44
+ const commitLayout = (next) => {
45
+ if (brushPlotLayoutsEqual(next, lastLayoutRef.current))
46
+ return;
47
+ lastLayoutRef.current = next;
48
+ setPlotLayout(next);
49
+ };
50
+ const runSync = () => {
51
+ cancelAnimationFrame(raf);
52
+ raf = requestAnimationFrame(() => {
53
+ const h = hostRef.current;
54
+ if (!h) {
55
+ commitLayout(null);
56
+ return;
57
+ }
58
+ const wrappers = h.querySelectorAll('.recharts-wrapper');
59
+ const wrapper = wrappers[wrappers.length - 1];
60
+ if (!(wrapper instanceof HTMLElement)) {
61
+ if (wrapperRo) {
62
+ wrapperRo.disconnect();
63
+ wrapperRo = null;
64
+ }
65
+ observedWrapper = null;
66
+ commitLayout(null);
67
+ return;
68
+ }
69
+ if (wrapper !== observedWrapper) {
70
+ if (wrapperRo) {
71
+ wrapperRo.disconnect();
72
+ wrapperRo = null;
73
+ }
74
+ observedWrapper = wrapper;
75
+ wrapperRo = new ResizeObserver(() => runSync());
76
+ wrapperRo.observe(wrapper);
77
+ }
78
+ let next = measureBrushPlotLayout(h, margin);
79
+ if (!next) {
80
+ requestAnimationFrame(() => {
81
+ const h2 = hostRef.current;
82
+ if (!h2) {
83
+ commitLayout(null);
84
+ return;
85
+ }
86
+ commitLayout(measureBrushPlotLayout(h2, margin));
87
+ });
88
+ return;
89
+ }
90
+ commitLayout(next);
91
+ });
92
+ };
93
+ const debouncedWindowResize = debounce(runSync, BRUSH_LAYOUT_RESIZE_DEBOUNCE_MS, { leading: false });
94
+ runSync();
95
+ const roHost = new ResizeObserver(() => runSync());
96
+ roHost.observe(host);
97
+ const mo = new MutationObserver(() => runSync());
98
+ mo.observe(host, { childList: true, subtree: true });
99
+ window.addEventListener('resize', debouncedWindowResize);
100
+ return () => {
101
+ cancelAnimationFrame(raf);
102
+ debouncedWindowResize.cancel();
103
+ roHost.disconnect();
104
+ mo.disconnect();
105
+ wrapperRo?.disconnect();
106
+ window.removeEventListener('resize', debouncedWindowResize);
107
+ };
108
+ }, [enabled, chartData.length, layoutKey]);
109
+ useLayoutEffect(() => {
110
+ moveRef.current = (e) => {
111
+ if (e.pointerId !== dragPointerIdRef.current)
112
+ return;
113
+ const plot = plotRef.current?.getBoundingClientRect();
114
+ if (!plot)
115
+ return;
116
+ const x0 = startClientXRef.current;
117
+ const x1 = e.clientX;
118
+ const left = Math.min(x0, x1);
119
+ const right = Math.max(x0, x1);
120
+ const leftClamped = Math.max(plot.left, Math.min(plot.right, left));
121
+ const rightClamped = Math.max(plot.left, Math.min(plot.right, right));
122
+ setBand({
123
+ left: leftClamped - plot.left,
124
+ width: Math.max(0, rightClamped - leftClamped),
125
+ });
126
+ };
127
+ upRef.current = (e) => {
128
+ if (e.pointerId !== dragPointerIdRef.current)
129
+ return;
130
+ const mv = onMoveStableRef.current;
131
+ const up = onUpStableRef.current;
132
+ if (mv)
133
+ window.removeEventListener('pointermove', mv);
134
+ if (up) {
135
+ window.removeEventListener('pointerup', up);
136
+ window.removeEventListener('pointercancel', up);
137
+ }
138
+ dragPointerIdRef.current = null;
139
+ const data = chartDataRef.current;
140
+ const plot = plotRef.current?.getBoundingClientRect();
141
+ const span = Math.abs(e.clientX - startClientXRef.current);
142
+ let committed = false;
143
+ if (plot && data.length > 1 && span >= BRUSH_MIN_DRAG_PX) {
144
+ const d0 = brushClientXToDate(startClientXRef.current, plot, data);
145
+ const d1 = brushClientXToDate(e.clientX, plot, data);
146
+ if (d0 && d1) {
147
+ onTimeRangeChangeRef.current(encodeDragTimeRange(d0, d1));
148
+ committed = true;
149
+ }
150
+ }
151
+ setBand(null);
152
+ if (committed) {
153
+ lastUpRef.current = null;
154
+ return;
155
+ }
156
+ const now = performance.now();
157
+ const prev = lastUpRef.current;
158
+ if (prev &&
159
+ now - prev.t <= BRUSH_DOUBLE_TAP_MS &&
160
+ Math.hypot(e.clientX - prev.x, e.clientY - prev.y) <=
161
+ BRUSH_DOUBLE_TAP_MAX_DIST_PX) {
162
+ onTimeRangeChangeRef.current('All');
163
+ lastUpRef.current = null;
164
+ }
165
+ else {
166
+ lastUpRef.current = { t: now, x: e.clientX, y: e.clientY };
167
+ }
168
+ };
169
+ }, []);
170
+ const onPointerDown = (e) => {
171
+ if (!enabled || e.button !== 0)
172
+ return;
173
+ if (e.target.tagName !== 'svg')
174
+ return;
175
+ if (chartData.length < 2)
176
+ return;
177
+ dragPointerIdRef.current = e.pointerId;
178
+ startClientXRef.current = e.clientX;
179
+ const plot = plotRef.current?.getBoundingClientRect();
180
+ if (plot) {
181
+ const x = Math.max(plot.left, Math.min(plot.right, e.clientX));
182
+ setBand({ left: x - plot.left, width: 0 });
183
+ }
184
+ window.addEventListener('pointermove', onMoveStableRef.current);
185
+ window.addEventListener('pointerup', onUpStableRef.current);
186
+ window.addEventListener('pointercancel', onUpStableRef.current);
187
+ };
188
+ if (!enabled) {
189
+ return jsx(Fragment, { children: children });
190
+ }
191
+ const plotBoxStyle = plotLayout && plotLayout.width > 0 && plotLayout.height > 0
192
+ ? {
193
+ position: 'absolute',
194
+ zIndex: 8,
195
+ pointerEvents: 'none',
196
+ left: plotLayout.left,
197
+ top: plotLayout.top,
198
+ width: plotLayout.width,
199
+ height: plotLayout.height,
200
+ }
201
+ : { display: 'none' };
202
+ return (jsxs("div", { ref: hostRef, className: S.host, onPointerDown: onPointerDown, children: [children, jsx("div", { ref: plotRef, className: S.plotBox, style: plotBoxStyle, "aria-hidden": true, children: band != null && band.width > 0 && (jsx("div", { className: S.selection, style: { left: band.left, width: band.width } })) })] }));
203
+ }
204
+
205
+ export { TimeRangeBrushHost };
@@ -0,0 +1,7 @@
1
+ import styleInject from 'style-inject';
2
+
3
+ var css_248z = ".TimeRangeBrushLayer_selection__X7h7U{background-color:var(--brand-color-500);border:1px solid var(--ring);bottom:0;box-sizing:border-box;opacity:.1;pointer-events:none;position:absolute;top:0}.TimeRangeBrushLayer_host__aAQTn{overflow:visible;position:relative;touch-action:pan-y;width:100%}.TimeRangeBrushLayer_plotBox__YRfgK{box-sizing:border-box;pointer-events:none;position:absolute;z-index:8}";
4
+ var S = {"selection":"TimeRangeBrushLayer_selection__X7h7U","host":"TimeRangeBrushLayer_host__aAQTn","plotBox":"TimeRangeBrushLayer_plotBox__YRfgK"};
5
+ styleInject(css_248z);
6
+
7
+ export { S as default };
@@ -0,0 +1,37 @@
1
+ import { measureHostRelativePlotRect } from '../Chart/tools/chartPlotGeometry.js';
2
+ export { resolveChartMargin } from '../Chart/tools/chartPlotGeometry.js';
3
+
4
+ /** Debounce for `window` resize only (RO uses rAF-coalesced sync). */
5
+ const BRUSH_LAYOUT_RESIZE_DEBOUNCE_MS = 500;
6
+ const BRUSH_MIN_DRAG_PX = 8;
7
+ const BRUSH_DOUBLE_TAP_MS = 300;
8
+ const BRUSH_DOUBLE_TAP_MAX_DIST_PX = 24;
9
+ /** Host-relative plot rect (grid-first, then wrapper + margins). */
10
+ const measureBrushPlotLayout = measureHostRelativePlotRect;
11
+ function brushPlotLayoutsEqual(a, b) {
12
+ if (a === b)
13
+ return true;
14
+ if (!a || !b)
15
+ return false;
16
+ return (a.left === b.left &&
17
+ a.top === b.top &&
18
+ a.width === b.width &&
19
+ a.height === b.height);
20
+ }
21
+ function brushClientXToDate(clientX, plotRect, chartData) {
22
+ if (!chartData.length || plotRect.width <= 0)
23
+ return null;
24
+ const rel = clientX - plotRect.left;
25
+ const pct = Math.max(0, Math.min(100, (rel / plotRect.width) * 100));
26
+ const n = chartData.length;
27
+ if (n === 1) {
28
+ const d = chartData[0]?.date;
29
+ return d ? new Date(d) : null;
30
+ }
31
+ const idx = Math.round((pct / 100) * (n - 1));
32
+ const clamped = Math.max(0, Math.min(n - 1, idx));
33
+ const raw = chartData[clamped]?.date;
34
+ return raw ? new Date(raw) : null;
35
+ }
36
+
37
+ export { BRUSH_DOUBLE_TAP_MAX_DIST_PX, BRUSH_DOUBLE_TAP_MS, BRUSH_LAYOUT_RESIZE_DEBOUNCE_MS, BRUSH_MIN_DRAG_PX, brushClientXToDate, brushPlotLayoutsEqual, measureBrushPlotLayout };
@@ -76,6 +76,7 @@ function useQuantileButton({ buttonRef, overlayContainerRef, quantiles, selected
76
76
  onQuantileChange,
77
77
  ]);
78
78
  const handleDragStart = useCallback((e) => {
79
+ e.stopPropagation();
79
80
  setIsDragging(true);
80
81
  if (buttonRef.current) {
81
82
  buttonRef.current.style.transition = 'none';
@@ -6,23 +6,19 @@ import 'recharts';
6
6
  import '../../../Chart/Chart.context.js';
7
7
  import '../../../Chart/Chart.styl.js';
8
8
  import { useDebounceCallback } from '../../../../../hooks/useDebounceCallback.js';
9
- import useElemDrag from '../../../../../hooks/useDragElem.js';
10
9
  import { ChevronsLeftRight } from 'lucide-react';
11
10
  import S from '../../ChartAreaInteractive.styl.js';
12
11
  import { useChartYRange } from '../useChartYRange.js';
13
12
  import S$1 from './PinOverlay.styl.js';
14
13
 
15
- function PinOverlay({ baseChartProps, pinMonth, onPinMonthChange, onPreviewMonthChange, className, }) {
14
+ function PinOverlay({ baseChartProps, pinMonth, onPinMonthChange, onPreviewMonthChange: _onPreviewMonthChange, className, }) {
16
15
  const { chartData } = baseChartProps;
17
16
  const chartRef = useRef(null);
18
17
  const pinRef = useRef(null);
19
18
  const pinPlaceholderRef = useRef(null);
20
19
  const pinContainerRef = useRef(null);
21
- const [isDraggingPin, setIsDraggingPin] = useState(false);
22
- const [isPinAnimating, setIsPinAnimating] = useState(true);
23
20
  const [isPinHovered, setIsPinHovered] = useState(false);
24
21
  const pinPosRef = useRef(0);
25
- const containerRectRef = useRef(null);
26
22
  const currPinMonthRef = useRef(pinMonth);
27
23
  const debouncedOnPinMonthChange = useDebounceCallback((...args) => onPinMonthChange?.(args[0]), 500, [onPinMonthChange]);
28
24
  const { yMin, yMax } = useChartYRange({
@@ -38,7 +34,6 @@ function PinOverlay({ baseChartProps, pinMonth, onPinMonthChange, onPreviewMonth
38
34
  pinPlaceholderRef.current.style.left = `${position}%`;
39
35
  }
40
36
  };
41
- // Get full month and year for pin position to send to parent component
42
37
  const getPinMonthAndYear = (position) => {
43
38
  if (!chartData.length)
44
39
  return null;
@@ -54,18 +49,15 @@ function PinOverlay({ baseChartProps, pinMonth, onPinMonthChange, onPreviewMonth
54
49
  return `${month} ${year}`;
55
50
  }
56
51
  };
57
- // Handle chart click to move pin to specific position
58
52
  const snapPinToPosition = (eventX, needMonthUpdate = true, immediateMonthUpdate = false) => {
59
53
  if (!pinContainerRef.current || !chartData.length)
60
54
  return;
61
55
  const pinContainerRect = pinContainerRef.current.getBoundingClientRect();
62
56
  const effectiveLeft = pinContainerRect.left;
63
57
  const effectiveWidth = pinContainerRect.width;
64
- // Calculate relative position within plotted area
65
58
  const relativeX = eventX - effectiveLeft;
66
59
  const rawPercentage = effectiveWidth > 0 ? (relativeX / effectiveWidth) * 100 : 0;
67
60
  const percentage = Math.max(0, Math.min(100, isNaN(rawPercentage) ? 0 : rawPercentage));
68
- // Snap to nearest data point
69
61
  const totalPoints = chartData.length;
70
62
  if (totalPoints > 1) {
71
63
  const nearestIndex = Math.round((percentage / 100) * (totalPoints - 1));
@@ -73,13 +65,11 @@ function PinOverlay({ baseChartProps, pinMonth, onPinMonthChange, onPreviewMonth
73
65
  const snappedPercentage = (clampedIndex / (totalPoints - 1)) * 100;
74
66
  setPinPosition(isNaN(snappedPercentage) ? 0 : snappedPercentage);
75
67
  if (needMonthUpdate) {
76
- // Update news for all months (historical and forecast)
77
68
  const monthAndYear = getPinMonthAndYear();
78
69
  if (monthAndYear) {
79
70
  const isNewMonth = monthAndYear !== currPinMonthRef.current;
80
71
  if (isNewMonth)
81
72
  currPinMonthRef.current = monthAndYear;
82
- // When immediate (e.g. dragEnd), always notify parent so showFutureOutlook is correct
83
73
  if (immediateMonthUpdate) {
84
74
  onPinMonthChange?.(monthAndYear);
85
75
  }
@@ -90,11 +80,9 @@ function PinOverlay({ baseChartProps, pinMonth, onPinMonthChange, onPreviewMonth
90
80
  }
91
81
  }
92
82
  };
93
- // Update pin position when pinMonth prop changes
94
83
  useEffect(() => {
95
84
  if (!pinMonth || !chartData.length)
96
85
  return;
97
- // Find the data point index for the given month
98
86
  const dataPointIndex = chartData.findIndex(point => {
99
87
  const date = new Date(point.date);
100
88
  const month = date.toLocaleDateString('en-US', { month: 'short' });
@@ -109,57 +97,16 @@ function PinOverlay({ baseChartProps, pinMonth, onPinMonthChange, onPreviewMonth
109
97
  currPinMonthRef.current = pinMonth;
110
98
  }
111
99
  }, [pinMonth, chartData]);
112
- useElemDrag({
113
- elem: [chartRef],
114
- onDragStart: () => {
115
- setIsDraggingPin(true);
116
- containerRectRef.current =
117
- pinContainerRef.current?.getBoundingClientRect() || null;
118
- },
119
- onDrag: (delta) => {
120
- if (pinRef.current) {
121
- const containerRect = containerRectRef.current;
122
- if (!containerRect)
123
- return;
124
- const pinCurrentLeft = (pinPosRef.current / 100) * containerRect.width;
125
- const leftLimit = -pinCurrentLeft;
126
- const rightLimit = containerRect.width - pinCurrentLeft;
127
- const clampedDeltaX = Math.max(leftLimit, Math.min(rightLimit, delta.x));
128
- setIsPinAnimating(false);
129
- pinRef.current.style.transform = `translateX(${clampedDeltaX}px)`;
130
- // Calculate preview month based on current drag position
131
- if (onPreviewMonthChange && chartData.length > 0) {
132
- const newPositionPixels = pinCurrentLeft + clampedDeltaX;
133
- const newPercentage = (newPositionPixels / containerRect.width) * 100;
134
- const clampedPercentage = Math.max(0, Math.min(100, newPercentage));
135
- const previewMonth = getPinMonthAndYear(clampedPercentage);
136
- if (previewMonth) {
137
- onPreviewMonthChange(previewMonth);
138
- }
139
- }
140
- }
141
- },
142
- onDragEnd: (e) => {
143
- setIsDraggingPin(false);
144
- setTimeout(() => setIsPinAnimating(true), 200);
145
- if (pinRef.current)
146
- pinRef.current.style.transform = '';
147
- // Snap first so onPinMonthChange runs before we clear preview (avoids showFutureOutlook fallback)
148
- snapPinToPosition(e.clientX, true, true);
149
- if (onPreviewMonthChange) {
150
- onPreviewMonthChange(undefined);
151
- }
152
- },
153
- });
154
- const onPointerDown = (e) => {
155
- if (e.target.tagName !== 'svg')
100
+ const onChartClick = (e) => {
101
+ if (e.button !== 0)
102
+ return;
103
+ const target = e.target;
104
+ if (!target.closest?.('svg'))
156
105
  return;
157
106
  snapPinToPosition(e.clientX, true, false);
158
107
  };
159
108
  const overlay = (jsxs(Fragment, { children: [jsx("div", { className: cn(S.overlay, S$1.pinContainer), ref: pinContainerRef, children: jsx("div", { className: S$1.pinLineBase, style: { left: `${pinPosRef.current}%` }, ref: pinRef, children: jsx("div", { className: S$1.pinButton, "aria-label": "News pin", title: `Current month: ${getPinMonthAndYear() || 'Loading...'}`, children: jsx(ChevronsLeftRight, { className: S$1.pinIcon, "aria-hidden": "true" }) }) }) }), jsx("div", { className: S$1.pinPlaceholder, style: { left: `${pinPosRef.current}%` }, ref: pinPlaceholderRef, onPointerEnter: () => setIsPinHovered(true), onPointerLeave: () => setIsPinHovered(false) })] }));
160
- return (jsx("div", { className: cn(className, S$1.root, isDraggingPin && S$1.pinDragging, isPinAnimating && S$1.pinAnimating, isPinHovered && S$1.pinHovered), onPointerDown: onPointerDown,
161
- // onClick={e => snapPinToPosition(e.clientX)}
162
- ref: chartRef, children: jsx(BaseChartWrapper, { ...baseChartProps, yMin: yMin, yMax: yMax, autoScaleYAxis: false, overlayElements: overlay }) }));
109
+ return (jsx("div", { className: cn(className, S$1.root, S$1.pinAnimating, isPinHovered && S$1.pinHovered), onClick: onChartClick, ref: chartRef, children: jsx(BaseChartWrapper, { ...baseChartProps, yMin: yMin, yMax: yMax, autoScaleYAxis: false, overlayElements: overlay }) }));
163
110
  }
164
111
 
165
112
  export { PinOverlay };
@@ -1,7 +1,7 @@
1
1
  import styleInject from 'style-inject';
2
2
 
3
- var css_248z = ".PinOverlay_root__RUZxO{touch-action:pan-y}.PinOverlay_pinContainer__6sc1J{height:calc(var(--chart-height) - 70px);inset:0;margin:var(--p-5) var(--p-8) var(--p-24) var(--p-12);pointer-events:none;position:absolute;touch-action:pan-y;transition:transform .2s ease-out,z-index 0ms;-webkit-user-select:none;-moz-user-select:none;user-select:none;z-index:2}.PinOverlay_pinHovered__cGPjN .PinOverlay_pinContainer__6sc1J{z-index:10}.PinOverlay_pinLineBase__0BWsD{height:100%;pointer-events:none;position:absolute;width:3px}.PinOverlay_pinLineBase__0BWsD:before{border-radius:50%;box-shadow:0 4px 6px -1px var(--shadow-color);content:\"\";height:36px;left:-16px;position:absolute;top:-8px;width:36px;z-index:-1}.PinOverlay_pinLineBase__0BWsD:after{background-color:var(--background);border-radius:12px;bottom:0;content:\"\";left:0;position:absolute;top:0;transform:translate3d(.5px,0,0);transition:background-color .3s ease-out;width:100%;z-index:1}.PinOverlay_pinDragging__gu3xp .PinOverlay_pinLineBase__0BWsD:after{background-color:var(--background-alpha-700)}.PinOverlay_pinAnimating__5XMJG .PinOverlay_pinLineBase__0BWsD{transition:left .3s ease-out}.PinOverlay_pinButton__cnV1K{position:absolute;--offset:-8px;align-items:center;background-color:var(--background);border-radius:9999px;cursor:grab;display:flex;justify-content:center;left:-8px;left:var(--offset);margin-left:-8px;margin-left:var(--offset);padding:10px;pointer-events:auto;top:-18px;touch-action:none;transition:transform .2s ease-out;-webkit-user-select:none;-moz-user-select:none;user-select:none}.PinOverlay_pinDragging__gu3xp .PinOverlay_pinButton__cnV1K{transition:none}.PinOverlay_pinHovered__cGPjN .PinOverlay_pinButton__cnV1K{transform:scale(1.1)}.PinOverlay_pinButton__cnV1K:active{cursor:grabbing}.PinOverlay_pinIcon__s7Ze0{color:var(--foreground);height:16px;width:16px}.PinOverlay_pinPlaceholder__JhKcQ{cursor:grab;height:72px;margin-left:-40px;margin-top:-40px;pointer-events:auto;position:absolute;top:var(--p-5);touch-action:none;width:72px;z-index:20}.PinOverlay_pinAnimating__5XMJG .PinOverlay_pinPlaceholder__JhKcQ{transition:left .3s ease-out}.PinOverlay_pinPlaceholder__JhKcQ:active{cursor:grabbing}";
4
- var S = {"root":"PinOverlay_root__RUZxO","pinContainer":"PinOverlay_pinContainer__6sc1J","pinHovered":"PinOverlay_pinHovered__cGPjN","pinLineBase":"PinOverlay_pinLineBase__0BWsD","pinDragging":"PinOverlay_pinDragging__gu3xp","pinAnimating":"PinOverlay_pinAnimating__5XMJG","pinButton":"PinOverlay_pinButton__cnV1K","pinIcon":"PinOverlay_pinIcon__s7Ze0","pinPlaceholder":"PinOverlay_pinPlaceholder__JhKcQ"};
3
+ var css_248z = ".PinOverlay_root__RUZxO{touch-action:pan-y}.PinOverlay_pinContainer__6sc1J{height:calc(var(--chart-height) - 70px);inset:0;margin:var(--p-5) var(--p-8) var(--p-24) var(--p-12);pointer-events:none;position:absolute;touch-action:pan-y;transition:transform .2s ease-out,z-index 0ms;-webkit-user-select:none;-moz-user-select:none;user-select:none;z-index:2}.PinOverlay_pinHovered__cGPjN .PinOverlay_pinContainer__6sc1J{z-index:10}.PinOverlay_pinLineBase__0BWsD{height:100%;pointer-events:none;position:absolute;width:3px}.PinOverlay_pinLineBase__0BWsD:before{border-radius:50%;box-shadow:0 4px 6px -1px var(--shadow-color);content:\"\";height:36px;left:-16px;position:absolute;top:-8px;width:36px;z-index:-1}.PinOverlay_pinLineBase__0BWsD:after{background-color:var(--background);border-radius:12px;bottom:0;content:\"\";left:0;position:absolute;top:0;transform:translate3d(.5px,0,0);transition:background-color .3s ease-out;width:100%;z-index:1}.PinOverlay_pinAnimating__5XMJG .PinOverlay_pinLineBase__0BWsD{transition:left .3s ease-out}.PinOverlay_pinButton__cnV1K{position:absolute;--offset:-8px;align-items:center;background-color:var(--background);border-radius:9999px;cursor:pointer;display:flex;justify-content:center;left:-8px;left:var(--offset);margin-left:-8px;margin-left:var(--offset);padding:10px;pointer-events:auto;top:-18px;touch-action:none;transition:transform .2s ease-out;-webkit-user-select:none;-moz-user-select:none;user-select:none}.PinOverlay_pinHovered__cGPjN .PinOverlay_pinButton__cnV1K{transform:scale(1.1)}.PinOverlay_pinButton__cnV1K:active{cursor:grabbing}.PinOverlay_pinIcon__s7Ze0{color:var(--foreground);height:16px;width:16px}.PinOverlay_pinPlaceholder__JhKcQ{cursor:pointer;height:72px;margin-left:-40px;margin-top:-40px;pointer-events:auto;position:absolute;top:var(--p-5);touch-action:none;width:72px;z-index:20}.PinOverlay_pinAnimating__5XMJG .PinOverlay_pinPlaceholder__JhKcQ{transition:left .3s ease-out}.PinOverlay_pinPlaceholder__JhKcQ:active{cursor:grabbing}";
4
+ var S = {"root":"PinOverlay_root__RUZxO","pinContainer":"PinOverlay_pinContainer__6sc1J","pinHovered":"PinOverlay_pinHovered__cGPjN","pinLineBase":"PinOverlay_pinLineBase__0BWsD","pinAnimating":"PinOverlay_pinAnimating__5XMJG","pinButton":"PinOverlay_pinButton__cnV1K","pinIcon":"PinOverlay_pinIcon__s7Ze0","pinPlaceholder":"PinOverlay_pinPlaceholder__JhKcQ"};
5
5
  styleInject(css_248z);
6
6
 
7
7
  export { S as default };
@@ -70,6 +70,7 @@ function useThresholdButton({ lineRef, overlayContainerRef, initialValue, minVal
70
70
  onValueChangeThrottled(clampedValue);
71
71
  }, [clientYToValue, valueToPercent, minValue, maxValue]);
72
72
  const handleDragStart = useCallback((e) => {
73
+ e.stopPropagation();
73
74
  initialValueRef.current = initialValue;
74
75
  startYRef.current = e.clientY;
75
76
  setIsDragging(true);
@@ -15,11 +15,9 @@ function useChartYRange({ baseChartProps, chartData, disableRescaleWhenQuantileC
15
15
  Object.entries(point).forEach(([key, value]) => {
16
16
  if (key === 'date')
17
17
  return;
18
- // When selectedForecastId is provided, scale only from selected forecast + its quantile band
19
- // (exclude historical, other forecasts, and other forecasts' quantile values)
18
+ // When selectedForecastId is provided, scale from historical + selected forecast + its
19
+ // quantile band (exclude other forecasts and their quantile values only).
20
20
  if (forecastId !== undefined) {
21
- if (key === 'historical')
22
- return;
23
21
  if (key.startsWith('forecast_') && key !== `forecast_${forecastId}`)
24
22
  return;
25
23
  // Exclude q{quantile}_{otherAnalysisId} - only include selected forecast's quantiles
@@ -1,4 +1,5 @@
1
1
  import { useMemo } from 'react';
2
+ import { applyQuantileBandConeToChartData } from '../../Chart/quantileBandConeChartData.js';
2
3
 
3
4
  /**
4
5
  * Hook to transform chart data and create quantile band configuration
@@ -12,128 +13,29 @@ function useQuantileBands({ color, chartData, selectedForecastId, forecastData,
12
13
  const forecastDataForSelected = forecastData[selectedForecastId];
13
14
  const allQuantilesData = forecastDataForSelected.allQuantiles;
14
15
  const clonedData = [...chartData];
15
- // Get forecast dates to map quantile array indices correctly
16
16
  const forecastDates = forecastDataForSelected.dates || [];
17
17
  const forecastDatesSet = new Set(forecastDates);
18
- // Find the last historical point for connection
19
- const historicalPoints = clonedData.filter(point => point.historical !== undefined);
20
- const lastHistoricalPoint = historicalPoints.length > 0
21
- ? historicalPoints[historicalPoints.length - 1]
22
- : null;
23
- const lastHistoricalDate = lastHistoricalPoint?.date;
24
- const lastHistoricalValue = lastHistoricalPoint?.historical;
25
- // Get first forecast date
26
- const firstForecastDate = forecastDates[0];
27
- const firstForecastDateObj = firstForecastDate
28
- ? new Date(firstForecastDate)
29
- : null;
30
- const lastHistoricalDateObj = lastHistoricalDate
31
- ? new Date(lastHistoricalDate)
32
- : null;
33
- // Check if there's a gap between historical and forecast data (forecast starts after historical)
34
- const hasGap = lastHistoricalDate &&
35
- firstForecastDate &&
36
- lastHistoricalValue !== undefined &&
37
- firstForecastDateObj &&
38
- lastHistoricalDateObj &&
39
- firstForecastDateObj.getTime() > lastHistoricalDateObj.getTime();
40
- // Check if forecast starts before or at last historical point (need bridge point)
41
- const needsBridgePoint = lastHistoricalDate &&
42
- firstForecastDate &&
43
- lastHistoricalValue !== undefined &&
44
- firstForecastDateObj &&
45
- lastHistoricalDateObj &&
46
- firstForecastDateObj.getTime() <= lastHistoricalDateObj.getTime();
47
- // Find bridge point when forecast starts before or at last historical point
48
- let bridgePoint = null;
49
- let pointBeforeForecast = null;
50
- if (needsBridgePoint && historicalPoints.length > 0) {
51
- // Find the last historical point before or at the first forecast date
52
- // If dates are equal, use lastHistoricalPoint; otherwise find the point before
53
- bridgePoint =
54
- firstForecastDateObj &&
55
- lastHistoricalDateObj &&
56
- firstForecastDateObj.getTime() === lastHistoricalDateObj.getTime()
57
- ? lastHistoricalPoint
58
- : [...historicalPoints]
59
- .reverse()
60
- .find(p => firstForecastDateObj &&
61
- new Date(p.date).getTime() < firstForecastDateObj.getTime()) || lastHistoricalPoint;
62
- // Find the actual point BEFORE the first forecast date for connection
63
- if (firstForecastDateObj) {
64
- pointBeforeForecast = [...historicalPoints].findLast(p => new Date(p.date).getTime() < firstForecastDateObj.getTime());
65
- }
66
- }
67
- // Create a map from date to quantile array index
68
18
  const dateToQuantileIndex = new Map();
69
19
  forecastDates.forEach((date, index) => {
70
20
  dateToQuantileIndex.set(date, index);
71
21
  });
72
- const result = clonedData.map(point => {
22
+ const withRawBands = clonedData.map(point => {
73
23
  const newPoint = { ...point };
74
- // If there's a gap and this is the last historical point, add band data for connection
75
- if (hasGap &&
76
- point.date === lastHistoricalDate &&
77
- lastHistoricalValue !== undefined) {
78
- // Set zero-width band at the last historical value for visual connection
79
- newPoint[bandKey] = [lastHistoricalValue, lastHistoricalValue];
80
- }
81
- // If forecast starts before or at last historical point, add bridge point connection
82
- // Set connection band at the point BEFORE the first forecast date (if exists)
83
- const isBridgePointDate = needsBridgePoint &&
84
- bridgePoint &&
85
- bridgePoint.historical !== undefined &&
86
- point.date === bridgePoint.date;
87
- const isPointBeforeForecast = needsBridgePoint &&
88
- pointBeforeForecast &&
89
- pointBeforeForecast.historical !== undefined &&
90
- point.date === pointBeforeForecast.date;
91
- const isAlsoForecastDate = forecastDatesSet.has(point.date);
92
- // Set zero-width connection band at the point BEFORE forecast starts
93
- if (isPointBeforeForecast && !isAlsoForecastDate) {
94
- newPoint[bandKey] = [
95
- pointBeforeForecast.historical,
96
- pointBeforeForecast.historical,
97
- ];
98
- }
99
- else if (isBridgePointDate && !isAlsoForecastDate) {
100
- // Fallback: if no point before forecast, use bridge point
101
- newPoint[bandKey] = [
102
- bridgePoint.historical,
103
- bridgePoint.historical,
104
- ];
105
- }
106
- // Only update band data for forecast dates
107
24
  if (forecastDatesSet.has(point.date)) {
108
25
  const quantileIndex = dateToQuantileIndex.get(point.date);
109
26
  if (quantileIndex !== undefined) {
110
27
  const bandValues = getBandValues(point.date, quantileIndex, allQuantilesData);
111
28
  if (bandValues) {
112
- // If this is also the bridge point (forecast starts at same date as last historical),
113
- // start the band from the historical value for smooth connection
114
- const isBridgePointDate = needsBridgePoint &&
115
- bridgePoint &&
116
- point.date === bridgePoint.date &&
117
- bridgePoint.historical !== undefined;
118
- if (isBridgePointDate && quantileIndex === 0) {
119
- // Start from historical value, expand to forecast upper bound
120
- newPoint[bandKey] = [bridgePoint.historical, bandValues[1]];
121
- }
122
- else {
123
- newPoint[bandKey] = bandValues;
124
- }
29
+ newPoint[bandKey] = bandValues;
125
30
  }
126
31
  else {
127
- // Remove band data if values don't exist
128
32
  delete newPoint[bandKey];
129
33
  }
130
34
  }
131
35
  }
132
- // For non-forecast dates, preserve existing band data if it exists
133
- // This ensures continuity of the band visualization
134
36
  return newPoint;
135
37
  });
136
- return result;
38
+ return applyQuantileBandConeToChartData(withRawBands, bandKey);
137
39
  }, [chartData, selectedForecastId, forecastData, bandKey, getBandValues]);
138
40
  const quantileBands = useMemo(() => {
139
41
  if (!selectedForecastId || !forecastData[selectedForecastId]) {
@@ -5,9 +5,14 @@ import { ToggleGroup, ToggleGroupItem } from '../ToggleGroup/ToggleGroup.js';
5
5
  import { TIME_RANGES } from './TimeRangeControls.constants.js';
6
6
  import S from './TimeRangeControls.styl.js';
7
7
 
8
- const TimeRangeControls = memo(({ timeRange, onTimeRangeChange, loading, }) => (jsx("div", { className: S.timeRangeContainer, children: jsx(ToggleGroup, { type: "single", value: timeRange, onValueChange: onTimeRangeChange, variant: "outline", disabled: loading, className: S.timeRangeToggleGroup, children: TIME_RANGES.map(range => (jsx(ToggleGroupItem, { value: range, className: S.timeRangeToggleItem, children: range }, range))) }) })));
8
+ function toggleValueForTimeRange(timeRange) {
9
+ return TIME_RANGES.includes(timeRange)
10
+ ? timeRange
11
+ : '';
12
+ }
13
+ const TimeRangeControls = memo(({ timeRange, onTimeRangeChange, loading }) => (jsx("div", { className: S.timeRangeContainer, children: jsx(ToggleGroup, { type: "single", value: toggleValueForTimeRange(timeRange), onValueChange: onTimeRangeChange, variant: "outline", disabled: loading, className: S.timeRangeToggleGroup, children: TIME_RANGES.map(range => (jsx(ToggleGroupItem, { value: range, className: S.timeRangeToggleItem, children: range }, range))) }) })));
9
14
  TimeRangeControls.displayName = 'TimeRangeControls';
10
- const TimeRangeSelect = memo(({ timeRange, onTimeRangeChange, loading, }) => (jsxs(Select, { value: timeRange, onValueChange: onTimeRangeChange, disabled: loading, children: [jsx(SelectTrigger, { className: S.timeRangeSelectTrigger, size: "sm", "aria-label": "Select a value", children: jsx(SelectValue, { placeholder: "1 year" }) }), jsx(SelectContent, { className: S.timeRangeSelectContent, children: TIME_RANGES.map(range => (jsx(SelectItem, { value: range, className: S.timeRangeSelectItem, children: range }, range))) })] })));
15
+ const TimeRangeSelect = memo(({ timeRange, onTimeRangeChange, loading, }) => (jsxs(Select, { value: toggleValueForTimeRange(timeRange) || undefined, onValueChange: onTimeRangeChange, disabled: loading, children: [jsx(SelectTrigger, { className: S.timeRangeSelectTrigger, size: "sm", "aria-label": "Select a value", children: jsx(SelectValue, { placeholder: "1 year" }) }), jsx(SelectContent, { className: S.timeRangeSelectContent, children: TIME_RANGES.map(range => (jsx(SelectItem, { value: range, className: S.timeRangeSelectItem, children: range }, range))) })] })));
11
16
  TimeRangeSelect.displayName = 'TimeRangeSelect';
12
17
 
13
18
  export { TimeRangeControls, TimeRangeSelect };
@@ -0,0 +1,11 @@
1
+ import { jsx } from 'react/jsx-runtime';
2
+ import cn from 'classnames';
3
+ import S from './WorldMap.styl.js';
4
+ import mapBgUrl from './map.svg.js';
5
+
6
+ function WorldMap({ className }) {
7
+ const src = mapBgUrl;
8
+ return (jsx("img", { alt: "", className: cn(S.worldMap, className), decoding: "async", draggable: false, src: src }));
9
+ }
10
+
11
+ export { WorldMap };
@@ -0,0 +1,7 @@
1
+ import styleInject from 'style-inject';
2
+
3
+ var css_248z = ".WorldMap_worldMap__XTiex{aspect-ratio:623.2/341.276;display:block;height:100%;max-width:100%;-o-object-fit:contain;object-fit:contain;-o-object-position:center;object-position:center;pointer-events:none;-webkit-user-select:none;-moz-user-select:none;user-select:none;width:100%}";
4
+ var S = {"worldMap":"WorldMap_worldMap__XTiex"};
5
+ styleInject(css_248z);
6
+
7
+ export { S as default };