@duckviz/dashboard
Embeddable dashboard component with D3 chart rendering and draggable grid layout.
@duckviz/dashboard provides the <Dashboard /> component — a draggable, resizable widget grid that renders 80+ D3 chart types via @duckviz/widgets. It auto-wires to the DuckDB context from @duckviz/db so each widget's dataKey runs as SQL. Every widget is wrapped in a ChartErrorBoundary so one bad query can't kill the grid.
Installation
npm install @duckviz/dashboard @duckviz/db @duckviz/ui @duckviz/widgets
npm install react react-dom d3Quick start
import { DuckvizDBProvider } from "@duckviz/db";
import { Dashboard, type DashboardConfig } from "@duckviz/dashboard";
const config: DashboardConfig = {
name: "Sales Dashboard",
widgets: [
{
id: "w1",
type: "bar",
title: "Revenue by Category",
description: "Top 10 categories",
dataKey: "SELECT category, SUM(amount) AS value FROM t_sales GROUP BY category ORDER BY value DESC LIMIT 10",
},
{
id: "w2",
type: "line",
title: "Monthly Trend",
description: "Revenue over time",
dataKey: "SELECT month AS category, total AS value FROM t_monthly",
},
],
};
function App() {
return (
<DuckvizDBProvider persistence>
<Dashboard config={config} />
</DuckvizDBProvider>
);
}<Dashboard> props
Prop
Type
Auto-ingest with datasets
Skip the manual db.ingest() call by passing your row data directly:
import { Dashboard } from "@duckviz/dashboard";
import type { DuckvizDataset } from "@duckviz/db";
const datasets: DuckvizDataset[] = [
{ name: "Sales", data: salesRows }, // derives t_sales
];
<Dashboard config={config} datasets={datasets} />Widgets render only after ingestion finishes. Tables are dropped on unmount by default.
Pre-fetch async data — don't pass a Promise or loader
<Dashboard> (and <ReportBuilder>, <DeckBuilder>) accept a resolved array of DuckvizDataset. The async loader mode is Explorer-only — Dashboard, Report, and Deck silently render empty if you pass a Promise, an async function, or a loader that returns datasets without an activeId. Await your fetch first, then pass the array.
DashboardConfig
interface DashboardConfig {
name?: string;
description?: string;
dataset?: string;
widgets: WidgetConfig[];
grid?: {
cols?: number; // Default: 12
rowHeight?: number; // Default: 30
margin?: [number, number]; // Default: [8, 8]
};
theme?: ThemeConfig;
}WidgetConfig
Each widget maps a SQL query to a chart type:
interface WidgetConfig {
id: string; // Unique identifier
type: string; // Chart type from the registry (e.g. "bar", "scatter", "heatmap")
title: string; // Displayed above the chart
description?: string; // Subtitle text
dataKey: string; // DuckDB SQL query to execute
config?: Record<string, unknown>; // Chart-specific options
layout?: {
x: number; // Grid column position
y: number; // Grid row position
w: number; // Width in grid columns
h: number; // Height in grid rows
};
}The dataKey field contains a SQL query with standardized column aliases that the chart component expects. For example, a bar chart expects columns named category and value.
Auto-wired data provider
<Dashboard> automatically looks for a DuckvizDBProvider in the component tree using useDuckDBOrNull(). When found, it builds a data provider that runs each widget's dataKey as a SQL query against DuckDB.
This means you don't need to pass data manually — just wrap your dashboard in a DuckvizDBProvider and the queries execute automatically.
renderWidgetActions slot
Add custom actions to each widget's toolbar:
import { Dashboard, type WidgetActionContext } from "@duckviz/dashboard";
<Dashboard
config={config}
renderWidgetActions={(ctx: WidgetActionContext) => (
<>
<button onClick={() => ctx.refresh()}>Refresh</button>
<button onClick={() => handleDelete(ctx.widgetId)}>Delete</button>
</>
)}
/>The WidgetActionContext provides:
widgetId— the widget's IDrefresh()— re-run the SQL query and re-render the chart
Grid layout
The dashboard uses react-grid-layout v2 with a 12-column grid. Widgets can be:
- Dragged to any position
- Resized from the bottom-right corner
- Auto-arranged — overlapping widgets push others down
If no layout is specified in WidgetConfig, widgets are auto-arranged.
ThemeConfig
interface ThemeConfig {
mode?: "light" | "dark";
variables?: Partial<ThemeVariables>;
}See @duckviz/ui theming for the full ThemeVariables reference.
Converting RecommendedWidget to WidgetConfig
When you get widgets from the Explorer (or from @duckviz/sdk), convert them to WidgetConfig:
import type { RecommendedWidget } from "@duckviz/explorer";
import type { WidgetConfig } from "@duckviz/dashboard";
function toWidgetConfig(widget: RecommendedWidget, id: string): WidgetConfig {
return {
id,
type: widget.type,
title: widget.title,
description: widget.description,
dataKey: widget.duckdbQuery,
config: widget.config,
};
}