drizzle-cube 0.4.35 → 0.4.37

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (74) hide show
  1. package/dist/adapters/express/index.cjs +1 -1
  2. package/dist/adapters/express/index.js +3 -3
  3. package/dist/adapters/fastify/index.cjs +1 -1
  4. package/dist/adapters/fastify/index.js +3 -3
  5. package/dist/adapters/{handler-DzUX7CBs.cjs → handler-dHHEEbG9.cjs} +1 -1
  6. package/dist/adapters/{handler-J55KQKe5.js → handler-fto6TSVn.js} +1 -1
  7. package/dist/adapters/hono/index.cjs +1 -1
  8. package/dist/adapters/hono/index.js +3 -3
  9. package/dist/adapters/{mcp-transport-Dmf2vbIb.cjs → mcp-transport-ro4OL4BW.cjs} +4 -4
  10. package/dist/adapters/{mcp-transport-Cdisv8il.js → mcp-transport-tB5a7Het.js} +71 -18
  11. package/dist/adapters/nextjs/index.cjs +1 -1
  12. package/dist/adapters/nextjs/index.js +3 -3
  13. package/dist/adapters/{utils-CDExg9uP.cjs → utils-2MwxiQ2o.cjs} +1 -1
  14. package/dist/adapters/{utils-DkKcpkx5.js → utils-_2o905Fi.js} +2 -1
  15. package/dist/adapters/utils.cjs +1 -1
  16. package/dist/adapters/utils.d.ts +2 -0
  17. package/dist/adapters/utils.js +1 -1
  18. package/dist/client/charts.js +3 -3
  19. package/dist/client/chunks/{DashboardEditModal-DsxYiMct.js → DashboardEditModal-B083DSGL.js} +7 -7
  20. package/dist/client/chunks/DashboardEditModal-B083DSGL.js.map +1 -0
  21. package/dist/client/chunks/{FieldSearchModal-CSzgVPdG.js → FieldSearchModal-BxQ5JhWz.js} +2 -2
  22. package/dist/client/chunks/{FieldSearchModal-CSzgVPdG.js.map → FieldSearchModal-BxQ5JhWz.js.map} +1 -1
  23. package/dist/client/chunks/{analysis-builder-CxXZDfFR.js → analysis-builder-BFb3zjl5.js} +1783 -2561
  24. package/dist/client/chunks/analysis-builder-BFb3zjl5.js.map +1 -0
  25. package/dist/client/chunks/{analysis-builder-shared-fwyLiZ48.js → analysis-builder-shared-BFzd2jx_.js} +1498 -716
  26. package/dist/client/chunks/analysis-builder-shared-BFzd2jx_.js.map +1 -0
  27. package/dist/client/chunks/{chart-data-table-CGG-_R93.js → chart-data-table-CW_qZDpt.js} +12 -2
  28. package/dist/client/chunks/chart-data-table-CW_qZDpt.js.map +1 -0
  29. package/dist/client/chunks/{chart-kpi-delta-D9eNhKR6.js → chart-kpi-delta-BPexzOe7.js} +2 -2
  30. package/dist/client/chunks/{chart-kpi-delta-D9eNhKR6.js.map → chart-kpi-delta-BPexzOe7.js.map} +1 -1
  31. package/dist/client/chunks/{chart-kpi-number-IJWReEix.js → chart-kpi-number-BBtGBtZL.js} +3 -3
  32. package/dist/client/chunks/{chart-kpi-number-IJWReEix.js.map → chart-kpi-number-BBtGBtZL.js.map} +1 -1
  33. package/dist/client/chunks/{chart-kpi-text-Dsr7nPmz.js → chart-kpi-text-BqHhmJEB.js} +2 -2
  34. package/dist/client/chunks/{chart-kpi-text-Dsr7nPmz.js.map → chart-kpi-text-BqHhmJEB.js.map} +1 -1
  35. package/dist/client/chunks/{charts-loader-DSdXpr7e.js → charts-loader-gZjOqZwG.js} +5 -5
  36. package/dist/client/chunks/{charts-loader-DSdXpr7e.js.map → charts-loader-gZjOqZwG.js.map} +1 -1
  37. package/dist/client/chunks/{schema-visualization-CBQrcxb8.js → schema-visualization-XLFzV_7p.js} +2 -2
  38. package/dist/client/chunks/{schema-visualization-CBQrcxb8.js.map → schema-visualization-XLFzV_7p.js.map} +1 -1
  39. package/dist/client/chunks/{useDebounce-BbYqb134.js → useDebounce-C_wstEud.js} +2 -2
  40. package/dist/client/chunks/{useDebounce-BbYqb134.js.map → useDebounce-C_wstEud.js.map} +1 -1
  41. package/dist/client/chunks/{useExplainAI-CbAR-1p5.js → useExplainAI-C9ytXRIC.js} +21 -18
  42. package/dist/client/chunks/{useExplainAI-CbAR-1p5.js.map → useExplainAI-C9ytXRIC.js.map} +1 -1
  43. package/dist/client/chunks/{utils-BxLeWUeg.js → utils-3FNmeZJR.js} +2 -2
  44. package/dist/client/chunks/{utils-BxLeWUeg.js.map → utils-3FNmeZJR.js.map} +1 -1
  45. package/dist/client/chunks/{vendor-QBbbDL5y.js → vendor-BPRWulB7.js} +2 -2
  46. package/dist/client/chunks/{vendor-QBbbDL5y.js.map → vendor-BPRWulB7.js.map} +1 -1
  47. package/dist/client/components/AIAssistant/utils.d.ts +1 -1
  48. package/dist/client/components/DataBrowser/DataBrowserSidebar.d.ts +16 -0
  49. package/dist/client/components/DataBrowser/DataBrowserTable.d.ts +16 -0
  50. package/dist/client/components/DataBrowser/DataBrowserToolbar.d.ts +22 -0
  51. package/dist/client/components/DataBrowser/index.d.ts +28 -0
  52. package/dist/client/components.js +3 -3
  53. package/dist/client/hooks/useDataBrowser.d.ts +67 -0
  54. package/dist/client/hooks.js +3 -3
  55. package/dist/client/icons/types.d.ts +1 -0
  56. package/dist/client/icons.js +1 -1
  57. package/dist/client/index.d.ts +3 -0
  58. package/dist/client/index.js +774 -192
  59. package/dist/client/index.js.map +1 -1
  60. package/dist/client/providers/CubeApiProvider.d.ts +1 -0
  61. package/dist/client/providers.js +1 -1
  62. package/dist/client/stores/dataBrowserStore.d.ts +34 -0
  63. package/dist/client/styles.css +1 -1
  64. package/dist/client/types.d.ts +2 -0
  65. package/dist/client/utils.js +3 -3
  66. package/dist/client-bundle-stats.html +1 -1
  67. package/dist/server/index.cjs +3 -3
  68. package/dist/server/index.d.ts +18 -0
  69. package/dist/server/index.js +69 -16
  70. package/package.json +1 -1
  71. package/dist/client/chunks/DashboardEditModal-DsxYiMct.js.map +0 -1
  72. package/dist/client/chunks/analysis-builder-CxXZDfFR.js.map +0 -1
  73. package/dist/client/chunks/analysis-builder-shared-fwyLiZ48.js.map +0 -1
  74. package/dist/client/chunks/chart-data-table-CGG-_R93.js.map +0 -1
@@ -1,5 +1,5 @@
1
1
  import { n as e } from "./rolldown-runtime-lc2dmIiU.js";
2
- import { b as t, x as n } from "./chart-data-table-CGG-_R93.js";
2
+ import { b as t, x as n } from "./chart-data-table-CW_qZDpt.js";
3
3
  import { _ as r } from "./chart-activity-grid-wR2Twpo7.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";
@@ -340,4 +340,4 @@ var g = i.memo(function({ data: e, chartConfig: i, displayConfig: u = {}, queryO
340
340
  //#endregion
341
341
  export { d as a, f as i, p as n, u as r, m as t };
342
342
 
343
- //# sourceMappingURL=chart-kpi-delta-D9eNhKR6.js.map
343
+ //# sourceMappingURL=chart-kpi-delta-BPexzOe7.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"chart-kpi-delta-D9eNhKR6.js","names":[],"sources":["../../../src/client/utils/periodUtils.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","import React, { useState, useRef, useEffect } from \"react\";\nimport { Icon } from \"@iconify/react\";\nimport infoCircleIcon from \"@iconify-icons/tabler/info-circle\";\nimport { useCubeFieldLabel } from \"../../hooks/useCubeFieldLabel\";\nimport { filterIncompletePeriod } from \"../../utils/periodUtils\";\nimport type { ChartProps } from \"../../types\";\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 rounded-xs\"\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 [fontSize, setFontSize] = useState(32);\n const [textWidth, setTextWidth] = useState(250);\n const containerRef = useRef<HTMLDivElement>(null);\n const valueRef = 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 const widthBasedSize = containerWidth / 4;\n const heightBasedSize = containerHeight / 4;\n const baseFontSize = Math.min(widthBasedSize, heightBasedSize);\n const clampedFontSize = Math.max(28, Math.min(baseFontSize, 140));\n setFontSize(clampedFontSize);\n\n setTimeout(() => {\n if (valueRef.current) {\n const textRect = valueRef.current.getBoundingClientRect();\n const measuredWidth = textRect.width;\n // Scale histogram width with container, accounting for labels on the right (~60px)\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 setTextWidth(Math.max(100, effectiveWidth)); // Minimum 100px\n }\n }, 10);\n }\n }\n };\n\n const timer = setTimeout(updateDimensions, 50);\n\n const resizeObserver = new ResizeObserver(() => {\n setTimeout(updateDimensions, 10);\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\">No data available</div>\n <div className=\"dc:text-xs text-dc-text-secondary\">\n No data points to display\n </div>\n </div>\n </div>\n );\n }\n\n // Extract value and dimension fields from chart config\n let valueFields: string[] = [];\n let dimensionFields: string[] = [];\n\n if (chartConfig?.yAxis) {\n valueFields = Array.isArray(chartConfig.yAxis)\n ? chartConfig.yAxis\n : [chartConfig.yAxis];\n }\n\n if (chartConfig?.xAxis) {\n dimensionFields = Array.isArray(chartConfig.xAxis)\n ? chartConfig.xAxis\n : [chartConfig.xAxis];\n }\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\">Configuration Error</div>\n <div className=\"dc:text-xs\">No measure field configured</div>\n </div>\n </div>\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 let sortedData = [...data];\n if (dimensionField) {\n sortedData = sortedData.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 // 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 = dataToUse\n .map((row) => row[valueField])\n .filter((val) => val !== null && val !== undefined && !isNaN(Number(val)))\n .map((val) => Number(val));\n\n if (values.length < 2) {\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\">Insufficient Data</div>\n <div className=\"dc:text-xs\">\n Delta calculation requires at least 2 data points\n </div>\n <div className=\"dc:text-xs\">Current data points: {values.length}</div>\n </div>\n </div>\n );\n }\n\n // Calculate delta between last and second-last values\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\n ? (absoluteChange / Math.abs(secondLastValue)) * 100\n : 0;\n\n const isPositiveChange = absoluteChange >= 0;\n\n // Format number with appropriate units and decimals\n const formatNumber = (value: number | null | undefined): string => {\n // If custom formatValue is provided, use it exclusively\n if (displayConfig.formatValue) {\n return displayConfig.formatValue(value);\n }\n\n // Null handling: Show placeholder for missing data\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 // Get colors from palette\n const getPositiveColor = (): string => {\n if (\n displayConfig.positiveColorIndex !== undefined &&\n colorPalette?.colors\n ) {\n const colorIndex = displayConfig.positiveColorIndex;\n if (colorIndex >= 0 && colorIndex < colorPalette.colors.length) {\n return colorPalette.colors[colorIndex];\n }\n }\n return \"#10b981\"; // Default green\n };\n\n const getNegativeColor = (): string => {\n if (\n displayConfig.negativeColorIndex !== undefined &&\n colorPalette?.colors\n ) {\n const colorIndex = displayConfig.negativeColorIndex;\n if (colorIndex >= 0 && colorIndex < colorPalette.colors.length) {\n return colorPalette.colors[colorIndex];\n }\n }\n return \"#ef4444\"; // Default red\n };\n\n const positiveColor = getPositiveColor();\n const negativeColor = getNegativeColor();\n const currentColor = isPositiveChange ? positiveColor : negativeColor;\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 {/* 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 {(() => {\n const label = getFieldLabel(valueField);\n return label && label.length > 1 ? label : valueField;\n })()}\n </span>\n {(excludedIncompletePeriod || skippedLastPeriod) && (\n <span\n title={\n skippedLastPeriod\n ? `Excludes last ${granularity || \"period\"}`\n : `Excludes current incomplete ${granularity}`\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 )}\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-BPexzOe7.js","names":[],"sources":["../../../src/client/utils/periodUtils.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","import React, { useState, useRef, useEffect } from \"react\";\nimport { Icon } from \"@iconify/react\";\nimport infoCircleIcon from \"@iconify-icons/tabler/info-circle\";\nimport { useCubeFieldLabel } from \"../../hooks/useCubeFieldLabel\";\nimport { filterIncompletePeriod } from \"../../utils/periodUtils\";\nimport type { ChartProps } from \"../../types\";\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 rounded-xs\"\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 [fontSize, setFontSize] = useState(32);\n const [textWidth, setTextWidth] = useState(250);\n const containerRef = useRef<HTMLDivElement>(null);\n const valueRef = 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 const widthBasedSize = containerWidth / 4;\n const heightBasedSize = containerHeight / 4;\n const baseFontSize = Math.min(widthBasedSize, heightBasedSize);\n const clampedFontSize = Math.max(28, Math.min(baseFontSize, 140));\n setFontSize(clampedFontSize);\n\n setTimeout(() => {\n if (valueRef.current) {\n const textRect = valueRef.current.getBoundingClientRect();\n const measuredWidth = textRect.width;\n // Scale histogram width with container, accounting for labels on the right (~60px)\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 setTextWidth(Math.max(100, effectiveWidth)); // Minimum 100px\n }\n }, 10);\n }\n }\n };\n\n const timer = setTimeout(updateDimensions, 50);\n\n const resizeObserver = new ResizeObserver(() => {\n setTimeout(updateDimensions, 10);\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\">No data available</div>\n <div className=\"dc:text-xs text-dc-text-secondary\">\n No data points to display\n </div>\n </div>\n </div>\n );\n }\n\n // Extract value and dimension fields from chart config\n let valueFields: string[] = [];\n let dimensionFields: string[] = [];\n\n if (chartConfig?.yAxis) {\n valueFields = Array.isArray(chartConfig.yAxis)\n ? chartConfig.yAxis\n : [chartConfig.yAxis];\n }\n\n if (chartConfig?.xAxis) {\n dimensionFields = Array.isArray(chartConfig.xAxis)\n ? chartConfig.xAxis\n : [chartConfig.xAxis];\n }\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\">Configuration Error</div>\n <div className=\"dc:text-xs\">No measure field configured</div>\n </div>\n </div>\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 let sortedData = [...data];\n if (dimensionField) {\n sortedData = sortedData.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 // 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 = dataToUse\n .map((row) => row[valueField])\n .filter((val) => val !== null && val !== undefined && !isNaN(Number(val)))\n .map((val) => Number(val));\n\n if (values.length < 2) {\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\">Insufficient Data</div>\n <div className=\"dc:text-xs\">\n Delta calculation requires at least 2 data points\n </div>\n <div className=\"dc:text-xs\">Current data points: {values.length}</div>\n </div>\n </div>\n );\n }\n\n // Calculate delta between last and second-last values\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\n ? (absoluteChange / Math.abs(secondLastValue)) * 100\n : 0;\n\n const isPositiveChange = absoluteChange >= 0;\n\n // Format number with appropriate units and decimals\n const formatNumber = (value: number | null | undefined): string => {\n // If custom formatValue is provided, use it exclusively\n if (displayConfig.formatValue) {\n return displayConfig.formatValue(value);\n }\n\n // Null handling: Show placeholder for missing data\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 // Get colors from palette\n const getPositiveColor = (): string => {\n if (\n displayConfig.positiveColorIndex !== undefined &&\n colorPalette?.colors\n ) {\n const colorIndex = displayConfig.positiveColorIndex;\n if (colorIndex >= 0 && colorIndex < colorPalette.colors.length) {\n return colorPalette.colors[colorIndex];\n }\n }\n return \"#10b981\"; // Default green\n };\n\n const getNegativeColor = (): string => {\n if (\n displayConfig.negativeColorIndex !== undefined &&\n colorPalette?.colors\n ) {\n const colorIndex = displayConfig.negativeColorIndex;\n if (colorIndex >= 0 && colorIndex < colorPalette.colors.length) {\n return colorPalette.colors[colorIndex];\n }\n }\n return \"#ef4444\"; // Default red\n };\n\n const positiveColor = getPositiveColor();\n const negativeColor = getNegativeColor();\n const currentColor = isPositiveChange ? positiveColor : negativeColor;\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 {/* 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 {(() => {\n const label = getFieldLabel(valueField);\n return label && label.length > 1 ? label : valueField;\n })()}\n </span>\n {(excludedIncompletePeriod || skippedLastPeriod) && (\n <span\n title={\n skippedLastPeriod\n ? `Excludes last ${granularity || \"period\"}`\n : `Excludes current incomplete ${granularity}`\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 )}\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-lc2dmIiU.js";
2
- import { b as t, x as n } from "./chart-data-table-CGG-_R93.js";
2
+ import { b as t, x as n } from "./chart-data-table-CW_qZDpt.js";
3
3
  import { _ as r } from "./chart-activity-grid-wR2Twpo7.js";
4
4
  import { a as i, i as a, r as o } from "./chart-area-e9ysnatQ.js";
5
- import { n as s } from "./chart-kpi-delta-D9eNhKR6.js";
5
+ import { n as s } from "./chart-kpi-delta-BPexzOe7.js";
6
6
  import c, { useCallback as l, useEffect as u, useMemo as d, useRef as f, useState as p } from "react";
7
7
  import { jsx as m, jsxs as h } from "react/jsx-runtime";
8
8
  //#region src/client/components/DataHistogram.tsx
@@ -323,4 +323,4 @@ var _ = /* @__PURE__ */ e({ default: () => v }), v = c.memo(function({ data: e,
323
323
  //#endregion
324
324
  export { g as n, _ as t };
325
325
 
326
- //# sourceMappingURL=chart-kpi-number-IJWReEix.js.map
326
+ //# sourceMappingURL=chart-kpi-number-BBtGBtZL.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"chart-kpi-number-IJWReEix.js","names":[],"sources":["../../../src/client/components/DataHistogram.tsx","../../../src/client/components/charts/KpiNumber.tsx"],"sourcesContent":["\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 * 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 // Create histogram buckets from actual data\n const buckets = new Array(bucketCount).fill(0)\n const range = max - min\n \n // Distribute actual values into buckets\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 // Find max bucket count for normalization\n const maxBucketCount = Math.max(...buckets)\n \n // Calculate average for indicator positioning\n const average = values.reduce((sum, val) => sum + val, 0) / values.length\n \n // Calculate average position as percentage of histogram width\n const averagePosition = range === 0 ? 50 : ((average - min) / range) * 100\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 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={{ \n height: `${height}px`,\n width: width ? `${width}px` : '200px',\n minWidth: '200px'\n }}\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 <div\n className=\"dc:absolute dc:top-0 dc:bottom-0 dc:pointer-events-none\"\n style={{\n left: `${averagePosition}%`,\n transform: 'translateX(-50%)',\n width: '2px',\n backgroundColor: '#ef4444',\n opacity: 0.8,\n zIndex: 10\n }}\n title={`Average: ${formatValue(average)}`}\n >\n {/* Small triangle at top to indicate average */}\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 #ef4444'\n }}\n />\n </div>\n )}\n \n {/* Target indicator line */}\n {targetPosition !== null && targetValue !== undefined && (\n <div\n className=\"dc:absolute dc:top-0 dc:bottom-0 dc:pointer-events-none\"\n style={{\n left: `${Math.max(0, Math.min(100, targetPosition))}%`,\n transform: 'translateX(-50%)',\n width: '2px',\n backgroundColor: '#10b981',\n opacity: 0.8,\n zIndex: 11\n }}\n title={`Target: ${formatValue(targetValue)}`}\n >\n {/* Small triangle at top to indicate target */}\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 #10b981'\n }}\n />\n </div>\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={{\n width: width ? `${width}px` : '200px',\n minWidth: '200px'\n }}\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 Average of {values.length} values\n </div>\n </div>\n )\n}","import React, { useState, useRef, useEffect, useMemo, useCallback } from 'react'\nimport { Icon } from '@iconify/react'\nimport infoCircleIcon from '@iconify-icons/tabler/info-circle'\nimport { useCubeFieldLabel } from '../../hooks/useCubeFieldLabel'\nimport DataHistogram from '../DataHistogram'\nimport { parseTargetValues, calculateVariance, formatVariance } from '../../utils/targetUtils'\nimport { filterIncompletePeriod } from '../../utils/periodUtils'\nimport type { ChartProps } from '../../types'\n\nconst KpiNumber = React.memo(function KpiNumber({\n data,\n chartConfig,\n displayConfig = {},\n queryObject,\n height = \"100%\",\n colorPalette\n}: ChartProps) {\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 // 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 if (!chartConfig?.yAxis) return []\n if (typeof chartConfig.yAxis === 'string') return [chartConfig.yAxis]\n if (Array.isArray(chartConfig.yAxis)) return chartConfig.yAxis\n return []\n }, [chartConfig?.yAxis])\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 if (!data || data.length === 0) return []\n let sorted = [...data]\n if (timeDimensionField) {\n sorted = 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 return sorted\n }, [data, timeDimensionField])\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 if (!valueField || dataToUse.length === 0) return []\n\n const rawValues = dataToUse.map(row => {\n // Try direct field access first\n if (row[valueField] !== undefined) {\n return row[valueField]\n }\n\n // If not found, try finding the first numeric field as fallback\n const numericFields = Object.keys(row).filter(key =>\n typeof row[key] === 'number' && !isNaN(row[key])\n )\n\n if (numericFields.length > 0) {\n return row[numericFields[0]]\n }\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 }, [dataToUse, valueField])\n\n // Memoize statistics calculations\n const { avg, min, max } = useMemo(() => {\n if (values.length === 0) return { avg: 0, min: 0, max: 0 }\n const sum = values.reduce((acc, val) => acc + val, 0)\n const avg = sum / values.length\n const min = Math.min(...values)\n const max = Math.max(...values)\n return { avg, min, max }\n }, [values])\n\n // Memoize format function to prevent re-creating on every render\n const formatNumber = useCallback((value: number | null | undefined): string => {\n // If custom formatValue is provided, use it exclusively\n if (displayConfig.formatValue) {\n return displayConfig.formatValue(value)\n }\n\n // Null handling: Show placeholder for missing data\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 }, [displayConfig])\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((): string => {\n if (displayConfig.valueColorIndex !== undefined && colorPalette?.colors) {\n const colorIndex = displayConfig.valueColorIndex\n if (colorIndex >= 0 && colorIndex < colorPalette.colors.length) {\n return colorPalette.colors[colorIndex]\n }\n }\n // Default to first color in palette if available, otherwise fallback to dark gray\n return colorPalette?.colors?.[0] || '#1f2937'\n }, [displayConfig.valueColorIndex, colorPalette?.colors])\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((): string => {\n if (variance === null) return '#6B7280' // Gray for no target\n\n if (variance >= 0) {\n // Positive variance - use positive color from palette\n const positiveIndex = displayConfig.positiveColorIndex ?? 1\n return colorPalette?.colors?.[positiveIndex] || '#10B981' // Green fallback\n } else {\n // Negative variance - use negative color from palette\n const negativeIndex = displayConfig.negativeColorIndex ?? 7\n return colorPalette?.colors?.[negativeIndex] || '#EF4444' // Red fallback\n }\n }, [variance, displayConfig.positiveColorIndex, displayConfig.negativeColorIndex, colorPalette?.colors])\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 displays, we want the text to be large and prominent\n // Reserve space for the label by using more conservative sizing\n const widthBasedSize = containerWidth / 5\n const heightBasedSize = containerHeight / 4 // More conservative to leave room for label\n const baseFontSize = Math.min(widthBasedSize, heightBasedSize)\n const clampedFontSize = Math.max(24, Math.min(baseFontSize, 120)) // Lower max to ensure label fits\n setFontSize(clampedFontSize)\n\n // Use a timeout to measure text width after font size is applied\n setTimeout(() => {\n if (valueRef.current) {\n const textRect = valueRef.current.getBoundingClientRect()\n const measuredWidth = textRect.width\n // Ensure we have a minimum width and use container width as fallback\n const effectiveWidth = Math.max(measuredWidth, Math.min(containerWidth * 0.6, 300))\n setTextWidth(effectiveWidth)\n }\n }, 10)\n }\n }\n }\n\n // Initial calculation - reduce delay for faster initial render\n const timer = setTimeout(updateDimensions, 50)\n\n const resizeObserver = new ResizeObserver(() => {\n // Debounce the resize updates\n setTimeout(updateDimensions, 10)\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 // Early returns AFTER all hooks\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\">No data available</div>\n <div className=\"dc:text-xs text-dc-text-secondary\">No data points to display</div>\n </div>\n </div>\n )\n }\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\">Configuration Error</div>\n <div className=\"dc:text-xs\">No measure fields configured</div>\n </div>\n </div>\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={{\n height: height === \"100%\" ? \"100%\" : height,\n minHeight: height === \"100%\" ? '200px' : undefined\n }}\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\">No data</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={{\n height: height === \"100%\" ? \"100%\" : height,\n minHeight: height === \"100%\" ? '200px' : undefined\n }}\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 {(() => {\n const label = getFieldLabel(valueField)\n // Temporary fix: if label seems wrong, use the field name directly\n const displayLabel = (label && label.length > 1) ? label : valueField\n return displayLabel\n })()}\n </span>\n {(excludedIncompletePeriod || skippedLastPeriod) && (\n <span\n title={skippedLastPeriod\n ? `Excludes last ${granularity || 'period'}`\n : `Excludes current incomplete ${granularity}`}\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 </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 vs {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-BBtGBtZL.js","names":[],"sources":["../../../src/client/components/DataHistogram.tsx","../../../src/client/components/charts/KpiNumber.tsx"],"sourcesContent":["\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 * 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 // Create histogram buckets from actual data\n const buckets = new Array(bucketCount).fill(0)\n const range = max - min\n \n // Distribute actual values into buckets\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 // Find max bucket count for normalization\n const maxBucketCount = Math.max(...buckets)\n \n // Calculate average for indicator positioning\n const average = values.reduce((sum, val) => sum + val, 0) / values.length\n \n // Calculate average position as percentage of histogram width\n const averagePosition = range === 0 ? 50 : ((average - min) / range) * 100\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 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={{ \n height: `${height}px`,\n width: width ? `${width}px` : '200px',\n minWidth: '200px'\n }}\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 <div\n className=\"dc:absolute dc:top-0 dc:bottom-0 dc:pointer-events-none\"\n style={{\n left: `${averagePosition}%`,\n transform: 'translateX(-50%)',\n width: '2px',\n backgroundColor: '#ef4444',\n opacity: 0.8,\n zIndex: 10\n }}\n title={`Average: ${formatValue(average)}`}\n >\n {/* Small triangle at top to indicate average */}\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 #ef4444'\n }}\n />\n </div>\n )}\n \n {/* Target indicator line */}\n {targetPosition !== null && targetValue !== undefined && (\n <div\n className=\"dc:absolute dc:top-0 dc:bottom-0 dc:pointer-events-none\"\n style={{\n left: `${Math.max(0, Math.min(100, targetPosition))}%`,\n transform: 'translateX(-50%)',\n width: '2px',\n backgroundColor: '#10b981',\n opacity: 0.8,\n zIndex: 11\n }}\n title={`Target: ${formatValue(targetValue)}`}\n >\n {/* Small triangle at top to indicate target */}\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 #10b981'\n }}\n />\n </div>\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={{\n width: width ? `${width}px` : '200px',\n minWidth: '200px'\n }}\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 Average of {values.length} values\n </div>\n </div>\n )\n}","import React, { useState, useRef, useEffect, useMemo, useCallback } from 'react'\nimport { Icon } from '@iconify/react'\nimport infoCircleIcon from '@iconify-icons/tabler/info-circle'\nimport { useCubeFieldLabel } from '../../hooks/useCubeFieldLabel'\nimport DataHistogram from '../DataHistogram'\nimport { parseTargetValues, calculateVariance, formatVariance } from '../../utils/targetUtils'\nimport { filterIncompletePeriod } from '../../utils/periodUtils'\nimport type { ChartProps } from '../../types'\n\nconst KpiNumber = React.memo(function KpiNumber({\n data,\n chartConfig,\n displayConfig = {},\n queryObject,\n height = \"100%\",\n colorPalette\n}: ChartProps) {\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 // 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 if (!chartConfig?.yAxis) return []\n if (typeof chartConfig.yAxis === 'string') return [chartConfig.yAxis]\n if (Array.isArray(chartConfig.yAxis)) return chartConfig.yAxis\n return []\n }, [chartConfig?.yAxis])\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 if (!data || data.length === 0) return []\n let sorted = [...data]\n if (timeDimensionField) {\n sorted = 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 return sorted\n }, [data, timeDimensionField])\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 if (!valueField || dataToUse.length === 0) return []\n\n const rawValues = dataToUse.map(row => {\n // Try direct field access first\n if (row[valueField] !== undefined) {\n return row[valueField]\n }\n\n // If not found, try finding the first numeric field as fallback\n const numericFields = Object.keys(row).filter(key =>\n typeof row[key] === 'number' && !isNaN(row[key])\n )\n\n if (numericFields.length > 0) {\n return row[numericFields[0]]\n }\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 }, [dataToUse, valueField])\n\n // Memoize statistics calculations\n const { avg, min, max } = useMemo(() => {\n if (values.length === 0) return { avg: 0, min: 0, max: 0 }\n const sum = values.reduce((acc, val) => acc + val, 0)\n const avg = sum / values.length\n const min = Math.min(...values)\n const max = Math.max(...values)\n return { avg, min, max }\n }, [values])\n\n // Memoize format function to prevent re-creating on every render\n const formatNumber = useCallback((value: number | null | undefined): string => {\n // If custom formatValue is provided, use it exclusively\n if (displayConfig.formatValue) {\n return displayConfig.formatValue(value)\n }\n\n // Null handling: Show placeholder for missing data\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 }, [displayConfig])\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((): string => {\n if (displayConfig.valueColorIndex !== undefined && colorPalette?.colors) {\n const colorIndex = displayConfig.valueColorIndex\n if (colorIndex >= 0 && colorIndex < colorPalette.colors.length) {\n return colorPalette.colors[colorIndex]\n }\n }\n // Default to first color in palette if available, otherwise fallback to dark gray\n return colorPalette?.colors?.[0] || '#1f2937'\n }, [displayConfig.valueColorIndex, colorPalette?.colors])\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((): string => {\n if (variance === null) return '#6B7280' // Gray for no target\n\n if (variance >= 0) {\n // Positive variance - use positive color from palette\n const positiveIndex = displayConfig.positiveColorIndex ?? 1\n return colorPalette?.colors?.[positiveIndex] || '#10B981' // Green fallback\n } else {\n // Negative variance - use negative color from palette\n const negativeIndex = displayConfig.negativeColorIndex ?? 7\n return colorPalette?.colors?.[negativeIndex] || '#EF4444' // Red fallback\n }\n }, [variance, displayConfig.positiveColorIndex, displayConfig.negativeColorIndex, colorPalette?.colors])\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 displays, we want the text to be large and prominent\n // Reserve space for the label by using more conservative sizing\n const widthBasedSize = containerWidth / 5\n const heightBasedSize = containerHeight / 4 // More conservative to leave room for label\n const baseFontSize = Math.min(widthBasedSize, heightBasedSize)\n const clampedFontSize = Math.max(24, Math.min(baseFontSize, 120)) // Lower max to ensure label fits\n setFontSize(clampedFontSize)\n\n // Use a timeout to measure text width after font size is applied\n setTimeout(() => {\n if (valueRef.current) {\n const textRect = valueRef.current.getBoundingClientRect()\n const measuredWidth = textRect.width\n // Ensure we have a minimum width and use container width as fallback\n const effectiveWidth = Math.max(measuredWidth, Math.min(containerWidth * 0.6, 300))\n setTextWidth(effectiveWidth)\n }\n }, 10)\n }\n }\n }\n\n // Initial calculation - reduce delay for faster initial render\n const timer = setTimeout(updateDimensions, 50)\n\n const resizeObserver = new ResizeObserver(() => {\n // Debounce the resize updates\n setTimeout(updateDimensions, 10)\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 // Early returns AFTER all hooks\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\">No data available</div>\n <div className=\"dc:text-xs text-dc-text-secondary\">No data points to display</div>\n </div>\n </div>\n )\n }\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\">Configuration Error</div>\n <div className=\"dc:text-xs\">No measure fields configured</div>\n </div>\n </div>\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={{\n height: height === \"100%\" ? \"100%\" : height,\n minHeight: height === \"100%\" ? '200px' : undefined\n }}\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\">No data</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={{\n height: height === \"100%\" ? \"100%\" : height,\n minHeight: height === \"100%\" ? '200px' : undefined\n }}\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 {(() => {\n const label = getFieldLabel(valueField)\n // Temporary fix: if label seems wrong, use the field name directly\n const displayLabel = (label && label.length > 1) ? label : valueField\n return displayLabel\n })()}\n </span>\n {(excludedIncompletePeriod || skippedLastPeriod) && (\n <span\n title={skippedLastPeriod\n ? `Excludes last ${granularity || 'period'}`\n : `Excludes current incomplete ${granularity}`}\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 </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 vs {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-lc2dmIiU.js";
2
2
  import { _ as t } from "./chart-activity-grid-wR2Twpo7.js";
3
- import { n } from "./chart-kpi-number-IJWReEix.js";
3
+ import { n } from "./chart-kpi-number-BBtGBtZL.js";
4
4
  import r, { useEffect as i, useRef as a, useState as o } from "react";
5
5
  import { jsx as s, jsxs as c } from "react/jsx-runtime";
6
6
  //#region src/client/components/charts/KpiText.tsx
@@ -146,4 +146,4 @@ var l = /* @__PURE__ */ e({ default: () => u }), u = r.memo(function({ data: e,
146
146
  //#endregion
147
147
  export { l as t };
148
148
 
149
- //# sourceMappingURL=chart-kpi-text-Dsr7nPmz.js.map
149
+ //# sourceMappingURL=chart-kpi-text-BqHhmJEB.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"chart-kpi-text-Dsr7nPmz.js","names":[],"sources":["../../../src/client/components/charts/KpiText.tsx"],"sourcesContent":["import React, { useState, useRef, useEffect } from 'react'\nimport { useCubeFieldLabel } from '../../hooks/useCubeFieldLabel'\nimport DataHistogram from '../DataHistogram'\nimport type { ChartProps } from '../../types'\n\nconst KpiText = React.memo(function KpiText({ \n data, \n chartConfig, \n displayConfig = {},\n height = \"100%\",\n colorPalette\n}: ChartProps) {\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\">No data available</div>\n <div className=\"dc:text-xs text-dc-text-secondary\">No data points to display</div>\n </div>\n </div>\n )\n }\n\n // Extract value field from chart config - handle both string and array formats\n let valueFields: string[] = []\n if (chartConfig?.yAxis) {\n // Handle both string and array formats\n if (typeof chartConfig.yAxis === 'string') {\n valueFields = [chartConfig.yAxis]\n } else if (Array.isArray(chartConfig.yAxis)) {\n valueFields = chartConfig.yAxis\n }\n }\n \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\">Configuration Error</div>\n <div className=\"dc:text-xs\">No measure fields configured</div>\n </div>\n </div>\n )\n }\n\n const valueField = valueFields[0] // Use first measure field\n \n // Debug logging (remove in production)\n \n // Extract values for the selected field\n const values = data\n .map(row => {\n // Try direct field access first\n if (row[valueField] !== undefined) {\n return row[valueField]\n }\n \n // If not found, try finding the first available field as fallback\n const availableFields = Object.keys(row)\n if (availableFields.length > 0) {\n // Field not found, using fallback\n return row[availableFields[0]]\n }\n \n return undefined\n })\n .filter(val => val !== null && val !== undefined)\n \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\">No valid data</div>\n <div className=\"dc:text-xs\">All values are null or invalid</div>\n </div>\n </div>\n )\n }\n\n // Calculate statistics for numeric values\n const numericValues = values\n .map(val => Number(val))\n .filter(val => !isNaN(val))\n\n let mainValue: any\n let min: number | null = null\n let max: number | null = null\n let showStats = false\n\n if (numericValues.length > 0) {\n if (values.length === 1) {\n mainValue = values[0]\n } else {\n // Calculate average for multiple numeric values\n const sum = numericValues.reduce((acc, val) => acc + val, 0)\n const avg = sum / numericValues.length\n mainValue = avg\n min = Math.min(...numericValues)\n max = Math.max(...numericValues)\n showStats = true\n }\n } else {\n // Non-numeric values - just use the first one or concatenate if multiple\n mainValue = values.length === 1 ? values[0] : values.join(', ')\n }\n\n // Format number with appropriate units and decimals\n const formatNumber = (value: number | null | undefined): string => {\n // If custom formatValue is provided, use it exclusively\n if (displayConfig.formatValue) {\n return displayConfig.formatValue(value)\n }\n\n // Null handling: Show placeholder for missing data\n if (value === null || value === undefined) {\n return '—'\n }\n\n const decimals = displayConfig.decimals ?? 2\n\n if (Math.abs(value) >= 1e9) {\n return (value / 1e9).toFixed(decimals) + 'B'\n } else if (Math.abs(value) >= 1e6) {\n return (value / 1e6).toFixed(decimals) + 'M'\n } else if (Math.abs(value) >= 1e3) {\n return (value / 1e3).toFixed(decimals) + 'K'\n } else {\n return value.toFixed(decimals)\n }\n }\n\n // Process template string\n const processTemplate = (template: string, value: any): string => {\n try {\n // Create template variables\n const templateVars = {\n value: typeof value === 'number' ? formatNumber(value) : String(value),\n rawValue: value,\n field: valueField,\n fieldLabel: getFieldLabel(valueField),\n min: min !== null ? formatNumber(min) : '',\n max: max !== null ? formatNumber(max) : '',\n count: values.length\n }\n\n // Simple template replacement using ${variable} syntax\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 // Error processing template\n return String(value)\n }\n }\n\n const template = displayConfig.template || '${fieldLabel}: ${value}'\n const displayText = processTemplate(template, mainValue)\n\n // Get color from palette by index, default to first color in palette\n const getValueColor = (): string => {\n if (displayConfig.valueColorIndex !== undefined && colorPalette?.colors) {\n const colorIndex = displayConfig.valueColorIndex\n if (colorIndex >= 0 && colorIndex < colorPalette.colors.length) {\n return colorPalette.colors[colorIndex]\n }\n }\n // Default to first color in palette if available, otherwise fallback to dark gray\n return colorPalette?.colors?.[0] || '#1f2937'\n }\n\n const valueColor = getValueColor()\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-BqHhmJEB.js","names":[],"sources":["../../../src/client/components/charts/KpiText.tsx"],"sourcesContent":["import React, { useState, useRef, useEffect } from 'react'\nimport { useCubeFieldLabel } from '../../hooks/useCubeFieldLabel'\nimport DataHistogram from '../DataHistogram'\nimport type { ChartProps } from '../../types'\n\nconst KpiText = React.memo(function KpiText({ \n data, \n chartConfig, \n displayConfig = {},\n height = \"100%\",\n colorPalette\n}: ChartProps) {\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\">No data available</div>\n <div className=\"dc:text-xs text-dc-text-secondary\">No data points to display</div>\n </div>\n </div>\n )\n }\n\n // Extract value field from chart config - handle both string and array formats\n let valueFields: string[] = []\n if (chartConfig?.yAxis) {\n // Handle both string and array formats\n if (typeof chartConfig.yAxis === 'string') {\n valueFields = [chartConfig.yAxis]\n } else if (Array.isArray(chartConfig.yAxis)) {\n valueFields = chartConfig.yAxis\n }\n }\n \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\">Configuration Error</div>\n <div className=\"dc:text-xs\">No measure fields configured</div>\n </div>\n </div>\n )\n }\n\n const valueField = valueFields[0] // Use first measure field\n \n // Debug logging (remove in production)\n \n // Extract values for the selected field\n const values = data\n .map(row => {\n // Try direct field access first\n if (row[valueField] !== undefined) {\n return row[valueField]\n }\n \n // If not found, try finding the first available field as fallback\n const availableFields = Object.keys(row)\n if (availableFields.length > 0) {\n // Field not found, using fallback\n return row[availableFields[0]]\n }\n \n return undefined\n })\n .filter(val => val !== null && val !== undefined)\n \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\">No valid data</div>\n <div className=\"dc:text-xs\">All values are null or invalid</div>\n </div>\n </div>\n )\n }\n\n // Calculate statistics for numeric values\n const numericValues = values\n .map(val => Number(val))\n .filter(val => !isNaN(val))\n\n let mainValue: any\n let min: number | null = null\n let max: number | null = null\n let showStats = false\n\n if (numericValues.length > 0) {\n if (values.length === 1) {\n mainValue = values[0]\n } else {\n // Calculate average for multiple numeric values\n const sum = numericValues.reduce((acc, val) => acc + val, 0)\n const avg = sum / numericValues.length\n mainValue = avg\n min = Math.min(...numericValues)\n max = Math.max(...numericValues)\n showStats = true\n }\n } else {\n // Non-numeric values - just use the first one or concatenate if multiple\n mainValue = values.length === 1 ? values[0] : values.join(', ')\n }\n\n // Format number with appropriate units and decimals\n const formatNumber = (value: number | null | undefined): string => {\n // If custom formatValue is provided, use it exclusively\n if (displayConfig.formatValue) {\n return displayConfig.formatValue(value)\n }\n\n // Null handling: Show placeholder for missing data\n if (value === null || value === undefined) {\n return '—'\n }\n\n const decimals = displayConfig.decimals ?? 2\n\n if (Math.abs(value) >= 1e9) {\n return (value / 1e9).toFixed(decimals) + 'B'\n } else if (Math.abs(value) >= 1e6) {\n return (value / 1e6).toFixed(decimals) + 'M'\n } else if (Math.abs(value) >= 1e3) {\n return (value / 1e3).toFixed(decimals) + 'K'\n } else {\n return value.toFixed(decimals)\n }\n }\n\n // Process template string\n const processTemplate = (template: string, value: any): string => {\n try {\n // Create template variables\n const templateVars = {\n value: typeof value === 'number' ? formatNumber(value) : String(value),\n rawValue: value,\n field: valueField,\n fieldLabel: getFieldLabel(valueField),\n min: min !== null ? formatNumber(min) : '',\n max: max !== null ? formatNumber(max) : '',\n count: values.length\n }\n\n // Simple template replacement using ${variable} syntax\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 // Error processing template\n return String(value)\n }\n }\n\n const template = displayConfig.template || '${fieldLabel}: ${value}'\n const displayText = processTemplate(template, mainValue)\n\n // Get color from palette by index, default to first color in palette\n const getValueColor = (): string => {\n if (displayConfig.valueColorIndex !== undefined && colorPalette?.colors) {\n const colorIndex = displayConfig.valueColorIndex\n if (colorIndex >= 0 && colorIndex < colorPalette.colors.length) {\n return colorPalette.colors[colorIndex]\n }\n }\n // Default to first color in palette if available, otherwise fallback to dark gray\n return colorPalette?.colors?.[0] || '#1f2937'\n }\n\n const valueColor = getValueColor()\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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"}
@@ -172,11 +172,11 @@ var a = /* @__PURE__ */ new Map(), o = /* @__PURE__ */ new Set(), s = {
172
172
  radialBar: () => import("./chart-radial-bar-N3MNUL7o.js").then((e) => e.n),
173
173
  treemap: () => import("./chart-tree-map-CbYjko2s.js").then((e) => e.n),
174
174
  bubble: () => import("./chart-bubble-DvyG15UB.js").then((e) => e.t),
175
- table: () => import("./chart-data-table-CGG-_R93.js").then((e) => e.n),
175
+ table: () => import("./chart-data-table-CW_qZDpt.js").then((e) => e.n),
176
176
  activityGrid: () => import("./chart-activity-grid-wR2Twpo7.js").then((e) => e.t),
177
- kpiNumber: () => import("./chart-kpi-number-IJWReEix.js").then((e) => e.t),
178
- kpiDelta: () => import("./chart-kpi-delta-D9eNhKR6.js").then((e) => e.t),
179
- kpiText: () => import("./chart-kpi-text-Dsr7nPmz.js").then((e) => e.t),
177
+ kpiNumber: () => import("./chart-kpi-number-BBtGBtZL.js").then((e) => e.t),
178
+ kpiDelta: () => import("./chart-kpi-delta-BPexzOe7.js").then((e) => e.t),
179
+ kpiText: () => import("./chart-kpi-text-BqHhmJEB.js").then((e) => e.t),
180
180
  markdown: () => import("./chart-markdown-B2X4IwLO.js").then((e) => e.n),
181
181
  funnel: () => import("./chart-funnel-C9JRW79j.js").then((e) => e.n),
182
182
  sankey: () => import("./chart-sankey-8nRYnupt.js").then((e) => e.t),
@@ -256,4 +256,4 @@ function y() {
256
256
  //#endregion
257
257
  export { f as a, v as i, _ as n, h as o, y as r, g as s, m as t };
258
258
 
259
- //# sourceMappingURL=charts-loader-DSdXpr7e.js.map
259
+ //# sourceMappingURL=charts-loader-gZjOqZwG.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"charts-loader-DSdXpr7e.js","names":[],"sources":["../../../src/client/components/charts/MissingDependencyFallback.tsx","../../../src/client/charts/ChartLoader.tsx"],"sourcesContent":["interface MissingDependencyFallbackProps {\n chartType: string\n packageName: string\n installCommand: string\n height?: string | number\n}\n\n/**\n * Fallback component shown when a chart's optional dependency is not installed.\n * Provides clear instructions on how to install the missing package.\n */\nexport function MissingDependencyFallback({\n chartType,\n packageName,\n installCommand,\n height = 200\n}: MissingDependencyFallbackProps) {\n return (\n <div\n className=\"dc-missing-dependency-fallback\"\n style={{\n height: typeof height === 'number' ? `${height}px` : height,\n display: 'flex',\n flexDirection: 'column',\n alignItems: 'center',\n justifyContent: 'center',\n width: '100%',\n padding: '1.5rem',\n textAlign: 'center',\n border: '1px dashed var(--dc-border, #e5e7eb)',\n borderRadius: '0.5rem',\n backgroundColor: 'var(--dc-surface, #f9fafb)'\n }}\n >\n <div style={{ fontSize: '2.5rem', marginBottom: '1rem' }}>📦</div>\n <h3 style={{\n fontSize: '1.125rem',\n fontWeight: 600,\n marginBottom: '0.5rem',\n color: 'var(--dc-text, #111827)'\n }}>\n Missing Dependency\n </h3>\n <p style={{\n fontSize: '0.875rem',\n color: 'var(--dc-text-secondary, #6b7280)',\n marginBottom: '1rem',\n maxWidth: '28rem'\n }}>\n The <code style={{\n padding: '0.125rem 0.375rem',\n backgroundColor: 'var(--dc-surface-secondary, #f3f4f6)',\n borderRadius: '0.25rem',\n fontFamily: 'monospace',\n fontSize: '0.75rem'\n }}>{chartType}</code> chart requires the <code style={{\n padding: '0.125rem 0.375rem',\n backgroundColor: 'var(--dc-surface-secondary, #f3f4f6)',\n borderRadius: '0.25rem',\n fontFamily: 'monospace',\n fontSize: '0.75rem'\n }}>{packageName}</code> package.\n </p>\n <div style={{\n backgroundColor: 'var(--dc-surface-secondary, #f3f4f6)',\n borderRadius: '0.5rem',\n padding: '0.75rem 1rem',\n fontFamily: 'monospace',\n fontSize: '0.875rem',\n color: 'var(--dc-text, #111827)'\n }}>\n <span style={{\n color: 'var(--dc-text-muted, #9ca3af)',\n userSelect: 'none'\n }}>$ </span>\n {installCommand}\n </div>\n <p style={{\n fontSize: '0.75rem',\n color: 'var(--dc-text-muted, #9ca3af)',\n marginTop: '0.75rem'\n }}>\n After installing, restart your development server.\n </p>\n </div>\n )\n}\n","/**\n * Lazy Chart Loader\n *\n * Provides React.lazy-based dynamic loading for chart components.\n * This enables code splitting - each chart type loads only when needed.\n *\n * Handles missing optional dependencies gracefully by showing a fallback\n * component instead of crashing the application.\n */\n\nimport { lazy, Suspense, ComponentType, ReactNode, LazyExoticComponent } from 'react'\nimport type { ChartType, ChartProps } from '../types'\nimport { MissingDependencyFallback } from '../components/charts/MissingDependencyFallback'\n\n// Type for lazy-loaded chart components\ntype LazyChartComponent = ComponentType<ChartProps>\n\n// Chart loader cache to prevent re-creating lazy components\nconst chartLoaderCache = new Map<ChartType, LazyExoticComponent<LazyChartComponent>>()\n\n// Track which chart types have failed to load due to missing dependencies\nconst failedChartTypes = new Set<ChartType>()\n\n/**\n * Maps chart types to their optional peer dependencies.\n * Charts not listed here have no external dependencies (table, KPIs, markdown).\n */\nconst chartDependencyMap: Partial<Record<ChartType, { packageName: string; installCommand: string }>> = {\n // Recharts-based charts\n bar: {\n packageName: 'recharts',\n installCommand: 'npm install recharts'\n },\n line: {\n packageName: 'recharts',\n installCommand: 'npm install recharts'\n },\n area: {\n packageName: 'recharts',\n installCommand: 'npm install recharts'\n },\n pie: {\n packageName: 'recharts',\n installCommand: 'npm install recharts'\n },\n scatter: {\n packageName: 'recharts',\n installCommand: 'npm install recharts'\n },\n radar: {\n packageName: 'recharts',\n installCommand: 'npm install recharts'\n },\n radialBar: {\n packageName: 'recharts',\n installCommand: 'npm install recharts'\n },\n treemap: {\n packageName: 'recharts',\n installCommand: 'npm install recharts'\n },\n bubble: {\n packageName: 'recharts',\n installCommand: 'npm install recharts'\n },\n funnel: {\n packageName: 'recharts',\n installCommand: 'npm install recharts'\n },\n sankey: {\n packageName: 'recharts',\n installCommand: 'npm install recharts'\n },\n sunburst: {\n packageName: 'recharts',\n installCommand: 'npm install recharts'\n },\n // Nivo-based charts\n heatmap: {\n packageName: '@nivo/heatmap',\n installCommand: 'npm install @nivo/heatmap'\n },\n waterfall: {\n packageName: 'recharts',\n installCommand: 'npm install recharts'\n },\n measureProfile: {\n packageName: 'recharts',\n installCommand: 'npm install recharts'\n },\n gauge: {\n packageName: 'd3-shape',\n installCommand: 'npm install d3-shape'\n },\n // Charts with no external deps: table, activityGrid, kpiNumber, kpiDelta, kpiText, markdown, retentionHeatmap, boxPlot, candlestick\n}\n\n// Dynamic import functions for each chart type\nconst chartImportMap: Record<ChartType, () => Promise<{ default: LazyChartComponent }>> = {\n bar: () => import('../components/charts/BarChart'),\n line: () => import('../components/charts/LineChart'),\n area: () => import('../components/charts/AreaChart'),\n pie: () => import('../components/charts/PieChart'),\n scatter: () => import('../components/charts/ScatterChart'),\n radar: () => import('../components/charts/RadarChart'),\n radialBar: () => import('../components/charts/RadialBarChart'),\n treemap: () => import('../components/charts/TreeMapChart'),\n bubble: () => import('../components/charts/BubbleChart'),\n table: () => import('../components/charts/DataTable'),\n activityGrid: () => import('../components/charts/ActivityGridChart'),\n kpiNumber: () => import('../components/charts/KpiNumber'),\n kpiDelta: () => import('../components/charts/KpiDelta'),\n kpiText: () => import('../components/charts/KpiText'),\n markdown: () => import('../components/charts/MarkdownChart'),\n funnel: () => import('../components/charts/FunnelChart'),\n sankey: () => import('../components/charts/SankeyChart'),\n sunburst: () => import('../components/charts/SunburstChart'),\n heatmap: () => import('../components/charts/HeatMapChart'),\n retentionHeatmap: () => import('../components/charts/RetentionHeatmap'),\n retentionCombined: () => import('../components/charts/RetentionCombinedChart'),\n boxPlot: () => import('../components/charts/BoxPlotChart'),\n waterfall: () => import('../components/charts/WaterfallChart'),\n candlestick: () => import('../components/charts/CandlestickChart'),\n measureProfile: () => import('../components/charts/MeasureProfileChart'),\n gauge: () => import('../components/charts/GaugeChart'),\n}\n\n/**\n * Creates a fallback component for a chart type with missing dependencies.\n */\nfunction createFallbackComponent(chartType: ChartType): LazyChartComponent {\n const depInfo = chartDependencyMap[chartType]\n\n const FallbackComponent: LazyChartComponent = ({ height }) => (\n <MissingDependencyFallback\n chartType={chartType}\n packageName={depInfo?.packageName || 'unknown'}\n installCommand={depInfo?.installCommand || `npm install [package-name]`}\n height={height}\n />\n )\n\n FallbackComponent.displayName = `${chartType}Fallback`\n return FallbackComponent\n}\n\n/**\n * Wraps a chart import function with error handling.\n * If the import fails (e.g., missing dependency), returns a fallback component.\n */\nfunction createSafeImport(\n chartType: ChartType,\n importFn: () => Promise<{ default: LazyChartComponent }>\n): () => Promise<{ default: LazyChartComponent }> {\n return async () => {\n try {\n return await importFn()\n } catch (error) {\n // Log the error for debugging\n console.warn(\n `[drizzle-cube] Failed to load ${chartType} chart:`,\n error instanceof Error ? error.message : error\n )\n\n // Track that this chart type failed\n failedChartTypes.add(chartType)\n\n // Return the fallback component wrapped in the expected format\n return { default: createFallbackComponent(chartType) }\n }\n }\n}\n\n/**\n * Get or create a lazy component for a chart type.\n * Handles import failures gracefully by returning a fallback component.\n */\nfunction getLazyChart(chartType: ChartType): LazyExoticComponent<LazyChartComponent> {\n if (!chartLoaderCache.has(chartType)) {\n const importFn = chartImportMap[chartType]\n if (!importFn) {\n throw new Error(`Unknown chart type: ${chartType}`)\n }\n // Wrap the import with error handling for graceful degradation\n const safeImport = createSafeImport(chartType, importFn)\n chartLoaderCache.set(chartType, lazy(safeImport))\n }\n return chartLoaderCache.get(chartType)!\n}\n\n/**\n * Check if a chart type is supported\n */\nexport function isValidChartType(chartType: string): chartType is ChartType {\n return chartType in chartImportMap\n}\n\n// Props for the LazyChart wrapper\nexport interface LazyChartProps extends ChartProps {\n chartType: ChartType\n fallback?: ReactNode\n}\n\n/**\n * Default loading placeholder for charts\n */\nfunction DefaultChartFallback({ height }: { height?: string | number }) {\n return (\n <div\n className=\"dc:flex dc:items-center dc:justify-center dc:w-full\"\n style={{ height: typeof height === 'number' ? `${height}px` : height || '200px' }}\n >\n <div className=\"dc:animate-pulse bg-dc-surface-secondary dc:rounded dc:w-full dc:h-full dc:min-h-[100px]\" />\n </div>\n )\n}\n\n/**\n * Lazy Chart Component\n *\n * Renders a chart component with React.lazy dynamic loading.\n * The chart type determines which chart component is loaded.\n *\n * @example\n * ```tsx\n * <LazyChart\n * chartType=\"bar\"\n * data={chartData}\n * chartConfig={{ yAxis: ['value'] }}\n * height={300}\n * />\n * ```\n */\nexport function LazyChart({\n chartType,\n fallback,\n height,\n ...chartProps\n}: LazyChartProps) {\n const ChartComponent = getLazyChart(chartType)\n\n return (\n <Suspense fallback={fallback ?? <DefaultChartFallback height={height} />}>\n <ChartComponent height={height} {...chartProps} />\n </Suspense>\n )\n}\n\n/**\n * Preload a chart type\n *\n * Triggers the dynamic import without rendering.\n * Useful for prefetching charts that will likely be needed.\n *\n * @example\n * ```tsx\n * // Preload bar chart on hover\n * onMouseEnter={() => preloadChart('bar')}\n * ```\n */\nexport function preloadChart(chartType: ChartType): void {\n const importFn = chartImportMap[chartType]\n if (importFn) {\n importFn()\n }\n}\n\n/**\n * Preload multiple chart types\n *\n * @example\n * ```tsx\n * // Preload common charts on app init\n * useEffect(() => {\n * preloadCharts(['bar', 'line', 'pie'])\n * }, [])\n * ```\n */\nexport function preloadCharts(chartTypes: ChartType[]): void {\n chartTypes.forEach(preloadChart)\n}\n\n/**\n * Get all available chart types\n */\nexport function getAvailableChartTypes(): ChartType[] {\n return Object.keys(chartImportMap) as ChartType[]\n}\n\n/**\n * Check if a chart type has loaded successfully.\n * Returns false if the chart failed to load due to missing dependencies.\n *\n * Note: This only returns accurate results after the chart has been attempted to load.\n * Use this for conditional UI logic (e.g., hiding unavailable chart types).\n */\nexport function isChartTypeAvailable(chartType: ChartType): boolean {\n return !failedChartTypes.has(chartType)\n}\n\n/**\n * Get list of chart types that failed to load due to missing dependencies.\n */\nexport function getUnavailableChartTypes(): ChartType[] {\n return Array.from(failedChartTypes)\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"charts-loader-gZjOqZwG.js","names":[],"sources":["../../../src/client/components/charts/MissingDependencyFallback.tsx","../../../src/client/charts/ChartLoader.tsx"],"sourcesContent":["interface MissingDependencyFallbackProps {\n chartType: string\n packageName: string\n installCommand: string\n height?: string | number\n}\n\n/**\n * Fallback component shown when a chart's optional dependency is not installed.\n * Provides clear instructions on how to install the missing package.\n */\nexport function MissingDependencyFallback({\n chartType,\n packageName,\n installCommand,\n height = 200\n}: MissingDependencyFallbackProps) {\n return (\n <div\n className=\"dc-missing-dependency-fallback\"\n style={{\n height: typeof height === 'number' ? `${height}px` : height,\n display: 'flex',\n flexDirection: 'column',\n alignItems: 'center',\n justifyContent: 'center',\n width: '100%',\n padding: '1.5rem',\n textAlign: 'center',\n border: '1px dashed var(--dc-border, #e5e7eb)',\n borderRadius: '0.5rem',\n backgroundColor: 'var(--dc-surface, #f9fafb)'\n }}\n >\n <div style={{ fontSize: '2.5rem', marginBottom: '1rem' }}>📦</div>\n <h3 style={{\n fontSize: '1.125rem',\n fontWeight: 600,\n marginBottom: '0.5rem',\n color: 'var(--dc-text, #111827)'\n }}>\n Missing Dependency\n </h3>\n <p style={{\n fontSize: '0.875rem',\n color: 'var(--dc-text-secondary, #6b7280)',\n marginBottom: '1rem',\n maxWidth: '28rem'\n }}>\n The <code style={{\n padding: '0.125rem 0.375rem',\n backgroundColor: 'var(--dc-surface-secondary, #f3f4f6)',\n borderRadius: '0.25rem',\n fontFamily: 'monospace',\n fontSize: '0.75rem'\n }}>{chartType}</code> chart requires the <code style={{\n padding: '0.125rem 0.375rem',\n backgroundColor: 'var(--dc-surface-secondary, #f3f4f6)',\n borderRadius: '0.25rem',\n fontFamily: 'monospace',\n fontSize: '0.75rem'\n }}>{packageName}</code> package.\n </p>\n <div style={{\n backgroundColor: 'var(--dc-surface-secondary, #f3f4f6)',\n borderRadius: '0.5rem',\n padding: '0.75rem 1rem',\n fontFamily: 'monospace',\n fontSize: '0.875rem',\n color: 'var(--dc-text, #111827)'\n }}>\n <span style={{\n color: 'var(--dc-text-muted, #9ca3af)',\n userSelect: 'none'\n }}>$ </span>\n {installCommand}\n </div>\n <p style={{\n fontSize: '0.75rem',\n color: 'var(--dc-text-muted, #9ca3af)',\n marginTop: '0.75rem'\n }}>\n After installing, restart your development server.\n </p>\n </div>\n )\n}\n","/**\n * Lazy Chart Loader\n *\n * Provides React.lazy-based dynamic loading for chart components.\n * This enables code splitting - each chart type loads only when needed.\n *\n * Handles missing optional dependencies gracefully by showing a fallback\n * component instead of crashing the application.\n */\n\nimport { lazy, Suspense, ComponentType, ReactNode, LazyExoticComponent } from 'react'\nimport type { ChartType, ChartProps } from '../types'\nimport { MissingDependencyFallback } from '../components/charts/MissingDependencyFallback'\n\n// Type for lazy-loaded chart components\ntype LazyChartComponent = ComponentType<ChartProps>\n\n// Chart loader cache to prevent re-creating lazy components\nconst chartLoaderCache = new Map<ChartType, LazyExoticComponent<LazyChartComponent>>()\n\n// Track which chart types have failed to load due to missing dependencies\nconst failedChartTypes = new Set<ChartType>()\n\n/**\n * Maps chart types to their optional peer dependencies.\n * Charts not listed here have no external dependencies (table, KPIs, markdown).\n */\nconst chartDependencyMap: Partial<Record<ChartType, { packageName: string; installCommand: string }>> = {\n // Recharts-based charts\n bar: {\n packageName: 'recharts',\n installCommand: 'npm install recharts'\n },\n line: {\n packageName: 'recharts',\n installCommand: 'npm install recharts'\n },\n area: {\n packageName: 'recharts',\n installCommand: 'npm install recharts'\n },\n pie: {\n packageName: 'recharts',\n installCommand: 'npm install recharts'\n },\n scatter: {\n packageName: 'recharts',\n installCommand: 'npm install recharts'\n },\n radar: {\n packageName: 'recharts',\n installCommand: 'npm install recharts'\n },\n radialBar: {\n packageName: 'recharts',\n installCommand: 'npm install recharts'\n },\n treemap: {\n packageName: 'recharts',\n installCommand: 'npm install recharts'\n },\n bubble: {\n packageName: 'recharts',\n installCommand: 'npm install recharts'\n },\n funnel: {\n packageName: 'recharts',\n installCommand: 'npm install recharts'\n },\n sankey: {\n packageName: 'recharts',\n installCommand: 'npm install recharts'\n },\n sunburst: {\n packageName: 'recharts',\n installCommand: 'npm install recharts'\n },\n // Nivo-based charts\n heatmap: {\n packageName: '@nivo/heatmap',\n installCommand: 'npm install @nivo/heatmap'\n },\n waterfall: {\n packageName: 'recharts',\n installCommand: 'npm install recharts'\n },\n measureProfile: {\n packageName: 'recharts',\n installCommand: 'npm install recharts'\n },\n gauge: {\n packageName: 'd3-shape',\n installCommand: 'npm install d3-shape'\n },\n // Charts with no external deps: table, activityGrid, kpiNumber, kpiDelta, kpiText, markdown, retentionHeatmap, boxPlot, candlestick\n}\n\n// Dynamic import functions for each chart type\nconst chartImportMap: Record<ChartType, () => Promise<{ default: LazyChartComponent }>> = {\n bar: () => import('../components/charts/BarChart'),\n line: () => import('../components/charts/LineChart'),\n area: () => import('../components/charts/AreaChart'),\n pie: () => import('../components/charts/PieChart'),\n scatter: () => import('../components/charts/ScatterChart'),\n radar: () => import('../components/charts/RadarChart'),\n radialBar: () => import('../components/charts/RadialBarChart'),\n treemap: () => import('../components/charts/TreeMapChart'),\n bubble: () => import('../components/charts/BubbleChart'),\n table: () => import('../components/charts/DataTable'),\n activityGrid: () => import('../components/charts/ActivityGridChart'),\n kpiNumber: () => import('../components/charts/KpiNumber'),\n kpiDelta: () => import('../components/charts/KpiDelta'),\n kpiText: () => import('../components/charts/KpiText'),\n markdown: () => import('../components/charts/MarkdownChart'),\n funnel: () => import('../components/charts/FunnelChart'),\n sankey: () => import('../components/charts/SankeyChart'),\n sunburst: () => import('../components/charts/SunburstChart'),\n heatmap: () => import('../components/charts/HeatMapChart'),\n retentionHeatmap: () => import('../components/charts/RetentionHeatmap'),\n retentionCombined: () => import('../components/charts/RetentionCombinedChart'),\n boxPlot: () => import('../components/charts/BoxPlotChart'),\n waterfall: () => import('../components/charts/WaterfallChart'),\n candlestick: () => import('../components/charts/CandlestickChart'),\n measureProfile: () => import('../components/charts/MeasureProfileChart'),\n gauge: () => import('../components/charts/GaugeChart'),\n}\n\n/**\n * Creates a fallback component for a chart type with missing dependencies.\n */\nfunction createFallbackComponent(chartType: ChartType): LazyChartComponent {\n const depInfo = chartDependencyMap[chartType]\n\n const FallbackComponent: LazyChartComponent = ({ height }) => (\n <MissingDependencyFallback\n chartType={chartType}\n packageName={depInfo?.packageName || 'unknown'}\n installCommand={depInfo?.installCommand || `npm install [package-name]`}\n height={height}\n />\n )\n\n FallbackComponent.displayName = `${chartType}Fallback`\n return FallbackComponent\n}\n\n/**\n * Wraps a chart import function with error handling.\n * If the import fails (e.g., missing dependency), returns a fallback component.\n */\nfunction createSafeImport(\n chartType: ChartType,\n importFn: () => Promise<{ default: LazyChartComponent }>\n): () => Promise<{ default: LazyChartComponent }> {\n return async () => {\n try {\n return await importFn()\n } catch (error) {\n // Log the error for debugging\n console.warn(\n `[drizzle-cube] Failed to load ${chartType} chart:`,\n error instanceof Error ? error.message : error\n )\n\n // Track that this chart type failed\n failedChartTypes.add(chartType)\n\n // Return the fallback component wrapped in the expected format\n return { default: createFallbackComponent(chartType) }\n }\n }\n}\n\n/**\n * Get or create a lazy component for a chart type.\n * Handles import failures gracefully by returning a fallback component.\n */\nfunction getLazyChart(chartType: ChartType): LazyExoticComponent<LazyChartComponent> {\n if (!chartLoaderCache.has(chartType)) {\n const importFn = chartImportMap[chartType]\n if (!importFn) {\n throw new Error(`Unknown chart type: ${chartType}`)\n }\n // Wrap the import with error handling for graceful degradation\n const safeImport = createSafeImport(chartType, importFn)\n chartLoaderCache.set(chartType, lazy(safeImport))\n }\n return chartLoaderCache.get(chartType)!\n}\n\n/**\n * Check if a chart type is supported\n */\nexport function isValidChartType(chartType: string): chartType is ChartType {\n return chartType in chartImportMap\n}\n\n// Props for the LazyChart wrapper\nexport interface LazyChartProps extends ChartProps {\n chartType: ChartType\n fallback?: ReactNode\n}\n\n/**\n * Default loading placeholder for charts\n */\nfunction DefaultChartFallback({ height }: { height?: string | number }) {\n return (\n <div\n className=\"dc:flex dc:items-center dc:justify-center dc:w-full\"\n style={{ height: typeof height === 'number' ? `${height}px` : height || '200px' }}\n >\n <div className=\"dc:animate-pulse bg-dc-surface-secondary dc:rounded dc:w-full dc:h-full dc:min-h-[100px]\" />\n </div>\n )\n}\n\n/**\n * Lazy Chart Component\n *\n * Renders a chart component with React.lazy dynamic loading.\n * The chart type determines which chart component is loaded.\n *\n * @example\n * ```tsx\n * <LazyChart\n * chartType=\"bar\"\n * data={chartData}\n * chartConfig={{ yAxis: ['value'] }}\n * height={300}\n * />\n * ```\n */\nexport function LazyChart({\n chartType,\n fallback,\n height,\n ...chartProps\n}: LazyChartProps) {\n const ChartComponent = getLazyChart(chartType)\n\n return (\n <Suspense fallback={fallback ?? <DefaultChartFallback height={height} />}>\n <ChartComponent height={height} {...chartProps} />\n </Suspense>\n )\n}\n\n/**\n * Preload a chart type\n *\n * Triggers the dynamic import without rendering.\n * Useful for prefetching charts that will likely be needed.\n *\n * @example\n * ```tsx\n * // Preload bar chart on hover\n * onMouseEnter={() => preloadChart('bar')}\n * ```\n */\nexport function preloadChart(chartType: ChartType): void {\n const importFn = chartImportMap[chartType]\n if (importFn) {\n importFn()\n }\n}\n\n/**\n * Preload multiple chart types\n *\n * @example\n * ```tsx\n * // Preload common charts on app init\n * useEffect(() => {\n * preloadCharts(['bar', 'line', 'pie'])\n * }, [])\n * ```\n */\nexport function preloadCharts(chartTypes: ChartType[]): void {\n chartTypes.forEach(preloadChart)\n}\n\n/**\n * Get all available chart types\n */\nexport function getAvailableChartTypes(): ChartType[] {\n return Object.keys(chartImportMap) as ChartType[]\n}\n\n/**\n * Check if a chart type has loaded successfully.\n * Returns false if the chart failed to load due to missing dependencies.\n *\n * Note: This only returns accurate results after the chart has been attempted to load.\n * Use this for conditional UI logic (e.g., hiding unavailable chart types).\n */\nexport function isChartTypeAvailable(chartType: ChartType): boolean {\n return !failedChartTypes.has(chartType)\n}\n\n/**\n * Get list of chart types that failed to load due to missing dependencies.\n */\nexport function getUnavailableChartTypes(): ChartType[] {\n return Array.from(failedChartTypes)\n}\n"],"mappings":";;;AAWA,SAAgB,EAA0B,EACxC,cACA,gBACA,mBACA,YAAS,OACwB;AACjC,QACE,kBAAC,OAAD;EACE,WAAU;EACV,OAAO;GACL,QAAQ,OAAO,KAAW,WAAW,GAAG,EAAO,MAAM;GACrD,SAAS;GACT,eAAe;GACf,YAAY;GACZ,gBAAgB;GAChB,OAAO;GACP,SAAS;GACT,WAAW;GACX,QAAQ;GACR,cAAc;GACd,iBAAiB;GAClB;YAdH;GAgBE,kBAAC,OAAD;IAAK,OAAO;KAAE,UAAU;KAAU,cAAc;KAAQ;cAAE;IAAQ,CAAA;GAClE,kBAAC,MAAD;IAAI,OAAO;KACT,UAAU;KACV,YAAY;KACZ,cAAc;KACd,OAAO;KACR;cAAE;IAEE,CAAA;GACL,kBAAC,KAAD;IAAG,OAAO;KACR,UAAU;KACV,OAAO;KACP,cAAc;KACd,UAAU;KACX;cALD;KAKG;KACG,kBAAC,QAAD;MAAM,OAAO;OACf,SAAS;OACT,iBAAiB;OACjB,cAAc;OACd,YAAY;OACZ,UAAU;OACX;gBAAG;MAAiB,CAAA;;KAAoB,kBAAC,QAAD;MAAM,OAAO;OACpD,SAAS;OACT,iBAAiB;OACjB,cAAc;OACd,YAAY;OACZ,UAAU;OACX;gBAAG;MAAmB,CAAA;;KACrB;;GACJ,kBAAC,OAAD;IAAK,OAAO;KACV,iBAAiB;KACjB,cAAc;KACd,SAAS;KACT,YAAY;KACZ,UAAU;KACV,OAAO;KACR;cAPD,CAQE,kBAAC,QAAD;KAAM,OAAO;MACX,OAAO;MACP,YAAY;MACb;eAAE;KAAS,CAAA,EACX,EACG;;GACN,kBAAC,KAAD;IAAG,OAAO;KACR,UAAU;KACV,OAAO;KACP,WAAW;KACZ;cAAE;IAEC,CAAA;GACA;;;;;AClEV,IAAM,oBAAmB,IAAI,KAAyD,EAGhF,oBAAmB,IAAI,KAAgB,EAMvC,IAAkG;CAEtG,KAAK;EACH,aAAa;EACb,gBAAgB;EACjB;CACD,MAAM;EACJ,aAAa;EACb,gBAAgB;EACjB;CACD,MAAM;EACJ,aAAa;EACb,gBAAgB;EACjB;CACD,KAAK;EACH,aAAa;EACb,gBAAgB;EACjB;CACD,SAAS;EACP,aAAa;EACb,gBAAgB;EACjB;CACD,OAAO;EACL,aAAa;EACb,gBAAgB;EACjB;CACD,WAAW;EACT,aAAa;EACb,gBAAgB;EACjB;CACD,SAAS;EACP,aAAa;EACb,gBAAgB;EACjB;CACD,QAAQ;EACN,aAAa;EACb,gBAAgB;EACjB;CACD,QAAQ;EACN,aAAa;EACb,gBAAgB;EACjB;CACD,QAAQ;EACN,aAAa;EACb,gBAAgB;EACjB;CACD,UAAU;EACR,aAAa;EACb,gBAAgB;EACjB;CAED,SAAS;EACP,aAAa;EACb,gBAAgB;EACjB;CACD,WAAW;EACT,aAAa;EACb,gBAAgB;EACjB;CACD,gBAAgB;EACd,aAAa;EACb,gBAAgB;EACjB;CACD,OAAO;EACL,aAAa;EACb,gBAAgB;EACjB;CAEF,EAGK,IAAoF;CACxF,WAAW,OAAO,2BAAA,MAAA,MAAA,EAAA,EAAA;CAClB,YAAY,OAAO,4BAAA,MAAA,MAAA,EAAA,EAAA;CACnB,YAAY,OAAO,4BAAA,MAAA,MAAA,EAAA,EAAA;CACnB,WAAW,OAAO,2BAAA,MAAA,MAAA,EAAA,EAAA;CAClB,eAAe,OAAO,+BAAA,MAAA,MAAA,EAAA,EAAA;CACtB,aAAa,OAAO,6BAAA,MAAA,MAAA,EAAA,EAAA;CACpB,iBAAiB,OAAO,kCAAA,MAAA,MAAA,EAAA,EAAA;CACxB,eAAe,OAAO,gCAAA,MAAA,MAAA,EAAA,EAAA;CACtB,cAAc,OAAO,8BAAA,MAAA,MAAA,EAAA,EAAA;CACrB,aAAa,OAAO,kCAAA,MAAA,MAAA,EAAA,EAAA;CACpB,oBAAoB,OAAO,qCAAA,MAAA,MAAA,EAAA,EAAA;CAC3B,iBAAiB,OAAO,kCAAA,MAAA,MAAA,EAAA,EAAA;CACxB,gBAAgB,OAAO,iCAAA,MAAA,MAAA,EAAA,EAAA;CACvB,eAAe,OAAO,gCAAA,MAAA,MAAA,EAAA,EAAA;CACtB,gBAAgB,OAAO,gCAAA,MAAA,MAAA,EAAA,EAAA;CACvB,cAAc,OAAO,8BAAA,MAAA,MAAA,EAAA,EAAA;CACrB,cAAc,OAAO,8BAAA,MAAA,MAAA,EAAA,EAAA;CACrB,gBAAgB,OAAO,gCAAA,MAAA,MAAA,EAAA,EAAA;CACvB,eAAe,OAAO,gCAAA,MAAA,MAAA,EAAA,EAAA;CACtB,wBAAwB,OAAO;CAC/B,yBAAyB,OAAO;CAChC,eAAe,OAAO,gCAAA,MAAA,MAAA,EAAA,EAAA;CACtB,iBAAiB,OAAO,iCAAA,MAAA,MAAA,EAAA,EAAA;CACxB,mBAAmB,OAAO,mCAAA,MAAA,MAAA,EAAA,EAAA;CAC1B,sBAAsB,OAAO,uCAAA,MAAA,MAAA,EAAA,EAAA;CAC7B,aAAa,OAAO,6BAAA,MAAA,MAAA,EAAA,EAAA;CACrB;AAKD,SAAS,EAAwB,GAA0C;CACzE,IAAM,IAAU,EAAmB,IAE7B,KAAyC,EAAE,gBAC/C,kBAAC,GAAD;EACa;EACX,aAAa,GAAS,eAAe;EACrC,gBAAgB,GAAS,kBAAkB;EACnC;EACR,CAAA;AAIJ,QADA,EAAkB,cAAc,GAAG,EAAU,WACtC;;AAOT,SAAS,EACP,GACA,GACgD;AAChD,QAAO,YAAY;AACjB,MAAI;AACF,UAAO,MAAM,GAAU;WAChB,GAAO;AAWd,UATA,QAAQ,KACN,iCAAiC,EAAU,UAC3C,aAAiB,QAAQ,EAAM,UAAU,EAC1C,EAGD,EAAiB,IAAI,EAAU,EAGxB,EAAE,SAAS,EAAwB,EAAU,EAAE;;;;AAS5D,SAAS,EAAa,GAA+D;AACnF,KAAI,CAAC,EAAiB,IAAI,EAAU,EAAE;EACpC,IAAM,IAAW,EAAe;AAChC,MAAI,CAAC,EACH,OAAU,MAAM,uBAAuB,IAAY;EAGrD,IAAM,IAAa,EAAiB,GAAW,EAAS;AACxD,IAAiB,IAAI,GAAW,EAAK,EAAW,CAAC;;AAEnD,QAAO,EAAiB,IAAI,EAAU;;AAMxC,SAAgB,EAAiB,GAA2C;AAC1E,QAAO,KAAa;;AAYtB,SAAS,EAAqB,EAAE,aAAwC;AACtE,QACE,kBAAC,OAAD;EACE,WAAU;EACV,OAAO,EAAE,QAAQ,OAAO,KAAW,WAAW,GAAG,EAAO,MAAM,KAAU,SAAS;YAEjF,kBAAC,OAAD,EAAK,WAAU,4FAA6F,CAAA;EACxG,CAAA;;AAoBV,SAAgB,EAAU,EACxB,cACA,aACA,WACA,GAAG,KACc;CACjB,IAAM,IAAiB,EAAa,EAAU;AAE9C,QACE,kBAAC,GAAD;EAAU,UAAU,KAAY,kBAAC,GAAD,EAA8B,WAAU,CAAA;YACtE,kBAAC,GAAD;GAAwB;GAAQ,GAAI;GAAc,CAAA;EACzC,CAAA;;AAgBf,SAAgB,EAAa,GAA4B;CACvD,IAAM,IAAW,EAAe;AAChC,CAAI,KACF,GAAU;;AAed,SAAgB,EAAc,GAA+B;AAC3D,GAAW,QAAQ,EAAa;;AAMlC,SAAgB,IAAsC;AACpD,QAAO,OAAO,KAAK,EAAe;;AAUpC,SAAgB,EAAqB,GAA+B;AAClE,QAAO,CAAC,EAAiB,IAAI,EAAU;;AAMzC,SAAgB,IAAwC;AACtD,QAAO,MAAM,KAAK,EAAiB"}
@@ -1,5 +1,5 @@
1
1
  import { n as e } from "./rolldown-runtime-lc2dmIiU.js";
2
- import { c as t, d as n } from "./chart-data-table-CGG-_R93.js";
2
+ import { c as t, d as n } from "./chart-data-table-CW_qZDpt.js";
3
3
  import { Suspense as r, createContext as i, lazy as a, useCallback as o, useContext as s, useEffect as c, useMemo as l, useRef as u, useState as d } from "react";
4
4
  import { Fragment as f, jsx as p, jsxs as m } from "react/jsx-runtime";
5
5
  //#region src/client/components/SchemaVisualization/xyflowContext.tsx
@@ -1069,4 +1069,4 @@ function z({ className: e = "", onFieldClick: r, highlightedCubes: i, highlighte
1069
1069
  //#endregion
1070
1070
  export { S as n, R as t };
1071
1071
 
1072
- //# sourceMappingURL=schema-visualization-CBQrcxb8.js.map
1072
+ //# sourceMappingURL=schema-visualization-XLFzV_7p.js.map