@sybilion/uilib 1.3.23 → 1.3.25

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (39) hide show
  1. package/dist/esm/components/widgets/PerformanceChart/HorizonsSelector/HorizonsSelector.js +34 -0
  2. package/dist/esm/components/widgets/PerformanceChart/HorizonsSelector/HorizonsSelector.styl.js +7 -0
  3. package/dist/esm/components/widgets/PerformanceChart/PerformanceChart.constants.js +17 -0
  4. package/dist/esm/components/widgets/PerformanceChart/PerformanceChart.js +807 -0
  5. package/dist/esm/components/widgets/PerformanceChart/PerformanceChart.styl.js +7 -0
  6. package/dist/esm/components/widgets/PerformanceChart/PerformanceTable.js +130 -0
  7. package/dist/esm/components/widgets/PerformanceChart/PerformanceUnderChartLegend/PerformanceUnderChartLegend.js +20 -0
  8. package/dist/esm/components/widgets/PerformanceChart/PerformanceUnderChartLegend/PerformanceUnderChartLegend.styl.js +7 -0
  9. package/dist/esm/components/widgets/PerformanceChart/performanceChart.helpers.js +591 -0
  10. package/dist/esm/components/widgets/PerformanceChart/performanceChartUserSeries.js +109 -0
  11. package/dist/esm/index.js +4 -0
  12. package/dist/esm/types/src/components/widgets/PerformanceChart/HorizonsSelector/HorizonsSelector.d.ts +7 -0
  13. package/dist/esm/types/src/components/widgets/PerformanceChart/PerformanceChart.constants.d.ts +3 -0
  14. package/dist/esm/types/src/components/widgets/PerformanceChart/PerformanceChart.d.ts +54 -0
  15. package/dist/esm/types/src/components/widgets/PerformanceChart/PerformanceTable.d.ts +31 -0
  16. package/dist/esm/types/src/components/widgets/PerformanceChart/PerformanceUnderChartLegend/PerformanceUnderChartLegend.d.ts +20 -0
  17. package/dist/esm/types/src/components/widgets/PerformanceChart/index.d.ts +4 -0
  18. package/dist/esm/types/src/components/widgets/PerformanceChart/performanceChart.helpers.d.ts +212 -0
  19. package/dist/esm/types/src/components/widgets/PerformanceChart/performanceChartUserSeries.d.ts +20 -0
  20. package/dist/esm/types/src/docs/pages/PerformanceChartPage.d.ts +1 -0
  21. package/dist/esm/types/src/index.d.ts +1 -0
  22. package/package.json +1 -1
  23. package/src/components/widgets/PerformanceChart/HorizonsSelector/HorizonsSelector.styl +25 -0
  24. package/src/components/widgets/PerformanceChart/HorizonsSelector/HorizonsSelector.styl.d.ts +11 -0
  25. package/src/components/widgets/PerformanceChart/HorizonsSelector/HorizonsSelector.tsx +67 -0
  26. package/src/components/widgets/PerformanceChart/PerformanceChart.constants.ts +17 -0
  27. package/src/components/widgets/PerformanceChart/PerformanceChart.styl +194 -0
  28. package/src/components/widgets/PerformanceChart/PerformanceChart.styl.d.ts +30 -0
  29. package/src/components/widgets/PerformanceChart/PerformanceChart.tsx +1251 -0
  30. package/src/components/widgets/PerformanceChart/PerformanceTable.tsx +381 -0
  31. package/src/components/widgets/PerformanceChart/PerformanceUnderChartLegend/PerformanceUnderChartLegend.styl +49 -0
  32. package/src/components/widgets/PerformanceChart/PerformanceUnderChartLegend/PerformanceUnderChartLegend.styl.d.ts +12 -0
  33. package/src/components/widgets/PerformanceChart/PerformanceUnderChartLegend/PerformanceUnderChartLegend.tsx +83 -0
  34. package/src/components/widgets/PerformanceChart/index.ts +28 -0
  35. package/src/components/widgets/PerformanceChart/performanceChart.helpers.ts +790 -0
  36. package/src/components/widgets/PerformanceChart/performanceChartUserSeries.ts +149 -0
  37. package/src/docs/pages/PerformanceChartPage.tsx +211 -0
  38. package/src/docs/registry.ts +6 -0
  39. package/src/index.ts +1 -0
@@ -0,0 +1,149 @@
1
+ import type { ForecastData } from '#uilib/types/forecast-data';
2
+ import { normalizeToMonthStart } from '#uilib/utils/chartConnectionPoint';
3
+
4
+ export const SPAGHETTI_TIME_SERIES_MATRIX_V = 2 as const;
5
+
6
+ export interface SpaghettiPerformanceMatrixPayload {
7
+ v: typeof SPAGHETTI_TIME_SERIES_MATRIX_V;
8
+ dates: string[];
9
+ horizonKeys: string[];
10
+ grid: number[][];
11
+ perHorizonDates?: string[][];
12
+ }
13
+
14
+ export const SPAGHETTI_MATRIX_SYNTHETIC_BASE = 9_800_000;
15
+ export const SPAGHETTI_MATRIX_MAX_COLS = 128;
16
+ export const SPAGHETTI_LOCAL_LS_USER_SERIES_ROW_ID = 9_000_001;
17
+
18
+ const PER_HORIZON_VIEW_CUSTOM_LINE_ID_BASE =
19
+ SPAGHETTI_MATRIX_SYNTHETIC_BASE + 4_000_000;
20
+
21
+ export function spaghettiMatrixSyntheticId(
22
+ userSeriesRowId: number,
23
+ horizonIndex: number,
24
+ ): number {
25
+ return (
26
+ SPAGHETTI_MATRIX_SYNTHETIC_BASE +
27
+ userSeriesRowId * SPAGHETTI_MATRIX_MAX_COLS +
28
+ horizonIndex
29
+ );
30
+ }
31
+
32
+ export function isSpaghettiMatrixSyntheticLineId(id: number): boolean {
33
+ return id >= SPAGHETTI_MATRIX_SYNTHETIC_BASE;
34
+ }
35
+
36
+ function perHorizonViewCustomMatrixSyntheticId(colIdx: number): number {
37
+ return PER_HORIZON_VIEW_CUSTOM_LINE_ID_BASE + colIdx;
38
+ }
39
+
40
+ function matrixHasValidPerHorizonDates(
41
+ matrix: SpaghettiPerformanceMatrixPayload,
42
+ ): boolean {
43
+ const ph = matrix.perHorizonDates;
44
+ if (!ph || ph.length !== matrix.grid.length) return false;
45
+ const h = matrix.horizonKeys.length;
46
+ return ph.every(
47
+ (row, r) =>
48
+ Array.isArray(row) &&
49
+ row.length === h &&
50
+ row.length === matrix.grid[r].length,
51
+ );
52
+ }
53
+
54
+ export function getCustomMatrixSeriesForHorizonTab(
55
+ matrix: SpaghettiPerformanceMatrixPayload,
56
+ horizonKey: string,
57
+ rowId: number,
58
+ forecastData: Record<string, ForecastData> | undefined,
59
+ ): {
60
+ lineId: number;
61
+ dates: string[];
62
+ forecastValues: number[];
63
+ } | null {
64
+ const colIdx = matrix.horizonKeys.indexOf(horizonKey);
65
+ if (colIdx < 0) return null;
66
+
67
+ if (matrixHasValidPerHorizonDates(matrix)) {
68
+ const ph = matrix.perHorizonDates!;
69
+ const dates: string[] = [];
70
+ const forecastValues: number[] = [];
71
+ for (let r = 0; r < matrix.grid.length; r++) {
72
+ const rawD = ph[r][colIdx];
73
+ const v = matrix.grid[r][colIdx];
74
+ if (typeof v !== 'number' || !Number.isFinite(v)) continue;
75
+ dates.push(
76
+ normalizeToMonthStart(typeof rawD === 'string' ? rawD : String(rawD)),
77
+ );
78
+ forecastValues.push(v);
79
+ }
80
+ if (dates.length === 0) return null;
81
+ return {
82
+ lineId: perHorizonViewCustomMatrixSyntheticId(colIdx),
83
+ dates,
84
+ forecastValues,
85
+ };
86
+ }
87
+
88
+ const lineId = spaghettiMatrixSyntheticId(rowId, colIdx);
89
+ const fd = forecastData?.[String(lineId)];
90
+ if (!fd?.dates?.length || fd.forecastValues.length !== fd.dates.length) {
91
+ return null;
92
+ }
93
+ return {
94
+ lineId,
95
+ dates: fd.dates,
96
+ forecastValues: fd.forecastValues,
97
+ };
98
+ }
99
+
100
+ function isRecord(value: unknown): value is Record<string, unknown> {
101
+ return typeof value === 'object' && value !== null;
102
+ }
103
+
104
+ export function tryParseSpaghettiPerformanceMatrix(
105
+ parsed: unknown,
106
+ ): SpaghettiPerformanceMatrixPayload | null {
107
+ if (!isRecord(parsed)) return null;
108
+ if (parsed.v !== SPAGHETTI_TIME_SERIES_MATRIX_V) return null;
109
+ const dates = parsed.dates;
110
+ const horizonKeys = parsed.horizonKeys;
111
+ const grid = parsed.grid;
112
+ if (
113
+ !Array.isArray(dates) ||
114
+ !Array.isArray(horizonKeys) ||
115
+ !Array.isArray(grid)
116
+ ) {
117
+ return null;
118
+ }
119
+ if (
120
+ dates.length === 0 ||
121
+ horizonKeys.length === 0 ||
122
+ grid.length !== dates.length
123
+ ) {
124
+ return null;
125
+ }
126
+ const h = horizonKeys.length;
127
+ if (!grid.every(row => Array.isArray(row) && row.length === h)) return null;
128
+ const perHorizonDates = parsed.perHorizonDates;
129
+ let ph: string[][] | undefined;
130
+ if (perHorizonDates !== undefined) {
131
+ if (
132
+ !Array.isArray(perHorizonDates) ||
133
+ perHorizonDates.length !== grid.length
134
+ ) {
135
+ return null;
136
+ }
137
+ ph = perHorizonDates.map(row => {
138
+ if (!Array.isArray(row) || row.length !== h) return [];
139
+ return row.map(d => String(d));
140
+ });
141
+ }
142
+ return {
143
+ v: SPAGHETTI_TIME_SERIES_MATRIX_V,
144
+ dates: dates.map(d => String(d)),
145
+ horizonKeys: horizonKeys.map(k => String(k)),
146
+ grid: grid.map(row => row.map(v => Number(v))),
147
+ ...(ph ? { perHorizonDates: ph } : {}),
148
+ };
149
+ }
@@ -0,0 +1,211 @@
1
+ import { useMemo, useState } from 'react';
2
+
3
+ import type { ChartDataPoint } from '#uilib/components/ui/ChartAreaInteractive/ChartAreaInteractive.types';
4
+ import { PageContentSection } from '#uilib/components/ui/Page';
5
+ import { Switch } from '#uilib/components/ui/Switch';
6
+ import {
7
+ PerformanceChart,
8
+ type PerformanceChartPayload,
9
+ SPAGHETTI_TIME_SERIES_MATRIX_V,
10
+ } from '#uilib/components/widgets/PerformanceChart';
11
+ import { useTheme } from '#uilib/contexts/theme-context';
12
+
13
+ import { AppPageHeader } from '../components/AppPageHeader/AppPageHeader';
14
+ import { DocsHeaderActions } from '../docsHeaderActions';
15
+
16
+ function monthKey(year: number, month: number): string {
17
+ return `${year}-${String(month).padStart(2, '0')}-01`;
18
+ }
19
+
20
+ function buildMonthlyHistorical(
21
+ startYear: number,
22
+ startMonth: number,
23
+ count: number,
24
+ base: number,
25
+ ): ChartDataPoint[] {
26
+ const out: ChartDataPoint[] = [];
27
+ let y = startYear;
28
+ let m = startMonth;
29
+ for (let i = 0; i < count; i++) {
30
+ out.push({
31
+ date: monthKey(y, m),
32
+ historical: base + Math.sin(i * 0.25) * 8 + i * 0.4,
33
+ });
34
+ m += 1;
35
+ if (m > 12) {
36
+ m = 1;
37
+ y += 1;
38
+ }
39
+ }
40
+ return out;
41
+ }
42
+
43
+ function buildHorizonForecasts(
44
+ horizonKey: string,
45
+ startYear: number,
46
+ startMonth: number,
47
+ count: number,
48
+ base: number,
49
+ drift: number,
50
+ ): Record<string, number> {
51
+ const out: Record<string, number> = {};
52
+ let y = startYear;
53
+ let m = startMonth;
54
+ for (let i = 0; i < count; i++) {
55
+ out[monthKey(y, m)] = base + Math.cos(i * 0.3 + drift) * 3 + i * 0.15;
56
+ m += 1;
57
+ if (m > 12) {
58
+ m = 1;
59
+ y += 1;
60
+ }
61
+ }
62
+ return out;
63
+ }
64
+
65
+ const MOCK_PERFORMANCE: PerformanceChartPayload = {
66
+ model: {
67
+ forecasts: {
68
+ horizon_1: buildHorizonForecasts('horizon_1', 2022, 1, 24, 112, 0),
69
+ horizon_2: buildHorizonForecasts('horizon_2', 2022, 1, 24, 112, 0.5),
70
+ horizon_3: buildHorizonForecasts('horizon_3', 2022, 1, 24, 112, 1),
71
+ },
72
+ metrics_history: {
73
+ horizon_1: {
74
+ mae: { '24m': 0.42 },
75
+ mape: { '24m': 0.038 },
76
+ },
77
+ horizon_2: {
78
+ mae: { '24m': 0.51 },
79
+ mape: { '24m': 0.045 },
80
+ },
81
+ horizon_3: {
82
+ mae: { '24m': 0.58 },
83
+ mape: { '24m': 0.052 },
84
+ },
85
+ },
86
+ },
87
+ drift: {
88
+ forecasts: {
89
+ horizon_1: buildHorizonForecasts('horizon_1', 2022, 1, 24, 112, 1.2),
90
+ horizon_2: buildHorizonForecasts('horizon_2', 2022, 1, 24, 112, 1.4),
91
+ horizon_3: buildHorizonForecasts('horizon_3', 2022, 1, 24, 112, 1.6),
92
+ },
93
+ metrics_history: {
94
+ horizon_1: {
95
+ mae: { '24m': 0.62 },
96
+ mape: { '24m': 0.055 },
97
+ },
98
+ horizon_2: {
99
+ mae: { '24m': 0.71 },
100
+ mape: { '24m': 0.061 },
101
+ },
102
+ horizon_3: {
103
+ mae: { '24m': 0.79 },
104
+ mape: { '24m': 0.068 },
105
+ },
106
+ },
107
+ },
108
+ };
109
+
110
+ export default function PerformanceChartPage() {
111
+ const { isDarkMode } = useTheme();
112
+ const [loading, setLoading] = useState(false);
113
+ const [runAnalysisHint, setRunAnalysisHint] = useState(false);
114
+ const [emptyPayload, setEmptyPayload] = useState(false);
115
+ const [timeRange, setTimeRange] = useState<string>('All');
116
+
117
+ const performanceData = useMemo(
118
+ () => (emptyPayload ? null : MOCK_PERFORMANCE),
119
+ [emptyPayload],
120
+ );
121
+ const historicalData = useMemo(
122
+ () => buildMonthlyHistorical(2020, 1, 48, 108),
123
+ [],
124
+ );
125
+
126
+ return (
127
+ <>
128
+ <AppPageHeader
129
+ breadcrumbs={[{ label: 'Performance chart' }]}
130
+ title="PerformanceChart"
131
+ subheader="Per-horizon and spaghetti performance plots with linked metrics table."
132
+ actions={<DocsHeaderActions />}
133
+ />
134
+ <PageContentSection>
135
+ <div
136
+ style={{
137
+ display: 'flex',
138
+ flexWrap: 'wrap',
139
+ alignItems: 'center',
140
+ gap: 16,
141
+ marginBottom: 16,
142
+ }}
143
+ >
144
+ <div style={{ display: 'flex', alignItems: 'center', gap: 12 }}>
145
+ <Switch
146
+ id="loading-overlay"
147
+ checked={loading}
148
+ onCheckedChange={setLoading}
149
+ />
150
+ <label htmlFor="loading-overlay">Loading overlay</label>
151
+ </div>
152
+ <div style={{ display: 'flex', alignItems: 'center', gap: 12 }}>
153
+ <Switch
154
+ id="run-analysis-hint"
155
+ checked={runAnalysisHint}
156
+ onCheckedChange={setRunAnalysisHint}
157
+ />
158
+ <label htmlFor="run-analysis-hint">Run analysis hint</label>
159
+ </div>
160
+ <div style={{ display: 'flex', alignItems: 'center', gap: 12 }}>
161
+ <Switch
162
+ id="empty-payload"
163
+ checked={emptyPayload}
164
+ onCheckedChange={setEmptyPayload}
165
+ />
166
+ <label htmlFor="empty-payload">Empty payload</label>
167
+ </div>
168
+ </div>
169
+ <PerformanceChart
170
+ performanceData={performanceData}
171
+ historicalData={historicalData}
172
+ combinedData={historicalData}
173
+ loading={loading}
174
+ chartLoading={loading}
175
+ performanceSectionPending={loading}
176
+ performanceDataLoading={loading}
177
+ perfFetchSettled={!loading}
178
+ isEmpty={emptyPayload}
179
+ runAnalysisHint={runAnalysisHint}
180
+ statusHint={
181
+ emptyPayload ? 'No performance data for this dataset.' : null
182
+ }
183
+ timeRange={timeRange}
184
+ onTimeRangeChange={setTimeRange}
185
+ isDarkTheme={isDarkMode}
186
+ performanceAnalysisId={1}
187
+ seriesInitKey={emptyPayload ? 'empty' : 'mock'}
188
+ showAddEditCustomDataButton
189
+ customPerformanceMatrix={
190
+ emptyPayload
191
+ ? null
192
+ : {
193
+ v: SPAGHETTI_TIME_SERIES_MATRIX_V,
194
+ dates: ['2022-01-01', '2022-02-01'],
195
+ horizonKeys: ['horizon_1', 'horizon_2', 'horizon_3'],
196
+ grid: [
197
+ [111, 110.5, 110],
198
+ [112, 111.2, 110.8],
199
+ ],
200
+ perHorizonDates: [
201
+ ['2022-01-01', '2022-01-01', '2022-01-01'],
202
+ ['2022-02-01', '2022-02-01', '2022-02-01'],
203
+ ],
204
+ }
205
+ }
206
+ customPerformanceLabel="Demo custom series"
207
+ />
208
+ </PageContentSection>
209
+ </>
210
+ );
211
+ }
@@ -151,6 +151,12 @@ export const DOC_REGISTRY: DocEntry[] = [
151
151
  section: 'Widgets',
152
152
  load: () => import('./pages/DriversComparisonChartPage'),
153
153
  },
154
+ {
155
+ slug: 'performance-chart',
156
+ title: 'PerformanceChart',
157
+ section: 'Widgets',
158
+ load: () => import('./pages/PerformanceChartPage'),
159
+ },
154
160
  {
155
161
  slug: 'dropdown-menu',
156
162
  title: 'DropdownMenu',
package/src/index.ts CHANGED
@@ -74,6 +74,7 @@ export * from './components/widgets/SidebarDatasetsItemsGrouped';
74
74
  export * from './components/widgets/DriverCard';
75
75
  export * from './components/widgets/DriverMap';
76
76
  export * from './components/widgets/DriversComparisonChart';
77
+ export * from './components/widgets/PerformanceChart';
77
78
  export * from './components/widgets/SybilionAppHeader';
78
79
  export * from './components/widgets/SybilionAuthLayout';
79
80
  export * from './components/widgets/SybilionSignInPanel';