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

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

TypeDescriptionExamples
quantitativeNumbers (continuous)Sales, temperature, count
nominalCategories (unordered)Region, product type
ordinalCategories (ordered)Rating, size (S/M/L)
temporalDates/timesOrder 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

FormatInputOutput
,.0f12345671,234,567
,.2f1234.51,234.50
.1%0.12512.5%
$,.0f1234$1,234

Date Formatting

FormatOutput
%Y2024
%BJanuary
%b %YJan 2024
%B %d, %YJanuary 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

  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:

PurposeRecommendedAvoid
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"}
          ]
        }
      }
    ]
  }
}

Was this page helpful?