Detailed instructions and examples for developing canvas dashboard resources in Rill
36
32%
Does it follow best practices?
Impact
—
No eval scenarios have been run
Passed
No known issues
Optimize this skill with Tessl
npx tessl skill review --optimize ./skills/rill-canvas/SKILL.mdCanvas 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:
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:
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"
# Optional filter settings
filters:
enable: true
pinned:
- region
- product_category
# Optional time range presets
time_ranges:
- P7D
- P30D
- P90D
- inf
# Optional maximum dashboard width
max_width: 1400
# Optional theme reference
theme: my_theme
# Default time settings for all components
defaults:
time_range: P7D
comparison_mode: time
# Optional security access control
security:
access: "'{{ .user.domain }}' == 'company.com'"
# Required dashboard content organized in rows
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_revenueCanvas dashboards use a 12-unit grid system for layout.
Each row defines a horizontal section with a specific height:
rows:
- height: 240px # Row height in pixels
items:
# Components go hereRecommended row heights:
Items within a row share the 12-unit width:
rows:
# Full width (1 component per row)
- items:
- width: 12
markdown:
content: "# Dashboard Title"
# Half width (2 components per row)
- items:
- width: 6
line_chart:
# ...
- width: 6
bar_chart:
# ...
# Third width (3 components per row)
- 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 chartswidth: 6 - Half width; use for side-by-side comparisonswidth: 4 - Third width; use for three equal chartswidth: 3 - Quarter width; use for four small components (minimum practical width)When building a new canvas dashboard, follow this recommended structure:
Choosing chart types:
line_chart or area_chart with temporal x-axisbar_chart or stacked_bar with nominal x-axisdonut_chart or stacked_bar_normalizedheatmapcombo_chart for two measures with different scalesfunnel_chart to visualize sequential stage drop-offsThe 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:
Add text content, headers, and documentation:
markdown:
content: |
## Dashboard Overview
This dashboard tracks key sales metrics across all regions.
---
alignment:
horizontal: left # left, center, right
vertical: middle # top, middle, bottomBest practices:
--- for horizontal rules to separate sectionsDisplay 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 # Absolute change
- percent_change # Percentage change
- previous # Previous period value
sparkline: right # right, bottom, noneWith 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: trueDisplay 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: 10With multiple dimensions:
leaderboard:
metrics_view: sales_metrics
dimensions:
- region
- product_category
measures:
- total_revenue
- average_order_value
- order_count
num_rows: 7Important: Never use time dimensions in leaderboard dimensions. Leaderboards are for categorical ranking, not time-series analysis.
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: trueWith 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: trueWith 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: quantitativeCompare 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: trueWith 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: quantitativeShow 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: trueWith 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: trueShow 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: trueWith 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
type: quantitativeShow 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: trueWith 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: trueShow 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: trueShow 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: -colorWith 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: 15With 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: 12Combine 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: trueWith 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: lineShow 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: quantitativeWith 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
- purchasesBreakdown 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)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_valueSimple pivot (rows only):
pivot:
metrics_view: sales_metrics
row_dimensions:
- region
col_dimensions: []
measures:
- total_revenue
- order_count
- margin_rateDisplay 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_priceWith dimension filters:
table:
metrics_view: sales_metrics
title: "North America Sales"
columns:
- product_name
- total_revenue
- order_count
dimension_filters: region IN ('North America')Display external images:
image:
url: https://example.com/logo.png
alignment:
horizontal: center
vertical: middleBuild 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 lets you write SELECT queries against metrics views as virtual tables. Each metrics view exposes its dimensions and measures as columns.
Query rules:
SUM(), COUNT(), AVG(), or other aggregate functionsGROUP BY unless combining with expressions like date_trunc()date_trunc('<grain>', <time_dimension>) for time bucketing (grain: minute, hour, day, week, month, quarter, year)ORDER BY for deterministic resultsLIMIT to keep result sets reasonable (default to 50 for top-N queries, 500 for time series)WHERE clauses for themquery1, query2, etc.Example queries:
Single view:
SELECT publisher, total_bids, bid_price FROM bids_metrics ORDER BY total_bids DESC LIMIT 20Time series:
SELECT date_trunc('day', __time) as day, impressions, revenue FROM ad_metrics ORDER BY dayCross-view (two queries):
-- query1:
SELECT campaign, spend FROM spend_metrics ORDER BY spend DESC LIMIT 10
-- query2:
SELECT campaign, conversions FROM conversion_metrics ORDER BY conversions DESC LIMIT 10{"name": "query1"}, {"name": "query2"}, etc. Do not include "data": {"values": [...]} sections; data comes from query results"width": "container" and "height": "container" so the chart fills its parent"autosize": {"type": "fit"} at the top level of the specdisplay_name values from the metrics view schema for axis titles, legend titles, and tooltip labelsformat_d3 or format_preset from measure metadata to axis and tooltip format strings"type": "temporal" and choose an appropriate timeUnit"layer" or "concat" composition operatorsSimple 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:
vega_spec field must contain valid JSON (not YAML) as a stringprompt field is optional and stores the user's natural language description for referencenominal: 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.x:
field: category_name # Field name from metrics view
type: nominal # Data type
limit: 10 # Max values to display
sort: -y # Sort order (see below)
showNull: true # Include null values
labelAngle: 45 # Label rotation angle"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)["Mon", "Tue", "Wed"])y:
field: total_revenue
type: quantitative
zeroBasedOrigin: true # Start y-axis at zeroMultiple measures:
y:
field: revenue
fields:
- revenue
- cost
- profit
type: quantitativeSimple color string:
color: primary # Named color
color: secondary
color: "#FF5733" # Hex color
color: hsl(210, 70%, 50%) # HSL colorField-based color:
color:
field: region
type: nominal
limit: 10
legendOrientation: top # top, bottom, left, right, noneCustom 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: sequentialUse 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: quantitativeFilter 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')Override the default time range for a specific component:
heatmap:
metrics_view: activity_metrics
time_range:
preset: last_7_days
# ... other configOverride time settings with detailed control:
stacked_bar:
metrics_view: sales_metrics
time_filters: tr=P12M&compare_tr=rill-PY&grain=week
# ... other configCustomize 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"]
}
}
# ... other configtype: 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:
- width: 12
kpi_grid:
metrics_view: business_metrics
measures:
- revenue
- profit
- customers
- orders
comparison:
- percent_change
- previous
sparkline: right
- height: 50px
items:
- width: 12
markdown:
content: "## Revenue Analysis"
alignment:
horizontal: left
vertical: middle
- height: 400px
items:
- width: 8
combo_chart:
metrics_view: business_metrics
title: "Revenue and Profit Margin"
color:
field: measures
type: value
legendOrientation: top
x:
field: report_date
type: temporal
limit: 30
y1:
field: revenue
type: quantitative
mark: bar
y2:
field: profit_margin
type: quantitative
mark: line
- width: 4
donut_chart:
metrics_view: business_metrics
title: "Revenue by Segment"
innerRadius: 50
color:
field: customer_segment
type: nominal
limit: 5
measure:
field: revenue
type: quantitative
showTotal: true
- height: 50px
items:
- width: 12
markdown:
content: "## Regional Performance"
alignment:
horizontal: left
vertical: middle
- height: 350px
items:
- width: 6
leaderboard:
metrics_view: business_metrics
dimensions:
- region
measures:
- revenue
- profit
- order_count
num_rows: 8
- width: 6
heatmap:
metrics_view: business_metrics
title: "Revenue by Region and Product"
color:
field: revenue
type: quantitative
x:
field: product_category
type: nominal
limit: 8
y:
field: region
type: nominal
limit: 665ccd1f
If you maintain this skill, you can claim it as your own. Once claimed, you can manage eval scenarios, bundle related skills, attach documentation or rules, and ensure cross-agent compatibility.