@databricks/appkit-ui 0.1.4 → 0.1.5
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/AGENTS.md +37 -12
- package/CLAUDE.md +37 -12
- package/NOTICE.md +2 -0
- package/dist/js/arrow/arrow-client.js.map +1 -1
- package/dist/js/arrow/lazy-arrow.js.map +1 -1
- package/dist/js/sse/connect-sse.js.map +1 -1
- package/dist/react/charts/base.js.map +1 -1
- package/dist/react/charts/create-chart.js +2 -1
- package/dist/react/charts/create-chart.js.map +1 -1
- package/dist/react/charts/normalize.d.ts +1 -1
- package/dist/react/charts/normalize.js +1 -1
- package/dist/react/charts/normalize.js.map +1 -1
- package/dist/react/charts/theme.js.map +1 -1
- package/dist/react/charts/types.d.ts +5 -0
- package/dist/react/charts/types.d.ts.map +1 -1
- package/dist/react/charts/types.js.map +1 -1
- package/dist/react/charts/utils.js.map +1 -1
- package/dist/react/charts/wrapper.d.ts +2 -0
- package/dist/react/charts/wrapper.d.ts.map +1 -1
- package/dist/react/charts/wrapper.js +4 -2
- package/dist/react/charts/wrapper.js.map +1 -1
- package/dist/react/hooks/types.d.ts +2 -0
- package/dist/react/hooks/types.d.ts.map +1 -1
- package/dist/react/hooks/use-analytics-query.js +9 -3
- package/dist/react/hooks/use-analytics-query.js.map +1 -1
- package/dist/react/hooks/use-chart-data.d.ts +2 -0
- package/dist/react/hooks/use-chart-data.d.ts.map +1 -1
- package/dist/react/hooks/use-chart-data.js +3 -2
- package/dist/react/hooks/use-chart-data.js.map +1 -1
- package/dist/react/table/table-wrapper.js +3 -2
- package/dist/react/table/table-wrapper.js.map +1 -1
- package/dist/react/table/types.d.ts.map +1 -1
- package/dist/react/ui/accordion.d.ts +5 -5
- package/dist/react/ui/alert-dialog.d.ts +12 -12
- package/dist/react/ui/alert-dialog.d.ts.map +1 -1
- package/dist/react/ui/alert.d.ts +4 -4
- package/dist/react/ui/alert.d.ts.map +1 -1
- package/dist/react/ui/aspect-ratio.d.ts +2 -2
- package/dist/react/ui/avatar.d.ts +4 -4
- package/dist/react/ui/badge.d.ts +2 -2
- package/dist/react/ui/breadcrumb.d.ts +8 -8
- package/dist/react/ui/button-group.d.ts +4 -4
- package/dist/react/ui/button.d.ts +2 -2
- package/dist/react/ui/calendar.d.ts +3 -3
- package/dist/react/ui/card.d.ts +8 -8
- package/dist/react/ui/carousel.d.ts +6 -6
- package/dist/react/ui/chart.d.ts +5 -5
- package/dist/react/ui/chart.d.ts.map +1 -1
- package/dist/react/ui/chart.js.map +1 -1
- package/dist/react/ui/checkbox.d.ts +2 -2
- package/dist/react/ui/collapsible.d.ts +4 -4
- package/dist/react/ui/command.d.ts +10 -10
- package/dist/react/ui/command.d.ts.map +1 -1
- package/dist/react/ui/context-menu.d.ts +16 -16
- package/dist/react/ui/dialog.d.ts +11 -11
- package/dist/react/ui/drawer.d.ts +11 -11
- package/dist/react/ui/dropdown-menu.d.ts +16 -16
- package/dist/react/ui/dropdown-menu.d.ts.map +1 -1
- package/dist/react/ui/empty.d.ts +7 -7
- package/dist/react/ui/field.d.ts +11 -11
- package/dist/react/ui/form.d.ts +7 -7
- package/dist/react/ui/hover-card.d.ts +4 -4
- package/dist/react/ui/input-group.d.ts +7 -7
- package/dist/react/ui/input-otp.d.ts +5 -5
- package/dist/react/ui/input.d.ts +2 -2
- package/dist/react/ui/input.d.ts.map +1 -1
- package/dist/react/ui/item.d.ts +11 -11
- package/dist/react/ui/kbd.d.ts +3 -3
- package/dist/react/ui/label.d.ts +2 -2
- package/dist/react/ui/menubar.d.ts +17 -17
- package/dist/react/ui/navigation-menu.d.ts +9 -9
- package/dist/shared/src/sql/helpers.js.map +1 -1
- package/llms.txt +37 -12
- package/package.json +1 -1
package/AGENTS.md
CHANGED
|
@@ -440,23 +440,49 @@ Formats:
|
|
|
440
440
|
- `format: "JSON"` (default) returns JSON rows
|
|
441
441
|
- `format: "ARROW"` returns an Arrow “statement_id” payload over SSE, then the client fetches binary Arrow from `/api/analytics/arrow-result/:jobId`
|
|
442
442
|
|
|
443
|
-
###
|
|
443
|
+
### Execution context and `asUser(req)`
|
|
444
444
|
|
|
445
|
-
|
|
445
|
+
AppKit manages Databricks authentication via two contexts:
|
|
446
446
|
|
|
447
|
-
|
|
447
|
+
- **ServiceContext** (singleton): Initialized at app startup with service principal credentials
|
|
448
|
+
- **ExecutionContext**: Determined at runtime - either service principal or user context
|
|
449
|
+
|
|
450
|
+
**Headers used for user context:**
|
|
448
451
|
|
|
449
452
|
- `x-forwarded-user`: required in production; identifies the user
|
|
450
|
-
- `x-forwarded-access-token`:
|
|
453
|
+
- `x-forwarded-access-token`: required for user token passthrough
|
|
454
|
+
|
|
455
|
+
**Using `asUser(req)` for user-scoped operations:**
|
|
456
|
+
|
|
457
|
+
The `asUser(req)` pattern allows plugins to execute operations using the requesting user's credentials:
|
|
458
|
+
|
|
459
|
+
```ts
|
|
460
|
+
// In a custom plugin route handler
|
|
461
|
+
router.post("/users/me/data", async (req, res) => {
|
|
462
|
+
// Execute as the user (uses their Databricks permissions)
|
|
463
|
+
const result = await this.asUser(req).query("SELECT ...");
|
|
464
|
+
res.json(result);
|
|
465
|
+
});
|
|
466
|
+
|
|
467
|
+
// Service principal execution (default)
|
|
468
|
+
router.post("/system/data", async (req, res) => {
|
|
469
|
+
const result = await this.query("SELECT ...");
|
|
470
|
+
res.json(result);
|
|
471
|
+
});
|
|
472
|
+
```
|
|
473
|
+
|
|
474
|
+
**Context helper functions (exported from `@databricks/appkit`):**
|
|
475
|
+
|
|
476
|
+
- `getExecutionContext()`: Returns current context (user or service)
|
|
477
|
+
- `getCurrentUserId()`: Returns user ID in user context, service user ID otherwise
|
|
478
|
+
- `getWorkspaceClient()`: Returns the appropriate WorkspaceClient for current context
|
|
479
|
+
- `getWarehouseId()`: `Promise<string>` (from `DATABRICKS_WAREHOUSE_ID` or auto-selected in dev)
|
|
480
|
+
- `getWorkspaceId()`: `Promise<string>` (from `DATABRICKS_WORKSPACE_ID` or fetched)
|
|
481
|
+
- `isInUserContext()`: Returns `true` if currently executing in user context
|
|
451
482
|
|
|
452
|
-
|
|
483
|
+
**Development mode behavior:**
|
|
453
484
|
|
|
454
|
-
|
|
455
|
-
- `serviceUserId`: service principal/user ID
|
|
456
|
-
- `warehouseId`: `Promise<string>` (from `DATABRICKS_WAREHOUSE_ID`, or auto-selected in development)
|
|
457
|
-
- `workspaceId`: `Promise<string>` (from `DATABRICKS_WORKSPACE_ID` or fetched)
|
|
458
|
-
- `userDatabricksClient`: present only when passthrough is available (or in dev it equals service client)
|
|
459
|
-
- `serviceDatabricksClient`: always present
|
|
485
|
+
In local development (`NODE_ENV=development`), if `asUser(req)` is called without a user token, it logs a warning and falls back to the service principal.
|
|
460
486
|
|
|
461
487
|
### Custom plugins (backend)
|
|
462
488
|
|
|
@@ -469,7 +495,6 @@ import type express from "express";
|
|
|
469
495
|
class MyPlugin extends Plugin {
|
|
470
496
|
name = "my-plugin";
|
|
471
497
|
envVars = []; // list required env vars here
|
|
472
|
-
requiresDatabricksClient = false; // set true if you need getRequestContext()
|
|
473
498
|
|
|
474
499
|
injectRoutes(router: express.Router) {
|
|
475
500
|
this.route(router, {
|
package/CLAUDE.md
CHANGED
|
@@ -440,23 +440,49 @@ Formats:
|
|
|
440
440
|
- `format: "JSON"` (default) returns JSON rows
|
|
441
441
|
- `format: "ARROW"` returns an Arrow “statement_id” payload over SSE, then the client fetches binary Arrow from `/api/analytics/arrow-result/:jobId`
|
|
442
442
|
|
|
443
|
-
###
|
|
443
|
+
### Execution context and `asUser(req)`
|
|
444
444
|
|
|
445
|
-
|
|
445
|
+
AppKit manages Databricks authentication via two contexts:
|
|
446
446
|
|
|
447
|
-
|
|
447
|
+
- **ServiceContext** (singleton): Initialized at app startup with service principal credentials
|
|
448
|
+
- **ExecutionContext**: Determined at runtime - either service principal or user context
|
|
449
|
+
|
|
450
|
+
**Headers used for user context:**
|
|
448
451
|
|
|
449
452
|
- `x-forwarded-user`: required in production; identifies the user
|
|
450
|
-
- `x-forwarded-access-token`:
|
|
453
|
+
- `x-forwarded-access-token`: required for user token passthrough
|
|
454
|
+
|
|
455
|
+
**Using `asUser(req)` for user-scoped operations:**
|
|
456
|
+
|
|
457
|
+
The `asUser(req)` pattern allows plugins to execute operations using the requesting user's credentials:
|
|
458
|
+
|
|
459
|
+
```ts
|
|
460
|
+
// In a custom plugin route handler
|
|
461
|
+
router.post("/users/me/data", async (req, res) => {
|
|
462
|
+
// Execute as the user (uses their Databricks permissions)
|
|
463
|
+
const result = await this.asUser(req).query("SELECT ...");
|
|
464
|
+
res.json(result);
|
|
465
|
+
});
|
|
466
|
+
|
|
467
|
+
// Service principal execution (default)
|
|
468
|
+
router.post("/system/data", async (req, res) => {
|
|
469
|
+
const result = await this.query("SELECT ...");
|
|
470
|
+
res.json(result);
|
|
471
|
+
});
|
|
472
|
+
```
|
|
473
|
+
|
|
474
|
+
**Context helper functions (exported from `@databricks/appkit`):**
|
|
475
|
+
|
|
476
|
+
- `getExecutionContext()`: Returns current context (user or service)
|
|
477
|
+
- `getCurrentUserId()`: Returns user ID in user context, service user ID otherwise
|
|
478
|
+
- `getWorkspaceClient()`: Returns the appropriate WorkspaceClient for current context
|
|
479
|
+
- `getWarehouseId()`: `Promise<string>` (from `DATABRICKS_WAREHOUSE_ID` or auto-selected in dev)
|
|
480
|
+
- `getWorkspaceId()`: `Promise<string>` (from `DATABRICKS_WORKSPACE_ID` or fetched)
|
|
481
|
+
- `isInUserContext()`: Returns `true` if currently executing in user context
|
|
451
482
|
|
|
452
|
-
|
|
483
|
+
**Development mode behavior:**
|
|
453
484
|
|
|
454
|
-
|
|
455
|
-
- `serviceUserId`: service principal/user ID
|
|
456
|
-
- `warehouseId`: `Promise<string>` (from `DATABRICKS_WAREHOUSE_ID`, or auto-selected in development)
|
|
457
|
-
- `workspaceId`: `Promise<string>` (from `DATABRICKS_WORKSPACE_ID` or fetched)
|
|
458
|
-
- `userDatabricksClient`: present only when passthrough is available (or in dev it equals service client)
|
|
459
|
-
- `serviceDatabricksClient`: always present
|
|
485
|
+
In local development (`NODE_ENV=development`), if `asUser(req)` is called without a user token, it logs a warning and falls back to the service principal.
|
|
460
486
|
|
|
461
487
|
### Custom plugins (backend)
|
|
462
488
|
|
|
@@ -469,7 +495,6 @@ import type express from "express";
|
|
|
469
495
|
class MyPlugin extends Plugin {
|
|
470
496
|
name = "my-plugin";
|
|
471
497
|
envVars = []; // list required env vars here
|
|
472
|
-
requiresDatabricksClient = false; // set true if you need getRequestContext()
|
|
473
498
|
|
|
474
499
|
injectRoutes(router: express.Router) {
|
|
475
500
|
this.route(router, {
|
package/NOTICE.md
CHANGED
|
@@ -49,6 +49,7 @@ This Software contains code from the following open source projects:
|
|
|
49
49
|
| [@radix-ui/react-tooltip](https://www.npmjs.com/package/@radix-ui/react-tooltip) | 1.2.8 | MIT | https://radix-ui.com/primitives |
|
|
50
50
|
| [@tanstack/react-table](https://www.npmjs.com/package/@tanstack/react-table) | 8.21.3 | MIT | https://tanstack.com/table |
|
|
51
51
|
| [@tanstack/react-virtual](https://www.npmjs.com/package/@tanstack/react-virtual) | 3.13.12 | MIT | https://tanstack.com/virtual |
|
|
52
|
+
| [@types/semver](https://www.npmjs.com/package/@types/semver) | 7.7.1 | MIT | https://github.com/DefinitelyTyped/DefinitelyTyped/tree/master/types/semver |
|
|
52
53
|
| [apache-arrow](https://www.npmjs.com/package/apache-arrow) | 21.1.0 | Apache-2.0 | https://arrow.apache.org/js/ |
|
|
53
54
|
| [class-variance-authority](https://www.npmjs.com/package/class-variance-authority) | 0.7.1 | Apache-2.0 | https://github.com/joe-bell/cva#readme |
|
|
54
55
|
| [clsx](https://www.npmjs.com/package/clsx) | 2.1.1 | MIT | https://github.com/lukeed/clsx#readme |
|
|
@@ -66,6 +67,7 @@ This Software contains code from the following open source projects:
|
|
|
66
67
|
| [react-day-picker](https://www.npmjs.com/package/react-day-picker) | 9.12.0 | MIT | https://daypicker.dev |
|
|
67
68
|
| [react-hook-form](https://www.npmjs.com/package/react-hook-form) | 7.68.0 | MIT | https://react-hook-form.com |
|
|
68
69
|
| [react-resizable-panels](https://www.npmjs.com/package/react-resizable-panels) | 3.0.6 | MIT | https://github.com/bvaughn/react-resizable-panels#readme |
|
|
70
|
+
| [semver](https://www.npmjs.com/package/semver) | 6.3.1, 7.7.3 | ISC | https://github.com/npm/node-semver#readme |
|
|
69
71
|
| [sonner](https://www.npmjs.com/package/sonner) | 2.0.7 | MIT | https://sonner.emilkowal.ski/ |
|
|
70
72
|
| [tailwind-merge](https://www.npmjs.com/package/tailwind-merge) | 3.4.0 | MIT | https://github.com/dcastil/tailwind-merge |
|
|
71
73
|
| [vaul](https://www.npmjs.com/package/vaul) | 1.1.2 | MIT | https://vaul.emilkowal.ski/ |
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"arrow-client.js","names":["cols: Record<string, any>","yDataMap: Record<string, (string | number)[]>","temporalFields: string[]","numericFields: string[]","stringFields: string[]","xField","EMPTY_RESULT: {\n xData: (string | number)[];\n yDataMap: Record<string, (string | number)[]>;\n}","result: (string | number)[]"],"sources":["../../../src/js/arrow/arrow-client.ts"],"sourcesContent":["import type { Field, Table } from \"apache-arrow\";\nimport {\n DATE_FIELD_PATTERNS,\n METADATA_DATE_PATTERNS,\n NAME_FIELD_PATTERNS,\n} from \"../constants\";\nimport {\n getArrowModule,\n initializeTypeIdSets,\n getTypeIdSets,\n getDecimalTypeId,\n} from \"./lazy-arrow\";\n\n// Re-export for backward compatibility\nexport { DATE_FIELD_PATTERNS, NAME_FIELD_PATTERNS };\n\n// Re-export Table type for consumers\nexport type { Table, Field };\n\nexport class ArrowClient {\n /**\n * Processes an Arrow IPC buffer into a Table.\n * Lazily loads the Apache Arrow library on first use.\n *\n * @param buffer - The Arrow IPC format buffer\n * @returns Promise resolving to an Arrow Table\n */\n static async processArrowBuffer(buffer: Uint8Array): Promise<Table> {\n try {\n const arrow = await getArrowModule();\n // Initialize type ID sets now that Arrow is loaded\n await initializeTypeIdSets();\n return arrow.tableFromIPC(buffer);\n } catch (error) {\n throw new Error(\n `Failed to process Arrow buffer: ${\n error instanceof Error ? error.message : \"Unknown error\"\n }`,\n );\n }\n }\n\n static async fetchAndProcessArrow(\n url: string,\n headers?: Record<string, string>,\n ): Promise<Table> {\n try {\n const buffer = await ArrowClient.fetchArrow(url, headers);\n\n return ArrowClient.processArrowBuffer(buffer);\n } catch (error) {\n throw new Error(\n `Failed to fetch Arrow data: ${\n error instanceof Error ? error.message : \"Unknown error\"\n }`,\n );\n }\n }\n\n static extractArrowFields(table: Table) {\n return table.schema.fields.map((field: Field) => {\n return {\n name: field.name,\n type: field.type,\n };\n });\n }\n\n static extractArrowColumns(table: Table): Record<string, any> {\n const cols: Record<string, any> = {};\n\n for (const field of table.schema.fields) {\n const child = table.getChild(field.name);\n\n if (child) {\n cols[field.name] = child.toArray();\n }\n }\n\n return cols;\n }\n\n /**\n * Extracts chart data from Arrow table.\n * Uses get(i) to properly handle complex types like Decimal128.\n * Applies decimal scaling for DECIMAL types.\n *\n * Note: This method assumes Arrow has been loaded (via processArrowBuffer).\n *\n * @returns xData for axis, yDataMap for series data\n */\n static extractChartData(table: Table, xKey: string, yKeys: string[]) {\n // Early exit for empty tables - return cached empty object\n if (table.numRows === 0) {\n return EMPTY_RESULT;\n }\n\n // Get the Decimal type ID (Arrow must be loaded to have a Table)\n const decimalType = getDecimalTypeId();\n\n // Build a map of field name -> pre-computed divisor (10^scale) for decimal types\n const decimalDivisors = new Map<string, number>();\n for (const field of table.schema.fields) {\n if (field.typeId === decimalType) {\n const decType = field.type as { scale: number };\n if (typeof decType.scale === \"number\") {\n // Pre-compute divisor once per field instead of per-column call\n decimalDivisors.set(field.name, 10 ** decType.scale);\n }\n }\n }\n\n // Extract X column using proper value extraction\n const xCol = table.getChild(xKey);\n const xData = extractColumnValues(xCol, decimalDivisors.get(xKey));\n\n // Extract Y columns using proper value extraction\n const yDataMap: Record<string, (string | number)[]> = {};\n for (let i = 0; i < yKeys.length; i++) {\n const key = yKeys[i];\n const col = table.getChild(key);\n yDataMap[key] = extractColumnValues(col, decimalDivisors.get(key));\n }\n\n return { xData, yDataMap };\n }\n\n /**\n * Automatically detect which fields to use for chart axes from an Arrow table\n * Uses the schema's type information for accurate field detection\n *\n * Note: This method assumes Arrow has been loaded (via processArrowBuffer).\n *\n * @param table - Arrow Table to analyze\n * @param orientation - Chart orientation (\"vertical\" for time-series, \"horizontal\" for categorical)\n * @returns Object containing the detected fields\n * @example\n * // Time-series data\n * detectFieldsFromArrow(timeSeriesTable)\n * // { xField: \"date\", yFields: [\"revenue\", \"cost\"], chartType: \"timeseries\" }\n *\n * // Categorical data\n * detectFieldsFromArrow(categoricalTable)\n * // { xField: \"app_name\", yFields: [\"totalSpend\"], chartType: \"categorical\" }\n */\n static detectFieldsFromArrow(\n table: Table,\n orientation?: \"vertical\" | \"horizontal\",\n ): DetectedFields & { chartType: \"timeseries\" | \"categorical\" } {\n const fields = table.schema.fields;\n\n if (fields.length === 0) {\n return { xField: \"x\", yFields: [\"y\"], chartType: \"categorical\" };\n }\n\n const fieldNames = fields.map((f) => f.name);\n\n // Get type ID sets (Arrow must be loaded to have a Table)\n const typeIdSets = getTypeIdSets();\n\n // Categorize fields by their Arrow type\n const temporalFields: string[] = [];\n const numericFields: string[] = [];\n const stringFields: string[] = [];\n\n for (const field of fields) {\n const typeId = field.typeId;\n\n if (typeIdSets.temporal.has(typeId)) {\n temporalFields.push(field.name);\n } else if (typeIdSets.numeric.has(typeId)) {\n numericFields.push(field.name);\n } else if (typeIdSets.string.has(typeId)) {\n stringFields.push(field.name);\n }\n }\n\n // Detect name/category fields: string fields matching name patterns\n let nameFields = stringFields.filter((name) =>\n NAME_FIELD_PATTERNS.some((pattern) =>\n name.toLowerCase().includes(pattern),\n ),\n );\n\n // Fallback: use any string field that doesn't end with _id\n if (nameFields.length === 0) {\n nameFields = stringFields.filter(\n (name) => !name.toLowerCase().endsWith(\"_id\"),\n );\n }\n\n // Separate temporal fields into \"chart-worthy\" dates vs metadata dates\n const chartDateFields = temporalFields.filter(\n (name) =>\n !METADATA_DATE_PATTERNS.some((pattern) =>\n name.toLowerCase().includes(pattern),\n ),\n );\n const metadataDateFields = temporalFields.filter((name) =>\n METADATA_DATE_PATTERNS.some((pattern) =>\n name.toLowerCase().includes(pattern),\n ),\n );\n\n // Also check string fields for date patterns (but not metadata patterns)\n const stringDateFields = stringFields.filter(\n (name) =>\n DATE_FIELD_PATTERNS.some((pattern) =>\n name.toLowerCase().includes(pattern),\n ) &&\n !METADATA_DATE_PATTERNS.some((pattern) =>\n name.toLowerCase().includes(pattern),\n ),\n );\n\n const primaryDateFields = [...chartDateFields, ...stringDateFields];\n\n // Determine chart type: if we have good date fields for charting, it's time-series\n // If we only have metadata dates (like createdAt) and name fields, it's categorical\n const isTimeSeries =\n primaryDateFields.length > 0 && orientation !== \"horizontal\";\n const isCategorical =\n nameFields.length > 0 &&\n (primaryDateFields.length === 0 || orientation === \"horizontal\");\n\n if (orientation === \"horizontal\" || isCategorical) {\n // Categorical: x is name/category field, y is numeric field\n const xField =\n nameFields[0] ||\n primaryDateFields[0] ||\n metadataDateFields[0] ||\n fieldNames[0];\n const yFields =\n numericFields.length > 0\n ? numericFields\n : fieldNames.filter((k) => k !== xField);\n return { xField, yFields, chartType: \"categorical\" };\n }\n\n // Time-series (default): x is date/time field, y is numeric field\n const xField =\n primaryDateFields[0] ||\n metadataDateFields[0] ||\n nameFields[0] ||\n fieldNames[0];\n const yFields =\n numericFields.length > 0\n ? numericFields\n : fieldNames.filter((k) => k !== xField);\n return {\n xField,\n yFields,\n chartType: isTimeSeries ? \"timeseries\" : \"categorical\",\n };\n }\n\n static async fetchArrow(\n url: string,\n headers?: Record<string, string>,\n ): Promise<Uint8Array> {\n try {\n const response = await fetch(url, {\n headers: { \"Content-Type\": \"application/octet-stream\", ...headers },\n });\n\n if (!response.ok) {\n throw new Error(`HTTP ${response.status}: ${response.statusText}`);\n }\n\n const buffer = await response.arrayBuffer();\n\n return new Uint8Array(buffer);\n } catch (error) {\n throw new Error(\n `Failed to fetch Arrow data: ${\n error instanceof Error ? error.message : \"Unknown error\"\n }`,\n );\n }\n }\n}\n\nexport interface DetectedFields {\n /** X field */\n xField: string;\n /** Y fields */\n yFields: string[];\n}\n\n// ============================================================================\n// Utility Functions\n// ============================================================================\n\n// Cached empty result to avoid allocations\nconst EMPTY_RESULT: {\n xData: (string | number)[];\n yDataMap: Record<string, (string | number)[]>;\n} = {\n xData: [],\n yDataMap: {},\n};\n\n/**\n * Extracts values from an Arrow Vector properly.\n * Uses get(i) to handle complex types like Decimal128 correctly.\n * toArray() doesn't work properly for Decimal types - it returns raw bytes.\n *\n * @param col - The Arrow column/vector\n * @param divisor - Pre-computed divisor for DECIMAL types (10^scale)\n */\nfunction extractColumnValues(\n col: { length: number; get: (i: number) => unknown } | null | undefined,\n divisor?: number,\n): (string | number)[] {\n if (!col) return [];\n\n // Pre-allocate array for better performance with large datasets\n const len = col.length;\n const result: (string | number)[] = new Array(len);\n\n for (let i = 0; i < len; i++) {\n const val = col.get(i);\n if (val === null || val === undefined) {\n result[i] = 0;\n } else if (typeof val === \"bigint\") {\n // Apply decimal scaling if needed\n const num = Number(val);\n result[i] = divisor !== undefined ? num / divisor : num;\n } else if (typeof val === \"number\") {\n // Apply decimal scaling if needed\n result[i] = divisor !== undefined ? val / divisor : val;\n } else if (typeof val === \"string\") {\n result[i] = val;\n } else {\n // For complex types (like Decimal), try to convert to number\n const num = Number(val);\n result[i] = divisor !== undefined ? num / divisor : num;\n }\n }\n return result;\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"arrow-client.js","names":["xField"],"sources":["../../../src/js/arrow/arrow-client.ts"],"sourcesContent":["import type { Field, Table } from \"apache-arrow\";\nimport {\n DATE_FIELD_PATTERNS,\n METADATA_DATE_PATTERNS,\n NAME_FIELD_PATTERNS,\n} from \"../constants\";\nimport {\n getArrowModule,\n initializeTypeIdSets,\n getTypeIdSets,\n getDecimalTypeId,\n} from \"./lazy-arrow\";\n\n// Re-export for backward compatibility\nexport { DATE_FIELD_PATTERNS, NAME_FIELD_PATTERNS };\n\n// Re-export Table type for consumers\nexport type { Table, Field };\n\nexport class ArrowClient {\n /**\n * Processes an Arrow IPC buffer into a Table.\n * Lazily loads the Apache Arrow library on first use.\n *\n * @param buffer - The Arrow IPC format buffer\n * @returns Promise resolving to an Arrow Table\n */\n static async processArrowBuffer(buffer: Uint8Array): Promise<Table> {\n try {\n const arrow = await getArrowModule();\n // Initialize type ID sets now that Arrow is loaded\n await initializeTypeIdSets();\n return arrow.tableFromIPC(buffer);\n } catch (error) {\n throw new Error(\n `Failed to process Arrow buffer: ${\n error instanceof Error ? error.message : \"Unknown error\"\n }`,\n );\n }\n }\n\n static async fetchAndProcessArrow(\n url: string,\n headers?: Record<string, string>,\n ): Promise<Table> {\n try {\n const buffer = await ArrowClient.fetchArrow(url, headers);\n\n return ArrowClient.processArrowBuffer(buffer);\n } catch (error) {\n throw new Error(\n `Failed to fetch Arrow data: ${\n error instanceof Error ? error.message : \"Unknown error\"\n }`,\n );\n }\n }\n\n static extractArrowFields(table: Table) {\n return table.schema.fields.map((field: Field) => {\n return {\n name: field.name,\n type: field.type,\n };\n });\n }\n\n static extractArrowColumns(table: Table): Record<string, any> {\n const cols: Record<string, any> = {};\n\n for (const field of table.schema.fields) {\n const child = table.getChild(field.name);\n\n if (child) {\n cols[field.name] = child.toArray();\n }\n }\n\n return cols;\n }\n\n /**\n * Extracts chart data from Arrow table.\n * Uses get(i) to properly handle complex types like Decimal128.\n * Applies decimal scaling for DECIMAL types.\n *\n * Note: This method assumes Arrow has been loaded (via processArrowBuffer).\n *\n * @returns xData for axis, yDataMap for series data\n */\n static extractChartData(table: Table, xKey: string, yKeys: string[]) {\n // Early exit for empty tables - return cached empty object\n if (table.numRows === 0) {\n return EMPTY_RESULT;\n }\n\n // Get the Decimal type ID (Arrow must be loaded to have a Table)\n const decimalType = getDecimalTypeId();\n\n // Build a map of field name -> pre-computed divisor (10^scale) for decimal types\n const decimalDivisors = new Map<string, number>();\n for (const field of table.schema.fields) {\n if (field.typeId === decimalType) {\n const decType = field.type as { scale: number };\n if (typeof decType.scale === \"number\") {\n // Pre-compute divisor once per field instead of per-column call\n decimalDivisors.set(field.name, 10 ** decType.scale);\n }\n }\n }\n\n // Extract X column using proper value extraction\n const xCol = table.getChild(xKey);\n const xData = extractColumnValues(xCol, decimalDivisors.get(xKey));\n\n // Extract Y columns using proper value extraction\n const yDataMap: Record<string, (string | number)[]> = {};\n for (let i = 0; i < yKeys.length; i++) {\n const key = yKeys[i];\n const col = table.getChild(key);\n yDataMap[key] = extractColumnValues(col, decimalDivisors.get(key));\n }\n\n return { xData, yDataMap };\n }\n\n /**\n * Automatically detect which fields to use for chart axes from an Arrow table\n * Uses the schema's type information for accurate field detection\n *\n * Note: This method assumes Arrow has been loaded (via processArrowBuffer).\n *\n * @param table - Arrow Table to analyze\n * @param orientation - Chart orientation (\"vertical\" for time-series, \"horizontal\" for categorical)\n * @returns Object containing the detected fields\n * @example\n * // Time-series data\n * detectFieldsFromArrow(timeSeriesTable)\n * // { xField: \"date\", yFields: [\"revenue\", \"cost\"], chartType: \"timeseries\" }\n *\n * // Categorical data\n * detectFieldsFromArrow(categoricalTable)\n * // { xField: \"app_name\", yFields: [\"totalSpend\"], chartType: \"categorical\" }\n */\n static detectFieldsFromArrow(\n table: Table,\n orientation?: \"vertical\" | \"horizontal\",\n ): DetectedFields & { chartType: \"timeseries\" | \"categorical\" } {\n const fields = table.schema.fields;\n\n if (fields.length === 0) {\n return { xField: \"x\", yFields: [\"y\"], chartType: \"categorical\" };\n }\n\n const fieldNames = fields.map((f) => f.name);\n\n // Get type ID sets (Arrow must be loaded to have a Table)\n const typeIdSets = getTypeIdSets();\n\n // Categorize fields by their Arrow type\n const temporalFields: string[] = [];\n const numericFields: string[] = [];\n const stringFields: string[] = [];\n\n for (const field of fields) {\n const typeId = field.typeId;\n\n if (typeIdSets.temporal.has(typeId)) {\n temporalFields.push(field.name);\n } else if (typeIdSets.numeric.has(typeId)) {\n numericFields.push(field.name);\n } else if (typeIdSets.string.has(typeId)) {\n stringFields.push(field.name);\n }\n }\n\n // Detect name/category fields: string fields matching name patterns\n let nameFields = stringFields.filter((name) =>\n NAME_FIELD_PATTERNS.some((pattern) =>\n name.toLowerCase().includes(pattern),\n ),\n );\n\n // Fallback: use any string field that doesn't end with _id\n if (nameFields.length === 0) {\n nameFields = stringFields.filter(\n (name) => !name.toLowerCase().endsWith(\"_id\"),\n );\n }\n\n // Separate temporal fields into \"chart-worthy\" dates vs metadata dates\n const chartDateFields = temporalFields.filter(\n (name) =>\n !METADATA_DATE_PATTERNS.some((pattern) =>\n name.toLowerCase().includes(pattern),\n ),\n );\n const metadataDateFields = temporalFields.filter((name) =>\n METADATA_DATE_PATTERNS.some((pattern) =>\n name.toLowerCase().includes(pattern),\n ),\n );\n\n // Also check string fields for date patterns (but not metadata patterns)\n const stringDateFields = stringFields.filter(\n (name) =>\n DATE_FIELD_PATTERNS.some((pattern) =>\n name.toLowerCase().includes(pattern),\n ) &&\n !METADATA_DATE_PATTERNS.some((pattern) =>\n name.toLowerCase().includes(pattern),\n ),\n );\n\n const primaryDateFields = [...chartDateFields, ...stringDateFields];\n\n // Determine chart type: if we have good date fields for charting, it's time-series\n // If we only have metadata dates (like createdAt) and name fields, it's categorical\n const isTimeSeries =\n primaryDateFields.length > 0 && orientation !== \"horizontal\";\n const isCategorical =\n nameFields.length > 0 &&\n (primaryDateFields.length === 0 || orientation === \"horizontal\");\n\n if (orientation === \"horizontal\" || isCategorical) {\n // Categorical: x is name/category field, y is numeric field\n const xField =\n nameFields[0] ||\n primaryDateFields[0] ||\n metadataDateFields[0] ||\n fieldNames[0];\n const yFields =\n numericFields.length > 0\n ? numericFields\n : fieldNames.filter((k) => k !== xField);\n return { xField, yFields, chartType: \"categorical\" };\n }\n\n // Time-series (default): x is date/time field, y is numeric field\n const xField =\n primaryDateFields[0] ||\n metadataDateFields[0] ||\n nameFields[0] ||\n fieldNames[0];\n const yFields =\n numericFields.length > 0\n ? numericFields\n : fieldNames.filter((k) => k !== xField);\n return {\n xField,\n yFields,\n chartType: isTimeSeries ? \"timeseries\" : \"categorical\",\n };\n }\n\n static async fetchArrow(\n url: string,\n headers?: Record<string, string>,\n ): Promise<Uint8Array> {\n try {\n const response = await fetch(url, {\n headers: { \"Content-Type\": \"application/octet-stream\", ...headers },\n });\n\n if (!response.ok) {\n throw new Error(`HTTP ${response.status}: ${response.statusText}`);\n }\n\n const buffer = await response.arrayBuffer();\n\n return new Uint8Array(buffer);\n } catch (error) {\n throw new Error(\n `Failed to fetch Arrow data: ${\n error instanceof Error ? error.message : \"Unknown error\"\n }`,\n );\n }\n }\n}\n\nexport interface DetectedFields {\n /** X field */\n xField: string;\n /** Y fields */\n yFields: string[];\n}\n\n// ============================================================================\n// Utility Functions\n// ============================================================================\n\n// Cached empty result to avoid allocations\nconst EMPTY_RESULT: {\n xData: (string | number)[];\n yDataMap: Record<string, (string | number)[]>;\n} = {\n xData: [],\n yDataMap: {},\n};\n\n/**\n * Extracts values from an Arrow Vector properly.\n * Uses get(i) to handle complex types like Decimal128 correctly.\n * toArray() doesn't work properly for Decimal types - it returns raw bytes.\n *\n * @param col - The Arrow column/vector\n * @param divisor - Pre-computed divisor for DECIMAL types (10^scale)\n */\nfunction extractColumnValues(\n col: { length: number; get: (i: number) => unknown } | null | undefined,\n divisor?: number,\n): (string | number)[] {\n if (!col) return [];\n\n // Pre-allocate array for better performance with large datasets\n const len = col.length;\n const result: (string | number)[] = new Array(len);\n\n for (let i = 0; i < len; i++) {\n const val = col.get(i);\n if (val === null || val === undefined) {\n result[i] = 0;\n } else if (typeof val === \"bigint\") {\n // Apply decimal scaling if needed\n const num = Number(val);\n result[i] = divisor !== undefined ? num / divisor : num;\n } else if (typeof val === \"number\") {\n // Apply decimal scaling if needed\n result[i] = divisor !== undefined ? val / divisor : val;\n } else if (typeof val === \"string\") {\n result[i] = val;\n } else {\n // For complex types (like Decimal), try to convert to number\n const num = Number(val);\n result[i] = divisor !== undefined ? num / divisor : num;\n }\n }\n return result;\n}\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"lazy-arrow.js","names":[
|
|
1
|
+
{"version":3,"file":"lazy-arrow.js","names":[],"sources":["../../../src/js/arrow/lazy-arrow.ts"],"sourcesContent":["/**\n * Lazy loader for Apache Arrow library.\n * The arrow library is substantial (~200KB+ gzipped), so we only load it\n * when Arrow format is actually used.\n *\n * This module caches the import promise to ensure the library is only\n * loaded once, even if multiple components request it simultaneously.\n */\n\nimport type { Table, Field } from \"apache-arrow\";\n\n// Re-export types for convenience (types don't add to bundle size)\nexport type { Table, Field };\n\n// ============================================================================\n// Lazy Module Loading\n// ============================================================================\n\n// Cache the import promise to avoid multiple loads\nlet arrowModulePromise: Promise<typeof import(\"apache-arrow\")> | null = null;\n\n/**\n * Lazily loads the Apache Arrow library.\n * Returns cached module if already loaded.\n *\n * @example\n * ```typescript\n * const arrow = await getArrowModule();\n * const table = arrow.tableFromIPC(buffer);\n * ```\n */\nexport async function getArrowModule(): Promise<typeof import(\"apache-arrow\")> {\n if (!arrowModulePromise) {\n arrowModulePromise = import(\"apache-arrow\");\n }\n return arrowModulePromise;\n}\n\n// ============================================================================\n// Cached Type ID Sets\n// ============================================================================\n\n// These are initialized lazily when Arrow is first loaded\nlet temporalTypeIds: Set<number> | null = null;\nlet numericTypeIds: Set<number> | null = null;\nlet stringTypeIds: Set<number> | null = null;\nlet decimalTypeId: number | null = null;\n\n/**\n * Initializes the type ID sets from the Arrow Type enum.\n * Call this after loading the Arrow module.\n */\nexport async function initializeTypeIdSets(): Promise<void> {\n if (temporalTypeIds !== null) return; // Already initialized\n\n const { Type } = await getArrowModule();\n\n temporalTypeIds = new Set([\n Type.Date,\n Type.DateDay,\n Type.DateMillisecond,\n Type.Timestamp,\n Type.TimestampSecond,\n Type.TimestampMillisecond,\n Type.TimestampMicrosecond,\n Type.TimestampNanosecond,\n Type.Time,\n Type.TimeSecond,\n Type.TimeMillisecond,\n Type.TimeMicrosecond,\n Type.TimeNanosecond,\n ]);\n\n numericTypeIds = new Set([\n Type.Int,\n Type.Int8,\n Type.Int16,\n Type.Int32,\n Type.Int64,\n Type.Uint8,\n Type.Uint16,\n Type.Uint32,\n Type.Uint64,\n Type.Float,\n Type.Float16,\n Type.Float32,\n Type.Float64,\n Type.Decimal,\n ]);\n\n stringTypeIds = new Set([Type.Utf8, Type.LargeUtf8]);\n\n decimalTypeId = Type.Decimal;\n}\n\n/**\n * Returns the cached type ID sets.\n * Throws if called before initializeTypeIdSets().\n * This is safe because you can't have a Table without first loading Arrow.\n */\nexport function getTypeIdSets(): {\n temporal: Set<number>;\n numeric: Set<number>;\n string: Set<number>;\n} {\n if (!temporalTypeIds || !numericTypeIds || !stringTypeIds) {\n throw new Error(\n \"Arrow type IDs not initialized. Call initializeTypeIdSets() first.\",\n );\n }\n return {\n temporal: temporalTypeIds,\n numeric: numericTypeIds,\n string: stringTypeIds,\n };\n}\n\n/**\n * Returns the Decimal type ID.\n * Throws if called before initializeTypeIdSets().\n */\nexport function getDecimalTypeId(): number {\n if (decimalTypeId === null) {\n throw new Error(\n \"Arrow type IDs not initialized. Call initializeTypeIdSets() first.\",\n );\n }\n return decimalTypeId;\n}\n"],"mappings":";AAmBA,IAAI,qBAAoE;;;;;;;;;;;AAYxE,eAAsB,iBAAyD;AAC7E,KAAI,CAAC,mBACH,sBAAqB,OAAO;AAE9B,QAAO;;AAQT,IAAI,kBAAsC;AAC1C,IAAI,iBAAqC;AACzC,IAAI,gBAAoC;AACxC,IAAI,gBAA+B;;;;;AAMnC,eAAsB,uBAAsC;AAC1D,KAAI,oBAAoB,KAAM;CAE9B,MAAM,EAAE,SAAS,MAAM,gBAAgB;AAEvC,mBAAkB,IAAI,IAAI;EACxB,KAAK;EACL,KAAK;EACL,KAAK;EACL,KAAK;EACL,KAAK;EACL,KAAK;EACL,KAAK;EACL,KAAK;EACL,KAAK;EACL,KAAK;EACL,KAAK;EACL,KAAK;EACL,KAAK;EACN,CAAC;AAEF,kBAAiB,IAAI,IAAI;EACvB,KAAK;EACL,KAAK;EACL,KAAK;EACL,KAAK;EACL,KAAK;EACL,KAAK;EACL,KAAK;EACL,KAAK;EACL,KAAK;EACL,KAAK;EACL,KAAK;EACL,KAAK;EACL,KAAK;EACL,KAAK;EACN,CAAC;AAEF,iBAAgB,IAAI,IAAI,CAAC,KAAK,MAAM,KAAK,UAAU,CAAC;AAEpD,iBAAgB,KAAK;;;;;;;AAQvB,SAAgB,gBAId;AACA,KAAI,CAAC,mBAAmB,CAAC,kBAAkB,CAAC,cAC1C,OAAM,IAAI,MACR,qEACD;AAEH,QAAO;EACL,UAAU;EACV,SAAS;EACT,QAAQ;EACT;;;;;;AAOH,SAAgB,mBAA2B;AACzC,KAAI,kBAAkB,KACpB,OAAM,IAAI,MACR,qEACD;AAEH,QAAO"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"connect-sse.js","names":[
|
|
1
|
+
{"version":3,"file":"connect-sse.js","names":[],"sources":["../../../src/js/sse/connect-sse.ts"],"sourcesContent":["import type { ConnectSSEOptions, SSEMessage } from \"./types\";\n\n/**\n * Connects to an SSE endpoint with automatic retries and exponential backoff\n * @param options - SSE connection options\n * @param attempt - Internal retry attempt counter\n * @returns Promise that resolves when the stream ends or retries stop\n */\nexport async function connectSSE<Payload = unknown>(\n options: ConnectSSEOptions<Payload>,\n attempt = 0,\n) {\n const {\n url,\n payload,\n onMessage,\n signal,\n lastEventId: initialLastEventId = null,\n retryDelay = 2000,\n maxRetries = 3,\n maxBufferSize = 1024 * 1024, // 1MB\n timeout = 300000, // 5 minutes\n onError,\n } = options;\n\n if (!url || url.trim().length <= 0) {\n throw new Error(\"connectSSE: 'url' must be a non-empty string.\");\n }\n\n if (retryDelay <= 0) {\n throw new Error(\"connectSSE: 'retryDelay' must be >= 0.\");\n }\n if (maxBufferSize <= 0) {\n throw new Error(\"connectSSE: 'maxBufferSize' must be > 0.\");\n }\n const headers: HeadersInit = {\n Accept: \"text/event-stream\",\n };\n\n const hasPayload = typeof payload !== \"undefined\";\n\n if (hasPayload) {\n headers[\"Content-Type\"] = \"application/json\";\n }\n\n let lastEventId = initialLastEventId;\n\n if (lastEventId) {\n headers[\"Last-Event-ID\"] = lastEventId;\n }\n\n const method = hasPayload ? \"POST\" : \"GET\";\n const body = hasPayload\n ? typeof payload === \"string\"\n ? payload\n : JSON.stringify(payload)\n : undefined;\n\n const timeoutController = new AbortController();\n const timeoutId = setTimeout(() => timeoutController.abort(), timeout);\n\n const combinedSignal = signal\n ? createCombinedSignal(signal, timeoutController.signal)\n : timeoutController.signal;\n\n try {\n const response = await fetch(url, {\n headers,\n method,\n body,\n signal: combinedSignal,\n });\n\n clearTimeout(timeoutId);\n\n if (!response.ok) {\n throw new Error(`HTTP ${response.status}`);\n }\n\n if (!response.body) {\n throw new Error(\"No response body\");\n }\n\n const reader = response.body.getReader();\n const decoder = new TextDecoder(\"utf-8\");\n\n let buffer = \"\";\n while (true) {\n const { done, value } = await reader.read();\n if (done) break;\n\n const decoded = decoder.decode(value, { stream: true });\n\n if (buffer.length + decoded.length > maxBufferSize) {\n throw new Error(\"Buffer size exceeded\");\n }\n buffer += decoded;\n\n const normalizedBuffer = buffer.replace(/\\r\\n/g, \"\\n\");\n const parts = normalizedBuffer.split(\"\\n\\n\");\n\n buffer = parts.pop() ?? \"\";\n for (const part of parts) {\n const message = parseSSEEvent(part);\n if (!message) continue;\n\n if (message.id) lastEventId = message.id;\n\n onMessage({\n id: lastEventId ?? \"\",\n data: message.data,\n });\n }\n }\n } catch (error) {\n clearTimeout(timeoutId);\n if (onError) onError(error);\n if (signal?.aborted) return;\n\n if (attempt >= maxRetries) {\n console.warn(\n `[connectSSE] Max retries (${maxRetries}) exceeded for ${url}`,\n );\n return;\n }\n\n const nextAttempt = attempt + 1;\n const delayMs = computeExponentialDelay(nextAttempt, retryDelay);\n\n if (delayMs <= 0) return;\n\n await new Promise<void>((resolve) => {\n setTimeout(() => {\n connectSSE({ ...options, lastEventId }, nextAttempt).finally(resolve);\n }, delayMs);\n });\n }\n}\n\n/**\n * Parses a raw SSE event chunk into a message\n * @param chunk - Raw SSE event block\n * @returns Parsed message or null when no data lines are present\n * @private\n */\nfunction parseSSEEvent(chunk: string): SSEMessage | null {\n const normalized = chunk.replace(/\\r\\n/g, \"\\n\");\n const lines = normalized.split(\"\\n\");\n\n let id: string | undefined;\n const dataLines: string[] = [];\n\n for (const rawLine of lines) {\n const line = rawLine.trimEnd();\n\n if (line === \"\") continue;\n if (line.startsWith(\":\")) continue;\n\n if (line.startsWith(\"id:\")) {\n id = line.slice(3).trimStart();\n } else if (line.startsWith(\"data:\")) {\n dataLines.push(line.slice(5).trimStart());\n }\n }\n\n if (dataLines.length === 0) return null;\n\n return {\n id: id ?? \"\",\n data: dataLines.join(\"\\n\"),\n };\n}\n\n/**\n * Compute exponential backoff delay in milliseconds.\n * Uses a random jitter factor to avoid thundering herd problem.\n * @param attempt - Retry attempt number (1-based)\n * @param baseDelayMs - Base delay in milliseconds\n * @returns Delay in milliseconds for this attempt\n */\nfunction computeExponentialDelay(attempt: number, baseDelayMs: number): number {\n const safeAttempt = Math.max(1, attempt);\n const multiplier = Math.min(2 ** (safeAttempt - 1), 32);\n const rawDelayMs = baseDelayMs * multiplier;\n\n const jitter = rawDelayMs * 0.2;\n const randomizedDelayMs = rawDelayMs + Math.random() * jitter;\n\n return Math.max(0, Math.floor(randomizedDelayMs));\n}\n\n/**\n * Combines two abort signals into a single abort signal\n * @param signal1 - First abort signal\n * @param signal2 - Second abort signal\n * @returns Combined signal\n */\nfunction createCombinedSignal(\n signal1: AbortSignal,\n signal2: AbortSignal,\n): AbortSignal {\n const controller = new AbortController();\n const abort = () => controller.abort();\n signal1.addEventListener(\"abort\", abort);\n signal2.addEventListener(\"abort\", abort);\n\n return controller.signal;\n}\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"base.js","names":["baseCtx: OptionBuilderContext","opt: Record<string, unknown>"],"sources":["../../../src/react/charts/base.tsx"],"sourcesContent":["import type { ECharts } from \"echarts\";\nimport ReactECharts from \"echarts-for-react\";\nimport { useCallback, useMemo, useRef } from \"react\";\nimport { normalizeChartData, normalizeHeatmapData } from \"./normalize\";\nimport {\n buildCartesianOption,\n buildHeatmapOption,\n buildHorizontalBarOption,\n buildPieOption,\n buildRadarOption,\n type OptionBuilderContext,\n} from \"./options\";\nimport { useThemeColors } from \"./theme\";\nimport type {\n ChartColorPalette,\n ChartData,\n ChartType,\n Orientation,\n} from \"./types\";\n\n// ============================================================================\n// Palette Selection\n// ============================================================================\n\n/**\n * Determines the appropriate color palette for a chart type.\n * - Heatmaps use sequential (low → high intensity)\n * - All other charts use categorical (distinct categories)\n */\nfunction getDefaultPalette(chartType: ChartType): ChartColorPalette {\n switch (chartType) {\n case \"heatmap\":\n return \"sequential\";\n default:\n return \"categorical\";\n }\n}\n\n// ============================================================================\n// Component Props\n// ============================================================================\n\nexport interface BaseChartProps {\n /** Chart data (Arrow Table or JSON array) - format is auto-detected */\n data: ChartData;\n /** Chart type */\n chartType: ChartType;\n /** X-axis field key (auto-detected from schema if not provided) */\n xKey?: string;\n /** Y-axis field key(s) (auto-detected from schema if not provided) */\n yKey?: string | string[];\n /** Chart orientation @default \"vertical\" */\n orientation?: Orientation;\n /** Chart height in pixels @default 300 */\n height?: number;\n /** Chart title */\n title?: string;\n /** Show legend @default true */\n showLegend?: boolean;\n /**\n * Color palette to use. Auto-selected based on chart type if not specified.\n * - \"categorical\": Distinct colors for different categories (bar, pie, line)\n * - \"sequential\": Gradient for magnitude (heatmap)\n * - \"diverging\": Two-tone for positive/negative (correlation)\n */\n colorPalette?: ChartColorPalette;\n /** Custom colors (overrides colorPalette) */\n colors?: string[];\n /** Show data point symbols (line/area charts) @default false */\n showSymbol?: boolean;\n /** Smooth line curves (line/area charts) @default true */\n smooth?: boolean;\n /** Stack series @default false */\n stacked?: boolean;\n /** Symbol size for scatter charts @default 8 */\n symbolSize?: number;\n /** Show area fill for radar charts @default true */\n showArea?: boolean;\n /** Inner radius for pie/donut (0-100) @default 0 */\n innerRadius?: number;\n /** Show labels on pie/donut slices @default true */\n showLabels?: boolean;\n /** Label position for pie/donut @default \"outside\" */\n labelPosition?: \"outside\" | \"inside\" | \"center\";\n /** Y-axis field key for heatmap (the row dimension) */\n yAxisKey?: string;\n /** Min value for heatmap color scale */\n min?: number;\n /** Max value for heatmap color scale */\n max?: number;\n /** Additional ECharts options to merge */\n options?: Record<string, unknown>;\n /** Additional CSS classes */\n className?: string;\n}\n\n// ============================================================================\n// Base Chart Component\n// ============================================================================\n\n/**\n * Base chart component that handles both Arrow and JSON data.\n * Renders using ECharts for consistent output across both formats.\n */\nexport function BaseChart({\n data,\n chartType,\n xKey,\n yKey,\n orientation,\n height = 300,\n title,\n showLegend = true,\n colorPalette,\n colors: customColors,\n showSymbol = false,\n smooth = true,\n stacked = false,\n symbolSize = 8,\n showArea = true,\n innerRadius = 0,\n showLabels = true,\n labelPosition = \"outside\",\n yAxisKey,\n min,\n max,\n options: customOptions,\n className,\n}: BaseChartProps) {\n // Determine the appropriate color palette based on chart type\n const resolvedPalette = colorPalette ?? getDefaultPalette(chartType);\n const themeColors = useThemeColors(resolvedPalette);\n const colors = customColors ?? themeColors;\n\n // Store ECharts instance directly to avoid stale ref issues on unmount\n const echartsInstanceRef = useRef<ECharts | null>(null);\n\n // Callback ref pattern: captures the ECharts instance when ReactECharts mounts\n // This ensures we always have a stable reference to the actual instance\n const chartRefCallback = useCallback((node: ReactECharts | null) => {\n // Dispose previous instance if component is being replaced\n if (\n echartsInstanceRef.current &&\n !echartsInstanceRef.current.isDisposed()\n ) {\n echartsInstanceRef.current.dispose();\n }\n\n // Store the new instance\n if (node) {\n echartsInstanceRef.current = node.getEchartsInstance();\n } else {\n // Component unmounting - dispose the stored instance\n if (\n echartsInstanceRef.current &&\n !echartsInstanceRef.current.isDisposed()\n ) {\n echartsInstanceRef.current.dispose();\n }\n echartsInstanceRef.current = null;\n }\n }, []);\n\n // Memoize data normalization\n const normalized = useMemo(\n () =>\n chartType === \"heatmap\"\n ? normalizeHeatmapData(data, xKey, yAxisKey, yKey)\n : normalizeChartData(data, xKey, yKey, orientation),\n [data, xKey, yKey, yAxisKey, orientation, chartType],\n );\n\n // Memoize option building\n const option = useMemo(() => {\n const { xData, yFields, chartType: detectedChartType } = normalized;\n\n if (xData.length === 0) return null;\n\n // Determine chart mode first (needed to handle yDataMap)\n const isHeatmap = chartType === \"heatmap\";\n\n // Heatmaps use heatmapData instead of yDataMap\n // For other charts, yDataMap is required\n const yDataMap = \"yDataMap\" in normalized ? normalized.yDataMap : {};\n\n const baseCtx: OptionBuilderContext = {\n xData,\n yDataMap,\n yFields,\n colors,\n title,\n showLegend,\n };\n const isPie = chartType === \"pie\" || chartType === \"donut\";\n const isRadar = chartType === \"radar\";\n const isHorizontal =\n !isPie &&\n !isRadar &&\n !isHeatmap &&\n (orientation === \"horizontal\" ||\n (detectedChartType === \"categorical\" &&\n !orientation &&\n chartType === \"bar\"));\n const isTimeSeries =\n detectedChartType === \"timeseries\" &&\n !isHorizontal &&\n !isRadar &&\n !isHeatmap;\n\n // Build option based on chart type\n let opt: Record<string, unknown>;\n\n if (isHeatmap && \"yAxisData\" in normalized && \"heatmapData\" in normalized) {\n const heatmapNorm = normalized as {\n yAxisData: (string | number)[];\n heatmapData: [number, number, number][];\n min: number;\n max: number;\n } & typeof normalized;\n opt = buildHeatmapOption({\n ...baseCtx,\n yAxisData: heatmapNorm.yAxisData,\n heatmapData: heatmapNorm.heatmapData,\n min: min ?? heatmapNorm.min,\n max: max ?? heatmapNorm.max,\n showLabels,\n });\n } else if (isRadar) {\n opt = buildRadarOption(baseCtx, showArea);\n } else if (isPie) {\n opt = buildPieOption(\n baseCtx,\n chartType as \"pie\" | \"donut\",\n innerRadius,\n showLabels,\n labelPosition,\n );\n } else if (isHorizontal) {\n opt = buildHorizontalBarOption(baseCtx, stacked);\n } else {\n opt = buildCartesianOption({\n ...baseCtx,\n chartType,\n isTimeSeries,\n stacked,\n smooth,\n showSymbol,\n symbolSize,\n });\n }\n\n // Merge custom options\n return customOptions ? { ...opt, ...customOptions } : opt;\n }, [\n normalized,\n colors,\n title,\n showLegend,\n chartType,\n orientation,\n innerRadius,\n showLabels,\n labelPosition,\n stacked,\n smooth,\n showSymbol,\n symbolSize,\n showArea,\n min,\n max,\n customOptions,\n ]);\n\n if (!option) {\n return (\n <div className=\"flex items-center justify-center h-full text-muted-foreground\">\n No data\n </div>\n );\n }\n\n return (\n <ReactECharts\n ref={chartRefCallback}\n option={option}\n style={{ height }}\n className={className}\n opts={{ renderer: \"canvas\" }}\n notMerge={false}\n lazyUpdate={true}\n />\n );\n}\n"],"mappings":";;;;;;;;;;;;;AA6BA,SAAS,kBAAkB,WAAyC;AAClE,SAAQ,WAAR;EACE,KAAK,UACH,QAAO;EACT,QACE,QAAO;;;;;;;AAsEb,SAAgB,UAAU,EACxB,MACA,WACA,MACA,MACA,aACA,SAAS,KACT,OACA,aAAa,MACb,cACA,QAAQ,cACR,aAAa,OACb,SAAS,MACT,UAAU,OACV,aAAa,GACb,WAAW,MACX,cAAc,GACd,aAAa,MACb,gBAAgB,WAChB,UACA,KACA,KACA,SAAS,eACT,aACiB;CAGjB,MAAM,cAAc,eADI,gBAAgB,kBAAkB,UAAU,CACjB;CACnD,MAAM,SAAS,gBAAgB;CAG/B,MAAM,qBAAqB,OAAuB,KAAK;CAIvD,MAAM,mBAAmB,aAAa,SAA8B;AAElE,MACE,mBAAmB,WACnB,CAAC,mBAAmB,QAAQ,YAAY,CAExC,oBAAmB,QAAQ,SAAS;AAItC,MAAI,KACF,oBAAmB,UAAU,KAAK,oBAAoB;OACjD;AAEL,OACE,mBAAmB,WACnB,CAAC,mBAAmB,QAAQ,YAAY,CAExC,oBAAmB,QAAQ,SAAS;AAEtC,sBAAmB,UAAU;;IAE9B,EAAE,CAAC;CAGN,MAAM,aAAa,cAEf,cAAc,YACV,qBAAqB,MAAM,MAAM,UAAU,KAAK,GAChD,mBAAmB,MAAM,MAAM,MAAM,YAAY,EACvD;EAAC;EAAM;EAAM;EAAM;EAAU;EAAa;EAAU,CACrD;CAGD,MAAM,SAAS,cAAc;EAC3B,MAAM,EAAE,OAAO,SAAS,WAAW,sBAAsB;AAEzD,MAAI,MAAM,WAAW,EAAG,QAAO;EAG/B,MAAM,YAAY,cAAc;EAMhC,MAAMA,UAAgC;GACpC;GACA,UAJe,cAAc,aAAa,WAAW,WAAW,EAAE;GAKlE;GACA;GACA;GACA;GACD;EACD,MAAM,QAAQ,cAAc,SAAS,cAAc;EACnD,MAAM,UAAU,cAAc;EAC9B,MAAM,eACJ,CAAC,SACD,CAAC,WACD,CAAC,cACA,gBAAgB,gBACd,sBAAsB,iBACrB,CAAC,eACD,cAAc;EACpB,MAAM,eACJ,sBAAsB,gBACtB,CAAC,gBACD,CAAC,WACD,CAAC;EAGH,IAAIC;AAEJ,MAAI,aAAa,eAAe,cAAc,iBAAiB,YAAY;GACzE,MAAM,cAAc;AAMpB,SAAM,mBAAmB;IACvB,GAAG;IACH,WAAW,YAAY;IACvB,aAAa,YAAY;IACzB,KAAK,OAAO,YAAY;IACxB,KAAK,OAAO,YAAY;IACxB;IACD,CAAC;aACO,QACT,OAAM,iBAAiB,SAAS,SAAS;WAChC,MACT,OAAM,eACJ,SACA,WACA,aACA,YACA,cACD;WACQ,aACT,OAAM,yBAAyB,SAAS,QAAQ;MAEhD,OAAM,qBAAqB;GACzB,GAAG;GACH;GACA;GACA;GACA;GACA;GACA;GACD,CAAC;AAIJ,SAAO,gBAAgB;GAAE,GAAG;GAAK,GAAG;GAAe,GAAG;IACrD;EACD;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACD,CAAC;AAEF,KAAI,CAAC,OACH,QACE,oBAAC;EAAI,WAAU;YAAgE;GAEzE;AAIV,QACE,oBAAC;EACC,KAAK;EACG;EACR,OAAO,EAAE,QAAQ;EACN;EACX,MAAM,EAAE,UAAU,UAAU;EAC5B,UAAU;EACV,YAAY;GACZ"}
|
|
1
|
+
{"version":3,"file":"base.js","names":[],"sources":["../../../src/react/charts/base.tsx"],"sourcesContent":["import type { ECharts } from \"echarts\";\nimport ReactECharts from \"echarts-for-react\";\nimport { useCallback, useMemo, useRef } from \"react\";\nimport { normalizeChartData, normalizeHeatmapData } from \"./normalize\";\nimport {\n buildCartesianOption,\n buildHeatmapOption,\n buildHorizontalBarOption,\n buildPieOption,\n buildRadarOption,\n type OptionBuilderContext,\n} from \"./options\";\nimport { useThemeColors } from \"./theme\";\nimport type {\n ChartColorPalette,\n ChartData,\n ChartType,\n Orientation,\n} from \"./types\";\n\n// ============================================================================\n// Palette Selection\n// ============================================================================\n\n/**\n * Determines the appropriate color palette for a chart type.\n * - Heatmaps use sequential (low → high intensity)\n * - All other charts use categorical (distinct categories)\n */\nfunction getDefaultPalette(chartType: ChartType): ChartColorPalette {\n switch (chartType) {\n case \"heatmap\":\n return \"sequential\";\n default:\n return \"categorical\";\n }\n}\n\n// ============================================================================\n// Component Props\n// ============================================================================\n\nexport interface BaseChartProps {\n /** Chart data (Arrow Table or JSON array) - format is auto-detected */\n data: ChartData;\n /** Chart type */\n chartType: ChartType;\n /** X-axis field key (auto-detected from schema if not provided) */\n xKey?: string;\n /** Y-axis field key(s) (auto-detected from schema if not provided) */\n yKey?: string | string[];\n /** Chart orientation @default \"vertical\" */\n orientation?: Orientation;\n /** Chart height in pixels @default 300 */\n height?: number;\n /** Chart title */\n title?: string;\n /** Show legend @default true */\n showLegend?: boolean;\n /**\n * Color palette to use. Auto-selected based on chart type if not specified.\n * - \"categorical\": Distinct colors for different categories (bar, pie, line)\n * - \"sequential\": Gradient for magnitude (heatmap)\n * - \"diverging\": Two-tone for positive/negative (correlation)\n */\n colorPalette?: ChartColorPalette;\n /** Custom colors (overrides colorPalette) */\n colors?: string[];\n /** Show data point symbols (line/area charts) @default false */\n showSymbol?: boolean;\n /** Smooth line curves (line/area charts) @default true */\n smooth?: boolean;\n /** Stack series @default false */\n stacked?: boolean;\n /** Symbol size for scatter charts @default 8 */\n symbolSize?: number;\n /** Show area fill for radar charts @default true */\n showArea?: boolean;\n /** Inner radius for pie/donut (0-100) @default 0 */\n innerRadius?: number;\n /** Show labels on pie/donut slices @default true */\n showLabels?: boolean;\n /** Label position for pie/donut @default \"outside\" */\n labelPosition?: \"outside\" | \"inside\" | \"center\";\n /** Y-axis field key for heatmap (the row dimension) */\n yAxisKey?: string;\n /** Min value for heatmap color scale */\n min?: number;\n /** Max value for heatmap color scale */\n max?: number;\n /** Additional ECharts options to merge */\n options?: Record<string, unknown>;\n /** Additional CSS classes */\n className?: string;\n}\n\n// ============================================================================\n// Base Chart Component\n// ============================================================================\n\n/**\n * Base chart component that handles both Arrow and JSON data.\n * Renders using ECharts for consistent output across both formats.\n */\nexport function BaseChart({\n data,\n chartType,\n xKey,\n yKey,\n orientation,\n height = 300,\n title,\n showLegend = true,\n colorPalette,\n colors: customColors,\n showSymbol = false,\n smooth = true,\n stacked = false,\n symbolSize = 8,\n showArea = true,\n innerRadius = 0,\n showLabels = true,\n labelPosition = \"outside\",\n yAxisKey,\n min,\n max,\n options: customOptions,\n className,\n}: BaseChartProps) {\n // Determine the appropriate color palette based on chart type\n const resolvedPalette = colorPalette ?? getDefaultPalette(chartType);\n const themeColors = useThemeColors(resolvedPalette);\n const colors = customColors ?? themeColors;\n\n // Store ECharts instance directly to avoid stale ref issues on unmount\n const echartsInstanceRef = useRef<ECharts | null>(null);\n\n // Callback ref pattern: captures the ECharts instance when ReactECharts mounts\n // This ensures we always have a stable reference to the actual instance\n const chartRefCallback = useCallback((node: ReactECharts | null) => {\n // Dispose previous instance if component is being replaced\n if (\n echartsInstanceRef.current &&\n !echartsInstanceRef.current.isDisposed()\n ) {\n echartsInstanceRef.current.dispose();\n }\n\n // Store the new instance\n if (node) {\n echartsInstanceRef.current = node.getEchartsInstance();\n } else {\n // Component unmounting - dispose the stored instance\n if (\n echartsInstanceRef.current &&\n !echartsInstanceRef.current.isDisposed()\n ) {\n echartsInstanceRef.current.dispose();\n }\n echartsInstanceRef.current = null;\n }\n }, []);\n\n // Memoize data normalization\n const normalized = useMemo(\n () =>\n chartType === \"heatmap\"\n ? normalizeHeatmapData(data, xKey, yAxisKey, yKey)\n : normalizeChartData(data, xKey, yKey, orientation),\n [data, xKey, yKey, yAxisKey, orientation, chartType],\n );\n\n // Memoize option building\n const option = useMemo(() => {\n const { xData, yFields, chartType: detectedChartType } = normalized;\n\n if (xData.length === 0) return null;\n\n // Determine chart mode first (needed to handle yDataMap)\n const isHeatmap = chartType === \"heatmap\";\n\n // Heatmaps use heatmapData instead of yDataMap\n // For other charts, yDataMap is required\n const yDataMap = \"yDataMap\" in normalized ? normalized.yDataMap : {};\n\n const baseCtx: OptionBuilderContext = {\n xData,\n yDataMap,\n yFields,\n colors,\n title,\n showLegend,\n };\n const isPie = chartType === \"pie\" || chartType === \"donut\";\n const isRadar = chartType === \"radar\";\n const isHorizontal =\n !isPie &&\n !isRadar &&\n !isHeatmap &&\n (orientation === \"horizontal\" ||\n (detectedChartType === \"categorical\" &&\n !orientation &&\n chartType === \"bar\"));\n const isTimeSeries =\n detectedChartType === \"timeseries\" &&\n !isHorizontal &&\n !isRadar &&\n !isHeatmap;\n\n // Build option based on chart type\n let opt: Record<string, unknown>;\n\n if (isHeatmap && \"yAxisData\" in normalized && \"heatmapData\" in normalized) {\n const heatmapNorm = normalized as {\n yAxisData: (string | number)[];\n heatmapData: [number, number, number][];\n min: number;\n max: number;\n } & typeof normalized;\n opt = buildHeatmapOption({\n ...baseCtx,\n yAxisData: heatmapNorm.yAxisData,\n heatmapData: heatmapNorm.heatmapData,\n min: min ?? heatmapNorm.min,\n max: max ?? heatmapNorm.max,\n showLabels,\n });\n } else if (isRadar) {\n opt = buildRadarOption(baseCtx, showArea);\n } else if (isPie) {\n opt = buildPieOption(\n baseCtx,\n chartType as \"pie\" | \"donut\",\n innerRadius,\n showLabels,\n labelPosition,\n );\n } else if (isHorizontal) {\n opt = buildHorizontalBarOption(baseCtx, stacked);\n } else {\n opt = buildCartesianOption({\n ...baseCtx,\n chartType,\n isTimeSeries,\n stacked,\n smooth,\n showSymbol,\n symbolSize,\n });\n }\n\n // Merge custom options\n return customOptions ? { ...opt, ...customOptions } : opt;\n }, [\n normalized,\n colors,\n title,\n showLegend,\n chartType,\n orientation,\n innerRadius,\n showLabels,\n labelPosition,\n stacked,\n smooth,\n showSymbol,\n symbolSize,\n showArea,\n min,\n max,\n customOptions,\n ]);\n\n if (!option) {\n return (\n <div className=\"flex items-center justify-center h-full text-muted-foreground\">\n No data\n </div>\n );\n }\n\n return (\n <ReactECharts\n ref={chartRefCallback}\n option={option}\n style={{ height }}\n className={className}\n opts={{ renderer: \"canvas\" }}\n notMerge={false}\n lazyUpdate={true}\n />\n );\n}\n"],"mappings":";;;;;;;;;;;;;AA6BA,SAAS,kBAAkB,WAAyC;AAClE,SAAQ,WAAR;EACE,KAAK,UACH,QAAO;EACT,QACE,QAAO;;;;;;;AAsEb,SAAgB,UAAU,EACxB,MACA,WACA,MACA,MACA,aACA,SAAS,KACT,OACA,aAAa,MACb,cACA,QAAQ,cACR,aAAa,OACb,SAAS,MACT,UAAU,OACV,aAAa,GACb,WAAW,MACX,cAAc,GACd,aAAa,MACb,gBAAgB,WAChB,UACA,KACA,KACA,SAAS,eACT,aACiB;CAGjB,MAAM,cAAc,eADI,gBAAgB,kBAAkB,UAAU,CACjB;CACnD,MAAM,SAAS,gBAAgB;CAG/B,MAAM,qBAAqB,OAAuB,KAAK;CAIvD,MAAM,mBAAmB,aAAa,SAA8B;AAElE,MACE,mBAAmB,WACnB,CAAC,mBAAmB,QAAQ,YAAY,CAExC,oBAAmB,QAAQ,SAAS;AAItC,MAAI,KACF,oBAAmB,UAAU,KAAK,oBAAoB;OACjD;AAEL,OACE,mBAAmB,WACnB,CAAC,mBAAmB,QAAQ,YAAY,CAExC,oBAAmB,QAAQ,SAAS;AAEtC,sBAAmB,UAAU;;IAE9B,EAAE,CAAC;CAGN,MAAM,aAAa,cAEf,cAAc,YACV,qBAAqB,MAAM,MAAM,UAAU,KAAK,GAChD,mBAAmB,MAAM,MAAM,MAAM,YAAY,EACvD;EAAC;EAAM;EAAM;EAAM;EAAU;EAAa;EAAU,CACrD;CAGD,MAAM,SAAS,cAAc;EAC3B,MAAM,EAAE,OAAO,SAAS,WAAW,sBAAsB;AAEzD,MAAI,MAAM,WAAW,EAAG,QAAO;EAG/B,MAAM,YAAY,cAAc;EAMhC,MAAM,UAAgC;GACpC;GACA,UAJe,cAAc,aAAa,WAAW,WAAW,EAAE;GAKlE;GACA;GACA;GACA;GACD;EACD,MAAM,QAAQ,cAAc,SAAS,cAAc;EACnD,MAAM,UAAU,cAAc;EAC9B,MAAM,eACJ,CAAC,SACD,CAAC,WACD,CAAC,cACA,gBAAgB,gBACd,sBAAsB,iBACrB,CAAC,eACD,cAAc;EACpB,MAAM,eACJ,sBAAsB,gBACtB,CAAC,gBACD,CAAC,WACD,CAAC;EAGH,IAAI;AAEJ,MAAI,aAAa,eAAe,cAAc,iBAAiB,YAAY;GACzE,MAAM,cAAc;AAMpB,SAAM,mBAAmB;IACvB,GAAG;IACH,WAAW,YAAY;IACvB,aAAa,YAAY;IACzB,KAAK,OAAO,YAAY;IACxB,KAAK,OAAO,YAAY;IACxB;IACD,CAAC;aACO,QACT,OAAM,iBAAiB,SAAS,SAAS;WAChC,MACT,OAAM,eACJ,SACA,WACA,aACA,YACA,cACD;WACQ,aACT,OAAM,yBAAyB,SAAS,QAAQ;MAEhD,OAAM,qBAAqB;GACzB,GAAG;GACH;GACA;GACA;GACA;GACA;GACA;GACD,CAAC;AAIJ,SAAO,gBAAgB;GAAE,GAAG;GAAK,GAAG;GAAe,GAAG;IACrD;EACD;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACD,CAAC;AAEF,KAAI,CAAC,OACH,QACE,oBAAC;EAAI,WAAU;YAAgE;GAEzE;AAIV,QACE,oBAAC;EACC,KAAK;EACG;EACR,OAAO,EAAE,QAAQ;EACN;EACX,MAAM,EAAE,UAAU,UAAU;EAC5B,UAAU;EACV,YAAY;GACZ"}
|
|
@@ -19,7 +19,7 @@ import { jsx } from "react/jsx-runtime";
|
|
|
19
19
|
*/
|
|
20
20
|
function createChart(chartType, displayName) {
|
|
21
21
|
const Component = (props) => {
|
|
22
|
-
const { queryKey, parameters, format, transformer, data, height = 300, className, ariaLabel, testId, ...chartProps } = props;
|
|
22
|
+
const { queryKey, parameters, format, transformer, asUser, data, height = 300, className, ariaLabel, testId, ...chartProps } = props;
|
|
23
23
|
return /* @__PURE__ */ jsx(ChartWrapper, {
|
|
24
24
|
...data !== void 0 ? {
|
|
25
25
|
data,
|
|
@@ -32,6 +32,7 @@ function createChart(chartType, displayName) {
|
|
|
32
32
|
parameters,
|
|
33
33
|
format,
|
|
34
34
|
transformer,
|
|
35
|
+
asUser,
|
|
35
36
|
height,
|
|
36
37
|
className,
|
|
37
38
|
ariaLabel,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"create-chart.js","names":[],"sources":["../../../src/react/charts/create-chart.tsx"],"sourcesContent":["import { BaseChart } from \"./base\";\nimport type { ChartType, UnifiedChartProps } from \"./types\";\nimport { ChartWrapper } from \"./wrapper\";\n\n/**\n * Factory function to create chart components.\n * Eliminates boilerplate by generating components with the same pattern.\n *\n * @param chartType - The ECharts chart type\n * @param displayName - Component display name for React DevTools\n * @returns A typed chart component\n *\n * @example\n * ```tsx\n * export const BarChart = createChart<BarChartProps>(\"bar\", \"BarChart\");\n * export const LineChart = createChart<LineChartProps>(\"line\", \"LineChart\");\n * ```\n */\nexport function createChart<TProps extends UnifiedChartProps>(\n chartType: ChartType,\n displayName: string,\n) {\n const Component = (props: TProps) => {\n const {\n // Query props\n queryKey,\n parameters,\n format,\n transformer,\n // Data props\n data,\n // Common props\n height = 300,\n className,\n ariaLabel,\n testId,\n // All remaining props pass through to BaseChart\n ...chartProps\n } = props as TProps & {\n queryKey?: string;\n parameters?: Record<string, unknown>;\n format?: string;\n transformer?: unknown;\n data?: unknown;\n height?: number;\n className?: string;\n ariaLabel?: string;\n testId?: string;\n };\n\n const wrapperProps =\n data !== undefined\n ? { data, height, className, ariaLabel, testId }\n : {\n queryKey: queryKey as string,\n parameters,\n format,\n transformer,\n height,\n className,\n ariaLabel,\n testId: testId ?? `${chartType}-chart-${queryKey}`,\n };\n\n return (\n <ChartWrapper {...wrapperProps}>\n {(chartData) => (\n <BaseChart\n data={chartData}\n chartType={chartType}\n height={height}\n className={className}\n {...chartProps}\n />\n )}\n </ChartWrapper>\n );\n };\n\n Component.displayName = displayName;\n return Component;\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;AAkBA,SAAgB,YACd,WACA,aACA;CACA,MAAM,aAAa,UAAkB;EACnC,MAAM,EAEJ,UACA,YACA,QACA,
|
|
1
|
+
{"version":3,"file":"create-chart.js","names":[],"sources":["../../../src/react/charts/create-chart.tsx"],"sourcesContent":["import { BaseChart } from \"./base\";\nimport type { ChartType, UnifiedChartProps } from \"./types\";\nimport { ChartWrapper } from \"./wrapper\";\n\n/**\n * Factory function to create chart components.\n * Eliminates boilerplate by generating components with the same pattern.\n *\n * @param chartType - The ECharts chart type\n * @param displayName - Component display name for React DevTools\n * @returns A typed chart component\n *\n * @example\n * ```tsx\n * export const BarChart = createChart<BarChartProps>(\"bar\", \"BarChart\");\n * export const LineChart = createChart<LineChartProps>(\"line\", \"LineChart\");\n * ```\n */\nexport function createChart<TProps extends UnifiedChartProps>(\n chartType: ChartType,\n displayName: string,\n) {\n const Component = (props: TProps) => {\n const {\n // Query props\n queryKey,\n parameters,\n format,\n transformer,\n asUser,\n // Data props\n data,\n // Common props\n height = 300,\n className,\n ariaLabel,\n testId,\n // All remaining props pass through to BaseChart\n ...chartProps\n } = props as TProps & {\n queryKey?: string;\n parameters?: Record<string, unknown>;\n format?: string;\n transformer?: unknown;\n asUser?: boolean;\n data?: unknown;\n height?: number;\n className?: string;\n ariaLabel?: string;\n testId?: string;\n };\n\n const wrapperProps =\n data !== undefined\n ? { data, height, className, ariaLabel, testId }\n : {\n queryKey: queryKey as string,\n parameters,\n format,\n transformer,\n asUser,\n height,\n className,\n ariaLabel,\n testId: testId ?? `${chartType}-chart-${queryKey}`,\n };\n\n return (\n <ChartWrapper {...wrapperProps}>\n {(chartData) => (\n <BaseChart\n data={chartData}\n chartType={chartType}\n height={height}\n className={className}\n {...chartProps}\n />\n )}\n </ChartWrapper>\n );\n };\n\n Component.displayName = displayName;\n return Component;\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;AAkBA,SAAgB,YACd,WACA,aACA;CACA,MAAM,aAAa,UAAkB;EACnC,MAAM,EAEJ,UACA,YACA,QACA,aACA,QAEA,MAEA,SAAS,KACT,WACA,WACA,QAEA,GAAG,eACD;AA4BJ,SACE,oBAAC;GAAa,GAfd,SAAS,SACL;IAAE;IAAM;IAAQ;IAAW;IAAW;IAAQ,GAC9C;IACY;IACV;IACA;IACA;IACA;IACA;IACA;IACA;IACA,QAAQ,UAAU,GAAG,UAAU,SAAS;IACzC;cAID,cACA,oBAAC;IACC,MAAM;IACK;IACH;IACG;IACX,GAAI;KACJ;IAES;;AAInB,WAAU,cAAc;AACxB,QAAO"}
|
|
@@ -24,7 +24,7 @@ interface NormalizedHeatmapData extends NormalizedChartDataBase {
|
|
|
24
24
|
}
|
|
25
25
|
/**
|
|
26
26
|
* Normalizes data specifically for heatmap charts.
|
|
27
|
-
* Expects data in format: { xKey: string, yAxisKey: string, valueKey: number }
|
|
27
|
+
* Expects data in format: `{ xKey: string, yAxisKey: string, valueKey: number }`
|
|
28
28
|
*
|
|
29
29
|
* @param data - Raw data (Arrow Table or JSON array)
|
|
30
30
|
* @param xKey - Field key for X-axis (columns)
|
|
@@ -161,7 +161,7 @@ function normalizeChartData(data, xKey, yKey, orientation) {
|
|
|
161
161
|
}
|
|
162
162
|
/**
|
|
163
163
|
* Normalizes data specifically for heatmap charts.
|
|
164
|
-
* Expects data in format: { xKey: string, yAxisKey: string, valueKey: number }
|
|
164
|
+
* Expects data in format: `{ xKey: string, yAxisKey: string, valueKey: number }`
|
|
165
165
|
*
|
|
166
166
|
* @param data - Raw data (Arrow Table or JSON array)
|
|
167
167
|
* @param xKey - Field key for X-axis (columns)
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"normalize.js","names":["xField","xData: (string | number)[]","yDataMap: Record<string, (string | number)[]>","detected","resolvedXKey","resolvedYKeys","rawXData","rawYDataMap","xData","yDataMap","heatmapData: [number, number, number][]","result: Record<string, unknown>[]","row: Record<string, unknown>"],"sources":["../../../src/react/charts/normalize.ts"],"sourcesContent":["import { ArrowClient } from \"@/js\";\nimport type { Table } from \"apache-arrow\";\nimport { DATE_FIELD_PATTERNS, NAME_FIELD_PATTERNS } from \"./constants\";\nimport type {\n ChartData,\n NormalizedChartData,\n NormalizedChartDataBase,\n Orientation,\n} from \"./types\";\nimport { isArrowTable } from \"./types\";\nimport { sortTimeSeriesAscending, toChartArray } from \"./utils\";\n\n// ============================================================================\n// Type Detection Helpers\n// ============================================================================\n\n/**\n * Checks if a value looks like an ISO date string\n */\nfunction isDateString(value: unknown): boolean {\n if (typeof value !== \"string\") return false;\n return /^\\d{4}-\\d{2}-\\d{2}(T|$)/.test(value);\n}\n\n/**\n * Checks if a value is numeric (number or numeric string)\n */\nfunction isNumericValue(value: unknown): boolean {\n if (typeof value === \"number\") return true;\n if (typeof value === \"bigint\") return true;\n if (typeof value === \"string\") {\n const trimmed = value.trim();\n if (trimmed === \"\" || isDateString(trimmed)) return false;\n const parsed = Number(trimmed);\n return !Number.isNaN(parsed) && Number.isFinite(parsed);\n }\n return false;\n}\n\n/**\n * Checks if a value looks like a category/label (non-numeric string)\n */\nfunction isCategoryValue(value: unknown): boolean {\n if (typeof value !== \"string\") return false;\n const trimmed = value.trim();\n if (trimmed === \"\") return false;\n if (/^\\d{4}-\\d{2}-\\d{2}/.test(trimmed)) return false;\n const parsed = Number(trimmed);\n return Number.isNaN(parsed) || !Number.isFinite(parsed);\n}\n\n// ============================================================================\n// Field Detection\n// ============================================================================\n\n/**\n * Detects fields from JSON data for charting\n */\nfunction detectFieldsFromJson(\n data: Record<string, unknown>[],\n orientation?: Orientation,\n): {\n xField: string;\n yFields: string[];\n chartType: \"timeseries\" | \"categorical\";\n} {\n if (!data || data.length === 0) {\n return { xField: \"x\", yFields: [\"y\"], chartType: \"categorical\" };\n }\n\n const firstRow = data[0];\n const keys = Object.keys(firstRow);\n\n // Detect date fields by key name OR by value being a date string\n const dateFields = keys.filter((key) => {\n const value = firstRow[key];\n const keyMatchesDatePattern = DATE_FIELD_PATTERNS.some((p) =>\n key.toLowerCase().includes(p),\n );\n const valueIsDateString = isDateString(value);\n return keyMatchesDatePattern || valueIsDateString;\n });\n\n // Detect name/category fields by pattern AND value type\n let nameFields = keys.filter((key) => {\n const value = firstRow[key];\n return (\n isCategoryValue(value) &&\n !isDateString(value) &&\n NAME_FIELD_PATTERNS.some((p) => key.toLowerCase().includes(p))\n );\n });\n\n // Fallback: any string field that isn't a date or ID\n if (nameFields.length === 0) {\n nameFields = keys.filter((key) => {\n const value = firstRow[key];\n return (\n isCategoryValue(value) &&\n !isDateString(value) &&\n !dateFields.includes(key) &&\n !key.toLowerCase().endsWith(\"_id\")\n );\n });\n }\n\n // Detect numeric fields\n const numericFields = keys.filter((key) => {\n const value = firstRow[key];\n return isNumericValue(value) && !dateFields.includes(key);\n });\n\n const isHorizontal = orientation === \"horizontal\";\n\n if (isHorizontal || (nameFields.length > 0 && dateFields.length === 0)) {\n const xField = nameFields[0] || dateFields[0] || keys[0];\n const yFields =\n numericFields.length > 0\n ? numericFields\n : keys.filter((k) => k !== xField);\n return { xField, yFields, chartType: \"categorical\" };\n }\n\n const xField = dateFields[0] || nameFields[0] || keys[0];\n const yFields =\n numericFields.length > 0 ? numericFields : keys.filter((k) => k !== xField);\n return {\n xField,\n yFields,\n chartType: dateFields.length > 0 ? \"timeseries\" : \"categorical\",\n };\n}\n\n// ============================================================================\n// Value Conversion\n// ============================================================================\n\n/**\n * Converts a JSON value to a chart-compatible value.\n */\nfunction jsonValueToChartValue(\n value: unknown,\n isYValue: boolean,\n isDateField: boolean,\n): string | number {\n if (value === null || value === undefined) {\n return isYValue ? 0 : \"\";\n }\n if (typeof value === \"number\") {\n return value;\n }\n if (typeof value === \"bigint\") {\n return Number(value);\n }\n if (typeof value === \"string\") {\n if (isDateField && isDateString(value)) {\n const timestamp = new Date(value).getTime();\n if (!Number.isNaN(timestamp)) {\n return timestamp;\n }\n }\n if (isYValue) {\n const trimmed = value.trim();\n const parsed = Number(trimmed);\n if (!Number.isNaN(parsed) && Number.isFinite(parsed)) {\n return parsed;\n }\n }\n return value;\n }\n return String(value);\n}\n\n// ============================================================================\n// Data Extraction\n// ============================================================================\n\n/**\n * Extracts chart data from JSON array\n */\nfunction extractFromJson(\n data: Record<string, unknown>[],\n xField: string,\n yFields: string[],\n): {\n xData: (string | number)[];\n yDataMap: Record<string, (string | number)[]>;\n} {\n const xData: (string | number)[] = [];\n const yDataMap: Record<string, (string | number)[]> = {};\n\n for (const field of yFields) {\n yDataMap[field] = [];\n }\n\n const xIsDateField = data.length > 0 && isDateString(data[0][xField]);\n\n for (const row of data) {\n xData.push(jsonValueToChartValue(row[xField], false, xIsDateField));\n for (const field of yFields) {\n yDataMap[field].push(jsonValueToChartValue(row[field], true, false));\n }\n }\n\n return { xData, yDataMap };\n}\n\n// ============================================================================\n// Main Normalization Function\n// ============================================================================\n\n/**\n * Normalizes chart data from either Arrow or JSON format.\n * Converts BigInt and Date values to chart-compatible types.\n */\nexport function normalizeChartData(\n data: ChartData,\n xKey?: string,\n yKey?: string | string[],\n orientation?: Orientation,\n): NormalizedChartData {\n if (isArrowTable(data)) {\n const table = data as Table;\n const detected = ArrowClient.detectFieldsFromArrow(table, orientation);\n const resolvedXKey = xKey ?? detected.xField;\n const resolvedYKeys = yKey\n ? Array.isArray(yKey)\n ? yKey\n : [yKey]\n : detected.yFields;\n\n const { xData: rawXData, yDataMap: rawYDataMap } =\n ArrowClient.extractChartData(table, resolvedXKey, resolvedYKeys);\n\n let xData = toChartArray(rawXData);\n let yDataMap: Record<string, (string | number)[]> = {};\n for (const key of resolvedYKeys) {\n yDataMap[key] = toChartArray(rawYDataMap[key] ?? []);\n }\n\n if (detected.chartType === \"timeseries\") {\n ({ xData, yDataMap } = sortTimeSeriesAscending(\n xData,\n yDataMap,\n resolvedYKeys,\n ));\n }\n\n return {\n xData,\n yDataMap,\n xField: resolvedXKey,\n yFields: resolvedYKeys,\n chartType: detected.chartType,\n };\n }\n\n // JSON Array\n const jsonData = data as Record<string, unknown>[];\n const detected = detectFieldsFromJson(jsonData, orientation);\n const resolvedXKey = xKey ?? detected.xField;\n const resolvedYKeys = yKey\n ? Array.isArray(yKey)\n ? yKey\n : [yKey]\n : detected.yFields;\n\n const { xData: rawXData, yDataMap: rawYDataMap } = extractFromJson(\n jsonData,\n resolvedXKey,\n resolvedYKeys,\n );\n\n let xData = toChartArray(rawXData);\n let yDataMap: Record<string, (string | number)[]> = {};\n for (const key of resolvedYKeys) {\n yDataMap[key] = toChartArray(rawYDataMap[key] ?? []);\n }\n\n if (detected.chartType === \"timeseries\") {\n ({ xData, yDataMap } = sortTimeSeriesAscending(\n xData,\n yDataMap,\n resolvedYKeys,\n ));\n }\n\n return {\n xData,\n yDataMap,\n xField: resolvedXKey,\n yFields: resolvedYKeys,\n chartType: detected.chartType,\n };\n}\n\n// ============================================================================\n// Heatmap Data Normalization\n// ============================================================================\n\n/**\n * Normalized data for heatmap charts.\n * Extends base (not NormalizedChartData) because heatmaps don't use yDataMap.\n * Instead, they use heatmapData which contains [xIndex, yIndex, value] tuples.\n */\nexport interface NormalizedHeatmapData extends NormalizedChartDataBase {\n /** Y-axis categories (rows) */\n yAxisData: (string | number)[];\n /** Heatmap data as [xIndex, yIndex, value] tuples */\n heatmapData: [number, number, number][];\n /** Min value in the data */\n min: number;\n /** Max value in the data */\n max: number;\n}\n\n/**\n * Normalizes data specifically for heatmap charts.\n * Expects data in format: { xKey: string, yAxisKey: string, valueKey: number }\n *\n * @param data - Raw data (Arrow Table or JSON array)\n * @param xKey - Field key for X-axis (columns)\n * @param yAxisKey - Field key for Y-axis (rows)\n * @param valueKey - Field key for the cell values\n */\nexport function normalizeHeatmapData(\n data: ChartData,\n xKey?: string,\n yAxisKey?: string,\n valueKey?: string | string[],\n): NormalizedHeatmapData {\n // First, get the standard normalization\n const jsonData = isArrowTable(data)\n ? extractJsonFromArrow(data)\n : (data as Record<string, unknown>[]);\n\n if (jsonData.length === 0) {\n return {\n xData: [],\n xField: xKey ?? \"x\",\n yFields: [],\n chartType: \"categorical\",\n yAxisData: [],\n heatmapData: [],\n min: 0,\n max: 0,\n };\n }\n\n // Detect fields if not provided\n const keys = Object.keys(jsonData[0]);\n const resolvedXKey = xKey ?? keys[0];\n const resolvedYAxisKey = yAxisKey ?? keys[1];\n const resolvedValueKey = valueKey\n ? Array.isArray(valueKey)\n ? valueKey[0]\n : valueKey\n : keys[2];\n\n // Extract unique X and Y categories\n const xSet = new Set<string | number>();\n const ySet = new Set<string | number>();\n\n for (const row of jsonData) {\n const xVal = jsonValueToChartValue(row[resolvedXKey], false, false);\n const yVal = jsonValueToChartValue(row[resolvedYAxisKey], false, false);\n xSet.add(xVal);\n ySet.add(yVal);\n }\n\n const xData = Array.from(xSet);\n const yAxisData = Array.from(ySet);\n\n // Create index maps for fast lookup\n const xIndexMap = new Map<string | number, number>();\n const yIndexMap = new Map<string | number, number>();\n xData.forEach((v, i) => {\n xIndexMap.set(v, i);\n });\n yAxisData.forEach((v, i) => {\n yIndexMap.set(v, i);\n });\n\n // Build heatmap data and track min/max\n const heatmapData: [number, number, number][] = [];\n let min = Number.POSITIVE_INFINITY;\n let max = Number.NEGATIVE_INFINITY;\n\n for (const row of jsonData) {\n const xVal = jsonValueToChartValue(row[resolvedXKey], false, false);\n const yVal = jsonValueToChartValue(row[resolvedYAxisKey], false, false);\n const value = jsonValueToChartValue(row[resolvedValueKey], true, false);\n\n const xIdx = xIndexMap.get(xVal);\n const yIdx = yIndexMap.get(yVal);\n const numValue = typeof value === \"number\" ? value : 0;\n\n if (xIdx !== undefined && yIdx !== undefined) {\n heatmapData.push([xIdx, yIdx, numValue]);\n min = Math.min(min, numValue);\n max = Math.max(max, numValue);\n }\n }\n\n // Handle edge case where no valid data was found\n if (heatmapData.length === 0) {\n min = 0;\n max = 0;\n }\n\n return {\n xData,\n xField: resolvedXKey,\n yFields: [resolvedValueKey],\n chartType: \"categorical\",\n yAxisData,\n heatmapData,\n min,\n max,\n };\n}\n\n/**\n * Helper to extract JSON array from Arrow table for heatmap processing.\n */\nfunction extractJsonFromArrow(table: Table): Record<string, unknown>[] {\n const result: Record<string, unknown>[] = [];\n const fields = table.schema.fields.map((f) => f.name);\n\n for (let i = 0; i < table.numRows; i++) {\n const row: Record<string, unknown> = {};\n for (const field of fields) {\n const col = table.getChild(field);\n row[field] = col?.get(i);\n }\n result.push(row);\n }\n\n return result;\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"normalize.js","names":["xField","detected","resolvedXKey","resolvedYKeys","rawXData","rawYDataMap","xData","yDataMap"],"sources":["../../../src/react/charts/normalize.ts"],"sourcesContent":["import { ArrowClient } from \"@/js\";\nimport type { Table } from \"apache-arrow\";\nimport { DATE_FIELD_PATTERNS, NAME_FIELD_PATTERNS } from \"./constants\";\nimport type {\n ChartData,\n NormalizedChartData,\n NormalizedChartDataBase,\n Orientation,\n} from \"./types\";\nimport { isArrowTable } from \"./types\";\nimport { sortTimeSeriesAscending, toChartArray } from \"./utils\";\n\n// ============================================================================\n// Type Detection Helpers\n// ============================================================================\n\n/**\n * Checks if a value looks like an ISO date string\n */\nfunction isDateString(value: unknown): boolean {\n if (typeof value !== \"string\") return false;\n return /^\\d{4}-\\d{2}-\\d{2}(T|$)/.test(value);\n}\n\n/**\n * Checks if a value is numeric (number or numeric string)\n */\nfunction isNumericValue(value: unknown): boolean {\n if (typeof value === \"number\") return true;\n if (typeof value === \"bigint\") return true;\n if (typeof value === \"string\") {\n const trimmed = value.trim();\n if (trimmed === \"\" || isDateString(trimmed)) return false;\n const parsed = Number(trimmed);\n return !Number.isNaN(parsed) && Number.isFinite(parsed);\n }\n return false;\n}\n\n/**\n * Checks if a value looks like a category/label (non-numeric string)\n */\nfunction isCategoryValue(value: unknown): boolean {\n if (typeof value !== \"string\") return false;\n const trimmed = value.trim();\n if (trimmed === \"\") return false;\n if (/^\\d{4}-\\d{2}-\\d{2}/.test(trimmed)) return false;\n const parsed = Number(trimmed);\n return Number.isNaN(parsed) || !Number.isFinite(parsed);\n}\n\n// ============================================================================\n// Field Detection\n// ============================================================================\n\n/**\n * Detects fields from JSON data for charting\n */\nfunction detectFieldsFromJson(\n data: Record<string, unknown>[],\n orientation?: Orientation,\n): {\n xField: string;\n yFields: string[];\n chartType: \"timeseries\" | \"categorical\";\n} {\n if (!data || data.length === 0) {\n return { xField: \"x\", yFields: [\"y\"], chartType: \"categorical\" };\n }\n\n const firstRow = data[0];\n const keys = Object.keys(firstRow);\n\n // Detect date fields by key name OR by value being a date string\n const dateFields = keys.filter((key) => {\n const value = firstRow[key];\n const keyMatchesDatePattern = DATE_FIELD_PATTERNS.some((p) =>\n key.toLowerCase().includes(p),\n );\n const valueIsDateString = isDateString(value);\n return keyMatchesDatePattern || valueIsDateString;\n });\n\n // Detect name/category fields by pattern AND value type\n let nameFields = keys.filter((key) => {\n const value = firstRow[key];\n return (\n isCategoryValue(value) &&\n !isDateString(value) &&\n NAME_FIELD_PATTERNS.some((p) => key.toLowerCase().includes(p))\n );\n });\n\n // Fallback: any string field that isn't a date or ID\n if (nameFields.length === 0) {\n nameFields = keys.filter((key) => {\n const value = firstRow[key];\n return (\n isCategoryValue(value) &&\n !isDateString(value) &&\n !dateFields.includes(key) &&\n !key.toLowerCase().endsWith(\"_id\")\n );\n });\n }\n\n // Detect numeric fields\n const numericFields = keys.filter((key) => {\n const value = firstRow[key];\n return isNumericValue(value) && !dateFields.includes(key);\n });\n\n const isHorizontal = orientation === \"horizontal\";\n\n if (isHorizontal || (nameFields.length > 0 && dateFields.length === 0)) {\n const xField = nameFields[0] || dateFields[0] || keys[0];\n const yFields =\n numericFields.length > 0\n ? numericFields\n : keys.filter((k) => k !== xField);\n return { xField, yFields, chartType: \"categorical\" };\n }\n\n const xField = dateFields[0] || nameFields[0] || keys[0];\n const yFields =\n numericFields.length > 0 ? numericFields : keys.filter((k) => k !== xField);\n return {\n xField,\n yFields,\n chartType: dateFields.length > 0 ? \"timeseries\" : \"categorical\",\n };\n}\n\n// ============================================================================\n// Value Conversion\n// ============================================================================\n\n/**\n * Converts a JSON value to a chart-compatible value.\n */\nfunction jsonValueToChartValue(\n value: unknown,\n isYValue: boolean,\n isDateField: boolean,\n): string | number {\n if (value === null || value === undefined) {\n return isYValue ? 0 : \"\";\n }\n if (typeof value === \"number\") {\n return value;\n }\n if (typeof value === \"bigint\") {\n return Number(value);\n }\n if (typeof value === \"string\") {\n if (isDateField && isDateString(value)) {\n const timestamp = new Date(value).getTime();\n if (!Number.isNaN(timestamp)) {\n return timestamp;\n }\n }\n if (isYValue) {\n const trimmed = value.trim();\n const parsed = Number(trimmed);\n if (!Number.isNaN(parsed) && Number.isFinite(parsed)) {\n return parsed;\n }\n }\n return value;\n }\n return String(value);\n}\n\n// ============================================================================\n// Data Extraction\n// ============================================================================\n\n/**\n * Extracts chart data from JSON array\n */\nfunction extractFromJson(\n data: Record<string, unknown>[],\n xField: string,\n yFields: string[],\n): {\n xData: (string | number)[];\n yDataMap: Record<string, (string | number)[]>;\n} {\n const xData: (string | number)[] = [];\n const yDataMap: Record<string, (string | number)[]> = {};\n\n for (const field of yFields) {\n yDataMap[field] = [];\n }\n\n const xIsDateField = data.length > 0 && isDateString(data[0][xField]);\n\n for (const row of data) {\n xData.push(jsonValueToChartValue(row[xField], false, xIsDateField));\n for (const field of yFields) {\n yDataMap[field].push(jsonValueToChartValue(row[field], true, false));\n }\n }\n\n return { xData, yDataMap };\n}\n\n// ============================================================================\n// Main Normalization Function\n// ============================================================================\n\n/**\n * Normalizes chart data from either Arrow or JSON format.\n * Converts BigInt and Date values to chart-compatible types.\n */\nexport function normalizeChartData(\n data: ChartData,\n xKey?: string,\n yKey?: string | string[],\n orientation?: Orientation,\n): NormalizedChartData {\n if (isArrowTable(data)) {\n const table = data as Table;\n const detected = ArrowClient.detectFieldsFromArrow(table, orientation);\n const resolvedXKey = xKey ?? detected.xField;\n const resolvedYKeys = yKey\n ? Array.isArray(yKey)\n ? yKey\n : [yKey]\n : detected.yFields;\n\n const { xData: rawXData, yDataMap: rawYDataMap } =\n ArrowClient.extractChartData(table, resolvedXKey, resolvedYKeys);\n\n let xData = toChartArray(rawXData);\n let yDataMap: Record<string, (string | number)[]> = {};\n for (const key of resolvedYKeys) {\n yDataMap[key] = toChartArray(rawYDataMap[key] ?? []);\n }\n\n if (detected.chartType === \"timeseries\") {\n ({ xData, yDataMap } = sortTimeSeriesAscending(\n xData,\n yDataMap,\n resolvedYKeys,\n ));\n }\n\n return {\n xData,\n yDataMap,\n xField: resolvedXKey,\n yFields: resolvedYKeys,\n chartType: detected.chartType,\n };\n }\n\n // JSON Array\n const jsonData = data as Record<string, unknown>[];\n const detected = detectFieldsFromJson(jsonData, orientation);\n const resolvedXKey = xKey ?? detected.xField;\n const resolvedYKeys = yKey\n ? Array.isArray(yKey)\n ? yKey\n : [yKey]\n : detected.yFields;\n\n const { xData: rawXData, yDataMap: rawYDataMap } = extractFromJson(\n jsonData,\n resolvedXKey,\n resolvedYKeys,\n );\n\n let xData = toChartArray(rawXData);\n let yDataMap: Record<string, (string | number)[]> = {};\n for (const key of resolvedYKeys) {\n yDataMap[key] = toChartArray(rawYDataMap[key] ?? []);\n }\n\n if (detected.chartType === \"timeseries\") {\n ({ xData, yDataMap } = sortTimeSeriesAscending(\n xData,\n yDataMap,\n resolvedYKeys,\n ));\n }\n\n return {\n xData,\n yDataMap,\n xField: resolvedXKey,\n yFields: resolvedYKeys,\n chartType: detected.chartType,\n };\n}\n\n// ============================================================================\n// Heatmap Data Normalization\n// ============================================================================\n\n/**\n * Normalized data for heatmap charts.\n * Extends base (not NormalizedChartData) because heatmaps don't use yDataMap.\n * Instead, they use heatmapData which contains [xIndex, yIndex, value] tuples.\n */\nexport interface NormalizedHeatmapData extends NormalizedChartDataBase {\n /** Y-axis categories (rows) */\n yAxisData: (string | number)[];\n /** Heatmap data as [xIndex, yIndex, value] tuples */\n heatmapData: [number, number, number][];\n /** Min value in the data */\n min: number;\n /** Max value in the data */\n max: number;\n}\n\n/**\n * Normalizes data specifically for heatmap charts.\n * Expects data in format: `{ xKey: string, yAxisKey: string, valueKey: number }`\n *\n * @param data - Raw data (Arrow Table or JSON array)\n * @param xKey - Field key for X-axis (columns)\n * @param yAxisKey - Field key for Y-axis (rows)\n * @param valueKey - Field key for the cell values\n */\nexport function normalizeHeatmapData(\n data: ChartData,\n xKey?: string,\n yAxisKey?: string,\n valueKey?: string | string[],\n): NormalizedHeatmapData {\n // First, get the standard normalization\n const jsonData = isArrowTable(data)\n ? extractJsonFromArrow(data)\n : (data as Record<string, unknown>[]);\n\n if (jsonData.length === 0) {\n return {\n xData: [],\n xField: xKey ?? \"x\",\n yFields: [],\n chartType: \"categorical\",\n yAxisData: [],\n heatmapData: [],\n min: 0,\n max: 0,\n };\n }\n\n // Detect fields if not provided\n const keys = Object.keys(jsonData[0]);\n const resolvedXKey = xKey ?? keys[0];\n const resolvedYAxisKey = yAxisKey ?? keys[1];\n const resolvedValueKey = valueKey\n ? Array.isArray(valueKey)\n ? valueKey[0]\n : valueKey\n : keys[2];\n\n // Extract unique X and Y categories\n const xSet = new Set<string | number>();\n const ySet = new Set<string | number>();\n\n for (const row of jsonData) {\n const xVal = jsonValueToChartValue(row[resolvedXKey], false, false);\n const yVal = jsonValueToChartValue(row[resolvedYAxisKey], false, false);\n xSet.add(xVal);\n ySet.add(yVal);\n }\n\n const xData = Array.from(xSet);\n const yAxisData = Array.from(ySet);\n\n // Create index maps for fast lookup\n const xIndexMap = new Map<string | number, number>();\n const yIndexMap = new Map<string | number, number>();\n xData.forEach((v, i) => {\n xIndexMap.set(v, i);\n });\n yAxisData.forEach((v, i) => {\n yIndexMap.set(v, i);\n });\n\n // Build heatmap data and track min/max\n const heatmapData: [number, number, number][] = [];\n let min = Number.POSITIVE_INFINITY;\n let max = Number.NEGATIVE_INFINITY;\n\n for (const row of jsonData) {\n const xVal = jsonValueToChartValue(row[resolvedXKey], false, false);\n const yVal = jsonValueToChartValue(row[resolvedYAxisKey], false, false);\n const value = jsonValueToChartValue(row[resolvedValueKey], true, false);\n\n const xIdx = xIndexMap.get(xVal);\n const yIdx = yIndexMap.get(yVal);\n const numValue = typeof value === \"number\" ? value : 0;\n\n if (xIdx !== undefined && yIdx !== undefined) {\n heatmapData.push([xIdx, yIdx, numValue]);\n min = Math.min(min, numValue);\n max = Math.max(max, numValue);\n }\n }\n\n // Handle edge case where no valid data was found\n if (heatmapData.length === 0) {\n min = 0;\n max = 0;\n }\n\n return {\n xData,\n xField: resolvedXKey,\n yFields: [resolvedValueKey],\n chartType: \"categorical\",\n yAxisData,\n heatmapData,\n min,\n max,\n };\n}\n\n/**\n * Helper to extract JSON array from Arrow table for heatmap processing.\n */\nfunction extractJsonFromArrow(table: Table): Record<string, unknown>[] {\n const result: Record<string, unknown>[] = [];\n const fields = table.schema.fields.map((f) => f.name);\n\n for (let i = 0; i < table.numRows; i++) {\n const row: Record<string, unknown> = {};\n for (const field of fields) {\n const col = table.getChild(field);\n row[field] = col?.get(i);\n }\n result.push(row);\n }\n\n return result;\n}\n"],"mappings":";;;;;;;;;;AAmBA,SAAS,aAAa,OAAyB;AAC7C,KAAI,OAAO,UAAU,SAAU,QAAO;AACtC,QAAO,0BAA0B,KAAK,MAAM;;;;;AAM9C,SAAS,eAAe,OAAyB;AAC/C,KAAI,OAAO,UAAU,SAAU,QAAO;AACtC,KAAI,OAAO,UAAU,SAAU,QAAO;AACtC,KAAI,OAAO,UAAU,UAAU;EAC7B,MAAM,UAAU,MAAM,MAAM;AAC5B,MAAI,YAAY,MAAM,aAAa,QAAQ,CAAE,QAAO;EACpD,MAAM,SAAS,OAAO,QAAQ;AAC9B,SAAO,CAAC,OAAO,MAAM,OAAO,IAAI,OAAO,SAAS,OAAO;;AAEzD,QAAO;;;;;AAMT,SAAS,gBAAgB,OAAyB;AAChD,KAAI,OAAO,UAAU,SAAU,QAAO;CACtC,MAAM,UAAU,MAAM,MAAM;AAC5B,KAAI,YAAY,GAAI,QAAO;AAC3B,KAAI,qBAAqB,KAAK,QAAQ,CAAE,QAAO;CAC/C,MAAM,SAAS,OAAO,QAAQ;AAC9B,QAAO,OAAO,MAAM,OAAO,IAAI,CAAC,OAAO,SAAS,OAAO;;;;;AAUzD,SAAS,qBACP,MACA,aAKA;AACA,KAAI,CAAC,QAAQ,KAAK,WAAW,EAC3B,QAAO;EAAE,QAAQ;EAAK,SAAS,CAAC,IAAI;EAAE,WAAW;EAAe;CAGlE,MAAM,WAAW,KAAK;CACtB,MAAM,OAAO,OAAO,KAAK,SAAS;CAGlC,MAAM,aAAa,KAAK,QAAQ,QAAQ;EACtC,MAAM,QAAQ,SAAS;EACvB,MAAM,wBAAwB,oBAAoB,MAAM,MACtD,IAAI,aAAa,CAAC,SAAS,EAAE,CAC9B;EACD,MAAM,oBAAoB,aAAa,MAAM;AAC7C,SAAO,yBAAyB;GAChC;CAGF,IAAI,aAAa,KAAK,QAAQ,QAAQ;EACpC,MAAM,QAAQ,SAAS;AACvB,SACE,gBAAgB,MAAM,IACtB,CAAC,aAAa,MAAM,IACpB,oBAAoB,MAAM,MAAM,IAAI,aAAa,CAAC,SAAS,EAAE,CAAC;GAEhE;AAGF,KAAI,WAAW,WAAW,EACxB,cAAa,KAAK,QAAQ,QAAQ;EAChC,MAAM,QAAQ,SAAS;AACvB,SACE,gBAAgB,MAAM,IACtB,CAAC,aAAa,MAAM,IACpB,CAAC,WAAW,SAAS,IAAI,IACzB,CAAC,IAAI,aAAa,CAAC,SAAS,MAAM;GAEpC;CAIJ,MAAM,gBAAgB,KAAK,QAAQ,QAAQ;EACzC,MAAM,QAAQ,SAAS;AACvB,SAAO,eAAe,MAAM,IAAI,CAAC,WAAW,SAAS,IAAI;GACzD;AAIF,KAFqB,gBAAgB,gBAEhB,WAAW,SAAS,KAAK,WAAW,WAAW,GAAI;EACtE,MAAMA,WAAS,WAAW,MAAM,WAAW,MAAM,KAAK;AAKtD,SAAO;GAAE;GAAQ,SAHf,cAAc,SAAS,IACnB,gBACA,KAAK,QAAQ,MAAM,MAAMA,SAAO;GACZ,WAAW;GAAe;;CAGtD,MAAM,SAAS,WAAW,MAAM,WAAW,MAAM,KAAK;AAGtD,QAAO;EACL;EACA,SAHA,cAAc,SAAS,IAAI,gBAAgB,KAAK,QAAQ,MAAM,MAAM,OAAO;EAI3E,WAAW,WAAW,SAAS,IAAI,eAAe;EACnD;;;;;AAUH,SAAS,sBACP,OACA,UACA,aACiB;AACjB,KAAI,UAAU,QAAQ,UAAU,OAC9B,QAAO,WAAW,IAAI;AAExB,KAAI,OAAO,UAAU,SACnB,QAAO;AAET,KAAI,OAAO,UAAU,SACnB,QAAO,OAAO,MAAM;AAEtB,KAAI,OAAO,UAAU,UAAU;AAC7B,MAAI,eAAe,aAAa,MAAM,EAAE;GACtC,MAAM,YAAY,IAAI,KAAK,MAAM,CAAC,SAAS;AAC3C,OAAI,CAAC,OAAO,MAAM,UAAU,CAC1B,QAAO;;AAGX,MAAI,UAAU;GACZ,MAAM,UAAU,MAAM,MAAM;GAC5B,MAAM,SAAS,OAAO,QAAQ;AAC9B,OAAI,CAAC,OAAO,MAAM,OAAO,IAAI,OAAO,SAAS,OAAO,CAClD,QAAO;;AAGX,SAAO;;AAET,QAAO,OAAO,MAAM;;;;;AAUtB,SAAS,gBACP,MACA,QACA,SAIA;CACA,MAAM,QAA6B,EAAE;CACrC,MAAM,WAAgD,EAAE;AAExD,MAAK,MAAM,SAAS,QAClB,UAAS,SAAS,EAAE;CAGtB,MAAM,eAAe,KAAK,SAAS,KAAK,aAAa,KAAK,GAAG,QAAQ;AAErE,MAAK,MAAM,OAAO,MAAM;AACtB,QAAM,KAAK,sBAAsB,IAAI,SAAS,OAAO,aAAa,CAAC;AACnE,OAAK,MAAM,SAAS,QAClB,UAAS,OAAO,KAAK,sBAAsB,IAAI,QAAQ,MAAM,MAAM,CAAC;;AAIxE,QAAO;EAAE;EAAO;EAAU;;;;;;AAW5B,SAAgB,mBACd,MACA,MACA,MACA,aACqB;AACrB,KAAI,aAAa,KAAK,EAAE;EACtB,MAAM,QAAQ;EACd,MAAMC,aAAW,YAAY,sBAAsB,OAAO,YAAY;EACtE,MAAMC,iBAAe,QAAQD,WAAS;EACtC,MAAME,kBAAgB,OAClB,MAAM,QAAQ,KAAK,GACjB,OACA,CAAC,KAAK,GACRF,WAAS;EAEb,MAAM,EAAE,OAAOG,YAAU,UAAUC,kBACjC,YAAY,iBAAiB,OAAOH,gBAAcC,gBAAc;EAElE,IAAIG,UAAQ,aAAaF,WAAS;EAClC,IAAIG,aAAgD,EAAE;AACtD,OAAK,MAAM,OAAOJ,gBAChB,YAAS,OAAO,aAAaE,cAAY,QAAQ,EAAE,CAAC;AAGtD,MAAIJ,WAAS,cAAc,aACzB,EAAC,CAAE,gBAAO,wBAAa,wBACrBK,SACAC,YACAJ,gBACD;AAGH,SAAO;GACL;GACA;GACA,QAAQD;GACR,SAASC;GACT,WAAWF,WAAS;GACrB;;CAIH,MAAM,WAAW;CACjB,MAAM,WAAW,qBAAqB,UAAU,YAAY;CAC5D,MAAM,eAAe,QAAQ,SAAS;CACtC,MAAM,gBAAgB,OAClB,MAAM,QAAQ,KAAK,GACjB,OACA,CAAC,KAAK,GACR,SAAS;CAEb,MAAM,EAAE,OAAO,UAAU,UAAU,gBAAgB,gBACjD,UACA,cACA,cACD;CAED,IAAI,QAAQ,aAAa,SAAS;CAClC,IAAI,WAAgD,EAAE;AACtD,MAAK,MAAM,OAAO,cAChB,UAAS,OAAO,aAAa,YAAY,QAAQ,EAAE,CAAC;AAGtD,KAAI,SAAS,cAAc,aACzB,EAAC,CAAE,OAAO,YAAa,wBACrB,OACA,UACA,cACD;AAGH,QAAO;EACL;EACA;EACA,QAAQ;EACR,SAAS;EACT,WAAW,SAAS;EACrB;;;;;;;;;;;AAgCH,SAAgB,qBACd,MACA,MACA,UACA,UACuB;CAEvB,MAAM,WAAW,aAAa,KAAK,GAC/B,qBAAqB,KAAK,GACzB;AAEL,KAAI,SAAS,WAAW,EACtB,QAAO;EACL,OAAO,EAAE;EACT,QAAQ,QAAQ;EAChB,SAAS,EAAE;EACX,WAAW;EACX,WAAW,EAAE;EACb,aAAa,EAAE;EACf,KAAK;EACL,KAAK;EACN;CAIH,MAAM,OAAO,OAAO,KAAK,SAAS,GAAG;CACrC,MAAM,eAAe,QAAQ,KAAK;CAClC,MAAM,mBAAmB,YAAY,KAAK;CAC1C,MAAM,mBAAmB,WACrB,MAAM,QAAQ,SAAS,GACrB,SAAS,KACT,WACF,KAAK;CAGT,MAAM,uBAAO,IAAI,KAAsB;CACvC,MAAM,uBAAO,IAAI,KAAsB;AAEvC,MAAK,MAAM,OAAO,UAAU;EAC1B,MAAM,OAAO,sBAAsB,IAAI,eAAe,OAAO,MAAM;EACnE,MAAM,OAAO,sBAAsB,IAAI,mBAAmB,OAAO,MAAM;AACvE,OAAK,IAAI,KAAK;AACd,OAAK,IAAI,KAAK;;CAGhB,MAAM,QAAQ,MAAM,KAAK,KAAK;CAC9B,MAAM,YAAY,MAAM,KAAK,KAAK;CAGlC,MAAM,4BAAY,IAAI,KAA8B;CACpD,MAAM,4BAAY,IAAI,KAA8B;AACpD,OAAM,SAAS,GAAG,MAAM;AACtB,YAAU,IAAI,GAAG,EAAE;GACnB;AACF,WAAU,SAAS,GAAG,MAAM;AAC1B,YAAU,IAAI,GAAG,EAAE;GACnB;CAGF,MAAM,cAA0C,EAAE;CAClD,IAAI,MAAM,OAAO;CACjB,IAAI,MAAM,OAAO;AAEjB,MAAK,MAAM,OAAO,UAAU;EAC1B,MAAM,OAAO,sBAAsB,IAAI,eAAe,OAAO,MAAM;EACnE,MAAM,OAAO,sBAAsB,IAAI,mBAAmB,OAAO,MAAM;EACvE,MAAM,QAAQ,sBAAsB,IAAI,mBAAmB,MAAM,MAAM;EAEvE,MAAM,OAAO,UAAU,IAAI,KAAK;EAChC,MAAM,OAAO,UAAU,IAAI,KAAK;EAChC,MAAM,WAAW,OAAO,UAAU,WAAW,QAAQ;AAErD,MAAI,SAAS,UAAa,SAAS,QAAW;AAC5C,eAAY,KAAK;IAAC;IAAM;IAAM;IAAS,CAAC;AACxC,SAAM,KAAK,IAAI,KAAK,SAAS;AAC7B,SAAM,KAAK,IAAI,KAAK,SAAS;;;AAKjC,KAAI,YAAY,WAAW,GAAG;AAC5B,QAAM;AACN,QAAM;;AAGR,QAAO;EACL;EACA,QAAQ;EACR,SAAS,CAAC,iBAAiB;EAC3B,WAAW;EACX;EACA;EACA;EACA;EACD;;;;;AAMH,SAAS,qBAAqB,OAAyC;CACrE,MAAM,SAAoC,EAAE;CAC5C,MAAM,SAAS,MAAM,OAAO,OAAO,KAAK,MAAM,EAAE,KAAK;AAErD,MAAK,IAAI,IAAI,GAAG,IAAI,MAAM,SAAS,KAAK;EACtC,MAAM,MAA+B,EAAE;AACvC,OAAK,MAAM,SAAS,OAElB,KAAI,SADQ,MAAM,SAAS,MAAM,EACf,IAAI,EAAE;AAE1B,SAAO,KAAK,IAAI;;AAGlB,QAAO"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"theme.js","names":[
|
|
1
|
+
{"version":3,"file":"theme.js","names":[],"sources":["../../../src/react/charts/theme.ts"],"sourcesContent":["import { useEffect, useState } from \"react\";\nimport {\n CHART_COLOR_VARS_CATEGORICAL,\n CHART_COLOR_VARS_DIVERGING,\n CHART_COLOR_VARS_SEQUENTIAL,\n FALLBACK_COLORS_CATEGORICAL,\n FALLBACK_COLORS_DIVERGING,\n FALLBACK_COLORS_SEQUENTIAL,\n} from \"./constants\";\nimport type { ChartColorPalette } from \"./types\";\n\n// ============================================================================\n// Theme Colors (resolved from CSS variables)\n// ============================================================================\n\nconst PALETTE_CONFIG: Record<\n ChartColorPalette,\n { vars: readonly string[]; fallback: string[] }\n> = {\n categorical: {\n vars: CHART_COLOR_VARS_CATEGORICAL,\n fallback: FALLBACK_COLORS_CATEGORICAL,\n },\n sequential: {\n vars: CHART_COLOR_VARS_SEQUENTIAL,\n fallback: FALLBACK_COLORS_SEQUENTIAL,\n },\n diverging: {\n vars: CHART_COLOR_VARS_DIVERGING,\n fallback: FALLBACK_COLORS_DIVERGING,\n },\n};\n\n// ============================================================================\n// Module-Level Caching\n// ============================================================================\n\n/**\n * Cache for computed theme colors to avoid repeated CSS variable lookups.\n * Cache is cleared when theme change events fire (MutationObserver/matchMedia).\n */\nconst colorCache = new Map<string, string[]>();\n\n/**\n * Clears the theme color cache.\n * Called when theme change events fire, or for testing when mocks change.\n * @internal\n */\nexport function resetThemeColorCache(): void {\n colorCache.clear();\n}\n\n/**\n * Gets theme colors with module-level caching.\n * Avoids repeated CSS variable lookups for the same palette within a theme.\n */\nfunction getThemeColors(palette: ChartColorPalette = \"categorical\"): string[] {\n const config = PALETTE_CONFIG[palette];\n\n if (typeof window === \"undefined\") return config.fallback;\n\n // Return cached colors if available\n const cached = colorCache.get(palette);\n if (cached) {\n return cached;\n }\n\n // Compute colors from CSS variables\n const styles = getComputedStyle(document.documentElement);\n const colors: string[] = [];\n\n for (const varName of config.vars) {\n const value = styles.getPropertyValue(varName).trim();\n if (value) colors.push(value);\n }\n\n const result = colors.length > 0 ? colors : config.fallback;\n\n // Cache the result\n colorCache.set(palette, result);\n\n return result;\n}\n\n/**\n * Hook to get theme colors with automatic updates on theme change.\n * Re-resolves CSS variables when color scheme or theme attributes change.\n *\n * @param palette - Color palette type: \"categorical\" (default), \"sequential\", or \"diverging\"\n */\nexport function useThemeColors(\n palette: ChartColorPalette = \"categorical\",\n): string[] {\n const [colors, setColors] = useState<string[]>(() =>\n typeof window === \"undefined\"\n ? PALETTE_CONFIG[palette].fallback\n : getThemeColors(palette),\n );\n\n useEffect(() => {\n // Clear cache and re-fetch colors when theme changes\n const updateColors = () => {\n resetThemeColorCache();\n setColors(getThemeColors(palette));\n };\n\n // Listen for system color scheme changes\n const mediaQuery = window.matchMedia(\"(prefers-color-scheme: dark)\");\n mediaQuery.addEventListener(\"change\", updateColors);\n\n // Listen for theme attribute changes (e.g., class=\"dark\", data-theme=\"dark\")\n const observer = new MutationObserver(updateColors);\n observer.observe(document.documentElement, {\n attributes: true,\n attributeFilter: [\"class\", \"data-theme\", \"data-mode\"],\n });\n\n return () => {\n mediaQuery.removeEventListener(\"change\", updateColors);\n observer.disconnect();\n };\n }, [palette]);\n\n return colors;\n}\n\n/**\n * Hook to get all three color palettes at once.\n * Useful when a component needs access to multiple palette types.\n */\nexport function useAllThemeColors(): {\n categorical: string[];\n sequential: string[];\n diverging: string[];\n} {\n const categorical = useThemeColors(\"categorical\");\n const sequential = useThemeColors(\"sequential\");\n const diverging = useThemeColors(\"diverging\");\n\n return { categorical, sequential, diverging };\n}\n"],"mappings":";;;;AAeA,MAAM,iBAGF;CACF,aAAa;EACX,MAAM;EACN,UAAU;EACX;CACD,YAAY;EACV,MAAM;EACN,UAAU;EACX;CACD,WAAW;EACT,MAAM;EACN,UAAU;EACX;CACF;;;;;AAUD,MAAM,6BAAa,IAAI,KAAuB;;;;;;AAO9C,SAAgB,uBAA6B;AAC3C,YAAW,OAAO;;;;;;AAOpB,SAAS,eAAe,UAA6B,eAAyB;CAC5E,MAAM,SAAS,eAAe;AAE9B,KAAI,OAAO,WAAW,YAAa,QAAO,OAAO;CAGjD,MAAM,SAAS,WAAW,IAAI,QAAQ;AACtC,KAAI,OACF,QAAO;CAIT,MAAM,SAAS,iBAAiB,SAAS,gBAAgB;CACzD,MAAM,SAAmB,EAAE;AAE3B,MAAK,MAAM,WAAW,OAAO,MAAM;EACjC,MAAM,QAAQ,OAAO,iBAAiB,QAAQ,CAAC,MAAM;AACrD,MAAI,MAAO,QAAO,KAAK,MAAM;;CAG/B,MAAM,SAAS,OAAO,SAAS,IAAI,SAAS,OAAO;AAGnD,YAAW,IAAI,SAAS,OAAO;AAE/B,QAAO;;;;;;;;AAST,SAAgB,eACd,UAA6B,eACnB;CACV,MAAM,CAAC,QAAQ,aAAa,eAC1B,OAAO,WAAW,cACd,eAAe,SAAS,WACxB,eAAe,QAAQ,CAC5B;AAED,iBAAgB;EAEd,MAAM,qBAAqB;AACzB,yBAAsB;AACtB,aAAU,eAAe,QAAQ,CAAC;;EAIpC,MAAM,aAAa,OAAO,WAAW,+BAA+B;AACpE,aAAW,iBAAiB,UAAU,aAAa;EAGnD,MAAM,WAAW,IAAI,iBAAiB,aAAa;AACnD,WAAS,QAAQ,SAAS,iBAAiB;GACzC,YAAY;GACZ,iBAAiB;IAAC;IAAS;IAAc;IAAY;GACtD,CAAC;AAEF,eAAa;AACX,cAAW,oBAAoB,UAAU,aAAa;AACtD,YAAS,YAAY;;IAEtB,CAAC,QAAQ,CAAC;AAEb,QAAO;;;;;;AAOT,SAAgB,oBAId;AAKA,QAAO;EAAE,aAJW,eAAe,cAAc;EAI3B,YAHH,eAAe,aAAa;EAGb,WAFhB,eAAe,YAAY;EAEA"}
|
|
@@ -57,6 +57,11 @@ interface QueryProps extends ChartBaseProps {
|
|
|
57
57
|
format?: DataFormat;
|
|
58
58
|
/** Transform raw data before rendering */
|
|
59
59
|
transformer?: <T>(data: T) => T;
|
|
60
|
+
/**
|
|
61
|
+
* Whether to execute the query as the current user
|
|
62
|
+
* @default false
|
|
63
|
+
*/
|
|
64
|
+
asUser?: boolean;
|
|
60
65
|
data?: never;
|
|
61
66
|
}
|
|
62
67
|
/** Props for direct data injection */
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","names":[],"sources":["../../../src/react/charts/types.ts"],"sourcesContent":[],"mappings":";;;;KAOY,UAAA;AAAZ;AAGY,KAAA,WAAA,GAAW,UAAA,GAAA,YAAA;AAGvB;AAWY,KAXA,SAAA,GAWS,KAAA,GAAA,MAAA,GAAA,MAAA,GAAA,KAAA,GAAA,OAAA,GAAA,SAAA,GAAA,OAAA,GAAA,SAAA;;AAAG,KAAZ,SAAA,GAAY,KAAA,GAAQ,MAAR,CAAA,MAAA,EAAA,OAAA,CAAA,EAAA;;AAAc,KAO1B,iBAAA,GAP0B,aAAA,GAAA,YAAA,GAAA,WAAA;AAOtC;AAGiB,UAAA,cAAA,CAAc;EAAA;OAWd,CAAA,EAAA,MAAA;;EAmBC,UAAA,CAAA,EAAA,OAAA;EAQD;;;;;;cAAmB,CAAA,EA3BnB,iBA2BmB;EAAc;
|
|
1
|
+
{"version":3,"file":"types.d.ts","names":[],"sources":["../../../src/react/charts/types.ts"],"sourcesContent":[],"mappings":";;;;KAOY,UAAA;AAAZ;AAGY,KAAA,WAAA,GAAW,UAAA,GAAA,YAAA;AAGvB;AAWY,KAXA,SAAA,GAWS,KAAA,GAAA,MAAA,GAAA,MAAA,GAAA,KAAA,GAAA,OAAA,GAAA,SAAA,GAAA,OAAA,GAAA,SAAA;;AAAG,KAAZ,SAAA,GAAY,KAAA,GAAQ,MAAR,CAAA,MAAA,EAAA,OAAA,CAAA,EAAA;;AAAc,KAO1B,iBAAA,GAP0B,aAAA,GAAA,YAAA,GAAA,WAAA;AAOtC;AAGiB,UAAA,cAAA,CAAc;EAAA;OAWd,CAAA,EAAA,MAAA;;EAmBC,UAAA,CAAA,EAAA,OAAA;EAQD;;;;;;cAAmB,CAAA,EA3BnB,iBA2BmB;EAAc;EA8BjC,MAAA,CAAA,EAAA,MAAU,EAAA;EAAA;QAEnB,CAAA,EAAA,MAAA;;EAFyC,SAAA,CAAA,EAAA,MAAA;EAgBrC;EAAiB,IAAA,CAAA,EAAA,MAAA;;MAAgB,CAAA,EAAA,MAAA,GAAA,MAAA,EAAA;EAAS;EAOrC,SAAA,CAAA,EAAA,MAAA;EAQA;EAUA,MAAA,CAAA,EAAA,MAAA;EAYA;EAMA,OAAA,CAAA,EAjGL,MAiGK,CAAA,MAAA,EAAqB,OAAA,CAAA;AAUtC;AAMA;AAkBY,UA3HK,UAAA,SAAmB,cA2HX,CAAA;EAAA;UAAI,EAAA,MAAA;;YAA0B,CAAA,EAvHxC,MAuHwC,CAAA,MAAA,EAAA,OAAA,CAAA;EAAqB;AAC5E;;;;;;EACY,MAAA,CAAA,EAjHD,UAiHe;EAAA;aAAI,CAAA,EAAA,CAAA,CAAA,CAAA,CAAA,IAAA,EA/GJ,CA+GI,EAAA,GA/GE,CA+GF;;;;AAC9B;EAA6B,MAAA,CAAA,EAAA,OAAA;MAAI,CAAA,EAAA,KAAA;;;AACN,UAjGV,SAAA,SAAkB,cAiGR,CAAA;EACf;EAAa,IAAA,EAhGjB,SAgGiB;UAAI,CAAA,EAAA,KAAA;YAAa,CAAA,EAAA,KAAA;QAAa,CAAA,EAAA,KAAA;EAAqB,WAAA,CAAA,EAAA,KAAA;AAC5E;;AAA+B,KAnFnB,iBAAA,GAAoB,UAmFD,GAnFc,SAmFd;;AAA0B,UA5ExC,qBAAA,CA4EwC;EAAqB;EAClE,WAAA,CAAA,EA3EI,WA2EW;EAAA;SAAI,CAAA,EAAA,OAAA;;;AACN,UAtER,sBAAA,CAsEQ;EACb;EAAiB,WAAA,CAAA,EArEb,WAqEa;;YAAiB,CAAA,EAAA,OAAA;;EACnB,MAAA,CAAA,EAAA,OAAA;AAO3B;AAQA;AAAqC,UA7EpB,sBAAA,CA6EoB;;aAAQ,CAAA,EA3E7B,WA2E6B;EAAuB;EAKpD,UAAA,CAAA,EAAA,OAAY;EAAA;QAAO,CAAA,EAAA,OAAA;;EAAyB,OAAA,CAAA,EAAA,OAAA;AAW5D;;AAAoC,UAjFnB,yBAAA,CAiFmB;;EAAuC,UAAA,CAAA,EAAA,MAAA;AAS3E;;AAAmC,UApFlB,qBAAA,CAoFkB;;EAAsC,WAAA,CAAA,EAAA,MAAA;;;;;;;UA1ExD,uBAAA;;;;;UAMA,yBAAA;;;;;;;;;;;;;KAkBL,aAAA,IAAiB,aAAa,aAAa;KAC3C,cAAA,IAAkB,aAAa,aAAa;KAC5C,cAAA,IAAkB,aAAa,aAAa;KAC5C,iBAAA,IAAqB,aAAa,aAC5C;KACU,aAAA,IAAiB,aAAa,aAAa;KAC3C,eAAA,IAAmB,aAAa,aAAa;KAC7C,eAAA,IAAmB,aAAa,aAC1C;KACU,iBAAA,IAAqB,aAAa,aAC5C;;UAOe,uBAAA;;;;;;;UAQA,mBAAA,SAA4B;YACjC;;;iBAII,YAAA,OAAmB,oBAAoB;;iBAWvC,YAAA,QAAoB,6BAA6B;;iBASjD,WAAA,QAAmB,6BAA6B"}
|