@sybilion/uilib 1.3.0 → 1.3.1

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 (34) hide show
  1. package/dist/esm/components/ui/Chart/Chart.js +4 -0
  2. package/dist/esm/components/ui/Chart/lightweight/LightweightForecastChart.js +460 -0
  3. package/dist/esm/components/ui/Chart/lightweight/LightweightForecastChart.styl.js +7 -0
  4. package/dist/esm/components/ui/Chart/lightweight/chartTime.js +16 -0
  5. package/dist/esm/components/ui/Chart/lightweight/lightweightForecastChart.helpers.js +114 -0
  6. package/dist/esm/components/ui/Chart/lightweight/quantileBandCustomSeries.js +147 -0
  7. package/dist/esm/components/ui/Chart/quantileBandConeChartData.js +131 -0
  8. package/dist/esm/components/ui/ChartAreaInteractive/overlays/useQuantileBands.js +4 -102
  9. package/dist/esm/components/widgets/DriverCard/DriverPerformanceChart.js +4 -0
  10. package/dist/esm/index.js +1 -0
  11. package/dist/esm/types/src/components/ui/Chart/Chart.d.ts +1 -0
  12. package/dist/esm/types/src/components/ui/Chart/lightweight/LightweightForecastChart.d.ts +26 -0
  13. package/dist/esm/types/src/components/ui/Chart/lightweight/chartTime.d.ts +5 -0
  14. package/dist/esm/types/src/components/ui/Chart/lightweight/lightweightForecastChart.helpers.d.ts +13 -0
  15. package/dist/esm/types/src/components/ui/Chart/lightweight/quantileBandCustomSeries.d.ts +24 -0
  16. package/dist/esm/types/src/components/ui/Chart/quantileBandConeChartData.d.ts +7 -0
  17. package/dist/esm/types/src/docs/pages/LightweightChartPage.d.ts +1 -0
  18. package/package.json +3 -2
  19. package/src/components/ui/Chart/Chart.tsx +4 -0
  20. package/src/components/ui/Chart/lightweight/LightweightForecastChart.styl +25 -0
  21. package/src/components/ui/Chart/lightweight/LightweightForecastChart.styl.d.ts +11 -0
  22. package/src/components/ui/Chart/lightweight/LightweightForecastChart.tsx +721 -0
  23. package/src/components/ui/Chart/lightweight/chartTime.ts +18 -0
  24. package/src/components/ui/Chart/lightweight/lightweightForecastChart.helpers.ts +141 -0
  25. package/src/components/ui/Chart/lightweight/quantileBandCustomSeries.ts +215 -0
  26. package/src/components/ui/Chart/quantileBandConeChartData.ts +171 -0
  27. package/src/components/ui/ChartAreaInteractive/overlays/useQuantileBands.ts +5 -131
  28. package/src/declarations.d.ts +2 -0
  29. package/src/docs/config/webpack.config.js +25 -2
  30. package/src/docs/index.tsx +1 -1
  31. package/src/docs/pages/LightweightChartPage.styl +18 -0
  32. package/src/docs/pages/LightweightChartPage.styl.d.ts +10 -0
  33. package/src/docs/pages/LightweightChartPage.tsx +195 -0
  34. package/src/docs/registry.ts +6 -0
@@ -0,0 +1,147 @@
1
+ import { customSeriesDefaultOptions } from 'lightweight-charts';
2
+
3
+ /** Catmull–Rom segment chain → cubic Béziers (matches curved line feel). */
4
+ function appendCatmullRomBezierChain(ctx, pts, startWithMoveTo) {
5
+ if (pts.length < 2)
6
+ return;
7
+ if (pts.length === 2) {
8
+ if (startWithMoveTo)
9
+ ctx.moveTo(pts[0].x, pts[0].y);
10
+ ctx.lineTo(pts[1].x, pts[1].y);
11
+ return;
12
+ }
13
+ if (startWithMoveTo) {
14
+ ctx.moveTo(pts[0].x, pts[0].y);
15
+ }
16
+ for (let i = 0; i < pts.length - 1; i += 1) {
17
+ const p0 = pts[Math.max(0, i - 1)];
18
+ const p1 = pts[i];
19
+ const p2 = pts[i + 1];
20
+ const p3 = pts[Math.min(pts.length - 1, i + 2)];
21
+ const cp1x = p1.x + (p2.x - p0.x) / 6;
22
+ const cp1y = p1.y + (p2.y - p0.y) / 6;
23
+ const cp2x = p2.x - (p3.x - p1.x) / 6;
24
+ const cp2y = p2.y - (p3.y - p1.y) / 6;
25
+ ctx.bezierCurveTo(cp1x, cp1y, cp2x, cp2y, p2.x, p2.y);
26
+ }
27
+ }
28
+ class QuantileBandPaneRenderer {
29
+ getData;
30
+ getStyle;
31
+ isWhitespaceFn;
32
+ constructor(getData, getStyle, isWhitespaceFn) {
33
+ this.getData = getData;
34
+ this.getStyle = getStyle;
35
+ this.isWhitespaceFn = isWhitespaceFn;
36
+ }
37
+ draw(target, priceConverter) {
38
+ const pane = this.getData();
39
+ if (!pane?.bars?.length) {
40
+ return;
41
+ }
42
+ const style = this.getStyle();
43
+ const segments = [];
44
+ for (const bar of pane.bars) {
45
+ const d = bar.originalData;
46
+ if (this.isWhitespaceFn(d)) {
47
+ continue;
48
+ }
49
+ const p = d;
50
+ const yU = priceConverter(p.upper);
51
+ const yL = priceConverter(p.lower);
52
+ if (yU === null || yL === null) {
53
+ continue;
54
+ }
55
+ segments.push({ x: bar.x, yU, yL });
56
+ }
57
+ if (segments.length < 2) {
58
+ return;
59
+ }
60
+ target.useMediaCoordinateSpace(({ context }) => {
61
+ const opacity = typeof style.strokeOpacity === 'number'
62
+ ? style.strokeOpacity
63
+ : undefined;
64
+ const prevAlpha = context.globalAlpha;
65
+ const upper = segments.map(s => ({ x: s.x, y: s.yU }));
66
+ const lowerRev = segments
67
+ .map(s => ({ x: s.x, y: s.yL }))
68
+ .reverse();
69
+ context.beginPath();
70
+ appendCatmullRomBezierChain(context, upper, true);
71
+ const last = segments[segments.length - 1];
72
+ context.lineTo(last.x, last.yL);
73
+ appendCatmullRomBezierChain(context, lowerRev, false);
74
+ context.closePath();
75
+ context.fillStyle = style.fill;
76
+ context.fill();
77
+ const sw = style.strokeWidth;
78
+ if (sw > 0) {
79
+ if (opacity !== undefined)
80
+ context.globalAlpha = opacity;
81
+ context.lineWidth = sw;
82
+ context.strokeStyle = style.stroke ?? style.fill;
83
+ if (style.strokeDasharray) {
84
+ const parts = style.strokeDasharray
85
+ .split(/[\s,]+/)
86
+ .map(Number)
87
+ .filter(n => Number.isFinite(n));
88
+ if (parts.length)
89
+ context.setLineDash(parts);
90
+ else
91
+ context.setLineDash([]);
92
+ }
93
+ else {
94
+ context.setLineDash([]);
95
+ }
96
+ context.stroke();
97
+ context.setLineDash([]);
98
+ }
99
+ context.globalAlpha = prevAlpha;
100
+ });
101
+ }
102
+ }
103
+ class QuantileBandPaneView {
104
+ _paneData;
105
+ _style;
106
+ constructor(initialStyle) {
107
+ this._style = initialStyle;
108
+ }
109
+ updateStyle(style) {
110
+ this._style = style;
111
+ }
112
+ renderer() {
113
+ return new QuantileBandPaneRenderer(() => this._paneData, () => this._style, (d) => this.isWhitespace(d));
114
+ }
115
+ update(paneData, seriesOptions) {
116
+ this._paneData = paneData;
117
+ const c = seriesOptions.color;
118
+ if (typeof c === 'string') {
119
+ this._style = { ...this._style, fill: c };
120
+ }
121
+ }
122
+ priceValueBuilder(plotRow) {
123
+ if (this.isWhitespace(plotRow)) {
124
+ return [];
125
+ }
126
+ const p = plotRow;
127
+ return [p.lower, p.upper, (p.lower + p.upper) / 2];
128
+ }
129
+ isWhitespace(data) {
130
+ const row = data;
131
+ if (typeof row.lower !== 'number' ||
132
+ typeof row.upper !== 'number' ||
133
+ !Number.isFinite(row.lower) ||
134
+ !Number.isFinite(row.upper)) {
135
+ return true;
136
+ }
137
+ return false;
138
+ }
139
+ defaultOptions() {
140
+ return customSeriesDefaultOptions;
141
+ }
142
+ destroy() {
143
+ this._paneData = undefined;
144
+ }
145
+ }
146
+
147
+ export { QuantileBandPaneView };
@@ -0,0 +1,131 @@
1
+ function isFiniteBandTuple(v) {
2
+ return (Array.isArray(v) &&
3
+ v.length === 2 &&
4
+ typeof v[0] === 'number' &&
5
+ typeof v[1] === 'number' &&
6
+ Number.isFinite(v[0]) &&
7
+ Number.isFinite(v[1]));
8
+ }
9
+ /**
10
+ * Matches ChartAreaInteractive `mode="intervals"`: cone / fan chart hand-off — collapse the
11
+ * interval to a point at the historical→forecast boundary, then expand along forecast anchors.
12
+ * (Same rules as `useQuantileBands`, without requiring `ForecastData` — reads tuples from each row.)
13
+ */
14
+ function applyQuantileBandConeToChartData(chartData, bandKey) {
15
+ if (!chartData.length)
16
+ return chartData;
17
+ const originalBandByDate = new Map();
18
+ for (const p of chartData) {
19
+ const t = p[bandKey];
20
+ if (isFiniteBandTuple(t))
21
+ originalBandByDate.set(p.date, [t[0], t[1]]);
22
+ }
23
+ const forecastDateList = [...originalBandByDate.keys()].sort((a, b) => new Date(a).getTime() - new Date(b).getTime());
24
+ if (forecastDateList.length === 0)
25
+ return chartData;
26
+ const forecastDatesSet = new Set(forecastDateList);
27
+ const dateToQuantileIndex = new Map();
28
+ forecastDateList.forEach((d, i) => dateToQuantileIndex.set(d, i));
29
+ const clonedData = [...chartData];
30
+ const historicalPoints = clonedData.filter(p => p.historical !== undefined &&
31
+ p.historical !== null &&
32
+ typeof p.historical === 'number' &&
33
+ Number.isFinite(p.historical));
34
+ const lastHistoricalPoint = historicalPoints.length > 0
35
+ ? historicalPoints[historicalPoints.length - 1]
36
+ : null;
37
+ const lastHistoricalDate = lastHistoricalPoint?.date;
38
+ const lastHistoricalValue = lastHistoricalPoint?.historical;
39
+ const firstForecastDate = forecastDateList[0];
40
+ const firstForecastDateObj = firstForecastDate
41
+ ? new Date(firstForecastDate)
42
+ : null;
43
+ const lastHistoricalDateObj = lastHistoricalDate
44
+ ? new Date(lastHistoricalDate)
45
+ : null;
46
+ const hasGap = !!lastHistoricalDate &&
47
+ !!firstForecastDate &&
48
+ lastHistoricalValue !== undefined &&
49
+ firstForecastDateObj &&
50
+ lastHistoricalDateObj &&
51
+ firstForecastDateObj.getTime() > lastHistoricalDateObj.getTime();
52
+ const needsBridgePoint = !!lastHistoricalDate &&
53
+ !!firstForecastDate &&
54
+ lastHistoricalValue !== undefined &&
55
+ firstForecastDateObj &&
56
+ lastHistoricalDateObj &&
57
+ firstForecastDateObj.getTime() <= lastHistoricalDateObj.getTime();
58
+ let bridgePoint = null;
59
+ let pointBeforeForecast = null;
60
+ if (needsBridgePoint && historicalPoints.length > 0) {
61
+ bridgePoint =
62
+ firstForecastDateObj &&
63
+ lastHistoricalDateObj &&
64
+ firstForecastDateObj.getTime() === lastHistoricalDateObj.getTime()
65
+ ? lastHistoricalPoint
66
+ : [...historicalPoints]
67
+ .reverse()
68
+ .find(p => firstForecastDateObj &&
69
+ new Date(p.date).getTime() < firstForecastDateObj.getTime()) || lastHistoricalPoint;
70
+ if (firstForecastDateObj) {
71
+ pointBeforeForecast =
72
+ historicalPoints.findLast(p => new Date(p.date).getTime() < firstForecastDateObj.getTime()) ?? null;
73
+ }
74
+ }
75
+ return clonedData.map(point => {
76
+ const newPoint = { ...point };
77
+ if (hasGap &&
78
+ point.date === lastHistoricalDate &&
79
+ lastHistoricalValue !== undefined) {
80
+ newPoint[bandKey] = [lastHistoricalValue, lastHistoricalValue];
81
+ }
82
+ const isBridgePointDate = needsBridgePoint &&
83
+ bridgePoint &&
84
+ bridgePoint.historical !== undefined &&
85
+ point.date === bridgePoint.date;
86
+ const isPointBeforeForecast = needsBridgePoint &&
87
+ pointBeforeForecast &&
88
+ pointBeforeForecast.historical !== undefined &&
89
+ point.date === pointBeforeForecast.date;
90
+ const isAlsoForecastDate = forecastDatesSet.has(point.date);
91
+ if (isPointBeforeForecast && !isAlsoForecastDate) {
92
+ newPoint[bandKey] = [
93
+ pointBeforeForecast.historical,
94
+ pointBeforeForecast.historical,
95
+ ];
96
+ }
97
+ else if (isBridgePointDate && !isAlsoForecastDate) {
98
+ newPoint[bandKey] = [
99
+ bridgePoint.historical,
100
+ bridgePoint.historical,
101
+ ];
102
+ }
103
+ if (forecastDatesSet.has(point.date)) {
104
+ const quantileIndex = dateToQuantileIndex.get(point.date);
105
+ if (quantileIndex !== undefined) {
106
+ const bandValues = originalBandByDate.get(point.date);
107
+ if (bandValues) {
108
+ const isBridgePointDateForecast = needsBridgePoint &&
109
+ bridgePoint &&
110
+ point.date === bridgePoint.date &&
111
+ bridgePoint.historical !== undefined;
112
+ if (isBridgePointDateForecast && quantileIndex === 0) {
113
+ newPoint[bandKey] = [
114
+ bridgePoint.historical,
115
+ bandValues[1],
116
+ ];
117
+ }
118
+ else {
119
+ newPoint[bandKey] = bandValues;
120
+ }
121
+ }
122
+ else {
123
+ delete newPoint[bandKey];
124
+ }
125
+ }
126
+ }
127
+ return newPoint;
128
+ });
129
+ }
130
+
131
+ export { applyQuantileBandConeToChartData };
@@ -1,4 +1,5 @@
1
1
  import { useMemo } from 'react';
2
+ import { applyQuantileBandConeToChartData } from '../../Chart/quantileBandConeChartData.js';
2
3
 
3
4
  /**
4
5
  * Hook to transform chart data and create quantile band configuration
@@ -12,128 +13,29 @@ function useQuantileBands({ color, chartData, selectedForecastId, forecastData,
12
13
  const forecastDataForSelected = forecastData[selectedForecastId];
13
14
  const allQuantilesData = forecastDataForSelected.allQuantiles;
14
15
  const clonedData = [...chartData];
15
- // Get forecast dates to map quantile array indices correctly
16
16
  const forecastDates = forecastDataForSelected.dates || [];
17
17
  const forecastDatesSet = new Set(forecastDates);
18
- // Find the last historical point for connection
19
- const historicalPoints = clonedData.filter(point => point.historical !== undefined);
20
- const lastHistoricalPoint = historicalPoints.length > 0
21
- ? historicalPoints[historicalPoints.length - 1]
22
- : null;
23
- const lastHistoricalDate = lastHistoricalPoint?.date;
24
- const lastHistoricalValue = lastHistoricalPoint?.historical;
25
- // Get first forecast date
26
- const firstForecastDate = forecastDates[0];
27
- const firstForecastDateObj = firstForecastDate
28
- ? new Date(firstForecastDate)
29
- : null;
30
- const lastHistoricalDateObj = lastHistoricalDate
31
- ? new Date(lastHistoricalDate)
32
- : null;
33
- // Check if there's a gap between historical and forecast data (forecast starts after historical)
34
- const hasGap = lastHistoricalDate &&
35
- firstForecastDate &&
36
- lastHistoricalValue !== undefined &&
37
- firstForecastDateObj &&
38
- lastHistoricalDateObj &&
39
- firstForecastDateObj.getTime() > lastHistoricalDateObj.getTime();
40
- // Check if forecast starts before or at last historical point (need bridge point)
41
- const needsBridgePoint = lastHistoricalDate &&
42
- firstForecastDate &&
43
- lastHistoricalValue !== undefined &&
44
- firstForecastDateObj &&
45
- lastHistoricalDateObj &&
46
- firstForecastDateObj.getTime() <= lastHistoricalDateObj.getTime();
47
- // Find bridge point when forecast starts before or at last historical point
48
- let bridgePoint = null;
49
- let pointBeforeForecast = null;
50
- if (needsBridgePoint && historicalPoints.length > 0) {
51
- // Find the last historical point before or at the first forecast date
52
- // If dates are equal, use lastHistoricalPoint; otherwise find the point before
53
- bridgePoint =
54
- firstForecastDateObj &&
55
- lastHistoricalDateObj &&
56
- firstForecastDateObj.getTime() === lastHistoricalDateObj.getTime()
57
- ? lastHistoricalPoint
58
- : [...historicalPoints]
59
- .reverse()
60
- .find(p => firstForecastDateObj &&
61
- new Date(p.date).getTime() < firstForecastDateObj.getTime()) || lastHistoricalPoint;
62
- // Find the actual point BEFORE the first forecast date for connection
63
- if (firstForecastDateObj) {
64
- pointBeforeForecast = [...historicalPoints].findLast(p => new Date(p.date).getTime() < firstForecastDateObj.getTime());
65
- }
66
- }
67
- // Create a map from date to quantile array index
68
18
  const dateToQuantileIndex = new Map();
69
19
  forecastDates.forEach((date, index) => {
70
20
  dateToQuantileIndex.set(date, index);
71
21
  });
72
- const result = clonedData.map(point => {
22
+ const withRawBands = clonedData.map(point => {
73
23
  const newPoint = { ...point };
74
- // If there's a gap and this is the last historical point, add band data for connection
75
- if (hasGap &&
76
- point.date === lastHistoricalDate &&
77
- lastHistoricalValue !== undefined) {
78
- // Set zero-width band at the last historical value for visual connection
79
- newPoint[bandKey] = [lastHistoricalValue, lastHistoricalValue];
80
- }
81
- // If forecast starts before or at last historical point, add bridge point connection
82
- // Set connection band at the point BEFORE the first forecast date (if exists)
83
- const isBridgePointDate = needsBridgePoint &&
84
- bridgePoint &&
85
- bridgePoint.historical !== undefined &&
86
- point.date === bridgePoint.date;
87
- const isPointBeforeForecast = needsBridgePoint &&
88
- pointBeforeForecast &&
89
- pointBeforeForecast.historical !== undefined &&
90
- point.date === pointBeforeForecast.date;
91
- const isAlsoForecastDate = forecastDatesSet.has(point.date);
92
- // Set zero-width connection band at the point BEFORE forecast starts
93
- if (isPointBeforeForecast && !isAlsoForecastDate) {
94
- newPoint[bandKey] = [
95
- pointBeforeForecast.historical,
96
- pointBeforeForecast.historical,
97
- ];
98
- }
99
- else if (isBridgePointDate && !isAlsoForecastDate) {
100
- // Fallback: if no point before forecast, use bridge point
101
- newPoint[bandKey] = [
102
- bridgePoint.historical,
103
- bridgePoint.historical,
104
- ];
105
- }
106
- // Only update band data for forecast dates
107
24
  if (forecastDatesSet.has(point.date)) {
108
25
  const quantileIndex = dateToQuantileIndex.get(point.date);
109
26
  if (quantileIndex !== undefined) {
110
27
  const bandValues = getBandValues(point.date, quantileIndex, allQuantilesData);
111
28
  if (bandValues) {
112
- // If this is also the bridge point (forecast starts at same date as last historical),
113
- // start the band from the historical value for smooth connection
114
- const isBridgePointDate = needsBridgePoint &&
115
- bridgePoint &&
116
- point.date === bridgePoint.date &&
117
- bridgePoint.historical !== undefined;
118
- if (isBridgePointDate && quantileIndex === 0) {
119
- // Start from historical value, expand to forecast upper bound
120
- newPoint[bandKey] = [bridgePoint.historical, bandValues[1]];
121
- }
122
- else {
123
- newPoint[bandKey] = bandValues;
124
- }
29
+ newPoint[bandKey] = bandValues;
125
30
  }
126
31
  else {
127
- // Remove band data if values don't exist
128
32
  delete newPoint[bandKey];
129
33
  }
130
34
  }
131
35
  }
132
- // For non-forecast dates, preserve existing band data if it exists
133
- // This ensures continuity of the band visualization
134
36
  return newPoint;
135
37
  });
136
- return result;
38
+ return applyQuantileBandConeToChartData(withRawBands, bandKey);
137
39
  }, [chartData, selectedForecastId, forecastData, bandKey, getBandValues]);
138
40
  const quantileBands = useMemo(() => {
139
41
  if (!selectedForecastId || !forecastData[selectedForecastId]) {
@@ -17,6 +17,10 @@ import '../../ui/TextShimmer/TextShimmer.js';
17
17
  import '@phosphor-icons/react';
18
18
  import '../../ui/AnalysesSelector/AnalysesSelector.styl.js';
19
19
  import '../../ui/Chart/components/CustomChartLegend/CustomChartLegend.styl.js';
20
+ import '../../ui/ChartAreaInteractive/ChartLines.js';
21
+ import '../../ui/Skeleton/Skeleton.styl.js';
22
+ import 'lightweight-charts';
23
+ import '../../ui/Chart/lightweight/LightweightForecastChart.styl.js';
20
24
  import { ChartEmptyState } from '../../ui/Chart/components/ChartEmptyState/ChartEmptyState.js';
21
25
  import S from './DriverPerformanceChart.styl.js';
22
26
  import { generateDriverChartData } from './driverPerformanceChartData.js';
package/dist/esm/index.js CHANGED
@@ -103,6 +103,7 @@ export { SignInPage } from './components/widgets/SignInPage/SignInPage.js';
103
103
  export { ChartTooltipItem } from './components/ui/Chart/components/ChartTooltipItem.js';
104
104
  export { ChartLegendItem } from './components/ui/Chart/components/ChartLegendItem.js';
105
105
  export { CustomChartLegend } from './components/ui/Chart/components/CustomChartLegend/CustomChartLegend.js';
106
+ export { LightweightForecastChart } from './components/ui/Chart/lightweight/LightweightForecastChart.js';
106
107
  export { ChartEmptyState } from './components/ui/Chart/components/ChartEmptyState/ChartEmptyState.js';
107
108
  export { ChartContainer, ChartStyle } from './components/ui/Chart/components/ChartContainer.js';
108
109
  export { ChartTooltipContent } from './components/ui/Chart/components/ChartTooltipContent.js';
@@ -10,4 +10,5 @@ export { ChartTooltipItem } from './components/ChartTooltipItem';
10
10
  export { ChartLegendItem } from './components/ChartLegendItem';
11
11
  export { CustomChartLegend } from './components/CustomChartLegend/CustomChartLegend';
12
12
  export { ChartContainer, ChartTooltip, ChartTooltipContent, ChartLegend, ChartLegendContent, ChartStyle, BaseChartWrapper, };
13
+ export { LightweightForecastChart, type LightweightForecastChartProps, } from './lightweight/LightweightForecastChart';
13
14
  export { ChartEmptyState, type ChartEmptyStateProps, type ChartEmptyStatusTone, } from './components/ChartEmptyState/ChartEmptyState';
@@ -0,0 +1,26 @@
1
+ import type { ChartConfig } from '#uilib/components/ui/Chart/Chart.types';
2
+ import type { QuantileBandConfig } from '#uilib/components/ui/Chart/chartForecastVisualization.types';
3
+ import type { ChartDataPoint } from '#uilib/components/ui/ChartAreaInteractive/ChartAreaInteractive.types';
4
+ import { ForecastItemData } from '#uilib/components/ui/ChartAreaInteractive/ChartLines';
5
+ export interface LightweightForecastChartProps {
6
+ chartData: ChartDataPoint[];
7
+ forecastData?: ForecastItemData[];
8
+ quantileBands?: QuantileBandConfig[];
9
+ chartConfig?: ChartConfig;
10
+ historicalLineColor?: string;
11
+ isDarkTheme: boolean;
12
+ height?: number;
13
+ className?: string;
14
+ hiddenSeries?: Set<string>;
15
+ onLegendClick?: (data: unknown, index: number, event: unknown) => void;
16
+ disableForecastHistoricalBridge?: boolean;
17
+ forecastLineStyle?: 'dashed' | 'solid';
18
+ formatDate?: (value: string, detailed?: boolean) => string;
19
+ formatNumber?: (value: number) => string;
20
+ loading?: boolean;
21
+ error?: string | null;
22
+ noDataMessage?: string;
23
+ showLegend?: boolean;
24
+ showTooltip?: boolean;
25
+ }
26
+ export declare function LightweightForecastChart(props: LightweightForecastChartProps): import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1,5 @@
1
+ import type { UTCTimestamp } from 'lightweight-charts';
2
+ /**
3
+ * Parse `YYYY-MM-DD` dates as UTC midnight → Lightweight Charts unix seconds.
4
+ */
5
+ export declare function chartDateToUtcTimestamp(dateStr: string): UTCTimestamp;
@@ -0,0 +1,13 @@
1
+ import type { ChartDataPoint } from '#uilib/components/ui/ChartAreaInteractive/ChartAreaInteractive.types';
2
+ import type { ChartOptions, DeepPartial, LineData, UTCTimestamp } from 'lightweight-charts';
3
+ import type { QuantileBandCustomData } from './quantileBandCustomSeries';
4
+ export declare function buildLightweightChartOptions(args: {
5
+ isDarkTheme: boolean;
6
+ width: number;
7
+ height: number;
8
+ autoSize?: boolean;
9
+ }): DeepPartial<ChartOptions>;
10
+ export declare function buildHistoricalLineData(rows: ChartDataPoint[]): LineData<UTCTimestamp>[];
11
+ export declare function buildForecastLineData(rows: ChartDataPoint[], forecastKey: string): LineData<UTCTimestamp>[];
12
+ export declare function buildQuantileBandCustomData(rows: ChartDataPoint[], bandKey: string): QuantileBandCustomData[];
13
+ export declare function findNearestChartRow(rows: ChartDataPoint[], time: UTCTimestamp): ChartDataPoint | null;
@@ -0,0 +1,24 @@
1
+ import type { CustomData, CustomSeriesOptions, CustomSeriesWhitespaceData, ICustomSeriesPaneRenderer, ICustomSeriesPaneView, PaneRendererCustomData, UTCTimestamp } from 'lightweight-charts';
2
+ export interface QuantileBandCustomData extends CustomData<UTCTimestamp> {
3
+ lower: number;
4
+ upper: number;
5
+ }
6
+ export type QuantileBandStyle = {
7
+ fill: string;
8
+ stroke?: string;
9
+ strokeWidth: number;
10
+ strokeDasharray?: string;
11
+ strokeOpacity?: number;
12
+ };
13
+ export declare class QuantileBandPaneView implements ICustomSeriesPaneView<UTCTimestamp, QuantileBandCustomData, CustomSeriesOptions> {
14
+ private _paneData;
15
+ private _style;
16
+ constructor(initialStyle: QuantileBandStyle);
17
+ updateStyle(style: QuantileBandStyle): void;
18
+ renderer(): ICustomSeriesPaneRenderer;
19
+ update(paneData: PaneRendererCustomData<UTCTimestamp, QuantileBandCustomData>, seriesOptions: CustomSeriesOptions): void;
20
+ priceValueBuilder(plotRow: QuantileBandCustomData): number[];
21
+ isWhitespace(data: QuantileBandCustomData | CustomSeriesWhitespaceData<UTCTimestamp>): data is CustomSeriesWhitespaceData<UTCTimestamp>;
22
+ defaultOptions(): CustomSeriesOptions;
23
+ destroy(): void;
24
+ }
@@ -0,0 +1,7 @@
1
+ import type { ChartDataPoint } from '#uilib/components/ui/ChartAreaInteractive/ChartAreaInteractive.types';
2
+ /**
3
+ * Matches ChartAreaInteractive `mode="intervals"`: cone / fan chart hand-off — collapse the
4
+ * interval to a point at the historical→forecast boundary, then expand along forecast anchors.
5
+ * (Same rules as `useQuantileBands`, without requiring `ForecastData` — reads tuples from each row.)
6
+ */
7
+ export declare function applyQuantileBandConeToChartData(chartData: ChartDataPoint[], bandKey: string): ChartDataPoint[];
@@ -0,0 +1 @@
1
+ export default function LightweightChartPage(): import("react/jsx-runtime").JSX.Element;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sybilion/uilib",
3
- "version": "1.3.0",
3
+ "version": "1.3.1",
4
4
  "description": "Sybilion Design System — React UI components (Webpack + Stylus)",
5
5
  "publishConfig": {
6
6
  "access": "public",
@@ -100,6 +100,7 @@
100
100
  "@radix-ui/react-tooltip": "^1.2.8",
101
101
  "@vimeo/player": "^2.29.3",
102
102
  "classnames": "^2.3.2",
103
+ "lightweight-charts": "^5.0.9",
103
104
  "lucide-react": "^0.546.0",
104
105
  "motion": "^12.23.12",
105
106
  "recharts": "^3.2.1",
@@ -130,7 +131,7 @@
130
131
  },
131
132
  "devDependencies": {
132
133
  "@auth0/auth0-react": "^2.3.1",
133
- "@sybilion/platform-sdk": "file:../sdk",
134
+ "@sybilion/platform-sdk": "file:../platform-sdk",
134
135
  "@babel/core": "^7.20.12",
135
136
  "@babel/preset-typescript": "^7.21.0",
136
137
  "@homecode/ui": "^4.30.6",
@@ -21,6 +21,10 @@ export {
21
21
  ChartStyle,
22
22
  BaseChartWrapper,
23
23
  };
24
+ export {
25
+ LightweightForecastChart,
26
+ type LightweightForecastChartProps,
27
+ } from './lightweight/LightweightForecastChart';
24
28
  export {
25
29
  ChartEmptyState,
26
30
  type ChartEmptyStateProps,
@@ -0,0 +1,25 @@
1
+ .root
2
+ display flex
3
+ flex-direction column
4
+ gap 12px
5
+ width 100%
6
+
7
+ .shell
8
+ position relative
9
+ width 100%
10
+
11
+ .host
12
+ display block
13
+ width 100%
14
+ box-sizing border-box
15
+
16
+ :global(#tv-attr-logo)
17
+ display none
18
+
19
+ .tooltipMove
20
+ position absolute
21
+ z-index 5
22
+ pointer-events none
23
+
24
+ .footer
25
+ width 100%
@@ -0,0 +1,11 @@
1
+ // This file is automatically generated.
2
+ // Please do not change this file!
3
+ interface CssExports {
4
+ 'footer': string;
5
+ 'host': string;
6
+ 'root': string;
7
+ 'shell': string;
8
+ 'tooltipMove': string;
9
+ }
10
+ export const cssExports: CssExports;
11
+ export default cssExports;