@sybilion/uilib 1.3.63 → 1.3.65
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/components/BaseChartWrapper.js +10 -5
- package/dist/esm/components/ui/Chart/components/QuantileBands.js +1 -1
- package/dist/esm/components/ui/ChartAreaInteractive/ChartAreaInteractive.helpers.js +60 -1
- package/dist/esm/components/ui/ChartAreaInteractive/overlays/useChartYRange.js +111 -61
- package/dist/esm/components/widgets/PerformanceChart/performanceChart.helpers.js +132 -33
- package/dist/esm/index.js +3 -1
- package/dist/esm/types/src/components/ui/Chart/components/BaseChartWrapper.d.ts +2 -0
- package/dist/esm/types/src/components/ui/ChartAreaInteractive/ChartAreaInteractive.helpers.d.ts +3 -2
- package/dist/esm/types/src/components/ui/ChartAreaInteractive/index.d.ts +2 -0
- package/dist/esm/types/src/components/ui/ChartAreaInteractive/overlays/useChartYRange.d.ts +15 -0
- package/dist/esm/types/src/components/ui/ChartAreaInteractive/overlays/useChartYRange.test.d.ts +1 -0
- package/dist/esm/types/src/components/widgets/PerformanceChart/index.d.ts +1 -1
- package/dist/esm/types/src/components/widgets/PerformanceChart/performanceChart.helpers.d.ts +22 -2
- package/dist/esm/types/src/docs/pages/IncludeHiddenInYScalePage.d.ts +1 -0
- package/package.json +4 -2
- package/src/components/ui/Chart/components/BaseChartWrapper.tsx +14 -4
- package/src/components/ui/ChartAreaInteractive/ChartAreaInteractive.helpers.ts +4 -3
- package/src/components/ui/ChartAreaInteractive/index.ts +2 -0
- package/src/components/ui/ChartAreaInteractive/overlays/useChartYRange.test.ts +87 -0
- package/src/components/ui/ChartAreaInteractive/overlays/useChartYRange.ts +152 -73
- package/src/components/ui/Page/AGENT.md +165 -0
- package/src/components/widgets/AGENT.md +1 -1
- package/src/components/widgets/PerformanceChart/index.ts +3 -0
- package/src/components/widgets/PerformanceChart/performanceChart.helpers.ts +197 -41
- package/src/docs/pages/IncludeHiddenInYScalePage.tsx +152 -0
- package/src/docs/registry.ts +6 -0
|
@@ -48,7 +48,7 @@ BaseChartWrapperLoading.displayName = 'BaseChartWrapperLoading';
|
|
|
48
48
|
* Separated to maintain hook order consistency
|
|
49
49
|
*/
|
|
50
50
|
const BaseChartWrapperContent = forwardRef((props, ref) => {
|
|
51
|
-
const { chartConfig = {}, chartData, historicalLineColor, forecastData = [], loading, hasCombinedData, renderId, isDarkTheme, height, className, loadingComponentClassName, footerClassName, chartClassName, xAxisClassName, yAxisClassName, legendClassName, footerActions, quantileBands, quantileBandKey, xMin, xMax, yMin, yMax, autoScaleYAxis = true, formatNumber, formatDate: formatDateFn = formatDate, labelFormatter, onLegendClick, margin, chartType = 'composed', disableAnimation = false, disableLineDrawAnimation = false, showGrid = true, showAxes = true, showTooltip = true, showLegend = true, showChartAxesLegend = true, xAxisLabel, yAxisLabel, showActiveDots = true, overlayElements, hiddenSeries, excludeLegendIds, onAnalysisSelect, onFailedAnalysisClick, containerProps, error, loadingMessage, noDataMessage = 'No data available', onGridHeightChange, forecastLineStyle = 'dashed', disableHistoricalAnimation = false, onShowAll: _onShowAll, onShowOnly: _onShowOnly, maxVisibleItems, preventDeselection, legendVariant = 'default', legendWidth = 1000, legendMarginLeft = 0, } = props;
|
|
51
|
+
const { chartConfig = {}, chartData, historicalLineColor, forecastData = [], loading, hasCombinedData, renderId, isDarkTheme, height, className, loadingComponentClassName, footerClassName, chartClassName, xAxisClassName, yAxisClassName, legendClassName, footerActions, quantileBands, quantileBandKey, xMin, xMax, yMin, yMax, autoScaleYAxis = true, formatNumber, formatDate: formatDateFn = formatDate, labelFormatter, onLegendClick, margin, chartType = 'composed', disableAnimation = false, disableLineDrawAnimation = false, showGrid = true, showAxes = true, showTooltip = true, showLegend = true, showChartAxesLegend = true, xAxisLabel, yAxisLabel, showActiveDots = true, overlayElements, hiddenSeries, includeHiddenInYScale = false, excludeLegendIds, onAnalysisSelect, onFailedAnalysisClick, containerProps, error, loadingMessage, noDataMessage = 'No data available', onGridHeightChange, forecastLineStyle = 'dashed', disableHistoricalAnimation = false, onShowAll: _onShowAll, onShowOnly: _onShowOnly, maxVisibleItems, preventDeselection, legendVariant = 'default', legendWidth = 1000, legendMarginLeft = 0, } = props;
|
|
52
52
|
const [shouldAnimate, setShouldAnimate] = useState(false);
|
|
53
53
|
const lineDataInitializedRef = useRef(false);
|
|
54
54
|
const rootRef = useRef(null);
|
|
@@ -291,12 +291,17 @@ const BaseChartWrapperContent = forwardRef((props, ref) => {
|
|
|
291
291
|
let effectiveAutoScale = autoScaleYAxis;
|
|
292
292
|
if (autoScaleYAxis !== false && yMin !== undefined && yMax !== undefined) {
|
|
293
293
|
const dataKeys = chartData.length > 0 ? Object.keys(chartData[0]) : [];
|
|
294
|
-
const historicalValues =
|
|
295
|
-
|
|
296
|
-
|
|
294
|
+
const historicalValues = !includeHiddenInYScale && hiddenSeries?.has('historical')
|
|
295
|
+
? []
|
|
296
|
+
: chartData
|
|
297
|
+
.map(p => p.historical)
|
|
298
|
+
.filter(v => v !== null && v !== undefined);
|
|
297
299
|
const forecastKeys = dataKeys.filter(k => k.startsWith('forecast_'));
|
|
300
|
+
const visibleForecastKeys = !includeHiddenInYScale && hiddenSeries
|
|
301
|
+
? forecastKeys.filter(k => !hiddenSeries.has(k))
|
|
302
|
+
: forecastKeys;
|
|
298
303
|
const forecastValues = chartData
|
|
299
|
-
.flatMap(p =>
|
|
304
|
+
.flatMap(p => visibleForecastKeys.map(k => p[k]))
|
|
300
305
|
.filter(v => v !== null && v !== undefined);
|
|
301
306
|
const allLineValues = [...historicalValues, ...forecastValues];
|
|
302
307
|
if (allLineValues.length > 0) {
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { jsx, Fragment } from 'react/jsx-runtime';
|
|
2
|
-
import {
|
|
2
|
+
import { FORECAST_COLORS_MAP, getForecastQuantileBandColor } from '../../ChartAreaInteractive/ChartLines.js';
|
|
3
3
|
import { Area } from 'recharts';
|
|
4
4
|
|
|
5
5
|
const DEFAULT_QUANTILE_BAND_COLOR = FORECAST_COLORS_MAP['#04ADC3'];
|
|
@@ -1,4 +1,48 @@
|
|
|
1
1
|
// Helper function to format large numbers with k/m abbreviations
|
|
2
|
+
const formatNumber = (value) => {
|
|
3
|
+
if (value >= 1000000) {
|
|
4
|
+
return (value / 1000000).toFixed(1).replace(/\.0$/, '') + 'm';
|
|
5
|
+
}
|
|
6
|
+
else if (value >= 1000) {
|
|
7
|
+
return (value / 1000).toFixed(1).replace(/\.0$/, '') + 'k';
|
|
8
|
+
}
|
|
9
|
+
return value.toFixed(0);
|
|
10
|
+
};
|
|
11
|
+
// Find pin position for a specific date in the current filtered data
|
|
12
|
+
const findPinPositionForDate = (targetDate, filteredData) => {
|
|
13
|
+
if (!filteredData.length)
|
|
14
|
+
return 0;
|
|
15
|
+
const targetDateObj = new Date(targetDate);
|
|
16
|
+
let closestIndex = 0;
|
|
17
|
+
let minDistance = Infinity;
|
|
18
|
+
// First, try to find an exact match
|
|
19
|
+
const exactMatch = filteredData.findIndex(dataPoint => dataPoint.date &&
|
|
20
|
+
new Date(dataPoint.date).getTime() === targetDateObj.getTime());
|
|
21
|
+
if (exactMatch !== -1) {
|
|
22
|
+
return (exactMatch / (filteredData.length - 1)) * 100;
|
|
23
|
+
}
|
|
24
|
+
// If no exact match, find the closest date
|
|
25
|
+
filteredData.forEach((dataPoint, index) => {
|
|
26
|
+
if (dataPoint.date) {
|
|
27
|
+
const pointDate = new Date(dataPoint.date);
|
|
28
|
+
const distance = Math.abs(pointDate.getTime() - targetDateObj.getTime());
|
|
29
|
+
if (distance < minDistance) {
|
|
30
|
+
minDistance = distance;
|
|
31
|
+
closestIndex = index;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
});
|
|
35
|
+
// If the target date is outside the current range, clamp to the nearest edge
|
|
36
|
+
const firstDate = new Date(filteredData[0].date);
|
|
37
|
+
const lastDate = new Date(filteredData[filteredData.length - 1].date);
|
|
38
|
+
if (targetDateObj < firstDate) {
|
|
39
|
+
return 0; // Pin at the beginning
|
|
40
|
+
}
|
|
41
|
+
else if (targetDateObj > lastDate) {
|
|
42
|
+
return 100; // Pin at the end
|
|
43
|
+
}
|
|
44
|
+
return (closestIndex / (filteredData.length - 1)) * 100;
|
|
45
|
+
};
|
|
2
46
|
const timeRangeToMonths = {
|
|
3
47
|
'6m': 6,
|
|
4
48
|
'1y': 12,
|
|
@@ -147,5 +191,20 @@ const longDateFormatter = (value) => {
|
|
|
147
191
|
year: 'numeric',
|
|
148
192
|
});
|
|
149
193
|
};
|
|
194
|
+
/**
|
|
195
|
+
* Checks if an analysis status is 'fail' (case-insensitive).
|
|
196
|
+
* @param analysis - The analysis object with optional status field
|
|
197
|
+
* @returns true if status is 'fail', false otherwise
|
|
198
|
+
*/
|
|
199
|
+
const isAnalysisFailed = (analysis) => {
|
|
200
|
+
return analysis.status?.toLowerCase() === 'fail';
|
|
201
|
+
};
|
|
202
|
+
/**
|
|
203
|
+
* Checks if an analysis status is not 'fail' (case-insensitive).
|
|
204
|
+
* @param analysis - The analysis object with optional status field
|
|
205
|
+
* @returns true if status is not 'fail', false otherwise
|
|
206
|
+
*/
|
|
207
|
+
const isAnalysisNotFailed = (analysis) => !isAnalysisFailed(analysis);
|
|
208
|
+
const isAnalysisDone = (analysis) => analysis.status?.toLowerCase() === 'done';
|
|
150
209
|
|
|
151
|
-
export { DRAG_TIME_RANGE_PREFIX, encodeDragTimeRange, filterDataForTimeRange, isTimeRangePreset, longDateFormatter, parseDragTimeRange, shortDateFormatter };
|
|
210
|
+
export { DRAG_TIME_RANGE_PREFIX, encodeDragTimeRange, filterDataForTimeRange, findPinPositionForDate, formatNumber, isAnalysisDone, isAnalysisFailed, isAnalysisNotFailed, isTimeRangePreset, longDateFormatter, parseDragTimeRange, shortDateFormatter };
|
|
@@ -1,75 +1,116 @@
|
|
|
1
1
|
import { useRef, useMemo } from 'react';
|
|
2
2
|
|
|
3
|
+
/** Serialize hidden series keys for stable cache comparison. */
|
|
4
|
+
function serializeHiddenSeries(hiddenSeries) {
|
|
5
|
+
if (!hiddenSeries || hiddenSeries.size === 0)
|
|
6
|
+
return '';
|
|
7
|
+
return [...hiddenSeries].sort().join(',');
|
|
8
|
+
}
|
|
3
9
|
/**
|
|
4
|
-
*
|
|
5
|
-
* Optionally includes quantile values from forecast data
|
|
10
|
+
* Returns true when a chart data key should be skipped for Y-scale (series hidden).
|
|
6
11
|
*/
|
|
7
|
-
function
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
12
|
+
function isKeyExcludedFromYScale(key, hiddenSeries) {
|
|
13
|
+
if (!hiddenSeries || hiddenSeries.size === 0)
|
|
14
|
+
return false;
|
|
15
|
+
if (key === 'historical') {
|
|
16
|
+
return hiddenSeries.has('historical');
|
|
17
|
+
}
|
|
18
|
+
if (key.startsWith('forecast_')) {
|
|
19
|
+
return hiddenSeries.has(key);
|
|
20
|
+
}
|
|
21
|
+
if (key.startsWith('q') && key.includes('_')) {
|
|
22
|
+
const forecastId = key.slice(key.lastIndexOf('_') + 1);
|
|
23
|
+
return hiddenSeries.has(`forecast_${forecastId}`);
|
|
24
|
+
}
|
|
25
|
+
if (key.startsWith('band_')) {
|
|
26
|
+
const forecastId = key.slice(key.lastIndexOf('_') + 1);
|
|
27
|
+
return hiddenSeries.has(`forecast_${forecastId}`);
|
|
28
|
+
}
|
|
29
|
+
return false;
|
|
30
|
+
}
|
|
31
|
+
/** Pure Y-range calculation from chart data points. */
|
|
32
|
+
function calculateChartYRange(dataToUse, { excludeQuantileBands, forecastId, hiddenSeries, includeHiddenInYScale = false, }) {
|
|
33
|
+
const allValues = [];
|
|
34
|
+
dataToUse.forEach(point => {
|
|
35
|
+
Object.entries(point).forEach(([key, value]) => {
|
|
36
|
+
if (key === 'date')
|
|
37
|
+
return;
|
|
38
|
+
if (!includeHiddenInYScale &&
|
|
39
|
+
isKeyExcludedFromYScale(key, hiddenSeries)) {
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
// When selectedForecastId is provided, scale from historical + selected forecast + its
|
|
43
|
+
// quantile band (exclude other forecasts and their quantile values only).
|
|
44
|
+
if (forecastId !== undefined) {
|
|
45
|
+
if (key.startsWith('forecast_') && key !== `forecast_${forecastId}`)
|
|
46
|
+
return;
|
|
47
|
+
// Exclude q{quantile}_{otherAnalysisId} - only include selected forecast's quantiles
|
|
48
|
+
if (key.startsWith('q') && !key.endsWith(`_${forecastId}`))
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
// When excludeQuantileBands is true, exclude:
|
|
52
|
+
// 1. Quantile band arrays ([number, number])
|
|
53
|
+
// 2. Individual quantile values (q{quantile}_{analysisId} properties)
|
|
54
|
+
// Only include historical and forecast line values
|
|
55
|
+
if (excludeQuantileBands) {
|
|
56
|
+
// Skip quantile band arrays
|
|
57
|
+
if (Array.isArray(value) &&
|
|
58
|
+
value.length === 2 &&
|
|
59
|
+
typeof value[0] === 'number' &&
|
|
60
|
+
typeof value[1] === 'number') {
|
|
17
61
|
return;
|
|
18
|
-
// When selectedForecastId is provided, scale from historical + selected forecast + its
|
|
19
|
-
// quantile band (exclude other forecasts and their quantile values only).
|
|
20
|
-
if (forecastId !== undefined) {
|
|
21
|
-
if (key.startsWith('forecast_') && key !== `forecast_${forecastId}`)
|
|
22
|
-
return;
|
|
23
|
-
// Exclude q{quantile}_{otherAnalysisId} - only include selected forecast's quantiles
|
|
24
|
-
if (key.startsWith('q') && !key.endsWith(`_${forecastId}`))
|
|
25
|
-
return;
|
|
26
62
|
}
|
|
27
|
-
//
|
|
28
|
-
//
|
|
29
|
-
|
|
30
|
-
// Only include historical and forecast line values
|
|
31
|
-
if (excludeQuantileBands) {
|
|
32
|
-
// Skip quantile band arrays
|
|
33
|
-
if (Array.isArray(value) &&
|
|
34
|
-
value.length === 2 &&
|
|
35
|
-
typeof value[0] === 'number' &&
|
|
36
|
-
typeof value[1] === 'number') {
|
|
37
|
-
return;
|
|
38
|
-
}
|
|
39
|
-
// Skip individual quantile values (properties starting with 'q' followed by number/underscore)
|
|
40
|
-
// Format: q{quantile}_{analysisId} (e.g., q0.05_123, q0.95_123)
|
|
41
|
-
if (key.startsWith('q') && typeof value === 'number') {
|
|
42
|
-
return;
|
|
43
|
-
}
|
|
44
|
-
// Include only historical and forecast line values
|
|
45
|
-
if (typeof value === 'number') {
|
|
46
|
-
allValues.push(value);
|
|
47
|
-
}
|
|
63
|
+
// Skip individual quantile values (properties starting with 'q' followed by number/underscore)
|
|
64
|
+
// Format: q{quantile}_{analysisId} (e.g., q0.05_123, q0.95_123)
|
|
65
|
+
if (key.startsWith('q') && typeof value === 'number') {
|
|
48
66
|
return;
|
|
49
67
|
}
|
|
50
|
-
//
|
|
68
|
+
// Include only historical and forecast line values
|
|
51
69
|
if (typeof value === 'number') {
|
|
52
70
|
allValues.push(value);
|
|
53
71
|
}
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
74
|
+
// When not excluding, include both numbers and quantile band arrays
|
|
75
|
+
if (typeof value === 'number') {
|
|
76
|
+
allValues.push(value);
|
|
77
|
+
}
|
|
78
|
+
else if (Array.isArray(value) &&
|
|
79
|
+
value.length === 2 &&
|
|
80
|
+
typeof value[0] === 'number' &&
|
|
81
|
+
typeof value[1] === 'number') {
|
|
82
|
+
allValues.push(value[0], value[1]);
|
|
83
|
+
}
|
|
61
84
|
});
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
85
|
+
});
|
|
86
|
+
if (allValues.length === 0) {
|
|
87
|
+
return { yMin: 0, yMax: 100 };
|
|
88
|
+
}
|
|
89
|
+
const min = Math.min(...allValues);
|
|
90
|
+
const max = Math.max(...allValues);
|
|
91
|
+
const diff = max - min;
|
|
92
|
+
return {
|
|
93
|
+
yMin: min - diff * 0.1,
|
|
94
|
+
yMax: max + diff * 0.1,
|
|
72
95
|
};
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* Hook to calculate yMin and yMax from chart data
|
|
99
|
+
* Optionally includes quantile values from forecast data
|
|
100
|
+
*/
|
|
101
|
+
function useChartYRange({ baseChartProps, chartData, disableRescaleWhenQuantileChanges, excludeQuantileBands = false, selectedForecastId, }) {
|
|
102
|
+
const includeHiddenInYScale = baseChartProps.includeHiddenInYScale ?? false;
|
|
103
|
+
const hiddenSeries = baseChartProps.hiddenSeries;
|
|
104
|
+
// Store initial Y-range when disableRescaleWhenQuantileChanges is true
|
|
105
|
+
const stableYRangeRef = useRef(null);
|
|
106
|
+
const prevBaseChartDataRef = useRef(baseChartProps.chartData);
|
|
107
|
+
const prevHiddenSeriesKeyRef = useRef(serializeHiddenSeries(hiddenSeries));
|
|
108
|
+
const calculateYRange = (dataToUse, excludeBands, forecastId) => calculateChartYRange(dataToUse, {
|
|
109
|
+
excludeQuantileBands: excludeBands,
|
|
110
|
+
forecastId,
|
|
111
|
+
hiddenSeries,
|
|
112
|
+
includeHiddenInYScale,
|
|
113
|
+
});
|
|
73
114
|
// When disableRescaleWhenQuantileChanges is true, calculate initial stable range
|
|
74
115
|
// If chartData (transformedChartData) is provided, use it WITH quantile bands for initial calculation
|
|
75
116
|
// Then keep it stable when dragging quantiles
|
|
@@ -84,7 +125,7 @@ function useChartYRange({ baseChartProps, chartData, disableRescaleWhenQuantileC
|
|
|
84
125
|
if (!disableRescaleWhenQuantileChanges) {
|
|
85
126
|
return null;
|
|
86
127
|
}
|
|
87
|
-
// Reset stable range when dataset
|
|
128
|
+
// Reset stable range when dataset, selected forecast, or hidden series changes
|
|
88
129
|
if (prevBaseChartDataRef.current !== baseChartProps.chartData) {
|
|
89
130
|
prevBaseChartDataRef.current = baseChartProps.chartData;
|
|
90
131
|
stableYRangeRef.current = null;
|
|
@@ -92,6 +133,11 @@ function useChartYRange({ baseChartProps, chartData, disableRescaleWhenQuantileC
|
|
|
92
133
|
if (stableYRangeRef.current?.forecastId !== selectedForecastId) {
|
|
93
134
|
stableYRangeRef.current = null;
|
|
94
135
|
}
|
|
136
|
+
const hiddenSeriesKey = serializeHiddenSeries(hiddenSeries);
|
|
137
|
+
if (prevHiddenSeriesKeyRef.current !== hiddenSeriesKey) {
|
|
138
|
+
prevHiddenSeriesKeyRef.current = hiddenSeriesKey;
|
|
139
|
+
stableYRangeRef.current = null;
|
|
140
|
+
}
|
|
95
141
|
// If chartData (transformedChartData) is provided, use it WITH quantile bands for initial calculation
|
|
96
142
|
// This ensures IntervalsOverlay includes quantile bands in Y-scale
|
|
97
143
|
const dataToUse = chartData ?? baseChartProps.chartData;
|
|
@@ -115,6 +161,8 @@ function useChartYRange({ baseChartProps, chartData, disableRescaleWhenQuantileC
|
|
|
115
161
|
disableRescaleWhenQuantileChanges,
|
|
116
162
|
excludeQuantileBands,
|
|
117
163
|
selectedForecastId,
|
|
164
|
+
hiddenSeries,
|
|
165
|
+
includeHiddenInYScale,
|
|
118
166
|
]);
|
|
119
167
|
// When disableRescaleWhenQuantileChanges is false, calculate from transformed data
|
|
120
168
|
const dynamicRange = useMemo(() => {
|
|
@@ -138,6 +186,8 @@ function useChartYRange({ baseChartProps, chartData, disableRescaleWhenQuantileC
|
|
|
138
186
|
disableRescaleWhenQuantileChanges,
|
|
139
187
|
excludeQuantileBands,
|
|
140
188
|
selectedForecastId,
|
|
189
|
+
hiddenSeries,
|
|
190
|
+
includeHiddenInYScale,
|
|
141
191
|
]);
|
|
142
192
|
// Return appropriate range
|
|
143
193
|
if (disableRescaleWhenQuantileChanges) {
|
|
@@ -150,4 +200,4 @@ function useChartYRange({ baseChartProps, chartData, disableRescaleWhenQuantileC
|
|
|
150
200
|
return dynamicRange;
|
|
151
201
|
}
|
|
152
202
|
|
|
153
|
-
export { useChartYRange };
|
|
203
|
+
export { calculateChartYRange, isKeyExcludedFromYScale, useChartYRange };
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { normalizeToMonthStart, getPreviousMonth } from '../../../utils/chartConnectionPoint.js';
|
|
1
|
+
import { normalizeToMonthStart, getNextMonth, getPreviousMonth } from '../../../utils/chartConnectionPoint.js';
|
|
2
2
|
|
|
3
3
|
/** Legacy: API backtest spaghetti lines used `SPAGHETTI_FORECAST_ID_BASE + i` (spaghetti view now uses drift per-horizon instead). */
|
|
4
4
|
const SPAGHETTI_FORECAST_ID_BASE = 6000;
|
|
@@ -36,14 +36,10 @@ function addSpaghettiHistoricalBridgeForSeries(map, dataKey, earliestForecastDat
|
|
|
36
36
|
row[dataKey] = hist;
|
|
37
37
|
map.set(connectionDate, row);
|
|
38
38
|
}
|
|
39
|
-
/**
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
*/
|
|
44
|
-
function buildPerHorizonSpaghettiEntries(forecastRoot, horizonKeys) {
|
|
45
|
-
if (!forecastRoot?.forecasts || horizonKeys.length === 0)
|
|
46
|
-
return [];
|
|
39
|
+
/** When horizons have different lengths, align on the latest `n` months (suffix), not the oldest. */
|
|
40
|
+
function perHorizonSortedKeyLists(forecastRoot, horizonKeys) {
|
|
41
|
+
if (!forecastRoot.forecasts || horizonKeys.length === 0)
|
|
42
|
+
return null;
|
|
47
43
|
const forecasts = forecastRoot.forecasts;
|
|
48
44
|
const sortedHorizons = [...horizonKeys].sort((a, b) => {
|
|
49
45
|
const na = parseInt(a.replace(/\D/g, ''), 10) || 0;
|
|
@@ -59,15 +55,45 @@ function buildPerHorizonSpaghettiEntries(forecastRoot, horizonKeys) {
|
|
|
59
55
|
});
|
|
60
56
|
const lengths = perHorizonKeyLists.map(l => l.length).filter(l => l > 0);
|
|
61
57
|
if (lengths.length === 0)
|
|
58
|
+
return null;
|
|
59
|
+
return { sortedHorizons, perHorizonKeyLists };
|
|
60
|
+
}
|
|
61
|
+
function alignedHorizonRowCount(perHorizonKeyLists) {
|
|
62
|
+
const lengths = perHorizonKeyLists.map(l => l.length).filter(l => l > 0);
|
|
63
|
+
if (lengths.length === 0)
|
|
64
|
+
return 0;
|
|
65
|
+
return Math.min(...lengths);
|
|
66
|
+
}
|
|
67
|
+
function horizonDateKeyAtRow(perHorizonKeyLists, horizonIndex, rowIndex, rowCount) {
|
|
68
|
+
const keys = perHorizonKeyLists[horizonIndex];
|
|
69
|
+
if (!keys?.length)
|
|
70
|
+
return undefined;
|
|
71
|
+
const offset = keys.length - rowCount;
|
|
72
|
+
return keys[offset + rowIndex];
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Converts `performance.model` / `performance.drift` per-horizon forecasts into synthetic
|
|
76
|
+
* backtest-shaped entries: each line is [horizon_1[i], …, horizon_n[i]] as date→value points
|
|
77
|
+
* (aligned on the latest shared months per horizon).
|
|
78
|
+
*/
|
|
79
|
+
function buildPerHorizonSpaghettiEntries(forecastRoot, horizonKeys) {
|
|
80
|
+
const aligned = perHorizonSortedKeyLists(forecastRoot ?? {}, horizonKeys);
|
|
81
|
+
if (!aligned)
|
|
82
|
+
return [];
|
|
83
|
+
const { sortedHorizons, perHorizonKeyLists } = aligned;
|
|
84
|
+
const forecasts = forecastRoot.forecasts;
|
|
85
|
+
const normalizeDateKey = (d) => d.split(' ')[0];
|
|
86
|
+
const n = alignedHorizonRowCount(perHorizonKeyLists);
|
|
87
|
+
if (n === 0)
|
|
62
88
|
return [];
|
|
63
|
-
const n = Math.min(...lengths);
|
|
64
89
|
const entries = [];
|
|
65
90
|
for (let i = 0; i < n; i++) {
|
|
66
91
|
const forecast_series = {};
|
|
67
92
|
for (let hi = 0; hi < sortedHorizons.length; hi++) {
|
|
68
93
|
const h = sortedHorizons[hi];
|
|
69
|
-
const
|
|
70
|
-
|
|
94
|
+
const dateKey = horizonDateKeyAtRow(perHorizonKeyLists, hi, i, n);
|
|
95
|
+
if (!dateKey)
|
|
96
|
+
continue;
|
|
71
97
|
const rawVal = forecasts[h]?.[dateKey];
|
|
72
98
|
if (typeof rawVal !== 'number' || !Number.isFinite(rawVal))
|
|
73
99
|
continue;
|
|
@@ -87,29 +113,19 @@ function buildPerHorizonSpaghettiEntries(forecastRoot, horizonKeys) {
|
|
|
87
113
|
}
|
|
88
114
|
/**
|
|
89
115
|
* Same row alignment as {@link buildPerHorizonSpaghettiEntries}: row `i` uses each horizon's
|
|
90
|
-
*
|
|
116
|
+
* latest-aligned forecast month; `dates[i]` is horizon_1's month at that row (Date column).
|
|
91
117
|
* Use this for custom dialog seed + “copy statistical baseline (drift)” prefill.
|
|
92
118
|
*/
|
|
93
119
|
function buildDriftSpaghettiMatrixForCustomDialog(driftRoot, horizonKeys) {
|
|
94
|
-
|
|
120
|
+
const aligned = perHorizonSortedKeyLists(driftRoot ?? {}, horizonKeys);
|
|
121
|
+
if (!aligned)
|
|
95
122
|
return null;
|
|
123
|
+
const { sortedHorizons, perHorizonKeyLists } = aligned;
|
|
96
124
|
const forecasts = driftRoot.forecasts;
|
|
97
|
-
const sortedHorizons = [...horizonKeys].sort((a, b) => {
|
|
98
|
-
const na = parseInt(a.replace(/\D/g, ''), 10) || 0;
|
|
99
|
-
const nb = parseInt(b.replace(/\D/g, ''), 10) || 0;
|
|
100
|
-
return na - nb;
|
|
101
|
-
});
|
|
102
125
|
const normalizeDateKey = (d) => d.split(' ')[0];
|
|
103
|
-
const
|
|
104
|
-
|
|
105
|
-
if (!m || typeof m !== 'object')
|
|
106
|
-
return [];
|
|
107
|
-
return Object.keys(m).sort((a, b) => normalizeDateKey(a).localeCompare(normalizeDateKey(b)));
|
|
108
|
-
});
|
|
109
|
-
const lengths = perHorizonKeyLists.map(l => l.length).filter(l => l > 0);
|
|
110
|
-
if (lengths.length === 0)
|
|
126
|
+
const n = alignedHorizonRowCount(perHorizonKeyLists);
|
|
127
|
+
if (n === 0)
|
|
111
128
|
return null;
|
|
112
|
-
const n = Math.min(...lengths);
|
|
113
129
|
const dates = [];
|
|
114
130
|
const grid = [];
|
|
115
131
|
const perHorizonDates = [];
|
|
@@ -118,15 +134,19 @@ function buildDriftSpaghettiMatrixForCustomDialog(driftRoot, horizonKeys) {
|
|
|
118
134
|
const dateRow = [];
|
|
119
135
|
for (let hi = 0; hi < sortedHorizons.length; hi++) {
|
|
120
136
|
const h = sortedHorizons[hi];
|
|
121
|
-
const
|
|
122
|
-
|
|
137
|
+
const dateKey = horizonDateKeyAtRow(perHorizonKeyLists, hi, i, n);
|
|
138
|
+
if (!dateKey) {
|
|
139
|
+
row.push(0);
|
|
140
|
+
dateRow.push('');
|
|
141
|
+
continue;
|
|
142
|
+
}
|
|
123
143
|
dateRow.push(normalizeToMonthStart(normalizeDateKey(dateKey)));
|
|
124
144
|
const rawVal = forecasts[h]?.[dateKey];
|
|
125
145
|
const v = typeof rawVal === 'number' && Number.isFinite(rawVal) ? rawVal : NaN;
|
|
126
146
|
row.push(v);
|
|
127
147
|
}
|
|
128
|
-
const d0 = perHorizonKeyLists
|
|
129
|
-
dates.push(normalizeToMonthStart(normalizeDateKey(d0)));
|
|
148
|
+
const d0 = horizonDateKeyAtRow(perHorizonKeyLists, 0, i, n);
|
|
149
|
+
dates.push(d0 ? normalizeToMonthStart(normalizeDateKey(d0)) : '');
|
|
130
150
|
perHorizonDates.push(dateRow);
|
|
131
151
|
grid.push(row.map(c => (Number.isFinite(c) ? c : 0)));
|
|
132
152
|
}
|
|
@@ -136,6 +156,85 @@ function buildDriftSpaghettiMatrixForCustomDialog(driftRoot, horizonKeys) {
|
|
|
136
156
|
perHorizonDates,
|
|
137
157
|
};
|
|
138
158
|
}
|
|
159
|
+
function latestHistoricalMonthKey(historicalByDate) {
|
|
160
|
+
let latest = null;
|
|
161
|
+
historicalByDate.forEach((_, d) => {
|
|
162
|
+
if (!latest || d.localeCompare(latest) > 0)
|
|
163
|
+
latest = d;
|
|
164
|
+
});
|
|
165
|
+
return latest;
|
|
166
|
+
}
|
|
167
|
+
/**
|
|
168
|
+
* Append monthly spaghetti rows after drift backtests so the dialog (and chart) reach the latest
|
|
169
|
+
* historical month (e.g. Apr 2026), not only the last drift origin (e.g. Nov 2025).
|
|
170
|
+
*/
|
|
171
|
+
function extendCustomPerformanceDriftSeedToHistoricalEnd(seed, historicalByDate) {
|
|
172
|
+
const latest = latestHistoricalMonthKey(historicalByDate);
|
|
173
|
+
if (!latest || seed.dates.length === 0)
|
|
174
|
+
return seed;
|
|
175
|
+
const norm = (d) => normalizeToMonthStart(String(d).split(' ')[0] ?? d);
|
|
176
|
+
const dates = seed.dates.map(d => norm(d));
|
|
177
|
+
const grid = seed.grid.map(r => [...r]);
|
|
178
|
+
const perHorizonDates = seed.perHorizonDates.map(row => row.map(d => norm(String(d))));
|
|
179
|
+
const horizonCount = grid[0]?.length ?? perHorizonDates[0]?.length ?? 0;
|
|
180
|
+
if (horizonCount === 0)
|
|
181
|
+
return seed;
|
|
182
|
+
let lastOrigin = dates[dates.length - 1];
|
|
183
|
+
if (!lastOrigin)
|
|
184
|
+
return seed;
|
|
185
|
+
while (lastOrigin.localeCompare(latest) < 0) {
|
|
186
|
+
const nextOrigin = norm(getNextMonth(lastOrigin));
|
|
187
|
+
const prevPh = perHorizonDates[perHorizonDates.length - 1];
|
|
188
|
+
const newPh = prevPh.length === horizonCount
|
|
189
|
+
? prevPh.map(d => norm(getNextMonth(norm(d))))
|
|
190
|
+
: Array.from({ length: horizonCount }, () => nextOrigin);
|
|
191
|
+
perHorizonDates.push(newPh);
|
|
192
|
+
dates.push(nextOrigin);
|
|
193
|
+
const baselineRow = spaghettiGridFromHistoricalPreviousMonth([newPh], horizonCount, historicalByDate);
|
|
194
|
+
grid.push(baselineRow[0] ?? Array.from({ length: horizonCount }, () => 0));
|
|
195
|
+
lastOrigin = nextOrigin;
|
|
196
|
+
}
|
|
197
|
+
return { dates, grid, perHorizonDates };
|
|
198
|
+
}
|
|
199
|
+
/** Pad a saved custom matrix with drift/baseline rows so edit + chart cover latest backtest months. */
|
|
200
|
+
function extendCustomPerformanceMatrixWithDriftSeed(saved, driftSeed, baselineGrid) {
|
|
201
|
+
const norm = (d) => normalizeToMonthStart(String(d).split(' ')[0] ?? d);
|
|
202
|
+
const horizonCount = saved.horizonKeys.length;
|
|
203
|
+
const savedByDate = new Map();
|
|
204
|
+
for (let r = 0; r < saved.dates.length; r++) {
|
|
205
|
+
savedByDate.set(norm(saved.dates[r]), {
|
|
206
|
+
grid: saved.grid[r],
|
|
207
|
+
perHorizon: saved.perHorizonDates?.[r],
|
|
208
|
+
});
|
|
209
|
+
}
|
|
210
|
+
const dates = [];
|
|
211
|
+
const grid = [];
|
|
212
|
+
const perHorizonDates = [];
|
|
213
|
+
for (let i = 0; i < driftSeed.dates.length; i++) {
|
|
214
|
+
const d = norm(driftSeed.dates[i]);
|
|
215
|
+
if (!d)
|
|
216
|
+
continue;
|
|
217
|
+
dates.push(d);
|
|
218
|
+
const existing = savedByDate.get(d);
|
|
219
|
+
if (existing) {
|
|
220
|
+
grid.push([...existing.grid]);
|
|
221
|
+
const ph = existing.perHorizon && existing.perHorizon.length === horizonCount
|
|
222
|
+
? existing.perHorizon.map(c => norm(String(c)))
|
|
223
|
+
: (driftSeed.perHorizonDates[i]?.map(c => norm(String(c))) ?? []);
|
|
224
|
+
perHorizonDates.push(ph);
|
|
225
|
+
continue;
|
|
226
|
+
}
|
|
227
|
+
grid.push(baselineGrid?.[i] ? [...baselineGrid[i]] : [...driftSeed.grid[i]]);
|
|
228
|
+
perHorizonDates.push((driftSeed.perHorizonDates[i] ?? []).map(c => norm(String(c))));
|
|
229
|
+
}
|
|
230
|
+
return {
|
|
231
|
+
v: saved.v,
|
|
232
|
+
dates,
|
|
233
|
+
horizonKeys: [...saved.horizonKeys],
|
|
234
|
+
grid,
|
|
235
|
+
perHorizonDates,
|
|
236
|
+
};
|
|
237
|
+
}
|
|
139
238
|
/**
|
|
140
239
|
* Prefill for custom performance when copying drift layout: each spaghetti row is flat at the
|
|
141
240
|
* historical value for the month before the earliest forecast month in that row (same anchor as
|
|
@@ -588,4 +687,4 @@ function calculateYRangeFromChartData(chartData) {
|
|
|
588
687
|
};
|
|
589
688
|
}
|
|
590
689
|
|
|
591
|
-
export { SPAGHETTI_DRIFT_PER_HORIZON_ID_BASE, SPAGHETTI_FORECAST_ID_BASE, SPAGHETTI_MODEL_PER_HORIZON_ID_BASE, averageForecastErrorsVsHistoricalForMatrixColumn, buildDriftSpaghettiMatrixForCustomDialog, buildPerHorizonSpaghettiEntries, buildSpaghettiMergedChartData, calculateAccuracy, calculateBenefit, calculateROI, calculateROIMultiple, calculateYRangeFromChartData, filterChartDataLast24Months, filterSpaghettiDataFromEarliestForecastStart, formatAccuracy, formatBenefit, formatError, formatROI, getForecastModelDisplayName, isSpaghettiDriftPerHorizonLineId, isSpaghettiModelPerHorizonLineId, mergeHistoricalIntoSpaghettiChartData, mergeSpaghettiMergedBases, mergeSpaghettiUserSeriesFromForecastData, spaghettiGridFromHistoricalPreviousMonth };
|
|
690
|
+
export { SPAGHETTI_DRIFT_PER_HORIZON_ID_BASE, SPAGHETTI_FORECAST_ID_BASE, SPAGHETTI_MODEL_PER_HORIZON_ID_BASE, averageForecastErrorsVsHistoricalForMatrixColumn, buildDriftSpaghettiMatrixForCustomDialog, buildPerHorizonSpaghettiEntries, buildSpaghettiMergedChartData, calculateAccuracy, calculateBenefit, calculateROI, calculateROIMultiple, calculateYRangeFromChartData, extendCustomPerformanceDriftSeedToHistoricalEnd, extendCustomPerformanceMatrixWithDriftSeed, filterChartDataLast24Months, filterSpaghettiDataFromEarliestForecastStart, formatAccuracy, formatBenefit, formatError, formatROI, getForecastModelDisplayName, isSpaghettiDriftPerHorizonLineId, isSpaghettiModelPerHorizonLineId, latestHistoricalMonthKey, mergeHistoricalIntoSpaghettiChartData, mergeSpaghettiMergedBases, mergeSpaghettiUserSeriesFromForecastData, spaghettiGridFromHistoricalPreviousMonth };
|
package/dist/esm/index.js
CHANGED
|
@@ -21,6 +21,8 @@ export { Card, CardAction, CardContent, CardDescription, CardFooter, CardHeader,
|
|
|
21
21
|
export { ChartLegend, ChartTooltip } from './components/ui/Chart/Chart.js';
|
|
22
22
|
export { THEMES } from './components/ui/Chart/Chart.types.js';
|
|
23
23
|
export { ChartAreaInteractive, chartConfig } from './components/ui/ChartAreaInteractive/ChartAreaInteractive.js';
|
|
24
|
+
export { DRAG_TIME_RANGE_PREFIX, encodeDragTimeRange, filterDataForTimeRange, findPinPositionForDate, formatNumber, isAnalysisDone, isAnalysisFailed, isAnalysisNotFailed, isTimeRangePreset, longDateFormatter, parseDragTimeRange, shortDateFormatter } from './components/ui/ChartAreaInteractive/ChartAreaInteractive.helpers.js';
|
|
25
|
+
export { ChartLines, FORECAST_COLORS_MAP, FORECAST_LINE_COLORS, getForecastColor, getForecastQuantileBandColor, resolveQuantileBandFillForForecastLine } from './components/ui/ChartAreaInteractive/ChartLines.js';
|
|
24
26
|
export { Chat } from './components/ui/Chat/Chat.js';
|
|
25
27
|
export { formatChatTranscript, usedPresetIdsFromMessages } from './components/ui/Chat/chat-preset-utils.js';
|
|
26
28
|
export { ChatChrome } from './components/ui/Chat/ChatChrome/ChatChrome.js';
|
|
@@ -111,7 +113,7 @@ export { DriversComparisonChart } from './components/widgets/DriversComparisonCh
|
|
|
111
113
|
export { DRIVER_COMPARISON_CHART_LEAD_MONTHS, DRIVER_FORECAST_ID_BASE, INITIAL_VISIBLE_SERIES_COUNT, applyDriversComparisonViewToPayload, buildDriversComparisonChartData, formatLagMonthsLabel, formatSeriesImportance, getLagDisplayForView, mergeBacktestsChartData, mergeDatasetHistoricalWithBacktestsChartData, parseLagMonthsFromLabel, resolveDriverLagLabel, shiftNormalizedSeriesForward } from './components/widgets/DriversComparisonChart/driversComparisonChart.helpers.js';
|
|
112
114
|
export { PerformanceChart } from './components/widgets/PerformanceChart/PerformanceChart.js';
|
|
113
115
|
export { PerformanceTable } from './components/widgets/PerformanceChart/PerformanceTable.js';
|
|
114
|
-
export { SPAGHETTI_DRIFT_PER_HORIZON_ID_BASE, SPAGHETTI_MODEL_PER_HORIZON_ID_BASE, averageForecastErrorsVsHistoricalForMatrixColumn, buildDriftSpaghettiMatrixForCustomDialog, buildPerHorizonSpaghettiEntries, buildSpaghettiMergedChartData, calculateYRangeFromChartData, getForecastModelDisplayName, spaghettiGridFromHistoricalPreviousMonth } from './components/widgets/PerformanceChart/performanceChart.helpers.js';
|
|
116
|
+
export { SPAGHETTI_DRIFT_PER_HORIZON_ID_BASE, SPAGHETTI_MODEL_PER_HORIZON_ID_BASE, averageForecastErrorsVsHistoricalForMatrixColumn, buildDriftSpaghettiMatrixForCustomDialog, buildPerHorizonSpaghettiEntries, buildSpaghettiMergedChartData, calculateYRangeFromChartData, extendCustomPerformanceDriftSeedToHistoricalEnd, extendCustomPerformanceMatrixWithDriftSeed, getForecastModelDisplayName, latestHistoricalMonthKey, spaghettiGridFromHistoricalPreviousMonth } from './components/widgets/PerformanceChart/performanceChart.helpers.js';
|
|
115
117
|
export { SPAGHETTI_LOCAL_LS_USER_SERIES_ROW_ID, SPAGHETTI_TIME_SERIES_MATRIX_V, tryParseSpaghettiPerformanceMatrix } from './components/widgets/PerformanceChart/performanceChartUserSeries.js';
|
|
116
118
|
export { SybilionAppHeader } from './components/widgets/SybilionAppHeader/SybilionAppHeader.js';
|
|
117
119
|
export { SybilionAuthLayout } from './components/widgets/SybilionAuthLayout/SybilionAuthLayout.js';
|
|
@@ -58,6 +58,8 @@ export interface BaseChartWrapperProps {
|
|
|
58
58
|
showActiveDots?: boolean;
|
|
59
59
|
overlayElements?: ReactNode;
|
|
60
60
|
hiddenSeries?: Set<string>;
|
|
61
|
+
/** When false (default), hidden series values are excluded from Y-axis domain calculation. */
|
|
62
|
+
includeHiddenInYScale?: boolean;
|
|
61
63
|
excludeLegendIds?: number[];
|
|
62
64
|
onAnalysisSelect?: (analysisId: number | string) => void;
|
|
63
65
|
onFailedAnalysisClick?: (analysisId?: number | string) => void;
|
package/dist/esm/types/src/components/ui/ChartAreaInteractive/ChartAreaInteractive.helpers.d.ts
CHANGED
|
@@ -9,8 +9,6 @@ declare const timeRangeToMonths: {
|
|
|
9
9
|
readonly All: 12;
|
|
10
10
|
};
|
|
11
11
|
export type TimeRangePreset = keyof typeof timeRangeToMonths;
|
|
12
|
-
/** @deprecated Use `TimeRangePreset` or `string` for brush-encoded ranges. */
|
|
13
|
-
export type TimeRange = TimeRangePreset;
|
|
14
12
|
export declare const DRAG_TIME_RANGE_PREFIX: "__drag:";
|
|
15
13
|
export declare function encodeDragTimeRange(a: Date, b: Date): string;
|
|
16
14
|
export declare function parseDragTimeRange(s: string): {
|
|
@@ -42,4 +40,7 @@ export declare const isAnalysisFailed: (analysis: Analysis | {
|
|
|
42
40
|
export declare const isAnalysisNotFailed: (analysis: Analysis | {
|
|
43
41
|
status?: string;
|
|
44
42
|
}) => boolean;
|
|
43
|
+
export declare const isAnalysisDone: (analysis: Analysis | {
|
|
44
|
+
status?: string;
|
|
45
|
+
}) => boolean;
|
|
45
46
|
export {};
|
|
@@ -7,6 +7,21 @@ interface UseChartYRangeOptions {
|
|
|
7
7
|
excludeQuantileBands?: boolean;
|
|
8
8
|
selectedForecastId?: number;
|
|
9
9
|
}
|
|
10
|
+
export interface CalculateChartYRangeOptions {
|
|
11
|
+
excludeQuantileBands: boolean;
|
|
12
|
+
forecastId?: number;
|
|
13
|
+
hiddenSeries?: Set<string>;
|
|
14
|
+
includeHiddenInYScale?: boolean;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Returns true when a chart data key should be skipped for Y-scale (series hidden).
|
|
18
|
+
*/
|
|
19
|
+
export declare function isKeyExcludedFromYScale(key: string, hiddenSeries?: Set<string>): boolean;
|
|
20
|
+
/** Pure Y-range calculation from chart data points. */
|
|
21
|
+
export declare function calculateChartYRange(dataToUse: ChartDataPoint[], { excludeQuantileBands, forecastId, hiddenSeries, includeHiddenInYScale, }: CalculateChartYRangeOptions): {
|
|
22
|
+
yMin: number;
|
|
23
|
+
yMax: number;
|
|
24
|
+
};
|
|
10
25
|
/**
|
|
11
26
|
* Hook to calculate yMin and yMax from chart data
|
|
12
27
|
* Optionally includes quantile values from forecast data
|
package/dist/esm/types/src/components/ui/ChartAreaInteractive/overlays/useChartYRange.test.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
export { PerformanceChart, type PerformanceChartPayload, type PerformanceChartProps, type PerformanceViewTab, } from './PerformanceChart';
|
|
2
2
|
export { type AdjustParameters, type ForecastModelData, PerformanceTable, } from './PerformanceTable';
|
|
3
|
-
export { SPAGHETTI_DRIFT_PER_HORIZON_ID_BASE, SPAGHETTI_MODEL_PER_HORIZON_ID_BASE, averageForecastErrorsVsHistoricalForMatrixColumn, buildDriftSpaghettiMatrixForCustomDialog, buildPerHorizonSpaghettiEntries, buildSpaghettiMergedChartData, calculateYRangeFromChartData, getForecastModelDisplayName, spaghettiGridFromHistoricalPreviousMonth, } from './performanceChart.helpers';
|
|
3
|
+
export { SPAGHETTI_DRIFT_PER_HORIZON_ID_BASE, SPAGHETTI_MODEL_PER_HORIZON_ID_BASE, averageForecastErrorsVsHistoricalForMatrixColumn, buildDriftSpaghettiMatrixForCustomDialog, buildPerHorizonSpaghettiEntries, extendCustomPerformanceDriftSeedToHistoricalEnd, extendCustomPerformanceMatrixWithDriftSeed, latestHistoricalMonthKey, buildSpaghettiMergedChartData, calculateYRangeFromChartData, getForecastModelDisplayName, spaghettiGridFromHistoricalPreviousMonth, } from './performanceChart.helpers';
|
|
4
4
|
export { SPAGHETTI_LOCAL_LS_USER_SERIES_ROW_ID, SPAGHETTI_TIME_SERIES_MATRIX_V, tryParseSpaghettiPerformanceMatrix, type SpaghettiPerformanceMatrixPayload, } from './performanceChartUserSeries';
|