@databricks/appkit-ui 0.17.0 → 0.18.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (42) hide show
  1. package/CLAUDE.md +1 -0
  2. package/dist/react/charts/base.js +3 -2
  3. package/dist/react/charts/base.js.map +1 -1
  4. package/dist/react/charts/normalize.d.ts.map +1 -1
  5. package/dist/react/charts/normalize.js +3 -1
  6. package/dist/react/charts/normalize.js.map +1 -1
  7. package/dist/react/charts/options.d.ts +1 -0
  8. package/dist/react/charts/options.d.ts.map +1 -1
  9. package/dist/react/charts/options.js +13 -8
  10. package/dist/react/charts/options.js.map +1 -1
  11. package/dist/react/charts/utils.d.ts.map +1 -1
  12. package/dist/react/charts/utils.js +23 -1
  13. package/dist/react/charts/utils.js.map +1 -1
  14. package/dist/react/genie/genie-chart-inference.d.ts +17 -0
  15. package/dist/react/genie/genie-chart-inference.d.ts.map +1 -0
  16. package/dist/react/genie/genie-chart-inference.js +75 -0
  17. package/dist/react/genie/genie-chart-inference.js.map +1 -0
  18. package/dist/react/genie/genie-chat-message.d.ts.map +1 -1
  19. package/dist/react/genie/genie-chat-message.js +26 -15
  20. package/dist/react/genie/genie-chat-message.js.map +1 -1
  21. package/dist/react/genie/genie-query-transform.d.ts +31 -0
  22. package/dist/react/genie/genie-query-transform.d.ts.map +1 -0
  23. package/dist/react/genie/genie-query-transform.js +79 -0
  24. package/dist/react/genie/genie-query-transform.js.map +1 -0
  25. package/dist/react/genie/genie-query-visualization.d.ts +25 -0
  26. package/dist/react/genie/genie-query-visualization.d.ts.map +1 -0
  27. package/dist/react/genie/genie-query-visualization.js +79 -0
  28. package/dist/react/genie/genie-query-visualization.js.map +1 -0
  29. package/dist/react/genie/index.d.ts +4 -1
  30. package/dist/react/genie/index.js +3 -0
  31. package/dist/react/genie/types.d.ts +2 -2
  32. package/dist/react/genie/types.d.ts.map +1 -1
  33. package/dist/react/index.d.ts +5 -2
  34. package/dist/react/index.js +6 -3
  35. package/dist/react/table/data-table.js +1 -1
  36. package/dist/react/ui/index.js +2 -2
  37. package/dist/shared/src/genie.d.ts +16 -2
  38. package/dist/shared/src/genie.d.ts.map +1 -1
  39. package/dist/shared/src/index.d.ts +1 -1
  40. package/docs/api/appkit-ui/genie/GenieQueryVisualization.md +29 -0
  41. package/llms.txt +1 -0
  42. package/package.json +1 -1
package/CLAUDE.md CHANGED
@@ -114,6 +114,7 @@ npx @databricks/appkit docs <query>
114
114
  - [GenieChatInput](./docs/api/appkit-ui/genie/GenieChatInput.md): Auto-expanding textarea input with a send button for chat messages. Submits on Enter (Shift+Enter for newline).
115
115
  - [GenieChatMessage](./docs/api/appkit-ui/genie/GenieChatMessage.md): Renders a single Genie message bubble with optional expandable SQL query attachments.
116
116
  - [GenieChatMessageList](./docs/api/appkit-ui/genie/GenieChatMessageList.md): Scrollable message list that renders Genie chat messages with auto-scroll, skeleton loaders, and a streaming indicator.
117
+ - [GenieQueryVisualization](./docs/api/appkit-ui/genie/GenieQueryVisualization.md): Renders a chart + data table for a Genie query result.
117
118
  - [Styling](./docs/api/appkit-ui/styling.md): This guide covers how to style AppKit UI components using CSS variables and theming.
118
119
  - [Accordion](./docs/api/appkit-ui/ui/Accordion.md): Collapsible content sections organized in a vertical stack
119
120
  - [Alert](./docs/api/appkit-ui/ui/Alert.md): Displays important information with optional icon and multiple variants
@@ -42,7 +42,7 @@ function BaseChart({ data, chartType, xKey, yKey, orientation, height = 300, tit
42
42
  chartType
43
43
  ]);
44
44
  const option = useMemo(() => {
45
- const { xData, yFields, chartType: detectedChartType } = normalized;
45
+ const { xData, yFields, xField, chartType: detectedChartType } = normalized;
46
46
  if (xData.length === 0) return null;
47
47
  const isHeatmap = chartType === "heatmap";
48
48
  const baseCtx = {
@@ -51,7 +51,8 @@ function BaseChart({ data, chartType, xKey, yKey, orientation, height = 300, tit
51
51
  yFields,
52
52
  colors,
53
53
  title,
54
- showLegend
54
+ showLegend,
55
+ xField
55
56
  };
56
57
  const isPie = chartType === "pie" || chartType === "donut";
57
58
  const isRadar = chartType === "radar";
@@ -1 +1 @@
1
- {"version":3,"file":"base.js","names":[],"sources":["../../../src/react/charts/base.tsx"],"sourcesContent":["import type { ECharts } from \"echarts\";\nimport ReactECharts from \"echarts-for-react\";\nimport { useCallback, useMemo, useRef } from \"react\";\nimport { normalizeChartData, normalizeHeatmapData } from \"./normalize\";\nimport {\n buildCartesianOption,\n buildHeatmapOption,\n buildHorizontalBarOption,\n buildPieOption,\n buildRadarOption,\n type OptionBuilderContext,\n} from \"./options\";\nimport { useThemeColors } from \"./theme\";\nimport type {\n ChartColorPalette,\n ChartData,\n ChartType,\n Orientation,\n} from \"./types\";\n\n// ============================================================================\n// Palette Selection\n// ============================================================================\n\n/**\n * Determines the appropriate color palette for a chart type.\n * - Heatmaps use sequential (low → high intensity)\n * - All other charts use categorical (distinct categories)\n */\nfunction getDefaultPalette(chartType: ChartType): ChartColorPalette {\n switch (chartType) {\n case \"heatmap\":\n return \"sequential\";\n default:\n return \"categorical\";\n }\n}\n\n// ============================================================================\n// Component Props\n// ============================================================================\n\nexport interface BaseChartProps {\n /** Chart data (Arrow Table or JSON array) - format is auto-detected */\n data: ChartData;\n /** Chart type */\n chartType: ChartType;\n /** X-axis field key (auto-detected from schema if not provided) */\n xKey?: string;\n /** Y-axis field key(s) (auto-detected from schema if not provided) */\n yKey?: string | string[];\n /** Chart orientation @default \"vertical\" */\n orientation?: Orientation;\n /** Chart height in pixels @default 300 */\n height?: number;\n /** Chart title */\n title?: string;\n /** Show legend @default true */\n showLegend?: boolean;\n /**\n * Color palette to use. Auto-selected based on chart type if not specified.\n * - \"categorical\": Distinct colors for different categories (bar, pie, line)\n * - \"sequential\": Gradient for magnitude (heatmap)\n * - \"diverging\": Two-tone for positive/negative (correlation)\n */\n colorPalette?: ChartColorPalette;\n /** Custom colors (overrides colorPalette) */\n colors?: string[];\n /** Show data point symbols (line/area charts) @default false */\n showSymbol?: boolean;\n /** Smooth line curves (line/area charts) @default true */\n smooth?: boolean;\n /** Stack series @default false */\n stacked?: boolean;\n /** Symbol size for scatter charts @default 8 */\n symbolSize?: number;\n /** Show area fill for radar charts @default true */\n showArea?: boolean;\n /** Inner radius for pie/donut (0-100) @default 0 */\n innerRadius?: number;\n /** Show labels on pie/donut slices @default true */\n showLabels?: boolean;\n /** Label position for pie/donut @default \"outside\" */\n labelPosition?: \"outside\" | \"inside\" | \"center\";\n /** Y-axis field key for heatmap (the row dimension) */\n yAxisKey?: string;\n /** Min value for heatmap color scale */\n min?: number;\n /** Max value for heatmap color scale */\n max?: number;\n /** Additional ECharts options to merge */\n options?: Record<string, unknown>;\n /** Additional CSS classes */\n className?: string;\n}\n\n// ============================================================================\n// Base Chart Component\n// ============================================================================\n\n/**\n * Base chart component that handles both Arrow and JSON data.\n * Renders using ECharts for consistent output across both formats.\n */\nexport function BaseChart({\n data,\n chartType,\n xKey,\n yKey,\n orientation,\n height = 300,\n title,\n showLegend = true,\n colorPalette,\n colors: customColors,\n showSymbol = false,\n smooth = true,\n stacked = false,\n symbolSize = 8,\n showArea = true,\n innerRadius = 0,\n showLabels = true,\n labelPosition = \"outside\",\n yAxisKey,\n min,\n max,\n options: customOptions,\n className,\n}: BaseChartProps) {\n // Determine the appropriate color palette based on chart type\n const resolvedPalette = colorPalette ?? getDefaultPalette(chartType);\n const themeColors = useThemeColors(resolvedPalette);\n const colors = customColors ?? themeColors;\n\n // Store ECharts instance directly to avoid stale ref issues on unmount\n const echartsInstanceRef = useRef<ECharts | null>(null);\n\n // Callback ref pattern: captures the ECharts instance when ReactECharts mounts\n // This ensures we always have a stable reference to the actual instance\n const chartRefCallback = useCallback((node: ReactECharts | null) => {\n // Dispose previous instance if component is being replaced\n if (\n echartsInstanceRef.current &&\n !echartsInstanceRef.current.isDisposed()\n ) {\n echartsInstanceRef.current.dispose();\n }\n\n // Store the new instance\n if (node) {\n echartsInstanceRef.current = node.getEchartsInstance();\n } else {\n // Component unmounting - dispose the stored instance\n if (\n echartsInstanceRef.current &&\n !echartsInstanceRef.current.isDisposed()\n ) {\n echartsInstanceRef.current.dispose();\n }\n echartsInstanceRef.current = null;\n }\n }, []);\n\n // Memoize data normalization\n const normalized = useMemo(\n () =>\n chartType === \"heatmap\"\n ? normalizeHeatmapData(data, xKey, yAxisKey, yKey)\n : normalizeChartData(data, xKey, yKey, orientation),\n [data, xKey, yKey, yAxisKey, orientation, chartType],\n );\n\n // Memoize option building\n const option = useMemo(() => {\n const { xData, yFields, chartType: detectedChartType } = normalized;\n\n if (xData.length === 0) return null;\n\n // Determine chart mode first (needed to handle yDataMap)\n const isHeatmap = chartType === \"heatmap\";\n\n // Heatmaps use heatmapData instead of yDataMap\n // For other charts, yDataMap is required\n const yDataMap = \"yDataMap\" in normalized ? normalized.yDataMap : {};\n\n const baseCtx: OptionBuilderContext = {\n xData,\n yDataMap,\n yFields,\n colors,\n title,\n showLegend,\n };\n const isPie = chartType === \"pie\" || chartType === \"donut\";\n const isRadar = chartType === \"radar\";\n const isHorizontal =\n !isPie &&\n !isRadar &&\n !isHeatmap &&\n (orientation === \"horizontal\" ||\n (detectedChartType === \"categorical\" &&\n !orientation &&\n chartType === \"bar\"));\n const isTimeSeries =\n detectedChartType === \"timeseries\" &&\n !isHorizontal &&\n !isRadar &&\n !isHeatmap;\n\n // Build option based on chart type\n let opt: Record<string, unknown>;\n\n if (isHeatmap && \"yAxisData\" in normalized && \"heatmapData\" in normalized) {\n const heatmapNorm = normalized as {\n yAxisData: (string | number)[];\n heatmapData: [number, number, number][];\n min: number;\n max: number;\n } & typeof normalized;\n opt = buildHeatmapOption({\n ...baseCtx,\n yAxisData: heatmapNorm.yAxisData,\n heatmapData: heatmapNorm.heatmapData,\n min: min ?? heatmapNorm.min,\n max: max ?? heatmapNorm.max,\n showLabels,\n });\n } else if (isRadar) {\n opt = buildRadarOption(baseCtx, showArea);\n } else if (isPie) {\n opt = buildPieOption(\n baseCtx,\n chartType as \"pie\" | \"donut\",\n innerRadius,\n showLabels,\n labelPosition,\n );\n } else if (isHorizontal) {\n opt = buildHorizontalBarOption(baseCtx, stacked);\n } else {\n opt = buildCartesianOption({\n ...baseCtx,\n chartType,\n isTimeSeries,\n stacked,\n smooth,\n showSymbol,\n symbolSize,\n });\n }\n\n // Merge custom options\n return customOptions ? { ...opt, ...customOptions } : opt;\n }, [\n normalized,\n colors,\n title,\n showLegend,\n chartType,\n orientation,\n innerRadius,\n showLabels,\n labelPosition,\n stacked,\n smooth,\n showSymbol,\n symbolSize,\n showArea,\n min,\n max,\n customOptions,\n ]);\n\n if (!option) {\n return (\n <div className=\"flex items-center justify-center h-full text-muted-foreground\">\n No data\n </div>\n );\n }\n\n return (\n <ReactECharts\n ref={chartRefCallback}\n option={option}\n style={{ height }}\n className={className}\n opts={{ renderer: \"canvas\" }}\n notMerge={false}\n lazyUpdate={true}\n />\n );\n}\n"],"mappings":";;;;;;;;;;;;;AA6BA,SAAS,kBAAkB,WAAyC;AAClE,SAAQ,WAAR;EACE,KAAK,UACH,QAAO;EACT,QACE,QAAO;;;;;;;AAsEb,SAAgB,UAAU,EACxB,MACA,WACA,MACA,MACA,aACA,SAAS,KACT,OACA,aAAa,MACb,cACA,QAAQ,cACR,aAAa,OACb,SAAS,MACT,UAAU,OACV,aAAa,GACb,WAAW,MACX,cAAc,GACd,aAAa,MACb,gBAAgB,WAChB,UACA,KACA,KACA,SAAS,eACT,aACiB;CAGjB,MAAM,cAAc,eADI,gBAAgB,kBAAkB,UAAU,CACjB;CACnD,MAAM,SAAS,gBAAgB;CAG/B,MAAM,qBAAqB,OAAuB,KAAK;CAIvD,MAAM,mBAAmB,aAAa,SAA8B;AAElE,MACE,mBAAmB,WACnB,CAAC,mBAAmB,QAAQ,YAAY,CAExC,oBAAmB,QAAQ,SAAS;AAItC,MAAI,KACF,oBAAmB,UAAU,KAAK,oBAAoB;OACjD;AAEL,OACE,mBAAmB,WACnB,CAAC,mBAAmB,QAAQ,YAAY,CAExC,oBAAmB,QAAQ,SAAS;AAEtC,sBAAmB,UAAU;;IAE9B,EAAE,CAAC;CAGN,MAAM,aAAa,cAEf,cAAc,YACV,qBAAqB,MAAM,MAAM,UAAU,KAAK,GAChD,mBAAmB,MAAM,MAAM,MAAM,YAAY,EACvD;EAAC;EAAM;EAAM;EAAM;EAAU;EAAa;EAAU,CACrD;CAGD,MAAM,SAAS,cAAc;EAC3B,MAAM,EAAE,OAAO,SAAS,WAAW,sBAAsB;AAEzD,MAAI,MAAM,WAAW,EAAG,QAAO;EAG/B,MAAM,YAAY,cAAc;EAMhC,MAAM,UAAgC;GACpC;GACA,UAJe,cAAc,aAAa,WAAW,WAAW,EAAE;GAKlE;GACA;GACA;GACA;GACD;EACD,MAAM,QAAQ,cAAc,SAAS,cAAc;EACnD,MAAM,UAAU,cAAc;EAC9B,MAAM,eACJ,CAAC,SACD,CAAC,WACD,CAAC,cACA,gBAAgB,gBACd,sBAAsB,iBACrB,CAAC,eACD,cAAc;EACpB,MAAM,eACJ,sBAAsB,gBACtB,CAAC,gBACD,CAAC,WACD,CAAC;EAGH,IAAI;AAEJ,MAAI,aAAa,eAAe,cAAc,iBAAiB,YAAY;GACzE,MAAM,cAAc;AAMpB,SAAM,mBAAmB;IACvB,GAAG;IACH,WAAW,YAAY;IACvB,aAAa,YAAY;IACzB,KAAK,OAAO,YAAY;IACxB,KAAK,OAAO,YAAY;IACxB;IACD,CAAC;aACO,QACT,OAAM,iBAAiB,SAAS,SAAS;WAChC,MACT,OAAM,eACJ,SACA,WACA,aACA,YACA,cACD;WACQ,aACT,OAAM,yBAAyB,SAAS,QAAQ;MAEhD,OAAM,qBAAqB;GACzB,GAAG;GACH;GACA;GACA;GACA;GACA;GACA;GACD,CAAC;AAIJ,SAAO,gBAAgB;GAAE,GAAG;GAAK,GAAG;GAAe,GAAG;IACrD;EACD;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACD,CAAC;AAEF,KAAI,CAAC,OACH,QACE,oBAAC;EAAI,WAAU;YAAgE;GAEzE;AAIV,QACE,oBAAC;EACC,KAAK;EACG;EACR,OAAO,EAAE,QAAQ;EACN;EACX,MAAM,EAAE,UAAU,UAAU;EAC5B,UAAU;EACV,YAAY;GACZ"}
1
+ {"version":3,"file":"base.js","names":[],"sources":["../../../src/react/charts/base.tsx"],"sourcesContent":["import type { ECharts } from \"echarts\";\nimport ReactECharts from \"echarts-for-react\";\nimport { useCallback, useMemo, useRef } from \"react\";\nimport { normalizeChartData, normalizeHeatmapData } from \"./normalize\";\nimport {\n buildCartesianOption,\n buildHeatmapOption,\n buildHorizontalBarOption,\n buildPieOption,\n buildRadarOption,\n type OptionBuilderContext,\n} from \"./options\";\nimport { useThemeColors } from \"./theme\";\nimport type {\n ChartColorPalette,\n ChartData,\n ChartType,\n Orientation,\n} from \"./types\";\n\n// ============================================================================\n// Palette Selection\n// ============================================================================\n\n/**\n * Determines the appropriate color palette for a chart type.\n * - Heatmaps use sequential (low → high intensity)\n * - All other charts use categorical (distinct categories)\n */\nfunction getDefaultPalette(chartType: ChartType): ChartColorPalette {\n switch (chartType) {\n case \"heatmap\":\n return \"sequential\";\n default:\n return \"categorical\";\n }\n}\n\n// ============================================================================\n// Component Props\n// ============================================================================\n\nexport interface BaseChartProps {\n /** Chart data (Arrow Table or JSON array) - format is auto-detected */\n data: ChartData;\n /** Chart type */\n chartType: ChartType;\n /** X-axis field key (auto-detected from schema if not provided) */\n xKey?: string;\n /** Y-axis field key(s) (auto-detected from schema if not provided) */\n yKey?: string | string[];\n /** Chart orientation @default \"vertical\" */\n orientation?: Orientation;\n /** Chart height in pixels @default 300 */\n height?: number;\n /** Chart title */\n title?: string;\n /** Show legend @default true */\n showLegend?: boolean;\n /**\n * Color palette to use. Auto-selected based on chart type if not specified.\n * - \"categorical\": Distinct colors for different categories (bar, pie, line)\n * - \"sequential\": Gradient for magnitude (heatmap)\n * - \"diverging\": Two-tone for positive/negative (correlation)\n */\n colorPalette?: ChartColorPalette;\n /** Custom colors (overrides colorPalette) */\n colors?: string[];\n /** Show data point symbols (line/area charts) @default false */\n showSymbol?: boolean;\n /** Smooth line curves (line/area charts) @default true */\n smooth?: boolean;\n /** Stack series @default false */\n stacked?: boolean;\n /** Symbol size for scatter charts @default 8 */\n symbolSize?: number;\n /** Show area fill for radar charts @default true */\n showArea?: boolean;\n /** Inner radius for pie/donut (0-100) @default 0 */\n innerRadius?: number;\n /** Show labels on pie/donut slices @default true */\n showLabels?: boolean;\n /** Label position for pie/donut @default \"outside\" */\n labelPosition?: \"outside\" | \"inside\" | \"center\";\n /** Y-axis field key for heatmap (the row dimension) */\n yAxisKey?: string;\n /** Min value for heatmap color scale */\n min?: number;\n /** Max value for heatmap color scale */\n max?: number;\n /** Additional ECharts options to merge */\n options?: Record<string, unknown>;\n /** Additional CSS classes */\n className?: string;\n}\n\n// ============================================================================\n// Base Chart Component\n// ============================================================================\n\n/**\n * Base chart component that handles both Arrow and JSON data.\n * Renders using ECharts for consistent output across both formats.\n */\nexport function BaseChart({\n data,\n chartType,\n xKey,\n yKey,\n orientation,\n height = 300,\n title,\n showLegend = true,\n colorPalette,\n colors: customColors,\n showSymbol = false,\n smooth = true,\n stacked = false,\n symbolSize = 8,\n showArea = true,\n innerRadius = 0,\n showLabels = true,\n labelPosition = \"outside\",\n yAxisKey,\n min,\n max,\n options: customOptions,\n className,\n}: BaseChartProps) {\n // Determine the appropriate color palette based on chart type\n const resolvedPalette = colorPalette ?? getDefaultPalette(chartType);\n const themeColors = useThemeColors(resolvedPalette);\n const colors = customColors ?? themeColors;\n\n // Store ECharts instance directly to avoid stale ref issues on unmount\n const echartsInstanceRef = useRef<ECharts | null>(null);\n\n // Callback ref pattern: captures the ECharts instance when ReactECharts mounts\n // This ensures we always have a stable reference to the actual instance\n const chartRefCallback = useCallback((node: ReactECharts | null) => {\n // Dispose previous instance if component is being replaced\n if (\n echartsInstanceRef.current &&\n !echartsInstanceRef.current.isDisposed()\n ) {\n echartsInstanceRef.current.dispose();\n }\n\n // Store the new instance\n if (node) {\n echartsInstanceRef.current = node.getEchartsInstance();\n } else {\n // Component unmounting - dispose the stored instance\n if (\n echartsInstanceRef.current &&\n !echartsInstanceRef.current.isDisposed()\n ) {\n echartsInstanceRef.current.dispose();\n }\n echartsInstanceRef.current = null;\n }\n }, []);\n\n // Memoize data normalization\n const normalized = useMemo(\n () =>\n chartType === \"heatmap\"\n ? normalizeHeatmapData(data, xKey, yAxisKey, yKey)\n : normalizeChartData(data, xKey, yKey, orientation),\n [data, xKey, yKey, yAxisKey, orientation, chartType],\n );\n\n // Memoize option building\n const option = useMemo(() => {\n const { xData, yFields, xField, chartType: detectedChartType } = normalized;\n\n if (xData.length === 0) return null;\n\n // Determine chart mode first (needed to handle yDataMap)\n const isHeatmap = chartType === \"heatmap\";\n\n // Heatmaps use heatmapData instead of yDataMap\n // For other charts, yDataMap is required\n const yDataMap = \"yDataMap\" in normalized ? normalized.yDataMap : {};\n\n const baseCtx: OptionBuilderContext = {\n xData,\n yDataMap,\n yFields,\n colors,\n title,\n showLegend,\n xField,\n };\n const isPie = chartType === \"pie\" || chartType === \"donut\";\n const isRadar = chartType === \"radar\";\n const isHorizontal =\n !isPie &&\n !isRadar &&\n !isHeatmap &&\n (orientation === \"horizontal\" ||\n (detectedChartType === \"categorical\" &&\n !orientation &&\n chartType === \"bar\"));\n const isTimeSeries =\n detectedChartType === \"timeseries\" &&\n !isHorizontal &&\n !isRadar &&\n !isHeatmap;\n\n // Build option based on chart type\n let opt: Record<string, unknown>;\n\n if (isHeatmap && \"yAxisData\" in normalized && \"heatmapData\" in normalized) {\n const heatmapNorm = normalized as {\n yAxisData: (string | number)[];\n heatmapData: [number, number, number][];\n min: number;\n max: number;\n } & typeof normalized;\n opt = buildHeatmapOption({\n ...baseCtx,\n yAxisData: heatmapNorm.yAxisData,\n heatmapData: heatmapNorm.heatmapData,\n min: min ?? heatmapNorm.min,\n max: max ?? heatmapNorm.max,\n showLabels,\n });\n } else if (isRadar) {\n opt = buildRadarOption(baseCtx, showArea);\n } else if (isPie) {\n opt = buildPieOption(\n baseCtx,\n chartType as \"pie\" | \"donut\",\n innerRadius,\n showLabels,\n labelPosition,\n );\n } else if (isHorizontal) {\n opt = buildHorizontalBarOption(baseCtx, stacked);\n } else {\n opt = buildCartesianOption({\n ...baseCtx,\n chartType,\n isTimeSeries,\n stacked,\n smooth,\n showSymbol,\n symbolSize,\n });\n }\n\n // Merge custom options\n return customOptions ? { ...opt, ...customOptions } : opt;\n }, [\n normalized,\n colors,\n title,\n showLegend,\n chartType,\n orientation,\n innerRadius,\n showLabels,\n labelPosition,\n stacked,\n smooth,\n showSymbol,\n symbolSize,\n showArea,\n min,\n max,\n customOptions,\n ]);\n\n if (!option) {\n return (\n <div className=\"flex items-center justify-center h-full text-muted-foreground\">\n No data\n </div>\n );\n }\n\n return (\n <ReactECharts\n ref={chartRefCallback}\n option={option}\n style={{ height }}\n className={className}\n opts={{ renderer: \"canvas\" }}\n notMerge={false}\n lazyUpdate={true}\n />\n );\n}\n"],"mappings":";;;;;;;;;;;;;AA6BA,SAAS,kBAAkB,WAAyC;AAClE,SAAQ,WAAR;EACE,KAAK,UACH,QAAO;EACT,QACE,QAAO;;;;;;;AAsEb,SAAgB,UAAU,EACxB,MACA,WACA,MACA,MACA,aACA,SAAS,KACT,OACA,aAAa,MACb,cACA,QAAQ,cACR,aAAa,OACb,SAAS,MACT,UAAU,OACV,aAAa,GACb,WAAW,MACX,cAAc,GACd,aAAa,MACb,gBAAgB,WAChB,UACA,KACA,KACA,SAAS,eACT,aACiB;CAGjB,MAAM,cAAc,eADI,gBAAgB,kBAAkB,UAAU,CACjB;CACnD,MAAM,SAAS,gBAAgB;CAG/B,MAAM,qBAAqB,OAAuB,KAAK;CAIvD,MAAM,mBAAmB,aAAa,SAA8B;AAElE,MACE,mBAAmB,WACnB,CAAC,mBAAmB,QAAQ,YAAY,CAExC,oBAAmB,QAAQ,SAAS;AAItC,MAAI,KACF,oBAAmB,UAAU,KAAK,oBAAoB;OACjD;AAEL,OACE,mBAAmB,WACnB,CAAC,mBAAmB,QAAQ,YAAY,CAExC,oBAAmB,QAAQ,SAAS;AAEtC,sBAAmB,UAAU;;IAE9B,EAAE,CAAC;CAGN,MAAM,aAAa,cAEf,cAAc,YACV,qBAAqB,MAAM,MAAM,UAAU,KAAK,GAChD,mBAAmB,MAAM,MAAM,MAAM,YAAY,EACvD;EAAC;EAAM;EAAM;EAAM;EAAU;EAAa;EAAU,CACrD;CAGD,MAAM,SAAS,cAAc;EAC3B,MAAM,EAAE,OAAO,SAAS,QAAQ,WAAW,sBAAsB;AAEjE,MAAI,MAAM,WAAW,EAAG,QAAO;EAG/B,MAAM,YAAY,cAAc;EAMhC,MAAM,UAAgC;GACpC;GACA,UAJe,cAAc,aAAa,WAAW,WAAW,EAAE;GAKlE;GACA;GACA;GACA;GACA;GACD;EACD,MAAM,QAAQ,cAAc,SAAS,cAAc;EACnD,MAAM,UAAU,cAAc;EAC9B,MAAM,eACJ,CAAC,SACD,CAAC,WACD,CAAC,cACA,gBAAgB,gBACd,sBAAsB,iBACrB,CAAC,eACD,cAAc;EACpB,MAAM,eACJ,sBAAsB,gBACtB,CAAC,gBACD,CAAC,WACD,CAAC;EAGH,IAAI;AAEJ,MAAI,aAAa,eAAe,cAAc,iBAAiB,YAAY;GACzE,MAAM,cAAc;AAMpB,SAAM,mBAAmB;IACvB,GAAG;IACH,WAAW,YAAY;IACvB,aAAa,YAAY;IACzB,KAAK,OAAO,YAAY;IACxB,KAAK,OAAO,YAAY;IACxB;IACD,CAAC;aACO,QACT,OAAM,iBAAiB,SAAS,SAAS;WAChC,MACT,OAAM,eACJ,SACA,WACA,aACA,YACA,cACD;WACQ,aACT,OAAM,yBAAyB,SAAS,QAAQ;MAEhD,OAAM,qBAAqB;GACzB,GAAG;GACH;GACA;GACA;GACA;GACA;GACA;GACD,CAAC;AAIJ,SAAO,gBAAgB;GAAE,GAAG;GAAK,GAAG;GAAe,GAAG;IACrD;EACD;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACD,CAAC;AAEF,KAAI,CAAC,OACH,QACE,oBAAC;EAAI,WAAU;YAAgE;GAEzE;AAIV,QACE,oBAAC;EACC,KAAK;EACG;EACR,OAAO,EAAE,QAAQ;EACN;EACX,MAAM,EAAE,UAAU,UAAU;EAC5B,UAAU;EACV,YAAY;GACZ"}
@@ -1 +1 @@
1
- {"version":3,"file":"normalize.d.ts","names":[],"sources":["../../../src/react/charts/normalize.ts"],"mappings":";;;;;AAuNA;;iBAAgB,kBAAA,CACd,IAAA,EAAM,SAAA,EACN,IAAA,WACA,IAAA,sBACA,WAAA,GAAc,WAAA,GACb,mBAAA;;;;;;UAqFc,qBAAA,SAA8B,uBAAA;EAzF7C;EA2FA,SAAA;EAzFA;EA2FA,WAAA;EA1FA;EA4FA,GAAA;EA3FoB;EA6FpB,GAAA;AAAA;;;;;;;;;;iBAYc,oBAAA,CACd,IAAA,EAAM,SAAA,EACN,IAAA,WACA,QAAA,WACA,QAAA,uBACC,qBAAA"}
1
+ {"version":3,"file":"normalize.d.ts","names":[],"sources":["../../../src/react/charts/normalize.ts"],"mappings":";;;;;AA2NA;;iBAAgB,kBAAA,CACd,IAAA,EAAM,SAAA,EACN,IAAA,WACA,IAAA,sBACA,WAAA,GAAc,WAAA,GACb,mBAAA;;;;;;UAiGc,qBAAA,SAA8B,uBAAA;EArG7C;EAuGA,SAAA;EArGA;EAuGA,WAAA;EAtGA;EAwGA,GAAA;EAvGoB;EAyGpB,GAAA;AAAA;;;;;;;;;;iBAYc,oBAAA,CACd,IAAA,EAAM,SAAA,EACN,IAAA,WACA,QAAA,WACA,QAAA,uBACC,qBAAA"}
@@ -2,7 +2,7 @@ import { DATE_FIELD_PATTERNS, NAME_FIELD_PATTERNS } from "../../js/constants.js"
2
2
  import { ArrowClient } from "../../js/arrow/arrow-client.js";
3
3
  import "../../js/index.js";
4
4
  import { isArrowTable } from "./types.js";
5
- import { sortTimeSeriesAscending, toChartArray } from "./utils.js";
5
+ import { sortNumericAscending, sortTimeSeriesAscending, toChartArray } from "./utils.js";
6
6
 
7
7
  //#region src/react/charts/normalize.ts
8
8
  /**
@@ -134,6 +134,7 @@ function normalizeChartData(data, xKey, yKey, orientation) {
134
134
  let yDataMap = {};
135
135
  for (const key of resolvedYKeys) yDataMap[key] = toChartArray(rawYDataMap[key] ?? []);
136
136
  if (detected.chartType === "timeseries") ({xData, yDataMap} = sortTimeSeriesAscending(xData, yDataMap, resolvedYKeys));
137
+ else if (xData.length > 0 && xData.every(isNumericValue)) ({xData, yDataMap} = sortNumericAscending(xData, yDataMap, resolvedYKeys));
137
138
  return {
138
139
  xData,
139
140
  yDataMap,
@@ -151,6 +152,7 @@ function normalizeChartData(data, xKey, yKey, orientation) {
151
152
  let yDataMap = {};
152
153
  for (const key of resolvedYKeys) yDataMap[key] = toChartArray(rawYDataMap[key] ?? []);
153
154
  if (detected.chartType === "timeseries") ({xData, yDataMap} = sortTimeSeriesAscending(xData, yDataMap, resolvedYKeys));
155
+ else if (xData.length > 0 && xData.every(isNumericValue)) ({xData, yDataMap} = sortNumericAscending(xData, yDataMap, resolvedYKeys));
154
156
  return {
155
157
  xData,
156
158
  yDataMap,
@@ -1 +1 @@
1
- {"version":3,"file":"normalize.js","names":[],"sources":["../../../src/react/charts/normalize.ts"],"sourcesContent":["import type { Table } from \"apache-arrow\";\nimport { ArrowClient } from \"@/js\";\nimport { DATE_FIELD_PATTERNS, NAME_FIELD_PATTERNS } from \"./constants\";\nimport type {\n ChartData,\n NormalizedChartData,\n NormalizedChartDataBase,\n Orientation,\n} from \"./types\";\nimport { isArrowTable } from \"./types\";\nimport { sortTimeSeriesAscending, toChartArray } from \"./utils\";\n\n// ============================================================================\n// Type Detection Helpers\n// ============================================================================\n\n/**\n * Checks if a value looks like an ISO date string\n */\nfunction isDateString(value: unknown): boolean {\n if (typeof value !== \"string\") return false;\n return /^\\d{4}-\\d{2}-\\d{2}(T|$)/.test(value);\n}\n\n/**\n * Checks if a value is numeric (number or numeric string)\n */\nfunction isNumericValue(value: unknown): boolean {\n if (typeof value === \"number\") return true;\n if (typeof value === \"bigint\") return true;\n if (typeof value === \"string\") {\n const trimmed = value.trim();\n if (trimmed === \"\" || isDateString(trimmed)) return false;\n const parsed = Number(trimmed);\n return !Number.isNaN(parsed) && Number.isFinite(parsed);\n }\n return false;\n}\n\n/**\n * Checks if a value looks like a category/label (non-numeric string)\n */\nfunction isCategoryValue(value: unknown): boolean {\n if (typeof value !== \"string\") return false;\n const trimmed = value.trim();\n if (trimmed === \"\") return false;\n if (/^\\d{4}-\\d{2}-\\d{2}/.test(trimmed)) return false;\n const parsed = Number(trimmed);\n return Number.isNaN(parsed) || !Number.isFinite(parsed);\n}\n\n// ============================================================================\n// Field Detection\n// ============================================================================\n\n/**\n * Detects fields from JSON data for charting\n */\nfunction detectFieldsFromJson(\n data: Record<string, unknown>[],\n orientation?: Orientation,\n): {\n xField: string;\n yFields: string[];\n chartType: \"timeseries\" | \"categorical\";\n} {\n if (!data || data.length === 0) {\n return { xField: \"x\", yFields: [\"y\"], chartType: \"categorical\" };\n }\n\n const firstRow = data[0];\n const keys = Object.keys(firstRow);\n\n // Detect date fields by key name OR by value being a date string\n const dateFields = keys.filter((key) => {\n const value = firstRow[key];\n const keyMatchesDatePattern = DATE_FIELD_PATTERNS.some((p) =>\n key.toLowerCase().includes(p),\n );\n const valueIsDateString = isDateString(value);\n return keyMatchesDatePattern || valueIsDateString;\n });\n\n // Detect name/category fields by pattern AND value type\n let nameFields = keys.filter((key) => {\n const value = firstRow[key];\n return (\n isCategoryValue(value) &&\n !isDateString(value) &&\n NAME_FIELD_PATTERNS.some((p) => key.toLowerCase().includes(p))\n );\n });\n\n // Fallback: any string field that isn't a date or ID\n if (nameFields.length === 0) {\n nameFields = keys.filter((key) => {\n const value = firstRow[key];\n return (\n isCategoryValue(value) &&\n !isDateString(value) &&\n !dateFields.includes(key) &&\n !key.toLowerCase().endsWith(\"_id\")\n );\n });\n }\n\n // Detect numeric fields\n const numericFields = keys.filter((key) => {\n const value = firstRow[key];\n return isNumericValue(value) && !dateFields.includes(key);\n });\n\n const isHorizontal = orientation === \"horizontal\";\n\n if (isHorizontal || (nameFields.length > 0 && dateFields.length === 0)) {\n const xField = nameFields[0] || dateFields[0] || keys[0];\n const yFields =\n numericFields.length > 0\n ? numericFields\n : keys.filter((k) => k !== xField);\n return { xField, yFields, chartType: \"categorical\" };\n }\n\n const xField = dateFields[0] || nameFields[0] || keys[0];\n const yFields =\n numericFields.length > 0 ? numericFields : keys.filter((k) => k !== xField);\n return {\n xField,\n yFields,\n chartType: dateFields.length > 0 ? \"timeseries\" : \"categorical\",\n };\n}\n\n// ============================================================================\n// Value Conversion\n// ============================================================================\n\n/**\n * Converts a JSON value to a chart-compatible value.\n */\nfunction jsonValueToChartValue(\n value: unknown,\n isYValue: boolean,\n isDateField: boolean,\n): string | number {\n if (value === null || value === undefined) {\n return isYValue ? 0 : \"\";\n }\n if (typeof value === \"number\") {\n return value;\n }\n if (typeof value === \"bigint\") {\n return Number(value);\n }\n if (typeof value === \"string\") {\n if (isDateField && isDateString(value)) {\n const timestamp = new Date(value).getTime();\n if (!Number.isNaN(timestamp)) {\n return timestamp;\n }\n }\n if (isYValue) {\n const trimmed = value.trim();\n const parsed = Number(trimmed);\n if (!Number.isNaN(parsed) && Number.isFinite(parsed)) {\n return parsed;\n }\n }\n return value;\n }\n return String(value);\n}\n\n// ============================================================================\n// Data Extraction\n// ============================================================================\n\n/**\n * Extracts chart data from JSON array\n */\nfunction extractFromJson(\n data: Record<string, unknown>[],\n xField: string,\n yFields: string[],\n): {\n xData: (string | number)[];\n yDataMap: Record<string, (string | number)[]>;\n} {\n const xData: (string | number)[] = [];\n const yDataMap: Record<string, (string | number)[]> = {};\n\n for (const field of yFields) {\n yDataMap[field] = [];\n }\n\n const xIsDateField = data.length > 0 && isDateString(data[0][xField]);\n\n for (const row of data) {\n xData.push(jsonValueToChartValue(row[xField], false, xIsDateField));\n for (const field of yFields) {\n yDataMap[field].push(jsonValueToChartValue(row[field], true, false));\n }\n }\n\n return { xData, yDataMap };\n}\n\n// ============================================================================\n// Main Normalization Function\n// ============================================================================\n\n/**\n * Normalizes chart data from either Arrow or JSON format.\n * Converts BigInt and Date values to chart-compatible types.\n */\nexport function normalizeChartData(\n data: ChartData,\n xKey?: string,\n yKey?: string | string[],\n orientation?: Orientation,\n): NormalizedChartData {\n if (isArrowTable(data)) {\n const table = data as Table;\n const detected = ArrowClient.detectFieldsFromArrow(table, orientation);\n const resolvedXKey = xKey ?? detected.xField;\n const resolvedYKeys = yKey\n ? Array.isArray(yKey)\n ? yKey\n : [yKey]\n : detected.yFields;\n\n const { xData: rawXData, yDataMap: rawYDataMap } =\n ArrowClient.extractChartData(table, resolvedXKey, resolvedYKeys);\n\n let xData = toChartArray(rawXData);\n let yDataMap: Record<string, (string | number)[]> = {};\n for (const key of resolvedYKeys) {\n yDataMap[key] = toChartArray(rawYDataMap[key] ?? []);\n }\n\n if (detected.chartType === \"timeseries\") {\n ({ xData, yDataMap } = sortTimeSeriesAscending(\n xData,\n yDataMap,\n resolvedYKeys,\n ));\n }\n\n return {\n xData,\n yDataMap,\n xField: resolvedXKey,\n yFields: resolvedYKeys,\n chartType: detected.chartType,\n };\n }\n\n // JSON Array\n const jsonData = data as Record<string, unknown>[];\n const detected = detectFieldsFromJson(jsonData, orientation);\n const resolvedXKey = xKey ?? detected.xField;\n const resolvedYKeys = yKey\n ? Array.isArray(yKey)\n ? yKey\n : [yKey]\n : detected.yFields;\n\n const { xData: rawXData, yDataMap: rawYDataMap } = extractFromJson(\n jsonData,\n resolvedXKey,\n resolvedYKeys,\n );\n\n let xData = toChartArray(rawXData);\n let yDataMap: Record<string, (string | number)[]> = {};\n for (const key of resolvedYKeys) {\n yDataMap[key] = toChartArray(rawYDataMap[key] ?? []);\n }\n\n if (detected.chartType === \"timeseries\") {\n ({ xData, yDataMap } = sortTimeSeriesAscending(\n xData,\n yDataMap,\n resolvedYKeys,\n ));\n }\n\n return {\n xData,\n yDataMap,\n xField: resolvedXKey,\n yFields: resolvedYKeys,\n chartType: detected.chartType,\n };\n}\n\n// ============================================================================\n// Heatmap Data Normalization\n// ============================================================================\n\n/**\n * Normalized data for heatmap charts.\n * Extends base (not NormalizedChartData) because heatmaps don't use yDataMap.\n * Instead, they use heatmapData which contains [xIndex, yIndex, value] tuples.\n */\nexport interface NormalizedHeatmapData extends NormalizedChartDataBase {\n /** Y-axis categories (rows) */\n yAxisData: (string | number)[];\n /** Heatmap data as [xIndex, yIndex, value] tuples */\n heatmapData: [number, number, number][];\n /** Min value in the data */\n min: number;\n /** Max value in the data */\n max: number;\n}\n\n/**\n * Normalizes data specifically for heatmap charts.\n * Expects data in format: `{ xKey: string, yAxisKey: string, valueKey: number }`\n *\n * @param data - Raw data (Arrow Table or JSON array)\n * @param xKey - Field key for X-axis (columns)\n * @param yAxisKey - Field key for Y-axis (rows)\n * @param valueKey - Field key for the cell values\n */\nexport function normalizeHeatmapData(\n data: ChartData,\n xKey?: string,\n yAxisKey?: string,\n valueKey?: string | string[],\n): NormalizedHeatmapData {\n // First, get the standard normalization\n const jsonData = isArrowTable(data)\n ? extractJsonFromArrow(data)\n : (data as Record<string, unknown>[]);\n\n if (jsonData.length === 0) {\n return {\n xData: [],\n xField: xKey ?? \"x\",\n yFields: [],\n chartType: \"categorical\",\n yAxisData: [],\n heatmapData: [],\n min: 0,\n max: 0,\n };\n }\n\n // Detect fields if not provided\n const keys = Object.keys(jsonData[0]);\n const resolvedXKey = xKey ?? keys[0];\n const resolvedYAxisKey = yAxisKey ?? keys[1];\n const resolvedValueKey = valueKey\n ? Array.isArray(valueKey)\n ? valueKey[0]\n : valueKey\n : keys[2];\n\n // Extract unique X and Y categories\n const xSet = new Set<string | number>();\n const ySet = new Set<string | number>();\n\n for (const row of jsonData) {\n const xVal = jsonValueToChartValue(row[resolvedXKey], false, false);\n const yVal = jsonValueToChartValue(row[resolvedYAxisKey], false, false);\n xSet.add(xVal);\n ySet.add(yVal);\n }\n\n const xData = Array.from(xSet);\n const yAxisData = Array.from(ySet);\n\n // Create index maps for fast lookup\n const xIndexMap = new Map<string | number, number>();\n const yIndexMap = new Map<string | number, number>();\n xData.forEach((v, i) => {\n xIndexMap.set(v, i);\n });\n yAxisData.forEach((v, i) => {\n yIndexMap.set(v, i);\n });\n\n // Build heatmap data and track min/max\n const heatmapData: [number, number, number][] = [];\n let min = Number.POSITIVE_INFINITY;\n let max = Number.NEGATIVE_INFINITY;\n\n for (const row of jsonData) {\n const xVal = jsonValueToChartValue(row[resolvedXKey], false, false);\n const yVal = jsonValueToChartValue(row[resolvedYAxisKey], false, false);\n const value = jsonValueToChartValue(row[resolvedValueKey], true, false);\n\n const xIdx = xIndexMap.get(xVal);\n const yIdx = yIndexMap.get(yVal);\n const numValue = typeof value === \"number\" ? value : 0;\n\n if (xIdx !== undefined && yIdx !== undefined) {\n heatmapData.push([xIdx, yIdx, numValue]);\n min = Math.min(min, numValue);\n max = Math.max(max, numValue);\n }\n }\n\n // Handle edge case where no valid data was found\n if (heatmapData.length === 0) {\n min = 0;\n max = 0;\n }\n\n return {\n xData,\n xField: resolvedXKey,\n yFields: [resolvedValueKey],\n chartType: \"categorical\",\n yAxisData,\n heatmapData,\n min,\n max,\n };\n}\n\n/**\n * Helper to extract JSON array from Arrow table for heatmap processing.\n */\nfunction extractJsonFromArrow(table: Table): Record<string, unknown>[] {\n const result: Record<string, unknown>[] = [];\n const fields = table.schema.fields.map((f) => f.name);\n\n for (let i = 0; i < table.numRows; i++) {\n const row: Record<string, unknown> = {};\n for (const field of fields) {\n const col = table.getChild(field);\n row[field] = col?.get(i);\n }\n result.push(row);\n }\n\n return result;\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"normalize.js","names":[],"sources":["../../../src/react/charts/normalize.ts"],"sourcesContent":["import type { Table } from \"apache-arrow\";\nimport { ArrowClient } from \"@/js\";\nimport { DATE_FIELD_PATTERNS, NAME_FIELD_PATTERNS } from \"./constants\";\nimport type {\n ChartData,\n NormalizedChartData,\n NormalizedChartDataBase,\n Orientation,\n} from \"./types\";\nimport { isArrowTable } from \"./types\";\nimport {\n sortNumericAscending,\n sortTimeSeriesAscending,\n toChartArray,\n} from \"./utils\";\n\n// ============================================================================\n// Type Detection Helpers\n// ============================================================================\n\n/**\n * Checks if a value looks like an ISO date string\n */\nfunction isDateString(value: unknown): boolean {\n if (typeof value !== \"string\") return false;\n return /^\\d{4}-\\d{2}-\\d{2}(T|$)/.test(value);\n}\n\n/**\n * Checks if a value is numeric (number or numeric string)\n */\nfunction isNumericValue(value: unknown): boolean {\n if (typeof value === \"number\") return true;\n if (typeof value === \"bigint\") return true;\n if (typeof value === \"string\") {\n const trimmed = value.trim();\n if (trimmed === \"\" || isDateString(trimmed)) return false;\n const parsed = Number(trimmed);\n return !Number.isNaN(parsed) && Number.isFinite(parsed);\n }\n return false;\n}\n\n/**\n * Checks if a value looks like a category/label (non-numeric string)\n */\nfunction isCategoryValue(value: unknown): boolean {\n if (typeof value !== \"string\") return false;\n const trimmed = value.trim();\n if (trimmed === \"\") return false;\n if (/^\\d{4}-\\d{2}-\\d{2}/.test(trimmed)) return false;\n const parsed = Number(trimmed);\n return Number.isNaN(parsed) || !Number.isFinite(parsed);\n}\n\n// ============================================================================\n// Field Detection\n// ============================================================================\n\n/**\n * Detects fields from JSON data for charting\n */\nfunction detectFieldsFromJson(\n data: Record<string, unknown>[],\n orientation?: Orientation,\n): {\n xField: string;\n yFields: string[];\n chartType: \"timeseries\" | \"categorical\";\n} {\n if (!data || data.length === 0) {\n return { xField: \"x\", yFields: [\"y\"], chartType: \"categorical\" };\n }\n\n const firstRow = data[0];\n const keys = Object.keys(firstRow);\n\n // Detect date fields by key name OR by value being a date string\n const dateFields = keys.filter((key) => {\n const value = firstRow[key];\n const keyMatchesDatePattern = DATE_FIELD_PATTERNS.some((p) =>\n key.toLowerCase().includes(p),\n );\n const valueIsDateString = isDateString(value);\n return keyMatchesDatePattern || valueIsDateString;\n });\n\n // Detect name/category fields by pattern AND value type\n let nameFields = keys.filter((key) => {\n const value = firstRow[key];\n return (\n isCategoryValue(value) &&\n !isDateString(value) &&\n NAME_FIELD_PATTERNS.some((p) => key.toLowerCase().includes(p))\n );\n });\n\n // Fallback: any string field that isn't a date or ID\n if (nameFields.length === 0) {\n nameFields = keys.filter((key) => {\n const value = firstRow[key];\n return (\n isCategoryValue(value) &&\n !isDateString(value) &&\n !dateFields.includes(key) &&\n !key.toLowerCase().endsWith(\"_id\")\n );\n });\n }\n\n // Detect numeric fields\n const numericFields = keys.filter((key) => {\n const value = firstRow[key];\n return isNumericValue(value) && !dateFields.includes(key);\n });\n\n const isHorizontal = orientation === \"horizontal\";\n\n if (isHorizontal || (nameFields.length > 0 && dateFields.length === 0)) {\n const xField = nameFields[0] || dateFields[0] || keys[0];\n const yFields =\n numericFields.length > 0\n ? numericFields\n : keys.filter((k) => k !== xField);\n return { xField, yFields, chartType: \"categorical\" };\n }\n\n const xField = dateFields[0] || nameFields[0] || keys[0];\n const yFields =\n numericFields.length > 0 ? numericFields : keys.filter((k) => k !== xField);\n return {\n xField,\n yFields,\n chartType: dateFields.length > 0 ? \"timeseries\" : \"categorical\",\n };\n}\n\n// ============================================================================\n// Value Conversion\n// ============================================================================\n\n/**\n * Converts a JSON value to a chart-compatible value.\n */\nfunction jsonValueToChartValue(\n value: unknown,\n isYValue: boolean,\n isDateField: boolean,\n): string | number {\n if (value === null || value === undefined) {\n return isYValue ? 0 : \"\";\n }\n if (typeof value === \"number\") {\n return value;\n }\n if (typeof value === \"bigint\") {\n return Number(value);\n }\n if (typeof value === \"string\") {\n if (isDateField && isDateString(value)) {\n const timestamp = new Date(value).getTime();\n if (!Number.isNaN(timestamp)) {\n return timestamp;\n }\n }\n if (isYValue) {\n const trimmed = value.trim();\n const parsed = Number(trimmed);\n if (!Number.isNaN(parsed) && Number.isFinite(parsed)) {\n return parsed;\n }\n }\n return value;\n }\n return String(value);\n}\n\n// ============================================================================\n// Data Extraction\n// ============================================================================\n\n/**\n * Extracts chart data from JSON array\n */\nfunction extractFromJson(\n data: Record<string, unknown>[],\n xField: string,\n yFields: string[],\n): {\n xData: (string | number)[];\n yDataMap: Record<string, (string | number)[]>;\n} {\n const xData: (string | number)[] = [];\n const yDataMap: Record<string, (string | number)[]> = {};\n\n for (const field of yFields) {\n yDataMap[field] = [];\n }\n\n const xIsDateField = data.length > 0 && isDateString(data[0][xField]);\n\n for (const row of data) {\n xData.push(jsonValueToChartValue(row[xField], false, xIsDateField));\n for (const field of yFields) {\n yDataMap[field].push(jsonValueToChartValue(row[field], true, false));\n }\n }\n\n return { xData, yDataMap };\n}\n\n// ============================================================================\n// Main Normalization Function\n// ============================================================================\n\n/**\n * Normalizes chart data from either Arrow or JSON format.\n * Converts BigInt and Date values to chart-compatible types.\n */\nexport function normalizeChartData(\n data: ChartData,\n xKey?: string,\n yKey?: string | string[],\n orientation?: Orientation,\n): NormalizedChartData {\n if (isArrowTable(data)) {\n const table = data as Table;\n const detected = ArrowClient.detectFieldsFromArrow(table, orientation);\n const resolvedXKey = xKey ?? detected.xField;\n const resolvedYKeys = yKey\n ? Array.isArray(yKey)\n ? yKey\n : [yKey]\n : detected.yFields;\n\n const { xData: rawXData, yDataMap: rawYDataMap } =\n ArrowClient.extractChartData(table, resolvedXKey, resolvedYKeys);\n\n let xData = toChartArray(rawXData);\n let yDataMap: Record<string, (string | number)[]> = {};\n for (const key of resolvedYKeys) {\n yDataMap[key] = toChartArray(rawYDataMap[key] ?? []);\n }\n\n if (detected.chartType === \"timeseries\") {\n ({ xData, yDataMap } = sortTimeSeriesAscending(\n xData,\n yDataMap,\n resolvedYKeys,\n ));\n } else if (xData.length > 0 && xData.every(isNumericValue)) {\n ({ xData, yDataMap } = sortNumericAscending(\n xData,\n yDataMap,\n resolvedYKeys,\n ));\n }\n\n return {\n xData,\n yDataMap,\n xField: resolvedXKey,\n yFields: resolvedYKeys,\n chartType: detected.chartType,\n };\n }\n\n // JSON Array\n const jsonData = data as Record<string, unknown>[];\n const detected = detectFieldsFromJson(jsonData, orientation);\n const resolvedXKey = xKey ?? detected.xField;\n const resolvedYKeys = yKey\n ? Array.isArray(yKey)\n ? yKey\n : [yKey]\n : detected.yFields;\n\n const { xData: rawXData, yDataMap: rawYDataMap } = extractFromJson(\n jsonData,\n resolvedXKey,\n resolvedYKeys,\n );\n\n let xData = toChartArray(rawXData);\n let yDataMap: Record<string, (string | number)[]> = {};\n for (const key of resolvedYKeys) {\n yDataMap[key] = toChartArray(rawYDataMap[key] ?? []);\n }\n\n if (detected.chartType === \"timeseries\") {\n ({ xData, yDataMap } = sortTimeSeriesAscending(\n xData,\n yDataMap,\n resolvedYKeys,\n ));\n } else if (xData.length > 0 && xData.every(isNumericValue)) {\n ({ xData, yDataMap } = sortNumericAscending(\n xData,\n yDataMap,\n resolvedYKeys,\n ));\n }\n\n return {\n xData,\n yDataMap,\n xField: resolvedXKey,\n yFields: resolvedYKeys,\n chartType: detected.chartType,\n };\n}\n\n// ============================================================================\n// Heatmap Data Normalization\n// ============================================================================\n\n/**\n * Normalized data for heatmap charts.\n * Extends base (not NormalizedChartData) because heatmaps don't use yDataMap.\n * Instead, they use heatmapData which contains [xIndex, yIndex, value] tuples.\n */\nexport interface NormalizedHeatmapData extends NormalizedChartDataBase {\n /** Y-axis categories (rows) */\n yAxisData: (string | number)[];\n /** Heatmap data as [xIndex, yIndex, value] tuples */\n heatmapData: [number, number, number][];\n /** Min value in the data */\n min: number;\n /** Max value in the data */\n max: number;\n}\n\n/**\n * Normalizes data specifically for heatmap charts.\n * Expects data in format: `{ xKey: string, yAxisKey: string, valueKey: number }`\n *\n * @param data - Raw data (Arrow Table or JSON array)\n * @param xKey - Field key for X-axis (columns)\n * @param yAxisKey - Field key for Y-axis (rows)\n * @param valueKey - Field key for the cell values\n */\nexport function normalizeHeatmapData(\n data: ChartData,\n xKey?: string,\n yAxisKey?: string,\n valueKey?: string | string[],\n): NormalizedHeatmapData {\n // First, get the standard normalization\n const jsonData = isArrowTable(data)\n ? extractJsonFromArrow(data)\n : (data as Record<string, unknown>[]);\n\n if (jsonData.length === 0) {\n return {\n xData: [],\n xField: xKey ?? \"x\",\n yFields: [],\n chartType: \"categorical\",\n yAxisData: [],\n heatmapData: [],\n min: 0,\n max: 0,\n };\n }\n\n // Detect fields if not provided\n const keys = Object.keys(jsonData[0]);\n const resolvedXKey = xKey ?? keys[0];\n const resolvedYAxisKey = yAxisKey ?? keys[1];\n const resolvedValueKey = valueKey\n ? Array.isArray(valueKey)\n ? valueKey[0]\n : valueKey\n : keys[2];\n\n // Extract unique X and Y categories\n const xSet = new Set<string | number>();\n const ySet = new Set<string | number>();\n\n for (const row of jsonData) {\n const xVal = jsonValueToChartValue(row[resolvedXKey], false, false);\n const yVal = jsonValueToChartValue(row[resolvedYAxisKey], false, false);\n xSet.add(xVal);\n ySet.add(yVal);\n }\n\n const xData = Array.from(xSet);\n const yAxisData = Array.from(ySet);\n\n // Create index maps for fast lookup\n const xIndexMap = new Map<string | number, number>();\n const yIndexMap = new Map<string | number, number>();\n xData.forEach((v, i) => {\n xIndexMap.set(v, i);\n });\n yAxisData.forEach((v, i) => {\n yIndexMap.set(v, i);\n });\n\n // Build heatmap data and track min/max\n const heatmapData: [number, number, number][] = [];\n let min = Number.POSITIVE_INFINITY;\n let max = Number.NEGATIVE_INFINITY;\n\n for (const row of jsonData) {\n const xVal = jsonValueToChartValue(row[resolvedXKey], false, false);\n const yVal = jsonValueToChartValue(row[resolvedYAxisKey], false, false);\n const value = jsonValueToChartValue(row[resolvedValueKey], true, false);\n\n const xIdx = xIndexMap.get(xVal);\n const yIdx = yIndexMap.get(yVal);\n const numValue = typeof value === \"number\" ? value : 0;\n\n if (xIdx !== undefined && yIdx !== undefined) {\n heatmapData.push([xIdx, yIdx, numValue]);\n min = Math.min(min, numValue);\n max = Math.max(max, numValue);\n }\n }\n\n // Handle edge case where no valid data was found\n if (heatmapData.length === 0) {\n min = 0;\n max = 0;\n }\n\n return {\n xData,\n xField: resolvedXKey,\n yFields: [resolvedValueKey],\n chartType: \"categorical\",\n yAxisData,\n heatmapData,\n min,\n max,\n };\n}\n\n/**\n * Helper to extract JSON array from Arrow table for heatmap processing.\n */\nfunction extractJsonFromArrow(table: Table): Record<string, unknown>[] {\n const result: Record<string, unknown>[] = [];\n const fields = table.schema.fields.map((f) => f.name);\n\n for (let i = 0; i < table.numRows; i++) {\n const row: Record<string, unknown> = {};\n for (const field of fields) {\n const col = table.getChild(field);\n row[field] = col?.get(i);\n }\n result.push(row);\n }\n\n return result;\n}\n"],"mappings":";;;;;;;;;;AAuBA,SAAS,aAAa,OAAyB;AAC7C,KAAI,OAAO,UAAU,SAAU,QAAO;AACtC,QAAO,0BAA0B,KAAK,MAAM;;;;;AAM9C,SAAS,eAAe,OAAyB;AAC/C,KAAI,OAAO,UAAU,SAAU,QAAO;AACtC,KAAI,OAAO,UAAU,SAAU,QAAO;AACtC,KAAI,OAAO,UAAU,UAAU;EAC7B,MAAM,UAAU,MAAM,MAAM;AAC5B,MAAI,YAAY,MAAM,aAAa,QAAQ,CAAE,QAAO;EACpD,MAAM,SAAS,OAAO,QAAQ;AAC9B,SAAO,CAAC,OAAO,MAAM,OAAO,IAAI,OAAO,SAAS,OAAO;;AAEzD,QAAO;;;;;AAMT,SAAS,gBAAgB,OAAyB;AAChD,KAAI,OAAO,UAAU,SAAU,QAAO;CACtC,MAAM,UAAU,MAAM,MAAM;AAC5B,KAAI,YAAY,GAAI,QAAO;AAC3B,KAAI,qBAAqB,KAAK,QAAQ,CAAE,QAAO;CAC/C,MAAM,SAAS,OAAO,QAAQ;AAC9B,QAAO,OAAO,MAAM,OAAO,IAAI,CAAC,OAAO,SAAS,OAAO;;;;;AAUzD,SAAS,qBACP,MACA,aAKA;AACA,KAAI,CAAC,QAAQ,KAAK,WAAW,EAC3B,QAAO;EAAE,QAAQ;EAAK,SAAS,CAAC,IAAI;EAAE,WAAW;EAAe;CAGlE,MAAM,WAAW,KAAK;CACtB,MAAM,OAAO,OAAO,KAAK,SAAS;CAGlC,MAAM,aAAa,KAAK,QAAQ,QAAQ;EACtC,MAAM,QAAQ,SAAS;EACvB,MAAM,wBAAwB,oBAAoB,MAAM,MACtD,IAAI,aAAa,CAAC,SAAS,EAAE,CAC9B;EACD,MAAM,oBAAoB,aAAa,MAAM;AAC7C,SAAO,yBAAyB;GAChC;CAGF,IAAI,aAAa,KAAK,QAAQ,QAAQ;EACpC,MAAM,QAAQ,SAAS;AACvB,SACE,gBAAgB,MAAM,IACtB,CAAC,aAAa,MAAM,IACpB,oBAAoB,MAAM,MAAM,IAAI,aAAa,CAAC,SAAS,EAAE,CAAC;GAEhE;AAGF,KAAI,WAAW,WAAW,EACxB,cAAa,KAAK,QAAQ,QAAQ;EAChC,MAAM,QAAQ,SAAS;AACvB,SACE,gBAAgB,MAAM,IACtB,CAAC,aAAa,MAAM,IACpB,CAAC,WAAW,SAAS,IAAI,IACzB,CAAC,IAAI,aAAa,CAAC,SAAS,MAAM;GAEpC;CAIJ,MAAM,gBAAgB,KAAK,QAAQ,QAAQ;EACzC,MAAM,QAAQ,SAAS;AACvB,SAAO,eAAe,MAAM,IAAI,CAAC,WAAW,SAAS,IAAI;GACzD;AAIF,KAFqB,gBAAgB,gBAEhB,WAAW,SAAS,KAAK,WAAW,WAAW,GAAI;EACtE,MAAM,SAAS,WAAW,MAAM,WAAW,MAAM,KAAK;AAKtD,SAAO;GAAE;GAAQ,SAHf,cAAc,SAAS,IACnB,gBACA,KAAK,QAAQ,MAAM,MAAM,OAAO;GACZ,WAAW;GAAe;;CAGtD,MAAM,SAAS,WAAW,MAAM,WAAW,MAAM,KAAK;AAGtD,QAAO;EACL;EACA,SAHA,cAAc,SAAS,IAAI,gBAAgB,KAAK,QAAQ,MAAM,MAAM,OAAO;EAI3E,WAAW,WAAW,SAAS,IAAI,eAAe;EACnD;;;;;AAUH,SAAS,sBACP,OACA,UACA,aACiB;AACjB,KAAI,UAAU,QAAQ,UAAU,OAC9B,QAAO,WAAW,IAAI;AAExB,KAAI,OAAO,UAAU,SACnB,QAAO;AAET,KAAI,OAAO,UAAU,SACnB,QAAO,OAAO,MAAM;AAEtB,KAAI,OAAO,UAAU,UAAU;AAC7B,MAAI,eAAe,aAAa,MAAM,EAAE;GACtC,MAAM,YAAY,IAAI,KAAK,MAAM,CAAC,SAAS;AAC3C,OAAI,CAAC,OAAO,MAAM,UAAU,CAC1B,QAAO;;AAGX,MAAI,UAAU;GACZ,MAAM,UAAU,MAAM,MAAM;GAC5B,MAAM,SAAS,OAAO,QAAQ;AAC9B,OAAI,CAAC,OAAO,MAAM,OAAO,IAAI,OAAO,SAAS,OAAO,CAClD,QAAO;;AAGX,SAAO;;AAET,QAAO,OAAO,MAAM;;;;;AAUtB,SAAS,gBACP,MACA,QACA,SAIA;CACA,MAAM,QAA6B,EAAE;CACrC,MAAM,WAAgD,EAAE;AAExD,MAAK,MAAM,SAAS,QAClB,UAAS,SAAS,EAAE;CAGtB,MAAM,eAAe,KAAK,SAAS,KAAK,aAAa,KAAK,GAAG,QAAQ;AAErE,MAAK,MAAM,OAAO,MAAM;AACtB,QAAM,KAAK,sBAAsB,IAAI,SAAS,OAAO,aAAa,CAAC;AACnE,OAAK,MAAM,SAAS,QAClB,UAAS,OAAO,KAAK,sBAAsB,IAAI,QAAQ,MAAM,MAAM,CAAC;;AAIxE,QAAO;EAAE;EAAO;EAAU;;;;;;AAW5B,SAAgB,mBACd,MACA,MACA,MACA,aACqB;AACrB,KAAI,aAAa,KAAK,EAAE;EACtB,MAAM,QAAQ;EACd,MAAM,WAAW,YAAY,sBAAsB,OAAO,YAAY;EACtE,MAAM,eAAe,QAAQ,SAAS;EACtC,MAAM,gBAAgB,OAClB,MAAM,QAAQ,KAAK,GACjB,OACA,CAAC,KAAK,GACR,SAAS;EAEb,MAAM,EAAE,OAAO,UAAU,UAAU,gBACjC,YAAY,iBAAiB,OAAO,cAAc,cAAc;EAElE,IAAI,QAAQ,aAAa,SAAS;EAClC,IAAI,WAAgD,EAAE;AACtD,OAAK,MAAM,OAAO,cAChB,UAAS,OAAO,aAAa,YAAY,QAAQ,EAAE,CAAC;AAGtD,MAAI,SAAS,cAAc,aACzB,EAAC,CAAE,OAAO,YAAa,wBACrB,OACA,UACA,cACD;WACQ,MAAM,SAAS,KAAK,MAAM,MAAM,eAAe,CACxD,EAAC,CAAE,OAAO,YAAa,qBACrB,OACA,UACA,cACD;AAGH,SAAO;GACL;GACA;GACA,QAAQ;GACR,SAAS;GACT,WAAW,SAAS;GACrB;;CAIH,MAAM,WAAW;CACjB,MAAM,WAAW,qBAAqB,UAAU,YAAY;CAC5D,MAAM,eAAe,QAAQ,SAAS;CACtC,MAAM,gBAAgB,OAClB,MAAM,QAAQ,KAAK,GACjB,OACA,CAAC,KAAK,GACR,SAAS;CAEb,MAAM,EAAE,OAAO,UAAU,UAAU,gBAAgB,gBACjD,UACA,cACA,cACD;CAED,IAAI,QAAQ,aAAa,SAAS;CAClC,IAAI,WAAgD,EAAE;AACtD,MAAK,MAAM,OAAO,cAChB,UAAS,OAAO,aAAa,YAAY,QAAQ,EAAE,CAAC;AAGtD,KAAI,SAAS,cAAc,aACzB,EAAC,CAAE,OAAO,YAAa,wBACrB,OACA,UACA,cACD;UACQ,MAAM,SAAS,KAAK,MAAM,MAAM,eAAe,CACxD,EAAC,CAAE,OAAO,YAAa,qBACrB,OACA,UACA,cACD;AAGH,QAAO;EACL;EACA;EACA,QAAQ;EACR,SAAS;EACT,WAAW,SAAS;EACrB;;;;;;;;;;;AAgCH,SAAgB,qBACd,MACA,MACA,UACA,UACuB;CAEvB,MAAM,WAAW,aAAa,KAAK,GAC/B,qBAAqB,KAAK,GACzB;AAEL,KAAI,SAAS,WAAW,EACtB,QAAO;EACL,OAAO,EAAE;EACT,QAAQ,QAAQ;EAChB,SAAS,EAAE;EACX,WAAW;EACX,WAAW,EAAE;EACb,aAAa,EAAE;EACf,KAAK;EACL,KAAK;EACN;CAIH,MAAM,OAAO,OAAO,KAAK,SAAS,GAAG;CACrC,MAAM,eAAe,QAAQ,KAAK;CAClC,MAAM,mBAAmB,YAAY,KAAK;CAC1C,MAAM,mBAAmB,WACrB,MAAM,QAAQ,SAAS,GACrB,SAAS,KACT,WACF,KAAK;CAGT,MAAM,uBAAO,IAAI,KAAsB;CACvC,MAAM,uBAAO,IAAI,KAAsB;AAEvC,MAAK,MAAM,OAAO,UAAU;EAC1B,MAAM,OAAO,sBAAsB,IAAI,eAAe,OAAO,MAAM;EACnE,MAAM,OAAO,sBAAsB,IAAI,mBAAmB,OAAO,MAAM;AACvE,OAAK,IAAI,KAAK;AACd,OAAK,IAAI,KAAK;;CAGhB,MAAM,QAAQ,MAAM,KAAK,KAAK;CAC9B,MAAM,YAAY,MAAM,KAAK,KAAK;CAGlC,MAAM,4BAAY,IAAI,KAA8B;CACpD,MAAM,4BAAY,IAAI,KAA8B;AACpD,OAAM,SAAS,GAAG,MAAM;AACtB,YAAU,IAAI,GAAG,EAAE;GACnB;AACF,WAAU,SAAS,GAAG,MAAM;AAC1B,YAAU,IAAI,GAAG,EAAE;GACnB;CAGF,MAAM,cAA0C,EAAE;CAClD,IAAI,MAAM,OAAO;CACjB,IAAI,MAAM,OAAO;AAEjB,MAAK,MAAM,OAAO,UAAU;EAC1B,MAAM,OAAO,sBAAsB,IAAI,eAAe,OAAO,MAAM;EACnE,MAAM,OAAO,sBAAsB,IAAI,mBAAmB,OAAO,MAAM;EACvE,MAAM,QAAQ,sBAAsB,IAAI,mBAAmB,MAAM,MAAM;EAEvE,MAAM,OAAO,UAAU,IAAI,KAAK;EAChC,MAAM,OAAO,UAAU,IAAI,KAAK;EAChC,MAAM,WAAW,OAAO,UAAU,WAAW,QAAQ;AAErD,MAAI,SAAS,UAAa,SAAS,QAAW;AAC5C,eAAY,KAAK;IAAC;IAAM;IAAM;IAAS,CAAC;AACxC,SAAM,KAAK,IAAI,KAAK,SAAS;AAC7B,SAAM,KAAK,IAAI,KAAK,SAAS;;;AAKjC,KAAI,YAAY,WAAW,GAAG;AAC5B,QAAM;AACN,QAAM;;AAGR,QAAO;EACL;EACA,QAAQ;EACR,SAAS,CAAC,iBAAiB;EAC3B,WAAW;EACX;EACA;EACA;EACA;EACD;;;;;AAMH,SAAS,qBAAqB,OAAyC;CACrE,MAAM,SAAoC,EAAE;CAC5C,MAAM,SAAS,MAAM,OAAO,OAAO,KAAK,MAAM,EAAE,KAAK;AAErD,MAAK,IAAI,IAAI,GAAG,IAAI,MAAM,SAAS,KAAK;EACtC,MAAM,MAA+B,EAAE;AACvC,OAAK,MAAM,SAAS,OAElB,KAAI,SADQ,MAAM,SAAS,MAAM,EACf,IAAI,EAAE;AAE1B,SAAO,KAAK,IAAI;;AAGlB,QAAO"}
@@ -8,6 +8,7 @@ interface OptionBuilderContext {
8
8
  colors: string[];
9
9
  title?: string;
10
10
  showLegend: boolean;
11
+ xField?: string;
11
12
  }
12
13
  interface CartesianContext extends OptionBuilderContext {
13
14
  chartType: ChartType;
@@ -1 +1 @@
1
- {"version":3,"file":"options.d.ts","names":[],"sources":["../../../src/react/charts/options.ts"],"mappings":";;;UAOiB,oBAAA;EACf,KAAA;EACA,QAAA,EAAU,MAAA;EACV,OAAA;EACA,MAAA;EACA,KAAA;EACA,UAAA;AAAA;AAAA,UAGe,gBAAA,SAAyB,oBAAA;EACxC,SAAA,EAAW,SAAA;EACX,YAAA;EACA,OAAA;EACA,MAAA;EACA,UAAA;EACA,UAAA;AAAA;AAAA,iBAkBc,gBAAA,CACd,GAAA,EAAK,oBAAA,EACL,QAAA,aACC,MAAA;AAAA,iBAmCa,cAAA,CACd,GAAA,EAAK,oBAAA,EACL,SAAA,mBACA,WAAA,UACA,UAAA,WACA,aAAA,WACC,MAAA;AAAA,iBAyCa,wBAAA,CACd,GAAA,EAAK,oBAAA,EACL,OAAA,YACC,MAAA;AAAA,UAsCc,cAAA,SAAuB,oBAAA;EArJtC;EAuJA,SAAA;EAtJA;EAwJA,WAAA;EAtJA;EAwJA,GAAA;EAtJA;EAwJA,GAAA;EAxJU;EA0JV,UAAA;AAAA;AAAA,iBAGc,kBAAA,CACd,GAAA,EAAK,cAAA,GACJ,MAAA;AAAA,iBAsEa,oBAAA,CACd,GAAA,EAAK,gBAAA,GACJ,MAAA"}
1
+ {"version":3,"file":"options.d.ts","names":[],"sources":["../../../src/react/charts/options.ts"],"mappings":";;;UAOiB,oBAAA;EACf,KAAA;EACA,QAAA,EAAU,MAAA;EACV,OAAA;EACA,MAAA;EACA,KAAA;EACA,UAAA;EACA,MAAA;AAAA;AAAA,UAGe,gBAAA,SAAyB,oBAAA;EACxC,SAAA,EAAW,SAAA;EACX,YAAA;EACA,OAAA;EACA,MAAA;EACA,UAAA;EACA,UAAA;AAAA;AAAA,iBAkBc,gBAAA,CACd,GAAA,EAAK,oBAAA,EACL,QAAA,aACC,MAAA;AAAA,iBAmCa,cAAA,CACd,GAAA,EAAK,oBAAA,EACL,SAAA,mBACA,WAAA,UACA,UAAA,WACA,aAAA,WACC,MAAA;AAAA,iBAyCa,wBAAA,CACd,GAAA,EAAK,oBAAA,EACL,OAAA,YACC,MAAA;AAAA,UAsCc,cAAA,SAAuB,oBAAA;EArJtC;EAuJA,SAAA;EAtJA;EAwJA,WAAA;EAtJA;EAwJA,GAAA;EAtJA;EAwJA,GAAA;EAxJU;EA0JV,UAAA;AAAA;AAAA,iBAGc,kBAAA,CACd,GAAA,EAAK,cAAA,GACJ,MAAA;AAAA,iBAsEa,oBAAA,CACd,GAAA,EAAK,gBAAA,GACJ,MAAA"}
@@ -167,9 +167,10 @@ function buildCartesianOption(ctx) {
167
167
  const { chartType, isTimeSeries, stacked, smooth, showSymbol, symbolSize } = ctx;
168
168
  const hasMultipleSeries = ctx.yFields.length > 1;
169
169
  const seriesType = chartType === "area" ? "line" : chartType;
170
+ const isScatter = chartType === "scatter";
170
171
  return {
171
172
  ...buildBaseOption(ctx),
172
- tooltip: { trigger: "axis" },
173
+ tooltip: { trigger: isScatter ? "item" : "axis" },
173
174
  legend: ctx.showLegend && hasMultipleSeries ? { top: "bottom" } : void 0,
174
175
  grid: {
175
176
  left: "10%",
@@ -178,22 +179,26 @@ function buildCartesianOption(ctx) {
178
179
  bottom: ctx.showLegend && hasMultipleSeries ? "20%" : "15%"
179
180
  },
180
181
  xAxis: {
181
- type: isTimeSeries ? "time" : "category",
182
- data: isTimeSeries ? void 0 : ctx.xData,
183
- axisLabel: isTimeSeries ? void 0 : {
182
+ type: isScatter ? "value" : isTimeSeries ? "time" : "category",
183
+ data: isScatter || isTimeSeries ? void 0 : ctx.xData,
184
+ name: ctx.xField ? formatLabel(ctx.xField) : void 0,
185
+ axisLabel: isScatter || isTimeSeries ? { show: true } : {
184
186
  rotate: ctx.xData.length > 10 ? 45 : 0,
185
187
  formatter: (v) => truncateLabel(String(v), 10)
186
188
  }
187
189
  },
188
- yAxis: { type: "value" },
190
+ yAxis: {
191
+ type: "value",
192
+ name: ctx.yFields.length === 1 ? formatLabel(ctx.yFields[0]) : void 0
193
+ },
189
194
  series: ctx.yFields.map((key, idx) => ({
190
195
  name: formatLabel(key),
191
196
  type: seriesType,
192
- data: isTimeSeries ? createTimeSeriesData(ctx.xData, ctx.yDataMap[key]) : ctx.yDataMap[key],
197
+ data: isScatter ? ctx.xData.map((x, i) => [x, ctx.yDataMap[key][i]]) : isTimeSeries ? createTimeSeriesData(ctx.xData, ctx.yDataMap[key]) : ctx.yDataMap[key],
193
198
  smooth: chartType === "line" || chartType === "area" ? smooth : void 0,
194
199
  showSymbol: chartType === "line" || chartType === "area" ? showSymbol : void 0,
195
- symbol: chartType === "scatter" ? "circle" : void 0,
196
- symbolSize: chartType === "scatter" ? symbolSize : void 0,
200
+ symbol: isScatter ? "circle" : void 0,
201
+ symbolSize: isScatter ? symbolSize : void 0,
197
202
  areaStyle: chartType === "area" ? { opacity: .3 } : void 0,
198
203
  stack: stacked && chartType === "area" ? "total" : void 0,
199
204
  itemStyle: chartType === "bar" ? { borderRadius: [
@@ -1 +1 @@
1
- {"version":3,"file":"options.js","names":[],"sources":["../../../src/react/charts/options.ts"],"sourcesContent":["import type { ChartType } from \"./types\";\nimport { createTimeSeriesData, formatLabel, truncateLabel } from \"./utils\";\n\n// ============================================================================\n// Option Builder Types\n// ============================================================================\n\nexport interface OptionBuilderContext {\n xData: (string | number)[];\n yDataMap: Record<string, (string | number)[]>;\n yFields: string[];\n colors: string[];\n title?: string;\n showLegend: boolean;\n}\n\nexport interface CartesianContext extends OptionBuilderContext {\n chartType: ChartType;\n isTimeSeries: boolean;\n stacked: boolean;\n smooth: boolean;\n showSymbol: boolean;\n symbolSize: number;\n}\n\n// ============================================================================\n// Base Option Builder\n// ============================================================================\n\nfunction buildBaseOption(ctx: OptionBuilderContext): Record<string, unknown> {\n return {\n title: ctx.title ? { text: ctx.title, left: \"center\" } : undefined,\n color: ctx.colors,\n };\n}\n\n// ============================================================================\n// Radar Chart Option\n// ============================================================================\n\nexport function buildRadarOption(\n ctx: OptionBuilderContext,\n showArea = true,\n): Record<string, unknown> {\n const maxValue = Math.max(\n ...ctx.yFields.flatMap((f) => ctx.yDataMap[f].map((v) => Number(v) || 0)),\n );\n\n return {\n ...buildBaseOption(ctx),\n tooltip: { trigger: \"item\" },\n legend:\n ctx.showLegend && ctx.yFields.length > 1 ? { top: \"bottom\" } : undefined,\n radar: {\n indicator: ctx.xData.map((name) => ({\n name: String(name),\n max: maxValue * 1.2,\n })),\n shape: \"polygon\",\n },\n series: [\n {\n type: \"radar\",\n data: ctx.yFields.map((key, idx) => ({\n name: formatLabel(key),\n value: ctx.yDataMap[key],\n itemStyle: { color: ctx.colors[idx % ctx.colors.length] },\n areaStyle: showArea ? { opacity: 0.3 } : undefined,\n })),\n },\n ],\n };\n}\n\n// ============================================================================\n// Pie/Donut Chart Option\n// ============================================================================\n\nexport function buildPieOption(\n ctx: OptionBuilderContext,\n chartType: \"pie\" | \"donut\",\n innerRadius: number,\n showLabels: boolean,\n labelPosition: string,\n): Record<string, unknown> {\n const pieData = ctx.xData.map((name, i) => ({\n name: String(name),\n value: ctx.yDataMap[ctx.yFields[0]]?.[i] ?? 0,\n }));\n\n const isDonut = chartType === \"donut\" || innerRadius > 0;\n\n return {\n ...buildBaseOption(ctx),\n tooltip: { trigger: \"item\", formatter: \"{b}: {c} ({d}%)\" },\n legend: ctx.showLegend\n ? { orient: \"vertical\", left: \"left\", top: \"middle\" }\n : undefined,\n series: [\n {\n type: \"pie\",\n radius: isDonut ? [`${innerRadius || 40}%`, \"70%\"] : \"70%\",\n center: [\"60%\", \"50%\"],\n data: pieData,\n label: {\n show: showLabels,\n position: labelPosition,\n formatter: \"{b}: {d}%\",\n },\n emphasis: {\n itemStyle: {\n shadowBlur: 10,\n shadowOffsetX: 0,\n shadowColor: \"rgba(0, 0, 0, 0.5)\",\n },\n },\n },\n ],\n };\n}\n\n// ============================================================================\n// Horizontal Bar Chart Option\n// ============================================================================\n\nexport function buildHorizontalBarOption(\n ctx: OptionBuilderContext,\n stacked: boolean,\n): Record<string, unknown> {\n const hasMultipleSeries = ctx.yFields.length > 1;\n\n return {\n ...buildBaseOption(ctx),\n tooltip: { trigger: \"axis\", axisPointer: { type: \"shadow\" } },\n legend: ctx.showLegend && hasMultipleSeries ? { top: \"bottom\" } : undefined,\n grid: {\n left: \"20%\",\n right: \"10%\",\n top: ctx.title ? \"15%\" : \"5%\",\n bottom: ctx.showLegend && hasMultipleSeries ? \"15%\" : \"5%\",\n },\n xAxis: { type: \"value\" },\n yAxis: {\n type: \"category\",\n data: ctx.xData,\n axisLabel: {\n width: 100,\n overflow: \"truncate\",\n formatter: (value: string) => truncateLabel(String(value)),\n },\n },\n series: ctx.yFields.map((key, idx) => ({\n name: formatLabel(key),\n type: \"bar\",\n data: ctx.yDataMap[key],\n stack: stacked ? \"total\" : undefined,\n itemStyle: { borderRadius: [0, 4, 4, 0] },\n color: ctx.colors[idx % ctx.colors.length],\n })),\n };\n}\n\n// ============================================================================\n// Heatmap Chart Option\n// ============================================================================\n\nexport interface HeatmapContext extends OptionBuilderContext {\n /** Y-axis categories (rows) */\n yAxisData: (string | number)[];\n /** Heatmap data as [xIndex, yIndex, value] tuples */\n heatmapData: [number, number, number][];\n /** Min value for color scale */\n min: number;\n /** Max value for color scale */\n max: number;\n /** Show value labels on cells */\n showLabels: boolean;\n}\n\nexport function buildHeatmapOption(\n ctx: HeatmapContext,\n): Record<string, unknown> {\n return {\n ...buildBaseOption(ctx),\n tooltip: {\n trigger: \"item\",\n formatter: (params: { data: [number, number, number] }) => {\n const [xIdx, yIdx, value] = params.data;\n const xLabel = ctx.xData[xIdx] ?? xIdx;\n const yLabel = ctx.yAxisData[yIdx] ?? yIdx;\n return `${xLabel}, ${yLabel}: ${value}`;\n },\n },\n grid: {\n left: \"15%\",\n right: \"15%\",\n top: ctx.title ? \"15%\" : \"10%\",\n bottom: \"15%\",\n },\n xAxis: {\n type: \"category\",\n data: ctx.xData,\n splitArea: { show: true },\n axisLabel: {\n rotate: ctx.xData.length > 10 ? 45 : 0,\n formatter: (v: string) => truncateLabel(String(v), 10),\n },\n },\n yAxis: {\n type: \"category\",\n data: ctx.yAxisData,\n splitArea: { show: true },\n axisLabel: {\n formatter: (v: string) => truncateLabel(String(v), 12),\n },\n },\n visualMap: {\n min: ctx.min,\n max: ctx.max,\n calculable: true,\n orient: \"vertical\",\n right: \"2%\",\n top: \"center\",\n inRange: {\n color: ctx.colors.length >= 2 ? ctx.colors : [\"#f0f0f0\", ctx.colors[0]],\n },\n },\n series: [\n {\n type: \"heatmap\",\n data: ctx.heatmapData,\n label: {\n show: ctx.showLabels,\n formatter: (params: { data: [number, number, number] }) =>\n String(params.data[2]),\n },\n emphasis: {\n itemStyle: {\n shadowBlur: 10,\n shadowColor: \"rgba(0, 0, 0, 0.5)\",\n },\n },\n },\n ],\n };\n}\n\n// ============================================================================\n// Cartesian Chart Option (line, bar, area, scatter)\n// ============================================================================\n\nexport function buildCartesianOption(\n ctx: CartesianContext,\n): Record<string, unknown> {\n const { chartType, isTimeSeries, stacked, smooth, showSymbol, symbolSize } =\n ctx;\n const hasMultipleSeries = ctx.yFields.length > 1;\n const seriesType = chartType === \"area\" ? \"line\" : chartType;\n\n return {\n ...buildBaseOption(ctx),\n tooltip: { trigger: \"axis\" },\n legend: ctx.showLegend && hasMultipleSeries ? { top: \"bottom\" } : undefined,\n grid: {\n left: \"10%\",\n right: \"10%\",\n top: ctx.title ? \"15%\" : \"10%\",\n bottom: ctx.showLegend && hasMultipleSeries ? \"20%\" : \"15%\",\n },\n xAxis: {\n type: isTimeSeries ? \"time\" : \"category\",\n data: isTimeSeries ? undefined : ctx.xData,\n axisLabel: isTimeSeries\n ? undefined\n : {\n rotate: ctx.xData.length > 10 ? 45 : 0,\n formatter: (v: string) => truncateLabel(String(v), 10),\n },\n },\n yAxis: { type: \"value\" },\n series: ctx.yFields.map((key, idx) => ({\n name: formatLabel(key),\n type: seriesType,\n data: isTimeSeries\n ? createTimeSeriesData(ctx.xData, ctx.yDataMap[key])\n : ctx.yDataMap[key],\n smooth: chartType === \"line\" || chartType === \"area\" ? smooth : undefined,\n showSymbol:\n chartType === \"line\" || chartType === \"area\" ? showSymbol : undefined,\n symbol: chartType === \"scatter\" ? \"circle\" : undefined,\n symbolSize: chartType === \"scatter\" ? symbolSize : undefined,\n areaStyle: chartType === \"area\" ? { opacity: 0.3 } : undefined,\n stack: stacked && chartType === \"area\" ? \"total\" : undefined,\n itemStyle:\n chartType === \"bar\" ? { borderRadius: [4, 4, 0, 0] } : undefined,\n color: ctx.colors[idx % ctx.colors.length],\n })),\n };\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"options.js","names":[],"sources":["../../../src/react/charts/options.ts"],"sourcesContent":["import type { ChartType } from \"./types\";\nimport { createTimeSeriesData, formatLabel, truncateLabel } from \"./utils\";\n\n// ============================================================================\n// Option Builder Types\n// ============================================================================\n\nexport interface OptionBuilderContext {\n xData: (string | number)[];\n yDataMap: Record<string, (string | number)[]>;\n yFields: string[];\n colors: string[];\n title?: string;\n showLegend: boolean;\n xField?: string;\n}\n\nexport interface CartesianContext extends OptionBuilderContext {\n chartType: ChartType;\n isTimeSeries: boolean;\n stacked: boolean;\n smooth: boolean;\n showSymbol: boolean;\n symbolSize: number;\n}\n\n// ============================================================================\n// Base Option Builder\n// ============================================================================\n\nfunction buildBaseOption(ctx: OptionBuilderContext): Record<string, unknown> {\n return {\n title: ctx.title ? { text: ctx.title, left: \"center\" } : undefined,\n color: ctx.colors,\n };\n}\n\n// ============================================================================\n// Radar Chart Option\n// ============================================================================\n\nexport function buildRadarOption(\n ctx: OptionBuilderContext,\n showArea = true,\n): Record<string, unknown> {\n const maxValue = Math.max(\n ...ctx.yFields.flatMap((f) => ctx.yDataMap[f].map((v) => Number(v) || 0)),\n );\n\n return {\n ...buildBaseOption(ctx),\n tooltip: { trigger: \"item\" },\n legend:\n ctx.showLegend && ctx.yFields.length > 1 ? { top: \"bottom\" } : undefined,\n radar: {\n indicator: ctx.xData.map((name) => ({\n name: String(name),\n max: maxValue * 1.2,\n })),\n shape: \"polygon\",\n },\n series: [\n {\n type: \"radar\",\n data: ctx.yFields.map((key, idx) => ({\n name: formatLabel(key),\n value: ctx.yDataMap[key],\n itemStyle: { color: ctx.colors[idx % ctx.colors.length] },\n areaStyle: showArea ? { opacity: 0.3 } : undefined,\n })),\n },\n ],\n };\n}\n\n// ============================================================================\n// Pie/Donut Chart Option\n// ============================================================================\n\nexport function buildPieOption(\n ctx: OptionBuilderContext,\n chartType: \"pie\" | \"donut\",\n innerRadius: number,\n showLabels: boolean,\n labelPosition: string,\n): Record<string, unknown> {\n const pieData = ctx.xData.map((name, i) => ({\n name: String(name),\n value: ctx.yDataMap[ctx.yFields[0]]?.[i] ?? 0,\n }));\n\n const isDonut = chartType === \"donut\" || innerRadius > 0;\n\n return {\n ...buildBaseOption(ctx),\n tooltip: { trigger: \"item\", formatter: \"{b}: {c} ({d}%)\" },\n legend: ctx.showLegend\n ? { orient: \"vertical\", left: \"left\", top: \"middle\" }\n : undefined,\n series: [\n {\n type: \"pie\",\n radius: isDonut ? [`${innerRadius || 40}%`, \"70%\"] : \"70%\",\n center: [\"60%\", \"50%\"],\n data: pieData,\n label: {\n show: showLabels,\n position: labelPosition,\n formatter: \"{b}: {d}%\",\n },\n emphasis: {\n itemStyle: {\n shadowBlur: 10,\n shadowOffsetX: 0,\n shadowColor: \"rgba(0, 0, 0, 0.5)\",\n },\n },\n },\n ],\n };\n}\n\n// ============================================================================\n// Horizontal Bar Chart Option\n// ============================================================================\n\nexport function buildHorizontalBarOption(\n ctx: OptionBuilderContext,\n stacked: boolean,\n): Record<string, unknown> {\n const hasMultipleSeries = ctx.yFields.length > 1;\n\n return {\n ...buildBaseOption(ctx),\n tooltip: { trigger: \"axis\", axisPointer: { type: \"shadow\" } },\n legend: ctx.showLegend && hasMultipleSeries ? { top: \"bottom\" } : undefined,\n grid: {\n left: \"20%\",\n right: \"10%\",\n top: ctx.title ? \"15%\" : \"5%\",\n bottom: ctx.showLegend && hasMultipleSeries ? \"15%\" : \"5%\",\n },\n xAxis: { type: \"value\" },\n yAxis: {\n type: \"category\",\n data: ctx.xData,\n axisLabel: {\n width: 100,\n overflow: \"truncate\",\n formatter: (value: string) => truncateLabel(String(value)),\n },\n },\n series: ctx.yFields.map((key, idx) => ({\n name: formatLabel(key),\n type: \"bar\",\n data: ctx.yDataMap[key],\n stack: stacked ? \"total\" : undefined,\n itemStyle: { borderRadius: [0, 4, 4, 0] },\n color: ctx.colors[idx % ctx.colors.length],\n })),\n };\n}\n\n// ============================================================================\n// Heatmap Chart Option\n// ============================================================================\n\nexport interface HeatmapContext extends OptionBuilderContext {\n /** Y-axis categories (rows) */\n yAxisData: (string | number)[];\n /** Heatmap data as [xIndex, yIndex, value] tuples */\n heatmapData: [number, number, number][];\n /** Min value for color scale */\n min: number;\n /** Max value for color scale */\n max: number;\n /** Show value labels on cells */\n showLabels: boolean;\n}\n\nexport function buildHeatmapOption(\n ctx: HeatmapContext,\n): Record<string, unknown> {\n return {\n ...buildBaseOption(ctx),\n tooltip: {\n trigger: \"item\",\n formatter: (params: { data: [number, number, number] }) => {\n const [xIdx, yIdx, value] = params.data;\n const xLabel = ctx.xData[xIdx] ?? xIdx;\n const yLabel = ctx.yAxisData[yIdx] ?? yIdx;\n return `${xLabel}, ${yLabel}: ${value}`;\n },\n },\n grid: {\n left: \"15%\",\n right: \"15%\",\n top: ctx.title ? \"15%\" : \"10%\",\n bottom: \"15%\",\n },\n xAxis: {\n type: \"category\",\n data: ctx.xData,\n splitArea: { show: true },\n axisLabel: {\n rotate: ctx.xData.length > 10 ? 45 : 0,\n formatter: (v: string) => truncateLabel(String(v), 10),\n },\n },\n yAxis: {\n type: \"category\",\n data: ctx.yAxisData,\n splitArea: { show: true },\n axisLabel: {\n formatter: (v: string) => truncateLabel(String(v), 12),\n },\n },\n visualMap: {\n min: ctx.min,\n max: ctx.max,\n calculable: true,\n orient: \"vertical\",\n right: \"2%\",\n top: \"center\",\n inRange: {\n color: ctx.colors.length >= 2 ? ctx.colors : [\"#f0f0f0\", ctx.colors[0]],\n },\n },\n series: [\n {\n type: \"heatmap\",\n data: ctx.heatmapData,\n label: {\n show: ctx.showLabels,\n formatter: (params: { data: [number, number, number] }) =>\n String(params.data[2]),\n },\n emphasis: {\n itemStyle: {\n shadowBlur: 10,\n shadowColor: \"rgba(0, 0, 0, 0.5)\",\n },\n },\n },\n ],\n };\n}\n\n// ============================================================================\n// Cartesian Chart Option (line, bar, area, scatter)\n// ============================================================================\n\nexport function buildCartesianOption(\n ctx: CartesianContext,\n): Record<string, unknown> {\n const { chartType, isTimeSeries, stacked, smooth, showSymbol, symbolSize } =\n ctx;\n const hasMultipleSeries = ctx.yFields.length > 1;\n const seriesType = chartType === \"area\" ? \"line\" : chartType;\n const isScatter = chartType === \"scatter\";\n\n return {\n ...buildBaseOption(ctx),\n tooltip: { trigger: isScatter ? \"item\" : \"axis\" },\n legend: ctx.showLegend && hasMultipleSeries ? { top: \"bottom\" } : undefined,\n grid: {\n left: \"10%\",\n right: \"10%\",\n top: ctx.title ? \"15%\" : \"10%\",\n bottom: ctx.showLegend && hasMultipleSeries ? \"20%\" : \"15%\",\n },\n xAxis: {\n type: isScatter ? \"value\" : isTimeSeries ? \"time\" : \"category\",\n data: isScatter || isTimeSeries ? undefined : ctx.xData,\n name: ctx.xField ? formatLabel(ctx.xField) : undefined,\n axisLabel:\n isScatter || isTimeSeries\n ? { show: true }\n : {\n rotate: ctx.xData.length > 10 ? 45 : 0,\n formatter: (v: string) => truncateLabel(String(v), 10),\n },\n },\n yAxis: {\n type: \"value\",\n name: ctx.yFields.length === 1 ? formatLabel(ctx.yFields[0]) : undefined,\n },\n series: ctx.yFields.map((key, idx) => ({\n name: formatLabel(key),\n type: seriesType,\n data: isScatter\n ? ctx.xData.map((x, i) => [x, ctx.yDataMap[key][i]])\n : isTimeSeries\n ? createTimeSeriesData(ctx.xData, ctx.yDataMap[key])\n : ctx.yDataMap[key],\n smooth: chartType === \"line\" || chartType === \"area\" ? smooth : undefined,\n showSymbol:\n chartType === \"line\" || chartType === \"area\" ? showSymbol : undefined,\n symbol: isScatter ? \"circle\" : undefined,\n symbolSize: isScatter ? symbolSize : undefined,\n areaStyle: chartType === \"area\" ? { opacity: 0.3 } : undefined,\n stack: stacked && chartType === \"area\" ? \"total\" : undefined,\n itemStyle:\n chartType === \"bar\" ? { borderRadius: [4, 4, 0, 0] } : undefined,\n color: ctx.colors[idx % ctx.colors.length],\n })),\n };\n}\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"utils.d.ts","names":[],"sources":["../../../src/react/charts/utils.ts"],"mappings":";;AASA;;;;iBAAgB,YAAA,CAAa,KAAA;AAmB7B;;;AAAA,iBAAgB,YAAA,CAAa,IAAA;;AAW7B;;;;;iBAAgB,WAAA,CAAY,KAAA;;;;iBAqBZ,aAAA,CAAc,KAAA,UAAe,SAAA;AAO7C;;;AAAA,iBAAgB,oBAAA,CACd,KAAA,uBACA,KAAA;;AAaF;;iBAAgB,uBAAA,CACd,KAAA,uBACA,QAAA,EAAU,MAAA,+BACV,OAAA;EAEA,KAAA;EACA,QAAA,EAAU,MAAA;AAAA"}
1
+ {"version":3,"file":"utils.d.ts","names":[],"sources":["../../../src/react/charts/utils.ts"],"mappings":";;AASA;;;;iBAAgB,YAAA,CAAa,KAAA;AAmB7B;;;AAAA,iBAAgB,YAAA,CAAa,IAAA;;AAW7B;;;;;iBAAgB,WAAA,CAAY,KAAA;;;;iBAqBZ,aAAA,CAAc,KAAA,UAAe,SAAA;AAO7C;;;AAAA,iBAAgB,oBAAA,CACd,KAAA,uBACA,KAAA;;;;iBA0Cc,uBAAA,CACd,KAAA,uBACA,QAAA,EAAU,MAAA,+BACV,OAAA;EAEA,KAAA;EACA,QAAA,EAAU,MAAA;AAAA"}
@@ -43,6 +43,28 @@ function createTimeSeriesData(xData, yData) {
43
43
  return result;
44
44
  }
45
45
  /**
46
+ * Sorts numeric x-data in ascending order, reordering y-data to match.
47
+ * Also coerces numeric string x-values to numbers.
48
+ */
49
+ function sortNumericAscending(xData, yDataMap, yFields) {
50
+ if (xData.length <= 1) return {
51
+ xData,
52
+ yDataMap
53
+ };
54
+ const indices = xData.map((_, i) => i);
55
+ indices.sort((a, b) => Number(xData[a]) - Number(xData[b]));
56
+ const sortedXData = indices.map((i) => Number(xData[i]));
57
+ const sortedYDataMap = {};
58
+ for (const key of yFields) {
59
+ const original = yDataMap[key];
60
+ sortedYDataMap[key] = indices.map((i) => original[i]);
61
+ }
62
+ return {
63
+ xData: sortedXData,
64
+ yDataMap: sortedYDataMap
65
+ };
66
+ }
67
+ /**
46
68
  * Sorts time-series data in ascending chronological order.
47
69
  */
48
70
  function sortTimeSeriesAscending(xData, yDataMap, yFields) {
@@ -73,5 +95,5 @@ function sortTimeSeriesAscending(xData, yDataMap, yFields) {
73
95
  }
74
96
 
75
97
  //#endregion
76
- export { createTimeSeriesData, formatLabel, sortTimeSeriesAscending, toChartArray, toChartValue, truncateLabel };
98
+ export { createTimeSeriesData, formatLabel, sortNumericAscending, sortTimeSeriesAscending, toChartArray, toChartValue, truncateLabel };
77
99
  //# sourceMappingURL=utils.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"utils.js","names":[],"sources":["../../../src/react/charts/utils.ts"],"sourcesContent":["// ============================================================================\n// Chart Utility Functions\n// ============================================================================\n\n/**\n * Converts a value to a chart-compatible type.\n * Handles BigInt conversion (Arrow can return BigInt64Array values).\n * Handles Date objects by converting to timestamps.\n */\nexport function toChartValue(value: unknown): string | number {\n if (value === null || value === undefined) {\n return 0;\n }\n if (typeof value === \"bigint\") {\n return Number(value);\n }\n if (value instanceof Date) {\n return value.getTime();\n }\n if (typeof value === \"string\" || typeof value === \"number\") {\n return value;\n }\n return String(value);\n}\n\n/**\n * Converts an array of values to chart-compatible types.\n */\nexport function toChartArray(data: unknown[]): (string | number)[] {\n if (data.length === 0) return [];\n return data.map(toChartValue);\n}\n\n/**\n * Formats a field name into a human-readable label.\n * Handles camelCase, snake_case, acronyms, and ALL_CAPS.\n * E.g., \"totalSpend\" -> \"Total Spend\", \"user_name\" -> \"User Name\",\n * \"userID\" -> \"User Id\", \"TOTAL_SPEND\" -> \"Total Spend\"\n */\nexport function formatLabel(field: string): string {\n return (\n field\n // Handle consecutive uppercase followed by lowercase (e.g., HTTPUrl → HTTP Url)\n .replace(/([A-Z]+)([A-Z][a-z])/g, \"$1 $2\")\n // Handle lowercase followed by uppercase (e.g., totalSpend → total Spend)\n .replace(/([a-z])([A-Z])/g, \"$1 $2\")\n // Replace underscores with spaces\n .replace(/_/g, \" \")\n // Collapse multiple spaces into one\n .replace(/\\s+/g, \" \")\n // Normalize to title case\n .toLowerCase()\n .replace(/\\b\\w/g, (l) => l.toUpperCase())\n .trim()\n );\n}\n\n/**\n * Truncates a label to a maximum length with ellipsis.\n */\nexport function truncateLabel(value: string, maxLength = 15): string {\n return value.length > maxLength ? `${value.slice(0, maxLength)}...` : value;\n}\n\n/**\n * Creates time-series data pairs for ECharts.\n */\nexport function createTimeSeriesData(\n xData: (string | number)[],\n yData: (string | number)[],\n): [string | number, string | number][] {\n const len = xData.length;\n const result: [string | number, string | number][] = new Array(len);\n for (let i = 0; i < len; i++) {\n result[i] = [xData[i], yData[i]];\n }\n return result;\n}\n\n/**\n * Sorts time-series data in ascending chronological order.\n */\nexport function sortTimeSeriesAscending(\n xData: (string | number)[],\n yDataMap: Record<string, (string | number)[]>,\n yFields: string[],\n): {\n xData: (string | number)[];\n yDataMap: Record<string, (string | number)[]>;\n} {\n if (xData.length <= 1) {\n return { xData, yDataMap };\n }\n\n const first = xData[0];\n const last = xData[xData.length - 1];\n\n if (typeof first === \"number\" && typeof last === \"number\" && first > last) {\n const indices = xData.map((_, i) => i);\n indices.sort((a, b) => (xData[a] as number) - (xData[b] as number));\n\n const sortedXData = indices.map((i) => xData[i]);\n const sortedYDataMap: Record<string, (string | number)[]> = {};\n for (const key of yFields) {\n const original = yDataMap[key];\n sortedYDataMap[key] = indices.map((i) => original[i]);\n }\n\n return { xData: sortedXData, yDataMap: sortedYDataMap };\n }\n\n return { xData, yDataMap };\n}\n"],"mappings":";;;;;;AASA,SAAgB,aAAa,OAAiC;AAC5D,KAAI,UAAU,QAAQ,UAAU,OAC9B,QAAO;AAET,KAAI,OAAO,UAAU,SACnB,QAAO,OAAO,MAAM;AAEtB,KAAI,iBAAiB,KACnB,QAAO,MAAM,SAAS;AAExB,KAAI,OAAO,UAAU,YAAY,OAAO,UAAU,SAChD,QAAO;AAET,QAAO,OAAO,MAAM;;;;;AAMtB,SAAgB,aAAa,MAAsC;AACjE,KAAI,KAAK,WAAW,EAAG,QAAO,EAAE;AAChC,QAAO,KAAK,IAAI,aAAa;;;;;;;;AAS/B,SAAgB,YAAY,OAAuB;AACjD,QACE,MAEG,QAAQ,yBAAyB,QAAQ,CAEzC,QAAQ,mBAAmB,QAAQ,CAEnC,QAAQ,MAAM,IAAI,CAElB,QAAQ,QAAQ,IAAI,CAEpB,aAAa,CACb,QAAQ,UAAU,MAAM,EAAE,aAAa,CAAC,CACxC,MAAM;;;;;AAOb,SAAgB,cAAc,OAAe,YAAY,IAAY;AACnE,QAAO,MAAM,SAAS,YAAY,GAAG,MAAM,MAAM,GAAG,UAAU,CAAC,OAAO;;;;;AAMxE,SAAgB,qBACd,OACA,OACsC;CACtC,MAAM,MAAM,MAAM;CAClB,MAAM,SAA+C,IAAI,MAAM,IAAI;AACnE,MAAK,IAAI,IAAI,GAAG,IAAI,KAAK,IACvB,QAAO,KAAK,CAAC,MAAM,IAAI,MAAM,GAAG;AAElC,QAAO;;;;;AAMT,SAAgB,wBACd,OACA,UACA,SAIA;AACA,KAAI,MAAM,UAAU,EAClB,QAAO;EAAE;EAAO;EAAU;CAG5B,MAAM,QAAQ,MAAM;CACpB,MAAM,OAAO,MAAM,MAAM,SAAS;AAElC,KAAI,OAAO,UAAU,YAAY,OAAO,SAAS,YAAY,QAAQ,MAAM;EACzE,MAAM,UAAU,MAAM,KAAK,GAAG,MAAM,EAAE;AACtC,UAAQ,MAAM,GAAG,MAAO,MAAM,KAAiB,MAAM,GAAc;EAEnE,MAAM,cAAc,QAAQ,KAAK,MAAM,MAAM,GAAG;EAChD,MAAM,iBAAsD,EAAE;AAC9D,OAAK,MAAM,OAAO,SAAS;GACzB,MAAM,WAAW,SAAS;AAC1B,kBAAe,OAAO,QAAQ,KAAK,MAAM,SAAS,GAAG;;AAGvD,SAAO;GAAE,OAAO;GAAa,UAAU;GAAgB;;AAGzD,QAAO;EAAE;EAAO;EAAU"}
1
+ {"version":3,"file":"utils.js","names":[],"sources":["../../../src/react/charts/utils.ts"],"sourcesContent":["// ============================================================================\n// Chart Utility Functions\n// ============================================================================\n\n/**\n * Converts a value to a chart-compatible type.\n * Handles BigInt conversion (Arrow can return BigInt64Array values).\n * Handles Date objects by converting to timestamps.\n */\nexport function toChartValue(value: unknown): string | number {\n if (value === null || value === undefined) {\n return 0;\n }\n if (typeof value === \"bigint\") {\n return Number(value);\n }\n if (value instanceof Date) {\n return value.getTime();\n }\n if (typeof value === \"string\" || typeof value === \"number\") {\n return value;\n }\n return String(value);\n}\n\n/**\n * Converts an array of values to chart-compatible types.\n */\nexport function toChartArray(data: unknown[]): (string | number)[] {\n if (data.length === 0) return [];\n return data.map(toChartValue);\n}\n\n/**\n * Formats a field name into a human-readable label.\n * Handles camelCase, snake_case, acronyms, and ALL_CAPS.\n * E.g., \"totalSpend\" -> \"Total Spend\", \"user_name\" -> \"User Name\",\n * \"userID\" -> \"User Id\", \"TOTAL_SPEND\" -> \"Total Spend\"\n */\nexport function formatLabel(field: string): string {\n return (\n field\n // Handle consecutive uppercase followed by lowercase (e.g., HTTPUrl → HTTP Url)\n .replace(/([A-Z]+)([A-Z][a-z])/g, \"$1 $2\")\n // Handle lowercase followed by uppercase (e.g., totalSpend → total Spend)\n .replace(/([a-z])([A-Z])/g, \"$1 $2\")\n // Replace underscores with spaces\n .replace(/_/g, \" \")\n // Collapse multiple spaces into one\n .replace(/\\s+/g, \" \")\n // Normalize to title case\n .toLowerCase()\n .replace(/\\b\\w/g, (l) => l.toUpperCase())\n .trim()\n );\n}\n\n/**\n * Truncates a label to a maximum length with ellipsis.\n */\nexport function truncateLabel(value: string, maxLength = 15): string {\n return value.length > maxLength ? `${value.slice(0, maxLength)}...` : value;\n}\n\n/**\n * Creates time-series data pairs for ECharts.\n */\nexport function createTimeSeriesData(\n xData: (string | number)[],\n yData: (string | number)[],\n): [string | number, string | number][] {\n const len = xData.length;\n const result: [string | number, string | number][] = new Array(len);\n for (let i = 0; i < len; i++) {\n result[i] = [xData[i], yData[i]];\n }\n return result;\n}\n\n/**\n * Sorts numeric x-data in ascending order, reordering y-data to match.\n * Also coerces numeric string x-values to numbers.\n */\nexport function sortNumericAscending(\n xData: (string | number)[],\n yDataMap: Record<string, (string | number)[]>,\n yFields: string[],\n): {\n xData: (string | number)[];\n yDataMap: Record<string, (string | number)[]>;\n} {\n if (xData.length <= 1) {\n return { xData, yDataMap };\n }\n\n const indices = xData.map((_, i) => i);\n indices.sort((a, b) => Number(xData[a]) - Number(xData[b]));\n\n const sortedXData = indices.map((i) => Number(xData[i]));\n const sortedYDataMap: Record<string, (string | number)[]> = {};\n for (const key of yFields) {\n const original = yDataMap[key];\n sortedYDataMap[key] = indices.map((i) => original[i]);\n }\n\n return { xData: sortedXData, yDataMap: sortedYDataMap };\n}\n\n/**\n * Sorts time-series data in ascending chronological order.\n */\nexport function sortTimeSeriesAscending(\n xData: (string | number)[],\n yDataMap: Record<string, (string | number)[]>,\n yFields: string[],\n): {\n xData: (string | number)[];\n yDataMap: Record<string, (string | number)[]>;\n} {\n if (xData.length <= 1) {\n return { xData, yDataMap };\n }\n\n const first = xData[0];\n const last = xData[xData.length - 1];\n\n if (typeof first === \"number\" && typeof last === \"number\" && first > last) {\n const indices = xData.map((_, i) => i);\n indices.sort((a, b) => (xData[a] as number) - (xData[b] as number));\n\n const sortedXData = indices.map((i) => xData[i]);\n const sortedYDataMap: Record<string, (string | number)[]> = {};\n for (const key of yFields) {\n const original = yDataMap[key];\n sortedYDataMap[key] = indices.map((i) => original[i]);\n }\n\n return { xData: sortedXData, yDataMap: sortedYDataMap };\n }\n\n return { xData, yDataMap };\n}\n"],"mappings":";;;;;;AASA,SAAgB,aAAa,OAAiC;AAC5D,KAAI,UAAU,QAAQ,UAAU,OAC9B,QAAO;AAET,KAAI,OAAO,UAAU,SACnB,QAAO,OAAO,MAAM;AAEtB,KAAI,iBAAiB,KACnB,QAAO,MAAM,SAAS;AAExB,KAAI,OAAO,UAAU,YAAY,OAAO,UAAU,SAChD,QAAO;AAET,QAAO,OAAO,MAAM;;;;;AAMtB,SAAgB,aAAa,MAAsC;AACjE,KAAI,KAAK,WAAW,EAAG,QAAO,EAAE;AAChC,QAAO,KAAK,IAAI,aAAa;;;;;;;;AAS/B,SAAgB,YAAY,OAAuB;AACjD,QACE,MAEG,QAAQ,yBAAyB,QAAQ,CAEzC,QAAQ,mBAAmB,QAAQ,CAEnC,QAAQ,MAAM,IAAI,CAElB,QAAQ,QAAQ,IAAI,CAEpB,aAAa,CACb,QAAQ,UAAU,MAAM,EAAE,aAAa,CAAC,CACxC,MAAM;;;;;AAOb,SAAgB,cAAc,OAAe,YAAY,IAAY;AACnE,QAAO,MAAM,SAAS,YAAY,GAAG,MAAM,MAAM,GAAG,UAAU,CAAC,OAAO;;;;;AAMxE,SAAgB,qBACd,OACA,OACsC;CACtC,MAAM,MAAM,MAAM;CAClB,MAAM,SAA+C,IAAI,MAAM,IAAI;AACnE,MAAK,IAAI,IAAI,GAAG,IAAI,KAAK,IACvB,QAAO,KAAK,CAAC,MAAM,IAAI,MAAM,GAAG;AAElC,QAAO;;;;;;AAOT,SAAgB,qBACd,OACA,UACA,SAIA;AACA,KAAI,MAAM,UAAU,EAClB,QAAO;EAAE;EAAO;EAAU;CAG5B,MAAM,UAAU,MAAM,KAAK,GAAG,MAAM,EAAE;AACtC,SAAQ,MAAM,GAAG,MAAM,OAAO,MAAM,GAAG,GAAG,OAAO,MAAM,GAAG,CAAC;CAE3D,MAAM,cAAc,QAAQ,KAAK,MAAM,OAAO,MAAM,GAAG,CAAC;CACxD,MAAM,iBAAsD,EAAE;AAC9D,MAAK,MAAM,OAAO,SAAS;EACzB,MAAM,WAAW,SAAS;AAC1B,iBAAe,OAAO,QAAQ,KAAK,MAAM,SAAS,GAAG;;AAGvD,QAAO;EAAE,OAAO;EAAa,UAAU;EAAgB;;;;;AAMzD,SAAgB,wBACd,OACA,UACA,SAIA;AACA,KAAI,MAAM,UAAU,EAClB,QAAO;EAAE;EAAO;EAAU;CAG5B,MAAM,QAAQ,MAAM;CACpB,MAAM,OAAO,MAAM,MAAM,SAAS;AAElC,KAAI,OAAO,UAAU,YAAY,OAAO,SAAS,YAAY,QAAQ,MAAM;EACzE,MAAM,UAAU,MAAM,KAAK,GAAG,MAAM,EAAE;AACtC,UAAQ,MAAM,GAAG,MAAO,MAAM,KAAiB,MAAM,GAAc;EAEnE,MAAM,cAAc,QAAQ,KAAK,MAAM,MAAM,GAAG;EAChD,MAAM,iBAAsD,EAAE;AAC9D,OAAK,MAAM,OAAO,SAAS;GACzB,MAAM,WAAW,SAAS;AAC1B,kBAAe,OAAO,QAAQ,KAAK,MAAM,SAAS,GAAG;;AAGvD,SAAO;GAAE,OAAO;GAAa,UAAU;GAAgB;;AAGzD,QAAO;EAAE;EAAO;EAAU"}
@@ -0,0 +1,17 @@
1
+ import { ChartType } from "../charts/types.js";
2
+ import { GenieColumnMeta } from "./genie-query-transform.js";
3
+
4
+ //#region src/react/genie/genie-chart-inference.d.ts
5
+ interface ChartInference {
6
+ chartType: ChartType;
7
+ xKey: string;
8
+ yKey: string | string[];
9
+ }
10
+ /**
11
+ * Infer the best chart type for the given Genie query result.
12
+ * Returns `null` when the data is not suitable for charting.
13
+ */
14
+ declare function inferChartType(rows: Record<string, unknown>[], columns: GenieColumnMeta[]): ChartInference | null;
15
+ //#endregion
16
+ export { ChartInference, inferChartType };
17
+ //# sourceMappingURL=genie-chart-inference.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"genie-chart-inference.d.ts","names":[],"sources":["../../../src/react/genie/genie-chart-inference.ts"],"mappings":";;;;UAqDiB,cAAA;EACf,SAAA,EAAW,SAAA;EACX,IAAA;EACA,IAAA;AAAA;;;;;iBA6Bc,cAAA,CACd,IAAA,EAAM,MAAA,qBACN,OAAA,EAAS,eAAA,KACR,cAAA"}
@@ -0,0 +1,75 @@
1
+ //#region src/react/genie/genie-chart-inference.ts
2
+ const INFERENCE_CONFIG = {
3
+ minRows: 2,
4
+ pieMaxCategories: 7,
5
+ barMaxCategories: 100,
6
+ groupedBarMaxCategories: 50
7
+ };
8
+ function countUnique(rows, key) {
9
+ const seen = /* @__PURE__ */ new Set();
10
+ for (const row of rows) seen.add(row[key]);
11
+ return seen.size;
12
+ }
13
+ function hasNegativeValues(rows, key) {
14
+ for (const row of rows) if (Number(row[key]) < 0) return true;
15
+ return false;
16
+ }
17
+ /**
18
+ * Infer the best chart type for the given Genie query result.
19
+ * Returns `null` when the data is not suitable for charting.
20
+ */
21
+ function inferChartType(rows, columns) {
22
+ if (rows.length < INFERENCE_CONFIG.minRows || columns.length < 2) return null;
23
+ const dateCols = columns.filter((c) => c.category === "date");
24
+ const numericCols = columns.filter((c) => c.category === "numeric");
25
+ const stringCols = columns.filter((c) => c.category === "string");
26
+ if (numericCols.length === 0) return null;
27
+ if (dateCols.length > 0 && numericCols.length >= 1) return {
28
+ chartType: "line",
29
+ xKey: dateCols[0].name,
30
+ yKey: numericCols.length === 1 ? numericCols[0].name : numericCols.map((c) => c.name)
31
+ };
32
+ if (stringCols.length > 0 && numericCols.length >= 1) {
33
+ const xKey = stringCols[0].name;
34
+ const uniqueCategories = countUnique(rows, xKey);
35
+ if (numericCols.length === 1) {
36
+ const yKey = numericCols[0].name;
37
+ if (uniqueCategories <= INFERENCE_CONFIG.pieMaxCategories && !hasNegativeValues(rows, yKey)) return {
38
+ chartType: "pie",
39
+ xKey,
40
+ yKey
41
+ };
42
+ if (uniqueCategories <= INFERENCE_CONFIG.barMaxCategories) return {
43
+ chartType: "bar",
44
+ xKey,
45
+ yKey
46
+ };
47
+ return {
48
+ chartType: "line",
49
+ xKey,
50
+ yKey
51
+ };
52
+ }
53
+ const yKey = numericCols.map((c) => c.name);
54
+ if (uniqueCategories <= INFERENCE_CONFIG.groupedBarMaxCategories) return {
55
+ chartType: "bar",
56
+ xKey,
57
+ yKey
58
+ };
59
+ return {
60
+ chartType: "line",
61
+ xKey,
62
+ yKey
63
+ };
64
+ }
65
+ if (numericCols.length >= 2 && stringCols.length === 0 && dateCols.length === 0) return {
66
+ chartType: "scatter",
67
+ xKey: numericCols[0].name,
68
+ yKey: numericCols[1].name
69
+ };
70
+ return null;
71
+ }
72
+
73
+ //#endregion
74
+ export { inferChartType };
75
+ //# sourceMappingURL=genie-chart-inference.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"genie-chart-inference.js","names":[],"sources":["../../../src/react/genie/genie-chart-inference.ts"],"sourcesContent":["/**\n * ┌─────────────────────────────────────────────────────────────────────┐\n * │ CHART INFERENCE RULES │\n * │ │\n * │ These rules determine what chart type is shown for Genie query │\n * │ results. Modify thresholds and chart type mappings here. │\n * │ │\n * │ Column types are classified from SQL type_name: │\n * │ DATE: DATE, TIMESTAMP, TIMESTAMP_NTZ │\n * │ NUMERIC: DECIMAL, INT, DOUBLE, FLOAT, LONG, etc. │\n * │ STRING: STRING, VARCHAR, CHAR │\n * │ │\n * │ Rules (applied in priority order): │\n * │ │\n * │ SKIP (return null): │\n * │ - < 2 rows │\n * │ - < 2 columns │\n * │ - No numeric columns │\n * │ │\n * │ MATCH: │\n * │ 1. DATE + numeric(s) → line (timeseries) │\n * │ 2. STRING + 1 numeric, ≤7 cats → pie │\n * │ 3. STRING + 1 numeric, ≤100 cats → bar │\n * │ 4. STRING + 1 numeric, >100 cats → line │\n * │ 5. STRING + N numerics, ≤50 cats → bar (grouped) │\n * │ 6. STRING + N numerics, >50 cats → line (multi-series) │\n * │ 7. 2+ numerics only → scatter │\n * │ 8. Otherwise → null (skip) │\n * │ │\n * │ KNOWN LIMITATIONS: │\n * │ - First-column heuristic: picks first string col as category │\n * │ - No semantic understanding (can't tell ID from meaningful val) │\n * └─────────────────────────────────────────────────────────────────────┘\n */\n\nimport type { ChartType } from \"../charts/types\";\nimport type { GenieColumnMeta } from \"./genie-query-transform\";\n\n// ---------------------------------------------------------------------------\n// Configuration — edit thresholds here\n// ---------------------------------------------------------------------------\n\nconst INFERENCE_CONFIG = {\n /** Min rows required to show any chart */\n minRows: 2,\n /** Max unique categories for pie chart */\n pieMaxCategories: 7,\n /** Max unique categories for bar chart (single series) */\n barMaxCategories: 100,\n /** Max unique categories for grouped bar chart (multi series) */\n groupedBarMaxCategories: 50,\n} as const;\n\nexport interface ChartInference {\n chartType: ChartType;\n xKey: string;\n yKey: string | string[];\n}\n\nfunction countUnique(rows: Record<string, unknown>[], key: string): number {\n const seen = new Set<unknown>();\n for (const row of rows) {\n seen.add(row[key]);\n }\n return seen.size;\n}\n\nfunction hasNegativeValues(\n rows: Record<string, unknown>[],\n key: string,\n): boolean {\n for (const row of rows) {\n if (Number(row[key]) < 0) return true;\n }\n return false;\n}\n\n// ---------------------------------------------------------------------------\n// Main inference function\n// ---------------------------------------------------------------------------\n\n/**\n * Infer the best chart type for the given Genie query result.\n * Returns `null` when the data is not suitable for charting.\n */\nexport function inferChartType(\n rows: Record<string, unknown>[],\n columns: GenieColumnMeta[],\n): ChartInference | null {\n // Guard: need at least minRows and 2 columns\n if (rows.length < INFERENCE_CONFIG.minRows || columns.length < 2) {\n return null;\n }\n\n const dateCols = columns.filter((c) => c.category === \"date\");\n const numericCols = columns.filter((c) => c.category === \"numeric\");\n const stringCols = columns.filter((c) => c.category === \"string\");\n\n // Guard: must have at least one numeric column\n if (numericCols.length === 0) return null;\n\n // Rule 1: DATE + numeric(s) → line (timeseries)\n if (dateCols.length > 0 && numericCols.length >= 1) {\n return {\n chartType: \"line\",\n xKey: dateCols[0].name,\n yKey:\n numericCols.length === 1\n ? numericCols[0].name\n : numericCols.map((c) => c.name),\n };\n }\n\n // Rules 2–6: STRING + numeric(s)\n if (stringCols.length > 0 && numericCols.length >= 1) {\n const xKey = stringCols[0].name;\n const uniqueCategories = countUnique(rows, xKey);\n\n if (numericCols.length === 1) {\n const yKey = numericCols[0].name;\n\n // Rule 2: few categories → pie (skip if negative values)\n if (\n uniqueCategories <= INFERENCE_CONFIG.pieMaxCategories &&\n !hasNegativeValues(rows, yKey)\n ) {\n return { chartType: \"pie\", xKey, yKey };\n }\n // Rule 3: moderate categories → bar\n if (uniqueCategories <= INFERENCE_CONFIG.barMaxCategories) {\n return { chartType: \"bar\", xKey, yKey };\n }\n // Rule 4: many categories → line\n return { chartType: \"line\", xKey, yKey };\n }\n\n // Multiple numerics\n const yKey = numericCols.map((c) => c.name);\n\n // Rule 5: moderate categories → bar (grouped)\n if (uniqueCategories <= INFERENCE_CONFIG.groupedBarMaxCategories) {\n return { chartType: \"bar\", xKey, yKey };\n }\n // Rule 6: many categories → line (multi-series)\n return { chartType: \"line\", xKey, yKey };\n }\n\n // Rule 7: 2+ numerics only (no string, no date) → scatter\n if (\n numericCols.length >= 2 &&\n stringCols.length === 0 &&\n dateCols.length === 0\n ) {\n return {\n chartType: \"scatter\",\n xKey: numericCols[0].name,\n yKey: numericCols[1].name,\n };\n }\n\n // Rule 8: fallback — no chart\n return null;\n}\n"],"mappings":";AA0CA,MAAM,mBAAmB;CAEvB,SAAS;CAET,kBAAkB;CAElB,kBAAkB;CAElB,yBAAyB;CAC1B;AAQD,SAAS,YAAY,MAAiC,KAAqB;CACzE,MAAM,uBAAO,IAAI,KAAc;AAC/B,MAAK,MAAM,OAAO,KAChB,MAAK,IAAI,IAAI,KAAK;AAEpB,QAAO,KAAK;;AAGd,SAAS,kBACP,MACA,KACS;AACT,MAAK,MAAM,OAAO,KAChB,KAAI,OAAO,IAAI,KAAK,GAAG,EAAG,QAAO;AAEnC,QAAO;;;;;;AAWT,SAAgB,eACd,MACA,SACuB;AAEvB,KAAI,KAAK,SAAS,iBAAiB,WAAW,QAAQ,SAAS,EAC7D,QAAO;CAGT,MAAM,WAAW,QAAQ,QAAQ,MAAM,EAAE,aAAa,OAAO;CAC7D,MAAM,cAAc,QAAQ,QAAQ,MAAM,EAAE,aAAa,UAAU;CACnE,MAAM,aAAa,QAAQ,QAAQ,MAAM,EAAE,aAAa,SAAS;AAGjE,KAAI,YAAY,WAAW,EAAG,QAAO;AAGrC,KAAI,SAAS,SAAS,KAAK,YAAY,UAAU,EAC/C,QAAO;EACL,WAAW;EACX,MAAM,SAAS,GAAG;EAClB,MACE,YAAY,WAAW,IACnB,YAAY,GAAG,OACf,YAAY,KAAK,MAAM,EAAE,KAAK;EACrC;AAIH,KAAI,WAAW,SAAS,KAAK,YAAY,UAAU,GAAG;EACpD,MAAM,OAAO,WAAW,GAAG;EAC3B,MAAM,mBAAmB,YAAY,MAAM,KAAK;AAEhD,MAAI,YAAY,WAAW,GAAG;GAC5B,MAAM,OAAO,YAAY,GAAG;AAG5B,OACE,oBAAoB,iBAAiB,oBACrC,CAAC,kBAAkB,MAAM,KAAK,CAE9B,QAAO;IAAE,WAAW;IAAO;IAAM;IAAM;AAGzC,OAAI,oBAAoB,iBAAiB,iBACvC,QAAO;IAAE,WAAW;IAAO;IAAM;IAAM;AAGzC,UAAO;IAAE,WAAW;IAAQ;IAAM;IAAM;;EAI1C,MAAM,OAAO,YAAY,KAAK,MAAM,EAAE,KAAK;AAG3C,MAAI,oBAAoB,iBAAiB,wBACvC,QAAO;GAAE,WAAW;GAAO;GAAM;GAAM;AAGzC,SAAO;GAAE,WAAW;GAAQ;GAAM;GAAM;;AAI1C,KACE,YAAY,UAAU,KACtB,WAAW,WAAW,KACtB,SAAS,WAAW,EAEpB,QAAO;EACL,WAAW;EACX,MAAM,YAAY,GAAG;EACrB,MAAM,YAAY,GAAG;EACtB;AAIH,QAAO"}
@@ -1 +1 @@
1
- {"version":3,"file":"genie-chat-message.d.ts","names":[],"sources":["../../../src/react/genie/genie-chat-message.tsx"],"mappings":";;;;UAyBiB,qBAAA;;EAEf,OAAA,EAAS,gBAAA;EAFM;EAIf,SAAA;AAAA;;iBAQc,gBAAA,CAAA;EACd,OAAA;EACA;AAAA,GACC,qBAAA,GAAqB,kBAAA,CAAA,GAAA,CAAA,OAAA"}
1
+ {"version":3,"file":"genie-chat-message.d.ts","names":[],"sources":["../../../src/react/genie/genie-chat-message.tsx"],"mappings":";;;;UA0BiB,qBAAA;;EAEf,OAAA,EAAS,gBAAA;EAFM;EAIf,SAAA;AAAA;;iBAQc,gBAAA,CAAA;EACd,OAAA;EACA;AAAA,GACC,qBAAA,GAAqB,kBAAA,CAAA,GAAA,CAAA,OAAA"}
@@ -1,6 +1,7 @@
1
1
  import { cn } from "../lib/utils.js";
2
2
  import { Avatar, AvatarFallback } from "../ui/avatar.js";
3
3
  import { Card } from "../ui/card.js";
4
+ import { GenieQueryVisualization } from "./genie-query-visualization.js";
4
5
  import { useMemo } from "react";
5
6
  import { jsx, jsxs } from "react/jsx-runtime";
6
7
  import { marked } from "marked";
@@ -45,22 +46,32 @@ function GenieChatMessage({ message, className }) {
45
46
  })]
46
47
  }), queryAttachments.length > 0 && /* @__PURE__ */ jsx("div", {
47
48
  className: "flex flex-col gap-2 w-full min-w-0",
48
- children: queryAttachments.map((att) => /* @__PURE__ */ jsx(Card, {
49
- className: "px-4 py-3 text-xs overflow-hidden shadow-none",
50
- children: /* @__PURE__ */ jsxs("details", { children: [/* @__PURE__ */ jsx("summary", {
51
- className: "cursor-pointer select-none font-medium",
52
- children: att.query?.title ?? "SQL Query"
53
- }), /* @__PURE__ */ jsxs("div", {
54
- className: "mt-2 flex flex-col gap-1",
55
- children: [att.query?.description && /* @__PURE__ */ jsx("span", {
56
- className: "text-muted-foreground",
57
- children: att.query.description
58
- }), att.query?.query && /* @__PURE__ */ jsx("pre", {
59
- className: "mt-1 p-2 rounded bg-background text-[11px] whitespace-pre-wrap break-all",
60
- children: att.query.query
49
+ children: queryAttachments.map((att) => {
50
+ const key = att.attachmentId ?? "query";
51
+ const queryResult = att.attachmentId ? message.queryResults.get(att.attachmentId) : void 0;
52
+ return /* @__PURE__ */ jsxs("div", {
53
+ className: "flex flex-col gap-2",
54
+ children: [/* @__PURE__ */ jsx(Card, {
55
+ className: "px-4 py-3 text-xs overflow-hidden shadow-none",
56
+ children: /* @__PURE__ */ jsxs("details", { children: [/* @__PURE__ */ jsx("summary", {
57
+ className: "cursor-pointer select-none font-medium",
58
+ children: att.query?.title ?? "SQL Query"
59
+ }), /* @__PURE__ */ jsxs("div", {
60
+ className: "mt-2 flex flex-col gap-1",
61
+ children: [att.query?.description && /* @__PURE__ */ jsx("span", {
62
+ className: "text-muted-foreground",
63
+ children: att.query.description
64
+ }), att.query?.query && /* @__PURE__ */ jsx("pre", {
65
+ className: "mt-1 p-2 rounded bg-background text-[11px] whitespace-pre-wrap break-all",
66
+ children: att.query.query
67
+ })]
68
+ })] })
69
+ }), queryResult != null && /* @__PURE__ */ jsx(Card, {
70
+ className: "px-4 py-3 overflow-hidden",
71
+ children: /* @__PURE__ */ jsx(GenieQueryVisualization, { data: queryResult })
61
72
  })]
62
- })] })
63
- }, att.attachmentId ?? "query"))
73
+ }, key);
74
+ })
64
75
  })]
65
76
  })]
66
77
  });