@databricks/appkit-ui 0.1.4 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +89 -12
- package/CLAUDE.md +89 -12
- package/NOTICE.md +4 -0
- package/README.md +21 -15
- 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/area/index.d.ts +2 -2
- package/dist/react/charts/bar/index.d.ts +2 -2
- package/dist/react/charts/base.d.ts +2 -2
- package/dist/react/charts/base.js.map +1 -1
- package/dist/react/charts/create-chart.d.ts +2 -2
- package/dist/react/charts/create-chart.js +2 -1
- package/dist/react/charts/create-chart.js.map +1 -1
- package/dist/react/charts/heatmap/index.d.ts +2 -2
- package/dist/react/charts/line/index.d.ts +2 -2
- 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/pie/index.d.ts +3 -3
- package/dist/react/charts/radar/index.d.ts +2 -2
- package/dist/react/charts/scatter/index.d.ts +2 -2
- 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 +4 -2
- 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/hooks/use-mobile.js +3 -3
- package/dist/react/hooks/use-mobile.js.map +1 -1
- package/dist/react/index.d.ts +3 -1
- package/dist/react/index.js +3 -1
- package/dist/react/lib/utils.d.ts +7 -0
- package/dist/react/lib/utils.d.ts.map +1 -0
- package/dist/react/portal-container-context.d.ts +48 -0
- package/dist/react/portal-container-context.d.ts.map +1 -0
- package/dist/react/portal-container-context.js +51 -0
- package/dist/react/portal-container-context.js.map +1 -0
- package/dist/react/table/data-table.d.ts +2 -2
- package/dist/react/table/data-table.d.ts.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 +6 -6
- package/dist/react/ui/accordion.d.ts.map +1 -1
- package/dist/react/ui/alert-dialog.d.ts +14 -13
- package/dist/react/ui/alert-dialog.d.ts.map +1 -1
- package/dist/react/ui/alert-dialog.js +3 -1
- package/dist/react/ui/alert-dialog.js.map +1 -1
- package/dist/react/ui/alert.d.ts +5 -5
- 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 +5 -5
- package/dist/react/ui/avatar.d.ts.map +1 -1
- package/dist/react/ui/badge.d.ts +6 -6
- package/dist/react/ui/badge.d.ts.map +1 -1
- package/dist/react/ui/breadcrumb.d.ts +10 -10
- package/dist/react/ui/breadcrumb.d.ts.map +1 -1
- package/dist/react/ui/button-group.d.ts +6 -6
- package/dist/react/ui/button.d.ts +6 -6
- package/dist/react/ui/button.d.ts.map +1 -1
- package/dist/react/ui/calendar.d.ts +6 -6
- package/dist/react/ui/calendar.d.ts.map +1 -1
- package/dist/react/ui/calendar.js +3 -3
- package/dist/react/ui/calendar.js.map +1 -1
- package/dist/react/ui/card.d.ts +9 -9
- package/dist/react/ui/card.d.ts.map +1 -1
- package/dist/react/ui/carousel.d.ts +7 -7
- package/dist/react/ui/carousel.d.ts.map +1 -1
- package/dist/react/ui/carousel.js +11 -11
- package/dist/react/ui/carousel.js.map +1 -1
- package/dist/react/ui/chart.d.ts +52 -16
- package/dist/react/ui/chart.d.ts.map +1 -1
- package/dist/react/ui/chart.js +23 -7
- package/dist/react/ui/chart.js.map +1 -1
- package/dist/react/ui/checkbox.d.ts +3 -3
- package/dist/react/ui/checkbox.d.ts.map +1 -1
- package/dist/react/ui/checkbox.js +1 -1
- package/dist/react/ui/checkbox.js.map +1 -1
- package/dist/react/ui/collapsible.d.ts +4 -4
- package/dist/react/ui/command.d.ts +12 -12
- package/dist/react/ui/command.d.ts.map +1 -1
- package/dist/react/ui/context-menu.d.ts +21 -20
- package/dist/react/ui/context-menu.d.ts.map +1 -1
- package/dist/react/ui/context-menu.js +11 -6
- package/dist/react/ui/context-menu.js.map +1 -1
- package/dist/react/ui/dialog.d.ts +14 -13
- package/dist/react/ui/dialog.d.ts.map +1 -1
- package/dist/react/ui/dialog.js +3 -1
- package/dist/react/ui/dialog.js.map +1 -1
- package/dist/react/ui/drawer.d.ts +13 -12
- package/dist/react/ui/drawer.d.ts.map +1 -1
- package/dist/react/ui/drawer.js +3 -1
- package/dist/react/ui/drawer.js.map +1 -1
- package/dist/react/ui/dropdown-menu.d.ts +21 -20
- package/dist/react/ui/dropdown-menu.d.ts.map +1 -1
- package/dist/react/ui/dropdown-menu.js +12 -7
- package/dist/react/ui/dropdown-menu.js.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 +9 -9
- package/dist/react/ui/form.d.ts.map +1 -1
- package/dist/react/ui/form.js +6 -6
- package/dist/react/ui/form.js.map +1 -1
- package/dist/react/ui/hover-card.d.ts +5 -5
- package/dist/react/ui/hover-card.d.ts.map +1 -1
- package/dist/react/ui/hover-card.js +2 -0
- package/dist/react/ui/hover-card.js.map +1 -1
- package/dist/react/ui/input-group.d.ts +11 -11
- package/dist/react/ui/input-group.d.ts.map +1 -1
- package/dist/react/ui/input-otp.d.ts +8 -8
- package/dist/react/ui/input-otp.d.ts.map +1 -1
- package/dist/react/ui/input-otp.js +2 -2
- package/dist/react/ui/input-otp.js.map +1 -1
- package/dist/react/ui/input.d.ts +3 -3
- package/dist/react/ui/input.d.ts.map +1 -1
- package/dist/react/ui/item.d.ts +16 -16
- package/dist/react/ui/item.d.ts.map +1 -1
- package/dist/react/ui/kbd.d.ts +3 -3
- package/dist/react/ui/label.d.ts +3 -3
- package/dist/react/ui/label.d.ts.map +1 -1
- package/dist/react/ui/menubar.d.ts +22 -21
- package/dist/react/ui/menubar.d.ts.map +1 -1
- package/dist/react/ui/menubar.js +3 -1
- package/dist/react/ui/menubar.js.map +1 -1
- package/dist/react/ui/navigation-menu.d.ts +11 -11
- package/dist/react/ui/navigation-menu.d.ts.map +1 -1
- package/dist/react/ui/pagination.d.ts +10 -10
- package/dist/react/ui/pagination.d.ts.map +1 -1
- package/dist/react/ui/popover.d.ts +6 -6
- package/dist/react/ui/popover.d.ts.map +1 -1
- package/dist/react/ui/popover.js +11 -7
- package/dist/react/ui/popover.js.map +1 -1
- package/dist/react/ui/progress.d.ts +3 -3
- package/dist/react/ui/progress.d.ts.map +1 -1
- package/dist/react/ui/radio-group.d.ts +4 -4
- package/dist/react/ui/radio-group.d.ts.map +1 -1
- package/dist/react/ui/resizable.d.ts +6 -6
- package/dist/react/ui/resizable.d.ts.map +1 -1
- package/dist/react/ui/scroll-area.d.ts +4 -4
- package/dist/react/ui/scroll-area.d.ts.map +1 -1
- package/dist/react/ui/select.d.ts +13 -13
- package/dist/react/ui/select.d.ts.map +1 -1
- package/dist/react/ui/select.js +19 -15
- package/dist/react/ui/select.js.map +1 -1
- package/dist/react/ui/separator.d.ts +3 -3
- package/dist/react/ui/separator.d.ts.map +1 -1
- package/dist/react/ui/sheet.d.ts +11 -11
- package/dist/react/ui/sheet.d.ts.map +1 -1
- package/dist/react/ui/sheet.js +3 -1
- package/dist/react/ui/sheet.js.map +1 -1
- package/dist/react/ui/sidebar.d.ts +34 -34
- package/dist/react/ui/sidebar.d.ts.map +1 -1
- package/dist/react/ui/sidebar.js +10 -10
- package/dist/react/ui/sidebar.js.map +1 -1
- package/dist/react/ui/skeleton.d.ts +2 -2
- package/dist/react/ui/slider.d.ts +3 -3
- package/dist/react/ui/slider.d.ts.map +1 -1
- package/dist/react/ui/slider.js +2 -2
- package/dist/react/ui/slider.js.map +1 -1
- package/dist/react/ui/sonner.d.ts +2 -2
- package/dist/react/ui/spinner.d.ts +2 -2
- package/dist/react/ui/switch.d.ts +3 -3
- package/dist/react/ui/switch.d.ts.map +1 -1
- package/dist/react/ui/table.d.ts +10 -10
- package/dist/react/ui/table.d.ts.map +1 -1
- package/dist/react/ui/tabs.d.ts +6 -6
- package/dist/react/ui/tabs.d.ts.map +1 -1
- package/dist/react/ui/textarea.d.ts +3 -3
- package/dist/react/ui/textarea.d.ts.map +1 -1
- package/dist/react/ui/toggle-group.d.ts +5 -5
- package/dist/react/ui/toggle-group.d.ts.map +1 -1
- package/dist/react/ui/toggle-group.js +3 -3
- package/dist/react/ui/toggle-group.js.map +1 -1
- package/dist/react/ui/toggle.d.ts +3 -3
- package/dist/react/ui/toggle.d.ts.map +1 -1
- package/dist/react/ui/tooltip.d.ts +6 -6
- package/dist/react/ui/tooltip.d.ts.map +1 -1
- package/dist/react/ui/tooltip.js +11 -7
- package/dist/react/ui/tooltip.js.map +1 -1
- package/dist/shared/src/sql/helpers.js.map +1 -1
- package/llms.txt +89 -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`):**
|
|
451
475
|
|
|
452
|
-
|
|
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
|
|
453
482
|
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
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
|
|
483
|
+
**Development mode behavior:**
|
|
484
|
+
|
|
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, {
|
|
@@ -643,6 +668,56 @@ export function SpendChart() {
|
|
|
643
668
|
}
|
|
644
669
|
```
|
|
645
670
|
|
|
671
|
+
**Chart props reference (important):**
|
|
672
|
+
|
|
673
|
+
Charts are **self-contained ECharts components**. Configure via props, NOT children:
|
|
674
|
+
|
|
675
|
+
```tsx
|
|
676
|
+
// ✅ Correct: use props for customization
|
|
677
|
+
<BarChart
|
|
678
|
+
queryKey="sales_by_region"
|
|
679
|
+
parameters={{}}
|
|
680
|
+
xKey="region" // X-axis field
|
|
681
|
+
yKey={["revenue", "expenses"]} // Y-axis field(s) - string or string[]
|
|
682
|
+
colors={['#40d1f5', '#4462c9']} // Custom colors
|
|
683
|
+
stacked // Stack bars (BarChart, AreaChart)
|
|
684
|
+
orientation="horizontal" // "vertical" (default) | "horizontal"
|
|
685
|
+
showLegend // Show legend
|
|
686
|
+
height={400} // Height in pixels (default: 300)
|
|
687
|
+
/>
|
|
688
|
+
|
|
689
|
+
<LineChart
|
|
690
|
+
queryKey="trend_data"
|
|
691
|
+
parameters={{}}
|
|
692
|
+
xKey="date"
|
|
693
|
+
yKey="value"
|
|
694
|
+
smooth // Smooth curves (default: true)
|
|
695
|
+
showSymbol={false} // Hide data point markers
|
|
696
|
+
/>
|
|
697
|
+
```
|
|
698
|
+
|
|
699
|
+
**❌ CRITICAL: Charts do NOT accept Recharts children**
|
|
700
|
+
|
|
701
|
+
```tsx
|
|
702
|
+
// ❌ WRONG - AppKit charts are NOT Recharts wrappers
|
|
703
|
+
import { BarChart } from "@databricks/appkit-ui/react";
|
|
704
|
+
import { Bar, XAxis, YAxis, CartesianGrid } from "recharts";
|
|
705
|
+
|
|
706
|
+
<BarChart queryKey="data" parameters={{}}>
|
|
707
|
+
<CartesianGrid /> // ❌ This will cause TypeScript errors
|
|
708
|
+
<XAxis dataKey="x" /> // ❌ Not supported
|
|
709
|
+
<Bar dataKey="y" /> // ❌ Not supported
|
|
710
|
+
</BarChart>
|
|
711
|
+
|
|
712
|
+
// ✅ CORRECT - use props instead
|
|
713
|
+
<BarChart
|
|
714
|
+
queryKey="data"
|
|
715
|
+
parameters={{}}
|
|
716
|
+
xKey="x"
|
|
717
|
+
yKey="y"
|
|
718
|
+
/>
|
|
719
|
+
```
|
|
720
|
+
|
|
646
721
|
### SQL helpers (`sql.*`)
|
|
647
722
|
|
|
648
723
|
Use these to build typed parameters (they return marker objects: `{ __sql_type, value }`):
|
|
@@ -1144,6 +1219,7 @@ env:
|
|
|
1144
1219
|
- `useMemo` wraps parameters objects
|
|
1145
1220
|
- Loading/error/empty states are explicit
|
|
1146
1221
|
- Charts use `format="auto"` unless you have a reason to force `"json"`/`"arrow"`
|
|
1222
|
+
- Charts use props (`xKey`, `yKey`, `colors`) NOT children (they're ECharts-based, not Recharts)
|
|
1147
1223
|
- If using tooltips: root is wrapped with `<TooltipProvider>`
|
|
1148
1224
|
|
|
1149
1225
|
- **Never**
|
|
@@ -1151,4 +1227,5 @@ env:
|
|
|
1151
1227
|
- Don't pass untyped raw params for annotated queries
|
|
1152
1228
|
- Don't ignore `createApp()`'s promise
|
|
1153
1229
|
- Don't invent UI components not listed in this file
|
|
1230
|
+
- Don't pass Recharts children (`<Bar>`, `<XAxis>`, etc.) to AppKit chart components
|
|
1154
1231
|
|
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`):**
|
|
451
475
|
|
|
452
|
-
|
|
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
|
|
453
482
|
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
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
|
|
483
|
+
**Development mode behavior:**
|
|
484
|
+
|
|
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, {
|
|
@@ -643,6 +668,56 @@ export function SpendChart() {
|
|
|
643
668
|
}
|
|
644
669
|
```
|
|
645
670
|
|
|
671
|
+
**Chart props reference (important):**
|
|
672
|
+
|
|
673
|
+
Charts are **self-contained ECharts components**. Configure via props, NOT children:
|
|
674
|
+
|
|
675
|
+
```tsx
|
|
676
|
+
// ✅ Correct: use props for customization
|
|
677
|
+
<BarChart
|
|
678
|
+
queryKey="sales_by_region"
|
|
679
|
+
parameters={{}}
|
|
680
|
+
xKey="region" // X-axis field
|
|
681
|
+
yKey={["revenue", "expenses"]} // Y-axis field(s) - string or string[]
|
|
682
|
+
colors={['#40d1f5', '#4462c9']} // Custom colors
|
|
683
|
+
stacked // Stack bars (BarChart, AreaChart)
|
|
684
|
+
orientation="horizontal" // "vertical" (default) | "horizontal"
|
|
685
|
+
showLegend // Show legend
|
|
686
|
+
height={400} // Height in pixels (default: 300)
|
|
687
|
+
/>
|
|
688
|
+
|
|
689
|
+
<LineChart
|
|
690
|
+
queryKey="trend_data"
|
|
691
|
+
parameters={{}}
|
|
692
|
+
xKey="date"
|
|
693
|
+
yKey="value"
|
|
694
|
+
smooth // Smooth curves (default: true)
|
|
695
|
+
showSymbol={false} // Hide data point markers
|
|
696
|
+
/>
|
|
697
|
+
```
|
|
698
|
+
|
|
699
|
+
**❌ CRITICAL: Charts do NOT accept Recharts children**
|
|
700
|
+
|
|
701
|
+
```tsx
|
|
702
|
+
// ❌ WRONG - AppKit charts are NOT Recharts wrappers
|
|
703
|
+
import { BarChart } from "@databricks/appkit-ui/react";
|
|
704
|
+
import { Bar, XAxis, YAxis, CartesianGrid } from "recharts";
|
|
705
|
+
|
|
706
|
+
<BarChart queryKey="data" parameters={{}}>
|
|
707
|
+
<CartesianGrid /> // ❌ This will cause TypeScript errors
|
|
708
|
+
<XAxis dataKey="x" /> // ❌ Not supported
|
|
709
|
+
<Bar dataKey="y" /> // ❌ Not supported
|
|
710
|
+
</BarChart>
|
|
711
|
+
|
|
712
|
+
// ✅ CORRECT - use props instead
|
|
713
|
+
<BarChart
|
|
714
|
+
queryKey="data"
|
|
715
|
+
parameters={{}}
|
|
716
|
+
xKey="x"
|
|
717
|
+
yKey="y"
|
|
718
|
+
/>
|
|
719
|
+
```
|
|
720
|
+
|
|
646
721
|
### SQL helpers (`sql.*`)
|
|
647
722
|
|
|
648
723
|
Use these to build typed parameters (they return marker objects: `{ __sql_type, value }`):
|
|
@@ -1144,6 +1219,7 @@ env:
|
|
|
1144
1219
|
- `useMemo` wraps parameters objects
|
|
1145
1220
|
- Loading/error/empty states are explicit
|
|
1146
1221
|
- Charts use `format="auto"` unless you have a reason to force `"json"`/`"arrow"`
|
|
1222
|
+
- Charts use props (`xKey`, `yKey`, `colors`) NOT children (they're ECharts-based, not Recharts)
|
|
1147
1223
|
- If using tooltips: root is wrapped with `<TooltipProvider>`
|
|
1148
1224
|
|
|
1149
1225
|
- **Never**
|
|
@@ -1151,4 +1227,5 @@ env:
|
|
|
1151
1227
|
- Don't pass untyped raw params for annotated queries
|
|
1152
1228
|
- Don't ignore `createApp()`'s promise
|
|
1153
1229
|
- Don't invent UI components not listed in this file
|
|
1230
|
+
- Don't pass Recharts children (`<Bar>`, `<XAxis>`, etc.) to AppKit chart components
|
|
1154
1231
|
|
package/NOTICE.md
CHANGED
|
@@ -6,6 +6,7 @@ This Software contains code from the following open source projects:
|
|
|
6
6
|
|
|
7
7
|
| Name | Installed version | License | Code |
|
|
8
8
|
| :--------------- | :---------------- | :----------- | :--------------------------------------------------- |
|
|
9
|
+
| [@ast-grep/napi](https://www.npmjs.com/package/@ast-grep/napi) | 0.37.0 | MIT | https://ast-grep.github.io |
|
|
9
10
|
| [@hookform/resolvers](https://www.npmjs.com/package/@hookform/resolvers) | 5.2.2 | MIT | https://react-hook-form.com |
|
|
10
11
|
| [@opentelemetry/api](https://www.npmjs.com/package/@opentelemetry/api) | 1.9.0 | Apache-2.0 | https://github.com/open-telemetry/opentelemetry-js/tree/main/api |
|
|
11
12
|
| [@opentelemetry/api-logs](https://www.npmjs.com/package/@opentelemetry/api-logs) | 0.208.0 | Apache-2.0 | https://github.com/open-telemetry/opentelemetry-js/tree/main/experimental/packages/api-logs |
|
|
@@ -49,6 +50,7 @@ This Software contains code from the following open source projects:
|
|
|
49
50
|
| [@radix-ui/react-tooltip](https://www.npmjs.com/package/@radix-ui/react-tooltip) | 1.2.8 | MIT | https://radix-ui.com/primitives |
|
|
50
51
|
| [@tanstack/react-table](https://www.npmjs.com/package/@tanstack/react-table) | 8.21.3 | MIT | https://tanstack.com/table |
|
|
51
52
|
| [@tanstack/react-virtual](https://www.npmjs.com/package/@tanstack/react-virtual) | 3.13.12 | MIT | https://tanstack.com/virtual |
|
|
53
|
+
| [@types/semver](https://www.npmjs.com/package/@types/semver) | 7.7.1 | MIT | https://github.com/DefinitelyTyped/DefinitelyTyped/tree/master/types/semver |
|
|
52
54
|
| [apache-arrow](https://www.npmjs.com/package/apache-arrow) | 21.1.0 | Apache-2.0 | https://arrow.apache.org/js/ |
|
|
53
55
|
| [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
56
|
| [clsx](https://www.npmjs.com/package/clsx) | 2.1.1 | MIT | https://github.com/lukeed/clsx#readme |
|
|
@@ -62,10 +64,12 @@ This Software contains code from the following open source projects:
|
|
|
62
64
|
| [input-otp](https://www.npmjs.com/package/input-otp) | 1.4.2 | MIT | https://input-otp.rodz.dev/ |
|
|
63
65
|
| [lucide-react](https://www.npmjs.com/package/lucide-react) | 0.554.0 | ISC | https://lucide.dev |
|
|
64
66
|
| [next-themes](https://www.npmjs.com/package/next-themes) | 0.4.6 | MIT | https://github.com/pacocoursey/next-themes#readme |
|
|
67
|
+
| [obug](https://www.npmjs.com/package/obug) | 2.1.1 | MIT | https://github.com/sxzz/obug#readme |
|
|
65
68
|
| [pg](https://www.npmjs.com/package/pg) | 8.16.3 | MIT | https://github.com/brianc/node-postgres |
|
|
66
69
|
| [react-day-picker](https://www.npmjs.com/package/react-day-picker) | 9.12.0 | MIT | https://daypicker.dev |
|
|
67
70
|
| [react-hook-form](https://www.npmjs.com/package/react-hook-form) | 7.68.0 | MIT | https://react-hook-form.com |
|
|
68
71
|
| [react-resizable-panels](https://www.npmjs.com/package/react-resizable-panels) | 3.0.6 | MIT | https://github.com/bvaughn/react-resizable-panels#readme |
|
|
72
|
+
| [semver](https://www.npmjs.com/package/semver) | 6.3.1, 7.7.3 | ISC | https://github.com/npm/node-semver#readme |
|
|
69
73
|
| [sonner](https://www.npmjs.com/package/sonner) | 2.0.7 | MIT | https://sonner.emilkowal.ski/ |
|
|
70
74
|
| [tailwind-merge](https://www.npmjs.com/package/tailwind-merge) | 3.4.0 | MIT | https://github.com/dcastil/tailwind-merge |
|
|
71
75
|
| [vaul](https://www.npmjs.com/package/vaul) | 1.1.2 | MIT | https://vaul.emilkowal.ski/ |
|
package/README.md
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
# AppKit
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
3
|
+
Build Databricks Apps faster with our brand-new Node.js + React SDK. Built for humans and AI.
|
|
4
|
+
|
|
5
|
+
> [!WARNING] PREVIEW - NOT FOR PRODUCTION USE
|
|
5
6
|
>
|
|
6
7
|
> **This SDK is in preview and is subject to change without notice.**
|
|
7
8
|
>
|
|
@@ -11,25 +12,30 @@
|
|
|
11
12
|
> - 📝 **Use for development and testing only**
|
|
12
13
|
>
|
|
13
14
|
|
|
14
|
-
##
|
|
15
|
+
## Introduction
|
|
15
16
|
|
|
16
|
-
|
|
17
|
+
AppKit is a TypeScript SDK for building production-ready Databricks applications with a plugin-based architecture. It provides opinionated defaults, built-in observability, and seamless integration with Databricks services.
|
|
17
18
|
|
|
18
|
-
|
|
19
|
+
AppKit simplifies building data applications on Databricks by providing:
|
|
20
|
+
|
|
21
|
+
- **Plugin architecture**: Modular design with built-in server and analytics plugins
|
|
22
|
+
- **Type safety**: End-to-end TypeScript with automatic query type generation
|
|
23
|
+
- **Production-ready features**: Built-in caching, telemetry, retry logic, and error handling
|
|
24
|
+
- **Developer experience**: Remote hot reload, file-based queries, optimized for AI-assisted development
|
|
25
|
+
- **Databricks native**: Seamless integration with SQL Warehouses, Unity Catalog, and other workspace resources
|
|
19
26
|
|
|
20
|
-
|
|
27
|
+
## Getting started
|
|
21
28
|
|
|
22
|
-
|
|
29
|
+
Follow the [Getting Started](https://databricks.github.io/appkit/docs/) guide to get started with AppKit.
|
|
23
30
|
|
|
24
|
-
|
|
25
|
-
# From root
|
|
26
|
-
pnpm docs:dev # Start dev server
|
|
27
|
-
pnpm docs:build # Build docs
|
|
28
|
-
pnpm docs:serve # Serve built docs
|
|
29
|
-
```
|
|
31
|
+
## Documentation
|
|
30
32
|
|
|
31
|
-
|
|
33
|
+
📖 For full AppKit documentation, visit the [AppKit Documentation](https://databricks.github.io/appkit/) website.
|
|
32
34
|
|
|
33
35
|
👉 For AI/code assistants:
|
|
34
36
|
- Use [llms-compact.txt](./llms-compact.txt) for quick usage patterns.
|
|
35
|
-
- See [llms.txt](./llms.txt) for full guidance and anti-patterns.
|
|
37
|
+
- See [llms.txt](./llms.txt) for full guidance and anti-patterns.
|
|
38
|
+
|
|
39
|
+
## Contributing
|
|
40
|
+
|
|
41
|
+
See [CONTRIBUTING.md](CONTRIBUTING.md) for development setup and contribution guidelines.
|
|
@@ -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,5 +1,5 @@
|
|
|
1
1
|
import { AreaChartProps } from "../types.js";
|
|
2
|
-
import * as
|
|
2
|
+
import * as react_jsx_runtime275 from "react/jsx-runtime";
|
|
3
3
|
|
|
4
4
|
//#region src/react/charts/area/index.d.ts
|
|
5
5
|
|
|
@@ -25,7 +25,7 @@ import * as react_jsx_runtime273 from "react/jsx-runtime";
|
|
|
25
25
|
* ```
|
|
26
26
|
*/
|
|
27
27
|
declare const AreaChart: {
|
|
28
|
-
(props: AreaChartProps):
|
|
28
|
+
(props: AreaChartProps): react_jsx_runtime275.JSX.Element;
|
|
29
29
|
displayName: string;
|
|
30
30
|
};
|
|
31
31
|
//#endregion
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { BarChartProps } from "../types.js";
|
|
2
|
-
import * as
|
|
2
|
+
import * as react_jsx_runtime276 from "react/jsx-runtime";
|
|
3
3
|
|
|
4
4
|
//#region src/react/charts/bar/index.d.ts
|
|
5
5
|
|
|
@@ -35,7 +35,7 @@ import * as react_jsx_runtime274 from "react/jsx-runtime";
|
|
|
35
35
|
* ```
|
|
36
36
|
*/
|
|
37
37
|
declare const BarChart: {
|
|
38
|
-
(props: BarChartProps):
|
|
38
|
+
(props: BarChartProps): react_jsx_runtime276.JSX.Element;
|
|
39
39
|
displayName: string;
|
|
40
40
|
};
|
|
41
41
|
//#endregion
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { ChartColorPalette, ChartData, ChartType, Orientation } from "./types.js";
|
|
2
|
-
import * as
|
|
2
|
+
import * as react_jsx_runtime283 from "react/jsx-runtime";
|
|
3
3
|
|
|
4
4
|
//#region src/react/charts/base.d.ts
|
|
5
5
|
interface BaseChartProps {
|
|
@@ -83,7 +83,7 @@ declare function BaseChart({
|
|
|
83
83
|
max,
|
|
84
84
|
options: customOptions,
|
|
85
85
|
className
|
|
86
|
-
}: BaseChartProps):
|
|
86
|
+
}: BaseChartProps): react_jsx_runtime283.JSX.Element;
|
|
87
87
|
//#endregion
|
|
88
88
|
export { BaseChart, BaseChartProps };
|
|
89
89
|
//# sourceMappingURL=base.d.ts.map
|
|
@@ -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"}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { ChartType, UnifiedChartProps } from "./types.js";
|
|
2
|
-
import * as
|
|
2
|
+
import * as react_jsx_runtime284 from "react/jsx-runtime";
|
|
3
3
|
|
|
4
4
|
//#region src/react/charts/create-chart.d.ts
|
|
5
5
|
|
|
@@ -18,7 +18,7 @@ import * as react_jsx_runtime282 from "react/jsx-runtime";
|
|
|
18
18
|
* ```
|
|
19
19
|
*/
|
|
20
20
|
declare function createChart<TProps extends UnifiedChartProps>(chartType: ChartType, displayName: string): {
|
|
21
|
-
(props: TProps):
|
|
21
|
+
(props: TProps): react_jsx_runtime284.JSX.Element;
|
|
22
22
|
displayName: string;
|
|
23
23
|
};
|
|
24
24
|
//#endregion
|