Dashboards
Create interactive data visualizations using Vega-Lite, a high-level grammar for building charts. Dashboards are backed by SQL queries and support cross-panel interactivity.
Dashboard Structure
A dashboard consists of:
- query: Single SQL query returning all data needed
- spec: Vega-Lite specification defining the visualization
The system automatically injects data and schema - never include $schema or data in your spec.
Workflow
Always follow this workflow:
- Test query - Verify SQL returns expected data
- Preview - Use
data_preview_dashboardto iterate - Verify - Check labels, colors, layout
- Create - Save with
data_create_dashboard - Render - Generate PNG with
data_render_dashboard
Chart Types
Bar Chart
{
"mark": "bar",
"encoding": {
"x": {"field": "category", "type": "nominal"},
"y": {"field": "value", "type": "quantitative"}
}
}
Line Chart
{
"mark": {"type": "line", "point": true},
"encoding": {
"x": {"field": "date", "type": "temporal"},
"y": {"field": "value", "type": "quantitative"}
}
}
Pie/Donut Chart
{
"mark": {"type": "arc", "innerRadius": 50},
"encoding": {
"theta": {"field": "value", "type": "quantitative"},
"color": {"field": "category", "type": "nominal"}
}
}
Scatter Plot
{
"mark": "point",
"encoding": {
"x": {"field": "x_value", "type": "quantitative"},
"y": {"field": "y_value", "type": "quantitative"},
"color": {"field": "category", "type": "nominal"},
"size": {"field": "weight", "type": "quantitative"}
}
}
Heatmap
{
"mark": "rect",
"encoding": {
"x": {"field": "x_category", "type": "nominal"},
"y": {"field": "y_category", "type": "nominal"},
"color": {"field": "value", "type": "quantitative", "scale": {"scheme": "blues"}}
}
}
Area Chart
{
"mark": "area",
"encoding": {
"x": {"field": "date", "type": "temporal"},
"y": {"field": "value", "type": "quantitative"}
}
}
Field Types
| Type | Description | Examples |
|---|---|---|
quantitative | Numbers (continuous) | Sales, temperature, count |
nominal | Categories (unordered) | Region, product type |
ordinal | Categories (ordered) | Rating, size (S/M/L) |
temporal | Dates/times | Order date, timestamp |
Layout Options
Vertical Stack (vconcat)
{
"vconcat": [
{"title": "Chart 1", "mark": "bar", "encoding": {...}},
{"title": "Chart 2", "mark": "line", "encoding": {...}}
]
}
Horizontal Layout (hconcat)
{
"hconcat": [
{"title": "Left", "mark": "bar", "encoding": {...}},
{"title": "Right", "mark": "arc", "encoding": {...}}
]
}
Grid Layout
{
"concat": [
{"title": "Chart 1", "mark": "bar", "encoding": {...}},
{"title": "Chart 2", "mark": "line", "encoding": {...}},
{"title": "Chart 3", "mark": "point", "encoding": {...}},
{"title": "Chart 4", "mark": "arc", "encoding": {...}}
],
"columns": 2
}
Cross-Panel Interactivity
The key advantage of combined specs: selections in one panel can filter another.
Point Selection
Click to select, filter other panels:
{
"vconcat": [
{
"params": [{"name": "regionSelect", "select": {"type": "point", "fields": ["region"]}}],
"mark": "bar",
"encoding": {
"x": {"field": "region", "type": "nominal"},
"y": {"aggregate": "sum", "field": "amount"},
"opacity": {"condition": {"param": "regionSelect", "value": 1}, "value": 0.3}
}
},
{
"transform": [{"filter": {"param": "regionSelect"}}],
"mark": "line",
"encoding": {
"x": {"field": "date", "type": "temporal"},
"y": {"aggregate": "sum", "field": "amount"}
}
}
]
}
Brush Selection
Drag to select a range:
{
"hconcat": [
{
"params": [{"name": "brush", "select": {"type": "interval", "encodings": ["x"]}}],
"mark": "area",
"encoding": {
"x": {"field": "date", "type": "temporal"},
"y": {"aggregate": "sum", "field": "amount"}
}
},
{
"transform": [{"filter": {"param": "brush"}}],
"mark": "bar",
"encoding": {
"x": {"field": "category", "type": "nominal"},
"y": {"aggregate": "sum", "field": "amount"}
}
}
]
}
Tooltips
Always add tooltips for hover context:
{
"encoding": {
"tooltip": [
{"field": "date", "type": "temporal", "title": "Date", "format": "%B %d, %Y"},
{"field": "revenue", "type": "quantitative", "title": "Revenue", "format": "$,.0f"},
{"field": "category", "type": "nominal", "title": "Category"}
]
}
}
Number Formatting
| Format | Input | Output |
|---|---|---|
,.0f | 1234567 | 1,234,567 |
,.2f | 1234.5 | 1,234.50 |
.1% | 0.125 | 12.5% |
$,.0f | 1234 | $1,234 |
Date Formatting
| Format | Output |
|---|---|
%Y | 2024 |
%B | January |
%b %Y | Jan 2024 |
%B %d, %Y | January 15, 2024 |
Color Schemes
Categorical
{
"color": {
"field": "category",
"type": "nominal",
"scale": {"scheme": "category10"}
}
}
Available: category10, category20, tableau10, tableau20, set1, set2, pastel1
Sequential
{
"color": {
"field": "value",
"type": "quantitative",
"scale": {"scheme": "blues"}
}
}
Available: blues, greens, oranges, reds, purples, viridis, plasma
Custom Colors
{
"color": {
"field": "status",
"type": "nominal",
"scale": {
"domain": ["low", "medium", "high"],
"range": ["#22c55e", "#eab308", "#ef4444"]
}
}
}
Query Parameters
Make dashboards dynamic with parameterized SQL:
{
"query": "SELECT * FROM sales WHERE date >= {{start_date}} AND region = {{region}}",
"queryParameters": {
"start_date": "CURRENT_DATE - INTERVAL 30 DAY",
"region": "'US'"
}
}
Override at render time:
{
"queryParameters": {
"start_date": "CURRENT_DATE - INTERVAL 90 DAY",
"region": "'EU'"
}
}
Transforms
Process data within the visualization:
Calculate
{
"transform": [
{"calculate": "datum.revenue - datum.cost", "as": "profit"}
]
}
Filter
{
"transform": [
{"filter": "datum.sales > 1000"}
]
}
Aggregate
{
"transform": [
{
"aggregate": [
{"op": "sum", "field": "sales", "as": "total_sales"}
],
"groupby": ["category"]
}
]
}
Window Functions
{
"transform": [
{
"window": [
{"op": "sum", "field": "sales", "as": "cumulative_sales"}
],
"sort": [{"field": "date"}]
}
]
}
Layering
Overlay multiple marks:
Line with Points
{
"layer": [
{"mark": "line"},
{"mark": "point"}
],
"encoding": {
"x": {"field": "date", "type": "temporal"},
"y": {"field": "value", "type": "quantitative"}
}
}
Bar with Labels
{
"layer": [
{"mark": "bar"},
{
"mark": {"type": "text", "dy": -5},
"encoding": {"text": {"field": "value", "type": "quantitative"}}
}
],
"encoding": {
"x": {"field": "category", "type": "nominal"},
"y": {"field": "value", "type": "quantitative"}
}
}
Reference Line
{
"layer": [
{"mark": "bar", "encoding": {...}},
{
"mark": "rule",
"encoding": {
"y": {"datum": 10000},
"color": {"value": "red"},
"strokeDash": {"value": [4, 4]}
}
}
]
}
Axes & Legends
Axis Customization
{
"encoding": {
"x": {
"field": "date",
"type": "temporal",
"axis": {"title": "Date", "format": "%b %Y", "labelAngle": -45}
},
"y": {
"field": "revenue",
"type": "quantitative",
"axis": {"title": "Revenue ($)", "format": "$,.0f"}
}
}
}
Legend Position
{
"encoding": {
"color": {
"field": "category",
"type": "nominal",
"legend": {"title": "Category", "orient": "bottom"}
}
}
}
Positions: left, right, top, bottom, top-left, top-right, bottom-left, bottom-right
Versioning
Dashboards support automatic versioning:
Create Snapshot
await dashboardApi.createSnapshot(dashboardId, {
name: 'q1-final',
message: 'Finalized Q1 dashboard'
});
List Versions
const versions = await dashboardApi.listVersions(dashboardId);
Restore Version
await dashboardApi.promoteVersion(dashboardId, versionId);
Toggle Versioning
await dashboardApi.setVersioningEnabled(dashboardId, false);
Best Practices
- Always add tooltips - Show values on hover for all charts
- Single query - Use JOINs/CTEs to get all data in one query
- Preview first - Iterate with
data_preview_dashboardbefore saving - Use muted colors - Avoid harsh saturated colors
- Clear titles - Each panel should explain itself
- Limit data - Use SQL LIMIT or
queryLimitparameter - Test interactivity - Verify selections filter correctly
Color Guidelines
Use soft, muted colors:
| Purpose | Recommended | Avoid |
|---|---|---|
| Success | #22c55e, #4ade80 | #00ff00 |
| Info | #3b82f6, #60a5fa | #0000ff |
| Danger | #ef4444, #f87171 | #ff0000 |
| Neutral | #6b7280, #9ca3af | #000000 |
Complete Example
{
"query": "SELECT date, region, SUM(amount) as revenue FROM sales GROUP BY date, region",
"spec": {
"vconcat": [
{
"title": "Click region to filter",
"params": [{"name": "sel", "select": {"type": "point", "fields": ["region"]}}],
"mark": "bar",
"encoding": {
"x": {"field": "region", "type": "nominal"},
"y": {"aggregate": "sum", "field": "revenue", "type": "quantitative"},
"opacity": {"condition": {"param": "sel", "value": 1}, "value": 0.3},
"tooltip": [
{"field": "region", "type": "nominal"},
{"aggregate": "sum", "field": "revenue", "type": "quantitative", "format": "$,.0f"}
]
}
},
{
"title": "Revenue trend (filtered)",
"transform": [{"filter": {"param": "sel"}}],
"mark": {"type": "line", "point": true},
"encoding": {
"x": {"field": "date", "type": "temporal"},
"y": {"aggregate": "sum", "field": "revenue", "type": "quantitative"},
"tooltip": [
{"field": "date", "type": "temporal", "format": "%B %Y"},
{"aggregate": "sum", "field": "revenue", "type": "quantitative", "format": "$,.0f"}
]
}
}
]
}
}
