@sybilion/uilib 1.2.26 → 1.3.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/esm/components/ui/Chart/Chart.js +1 -0
- package/dist/esm/components/ui/Chart/components/BaseChartWrapper.js +7 -32
- package/dist/esm/components/ui/Chart/components/ChartEmptyState/ChartEmptyState.js +21 -0
- package/dist/esm/components/ui/Chart/components/ChartEmptyState/ChartEmptyState.styl.js +7 -0
- package/dist/esm/components/ui/Chart/tools/chartPlotGeometry.js +65 -0
- package/dist/esm/components/ui/ChartAreaInteractive/ChartAreaInteractive.helpers.js +37 -1
- package/dist/esm/components/ui/ChartAreaInteractive/ChartAreaInteractive.js +5 -2
- package/dist/esm/components/ui/ChartAreaInteractive/TimeRangeBrushLayer.js +205 -0
- package/dist/esm/components/ui/ChartAreaInteractive/TimeRangeBrushLayer.styl.js +7 -0
- package/dist/esm/components/ui/ChartAreaInteractive/TimeRangeBrushLayout.helpers.js +37 -0
- package/dist/esm/components/ui/ChartAreaInteractive/overlays/IntervalsOverlay/IntervalsOverlay.hooks.js +1 -0
- package/dist/esm/components/ui/ChartAreaInteractive/overlays/PinOverlay/PinOverlay.js +7 -60
- package/dist/esm/components/ui/ChartAreaInteractive/overlays/PinOverlay/PinOverlay.styl.js +2 -2
- package/dist/esm/components/ui/ChartAreaInteractive/overlays/ThresholdsOverlay/ThresholdsOverlay.hooks.js +1 -0
- package/dist/esm/components/ui/ChartAreaInteractive/overlays/useChartYRange.js +2 -4
- package/dist/esm/components/ui/TimeRangeControls/TimeRangeControls.js +7 -2
- package/dist/esm/components/ui/WorldMap/WorldMap.js +11 -0
- package/dist/esm/components/ui/WorldMap/WorldMap.styl.js +7 -0
- package/dist/esm/components/widgets/DriverCard/DriverCard.js +89 -0
- package/dist/esm/components/widgets/DriverCard/DriverCard.styl.js +7 -0
- package/dist/esm/components/widgets/DriverCard/DriverPerformanceChart.js +79 -0
- package/dist/esm/components/widgets/DriverCard/DriverPerformanceChart.styl.js +7 -0
- package/dist/esm/components/widgets/DriverCard/driverPerformanceChartData.js +50 -0
- package/dist/esm/components/widgets/DriverMap/DriverMap.js +2 -2
- package/dist/esm/components/widgets/DriverMap/DriverMap.styl.js +2 -2
- package/dist/esm/index.js +3 -2
- package/dist/esm/types/src/components/ui/Chart/Chart.d.ts +1 -0
- package/dist/esm/types/src/components/ui/Chart/components/BaseChartWrapper.d.ts +2 -1
- package/dist/esm/types/src/components/ui/Chart/components/ChartEmptyState/ChartEmptyState.d.ts +14 -0
- package/dist/esm/types/src/components/ui/Chart/tools/chartPlotGeometry.d.ts +30 -0
- package/dist/esm/types/src/components/ui/ChartAreaInteractive/ChartAreaInteractive.d.ts +1 -1
- package/dist/esm/types/src/components/ui/ChartAreaInteractive/ChartAreaInteractive.helpers.d.ts +11 -2
- package/dist/esm/types/src/components/ui/ChartAreaInteractive/ChartAreaInteractive.types.d.ts +2 -2
- package/dist/esm/types/src/components/ui/ChartAreaInteractive/TimeRangeBrushLayer.d.ts +15 -0
- package/dist/esm/types/src/components/ui/ChartAreaInteractive/TimeRangeBrushLayout.helpers.d.ts +14 -0
- package/dist/esm/types/src/components/ui/ChartAreaInteractive/overlays/PinOverlay/PinOverlay.d.ts +1 -1
- package/dist/esm/types/src/components/ui/Page/PageColumns/PageColumns.d.ts +1 -1
- package/dist/esm/types/src/components/ui/TimeRangeControls/TimeRangeControls.d.ts +5 -7
- package/dist/esm/types/src/components/ui/WorldMap/WorldMap.d.ts +4 -0
- package/dist/esm/types/src/components/ui/WorldMap/index.d.ts +2 -0
- package/dist/esm/types/src/components/widgets/DriverCard/DriverCard.d.ts +9 -0
- package/dist/esm/types/src/components/widgets/DriverCard/DriverPerformanceChart.d.ts +5 -0
- package/dist/esm/types/src/components/widgets/DriverCard/driverPerformanceChartData.d.ts +7 -0
- package/dist/esm/types/src/components/widgets/DriverCard/index.d.ts +1 -0
- package/dist/esm/types/src/components/widgets/DriverMap/index.d.ts +0 -2
- package/dist/esm/types/src/docs/pages/PageColumnsPage.d.ts +1 -0
- package/dist/esm/types/src/docs/pages/WorldMapPage.d.ts +1 -0
- package/dist/esm/types/src/index.d.ts +2 -0
- package/package.json +1 -1
- package/src/components/ui/Chart/Chart.tsx +5 -0
- package/src/components/ui/Chart/components/BaseChartWrapper.tsx +8 -41
- package/src/components/ui/Chart/components/ChartEmptyState/ChartEmptyState.styl +60 -0
- package/src/components/ui/Chart/components/ChartEmptyState/ChartEmptyState.styl.d.ts +15 -0
- package/src/components/ui/Chart/components/ChartEmptyState/ChartEmptyState.tsx +66 -0
- package/src/components/ui/Chart/tools/chartPlotGeometry.ts +89 -0
- package/src/components/ui/ChartAreaInteractive/ChartAreaInteractive.helpers.ts +44 -2
- package/src/components/ui/ChartAreaInteractive/ChartAreaInteractive.tsx +14 -1
- package/src/components/ui/ChartAreaInteractive/ChartAreaInteractive.types.ts +2 -3
- package/src/components/ui/ChartAreaInteractive/TimeRangeBrushLayer.styl +21 -0
- package/src/components/{widgets/DriverMap/LoadingSpinner/LoadingSpinner.styl.d.ts → ui/ChartAreaInteractive/TimeRangeBrushLayer.styl.d.ts} +3 -3
- package/src/components/ui/ChartAreaInteractive/TimeRangeBrushLayer.tsx +285 -0
- package/src/components/ui/ChartAreaInteractive/TimeRangeBrushLayout.helpers.ts +55 -0
- package/src/components/ui/ChartAreaInteractive/overlays/IntervalsOverlay/IntervalsOverlay.hooks.ts +1 -0
- package/src/components/ui/ChartAreaInteractive/overlays/PinOverlay/PinOverlay.styl +2 -7
- package/src/components/ui/ChartAreaInteractive/overlays/PinOverlay/PinOverlay.styl.d.ts +0 -1
- package/src/components/ui/ChartAreaInteractive/overlays/PinOverlay/PinOverlay.tsx +7 -71
- package/src/components/ui/ChartAreaInteractive/overlays/ThresholdsOverlay/ThresholdsOverlay.hooks.ts +1 -0
- package/src/components/ui/ChartAreaInteractive/overlays/useChartYRange.ts +2 -3
- package/src/components/ui/Page/PageColumns/PageColumns.tsx +1 -1
- package/src/components/ui/TimeRangeControls/TimeRangeControls.tsx +16 -17
- package/src/components/{widgets/DriverMap/MapBackground/MapBackground.styl → ui/WorldMap/WorldMap.styl} +1 -3
- package/src/components/{widgets/DriverMap/MapBackground/MapBackground.styl.d.ts → ui/WorldMap/WorldMap.styl.d.ts} +1 -1
- package/src/components/ui/WorldMap/WorldMap.tsx +22 -0
- package/src/components/ui/WorldMap/index.ts +2 -0
- package/src/components/widgets/DriverCard/DriverCard.styl +169 -0
- package/src/components/widgets/DriverCard/DriverCard.styl.d.ts +40 -0
- package/src/components/widgets/DriverCard/DriverCard.tsx +219 -0
- package/src/components/widgets/DriverCard/DriverPerformanceChart.styl +43 -0
- package/src/components/widgets/DriverCard/DriverPerformanceChart.styl.d.ts +13 -0
- package/src/components/widgets/DriverCard/DriverPerformanceChart.tsx +150 -0
- package/src/components/widgets/DriverCard/driverPerformanceChartData.ts +64 -0
- package/src/components/widgets/DriverCard/index.ts +1 -0
- package/src/components/widgets/DriverMap/DriverIcon/DriverIcon.tsx +0 -2
- package/src/components/widgets/DriverMap/DriverMap.styl +6 -1
- package/src/components/widgets/DriverMap/DriverMap.styl.d.ts +1 -0
- package/src/components/widgets/DriverMap/DriverMap.tsx +2 -4
- package/src/components/widgets/DriverMap/driverCategoryIcon.tsx +0 -2
- package/src/components/widgets/DriverMap/index.ts +0 -2
- package/src/docs/config/webpack.config.js +1 -1
- package/src/docs/pages/ChartAreaInteractivePage.tsx +2 -3
- package/src/docs/pages/DriverMapPage.tsx +214 -60
- package/src/docs/pages/PageColumnsPage.tsx +92 -0
- package/src/docs/pages/TimeRangeControlsPage.tsx +2 -3
- package/src/docs/pages/WorldMapPage.styl +14 -0
- package/src/docs/pages/WorldMapPage.styl.d.ts +8 -0
- package/src/docs/pages/WorldMapPage.tsx +26 -0
- package/src/docs/registry.ts +13 -1
- package/src/index.ts +2 -0
- package/dist/esm/components/widgets/DriverMap/LoadingSpinner/LoadingSpinner.js +0 -8
- package/dist/esm/components/widgets/DriverMap/LoadingSpinner/LoadingSpinner.styl.js +0 -7
- package/dist/esm/components/widgets/DriverMap/MapBackground/MapBackground.js +0 -10
- package/dist/esm/components/widgets/DriverMap/MapBackground/MapBackground.styl.js +0 -7
- package/dist/esm/types/src/components/widgets/DriverMap/LoadingSpinner/LoadingSpinner.d.ts +0 -1
- package/dist/esm/types/src/components/widgets/DriverMap/MapBackground/MapBackground.d.ts +0 -1
- package/src/components/widgets/DriverMap/LoadingSpinner/LoadingSpinner.styl +0 -24
- package/src/components/widgets/DriverMap/LoadingSpinner/LoadingSpinner.tsx +0 -11
- package/src/components/widgets/DriverMap/MapBackground/MapBackground.tsx +0 -18
- /package/dist/esm/components/{widgets/DriverMap/MapBackground → ui/WorldMap}/map.svg.js +0 -0
- /package/src/components/{widgets/DriverMap/MapBackground → ui/WorldMap}/map.svg +0 -0
- /package/src/components/{widgets/DriverMap/MapBackground → ui/WorldMap}/mapAspect.mixin.styl +0 -0
- /package/src/components/{widgets/DriverMap/MapBackground → ui/WorldMap}/mapAspect.mixin.styl.d.ts +0 -0
|
@@ -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
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
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,
|
|
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}.
|
|
4
|
-
var S = {"root":"PinOverlay_root__RUZxO","pinContainer":"PinOverlay_pinContainer__6sc1J","pinHovered":"PinOverlay_pinHovered__cGPjN","pinLineBase":"PinOverlay_pinLineBase__0BWsD","
|
|
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
|
|
19
|
-
// (exclude
|
|
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
|
|
@@ -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
|
-
|
|
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 };
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import { jsx, jsxs } from 'react/jsx-runtime';
|
|
2
|
+
import React__default, { useMemo } from 'react';
|
|
3
|
+
import { CoffeeIcon, TrendUpIcon, TrendDownIcon } from '@phosphor-icons/react';
|
|
4
|
+
import { Badge } from '../../ui/Badge/Badge.js';
|
|
5
|
+
import { Card, CardContent } from '../../ui/Card/Card.js';
|
|
6
|
+
import { LabelWithId } from '../../ui/LabelWithId/LabelWithId.js';
|
|
7
|
+
import { Skeleton } from '../../ui/Skeleton/Skeleton.js';
|
|
8
|
+
import { Tooltip, TooltipTrigger, TooltipContent } from '../../ui/Tooltip/Tooltip.js';
|
|
9
|
+
import { getCategoryIcon } from '../DriverMap/driverCategoryIcon.js';
|
|
10
|
+
import S from './DriverCard.styl.js';
|
|
11
|
+
import { DriverPerformanceChart } from './DriverPerformanceChart.js';
|
|
12
|
+
|
|
13
|
+
function DriverCard({ selectedDriver, isLoading, inQueue = false, driverSelector, }) {
|
|
14
|
+
const importanceDisplay = useMemo(() => {
|
|
15
|
+
if (!selectedDriver)
|
|
16
|
+
return '';
|
|
17
|
+
let importance = 0;
|
|
18
|
+
if (selectedDriver.rawImportance &&
|
|
19
|
+
typeof selectedDriver.rawImportance === 'object') {
|
|
20
|
+
const importanceObj = selectedDriver.rawImportance;
|
|
21
|
+
if (importanceObj.overall && typeof importanceObj.overall === 'object') {
|
|
22
|
+
const overall = importanceObj.overall;
|
|
23
|
+
if (typeof overall.mean === 'number') {
|
|
24
|
+
importance = Math.round(overall.mean);
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
return importance > 0
|
|
29
|
+
? `${importance}%`
|
|
30
|
+
: `${selectedDriver.importance.toFixed(1)}%`;
|
|
31
|
+
}, [selectedDriver]);
|
|
32
|
+
const regionDisplay = React__default.useMemo(() => {
|
|
33
|
+
if (!selectedDriver)
|
|
34
|
+
return '';
|
|
35
|
+
// Check for src_region and tgt_region properties first
|
|
36
|
+
const driver = selectedDriver;
|
|
37
|
+
const srcRegion = driver.src_region;
|
|
38
|
+
const tgtRegion = driver.tgt_region;
|
|
39
|
+
const srcName = srcRegion?.name;
|
|
40
|
+
const tgtName = tgtRegion?.name;
|
|
41
|
+
// If both src_region and tgt_region have valid names
|
|
42
|
+
if (srcName && tgtName) {
|
|
43
|
+
// If source and target are the same, just show one
|
|
44
|
+
if (srcName === tgtName) {
|
|
45
|
+
return srcName;
|
|
46
|
+
}
|
|
47
|
+
return `${srcName} → ${tgtName}`;
|
|
48
|
+
}
|
|
49
|
+
// If only src_region has a valid name
|
|
50
|
+
if (srcName) {
|
|
51
|
+
if (srcName === 'World') {
|
|
52
|
+
return 'World';
|
|
53
|
+
}
|
|
54
|
+
return `${srcName} → World`;
|
|
55
|
+
}
|
|
56
|
+
// If only tgt_region has a valid name
|
|
57
|
+
if (tgtName) {
|
|
58
|
+
if (tgtName === 'World') {
|
|
59
|
+
return 'World';
|
|
60
|
+
}
|
|
61
|
+
return `World → ${tgtName}`;
|
|
62
|
+
}
|
|
63
|
+
if (selectedDriver.region.length === 1)
|
|
64
|
+
return selectedDriver.region[0];
|
|
65
|
+
if (selectedDriver.region.length > 1) {
|
|
66
|
+
return (selectedDriver.region
|
|
67
|
+
// .filter(region => region !== 'World')
|
|
68
|
+
.join(' → '));
|
|
69
|
+
}
|
|
70
|
+
// Fallback to "World" when no region information is available
|
|
71
|
+
return 'World';
|
|
72
|
+
}, [selectedDriver]);
|
|
73
|
+
if (inQueue) {
|
|
74
|
+
return (jsx(Card, { className: S.root, children: jsx(CardContent, { centered: true, fullHeight: true, noScroll: true, children: jsxs("div", { className: S.queueMessage, children: [jsx(CoffeeIcon, { size: 24, weight: "regular" }), jsxs("div", { children: [jsx("p", { children: "Retrieving drivers data." }), jsx("p", { children: "This may take a while." })] })] }) }) }));
|
|
75
|
+
}
|
|
76
|
+
if (isLoading) {
|
|
77
|
+
return (jsx(Card, { className: S.root, children: jsx(CardContent, { noScroll: true, children: jsxs("div", { className: S.skeletonContainer, children: [jsx(Skeleton, { className: `${S.skeletonItem} ${S.hs} ${S.w75}` }), jsx(Skeleton, { className: `${S.skeletonItem} ${S.hm} ${S.w50}` }), jsx(Skeleton, { className: `${S.skeletonItem} ${S.hs} ${S.w33}` }), jsxs("div", { className: S.skeletonGroup, children: [jsx(Skeleton, { className: `${S.skeletonItem} ${S.hs} ${S.wFull}` }), jsx(Skeleton, { className: `${S.skeletonItem} ${S.hs} ${S.w75}` }), jsx(Skeleton, { className: `${S.skeletonItem} ${S.hs} ${S.w50}` })] }), jsx(Skeleton, { className: `${S.skeletonItem} ${S.hl} ${S.wFull}` }), jsxs("div", { className: S.skeletonGroup, children: [jsx(Skeleton, { className: `${S.skeletonItem} ${S.hs} ${S.wFull}` }), jsx(Skeleton, { className: `${S.skeletonItem} ${S.hs} ${S.w75}` })] })] }) }) }));
|
|
78
|
+
}
|
|
79
|
+
if (!selectedDriver) {
|
|
80
|
+
return (jsx(Card, { className: S.root, children: jsx(CardContent, { centered: true, fullHeight: true, noScroll: true, children: jsx("span", { className: S.noDriverSelected, children: "No driver selected" }) }) }));
|
|
81
|
+
}
|
|
82
|
+
const { id, name, category, direction, lag, summary } = selectedDriver;
|
|
83
|
+
const directionText = direction > 0 ? 'Positive' : 'Negative';
|
|
84
|
+
const DirectionIcon = direction > 0 ? TrendUpIcon : TrendDownIcon;
|
|
85
|
+
const nameElem = (jsx("h4", { className: `${S.driverTitle} ${S.truncated}`, children: name }));
|
|
86
|
+
return (jsx(Card, { className: S.root, paddingSize: "l", children: jsx(CardContent, { noScroll: true, children: jsxs("div", { className: S.cardContent, children: [jsx("div", { className: S.driverHeader, children: jsxs("div", { className: S.headerContent, children: [jsxs("div", { className: S.topHeader, children: [jsxs("p", { className: S.categoryInfo, children: [jsx("span", { className: S.categoryIcon, children: getCategoryIcon(category) }), jsx("span", { className: S.categoryText, children: category })] }), driverSelector] }), name.length > 60 ? (jsxs(Tooltip, { children: [jsx(LabelWithId, { id: id, label: jsx(TooltipTrigger, { asChild: true, children: nameElem }) }), jsx(TooltipContent, { side: "left", className: S.tooltipContent, children: jsx("div", { className: S.tooltipTitle, children: name }) })] })) : (jsx(LabelWithId, { id: id, label: nameElem })), jsx("p", { className: S.regionDisplay, children: regionDisplay })] }) }), jsx("div", { className: S.metricsSection, children: jsx("div", { className: S.importanceScore, children: importanceDisplay }) }), jsxs("div", { className: S.directionLagSection, children: [jsxs(Badge, { variant: direction > 0 ? 'green' : 'red', className: S.directionBadge, children: [jsx(DirectionIcon, { className: S.trendIcon }), directionText, " correlation"] }), jsxs("span", { className: S.lagInfo, children: ["Lag: ", lag] })] }), jsx(DriverPerformanceChart, { driver: selectedDriver }), jsx("p", { className: S.description, children: summary ?? '' })] }) }) }));
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
export { DriverCard };
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import styleInject from 'style-inject';
|
|
2
|
+
|
|
3
|
+
var css_248z = "@media (max-width:768px){:root{--page-x-padding:var(--p-6);--page-y-padding:var(--p-6)}}.DriverCard_root__Kr5Xz{height:auto;min-height:500px;width:100%}@media (min-width:640px){.DriverCard_root__Kr5Xz{min-height:25rem}}@media (min-width:768px){.DriverCard_root__Kr5Xz{min-height:31.25rem}}.DriverCard_cardContent__zB8UV{display:flex;flex-direction:column;gap:.5rem}.DriverCard_noDriverSelected__-mVm-{color:var(--muted-foreground)}.DriverCard_driverHeader__0aeJ-{align-items:flex-start;display:flex;flex-direction:column;gap:.75rem;padding-bottom:.25rem}.DriverCard_headerContent__YNqtK{display:flex;flex-direction:column;flex-grow:1;gap:.5rem;width:100%}.DriverCard_topHeader__sUDUc{align-items:center;display:flex;justify-content:space-between;margin-top:-12px;min-height:36px}.DriverCard_categoryInfo__8Dhqn{align-items:center;color:var(--muted-foreground);display:flex;font-size:.75rem;gap:.25rem}.DriverCard_categoryIcon__lSIM8{color:var(--muted-foreground);height:.75rem;width:.75rem}.DriverCard_categoryText__DuifF{display:-webkit-box;-webkit-line-clamp:1;-webkit-box-orient:vertical;overflow:hidden;text-transform:lowercase}.DriverCard_categoryText__DuifF:first-letter{text-transform:uppercase}.DriverCard_driverTitle__tYQO1{color:var(--foreground);font-size:1.1rem;font-weight:500;line-height:1.4;-webkit-user-select:text;-moz-user-select:text;user-select:text}.DriverCard_driverTitle__tYQO1.DriverCard_truncated__RIn6f{display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;cursor:help;overflow:hidden}.DriverCard_regionDisplay__QYVWx{color:var(--muted-foreground);font-size:.75rem}.DriverCard_metricsSection__uZViQ{margin-bottom:.5rem;text-align:left}.DriverCard_importanceScore__RGh5Y{align-items:center;color:var(--foreground);display:flex;font-size:2.25rem;font-weight:400;height:auto;line-height:1;-webkit-user-select:text;-moz-user-select:text;user-select:text}.DriverCard_directionLagSection__Mh-ep{align-items:center;display:flex;justify-content:space-between}.DriverCard_directionBadge__7EAHe{align-items:center;display:flex;gap:.5rem}.DriverCard_trendIcon__GtkDe{height:.75rem;width:.75rem}.DriverCard_lagInfo__enbxl{color:var(--muted-foreground);font-size:.75rem}.DriverCard_description__fNR4e{font-size:.875rem;line-height:1.625;-webkit-user-select:text;-moz-user-select:text;user-select:text}.DriverCard_skeletonContainer__AE9MP{display:flex;flex-direction:column;gap:1rem}.DriverCard_skeletonGroup__lQXls{display:flex;flex-direction:column;gap:.5rem}.DriverCard_skeletonItem__pmx4S.DriverCard_hxs__taJow{height:.75rem}.DriverCard_skeletonItem__pmx4S.DriverCard_hs__PlDPB{height:1rem}.DriverCard_skeletonItem__pmx4S.DriverCard_hm__oFwUK{height:2rem}.DriverCard_skeletonItem__pmx4S.DriverCard_hl__McKZn{height:8rem}.DriverCard_skeletonItem__pmx4S.DriverCard_w33__cI-vq{width:33.333333%}.DriverCard_skeletonItem__pmx4S.DriverCard_w50__DcUSo{width:50%}.DriverCard_skeletonItem__pmx4S.DriverCard_w75__To5oR{width:75%}.DriverCard_skeletonItem__pmx4S.DriverCard_w80__y8wQl{width:80%}.DriverCard_skeletonItem__pmx4S.DriverCard_wFull__TnaxF{width:100%}.DriverCard_tooltipContent__F6DUH{font-size:.75rem;max-width:20rem}.DriverCard_tooltipTitle__NieWB{font-family:var(--font-family-body);font-weight:400}.DriverCard_queueMessage__4hQxI{align-items:center;color:var(--muted-foreground);display:flex;flex-direction:column;font-size:.875rem;gap:var(--p-4);line-height:1.5;padding:var(--p-8) var(--p-4);text-align:center}.DriverCard_queueMessage__4hQxI svg{color:var(--muted-foreground);flex-shrink:0;opacity:.8}";
|
|
4
|
+
var S = {"root":"DriverCard_root__Kr5Xz","cardContent":"DriverCard_cardContent__zB8UV","noDriverSelected":"DriverCard_noDriverSelected__-mVm-","driverHeader":"DriverCard_driverHeader__0aeJ-","headerContent":"DriverCard_headerContent__YNqtK","topHeader":"DriverCard_topHeader__sUDUc","categoryInfo":"DriverCard_categoryInfo__8Dhqn","categoryIcon":"DriverCard_categoryIcon__lSIM8","categoryText":"DriverCard_categoryText__DuifF","driverTitle":"DriverCard_driverTitle__tYQO1","truncated":"DriverCard_truncated__RIn6f","regionDisplay":"DriverCard_regionDisplay__QYVWx","metricsSection":"DriverCard_metricsSection__uZViQ","importanceScore":"DriverCard_importanceScore__RGh5Y","directionLagSection":"DriverCard_directionLagSection__Mh-ep","directionBadge":"DriverCard_directionBadge__7EAHe","trendIcon":"DriverCard_trendIcon__GtkDe","lagInfo":"DriverCard_lagInfo__enbxl","description":"DriverCard_description__fNR4e","skeletonContainer":"DriverCard_skeletonContainer__AE9MP","skeletonGroup":"DriverCard_skeletonGroup__lQXls","skeletonItem":"DriverCard_skeletonItem__pmx4S","hxs":"DriverCard_hxs__taJow","hs":"DriverCard_hs__PlDPB","hm":"DriverCard_hm__oFwUK","hl":"DriverCard_hl__McKZn","w33":"DriverCard_w33__cI-vq","w50":"DriverCard_w50__DcUSo","w75":"DriverCard_w75__To5oR","w80":"DriverCard_w80__y8wQl","wFull":"DriverCard_wFull__TnaxF","tooltipContent":"DriverCard_tooltipContent__F6DUH","tooltipTitle":"DriverCard_tooltipTitle__NieWB","queueMessage":"DriverCard_queueMessage__4hQxI"};
|
|
5
|
+
styleInject(css_248z);
|
|
6
|
+
|
|
7
|
+
export { S as default };
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import { jsxs, jsx } from 'react/jsx-runtime';
|
|
2
|
+
import cn from 'classnames';
|
|
3
|
+
import { useMemo } from 'react';
|
|
4
|
+
import { LineChart, CartesianGrid, XAxis, YAxis, Tooltip, Line } from 'recharts';
|
|
5
|
+
import '../../ui/Chart/components/BaseChartWrapper.js';
|
|
6
|
+
import { ChartContainer } from '../../ui/Chart/components/ChartContainer.js';
|
|
7
|
+
import '../../ui/Chart/Chart.context.js';
|
|
8
|
+
import '../../ui/Chart/Chart.styl.js';
|
|
9
|
+
import '../../ui/AnalysisLineIcon/AnalysisLineIcon.styl.js';
|
|
10
|
+
import '@radix-ui/react-slot';
|
|
11
|
+
import '../../ui/Badge/Badge.styl.js';
|
|
12
|
+
import '../../ui/LabelWithId/LabelWithId.styl.js';
|
|
13
|
+
import '@radix-ui/react-select';
|
|
14
|
+
import 'lucide-react';
|
|
15
|
+
import '../../ui/Select/Select.styl.js';
|
|
16
|
+
import '../../ui/TextShimmer/TextShimmer.js';
|
|
17
|
+
import '@phosphor-icons/react';
|
|
18
|
+
import '../../ui/AnalysesSelector/AnalysesSelector.styl.js';
|
|
19
|
+
import '../../ui/Chart/components/CustomChartLegend/CustomChartLegend.styl.js';
|
|
20
|
+
import { ChartEmptyState } from '../../ui/Chart/components/ChartEmptyState/ChartEmptyState.js';
|
|
21
|
+
import S from './DriverPerformanceChart.styl.js';
|
|
22
|
+
import { generateDriverChartData } from './driverPerformanceChartData.js';
|
|
23
|
+
|
|
24
|
+
const driverChartConfig = {
|
|
25
|
+
value: {
|
|
26
|
+
label: 'Performance',
|
|
27
|
+
color: 'var(--primary)',
|
|
28
|
+
},
|
|
29
|
+
};
|
|
30
|
+
function DriverPerformanceChart({ driver, }) {
|
|
31
|
+
const precision = useMemo(() => {
|
|
32
|
+
if (!driver.normalized_series)
|
|
33
|
+
return 7;
|
|
34
|
+
const values = Object.values(driver.normalized_series).filter((v) => typeof v === 'number' && v !== null);
|
|
35
|
+
if (values.length === 0)
|
|
36
|
+
return 7;
|
|
37
|
+
const absoluteValues = values.map(Math.abs).filter(v => v > 0);
|
|
38
|
+
if (absoluteValues.length === 0)
|
|
39
|
+
return 7;
|
|
40
|
+
const minAbs = Math.min(...absoluteValues);
|
|
41
|
+
if (minAbs < 0.0001)
|
|
42
|
+
return 7;
|
|
43
|
+
if (minAbs < 0.001)
|
|
44
|
+
return 6;
|
|
45
|
+
if (minAbs < 0.01)
|
|
46
|
+
return 5;
|
|
47
|
+
if (minAbs < 0.1)
|
|
48
|
+
return 4;
|
|
49
|
+
if (minAbs < 1)
|
|
50
|
+
return 3;
|
|
51
|
+
return 2;
|
|
52
|
+
}, [driver.normalized_series]);
|
|
53
|
+
const chartData = useMemo(() => generateDriverChartData(driver, precision), [driver, precision]);
|
|
54
|
+
const noSeriesData = useMemo(() => {
|
|
55
|
+
if (!driver.normalized_series)
|
|
56
|
+
return false;
|
|
57
|
+
return Object.values(driver.normalized_series).every(value => value === null);
|
|
58
|
+
}, [driver.normalized_series]);
|
|
59
|
+
const renderTooltipContent = ({ active, payload, }) => {
|
|
60
|
+
if (active && payload?.length) {
|
|
61
|
+
const dataPoint = payload[0];
|
|
62
|
+
const row = dataPoint?.payload;
|
|
63
|
+
const date = row?.date;
|
|
64
|
+
const value = dataPoint?.value;
|
|
65
|
+
return (jsxs("div", { className: S.chartTooltip, children: [jsx("div", { className: S.tooltipDate, children: date instanceof Date
|
|
66
|
+
? date.toLocaleDateString('en-US', {
|
|
67
|
+
month: 'long',
|
|
68
|
+
year: 'numeric',
|
|
69
|
+
})
|
|
70
|
+
: 'Unknown Date' }), jsxs("div", { className: S.tooltipPerformance, children: ["Performance:", ' ', typeof value === 'number'
|
|
71
|
+
? value.toFixed(precision)
|
|
72
|
+
: String(value ?? '')] })] }));
|
|
73
|
+
}
|
|
74
|
+
return null;
|
|
75
|
+
};
|
|
76
|
+
return (jsxs("div", { className: S.driverChartContainer, children: [jsx(ChartContainer, { config: driverChartConfig, className: cn(S.chartContainer, noSeriesData && S.chartContainerDisabled), children: jsxs(LineChart, { data: chartData, margin: { left: 8, right: 8, top: 8, bottom: -20 }, children: [jsx(CartesianGrid, { vertical: false }), jsx(XAxis, { dataKey: "date", tickLine: false, axisLine: false, tick: false, tickMargin: 0, minTickGap: 0 }), jsx(YAxis, { tickLine: false, axisLine: false, tick: false, tickMargin: 0, minTickGap: 0, width: 0, domain: ['dataMin', 'dataMax'] }), jsx(Tooltip, { cursor: false, content: renderTooltipContent }), jsx(Line, { dataKey: "value", type: "natural", stroke: "var(--color-value)" })] }) }), noSeriesData && (jsx(ChartEmptyState, { variant: "inline", align: "center", status: "No series data", className: S.noDataMessage }))] }));
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
export { DriverPerformanceChart };
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import styleInject from 'style-inject';
|
|
2
|
+
|
|
3
|
+
var css_248z = ".DriverPerformanceChart_driverChartContainer__27xZ-{position:relative;width:100%}.DriverPerformanceChart_driverChartContainer__27xZ- svg{overflow:visible}.DriverPerformanceChart_chartContainer__--rGL{height:214px;margin-left:-.5rem;padding:var(--p-4) 0;width:100%}.DriverPerformanceChart_chartContainerDisabled__ndtPZ{opacity:.1}.DriverPerformanceChart_noDataMessage__RO3u1{color:var(--foreground);font-size:.875rem;font-weight:500;left:50%;pointer-events:none;position:absolute;top:50%;transform:translate(-50%,-50%);z-index:10}.DriverPerformanceChart_chartTooltip__cpaQw{background-color:var(--popover);border:1px solid var(--border);border-radius:.5rem;box-shadow:0 10px 15px -3px rgba(0,0,0,.1);padding:.75rem}.DriverPerformanceChart_tooltipDate__B2aeP{color:var(--popover-foreground);font-size:.875rem;font-weight:500}.DriverPerformanceChart_tooltipPerformance__3hfgE{color:var(--muted-foreground);font-size:.875rem}";
|
|
4
|
+
var S = {"driverChartContainer":"DriverPerformanceChart_driverChartContainer__27xZ-","chartContainer":"DriverPerformanceChart_chartContainer__--rGL","chartContainerDisabled":"DriverPerformanceChart_chartContainerDisabled__ndtPZ","noDataMessage":"DriverPerformanceChart_noDataMessage__RO3u1","chartTooltip":"DriverPerformanceChart_chartTooltip__cpaQw","tooltipDate":"DriverPerformanceChart_tooltipDate__B2aeP","tooltipPerformance":"DriverPerformanceChart_tooltipPerformance__3hfgE"};
|
|
5
|
+
styleInject(css_248z);
|
|
6
|
+
|
|
7
|
+
export { S as default };
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/** Last non-null points from `normalized_series`, or deterministic fallback sample. */
|
|
2
|
+
function generateDriverChartData(driver, precision = 7) {
|
|
3
|
+
if (driver.normalized_series) {
|
|
4
|
+
const entries = Object.entries(driver.normalized_series);
|
|
5
|
+
const seriesData = entries
|
|
6
|
+
.filter(([, value]) => value !== null)
|
|
7
|
+
.map(([date, value]) => {
|
|
8
|
+
let parsedDate;
|
|
9
|
+
try {
|
|
10
|
+
const dateStr = date.toString();
|
|
11
|
+
if (dateStr.includes('-')) {
|
|
12
|
+
parsedDate = new Date(`${dateStr}T00:00:00`);
|
|
13
|
+
}
|
|
14
|
+
else {
|
|
15
|
+
parsedDate = new Date(dateStr);
|
|
16
|
+
}
|
|
17
|
+
if (Number.isNaN(parsedDate.getTime())) {
|
|
18
|
+
return null;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
catch {
|
|
22
|
+
return null;
|
|
23
|
+
}
|
|
24
|
+
const numericValue = parseFloat(value.toFixed(precision));
|
|
25
|
+
return {
|
|
26
|
+
date: parsedDate,
|
|
27
|
+
value: numericValue,
|
|
28
|
+
};
|
|
29
|
+
})
|
|
30
|
+
.filter((item) => item !== null)
|
|
31
|
+
.sort((a, b) => a.date.getTime() - b.date.getTime())
|
|
32
|
+
.slice(-24);
|
|
33
|
+
if (seriesData.length > 0) {
|
|
34
|
+
return seriesData;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
const data = [];
|
|
38
|
+
const baseValue = driver.importance / 100;
|
|
39
|
+
for (let i = 0; i < 12; i++) {
|
|
40
|
+
const randomVariation = (Math.random() - 0.5) * 0.2;
|
|
41
|
+
const value = Math.max(0, Math.min(1, baseValue + randomVariation));
|
|
42
|
+
data.push({
|
|
43
|
+
date: new Date(2023, i, 1),
|
|
44
|
+
value: parseFloat(value.toFixed(precision)),
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
return data;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export { generateDriverChartData };
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
import { jsxs, jsx } from 'react/jsx-runtime';
|
|
2
2
|
import cn from 'classnames';
|
|
3
3
|
import { useRef, useState, useMemo, useEffect, useCallback } from 'react';
|
|
4
|
+
import { WorldMap } from '../../ui/WorldMap/WorldMap.js';
|
|
4
5
|
import useEvent from '../../../hooks/useEvent.js';
|
|
5
6
|
import { Shimmer } from '@homecode/ui';
|
|
6
7
|
import { DriverIcon } from './DriverIcon/DriverIcon.js';
|
|
7
8
|
import constants from './DriverIcon/DriverIcon.constants.json.js';
|
|
8
9
|
import { findMostSpecificRegion, getDriverCoordinates, hasValidCoords, calculateBadgeSizes } from './DriverMap.helpers.js';
|
|
9
10
|
import S from './DriverMap.styl.js';
|
|
10
|
-
import { MapBackground } from './MapBackground/MapBackground.js';
|
|
11
11
|
import { getHighestImportanceDriver } from './driverMapSelection.js';
|
|
12
12
|
|
|
13
13
|
const delay = (ms) => new Promise(r => setTimeout(r, ms));
|
|
@@ -113,7 +113,7 @@ function DriverMap({ drivers, isLoading, setSelectedDriver, selectedDriver, }) {
|
|
|
113
113
|
callback: handleKeyDown,
|
|
114
114
|
isCapture: true,
|
|
115
115
|
});
|
|
116
|
-
return (jsxs("div", { className: cn(S.root, isTransitioning && S.inTransition), children: [jsxs("div", { className: S.mapInner, children: [jsx(
|
|
116
|
+
return (jsxs("div", { className: cn(S.root, isTransitioning && S.inTransition), children: [jsxs("div", { className: S.mapInner, children: [jsx(WorldMap, { className: S.mapWorld }), isLoading && jsx(Shimmer, { className: S.shimmerOverlay, size: "l" }), otherDrivers.map(driver => (jsx(DriverIcon, { isLoading: isLoading, isVisible: isVisible, driver: driver, size: badgeSizes[driver.id] || 's', isSelected: selectedDriver?.id === driver.id, onClick: () => setSelectedDriver(driver) }, driver.id)))] }), jsx("div", { className: S.worldDrivers, children: worldDrivers.map(driver => {
|
|
117
117
|
const driverWithCoords = {
|
|
118
118
|
...driver,
|
|
119
119
|
coordinates: driver.coordinates || {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import styleInject from 'style-inject';
|
|
2
2
|
|
|
3
|
-
var css_248z = "@media (max-width:768px){:root{--page-x-padding:var(--p-6);--page-y-padding:var(--p-6)}}.DriverMap_root__JszhG{aspect-ratio:623.2/341.276;max-width:100%;overflow:hidden;padding:0;position:relative;width:100%}@media (min-width:768px){.DriverMap_root__JszhG{flex:1;height:35rem;width:-moz-fit-content;width:fit-content}}.DriverMap_mapInner__E7rZR{aspect-ratio:623.2/341.276;border-radius:var(--p-4);margin:0 auto;max-height:100%;max-width:100%;overflow:hidden;position:relative}.DriverMap_shimmerOverlay__UH2qz{z-index:20}.DriverMap_inTransition__lvYwJ{pointer-events:none}.DriverMap_worldDrivers__sZOIW{align-items:center;bottom:0;display:flex;gap:.5rem;padding:.5rem;position:absolute}.DriverMap_worldDrivers__sZOIW button{position:static!important;transform:none!important}.DriverMap_worldDrivers__sZOIW button>span{opacity:1!important;transform:none!important}";
|
|
4
|
-
var S = {"root":"DriverMap_root__JszhG","mapInner":"DriverMap_mapInner__E7rZR","shimmerOverlay":"DriverMap_shimmerOverlay__UH2qz","inTransition":"DriverMap_inTransition__lvYwJ","worldDrivers":"DriverMap_worldDrivers__sZOIW"};
|
|
3
|
+
var css_248z = "@media (max-width:768px){:root{--page-x-padding:var(--p-6);--page-y-padding:var(--p-6)}}.DriverMap_root__JszhG{aspect-ratio:623.2/341.276;max-width:100%;overflow:hidden;padding:0;position:relative;width:100%}@media (min-width:768px){.DriverMap_root__JszhG{flex:1;height:35rem;width:-moz-fit-content;width:fit-content}}.DriverMap_mapInner__E7rZR{aspect-ratio:623.2/341.276;border-radius:var(--p-4);margin:0 auto;max-height:100%;max-width:100%;overflow:hidden;position:relative}.DriverMap_mapWorld__vmPp-{box-sizing:border-box;inset:0;position:absolute}.DriverMap_shimmerOverlay__UH2qz{z-index:20}.DriverMap_inTransition__lvYwJ{pointer-events:none}.DriverMap_worldDrivers__sZOIW{align-items:center;bottom:0;display:flex;gap:.5rem;padding:.5rem;position:absolute}.DriverMap_worldDrivers__sZOIW button{position:static!important;transform:none!important}.DriverMap_worldDrivers__sZOIW button>span{opacity:1!important;transform:none!important}";
|
|
4
|
+
var S = {"root":"DriverMap_root__JszhG","mapInner":"DriverMap_mapInner__E7rZR","mapWorld":"DriverMap_mapWorld__vmPp-","shimmerOverlay":"DriverMap_shimmerOverlay__UH2qz","inTransition":"DriverMap_inTransition__lvYwJ","worldDrivers":"DriverMap_worldDrivers__sZOIW"};
|
|
5
5
|
styleInject(css_248z);
|
|
6
6
|
|
|
7
7
|
export { S as default };
|
package/dist/esm/index.js
CHANGED
|
@@ -81,6 +81,7 @@ export { Toggle } from './components/ui/Toggle/Toggle.js';
|
|
|
81
81
|
export { ToggleGroup, ToggleGroupItem } from './components/ui/ToggleGroup/ToggleGroup.js';
|
|
82
82
|
export { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from './components/ui/Tooltip/Tooltip.js';
|
|
83
83
|
export { VimeoEmbed } from './components/ui/VimeoEmbed/VimeoEmbed.js';
|
|
84
|
+
export { WorldMap } from './components/ui/WorldMap/WorldMap.js';
|
|
84
85
|
export { WorkspaceAppSwitcher } from './components/ui/WorkspaceAppSwitcher/WorkspaceAppSwitcher.js';
|
|
85
86
|
export { WORKSPACE_APP_SLUG_BASE_PATH } from './components/ui/WorkspaceAppSwitcher/workspaceApp.types.js';
|
|
86
87
|
export { WORKSPACE_APPS_LS_KEY } from './components/ui/WorkspaceAppSwitcher/workspaceAppsConstants.js';
|
|
@@ -89,12 +90,11 @@ export { WORKSPACE_APP_ICONS, isWorkspaceAppIconKey } from './components/ui/Work
|
|
|
89
90
|
export { findWorkspaceAppByPathname, workspaceAppSlugPath } from './components/ui/WorkspaceAppSwitcher/workspaceAppPaths.js';
|
|
90
91
|
export { SidebarDatasetsItemsGrouped } from './components/widgets/SidebarDatasetsItemsGrouped/SidebarDatasetsItemsGrouped.js';
|
|
91
92
|
export { groupSidebarDatasets } from './components/widgets/SidebarDatasetsItemsGrouped/groupSidebarDatasets.js';
|
|
93
|
+
export { DriverCard } from './components/widgets/DriverCard/DriverCard.js';
|
|
92
94
|
export { DriverMap } from './components/widgets/DriverMap/DriverMap.js';
|
|
93
95
|
export { getCategoryIcon } from './components/widgets/DriverMap/driverCategoryIcon.js';
|
|
94
96
|
export { getDriverImportance, getHighestImportanceDriver } from './components/widgets/DriverMap/driverMapSelection.js';
|
|
95
97
|
export { geographicCoordinates, geographicToSVG, getContinentFromRegion, getPreciseCoordinates, getResponsiveCoordinates, svgToPercentage } from './components/widgets/DriverMap/driverMapGeography.js';
|
|
96
|
-
export { MapBackground } from './components/widgets/DriverMap/MapBackground/MapBackground.js';
|
|
97
|
-
export { LoadingSpinner } from './components/widgets/DriverMap/LoadingSpinner/LoadingSpinner.js';
|
|
98
98
|
export { SybilionAppHeader } from './components/widgets/SybilionAppHeader/SybilionAppHeader.js';
|
|
99
99
|
export { SybilionAuthLayout } from './components/widgets/SybilionAuthLayout/SybilionAuthLayout.js';
|
|
100
100
|
export { SybilionAuthHeadline } from './components/widgets/SybilionAuthLayout/SybilionAuthHeadline.js';
|
|
@@ -103,6 +103,7 @@ export { SignInPage } from './components/widgets/SignInPage/SignInPage.js';
|
|
|
103
103
|
export { ChartTooltipItem } from './components/ui/Chart/components/ChartTooltipItem.js';
|
|
104
104
|
export { ChartLegendItem } from './components/ui/Chart/components/ChartLegendItem.js';
|
|
105
105
|
export { CustomChartLegend } from './components/ui/Chart/components/CustomChartLegend/CustomChartLegend.js';
|
|
106
|
+
export { ChartEmptyState } from './components/ui/Chart/components/ChartEmptyState/ChartEmptyState.js';
|
|
106
107
|
export { ChartContainer, ChartStyle } from './components/ui/Chart/components/ChartContainer.js';
|
|
107
108
|
export { ChartTooltipContent } from './components/ui/Chart/components/ChartTooltipContent.js';
|
|
108
109
|
export { ChartLegendContent } from './components/ui/Chart/components/ChartLegendContent.js';
|
|
@@ -10,3 +10,4 @@ export { ChartTooltipItem } from './components/ChartTooltipItem';
|
|
|
10
10
|
export { ChartLegendItem } from './components/ChartLegendItem';
|
|
11
11
|
export { CustomChartLegend } from './components/CustomChartLegend/CustomChartLegend';
|
|
12
12
|
export { ChartContainer, ChartTooltip, ChartTooltipContent, ChartLegend, ChartLegendContent, ChartStyle, BaseChartWrapper, };
|
|
13
|
+
export { ChartEmptyState, type ChartEmptyStateProps, type ChartEmptyStatusTone, } from './components/ChartEmptyState/ChartEmptyState';
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { ComponentProps, ReactNode } from 'react';
|
|
2
|
-
import { ChartConfig, ChartContainer } from '#uilib/components/ui/Chart/Chart';
|
|
3
2
|
import type { QuantileBandConfig } from '#uilib/components/ui/Chart/chartForecastVisualization.types';
|
|
4
3
|
import { ChartDataPoint } from '#uilib/components/ui/ChartAreaInteractive/ChartAreaInteractive.types';
|
|
5
4
|
import { ForecastItemData } from '#uilib/components/ui/ChartAreaInteractive/ChartLines';
|
|
5
|
+
import type { ChartConfig } from '../Chart.types';
|
|
6
|
+
import { ChartContainer } from './ChartContainer';
|
|
6
7
|
import { LegendPayload } from 'recharts/types/component/DefaultLegendContent';
|
|
7
8
|
export interface BaseChartWrapperProps {
|
|
8
9
|
renderId?: string;
|
package/dist/esm/types/src/components/ui/Chart/components/ChartEmptyState/ChartEmptyState.d.ts
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { ReactNode } from 'react';
|
|
2
|
+
export type ChartEmptyStatusTone = 'muted' | 'destructive';
|
|
3
|
+
export interface ChartEmptyStateProps {
|
|
4
|
+
className?: string;
|
|
5
|
+
/** Primary guidance (muted). */
|
|
6
|
+
hint?: ReactNode;
|
|
7
|
+
/** Status / technical detail. */
|
|
8
|
+
status?: ReactNode;
|
|
9
|
+
statusTone?: ChartEmptyStatusTone;
|
|
10
|
+
/** `panel`: chart-sized block with light fill. `inline`: text only (e.g. above chart). */
|
|
11
|
+
variant?: 'panel' | 'inline';
|
|
12
|
+
align?: 'center' | 'start';
|
|
13
|
+
}
|
|
14
|
+
export declare function ChartEmptyState({ className, hint, status, statusTone, variant, align, }: ChartEmptyStateProps): import("react/jsx-runtime").JSX.Element;
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared Recharts plot / margin math (DOM-agnostic except measurement entry points).
|
|
3
|
+
* Keeps BaseChartWrapper tooltip clamp and ChartAreaInteractive brush in sync.
|
|
4
|
+
*/
|
|
5
|
+
export type ChartMargin = {
|
|
6
|
+
top: number;
|
|
7
|
+
right: number;
|
|
8
|
+
bottom: number;
|
|
9
|
+
left: number;
|
|
10
|
+
};
|
|
11
|
+
export declare const DEFAULT_CHART_MARGIN: ChartMargin;
|
|
12
|
+
export declare function resolveChartMargin(margin: Partial<ChartMargin> | undefined): ChartMargin;
|
|
13
|
+
/** Plot box inside `.recharts-wrapper` (Recharts cartesian convention). */
|
|
14
|
+
export declare function getPlotViewBox(wrapper: HTMLElement, m: ChartMargin): {
|
|
15
|
+
x: number;
|
|
16
|
+
y: number;
|
|
17
|
+
width: number;
|
|
18
|
+
height: number;
|
|
19
|
+
};
|
|
20
|
+
export type PlotRect = {
|
|
21
|
+
left: number;
|
|
22
|
+
top: number;
|
|
23
|
+
width: number;
|
|
24
|
+
height: number;
|
|
25
|
+
};
|
|
26
|
+
/**
|
|
27
|
+
* Plot area in `host` local px: prefer painted `.recharts-cartesian-grid`, else
|
|
28
|
+
* last `.recharts-wrapper` + margins. One `hostRect` read; grid/wrapper rects as needed.
|
|
29
|
+
*/
|
|
30
|
+
export declare function measureHostRelativePlotRect(host: HTMLElement, margin: ChartMargin): PlotRect | null;
|