| name | rill-canvas |
| description | Detailed instructions and examples for developing canvas dashboard resources in Rill |
Instructions for developing a canvas dashboard in Rill
Introduction
Canvas dashboards are free-form dashboard resources that display custom chart and table components laid out in a grid. They enable building overview and report-style dashboards with multiple visualizations, similar to traditional business intelligence tools.
Canvas dashboards differ from explore dashboards in important ways:
- Explore dashboards: Best for explorative analysis, drill-down investigations, and letting users freely slice data by any dimension.
- Canvas dashboards: Best for fixed reports, executive summaries, or combining multiple metrics views into a single view.
Canvas dashboards are lightweight resources found downstream of metrics views in the project DAG. Each component within a canvas fetches data individually, typically from a metrics view resource.
When to use canvas dashboards:
- Building executive summaries with KPIs and multiple visualizations
- Creating report-style dashboards with markdown explanations
- Comparing metrics across different metrics views
- Designing custom layouts not possible with explore dashboards
Canvas Structure
A canvas dashboard is defined in a YAML file with type: canvas. Here is the basic structure (most canvas dashboards work great without any of the optional properties here):
type: canvas
display_name: "Sales Overview Dashboard"
filters:
enable: true
pinned:
- region
- product_category
time_ranges:
- P7D
- P30D
- P90D
- inf
max_width: 1400
theme: my_theme
defaults:
time_range: P7D
comparison_mode: time
security:
access: "'{{ .user.domain }}' == 'company.com'"
rows:
- height: 240px
items:
- width: 12
kpi_grid:
metrics_view: sales_metrics
measures:
- total_revenue
- order_count
- height: 400px
items:
- width: 6
line_chart:
metrics_view: sales_metrics
title: "Revenue Trend"
x:
type: temporal
field: event_time
y:
type: quantitative
field: total_revenue
- width: 6
bar_chart:
metrics_view: sales_metrics
title: "Revenue by Region"
color: primary
x:
type: nominal
field: region
limit: 10
sort: -y
y:
type: quantitative
field: total_revenue
Layout System
Canvas dashboards use a 12-unit grid system for layout.
Row Configuration
Each row defines a horizontal section with a specific height:
rows:
- height: 240px
items:
Recommended row heights:
- Markdown headers: 40px - 80px
- KPI grids: 128px - 240px (depending on number of measures)
- Charts and visualizations: 300px - 500px
- Leaderboards: 300px - 450px
- Tables: 300px - 500px
Item Widths
Items within a row share the 12-unit width:
rows:
- items:
- width: 12
markdown:
content: "# Dashboard Title"
- items:
- width: 6
line_chart:
- width: 6
bar_chart:
- items:
- width: 4
donut_chart:
- width: 4
bar_chart:
- width: 4
area_chart:
Width guidelines:
width: 12 - Full width; use for KPI grids, markdown headers, wide charts
width: 6 - Half width; use for side-by-side comparisons
width: 4 - Third width; use for three equal charts
width: 3 - Quarter width; use for four small components (minimum practical width)
Dashboard Composition Best Practices
When building a new canvas dashboard, follow this recommended structure:
- Row 1 - Context: Start with a markdown component providing dashboard title and overview
- Row 2 - Key Metrics: Add a KPI grid with 2-4 of the most business-relevant measures
- Row 3 - Primary Analysis: Split into two halves:
- Left (width 6): A leaderboard showing top entities by a key dimension
- Right (width 6): A time-series chart (line_chart or stacked_bar) showing trends
- Additional Rows: Add 1-2 more rows with relevant charts based on the data
Choosing chart types:
- Time-series analysis: Use
line_chart or area_chart with temporal x-axis
- Categorical comparisons: Use
bar_chart or stacked_bar with nominal x-axis
- Part-to-whole: Use
donut_chart or stacked_bar_normalized
- Two-dimensional patterns: Use
heatmap
- Dual-metric comparison: Use
combo_chart for two measures with different scales
- Funnel analysis: Use
funnel_chart to visualize sequential stage drop-offs
Field guidelines
The field names are case sensitive and should match exactly to the fields present in the metrics view.
Time dimension restrictions:
The time dimension (timeseries field from the metrics view) is special and can ONLY be used in the x-axis field for temporal charts. Never use the time dimension in:
- Leaderboard dimensions
- Color fields
- Any other dimension configuration
Component Types
Markdown
Add text content, headers, and documentation:
markdown:
content: |
## Dashboard Overview
This dashboard tracks key sales metrics across all regions.
---
alignment:
horizontal: left
vertical: middle
Best practices:
- Use markdown for dashboard titles, section headers, and explanatory text
- Add blank lines between markdown elements for proper rendering
- Use
--- for horizontal rules to separate sections
KPI Grid
Display key metrics with comparison values and sparklines:
kpi_grid:
metrics_view: sales_metrics
measures:
- total_revenue
- order_count
- average_order_value
- customer_count
comparison:
- delta
- percent_change
- previous
sparkline: right
With dimension filters:
kpi_grid:
metrics_view: sales_metrics
measures:
- total_revenue
- order_count
dimension_filters: region IN ('North America', 'Europe')
comparison:
- percent_change
sparkline: bottom
hide_time_range: true
Leaderboard
Display ranked dimension values by measures:
leaderboard:
metrics_view: sales_metrics
title: "Top Products"
description: "Products ranked by total revenue"
dimensions:
- product_category
measures:
- total_revenue
- order_count
num_rows: 10
With multiple dimensions:
leaderboard:
metrics_view: sales_metrics
dimensions:
- region
- product_category
measures:
- total_revenue
- average_order_value
- order_count
num_rows: 7
Important: Never use time dimensions in leaderboard dimensions. Leaderboards are for categorical ranking, not time-series analysis.
Line Chart
Show trends over time:
line_chart:
metrics_view: sales_metrics
title: "Revenue Trend"
color: primary
x:
field: order_date
type: temporal
limit: 30
y:
field: total_revenue
type: quantitative
zeroBasedOrigin: true
With color dimension breakdown:
line_chart:
metrics_view: sales_metrics
title: "Revenue by Region"
color:
field: region
type: nominal
limit: 5
legendOrientation: top
x:
field: order_date
type: temporal
y:
field: total_revenue
type: quantitative
zeroBasedOrigin: true
With custom color mapping:
line_chart:
metrics_view: sales_metrics
title: "Performance Comparison"
color:
field: status
type: nominal
colorMapping:
- value: "active"
color: hsl(120, 70%, 45%)
- value: "inactive"
color: hsl(0, 70%, 50%)
x:
field: event_date
type: temporal
y:
field: event_count
type: quantitative
Bar Chart
Compare values across categories:
bar_chart:
metrics_view: sales_metrics
title: "Revenue by Product Category"
color: hsl(210, 70%, 50%)
x:
field: product_category
type: nominal
limit: 10
sort: -y
labelAngle: 0
y:
field: total_revenue
type: quantitative
zeroBasedOrigin: true
With color dimension:
bar_chart:
metrics_view: sales_metrics
title: "Revenue by Category and Region"
color:
field: region
type: nominal
limit: 5
x:
field: product_category
type: nominal
limit: 8
sort: -y
y:
field: total_revenue
type: quantitative
Stacked Bar
Show cumulative values across categories or time:
stacked_bar:
metrics_view: sales_metrics
title: "Revenue Over Time by Region"
color:
field: region
type: nominal
limit: 5
x:
field: order_date
type: temporal
limit: 20
y:
field: total_revenue
type: quantitative
zeroBasedOrigin: true
With multiple measures:
stacked_bar:
metrics_view: sales_metrics
title: "Cost Breakdown Over Time"
color:
field: rill_measures
type: value
legendOrientation: top
x:
field: order_date
type: temporal
limit: 20
y:
field: cost_of_goods
fields:
- cost_of_goods
- shipping_cost
- marketing_cost
type: quantitative
zeroBasedOrigin: true
Stacked Bar Normalized
Show proportional distribution (100% stacked):
stacked_bar_normalized:
metrics_view: sales_metrics
title: "Revenue Share by Region"
color:
field: region
type: nominal
limit: 5
x:
field: order_date
type: temporal
limit: 20
y:
field: total_revenue
type: quantitative
zeroBasedOrigin: true
With custom color mapping for measures:
stacked_bar_normalized:
metrics_view: inventory_metrics
title: "Inventory Status Distribution"
color:
field: rill_measures
type: value
legendOrientation: top
colorMapping:
- value: "in_stock"
color: hsl(120, 60%, 50%)
- value: "low_stock"
color: hsl(45, 90%, 50%)
- value: "out_of_stock"
color: hsl(0, 70%, 50%)
x:
field: report_date
type: temporal
limit: 20
y:
field: in_stock
fields:
- in_stock
- low_stock
- out_of_stock
Area Chart
Show magnitude over time with optional stacking:
area_chart:
metrics_view: sales_metrics
title: "Order Volume Over Time"
color: primary
x:
field: order_date
type: temporal
limit: 30
y:
field: order_count
type: quantitative
zeroBasedOrigin: true
With color dimension:
area_chart:
metrics_view: sales_metrics
title: "Revenue by Channel"
color:
field: sales_channel
type: nominal
limit: 4
x:
field: order_date
type: temporal
limit: 20
y:
field: total_revenue
type: quantitative
zeroBasedOrigin: true
Donut Chart
Show proportional breakdown:
donut_chart:
metrics_view: sales_metrics
title: "Revenue by Region"
innerRadius: 50
color:
field: region
type: nominal
limit: 8
sort: -measure
measure:
field: total_revenue
type: quantitative
showTotal: true
Heatmap
Show patterns across two dimensions:
heatmap:
metrics_view: activity_metrics
title: "Activity by Day and Hour"
color:
field: event_count
type: quantitative
x:
field: day_of_week
type: nominal
limit: 7
y:
field: hour_of_day
type: nominal
limit: 24
sort: -color
With custom color range:
heatmap:
metrics_view: performance_metrics
title: "Performance Score Matrix"
color:
field: score
type: quantitative
colorRange:
mode: scheme
scheme: sequential
x:
field: category
type: nominal
limit: 10
y:
field: subcategory
type: nominal
limit: 15
With custom Vega-Lite config for colors:
heatmap:
metrics_view: utilization_metrics
title: "Resource Utilization"
vl_config: |
{
"range": {
"heatmap": ["#F4A261", "#D63946", "#457B9D"]
}
}
color:
field: utilization_rate
type: quantitative
x:
field: resource_name
type: nominal
limit: 20
y:
field: time_slot
type: nominal
limit: 12
Combo Chart
Combine bar and line on dual axes:
combo_chart:
metrics_view: sales_metrics
title: "Revenue and Order Count"
color:
field: measures
type: value
legendOrientation: top
x:
field: order_date
type: temporal
limit: 20
y1:
field: total_revenue
type: quantitative
mark: bar
zeroBasedOrigin: true
y2:
field: order_count
type: quantitative
mark: line
zeroBasedOrigin: true
With custom color mapping:
combo_chart:
metrics_view: funnel_metrics
title: "Conversions and Conversion Rate"
color:
field: measures
type: value
legendOrientation: top
colorMapping:
- value: "Conversions"
color: hsl(210, 100%, 73%)
- value: "Conversion Rate"
color: hsl(280, 70%, 55%)
x:
field: event_date
type: temporal
limit: 30
y1:
field: conversions
type: quantitative
mark: bar
y2:
field: conversion_rate
type: quantitative
mark: line
Funnel Chart
Show flow through stages or conversion processes:
funnel_chart:
metrics_view: conversion_metrics
title: "Conversion Funnel"
breakdownMode: dimension
color: stage
mode: width
stage:
field: funnel_stage
type: nominal
limit: 10
measure:
field: user_count
type: quantitative
With multiple measures breakdown:
funnel_chart:
metrics_view: engagement_metrics
title: "Engagement Funnel"
breakdownMode: measures
color: value
mode: width
measure:
field: impressions
type: quantitative
fields:
- impressions
- clicks
- signups
- purchases
Breakdown modes and color options:
breakdownMode: dimension with color: stage (different colors per stage) or color: measure (similar colors by value)
breakdownMode: measures with color: name (different colors per measure) or color: value (similar colors by value)
Pivot
Create pivot tables with row and column dimensions:
pivot:
metrics_view: sales_metrics
title: "Sales by Region and Category"
row_dimensions:
- region
- product_category
col_dimensions:
- quarter
measures:
- total_revenue
- order_count
- average_order_value
Simple pivot (rows only):
pivot:
metrics_view: sales_metrics
row_dimensions:
- region
col_dimensions: []
measures:
- total_revenue
- order_count
- margin_rate
Table
Display tabular data with specified columns:
table:
metrics_view: sales_metrics
title: "Product Performance"
description: "Detailed breakdown of product metrics"
columns:
- product_name
- product_category
- total_revenue
- order_count
- average_price
With dimension filters:
table:
metrics_view: sales_metrics
title: "North America Sales"
columns:
- product_name
- total_revenue
- order_count
dimension_filters: region IN ('North America')
Image
Display external images:
image:
url: https://example.com/logo.png
alignment:
horizontal: center
vertical: middle
Custom Chart
Build fully custom visualizations using Metrics SQL queries and Vega-Lite specifications. Use this when the built-in chart types are insufficient and you need complete control over the visualization.
Custom charts use metrics_sql to query data from metrics views and vega_spec to define the Vega-Lite visualization. The data from each query is available in the Vega-Lite spec as named datasets: query1, query2, etc.
metrics_sql Query Language
metrics_sql lets you write SELECT queries against metrics views as virtual tables. Each metrics view exposes its dimensions and measures as columns.
Query rules:
- Table names: use the name of any valid metrics view in the project
- Columns: only reference dimension and measure names defined in that metrics view's schema
- Measures are pre-aggregated; never wrap them in
SUM(), COUNT(), AVG(), or other aggregate functions
- Grouping is implicit by selected dimensions; you do not need
GROUP BY unless combining with expressions like date_trunc()
- Use
date_trunc('<grain>', <time_dimension>) for time bucketing (grain: minute, hour, day, week, month, quarter, year)
- Always include
ORDER BY for deterministic results
- Use
LIMIT to keep result sets reasonable (default to 50 for top-N queries, 500 for time series)
- Do not alias column names unless strictly required for disambiguation in multi-query specs
- Canvas-level time and dimension filters are injected automatically at runtime; do not add
WHERE clauses for them
- You may write multiple queries against different metrics views; results are bound as
query1, query2, etc.
Example queries:
Single view:
SELECT publisher, total_bids, bid_price FROM bids_metrics ORDER BY total_bids DESC LIMIT 20
Time series:
SELECT date_trunc('day', __time) as day, impressions, revenue FROM ad_metrics ORDER BY day
Cross-view (two queries):
SELECT campaign, spend FROM spend_metrics ORDER BY spend DESC LIMIT 10
SELECT campaign, conversions FROM conversion_metrics ORDER BY conversions DESC LIMIT 10
Vega-Lite Specification Rules
- Generate a valid Vega-Lite v5 JSON specification
- Bind data with
{"name": "query1"}, {"name": "query2"}, etc. Do not include "data": {"values": [...]} sections; data comes from query results
- Set
"width": "container" and "height": "container" so the chart fills its parent
- Always include
"autosize": {"type": "fit"} at the top level of the spec
- Use
display_name values from the metrics view schema for axis titles, legend titles, and tooltip labels
- Apply
format_d3 or format_preset from measure metadata to axis and tooltip format strings
- Pick the best mark type for the data: bar, line, area, point, rect (heatmap), arc (pie/donut), etc.
- Include tooltips with all relevant fields and human-readable formatting
- Use a clean, professional color scheme; prefer Rill's categorical palette when possible
- For temporal axes: set
"type": "temporal" and choose an appropriate timeUnit
- For categorical axes: sort by the primary measure descending unless the user specifies otherwise
- For layered or multi-view charts, use the
"layer" or "concat" composition operators
- Avoid unnecessary chart junk: remove gridlines on categorical axes, use concise axis labels
YAML Examples
Simple custom chart with one query:
custom_chart:
metrics_sql:
- |
SELECT publisher, total_bids, bid_price
FROM bids_metrics
ORDER BY total_bids DESC
LIMIT 20
vega_spec: |
{
"$schema": "https://vega.github.io/schema/vega-lite/v5.json",
"width": "container",
"height": "container",
"autosize": {"type": "fit"},
"data": {"name": "query1"},
"mark": "bar",
"encoding": {
"x": {"field": "publisher", "type": "nominal", "sort": "-y", "axis": {"labelAngle": -45}},
"y": {"field": "total_bids", "type": "quantitative", "title": "Total Bids"},
"tooltip": [
{"field": "publisher", "type": "nominal"},
{"field": "total_bids", "type": "quantitative", "title": "Total Bids"},
{"field": "bid_price", "type": "quantitative", "title": "Bid Price", "format": ",.2f"}
]
}
}
Time series custom chart:
custom_chart:
metrics_sql:
- |
SELECT date_trunc('day', __time) as day, impressions, revenue
FROM ad_metrics
ORDER BY day
LIMIT 500
vega_spec: |
{
"$schema": "https://vega.github.io/schema/vega-lite/v5.json",
"width": "container",
"height": "container",
"autosize": {"type": "fit"},
"data": {"name": "query1"},
"layer": [
{
"mark": {"type": "line", "color": "#4C78A8"},
"encoding": {
"x": {"field": "day", "type": "temporal", "title": "Date"},
"y": {"field": "impressions", "type": "quantitative", "title": "Impressions"}
}
}
],
"encoding": {
"tooltip": [
{"field": "day", "type": "temporal", "title": "Date"},
{"field": "impressions", "type": "quantitative", "title": "Impressions"},
{"field": "revenue", "type": "quantitative", "title": "Revenue", "format": "$,.2f"}
]
}
}
Custom chart with multiple queries (cross-view):
custom_chart:
metrics_sql:
- |
SELECT campaign, spend
FROM spend_metrics
ORDER BY spend DESC
LIMIT 10
- |
SELECT campaign, conversions
FROM conversion_metrics
ORDER BY conversions DESC
LIMIT 10
vega_spec: |
{
"$schema": "https://vega.github.io/schema/vega-lite/v5.json",
"width": "container",
"height": "container",
"autosize": {"type": "fit"},
"layer": [
{
"data": {"name": "query1"},
"mark": {"type": "bar", "opacity": 0.7},
"encoding": {
"x": {"field": "campaign", "type": "nominal", "sort": "-y"},
"y": {"field": "spend", "type": "quantitative", "title": "Spend"},
"color": {"datum": "Spend"}
}
},
{
"data": {"name": "query2"},
"mark": {"type": "line", "point": true},
"encoding": {
"x": {"field": "campaign", "type": "nominal"},
"y": {"field": "conversions", "type": "quantitative", "title": "Conversions", "axis": {"orient": "right"}},
"color": {"datum": "Conversions"}
}
}
]
}
Additional guidelines:
- The
vega_spec field must contain valid JSON (not YAML) as a string
- The
prompt field is optional and stores the user's natural language description for reference
- When refining an existing custom chart, apply targeted edits to the existing SQL and spec rather than regenerating from scratch
Field Configuration
Data Types
nominal: Categorical data (strings, categories). Use for dimensions.
temporal: Time-based data (dates, timestamps). Use for time dimensions.
quantitative: Numerical data (counts, amounts). Use for measures.
value: Special type for multiple measures. Use only in color field with rill_measures.
Axis Properties
x:
field: category_name
type: nominal
limit: 10
sort: -y
showNull: true
labelAngle: 45
Sort Options
"x" or "-x": Sort by x-axis values (ascending/descending)
"y" or "-y": Sort by y-axis values (ascending/descending)
"color" or "-color": Sort by color field (heatmaps)
"measure" or "-measure": Sort by measure (donut charts)
- Array of values: Custom sort order (e.g.,
["Mon", "Tue", "Wed"])
Y-Axis Properties
y:
field: total_revenue
type: quantitative
zeroBasedOrigin: true
Multiple measures:
y:
field: revenue
fields:
- revenue
- cost
- profit
type: quantitative
Color Configuration
Simple color string:
color: primary
color: secondary
color: "#FF5733"
color: hsl(210, 70%, 50%)
Field-based color:
color:
field: region
type: nominal
limit: 10
legendOrientation: top
Custom color mapping:
color:
field: status
type: nominal
colorMapping:
- value: "success"
color: hsl(120, 70%, 45%)
- value: "warning"
color: hsl(45, 90%, 50%)
- value: "error"
color: hsl(0, 70%, 50%)
Color scheme:
color:
field: score
type: quantitative
colorRange:
mode: scheme
scheme: sequential
Special Field: rill_measures
Use rill_measures in the color field when displaying multiple measures in stacked charts:
color:
field: rill_measures
type: value
legendOrientation: top
y:
field: revenue
fields:
- revenue
- cost
- profit
type: quantitative
Advanced Features
Dimension Filters
Filter component data without affecting other components:
kpi_grid:
metrics_view: sales_metrics
measures:
- total_revenue
dimension_filters: region IN ('North America') AND status IN ('active')
Time Range Override
Override the default time range for a specific component:
heatmap:
metrics_view: activity_metrics
time_range:
preset: last_7_days
Time Filters
Override time settings with detailed control:
stacked_bar:
metrics_view: sales_metrics
time_filters: tr=P12M&compare_tr=rill-PY&grain=week
Vega-Lite Configuration
Customize chart appearance with Vega-Lite config:
bar_chart:
metrics_view: sales_metrics
vl_config: |
{
"axisX": {
"grid": true,
"labelAngle": 45
},
"range": {
"category": ["#D63946", "#457B9D", "#F4A261", "#2A9D8F"]
}
}
Complete Example
type: canvas
display_name: "Monthly Business Report"
defaults:
time_range: P30D
comparison_mode: time
max_width: 1400
theme: corporate_theme
rows:
- height: 100px
items:
- width: 12
markdown:
content: |
# Monthly Business Report
Comprehensive overview of business performance metrics.
---
alignment:
horizontal: center
vertical: middle
- height: 50px
items:
- width: 12
markdown:
content: "## Key Metrics"
alignment:
horizontal: left
vertical: middle
- height: 200px
items: