Installs into .claude/skills of the current project.
Are you the author of Source 49fd0df75f9134176f2cbadc?
Add the live security badge to your README. It updates with every re-scan.
[](https://www.skillsdirectory.com/skills/gutsofpratik-source-49fd0df75f9134176f2cbadc)
---
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):
```yaml
type: canvas
display_name: "Sales Overview Dashboard"
# Optional filter settings
filters:
enable: true
pinned:
- region
- product_category
required:
- customer_id # Dashboard refuses to render until this filter has a value
# 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 default filters, keyed by metrics view name
filters:
sales_metrics: "region IN ('US', 'CA')"
# 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_revenue
```
### Filter settings
`filters.pinned` and `filters.required` both reference dimensions or measures by name (they must exist on at least one referenced metrics view). They control different things:
- **`pinned`**: keeps a filter pill visible at the top of the dashboard so users can change it without opening the filter menu. The dashboard renders normally whether or not the user has set a value.
- **`required`**: the dashboard refuses to render until every required filter has a value. Set a default value in `defaults.filters` to satisfy a required filter automatically; otherwise the user is prompted to pick one. Required filters are implicitly pinned — do not also list them under `pinned`.
Use `required` for filters that are unsafe or meaningless to omit (e.g. tenant ID, customer scope, region when results would otherwise mix incompatible data).
#### Default filters
`defaults.filters` is a **map keyed by metrics view name**, where each value is a **Metrics SQL `WHERE` expression** applied when the dashboard first loads.
```yaml
defaults:
time_range: P7D
filters:
sales_metrics: "region IN ('US', 'CA') AND total_revenue > 1000"
support_metrics: "ticket_status != 'closed'"
```
Rules for writing `defaults.filters`:
- Each key must be the name of a metrics view that is referenced by at least one component on the canvas.
- A filter only applies to the components backed by its metrics view. To filter several metrics views, add one entry per metrics view, including duplicating shared dimension/measure names.
- Expressions reference dimension and measure **names as defined on that metrics view**, not the underlying table columns. Table prefixes, aliases, and subqueries are not supported.
- Write only the expression itself; do not include the `WHERE` keyword.
- Supported syntax: `=`, `!=`, `<`, `<=`, `>`, `>=`, `IN`, `NOT IN`, `LIKE`, `ILIKE`, `IS NULL`, `IS NOT NULL`, `BETWEEN`, combined with `AND`, `OR`, and parentheses. String literals use single quotes.
## Layout System
Canvas dashboards use a 12-unit grid system for layout.
### Row Configuration
Each row defines a horizontal section with a specific height:
```yaml
rows:
- height: 240px # Row height in pixels
items:
# Components go here
```
**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:
```yaml
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 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)
### Tab groups
A row entry can be a tab group instead of a plain row. A tab group has a `name` (URL key) and a list of `tabs`, each with a `label`, optional `name`, and its own `rows`. Only the active tab loads data. Allowed only at the top level, not nested inside a tab.
```yaml
rows:
- name: by_region
tabs:
- label: North America
rows:
- items:
- width: 6
bar_chart: # ...
- label: EMEA
rows:
- items:
- width: 6
bar_chart: # ...
```
## Dashboard Composition Best Practices
When building a new canvas dashboard, follow this recommended structure:
1. **Row 1 - Context**: Start with a markdown component providing dashboard title and overview
2. **Row 2 - Key Metrics**: Add a KPI grid with 2-4 of the most business-relevant measures
3. **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
4. **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
- **Two-measure relationships**: Use `scatter_plot` to explore correlation, clusters, or outliers between two numeric measures
# 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:
```yaml
markdown:
content: |
## Dashboard Overview
This dashboard tracks key sales metrics across all regions.
---
alignment:
horizontal: left # left, center, right
vertical: middle # top, middle, bottom
```
**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:
```yaml
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, none
```
**With dimension filters:**
```yaml
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:
```yaml
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:**
```yaml
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:
```yaml
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:**
```yaml
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:**
```yaml
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:
```yaml
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
y:
field: total_revenue
type: quantitative
zeroBasedOrigin: true
```
**With color dimension:**
```yaml
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
```
**Horizontal bars** (bars run left to right; useful for long category labels or many categories). `x` and `y` always name the field drawn on that axis, so put the measure on `x` and the dimension on `y`:
```yaml
bar_chart:
metrics_view: sales_metrics
title: "Revenue by Product Category"
color: primary
x:
field: total_revenue
type: quantitative
zeroBasedOrigin: true
y:
field: product_category
type: nominal
limit: 15
sort: -x
```
Sort values refer to axes, so `sort: -x` on `y` orders the categories by the measure. The same layout works for `stacked_bar` and `stacked_bar_normalized`; `line_chart` and `area_chart` always keep the dimension on `x`.
### Stacked Bar
Show cumulative values across categories or time:
```yaml
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:**
```yaml
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):
```yaml
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:**
```yaml
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: quantitative
```
### Area Chart
Show magnitude over time with optional stacking:
```yaml
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:**
```yaml
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:
```yaml
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:
```yaml
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:**
```yaml
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:**
```yaml
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:
```yaml
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:**
```yaml
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
```
### Scatter Plot
Visualize the relationship between two measures, with each point representing a data value. Points can be optionally colored by a dimension and sized by a measure. Useful for correlation analysis and identifying outliers across two numeric metrics.
**Interactive controls** (rendered automatically):
- `Shift + Drag`: pan the plot
- Scroll wheel: zoom in/out
- Double-click: reset zoom and pan to default view
```yaml
scatter_plot:
metrics_view: bids_metrics
title: "Bids vs Clicks by Advertiser"
x:
field: total_bids
type: quantitative
zeroBasedOrigin: false
y:
field: total_clicks
type: quantitative
zeroBasedOrigin: false
dimension:
field: advertiser_name
limit: 10
type: nominal
size:
field: total_impressions
type: quantitative
color:
field: device_os
limit: 5
type: nominal
legendOrientation: top
```
**Scatter-specific fields:**
- `x` (required): Measure on the x-axis. Must be `type: quantitative`.
- `y` (required): Measure on the y-axis. Must be `type: quantitative`.
- `dimension` (optional): Nominal dimension; each distinct value renders as a separate point. Use `limit` to cap the number of points.
- `size` (optional): Measure that controls point radius. Must be `type: quantitative`.
- `color` (optional): A color string or a nominal dimension field object to color points by.
**Notes:**
- Setting `zeroBasedOrigin: false` on both axes is usually preferred for scatter plots, since the interesting range often does not include zero.
- `comparison_time_range` is not supported for scatter plots.
### Funnel Chart
Show flow through stages or conversion processes:
```yaml
funnel_chart:
metrics_view: conversion_metrics
title: "Conversion Funnel"
breakdownMode: dimension
color: stage
mode: width
percentMode: top
stage:
field: funnel_stage
type: nominal
limit: 10
measure:
field: user_count
type: quantitative
```
**With multiple measures breakdown and step-to-step percentages:**
```yaml
funnel_chart:
metrics_view: engagement_metrics
title: "Engagement Funnel"
breakdownMode: measures
color: value
mode: width
percentMode: previous
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)
**Percent mode (`percentMode`):**
- `top` (default): the on-bar percentage label is each stage's value as a share of the top stage. Use for overall conversion ("how many of the entry-point users reached this stage").
- `previous`: the on-bar percentage label is each stage's value as a share of the immediately prior stage. Use for stage-to-stage drop-off ("what fraction of the previous stage continued").
- The tooltip always shows both `% of top` and `% of previous` regardless of this setting, so users can disambiguate without the author surfacing both.
### Pivot
Create pivot tables with row and column dimensions:
```yaml
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
hide_totals_row: false
hide_totals_col: false
```
**With column widths, wrapping, and an initial sort:**
Any entry of `measures`, `row_dimensions`, or `col_dimensions` can be an object with the field `name` plus overrides. In a pivot only measures and the first row dimension render a column of their own: a measure's `width` applies under every column-dimension value (as do `delta: { width }` and `percent_change: { width }` for its comparison columns), and `col_dimensions` entries accept only `name` and `label` (set widths on the measures instead).
```yaml
pivot:
metrics_view: sales_metrics
fit_to_width: true # shrink unconfigured columns to fit instead of scrolling
wrap_headers: true # wrap header labels over `wrap_lines` lines (default 2)
sort_by: total_revenue # a measure (by row total) or a row dimension
sort_dir: desc
row_dimensions:
- name: region
width: 220
wrap: true
- product_category
col_dimensions:
- name: quarter
label: Quarter
measures:
- name: total_revenue
width: 120
format_preset: currency_usd
- name: order_count
format_d3: ".3s"
align: center
```
**Simple pivot (rows only):**
```yaml
pivot:
metrics_view: sales_metrics
row_dimensions:
- region
col_dimensions: []
measures:
- total_revenue
- order_count
- margin_rate
```
### Table
Display tabular data with specified columns:
```yaml
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
hide_totals_row: false
```
**With column widths, wrapping, and an initial sort:**
Any entry of `columns` can be an object with the field `name` plus overrides: `width` (pixels; measures 60–300, dimensions 100–600), `wrap` (dimension columns), `align` (`left`, `center`, `right`), `label`, and for measures `format_preset` or `format_d3`, plus `delta: { width }` or `percent_change: { width }` for the comparison columns a metrics view measure gets while a time comparison is shown. Component-level `fit_to_width`, `wrap`, `wrap_headers`, `wrap_lines`, `sort_by`, and `sort_dir` apply to the whole table; `sort_comparison: delta` or `percent_change` sorts on a measure's comparison column while a time comparison is shown.
```yaml
table:
metrics_view: sales_metrics
title: "Product Performance"
fit_to_width: true
wrap: true
wrap_lines: 2
sort_by: total_revenue
sort_dir: desc
columns:
- name: product_name
width: 260
label: Product
- product_category
- name: total_revenue
width: 120
format_preset: currency_usd
- name: order_count
format_d3: ".3s"
align: center
```
**With dimension filters:**
```yaml
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. Set `dark_url` to show a different image when the dashboard is in dark mode (falls back to `url`):
```yaml
image:
url: https://example.com/logo.png
dark_url: https://example.com/logo-dark.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.
**Prefer a standalone component over an inline block.** Instead of embedding the `custom_chart` block in the canvas, create a `type: component` file under `viz_library/` that declares typed params, and reference it from the canvas item with `component: <name>` plus a `params:` map. This keeps canvas files small and makes the visualization reusable across dashboards. Note that component files describe charts with a chart spec under `spec` rather than with `vega_spec`, switching to `vega_spec` only when the chart spec cannot express the chart: the format below applies to inline blocks, and to an ejected component with the addition of param templating. See the component resource instructions for the file format, param types, and templating conventions. Use an inline `custom_chart` block only for trivial one-off charts. Example reference:
```yaml
rows:
- items:
- component: measure_trend
params:
metrics_view: bids_metrics
measure: total_bids
time_dim: __time
width: 8
```
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:
```sql
SELECT publisher, total_bids, bid_price FROM bids_metrics ORDER BY total_bids DESC LIMIT 20
```
Time series:
```sql
SELECT date_trunc('day', __time) as day, impressions, revenue FROM ad_metrics ORDER BY day
```
Cross-view (two queries):
```sql
-- 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
```
#### 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:**
```yaml
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:**
```yaml
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):**
```yaml
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
```yaml
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; omit for automatic orientation (0, -45 or -90 based on available width)
```
### Sort Options
- `"x"` or `"-x"`: Sort by x-axis values (ascending/descending)
- `"y"` or `"-y"`: Sort by y-axis values (ascending/descending)
- `"y_delta"` or `"-y_delta"`: Sort by the change versus the comparison period (`"x_delta"` / `"-x_delta"` on the y field of a horizontal bar chart)
- `"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
```yaml
y:
field: total_revenue
type: quantitative
zeroBasedOrigin: true # Start y-axis at zero
```
### Horizontal Bars
`bar_chart`, `stacked_bar` and `stacked_bar_normalized` draw horizontal bars when the measure is on `x` and the dimension on `y`:
```yaml
x:
field: total_revenue
type: quantitative
y:
field: category_name
type: nominal
sort: -x # Sort categories by the measure (x-axis)
```
**Multiple measures:**
```yaml
y:
field: revenue
fields:
- revenue
- cost
- profit
type: quantitative
```
### Color Configuration
**Simple color string:**
```yaml
color: primary # Named color
color: secondary
color: "#FF5733" # Hex color
color: hsl(210, 70%, 50%) # HSL color
```
**Field-based color:**
```yaml
color:
field: region
type: nominal
limit: 10
legendOrientation: top # top, bottom, left, right, none
```
**Custom color mapping:**
```yaml
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:**
```yaml
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:
```yaml
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:
```yaml
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:
```yaml
heatmap:
metrics_view: activity_metrics
time_range:
preset: last_7_days
# ... other config
```
### Time Filters
Override time settings with detailed control:
```yaml
stacked_bar:
metrics_view: sales_metrics
time_filters: tr=P12M&compare_tr=rill-PY&grain=week
# ... other config
```
### Comparison Per Widget
Components inherit the canvas time range and time comparison. `time_filters` overrides either one per component, using the same `tr` and `compare_tr` parameters as explore URLs. `inherit` is a special value for both: `tr=inherit` keeps the canvas time range, and once `tr` is set a missing `compare_tr` means no comparison. A comparison range is `rill-PP`, `rill-PD`, `rill-PW`, `rill-PM`, `rill-PQ`, `rill-PY`, or a custom `<start>,<end>` pair, and it applies even when the canvas comparison is off. Applies to `kpi_grid`, `table`, `pivot`, `leaderboard`, and time-series charts:
| `time_filters` | Time range | Comparison |
| --- | --- | --- |
| (absent) | canvas | canvas |
| `tr=inherit` | canvas | off |
| `tr=inherit&compare_tr=rill-PP` | canvas | previous period |
| `tr=P7D` | last 7 days | off |
| `tr=P7D&compare_tr=inherit` | last 7 days | canvas |
| `tr=P7D&compare_tr=rill-PP` | last 7 days | previous period |
```yaml
kpi_grid:
metrics_view: sales_metrics
measures:
- total_revenue
time_filters: tr=inherit
```
### Hiding Local Filters
Components with `dimension_filters` or `time_filters` show the local filters as chips in the component header. Set `hide_local_filters: true` to hide the chips while the filters still apply to the component's data. Useful when the title already describes the filter:
```yaml
kpi_grid:
metrics_view: sales_metrics
title: North America revenue
measures:
- total_revenue
dimension_filters: region IN ('North America')
hide_local_filters: true
```
### Vega-Lite Configuration
Customize chart appearance with Vega-Lite config:
```yaml
bar_chart:
metrics_view: sales_metrics
vl_config: |
{
"axisX": {
"grid": true,
"labelAngle": 45
},
"range": {
"category": ["#D63946", "#457B9D", "#F4A261", "#2A9D8F"]
}
}
# ... other config
```
## Complete Example
```yaml
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:
- 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: 6
```