drizzle-cube 0.6.7 → 0.7.0
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/adapters/ActivityGridChart.config-DaYILvFZ.cjs +1 -0
- package/dist/adapters/ActivityGridChart.config-_SyTBEyR.js +2 -0
- package/dist/adapters/AreaChart.config-BKh6fY1_.cjs +1 -0
- package/dist/adapters/AreaChart.config-CJoxLP3Y.js +2 -0
- package/dist/adapters/BarChart.config-Bm1bfRbI.cjs +1 -0
- package/dist/adapters/BarChart.config-CGu2Bqrx.js +2 -0
- package/dist/adapters/BoxPlotChart.config-B7M9CQaE.js +2 -0
- package/dist/adapters/BoxPlotChart.config-CXQel8Hk.cjs +1 -0
- package/dist/adapters/BubbleChart.config-BJ36IuVS.cjs +1 -0
- package/dist/adapters/BubbleChart.config-CWoXeLdm.js +2 -0
- package/dist/adapters/CandlestickChart.config-CGMLZVIy.cjs +1 -0
- package/dist/adapters/CandlestickChart.config-CzOcfF1e.js +2 -0
- package/dist/adapters/DataTable.config-CdAzueTs.cjs +1 -0
- package/dist/adapters/DataTable.config-D_KmPyxV.js +2 -0
- package/dist/adapters/FunnelChart.config-D1QAUuYl.cjs +1 -0
- package/dist/adapters/FunnelChart.config-DCRHQoFc.js +2 -0
- package/dist/adapters/GaugeChart.config-DAybRXP3.js +2 -0
- package/dist/adapters/GaugeChart.config-WUPfyLP-.cjs +1 -0
- package/dist/adapters/HeatMapChart.config-CziltPnC.cjs +1 -0
- package/dist/adapters/HeatMapChart.config-DHpXNONW.js +2 -0
- package/dist/adapters/KpiDelta.config-Bw5o0ocY.js +2 -0
- package/dist/adapters/KpiDelta.config-CdAmE1pw.cjs +1 -0
- package/dist/adapters/KpiNumber.config-CQ_wq2gx.js +2 -0
- package/dist/adapters/KpiNumber.config-wxN8x4DC.cjs +1 -0
- package/dist/adapters/KpiText.config-DIRIC97F.cjs +1 -0
- package/dist/adapters/KpiText.config-eODcP6Hn.js +2 -0
- package/dist/adapters/LineChart.config-32-9zA2V.js +2 -0
- package/dist/adapters/LineChart.config-BukeFp6z.cjs +1 -0
- package/dist/adapters/MarkdownChart.config-BVsR5LDy.js +2 -0
- package/dist/adapters/MarkdownChart.config-j4veYjcZ.cjs +1 -0
- package/dist/adapters/MeasureProfileChart.config-BRDYx1hw.js +2 -0
- package/dist/adapters/MeasureProfileChart.config-CIosBxco.cjs +1 -0
- package/dist/adapters/PieChart.config-Bj8KiA0n.cjs +1 -0
- package/dist/adapters/PieChart.config-DgAj3y-I.js +2 -0
- package/dist/adapters/ProportionBarChart.config-BfFOppHY.js +2 -0
- package/dist/adapters/ProportionBarChart.config-c8GGE1aK.cjs +1 -0
- package/dist/adapters/RadarChart.config-D7iHY1v9.js +2 -0
- package/dist/adapters/RadarChart.config-ghlANG2-.cjs +1 -0
- package/dist/adapters/RadialBarChart.config-CsXIz-0v.js +2 -0
- package/dist/adapters/RadialBarChart.config-Dsvu2CPV.cjs +1 -0
- package/dist/adapters/RetentionCombinedChart.config-CdT1kXC7.js +2 -0
- package/dist/adapters/RetentionCombinedChart.config-sDjFKH9K.cjs +1 -0
- package/dist/adapters/RetentionHeatmap.config-CvKBvQmu.cjs +1 -0
- package/dist/adapters/RetentionHeatmap.config-DbM3ICwc.js +2 -0
- package/dist/adapters/SankeyChart.config-BNBcIHaD.js +2 -0
- package/dist/adapters/SankeyChart.config-DZscz3kh.cjs +1 -0
- package/dist/adapters/ScatterChart.config-BNYIddyM.cjs +1 -0
- package/dist/adapters/ScatterChart.config-CEhtoLWc.js +2 -0
- package/dist/adapters/SunburstChart.config-ClseCzji.cjs +1 -0
- package/dist/adapters/SunburstChart.config-nnphNoVO.js +2 -0
- package/dist/adapters/TreeMapChart.config-C8-75muA.js +2 -0
- package/dist/adapters/TreeMapChart.config-DoXeUg9Y.cjs +1 -0
- package/dist/adapters/WaterfallChart.config-BOF1qxY3.cjs +1 -0
- package/dist/adapters/WaterfallChart.config-uW5YyOji.js +2 -0
- package/dist/adapters/{compiler-CGJQlQ6Q.cjs → compiler-DAyodAnH.cjs} +1 -1
- package/dist/adapters/{compiler-mz2Uudwd.js → compiler-Dq04wAjm.js} +1 -1
- package/dist/adapters/core/index.cjs +1 -1
- package/dist/adapters/core/index.js +1 -1
- package/dist/adapters/{core-4WY4M7hp.js → core-BjxfG6Uf.js} +2 -2
- package/dist/adapters/{core-FpI2NfHt.cjs → core-zV2w0hO2.cjs} +1 -1
- package/dist/adapters/express/index.cjs +1 -1
- package/dist/adapters/express/index.js +5 -5
- package/dist/adapters/fastify/index.cjs +1 -1
- package/dist/adapters/fastify/index.js +5 -5
- package/dist/adapters/{handler-g8k2CZG2.cjs → handler-C6Hj6_Yk.cjs} +3 -3
- package/dist/adapters/{handler-PjEeNmXY.js → handler-ox6qxLw5.js} +29 -29
- package/dist/adapters/hono/index.cjs +1 -1
- package/dist/adapters/hono/index.js +4 -4
- package/dist/adapters/mcp-tools.cjs +1 -1
- package/dist/adapters/mcp-tools.js +2 -2
- package/dist/adapters/mcp-transport-C_t_p0wU.cjs +40 -0
- package/dist/adapters/mcp-transport-DFH9e6de.js +603 -0
- package/dist/adapters/nextjs/index.cjs +1 -1
- package/dist/adapters/nextjs/index.js +5 -5
- package/dist/adapters/{openai-DnWF0wCU.cjs → openai-BffpdO4n.cjs} +1 -1
- package/dist/adapters/{openai-DHjN2OwG.js → openai-C9LNhHfK.js} +1 -1
- package/dist/adapters/{openai-CcY2fW3E.js → openai-CmyXJWti.js} +1 -1
- package/dist/adapters/{openai-7xjDI7R2.cjs → openai-DlT-0VRC.cjs} +1 -1
- package/dist/adapters/utils-C5FLHK04.cjs +128 -0
- package/dist/adapters/{utils-G52pPkce.js → utils-tjiwXFnP.js} +18 -0
- package/dist/adapters/utils.cjs +1 -1
- package/dist/adapters/utils.js +1 -1
- package/dist/cjs/client/components/DashboardPortletCard.d.ts +1 -1
- package/dist/cjs/client/components/PortletGroupCard.d.ts +18 -0
- package/dist/cjs/client/components/RowManagedLayout.d.ts +15 -3
- package/dist/cjs/client/components/dashboard/DashboardContext.d.ts +5 -1
- package/dist/cjs/client/components/dashboard/dashboardGridUtils.d.ts +2 -4
- package/dist/cjs/client/components/dashboardPortletCard/EditActionButtons.d.ts +34 -0
- package/dist/cjs/client/components/dashboardPortletCard/PortletCardHeader.d.ts +2 -12
- package/dist/cjs/client/components/dashboardPortletCard/PortletFloatingActions.d.ts +24 -0
- package/dist/cjs/client/components/dashboardPortletCard/cardStyles.d.ts +5 -0
- package/dist/cjs/client/components/dashboardPortletCard/propsEqual.d.ts +2 -0
- package/dist/cjs/client/components/mobileStackedLayout/memberDisplay.d.ts +21 -0
- package/dist/cjs/client/components.d.ts +1 -1
- package/dist/cjs/client/hooks/dashboard/groupGeometry.d.ts +23 -0
- package/dist/cjs/client/hooks/dashboard/groupUtils.d.ts +57 -0
- package/dist/cjs/client/hooks/dashboard/layoutUtils.d.ts +34 -3
- package/dist/cjs/client/hooks/dashboard/useDashboardController.d.ts +11 -3
- package/dist/cjs/client/hooks/dashboard/useRowLayoutEngine.d.ts +12 -2
- package/dist/cjs/client/hooks/useDashboardHook.d.ts +14 -1
- package/dist/cjs/client/index.d.ts +1 -1
- package/dist/cjs/client/stores/dashboardStore.d.ts +11 -1
- package/dist/cjs/client/types.d.ts +30 -1
- package/dist/client/charts.js +12 -12
- package/dist/client/chunks/{DashboardEditModal-BEG0d7wI.js → DashboardEditModal-ByXLlXZD.js} +3289 -2370
- package/dist/client/chunks/DashboardEditModal-ByXLlXZD.js.map +1 -0
- package/dist/client/chunks/{ProportionBarChart-xLX6XD4W.js → ProportionBarChart-lOeVz8ZC.js} +3 -3
- package/dist/client/chunks/{ProportionBarChart-xLX6XD4W.js.map → ProportionBarChart-lOeVz8ZC.js.map} +1 -1
- package/dist/client/chunks/{RetentionCombinedChart-DeFz5wJh.js → RetentionCombinedChart-Mcm7zHFx.js} +3 -3
- package/dist/client/chunks/{RetentionCombinedChart-DeFz5wJh.js.map → RetentionCombinedChart-Mcm7zHFx.js.map} +1 -1
- package/dist/client/chunks/{RetentionHeatmap-DkJOiNwz.js → RetentionHeatmap-4VJx5KDM.js} +2 -2
- package/dist/client/chunks/{RetentionHeatmap-DkJOiNwz.js.map → RetentionHeatmap-4VJx5KDM.js.map} +1 -1
- package/dist/client/chunks/{analysis-builder-BWLL6ztg.js → analysis-builder-CWTJQQtL.js} +9 -9
- package/dist/client/chunks/{analysis-builder-BWLL6ztg.js.map → analysis-builder-CWTJQQtL.js.map} +1 -1
- package/dist/client/chunks/{analysis-builder-shared-Ba3JsRNw.js → analysis-builder-shared-l-4WKVS1.js} +9 -9
- package/dist/client/chunks/{analysis-builder-shared-Ba3JsRNw.js.map → analysis-builder-shared-l-4WKVS1.js.map} +1 -1
- package/dist/client/chunks/{chart-activity-grid-CyVDJSAI.js → chart-activity-grid-DVOhLgxv.js} +20 -2
- package/dist/client/chunks/{chart-activity-grid-CyVDJSAI.js.map → chart-activity-grid-DVOhLgxv.js.map} +1 -1
- package/dist/client/chunks/{chart-area-b5UAS6NZ.js → chart-area-C-5ATxvi.js} +2 -2
- package/dist/client/chunks/{chart-area-b5UAS6NZ.js.map → chart-area-C-5ATxvi.js.map} +1 -1
- package/dist/client/chunks/{chart-bar-aVNonvWw.js → chart-bar-CPiLaXHP.js} +3 -3
- package/dist/client/chunks/{chart-bar-aVNonvWw.js.map → chart-bar-CPiLaXHP.js.map} +1 -1
- package/dist/client/chunks/{chart-box-plot-Bx6M97VL.js → chart-box-plot-Bae5O9xC.js} +2 -2
- package/dist/client/chunks/{chart-box-plot-Bx6M97VL.js.map → chart-box-plot-Bae5O9xC.js.map} +1 -1
- package/dist/client/chunks/{chart-bubble-EWd4_ucG.js → chart-bubble-BFN5WIpf.js} +2 -2
- package/dist/client/chunks/{chart-bubble-EWd4_ucG.js.map → chart-bubble-BFN5WIpf.js.map} +1 -1
- package/dist/client/chunks/{chart-candlestick-CG5EX67b.js → chart-candlestick-PBQKp_z7.js} +2 -2
- package/dist/client/chunks/{chart-candlestick-CG5EX67b.js.map → chart-candlestick-PBQKp_z7.js.map} +1 -1
- package/dist/client/chunks/{chart-data-table-D6Cu86m_.js → chart-data-table-SNSqZj9B.js} +2 -2
- package/dist/client/chunks/{chart-data-table-D6Cu86m_.js.map → chart-data-table-SNSqZj9B.js.map} +1 -1
- package/dist/client/chunks/{chart-funnel-CLRYMbFj.js → chart-funnel-CkHSXp6h.js} +2 -2
- package/dist/client/chunks/{chart-funnel-CLRYMbFj.js.map → chart-funnel-CkHSXp6h.js.map} +1 -1
- package/dist/client/chunks/{chart-gauge-BRqb8eNU.js → chart-gauge-DHIGq3TH.js} +3 -3
- package/dist/client/chunks/{chart-gauge-BRqb8eNU.js.map → chart-gauge-DHIGq3TH.js.map} +1 -1
- package/dist/client/chunks/{chart-heat-map-BCfr3nYJ.js → chart-heat-map-Bb8L1XQr.js} +3 -3
- package/dist/client/chunks/{chart-heat-map-BCfr3nYJ.js.map → chart-heat-map-Bb8L1XQr.js.map} +1 -1
- package/dist/client/chunks/{chart-kpi-delta-ISxzG6g_.js → chart-kpi-delta-BJCOXE-F.js} +3 -3
- package/dist/client/chunks/{chart-kpi-delta-ISxzG6g_.js.map → chart-kpi-delta-BJCOXE-F.js.map} +1 -1
- package/dist/client/chunks/{chart-kpi-number-OmRCPf7D.js → chart-kpi-number-Cdqgv20g.js} +5 -5
- package/dist/client/chunks/{chart-kpi-number-OmRCPf7D.js.map → chart-kpi-number-Cdqgv20g.js.map} +1 -1
- package/dist/client/chunks/{chart-kpi-text-BUm932QA.js → chart-kpi-text-B10ZHCsI.js} +3 -3
- package/dist/client/chunks/{chart-kpi-text-BUm932QA.js.map → chart-kpi-text-B10ZHCsI.js.map} +1 -1
- package/dist/client/chunks/{chart-line-BM_DrpQW.js → chart-line-CzvVL3Be.js} +3 -3
- package/dist/client/chunks/{chart-line-BM_DrpQW.js.map → chart-line-CzvVL3Be.js.map} +1 -1
- package/dist/client/chunks/{chart-markdown-BNrsZpqM.js → chart-markdown-C0xxBW81.js} +2 -2
- package/dist/client/chunks/{chart-markdown-BNrsZpqM.js.map → chart-markdown-C0xxBW81.js.map} +1 -1
- package/dist/client/chunks/{chart-measure-profile-DttUWxIu.js → chart-measure-profile-BSUPFZCR.js} +3 -3
- package/dist/client/chunks/{chart-measure-profile-DttUWxIu.js.map → chart-measure-profile-BSUPFZCR.js.map} +1 -1
- package/dist/client/chunks/{chart-pie-D7pxCMll.js → chart-pie-CwRfwWWZ.js} +3 -3
- package/dist/client/chunks/{chart-pie-D7pxCMll.js.map → chart-pie-CwRfwWWZ.js.map} +1 -1
- package/dist/client/chunks/{chart-radar-sUnyHn3a.js → chart-radar-Dfql2E7i.js} +3 -3
- package/dist/client/chunks/{chart-radar-sUnyHn3a.js.map → chart-radar-Dfql2E7i.js.map} +1 -1
- package/dist/client/chunks/{chart-radial-bar-Df9zbhst.js → chart-radial-bar-oE9OZ1IX.js} +3 -3
- package/dist/client/chunks/{chart-radial-bar-Df9zbhst.js.map → chart-radial-bar-oE9OZ1IX.js.map} +1 -1
- package/dist/client/chunks/{chart-sankey-d_iS5fjB.js → chart-sankey-tK7Oz1bC.js} +2 -2
- package/dist/client/chunks/{chart-sankey-d_iS5fjB.js.map → chart-sankey-tK7Oz1bC.js.map} +1 -1
- package/dist/client/chunks/{chart-scatter-DD5v7r4K.js → chart-scatter-BtLfAsZb.js} +3 -3
- package/dist/client/chunks/{chart-scatter-DD5v7r4K.js.map → chart-scatter-BtLfAsZb.js.map} +1 -1
- package/dist/client/chunks/{chart-sunburst-jL5f9PiB.js → chart-sunburst-B7ryWwQ1.js} +3 -3
- package/dist/client/chunks/{chart-sunburst-jL5f9PiB.js.map → chart-sunburst-B7ryWwQ1.js.map} +1 -1
- package/dist/client/chunks/{chart-tree-map-DK-pNvh4.js → chart-tree-map-DU5M3-qe.js} +3 -3
- package/dist/client/chunks/{chart-tree-map-DK-pNvh4.js.map → chart-tree-map-DU5M3-qe.js.map} +1 -1
- package/dist/client/chunks/{chart-waterfall-CBGewe2Z.js → chart-waterfall-CAwutpzd.js} +3 -3
- package/dist/client/chunks/{chart-waterfall-CBGewe2Z.js.map → chart-waterfall-CAwutpzd.js.map} +1 -1
- package/dist/client/chunks/{charts-core-BHjcM-rm.js → charts-core-BxBB2zBE.js} +2 -2
- package/dist/client/chunks/{charts-core-BHjcM-rm.js.map → charts-core-BxBB2zBE.js.map} +1 -1
- package/dist/client/chunks/charts-loader-0avVzeDd.js +85 -0
- package/dist/client/chunks/{charts-loader-FXj1JiQZ.js.map → charts-loader-0avVzeDd.js.map} +1 -1
- package/dist/client/chunks/{nl-NL-B1MNDJT-.js → nl-NL-BSMhprPr.js} +19 -1
- package/dist/client/chunks/{nl-NL-B1MNDJT-.js.map → nl-NL-BSMhprPr.js.map} +1 -1
- package/dist/client/chunks/{schema-visualization-BXdd320w.js → schema-visualization-CGbnbzj_.js} +3 -3
- package/dist/client/chunks/{schema-visualization-BXdd320w.js.map → schema-visualization-CGbnbzj_.js.map} +1 -1
- package/dist/client/chunks/{useDirtyStateTracking-CFgSTyny.js → useDirtyStateTracking-Dc-Si4db.js} +4 -4
- package/dist/client/chunks/{useDirtyStateTracking-CFgSTyny.js.map → useDirtyStateTracking-Dc-Si4db.js.map} +1 -1
- package/dist/client/chunks/{useExplainAI-CisDjZNp.js → useExplainAI-DJbc7Lu4.js} +4 -4
- package/dist/client/chunks/{useExplainAI-CisDjZNp.js.map → useExplainAI-DJbc7Lu4.js.map} +1 -1
- package/dist/client/chunks/{vendor-D5xADz_A.js → vendor-cTfe9Kho.js} +2 -2
- package/dist/client/chunks/{vendor-D5xADz_A.js.map → vendor-cTfe9Kho.js.map} +1 -1
- package/dist/client/components/DashboardPortletCard.d.ts +1 -1
- package/dist/client/components/PortletGroupCard.d.ts +18 -0
- package/dist/client/components/RowManagedLayout.d.ts +15 -3
- package/dist/client/components/dashboard/DashboardContext.d.ts +5 -1
- package/dist/client/components/dashboard/dashboardGridUtils.d.ts +2 -4
- package/dist/client/components/dashboardPortletCard/EditActionButtons.d.ts +34 -0
- package/dist/client/components/dashboardPortletCard/PortletCardHeader.d.ts +2 -12
- package/dist/client/components/dashboardPortletCard/PortletFloatingActions.d.ts +24 -0
- package/dist/client/components/dashboardPortletCard/cardStyles.d.ts +5 -0
- package/dist/client/components/dashboardPortletCard/propsEqual.d.ts +2 -0
- package/dist/client/components/mobileStackedLayout/memberDisplay.d.ts +21 -0
- package/dist/client/components.d.ts +1 -1
- package/dist/client/components.js +1 -1
- package/dist/client/hooks/dashboard/groupGeometry.d.ts +23 -0
- package/dist/client/hooks/dashboard/groupUtils.d.ts +57 -0
- package/dist/client/hooks/dashboard/layoutUtils.d.ts +34 -3
- package/dist/client/hooks/dashboard/useDashboardController.d.ts +11 -3
- package/dist/client/hooks/dashboard/useRowLayoutEngine.d.ts +12 -2
- package/dist/client/hooks/useDashboardHook.d.ts +14 -1
- package/dist/client/hooks.js +3 -3
- package/dist/client/icons.js +1 -1
- package/dist/client/index.d.ts +1 -1
- package/dist/client/index.js +13 -13
- package/dist/client/providers.js +1 -1
- package/dist/client/schema.js +1 -1
- package/dist/client/stores/dashboardStore.d.ts +11 -1
- package/dist/client/styles.css +1 -1
- package/dist/client/types.d.ts +30 -1
- package/dist/client/utils.js +4 -4
- package/dist/client-bundle-stats.html +1 -1
- package/dist/mcp-app/mcp-app.html +3 -3
- package/dist/server/index.cjs +1 -1
- package/dist/server/index.js +18 -0
- package/package.json +1 -1
- package/dist/adapters/ActivityGridChart.config-CA4DWvoH.js +0 -2
- package/dist/adapters/ActivityGridChart.config-DC2PXV56.cjs +0 -1
- package/dist/adapters/AreaChart.config-BY4hUUJY.js +0 -2
- package/dist/adapters/AreaChart.config-CPwW53vL.cjs +0 -1
- package/dist/adapters/BarChart.config-Be-PGzp5.js +0 -2
- package/dist/adapters/BarChart.config-CfdsJtHY.cjs +0 -1
- package/dist/adapters/BoxPlotChart.config-BYwJZm4S.js +0 -2
- package/dist/adapters/BoxPlotChart.config-BoZN94lR.cjs +0 -1
- package/dist/adapters/BubbleChart.config-DX9aSqMD.js +0 -2
- package/dist/adapters/BubbleChart.config-ijpB_fme.cjs +0 -1
- package/dist/adapters/CandlestickChart.config-C3iEXUN1.js +0 -2
- package/dist/adapters/CandlestickChart.config-DIGeboSZ.cjs +0 -1
- package/dist/adapters/DataTable.config-BjFTY-Md.js +0 -2
- package/dist/adapters/DataTable.config-ClJZRtsp.cjs +0 -1
- package/dist/adapters/FunnelChart.config-DYDd9Y2z.cjs +0 -1
- package/dist/adapters/FunnelChart.config-cVxnY9hD.js +0 -2
- package/dist/adapters/GaugeChart.config-BxWKW2b5.js +0 -2
- package/dist/adapters/GaugeChart.config-DsmevM9y.cjs +0 -1
- package/dist/adapters/HeatMapChart.config-BhO-Wj54.js +0 -2
- package/dist/adapters/HeatMapChart.config-HWcgNRuG.cjs +0 -1
- package/dist/adapters/KpiDelta.config-D3JOnd5n.js +0 -2
- package/dist/adapters/KpiDelta.config-DmLmTPSH.cjs +0 -1
- package/dist/adapters/KpiNumber.config-CVghzBbF.cjs +0 -1
- package/dist/adapters/KpiNumber.config-aK-Ueuhk.js +0 -2
- package/dist/adapters/KpiText.config-DLl8NWit.js +0 -2
- package/dist/adapters/KpiText.config-DbWJ42vd.cjs +0 -1
- package/dist/adapters/LineChart.config-C3i3Tgro.cjs +0 -1
- package/dist/adapters/LineChart.config-El3XPCFT.js +0 -2
- package/dist/adapters/MarkdownChart.config-D39QzWHW.js +0 -2
- package/dist/adapters/MarkdownChart.config-e_9gUs93.cjs +0 -1
- package/dist/adapters/MeasureProfileChart.config-BWQ82qcV.js +0 -2
- package/dist/adapters/MeasureProfileChart.config-l9WITF80.cjs +0 -1
- package/dist/adapters/PieChart.config-CO3Gib0h.js +0 -2
- package/dist/adapters/PieChart.config-yqXTNkix.cjs +0 -1
- package/dist/adapters/ProportionBarChart.config-B7CtjFwg.cjs +0 -1
- package/dist/adapters/ProportionBarChart.config-tGjQou_c.js +0 -2
- package/dist/adapters/RadarChart.config-B2YZnD_e.js +0 -2
- package/dist/adapters/RadarChart.config-DwJmHK8y.cjs +0 -1
- package/dist/adapters/RadialBarChart.config-B9XP-cQ1.cjs +0 -1
- package/dist/adapters/RadialBarChart.config-y2riBDwJ.js +0 -2
- package/dist/adapters/RetentionCombinedChart.config-Fzxwm_WV.cjs +0 -1
- package/dist/adapters/RetentionCombinedChart.config-ohy-zQbu.js +0 -2
- package/dist/adapters/RetentionHeatmap.config-0ykBWgUp.js +0 -2
- package/dist/adapters/RetentionHeatmap.config-DF_4ARr0.cjs +0 -1
- package/dist/adapters/SankeyChart.config-ChdkKqAn.cjs +0 -1
- package/dist/adapters/SankeyChart.config-Cl8kxpKq.js +0 -2
- package/dist/adapters/ScatterChart.config-BIqh_Z8u.cjs +0 -1
- package/dist/adapters/ScatterChart.config-D4v0Z_w4.js +0 -2
- package/dist/adapters/SunburstChart.config-CGMw6hSr.cjs +0 -1
- package/dist/adapters/SunburstChart.config-CmK6DVYQ.js +0 -2
- package/dist/adapters/TreeMapChart.config-B6WC-TVm.js +0 -2
- package/dist/adapters/TreeMapChart.config-q-kBddZC.cjs +0 -1
- package/dist/adapters/WaterfallChart.config-BiOMGjjF.cjs +0 -1
- package/dist/adapters/WaterfallChart.config-C_K8zN09.js +0 -2
- package/dist/adapters/mcp-transport-OBc8jVG5.js +0 -603
- package/dist/adapters/mcp-transport-n1r1Kqzn.cjs +0 -40
- package/dist/adapters/utils-DJ0_72_G.cjs +0 -128
- package/dist/client/chunks/DashboardEditModal-BEG0d7wI.js.map +0 -1
- package/dist/client/chunks/charts-loader-FXj1JiQZ.js +0 -85
package/dist/client/chunks/{chart-heat-map-BCfr3nYJ.js.map → chart-heat-map-Bb8L1XQr.js.map}
RENAMED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"chart-heat-map-BCfr3nYJ.js","names":[],"sources":["../../../src/client/components/charts/HeatMapChart.helpers.ts","../../../src/client/components/charts/HeatMapCanvas.tsx","../../../src/client/components/charts/HeatMapChart.tsx"],"sourcesContent":["/**\n * Co-located helpers for HeatMapChart.\n *\n * Pure colour math, field resolution, data transformation and nivo config\n * builders extracted from the component. No behaviour change.\n */\nimport { formatTimeValue, formatAxisValue } from '../../utils/chartUtils.js'\nimport type { AxisFormatConfig, ChartDisplayConfig } from '../../types.js'\n\n/**\n * Maximum dimensions for heatmap to prevent browser lockup. 50x50 = 2500 cells.\n */\nexport const MAX_HEATMAP_ROWS = 50\nexport const MAX_HEATMAP_COLS = 50\n\n/** Parse color string (hex or rgb) to RGB values. */\nexport function parseColor(color: string): { r: number; g: number; b: number } | null {\n if (color.startsWith('#')) {\n const hex = color.slice(1)\n return {\n r: parseInt(hex.substring(0, 2), 16),\n g: parseInt(hex.substring(2, 4), 16),\n b: parseInt(hex.substring(4, 6), 16),\n }\n }\n\n const rgbMatch = color.match(/rgba?\\((\\d+),\\s*(\\d+),\\s*(\\d+)/)\n if (rgbMatch) {\n return {\n r: parseInt(rgbMatch[1], 10),\n g: parseInt(rgbMatch[2], 10),\n b: parseInt(rgbMatch[3], 10),\n }\n }\n\n return null\n}\n\n/** Relative luminance of a color (0 black .. 1 white) per WCAG. */\nexport function getLuminance(color: string): number {\n const rgb = parseColor(color)\n if (!rgb) return 0.5 // Default to mid-gray if parsing fails\n\n const r = rgb.r / 255\n const g = rgb.g / 255\n const b = rgb.b / 255\n\n const rLinear = r <= 0.03928 ? r / 12.92 : Math.pow((r + 0.055) / 1.055, 2.4)\n const gLinear = g <= 0.03928 ? g / 12.92 : Math.pow((g + 0.055) / 1.055, 2.4)\n const bLinear = b <= 0.03928 ? b / 12.92 : Math.pow((b + 0.055) / 1.055, 2.4)\n\n return 0.2126 * rLinear + 0.7152 * gLinear + 0.0722 * bLinear\n}\n\n/** Contrasting text color (white or dark) based on a background color. */\nexport function getContrastingTextColor(bgColor: string): string {\n const luminance = getLuminance(bgColor)\n return luminance < 0.4 ? '#ffffff' : '#1f2937'\n}\n\ninterface HeatMapDatum {\n x: string\n y: number | null\n}\n\ninterface HeatMapSerie {\n id: string\n data: HeatMapDatum[]\n}\n\nexport interface HeatMapTransformResult {\n data: HeatMapSerie[]\n truncated: boolean\n originalRows: number\n originalCols: number\n}\n\n/**\n * Transform drizzle-cube flat query results to nivo heatmap format.\n *\n * Groups rows by the Y dimension, sorts X chronologically when possible, and\n * truncates to MAX_HEATMAP_ROWS x MAX_HEATMAP_COLS to prevent browser lockup.\n */\nexport function transformToHeatMapFormat(\n data: Record<string, unknown>[],\n xAxisField: string | undefined,\n yAxisField: string | undefined,\n valueField: string | undefined,\n xGranularity?: string,\n yGranularity?: string\n): HeatMapTransformResult {\n if (!xAxisField || !yAxisField || !valueField) {\n return { data: [], truncated: false, originalRows: 0, originalCols: 0 }\n }\n\n const groupedByY = new Map<string, Map<string, number>>()\n const allXValues = new Set<string>()\n const xValueOriginals = new Map<string, unknown>()\n\n for (const row of data) {\n const rawYValue = row[yAxisField]\n const rawXValue = row[xAxisField]\n\n const yValue = formatTimeValue(rawYValue, yGranularity) || String(rawYValue ?? '(empty)')\n const xValue = formatTimeValue(rawXValue, xGranularity) || String(rawXValue ?? '(empty)')\n const value = Number(row[valueField]) || 0\n\n allXValues.add(xValue)\n if (!xValueOriginals.has(xValue)) {\n xValueOriginals.set(xValue, rawXValue)\n }\n\n if (!groupedByY.has(yValue)) {\n groupedByY.set(yValue, new Map())\n }\n groupedByY.get(yValue)!.set(xValue, value)\n }\n\n const xValueArray = Array.from(allXValues).sort((a, b) => {\n const origA = xValueOriginals.get(a)\n const origB = xValueOriginals.get(b)\n if (typeof origA === 'string' && typeof origB === 'string' &&\n origA.match(/^\\d{4}-\\d{2}-\\d{2}/) && origB.match(/^\\d{4}-\\d{2}-\\d{2}/)) {\n return origA.localeCompare(origB)\n }\n return a.localeCompare(b)\n })\n\n const originalRows = groupedByY.size\n const originalCols = xValueArray.length\n const truncated = originalRows > MAX_HEATMAP_ROWS || originalCols > MAX_HEATMAP_COLS\n\n const limitedXValues = xValueArray.slice(0, MAX_HEATMAP_COLS)\n\n const result: HeatMapSerie[] = []\n let rowCount = 0\n for (const [yValue, xMap] of groupedByY) {\n if (rowCount >= MAX_HEATMAP_ROWS) break\n result.push({\n id: yValue,\n data: limitedXValues.map((x) => ({ x, y: xMap.get(x) ?? null })),\n })\n rowCount++\n }\n\n return { data: result, truncated, originalRows, originalCols }\n}\n\n/** Coerce an axis config value (string | array | undefined) to a single field. */\nexport function firstField(value: string | string[] | undefined): string | undefined {\n if (!value) return undefined\n return Array.isArray(value) ? value[0] : value\n}\n\nexport interface HeatMapDisplayOptions {\n showLabels: boolean\n cellShape: 'rect' | 'circle'\n showLegend: boolean\n xAxisFormat?: AxisFormatConfig\n yAxisFormat?: AxisFormatConfig\n valueFormat?: AxisFormatConfig\n}\n\n/** Resolve heatmap display options from the (loosely-typed) display config. */\nexport function resolveHeatMapDisplayOptions(\n displayConfig: ChartDisplayConfig | undefined\n): HeatMapDisplayOptions {\n const cfg = displayConfig as Record<string, unknown> | undefined\n return {\n showLabels: (cfg?.showLabels as boolean) ?? false,\n cellShape: (cfg?.cellShape as 'rect' | 'circle') ?? 'rect',\n showLegend: (cfg?.showLegend as boolean) ?? true,\n xAxisFormat: cfg?.xAxisFormat as AxisFormatConfig | undefined,\n yAxisFormat: cfg?.yAxisFormat as AxisFormatConfig | undefined,\n valueFormat: cfg?.valueFormat as AxisFormatConfig | undefined,\n }\n}\n\n/** Build an axis `format` fn that applies the given axis format config (or none). */\nexport function makeAxisFormatter(\n format: AxisFormatConfig | undefined\n): ((v: string | number) => string) | undefined {\n if (!format) return undefined\n return (v) => {\n const num = parseFloat(String(v))\n return isNaN(num) ? String(v) : formatAxisValue(num, format)\n }\n}\n\n/** Resolve the heatmap value formatter (custom format or nivo default). */\nexport function makeValueFormatter(\n valueFormat: AxisFormatConfig | undefined\n): ((v: number) => string) | string {\n return valueFormat ? (v: number) => formatAxisValue(v, valueFormat) : '>-.2s'\n}\n\n/** Sequential colours config for nivo; falls back to a built-in scheme. */\nexport function resolveHeatMapColors(colors: string[]):\n | { type: 'sequential'; colors: [string, string] }\n | { type: 'sequential'; scheme: 'greens' } {\n return colors.length >= 2\n ? { type: 'sequential', colors: [colors[0], colors[colors.length - 1]] as [string, string] }\n : { type: 'sequential', scheme: 'greens' }\n}\n\n/** Default sequential blue gradient when no palette gradient is supplied. */\nexport const DEFAULT_HEATMAP_COLORS = [\n '#eff3ff', // lightest blue\n '#c6dbef',\n '#9ecae1',\n '#6baed6',\n '#3182bd',\n '#08519c', // darkest blue\n]\n","import { ResponsiveHeatMap } from '@nivo/heatmap'\nimport {\n getContrastingTextColor,\n makeAxisFormatter,\n makeValueFormatter,\n resolveHeatMapColors,\n type HeatMapDisplayOptions\n} from './HeatMapChart.helpers.js'\n\ninterface HeatMapDatum {\n x: string\n y: number | null\n}\ninterface HeatMapSerie {\n id: string\n data: HeatMapDatum[]\n}\n\ninterface HeatMapCanvasProps {\n data: HeatMapSerie[]\n truncated: boolean\n colors: string[]\n options: HeatMapDisplayOptions\n xAxisField: string\n yAxisField: string\n valueField: string\n}\n\nconst NIVO_THEME = {\n text: { fill: 'var(--dc-text)' },\n axis: {\n legend: { text: { fill: 'var(--dc-text)' } },\n ticks: { text: { fill: 'var(--dc-text-secondary)' } },\n },\n legends: {\n text: { fill: 'var(--dc-text-secondary)' },\n title: { text: { fill: 'var(--dc-text)' } },\n },\n tooltip: {\n container: {\n background: 'var(--dc-surface)',\n color: 'var(--dc-text)',\n borderRadius: '4px',\n boxShadow: '0 1px 2px rgba(0, 0, 0, 0.25)',\n },\n },\n}\n\n/**\n * The nivo `<ResponsiveHeatMap>` for HeatMapChart, with all axis/legend/colour\n * config resolved from helpers. Extracted so the chart component stays a thin\n * orchestrator. Behaviour matches the original inline JSX.\n */\nexport function HeatMapCanvas({\n data,\n truncated,\n colors,\n options,\n xAxisField,\n yAxisField,\n valueField,\n}: HeatMapCanvasProps) {\n const { showLabels, cellShape, showLegend, xAxisFormat, yAxisFormat, valueFormat } = options\n\n return (\n <ResponsiveHeatMap\n data={data}\n margin={{ top: truncated ? 40 : 20, right: 20, bottom: 120, left: 120 }}\n valueFormat={makeValueFormatter(valueFormat)}\n axisTop={null}\n axisRight={null}\n axisBottom={{\n tickSize: 5,\n tickPadding: 5,\n tickRotation: -45,\n legend: xAxisFormat?.label || xAxisField?.split('.').pop() || 'X Axis',\n legendPosition: 'middle',\n legendOffset: 70,\n format: makeAxisFormatter(xAxisFormat),\n }}\n axisLeft={{\n tickSize: 5,\n tickPadding: 5,\n tickRotation: 0,\n legend: yAxisFormat?.label || yAxisField?.split('.').pop() || 'Y Axis',\n legendPosition: 'middle',\n legendOffset: -80,\n format: makeAxisFormatter(yAxisFormat),\n }}\n colors={resolveHeatMapColors(colors)}\n emptyColor=\"var(--dc-surface-tertiary)\"\n cellComponent={cellShape === 'circle' ? 'circle' : 'rect'}\n enableLabels={showLabels}\n labelTextColor={({ color }) => getContrastingTextColor(color)}\n legends={\n showLegend\n ? [\n {\n anchor: 'bottom',\n translateX: 0,\n translateY: 95,\n length: 400,\n thickness: 8,\n direction: 'row',\n tickPosition: 'after',\n tickSize: 3,\n tickSpacing: 4,\n tickOverlap: false,\n title: valueField?.split('.').pop() || 'Value',\n titleAlign: 'start',\n titleOffset: 4,\n },\n ]\n : []\n }\n annotations={[]}\n theme={NIVO_THEME}\n />\n )\n}\n","/**\n * HeatMapChart Component\n *\n * Visualizes intensity across two categorical dimensions using a color matrix.\n * Uses @nivo/heatmap for rendering.\n *\n * The chart displays:\n * - Rows: Y-axis dimension values\n * - Columns: X-axis dimension values\n * - Cell color: Intensity based on measure value\n */\n\nimport React, { useMemo } from 'react'\nimport { useTranslation } from '../../hooks/useTranslation.js'\nimport { getFieldGranularity } from '../../utils/chartUtils.js'\nimport { ChartEmptyState } from './ChartStates.js'\nimport { HeatMapCanvas } from './HeatMapCanvas.js'\nimport {\n MAX_HEATMAP_ROWS,\n MAX_HEATMAP_COLS,\n transformToHeatMapFormat,\n firstField,\n resolveHeatMapDisplayOptions,\n DEFAULT_HEATMAP_COLORS,\n} from './HeatMapChart.helpers.js'\nimport type { ChartProps } from '../../types.js'\n\n/**\n * HeatMapChart Component\n *\n * Renders a heatmap visualization from query results.\n * Shows intensity patterns across two categorical dimensions.\n */\nconst HeatMapChart = React.memo(function HeatMapChart({\n data,\n height = '100%',\n chartConfig,\n colorPalette,\n displayConfig,\n queryObject,\n}: ChartProps) {\n const { t } = useTranslation()\n const options = resolveHeatMapDisplayOptions(displayConfig)\n\n // Extract field names from chartConfig (handle both array and string formats)\n const xAxisField = firstField(chartConfig?.xAxis)\n const yAxisField = firstField(chartConfig?.yAxis)\n const valueField = firstField(chartConfig?.valueField)\n\n // Get granularity for time dimensions (only if field is defined)\n const xGranularity = xAxisField ? getFieldGranularity(queryObject, xAxisField) : undefined\n const yGranularity = yAxisField ? getFieldGranularity(queryObject, yAxisField) : undefined\n\n // Transform data to nivo format\n const { data: heatmapData, truncated, originalRows, originalCols } = useMemo(() => {\n if (!data || data.length === 0) {\n return { data: [], truncated: false, originalRows: 0, originalCols: 0 }\n }\n return transformToHeatMapFormat(\n data as Record<string, unknown>[],\n xAxisField,\n yAxisField,\n valueField,\n xGranularity,\n yGranularity\n )\n }, [data, xAxisField, yAxisField, valueField, xGranularity, yGranularity])\n\n // Handle no data or missing config\n if (!data || data.length === 0) {\n return <ChartEmptyState height={height} hint={t('chart.runtime.noDataHint.heatmap')} />\n }\n\n if (!xAxisField || !yAxisField || !valueField) {\n return (\n <ChartEmptyState\n height={height}\n titleKey=\"chart.runtime.heatmapConfigRequired\"\n hint={\n <>\n {!xAxisField && t('chart.runtime.heatmapXRequired')}\n {!yAxisField && t('chart.runtime.heatmapYRequired')}\n {!valueField && t('chart.runtime.heatmapValueRequired')}\n </>\n }\n />\n )\n }\n\n if (heatmapData.length === 0) {\n return (\n <ChartEmptyState\n height={height}\n titleKey=\"chart.runtime.noDataToDisplay\"\n hint={t('chart.runtime.heatmapNoResults')}\n />\n )\n }\n\n // Use gradient colors from palette, or default sequential blue gradient.\n // Sequential single-hue gradients are ideal for heatmaps showing magnitude.\n const colors = colorPalette?.gradient || DEFAULT_HEATMAP_COLORS\n\n return (\n <div className=\"dc:relative dc:w-full dc:h-full\" style={{ height }}>\n {truncated && (\n <div className=\"dc:absolute dc:top-0 dc:left-0 dc:right-0 dc:z-10 dc:px-3 dc:py-1.5 dc:text-xs bg-dc-warning-bg text-dc-warning dc:border-b border-dc-border\">\n {t('chart.runtime.heatmapTruncated', {\n maxRows: MAX_HEATMAP_ROWS,\n maxCols: MAX_HEATMAP_COLS,\n originalRows,\n originalCols\n })}\n </div>\n )}\n <HeatMapCanvas\n data={heatmapData}\n truncated={truncated}\n colors={colors}\n options={options}\n xAxisField={xAxisField}\n yAxisField={yAxisField}\n valueField={valueField}\n />\n </div>\n )\n})\n\nexport default HeatMapChart\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"chart-heat-map-Bb8L1XQr.js","names":[],"sources":["../../../src/client/components/charts/HeatMapChart.helpers.ts","../../../src/client/components/charts/HeatMapCanvas.tsx","../../../src/client/components/charts/HeatMapChart.tsx"],"sourcesContent":["/**\n * Co-located helpers for HeatMapChart.\n *\n * Pure colour math, field resolution, data transformation and nivo config\n * builders extracted from the component. No behaviour change.\n */\nimport { formatTimeValue, formatAxisValue } from '../../utils/chartUtils.js'\nimport type { AxisFormatConfig, ChartDisplayConfig } from '../../types.js'\n\n/**\n * Maximum dimensions for heatmap to prevent browser lockup. 50x50 = 2500 cells.\n */\nexport const MAX_HEATMAP_ROWS = 50\nexport const MAX_HEATMAP_COLS = 50\n\n/** Parse color string (hex or rgb) to RGB values. */\nexport function parseColor(color: string): { r: number; g: number; b: number } | null {\n if (color.startsWith('#')) {\n const hex = color.slice(1)\n return {\n r: parseInt(hex.substring(0, 2), 16),\n g: parseInt(hex.substring(2, 4), 16),\n b: parseInt(hex.substring(4, 6), 16),\n }\n }\n\n const rgbMatch = color.match(/rgba?\\((\\d+),\\s*(\\d+),\\s*(\\d+)/)\n if (rgbMatch) {\n return {\n r: parseInt(rgbMatch[1], 10),\n g: parseInt(rgbMatch[2], 10),\n b: parseInt(rgbMatch[3], 10),\n }\n }\n\n return null\n}\n\n/** Relative luminance of a color (0 black .. 1 white) per WCAG. */\nexport function getLuminance(color: string): number {\n const rgb = parseColor(color)\n if (!rgb) return 0.5 // Default to mid-gray if parsing fails\n\n const r = rgb.r / 255\n const g = rgb.g / 255\n const b = rgb.b / 255\n\n const rLinear = r <= 0.03928 ? r / 12.92 : Math.pow((r + 0.055) / 1.055, 2.4)\n const gLinear = g <= 0.03928 ? g / 12.92 : Math.pow((g + 0.055) / 1.055, 2.4)\n const bLinear = b <= 0.03928 ? b / 12.92 : Math.pow((b + 0.055) / 1.055, 2.4)\n\n return 0.2126 * rLinear + 0.7152 * gLinear + 0.0722 * bLinear\n}\n\n/** Contrasting text color (white or dark) based on a background color. */\nexport function getContrastingTextColor(bgColor: string): string {\n const luminance = getLuminance(bgColor)\n return luminance < 0.4 ? '#ffffff' : '#1f2937'\n}\n\ninterface HeatMapDatum {\n x: string\n y: number | null\n}\n\ninterface HeatMapSerie {\n id: string\n data: HeatMapDatum[]\n}\n\nexport interface HeatMapTransformResult {\n data: HeatMapSerie[]\n truncated: boolean\n originalRows: number\n originalCols: number\n}\n\n/**\n * Transform drizzle-cube flat query results to nivo heatmap format.\n *\n * Groups rows by the Y dimension, sorts X chronologically when possible, and\n * truncates to MAX_HEATMAP_ROWS x MAX_HEATMAP_COLS to prevent browser lockup.\n */\nexport function transformToHeatMapFormat(\n data: Record<string, unknown>[],\n xAxisField: string | undefined,\n yAxisField: string | undefined,\n valueField: string | undefined,\n xGranularity?: string,\n yGranularity?: string\n): HeatMapTransformResult {\n if (!xAxisField || !yAxisField || !valueField) {\n return { data: [], truncated: false, originalRows: 0, originalCols: 0 }\n }\n\n const groupedByY = new Map<string, Map<string, number>>()\n const allXValues = new Set<string>()\n const xValueOriginals = new Map<string, unknown>()\n\n for (const row of data) {\n const rawYValue = row[yAxisField]\n const rawXValue = row[xAxisField]\n\n const yValue = formatTimeValue(rawYValue, yGranularity) || String(rawYValue ?? '(empty)')\n const xValue = formatTimeValue(rawXValue, xGranularity) || String(rawXValue ?? '(empty)')\n const value = Number(row[valueField]) || 0\n\n allXValues.add(xValue)\n if (!xValueOriginals.has(xValue)) {\n xValueOriginals.set(xValue, rawXValue)\n }\n\n if (!groupedByY.has(yValue)) {\n groupedByY.set(yValue, new Map())\n }\n groupedByY.get(yValue)!.set(xValue, value)\n }\n\n const xValueArray = Array.from(allXValues).sort((a, b) => {\n const origA = xValueOriginals.get(a)\n const origB = xValueOriginals.get(b)\n if (typeof origA === 'string' && typeof origB === 'string' &&\n origA.match(/^\\d{4}-\\d{2}-\\d{2}/) && origB.match(/^\\d{4}-\\d{2}-\\d{2}/)) {\n return origA.localeCompare(origB)\n }\n return a.localeCompare(b)\n })\n\n const originalRows = groupedByY.size\n const originalCols = xValueArray.length\n const truncated = originalRows > MAX_HEATMAP_ROWS || originalCols > MAX_HEATMAP_COLS\n\n const limitedXValues = xValueArray.slice(0, MAX_HEATMAP_COLS)\n\n const result: HeatMapSerie[] = []\n let rowCount = 0\n for (const [yValue, xMap] of groupedByY) {\n if (rowCount >= MAX_HEATMAP_ROWS) break\n result.push({\n id: yValue,\n data: limitedXValues.map((x) => ({ x, y: xMap.get(x) ?? null })),\n })\n rowCount++\n }\n\n return { data: result, truncated, originalRows, originalCols }\n}\n\n/** Coerce an axis config value (string | array | undefined) to a single field. */\nexport function firstField(value: string | string[] | undefined): string | undefined {\n if (!value) return undefined\n return Array.isArray(value) ? value[0] : value\n}\n\nexport interface HeatMapDisplayOptions {\n showLabels: boolean\n cellShape: 'rect' | 'circle'\n showLegend: boolean\n xAxisFormat?: AxisFormatConfig\n yAxisFormat?: AxisFormatConfig\n valueFormat?: AxisFormatConfig\n}\n\n/** Resolve heatmap display options from the (loosely-typed) display config. */\nexport function resolveHeatMapDisplayOptions(\n displayConfig: ChartDisplayConfig | undefined\n): HeatMapDisplayOptions {\n const cfg = displayConfig as Record<string, unknown> | undefined\n return {\n showLabels: (cfg?.showLabels as boolean) ?? false,\n cellShape: (cfg?.cellShape as 'rect' | 'circle') ?? 'rect',\n showLegend: (cfg?.showLegend as boolean) ?? true,\n xAxisFormat: cfg?.xAxisFormat as AxisFormatConfig | undefined,\n yAxisFormat: cfg?.yAxisFormat as AxisFormatConfig | undefined,\n valueFormat: cfg?.valueFormat as AxisFormatConfig | undefined,\n }\n}\n\n/** Build an axis `format` fn that applies the given axis format config (or none). */\nexport function makeAxisFormatter(\n format: AxisFormatConfig | undefined\n): ((v: string | number) => string) | undefined {\n if (!format) return undefined\n return (v) => {\n const num = parseFloat(String(v))\n return isNaN(num) ? String(v) : formatAxisValue(num, format)\n }\n}\n\n/** Resolve the heatmap value formatter (custom format or nivo default). */\nexport function makeValueFormatter(\n valueFormat: AxisFormatConfig | undefined\n): ((v: number) => string) | string {\n return valueFormat ? (v: number) => formatAxisValue(v, valueFormat) : '>-.2s'\n}\n\n/** Sequential colours config for nivo; falls back to a built-in scheme. */\nexport function resolveHeatMapColors(colors: string[]):\n | { type: 'sequential'; colors: [string, string] }\n | { type: 'sequential'; scheme: 'greens' } {\n return colors.length >= 2\n ? { type: 'sequential', colors: [colors[0], colors[colors.length - 1]] as [string, string] }\n : { type: 'sequential', scheme: 'greens' }\n}\n\n/** Default sequential blue gradient when no palette gradient is supplied. */\nexport const DEFAULT_HEATMAP_COLORS = [\n '#eff3ff', // lightest blue\n '#c6dbef',\n '#9ecae1',\n '#6baed6',\n '#3182bd',\n '#08519c', // darkest blue\n]\n","import { ResponsiveHeatMap } from '@nivo/heatmap'\nimport {\n getContrastingTextColor,\n makeAxisFormatter,\n makeValueFormatter,\n resolveHeatMapColors,\n type HeatMapDisplayOptions\n} from './HeatMapChart.helpers.js'\n\ninterface HeatMapDatum {\n x: string\n y: number | null\n}\ninterface HeatMapSerie {\n id: string\n data: HeatMapDatum[]\n}\n\ninterface HeatMapCanvasProps {\n data: HeatMapSerie[]\n truncated: boolean\n colors: string[]\n options: HeatMapDisplayOptions\n xAxisField: string\n yAxisField: string\n valueField: string\n}\n\nconst NIVO_THEME = {\n text: { fill: 'var(--dc-text)' },\n axis: {\n legend: { text: { fill: 'var(--dc-text)' } },\n ticks: { text: { fill: 'var(--dc-text-secondary)' } },\n },\n legends: {\n text: { fill: 'var(--dc-text-secondary)' },\n title: { text: { fill: 'var(--dc-text)' } },\n },\n tooltip: {\n container: {\n background: 'var(--dc-surface)',\n color: 'var(--dc-text)',\n borderRadius: '4px',\n boxShadow: '0 1px 2px rgba(0, 0, 0, 0.25)',\n },\n },\n}\n\n/**\n * The nivo `<ResponsiveHeatMap>` for HeatMapChart, with all axis/legend/colour\n * config resolved from helpers. Extracted so the chart component stays a thin\n * orchestrator. Behaviour matches the original inline JSX.\n */\nexport function HeatMapCanvas({\n data,\n truncated,\n colors,\n options,\n xAxisField,\n yAxisField,\n valueField,\n}: HeatMapCanvasProps) {\n const { showLabels, cellShape, showLegend, xAxisFormat, yAxisFormat, valueFormat } = options\n\n return (\n <ResponsiveHeatMap\n data={data}\n margin={{ top: truncated ? 40 : 20, right: 20, bottom: 120, left: 120 }}\n valueFormat={makeValueFormatter(valueFormat)}\n axisTop={null}\n axisRight={null}\n axisBottom={{\n tickSize: 5,\n tickPadding: 5,\n tickRotation: -45,\n legend: xAxisFormat?.label || xAxisField?.split('.').pop() || 'X Axis',\n legendPosition: 'middle',\n legendOffset: 70,\n format: makeAxisFormatter(xAxisFormat),\n }}\n axisLeft={{\n tickSize: 5,\n tickPadding: 5,\n tickRotation: 0,\n legend: yAxisFormat?.label || yAxisField?.split('.').pop() || 'Y Axis',\n legendPosition: 'middle',\n legendOffset: -80,\n format: makeAxisFormatter(yAxisFormat),\n }}\n colors={resolveHeatMapColors(colors)}\n emptyColor=\"var(--dc-surface-tertiary)\"\n cellComponent={cellShape === 'circle' ? 'circle' : 'rect'}\n enableLabels={showLabels}\n labelTextColor={({ color }) => getContrastingTextColor(color)}\n legends={\n showLegend\n ? [\n {\n anchor: 'bottom',\n translateX: 0,\n translateY: 95,\n length: 400,\n thickness: 8,\n direction: 'row',\n tickPosition: 'after',\n tickSize: 3,\n tickSpacing: 4,\n tickOverlap: false,\n title: valueField?.split('.').pop() || 'Value',\n titleAlign: 'start',\n titleOffset: 4,\n },\n ]\n : []\n }\n annotations={[]}\n theme={NIVO_THEME}\n />\n )\n}\n","/**\n * HeatMapChart Component\n *\n * Visualizes intensity across two categorical dimensions using a color matrix.\n * Uses @nivo/heatmap for rendering.\n *\n * The chart displays:\n * - Rows: Y-axis dimension values\n * - Columns: X-axis dimension values\n * - Cell color: Intensity based on measure value\n */\n\nimport React, { useMemo } from 'react'\nimport { useTranslation } from '../../hooks/useTranslation.js'\nimport { getFieldGranularity } from '../../utils/chartUtils.js'\nimport { ChartEmptyState } from './ChartStates.js'\nimport { HeatMapCanvas } from './HeatMapCanvas.js'\nimport {\n MAX_HEATMAP_ROWS,\n MAX_HEATMAP_COLS,\n transformToHeatMapFormat,\n firstField,\n resolveHeatMapDisplayOptions,\n DEFAULT_HEATMAP_COLORS,\n} from './HeatMapChart.helpers.js'\nimport type { ChartProps } from '../../types.js'\n\n/**\n * HeatMapChart Component\n *\n * Renders a heatmap visualization from query results.\n * Shows intensity patterns across two categorical dimensions.\n */\nconst HeatMapChart = React.memo(function HeatMapChart({\n data,\n height = '100%',\n chartConfig,\n colorPalette,\n displayConfig,\n queryObject,\n}: ChartProps) {\n const { t } = useTranslation()\n const options = resolveHeatMapDisplayOptions(displayConfig)\n\n // Extract field names from chartConfig (handle both array and string formats)\n const xAxisField = firstField(chartConfig?.xAxis)\n const yAxisField = firstField(chartConfig?.yAxis)\n const valueField = firstField(chartConfig?.valueField)\n\n // Get granularity for time dimensions (only if field is defined)\n const xGranularity = xAxisField ? getFieldGranularity(queryObject, xAxisField) : undefined\n const yGranularity = yAxisField ? getFieldGranularity(queryObject, yAxisField) : undefined\n\n // Transform data to nivo format\n const { data: heatmapData, truncated, originalRows, originalCols } = useMemo(() => {\n if (!data || data.length === 0) {\n return { data: [], truncated: false, originalRows: 0, originalCols: 0 }\n }\n return transformToHeatMapFormat(\n data as Record<string, unknown>[],\n xAxisField,\n yAxisField,\n valueField,\n xGranularity,\n yGranularity\n )\n }, [data, xAxisField, yAxisField, valueField, xGranularity, yGranularity])\n\n // Handle no data or missing config\n if (!data || data.length === 0) {\n return <ChartEmptyState height={height} hint={t('chart.runtime.noDataHint.heatmap')} />\n }\n\n if (!xAxisField || !yAxisField || !valueField) {\n return (\n <ChartEmptyState\n height={height}\n titleKey=\"chart.runtime.heatmapConfigRequired\"\n hint={\n <>\n {!xAxisField && t('chart.runtime.heatmapXRequired')}\n {!yAxisField && t('chart.runtime.heatmapYRequired')}\n {!valueField && t('chart.runtime.heatmapValueRequired')}\n </>\n }\n />\n )\n }\n\n if (heatmapData.length === 0) {\n return (\n <ChartEmptyState\n height={height}\n titleKey=\"chart.runtime.noDataToDisplay\"\n hint={t('chart.runtime.heatmapNoResults')}\n />\n )\n }\n\n // Use gradient colors from palette, or default sequential blue gradient.\n // Sequential single-hue gradients are ideal for heatmaps showing magnitude.\n const colors = colorPalette?.gradient || DEFAULT_HEATMAP_COLORS\n\n return (\n <div className=\"dc:relative dc:w-full dc:h-full\" style={{ height }}>\n {truncated && (\n <div className=\"dc:absolute dc:top-0 dc:left-0 dc:right-0 dc:z-10 dc:px-3 dc:py-1.5 dc:text-xs bg-dc-warning-bg text-dc-warning dc:border-b border-dc-border\">\n {t('chart.runtime.heatmapTruncated', {\n maxRows: MAX_HEATMAP_ROWS,\n maxCols: MAX_HEATMAP_COLS,\n originalRows,\n originalCols\n })}\n </div>\n )}\n <HeatMapCanvas\n data={heatmapData}\n truncated={truncated}\n colors={colors}\n options={options}\n xAxisField={xAxisField}\n yAxisField={yAxisField}\n valueField={valueField}\n />\n </div>\n )\n})\n\nexport default HeatMapChart\n"],"mappings":";;;;;;AAgBA,SAAgB,EAAW,GAA2D;CACpF,IAAI,EAAM,WAAW,GAAG,GAAG;EACzB,IAAM,IAAM,EAAM,MAAM,CAAC;EACzB,OAAO;GACL,GAAG,SAAS,EAAI,UAAU,GAAG,CAAC,GAAG,EAAE;GACnC,GAAG,SAAS,EAAI,UAAU,GAAG,CAAC,GAAG,EAAE;GACnC,GAAG,SAAS,EAAI,UAAU,GAAG,CAAC,GAAG,EAAE;EACrC;CACF;CAEA,IAAM,IAAW,EAAM,MAAM,gCAAgC;CAS7D,OARI,IACK;EACL,GAAG,SAAS,EAAS,IAAI,EAAE;EAC3B,GAAG,SAAS,EAAS,IAAI,EAAE;EAC3B,GAAG,SAAS,EAAS,IAAI,EAAE;CAC7B,IAGK;AACT;AAGA,SAAgB,EAAa,GAAuB;CAClD,IAAM,IAAM,EAAW,CAAK;CAC5B,IAAI,CAAC,GAAK,OAAO;CAEjB,IAAM,IAAI,EAAI,IAAI,KACZ,IAAI,EAAI,IAAI,KACZ,IAAI,EAAI,IAAI,KAEZ,IAAU,KAAK,SAAU,IAAI,UAAkB,IAAI,QAAS,UAAO,KACnE,IAAU,KAAK,SAAU,IAAI,UAAkB,IAAI,QAAS,UAAO,KACnE,IAAU,KAAK,SAAU,IAAI,UAAkB,IAAI,QAAS,UAAO;CAEzE,OAAO,QAAS,IAAU,QAAS,IAAU,QAAS;AACxD;AAGA,SAAgB,EAAwB,GAAyB;CAE/D,OADkB,EAAa,CACxB,IAAY,KAAM,YAAY;AACvC;AAyBA,SAAgB,EACd,GACA,GACA,GACA,GACA,GACA,GACwB;CACxB,IAAI,CAAC,KAAc,CAAC,KAAc,CAAC,GACjC,OAAO;EAAE,MAAM,CAAC;EAAG,WAAW;EAAO,cAAc;EAAG,cAAc;CAAE;CAGxE,IAAM,oBAAa,IAAI,IAAiC,GAClD,oBAAa,IAAI,IAAY,GAC7B,oBAAkB,IAAI,IAAqB;CAEjD,KAAK,IAAM,KAAO,GAAM;EACtB,IAAM,IAAY,EAAI,IAChB,IAAY,EAAI,IAEhB,IAAS,EAAgB,GAAW,CAAY,KAAK,OAAO,KAAa,SAAS,GAClF,IAAS,EAAgB,GAAW,CAAY,KAAK,OAAO,KAAa,SAAS,GAClF,IAAQ,OAAO,EAAI,EAAW,KAAK;EAUzC,AARA,EAAW,IAAI,CAAM,GAChB,EAAgB,IAAI,CAAM,KAC7B,EAAgB,IAAI,GAAQ,CAAS,GAGlC,EAAW,IAAI,CAAM,KACxB,EAAW,IAAI,mBAAQ,IAAI,IAAI,CAAC,GAElC,EAAW,IAAI,CAAM,CAAC,CAAE,IAAI,GAAQ,CAAK;CAC3C;CAEA,IAAM,IAAc,MAAM,KAAK,CAAU,CAAC,CAAC,MAAM,GAAG,MAAM;EACxD,IAAM,IAAQ,EAAgB,IAAI,CAAC,GAC7B,IAAQ,EAAgB,IAAI,CAAC;EAKnC,OAJI,OAAO,KAAU,YAAY,OAAO,KAAU,YAC9C,EAAM,MAAM,oBAAoB,KAAK,EAAM,MAAM,oBAAoB,IAChE,EAAM,cAAc,CAAK,IAE3B,EAAE,cAAc,CAAC;CAC1B,CAAC,GAEK,IAAe,EAAW,MAC1B,IAAe,EAAY,QAC3B,IAAY,IAAA,MAAmC,IAAA,IAE/C,IAAiB,EAAY,MAAM,GAAA,EAAmB,GAEtD,IAAyB,CAAC,GAC5B,IAAW;CACf,KAAK,IAAM,CAAC,GAAQ,MAAS,GAAY;EACvC,IAAI,KAAA,IAA8B;EAKlC,AAJA,EAAO,KAAK;GACV,IAAI;GACJ,MAAM,EAAe,KAAK,OAAO;IAAE;IAAG,GAAG,EAAK,IAAI,CAAC,KAAK;GAAK,EAAE;EACjE,CAAC,GACD;CACF;CAEA,OAAO;EAAE,MAAM;EAAQ;EAAW;EAAc;CAAa;AAC/D;AAGA,SAAgB,EAAW,GAA0D;CAC9E,OACL,OAAO,MAAM,QAAQ,CAAK,IAAI,EAAM,KAAK;AAC3C;AAYA,SAAgB,EACd,GACuB;CACvB,IAAM,IAAM;CACZ,OAAO;EACL,YAAa,GAAK,cAA0B;EAC5C,WAAY,GAAK,aAAmC;EACpD,YAAa,GAAK,cAA0B;EAC5C,aAAa,GAAK;EAClB,aAAa,GAAK;EAClB,aAAa,GAAK;CACpB;AACF;AAGA,SAAgB,EACd,GAC8C;CACzC,OACL,QAAQ,MAAM;EACZ,IAAM,IAAM,WAAW,OAAO,CAAC,CAAC;EAChC,OAAO,MAAM,CAAG,IAAI,OAAO,CAAC,IAAI,EAAgB,GAAK,CAAM;CAC7D;AACF;AAGA,SAAgB,EACd,GACkC;CAClC,OAAO,KAAe,MAAc,EAAgB,GAAG,CAAW,IAAI;AACxE;AAGA,SAAgB,EAAqB,GAEQ;CAC3C,OAAO,EAAO,UAAU,IACpB;EAAE,MAAM;EAAc,QAAQ,CAAC,EAAO,IAAI,EAAO,EAAO,SAAS,EAAE;CAAsB,IACzF;EAAE,MAAM;EAAc,QAAQ;CAAS;AAC7C;AAGA,IAAa,IAAyB;CACpC;CACA;CACA;CACA;CACA;CACA;AACF,GCzLM,IAAa;CACjB,MAAM,EAAE,MAAM,iBAAiB;CAC/B,MAAM;EACJ,QAAQ,EAAE,MAAM,EAAE,MAAM,iBAAiB,EAAE;EAC3C,OAAO,EAAE,MAAM,EAAE,MAAM,2BAA2B,EAAE;CACtD;CACA,SAAS;EACP,MAAM,EAAE,MAAM,2BAA2B;EACzC,OAAO,EAAE,MAAM,EAAE,MAAM,iBAAiB,EAAE;CAC5C;CACA,SAAS,EACP,WAAW;EACT,YAAY;EACZ,OAAO;EACP,cAAc;EACd,WAAW;CACb,EACF;AACF;AAOA,SAAgB,EAAc,EAC5B,SACA,cACA,WACA,YACA,eACA,eACA,iBACqB;CACrB,IAAM,EAAE,eAAY,cAAW,eAAY,gBAAa,gBAAa,mBAAgB;CAErF,OACE,kBAAC,GAAD;EACQ;EACN,QAAQ;GAAE,KAAK,IAAY,KAAK;GAAI,OAAO;GAAI,QAAQ;GAAK,MAAM;EAAI;EACtE,aAAa,EAAmB,CAAW;EAC3C,SAAS;EACT,WAAW;EACX,YAAY;GACV,UAAU;GACV,aAAa;GACb,cAAc;GACd,QAAQ,GAAa,SAAS,GAAY,MAAM,GAAG,CAAC,CAAC,IAAI,KAAK;GAC9D,gBAAgB;GAChB,cAAc;GACd,QAAQ,EAAkB,CAAW;EACvC;EACA,UAAU;GACR,UAAU;GACV,aAAa;GACb,cAAc;GACd,QAAQ,GAAa,SAAS,GAAY,MAAM,GAAG,CAAC,CAAC,IAAI,KAAK;GAC9D,gBAAgB;GAChB,cAAc;GACd,QAAQ,EAAkB,CAAW;EACvC;EACA,QAAQ,EAAqB,CAAM;EACnC,YAAW;EACX,eAAe,MAAc,WAAW,WAAW;EACnD,cAAc;EACd,iBAAiB,EAAE,eAAY,EAAwB,CAAK;EAC5D,SACE,IACI,CACE;GACE,QAAQ;GACR,YAAY;GACZ,YAAY;GACZ,QAAQ;GACR,WAAW;GACX,WAAW;GACX,cAAc;GACd,UAAU;GACV,aAAa;GACb,aAAa;GACb,OAAO,GAAY,MAAM,GAAG,CAAC,CAAC,IAAI,KAAK;GACvC,YAAY;GACZ,aAAa;EACf,CACF,IACA,CAAC;EAEP,aAAa,CAAC;EACd,OAAO;CACR,CAAA;AAEL;;;iDCtFM,IAAe,EAAM,KAAK,SAAsB,EACpD,SACA,YAAS,QACT,gBACA,iBACA,kBACA,kBACa;CACb,IAAM,EAAE,SAAM,EAAe,GACvB,IAAU,EAA6B,CAAa,GAGpD,IAAa,EAAW,GAAa,KAAK,GAC1C,IAAa,EAAW,GAAa,KAAK,GAC1C,IAAa,EAAW,GAAa,UAAU,GAG/C,IAAe,IAAa,EAAoB,GAAa,CAAU,IAAI,KAAA,GAC3E,IAAe,IAAa,EAAoB,GAAa,CAAU,IAAI,KAAA,GAG3E,EAAE,MAAM,GAAa,cAAW,iBAAc,oBAAiB,QAC/D,CAAC,KAAQ,EAAK,WAAW,IACpB;EAAE,MAAM,CAAC;EAAG,WAAW;EAAO,cAAc;EAAG,cAAc;CAAE,IAEjE,EACL,GACA,GACA,GACA,GACA,GACA,CACF,GACC;EAAC;EAAM;EAAY;EAAY;EAAY;EAAc;CAAY,CAAC;CAGzE,IAAI,CAAC,KAAQ,EAAK,WAAW,GAC3B,OAAO,kBAAC,GAAD;EAAyB;EAAQ,MAAM,EAAE,kCAAkC;CAAI,CAAA;CAGxF,IAAI,CAAC,KAAc,CAAC,KAAc,CAAC,GACjC,OACE,kBAAC,GAAD;EACU;EACR,UAAS;EACT,MACE,kBAAA,GAAA,EAAA,UAAA;GACG,CAAC,KAAc,EAAE,gCAAgC;GACjD,CAAC,KAAc,EAAE,gCAAgC;GACjD,CAAC,KAAc,EAAE,oCAAoC;EACtD,EAAA,CAAA;CAEL,CAAA;CAIL,IAAI,EAAY,WAAW,GACzB,OACE,kBAAC,GAAD;EACU;EACR,UAAS;EACT,MAAM,EAAE,gCAAgC;CACzC,CAAA;CAML,IAAM,IAAS,GAAc,YAAY;CAEzC,OACE,kBAAC,OAAD;EAAK,WAAU;EAAkC,OAAO,EAAE,UAAO;EAAjE,UAAA,CACG,KACC,kBAAC,OAAD;GAAK,WAAU;GACZ,UAAA,EAAE,kCAAkC;IACnC,SAAA;IACA,SAAA;IACA;IACA;GACF,CAAC;EACE,CAAA,GAEP,kBAAC,GAAD;GACE,MAAM;GACK;GACH;GACC;GACG;GACA;GACA;EACb,CAAA,CACE;;AAET,CAAC"}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { n as e } from "./rolldown-runtime-DArdT4gl.js";
|
|
2
|
-
import { D as t, O as n } from "./chart-data-table-
|
|
3
|
-
import { P as r, S as i } from "./chart-activity-grid-
|
|
2
|
+
import { D as t, O as n } from "./chart-data-table-SNSqZj9B.js";
|
|
3
|
+
import { P as r, S as i } from "./chart-activity-grid-DVOhLgxv.js";
|
|
4
4
|
import a, { useEffect as o, useRef as s, useState as c } from "react";
|
|
5
5
|
import { jsx as l, jsxs as u } from "react/jsx-runtime";
|
|
6
6
|
//#region src/client/utils/periodUtils.ts
|
|
@@ -479,4 +479,4 @@ var F = a.memo(function({ data: e, chartConfig: a, displayConfig: o = {}, queryO
|
|
|
479
479
|
//#endregion
|
|
480
480
|
export { f as _, C as a, y as c, x as d, S as f, p as g, d as h, T as i, h as l, m, D as n, v as o, g as p, E as r, _ as s, N as t, b as u };
|
|
481
481
|
|
|
482
|
-
//# sourceMappingURL=chart-kpi-delta-
|
|
482
|
+
//# sourceMappingURL=chart-kpi-delta-BJCOXE-F.js.map
|
package/dist/client/chunks/{chart-kpi-delta-ISxzG6g_.js.map → chart-kpi-delta-BJCOXE-F.js.map}
RENAMED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"chart-kpi-delta-ISxzG6g_.js","names":[],"sources":["../../../src/client/utils/periodUtils.ts","../../../src/client/components/charts/KpiNumber.helpers.ts","../../../src/client/components/charts/useKpiDimensions.ts","../../../src/client/components/charts/KpiStates.tsx","../../../src/client/components/charts/KpiCompactLayout.tsx","../../../src/client/components/charts/KpiDelta.helpers.ts","../../../src/client/components/charts/KpiDelta.tsx"],"sourcesContent":["/**\n * Utility functions for detecting incomplete time periods in KPI charts\n */\n\n/**\n * Get the end date of a period based on granularity\n * @param date - The date within the period\n * @param granularity - The time granularity (day, week, month, quarter, year)\n * @returns The end date of the period (end of day)\n */\nexport function getPeriodEndDate(date: Date, granularity: string): Date {\n const endDate = new Date(date)\n\n switch (granularity.toLowerCase()) {\n case 'day':\n // End of the same day\n endDate.setHours(23, 59, 59, 999)\n break\n\n case 'week': {\n // End of the week (Saturday, assuming week starts Sunday)\n const dayOfWeek = endDate.getDay()\n const daysUntilSaturday = 6 - dayOfWeek\n endDate.setDate(endDate.getDate() + daysUntilSaturday)\n endDate.setHours(23, 59, 59, 999)\n break\n }\n\n case 'month':\n // Last day of the month\n endDate.setMonth(endDate.getMonth() + 1, 0) // Set to day 0 of next month = last day of current month\n endDate.setHours(23, 59, 59, 999)\n break\n\n case 'quarter': {\n // Last day of the quarter\n const currentMonth = endDate.getMonth()\n const quarterEndMonth = Math.floor(currentMonth / 3) * 3 + 2 // 0->2, 3->5, 6->8, 9->11\n endDate.setMonth(quarterEndMonth + 1, 0) // Last day of quarter end month\n endDate.setHours(23, 59, 59, 999)\n break\n }\n\n case 'year':\n // December 31st\n endDate.setMonth(11, 31) // December 31\n endDate.setHours(23, 59, 59, 999)\n break\n\n default:\n // Unknown granularity - treat as complete\n endDate.setHours(23, 59, 59, 999)\n }\n\n return endDate\n}\n\n/**\n * Check if the last period in the data is complete based on granularity\n * @param lastDataPoint - The last data point in the sorted dataset\n * @param timeDimensionField - The field name containing the time value\n * @param granularity - The time granularity\n * @returns true if the period is complete, false if it's incomplete\n */\nexport function isLastPeriodComplete(\n lastDataPoint: any,\n timeDimensionField: string,\n granularity: string\n): boolean {\n if (!lastDataPoint || !timeDimensionField || !granularity) {\n return true // Assume complete if we can't determine\n }\n\n const timeValue = lastDataPoint[timeDimensionField]\n if (!timeValue) {\n return true // Assume complete if no time value\n }\n\n // Parse the time value\n const date = new Date(timeValue)\n if (isNaN(date.getTime())) {\n return true // Assume complete if invalid date\n }\n\n // Get the end of this period\n const periodEnd = getPeriodEndDate(date, granularity)\n\n // Compare with current time\n const now = new Date()\n\n // If the period end is in the future, the period is incomplete\n return periodEnd <= now\n}\n\n/**\n * Extract granularity from a query object\n * @param queryObject - The CubeQuery object\n * @param dimensionField - Optional specific dimension field to match\n * @returns The granularity string or null if not found\n */\nexport function getQueryGranularity(\n queryObject: any,\n dimensionField?: string\n): string | null {\n if (!queryObject?.timeDimensions || queryObject.timeDimensions.length === 0) {\n return null\n }\n\n // If a specific dimension field is provided, try to find its granularity\n if (dimensionField) {\n const matchingDim = queryObject.timeDimensions.find((td: any) =>\n td.dimension === dimensionField ||\n td.dimension?.includes(dimensionField) ||\n dimensionField?.includes(td.dimension)\n )\n if (matchingDim?.granularity) {\n return matchingDim.granularity\n }\n }\n\n // Fallback to first time dimension's granularity\n const firstTimeDim = queryObject.timeDimensions[0]\n return firstTimeDim?.granularity || null\n}\n\n/**\n * Filter data to exclude incomplete or last period\n * @param data - The data array sorted by time\n * @param timeDimensionField - The field containing time values\n * @param queryObject - The query object containing timeDimensions\n * @param useLastCompletePeriod - Whether to check for incomplete periods\n * @param skipLastPeriod - Whether to always skip the last period (overrides useLastCompletePeriod)\n * @returns Object with filtered data and whether filtering was applied\n */\nexport function filterIncompletePeriod(\n data: any[],\n timeDimensionField: string | undefined,\n queryObject: any,\n useLastCompletePeriod: boolean,\n skipLastPeriod: boolean = false\n): { filteredData: any[]; excludedIncompletePeriod: boolean; skippedLastPeriod: boolean; granularity: string | null } {\n // Default return - no filtering\n const noFilter = {\n filteredData: data,\n excludedIncompletePeriod: false,\n skippedLastPeriod: false,\n granularity: null\n }\n\n // Need at least 2 data points to filter\n if (data.length < 2) {\n return noFilter\n }\n\n const granularity = getQueryGranularity(queryObject, timeDimensionField)\n\n // If skipLastPeriod is enabled, always skip the last period\n if (skipLastPeriod) {\n return {\n filteredData: data.slice(0, -1),\n excludedIncompletePeriod: false,\n skippedLastPeriod: true,\n granularity\n }\n }\n\n // Skip incomplete period check if feature is disabled\n if (!useLastCompletePeriod) {\n return { ...noFilter, granularity }\n }\n\n // Skip if no time dimension field\n if (!timeDimensionField) {\n return { ...noFilter, granularity }\n }\n\n // Skip if no time dimensions in query\n if (!queryObject?.timeDimensions || queryObject.timeDimensions.length === 0) {\n return { ...noFilter, granularity }\n }\n\n if (!granularity) {\n return noFilter\n }\n\n // Check if last period is incomplete\n const lastRow = data[data.length - 1]\n if (!isLastPeriodComplete(lastRow, timeDimensionField, granularity)) {\n return {\n filteredData: data.slice(0, -1),\n excludedIncompletePeriod: true,\n skippedLastPeriod: false,\n granularity\n }\n }\n\n return { ...noFilter, granularity }\n}\n","/**\n * Co-located helpers for KpiNumber.\n *\n * Pure data-shaping + formatting extracted from the component to keep the\n * render function lean. No behaviour change — these mirror the original inline\n * logic exactly.\n */\nimport type { ChartAxisConfig, ChartDisplayConfig } from '../../types.js'\n\n/** Extract the value field list from chart config (string | array | undefined). */\nexport function getKpiValueFields(yAxis: ChartAxisConfig['yAxis']): string[] {\n if (!yAxis) return []\n if (typeof yAxis === 'string') return [yAxis]\n if (Array.isArray(yAxis)) return yAxis\n return []\n}\n\n/** Sort rows ascending by the time-dimension field (stable when absent). */\nexport function sortKpiData<T extends Record<string, any>>(\n data: T[] | undefined,\n timeDimensionField: string | undefined\n): T[] {\n if (!data || data.length === 0) return []\n const sorted = [...data]\n if (!timeDimensionField) return sorted\n return sorted.sort((a, b) => {\n const aVal = a[timeDimensionField]\n const bVal = b[timeDimensionField]\n if (aVal < bVal) return -1\n if (aVal > bVal) return 1\n return 0\n })\n}\n\n/**\n * Extract numeric values for the configured field, falling back to the first\n * numeric field on each row when the configured field is absent.\n */\nexport function extractKpiValues(\n dataToUse: Record<string, any>[],\n valueField: string\n): number[] {\n if (!valueField || dataToUse.length === 0) return []\n\n const rawValues = dataToUse.map(row => {\n if (row[valueField] !== undefined) {\n return row[valueField]\n }\n const numericFields = Object.keys(row).filter(key =>\n typeof row[key] === 'number' && !isNaN(row[key])\n )\n if (numericFields.length > 0) {\n return row[numericFields[0]]\n }\n return undefined\n })\n\n return rawValues\n .filter(val => val !== null && val !== undefined && !isNaN(Number(val)))\n .map(val => Number(val))\n}\n\n/** Compute avg/min/max over a numeric series (0s when empty). */\nexport function computeKpiStats(values: number[]): { avg: number; min: number; max: number } {\n if (values.length === 0) return { avg: 0, min: 0, max: 0 }\n const sum = values.reduce((acc, val) => acc + val, 0)\n return {\n avg: sum / values.length,\n min: Math.min(...values),\n max: Math.max(...values)\n }\n}\n\n/**\n * Format a numeric KPI value with thousands/millions/billions suffixes,\n * honouring a custom `formatValue`, `decimals` and `prefix` from displayConfig.\n */\nexport function formatKpiNumber(\n value: number | null | undefined,\n displayConfig: ChartDisplayConfig\n): string {\n if (displayConfig.formatValue) {\n return displayConfig.formatValue(value)\n }\n\n if (value === null || value === undefined) {\n return '—'\n }\n\n const decimals = displayConfig.decimals ?? 0\n const prefix = displayConfig.prefix ?? ''\n\n let formattedValue: string\n\n if (Math.abs(value) >= 1e9) {\n formattedValue = (value / 1e9).toFixed(decimals) + 'B'\n } else if (Math.abs(value) >= 1e6) {\n formattedValue = (value / 1e6).toFixed(decimals) + 'M'\n } else if (Math.abs(value) >= 1e3) {\n formattedValue = (value / 1e3).toFixed(decimals) + 'K'\n } else {\n formattedValue = value.toFixed(decimals)\n }\n\n return prefix + formattedValue\n}\n\n/**\n * Resolve the label to display for a KPI field: prefer the resolved field label,\n * but fall back to the raw field name when the label looks empty/too short.\n */\nexport function resolveDisplayLabel(label: string | undefined, fieldName: string): string {\n return label && label.length > 1 ? label : fieldName\n}\n\n/** Resolve the main value colour from the palette / displayConfig overrides. */\nexport function resolveValueColor(\n valueColorIndex: number | undefined,\n colors: string[] | undefined\n): string {\n if (valueColorIndex !== undefined && colors) {\n if (valueColorIndex >= 0 && valueColorIndex < colors.length) {\n return colors[valueColorIndex]\n }\n }\n return colors?.[0] || '#1f2937'\n}\n\n/** Resolve the variance colour (green/red based on sign), with palette overrides. */\nexport function resolveVarianceColor(\n variance: number | null,\n positiveColorIndex: number | undefined,\n negativeColorIndex: number | undefined,\n colors: string[] | undefined\n): string {\n if (variance === null) return '#6B7280'\n if (variance >= 0) {\n const positiveIndex = positiveColorIndex ?? 1\n return colors?.[positiveIndex] || '#10B981'\n }\n const negativeIndex = negativeColorIndex ?? 7\n return colors?.[negativeIndex] || '#EF4444'\n}\n","import { useState, useRef, useEffect, type RefObject } from 'react'\n\ninterface KpiDimensionOptions {\n /** Container divisor for the width-based font size candidate. */\n widthDivisor: number\n /** Container divisor for the height-based font size candidate. */\n heightDivisor: number\n /** Minimum clamped font size. */\n minFontSize: number\n /** Maximum clamped font size. */\n maxFontSize: number\n /** Computes the measured-text width from the value element's box + container width. */\n measureWidth: (measuredWidth: number, containerWidth: number) => number\n /** Re-run measurement whenever any of these change. */\n deps: unknown[]\n}\n\ninterface KpiDimensions {\n containerRef: RefObject<HTMLDivElement>\n valueRef: RefObject<HTMLDivElement>\n fontSize: number\n textWidth: number\n}\n\n/**\n * Shared container-measurement hook for KPI cards (KpiNumber / KpiDelta).\n *\n * Measures the container to derive a responsive font size, then (after the font\n * is applied) measures the rendered value element to size the histogram. Mirrors\n * the original per-component effects exactly — only the tunable constants differ.\n */\nexport function useKpiDimensions({\n widthDivisor,\n heightDivisor,\n minFontSize,\n maxFontSize,\n measureWidth,\n deps\n}: KpiDimensionOptions): KpiDimensions {\n const [fontSize, setFontSize] = useState(32)\n const [textWidth, setTextWidth] = useState(250)\n const containerRef = useRef<HTMLDivElement>(null)\n const valueRef = useRef<HTMLDivElement>(null)\n\n useEffect(() => {\n const updateDimensions = () => {\n if (!containerRef.current) return\n const rect = containerRef.current.getBoundingClientRect()\n const containerWidth = rect.width\n const containerHeight = rect.height\n if (containerWidth <= 0 || containerHeight <= 0) return\n\n const widthBasedSize = containerWidth / widthDivisor\n const heightBasedSize = containerHeight / heightDivisor\n const baseFontSize = Math.min(widthBasedSize, heightBasedSize)\n const clampedFontSize = Math.max(minFontSize, Math.min(baseFontSize, maxFontSize))\n setFontSize(clampedFontSize)\n\n setTimeout(() => {\n if (valueRef.current) {\n const measuredWidth = valueRef.current.getBoundingClientRect().width\n setTextWidth(measureWidth(measuredWidth, containerWidth))\n }\n }, 10)\n }\n\n const timer = setTimeout(updateDimensions, 50)\n const resizeObserver = new ResizeObserver(() => {\n setTimeout(updateDimensions, 10)\n })\n if (containerRef.current) {\n resizeObserver.observe(containerRef.current)\n }\n\n return () => {\n clearTimeout(timer)\n resizeObserver.disconnect()\n }\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, deps)\n\n return { containerRef, valueRef, fontSize, textWidth }\n}\n","import React from 'react'\n\ntype KpiStateVariant = 'muted' | 'danger' | 'warning'\n\nconst VARIANT_STYLE: Record<KpiStateVariant, React.CSSProperties> = {\n muted: {},\n danger: {\n backgroundColor: 'var(--dc-danger-bg)',\n color: 'var(--dc-danger)',\n borderColor: 'var(--dc-danger-border)'\n },\n warning: {\n backgroundColor: 'var(--dc-warning-bg)',\n color: 'var(--dc-warning)',\n borderColor: 'var(--dc-warning-border)'\n }\n}\n\n/**\n * Resolve the shared `height`/`minHeight` style used by every KPI card state.\n *\n * `minHeight` only applies when the card is sized at `100%`; the compact layout\n * passes a smaller floor since it is meant to sit in a short metric strip.\n */\nexport function kpiHeightStyle(\n height: string | number,\n minHeight: string | number = '200px'\n): React.CSSProperties {\n return {\n height: height === '100%' ? '100%' : height,\n minHeight: height === '100%' ? minHeight : undefined\n }\n}\n\n/**\n * Centred KPI state card (no-data / config-error / insufficient-data).\n *\n * Owns the duplicated centred flex wrapper shared by KpiNumber and KpiDelta.\n * Callers supply already-resolved title/hint text (i18n resolved at the call\n * site) plus an optional colour variant and extra children.\n */\nexport function KpiCenteredState({\n height,\n title,\n hint,\n variant = 'muted',\n children\n}: {\n height: string | number\n title: React.ReactNode\n hint?: React.ReactNode\n variant?: KpiStateVariant\n children?: React.ReactNode\n}) {\n return (\n <div\n className=\"dc:flex dc:items-center dc:justify-center dc:w-full dc:h-full\"\n style={{ ...kpiHeightStyle(height), ...VARIANT_STYLE[variant] }}\n >\n <div className={`dc:text-center${variant === 'muted' ? ' text-dc-text-muted' : ''}`}>\n <div className=\"dc:text-sm dc:font-semibold dc:mb-1\">{title}</div>\n {hint != null && (\n <div className={`dc:text-xs${variant === 'muted' ? ' text-dc-text-secondary' : ''}`}>{hint}</div>\n )}\n {children}\n </div>\n </div>\n )\n}\n","import React from 'react'\n\ninterface KpiCompactLayoutProps {\n /** Resolved measure label, rendered small and uppercase. */\n label: string\n /** Optional adornment next to the label (e.g. the incomplete-period info icon). */\n labelAdornment?: React.ReactNode\n /** Pre-formatted main value. */\n value: string\n /** Colour for the main value; falls back to the themed text colour. */\n valueColor?: string\n /** Unit rendered next to the value at a smaller size. */\n suffix?: string\n /**\n * Rendered immediately after the value — used for the coloured delta so the\n * headline number keeps its usual meaning.\n */\n valueAdornment?: React.ReactNode\n /** Quiet sub-line — a before/after pair, a target comparison, etc. */\n detail?: React.ReactNode\n /** Forwarded so callers can keep measuring the container if they need to. */\n containerRef?: React.Ref<HTMLDivElement>\n}\n\n/**\n * Dense KPI presentation shared by KpiNumber and KpiDelta.\n *\n * Unlike the default layout, nothing here scales with the container: the type\n * sizes are fixed so that a row of KPI portlets reads as a consistent metric\n * strip rather than a wall of differently-sized numbers.\n */\nconst KpiCompactLayout = React.memo(function KpiCompactLayout({\n label,\n labelAdornment,\n value,\n valueColor,\n suffix,\n valueAdornment,\n detail,\n containerRef\n}: KpiCompactLayoutProps) {\n return (\n <div\n ref={containerRef}\n className=\"dc:flex dc:flex-col dc:justify-center dc:w-full dc:h-full dc:px-4 dc:py-3 dc:gap-1.5 dc:overflow-hidden\"\n // Always fill the parent box. The `height` prop charts receive is a pixel\n // value that can exceed the visible panel, which would push this short\n // content below the fold once centred.\n style={{ height: '100%', minHeight: '80px' }}\n >\n {/* Matches the default KPI layout's label (bold, 14px, secondary) so\n compact and auto KPIs sitting on one dashboard read as a set. Only\n the alignment differs. */}\n <div\n className=\"dc:flex dc:items-center dc:gap-1 dc:font-bold text-dc-text-secondary\"\n style={{ fontSize: '14px', lineHeight: 1.2 }}\n >\n <span className=\"dc:truncate\">{label}</span>\n {labelAdornment}\n </div>\n\n <div className=\"dc:flex dc:items-baseline dc:gap-1.5\">\n <span\n className=\"dc:font-semibold dc:leading-none dc:truncate\"\n style={{ fontSize: '30px', color: valueColor || 'var(--dc-text)' }}\n >\n {value}\n </span>\n {suffix && (\n <span className=\"text-dc-text-muted dc:truncate\" style={{ fontSize: '14px' }}>\n {suffix}\n </span>\n )}\n {valueAdornment}\n </div>\n\n {detail && (\n <div className=\"text-dc-text-muted dc:truncate\" style={{ fontSize: '12px', lineHeight: 1.4 }}>\n {detail}\n </div>\n )}\n </div>\n )\n})\n\nexport default KpiCompactLayout\n","/**\n * Co-located helpers for KpiDelta.\n *\n * Pure data-shaping + colour resolution extracted from the component. No\n * behaviour change — these mirror the original inline logic exactly.\n */\nimport type { ChartAxisConfig } from '../../types.js'\n\n/** Coerce a config axis value (string | array | undefined) to a field list. */\nexport function toFieldList(axis: ChartAxisConfig['yAxis']): string[] {\n if (!axis) return []\n return Array.isArray(axis) ? axis : [axis]\n}\n\n/** Sort rows ascending by the dimension field (stable when absent). */\nexport function sortByDimension<T extends Record<string, any>>(\n data: T[],\n dimensionField: string | undefined\n): T[] {\n const sorted = [...data]\n if (!dimensionField) return sorted\n return sorted.sort((a, b) => {\n const aVal = a[dimensionField]\n const bVal = b[dimensionField]\n if (aVal < bVal) return -1\n if (aVal > bVal) return 1\n return 0\n })\n}\n\n/** Extract finite numeric values for a field. */\nexport function extractNumericValues(\n rows: Record<string, any>[],\n valueField: string\n): number[] {\n return rows\n .map((row) => row[valueField])\n .filter((val) => val !== null && val !== undefined && !isNaN(Number(val)))\n .map((val) => Number(val))\n}\n\nexport interface DeltaStats {\n lastValue: number\n /** The value the delta is measured against — the baseline in `prev → current`. */\n previousValue: number\n absoluteChange: number\n percentageChange: number\n isPositiveChange: boolean\n}\n\n/** Compute last value + delta vs the previous value. Requires >= 2 values. */\nexport function computeDelta(values: number[]): DeltaStats {\n const lastValue = values[values.length - 1]\n const secondLastValue = values[values.length - 2]\n const absoluteChange = lastValue - secondLastValue\n const percentageChange =\n secondLastValue !== 0 ? (absoluteChange / Math.abs(secondLastValue)) * 100 : 0\n return {\n lastValue,\n previousValue: secondLastValue,\n absoluteChange,\n percentageChange,\n isPositiveChange: absoluteChange >= 0\n }\n}\n\n/**\n * Resolve a palette colour by index, falling back to a default. Used for both\n * the positive and negative delta colours.\n */\nexport function resolvePaletteColor(\n colorIndex: number | undefined,\n colors: string[] | undefined,\n fallback: string\n): string {\n if (colorIndex !== undefined && colors) {\n if (colorIndex >= 0 && colorIndex < colors.length) {\n return colors[colorIndex]\n }\n }\n return fallback\n}\n","import React from \"react\";\nimport { useTranslation } from '../../hooks/useTranslation.js';\nimport { Icon } from \"@iconify/react\";\nimport infoCircleIcon from \"@iconify-icons/tabler/info-circle\";\nimport { useCubeFieldLabel } from \"../../hooks/useCubeFieldLabel.js\";\nimport { filterIncompletePeriod } from \"../../utils/periodUtils.js\";\nimport { formatKpiNumber, resolveDisplayLabel } from \"./KpiNumber.helpers.js\";\nimport { useKpiDimensions } from \"./useKpiDimensions.js\";\nimport { KpiCenteredState, kpiHeightStyle } from \"./KpiStates.js\";\nimport KpiCompactLayout from \"./KpiCompactLayout.js\";\nimport {\n toFieldList,\n sortByDimension,\n extractNumericValues,\n computeDelta,\n resolvePaletteColor,\n} from \"./KpiDelta.helpers.js\";\nimport type { ChartProps } from \"../../types.js\";\n\ninterface VarianceHistogramProps {\n values: number[];\n lastValue: number;\n positiveColor: string;\n negativeColor: string;\n formatValue: (value: number) => string;\n width: number;\n height: number;\n}\n\nfunction VarianceHistogram({\n values,\n lastValue,\n positiveColor,\n negativeColor,\n formatValue,\n width,\n height,\n}: VarianceHistogramProps) {\n // Limit to most recent N values to fit in the histogram\n // Calculate max bars based on width (minimum 8px per bar including gap)\n const maxBars = Math.max(10, Math.floor(width / 10));\n const limitedValues =\n values.length > maxBars\n ? values.slice(-maxBars) // Take the most recent values\n : values;\n\n // Calculate variance (difference) from current/last value for each point\n const variances = limitedValues.map((value) => value - lastValue);\n\n // Find min/max variance for scaling (include 0 as baseline)\n const minVariance = Math.min(...variances, 0);\n const maxVariance = Math.max(...variances, 0);\n const range = Math.max(Math.abs(minVariance), Math.abs(maxVariance));\n\n if (range === 0 || variances.length === 0) {\n return (\n <div\n className=\"dc:flex dc:items-center dc:justify-center bg-dc-bg-secondary dc:rounded-sm dc:border border-dc-border\"\n style={{ width: `${width}px`, height: `${height}px` }}\n >\n <span className=\"dc:text-xs text-dc-text-muted\">No variance data</span>\n </div>\n );\n }\n\n // Calculate bar dimensions\n const barGap = 2;\n const availableWidth = width - (limitedValues.length - 1) * barGap;\n const barWidth = Math.max(4, availableWidth / limitedValues.length);\n\n // Calculate where zero line should be positioned (as percentage from top)\n // If maxVariance = 67 and minVariance = -24, total range = 91\n // Zero should be at 67/91 = 73.6% from top\n const totalRange = maxVariance - minVariance;\n const zeroLinePercent =\n totalRange > 0 ? (maxVariance / totalRange) * 100 : 50;\n\n return (\n <div className=\"dc:flex dc:items-center dc:space-x-2\">\n {/* Histogram bars */}\n <div\n className=\"dc:relative\"\n style={{\n width: `${width}px`,\n height: `${height}px`,\n }}\n >\n {/* Zero line (represents current value) */}\n <div\n className=\"dc:absolute dc:left-0 dc:right-0\"\n style={{\n height: \"1px\",\n top: `${zeroLinePercent}%`,\n backgroundColor: \"var(--dc-border)\",\n zIndex: 1,\n }}\n />\n\n {/* Variance bars - oldest to newest (left to right) */}\n {variances.map((variance, index) => {\n // Calculate bar height as proportion of total range\n const normalizedHeight = Math.abs(variance) / totalRange;\n const barHeight = Math.max(2, normalizedHeight * (height - 4));\n const isPositive = variance >= 0;\n const isLastValue = index === limitedValues.length - 1;\n const color = isPositive ? positiveColor : negativeColor;\n const xPosition = index * (barWidth + barGap);\n\n return (\n <div\n key={index}\n className=\"dc:absolute dc:rounded-sm\"\n style={{\n left: `${xPosition}px`,\n width: `${barWidth}px`,\n height: `${barHeight}px`,\n backgroundColor: color,\n opacity: isLastValue ? 1 : 0.6,\n // Position bar relative to zero line\n ...(isPositive\n ? { bottom: `${100 - zeroLinePercent}%` }\n : { top: `${zeroLinePercent}%` }),\n zIndex: 2,\n }}\n title={`${formatValue(limitedValues[index])}: ${variance >= 0 ? \"+\" : \"\"}${formatValue(variance)} vs current`}\n />\n );\n })}\n </div>\n\n {/* Variance labels on the right - show actual value difference */}\n <div\n className=\"dc:flex dc:flex-col dc:justify-between dc:text-xs text-dc-text-muted\"\n style={{ height: `${height}px` }}\n >\n <span>+{formatValue(maxVariance)}</span>\n <span>\n {minVariance < 0 ? \"\" : \"\"}\n {formatValue(minVariance)}\n </span>\n </div>\n </div>\n );\n}\n\nconst KpiDelta = React.memo(function KpiDelta({\n data,\n chartConfig,\n displayConfig = {},\n queryObject,\n height = \"100%\",\n colorPalette,\n}: ChartProps) {\n const { t } = useTranslation();\n // Use specialized hook to avoid re-renders from unrelated context changes\n const getFieldLabel = useCubeFieldLabel();\n\n // Calculate font size and text width based on container dimensions\n const { containerRef, valueRef, fontSize, textWidth } = useKpiDimensions({\n widthDivisor: 4,\n heightDivisor: 4,\n minFontSize: 28,\n maxFontSize: 140,\n // Scale histogram width with container, accounting for labels on the right (~60px)\n measureWidth: (measuredWidth, containerWidth) => {\n const maxHistogramWidth = containerWidth - 100; // Leave room for padding and labels\n const effectiveWidth = Math.max(\n measuredWidth,\n Math.min(maxHistogramWidth, containerWidth * 0.7),\n );\n return Math.max(100, effectiveWidth); // Minimum 100px\n },\n deps: [data, chartConfig],\n });\n\n if (!data || data.length === 0) {\n return (\n <KpiCenteredState\n height={height}\n title={t('chart.runtime.noData')}\n hint={t('chart.runtime.noDataHint.kpi')}\n />\n );\n }\n\n // Extract value and dimension fields from chart config\n const valueFields = toFieldList(chartConfig?.yAxis);\n const dimensionFields = toFieldList(chartConfig?.xAxis);\n\n if (valueFields.length === 0) {\n return (\n <KpiCenteredState\n height={height}\n variant=\"danger\"\n title={t('chart.runtime.configError')}\n hint={t('chart.runtime.configErrorHint.noMeasure')}\n />\n );\n }\n\n const valueField = valueFields[0];\n const dimensionField = dimensionFields[0]; // Optional\n\n // Sort data by dimension if available (for time series)\n const sortedData = sortByDimension(data, dimensionField);\n\n // Filter out incomplete or last period if enabled\n const { useLastCompletePeriod = true, skipLastPeriod = false } =\n displayConfig;\n const {\n filteredData,\n excludedIncompletePeriod,\n skippedLastPeriod,\n granularity,\n } = filterIncompletePeriod(\n sortedData,\n dimensionField,\n queryObject,\n useLastCompletePeriod,\n skipLastPeriod,\n );\n\n // Use filtered data for calculations\n const dataToUse = filteredData;\n\n // Extract values from filtered data\n const values = extractNumericValues(dataToUse, valueField);\n\n if (values.length < 2) {\n return (\n <KpiCenteredState\n height={height}\n variant=\"warning\"\n title={t('chart.runtime.kpiDelta.insufficientData')}\n hint={t('chart.runtime.kpiDelta.requiresTwoPoints')}\n >\n <div className=\"dc:text-xs\">{t('chart.runtime.kpiDelta.currentPoints', { count: values.length })}</div>\n </KpiCenteredState>\n );\n }\n\n // Calculate delta between last and second-last values\n const { lastValue, previousValue, absoluteChange, percentageChange, isPositiveChange } =\n computeDelta(values);\n\n // Format number with appropriate units and decimals\n const formatNumber = (value: number | null | undefined): string =>\n formatKpiNumber(value, displayConfig);\n\n // Get colors from palette\n const positiveColor = resolvePaletteColor(\n displayConfig.positiveColorIndex,\n colorPalette?.colors,\n \"#10b981\", // Default green\n );\n const negativeColor = resolvePaletteColor(\n displayConfig.negativeColorIndex,\n colorPalette?.colors,\n \"#ef4444\", // Default red\n );\n const currentColor = isPositiveChange ? positiveColor : negativeColor;\n\n const periodAdornment = (excludedIncompletePeriod || skippedLastPeriod) ? (\n <span\n title={\n skippedLastPeriod\n ? t('chart.runtime.kpiExcludesLastPeriod', { period: granularity || t('chart.runtime.kpiPeriodFallback') })\n : t('chart.runtime.kpiExcludesIncompletePeriod', { period: granularity || t('chart.runtime.kpiPeriodFallback') })\n }\n className=\"dc:cursor-help\"\n >\n <Icon\n icon={infoCircleIcon}\n className=\"dc:w-4 dc:h-4 text-dc-text-muted dc:opacity-70\"\n />\n </span>\n ) : null;\n\n // Compact layout: fixed type scale, delta beside the value rather than below\n // it, and no histogram — so several KPIs line up as a metric strip.\n if (displayConfig.layout === 'compact') {\n return (\n <KpiCompactLayout\n containerRef={containerRef}\n label={resolveDisplayLabel(getFieldLabel(valueField), valueField)}\n labelAdornment={periodAdornment}\n value={formatNumber(lastValue)}\n suffix={displayConfig.suffix && !displayConfig.formatValue ? displayConfig.suffix : undefined}\n valueAdornment={\n <span\n className=\"dc:font-semibold dc:whitespace-nowrap\"\n style={{ color: currentColor, fontSize: '15px' }}\n >\n {isPositiveChange ? \"\\u25B2\" : \"\\u25BC\"} {isPositiveChange ? \"+\" : \"\"}\n {percentageChange.toFixed(1)}%\n </span>\n }\n detail={displayConfig.showBaseline\n ? `${formatNumber(previousValue)} \\u2192 ${formatNumber(lastValue)}`\n : undefined}\n />\n );\n }\n\n return (\n <div\n ref={containerRef}\n className=\"dc:flex dc:flex-col dc:items-center dc:justify-center dc:w-full dc:h-full dc:p-4\"\n style={kpiHeightStyle(height)}\n >\n {/* Field Label */}\n <div\n className=\"text-dc-text-secondary dc:font-bold dc:text-center dc:mb-2 dc:flex dc:items-center dc:justify-center dc:gap-1\"\n style={{\n fontSize: \"14px\",\n lineHeight: \"1.2\",\n }}\n >\n <span>\n {resolveDisplayLabel(getFieldLabel(valueField), valueField)}\n </span>\n {periodAdornment}\n </div>\n\n {/* Main KPI Value and Delta */}\n <div className=\"dc:flex dc:items-center dc:justify-center dc:space-x-4 dc:mb-2\">\n {/* Main KPI Value */}\n <div\n ref={valueRef}\n className=\"dc:font-bold dc:leading-none\"\n style={{\n fontSize: `${fontSize}px`,\n color: \"var(--dc-text)\", // Keep main value neutral\n }}\n >\n {formatNumber(lastValue)}\n </div>\n\n {/* Delta Information */}\n <div className=\"dc:flex dc:items-center dc:space-x-1\">\n {/* Arrow */}\n <div\n className=\"dc:font-bold\"\n style={{\n color: currentColor,\n fontSize: `${fontSize * 0.35}px`,\n }}\n >\n {isPositiveChange ? \"▲\" : \"▼\"}\n </div>\n\n {/* Delta Values */}\n <div className=\"dc:text-left\">\n <div\n className=\"dc:font-bold dc:leading-tight\"\n style={{\n fontSize: `${fontSize * 0.35}px`,\n color: currentColor,\n }}\n >\n {isPositiveChange ? \"+\" : \"\"}\n {formatNumber(absoluteChange)}\n </div>\n <div\n className=\"dc:font-semibold dc:leading-tight\"\n style={{\n fontSize: `${fontSize * 0.28}px`,\n color: currentColor,\n opacity: 0.8,\n }}\n >\n {isPositiveChange ? \"+\" : \"\"}\n {percentageChange.toFixed(1)}%\n </div>\n </div>\n </div>\n </div>\n\n {/* Unit/Suffix (hidden when formatValue is provided) */}\n {displayConfig.suffix && !displayConfig.formatValue && (\n <div\n className=\"text-dc-text-muted dc:text-center dc:mb-3\"\n style={{\n fontSize: \"14px\",\n lineHeight: \"1.2\",\n opacity: 0.8,\n }}\n >\n {displayConfig.suffix}\n </div>\n )}\n\n {/* Variance Histogram */}\n {displayConfig.showHistogram !== false && values.length > 2 && (\n <div className=\"dc:mt-2 dc:w-full dc:flex dc:justify-center dc:overflow-hidden\">\n <VarianceHistogram\n values={values}\n lastValue={lastValue}\n positiveColor={positiveColor}\n negativeColor={negativeColor}\n formatValue={formatNumber}\n width={textWidth}\n height={64}\n />\n </div>\n )}\n </div>\n );\n})\n\nexport default KpiDelta\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"chart-kpi-delta-BJCOXE-F.js","names":[],"sources":["../../../src/client/utils/periodUtils.ts","../../../src/client/components/charts/KpiNumber.helpers.ts","../../../src/client/components/charts/useKpiDimensions.ts","../../../src/client/components/charts/KpiStates.tsx","../../../src/client/components/charts/KpiCompactLayout.tsx","../../../src/client/components/charts/KpiDelta.helpers.ts","../../../src/client/components/charts/KpiDelta.tsx"],"sourcesContent":["/**\n * Utility functions for detecting incomplete time periods in KPI charts\n */\n\n/**\n * Get the end date of a period based on granularity\n * @param date - The date within the period\n * @param granularity - The time granularity (day, week, month, quarter, year)\n * @returns The end date of the period (end of day)\n */\nexport function getPeriodEndDate(date: Date, granularity: string): Date {\n const endDate = new Date(date)\n\n switch (granularity.toLowerCase()) {\n case 'day':\n // End of the same day\n endDate.setHours(23, 59, 59, 999)\n break\n\n case 'week': {\n // End of the week (Saturday, assuming week starts Sunday)\n const dayOfWeek = endDate.getDay()\n const daysUntilSaturday = 6 - dayOfWeek\n endDate.setDate(endDate.getDate() + daysUntilSaturday)\n endDate.setHours(23, 59, 59, 999)\n break\n }\n\n case 'month':\n // Last day of the month\n endDate.setMonth(endDate.getMonth() + 1, 0) // Set to day 0 of next month = last day of current month\n endDate.setHours(23, 59, 59, 999)\n break\n\n case 'quarter': {\n // Last day of the quarter\n const currentMonth = endDate.getMonth()\n const quarterEndMonth = Math.floor(currentMonth / 3) * 3 + 2 // 0->2, 3->5, 6->8, 9->11\n endDate.setMonth(quarterEndMonth + 1, 0) // Last day of quarter end month\n endDate.setHours(23, 59, 59, 999)\n break\n }\n\n case 'year':\n // December 31st\n endDate.setMonth(11, 31) // December 31\n endDate.setHours(23, 59, 59, 999)\n break\n\n default:\n // Unknown granularity - treat as complete\n endDate.setHours(23, 59, 59, 999)\n }\n\n return endDate\n}\n\n/**\n * Check if the last period in the data is complete based on granularity\n * @param lastDataPoint - The last data point in the sorted dataset\n * @param timeDimensionField - The field name containing the time value\n * @param granularity - The time granularity\n * @returns true if the period is complete, false if it's incomplete\n */\nexport function isLastPeriodComplete(\n lastDataPoint: any,\n timeDimensionField: string,\n granularity: string\n): boolean {\n if (!lastDataPoint || !timeDimensionField || !granularity) {\n return true // Assume complete if we can't determine\n }\n\n const timeValue = lastDataPoint[timeDimensionField]\n if (!timeValue) {\n return true // Assume complete if no time value\n }\n\n // Parse the time value\n const date = new Date(timeValue)\n if (isNaN(date.getTime())) {\n return true // Assume complete if invalid date\n }\n\n // Get the end of this period\n const periodEnd = getPeriodEndDate(date, granularity)\n\n // Compare with current time\n const now = new Date()\n\n // If the period end is in the future, the period is incomplete\n return periodEnd <= now\n}\n\n/**\n * Extract granularity from a query object\n * @param queryObject - The CubeQuery object\n * @param dimensionField - Optional specific dimension field to match\n * @returns The granularity string or null if not found\n */\nexport function getQueryGranularity(\n queryObject: any,\n dimensionField?: string\n): string | null {\n if (!queryObject?.timeDimensions || queryObject.timeDimensions.length === 0) {\n return null\n }\n\n // If a specific dimension field is provided, try to find its granularity\n if (dimensionField) {\n const matchingDim = queryObject.timeDimensions.find((td: any) =>\n td.dimension === dimensionField ||\n td.dimension?.includes(dimensionField) ||\n dimensionField?.includes(td.dimension)\n )\n if (matchingDim?.granularity) {\n return matchingDim.granularity\n }\n }\n\n // Fallback to first time dimension's granularity\n const firstTimeDim = queryObject.timeDimensions[0]\n return firstTimeDim?.granularity || null\n}\n\n/**\n * Filter data to exclude incomplete or last period\n * @param data - The data array sorted by time\n * @param timeDimensionField - The field containing time values\n * @param queryObject - The query object containing timeDimensions\n * @param useLastCompletePeriod - Whether to check for incomplete periods\n * @param skipLastPeriod - Whether to always skip the last period (overrides useLastCompletePeriod)\n * @returns Object with filtered data and whether filtering was applied\n */\nexport function filterIncompletePeriod(\n data: any[],\n timeDimensionField: string | undefined,\n queryObject: any,\n useLastCompletePeriod: boolean,\n skipLastPeriod: boolean = false\n): { filteredData: any[]; excludedIncompletePeriod: boolean; skippedLastPeriod: boolean; granularity: string | null } {\n // Default return - no filtering\n const noFilter = {\n filteredData: data,\n excludedIncompletePeriod: false,\n skippedLastPeriod: false,\n granularity: null\n }\n\n // Need at least 2 data points to filter\n if (data.length < 2) {\n return noFilter\n }\n\n const granularity = getQueryGranularity(queryObject, timeDimensionField)\n\n // If skipLastPeriod is enabled, always skip the last period\n if (skipLastPeriod) {\n return {\n filteredData: data.slice(0, -1),\n excludedIncompletePeriod: false,\n skippedLastPeriod: true,\n granularity\n }\n }\n\n // Skip incomplete period check if feature is disabled\n if (!useLastCompletePeriod) {\n return { ...noFilter, granularity }\n }\n\n // Skip if no time dimension field\n if (!timeDimensionField) {\n return { ...noFilter, granularity }\n }\n\n // Skip if no time dimensions in query\n if (!queryObject?.timeDimensions || queryObject.timeDimensions.length === 0) {\n return { ...noFilter, granularity }\n }\n\n if (!granularity) {\n return noFilter\n }\n\n // Check if last period is incomplete\n const lastRow = data[data.length - 1]\n if (!isLastPeriodComplete(lastRow, timeDimensionField, granularity)) {\n return {\n filteredData: data.slice(0, -1),\n excludedIncompletePeriod: true,\n skippedLastPeriod: false,\n granularity\n }\n }\n\n return { ...noFilter, granularity }\n}\n","/**\n * Co-located helpers for KpiNumber.\n *\n * Pure data-shaping + formatting extracted from the component to keep the\n * render function lean. No behaviour change — these mirror the original inline\n * logic exactly.\n */\nimport type { ChartAxisConfig, ChartDisplayConfig } from '../../types.js'\n\n/** Extract the value field list from chart config (string | array | undefined). */\nexport function getKpiValueFields(yAxis: ChartAxisConfig['yAxis']): string[] {\n if (!yAxis) return []\n if (typeof yAxis === 'string') return [yAxis]\n if (Array.isArray(yAxis)) return yAxis\n return []\n}\n\n/** Sort rows ascending by the time-dimension field (stable when absent). */\nexport function sortKpiData<T extends Record<string, any>>(\n data: T[] | undefined,\n timeDimensionField: string | undefined\n): T[] {\n if (!data || data.length === 0) return []\n const sorted = [...data]\n if (!timeDimensionField) return sorted\n return sorted.sort((a, b) => {\n const aVal = a[timeDimensionField]\n const bVal = b[timeDimensionField]\n if (aVal < bVal) return -1\n if (aVal > bVal) return 1\n return 0\n })\n}\n\n/**\n * Extract numeric values for the configured field, falling back to the first\n * numeric field on each row when the configured field is absent.\n */\nexport function extractKpiValues(\n dataToUse: Record<string, any>[],\n valueField: string\n): number[] {\n if (!valueField || dataToUse.length === 0) return []\n\n const rawValues = dataToUse.map(row => {\n if (row[valueField] !== undefined) {\n return row[valueField]\n }\n const numericFields = Object.keys(row).filter(key =>\n typeof row[key] === 'number' && !isNaN(row[key])\n )\n if (numericFields.length > 0) {\n return row[numericFields[0]]\n }\n return undefined\n })\n\n return rawValues\n .filter(val => val !== null && val !== undefined && !isNaN(Number(val)))\n .map(val => Number(val))\n}\n\n/** Compute avg/min/max over a numeric series (0s when empty). */\nexport function computeKpiStats(values: number[]): { avg: number; min: number; max: number } {\n if (values.length === 0) return { avg: 0, min: 0, max: 0 }\n const sum = values.reduce((acc, val) => acc + val, 0)\n return {\n avg: sum / values.length,\n min: Math.min(...values),\n max: Math.max(...values)\n }\n}\n\n/**\n * Format a numeric KPI value with thousands/millions/billions suffixes,\n * honouring a custom `formatValue`, `decimals` and `prefix` from displayConfig.\n */\nexport function formatKpiNumber(\n value: number | null | undefined,\n displayConfig: ChartDisplayConfig\n): string {\n if (displayConfig.formatValue) {\n return displayConfig.formatValue(value)\n }\n\n if (value === null || value === undefined) {\n return '—'\n }\n\n const decimals = displayConfig.decimals ?? 0\n const prefix = displayConfig.prefix ?? ''\n\n let formattedValue: string\n\n if (Math.abs(value) >= 1e9) {\n formattedValue = (value / 1e9).toFixed(decimals) + 'B'\n } else if (Math.abs(value) >= 1e6) {\n formattedValue = (value / 1e6).toFixed(decimals) + 'M'\n } else if (Math.abs(value) >= 1e3) {\n formattedValue = (value / 1e3).toFixed(decimals) + 'K'\n } else {\n formattedValue = value.toFixed(decimals)\n }\n\n return prefix + formattedValue\n}\n\n/**\n * Resolve the label to display for a KPI field: prefer the resolved field label,\n * but fall back to the raw field name when the label looks empty/too short.\n */\nexport function resolveDisplayLabel(label: string | undefined, fieldName: string): string {\n return label && label.length > 1 ? label : fieldName\n}\n\n/** Resolve the main value colour from the palette / displayConfig overrides. */\nexport function resolveValueColor(\n valueColorIndex: number | undefined,\n colors: string[] | undefined\n): string {\n if (valueColorIndex !== undefined && colors) {\n if (valueColorIndex >= 0 && valueColorIndex < colors.length) {\n return colors[valueColorIndex]\n }\n }\n return colors?.[0] || '#1f2937'\n}\n\n/** Resolve the variance colour (green/red based on sign), with palette overrides. */\nexport function resolveVarianceColor(\n variance: number | null,\n positiveColorIndex: number | undefined,\n negativeColorIndex: number | undefined,\n colors: string[] | undefined\n): string {\n if (variance === null) return '#6B7280'\n if (variance >= 0) {\n const positiveIndex = positiveColorIndex ?? 1\n return colors?.[positiveIndex] || '#10B981'\n }\n const negativeIndex = negativeColorIndex ?? 7\n return colors?.[negativeIndex] || '#EF4444'\n}\n","import { useState, useRef, useEffect, type RefObject } from 'react'\n\ninterface KpiDimensionOptions {\n /** Container divisor for the width-based font size candidate. */\n widthDivisor: number\n /** Container divisor for the height-based font size candidate. */\n heightDivisor: number\n /** Minimum clamped font size. */\n minFontSize: number\n /** Maximum clamped font size. */\n maxFontSize: number\n /** Computes the measured-text width from the value element's box + container width. */\n measureWidth: (measuredWidth: number, containerWidth: number) => number\n /** Re-run measurement whenever any of these change. */\n deps: unknown[]\n}\n\ninterface KpiDimensions {\n containerRef: RefObject<HTMLDivElement>\n valueRef: RefObject<HTMLDivElement>\n fontSize: number\n textWidth: number\n}\n\n/**\n * Shared container-measurement hook for KPI cards (KpiNumber / KpiDelta).\n *\n * Measures the container to derive a responsive font size, then (after the font\n * is applied) measures the rendered value element to size the histogram. Mirrors\n * the original per-component effects exactly — only the tunable constants differ.\n */\nexport function useKpiDimensions({\n widthDivisor,\n heightDivisor,\n minFontSize,\n maxFontSize,\n measureWidth,\n deps\n}: KpiDimensionOptions): KpiDimensions {\n const [fontSize, setFontSize] = useState(32)\n const [textWidth, setTextWidth] = useState(250)\n const containerRef = useRef<HTMLDivElement>(null)\n const valueRef = useRef<HTMLDivElement>(null)\n\n useEffect(() => {\n const updateDimensions = () => {\n if (!containerRef.current) return\n const rect = containerRef.current.getBoundingClientRect()\n const containerWidth = rect.width\n const containerHeight = rect.height\n if (containerWidth <= 0 || containerHeight <= 0) return\n\n const widthBasedSize = containerWidth / widthDivisor\n const heightBasedSize = containerHeight / heightDivisor\n const baseFontSize = Math.min(widthBasedSize, heightBasedSize)\n const clampedFontSize = Math.max(minFontSize, Math.min(baseFontSize, maxFontSize))\n setFontSize(clampedFontSize)\n\n setTimeout(() => {\n if (valueRef.current) {\n const measuredWidth = valueRef.current.getBoundingClientRect().width\n setTextWidth(measureWidth(measuredWidth, containerWidth))\n }\n }, 10)\n }\n\n const timer = setTimeout(updateDimensions, 50)\n const resizeObserver = new ResizeObserver(() => {\n setTimeout(updateDimensions, 10)\n })\n if (containerRef.current) {\n resizeObserver.observe(containerRef.current)\n }\n\n return () => {\n clearTimeout(timer)\n resizeObserver.disconnect()\n }\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, deps)\n\n return { containerRef, valueRef, fontSize, textWidth }\n}\n","import React from 'react'\n\ntype KpiStateVariant = 'muted' | 'danger' | 'warning'\n\nconst VARIANT_STYLE: Record<KpiStateVariant, React.CSSProperties> = {\n muted: {},\n danger: {\n backgroundColor: 'var(--dc-danger-bg)',\n color: 'var(--dc-danger)',\n borderColor: 'var(--dc-danger-border)'\n },\n warning: {\n backgroundColor: 'var(--dc-warning-bg)',\n color: 'var(--dc-warning)',\n borderColor: 'var(--dc-warning-border)'\n }\n}\n\n/**\n * Resolve the shared `height`/`minHeight` style used by every KPI card state.\n *\n * `minHeight` only applies when the card is sized at `100%`; the compact layout\n * passes a smaller floor since it is meant to sit in a short metric strip.\n */\nexport function kpiHeightStyle(\n height: string | number,\n minHeight: string | number = '200px'\n): React.CSSProperties {\n return {\n height: height === '100%' ? '100%' : height,\n minHeight: height === '100%' ? minHeight : undefined\n }\n}\n\n/**\n * Centred KPI state card (no-data / config-error / insufficient-data).\n *\n * Owns the duplicated centred flex wrapper shared by KpiNumber and KpiDelta.\n * Callers supply already-resolved title/hint text (i18n resolved at the call\n * site) plus an optional colour variant and extra children.\n */\nexport function KpiCenteredState({\n height,\n title,\n hint,\n variant = 'muted',\n children\n}: {\n height: string | number\n title: React.ReactNode\n hint?: React.ReactNode\n variant?: KpiStateVariant\n children?: React.ReactNode\n}) {\n return (\n <div\n className=\"dc:flex dc:items-center dc:justify-center dc:w-full dc:h-full\"\n style={{ ...kpiHeightStyle(height), ...VARIANT_STYLE[variant] }}\n >\n <div className={`dc:text-center${variant === 'muted' ? ' text-dc-text-muted' : ''}`}>\n <div className=\"dc:text-sm dc:font-semibold dc:mb-1\">{title}</div>\n {hint != null && (\n <div className={`dc:text-xs${variant === 'muted' ? ' text-dc-text-secondary' : ''}`}>{hint}</div>\n )}\n {children}\n </div>\n </div>\n )\n}\n","import React from 'react'\n\ninterface KpiCompactLayoutProps {\n /** Resolved measure label, rendered small and uppercase. */\n label: string\n /** Optional adornment next to the label (e.g. the incomplete-period info icon). */\n labelAdornment?: React.ReactNode\n /** Pre-formatted main value. */\n value: string\n /** Colour for the main value; falls back to the themed text colour. */\n valueColor?: string\n /** Unit rendered next to the value at a smaller size. */\n suffix?: string\n /**\n * Rendered immediately after the value — used for the coloured delta so the\n * headline number keeps its usual meaning.\n */\n valueAdornment?: React.ReactNode\n /** Quiet sub-line — a before/after pair, a target comparison, etc. */\n detail?: React.ReactNode\n /** Forwarded so callers can keep measuring the container if they need to. */\n containerRef?: React.Ref<HTMLDivElement>\n}\n\n/**\n * Dense KPI presentation shared by KpiNumber and KpiDelta.\n *\n * Unlike the default layout, nothing here scales with the container: the type\n * sizes are fixed so that a row of KPI portlets reads as a consistent metric\n * strip rather than a wall of differently-sized numbers.\n */\nconst KpiCompactLayout = React.memo(function KpiCompactLayout({\n label,\n labelAdornment,\n value,\n valueColor,\n suffix,\n valueAdornment,\n detail,\n containerRef\n}: KpiCompactLayoutProps) {\n return (\n <div\n ref={containerRef}\n className=\"dc:flex dc:flex-col dc:justify-center dc:w-full dc:h-full dc:px-4 dc:py-3 dc:gap-1.5 dc:overflow-hidden\"\n // Always fill the parent box. The `height` prop charts receive is a pixel\n // value that can exceed the visible panel, which would push this short\n // content below the fold once centred.\n style={{ height: '100%', minHeight: '80px' }}\n >\n {/* Matches the default KPI layout's label (bold, 14px, secondary) so\n compact and auto KPIs sitting on one dashboard read as a set. Only\n the alignment differs. */}\n <div\n className=\"dc:flex dc:items-center dc:gap-1 dc:font-bold text-dc-text-secondary\"\n style={{ fontSize: '14px', lineHeight: 1.2 }}\n >\n <span className=\"dc:truncate\">{label}</span>\n {labelAdornment}\n </div>\n\n <div className=\"dc:flex dc:items-baseline dc:gap-1.5\">\n <span\n className=\"dc:font-semibold dc:leading-none dc:truncate\"\n style={{ fontSize: '30px', color: valueColor || 'var(--dc-text)' }}\n >\n {value}\n </span>\n {suffix && (\n <span className=\"text-dc-text-muted dc:truncate\" style={{ fontSize: '14px' }}>\n {suffix}\n </span>\n )}\n {valueAdornment}\n </div>\n\n {detail && (\n <div className=\"text-dc-text-muted dc:truncate\" style={{ fontSize: '12px', lineHeight: 1.4 }}>\n {detail}\n </div>\n )}\n </div>\n )\n})\n\nexport default KpiCompactLayout\n","/**\n * Co-located helpers for KpiDelta.\n *\n * Pure data-shaping + colour resolution extracted from the component. No\n * behaviour change — these mirror the original inline logic exactly.\n */\nimport type { ChartAxisConfig } from '../../types.js'\n\n/** Coerce a config axis value (string | array | undefined) to a field list. */\nexport function toFieldList(axis: ChartAxisConfig['yAxis']): string[] {\n if (!axis) return []\n return Array.isArray(axis) ? axis : [axis]\n}\n\n/** Sort rows ascending by the dimension field (stable when absent). */\nexport function sortByDimension<T extends Record<string, any>>(\n data: T[],\n dimensionField: string | undefined\n): T[] {\n const sorted = [...data]\n if (!dimensionField) return sorted\n return sorted.sort((a, b) => {\n const aVal = a[dimensionField]\n const bVal = b[dimensionField]\n if (aVal < bVal) return -1\n if (aVal > bVal) return 1\n return 0\n })\n}\n\n/** Extract finite numeric values for a field. */\nexport function extractNumericValues(\n rows: Record<string, any>[],\n valueField: string\n): number[] {\n return rows\n .map((row) => row[valueField])\n .filter((val) => val !== null && val !== undefined && !isNaN(Number(val)))\n .map((val) => Number(val))\n}\n\nexport interface DeltaStats {\n lastValue: number\n /** The value the delta is measured against — the baseline in `prev → current`. */\n previousValue: number\n absoluteChange: number\n percentageChange: number\n isPositiveChange: boolean\n}\n\n/** Compute last value + delta vs the previous value. Requires >= 2 values. */\nexport function computeDelta(values: number[]): DeltaStats {\n const lastValue = values[values.length - 1]\n const secondLastValue = values[values.length - 2]\n const absoluteChange = lastValue - secondLastValue\n const percentageChange =\n secondLastValue !== 0 ? (absoluteChange / Math.abs(secondLastValue)) * 100 : 0\n return {\n lastValue,\n previousValue: secondLastValue,\n absoluteChange,\n percentageChange,\n isPositiveChange: absoluteChange >= 0\n }\n}\n\n/**\n * Resolve a palette colour by index, falling back to a default. Used for both\n * the positive and negative delta colours.\n */\nexport function resolvePaletteColor(\n colorIndex: number | undefined,\n colors: string[] | undefined,\n fallback: string\n): string {\n if (colorIndex !== undefined && colors) {\n if (colorIndex >= 0 && colorIndex < colors.length) {\n return colors[colorIndex]\n }\n }\n return fallback\n}\n","import React from \"react\";\nimport { useTranslation } from '../../hooks/useTranslation.js';\nimport { Icon } from \"@iconify/react\";\nimport infoCircleIcon from \"@iconify-icons/tabler/info-circle\";\nimport { useCubeFieldLabel } from \"../../hooks/useCubeFieldLabel.js\";\nimport { filterIncompletePeriod } from \"../../utils/periodUtils.js\";\nimport { formatKpiNumber, resolveDisplayLabel } from \"./KpiNumber.helpers.js\";\nimport { useKpiDimensions } from \"./useKpiDimensions.js\";\nimport { KpiCenteredState, kpiHeightStyle } from \"./KpiStates.js\";\nimport KpiCompactLayout from \"./KpiCompactLayout.js\";\nimport {\n toFieldList,\n sortByDimension,\n extractNumericValues,\n computeDelta,\n resolvePaletteColor,\n} from \"./KpiDelta.helpers.js\";\nimport type { ChartProps } from \"../../types.js\";\n\ninterface VarianceHistogramProps {\n values: number[];\n lastValue: number;\n positiveColor: string;\n negativeColor: string;\n formatValue: (value: number) => string;\n width: number;\n height: number;\n}\n\nfunction VarianceHistogram({\n values,\n lastValue,\n positiveColor,\n negativeColor,\n formatValue,\n width,\n height,\n}: VarianceHistogramProps) {\n // Limit to most recent N values to fit in the histogram\n // Calculate max bars based on width (minimum 8px per bar including gap)\n const maxBars = Math.max(10, Math.floor(width / 10));\n const limitedValues =\n values.length > maxBars\n ? values.slice(-maxBars) // Take the most recent values\n : values;\n\n // Calculate variance (difference) from current/last value for each point\n const variances = limitedValues.map((value) => value - lastValue);\n\n // Find min/max variance for scaling (include 0 as baseline)\n const minVariance = Math.min(...variances, 0);\n const maxVariance = Math.max(...variances, 0);\n const range = Math.max(Math.abs(minVariance), Math.abs(maxVariance));\n\n if (range === 0 || variances.length === 0) {\n return (\n <div\n className=\"dc:flex dc:items-center dc:justify-center bg-dc-bg-secondary dc:rounded-sm dc:border border-dc-border\"\n style={{ width: `${width}px`, height: `${height}px` }}\n >\n <span className=\"dc:text-xs text-dc-text-muted\">No variance data</span>\n </div>\n );\n }\n\n // Calculate bar dimensions\n const barGap = 2;\n const availableWidth = width - (limitedValues.length - 1) * barGap;\n const barWidth = Math.max(4, availableWidth / limitedValues.length);\n\n // Calculate where zero line should be positioned (as percentage from top)\n // If maxVariance = 67 and minVariance = -24, total range = 91\n // Zero should be at 67/91 = 73.6% from top\n const totalRange = maxVariance - minVariance;\n const zeroLinePercent =\n totalRange > 0 ? (maxVariance / totalRange) * 100 : 50;\n\n return (\n <div className=\"dc:flex dc:items-center dc:space-x-2\">\n {/* Histogram bars */}\n <div\n className=\"dc:relative\"\n style={{\n width: `${width}px`,\n height: `${height}px`,\n }}\n >\n {/* Zero line (represents current value) */}\n <div\n className=\"dc:absolute dc:left-0 dc:right-0\"\n style={{\n height: \"1px\",\n top: `${zeroLinePercent}%`,\n backgroundColor: \"var(--dc-border)\",\n zIndex: 1,\n }}\n />\n\n {/* Variance bars - oldest to newest (left to right) */}\n {variances.map((variance, index) => {\n // Calculate bar height as proportion of total range\n const normalizedHeight = Math.abs(variance) / totalRange;\n const barHeight = Math.max(2, normalizedHeight * (height - 4));\n const isPositive = variance >= 0;\n const isLastValue = index === limitedValues.length - 1;\n const color = isPositive ? positiveColor : negativeColor;\n const xPosition = index * (barWidth + barGap);\n\n return (\n <div\n key={index}\n className=\"dc:absolute dc:rounded-sm\"\n style={{\n left: `${xPosition}px`,\n width: `${barWidth}px`,\n height: `${barHeight}px`,\n backgroundColor: color,\n opacity: isLastValue ? 1 : 0.6,\n // Position bar relative to zero line\n ...(isPositive\n ? { bottom: `${100 - zeroLinePercent}%` }\n : { top: `${zeroLinePercent}%` }),\n zIndex: 2,\n }}\n title={`${formatValue(limitedValues[index])}: ${variance >= 0 ? \"+\" : \"\"}${formatValue(variance)} vs current`}\n />\n );\n })}\n </div>\n\n {/* Variance labels on the right - show actual value difference */}\n <div\n className=\"dc:flex dc:flex-col dc:justify-between dc:text-xs text-dc-text-muted\"\n style={{ height: `${height}px` }}\n >\n <span>+{formatValue(maxVariance)}</span>\n <span>\n {minVariance < 0 ? \"\" : \"\"}\n {formatValue(minVariance)}\n </span>\n </div>\n </div>\n );\n}\n\nconst KpiDelta = React.memo(function KpiDelta({\n data,\n chartConfig,\n displayConfig = {},\n queryObject,\n height = \"100%\",\n colorPalette,\n}: ChartProps) {\n const { t } = useTranslation();\n // Use specialized hook to avoid re-renders from unrelated context changes\n const getFieldLabel = useCubeFieldLabel();\n\n // Calculate font size and text width based on container dimensions\n const { containerRef, valueRef, fontSize, textWidth } = useKpiDimensions({\n widthDivisor: 4,\n heightDivisor: 4,\n minFontSize: 28,\n maxFontSize: 140,\n // Scale histogram width with container, accounting for labels on the right (~60px)\n measureWidth: (measuredWidth, containerWidth) => {\n const maxHistogramWidth = containerWidth - 100; // Leave room for padding and labels\n const effectiveWidth = Math.max(\n measuredWidth,\n Math.min(maxHistogramWidth, containerWidth * 0.7),\n );\n return Math.max(100, effectiveWidth); // Minimum 100px\n },\n deps: [data, chartConfig],\n });\n\n if (!data || data.length === 0) {\n return (\n <KpiCenteredState\n height={height}\n title={t('chart.runtime.noData')}\n hint={t('chart.runtime.noDataHint.kpi')}\n />\n );\n }\n\n // Extract value and dimension fields from chart config\n const valueFields = toFieldList(chartConfig?.yAxis);\n const dimensionFields = toFieldList(chartConfig?.xAxis);\n\n if (valueFields.length === 0) {\n return (\n <KpiCenteredState\n height={height}\n variant=\"danger\"\n title={t('chart.runtime.configError')}\n hint={t('chart.runtime.configErrorHint.noMeasure')}\n />\n );\n }\n\n const valueField = valueFields[0];\n const dimensionField = dimensionFields[0]; // Optional\n\n // Sort data by dimension if available (for time series)\n const sortedData = sortByDimension(data, dimensionField);\n\n // Filter out incomplete or last period if enabled\n const { useLastCompletePeriod = true, skipLastPeriod = false } =\n displayConfig;\n const {\n filteredData,\n excludedIncompletePeriod,\n skippedLastPeriod,\n granularity,\n } = filterIncompletePeriod(\n sortedData,\n dimensionField,\n queryObject,\n useLastCompletePeriod,\n skipLastPeriod,\n );\n\n // Use filtered data for calculations\n const dataToUse = filteredData;\n\n // Extract values from filtered data\n const values = extractNumericValues(dataToUse, valueField);\n\n if (values.length < 2) {\n return (\n <KpiCenteredState\n height={height}\n variant=\"warning\"\n title={t('chart.runtime.kpiDelta.insufficientData')}\n hint={t('chart.runtime.kpiDelta.requiresTwoPoints')}\n >\n <div className=\"dc:text-xs\">{t('chart.runtime.kpiDelta.currentPoints', { count: values.length })}</div>\n </KpiCenteredState>\n );\n }\n\n // Calculate delta between last and second-last values\n const { lastValue, previousValue, absoluteChange, percentageChange, isPositiveChange } =\n computeDelta(values);\n\n // Format number with appropriate units and decimals\n const formatNumber = (value: number | null | undefined): string =>\n formatKpiNumber(value, displayConfig);\n\n // Get colors from palette\n const positiveColor = resolvePaletteColor(\n displayConfig.positiveColorIndex,\n colorPalette?.colors,\n \"#10b981\", // Default green\n );\n const negativeColor = resolvePaletteColor(\n displayConfig.negativeColorIndex,\n colorPalette?.colors,\n \"#ef4444\", // Default red\n );\n const currentColor = isPositiveChange ? positiveColor : negativeColor;\n\n const periodAdornment = (excludedIncompletePeriod || skippedLastPeriod) ? (\n <span\n title={\n skippedLastPeriod\n ? t('chart.runtime.kpiExcludesLastPeriod', { period: granularity || t('chart.runtime.kpiPeriodFallback') })\n : t('chart.runtime.kpiExcludesIncompletePeriod', { period: granularity || t('chart.runtime.kpiPeriodFallback') })\n }\n className=\"dc:cursor-help\"\n >\n <Icon\n icon={infoCircleIcon}\n className=\"dc:w-4 dc:h-4 text-dc-text-muted dc:opacity-70\"\n />\n </span>\n ) : null;\n\n // Compact layout: fixed type scale, delta beside the value rather than below\n // it, and no histogram — so several KPIs line up as a metric strip.\n if (displayConfig.layout === 'compact') {\n return (\n <KpiCompactLayout\n containerRef={containerRef}\n label={resolveDisplayLabel(getFieldLabel(valueField), valueField)}\n labelAdornment={periodAdornment}\n value={formatNumber(lastValue)}\n suffix={displayConfig.suffix && !displayConfig.formatValue ? displayConfig.suffix : undefined}\n valueAdornment={\n <span\n className=\"dc:font-semibold dc:whitespace-nowrap\"\n style={{ color: currentColor, fontSize: '15px' }}\n >\n {isPositiveChange ? \"\\u25B2\" : \"\\u25BC\"} {isPositiveChange ? \"+\" : \"\"}\n {percentageChange.toFixed(1)}%\n </span>\n }\n detail={displayConfig.showBaseline\n ? `${formatNumber(previousValue)} \\u2192 ${formatNumber(lastValue)}`\n : undefined}\n />\n );\n }\n\n return (\n <div\n ref={containerRef}\n className=\"dc:flex dc:flex-col dc:items-center dc:justify-center dc:w-full dc:h-full dc:p-4\"\n style={kpiHeightStyle(height)}\n >\n {/* Field Label */}\n <div\n className=\"text-dc-text-secondary dc:font-bold dc:text-center dc:mb-2 dc:flex dc:items-center dc:justify-center dc:gap-1\"\n style={{\n fontSize: \"14px\",\n lineHeight: \"1.2\",\n }}\n >\n <span>\n {resolveDisplayLabel(getFieldLabel(valueField), valueField)}\n </span>\n {periodAdornment}\n </div>\n\n {/* Main KPI Value and Delta */}\n <div className=\"dc:flex dc:items-center dc:justify-center dc:space-x-4 dc:mb-2\">\n {/* Main KPI Value */}\n <div\n ref={valueRef}\n className=\"dc:font-bold dc:leading-none\"\n style={{\n fontSize: `${fontSize}px`,\n color: \"var(--dc-text)\", // Keep main value neutral\n }}\n >\n {formatNumber(lastValue)}\n </div>\n\n {/* Delta Information */}\n <div className=\"dc:flex dc:items-center dc:space-x-1\">\n {/* Arrow */}\n <div\n className=\"dc:font-bold\"\n style={{\n color: currentColor,\n fontSize: `${fontSize * 0.35}px`,\n }}\n >\n {isPositiveChange ? \"▲\" : \"▼\"}\n </div>\n\n {/* Delta Values */}\n <div className=\"dc:text-left\">\n <div\n className=\"dc:font-bold dc:leading-tight\"\n style={{\n fontSize: `${fontSize * 0.35}px`,\n color: currentColor,\n }}\n >\n {isPositiveChange ? \"+\" : \"\"}\n {formatNumber(absoluteChange)}\n </div>\n <div\n className=\"dc:font-semibold dc:leading-tight\"\n style={{\n fontSize: `${fontSize * 0.28}px`,\n color: currentColor,\n opacity: 0.8,\n }}\n >\n {isPositiveChange ? \"+\" : \"\"}\n {percentageChange.toFixed(1)}%\n </div>\n </div>\n </div>\n </div>\n\n {/* Unit/Suffix (hidden when formatValue is provided) */}\n {displayConfig.suffix && !displayConfig.formatValue && (\n <div\n className=\"text-dc-text-muted dc:text-center dc:mb-3\"\n style={{\n fontSize: \"14px\",\n lineHeight: \"1.2\",\n opacity: 0.8,\n }}\n >\n {displayConfig.suffix}\n </div>\n )}\n\n {/* Variance Histogram */}\n {displayConfig.showHistogram !== false && values.length > 2 && (\n <div className=\"dc:mt-2 dc:w-full dc:flex dc:justify-center dc:overflow-hidden\">\n <VarianceHistogram\n values={values}\n lastValue={lastValue}\n positiveColor={positiveColor}\n negativeColor={negativeColor}\n formatValue={formatNumber}\n width={textWidth}\n height={64}\n />\n </div>\n )}\n </div>\n );\n})\n\nexport default KpiDelta\n"],"mappings":"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"}
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { n as e } from "./rolldown-runtime-DArdT4gl.js";
|
|
2
|
-
import { D as t, O as n } from "./chart-data-table-
|
|
3
|
-
import { P as r, S as i } from "./chart-activity-grid-
|
|
4
|
-
import { C as a, T as o, w as s } from "./chart-area-
|
|
5
|
-
import { a as c, c as l, d as u, f as d, i as f, l as p, m, n as h, o as g, p as _, r as v, s as ee, u as y } from "./chart-kpi-delta-
|
|
2
|
+
import { D as t, O as n } from "./chart-data-table-SNSqZj9B.js";
|
|
3
|
+
import { P as r, S as i } from "./chart-activity-grid-DVOhLgxv.js";
|
|
4
|
+
import { C as a, T as o, w as s } from "./chart-area-C-5ATxvi.js";
|
|
5
|
+
import { a as c, c as l, d as u, f as d, i as f, l as p, m, n as h, o as g, p as _, r as v, s as ee, u as y } from "./chart-kpi-delta-BJCOXE-F.js";
|
|
6
6
|
import b, { useCallback as x, useMemo as S } from "react";
|
|
7
7
|
import { Fragment as te, jsx as C, jsxs as w } from "react/jsx-runtime";
|
|
8
8
|
//#region src/client/components/DataHistogram.tsx
|
|
@@ -260,4 +260,4 @@ var O = /* @__PURE__ */ e({ default: () => k }), k = b.memo(function({ data: e,
|
|
|
260
260
|
//#endregion
|
|
261
261
|
export { D as n, O as t };
|
|
262
262
|
|
|
263
|
-
//# sourceMappingURL=chart-kpi-number-
|
|
263
|
+
//# sourceMappingURL=chart-kpi-number-Cdqgv20g.js.map
|
package/dist/client/chunks/{chart-kpi-number-OmRCPf7D.js.map → chart-kpi-number-Cdqgv20g.js.map}
RENAMED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"chart-kpi-number-OmRCPf7D.js","names":[],"sources":["../../../src/client/components/DataHistogram.tsx","../../../src/client/components/charts/KpiNumber.tsx"],"sourcesContent":["\nimport { useTranslation } from '../hooks/useTranslation.js'\n\ninterface DataHistogramProps {\n /** Array of numeric values to create histogram from */\n values: number[]\n /** Minimum value in the dataset */\n min: number\n /** Maximum value in the dataset */\n max: number\n /** Color for the histogram bars */\n color?: string\n /** Number of buckets/bars to create (default: 12) */\n bucketCount?: number\n /** Height of the histogram in pixels (default: 32) */\n height?: number\n /** Format function for min/max labels */\n formatValue?: (value: number) => string\n /** Width of the histogram to match text above */\n width?: number\n /** Whether to show average indicator line (default: true) */\n showAverageIndicator?: boolean\n /** Target value to show as green line */\n targetValue?: number\n}\n\n/**\n * Distribute values into histogram buckets and derive summary stats.\n */\nfunction buildHistogram(values: number[], min: number, max: number, bucketCount: number) {\n const buckets = new Array(bucketCount).fill(0)\n const range = max - min\n\n values.forEach(value => {\n if (range === 0) {\n // All values are the same, put everything in middle bucket\n buckets[Math.floor(bucketCount / 2)]++\n } else {\n // Calculate which bucket this value belongs to\n let bucketIndex = Math.floor(((value - min) / range) * (bucketCount - 1))\n // Clamp to valid bucket range\n bucketIndex = Math.max(0, Math.min(bucketCount - 1, bucketIndex))\n buckets[bucketIndex]++\n }\n })\n\n const maxBucketCount = Math.max(...buckets)\n const average = values.reduce((sum, val) => sum + val, 0) / values.length\n const averagePosition = range === 0 ? 50 : ((average - min) / range) * 100\n\n return { buckets, range, maxBucketCount, average, averagePosition }\n}\n\ninterface IndicatorLineProps {\n position: number\n clamp?: boolean\n color: string\n zIndex: number\n title: string\n}\n\n/** Vertical indicator line (average / target) with a small triangle marker. */\nfunction IndicatorLine({ position, clamp = false, color, zIndex, title }: IndicatorLineProps) {\n const left = clamp ? Math.max(0, Math.min(100, position)) : position\n return (\n <div\n className=\"dc:absolute dc:top-0 dc:bottom-0 dc:pointer-events-none\"\n style={{\n left: `${left}%`,\n transform: 'translateX(-50%)',\n width: '2px',\n backgroundColor: color,\n opacity: 0.8,\n zIndex\n }}\n title={title}\n >\n <div\n className=\"dc:absolute dc:-top-1\"\n style={{\n left: '50%',\n transform: 'translateX(-50%)',\n width: '0',\n height: '0',\n borderLeft: '4px solid transparent',\n borderRight: '4px solid transparent',\n borderTop: `6px solid ${color}`\n }}\n />\n </div>\n )\n}\n\n/**\n * Reusable histogram component that shows the distribution of actual data values\n */\nexport default function DataHistogram({\n values,\n min,\n max,\n color = '#1f2937',\n bucketCount = 12,\n height = 32,\n formatValue = (val) => val.toString(),\n width,\n showAverageIndicator = true,\n targetValue\n}: DataHistogramProps) {\n const { t } = useTranslation()\n\n const { buckets, range, maxBucketCount, average, averagePosition } = buildHistogram(\n values,\n min,\n max,\n bucketCount\n )\n\n // Calculate target position if target value is provided\n const targetPosition = targetValue !== undefined && range > 0\n ? ((targetValue - min) / range) * 100\n : null\n\n const sizingStyle = {\n width: width ? `${width}px` : '200px',\n minWidth: '200px'\n }\n\n return (\n <div className=\"dc:flex dc:flex-col dc:items-center\">\n {/* Horizontal bars representing actual data distribution */}\n <div\n className=\"dc:relative dc:flex dc:items-end dc:justify-center dc:space-x-0.5\"\n style={{ height: `${height}px`, ...sizingStyle }}\n >\n {buckets.map((count, i) => {\n // Normalize height based on actual data frequency\n const normalizedHeight = maxBucketCount > 0 ? count / maxBucketCount : 0\n const minHeight = 0.1 // minimum height for empty buckets\n const displayHeight = count > 0 ? Math.max(minHeight, normalizedHeight) : minHeight\n\n return (\n <div\n key={i}\n className=\"dc:flex-1 dc:rounded-t-sm dc:transition-all dc:duration-300 dc:ease-out\"\n style={{\n height: `${displayHeight * height}px`,\n backgroundColor: color,\n opacity: count > 0 ? 0.7 + (normalizedHeight * 0.3) : 0.2 // higher opacity for buckets with data\n }}\n title={`${count} values in this range`} // tooltip showing actual count\n />\n )\n })}\n\n {/* Average indicator line */}\n {showAverageIndicator && (\n <IndicatorLine\n position={averagePosition}\n color=\"#ef4444\"\n zIndex={10}\n title={`Average: ${formatValue(average)}`}\n />\n )}\n\n {/* Target indicator line */}\n {targetPosition !== null && targetValue !== undefined && (\n <IndicatorLine\n position={targetPosition}\n clamp\n color=\"#10b981\"\n zIndex={11}\n title={`Target: ${formatValue(targetValue)}`}\n />\n )}\n </div>\n\n {/* Min/Max values aligned with histogram width */}\n <div\n className=\"dc:flex dc:justify-between dc:mt-2 dc:text-xs text-dc-text-muted\"\n style={sizingStyle}\n >\n <span>{formatValue(min)}</span>\n <span>{formatValue(max)}</span>\n </div>\n\n {/* Average indicator */}\n <div className=\"dc:text-center dc:mt-1 dc:text-xs text-dc-text-muted\">\n {t('dataHistogram.average', { count: values.length })}\n </div>\n </div>\n )\n}\n","import React, { useMemo, useCallback } from 'react'\nimport { useTranslation } from '../../hooks/useTranslation.js'\nimport { Icon } from '@iconify/react'\nimport infoCircleIcon from '@iconify-icons/tabler/info-circle'\nimport { useCubeFieldLabel } from '../../hooks/useCubeFieldLabel.js'\nimport DataHistogram from '../DataHistogram.js'\nimport { parseTargetValues, calculateVariance, formatVariance } from '../../utils/targetUtils.js'\nimport { filterIncompletePeriod } from '../../utils/periodUtils.js'\nimport {\n getKpiValueFields,\n sortKpiData,\n extractKpiValues,\n computeKpiStats,\n formatKpiNumber,\n resolveValueColor,\n resolveVarianceColor,\n resolveDisplayLabel\n} from './KpiNumber.helpers.js'\nimport { useKpiDimensions } from './useKpiDimensions.js'\nimport { KpiCenteredState, kpiHeightStyle } from './KpiStates.js'\nimport KpiCompactLayout from './KpiCompactLayout.js'\nimport type { ChartProps } from '../../types.js'\n\nconst KpiNumber = React.memo(function KpiNumber({\n data,\n chartConfig,\n displayConfig = {},\n queryObject,\n height = \"100%\",\n colorPalette\n}: ChartProps) {\n const { t } = useTranslation()\n\n // Use specialized hook to avoid re-renders from unrelated context changes\n const getFieldLabel = useCubeFieldLabel()\n\n // Extract value field from chart config - handle both string and array formats\n const valueFields = useMemo(\n () => getKpiValueFields(chartConfig?.yAxis),\n [chartConfig?.yAxis]\n )\n\n const valueField = valueFields[0] || '' // Use first measure field\n\n // Get time dimension field if present (for incomplete period filtering)\n const timeDimensionField = queryObject?.timeDimensions?.[0]?.dimension || undefined\n\n // Memoize sorted data to prevent recalculation on every render\n const sortedData = useMemo(\n () => sortKpiData(data, timeDimensionField),\n [data, timeDimensionField]\n )\n\n // Filter out incomplete or last period if enabled\n const { useLastCompletePeriod = true, skipLastPeriod = false } = displayConfig\n\n // Memoize filtered data\n const {\n filteredData,\n excludedIncompletePeriod,\n skippedLastPeriod,\n granularity\n } = useMemo(() => {\n if (sortedData.length === 0) {\n return { filteredData: [], excludedIncompletePeriod: false, skippedLastPeriod: false, granularity: undefined }\n }\n return filterIncompletePeriod(sortedData, timeDimensionField, queryObject, useLastCompletePeriod, skipLastPeriod)\n }, [sortedData, timeDimensionField, queryObject, useLastCompletePeriod, skipLastPeriod])\n\n // Use filtered data for calculations\n const dataToUse = filteredData\n\n // Memoize value extraction to prevent recalculation\n const values = useMemo(\n () => extractKpiValues(dataToUse, valueField),\n [dataToUse, valueField]\n )\n\n // Memoize statistics calculations\n const { avg, min, max } = useMemo(() => computeKpiStats(values), [values])\n\n // Memoize format function to prevent re-creating on every render\n const formatNumber = useCallback(\n (value: number | null | undefined): string => formatKpiNumber(value, displayConfig),\n [displayConfig]\n )\n\n const mainValue = values.length === 1 ? values[0] : avg\n const showStats = values.length > 1\n\n // Memoize color calculation to prevent re-creating function on every render\n const valueColor = useMemo(\n (): string => resolveValueColor(displayConfig.valueColorIndex, colorPalette?.colors),\n [displayConfig.valueColorIndex, colorPalette?.colors]\n )\n\n // Process target values for variance calculation\n const targetValues = useMemo(() => parseTargetValues(displayConfig?.target || ''), [displayConfig?.target])\n const targetValue = targetValues.length > 0 ? targetValues[0] : null // Use first target value\n const variance = targetValue !== null && values.length > 0 ? calculateVariance(mainValue, targetValue) : null\n\n // Memoize variance color calculation\n const varianceColor = useMemo(\n (): string => resolveVarianceColor(\n variance,\n displayConfig.positiveColorIndex,\n displayConfig.negativeColorIndex,\n colorPalette?.colors\n ),\n [variance, displayConfig.positiveColorIndex, displayConfig.negativeColorIndex, colorPalette?.colors]\n )\n\n const isCompact = displayConfig.layout === 'compact'\n\n // Calculate font size and text width based on container dimensions\n const { containerRef, valueRef, fontSize, textWidth } = useKpiDimensions({\n widthDivisor: 5,\n heightDivisor: 4, // More conservative to leave room for label\n minFontSize: 24,\n maxFontSize: 120, // Lower max to ensure label fits\n // Ensure we have a minimum width and use container width as fallback\n measureWidth: (measuredWidth, containerWidth) =>\n Math.max(measuredWidth, Math.min(containerWidth * 0.6, 300)),\n deps: [data, chartConfig]\n })\n\n // Early returns AFTER all hooks\n if (!data || data.length === 0) {\n return (\n <KpiCenteredState\n height={height}\n title={t('chart.runtime.noData')}\n hint={t('chart.runtime.noDataHint.kpi')}\n />\n )\n }\n\n if (valueFields.length === 0) {\n return (\n <KpiCenteredState\n height={height}\n variant=\"danger\"\n title={t('chart.runtime.configError')}\n hint={t('chart.runtime.configErrorHint.noMeasures')}\n />\n )\n }\n\n const periodAdornment = (excludedIncompletePeriod || skippedLastPeriod)\n ? (\n <span\n title={skippedLastPeriod\n ? t('chart.runtime.kpiExcludesLastPeriod', { period: granularity || t('chart.runtime.kpiPeriodFallback') })\n : t('chart.runtime.kpiExcludesIncompletePeriod', { period: granularity || t('chart.runtime.kpiPeriodFallback') })}\n className=\"dc:cursor-help\"\n >\n <Icon icon={infoCircleIcon} className=\"dc:w-4 dc:h-4 text-dc-text-muted dc:opacity-70\" />\n </span>\n )\n : null\n\n // Compact layout: fixed type scale, no container-derived sizing and no\n // histogram, so a row of KPIs reads as a tidy metric strip.\n if (isCompact) {\n return (\n <KpiCompactLayout\n containerRef={containerRef}\n label={resolveDisplayLabel(getFieldLabel(valueField), valueField)}\n labelAdornment={periodAdornment}\n value={values.length === 0 ? '—' : formatNumber(mainValue)}\n valueColor={values.length === 0 ? undefined : valueColor}\n suffix={displayConfig.suffix && !displayConfig.formatValue ? displayConfig.suffix : undefined}\n detail={targetValue !== null && variance !== null\n ? (\n <>\n <span className=\"dc:font-semibold\" style={{ color: varianceColor }}>\n {formatVariance(variance, 1)}\n </span>\n {' '}\n {t('chart.runtime.kpiVsTarget', { target: formatNumber(targetValue) })}\n </>\n )\n : undefined}\n />\n )\n }\n\n // Null handling: If all values are null, show placeholder instead of error\n if (values.length === 0) {\n return (\n <div\n ref={containerRef}\n className=\"dc:flex dc:flex-col dc:items-center dc:justify-center dc:w-full dc:h-full dc:p-4\"\n style={kpiHeightStyle(height)}\n >\n {/* Field Label */}\n <div\n className=\"text-dc-text-secondary dc:font-bold dc:text-center dc:mb-3\"\n style={{\n fontSize: '14px',\n lineHeight: '1.2'\n }}\n >\n {getFieldLabel(valueField)}\n </div>\n\n {/* No Data Placeholder */}\n <div\n className=\"dc:font-bold dc:leading-none text-dc-text-muted\"\n style={{\n fontSize: `${fontSize}px`\n }}\n >\n —\n </div>\n\n <div className=\"dc:text-xs text-dc-text-muted dc:mt-2\">{t('chart.runtime.noDataShort')}</div>\n </div>\n )\n }\n\n return (\n <div\n ref={containerRef}\n className=\"dc:flex dc:flex-col dc:items-center dc:justify-center dc:w-full dc:h-full dc:p-4\"\n style={kpiHeightStyle(height)}\n >\n {/* Field Label - Bolder and bigger */}\n <div\n className=\"text-dc-text-secondary dc:font-bold dc:text-center dc:mb-3 dc:flex dc:items-center dc:justify-center dc:gap-1\"\n style={{\n fontSize: '14px',\n lineHeight: '1.2'\n }}\n >\n <span>\n {resolveDisplayLabel(getFieldLabel(valueField), valueField)}\n </span>\n {periodAdornment}\n </div>\n\n {/* Main KPI Value and Variance - Horizontal layout */}\n <div className=\"dc:flex dc:items-center dc:justify-center dc:gap-4 dc:mb-3\">\n <div\n ref={valueRef}\n className=\"dc:font-bold dc:leading-none\"\n style={{\n fontSize: `${fontSize}px`,\n color: valueColor\n }}\n >\n {formatNumber(mainValue)}\n </div>\n\n {/* Target Variance Display - To the right of main value */}\n {targetValue !== null && variance !== null && (\n <div className=\"dc:flex dc:flex-col dc:items-start\">\n <div\n className=\"dc:font-semibold\"\n style={{\n fontSize: `${Math.max(12, fontSize * 0.3)}px`,\n color: varianceColor,\n lineHeight: '1.2'\n }}\n >\n {formatVariance(variance, 1)}\n </div>\n <div\n className=\"text-dc-text-muted dc:text-xs\"\n style={{\n opacity: 0.7,\n fontSize: `${Math.max(10, fontSize * 0.2)}px`\n }}\n >\n {t('chart.runtime.kpiVsTarget', { target: formatNumber(targetValue) })}\n </div>\n </div>\n )}\n </div>\n\n {/* Unit/Suffix - Larger, not bold (hidden when formatValue is provided) */}\n {displayConfig.suffix && !displayConfig.formatValue && (\n <div\n className=\"text-dc-text-muted dc:text-center\"\n style={{\n fontSize: '14px',\n lineHeight: '1.2',\n opacity: 0.8\n }}\n >\n {displayConfig.suffix}\n </div>\n )}\n\n {/* Data Histogram for multiple values */}\n {showStats && (\n <div className=\"dc:mt-4\">\n <DataHistogram\n values={values}\n min={min}\n max={max}\n color={valueColor}\n formatValue={formatNumber}\n height={24}\n width={textWidth}\n targetValue={targetValue || undefined}\n />\n </div>\n )}\n </div>\n )\n})\n\nexport default KpiNumber\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"chart-kpi-number-Cdqgv20g.js","names":[],"sources":["../../../src/client/components/DataHistogram.tsx","../../../src/client/components/charts/KpiNumber.tsx"],"sourcesContent":["\nimport { useTranslation } from '../hooks/useTranslation.js'\n\ninterface DataHistogramProps {\n /** Array of numeric values to create histogram from */\n values: number[]\n /** Minimum value in the dataset */\n min: number\n /** Maximum value in the dataset */\n max: number\n /** Color for the histogram bars */\n color?: string\n /** Number of buckets/bars to create (default: 12) */\n bucketCount?: number\n /** Height of the histogram in pixels (default: 32) */\n height?: number\n /** Format function for min/max labels */\n formatValue?: (value: number) => string\n /** Width of the histogram to match text above */\n width?: number\n /** Whether to show average indicator line (default: true) */\n showAverageIndicator?: boolean\n /** Target value to show as green line */\n targetValue?: number\n}\n\n/**\n * Distribute values into histogram buckets and derive summary stats.\n */\nfunction buildHistogram(values: number[], min: number, max: number, bucketCount: number) {\n const buckets = new Array(bucketCount).fill(0)\n const range = max - min\n\n values.forEach(value => {\n if (range === 0) {\n // All values are the same, put everything in middle bucket\n buckets[Math.floor(bucketCount / 2)]++\n } else {\n // Calculate which bucket this value belongs to\n let bucketIndex = Math.floor(((value - min) / range) * (bucketCount - 1))\n // Clamp to valid bucket range\n bucketIndex = Math.max(0, Math.min(bucketCount - 1, bucketIndex))\n buckets[bucketIndex]++\n }\n })\n\n const maxBucketCount = Math.max(...buckets)\n const average = values.reduce((sum, val) => sum + val, 0) / values.length\n const averagePosition = range === 0 ? 50 : ((average - min) / range) * 100\n\n return { buckets, range, maxBucketCount, average, averagePosition }\n}\n\ninterface IndicatorLineProps {\n position: number\n clamp?: boolean\n color: string\n zIndex: number\n title: string\n}\n\n/** Vertical indicator line (average / target) with a small triangle marker. */\nfunction IndicatorLine({ position, clamp = false, color, zIndex, title }: IndicatorLineProps) {\n const left = clamp ? Math.max(0, Math.min(100, position)) : position\n return (\n <div\n className=\"dc:absolute dc:top-0 dc:bottom-0 dc:pointer-events-none\"\n style={{\n left: `${left}%`,\n transform: 'translateX(-50%)',\n width: '2px',\n backgroundColor: color,\n opacity: 0.8,\n zIndex\n }}\n title={title}\n >\n <div\n className=\"dc:absolute dc:-top-1\"\n style={{\n left: '50%',\n transform: 'translateX(-50%)',\n width: '0',\n height: '0',\n borderLeft: '4px solid transparent',\n borderRight: '4px solid transparent',\n borderTop: `6px solid ${color}`\n }}\n />\n </div>\n )\n}\n\n/**\n * Reusable histogram component that shows the distribution of actual data values\n */\nexport default function DataHistogram({\n values,\n min,\n max,\n color = '#1f2937',\n bucketCount = 12,\n height = 32,\n formatValue = (val) => val.toString(),\n width,\n showAverageIndicator = true,\n targetValue\n}: DataHistogramProps) {\n const { t } = useTranslation()\n\n const { buckets, range, maxBucketCount, average, averagePosition } = buildHistogram(\n values,\n min,\n max,\n bucketCount\n )\n\n // Calculate target position if target value is provided\n const targetPosition = targetValue !== undefined && range > 0\n ? ((targetValue - min) / range) * 100\n : null\n\n const sizingStyle = {\n width: width ? `${width}px` : '200px',\n minWidth: '200px'\n }\n\n return (\n <div className=\"dc:flex dc:flex-col dc:items-center\">\n {/* Horizontal bars representing actual data distribution */}\n <div\n className=\"dc:relative dc:flex dc:items-end dc:justify-center dc:space-x-0.5\"\n style={{ height: `${height}px`, ...sizingStyle }}\n >\n {buckets.map((count, i) => {\n // Normalize height based on actual data frequency\n const normalizedHeight = maxBucketCount > 0 ? count / maxBucketCount : 0\n const minHeight = 0.1 // minimum height for empty buckets\n const displayHeight = count > 0 ? Math.max(minHeight, normalizedHeight) : minHeight\n\n return (\n <div\n key={i}\n className=\"dc:flex-1 dc:rounded-t-sm dc:transition-all dc:duration-300 dc:ease-out\"\n style={{\n height: `${displayHeight * height}px`,\n backgroundColor: color,\n opacity: count > 0 ? 0.7 + (normalizedHeight * 0.3) : 0.2 // higher opacity for buckets with data\n }}\n title={`${count} values in this range`} // tooltip showing actual count\n />\n )\n })}\n\n {/* Average indicator line */}\n {showAverageIndicator && (\n <IndicatorLine\n position={averagePosition}\n color=\"#ef4444\"\n zIndex={10}\n title={`Average: ${formatValue(average)}`}\n />\n )}\n\n {/* Target indicator line */}\n {targetPosition !== null && targetValue !== undefined && (\n <IndicatorLine\n position={targetPosition}\n clamp\n color=\"#10b981\"\n zIndex={11}\n title={`Target: ${formatValue(targetValue)}`}\n />\n )}\n </div>\n\n {/* Min/Max values aligned with histogram width */}\n <div\n className=\"dc:flex dc:justify-between dc:mt-2 dc:text-xs text-dc-text-muted\"\n style={sizingStyle}\n >\n <span>{formatValue(min)}</span>\n <span>{formatValue(max)}</span>\n </div>\n\n {/* Average indicator */}\n <div className=\"dc:text-center dc:mt-1 dc:text-xs text-dc-text-muted\">\n {t('dataHistogram.average', { count: values.length })}\n </div>\n </div>\n )\n}\n","import React, { useMemo, useCallback } from 'react'\nimport { useTranslation } from '../../hooks/useTranslation.js'\nimport { Icon } from '@iconify/react'\nimport infoCircleIcon from '@iconify-icons/tabler/info-circle'\nimport { useCubeFieldLabel } from '../../hooks/useCubeFieldLabel.js'\nimport DataHistogram from '../DataHistogram.js'\nimport { parseTargetValues, calculateVariance, formatVariance } from '../../utils/targetUtils.js'\nimport { filterIncompletePeriod } from '../../utils/periodUtils.js'\nimport {\n getKpiValueFields,\n sortKpiData,\n extractKpiValues,\n computeKpiStats,\n formatKpiNumber,\n resolveValueColor,\n resolveVarianceColor,\n resolveDisplayLabel\n} from './KpiNumber.helpers.js'\nimport { useKpiDimensions } from './useKpiDimensions.js'\nimport { KpiCenteredState, kpiHeightStyle } from './KpiStates.js'\nimport KpiCompactLayout from './KpiCompactLayout.js'\nimport type { ChartProps } from '../../types.js'\n\nconst KpiNumber = React.memo(function KpiNumber({\n data,\n chartConfig,\n displayConfig = {},\n queryObject,\n height = \"100%\",\n colorPalette\n}: ChartProps) {\n const { t } = useTranslation()\n\n // Use specialized hook to avoid re-renders from unrelated context changes\n const getFieldLabel = useCubeFieldLabel()\n\n // Extract value field from chart config - handle both string and array formats\n const valueFields = useMemo(\n () => getKpiValueFields(chartConfig?.yAxis),\n [chartConfig?.yAxis]\n )\n\n const valueField = valueFields[0] || '' // Use first measure field\n\n // Get time dimension field if present (for incomplete period filtering)\n const timeDimensionField = queryObject?.timeDimensions?.[0]?.dimension || undefined\n\n // Memoize sorted data to prevent recalculation on every render\n const sortedData = useMemo(\n () => sortKpiData(data, timeDimensionField),\n [data, timeDimensionField]\n )\n\n // Filter out incomplete or last period if enabled\n const { useLastCompletePeriod = true, skipLastPeriod = false } = displayConfig\n\n // Memoize filtered data\n const {\n filteredData,\n excludedIncompletePeriod,\n skippedLastPeriod,\n granularity\n } = useMemo(() => {\n if (sortedData.length === 0) {\n return { filteredData: [], excludedIncompletePeriod: false, skippedLastPeriod: false, granularity: undefined }\n }\n return filterIncompletePeriod(sortedData, timeDimensionField, queryObject, useLastCompletePeriod, skipLastPeriod)\n }, [sortedData, timeDimensionField, queryObject, useLastCompletePeriod, skipLastPeriod])\n\n // Use filtered data for calculations\n const dataToUse = filteredData\n\n // Memoize value extraction to prevent recalculation\n const values = useMemo(\n () => extractKpiValues(dataToUse, valueField),\n [dataToUse, valueField]\n )\n\n // Memoize statistics calculations\n const { avg, min, max } = useMemo(() => computeKpiStats(values), [values])\n\n // Memoize format function to prevent re-creating on every render\n const formatNumber = useCallback(\n (value: number | null | undefined): string => formatKpiNumber(value, displayConfig),\n [displayConfig]\n )\n\n const mainValue = values.length === 1 ? values[0] : avg\n const showStats = values.length > 1\n\n // Memoize color calculation to prevent re-creating function on every render\n const valueColor = useMemo(\n (): string => resolveValueColor(displayConfig.valueColorIndex, colorPalette?.colors),\n [displayConfig.valueColorIndex, colorPalette?.colors]\n )\n\n // Process target values for variance calculation\n const targetValues = useMemo(() => parseTargetValues(displayConfig?.target || ''), [displayConfig?.target])\n const targetValue = targetValues.length > 0 ? targetValues[0] : null // Use first target value\n const variance = targetValue !== null && values.length > 0 ? calculateVariance(mainValue, targetValue) : null\n\n // Memoize variance color calculation\n const varianceColor = useMemo(\n (): string => resolveVarianceColor(\n variance,\n displayConfig.positiveColorIndex,\n displayConfig.negativeColorIndex,\n colorPalette?.colors\n ),\n [variance, displayConfig.positiveColorIndex, displayConfig.negativeColorIndex, colorPalette?.colors]\n )\n\n const isCompact = displayConfig.layout === 'compact'\n\n // Calculate font size and text width based on container dimensions\n const { containerRef, valueRef, fontSize, textWidth } = useKpiDimensions({\n widthDivisor: 5,\n heightDivisor: 4, // More conservative to leave room for label\n minFontSize: 24,\n maxFontSize: 120, // Lower max to ensure label fits\n // Ensure we have a minimum width and use container width as fallback\n measureWidth: (measuredWidth, containerWidth) =>\n Math.max(measuredWidth, Math.min(containerWidth * 0.6, 300)),\n deps: [data, chartConfig]\n })\n\n // Early returns AFTER all hooks\n if (!data || data.length === 0) {\n return (\n <KpiCenteredState\n height={height}\n title={t('chart.runtime.noData')}\n hint={t('chart.runtime.noDataHint.kpi')}\n />\n )\n }\n\n if (valueFields.length === 0) {\n return (\n <KpiCenteredState\n height={height}\n variant=\"danger\"\n title={t('chart.runtime.configError')}\n hint={t('chart.runtime.configErrorHint.noMeasures')}\n />\n )\n }\n\n const periodAdornment = (excludedIncompletePeriod || skippedLastPeriod)\n ? (\n <span\n title={skippedLastPeriod\n ? t('chart.runtime.kpiExcludesLastPeriod', { period: granularity || t('chart.runtime.kpiPeriodFallback') })\n : t('chart.runtime.kpiExcludesIncompletePeriod', { period: granularity || t('chart.runtime.kpiPeriodFallback') })}\n className=\"dc:cursor-help\"\n >\n <Icon icon={infoCircleIcon} className=\"dc:w-4 dc:h-4 text-dc-text-muted dc:opacity-70\" />\n </span>\n )\n : null\n\n // Compact layout: fixed type scale, no container-derived sizing and no\n // histogram, so a row of KPIs reads as a tidy metric strip.\n if (isCompact) {\n return (\n <KpiCompactLayout\n containerRef={containerRef}\n label={resolveDisplayLabel(getFieldLabel(valueField), valueField)}\n labelAdornment={periodAdornment}\n value={values.length === 0 ? '—' : formatNumber(mainValue)}\n valueColor={values.length === 0 ? undefined : valueColor}\n suffix={displayConfig.suffix && !displayConfig.formatValue ? displayConfig.suffix : undefined}\n detail={targetValue !== null && variance !== null\n ? (\n <>\n <span className=\"dc:font-semibold\" style={{ color: varianceColor }}>\n {formatVariance(variance, 1)}\n </span>\n {' '}\n {t('chart.runtime.kpiVsTarget', { target: formatNumber(targetValue) })}\n </>\n )\n : undefined}\n />\n )\n }\n\n // Null handling: If all values are null, show placeholder instead of error\n if (values.length === 0) {\n return (\n <div\n ref={containerRef}\n className=\"dc:flex dc:flex-col dc:items-center dc:justify-center dc:w-full dc:h-full dc:p-4\"\n style={kpiHeightStyle(height)}\n >\n {/* Field Label */}\n <div\n className=\"text-dc-text-secondary dc:font-bold dc:text-center dc:mb-3\"\n style={{\n fontSize: '14px',\n lineHeight: '1.2'\n }}\n >\n {getFieldLabel(valueField)}\n </div>\n\n {/* No Data Placeholder */}\n <div\n className=\"dc:font-bold dc:leading-none text-dc-text-muted\"\n style={{\n fontSize: `${fontSize}px`\n }}\n >\n —\n </div>\n\n <div className=\"dc:text-xs text-dc-text-muted dc:mt-2\">{t('chart.runtime.noDataShort')}</div>\n </div>\n )\n }\n\n return (\n <div\n ref={containerRef}\n className=\"dc:flex dc:flex-col dc:items-center dc:justify-center dc:w-full dc:h-full dc:p-4\"\n style={kpiHeightStyle(height)}\n >\n {/* Field Label - Bolder and bigger */}\n <div\n className=\"text-dc-text-secondary dc:font-bold dc:text-center dc:mb-3 dc:flex dc:items-center dc:justify-center dc:gap-1\"\n style={{\n fontSize: '14px',\n lineHeight: '1.2'\n }}\n >\n <span>\n {resolveDisplayLabel(getFieldLabel(valueField), valueField)}\n </span>\n {periodAdornment}\n </div>\n\n {/* Main KPI Value and Variance - Horizontal layout */}\n <div className=\"dc:flex dc:items-center dc:justify-center dc:gap-4 dc:mb-3\">\n <div\n ref={valueRef}\n className=\"dc:font-bold dc:leading-none\"\n style={{\n fontSize: `${fontSize}px`,\n color: valueColor\n }}\n >\n {formatNumber(mainValue)}\n </div>\n\n {/* Target Variance Display - To the right of main value */}\n {targetValue !== null && variance !== null && (\n <div className=\"dc:flex dc:flex-col dc:items-start\">\n <div\n className=\"dc:font-semibold\"\n style={{\n fontSize: `${Math.max(12, fontSize * 0.3)}px`,\n color: varianceColor,\n lineHeight: '1.2'\n }}\n >\n {formatVariance(variance, 1)}\n </div>\n <div\n className=\"text-dc-text-muted dc:text-xs\"\n style={{\n opacity: 0.7,\n fontSize: `${Math.max(10, fontSize * 0.2)}px`\n }}\n >\n {t('chart.runtime.kpiVsTarget', { target: formatNumber(targetValue) })}\n </div>\n </div>\n )}\n </div>\n\n {/* Unit/Suffix - Larger, not bold (hidden when formatValue is provided) */}\n {displayConfig.suffix && !displayConfig.formatValue && (\n <div\n className=\"text-dc-text-muted dc:text-center\"\n style={{\n fontSize: '14px',\n lineHeight: '1.2',\n opacity: 0.8\n }}\n >\n {displayConfig.suffix}\n </div>\n )}\n\n {/* Data Histogram for multiple values */}\n {showStats && (\n <div className=\"dc:mt-4\">\n <DataHistogram\n values={values}\n min={min}\n max={max}\n color={valueColor}\n formatValue={formatNumber}\n height={24}\n width={textWidth}\n targetValue={targetValue || undefined}\n />\n </div>\n )}\n </div>\n )\n})\n\nexport default KpiNumber\n"],"mappings":"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"}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { n as e } from "./rolldown-runtime-DArdT4gl.js";
|
|
2
|
-
import { P as t, S as n } from "./chart-activity-grid-
|
|
3
|
-
import { n as r } from "./chart-kpi-number-
|
|
2
|
+
import { P as t, S as n } from "./chart-activity-grid-DVOhLgxv.js";
|
|
3
|
+
import { n as r } from "./chart-kpi-number-Cdqgv20g.js";
|
|
4
4
|
import i, { useEffect as a, useRef as o, useState as s } from "react";
|
|
5
5
|
import { jsx as c, jsxs as l } from "react/jsx-runtime";
|
|
6
6
|
//#region src/client/components/charts/kpiTextHelpers.ts
|
|
@@ -185,4 +185,4 @@ var g = /* @__PURE__ */ e({ default: () => _ }), _ = i.memo(function({ data: e,
|
|
|
185
185
|
//#endregion
|
|
186
186
|
export { g as t };
|
|
187
187
|
|
|
188
|
-
//# sourceMappingURL=chart-kpi-text-
|
|
188
|
+
//# sourceMappingURL=chart-kpi-text-B10ZHCsI.js.map
|
package/dist/client/chunks/{chart-kpi-text-BUm932QA.js.map → chart-kpi-text-B10ZHCsI.js.map}
RENAMED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"chart-kpi-text-BUm932QA.js","names":[],"sources":["../../../src/client/components/charts/kpiTextHelpers.ts","../../../src/client/components/charts/KpiText.tsx"],"sourcesContent":["import type { ColorPalette } from '../../types.js'\n\n/**\n * Co-located pure helpers for KpiText: value extraction, statistics, number\n * formatting, template processing, and colour resolution. Extracted so the\n * component body stays focused on dimensioning + render. Pure extraction — no\n * behaviour change.\n */\n\n/** Extract the configured measure fields from a chart config (string or array). */\nexport function extractValueFields(yAxis: string | string[] | undefined): string[] {\n if (!yAxis) return []\n if (typeof yAxis === 'string') return [yAxis]\n if (Array.isArray(yAxis)) return yAxis\n return []\n}\n\n/**\n * Extract the values for a field from each row, falling back to the first\n * available field when the configured field is absent. Null/undefined dropped.\n */\nexport function extractValues(data: Record<string, any>[], valueField: string): any[] {\n return data\n .map(row => {\n if (row[valueField] !== undefined) return row[valueField]\n const availableFields = Object.keys(row)\n if (availableFields.length > 0) return row[availableFields[0]]\n return undefined\n })\n .filter(val => val !== null && val !== undefined)\n}\n\nexport interface KpiStats {\n mainValue: any\n min: number | null\n max: number | null\n showStats: boolean\n}\n\n/**\n * Reduce extracted values to the displayed main value plus optional min/max\n * statistics. Numeric multi-value sets average and expose min/max; non-numeric\n * sets concatenate.\n */\nexport function computeKpiStats(values: any[]): KpiStats {\n const numericValues = values.map(val => Number(val)).filter(val => !isNaN(val))\n\n if (numericValues.length > 0) {\n if (values.length === 1) {\n return { mainValue: values[0], min: null, max: null, showStats: false }\n }\n const sum = numericValues.reduce((acc, val) => acc + val, 0)\n return {\n mainValue: sum / numericValues.length,\n min: Math.min(...numericValues),\n max: Math.max(...numericValues),\n showStats: true\n }\n }\n\n return {\n mainValue: values.length === 1 ? values[0] : values.join(', '),\n min: null,\n max: null,\n showStats: false\n }\n}\n\ninterface NumberFormatOptions {\n formatValue?: (value: any) => string\n decimals?: number\n}\n\n/** Format a number with K/M/B abbreviation, honouring a custom formatter. */\nexport function formatKpiNumber(value: number | null | undefined, options: NumberFormatOptions): string {\n if (options.formatValue) {\n return options.formatValue(value)\n }\n if (value === null || value === undefined) {\n return '—'\n }\n\n const decimals = options.decimals ?? 2\n const abs = Math.abs(value)\n if (abs >= 1e9) return (value / 1e9).toFixed(decimals) + 'B'\n if (abs >= 1e6) return (value / 1e6).toFixed(decimals) + 'M'\n if (abs >= 1e3) return (value / 1e3).toFixed(decimals) + 'K'\n return value.toFixed(decimals)\n}\n\ninterface TemplateContext {\n value: any\n valueField: string\n fieldLabel: string\n min: number | null\n max: number | null\n count: number\n formatNumber: (value: number | null | undefined) => string\n}\n\n/** Replace `${var}` tokens in a KPI template, falling back to the raw value. */\nexport function processKpiTemplate(template: string, ctx: TemplateContext): string {\n const { value, valueField, fieldLabel, min, max, count, formatNumber } = ctx\n try {\n const templateVars = {\n value: typeof value === 'number' ? formatNumber(value) : String(value),\n rawValue: value,\n field: valueField,\n fieldLabel,\n min: min !== null ? formatNumber(min) : '',\n max: max !== null ? formatNumber(max) : '',\n count\n }\n return template.replace(/\\$\\{(\\w+)\\}/g, (match, varName) => {\n if (varName in templateVars) {\n return String(templateVars[varName as keyof typeof templateVars])\n }\n return match\n })\n } catch {\n return String(value)\n }\n}\n\n/** Resolve the KPI text colour from the palette by index, with a fallback. */\nexport function resolveValueColor(\n valueColorIndex: number | undefined,\n colorPalette: ColorPalette | undefined\n): string {\n if (valueColorIndex !== undefined && colorPalette?.colors) {\n if (valueColorIndex >= 0 && valueColorIndex < colorPalette.colors.length) {\n return colorPalette.colors[valueColorIndex]\n }\n }\n return colorPalette?.colors?.[0] || '#1f2937'\n}\n","import React, { useState, useRef, useEffect } from 'react'\nimport { useTranslation } from '../../hooks/useTranslation.js'\nimport { useCubeFieldLabel } from '../../hooks/useCubeFieldLabel.js'\nimport DataHistogram from '../DataHistogram.js'\nimport {\n extractValueFields,\n extractValues,\n computeKpiStats,\n formatKpiNumber,\n processKpiTemplate,\n resolveValueColor\n} from './kpiTextHelpers.js'\nimport type { ChartProps } from '../../types.js'\n\nconst KpiText = React.memo(function KpiText({\n data,\n chartConfig,\n displayConfig = {},\n height = \"100%\",\n colorPalette\n}: ChartProps) {\n const { t } = useTranslation()\n const [fontSize, setFontSize] = useState(28)\n const [textWidth, setTextWidth] = useState(0)\n const containerRef = useRef<HTMLDivElement>(null)\n const textRef = useRef<HTMLDivElement>(null)\n // Use specialized hook to avoid re-renders from unrelated context changes\n const getFieldLabel = useCubeFieldLabel()\n\n // Calculate font size and text width based on container dimensions\n useEffect(() => {\n const updateDimensions = () => {\n if (containerRef.current) {\n const container = containerRef.current\n const rect = container.getBoundingClientRect()\n const containerWidth = rect.width\n const containerHeight = rect.height\n \n if (containerWidth > 0 && containerHeight > 0) {\n // Calculate font size based on container dimensions\n // For KPI Text displays, text might be longer so use more conservative sizing\n // Also reserve space for potential statistics display\n const widthBasedSize = containerWidth / 8\n const heightBasedSize = containerHeight / 5\n const baseFontSize = Math.min(widthBasedSize, heightBasedSize)\n const clampedFontSize = Math.max(18, Math.min(baseFontSize, 80))\n setFontSize(clampedFontSize)\n }\n }\n \n // Measure the text width after font size is set\n if (textRef.current) {\n const textRect = textRef.current.getBoundingClientRect()\n setTextWidth(textRect.width)\n }\n }\n\n // Initial calculation after a short delay to ensure the container is fully rendered\n const timer = setTimeout(updateDimensions, 100)\n \n const resizeObserver = new ResizeObserver(() => {\n // Debounce the resize updates\n clearTimeout(timer)\n setTimeout(updateDimensions, 50)\n })\n \n if (containerRef.current) {\n resizeObserver.observe(containerRef.current)\n }\n\n return () => {\n clearTimeout(timer)\n resizeObserver.disconnect()\n }\n }, [data, chartConfig])\n\n if (!data || data.length === 0) {\n return (\n <div\n className=\"dc:flex dc:items-center dc:justify-center dc:w-full dc:h-full\"\n style={{\n height: height === \"100%\" ? \"100%\" : height,\n minHeight: height === \"100%\" ? '200px' : undefined\n }}\n >\n <div className=\"dc:text-center text-dc-text-muted\">\n <div className=\"dc:text-sm dc:font-semibold dc:mb-1\">{t('chart.runtime.noData')}</div>\n <div className=\"dc:text-xs text-dc-text-secondary\">{t('chart.runtime.noDataHint.kpi')}</div>\n </div>\n </div>\n )\n }\n\n // Extract value field from chart config - handle both string and array formats\n const valueFields = extractValueFields(chartConfig?.yAxis)\n\n if (valueFields.length === 0) {\n return (\n <div\n className=\"dc:flex dc:items-center dc:justify-center dc:w-full dc:h-full\"\n style={{\n height: height === \"100%\" ? \"100%\" : height,\n minHeight: height === \"100%\" ? '200px' : undefined,\n backgroundColor: 'var(--dc-danger-bg)',\n color: 'var(--dc-danger)',\n borderColor: 'var(--dc-danger-border)'\n }}\n >\n <div className=\"dc:text-center\">\n <div className=\"dc:text-sm dc:font-semibold dc:mb-1\">{t('chart.runtime.configError')}</div>\n <div className=\"dc:text-xs\">{t('chart.runtime.configErrorHint.noMeasures')}</div>\n </div>\n </div>\n )\n }\n\n const valueField = valueFields[0] // Use first measure field\n\n // Extract values for the selected field\n const values = extractValues(data as Record<string, any>[], valueField)\n\n if (values.length === 0) {\n return (\n <div\n className=\"dc:flex dc:items-center dc:justify-center dc:w-full dc:h-full\"\n style={{\n height: height === \"100%\" ? \"100%\" : height,\n minHeight: height === \"100%\" ? '200px' : undefined,\n backgroundColor: 'var(--dc-warning-bg)',\n color: 'var(--dc-warning)',\n borderColor: 'var(--dc-warning-border)'\n }}\n >\n <div className=\"dc:text-center\">\n <div className=\"dc:text-sm dc:font-semibold dc:mb-1\">{t('chart.runtime.noValidData')}</div>\n <div className=\"dc:text-xs\">{t('chart.runtime.noValidDataHint.kpiText')}</div>\n </div>\n </div>\n )\n }\n\n // Calculate statistics for numeric values\n const { mainValue, min, max, showStats } = computeKpiStats(values)\n\n // Format number with appropriate units and decimals\n const formatNumber = (value: number | null | undefined): string =>\n formatKpiNumber(value, { formatValue: displayConfig.formatValue, decimals: displayConfig.decimals })\n\n const template = displayConfig.template || '${fieldLabel}: ${value}'\n const displayText = processKpiTemplate(template, {\n value: mainValue,\n valueField,\n fieldLabel: getFieldLabel(valueField),\n min,\n max,\n count: values.length,\n formatNumber\n })\n\n // Get color from palette by index, default to first color in palette\n const valueColor = resolveValueColor(displayConfig.valueColorIndex, colorPalette)\n\n return (\n <div \n ref={containerRef}\n className=\"dc:flex dc:flex-col dc:items-center dc:justify-center dc:w-full dc:h-full dc:p-4\"\n style={{ \n height: height === \"100%\" ? \"100%\" : height,\n minHeight: height === \"100%\" ? '200px' : undefined\n }}\n >\n {/* Main KPI Text */}\n <div \n ref={textRef}\n className=\"dc:font-bold dc:leading-tight dc:text-center\"\n style={{ \n fontSize: `${fontSize}px`,\n color: valueColor \n }}\n >\n {displayText}\n </div>\n\n {/* Data Histogram for multiple values */}\n {showStats && min !== null && max !== null && (\n <div className=\"dc:mt-4\">\n <DataHistogram\n values={values}\n min={min}\n max={max}\n color={valueColor}\n formatValue={formatNumber}\n height={24}\n width={textWidth || 200}\n />\n </div>\n )}\n </div>\n )\n})\n\nexport default KpiText"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"chart-kpi-text-B10ZHCsI.js","names":[],"sources":["../../../src/client/components/charts/kpiTextHelpers.ts","../../../src/client/components/charts/KpiText.tsx"],"sourcesContent":["import type { ColorPalette } from '../../types.js'\n\n/**\n * Co-located pure helpers for KpiText: value extraction, statistics, number\n * formatting, template processing, and colour resolution. Extracted so the\n * component body stays focused on dimensioning + render. Pure extraction — no\n * behaviour change.\n */\n\n/** Extract the configured measure fields from a chart config (string or array). */\nexport function extractValueFields(yAxis: string | string[] | undefined): string[] {\n if (!yAxis) return []\n if (typeof yAxis === 'string') return [yAxis]\n if (Array.isArray(yAxis)) return yAxis\n return []\n}\n\n/**\n * Extract the values for a field from each row, falling back to the first\n * available field when the configured field is absent. Null/undefined dropped.\n */\nexport function extractValues(data: Record<string, any>[], valueField: string): any[] {\n return data\n .map(row => {\n if (row[valueField] !== undefined) return row[valueField]\n const availableFields = Object.keys(row)\n if (availableFields.length > 0) return row[availableFields[0]]\n return undefined\n })\n .filter(val => val !== null && val !== undefined)\n}\n\nexport interface KpiStats {\n mainValue: any\n min: number | null\n max: number | null\n showStats: boolean\n}\n\n/**\n * Reduce extracted values to the displayed main value plus optional min/max\n * statistics. Numeric multi-value sets average and expose min/max; non-numeric\n * sets concatenate.\n */\nexport function computeKpiStats(values: any[]): KpiStats {\n const numericValues = values.map(val => Number(val)).filter(val => !isNaN(val))\n\n if (numericValues.length > 0) {\n if (values.length === 1) {\n return { mainValue: values[0], min: null, max: null, showStats: false }\n }\n const sum = numericValues.reduce((acc, val) => acc + val, 0)\n return {\n mainValue: sum / numericValues.length,\n min: Math.min(...numericValues),\n max: Math.max(...numericValues),\n showStats: true\n }\n }\n\n return {\n mainValue: values.length === 1 ? values[0] : values.join(', '),\n min: null,\n max: null,\n showStats: false\n }\n}\n\ninterface NumberFormatOptions {\n formatValue?: (value: any) => string\n decimals?: number\n}\n\n/** Format a number with K/M/B abbreviation, honouring a custom formatter. */\nexport function formatKpiNumber(value: number | null | undefined, options: NumberFormatOptions): string {\n if (options.formatValue) {\n return options.formatValue(value)\n }\n if (value === null || value === undefined) {\n return '—'\n }\n\n const decimals = options.decimals ?? 2\n const abs = Math.abs(value)\n if (abs >= 1e9) return (value / 1e9).toFixed(decimals) + 'B'\n if (abs >= 1e6) return (value / 1e6).toFixed(decimals) + 'M'\n if (abs >= 1e3) return (value / 1e3).toFixed(decimals) + 'K'\n return value.toFixed(decimals)\n}\n\ninterface TemplateContext {\n value: any\n valueField: string\n fieldLabel: string\n min: number | null\n max: number | null\n count: number\n formatNumber: (value: number | null | undefined) => string\n}\n\n/** Replace `${var}` tokens in a KPI template, falling back to the raw value. */\nexport function processKpiTemplate(template: string, ctx: TemplateContext): string {\n const { value, valueField, fieldLabel, min, max, count, formatNumber } = ctx\n try {\n const templateVars = {\n value: typeof value === 'number' ? formatNumber(value) : String(value),\n rawValue: value,\n field: valueField,\n fieldLabel,\n min: min !== null ? formatNumber(min) : '',\n max: max !== null ? formatNumber(max) : '',\n count\n }\n return template.replace(/\\$\\{(\\w+)\\}/g, (match, varName) => {\n if (varName in templateVars) {\n return String(templateVars[varName as keyof typeof templateVars])\n }\n return match\n })\n } catch {\n return String(value)\n }\n}\n\n/** Resolve the KPI text colour from the palette by index, with a fallback. */\nexport function resolveValueColor(\n valueColorIndex: number | undefined,\n colorPalette: ColorPalette | undefined\n): string {\n if (valueColorIndex !== undefined && colorPalette?.colors) {\n if (valueColorIndex >= 0 && valueColorIndex < colorPalette.colors.length) {\n return colorPalette.colors[valueColorIndex]\n }\n }\n return colorPalette?.colors?.[0] || '#1f2937'\n}\n","import React, { useState, useRef, useEffect } from 'react'\nimport { useTranslation } from '../../hooks/useTranslation.js'\nimport { useCubeFieldLabel } from '../../hooks/useCubeFieldLabel.js'\nimport DataHistogram from '../DataHistogram.js'\nimport {\n extractValueFields,\n extractValues,\n computeKpiStats,\n formatKpiNumber,\n processKpiTemplate,\n resolveValueColor\n} from './kpiTextHelpers.js'\nimport type { ChartProps } from '../../types.js'\n\nconst KpiText = React.memo(function KpiText({\n data,\n chartConfig,\n displayConfig = {},\n height = \"100%\",\n colorPalette\n}: ChartProps) {\n const { t } = useTranslation()\n const [fontSize, setFontSize] = useState(28)\n const [textWidth, setTextWidth] = useState(0)\n const containerRef = useRef<HTMLDivElement>(null)\n const textRef = useRef<HTMLDivElement>(null)\n // Use specialized hook to avoid re-renders from unrelated context changes\n const getFieldLabel = useCubeFieldLabel()\n\n // Calculate font size and text width based on container dimensions\n useEffect(() => {\n const updateDimensions = () => {\n if (containerRef.current) {\n const container = containerRef.current\n const rect = container.getBoundingClientRect()\n const containerWidth = rect.width\n const containerHeight = rect.height\n \n if (containerWidth > 0 && containerHeight > 0) {\n // Calculate font size based on container dimensions\n // For KPI Text displays, text might be longer so use more conservative sizing\n // Also reserve space for potential statistics display\n const widthBasedSize = containerWidth / 8\n const heightBasedSize = containerHeight / 5\n const baseFontSize = Math.min(widthBasedSize, heightBasedSize)\n const clampedFontSize = Math.max(18, Math.min(baseFontSize, 80))\n setFontSize(clampedFontSize)\n }\n }\n \n // Measure the text width after font size is set\n if (textRef.current) {\n const textRect = textRef.current.getBoundingClientRect()\n setTextWidth(textRect.width)\n }\n }\n\n // Initial calculation after a short delay to ensure the container is fully rendered\n const timer = setTimeout(updateDimensions, 100)\n \n const resizeObserver = new ResizeObserver(() => {\n // Debounce the resize updates\n clearTimeout(timer)\n setTimeout(updateDimensions, 50)\n })\n \n if (containerRef.current) {\n resizeObserver.observe(containerRef.current)\n }\n\n return () => {\n clearTimeout(timer)\n resizeObserver.disconnect()\n }\n }, [data, chartConfig])\n\n if (!data || data.length === 0) {\n return (\n <div\n className=\"dc:flex dc:items-center dc:justify-center dc:w-full dc:h-full\"\n style={{\n height: height === \"100%\" ? \"100%\" : height,\n minHeight: height === \"100%\" ? '200px' : undefined\n }}\n >\n <div className=\"dc:text-center text-dc-text-muted\">\n <div className=\"dc:text-sm dc:font-semibold dc:mb-1\">{t('chart.runtime.noData')}</div>\n <div className=\"dc:text-xs text-dc-text-secondary\">{t('chart.runtime.noDataHint.kpi')}</div>\n </div>\n </div>\n )\n }\n\n // Extract value field from chart config - handle both string and array formats\n const valueFields = extractValueFields(chartConfig?.yAxis)\n\n if (valueFields.length === 0) {\n return (\n <div\n className=\"dc:flex dc:items-center dc:justify-center dc:w-full dc:h-full\"\n style={{\n height: height === \"100%\" ? \"100%\" : height,\n minHeight: height === \"100%\" ? '200px' : undefined,\n backgroundColor: 'var(--dc-danger-bg)',\n color: 'var(--dc-danger)',\n borderColor: 'var(--dc-danger-border)'\n }}\n >\n <div className=\"dc:text-center\">\n <div className=\"dc:text-sm dc:font-semibold dc:mb-1\">{t('chart.runtime.configError')}</div>\n <div className=\"dc:text-xs\">{t('chart.runtime.configErrorHint.noMeasures')}</div>\n </div>\n </div>\n )\n }\n\n const valueField = valueFields[0] // Use first measure field\n\n // Extract values for the selected field\n const values = extractValues(data as Record<string, any>[], valueField)\n\n if (values.length === 0) {\n return (\n <div\n className=\"dc:flex dc:items-center dc:justify-center dc:w-full dc:h-full\"\n style={{\n height: height === \"100%\" ? \"100%\" : height,\n minHeight: height === \"100%\" ? '200px' : undefined,\n backgroundColor: 'var(--dc-warning-bg)',\n color: 'var(--dc-warning)',\n borderColor: 'var(--dc-warning-border)'\n }}\n >\n <div className=\"dc:text-center\">\n <div className=\"dc:text-sm dc:font-semibold dc:mb-1\">{t('chart.runtime.noValidData')}</div>\n <div className=\"dc:text-xs\">{t('chart.runtime.noValidDataHint.kpiText')}</div>\n </div>\n </div>\n )\n }\n\n // Calculate statistics for numeric values\n const { mainValue, min, max, showStats } = computeKpiStats(values)\n\n // Format number with appropriate units and decimals\n const formatNumber = (value: number | null | undefined): string =>\n formatKpiNumber(value, { formatValue: displayConfig.formatValue, decimals: displayConfig.decimals })\n\n const template = displayConfig.template || '${fieldLabel}: ${value}'\n const displayText = processKpiTemplate(template, {\n value: mainValue,\n valueField,\n fieldLabel: getFieldLabel(valueField),\n min,\n max,\n count: values.length,\n formatNumber\n })\n\n // Get color from palette by index, default to first color in palette\n const valueColor = resolveValueColor(displayConfig.valueColorIndex, colorPalette)\n\n return (\n <div \n ref={containerRef}\n className=\"dc:flex dc:flex-col dc:items-center dc:justify-center dc:w-full dc:h-full dc:p-4\"\n style={{ \n height: height === \"100%\" ? \"100%\" : height,\n minHeight: height === \"100%\" ? '200px' : undefined\n }}\n >\n {/* Main KPI Text */}\n <div \n ref={textRef}\n className=\"dc:font-bold dc:leading-tight dc:text-center\"\n style={{ \n fontSize: `${fontSize}px`,\n color: valueColor \n }}\n >\n {displayText}\n </div>\n\n {/* Data Histogram for multiple values */}\n {showStats && min !== null && max !== null && (\n <div className=\"dc:mt-4\">\n <DataHistogram\n values={values}\n min={min}\n max={max}\n color={valueColor}\n formatValue={formatNumber}\n height={24}\n width={textWidth || 200}\n />\n </div>\n )}\n </div>\n )\n})\n\nexport default KpiText"],"mappings":"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"}
|