@databricks/appkit-ui 0.58.0 → 0.59.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 (54) hide show
  1. package/dist/js/format/index.d.ts +38 -0
  2. package/dist/js/format/index.d.ts.map +1 -0
  3. package/dist/js/format/index.js +160 -0
  4. package/dist/js/format/index.js.map +1 -0
  5. package/dist/js/index.d.ts +5 -1
  6. package/dist/js/index.js +3 -1
  7. package/dist/js/metric-filter/index.d.ts +40 -0
  8. package/dist/js/metric-filter/index.d.ts.map +1 -0
  9. package/dist/js/metric-filter/index.js +56 -0
  10. package/dist/js/metric-filter/index.js.map +1 -0
  11. package/dist/react/charts/base.d.ts +19 -2
  12. package/dist/react/charts/base.d.ts.map +1 -1
  13. package/dist/react/charts/base.js +28 -8
  14. package/dist/react/charts/base.js.map +1 -1
  15. package/dist/react/charts/index.d.ts +4 -3
  16. package/dist/react/charts/normalize.d.ts.map +1 -1
  17. package/dist/react/charts/normalize.js +6 -6
  18. package/dist/react/charts/normalize.js.map +1 -1
  19. package/dist/react/charts/options.d.ts +31 -2
  20. package/dist/react/charts/options.d.ts.map +1 -1
  21. package/dist/react/charts/options.js +150 -13
  22. package/dist/react/charts/options.js.map +1 -1
  23. package/dist/react/charts/types.d.ts +54 -1
  24. package/dist/react/charts/types.d.ts.map +1 -1
  25. package/dist/react/charts/types.js.map +1 -1
  26. package/dist/react/charts/utils.d.ts +8 -9
  27. package/dist/react/charts/utils.d.ts.map +1 -1
  28. package/dist/react/charts/utils.js +135 -28
  29. package/dist/react/charts/utils.js.map +1 -1
  30. package/dist/react/hooks/analytics-sse.js +117 -0
  31. package/dist/react/hooks/analytics-sse.js.map +1 -0
  32. package/dist/react/hooks/index.d.ts +4 -1
  33. package/dist/react/hooks/types.d.ts +90 -4
  34. package/dist/react/hooks/types.d.ts.map +1 -1
  35. package/dist/react/hooks/use-analytics-query.d.ts.map +1 -1
  36. package/dist/react/hooks/use-analytics-query.js +14 -74
  37. package/dist/react/hooks/use-analytics-query.js.map +1 -1
  38. package/dist/react/hooks/use-metric-view.d.ts +39 -0
  39. package/dist/react/hooks/use-metric-view.d.ts.map +1 -0
  40. package/dist/react/hooks/use-metric-view.js +178 -0
  41. package/dist/react/hooks/use-metric-view.js.map +1 -0
  42. package/dist/react/index.d.ts +9 -5
  43. package/dist/react/index.js +5 -3
  44. package/dist/react/lib/format.js +3 -1
  45. package/dist/react/lib/format.js.map +1 -1
  46. package/dist/shared/src/index.d.ts +2 -0
  47. package/dist/shared/src/metric-filter.d.ts +36 -0
  48. package/dist/shared/src/metric-filter.d.ts.map +1 -0
  49. package/dist/shared/src/metric-metadata.d.ts +15 -0
  50. package/dist/shared/src/metric-metadata.d.ts.map +1 -0
  51. package/docs/development/type-generation.md +7 -6
  52. package/docs/plugins/analytics.md +287 -12
  53. package/package.json +1 -1
  54. package/sbom.cdx.json +1 -1
@@ -1,15 +1,15 @@
1
1
  import { DATE_FIELD_PATTERNS, NAME_FIELD_PATTERNS } from "../../js/constants.js";
2
2
  import { ArrowClient } from "../../js/arrow/arrow-client.js";
3
3
  import { isArrowTable } from "./types.js";
4
- import { sortNumericAscending, sortTimeSeriesAscending, toChartArray } from "./utils.js";
4
+ import { sortNumericAscending, sortTimeSeriesAscending, toChartArray, toChronologicalValue } from "./utils.js";
5
5
 
6
6
  //#region src/react/charts/normalize.ts
7
7
  /**
8
- * Checks if a value looks like an ISO date string
8
+ * Checks if a value is a date string a time axis can plot.
9
9
  */
10
10
  function isDateString(value) {
11
11
  if (typeof value !== "string") return false;
12
- return /^\d{4}-\d{2}-\d{2}(T|$)/.test(value);
12
+ return toChronologicalValue(value) !== null;
13
13
  }
14
14
  /**
15
15
  * Checks if a value is numeric (number or numeric string)
@@ -88,9 +88,9 @@ function jsonValueToChartValue(value, isYValue, isDateField) {
88
88
  if (typeof value === "number") return value;
89
89
  if (typeof value === "bigint") return Number(value);
90
90
  if (typeof value === "string") {
91
- if (isDateField && isDateString(value)) {
92
- const timestamp = new Date(value).getTime();
93
- if (!Number.isNaN(timestamp)) return timestamp;
91
+ if (isDateField) {
92
+ const timestamp = toChronologicalValue(value);
93
+ if (timestamp !== null) return timestamp;
94
94
  }
95
95
  if (isYValue) {
96
96
  const trimmed = value.trim();
@@ -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 {\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":"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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 toChronologicalValue,\n} from \"./utils\";\n\n// ============================================================================\n// Type Detection Helpers\n// ============================================================================\n\n/**\n * Checks if a value is a date string a time axis can plot.\n */\nfunction isDateString(value: unknown): boolean {\n if (typeof value !== \"string\") return false;\n return toChronologicalValue(value) !== null;\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) {\n const timestamp = toChronologicalValue(value);\n if (timestamp !== null) {\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":"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"}
@@ -1,4 +1,4 @@
1
- import { ChartType, ChartUITokens } from "./types.js";
1
+ import { ChartType, ChartUITokens, ChartValueFormatter } from "./types.js";
2
2
 
3
3
  //#region src/react/charts/options.d.ts
4
4
  interface OptionBuilderContext {
@@ -10,6 +10,7 @@ interface OptionBuilderContext {
10
10
  showLegend: boolean;
11
11
  xField?: string;
12
12
  ui?: ChartUITokens;
13
+ valueFormatter?: ChartValueFormatter;
13
14
  }
14
15
  interface CartesianContext extends OptionBuilderContext {
15
16
  chartType: ChartType;
@@ -18,6 +19,12 @@ interface CartesianContext extends OptionBuilderContext {
18
19
  smooth: boolean;
19
20
  showSymbol: boolean;
20
21
  symbolSize: number;
22
+ /**
23
+ * Whether a click handler is attached. When true, line/area series set
24
+ * `triggerLineEvent` so a click anywhere on the stroke fires (not just on a
25
+ * symbol) — otherwise clicking a thin line is nearly impossible to land.
26
+ */
27
+ interactive?: boolean;
21
28
  }
22
29
  declare function buildRadarOption(ctx: OptionBuilderContext, showArea?: boolean): Record<string, unknown>;
23
30
  declare function buildPieOption(ctx: OptionBuilderContext, chartType: "pie" | "donut", innerRadius: number, showLabels: boolean, labelPosition: string): Record<string, unknown>;
@@ -36,6 +43,28 @@ interface HeatmapContext extends OptionBuilderContext {
36
43
  }
37
44
  declare function buildHeatmapOption(ctx: HeatmapContext): Record<string, unknown>;
38
45
  declare function buildCartesianOption(ctx: CartesianContext): Record<string, unknown>;
46
+ /** Options controlling {@link applySelectionEmphasis}. */
47
+ interface SelectionEmphasisOptions {
48
+ /** Opacity for dimmed (non-selected) elements. @default 0.3 */
49
+ dimmedOpacity?: number;
50
+ /** Opacity for emphasized (selected) elements. @default 1 */
51
+ selectedOpacity?: number;
52
+ }
53
+ /**
54
+ * Pure, declarative selection-emphasis transform for a built ECharts `option`.
55
+ *
56
+ * Given one or more selected category names, returns a new `option` in which the
57
+ * matching data element(s) render at full prominence while the rest are dimmed
58
+ * via `itemStyle.opacity`. It is a **no-op** (returns the input unchanged) when
59
+ * `selected` is `undefined` or empty.
60
+ *
61
+ * @typeParam T - The option object type (typically `Record<string, unknown>`).
62
+ * @param option - The ECharts option produced by one of the `build*Option` helpers.
63
+ * @param selected - The selected category name(s); `undefined`/empty means no emphasis.
64
+ * @param opts - Optional opacity overrides. See {@link SelectionEmphasisOptions}.
65
+ * @returns A new option with emphasis applied, or the original `option` when there is no selection.
66
+ */
67
+ declare function applySelectionEmphasis<T>(option: T, selected: string | string[] | undefined, opts?: SelectionEmphasisOptions): T;
39
68
  //#endregion
40
- export { CartesianContext, HeatmapContext, OptionBuilderContext, buildCartesianOption, buildHeatmapOption, buildHorizontalBarOption, buildPieOption, buildRadarOption };
69
+ export { CartesianContext, HeatmapContext, OptionBuilderContext, applySelectionEmphasis, buildCartesianOption, buildHeatmapOption, buildHorizontalBarOption, buildPieOption, buildRadarOption };
41
70
  //# sourceMappingURL=options.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"options.d.ts","names":[],"sources":["../../../src/react/charts/options.ts"],"mappings":";;;UAaiB,oBAAA;EACf,KAAA;EACA,QAAA,EAAU,MAAA;EACV,OAAA;EACA,MAAA;EACA,KAAA;EACA,UAAA;EACA,MAAA;EACA,EAAA,GAAK,aAAA;AAAA;AAAA,UAGU,gBAAA,SAAyB,oBAAA;EACxC,SAAA,EAAW,SAAA;EACX,YAAA;EACA,OAAA;EACA,MAAA;EACA,UAAA;EACA,UAAA;AAAA;AAAA,iBAyDc,gBAAA,CACd,GAAA,EAAK,oBAAA,EACL,QAAA,aACC,MAAA;AAAA,iBAyCa,cAAA,CACd,GAAA,EAAK,oBAAA,EACL,SAAA,mBACA,WAAA,UACA,UAAA,WACA,aAAA,WACC,MAAA;AAAA,iBAqDa,wBAAA,CACd,GAAA,EAAK,oBAAA,EACL,OAAA,YACC,MAAA;AAAA,UA8Cc,cAAA,SAAuB,oBAAA;EAvNE;EAyNxC,SAAA;EAxNW;EA0NX,WAAA;EAxNA;EA0NA,GAAA;EAxNA;EA0NA,GAAA;EAzNU;EA2NV,UAAA;AAAA;AAAA,iBAGc,kBAAA,CACd,GAAA,EAAK,cAAA,GACJ,MAAA;AAAA,iBA2Ea,oBAAA,CACd,GAAA,EAAK,gBAAA,GACJ,MAAA"}
1
+ {"version":3,"file":"options.d.ts","names":[],"sources":["../../../src/react/charts/options.ts"],"mappings":";;;UAaiB,oBAAA;EACf,KAAA;EACA,QAAA,EAAU,MAAA;EACV,OAAA;EACA,MAAA;EACA,KAAA;EACA,UAAA;EACA,MAAA;EACA,EAAA,GAAK,aAAA;EACL,cAAA,GAAiB,mBAAA;AAAA;AAAA,UAGF,gBAAA,SAAyB,oBAAA;EACxC,SAAA,EAAW,SAAA;EACX,YAAA;EACA,OAAA;EACA,MAAA;EACA,UAAA;EACA,UAAA;EAVA;;;;;EAgBA,WAAA;AAAA;AAAA,iBA+Ec,gBAAA,CACd,GAAA,EAAK,oBAAA,EACL,QAAA,aACC,MAAA;AAAA,iBAoDa,cAAA,CACd,GAAA,EAAK,oBAAA,EACL,SAAA,mBACA,WAAA,UACA,UAAA,WACA,aAAA,WACC,MAAA;AAAA,iBAqEa,wBAAA,CACd,GAAA,EAAK,oBAAA,EACL,OAAA,YACC,MAAA;AAAA,UAmDc,cAAA,SAAuB,oBAAA;EAlRtC;EAoRA,SAAA;EAnRA;EAqRA,WAAA;EAnRA;EAqRA,GAAA;EAnRA;EAqRA,GAAA;EA/QW;EAiRX,UAAA;AAAA;AAAA,iBAGc,kBAAA,CACd,GAAA,EAAK,cAAA,GACJ,MAAA;AAAA,iBAsFa,oBAAA,CACd,GAAA,EAAK,gBAAA,GACJ,MAAA;;UA2FO,wBAAA;EAzXR;EA2XA,aAAA;EAzXC;EA2XD,eAAA;AAAA;AAvUF;;;;;;;;;;;;;AA2EA;AA3EA,iBA2bgB,sBAAA,GAAA,CACd,MAAA,EAAQ,CAAA,EACR,QAAA,iCACA,IAAA,GAAM,wBAAA,GACL,CAAA"}
@@ -1,5 +1,6 @@
1
+ import { formatLabel } from "../../js/format/index.js";
1
2
  import { FALLBACK_UI_TOKENS } from "./constants.js";
2
- import { createTimeSeriesData, escapeHtml, formatLabel, truncateLabel } from "./utils.js";
3
+ import { createTimeSeriesData, escapeHtml, truncateLabel } from "./utils.js";
3
4
 
4
5
  //#region src/react/charts/options.ts
5
6
  function buildBaseOption(ctx) {
@@ -41,8 +42,26 @@ function tooltipTokens(ui) {
41
42
  textStyle: { color: ui.axisTitle }
42
43
  };
43
44
  }
45
+ /**
46
+ * ECharts passes tuple-backed series (time-series, scatter, heatmap) to tooltip
47
+ * value formatters as the whole tuple. The measure is always the final entry.
48
+ */
49
+ function toMeasureValue(value) {
50
+ const scalar = Array.isArray(value) ? value[value.length - 1] : value;
51
+ if (typeof scalar === "string" || typeof scalar === "number") return scalar;
52
+ return scalar == null ? "" : String(scalar);
53
+ }
54
+ function tooltipValueFormatter(formatter, field) {
55
+ return (value) => formatter(toMeasureValue(value), field);
56
+ }
57
+ function valueAxisLabel(ctx) {
58
+ const field = ctx.yFields[0];
59
+ const formatter = ctx.valueFormatter;
60
+ return formatter && field ? { formatter: (value) => formatter(value, field) } : {};
61
+ }
44
62
  function buildRadarOption(ctx, showArea = true) {
45
63
  const ui = ctx.ui ?? FALLBACK_UI_TOKENS;
64
+ const formatter = ctx.valueFormatter;
46
65
  const maxValue = Math.max(...ctx.yFields.flatMap((f) => ctx.yDataMap[f].map((v) => Number(v) || 0)));
47
66
  return {
48
67
  ...buildBaseOption(ctx),
@@ -69,6 +88,9 @@ function buildRadarOption(ctx, showArea = true) {
69
88
  data: ctx.yFields.map((key, idx) => ({
70
89
  name: formatLabel(key),
71
90
  value: ctx.yDataMap[key],
91
+ tooltip: formatter ? { valueFormatter: (value) => {
92
+ return (Array.isArray(value) ? value : [value]).map((item) => formatter(toMeasureValue(item), key)).join(", ");
93
+ } } : void 0,
72
94
  itemStyle: { color: ctx.colors[idx % ctx.colors.length] },
73
95
  areaStyle: showArea ? { opacity: .3 } : void 0
74
96
  }))
@@ -82,12 +104,14 @@ function buildPieOption(ctx, chartType, innerRadius, showLabels, labelPosition)
82
104
  value: ctx.yDataMap[ctx.yFields[0]]?.[i] ?? 0
83
105
  }));
84
106
  const isDonut = chartType === "donut" || innerRadius > 0;
107
+ const valueField = ctx.yFields[0];
108
+ const formatter = ctx.valueFormatter;
85
109
  return {
86
110
  ...buildBaseOption(ctx),
87
111
  tooltip: {
88
112
  ...tooltipTokens(ui),
89
113
  trigger: "item",
90
- formatter: "{b}: {c} ({d}%)"
114
+ formatter: formatter && valueField ? (params) => `${escapeHtml(String(params.name))}: ${escapeHtml(formatter(params.value, valueField))} (${params.percent}%)` : "{b}: {c} ({d}%)"
91
115
  },
92
116
  legend: ctx.showLegend ? {
93
117
  orient: "vertical",
@@ -99,6 +123,7 @@ function buildPieOption(ctx, chartType, innerRadius, showLabels, labelPosition)
99
123
  type: "pie",
100
124
  radius: isDonut ? [`${innerRadius || 40}%`, "70%"] : "70%",
101
125
  center: ["60%", "50%"],
126
+ minAngle: 3,
102
127
  data: pieData,
103
128
  label: {
104
129
  show: showLabels,
@@ -137,7 +162,7 @@ function buildHorizontalBarOption(ctx, stacked) {
137
162
  },
138
163
  xAxis: {
139
164
  type: "value",
140
- ...axisCommon(ui)
165
+ ...mergeAxisLabel(ui, valueAxisLabel(ctx))
141
166
  },
142
167
  yAxis: {
143
168
  type: "category",
@@ -159,12 +184,16 @@ function buildHorizontalBarOption(ctx, stacked) {
159
184
  4,
160
185
  0
161
186
  ] },
162
- color: ctx.colors[idx % ctx.colors.length]
187
+ color: ctx.colors[idx % ctx.colors.length],
188
+ tooltip: ctx.valueFormatter ? { valueFormatter: tooltipValueFormatter(ctx.valueFormatter, key) } : void 0
163
189
  }))
164
190
  };
165
191
  }
166
192
  function buildHeatmapOption(ctx) {
167
193
  const ui = ctx.ui ?? FALLBACK_UI_TOKENS;
194
+ const valueField = ctx.yFields[0];
195
+ const formatter = ctx.valueFormatter;
196
+ const formatHeatmapValue = (value) => formatter && valueField ? formatter(value, valueField) : String(value);
168
197
  return {
169
198
  ...buildBaseOption(ctx),
170
199
  tooltip: {
@@ -172,7 +201,7 @@ function buildHeatmapOption(ctx) {
172
201
  trigger: "item",
173
202
  formatter: (params) => {
174
203
  const [xIdx, yIdx, value] = params.data;
175
- return `${escapeHtml(String(ctx.xData[xIdx] ?? xIdx))}, ${escapeHtml(String(ctx.yAxisData[yIdx] ?? yIdx))}: ${escapeHtml(String(value))}`;
204
+ return `${escapeHtml(String(ctx.xData[xIdx] ?? xIdx))}, ${escapeHtml(String(ctx.yAxisData[yIdx] ?? yIdx))}: ${escapeHtml(formatHeatmapValue(value))}`;
176
205
  }
177
206
  },
178
207
  grid: {
@@ -204,6 +233,7 @@ function buildHeatmapOption(ctx) {
204
233
  right: "2%",
205
234
  top: "center",
206
235
  textStyle: { color: ui.axisTitle },
236
+ formatter: formatter && valueField ? (value) => formatter(value, valueField) : void 0,
207
237
  inRange: { color: ctx.colors.length >= 2 ? ctx.colors : ["#f0f0f0", ctx.colors[0]] }
208
238
  },
209
239
  series: [{
@@ -211,7 +241,7 @@ function buildHeatmapOption(ctx) {
211
241
  data: ctx.heatmapData,
212
242
  label: {
213
243
  show: ctx.showLabels,
214
- formatter: (params) => String(params.data[2])
244
+ formatter: (params) => formatHeatmapValue(params.data[2])
215
245
  },
216
246
  emphasis: { itemStyle: {
217
247
  shadowBlur: 10,
@@ -222,10 +252,11 @@ function buildHeatmapOption(ctx) {
222
252
  }
223
253
  function buildCartesianOption(ctx) {
224
254
  const ui = ctx.ui ?? FALLBACK_UI_TOKENS;
225
- const { chartType, isTimeSeries, stacked, smooth, showSymbol, symbolSize } = ctx;
255
+ const { chartType, isTimeSeries, stacked, smooth, showSymbol, symbolSize, interactive } = ctx;
226
256
  const hasMultipleSeries = ctx.yFields.length > 1;
227
257
  const seriesType = chartType === "area" ? "line" : chartType;
228
258
  const isScatter = chartType === "scatter";
259
+ const isLineLike = chartType === "line" || chartType === "area";
229
260
  return {
230
261
  ...buildBaseOption(ctx),
231
262
  tooltip: {
@@ -255,16 +286,17 @@ function buildCartesianOption(ctx) {
255
286
  yAxis: {
256
287
  type: "value",
257
288
  name: ctx.yFields.length === 1 ? formatLabel(ctx.yFields[0]) : void 0,
258
- ...axisCommon(ui)
289
+ ...mergeAxisLabel(ui, valueAxisLabel(ctx))
259
290
  },
260
291
  series: ctx.yFields.map((key, idx) => ({
261
292
  name: formatLabel(key),
262
293
  type: seriesType,
263
294
  data: isScatter ? ctx.xData.map((x, i) => [x, ctx.yDataMap[key][i]]) : isTimeSeries ? createTimeSeriesData(ctx.xData, ctx.yDataMap[key]) : ctx.yDataMap[key],
264
- smooth: chartType === "line" || chartType === "area" ? smooth : void 0,
265
- showSymbol: chartType === "line" || chartType === "area" ? showSymbol : void 0,
295
+ smooth: isLineLike ? smooth : void 0,
296
+ showSymbol: isLineLike ? showSymbol : void 0,
266
297
  symbol: isScatter ? "circle" : void 0,
267
- symbolSize: isScatter ? symbolSize : void 0,
298
+ symbolSize: isScatter || isLineLike ? symbolSize : void 0,
299
+ triggerLineEvent: isLineLike && interactive ? true : void 0,
268
300
  areaStyle: chartType === "area" ? { opacity: .3 } : void 0,
269
301
  stack: stacked && chartType === "area" ? "total" : void 0,
270
302
  itemStyle: chartType === "bar" ? { borderRadius: [
@@ -273,11 +305,116 @@ function buildCartesianOption(ctx) {
273
305
  0,
274
306
  0
275
307
  ] } : void 0,
276
- color: ctx.colors[idx % ctx.colors.length]
308
+ color: ctx.colors[idx % ctx.colors.length],
309
+ tooltip: ctx.valueFormatter ? { valueFormatter: tooltipValueFormatter(ctx.valueFormatter, key) } : void 0
277
310
  }))
278
311
  };
279
312
  }
313
+ const DIMMED_OPACITY = .3;
314
+ /** Opacity applied to selected (emphasized) data elements. */
315
+ const SELECTED_OPACITY = 1;
316
+ /**
317
+ * Returns selected category names, or `null` when empty.
318
+ */
319
+ function toSelectionSet(selected) {
320
+ if (selected == null) return null;
321
+ const names = Array.isArray(selected) ? selected : [selected];
322
+ const set = new Set(names.map((name) => String(name)).filter((name) => name !== ""));
323
+ return set.size > 0 ? set : null;
324
+ }
325
+ /**
326
+ * Finds category-axis labels used to map bar positions to names.
327
+ * Built-in chart options expose at most one category axis.
328
+ * Horizontal and vertical bars put that category on different axes.
329
+ */
330
+ function categoryNamesFromAxes(option) {
331
+ for (const axisKey of ["xAxis", "yAxis"]) {
332
+ const axis = option[axisKey];
333
+ if (axis !== null && typeof axis === "object" && !Array.isArray(axis)) {
334
+ const a = axis;
335
+ if (a.type === "category" && Array.isArray(a.data)) return a.data;
336
+ }
337
+ }
338
+ return null;
339
+ }
340
+ /**
341
+ * Returns a copy of a single data item with its `itemStyle.opacity` set.
342
+ * Object data items (e.g. pie `{ name, value }`) are spread and their existing
343
+ * `itemStyle` preserved; primitive data items (e.g. raw bar values) are wrapped
344
+ * into `{ value, itemStyle }`.
345
+ * per-datum `itemStyle` merges over the series-level `itemStyle` in ECharts, so
346
+ * styling such as bar `borderRadius` is retained.
347
+ */
348
+ function withDatumOpacity(datum, opacity) {
349
+ if (datum !== null && typeof datum === "object" && !Array.isArray(datum)) {
350
+ const d = datum;
351
+ const prev = d.itemStyle !== null && typeof d.itemStyle === "object" && !Array.isArray(d.itemStyle) ? d.itemStyle : {};
352
+ return {
353
+ ...d,
354
+ itemStyle: {
355
+ ...prev,
356
+ opacity
357
+ }
358
+ };
359
+ }
360
+ return {
361
+ value: datum,
362
+ itemStyle: { opacity }
363
+ };
364
+ }
365
+ /**
366
+ * Applies opacity to pie/bar categories.
367
+ */
368
+ function emphasizeSeries(series, selected, dimmedOpacity, selectedOpacity, categoryNames) {
369
+ if (series === null || typeof series !== "object" || Array.isArray(series)) return series;
370
+ const s = series;
371
+ if (!Array.isArray(s.data)) return series;
372
+ let nameAt;
373
+ if (s.type === "pie") nameAt = (datum) => datum !== null && typeof datum === "object" && "name" in datum ? String(datum.name) : void 0;
374
+ else if (s.type === "bar") {
375
+ if (!categoryNames) return series;
376
+ nameAt = (_datum, index) => categoryNames[index] !== void 0 ? String(categoryNames[index]) : void 0;
377
+ } else return series;
378
+ const data = s.data.map((datum, index) => {
379
+ const name = nameAt(datum, index);
380
+ if (name === void 0) return datum;
381
+ return withDatumOpacity(datum, selected.has(name) ? selectedOpacity : dimmedOpacity);
382
+ });
383
+ return {
384
+ ...s,
385
+ data
386
+ };
387
+ }
388
+ /**
389
+ * Pure, declarative selection-emphasis transform for a built ECharts `option`.
390
+ *
391
+ * Given one or more selected category names, returns a new `option` in which the
392
+ * matching data element(s) render at full prominence while the rest are dimmed
393
+ * via `itemStyle.opacity`. It is a **no-op** (returns the input unchanged) when
394
+ * `selected` is `undefined` or empty.
395
+ *
396
+ * @typeParam T - The option object type (typically `Record<string, unknown>`).
397
+ * @param option - The ECharts option produced by one of the `build*Option` helpers.
398
+ * @param selected - The selected category name(s); `undefined`/empty means no emphasis.
399
+ * @param opts - Optional opacity overrides. See {@link SelectionEmphasisOptions}.
400
+ * @returns A new option with emphasis applied, or the original `option` when there is no selection.
401
+ */
402
+ function applySelectionEmphasis(option, selected, opts = {}) {
403
+ const selectedSet = toSelectionSet(selected);
404
+ if (!selectedSet) return option;
405
+ if (option === null || typeof option !== "object" || Array.isArray(option)) return option;
406
+ const opt = option;
407
+ if (!Array.isArray(opt.series)) return option;
408
+ const dimmedOpacity = opts.dimmedOpacity ?? DIMMED_OPACITY;
409
+ const selectedOpacity = opts.selectedOpacity ?? SELECTED_OPACITY;
410
+ const categoryNames = categoryNamesFromAxes(opt);
411
+ const series = opt.series.map((s) => emphasizeSeries(s, selectedSet, dimmedOpacity, selectedOpacity, categoryNames));
412
+ return {
413
+ ...opt,
414
+ series
415
+ };
416
+ }
280
417
 
281
418
  //#endregion
282
- export { buildCartesianOption, buildHeatmapOption, buildHorizontalBarOption, buildPieOption, buildRadarOption };
419
+ export { applySelectionEmphasis, buildCartesianOption, buildHeatmapOption, buildHorizontalBarOption, buildPieOption, buildRadarOption };
283
420
  //# sourceMappingURL=options.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"options.js","names":[],"sources":["../../../src/react/charts/options.ts"],"sourcesContent":["import { FALLBACK_UI_TOKENS } from \"./constants\";\nimport type { ChartType, ChartUITokens } from \"./types\";\nimport {\n createTimeSeriesData,\n escapeHtml,\n formatLabel,\n truncateLabel,\n} 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 ui?: ChartUITokens;\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 const ui = ctx.ui ?? FALLBACK_UI_TOKENS;\n return {\n title: ctx.title\n ? {\n text: ctx.title,\n left: \"center\",\n textStyle: { color: ui.axisTitle },\n }\n : undefined,\n color: ctx.colors,\n };\n}\n\nfunction axisCommon(ui: ChartUITokens) {\n return {\n axisLabel: { color: ui.axisLabel },\n axisLine: { lineStyle: { color: ui.grid } },\n axisTick: { lineStyle: { color: ui.grid } },\n splitLine: { lineStyle: { color: ui.grid } },\n nameTextStyle: { color: ui.axisTitle },\n };\n}\n\nfunction mergeAxisLabel(\n ui: ChartUITokens,\n axisLabel: Record<string, unknown>,\n): Record<string, unknown> {\n return {\n ...axisCommon(ui),\n axisLabel: { color: ui.axisLabel, ...axisLabel },\n };\n}\n\nfunction legendTextStyle(ui: ChartUITokens) {\n return { textStyle: { color: ui.axisTitle } };\n}\n\nfunction tooltipTokens(ui: ChartUITokens) {\n return {\n backgroundColor: ui.tooltipBg,\n borderColor: ui.grid,\n textStyle: { color: ui.axisTitle },\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 ui = ctx.ui ?? FALLBACK_UI_TOKENS;\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: { ...tooltipTokens(ui), trigger: \"item\" },\n legend:\n ctx.showLegend && ctx.yFields.length > 1\n ? { top: \"bottom\", ...legendTextStyle(ui) }\n : undefined,\n radar: {\n indicator: ctx.xData.map((name) => ({\n name: String(name),\n max: maxValue * 1.2,\n })),\n shape: \"polygon\",\n axisName: { color: ui.axisTitle },\n axisLine: { lineStyle: { color: ui.grid } },\n splitLine: { lineStyle: { color: ui.grid } },\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 ui = ctx.ui ?? FALLBACK_UI_TOKENS;\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: {\n ...tooltipTokens(ui),\n trigger: \"item\",\n formatter: \"{b}: {c} ({d}%)\",\n },\n legend: ctx.showLegend\n ? {\n orient: \"vertical\",\n left: \"left\",\n top: \"middle\",\n ...legendTextStyle(ui),\n }\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 color: \"inherit\",\n textBorderWidth: 0,\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 ui = ctx.ui ?? FALLBACK_UI_TOKENS;\n const hasMultipleSeries = ctx.yFields.length > 1;\n\n return {\n ...buildBaseOption(ctx),\n tooltip: {\n ...tooltipTokens(ui),\n trigger: \"axis\",\n axisPointer: { type: \"shadow\" },\n },\n legend:\n ctx.showLegend && hasMultipleSeries\n ? { top: \"bottom\", ...legendTextStyle(ui) }\n : 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\", ...axisCommon(ui) },\n yAxis: {\n type: \"category\",\n data: ctx.xData,\n ...mergeAxisLabel(ui, {\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 const ui = ctx.ui ?? FALLBACK_UI_TOKENS;\n return {\n ...buildBaseOption(ctx),\n tooltip: {\n ...tooltipTokens(ui),\n trigger: \"item\",\n formatter: (params: { data: [number, number, number] }) => {\n const [xIdx, yIdx, value] = params.data;\n // Function formatter output is injected as raw HTML into the\n // tooltip DOM, so data-derived labels must be escaped.\n const xLabel = escapeHtml(String(ctx.xData[xIdx] ?? xIdx));\n const yLabel = escapeHtml(String(ctx.yAxisData[yIdx] ?? yIdx));\n return `${xLabel}, ${yLabel}: ${escapeHtml(String(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 ...mergeAxisLabel(ui, {\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 ...mergeAxisLabel(ui, {\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 textStyle: { color: ui.axisTitle },\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 ui = ctx.ui ?? FALLBACK_UI_TOKENS;\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: { ...tooltipTokens(ui), trigger: isScatter ? \"item\" : \"axis\" },\n legend:\n ctx.showLegend && hasMultipleSeries\n ? { top: \"bottom\", ...legendTextStyle(ui) }\n : undefined,\n grid: {\n left: \"10%\",\n right: \"10%\",\n top: ctx.title ? \"15%\" : \"10%\",\n bottom: ctx.showLegend && hasMultipleSeries ? \"20%\" : \"15%\",\n containLabel: true,\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 ...mergeAxisLabel(\n ui,\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 },\n yAxis: {\n type: \"value\",\n name: ctx.yFields.length === 1 ? formatLabel(ctx.yFields[0]) : undefined,\n ...axisCommon(ui),\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
+ {"version":3,"file":"options.js","names":[],"sources":["../../../src/react/charts/options.ts"],"sourcesContent":["import { FALLBACK_UI_TOKENS } from \"./constants\";\nimport type { ChartType, ChartUITokens, ChartValueFormatter } from \"./types\";\nimport {\n createTimeSeriesData,\n escapeHtml,\n formatLabel,\n truncateLabel,\n} 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 ui?: ChartUITokens;\n valueFormatter?: ChartValueFormatter;\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 * Whether a click handler is attached. When true, line/area series set\n * `triggerLineEvent` so a click anywhere on the stroke fires (not just on a\n * symbol) — otherwise clicking a thin line is nearly impossible to land.\n */\n interactive?: boolean;\n}\n\n// ============================================================================\n// Base Option Builder\n// ============================================================================\n\nfunction buildBaseOption(ctx: OptionBuilderContext): Record<string, unknown> {\n const ui = ctx.ui ?? FALLBACK_UI_TOKENS;\n return {\n title: ctx.title\n ? {\n text: ctx.title,\n left: \"center\",\n textStyle: { color: ui.axisTitle },\n }\n : undefined,\n color: ctx.colors,\n };\n}\n\nfunction axisCommon(ui: ChartUITokens) {\n return {\n axisLabel: { color: ui.axisLabel },\n axisLine: { lineStyle: { color: ui.grid } },\n axisTick: { lineStyle: { color: ui.grid } },\n splitLine: { lineStyle: { color: ui.grid } },\n nameTextStyle: { color: ui.axisTitle },\n };\n}\n\nfunction mergeAxisLabel(\n ui: ChartUITokens,\n axisLabel: Record<string, unknown>,\n): Record<string, unknown> {\n return {\n ...axisCommon(ui),\n axisLabel: { color: ui.axisLabel, ...axisLabel },\n };\n}\n\nfunction legendTextStyle(ui: ChartUITokens) {\n return { textStyle: { color: ui.axisTitle } };\n}\n\nfunction tooltipTokens(ui: ChartUITokens) {\n return {\n backgroundColor: ui.tooltipBg,\n borderColor: ui.grid,\n textStyle: { color: ui.axisTitle },\n };\n}\n\n/**\n * ECharts passes tuple-backed series (time-series, scatter, heatmap) to tooltip\n * value formatters as the whole tuple. The measure is always the final entry.\n */\nfunction toMeasureValue(value: unknown): string | number {\n const scalar = Array.isArray(value) ? value[value.length - 1] : value;\n if (typeof scalar === \"string\" || typeof scalar === \"number\") return scalar;\n return scalar == null ? \"\" : String(scalar);\n}\n\nfunction tooltipValueFormatter(formatter: ChartValueFormatter, field: string) {\n return (value: unknown) => formatter(toMeasureValue(value), field);\n}\n\nfunction valueAxisLabel(ctx: OptionBuilderContext) {\n const field = ctx.yFields[0];\n const formatter = ctx.valueFormatter;\n return formatter && field\n ? { formatter: (value: string | number) => formatter(value, field) }\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 ui = ctx.ui ?? FALLBACK_UI_TOKENS;\n const formatter = ctx.valueFormatter;\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: { ...tooltipTokens(ui), trigger: \"item\" },\n legend:\n ctx.showLegend && ctx.yFields.length > 1\n ? { top: \"bottom\", ...legendTextStyle(ui) }\n : undefined,\n radar: {\n indicator: ctx.xData.map((name) => ({\n name: String(name),\n max: maxValue * 1.2,\n })),\n shape: \"polygon\",\n axisName: { color: ui.axisTitle },\n axisLine: { lineStyle: { color: ui.grid } },\n splitLine: { lineStyle: { color: ui.grid } },\n },\n series: [\n {\n type: \"radar\",\n data: ctx.yFields.map((key, idx) => ({\n name: formatLabel(key),\n value: ctx.yDataMap[key],\n tooltip: formatter\n ? {\n valueFormatter: (value: unknown) => {\n const values = Array.isArray(value) ? value : [value];\n return values\n .map((item) => formatter(toMeasureValue(item), key))\n .join(\", \");\n },\n }\n : undefined,\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 ui = ctx.ui ?? FALLBACK_UI_TOKENS;\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 const valueField = ctx.yFields[0];\n const formatter = ctx.valueFormatter;\n\n return {\n ...buildBaseOption(ctx),\n tooltip: {\n ...tooltipTokens(ui),\n trigger: \"item\",\n formatter:\n formatter && valueField\n ? (params: {\n name: string;\n value: string | number;\n percent: number;\n }) =>\n `${escapeHtml(String(params.name))}: ${escapeHtml(\n formatter(params.value, valueField),\n )} (${params.percent}%)`\n : \"{b}: {c} ({d}%)\",\n },\n legend: ctx.showLegend\n ? {\n orient: \"vertical\",\n left: \"left\",\n top: \"middle\",\n ...legendTextStyle(ui),\n }\n : undefined,\n series: [\n {\n type: \"pie\",\n radius: isDonut ? [`${innerRadius || 40}%`, \"70%\"] : \"70%\",\n center: [\"60%\", \"50%\"],\n // Keep very small categories pointer-selectable. ECharts still reports\n // labels/tooltips from the true values, while a three-degree sector is\n // wide enough to survive CSS-pixel rounding on typical card sizes.\n minAngle: 3,\n data: pieData,\n label: {\n show: showLabels,\n position: labelPosition,\n formatter: \"{b}: {d}%\",\n color: \"inherit\",\n textBorderWidth: 0,\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 ui = ctx.ui ?? FALLBACK_UI_TOKENS;\n const hasMultipleSeries = ctx.yFields.length > 1;\n\n return {\n ...buildBaseOption(ctx),\n tooltip: {\n ...tooltipTokens(ui),\n trigger: \"axis\",\n axisPointer: { type: \"shadow\" },\n },\n legend:\n ctx.showLegend && hasMultipleSeries\n ? { top: \"bottom\", ...legendTextStyle(ui) }\n : 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\", ...mergeAxisLabel(ui, valueAxisLabel(ctx)) },\n yAxis: {\n type: \"category\",\n data: ctx.xData,\n ...mergeAxisLabel(ui, {\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 tooltip: ctx.valueFormatter\n ? {\n valueFormatter: tooltipValueFormatter(ctx.valueFormatter, key),\n }\n : undefined,\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 const ui = ctx.ui ?? FALLBACK_UI_TOKENS;\n const valueField = ctx.yFields[0];\n const formatter = ctx.valueFormatter;\n const formatHeatmapValue = (value: number) =>\n formatter && valueField ? formatter(value, valueField) : String(value);\n return {\n ...buildBaseOption(ctx),\n tooltip: {\n ...tooltipTokens(ui),\n trigger: \"item\",\n formatter: (params: { data: [number, number, number] }) => {\n const [xIdx, yIdx, value] = params.data;\n // Function formatter output is injected as raw HTML into the\n // tooltip DOM, so data-derived labels must be escaped.\n const xLabel = escapeHtml(String(ctx.xData[xIdx] ?? xIdx));\n const yLabel = escapeHtml(String(ctx.yAxisData[yIdx] ?? yIdx));\n return `${xLabel}, ${yLabel}: ${escapeHtml(formatHeatmapValue(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 ...mergeAxisLabel(ui, {\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 ...mergeAxisLabel(ui, {\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 textStyle: { color: ui.axisTitle },\n formatter:\n formatter && valueField\n ? (value: number) => formatter(value, valueField)\n : undefined,\n inRange: {\n // A visualMap gradient needs at least two stops; with a single-color\n // palette, ramp from a light grey to that color instead of passing a\n // one-entry array (which ECharts renders as a flat, unreadable scale).\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 formatHeatmapValue(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 ui = ctx.ui ?? FALLBACK_UI_TOKENS;\n const {\n chartType,\n isTimeSeries,\n stacked,\n smooth,\n showSymbol,\n symbolSize,\n interactive,\n } = ctx;\n const hasMultipleSeries = ctx.yFields.length > 1;\n const seriesType = chartType === \"area\" ? \"line\" : chartType;\n const isScatter = chartType === \"scatter\";\n const isLineLike = chartType === \"line\" || chartType === \"area\";\n\n return {\n ...buildBaseOption(ctx),\n tooltip: { ...tooltipTokens(ui), trigger: isScatter ? \"item\" : \"axis\" },\n legend:\n ctx.showLegend && hasMultipleSeries\n ? { top: \"bottom\", ...legendTextStyle(ui) }\n : undefined,\n grid: {\n left: \"10%\",\n right: \"10%\",\n top: ctx.title ? \"15%\" : \"10%\",\n bottom: ctx.showLegend && hasMultipleSeries ? \"20%\" : \"15%\",\n containLabel: true,\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 ...mergeAxisLabel(\n ui,\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 },\n yAxis: {\n type: \"value\",\n name: ctx.yFields.length === 1 ? formatLabel(ctx.yFields[0]) : undefined,\n ...mergeAxisLabel(ui, valueAxisLabel(ctx)),\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: isLineLike ? smooth : undefined,\n showSymbol: isLineLike ? showSymbol : undefined,\n symbol: isScatter ? \"circle\" : undefined,\n // Symbol size applies to line/area as well as scatter, so an interactive\n // line can present a clickable point, not just a hairline.\n symbolSize: isScatter || isLineLike ? symbolSize : undefined,\n // Fire click events along the whole line stroke, not only on symbols,\n // when the chart is interactive. No effect on non-line series.\n triggerLineEvent: isLineLike && interactive ? true : 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 tooltip: ctx.valueFormatter\n ? {\n valueFormatter: tooltipValueFormatter(ctx.valueFormatter, key),\n }\n : undefined,\n })),\n };\n}\n\n// ============================================================================\n// Selection Emphasis (declarative cross-filter highlighting)\n// ============================================================================\n\n// Selection opacity stays local because it is not a theme token.\nconst DIMMED_OPACITY = 0.3;\n\n/** Opacity applied to selected (emphasized) data elements. */\nconst SELECTED_OPACITY = 1;\n\n/** Options controlling {@link applySelectionEmphasis}. */\ninterface SelectionEmphasisOptions {\n /** Opacity for dimmed (non-selected) elements. @default 0.3 */\n dimmedOpacity?: number;\n /** Opacity for emphasized (selected) elements. @default 1 */\n selectedOpacity?: number;\n}\n\n/**\n * Returns selected category names, or `null` when empty.\n */\nfunction toSelectionSet(\n selected: string | string[] | undefined,\n): Set<string> | null {\n if (selected == null) return null;\n const names = Array.isArray(selected) ? selected : [selected];\n const set = new Set(\n names.map((name) => String(name)).filter((name) => name !== \"\"),\n );\n return set.size > 0 ? set : null;\n}\n\n/**\n * Finds category-axis labels used to map bar positions to names.\n * Built-in chart options expose at most one category axis.\n * Horizontal and vertical bars put that category on different axes.\n */\nfunction categoryNamesFromAxes(\n option: Record<string, unknown>,\n): (string | number)[] | null {\n for (const axisKey of [\"xAxis\", \"yAxis\"] as const) {\n const axis = option[axisKey];\n if (axis !== null && typeof axis === \"object\" && !Array.isArray(axis)) {\n const a = axis as Record<string, unknown>;\n if (a.type === \"category\" && Array.isArray(a.data)) {\n return a.data as (string | number)[];\n }\n }\n }\n return null;\n}\n\n/**\n * Returns a copy of a single data item with its `itemStyle.opacity` set.\n * Object data items (e.g. pie `{ name, value }`) are spread and their existing\n * `itemStyle` preserved; primitive data items (e.g. raw bar values) are wrapped\n * into `{ value, itemStyle }`.\n * per-datum `itemStyle` merges over the series-level `itemStyle` in ECharts, so\n * styling such as bar `borderRadius` is retained.\n */\nfunction withDatumOpacity(datum: unknown, opacity: number): unknown {\n if (datum !== null && typeof datum === \"object\" && !Array.isArray(datum)) {\n const d = datum as Record<string, unknown>;\n const prev =\n d.itemStyle !== null &&\n typeof d.itemStyle === \"object\" &&\n !Array.isArray(d.itemStyle)\n ? (d.itemStyle as Record<string, unknown>)\n : {};\n return { ...d, itemStyle: { ...prev, opacity } };\n }\n return { value: datum as number | string, itemStyle: { opacity } };\n}\n\n/**\n * Applies opacity to pie/bar categories.\n */\nfunction emphasizeSeries(\n series: unknown,\n selected: Set<string>,\n dimmedOpacity: number,\n selectedOpacity: number,\n categoryNames: (string | number)[] | null,\n): unknown {\n if (series === null || typeof series !== \"object\" || Array.isArray(series)) {\n return series;\n }\n const s = series as Record<string, unknown>;\n if (!Array.isArray(s.data)) return series;\n\n let nameAt: (datum: unknown, index: number) => string | undefined;\n if (s.type === \"pie\") {\n nameAt = (datum) =>\n datum !== null && typeof datum === \"object\" && \"name\" in datum\n ? String((datum as Record<string, unknown>).name)\n : undefined;\n } else if (s.type === \"bar\") {\n // Bar data items are raw values; the category name lives on the category axis.\n if (!categoryNames) return series;\n nameAt = (_datum, index) =>\n categoryNames[index] !== undefined\n ? String(categoryNames[index])\n : undefined;\n } else {\n return series;\n }\n\n const data = (s.data as unknown[]).map((datum, index) => {\n const name = nameAt(datum, index);\n if (name === undefined) return datum;\n const opacity = selected.has(name) ? selectedOpacity : dimmedOpacity;\n return withDatumOpacity(datum, opacity);\n });\n\n return { ...s, data };\n}\n\n/**\n * Pure, declarative selection-emphasis transform for a built ECharts `option`.\n *\n * Given one or more selected category names, returns a new `option` in which the\n * matching data element(s) render at full prominence while the rest are dimmed\n * via `itemStyle.opacity`. It is a **no-op** (returns the input unchanged) when\n * `selected` is `undefined` or empty.\n *\n * @typeParam T - The option object type (typically `Record<string, unknown>`).\n * @param option - The ECharts option produced by one of the `build*Option` helpers.\n * @param selected - The selected category name(s); `undefined`/empty means no emphasis.\n * @param opts - Optional opacity overrides. See {@link SelectionEmphasisOptions}.\n * @returns A new option with emphasis applied, or the original `option` when there is no selection.\n */\nexport function applySelectionEmphasis<T>(\n option: T,\n selected: string | string[] | undefined,\n opts: SelectionEmphasisOptions = {},\n): T {\n const selectedSet = toSelectionSet(selected);\n if (!selectedSet) return option;\n\n if (option === null || typeof option !== \"object\" || Array.isArray(option)) {\n return option;\n }\n const opt = option as Record<string, unknown>;\n if (!Array.isArray(opt.series)) return option;\n\n const dimmedOpacity = opts.dimmedOpacity ?? DIMMED_OPACITY;\n const selectedOpacity = opts.selectedOpacity ?? SELECTED_OPACITY;\n const categoryNames = categoryNamesFromAxes(opt);\n\n const series = (opt.series as unknown[]).map((s) =>\n emphasizeSeries(\n s,\n selectedSet,\n dimmedOpacity,\n selectedOpacity,\n categoryNames,\n ),\n );\n\n return { ...opt, series } as T;\n}\n"],"mappings":"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"}