Skip to content
Open
21 changes: 21 additions & 0 deletions apps/dev-playground/client/src/routeTree.gen.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import { Route as ReconnectRouteRouteImport } from './routes/reconnect.route'
import { Route as LakebaseRouteRouteImport } from './routes/lakebase.route'
import { Route as GenieRouteRouteImport } from './routes/genie.route'
import { Route as DataVisualizationRouteRouteImport } from './routes/data-visualization.route'
import { Route as ChartInferenceRouteRouteImport } from './routes/chart-inference.route'
import { Route as ArrowAnalyticsRouteRouteImport } from './routes/arrow-analytics.route'
import { Route as AnalyticsRouteRouteImport } from './routes/analytics.route'
import { Route as IndexRouteImport } from './routes/index'
Expand Down Expand Up @@ -55,6 +56,11 @@ const DataVisualizationRouteRoute = DataVisualizationRouteRouteImport.update({
path: '/data-visualization',
getParentRoute: () => rootRouteImport,
} as any)
const ChartInferenceRouteRoute = ChartInferenceRouteRouteImport.update({
id: '/chart-inference',
path: '/chart-inference',
getParentRoute: () => rootRouteImport,
} as any)
const ArrowAnalyticsRouteRoute = ArrowAnalyticsRouteRouteImport.update({
id: '/arrow-analytics',
path: '/arrow-analytics',
Expand All @@ -75,6 +81,7 @@ export interface FileRoutesByFullPath {
'/': typeof IndexRoute
'/analytics': typeof AnalyticsRouteRoute
'/arrow-analytics': typeof ArrowAnalyticsRouteRoute
'/chart-inference': typeof ChartInferenceRouteRoute
'/data-visualization': typeof DataVisualizationRouteRoute
'/genie': typeof GenieRouteRoute
'/lakebase': typeof LakebaseRouteRoute
Expand All @@ -87,6 +94,7 @@ export interface FileRoutesByTo {
'/': typeof IndexRoute
'/analytics': typeof AnalyticsRouteRoute
'/arrow-analytics': typeof ArrowAnalyticsRouteRoute
'/chart-inference': typeof ChartInferenceRouteRoute
'/data-visualization': typeof DataVisualizationRouteRoute
'/genie': typeof GenieRouteRoute
'/lakebase': typeof LakebaseRouteRoute
Expand All @@ -100,6 +108,7 @@ export interface FileRoutesById {
'/': typeof IndexRoute
'/analytics': typeof AnalyticsRouteRoute
'/arrow-analytics': typeof ArrowAnalyticsRouteRoute
'/chart-inference': typeof ChartInferenceRouteRoute
'/data-visualization': typeof DataVisualizationRouteRoute
'/genie': typeof GenieRouteRoute
'/lakebase': typeof LakebaseRouteRoute
Expand All @@ -114,6 +123,7 @@ export interface FileRouteTypes {
| '/'
| '/analytics'
| '/arrow-analytics'
| '/chart-inference'
| '/data-visualization'
| '/genie'
| '/lakebase'
Expand All @@ -126,6 +136,7 @@ export interface FileRouteTypes {
| '/'
| '/analytics'
| '/arrow-analytics'
| '/chart-inference'
| '/data-visualization'
| '/genie'
| '/lakebase'
Expand All @@ -138,6 +149,7 @@ export interface FileRouteTypes {
| '/'
| '/analytics'
| '/arrow-analytics'
| '/chart-inference'
| '/data-visualization'
| '/genie'
| '/lakebase'
Expand All @@ -151,6 +163,7 @@ export interface RootRouteChildren {
IndexRoute: typeof IndexRoute
AnalyticsRouteRoute: typeof AnalyticsRouteRoute
ArrowAnalyticsRouteRoute: typeof ArrowAnalyticsRouteRoute
ChartInferenceRouteRoute: typeof ChartInferenceRouteRoute
DataVisualizationRouteRoute: typeof DataVisualizationRouteRoute
GenieRouteRoute: typeof GenieRouteRoute
LakebaseRouteRoute: typeof LakebaseRouteRoute
Expand Down Expand Up @@ -211,6 +224,13 @@ declare module '@tanstack/react-router' {
preLoaderRoute: typeof DataVisualizationRouteRouteImport
parentRoute: typeof rootRouteImport
}
'/chart-inference': {
id: '/chart-inference'
path: '/chart-inference'
fullPath: '/chart-inference'
preLoaderRoute: typeof ChartInferenceRouteRouteImport
parentRoute: typeof rootRouteImport
}
'/arrow-analytics': {
id: '/arrow-analytics'
path: '/arrow-analytics'
Expand Down Expand Up @@ -239,6 +259,7 @@ const rootRouteChildren: RootRouteChildren = {
IndexRoute: IndexRoute,
AnalyticsRouteRoute: AnalyticsRouteRoute,
ArrowAnalyticsRouteRoute: ArrowAnalyticsRouteRoute,
ChartInferenceRouteRoute: ChartInferenceRouteRoute,
DataVisualizationRouteRoute: DataVisualizationRouteRoute,
GenieRouteRoute: GenieRouteRoute,
LakebaseRouteRoute: LakebaseRouteRoute,
Expand Down
8 changes: 8 additions & 0 deletions apps/dev-playground/client/src/routes/__root.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,14 @@ function RootComponent() {
Genie
</Button>
</Link>
<Link to="/chart-inference" className="no-underline">
<Button
variant="ghost"
className="text-foreground hover:text-secondary-foreground"
>
Chart Inference
</Button>
</Link>
<ThemeSelector />
</div>
</nav>
Expand Down
280 changes: 280 additions & 0 deletions apps/dev-playground/client/src/routes/chart-inference.route.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,280 @@
import {
Card,
GenieQueryVisualization,
inferChartType,
transformGenieData,
} from "@databricks/appkit-ui/react";
import { createFileRoute } from "@tanstack/react-router";
import { useMemo } from "react";

export const Route = createFileRoute("/chart-inference")({
component: ChartInferenceRoute,
});

// ---------------------------------------------------------------------------
// Helper to build a Genie-shaped statement_response from simple definitions
// ---------------------------------------------------------------------------

interface SampleColumn {
name: string;
type_name: string;
}

function makeStatementResponse(
columns: SampleColumn[],
rows: (string | null)[][],
) {
return {
manifest: { schema: { columns } },
result: { data_array: rows },
};
}

// ---------------------------------------------------------------------------
// Sample datasets — one per inference rule
// ---------------------------------------------------------------------------

const SAMPLES: {
title: string;
description: string;
expected: string;
data: ReturnType<typeof makeStatementResponse>;
}[] = [
{
title: "Timeseries (date + revenue)",
description: "Rule 1: DATE + numeric → line chart",
expected: "line",
data: makeStatementResponse(
[
{ name: "date", type_name: "DATE" },
{ name: "revenue", type_name: "DECIMAL" },
],
[
["2024-01-01", "12000"],
["2024-02-01", "15500"],
["2024-03-01", "13200"],
["2024-04-01", "17800"],
["2024-05-01", "19200"],
["2024-06-01", "21000"],
["2024-07-01", "18500"],
["2024-08-01", "22100"],
["2024-09-01", "24500"],
["2024-10-01", "23000"],
["2024-11-01", "26800"],
["2024-12-01", "29000"],
],
),
},
{
title: "Few categories (region + sales)",
description: "Rule 2: STRING + 1 numeric, 3 categories → pie chart",
expected: "pie",
data: makeStatementResponse(
[
{ name: "region", type_name: "STRING" },
{ name: "sales", type_name: "DECIMAL" },
],
[
["North America", "45000"],
["Europe", "32000"],
["Asia Pacific", "28000"],
],
),
},
{
title: "Moderate categories (product + revenue)",
description: "Rule 3: STRING + 1 numeric, 15 categories → bar chart",
expected: "bar",
data: makeStatementResponse(
[
{ name: "product", type_name: "STRING" },
{ name: "revenue", type_name: "DECIMAL" },
],
Array.from({ length: 15 }, (_, i) => [
`Product ${String.fromCharCode(65 + i)}`,
String(Math.round(5000 + Math.sin(i) * 3000)),
]),
),
},
{
title: "Many categories (city + population)",
description: "Rule 4: STRING + 1 numeric, 150 categories → line chart",
expected: "line",
data: makeStatementResponse(
[
{ name: "city", type_name: "STRING" },
{ name: "population", type_name: "INT" },
],
Array.from({ length: 150 }, (_, i) => [
`City ${i + 1}`,
String(Math.round(10000 + Math.random() * 90000)),
]),
),
},
{
title: "Multi-series timeseries (month + revenue + cost)",
description: "Rule 1: DATE + multiple numerics → line chart",
expected: "line",
data: makeStatementResponse(
[
{ name: "month", type_name: "DATE" },
{ name: "revenue", type_name: "DECIMAL" },
{ name: "cost", type_name: "DECIMAL" },
],
[
["2024-01-01", "12000", "8000"],
["2024-02-01", "15500", "9200"],
["2024-03-01", "13200", "8800"],
["2024-04-01", "17800", "10500"],
["2024-05-01", "19200", "11000"],
["2024-06-01", "21000", "12500"],
],
),
},
{
title: "Grouped bar (department + budget + actual)",
description: "Rule 5: STRING + N numerics, 8 categories → bar chart",
expected: "bar",
data: makeStatementResponse(
[
{ name: "department", type_name: "STRING" },
{ name: "budget", type_name: "DECIMAL" },
{ name: "actual", type_name: "DECIMAL" },
],
[
["Engineering", "500000", "480000"],
["Marketing", "300000", "320000"],
["Sales", "400000", "410000"],
["Support", "200000", "190000"],
["HR", "150000", "145000"],
["Finance", "180000", "175000"],
["Legal", "120000", "115000"],
["Operations", "250000", "240000"],
],
),
},
{
title: "Scatter (height + weight)",
description: "Rule 7: 2 numerics only → scatter chart",
expected: "scatter",
data: makeStatementResponse(
[
{ name: "height_cm", type_name: "DOUBLE" },
{ name: "weight_kg", type_name: "DOUBLE" },
],
Array.from({ length: 30 }, (_, i) => [
String(150 + i * 1.2),
String(Math.round(45 + i * 1.5 + (Math.random() - 0.5) * 10)),
]),
),
},
{
title: "Single row (name + value)",
description: "Skip: < 2 rows → table only",
expected: "none (table only)",
data: makeStatementResponse(
[
{ name: "metric", type_name: "STRING" },
{ name: "value", type_name: "DECIMAL" },
],
[["Total Revenue", "125000"]],
),
},
{
title: "All strings (first_name + last_name + city)",
description: "Skip: no numeric columns → table only",
expected: "none (table only)",
data: makeStatementResponse(
[
{ name: "first_name", type_name: "STRING" },
{ name: "last_name", type_name: "STRING" },
{ name: "city", type_name: "STRING" },
],
[
["Alice", "Smith", "New York"],
["Bob", "Jones", "London"],
["Carol", "Lee", "Tokyo"],
],
),
},
];

// ---------------------------------------------------------------------------
// Per-sample card component
// ---------------------------------------------------------------------------

function SampleCard({
title,
description,
expected,
data,
}: (typeof SAMPLES)[number]) {
const transformed = useMemo(() => transformGenieData(data), [data]);
const inference = useMemo(
() =>
transformed
? inferChartType(transformed.rows, transformed.columns)
: null,
[transformed],
);

return (
<Card className="p-6 flex flex-col gap-4">
<div>
<h3 className="text-lg font-semibold">{title}</h3>
<p className="text-sm text-muted-foreground">{description}</p>
</div>

<div className="flex gap-4 text-xs">
<span>
<strong>Expected:</strong> {expected}
</span>
<span>
<strong>Inferred:</strong>{" "}
{inference
? `${inference.chartType} (x: ${inference.xKey}, y: ${Array.isArray(inference.yKey) ? inference.yKey.join(", ") : inference.yKey})`
: "null (no chart)"}
</span>
<span>
<strong>Rows:</strong> {transformed?.rows.length ?? 0}
</span>
<span>
<strong>Columns:</strong> {transformed?.columns.length ?? 0}
</span>
</div>

<GenieQueryVisualization data={data} />
</Card>
);
}

// ---------------------------------------------------------------------------
// Route component
// ---------------------------------------------------------------------------

function ChartInferenceRoute() {
return (
<div className="min-h-screen bg-background">
<main className="max-w-5xl mx-auto px-6 py-12">
<div className="flex flex-col gap-6">
<div>
<h1 className="text-3xl font-bold tracking-tight text-foreground">
Chart Inference Demo
</h1>
<p className="text-muted-foreground mt-2">
Sample datasets exercising each Genie chart inference rule. Each
card shows the inferred chart type, axes, and the rendered
visualization.
</p>
</div>

<div className="flex flex-col gap-6">
{SAMPLES.map((sample) => (
<SampleCard key={sample.title} {...sample} />
))}
</div>
</div>
</main>
</div>
);
}
Loading