@sybilion/uilib 1.3.56 → 1.3.58
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/Card/Card.js +1 -1
- package/dist/esm/components/ui/Chart/components/BaseChartWrapper.js +1 -1
- package/dist/esm/components/widgets/DriversComparisonChart/DriversComparisonChart.js +18 -4
- package/dist/esm/components/widgets/DriversComparisonChart/driversComparisonChart.helpers.js +14 -5
- package/dist/esm/types/src/components/widgets/DriversComparisonChart/driversComparisonChart.helpers.d.ts +3 -1
- package/package.json +1 -1
- package/src/components/ui/Card/Card.tsx +1 -0
- package/src/components/ui/Chart/components/BaseChartWrapper.tsx +2 -10
- package/src/components/widgets/DriversComparisonChart/AGENT.md +2 -0
- package/src/components/widgets/DriversComparisonChart/DriversComparisonChart.tsx +30 -3
- package/src/components/widgets/DriversComparisonChart/driversComparisonChart.helpers.test.ts +179 -0
- package/src/components/widgets/DriversComparisonChart/driversComparisonChart.helpers.ts +26 -7
|
@@ -19,7 +19,7 @@ function CardTitle({ className, ...props }) {
|
|
|
19
19
|
return (jsx(TextWithDeferTooltip, { maxWidth: 400, "data-slot": "card-title", className: cn(S.title, className), ...props }));
|
|
20
20
|
}
|
|
21
21
|
function CardDescription({ className, ...props }) {
|
|
22
|
-
return (jsx(TextWithDeferTooltip, { maxWidth: 400, "data-slot": "card-description", className: cn(S.description, className), ...props }));
|
|
22
|
+
return (jsx(TextWithDeferTooltip, { maxWidth: 400, "data-slot": "card-description", className: cn(S.description, className), overTrigger: true, ...props }));
|
|
23
23
|
}
|
|
24
24
|
function CardAction({ className, ...props }) {
|
|
25
25
|
return (jsx("div", { "data-slot": "card-action", className: cn(S.action, className), ...props }));
|
|
@@ -315,7 +315,7 @@ const BaseChartWrapperContent = forwardRef((props, ref) => {
|
|
|
315
315
|
}
|
|
316
316
|
const ChartComponent = chartType === 'line' ? LineChart : ComposedChart;
|
|
317
317
|
const defaultLabelFormatter = (v) => formatDateFn(v, true);
|
|
318
|
-
return (jsxs("div", { className: cn(S.root, !showLegend && S.noLegend, !showChartAxesLegend && S.hideChartAxesLegend, disableLineDrawAnimation && S.noLineDrawAnimation, isLoaded && S.loaded, className), ref: setRefs, children: [loading && (jsx("div", { className: S.loadingOverlay, children: jsx(Skeleton, {}) })), showGrid && (jsx(ChartContainer, { config: chartConfig, className: cn(S.gridLayer, chartClassName), style: height ? { height: `${height}px` } : undefined, children: jsxs(ChartComponent, { data: chartData, margin: margin,
|
|
318
|
+
return (jsxs("div", { className: cn(S.root, !showLegend && S.noLegend, !showChartAxesLegend && S.hideChartAxesLegend, disableLineDrawAnimation && S.noLineDrawAnimation, isLoaded && S.loaded, className), ref: setRefs, children: [loading && (jsx("div", { className: S.loadingOverlay, children: jsx(Skeleton, {}) })), showGrid && (jsx(ChartContainer, { config: chartConfig, className: cn(S.gridLayer, chartClassName), style: height ? { height: `${height}px` } : undefined, children: jsxs(ChartComponent, { data: chartData, margin: margin, children: [jsx(ChartGrid, {}), showAxes && (jsx(ChartAxes, { formatDate: formatDateFn, formatNumber: formatNumber, xAxisClassName: xAxisClassName, yAxisClassName: yAxisClassName, xAxisLabel: xAxisLabel, yAxisLabel: yAxisLabel, xMin: xMin, xMax: xMax, yMin: yMin, yMax: yMax, autoScaleYAxis: effectiveAutoScale }))] }) })), jsx(ChartContainer, { config: chartConfig, className: cn(S.chartLayer, chartClassName), style: height ? { height: `${height}px` } : undefined, ...containerProps, children: jsxs(ChartComponent, { data: chartData, margin: margin, children: [showAxes && (jsx(ChartAxes, { formatDate: formatDateFn, formatNumber: formatNumber, xAxisClassName: cn(xAxisClassName), yAxisClassName: cn(yAxisClassName), xAxisLabel: xAxisLabel, yAxisLabel: yAxisLabel, xMin: xMin, xMax: xMax, yMin: yMin, yMax: yMax, autoScaleYAxis: effectiveAutoScale })), quantileBands?.[0] && (jsx(QuantileBands, { hiddenBands: hiddenSeries, quantileBandKey: quantileBandKey, animate: true, animationDuration: 150, animationBegin: 0, customBands: quantileBands, showLegend: showLegend })), jsx(ChartLines, { historicalLineColor: historicalLineColor, chartData: chartData, forecastData: forecastData, hiddenSeries: hiddenSeries, isDarkTheme: isDarkTheme, shouldAnimate: shouldAnimate, disableAnimation: disableAnimation, disableHistoricalAnimation: disableHistoricalAnimation, showLegend: showLegend, forecastLineStyle: forecastLineStyle }), showTooltip && (jsx("div", { children: jsx(Tooltip, { cursor: false, offset: TOOLTIP_OFFSET, allowEscapeViewBox: { x: false, y: false }, content: renderTooltipContent }) }))] }) }), overlayElements, jsxs("div", { className: cn(S.footer, footerClassName), children: [showLegend &&
|
|
319
319
|
(legendVariant === 'svg' ? (jsx(LegendSvg, { payload: legendPayload.map(p => ({
|
|
320
320
|
value: p.value,
|
|
321
321
|
color: p.color,
|
|
@@ -28,7 +28,7 @@ import { Table, TableHeader, TableRow, TableHead, TableCellValue, TableBody, Tab
|
|
|
28
28
|
import { TextShimmer } from '../../ui/TextShimmer/TextShimmer.js';
|
|
29
29
|
import { TIME_RANGES } from '../../ui/TimeRangeControls/TimeRangeControls.constants.js';
|
|
30
30
|
import S from './DriversComparisonChart.styl.js';
|
|
31
|
-
import { applyDriversComparisonViewToPayload, mergeBacktestsChartData, mergeDatasetHistoricalWithBacktestsChartData, DRIVER_FORECAST_ID_BASE, getLagDisplayForView, resolveDriverLagLabel, INITIAL_VISIBLE_SERIES_COUNT, buildDriversComparisonChartData, formatSeriesImportance } from './driversComparisonChart.helpers.js';
|
|
31
|
+
import { applyDriversComparisonViewToPayload, mergeBacktestsChartData, mergeDatasetHistoricalWithBacktestsChartData, computeDriversComparisonHistoricalWindowFloor, DRIVER_FORECAST_ID_BASE, getLagDisplayForView, resolveDriverLagLabel, INITIAL_VISIBLE_SERIES_COUNT, buildDriversComparisonChartData, formatSeriesImportance } from './driversComparisonChart.helpers.js';
|
|
32
32
|
|
|
33
33
|
const ALL_TIME_RANGE = TIME_RANGES[TIME_RANGES.length - 1];
|
|
34
34
|
function toForecastDataKey(id) {
|
|
@@ -82,6 +82,16 @@ function DriversComparisonChart({ payload, datasetHistorical = [], loading = fal
|
|
|
82
82
|
}, [payloadForView?.drivers]);
|
|
83
83
|
const mergedChartData = useMemo(() => mergeBacktestsChartData(payloadForView), [payloadForView]);
|
|
84
84
|
const mergedWithHistorical = useMemo(() => mergeDatasetHistoricalWithBacktestsChartData(datasetHistorical, mergedChartData), [datasetHistorical, mergedChartData]);
|
|
85
|
+
const historicalWindowFloor = useMemo(() => {
|
|
86
|
+
const overlappedMerged = mergeBacktestsChartData(payload);
|
|
87
|
+
const merged = mergeDatasetHistoricalWithBacktestsChartData(datasetHistorical, overlappedMerged);
|
|
88
|
+
const sortedDrivers = [...(payload?.drivers ?? [])]
|
|
89
|
+
.filter(d => d.normalized_series &&
|
|
90
|
+
Object.keys(d.normalized_series).some(key => d.normalized_series[key] != null))
|
|
91
|
+
.sort((a, b) => String(a.id).localeCompare(String(b.id)));
|
|
92
|
+
const forecastIds = sortedDrivers.map((_, idx) => DRIVER_FORECAST_ID_BASE + idx);
|
|
93
|
+
return computeDriversComparisonHistoricalWindowFloor(merged, forecastIds);
|
|
94
|
+
}, [payload, datasetHistorical]);
|
|
85
95
|
const chartForecastData = useMemo(() => {
|
|
86
96
|
if (!payloadForView?.target?.normalized_series)
|
|
87
97
|
return [];
|
|
@@ -114,8 +124,7 @@ function DriversComparisonChart({ payload, datasetHistorical = [], loading = fal
|
|
|
114
124
|
};
|
|
115
125
|
});
|
|
116
126
|
}, [originalDriversById, sortedDriversWithData, viewTab]);
|
|
117
|
-
const seriesInitKeyResolved = seriesInitKey ??
|
|
118
|
-
`${tableSeriesRows.map(r => r.id).join(',') || 'none'}`;
|
|
127
|
+
const seriesInitKeyResolved = seriesInitKey ?? `${tableSeriesRows.map(r => r.id).join(',') || 'none'}`;
|
|
119
128
|
const backtestsSeriesInitKeyRef = useRef('');
|
|
120
129
|
useEffect(() => {
|
|
121
130
|
if (tableSeriesRows.length === 0)
|
|
@@ -135,7 +144,12 @@ function DriversComparisonChart({ payload, datasetHistorical = [], loading = fal
|
|
|
135
144
|
return next;
|
|
136
145
|
});
|
|
137
146
|
}, [seriesInitKeyResolved, tableSeriesRows]);
|
|
138
|
-
const driversComparisonChartData = useMemo(() => buildDriversComparisonChartData(mergedWithHistorical, datasetHistorical, chartForecastData.map(f => f.id)), [
|
|
147
|
+
const driversComparisonChartData = useMemo(() => buildDriversComparisonChartData(mergedWithHistorical, datasetHistorical, chartForecastData.map(f => f.id), historicalWindowFloor), [
|
|
148
|
+
chartForecastData,
|
|
149
|
+
datasetHistorical,
|
|
150
|
+
historicalWindowFloor,
|
|
151
|
+
mergedWithHistorical,
|
|
152
|
+
]);
|
|
139
153
|
const showEmptyOverlay = (runAnalysisHint || Boolean(statusHint)) && !loading;
|
|
140
154
|
return (jsxs("div", { className: cn(S.root, className), children: [jsxs("div", { className: cn(S.chartShell, loading && S.chartShellLoading), children: [jsx("div", { className: S.chartSlot, children: jsxs("div", { className: S.chartWithOverlay, children: [jsx("div", { className: cn(S.chartInteractiveLayer, showEmptyOverlay && S.chartInteractiveDimmed), children: jsx(ChartAreaInteractive, { chartRenderId: "drivers-comparison", disableLineDrawAnimation: true, disableHistoricalAnimation: true, disableTimeRangeSelector: true, enableTimeRangeBrush: true, chartContainerClassName: S.chartContainer, chartData: driversComparisonChartData, forecastData: chartForecastData, timeRange: timeRange, onTimeRangeChange: handleTimeRangeChange, pinMonth: undefined, onPinMonthChange: () => { }, isDarkTheme: isDarkTheme, loading: chartLoading, hasCombinedData: mergedWithHistorical.length > 0, forecastLineStyle: "solid", showLegend: false, hiddenSeries: hiddenSeries, toggleLegendSeries: toggleSeries, ensureAnalysisSeriesVisible: showSeries }) }), showEmptyOverlay && (jsx("div", { className: S.chartEmptyOverlay, role: "status", "aria-live": "polite", children: jsx("div", { className: S.chartEmptyBlurb, children: jsx(ChartEmptyState, { variant: "inline", hint: runAnalysisHint
|
|
141
155
|
? 'Run a completed analysis to load the drivers comparison chart for an analysis.'
|
package/dist/esm/components/widgets/DriversComparisonChart/driversComparisonChart.helpers.js
CHANGED
|
@@ -231,17 +231,26 @@ function prependHistoricalLeadFromDataset(points, datasetHistorical, xMin) {
|
|
|
231
231
|
}
|
|
232
232
|
return [...lead, ...points];
|
|
233
233
|
}
|
|
234
|
-
|
|
234
|
+
/** Overlapped (unshifted) anchor minus lead months — stable xMin across lagged/overlapped tabs. */
|
|
235
|
+
function computeDriversComparisonHistoricalWindowFloor(mergedWithHistorical, forecastIds) {
|
|
236
|
+
if (mergedWithHistorical.length === 0 || forecastIds.length === 0)
|
|
237
|
+
return null;
|
|
238
|
+
const { anchor: anchorMonth } = getZoomAnchorMonthFromDriverMaterialStarts(mergedWithHistorical, forecastIds);
|
|
239
|
+
if (anchorMonth === null)
|
|
240
|
+
return null;
|
|
241
|
+
return subtractMonthsFromMonthStart(anchorMonth, DRIVER_COMPARISON_CHART_LEAD_MONTHS);
|
|
242
|
+
}
|
|
243
|
+
function buildDriversComparisonChartData(mergedWithHistorical, datasetHistorical, forecastIds, historicalWindowFloor) {
|
|
235
244
|
if (mergedWithHistorical.length === 0)
|
|
236
245
|
return mergedWithHistorical;
|
|
237
246
|
if (forecastIds.length === 0)
|
|
238
247
|
return mergedWithHistorical;
|
|
239
|
-
const
|
|
240
|
-
|
|
248
|
+
const xMin = historicalWindowFloor ??
|
|
249
|
+
computeDriversComparisonHistoricalWindowFloor(mergedWithHistorical, forecastIds);
|
|
250
|
+
if (xMin === null)
|
|
241
251
|
return mergedWithHistorical;
|
|
242
|
-
const xMin = subtractMonthsFromMonthStart(anchorMonth, DRIVER_COMPARISON_CHART_LEAD_MONTHS);
|
|
243
252
|
const withLead = prependHistoricalLeadFromDataset(mergedWithHistorical, datasetHistorical, xMin);
|
|
244
253
|
return withLead.filter(p => normalizeToMonthStart(p.date) >= xMin);
|
|
245
254
|
}
|
|
246
255
|
|
|
247
|
-
export { DRIVER_COMPARISON_CHART_LEAD_MONTHS, DRIVER_FORECAST_ID_BASE, INITIAL_VISIBLE_SERIES_COUNT, applyDriversComparisonViewToPayload, buildDriversComparisonChartData, formatLagMonthsLabel, formatSeriesImportance, getLagDisplayForView, getZoomAnchorMonthFromDriverMaterialStarts, mergeBacktestsChartData, mergeDatasetHistoricalWithBacktestsChartData, parseLagMonthsFromLabel, prependHistoricalLeadFromDataset, resolveDriverLagLabel, shiftNormalizedSeriesForward, subtractMonthsFromMonthStart };
|
|
256
|
+
export { DRIVER_COMPARISON_CHART_LEAD_MONTHS, DRIVER_FORECAST_ID_BASE, INITIAL_VISIBLE_SERIES_COUNT, applyDriversComparisonViewToPayload, buildDriversComparisonChartData, computeDriversComparisonHistoricalWindowFloor, formatLagMonthsLabel, formatSeriesImportance, getLagDisplayForView, getZoomAnchorMonthFromDriverMaterialStarts, mergeBacktestsChartData, mergeDatasetHistoricalWithBacktestsChartData, parseLagMonthsFromLabel, prependHistoricalLeadFromDataset, resolveDriverLagLabel, shiftNormalizedSeriesForward, subtractMonthsFromMonthStart };
|
|
@@ -35,4 +35,6 @@ export declare function subtractMonthsFromMonthStart(date: string, count: number
|
|
|
35
35
|
* (scaled to the first normalized point) so the lead-in shows the target line alone.
|
|
36
36
|
*/
|
|
37
37
|
export declare function prependHistoricalLeadFromDataset(points: ChartDataPoint[], datasetHistorical: ChartDataPoint[], xMin: string): ChartDataPoint[];
|
|
38
|
-
|
|
38
|
+
/** Overlapped (unshifted) anchor minus lead months — stable xMin across lagged/overlapped tabs. */
|
|
39
|
+
export declare function computeDriversComparisonHistoricalWindowFloor(mergedWithHistorical: ChartDataPoint[], forecastIds: number[]): string | null;
|
|
40
|
+
export declare function buildDriversComparisonChartData(mergedWithHistorical: ChartDataPoint[], datasetHistorical: ChartDataPoint[], forecastIds: number[], historicalWindowFloor?: string | null): ChartDataPoint[];
|
package/package.json
CHANGED
|
@@ -620,11 +620,7 @@ const BaseChartWrapperContent = forwardRef<
|
|
|
620
620
|
className={cn(S.gridLayer, chartClassName)}
|
|
621
621
|
style={height ? { height: `${height}px` } : undefined}
|
|
622
622
|
>
|
|
623
|
-
<ChartComponent
|
|
624
|
-
data={chartData}
|
|
625
|
-
margin={margin}
|
|
626
|
-
isAnimationActive={!disableAnimation}
|
|
627
|
-
>
|
|
623
|
+
<ChartComponent data={chartData} margin={margin}>
|
|
628
624
|
<ChartGrid />
|
|
629
625
|
{showAxes && (
|
|
630
626
|
<ChartAxes
|
|
@@ -652,11 +648,7 @@ const BaseChartWrapperContent = forwardRef<
|
|
|
652
648
|
style={height ? { height: `${height}px` } : undefined}
|
|
653
649
|
{...containerProps}
|
|
654
650
|
>
|
|
655
|
-
<ChartComponent
|
|
656
|
-
data={chartData}
|
|
657
|
-
margin={margin}
|
|
658
|
-
isAnimationActive={!disableAnimation}
|
|
659
|
-
>
|
|
651
|
+
<ChartComponent data={chartData} margin={margin}>
|
|
660
652
|
{/* Render invisible axes for coordinate system, but hide labels */}
|
|
661
653
|
{showAxes && (
|
|
662
654
|
<ChartAxes
|
|
@@ -20,4 +20,6 @@ Requires: `payload` — target + driver normalized_series; `loading` / `chartLoa
|
|
|
20
20
|
|
|
21
21
|
Lag column: **Overlapped** shows API `lag` string (may be a range). **Lagged** shows single `N month(s)` from `parseLagMonthsFromLabel` (range uses max month).
|
|
22
22
|
|
|
23
|
+
Historical window: lead-in is anchored to the **overlapped** view (`computeDriversComparisonHistoricalWindowFloor`); lagged tab shifts driver lines only — switching tabs does not change historical extent.
|
|
24
|
+
|
|
23
25
|
Empty/loading: loading props shimmer chart; null `payload` with `runAnalysisHint` prompts to run analysis.
|
|
@@ -35,6 +35,7 @@ import {
|
|
|
35
35
|
INITIAL_VISIBLE_SERIES_COUNT,
|
|
36
36
|
applyDriversComparisonViewToPayload,
|
|
37
37
|
buildDriversComparisonChartData,
|
|
38
|
+
computeDriversComparisonHistoricalWindowFloor,
|
|
38
39
|
formatSeriesImportance,
|
|
39
40
|
getLagDisplayForView,
|
|
40
41
|
mergeBacktestsChartData,
|
|
@@ -158,6 +159,27 @@ export function DriversComparisonChart({
|
|
|
158
159
|
[datasetHistorical, mergedChartData],
|
|
159
160
|
);
|
|
160
161
|
|
|
162
|
+
const historicalWindowFloor = useMemo(() => {
|
|
163
|
+
const overlappedMerged = mergeBacktestsChartData(payload);
|
|
164
|
+
const merged = mergeDatasetHistoricalWithBacktestsChartData(
|
|
165
|
+
datasetHistorical,
|
|
166
|
+
overlappedMerged,
|
|
167
|
+
);
|
|
168
|
+
const sortedDrivers = [...(payload?.drivers ?? [])]
|
|
169
|
+
.filter(
|
|
170
|
+
d =>
|
|
171
|
+
d.normalized_series &&
|
|
172
|
+
Object.keys(d.normalized_series).some(
|
|
173
|
+
key => d.normalized_series![key] != null,
|
|
174
|
+
),
|
|
175
|
+
)
|
|
176
|
+
.sort((a, b) => String(a.id).localeCompare(String(b.id)));
|
|
177
|
+
const forecastIds = sortedDrivers.map(
|
|
178
|
+
(_, idx) => DRIVER_FORECAST_ID_BASE + idx,
|
|
179
|
+
);
|
|
180
|
+
return computeDriversComparisonHistoricalWindowFloor(merged, forecastIds);
|
|
181
|
+
}, [payload, datasetHistorical]);
|
|
182
|
+
|
|
161
183
|
const chartForecastData = useMemo((): ForecastItemData[] => {
|
|
162
184
|
if (!payloadForView?.target?.normalized_series) return [];
|
|
163
185
|
return sortedDriversWithData.map((driver, idx) => ({
|
|
@@ -193,8 +215,7 @@ export function DriversComparisonChart({
|
|
|
193
215
|
}, [originalDriversById, sortedDriversWithData, viewTab]);
|
|
194
216
|
|
|
195
217
|
const seriesInitKeyResolved =
|
|
196
|
-
seriesInitKey ??
|
|
197
|
-
`${tableSeriesRows.map(r => r.id).join(',') || 'none'}`;
|
|
218
|
+
seriesInitKey ?? `${tableSeriesRows.map(r => r.id).join(',') || 'none'}`;
|
|
198
219
|
|
|
199
220
|
const backtestsSeriesInitKeyRef = useRef<string>('');
|
|
200
221
|
|
|
@@ -222,8 +243,14 @@ export function DriversComparisonChart({
|
|
|
222
243
|
mergedWithHistorical,
|
|
223
244
|
datasetHistorical,
|
|
224
245
|
chartForecastData.map(f => f.id),
|
|
246
|
+
historicalWindowFloor,
|
|
225
247
|
),
|
|
226
|
-
[
|
|
248
|
+
[
|
|
249
|
+
chartForecastData,
|
|
250
|
+
datasetHistorical,
|
|
251
|
+
historicalWindowFloor,
|
|
252
|
+
mergedWithHistorical,
|
|
253
|
+
],
|
|
227
254
|
);
|
|
228
255
|
|
|
229
256
|
const showEmptyOverlay = (runAnalysisHint || Boolean(statusHint)) && !loading;
|
package/src/components/widgets/DriversComparisonChart/driversComparisonChart.helpers.test.ts
CHANGED
|
@@ -1,10 +1,19 @@
|
|
|
1
|
+
import type { ChartDataPoint } from '#uilib/components/ui/ChartAreaInteractive/ChartAreaInteractive.types';
|
|
2
|
+
|
|
1
3
|
import {
|
|
4
|
+
DRIVER_COMPARISON_CHART_LEAD_MONTHS,
|
|
5
|
+
DRIVER_FORECAST_ID_BASE,
|
|
2
6
|
applyDriversComparisonViewToPayload,
|
|
7
|
+
buildDriversComparisonChartData,
|
|
8
|
+
computeDriversComparisonHistoricalWindowFloor,
|
|
3
9
|
formatLagMonthsLabel,
|
|
4
10
|
getLagDisplayForView,
|
|
11
|
+
mergeBacktestsChartData,
|
|
12
|
+
mergeDatasetHistoricalWithBacktestsChartData,
|
|
5
13
|
parseLagMonthsFromLabel,
|
|
6
14
|
resolveDriverLagLabel,
|
|
7
15
|
shiftNormalizedSeriesForward,
|
|
16
|
+
subtractMonthsFromMonthStart,
|
|
8
17
|
} from './driversComparisonChart.helpers';
|
|
9
18
|
|
|
10
19
|
describe('parseLagMonthsFromLabel', () => {
|
|
@@ -133,3 +142,173 @@ describe('applyDriversComparisonViewToPayload', () => {
|
|
|
133
142
|
expect(lagged?.drivers[0].normalized_series['2015-07-01']).toBe(0.8);
|
|
134
143
|
});
|
|
135
144
|
});
|
|
145
|
+
|
|
146
|
+
describe('computeDriversComparisonHistoricalWindowFloor', () => {
|
|
147
|
+
const laggedDriverPayload = {
|
|
148
|
+
target: {
|
|
149
|
+
id: 'target',
|
|
150
|
+
name: 'Target',
|
|
151
|
+
normalized_series: {
|
|
152
|
+
'2014-10-01': 1.0,
|
|
153
|
+
'2014-11-01': 1.01,
|
|
154
|
+
'2014-12-01': 1.02,
|
|
155
|
+
'2015-01-01': 1.03,
|
|
156
|
+
'2015-02-01': 1.04,
|
|
157
|
+
'2015-03-01': 1.05,
|
|
158
|
+
'2015-04-01': 1.06,
|
|
159
|
+
'2015-05-01': 1.07,
|
|
160
|
+
},
|
|
161
|
+
},
|
|
162
|
+
drivers: [
|
|
163
|
+
{
|
|
164
|
+
id: 'd1',
|
|
165
|
+
name: 'Driver',
|
|
166
|
+
lag: '3 month(s)',
|
|
167
|
+
normalized_series: {
|
|
168
|
+
'2015-04-01': 0.5,
|
|
169
|
+
'2015-05-01': 0.6,
|
|
170
|
+
},
|
|
171
|
+
},
|
|
172
|
+
],
|
|
173
|
+
};
|
|
174
|
+
|
|
175
|
+
const datasetHistorical: ChartDataPoint[] = [
|
|
176
|
+
{ date: '2014-10-01', historical: 100 },
|
|
177
|
+
{ date: '2014-11-01', historical: 101 },
|
|
178
|
+
{ date: '2014-12-01', historical: 102 },
|
|
179
|
+
{ date: '2015-01-01', historical: 103 },
|
|
180
|
+
{ date: '2015-02-01', historical: 104 },
|
|
181
|
+
{ date: '2015-03-01', historical: 105 },
|
|
182
|
+
{ date: '2015-04-01', historical: 106 },
|
|
183
|
+
];
|
|
184
|
+
|
|
185
|
+
const forecastIds = [DRIVER_FORECAST_ID_BASE];
|
|
186
|
+
|
|
187
|
+
it('returns anchor minus lead months from overlapped merged data', () => {
|
|
188
|
+
const merged = mergeDatasetHistoricalWithBacktestsChartData(
|
|
189
|
+
datasetHistorical,
|
|
190
|
+
mergeBacktestsChartData(laggedDriverPayload),
|
|
191
|
+
);
|
|
192
|
+
const floor = computeDriversComparisonHistoricalWindowFloor(
|
|
193
|
+
merged,
|
|
194
|
+
forecastIds,
|
|
195
|
+
);
|
|
196
|
+
expect(floor).toBe(
|
|
197
|
+
subtractMonthsFromMonthStart(
|
|
198
|
+
'2015-04-01',
|
|
199
|
+
DRIVER_COMPARISON_CHART_LEAD_MONTHS,
|
|
200
|
+
),
|
|
201
|
+
);
|
|
202
|
+
expect(floor).toBe('2014-10-01');
|
|
203
|
+
});
|
|
204
|
+
});
|
|
205
|
+
|
|
206
|
+
describe('buildDriversComparisonChartData historical window floor', () => {
|
|
207
|
+
const laggedDriverPayload = {
|
|
208
|
+
target: {
|
|
209
|
+
id: 'target',
|
|
210
|
+
name: 'Target',
|
|
211
|
+
normalized_series: {
|
|
212
|
+
'2014-10-01': 1.0,
|
|
213
|
+
'2014-11-01': 1.01,
|
|
214
|
+
'2014-12-01': 1.02,
|
|
215
|
+
'2015-01-01': 1.03,
|
|
216
|
+
'2015-02-01': 1.04,
|
|
217
|
+
'2015-03-01': 1.05,
|
|
218
|
+
'2015-04-01': 1.06,
|
|
219
|
+
'2015-05-01': 1.07,
|
|
220
|
+
},
|
|
221
|
+
},
|
|
222
|
+
drivers: [
|
|
223
|
+
{
|
|
224
|
+
id: 'd1',
|
|
225
|
+
name: 'Driver',
|
|
226
|
+
lag: '3 month(s)',
|
|
227
|
+
normalized_series: {
|
|
228
|
+
'2015-04-01': 0.5,
|
|
229
|
+
'2015-05-01': 0.6,
|
|
230
|
+
},
|
|
231
|
+
},
|
|
232
|
+
],
|
|
233
|
+
};
|
|
234
|
+
|
|
235
|
+
const datasetHistorical: ChartDataPoint[] = [
|
|
236
|
+
{ date: '2014-10-01', historical: 100 },
|
|
237
|
+
{ date: '2014-11-01', historical: 101 },
|
|
238
|
+
{ date: '2014-12-01', historical: 102 },
|
|
239
|
+
{ date: '2015-01-01', historical: 103 },
|
|
240
|
+
{ date: '2015-02-01', historical: 104 },
|
|
241
|
+
{ date: '2015-03-01', historical: 105 },
|
|
242
|
+
{ date: '2015-04-01', historical: 106 },
|
|
243
|
+
];
|
|
244
|
+
|
|
245
|
+
const forecastIds = [DRIVER_FORECAST_ID_BASE];
|
|
246
|
+
|
|
247
|
+
it('keeps same xMin for lagged and overlapped when floor is pinned', () => {
|
|
248
|
+
const overlappedMerged = mergeDatasetHistoricalWithBacktestsChartData(
|
|
249
|
+
datasetHistorical,
|
|
250
|
+
mergeBacktestsChartData(laggedDriverPayload),
|
|
251
|
+
);
|
|
252
|
+
const laggedPayload = applyDriversComparisonViewToPayload(
|
|
253
|
+
laggedDriverPayload,
|
|
254
|
+
'lagged',
|
|
255
|
+
);
|
|
256
|
+
const laggedMerged = mergeDatasetHistoricalWithBacktestsChartData(
|
|
257
|
+
datasetHistorical,
|
|
258
|
+
mergeBacktestsChartData(laggedPayload),
|
|
259
|
+
);
|
|
260
|
+
const floor = computeDriversComparisonHistoricalWindowFloor(
|
|
261
|
+
overlappedMerged,
|
|
262
|
+
forecastIds,
|
|
263
|
+
)!;
|
|
264
|
+
|
|
265
|
+
const overlappedChart = buildDriversComparisonChartData(
|
|
266
|
+
overlappedMerged,
|
|
267
|
+
datasetHistorical,
|
|
268
|
+
forecastIds,
|
|
269
|
+
floor,
|
|
270
|
+
);
|
|
271
|
+
const laggedChart = buildDriversComparisonChartData(
|
|
272
|
+
laggedMerged,
|
|
273
|
+
datasetHistorical,
|
|
274
|
+
forecastIds,
|
|
275
|
+
floor,
|
|
276
|
+
);
|
|
277
|
+
|
|
278
|
+
expect(overlappedChart[0]?.date).toBe('2014-10-01');
|
|
279
|
+
expect(laggedChart[0]?.date).toBe('2014-10-01');
|
|
280
|
+
expect(laggedChart[0]?.date).toBe(overlappedChart[0]?.date);
|
|
281
|
+
});
|
|
282
|
+
|
|
283
|
+
it('without floor override lagged chart starts later than overlapped', () => {
|
|
284
|
+
const overlappedMerged = mergeDatasetHistoricalWithBacktestsChartData(
|
|
285
|
+
datasetHistorical,
|
|
286
|
+
mergeBacktestsChartData(laggedDriverPayload),
|
|
287
|
+
);
|
|
288
|
+
const laggedPayload = applyDriversComparisonViewToPayload(
|
|
289
|
+
laggedDriverPayload,
|
|
290
|
+
'lagged',
|
|
291
|
+
);
|
|
292
|
+
const laggedMerged = mergeDatasetHistoricalWithBacktestsChartData(
|
|
293
|
+
datasetHistorical,
|
|
294
|
+
mergeBacktestsChartData(laggedPayload),
|
|
295
|
+
);
|
|
296
|
+
|
|
297
|
+
const overlappedChart = buildDriversComparisonChartData(
|
|
298
|
+
overlappedMerged,
|
|
299
|
+
datasetHistorical,
|
|
300
|
+
forecastIds,
|
|
301
|
+
);
|
|
302
|
+
const laggedChart = buildDriversComparisonChartData(
|
|
303
|
+
laggedMerged,
|
|
304
|
+
datasetHistorical,
|
|
305
|
+
forecastIds,
|
|
306
|
+
);
|
|
307
|
+
|
|
308
|
+
expect(overlappedChart[0]?.date).toBe('2014-10-01');
|
|
309
|
+
expect(laggedChart[0]?.date).toBe('2015-01-01');
|
|
310
|
+
expect(laggedChart[0]?.date!.localeCompare(overlappedChart[0]?.date!)).toBe(
|
|
311
|
+
1,
|
|
312
|
+
);
|
|
313
|
+
});
|
|
314
|
+
});
|
|
@@ -290,22 +290,41 @@ export function prependHistoricalLeadFromDataset(
|
|
|
290
290
|
return [...lead, ...points];
|
|
291
291
|
}
|
|
292
292
|
|
|
293
|
-
|
|
293
|
+
/** Overlapped (unshifted) anchor minus lead months — stable xMin across lagged/overlapped tabs. */
|
|
294
|
+
export function computeDriversComparisonHistoricalWindowFloor(
|
|
294
295
|
mergedWithHistorical: ChartDataPoint[],
|
|
295
|
-
datasetHistorical: ChartDataPoint[],
|
|
296
296
|
forecastIds: number[],
|
|
297
|
-
):
|
|
298
|
-
if (mergedWithHistorical.length === 0
|
|
299
|
-
|
|
297
|
+
): string | null {
|
|
298
|
+
if (mergedWithHistorical.length === 0 || forecastIds.length === 0)
|
|
299
|
+
return null;
|
|
300
300
|
const { anchor: anchorMonth } = getZoomAnchorMonthFromDriverMaterialStarts(
|
|
301
301
|
mergedWithHistorical,
|
|
302
302
|
forecastIds,
|
|
303
303
|
);
|
|
304
|
-
if (anchorMonth === null) return
|
|
305
|
-
|
|
304
|
+
if (anchorMonth === null) return null;
|
|
305
|
+
return subtractMonthsFromMonthStart(
|
|
306
306
|
anchorMonth,
|
|
307
307
|
DRIVER_COMPARISON_CHART_LEAD_MONTHS,
|
|
308
308
|
);
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
export function buildDriversComparisonChartData(
|
|
312
|
+
mergedWithHistorical: ChartDataPoint[],
|
|
313
|
+
datasetHistorical: ChartDataPoint[],
|
|
314
|
+
forecastIds: number[],
|
|
315
|
+
historicalWindowFloor?: string | null,
|
|
316
|
+
): ChartDataPoint[] {
|
|
317
|
+
if (mergedWithHistorical.length === 0) return mergedWithHistorical;
|
|
318
|
+
if (forecastIds.length === 0) return mergedWithHistorical;
|
|
319
|
+
|
|
320
|
+
const xMin =
|
|
321
|
+
historicalWindowFloor ??
|
|
322
|
+
computeDriversComparisonHistoricalWindowFloor(
|
|
323
|
+
mergedWithHistorical,
|
|
324
|
+
forecastIds,
|
|
325
|
+
);
|
|
326
|
+
if (xMin === null) return mergedWithHistorical;
|
|
327
|
+
|
|
309
328
|
const withLead = prependHistoricalLeadFromDataset(
|
|
310
329
|
mergedWithHistorical,
|
|
311
330
|
datasetHistorical,
|