@sybilion/uilib 1.3.0 → 1.3.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/esm/components/ui/Chart/Chart.js +4 -0
- package/dist/esm/components/ui/Chart/lightweight/LightweightForecastChart.js +460 -0
- package/dist/esm/components/ui/Chart/lightweight/LightweightForecastChart.styl.js +7 -0
- package/dist/esm/components/ui/Chart/lightweight/chartTime.js +16 -0
- package/dist/esm/components/ui/Chart/lightweight/lightweightForecastChart.helpers.js +114 -0
- package/dist/esm/components/ui/Chart/lightweight/quantileBandCustomSeries.js +147 -0
- package/dist/esm/components/ui/Chart/quantileBandConeChartData.js +131 -0
- package/dist/esm/components/ui/ChartAreaInteractive/overlays/useQuantileBands.js +4 -102
- package/dist/esm/components/widgets/DriverCard/DriverPerformanceChart.js +4 -0
- package/dist/esm/index.js +1 -0
- package/dist/esm/types/src/components/ui/Chart/Chart.d.ts +1 -0
- package/dist/esm/types/src/components/ui/Chart/lightweight/LightweightForecastChart.d.ts +26 -0
- package/dist/esm/types/src/components/ui/Chart/lightweight/chartTime.d.ts +5 -0
- package/dist/esm/types/src/components/ui/Chart/lightweight/lightweightForecastChart.helpers.d.ts +13 -0
- package/dist/esm/types/src/components/ui/Chart/lightweight/quantileBandCustomSeries.d.ts +24 -0
- package/dist/esm/types/src/components/ui/Chart/quantileBandConeChartData.d.ts +7 -0
- package/dist/esm/types/src/docs/pages/LightweightChartPage.d.ts +1 -0
- package/package.json +3 -2
- package/src/components/ui/Chart/Chart.tsx +4 -0
- package/src/components/ui/Chart/lightweight/LightweightForecastChart.styl +25 -0
- package/src/components/ui/Chart/lightweight/LightweightForecastChart.styl.d.ts +11 -0
- package/src/components/ui/Chart/lightweight/LightweightForecastChart.tsx +721 -0
- package/src/components/ui/Chart/lightweight/chartTime.ts +18 -0
- package/src/components/ui/Chart/lightweight/lightweightForecastChart.helpers.ts +141 -0
- package/src/components/ui/Chart/lightweight/quantileBandCustomSeries.ts +215 -0
- package/src/components/ui/Chart/quantileBandConeChartData.ts +171 -0
- package/src/components/ui/ChartAreaInteractive/overlays/useQuantileBands.ts +5 -131
- package/src/declarations.d.ts +2 -0
- package/src/docs/config/webpack.config.js +25 -2
- package/src/docs/index.tsx +1 -1
- package/src/docs/pages/LightweightChartPage.styl +18 -0
- package/src/docs/pages/LightweightChartPage.styl.d.ts +10 -0
- package/src/docs/pages/LightweightChartPage.tsx +195 -0
- package/src/docs/registry.ts +6 -0
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { useMemo } from 'react';
|
|
2
2
|
|
|
3
3
|
import type { QuantileBandConfig } from '#uilib/components/ui/Chart/chartForecastVisualization.types';
|
|
4
|
+
import { applyQuantileBandConeToChartData } from '#uilib/components/ui/Chart/quantileBandConeChartData';
|
|
4
5
|
import { ChartDataPoint } from '#uilib/components/ui/ChartAreaInteractive/ChartAreaInteractive.types';
|
|
5
6
|
import type { ForecastData } from '#uilib/types/forecast-data';
|
|
6
7
|
|
|
@@ -45,126 +46,17 @@ export function useQuantileBands({
|
|
|
45
46
|
const allQuantilesData = forecastDataForSelected.allQuantiles;
|
|
46
47
|
const clonedData = [...chartData];
|
|
47
48
|
|
|
48
|
-
// Get forecast dates to map quantile array indices correctly
|
|
49
49
|
const forecastDates = forecastDataForSelected.dates || [];
|
|
50
50
|
const forecastDatesSet = new Set(forecastDates);
|
|
51
51
|
|
|
52
|
-
// Find the last historical point for connection
|
|
53
|
-
const historicalPoints = clonedData.filter(
|
|
54
|
-
point => point.historical !== undefined,
|
|
55
|
-
);
|
|
56
|
-
const lastHistoricalPoint =
|
|
57
|
-
historicalPoints.length > 0
|
|
58
|
-
? historicalPoints[historicalPoints.length - 1]
|
|
59
|
-
: null;
|
|
60
|
-
const lastHistoricalDate = lastHistoricalPoint?.date;
|
|
61
|
-
const lastHistoricalValue = lastHistoricalPoint?.historical;
|
|
62
|
-
|
|
63
|
-
// Get first forecast date
|
|
64
|
-
const firstForecastDate = forecastDates[0];
|
|
65
|
-
const firstForecastDateObj = firstForecastDate
|
|
66
|
-
? new Date(firstForecastDate)
|
|
67
|
-
: null;
|
|
68
|
-
const lastHistoricalDateObj = lastHistoricalDate
|
|
69
|
-
? new Date(lastHistoricalDate)
|
|
70
|
-
: null;
|
|
71
|
-
|
|
72
|
-
// Check if there's a gap between historical and forecast data (forecast starts after historical)
|
|
73
|
-
const hasGap =
|
|
74
|
-
lastHistoricalDate &&
|
|
75
|
-
firstForecastDate &&
|
|
76
|
-
lastHistoricalValue !== undefined &&
|
|
77
|
-
firstForecastDateObj &&
|
|
78
|
-
lastHistoricalDateObj &&
|
|
79
|
-
firstForecastDateObj.getTime() > lastHistoricalDateObj.getTime();
|
|
80
|
-
|
|
81
|
-
// Check if forecast starts before or at last historical point (need bridge point)
|
|
82
|
-
const needsBridgePoint =
|
|
83
|
-
lastHistoricalDate &&
|
|
84
|
-
firstForecastDate &&
|
|
85
|
-
lastHistoricalValue !== undefined &&
|
|
86
|
-
firstForecastDateObj &&
|
|
87
|
-
lastHistoricalDateObj &&
|
|
88
|
-
firstForecastDateObj.getTime() <= lastHistoricalDateObj.getTime();
|
|
89
|
-
|
|
90
|
-
// Find bridge point when forecast starts before or at last historical point
|
|
91
|
-
let bridgePoint = null;
|
|
92
|
-
let pointBeforeForecast = null;
|
|
93
|
-
if (needsBridgePoint && historicalPoints.length > 0) {
|
|
94
|
-
// Find the last historical point before or at the first forecast date
|
|
95
|
-
// If dates are equal, use lastHistoricalPoint; otherwise find the point before
|
|
96
|
-
bridgePoint =
|
|
97
|
-
firstForecastDateObj &&
|
|
98
|
-
lastHistoricalDateObj &&
|
|
99
|
-
firstForecastDateObj.getTime() === lastHistoricalDateObj.getTime()
|
|
100
|
-
? lastHistoricalPoint
|
|
101
|
-
: [...historicalPoints]
|
|
102
|
-
.reverse()
|
|
103
|
-
.find(
|
|
104
|
-
p =>
|
|
105
|
-
firstForecastDateObj &&
|
|
106
|
-
new Date(p.date).getTime() < firstForecastDateObj.getTime(),
|
|
107
|
-
) || lastHistoricalPoint;
|
|
108
|
-
|
|
109
|
-
// Find the actual point BEFORE the first forecast date for connection
|
|
110
|
-
if (firstForecastDateObj) {
|
|
111
|
-
pointBeforeForecast = [...historicalPoints].findLast(
|
|
112
|
-
p => new Date(p.date).getTime() < firstForecastDateObj.getTime(),
|
|
113
|
-
);
|
|
114
|
-
}
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
// Create a map from date to quantile array index
|
|
118
52
|
const dateToQuantileIndex = new Map<string, number>();
|
|
119
53
|
forecastDates.forEach((date, index) => {
|
|
120
54
|
dateToQuantileIndex.set(date, index);
|
|
121
55
|
});
|
|
122
56
|
|
|
123
|
-
const
|
|
57
|
+
const withRawBands = clonedData.map(point => {
|
|
124
58
|
const newPoint = { ...point };
|
|
125
59
|
|
|
126
|
-
// If there's a gap and this is the last historical point, add band data for connection
|
|
127
|
-
if (
|
|
128
|
-
hasGap &&
|
|
129
|
-
point.date === lastHistoricalDate &&
|
|
130
|
-
lastHistoricalValue !== undefined
|
|
131
|
-
) {
|
|
132
|
-
// Set zero-width band at the last historical value for visual connection
|
|
133
|
-
newPoint[bandKey] = [lastHistoricalValue, lastHistoricalValue] as [
|
|
134
|
-
number,
|
|
135
|
-
number,
|
|
136
|
-
];
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
// If forecast starts before or at last historical point, add bridge point connection
|
|
140
|
-
// Set connection band at the point BEFORE the first forecast date (if exists)
|
|
141
|
-
const isBridgePointDate =
|
|
142
|
-
needsBridgePoint &&
|
|
143
|
-
bridgePoint &&
|
|
144
|
-
bridgePoint.historical !== undefined &&
|
|
145
|
-
point.date === bridgePoint.date;
|
|
146
|
-
const isPointBeforeForecast =
|
|
147
|
-
needsBridgePoint &&
|
|
148
|
-
pointBeforeForecast &&
|
|
149
|
-
pointBeforeForecast.historical !== undefined &&
|
|
150
|
-
point.date === pointBeforeForecast.date;
|
|
151
|
-
const isAlsoForecastDate = forecastDatesSet.has(point.date);
|
|
152
|
-
|
|
153
|
-
// Set zero-width connection band at the point BEFORE forecast starts
|
|
154
|
-
if (isPointBeforeForecast && !isAlsoForecastDate) {
|
|
155
|
-
newPoint[bandKey] = [
|
|
156
|
-
pointBeforeForecast.historical,
|
|
157
|
-
pointBeforeForecast.historical,
|
|
158
|
-
] as [number, number];
|
|
159
|
-
} else if (isBridgePointDate && !isAlsoForecastDate) {
|
|
160
|
-
// Fallback: if no point before forecast, use bridge point
|
|
161
|
-
newPoint[bandKey] = [
|
|
162
|
-
bridgePoint.historical,
|
|
163
|
-
bridgePoint.historical,
|
|
164
|
-
] as [number, number];
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
// Only update band data for forecast dates
|
|
168
60
|
if (forecastDatesSet.has(point.date)) {
|
|
169
61
|
const quantileIndex = dateToQuantileIndex.get(point.date);
|
|
170
62
|
|
|
@@ -176,35 +68,17 @@ export function useQuantileBands({
|
|
|
176
68
|
);
|
|
177
69
|
|
|
178
70
|
if (bandValues) {
|
|
179
|
-
|
|
180
|
-
// start the band from the historical value for smooth connection
|
|
181
|
-
const isBridgePointDate =
|
|
182
|
-
needsBridgePoint &&
|
|
183
|
-
bridgePoint &&
|
|
184
|
-
point.date === bridgePoint.date &&
|
|
185
|
-
bridgePoint.historical !== undefined;
|
|
186
|
-
|
|
187
|
-
if (isBridgePointDate && quantileIndex === 0) {
|
|
188
|
-
// Start from historical value, expand to forecast upper bound
|
|
189
|
-
newPoint[bandKey] = [bridgePoint.historical, bandValues[1]] as [
|
|
190
|
-
number,
|
|
191
|
-
number,
|
|
192
|
-
];
|
|
193
|
-
} else {
|
|
194
|
-
newPoint[bandKey] = bandValues;
|
|
195
|
-
}
|
|
71
|
+
newPoint[bandKey] = bandValues;
|
|
196
72
|
} else {
|
|
197
|
-
// Remove band data if values don't exist
|
|
198
73
|
delete newPoint[bandKey];
|
|
199
74
|
}
|
|
200
75
|
}
|
|
201
76
|
}
|
|
202
|
-
// For non-forecast dates, preserve existing band data if it exists
|
|
203
|
-
// This ensures continuity of the band visualization
|
|
204
77
|
|
|
205
78
|
return newPoint;
|
|
206
79
|
});
|
|
207
|
-
|
|
80
|
+
|
|
81
|
+
return applyQuantileBandConeToChartData(withRawBands, bandKey);
|
|
208
82
|
}, [chartData, selectedForecastId, forecastData, bandKey, getBandValues]);
|
|
209
83
|
|
|
210
84
|
const quantileBands: QuantileBandConfig[] = useMemo(() => {
|
package/src/declarations.d.ts
CHANGED
|
@@ -22,9 +22,27 @@ const pkg = require('../../../package.json');
|
|
|
22
22
|
const themeStyl = pathResolve(paths.src, 'theme.styl');
|
|
23
23
|
const logoSvgPath = pathResolve(paths.assets, 'logo.svg');
|
|
24
24
|
|
|
25
|
+
/** GitHub Pages project sites live at /<repo>/; set PUBLIC_PATH=/repo-name/ for production deploy. */
|
|
26
|
+
function normalizePublicPath(raw) {
|
|
27
|
+
const v = (raw && String(raw).trim()) || '/';
|
|
28
|
+
if (v === '/') {
|
|
29
|
+
return '/';
|
|
30
|
+
}
|
|
31
|
+
const withLeading = v.startsWith('/') ? v : `/${v}`;
|
|
32
|
+
return withLeading.endsWith('/') ? withLeading : `${withLeading}/`;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function routerBasenameFromPublicPath(publicPath) {
|
|
36
|
+
if (publicPath === '/') {
|
|
37
|
+
return '';
|
|
38
|
+
}
|
|
39
|
+
return publicPath.replace(/\/$/, '');
|
|
40
|
+
}
|
|
41
|
+
|
|
25
42
|
export default (env, argv) => {
|
|
26
43
|
const isDev = argv.mode === 'development';
|
|
27
|
-
const
|
|
44
|
+
const publicPath = normalizePublicPath(process.env.PUBLIC_PATH);
|
|
45
|
+
const docsRouterBasename = routerBasenameFromPublicPath(publicPath);
|
|
28
46
|
const alias = {
|
|
29
47
|
'#uilib': paths.src,
|
|
30
48
|
uilib: paths.src,
|
|
@@ -38,6 +56,7 @@ export default (env, argv) => {
|
|
|
38
56
|
entry: [paths.docs],
|
|
39
57
|
output: {
|
|
40
58
|
path: paths.build,
|
|
59
|
+
publicPath,
|
|
41
60
|
},
|
|
42
61
|
|
|
43
62
|
resolve: {
|
|
@@ -152,6 +171,7 @@ export default (env, argv) => {
|
|
|
152
171
|
new webpack.DefinePlugin({
|
|
153
172
|
isDEV: JSON.stringify(isDev),
|
|
154
173
|
VERSION: JSON.stringify(pkg.version),
|
|
174
|
+
DOCS_ROUTER_BASENAME: JSON.stringify(docsRouterBasename),
|
|
155
175
|
}),
|
|
156
176
|
new CopyWebpackPlugin({
|
|
157
177
|
patterns: [
|
|
@@ -174,7 +194,7 @@ export default (env, argv) => {
|
|
|
174
194
|
|
|
175
195
|
new HtmlWebpackPlugin({
|
|
176
196
|
lang: 'en',
|
|
177
|
-
baseUrl:
|
|
197
|
+
baseUrl: publicPath,
|
|
178
198
|
filename: 'index.html',
|
|
179
199
|
template: `${paths.assets}/index.html`,
|
|
180
200
|
minify: isDev
|
|
@@ -221,6 +241,9 @@ export default (env, argv) => {
|
|
|
221
241
|
hot: true,
|
|
222
242
|
port: process.env.PORT || 8181,
|
|
223
243
|
historyApiFallback: true,
|
|
244
|
+
...(publicPath !== '/' && {
|
|
245
|
+
devMiddleware: { publicPath },
|
|
246
|
+
}),
|
|
224
247
|
},
|
|
225
248
|
});
|
|
226
249
|
}
|
package/src/docs/index.tsx
CHANGED
|
@@ -10,7 +10,7 @@ const elem = document.getElementById('app-root') as HTMLElement;
|
|
|
10
10
|
const root = createRoot(elem);
|
|
11
11
|
|
|
12
12
|
root.render(
|
|
13
|
-
<BrowserRouter>
|
|
13
|
+
<BrowserRouter basename={DOCS_ROUTER_BASENAME || undefined}>
|
|
14
14
|
<ThemeProvider activeColor={DEFAULT_THEME_ACTIVE_COLOR}>
|
|
15
15
|
<App />
|
|
16
16
|
</ThemeProvider>
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
.sectionTitle
|
|
2
|
+
font-size var(--font-size-heading-5, 14px)
|
|
3
|
+
font-weight 600
|
|
4
|
+
margin 0 0 8px
|
|
5
|
+
|
|
6
|
+
.caption
|
|
7
|
+
margin 0 0 10px
|
|
8
|
+
color var(--muted-foreground, #71717a)
|
|
9
|
+
font-size var(--font-size-body-5, 12px)
|
|
10
|
+
|
|
11
|
+
.demo
|
|
12
|
+
width 100%
|
|
13
|
+
max-width 56rem
|
|
14
|
+
|
|
15
|
+
.stack
|
|
16
|
+
display flex
|
|
17
|
+
flex-direction column
|
|
18
|
+
gap 28px
|
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
import { useMemo } from 'react';
|
|
2
|
+
|
|
3
|
+
import { LightweightForecastChart } from '#uilib/components/ui/Chart';
|
|
4
|
+
import type { QuantileBandConfig } from '#uilib/components/ui/Chart/chartForecastVisualization.types';
|
|
5
|
+
import type { ChartDataPoint } from '#uilib/components/ui/ChartAreaInteractive/ChartAreaInteractive.types';
|
|
6
|
+
import type { ForecastItemData } from '#uilib/components/ui/ChartAreaInteractive/ChartLines';
|
|
7
|
+
import { PageContentSection } from '#uilib/components/ui/Page';
|
|
8
|
+
import { useTheme } from '#uilib/contexts/theme-context';
|
|
9
|
+
|
|
10
|
+
import { AppPageHeader } from '../components/AppPageHeader/AppPageHeader';
|
|
11
|
+
import { DocsHeaderActions } from '../docsHeaderActions';
|
|
12
|
+
import S from './LightweightChartPage.styl';
|
|
13
|
+
|
|
14
|
+
const DEMO_FORECAST_ID = 1;
|
|
15
|
+
|
|
16
|
+
const DEMO_FORECAST_DATES = ['2023-06-01', '2023-07-01', '2023-08-01'] as const;
|
|
17
|
+
|
|
18
|
+
const DEMO_OVERLAY_QUANTILES = {
|
|
19
|
+
'0.1': [12.5, 13.2, 14],
|
|
20
|
+
'0.25': [13, 13.6, 14.5],
|
|
21
|
+
'0.5': [13.5, 14, 15],
|
|
22
|
+
'0.75': [14, 14.5, 15.5],
|
|
23
|
+
'0.9': [14.5, 15, 16.2],
|
|
24
|
+
} as const;
|
|
25
|
+
|
|
26
|
+
const DEMO_BASE: ChartDataPoint[] = [
|
|
27
|
+
{ date: '2021-01-01', historical: 6 },
|
|
28
|
+
{ date: '2021-02-01', historical: 8 },
|
|
29
|
+
{ date: '2021-03-01', historical: 7 },
|
|
30
|
+
{ date: '2021-04-01', historical: 10 },
|
|
31
|
+
{ date: '2021-05-01', historical: 9 },
|
|
32
|
+
{ date: '2021-06-01', historical: 10 },
|
|
33
|
+
{ date: '2021-07-01', historical: 11 },
|
|
34
|
+
{ date: '2021-08-01', historical: 10 },
|
|
35
|
+
{ date: '2021-09-01', historical: 12 },
|
|
36
|
+
{ date: '2021-10-01', historical: 11 },
|
|
37
|
+
{ date: '2021-11-01', historical: 13 },
|
|
38
|
+
{ date: '2021-12-01', historical: 12 },
|
|
39
|
+
{ date: '2022-01-01', historical: 8 },
|
|
40
|
+
{ date: '2022-02-01', historical: 10 },
|
|
41
|
+
{ date: '2022-03-01', historical: 9 },
|
|
42
|
+
{ date: '2022-04-01', historical: 12 },
|
|
43
|
+
{ date: '2022-05-01', historical: 11 },
|
|
44
|
+
{ date: '2022-06-01', historical: 12 },
|
|
45
|
+
{ date: '2022-07-01', historical: 13 },
|
|
46
|
+
{ date: '2022-08-01', historical: 12 },
|
|
47
|
+
{ date: '2022-09-01', historical: 14 },
|
|
48
|
+
{ date: '2022-10-01', historical: 13 },
|
|
49
|
+
{ date: '2022-11-01', historical: 15 },
|
|
50
|
+
{ date: '2022-12-01', historical: 14 },
|
|
51
|
+
{ date: '2023-01-01', historical: 10 },
|
|
52
|
+
{ date: '2023-02-01', historical: 12 },
|
|
53
|
+
{ date: '2023-03-01', historical: 11 },
|
|
54
|
+
{ date: '2023-04-01', historical: 14 },
|
|
55
|
+
{ date: '2023-05-01', historical: 13 },
|
|
56
|
+
{ date: '2023-06-01', [`forecast_${DEMO_FORECAST_ID}`]: 13.5 },
|
|
57
|
+
{ date: '2023-07-01', [`forecast_${DEMO_FORECAST_ID}`]: 14 },
|
|
58
|
+
{ date: '2023-08-01', [`forecast_${DEMO_FORECAST_ID}`]: 15 },
|
|
59
|
+
];
|
|
60
|
+
|
|
61
|
+
const DEMO_FORECAST_ITEMS: ForecastItemData[] = [
|
|
62
|
+
{ id: DEMO_FORECAST_ID, name: 'My custom forecast' },
|
|
63
|
+
];
|
|
64
|
+
|
|
65
|
+
function withDemoBands(data: ChartDataPoint[]): ChartDataPoint[] {
|
|
66
|
+
return data.map(row => {
|
|
67
|
+
const idx = (DEMO_FORECAST_DATES as readonly string[]).indexOf(row.date);
|
|
68
|
+
if (idx === -1) return row;
|
|
69
|
+
const lowerWide = DEMO_OVERLAY_QUANTILES['0.1'][idx];
|
|
70
|
+
const upperWide = DEMO_OVERLAY_QUANTILES['0.9'][idx];
|
|
71
|
+
const lowerMid = DEMO_OVERLAY_QUANTILES['0.25'][idx];
|
|
72
|
+
const upperMid = DEMO_OVERLAY_QUANTILES['0.75'][idx];
|
|
73
|
+
return {
|
|
74
|
+
...row,
|
|
75
|
+
demo_band_wide: [lowerWide, upperWide],
|
|
76
|
+
demo_band_mid: [lowerMid, upperMid],
|
|
77
|
+
};
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
const DEMO_QUANTILE_BANDS: QuantileBandConfig[] = [
|
|
82
|
+
{
|
|
83
|
+
key: 'demo_band_wide',
|
|
84
|
+
quantiles: ['0.1', '0.9'],
|
|
85
|
+
opacity: 0.35,
|
|
86
|
+
name: 'P10–P90',
|
|
87
|
+
type: 'monotone',
|
|
88
|
+
color: 'rgba(100,190,220,0.35)',
|
|
89
|
+
strokeWidth: 0,
|
|
90
|
+
},
|
|
91
|
+
{
|
|
92
|
+
key: 'demo_band_mid',
|
|
93
|
+
quantiles: ['0.25', '0.75'],
|
|
94
|
+
opacity: 0.45,
|
|
95
|
+
name: 'P25–P75',
|
|
96
|
+
type: 'monotone',
|
|
97
|
+
color: 'rgba(65,165,238,0.45)',
|
|
98
|
+
strokeWidth: 0,
|
|
99
|
+
},
|
|
100
|
+
];
|
|
101
|
+
|
|
102
|
+
export default function LightweightChartPage() {
|
|
103
|
+
const { isDarkMode } = useTheme();
|
|
104
|
+
const historicalOnlyData = useMemo(
|
|
105
|
+
() => DEMO_BASE.filter(r => r.date <= '2023-05-01'),
|
|
106
|
+
[],
|
|
107
|
+
);
|
|
108
|
+
const bandsData = useMemo(() => withDemoBands(DEMO_BASE), []);
|
|
109
|
+
|
|
110
|
+
return (
|
|
111
|
+
<>
|
|
112
|
+
<AppPageHeader
|
|
113
|
+
breadcrumbs={[{ label: 'LightweightForecastChart' }]}
|
|
114
|
+
title="LightweightForecastChart"
|
|
115
|
+
subheader={
|
|
116
|
+
<>
|
|
117
|
+
TradingView{' '}
|
|
118
|
+
<a href="https://www.tradingview.com/lightweight-charts/">
|
|
119
|
+
Lightweight Charts
|
|
120
|
+
</a>{' '}
|
|
121
|
+
with historical + forecast lines, quantile fills, tooltip, legend,
|
|
122
|
+
and shared <code>ensureChartForecastBridge</code> preprocessing.
|
|
123
|
+
</>
|
|
124
|
+
}
|
|
125
|
+
actions={<DocsHeaderActions />}
|
|
126
|
+
/>
|
|
127
|
+
<PageContentSection>
|
|
128
|
+
<div className={S.stack}>
|
|
129
|
+
<div>
|
|
130
|
+
<h3 className={S.sectionTitle}>Historical line</h3>
|
|
131
|
+
<p className={S.caption}>Subset of demo months through May 2023.</p>
|
|
132
|
+
<div className={S.demo}>
|
|
133
|
+
<LightweightForecastChart
|
|
134
|
+
chartData={historicalOnlyData}
|
|
135
|
+
forecastData={[]}
|
|
136
|
+
isDarkTheme={isDarkMode}
|
|
137
|
+
height={320}
|
|
138
|
+
showLegend={false}
|
|
139
|
+
formatNumber={v =>
|
|
140
|
+
Number.isFinite(v) ? v.toFixed(2) : String(v)
|
|
141
|
+
}
|
|
142
|
+
/>
|
|
143
|
+
</div>
|
|
144
|
+
</div>
|
|
145
|
+
|
|
146
|
+
<div>
|
|
147
|
+
<h3 className={S.sectionTitle}>
|
|
148
|
+
Historical + dashed forecast + bridge helper
|
|
149
|
+
</h3>
|
|
150
|
+
<p className={S.caption}>
|
|
151
|
+
Rows use raw forecast anchors; the chart applies{' '}
|
|
152
|
+
<code>ensureChartForecastBridge</code> internally (same defaults
|
|
153
|
+
as <code>ChartAreaInteractive</code>).
|
|
154
|
+
</p>
|
|
155
|
+
<div className={S.demo}>
|
|
156
|
+
<LightweightForecastChart
|
|
157
|
+
chartData={DEMO_BASE}
|
|
158
|
+
forecastData={DEMO_FORECAST_ITEMS}
|
|
159
|
+
isDarkTheme={isDarkMode}
|
|
160
|
+
height={320}
|
|
161
|
+
forecastLineStyle="dashed"
|
|
162
|
+
formatNumber={v =>
|
|
163
|
+
Number.isFinite(v) ? v.toFixed(2) : String(v)
|
|
164
|
+
}
|
|
165
|
+
/>
|
|
166
|
+
</div>
|
|
167
|
+
</div>
|
|
168
|
+
|
|
169
|
+
<div>
|
|
170
|
+
<h3 className={S.sectionTitle}>
|
|
171
|
+
Forecast lines + stacked quantile bands
|
|
172
|
+
</h3>
|
|
173
|
+
<p className={S.caption}>
|
|
174
|
+
Each band key maps to tuples on forecast months; overlays from{' '}
|
|
175
|
+
<code>ChartAreaInteractive</code> are intentionally omitted here.
|
|
176
|
+
</p>
|
|
177
|
+
<div className={S.demo}>
|
|
178
|
+
<LightweightForecastChart
|
|
179
|
+
chartData={bandsData}
|
|
180
|
+
forecastData={DEMO_FORECAST_ITEMS}
|
|
181
|
+
quantileBands={DEMO_QUANTILE_BANDS}
|
|
182
|
+
isDarkTheme={isDarkMode}
|
|
183
|
+
height={360}
|
|
184
|
+
forecastLineStyle="dashed"
|
|
185
|
+
formatNumber={v =>
|
|
186
|
+
Number.isFinite(v) ? v.toFixed(2) : String(v)
|
|
187
|
+
}
|
|
188
|
+
/>
|
|
189
|
+
</div>
|
|
190
|
+
</div>
|
|
191
|
+
</div>
|
|
192
|
+
</PageContentSection>
|
|
193
|
+
</>
|
|
194
|
+
);
|
|
195
|
+
}
|
package/src/docs/registry.ts
CHANGED
|
@@ -91,6 +91,12 @@ export const DOC_REGISTRY: DocEntry[] = [
|
|
|
91
91
|
section: 'Charts',
|
|
92
92
|
load: () => import('./pages/ChartAreaInteractivePage'),
|
|
93
93
|
},
|
|
94
|
+
{
|
|
95
|
+
slug: 'lightweight-forecast-chart',
|
|
96
|
+
title: 'LightweightForecastChart',
|
|
97
|
+
section: 'Charts',
|
|
98
|
+
load: () => import('./pages/LightweightChartPage'),
|
|
99
|
+
},
|
|
94
100
|
{
|
|
95
101
|
slug: 'chat',
|
|
96
102
|
title: 'Chat',
|