@sybilion/uilib 1.3.23 → 1.3.26
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/TextWithDeferTooltip/TextWithDeferTooltip.js +1 -25
- package/dist/esm/components/ui/Tooltip/Tooltip.js +92 -7
- package/dist/esm/components/ui/Tooltip/Tooltip.styl.js +2 -2
- package/dist/esm/components/widgets/DriversComparisonChart/DriversComparisonChart.js +1 -2
- package/dist/esm/components/widgets/PerformanceChart/HorizonsSelector/HorizonsSelector.js +34 -0
- package/dist/esm/components/widgets/PerformanceChart/HorizonsSelector/HorizonsSelector.styl.js +7 -0
- package/dist/esm/components/widgets/PerformanceChart/PerformanceChart.constants.js +17 -0
- package/dist/esm/components/widgets/PerformanceChart/PerformanceChart.js +807 -0
- package/dist/esm/components/widgets/PerformanceChart/PerformanceChart.styl.js +7 -0
- package/dist/esm/components/widgets/PerformanceChart/PerformanceTable.js +130 -0
- package/dist/esm/components/widgets/PerformanceChart/PerformanceUnderChartLegend/PerformanceUnderChartLegend.js +20 -0
- package/dist/esm/components/widgets/PerformanceChart/PerformanceUnderChartLegend/PerformanceUnderChartLegend.styl.js +7 -0
- package/dist/esm/components/widgets/PerformanceChart/performanceChart.helpers.js +591 -0
- package/dist/esm/components/widgets/PerformanceChart/performanceChartUserSeries.js +109 -0
- package/dist/esm/index.js +4 -0
- package/dist/esm/types/src/components/ui/Tooltip/Tooltip.d.ts +3 -3
- package/dist/esm/types/src/components/ui/Tooltip/Tooltip.types.d.ts +1 -0
- package/dist/esm/types/src/components/widgets/PerformanceChart/HorizonsSelector/HorizonsSelector.d.ts +7 -0
- package/dist/esm/types/src/components/widgets/PerformanceChart/PerformanceChart.constants.d.ts +3 -0
- package/dist/esm/types/src/components/widgets/PerformanceChart/PerformanceChart.d.ts +54 -0
- package/dist/esm/types/src/components/widgets/PerformanceChart/PerformanceTable.d.ts +31 -0
- package/dist/esm/types/src/components/widgets/PerformanceChart/PerformanceUnderChartLegend/PerformanceUnderChartLegend.d.ts +20 -0
- package/dist/esm/types/src/components/widgets/PerformanceChart/index.d.ts +4 -0
- package/dist/esm/types/src/components/widgets/PerformanceChart/performanceChart.helpers.d.ts +212 -0
- package/dist/esm/types/src/components/widgets/PerformanceChart/performanceChartUserSeries.d.ts +20 -0
- package/dist/esm/types/src/docs/pages/PerformanceChartPage.d.ts +1 -0
- package/dist/esm/types/src/index.d.ts +1 -0
- package/package.json +1 -1
- package/src/components/ui/TextWithDeferTooltip/TextWithDeferTooltip.tsx +5 -37
- package/src/components/ui/Tooltip/Tooltip.styl +12 -0
- package/src/components/ui/Tooltip/Tooltip.styl.d.ts +1 -0
- package/src/components/ui/Tooltip/Tooltip.tsx +156 -8
- package/src/components/ui/Tooltip/Tooltip.types.ts +1 -0
- package/src/components/widgets/PerformanceChart/HorizonsSelector/HorizonsSelector.styl +25 -0
- package/src/components/widgets/PerformanceChart/HorizonsSelector/HorizonsSelector.styl.d.ts +11 -0
- package/src/components/widgets/PerformanceChart/HorizonsSelector/HorizonsSelector.tsx +67 -0
- package/src/components/widgets/PerformanceChart/PerformanceChart.constants.ts +17 -0
- package/src/components/widgets/PerformanceChart/PerformanceChart.styl +194 -0
- package/src/components/widgets/PerformanceChart/PerformanceChart.styl.d.ts +30 -0
- package/src/components/widgets/PerformanceChart/PerformanceChart.tsx +1251 -0
- package/src/components/widgets/PerformanceChart/PerformanceTable.tsx +381 -0
- package/src/components/widgets/PerformanceChart/PerformanceUnderChartLegend/PerformanceUnderChartLegend.styl +49 -0
- package/src/components/widgets/PerformanceChart/PerformanceUnderChartLegend/PerformanceUnderChartLegend.styl.d.ts +12 -0
- package/src/components/widgets/PerformanceChart/PerformanceUnderChartLegend/PerformanceUnderChartLegend.tsx +83 -0
- package/src/components/widgets/PerformanceChart/index.ts +28 -0
- package/src/components/widgets/PerformanceChart/performanceChart.helpers.ts +790 -0
- package/src/components/widgets/PerformanceChart/performanceChartUserSeries.ts +149 -0
- package/src/docs/pages/PerformanceChartPage.tsx +211 -0
- package/src/docs/pages/TextWithDeferTooltipPage.tsx +26 -10
- package/src/docs/pages/TooltipPage.tsx +30 -0
- package/src/docs/registry.ts +6 -0
- package/src/index.ts +1 -0
|
@@ -0,0 +1,381 @@
|
|
|
1
|
+
import cn from 'classnames';
|
|
2
|
+
import React, { useMemo, useRef } from 'react';
|
|
3
|
+
|
|
4
|
+
import { Button } from '#uilib/components/ui/Button';
|
|
5
|
+
import { FORECAST_LINE_COLORS } from '#uilib/components/ui/ChartAreaInteractive/ChartLines';
|
|
6
|
+
import { PageXScroll } from '#uilib/components/ui/Page';
|
|
7
|
+
import {
|
|
8
|
+
Table,
|
|
9
|
+
TableBody,
|
|
10
|
+
TableCell,
|
|
11
|
+
TableCellValue,
|
|
12
|
+
TableHead,
|
|
13
|
+
TableHeader,
|
|
14
|
+
TableRow,
|
|
15
|
+
} from '#uilib/components/ui/Table';
|
|
16
|
+
import {
|
|
17
|
+
Tooltip,
|
|
18
|
+
TooltipContent,
|
|
19
|
+
TooltipTrigger,
|
|
20
|
+
} from '#uilib/components/ui/Tooltip';
|
|
21
|
+
import { InfoIcon, Pencil, TableIcon } from 'lucide-react';
|
|
22
|
+
|
|
23
|
+
import S from './PerformanceChart.styl';
|
|
24
|
+
import {
|
|
25
|
+
calculateAccuracy,
|
|
26
|
+
calculateBenefit,
|
|
27
|
+
formatAccuracy,
|
|
28
|
+
formatBenefit,
|
|
29
|
+
formatError,
|
|
30
|
+
formatROI,
|
|
31
|
+
getForecastModelDisplayName,
|
|
32
|
+
} from './performanceChart.helpers';
|
|
33
|
+
|
|
34
|
+
const DEFAULT_INVESTMENT_COST = 35000; // Investment Cost (IC) - annual cost of solution
|
|
35
|
+
|
|
36
|
+
const CUSTOM_PERFORMANCE_ROW_COLOR = 'var(--sb-purple-400)';
|
|
37
|
+
|
|
38
|
+
// Component wrapper for table rows with long tap support
|
|
39
|
+
function ModelTableRow({
|
|
40
|
+
modelKey,
|
|
41
|
+
row,
|
|
42
|
+
}: {
|
|
43
|
+
modelKey: string;
|
|
44
|
+
row: React.ReactElement;
|
|
45
|
+
onToggleSeries?: (modelKey: string) => void;
|
|
46
|
+
isHidden?: boolean;
|
|
47
|
+
}) {
|
|
48
|
+
const rowRef = useRef<HTMLTableRowElement>(null);
|
|
49
|
+
|
|
50
|
+
return React.cloneElement(row, {
|
|
51
|
+
ref: (el: HTMLTableRowElement | null) => {
|
|
52
|
+
rowRef.current = el;
|
|
53
|
+
// Call original ref if it exists
|
|
54
|
+
const originalRef = (row as any).ref;
|
|
55
|
+
if (typeof originalRef === 'function') {
|
|
56
|
+
originalRef(el);
|
|
57
|
+
} else if (originalRef) {
|
|
58
|
+
originalRef.current = el;
|
|
59
|
+
}
|
|
60
|
+
},
|
|
61
|
+
className: (row.props as any).className,
|
|
62
|
+
} as any);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export interface ForecastModelData {
|
|
66
|
+
key: string;
|
|
67
|
+
mae: number;
|
|
68
|
+
mape: number;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export interface AdjustParameters {
|
|
72
|
+
procurementVolume: number;
|
|
73
|
+
variableRawMaterialCostShare: number;
|
|
74
|
+
controllableCosts: number;
|
|
75
|
+
currentForecastAccuracy: number;
|
|
76
|
+
expectedImprovement: number;
|
|
77
|
+
analystHourlyRate: number;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
interface PerformanceTableProps {
|
|
81
|
+
forecastModels: ForecastModelData[];
|
|
82
|
+
adjustParameters: AdjustParameters;
|
|
83
|
+
onAdjustParametersChange: (params: AdjustParameters) => void;
|
|
84
|
+
/** Shown when custom spaghetti matrix is saved (localStorage). Metrics vs historical for selected horizon. */
|
|
85
|
+
customPerformance?: { label: string; mae: number; mape: number } | null;
|
|
86
|
+
/** Opens custom performance dialog; used by the custom row edit control. */
|
|
87
|
+
onEditCustomPerformance?: () => void;
|
|
88
|
+
/** Last row with Add custom data (hidden when custom row already present); per-horizon + spaghetti. */
|
|
89
|
+
showAddEditCustomDataButton?: boolean;
|
|
90
|
+
addEditCustomDataDisabled?: boolean;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// Calculate color for a model key
|
|
94
|
+
const getModelColor = (key: string) => {
|
|
95
|
+
if (key === 'model') return 'var(--sb-cyan-400)';
|
|
96
|
+
return FORECAST_LINE_COLORS[0];
|
|
97
|
+
};
|
|
98
|
+
|
|
99
|
+
export function PerformanceTable({
|
|
100
|
+
forecastModels,
|
|
101
|
+
adjustParameters,
|
|
102
|
+
onAdjustParametersChange,
|
|
103
|
+
customPerformance = null,
|
|
104
|
+
onEditCustomPerformance,
|
|
105
|
+
showAddEditCustomDataButton = false,
|
|
106
|
+
addEditCustomDataDisabled = false,
|
|
107
|
+
}: PerformanceTableProps) {
|
|
108
|
+
// Separate Sybilion AI model from drift model
|
|
109
|
+
const { sybilionModel, driftModel } = useMemo(() => {
|
|
110
|
+
const sybilion = forecastModels.find(model => model.key === 'model');
|
|
111
|
+
const drift = forecastModels.find(model => model.key === 'drift');
|
|
112
|
+
return {
|
|
113
|
+
sybilionModel: sybilion,
|
|
114
|
+
driftModel: drift,
|
|
115
|
+
};
|
|
116
|
+
}, [forecastModels]);
|
|
117
|
+
|
|
118
|
+
// Calculate table rows for Sybilion AI model
|
|
119
|
+
const sybilionRow = useMemo(() => {
|
|
120
|
+
if (!sybilionModel) return null;
|
|
121
|
+
|
|
122
|
+
const accuracy = calculateAccuracy(sybilionModel.mape);
|
|
123
|
+
const error = formatError(sybilionModel.mae);
|
|
124
|
+
// Calculate benefit using actual accuracy improvement over baseline (drift)
|
|
125
|
+
const baselineAccuracy = driftModel
|
|
126
|
+
? calculateAccuracy(driftModel.mape)
|
|
127
|
+
: undefined;
|
|
128
|
+
const benefit = calculateBenefit(
|
|
129
|
+
sybilionModel.mae,
|
|
130
|
+
sybilionModel.mape,
|
|
131
|
+
adjustParameters,
|
|
132
|
+
baselineAccuracy,
|
|
133
|
+
);
|
|
134
|
+
const roi = formatROI(benefit, DEFAULT_INVESTMENT_COST);
|
|
135
|
+
|
|
136
|
+
return {
|
|
137
|
+
key: sybilionModel.key,
|
|
138
|
+
forecast: getForecastModelDisplayName(sybilionModel.key),
|
|
139
|
+
color: getModelColor(sybilionModel.key),
|
|
140
|
+
accuracy: formatAccuracy(accuracy),
|
|
141
|
+
error,
|
|
142
|
+
roi,
|
|
143
|
+
benefit: formatBenefit(benefit),
|
|
144
|
+
};
|
|
145
|
+
}, [sybilionModel, driftModel, adjustParameters, getModelColor]);
|
|
146
|
+
|
|
147
|
+
// Calculate table row for drift model
|
|
148
|
+
const driftRow = useMemo(() => {
|
|
149
|
+
if (!driftModel) return null;
|
|
150
|
+
|
|
151
|
+
const accuracy = calculateAccuracy(driftModel.mape);
|
|
152
|
+
const error = formatError(driftModel.mae);
|
|
153
|
+
// Baseline model has no improvement, so benefit is 0
|
|
154
|
+
const benefit = 0;
|
|
155
|
+
const roi = formatROI(benefit, DEFAULT_INVESTMENT_COST);
|
|
156
|
+
|
|
157
|
+
return {
|
|
158
|
+
key: driftModel.key,
|
|
159
|
+
forecast: getForecastModelDisplayName(driftModel.key),
|
|
160
|
+
color: getModelColor(driftModel.key),
|
|
161
|
+
accuracy: formatAccuracy(accuracy),
|
|
162
|
+
error,
|
|
163
|
+
roi,
|
|
164
|
+
benefit: formatBenefit(benefit),
|
|
165
|
+
};
|
|
166
|
+
}, [driftModel, adjustParameters, getModelColor]);
|
|
167
|
+
|
|
168
|
+
const customRow = useMemo(() => {
|
|
169
|
+
if (!customPerformance) return null;
|
|
170
|
+
const accuracy = calculateAccuracy(customPerformance.mape);
|
|
171
|
+
const error = formatError(customPerformance.mae);
|
|
172
|
+
const baselineAccuracy = driftModel
|
|
173
|
+
? calculateAccuracy(driftModel.mape)
|
|
174
|
+
: undefined;
|
|
175
|
+
const benefit = calculateBenefit(
|
|
176
|
+
customPerformance.mae,
|
|
177
|
+
customPerformance.mape,
|
|
178
|
+
adjustParameters,
|
|
179
|
+
baselineAccuracy,
|
|
180
|
+
);
|
|
181
|
+
const roi = formatROI(benefit, DEFAULT_INVESTMENT_COST);
|
|
182
|
+
|
|
183
|
+
return {
|
|
184
|
+
key: 'custom_performance',
|
|
185
|
+
forecast: customPerformance.label,
|
|
186
|
+
color: CUSTOM_PERFORMANCE_ROW_COLOR,
|
|
187
|
+
accuracy: formatAccuracy(accuracy),
|
|
188
|
+
error,
|
|
189
|
+
roi,
|
|
190
|
+
benefit: formatBenefit(benefit),
|
|
191
|
+
};
|
|
192
|
+
}, [customPerformance, driftModel, adjustParameters]);
|
|
193
|
+
|
|
194
|
+
const showCustomActionsColumn = Boolean(customRow);
|
|
195
|
+
|
|
196
|
+
return (
|
|
197
|
+
<div className={S.tableSection}>
|
|
198
|
+
<div className={S.tableWrapper}>
|
|
199
|
+
<PageXScroll
|
|
200
|
+
size="md"
|
|
201
|
+
fullWidth
|
|
202
|
+
innerClassName={S.tableContainer}
|
|
203
|
+
scrollbarClassName={S.scrollbar}
|
|
204
|
+
>
|
|
205
|
+
<Table withPaddings className={S.table}>
|
|
206
|
+
<TableHeader>
|
|
207
|
+
<TableRow>
|
|
208
|
+
<TableHead>
|
|
209
|
+
<TableCellValue>
|
|
210
|
+
Forecast
|
|
211
|
+
<Tooltip>
|
|
212
|
+
<TooltipTrigger asChild>
|
|
213
|
+
<span>
|
|
214
|
+
<InfoIcon size={16} style={{ cursor: 'help' }} />
|
|
215
|
+
</span>
|
|
216
|
+
</TooltipTrigger>
|
|
217
|
+
<TooltipContent side="top" maxWidth={300}>
|
|
218
|
+
Sybilion AI forecast vs other forecasting methods: MEAN,
|
|
219
|
+
Seasonal, ARMA, ARIMA, Linear, Theta
|
|
220
|
+
</TooltipContent>
|
|
221
|
+
</Tooltip>
|
|
222
|
+
</TableCellValue>
|
|
223
|
+
</TableHead>
|
|
224
|
+
<TableHead>Accuracy</TableHead>
|
|
225
|
+
<TableHead>Error</TableHead>
|
|
226
|
+
<TableHead>
|
|
227
|
+
<TableCellValue>
|
|
228
|
+
ROI
|
|
229
|
+
<Tooltip>
|
|
230
|
+
<TooltipTrigger asChild>
|
|
231
|
+
<span>
|
|
232
|
+
<InfoIcon size={16} style={{ cursor: 'help' }} />
|
|
233
|
+
</span>
|
|
234
|
+
</TooltipTrigger>
|
|
235
|
+
<TooltipContent side="top" maxWidth={300}>
|
|
236
|
+
ROI is calculated using default contract annual price.
|
|
237
|
+
</TooltipContent>
|
|
238
|
+
</Tooltip>
|
|
239
|
+
</TableCellValue>
|
|
240
|
+
</TableHead>
|
|
241
|
+
<TableHead>
|
|
242
|
+
<TableCellValue>
|
|
243
|
+
Benefit p/a
|
|
244
|
+
<Tooltip>
|
|
245
|
+
<TooltipTrigger asChild>
|
|
246
|
+
<span>
|
|
247
|
+
<InfoIcon size={16} style={{ cursor: 'help' }} />
|
|
248
|
+
</span>
|
|
249
|
+
</TooltipTrigger>
|
|
250
|
+
<TooltipContent side="top" maxWidth={300}>
|
|
251
|
+
Annual Benefit of using Sybilion AI forecast compared to
|
|
252
|
+
alternative forecasting methods. Calculated using
|
|
253
|
+
default assumptions: Procurement Volume €100,000,000 ·
|
|
254
|
+
Variable Share 50% · Controllable Share 60%
|
|
255
|
+
</TooltipContent>
|
|
256
|
+
</Tooltip>
|
|
257
|
+
</TableCellValue>
|
|
258
|
+
</TableHead>
|
|
259
|
+
{showCustomActionsColumn && <TableHead />}
|
|
260
|
+
</TableRow>
|
|
261
|
+
</TableHeader>
|
|
262
|
+
<TableBody>
|
|
263
|
+
{/* Sybilion AI row */}
|
|
264
|
+
{sybilionRow && (
|
|
265
|
+
<ModelTableRow
|
|
266
|
+
modelKey={sybilionRow.key}
|
|
267
|
+
row={
|
|
268
|
+
<TableRow key={sybilionRow.key}>
|
|
269
|
+
<TableCell>
|
|
270
|
+
<div className={S.forecastCell}>
|
|
271
|
+
<span
|
|
272
|
+
className={S.colorCircle}
|
|
273
|
+
style={{ backgroundColor: sybilionRow.color }}
|
|
274
|
+
/>
|
|
275
|
+
{sybilionRow.forecast}
|
|
276
|
+
</div>
|
|
277
|
+
</TableCell>
|
|
278
|
+
<TableCell>{sybilionRow.accuracy}</TableCell>
|
|
279
|
+
<TableCell>{sybilionRow.error}</TableCell>
|
|
280
|
+
<TableCell>{sybilionRow.roi}</TableCell>
|
|
281
|
+
<TableCell>{sybilionRow.benefit}</TableCell>
|
|
282
|
+
{showCustomActionsColumn && <TableCell />}
|
|
283
|
+
</TableRow>
|
|
284
|
+
}
|
|
285
|
+
/>
|
|
286
|
+
)}
|
|
287
|
+
|
|
288
|
+
{/* Drift model row */}
|
|
289
|
+
{driftRow && (
|
|
290
|
+
<ModelTableRow
|
|
291
|
+
modelKey={driftRow.key}
|
|
292
|
+
row={
|
|
293
|
+
<TableRow key={driftRow.key}>
|
|
294
|
+
<TableCell>
|
|
295
|
+
<div className={S.forecastCell}>
|
|
296
|
+
<span
|
|
297
|
+
className={S.colorCircle}
|
|
298
|
+
style={{ backgroundColor: driftRow.color }}
|
|
299
|
+
/>
|
|
300
|
+
{driftRow.forecast}
|
|
301
|
+
</div>
|
|
302
|
+
</TableCell>
|
|
303
|
+
<TableCell>{driftRow.accuracy}</TableCell>
|
|
304
|
+
<TableCell>{driftRow.error}</TableCell>
|
|
305
|
+
<TableCell>{driftRow.roi}</TableCell>
|
|
306
|
+
<TableCell>{driftRow.benefit}</TableCell>
|
|
307
|
+
{showCustomActionsColumn && <TableCell />}
|
|
308
|
+
</TableRow>
|
|
309
|
+
}
|
|
310
|
+
/>
|
|
311
|
+
)}
|
|
312
|
+
|
|
313
|
+
{customRow && (
|
|
314
|
+
<ModelTableRow
|
|
315
|
+
modelKey={customRow.key}
|
|
316
|
+
row={
|
|
317
|
+
<TableRow key={customRow.key}>
|
|
318
|
+
<TableCell>
|
|
319
|
+
<div className={S.forecastCell}>
|
|
320
|
+
<span
|
|
321
|
+
className={S.colorCircle}
|
|
322
|
+
style={{ backgroundColor: customRow.color }}
|
|
323
|
+
/>
|
|
324
|
+
{customRow.forecast}
|
|
325
|
+
</div>
|
|
326
|
+
</TableCell>
|
|
327
|
+
<TableCell>{customRow.accuracy}</TableCell>
|
|
328
|
+
<TableCell>{customRow.error}</TableCell>
|
|
329
|
+
<TableCell>{customRow.roi}</TableCell>
|
|
330
|
+
<TableCell>{customRow.benefit}</TableCell>
|
|
331
|
+
{showCustomActionsColumn && (
|
|
332
|
+
<TableCell>
|
|
333
|
+
<Tooltip>
|
|
334
|
+
<TooltipTrigger asChild>
|
|
335
|
+
<Button
|
|
336
|
+
type="button"
|
|
337
|
+
variant="ghost"
|
|
338
|
+
icon
|
|
339
|
+
aria-label="Edit custom performance data"
|
|
340
|
+
onClick={() => onEditCustomPerformance?.()}
|
|
341
|
+
>
|
|
342
|
+
<Pencil size={16} />
|
|
343
|
+
</Button>
|
|
344
|
+
</TooltipTrigger>
|
|
345
|
+
<TooltipContent side="top">
|
|
346
|
+
Edit custom performance data
|
|
347
|
+
</TooltipContent>
|
|
348
|
+
</Tooltip>
|
|
349
|
+
</TableCell>
|
|
350
|
+
)}
|
|
351
|
+
</TableRow>
|
|
352
|
+
}
|
|
353
|
+
/>
|
|
354
|
+
)}
|
|
355
|
+
|
|
356
|
+
{showAddEditCustomDataButton && !customPerformance && (
|
|
357
|
+
<TableRow className={S.tableCustomDataFooterRow}>
|
|
358
|
+
<TableCell
|
|
359
|
+
colSpan={5 + (showCustomActionsColumn ? 1 : 0)}
|
|
360
|
+
className={S.tableCustomDataFooterCell}
|
|
361
|
+
>
|
|
362
|
+
<Button
|
|
363
|
+
type="button"
|
|
364
|
+
variant="ghost"
|
|
365
|
+
size="sm"
|
|
366
|
+
disabled={addEditCustomDataDisabled}
|
|
367
|
+
onClick={() => onEditCustomPerformance?.()}
|
|
368
|
+
>
|
|
369
|
+
<TableIcon size={16} />
|
|
370
|
+
Add custom data
|
|
371
|
+
</Button>
|
|
372
|
+
</TableCell>
|
|
373
|
+
</TableRow>
|
|
374
|
+
)}
|
|
375
|
+
</TableBody>
|
|
376
|
+
</Table>
|
|
377
|
+
</PageXScroll>
|
|
378
|
+
</div>
|
|
379
|
+
</div>
|
|
380
|
+
);
|
|
381
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
@import '../../../../lib/theme.styl'
|
|
2
|
+
|
|
3
|
+
.root
|
|
4
|
+
display flex
|
|
5
|
+
align-items center
|
|
6
|
+
flex-wrap wrap
|
|
7
|
+
gap var(--p-4)
|
|
8
|
+
|
|
9
|
+
.row
|
|
10
|
+
display inline-flex
|
|
11
|
+
align-items center
|
|
12
|
+
gap var(--p-2)
|
|
13
|
+
padding var(--p-1) var(--p-2)
|
|
14
|
+
border none
|
|
15
|
+
background transparent
|
|
16
|
+
cursor pointer
|
|
17
|
+
font inherit
|
|
18
|
+
color var(--foreground)
|
|
19
|
+
border-radius var(--radius-sm)
|
|
20
|
+
transition opacity 0.2s ease
|
|
21
|
+
|
|
22
|
+
&:hover
|
|
23
|
+
opacity 0.85
|
|
24
|
+
|
|
25
|
+
.swatch
|
|
26
|
+
width 16px
|
|
27
|
+
height 3px
|
|
28
|
+
border-radius 1px
|
|
29
|
+
flex-shrink 0
|
|
30
|
+
|
|
31
|
+
.swatchDashed
|
|
32
|
+
background transparent !important
|
|
33
|
+
box-sizing border-box
|
|
34
|
+
width 16px
|
|
35
|
+
height 2px
|
|
36
|
+
border none
|
|
37
|
+
border-bottom 2px dashed currentColor
|
|
38
|
+
border-radius 0
|
|
39
|
+
|
|
40
|
+
.label
|
|
41
|
+
font-size var(--text-xs)
|
|
42
|
+
line-height var(--line-height-base)
|
|
43
|
+
white-space nowrap
|
|
44
|
+
|
|
45
|
+
.eye
|
|
46
|
+
width 14px
|
|
47
|
+
height 14px
|
|
48
|
+
flex-shrink 0
|
|
49
|
+
opacity 0.8
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
// This file is automatically generated.
|
|
2
|
+
// Please do not change this file!
|
|
3
|
+
interface CssExports {
|
|
4
|
+
'eye': string;
|
|
5
|
+
'label': string;
|
|
6
|
+
'root': string;
|
|
7
|
+
'row': string;
|
|
8
|
+
'swatch': string;
|
|
9
|
+
'swatchDashed': string;
|
|
10
|
+
}
|
|
11
|
+
export const cssExports: CssExports;
|
|
12
|
+
export default cssExports;
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import cn from 'classnames';
|
|
2
|
+
|
|
3
|
+
import { EyeClosedIcon, EyeIcon } from '@phosphor-icons/react';
|
|
4
|
+
|
|
5
|
+
import S from './PerformanceUnderChartLegend.styl';
|
|
6
|
+
|
|
7
|
+
export type PerformanceUnderChartLegendRowProps = {
|
|
8
|
+
label: React.ReactNode;
|
|
9
|
+
/** Line color (CSS color string). */
|
|
10
|
+
lineColor: string;
|
|
11
|
+
/** Historical uses solid bar; forecast lines use dashed preview. */
|
|
12
|
+
lineStyle?: 'solid' | 'dashed';
|
|
13
|
+
hidden: boolean;
|
|
14
|
+
onToggle?: () => void;
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
export type PerformanceUnderChartLegendItemConfig =
|
|
18
|
+
PerformanceUnderChartLegendRowProps & { id: string };
|
|
19
|
+
|
|
20
|
+
export function PerformanceUnderChartLegendRow({
|
|
21
|
+
label,
|
|
22
|
+
lineColor,
|
|
23
|
+
lineStyle = 'solid',
|
|
24
|
+
hidden,
|
|
25
|
+
onToggle,
|
|
26
|
+
}: PerformanceUnderChartLegendRowProps) {
|
|
27
|
+
return (
|
|
28
|
+
<button type="button" className={S.row} onClick={onToggle}>
|
|
29
|
+
{lineStyle === 'solid' ? (
|
|
30
|
+
<span
|
|
31
|
+
className={S.swatch}
|
|
32
|
+
style={{ background: lineColor }}
|
|
33
|
+
aria-hidden
|
|
34
|
+
/>
|
|
35
|
+
) : (
|
|
36
|
+
<span
|
|
37
|
+
className={cn(S.swatch, S.swatchDashed)}
|
|
38
|
+
style={{ color: lineColor }}
|
|
39
|
+
aria-hidden
|
|
40
|
+
/>
|
|
41
|
+
)}
|
|
42
|
+
<span className={S.label}>{label}</span>
|
|
43
|
+
{onToggle &&
|
|
44
|
+
(hidden ? (
|
|
45
|
+
<EyeClosedIcon className={S.eye} />
|
|
46
|
+
) : (
|
|
47
|
+
<EyeIcon className={S.eye} />
|
|
48
|
+
))}
|
|
49
|
+
</button>
|
|
50
|
+
);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/** Renders rows from tab-agnostic config (labels + toggles supplied by parent). */
|
|
54
|
+
export function PerformanceUnderChartLegendFromItems({
|
|
55
|
+
items,
|
|
56
|
+
}: {
|
|
57
|
+
items: PerformanceUnderChartLegendItemConfig[];
|
|
58
|
+
}) {
|
|
59
|
+
if (items?.length === 0) return null;
|
|
60
|
+
|
|
61
|
+
return (
|
|
62
|
+
<PerformanceUnderChartLegend>
|
|
63
|
+
{items.map(item => (
|
|
64
|
+
<PerformanceUnderChartLegendRow
|
|
65
|
+
key={item.id}
|
|
66
|
+
label={item.label}
|
|
67
|
+
lineColor={item.lineColor}
|
|
68
|
+
lineStyle={item.lineStyle ?? 'solid'}
|
|
69
|
+
hidden={item.hidden}
|
|
70
|
+
onToggle={item.onToggle}
|
|
71
|
+
/>
|
|
72
|
+
))}
|
|
73
|
+
</PerformanceUnderChartLegend>
|
|
74
|
+
);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
export function PerformanceUnderChartLegend({
|
|
78
|
+
children,
|
|
79
|
+
}: {
|
|
80
|
+
children: React.ReactNode;
|
|
81
|
+
}) {
|
|
82
|
+
return <div className={S.root}>{children}</div>;
|
|
83
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
export {
|
|
2
|
+
PerformanceChart,
|
|
3
|
+
type PerformanceChartPayload,
|
|
4
|
+
type PerformanceChartProps,
|
|
5
|
+
type PerformanceViewTab,
|
|
6
|
+
} from './PerformanceChart';
|
|
7
|
+
export {
|
|
8
|
+
type AdjustParameters,
|
|
9
|
+
type ForecastModelData,
|
|
10
|
+
PerformanceTable,
|
|
11
|
+
} from './PerformanceTable';
|
|
12
|
+
export {
|
|
13
|
+
SPAGHETTI_DRIFT_PER_HORIZON_ID_BASE,
|
|
14
|
+
SPAGHETTI_MODEL_PER_HORIZON_ID_BASE,
|
|
15
|
+
averageForecastErrorsVsHistoricalForMatrixColumn,
|
|
16
|
+
buildDriftSpaghettiMatrixForCustomDialog,
|
|
17
|
+
buildPerHorizonSpaghettiEntries,
|
|
18
|
+
buildSpaghettiMergedChartData,
|
|
19
|
+
calculateYRangeFromChartData,
|
|
20
|
+
getForecastModelDisplayName,
|
|
21
|
+
spaghettiGridFromHistoricalPreviousMonth,
|
|
22
|
+
} from './performanceChart.helpers';
|
|
23
|
+
export {
|
|
24
|
+
SPAGHETTI_LOCAL_LS_USER_SERIES_ROW_ID,
|
|
25
|
+
SPAGHETTI_TIME_SERIES_MATRIX_V,
|
|
26
|
+
tryParseSpaghettiPerformanceMatrix,
|
|
27
|
+
type SpaghettiPerformanceMatrixPayload,
|
|
28
|
+
} from './performanceChartUserSeries';
|