@databricks/appkit-ui 0.57.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.
- package/dist/js/format/index.d.ts +38 -0
- package/dist/js/format/index.d.ts.map +1 -0
- package/dist/js/format/index.js +160 -0
- package/dist/js/format/index.js.map +1 -0
- package/dist/js/index.d.ts +5 -1
- package/dist/js/index.js +3 -1
- package/dist/js/metric-filter/index.d.ts +40 -0
- package/dist/js/metric-filter/index.d.ts.map +1 -0
- package/dist/js/metric-filter/index.js +56 -0
- package/dist/js/metric-filter/index.js.map +1 -0
- package/dist/react/charts/base.d.ts +19 -2
- package/dist/react/charts/base.d.ts.map +1 -1
- package/dist/react/charts/base.js +28 -8
- package/dist/react/charts/base.js.map +1 -1
- package/dist/react/charts/index.d.ts +4 -3
- package/dist/react/charts/normalize.d.ts.map +1 -1
- package/dist/react/charts/normalize.js +6 -6
- package/dist/react/charts/normalize.js.map +1 -1
- package/dist/react/charts/options.d.ts +31 -2
- package/dist/react/charts/options.d.ts.map +1 -1
- package/dist/react/charts/options.js +150 -13
- package/dist/react/charts/options.js.map +1 -1
- package/dist/react/charts/types.d.ts +54 -1
- package/dist/react/charts/types.d.ts.map +1 -1
- package/dist/react/charts/types.js.map +1 -1
- package/dist/react/charts/utils.d.ts +8 -9
- package/dist/react/charts/utils.d.ts.map +1 -1
- package/dist/react/charts/utils.js +135 -28
- package/dist/react/charts/utils.js.map +1 -1
- package/dist/react/hooks/analytics-sse.js +117 -0
- package/dist/react/hooks/analytics-sse.js.map +1 -0
- package/dist/react/hooks/index.d.ts +4 -1
- package/dist/react/hooks/types.d.ts +90 -4
- package/dist/react/hooks/types.d.ts.map +1 -1
- package/dist/react/hooks/use-analytics-query.d.ts.map +1 -1
- package/dist/react/hooks/use-analytics-query.js +14 -74
- package/dist/react/hooks/use-analytics-query.js.map +1 -1
- package/dist/react/hooks/use-metric-view.d.ts +39 -0
- package/dist/react/hooks/use-metric-view.d.ts.map +1 -0
- package/dist/react/hooks/use-metric-view.js +178 -0
- package/dist/react/hooks/use-metric-view.js.map +1 -0
- package/dist/react/index.d.ts +9 -5
- package/dist/react/index.js +5 -3
- package/dist/react/lib/format.js +3 -1
- package/dist/react/lib/format.js.map +1 -1
- package/dist/shared/src/index.d.ts +2 -0
- package/dist/shared/src/metric-filter.d.ts +36 -0
- package/dist/shared/src/metric-filter.d.ts.map +1 -0
- package/dist/shared/src/metric-metadata.d.ts +15 -0
- package/dist/shared/src/metric-metadata.d.ts.map +1 -0
- package/docs/development/type-generation.md +7 -6
- package/docs/plugins/ai-search.md +10 -0
- package/docs/plugins/analytics.md +287 -12
- package/package.json +1 -1
- 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
|
|
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
|
|
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
|
|
92
|
-
const timestamp =
|
|
93
|
-
if (
|
|
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,
|
|
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,
|
|
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
|
-
...
|
|
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(
|
|
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) =>
|
|
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
|
-
...
|
|
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:
|
|
265
|
-
showSymbol:
|
|
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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"}
|