@sybilion/uilib 1.3.55 → 1.3.57
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 +16 -8
- package/dist/esm/components/ui/Chart/components/BaseChartWrapper.styl.js +2 -2
- package/dist/esm/components/ui/ChartAreaInteractive/ChartLines.js +8 -3
- package/dist/esm/components/widgets/DriversComparisonChart/DriversComparisonChart.js +3 -2
- package/dist/esm/components/widgets/DriversComparisonChart/driversComparisonChart.helpers.js +1 -2
- package/dist/esm/types/src/components/ui/Chart/components/BaseChartWrapper.d.ts +6 -1
- package/dist/esm/types/src/components/ui/ChartAreaInteractive/ChartLines.d.ts +2 -1
- package/dist/esm/types/src/components/widgets/DriversComparisonChart/driversComparisonChart.helpers.d.ts +2 -0
- package/package.json +1 -1
- package/src/components/ui/Card/Card.tsx +1 -0
- package/src/components/ui/Chart/components/BaseChartWrapper.styl +5 -0
- package/src/components/ui/Chart/components/BaseChartWrapper.styl.d.ts +1 -0
- package/src/components/ui/Chart/components/BaseChartWrapper.tsx +26 -10
- package/src/components/ui/ChartAreaInteractive/ChartLines.tsx +19 -8
- package/src/components/widgets/DriversComparisonChart/DriversComparisonChart.tsx +5 -3
- package/src/components/widgets/DriversComparisonChart/driversComparisonChart.helpers.ts +3 -2
|
@@ -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 }));
|
|
@@ -5,16 +5,16 @@ import { getForecastColor, ChartLines } from '../../ChartAreaInteractive/ChartLi
|
|
|
5
5
|
import { Skeleton } from '../../Skeleton/Skeleton.js';
|
|
6
6
|
import { chartRenderQueue } from '../../../../utils/chartRenderQueue.js';
|
|
7
7
|
import { Tooltip, LineChart, ComposedChart } from 'recharts';
|
|
8
|
-
import { ChartContainer } from './ChartContainer.js';
|
|
9
|
-
import { ChartTooltipContent } from './ChartTooltipContent.js';
|
|
10
|
-
import { CustomChartLegend } from './CustomChartLegend/CustomChartLegend.js';
|
|
11
|
-
import { QuantileBands } from './QuantileBands.js';
|
|
12
8
|
import { resolveChartMargin, getPlotViewBox } from '../tools/chartPlotGeometry.js';
|
|
13
9
|
import { formatDate } from '../tools/formatters.js';
|
|
14
10
|
import S from './BaseChartWrapper.styl.js';
|
|
15
11
|
import { ChartAxes } from './ChartAxes.js';
|
|
12
|
+
import { ChartContainer } from './ChartContainer.js';
|
|
16
13
|
import { ChartGrid } from './ChartGrid.js';
|
|
14
|
+
import { ChartTooltipContent } from './ChartTooltipContent.js';
|
|
15
|
+
import { CustomChartLegend } from './CustomChartLegend/CustomChartLegend.js';
|
|
17
16
|
import { LegendSvg } from './LegendSvg/LegendSvg.js';
|
|
17
|
+
import { QuantileBands } from './QuantileBands.js';
|
|
18
18
|
|
|
19
19
|
function clampTooltipTranslate(args) {
|
|
20
20
|
const { coordinate, viewBox, tooltipWidth: tw, tooltipHeight: th, offset, edgeMargin, } = args;
|
|
@@ -48,8 +48,9 @@ BaseChartWrapperLoading.displayName = 'BaseChartWrapperLoading';
|
|
|
48
48
|
* Separated to maintain hook order consistency
|
|
49
49
|
*/
|
|
50
50
|
const BaseChartWrapperContent = forwardRef((props, ref) => {
|
|
51
|
-
const { chartConfig = {}, chartData, historicalLineColor, forecastData = [], loading, hasCombinedData, renderId, isDarkTheme, height, className, loadingComponentClassName, footerClassName, chartClassName, xAxisClassName, yAxisClassName, legendClassName, footerActions, quantileBands, quantileBandKey, xMin, xMax, yMin, yMax, autoScaleYAxis = true, formatNumber, formatDate: formatDateFn = formatDate, labelFormatter, onLegendClick, margin, chartType = 'composed', disableAnimation = false, showGrid = true, showAxes = true, showTooltip = true, showLegend = true, showChartAxesLegend = true, xAxisLabel, yAxisLabel, showActiveDots = true, overlayElements, hiddenSeries, excludeLegendIds, onAnalysisSelect, onFailedAnalysisClick, containerProps, error, loadingMessage, noDataMessage = 'No data available', onGridHeightChange, forecastLineStyle = 'dashed', disableHistoricalAnimation = false, onShowAll: _onShowAll, onShowOnly: _onShowOnly, maxVisibleItems, preventDeselection, legendVariant = 'default', legendWidth = 1000, legendMarginLeft = 0, } = props;
|
|
51
|
+
const { chartConfig = {}, chartData, historicalLineColor, forecastData = [], loading, hasCombinedData, renderId, isDarkTheme, height, className, loadingComponentClassName, footerClassName, chartClassName, xAxisClassName, yAxisClassName, legendClassName, footerActions, quantileBands, quantileBandKey, xMin, xMax, yMin, yMax, autoScaleYAxis = true, formatNumber, formatDate: formatDateFn = formatDate, labelFormatter, onLegendClick, margin, chartType = 'composed', disableAnimation = false, disableLineDrawAnimation = false, showGrid = true, showAxes = true, showTooltip = true, showLegend = true, showChartAxesLegend = true, xAxisLabel, yAxisLabel, showActiveDots = true, overlayElements, hiddenSeries, excludeLegendIds, onAnalysisSelect, onFailedAnalysisClick, containerProps, error, loadingMessage, noDataMessage = 'No data available', onGridHeightChange, forecastLineStyle = 'dashed', disableHistoricalAnimation = false, onShowAll: _onShowAll, onShowOnly: _onShowOnly, maxVisibleItems, preventDeselection, legendVariant = 'default', legendWidth = 1000, legendMarginLeft = 0, } = props;
|
|
52
52
|
const [shouldAnimate, setShouldAnimate] = useState(false);
|
|
53
|
+
const lineDataInitializedRef = useRef(false);
|
|
53
54
|
const rootRef = useRef(null);
|
|
54
55
|
const setRefs = (node) => {
|
|
55
56
|
rootRef.current = node;
|
|
@@ -238,13 +239,20 @@ const BaseChartWrapperContent = forwardRef((props, ref) => {
|
|
|
238
239
|
}));
|
|
239
240
|
}, [forecastData, excludeLegendIds, showLegend]);
|
|
240
241
|
useEffect(() => {
|
|
241
|
-
if (disableAnimation)
|
|
242
|
+
if (disableAnimation) {
|
|
243
|
+
setShouldAnimate(false);
|
|
242
244
|
return;
|
|
245
|
+
}
|
|
246
|
+
if (disableLineDrawAnimation && !lineDataInitializedRef.current) {
|
|
247
|
+
lineDataInitializedRef.current = true;
|
|
248
|
+
setShouldAnimate(false);
|
|
249
|
+
return;
|
|
250
|
+
}
|
|
243
251
|
// Enable animation briefly when line data changes (not quantile band data)
|
|
244
252
|
setShouldAnimate(true);
|
|
245
253
|
const timer = setTimeout(() => setShouldAnimate(false), 1000);
|
|
246
254
|
return () => clearTimeout(timer);
|
|
247
|
-
}, [lineDataForAnimation, disableAnimation]);
|
|
255
|
+
}, [lineDataForAnimation, disableAnimation, disableLineDrawAnimation]);
|
|
248
256
|
const isLoaded = useMemo(() => !loading && chartData.length > 0, [loading, chartData.length]);
|
|
249
257
|
// const resizePinContainer = useThrottleCallback(() => {
|
|
250
258
|
// const grid = rootRef.current?.querySelector('.recharts-cartesian-grid');
|
|
@@ -307,7 +315,7 @@ const BaseChartWrapperContent = forwardRef((props, ref) => {
|
|
|
307
315
|
}
|
|
308
316
|
const ChartComponent = chartType === 'line' ? LineChart : ComposedChart;
|
|
309
317
|
const defaultLabelFormatter = (v) => formatDateFn(v, true);
|
|
310
|
-
return (jsxs("div", { className: cn(S.root, !showLegend && S.noLegend, !showChartAxesLegend && S.hideChartAxesLegend, 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, 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 &&
|
|
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 &&
|
|
311
319
|
(legendVariant === 'svg' ? (jsx(LegendSvg, { payload: legendPayload.map(p => ({
|
|
312
320
|
value: p.value,
|
|
313
321
|
color: p.color,
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import styleInject from 'style-inject';
|
|
2
2
|
|
|
3
|
-
var css_248z = ".BaseChartWrapper_chartContainer__J6CIc,.BaseChartWrapper_root__hhfho{min-height:100%}.BaseChartWrapper_root__hhfho{position:relative;width:100%}.BaseChartWrapper_gridLayer__cwJXA{pointer-events:none;position:absolute;z-index:1}.BaseChartWrapper_gridLayer__cwJXA .recharts-cartesian-axis{pointer-events:auto;z-index:1}.BaseChartWrapper_chartLayer__HmBaJ{inset:0;pointer-events:auto;position:relative;z-index:3}.BaseChartWrapper_chartLayer__HmBaJ .recharts-surface{background:transparent}.BaseChartWrapper_chartLayer__HmBaJ .recharts-layer{pointer-events:none}.BaseChartWrapper_chartLayer__HmBaJ .recharts-tooltip-wrapper{visibility:visible!important}.BaseChartWrapper_invisibleAxis__TQ-Zk .recharts-cartesian-axis-tick{visibility:hidden}.BaseChartWrapper_legend__gkSy1{position:absolute;top:100%;width:100%}.BaseChartWrapper_noLegend__jqaPa{display:block}.BaseChartWrapper_hideChartAxesLegend__dgVx- .recharts-cartesian-axis-tick-label{display:none!important}.BaseChartWrapper_chartLoadingContainer__AJP-0{align-items:center;display:flex;height:var(--chart-height);justify-content:center;width:100%}.BaseChartWrapper_loadingOverlay__GcOkA{height:100%;inset:0;opacity:.5;pointer-events:none;position:absolute;transition:opacity .1s ease-out;width:100%;z-index:10}.BaseChartWrapper_loadingOverlay__GcOkA [data-slot=skeleton]{border-radius:var(--p-4)!important}.BaseChartWrapper_loaded__Gj15C .BaseChartWrapper_loadingOverlay__GcOkA{opacity:0}.BaseChartWrapper_errorContainer__1bXy1{align-items:center;color:var(--destructive);display:flex;justify-content:center;min-height:250px}.BaseChartWrapper_errorMessage__veSmB{color:var(--destructive)}.BaseChartWrapper_footer__nP5u6{align-items:center;display:flex;gap:var(--p-2);justify-content:space-between}";
|
|
4
|
-
var S = {"root":"BaseChartWrapper_root__hhfho","chartContainer":"BaseChartWrapper_chartContainer__J6CIc","gridLayer":"BaseChartWrapper_gridLayer__cwJXA","chartLayer":"BaseChartWrapper_chartLayer__HmBaJ","invisibleAxis":"BaseChartWrapper_invisibleAxis__TQ-Zk","legend":"BaseChartWrapper_legend__gkSy1","noLegend":"BaseChartWrapper_noLegend__jqaPa","hideChartAxesLegend":"BaseChartWrapper_hideChartAxesLegend__dgVx-","chartLoadingContainer":"BaseChartWrapper_chartLoadingContainer__AJP-0","loadingOverlay":"BaseChartWrapper_loadingOverlay__GcOkA","loaded":"BaseChartWrapper_loaded__Gj15C","errorContainer":"BaseChartWrapper_errorContainer__1bXy1","errorMessage":"BaseChartWrapper_errorMessage__veSmB","footer":"BaseChartWrapper_footer__nP5u6"};
|
|
3
|
+
var css_248z = ".BaseChartWrapper_chartContainer__J6CIc,.BaseChartWrapper_root__hhfho{min-height:100%}.BaseChartWrapper_root__hhfho{position:relative;width:100%}.BaseChartWrapper_gridLayer__cwJXA{pointer-events:none;position:absolute;z-index:1}.BaseChartWrapper_gridLayer__cwJXA .recharts-cartesian-axis{pointer-events:auto;z-index:1}.BaseChartWrapper_chartLayer__HmBaJ{inset:0;pointer-events:auto;position:relative;z-index:3}.BaseChartWrapper_chartLayer__HmBaJ .recharts-surface{background:transparent}.BaseChartWrapper_chartLayer__HmBaJ .recharts-layer{pointer-events:none}.BaseChartWrapper_chartLayer__HmBaJ .recharts-tooltip-wrapper{visibility:visible!important}.BaseChartWrapper_invisibleAxis__TQ-Zk .recharts-cartesian-axis-tick{visibility:hidden}.BaseChartWrapper_legend__gkSy1{position:absolute;top:100%;width:100%}.BaseChartWrapper_noLegend__jqaPa{display:block}.BaseChartWrapper_hideChartAxesLegend__dgVx- .recharts-cartesian-axis-tick-label{display:none!important}.BaseChartWrapper_chartLoadingContainer__AJP-0{align-items:center;display:flex;height:var(--chart-height);justify-content:center;width:100%}.BaseChartWrapper_loadingOverlay__GcOkA{height:100%;inset:0;opacity:.5;pointer-events:none;position:absolute;transition:opacity .1s ease-out;width:100%;z-index:10}.BaseChartWrapper_loadingOverlay__GcOkA [data-slot=skeleton]{border-radius:var(--p-4)!important}.BaseChartWrapper_loaded__Gj15C .BaseChartWrapper_loadingOverlay__GcOkA{opacity:0}.BaseChartWrapper_errorContainer__1bXy1{align-items:center;color:var(--destructive);display:flex;justify-content:center;min-height:250px}.BaseChartWrapper_errorMessage__veSmB{color:var(--destructive)}.BaseChartWrapper_noLineDrawAnimation__cyrzV .recharts-line-curve{stroke-dasharray:inherit!important}.BaseChartWrapper_footer__nP5u6{align-items:center;display:flex;gap:var(--p-2);justify-content:space-between}";
|
|
4
|
+
var S = {"root":"BaseChartWrapper_root__hhfho","chartContainer":"BaseChartWrapper_chartContainer__J6CIc","gridLayer":"BaseChartWrapper_gridLayer__cwJXA","chartLayer":"BaseChartWrapper_chartLayer__HmBaJ","invisibleAxis":"BaseChartWrapper_invisibleAxis__TQ-Zk","legend":"BaseChartWrapper_legend__gkSy1","noLegend":"BaseChartWrapper_noLegend__jqaPa","hideChartAxesLegend":"BaseChartWrapper_hideChartAxesLegend__dgVx-","chartLoadingContainer":"BaseChartWrapper_chartLoadingContainer__AJP-0","loadingOverlay":"BaseChartWrapper_loadingOverlay__GcOkA","loaded":"BaseChartWrapper_loaded__Gj15C","errorContainer":"BaseChartWrapper_errorContainer__1bXy1","errorMessage":"BaseChartWrapper_errorMessage__veSmB","noLineDrawAnimation":"BaseChartWrapper_noLineDrawAnimation__cyrzV","footer":"BaseChartWrapper_footer__nP5u6"};
|
|
5
5
|
styleInject(css_248z);
|
|
6
6
|
|
|
7
7
|
export { S as default };
|
|
@@ -23,7 +23,7 @@ function resolveQuantileBandFillForForecastLine(lineColor, forecastIndex) {
|
|
|
23
23
|
return mapped ?? getForecastQuantileBandColor(forecastIndex);
|
|
24
24
|
}
|
|
25
25
|
// Memoized component for chart lines - only re-renders when data/analyses/hiddenSeries change
|
|
26
|
-
const ChartLines = memo(({ chartData, forecastData, hiddenSeries, isDarkTheme, shouldAnimate, historicalLineColor = isDarkTheme ? '#ffffff' : '#000000', showLegend = true, forecastLineStyle = 'dashed', disableHistoricalAnimation = false, }) => {
|
|
26
|
+
const ChartLines = memo(({ chartData, forecastData, hiddenSeries, isDarkTheme, shouldAnimate, historicalLineColor = isDarkTheme ? '#ffffff' : '#000000', showLegend = true, forecastLineStyle = 'dashed', disableAnimation = false, disableHistoricalAnimation = false, }) => {
|
|
27
27
|
const dotStroke = isDarkTheme ? '#000000' : '#FFFFFF';
|
|
28
28
|
const activeDotHistorical = {
|
|
29
29
|
r: 6,
|
|
@@ -75,10 +75,15 @@ const ChartLines = memo(({ chartData, forecastData, hiddenSeries, isDarkTheme, s
|
|
|
75
75
|
};
|
|
76
76
|
const renderHistoricalDot = (props) => renderDot(props, lastHistoricalIndex, 'historical', historicalLineColor);
|
|
77
77
|
const renderForecastDot = (props, id, color) => renderDot(props, lastForecastIndices[id] ?? -1, `forecast_${id}`, color);
|
|
78
|
-
|
|
78
|
+
const animationActive = Boolean(shouldAnimate) && !disableAnimation;
|
|
79
|
+
const historicalAnimationActive = animationActive && !disableHistoricalAnimation;
|
|
80
|
+
const forecastAnimationActive = animationActive;
|
|
81
|
+
const animationDuration = disableAnimation ? 0 : ANIMATION_DURATION;
|
|
82
|
+
const valueAnimationEnabled = !disableAnimation;
|
|
83
|
+
return (jsxs(Fragment, { children: [jsx(Line, { type: "monotone", dataKey: "historical", stroke: historicalLineColor, strokeWidth: 1, dot: props => renderHistoricalDot(props), activeDot: activeDotHistorical, name: "Historical Data", legendType: showLegend === false ? 'none' : undefined, strokeDasharray: "0", hide: hiddenSeries?.has('historical'), isAnimationActive: historicalAnimationActive, animationDuration: animationDuration, animateNewValues: valueAnimationEnabled, animationBegin: 0, animationEasing: "ease-out" }, "historical"), forecastData?.map((data, index) => {
|
|
79
84
|
const { id, name, color = FORECAST_LINE_COLORS[index % FORECAST_LINE_COLORS.length], } = data;
|
|
80
85
|
const dataKey = `forecast_${id}`;
|
|
81
|
-
return (jsx(Line, { type: "monotone", dataKey: dataKey, stroke: color, strokeWidth: 1, strokeDasharray: forecastLineStyle === 'dashed' ? '4 2' : '0', dot: props => renderForecastDot(props, id, color), activeDot: activeDotForecast(color), name: `${name || `${id}`}`, legendType: showLegend === false ? 'none' : undefined, hide: hiddenSeries?.has(dataKey), isAnimationActive:
|
|
86
|
+
return (jsx(Line, { type: "monotone", dataKey: dataKey, stroke: color, strokeWidth: 1, strokeDasharray: forecastLineStyle === 'dashed' ? '4 2' : '0', dot: props => renderForecastDot(props, id, color), activeDot: activeDotForecast(color), name: `${name || `${id}`}`, legendType: showLegend === false ? 'none' : undefined, hide: hiddenSeries?.has(dataKey), isAnimationActive: forecastAnimationActive, animationDuration: animationDuration, animateNewValues: valueAnimationEnabled, animationBegin: 0, animationEasing: "ease-out", className: data.status === 'pending' ? 'chart-line-blinking' : undefined }, `forecast-${id}`));
|
|
82
87
|
})] }));
|
|
83
88
|
});
|
|
84
89
|
ChartLines.displayName = 'ChartLines';
|
|
@@ -114,7 +114,8 @@ function DriversComparisonChart({ payload, datasetHistorical = [], loading = fal
|
|
|
114
114
|
};
|
|
115
115
|
});
|
|
116
116
|
}, [originalDriversById, sortedDriversWithData, viewTab]);
|
|
117
|
-
const seriesInitKeyResolved =
|
|
117
|
+
const seriesInitKeyResolved = seriesInitKey ??
|
|
118
|
+
`${tableSeriesRows.map(r => r.id).join(',') || 'none'}`;
|
|
118
119
|
const backtestsSeriesInitKeyRef = useRef('');
|
|
119
120
|
useEffect(() => {
|
|
120
121
|
if (tableSeriesRows.length === 0)
|
|
@@ -136,7 +137,7 @@ function DriversComparisonChart({ payload, datasetHistorical = [], loading = fal
|
|
|
136
137
|
}, [seriesInitKeyResolved, tableSeriesRows]);
|
|
137
138
|
const driversComparisonChartData = useMemo(() => buildDriversComparisonChartData(mergedWithHistorical, datasetHistorical, chartForecastData.map(f => f.id)), [chartForecastData, datasetHistorical, mergedWithHistorical]);
|
|
138
139
|
const showEmptyOverlay = (runAnalysisHint || Boolean(statusHint)) && !loading;
|
|
139
|
-
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:
|
|
140
|
+
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
|
|
140
141
|
? 'Run a completed analysis to load the drivers comparison chart for an analysis.'
|
|
141
142
|
: undefined, status: statusHint ?? undefined, statusTone: statusTone }) }) }))] }) }), loading && (jsx("div", { className: S.loadingLayer, "aria-busy": "true", "aria-live": "polite", children: jsx("div", { className: S.loadingMessage, children: jsx(TextShimmer, { as: "span", className: S.loadingText, children: "Loading drivers comparison\u2026" }) }) }))] }), jsx("div", { className: S.seriesSection, children: tableSeriesRows.length === 0 ? (jsx("div", { className: S.seriesEmptyWrap, children: jsx("div", { className: S.chartEmptyBlurb, children: jsx(ChartEmptyState, { variant: "inline", align: "center", status: "No series" }) }) })) : (jsx("div", { className: S.seriesTableWrapper, children: jsx(PageXScroll, { size: "md", fullWidth: true, innerClassName: S.seriesTableContainer, scrollbarClassName: S.seriesScrollbar, children: jsxs(Table, { withBackground: true, withPaddings: true, className: S.seriesTable, children: [jsx(TableHeader, { children: jsxs(TableRow, { children: [jsx(TableHead, { className: S.seriesColSeries, children: "Driver name" }), jsx(TableHead, { children: jsxs(TableCellValue, { children: ["Importance", jsxs(Tooltip, { children: [jsx(TooltipTrigger, { asChild: true, children: jsx("span", { children: jsx(InfoIcon, { size: 16, style: { cursor: 'help' } }) }) }), jsx(TooltipContent, { side: "top", maxWidth: 300, children: "How much this driver contributes to price movements in the forecast model. Higher = stronger influence. Reflects relative weight of each driver, scored from 0% to 100%." })] })] }) }), jsx(TableHead, { children: jsxs(TableCellValue, { children: ["Lag", jsxs(Tooltip, { children: [jsx(TooltipTrigger, { asChild: true, children: jsx("span", { children: jsx(InfoIcon, { size: 16, style: { cursor: 'help' } }) }) }), jsx(TooltipContent, { side: "top", maxWidth: 300, children: "Lag shows how far ahead this driver predicts price movement. A range (e.g. 9\u201312 months) means the predictive signal is strongest somewhere in that window \u2014 not at a single fixed point." })] })] }) })] }) }), jsx(TableBody, { children: tableSeriesRows.map(row => {
|
|
142
143
|
const dataKey = toForecastDataKey(row.id);
|
package/dist/esm/components/widgets/DriversComparisonChart/driversComparisonChart.helpers.js
CHANGED
|
@@ -15,8 +15,7 @@ function resolveDriverLagLabel(driver) {
|
|
|
15
15
|
if (typeof driver.lag === 'string' && driver.lag.trim()) {
|
|
16
16
|
return driver.lag;
|
|
17
17
|
}
|
|
18
|
-
const
|
|
19
|
-
const overallLag = raw.overall_lag ?? raw.overallLag;
|
|
18
|
+
const overallLag = driver.overall_lag ?? driver.overallLag;
|
|
20
19
|
if (typeof overallLag === 'string' && overallLag.trim()) {
|
|
21
20
|
return overallLag;
|
|
22
21
|
}
|
|
@@ -2,9 +2,9 @@ import { ComponentProps, ReactNode } from 'react';
|
|
|
2
2
|
import type { QuantileBandConfig } from '#uilib/components/ui/Chart/chartForecastVisualization.types';
|
|
3
3
|
import { ChartDataPoint } from '#uilib/components/ui/ChartAreaInteractive/ChartAreaInteractive.types';
|
|
4
4
|
import { ForecastItemData } from '#uilib/components/ui/ChartAreaInteractive/ChartLines';
|
|
5
|
+
import { LegendPayload } from 'recharts/types/component/DefaultLegendContent';
|
|
5
6
|
import type { ChartConfig } from '../Chart.types';
|
|
6
7
|
import { ChartContainer } from './ChartContainer';
|
|
7
|
-
import { LegendPayload } from 'recharts/types/component/DefaultLegendContent';
|
|
8
8
|
export interface BaseChartWrapperProps {
|
|
9
9
|
renderId?: string;
|
|
10
10
|
chartConfig?: ChartConfig;
|
|
@@ -43,6 +43,11 @@ export interface BaseChartWrapperProps {
|
|
|
43
43
|
children?: ReactNode;
|
|
44
44
|
chartType?: 'composed' | 'line';
|
|
45
45
|
disableAnimation?: boolean;
|
|
46
|
+
/**
|
|
47
|
+
* Skip left-to-right line path draw; still animate Y on data updates.
|
|
48
|
+
* Independent of `disableAnimation` (full off).
|
|
49
|
+
*/
|
|
50
|
+
disableLineDrawAnimation?: boolean;
|
|
46
51
|
showGrid?: boolean;
|
|
47
52
|
showAxes?: boolean;
|
|
48
53
|
showTooltip?: boolean;
|
|
@@ -34,7 +34,7 @@ export declare const getForecastColor: (index: number) => string;
|
|
|
34
34
|
export declare const getForecastQuantileBandColor: (index: number) => any;
|
|
35
35
|
/** Same tint logic as intervals/threshold overlays: band fill follows forecast line hex when known. */
|
|
36
36
|
export declare function resolveQuantileBandFillForForecastLine(lineColor: string, forecastIndex: number): string;
|
|
37
|
-
export declare const ChartLines: import("react").MemoExoticComponent<({ chartData, forecastData, hiddenSeries, isDarkTheme, shouldAnimate, historicalLineColor, showLegend, forecastLineStyle, disableHistoricalAnimation, }: {
|
|
37
|
+
export declare const ChartLines: import("react").MemoExoticComponent<({ chartData, forecastData, hiddenSeries, isDarkTheme, shouldAnimate, historicalLineColor, showLegend, forecastLineStyle, disableAnimation, disableHistoricalAnimation, }: {
|
|
38
38
|
chartData: ChartDataPoint[];
|
|
39
39
|
forecastData?: ForecastItemData[];
|
|
40
40
|
hiddenSeries?: Set<string>;
|
|
@@ -43,5 +43,6 @@ export declare const ChartLines: import("react").MemoExoticComponent<({ chartDat
|
|
|
43
43
|
historicalLineColor?: string;
|
|
44
44
|
showLegend?: boolean;
|
|
45
45
|
forecastLineStyle?: "dashed" | "solid";
|
|
46
|
+
disableAnimation?: boolean;
|
|
46
47
|
disableHistoricalAnimation?: boolean;
|
|
47
48
|
}) => import("react/jsx-runtime").JSX.Element>;
|
|
@@ -8,6 +8,8 @@ export type DriversComparisonViewTab = 'overlapped' | 'lagged';
|
|
|
8
8
|
export declare function formatSeriesImportance(value: number | null): string;
|
|
9
9
|
export declare function resolveDriverLagLabel(driver: {
|
|
10
10
|
lag?: string | null;
|
|
11
|
+
overall_lag?: string | null;
|
|
12
|
+
overallLag?: string | null;
|
|
11
13
|
}): string | null;
|
|
12
14
|
/** Parse lag label to month count; ranges use the upper bound. */
|
|
13
15
|
export declare function parseLagMonthsFromLabel(lag: string | null | undefined): number | null;
|
package/package.json
CHANGED
|
@@ -18,21 +18,20 @@ import {
|
|
|
18
18
|
} from '#uilib/components/ui/ChartAreaInteractive/ChartLines';
|
|
19
19
|
import { Skeleton } from '#uilib/components/ui/Skeleton';
|
|
20
20
|
import { chartRenderQueue } from '#uilib/utils/chartRenderQueue';
|
|
21
|
-
import {
|
|
22
|
-
|
|
23
|
-
import type { ChartConfig } from '../Chart.types';
|
|
24
|
-
import { ChartContainer } from './ChartContainer';
|
|
25
|
-
import { ChartTooltipContent } from './ChartTooltipContent';
|
|
26
|
-
import { CustomChartLegend } from './CustomChartLegend/CustomChartLegend';
|
|
27
|
-
import { QuantileBands } from './QuantileBands';
|
|
21
|
+
import { Tooltip as ChartTooltip, ComposedChart, LineChart } from 'recharts';
|
|
28
22
|
import { LegendPayload } from 'recharts/types/component/DefaultLegendContent';
|
|
29
23
|
|
|
24
|
+
import type { ChartConfig } from '../Chart.types';
|
|
30
25
|
import { getPlotViewBox, resolveChartMargin } from '../tools/chartPlotGeometry';
|
|
31
26
|
import { formatDate } from '../tools/formatters';
|
|
32
27
|
import S from './BaseChartWrapper.styl';
|
|
33
28
|
import { ChartAxes } from './ChartAxes';
|
|
29
|
+
import { ChartContainer } from './ChartContainer';
|
|
34
30
|
import { ChartGrid } from './ChartGrid';
|
|
31
|
+
import { ChartTooltipContent } from './ChartTooltipContent';
|
|
32
|
+
import { CustomChartLegend } from './CustomChartLegend/CustomChartLegend';
|
|
35
33
|
import { LegendSvg } from './LegendSvg/LegendSvg';
|
|
34
|
+
import { QuantileBands } from './QuantileBands';
|
|
36
35
|
|
|
37
36
|
function clampTooltipTranslate(args: {
|
|
38
37
|
coordinate: { x: number; y: number };
|
|
@@ -107,6 +106,11 @@ export interface BaseChartWrapperProps {
|
|
|
107
106
|
children?: ReactNode;
|
|
108
107
|
chartType?: 'composed' | 'line';
|
|
109
108
|
disableAnimation?: boolean;
|
|
109
|
+
/**
|
|
110
|
+
* Skip left-to-right line path draw; still animate Y on data updates.
|
|
111
|
+
* Independent of `disableAnimation` (full off).
|
|
112
|
+
*/
|
|
113
|
+
disableLineDrawAnimation?: boolean;
|
|
110
114
|
showGrid?: boolean;
|
|
111
115
|
showAxes?: boolean;
|
|
112
116
|
showTooltip?: boolean;
|
|
@@ -210,6 +214,7 @@ const BaseChartWrapperContent = forwardRef<
|
|
|
210
214
|
margin,
|
|
211
215
|
chartType = 'composed',
|
|
212
216
|
disableAnimation = false,
|
|
217
|
+
disableLineDrawAnimation = false,
|
|
213
218
|
showGrid = true,
|
|
214
219
|
showAxes = true,
|
|
215
220
|
showTooltip = true,
|
|
@@ -240,6 +245,7 @@ const BaseChartWrapperContent = forwardRef<
|
|
|
240
245
|
} = props;
|
|
241
246
|
|
|
242
247
|
const [shouldAnimate, setShouldAnimate] = useState(false);
|
|
248
|
+
const lineDataInitializedRef = useRef(false);
|
|
243
249
|
|
|
244
250
|
const rootRef = useRef<HTMLDivElement>(null);
|
|
245
251
|
|
|
@@ -483,12 +489,20 @@ const BaseChartWrapperContent = forwardRef<
|
|
|
483
489
|
}, [forecastData, excludeLegendIds, showLegend]);
|
|
484
490
|
|
|
485
491
|
useEffect(() => {
|
|
486
|
-
if (disableAnimation)
|
|
492
|
+
if (disableAnimation) {
|
|
493
|
+
setShouldAnimate(false);
|
|
494
|
+
return;
|
|
495
|
+
}
|
|
496
|
+
if (disableLineDrawAnimation && !lineDataInitializedRef.current) {
|
|
497
|
+
lineDataInitializedRef.current = true;
|
|
498
|
+
setShouldAnimate(false);
|
|
499
|
+
return;
|
|
500
|
+
}
|
|
487
501
|
// Enable animation briefly when line data changes (not quantile band data)
|
|
488
502
|
setShouldAnimate(true);
|
|
489
503
|
const timer = setTimeout(() => setShouldAnimate(false), 1000);
|
|
490
504
|
return () => clearTimeout(timer);
|
|
491
|
-
}, [lineDataForAnimation, disableAnimation]);
|
|
505
|
+
}, [lineDataForAnimation, disableAnimation, disableLineDrawAnimation]);
|
|
492
506
|
|
|
493
507
|
const isLoaded = useMemo(
|
|
494
508
|
() => !loading && chartData.length > 0,
|
|
@@ -587,6 +601,7 @@ const BaseChartWrapperContent = forwardRef<
|
|
|
587
601
|
S.root,
|
|
588
602
|
!showLegend && S.noLegend,
|
|
589
603
|
!showChartAxesLegend && S.hideChartAxesLegend,
|
|
604
|
+
disableLineDrawAnimation && S.noLineDrawAnimation,
|
|
590
605
|
isLoaded && S.loaded,
|
|
591
606
|
className,
|
|
592
607
|
)}
|
|
@@ -671,9 +686,10 @@ const BaseChartWrapperContent = forwardRef<
|
|
|
671
686
|
hiddenSeries={hiddenSeries}
|
|
672
687
|
isDarkTheme={isDarkTheme}
|
|
673
688
|
shouldAnimate={shouldAnimate}
|
|
689
|
+
disableAnimation={disableAnimation}
|
|
690
|
+
disableHistoricalAnimation={disableHistoricalAnimation}
|
|
674
691
|
showLegend={showLegend}
|
|
675
692
|
forecastLineStyle={forecastLineStyle}
|
|
676
|
-
// disableHistoricalAnimation={disableHistoricalAnimation}
|
|
677
693
|
/>
|
|
678
694
|
|
|
679
695
|
{showTooltip && (
|
|
@@ -73,6 +73,7 @@ export const ChartLines = memo(
|
|
|
73
73
|
historicalLineColor = isDarkTheme ? '#ffffff' : '#000000',
|
|
74
74
|
showLegend = true,
|
|
75
75
|
forecastLineStyle = 'dashed',
|
|
76
|
+
disableAnimation = false,
|
|
76
77
|
disableHistoricalAnimation = false,
|
|
77
78
|
}: {
|
|
78
79
|
chartData: ChartDataPoint[];
|
|
@@ -83,6 +84,7 @@ export const ChartLines = memo(
|
|
|
83
84
|
historicalLineColor?: string;
|
|
84
85
|
showLegend?: boolean;
|
|
85
86
|
forecastLineStyle?: 'dashed' | 'solid';
|
|
87
|
+
disableAnimation?: boolean;
|
|
86
88
|
disableHistoricalAnimation?: boolean;
|
|
87
89
|
}) => {
|
|
88
90
|
const dotStroke = isDarkTheme ? '#000000' : '#FFFFFF';
|
|
@@ -152,6 +154,14 @@ export const ChartLines = memo(
|
|
|
152
154
|
|
|
153
155
|
const renderForecastDot = (props: any, id: number, color: string) =>
|
|
154
156
|
renderDot(props, lastForecastIndices[id] ?? -1, `forecast_${id}`, color);
|
|
157
|
+
|
|
158
|
+
const animationActive = Boolean(shouldAnimate) && !disableAnimation;
|
|
159
|
+
const historicalAnimationActive =
|
|
160
|
+
animationActive && !disableHistoricalAnimation;
|
|
161
|
+
const forecastAnimationActive = animationActive;
|
|
162
|
+
const animationDuration = disableAnimation ? 0 : ANIMATION_DURATION;
|
|
163
|
+
const valueAnimationEnabled = !disableAnimation;
|
|
164
|
+
|
|
155
165
|
return (
|
|
156
166
|
<>
|
|
157
167
|
{/* Historical Data Line */}
|
|
@@ -167,10 +177,9 @@ export const ChartLines = memo(
|
|
|
167
177
|
legendType={showLegend === false ? 'none' : undefined}
|
|
168
178
|
strokeDasharray="0"
|
|
169
179
|
hide={hiddenSeries?.has('historical')}
|
|
170
|
-
isAnimationActive={
|
|
171
|
-
animationDuration={
|
|
172
|
-
|
|
173
|
-
}
|
|
180
|
+
isAnimationActive={historicalAnimationActive}
|
|
181
|
+
animationDuration={animationDuration}
|
|
182
|
+
animateNewValues={valueAnimationEnabled}
|
|
174
183
|
animationBegin={0}
|
|
175
184
|
animationEasing="ease-out"
|
|
176
185
|
/>
|
|
@@ -197,12 +206,14 @@ export const ChartLines = memo(
|
|
|
197
206
|
name={`${name || `${id}`}`}
|
|
198
207
|
legendType={showLegend === false ? 'none' : undefined}
|
|
199
208
|
hide={hiddenSeries?.has(dataKey)}
|
|
200
|
-
isAnimationActive={
|
|
201
|
-
animationDuration={
|
|
209
|
+
isAnimationActive={forecastAnimationActive}
|
|
210
|
+
animationDuration={animationDuration}
|
|
211
|
+
animateNewValues={valueAnimationEnabled}
|
|
202
212
|
animationBegin={0}
|
|
203
213
|
animationEasing="ease-out"
|
|
204
|
-
className=
|
|
205
|
-
|
|
214
|
+
className={
|
|
215
|
+
data.status === 'pending' ? 'chart-line-blinking' : undefined
|
|
216
|
+
}
|
|
206
217
|
/>
|
|
207
218
|
);
|
|
208
219
|
})}
|
|
@@ -192,7 +192,9 @@ export function DriversComparisonChart({
|
|
|
192
192
|
});
|
|
193
193
|
}, [originalDriversById, sortedDriversWithData, viewTab]);
|
|
194
194
|
|
|
195
|
-
const seriesInitKeyResolved =
|
|
195
|
+
const seriesInitKeyResolved =
|
|
196
|
+
seriesInitKey ??
|
|
197
|
+
`${tableSeriesRows.map(r => r.id).join(',') || 'none'}`;
|
|
196
198
|
|
|
197
199
|
const backtestsSeriesInitKeyRef = useRef<string>('');
|
|
198
200
|
|
|
@@ -238,8 +240,8 @@ export function DriversComparisonChart({
|
|
|
238
240
|
)}
|
|
239
241
|
>
|
|
240
242
|
<ChartAreaInteractive
|
|
241
|
-
|
|
242
|
-
|
|
243
|
+
chartRenderId="drivers-comparison"
|
|
244
|
+
disableLineDrawAnimation
|
|
243
245
|
disableHistoricalAnimation
|
|
244
246
|
disableTimeRangeSelector
|
|
245
247
|
enableTimeRangeBrush
|
|
@@ -23,12 +23,13 @@ export function formatSeriesImportance(value: number | null): string {
|
|
|
23
23
|
|
|
24
24
|
export function resolveDriverLagLabel(driver: {
|
|
25
25
|
lag?: string | null;
|
|
26
|
+
overall_lag?: string | null;
|
|
27
|
+
overallLag?: string | null;
|
|
26
28
|
}): string | null {
|
|
27
29
|
if (typeof driver.lag === 'string' && driver.lag.trim()) {
|
|
28
30
|
return driver.lag;
|
|
29
31
|
}
|
|
30
|
-
const
|
|
31
|
-
const overallLag = raw.overall_lag ?? raw.overallLag;
|
|
32
|
+
const overallLag = driver.overall_lag ?? driver.overallLag;
|
|
32
33
|
if (typeof overallLag === 'string' && overallLag.trim()) {
|
|
33
34
|
return overallLag;
|
|
34
35
|
}
|