---
title: "Dashboards"
source: "https://docs.vertesiahq.com/data-platform/dashboards"
markdown: "https://docs.vertesiahq.com/llms/data-platform/dashboards.md"
---

# 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:

1. **Test query** - Verify SQL returns expected data
2. **Preview** - Use `data_preview_dashboard` to iterate
3. **Verify** - Check labels, colors, layout
4. **Create** - Save with `data_create_dashboard`
5. **Render** - Generate PNG with `data_render_dashboard`

## Chart Types

### Bar Chart

```json
{
  "mark": "bar",
  "encoding": {
    "x": {"field": "category", "type": "nominal"},
    "y": {"field": "value", "type": "quantitative"}
  }
}
```

### Line Chart

```json
{
  "mark": {"type": "line", "point": true},
  "encoding": {
    "x": {"field": "date", "type": "temporal"},
    "y": {"field": "value", "type": "quantitative"}
  }
}
```

### Pie/Donut Chart

```json
{
  "mark": {"type": "arc", "innerRadius": 50},
  "encoding": {
    "theta": {"field": "value", "type": "quantitative"},
    "color": {"field": "category", "type": "nominal"}
  }
}
```

### Scatter Plot

```json
{
  "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

```json
{
  "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

```json
{
  "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)

```json
{
  "vconcat": [
    {"title": "Chart 1", "mark": "bar", "encoding": {...}},
    {"title": "Chart 2", "mark": "line", "encoding": {...}}
  ]
}
```

### Horizontal Layout (hconcat)

```json
{
  "hconcat": [
    {"title": "Left", "mark": "bar", "encoding": {...}},
    {"title": "Right", "mark": "arc", "encoding": {...}}
  ]
}
```

### Grid Layout

```json
{
  "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:

```json
{
  "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:

```json
{
  "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:

```json
{
  "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

```json
{
  "color": {
    "field": "category",
    "type": "nominal",
    "scale": {"scheme": "category10"}
  }
}
```

Available: `category10`, `category20`, `tableau10`, `tableau20`, `set1`, `set2`, `pastel1`

### Sequential

```json
{
  "color": {
    "field": "value",
    "type": "quantitative",
    "scale": {"scheme": "blues"}
  }
}
```

Available: `blues`, `greens`, `oranges`, `reds`, `purples`, `viridis`, `plasma`

### Custom Colors

```json
{
  "color": {
    "field": "status",
    "type": "nominal",
    "scale": {
      "domain": ["low", "medium", "high"],
      "range": ["#22c55e", "#eab308", "#ef4444"]
    }
  }
}
```

## Query Parameters

Make dashboards dynamic with parameterized SQL:

```json
{
  "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:

```json
{
  "queryParameters": {
    "start_date": "CURRENT_DATE - INTERVAL 90 DAY",
    "region": "'EU'"
  }
}
```

## Transforms

Process data within the visualization:

### Calculate

```json
{
  "transform": [
    {"calculate": "datum.revenue - datum.cost", "as": "profit"}
  ]
}
```

### Filter

```json
{
  "transform": [
    {"filter": "datum.sales > 1000"}
  ]
}
```

### Aggregate

```json
{
  "transform": [
    {
      "aggregate": [
        {"op": "sum", "field": "sales", "as": "total_sales"}
      ],
      "groupby": ["category"]
    }
  ]
}
```

### Window Functions

```json
{
  "transform": [
    {
      "window": [
        {"op": "sum", "field": "sales", "as": "cumulative_sales"}
      ],
      "sort": [{"field": "date"}]
    }
  ]
}
```

## Layering

Overlay multiple marks:

### Line with Points

```json
{
  "layer": [
    {"mark": "line"},
    {"mark": "point"}
  ],
  "encoding": {
    "x": {"field": "date", "type": "temporal"},
    "y": {"field": "value", "type": "quantitative"}
  }
}
```

### Bar with Labels

```json
{
  "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

```json
{
  "layer": [
    {"mark": "bar", "encoding": {...}},
    {
      "mark": "rule",
      "encoding": {
        "y": {"datum": 10000},
        "color": {"value": "red"},
        "strokeDash": {"value": [4, 4]}
      }
    }
  ]
}
```

## Axes & Legends

### Axis Customization

```json
{
  "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

```json
{
  "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

```typescript
await dashboardApi.createSnapshot(dashboardId, {
  name: 'q1-final',
  message: 'Finalized Q1 dashboard'
});
```

### List Versions

```typescript
const versions = await dashboardApi.listVersions(dashboardId);
```

### Restore Version

```typescript
await dashboardApi.promoteVersion(dashboardId, versionId);
```

### Toggle Versioning

```typescript
await dashboardApi.setVersioningEnabled(dashboardId, false);
```

## Best Practices

1. **Always add tooltips** - Show values on hover for all charts
2. **Single query** - Use JOINs/CTEs to get all data in one query
3. **Preview first** - Iterate with `data_preview_dashboard` before saving
4. **Use muted colors** - Avoid harsh saturated colors
5. **Clear titles** - Each panel should explain itself
6. **Limit data** - Use SQL LIMIT or `queryLimit` parameter
7. **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

```json
{
  "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"}
          ]
        }
      }
    ]
  }
}
```