Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 10 additions & 10 deletions docs/docs/developers/build/dashboards/canvas-widgets/custom-viz.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,14 @@ Custom viz components are reusable visualizations defined as standalone `type: c

Charts are described declaratively: you state the chart type and which field goes on which channel, and Rill derives the scales, axes, number formats, sorting, stacking, colors, and layout. Rill supplies the semantics of each field from your metrics view — a currency measure formats as currency, the time dimension is treated as temporal at the dashboard's grain — so the spec stays short and the same component adapts when it is bound to a different metrics view.

Unlike an inline `custom_chart` widget, a custom viz lives in its own file (conventionally under `viz_library/`), has a declared parameter contract that Rill validates, and can be developed in a dedicated editor with a live preview.
Unlike an inline `custom_chart` widget, a custom viz lives in its own file (conventionally under `viz/`), has a declared parameter contract that Rill validates, and can be developed in a dedicated editor with a live preview.

## Creating a custom viz

In Rill Developer, click **Add → Custom viz → Blank** (or create a file under `viz_library/` by hand):
In Rill Developer, click **Add → Custom viz → Blank** (or create a file under `viz/` by hand):

```yaml
# viz_library/measure_trend.yaml
# viz/measure_trend.yaml
type: component
display_name: Measure trend

Expand Down Expand Up @@ -95,13 +95,13 @@ A time dimension selected by the `metrics_sql` is bucketed at the dashboard's ti

When the AI assistant is enabled, the chart type is handed to it instead, and it authors the same component against your project's own metrics views so the params come pre-bound to real fields. You're taken to the component immediately and watch an "Importing chart using AI…" screen while it generates; the editor appears once the component reconciles.

## Ejecting to Vega-Lite
## Converting to Vega-Lite

When a chart needs something the chart spec cannot express, **Eject to Vega-Lite** in the component
When a chart needs something the chart spec cannot express, **Convert to Vega-Lite** in the component
editor's header replaces `spec` with the Vega-Lite it currently compiles to, under `vega_spec`. From
there the spec is yours to edit: marks, layers, transforms, interactions, anything Vega-Lite can do.

Ejecting keeps the component parameterized. The compiled spec has the bound values resolved into it,
Converting keeps the component parameterized. The compiled spec has the bound values resolved into it,
so before writing the file Rill turns them back into template references:

| Resolved value | Written as |
Expand All @@ -115,7 +115,7 @@ dashboard that references it keep working, and re-binding a param in a dashboard
axes and re-formats the numbers.

```yaml
# viz_library/measure_trend.yaml, after ejecting
# viz/measure_trend.yaml, after converting
custom_chart:
metrics_sql: |
SELECT {{ .params.time_dim }}, {{ .params.measure }}
Expand Down Expand Up @@ -154,9 +154,9 @@ Alongside `display_name` and `format_type`, a field-typed param also exposes `na
as `{{ .params.<param> }}`), `format_d3` and `format_preset`, which are useful when you would rather
format with Vega-Lite's own `format` than route through Rill's formatter.

Ejecting is one-way, and it is a snapshot: the Vega-Lite it writes reflects the data the chart was
Converting is one-way, and it is a snapshot: the Vega-Lite it writes reflects the data the chart was
compiled against, and Rill stops deriving scales, axes, formats, sorting, stacking and layout, so
the chart no longer adapts when the data changes shape. Eject when you need control that the chart
the chart no longer adapts when the data changes shape. Convert when you need control that the chart
spec does not offer, not as a matter of course. To get back, undo the edit or revert the file.

## Limits
Expand All @@ -165,7 +165,7 @@ A component issues exactly one Metrics SQL query, so charts that layer several d

Rows that overflow what an axis can legibly fit are dropped and reported above the chart, so a row limit set far above a chart's density silently truncates rather than crowds.

Inline `custom_chart` widgets inside a canvas are a separate, Vega-Lite-based widget and continue to use `vega_spec`. A component file authors a chart spec under `spec`, and only carries a `vega_spec` once it has been ejected.
Inline `custom_chart` widgets inside a canvas are a separate, Vega-Lite-based widget and continue to use `vega_spec`. A component file authors a chart spec under `spec`, and only carries a `vega_spec` once it has been converted.

:::note Feature flag
Custom viz is currently gated behind the `customComponents` feature flag. Enable it in `rill.yaml`:
Expand Down
2 changes: 1 addition & 1 deletion docs/docs/reference/project-files/component.md
Original file line number Diff line number Diff line change
Expand Up @@ -523,7 +523,7 @@ metrics view's semantics, so the spec only states the chart type and which field
- **`chartProperties`** - _[object]_ - Per-chart-type presentation tuning, e.g. `innerRadius`, `stackMode`, `interpolate`.

- **`vega_spec`** - _[string]_ - A Vega-Lite spec, as an alternative to `spec` for a chart Rill cannot describe.
This is what ejecting a chart spec in the component editor produces: the Vega-Lite it
This is what converting a chart spec in the component editor produces: the Vega-Lite it
compiled to, with the param bindings turned back into template references. Rill no longer
derives scales, axes, formats, sorting or layout, so the chart stops adapting on its own.
Read the query result from the `query1` dataset, i.e. `"data": {"name": "query1"}`,
Expand Down
2 changes: 1 addition & 1 deletion runtime/ai/instructions/data/resources/canvas.md
Original file line number Diff line number Diff line change
Expand Up @@ -994,7 +994,7 @@ image:

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:
**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/` 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 a converted 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:
Expand Down
24 changes: 12 additions & 12 deletions runtime/ai/instructions/data/resources/component.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ description: Detailed instructions and examples for developing component (custom

## Introduction

Components are reusable visualizations ("custom viz") defined as standalone `type: component` files, conventionally under `viz_library/`. A component declares typed **params** and a **renderer**; canvas dashboards reference the component by name and bind values to its params. This makes one visualization reusable across many dashboards with different metrics views, measures, and dimensions.
Components are reusable visualizations ("custom viz") defined as standalone `type: component` files, conventionally under `viz/`. A component declares typed **params** and a **renderer**; canvas dashboards reference the component by name and bind values to its params. This makes one visualization reusable across many dashboards with different metrics views, measures, and dimensions.

Charts are described with a declarative chart spec: you state the chart type and which field goes on which channel, and Rill derives the scales, axes, number formats, sorting, stacking, colors and layout from the data and from the metrics view's semantics. **In a chart spec, do not set axis titles, tick counts, colors, fonts, widths or heights anywhere**; they are computed. Reach for Vega-Lite only when the chart spec cannot express what was asked, and then only as described in "Going beyond the chart spec" below.

Expand All @@ -15,7 +15,7 @@ Charts are described with a declarative chart spec: you state the chart type and
A component file's only top-level keys are `type`, `display_name`, `description` (optional), `params`, and one renderer block such as `custom_chart` (which contains `metrics_sql` and `spec`). **Never place `metrics_sql` or `spec` at the top level of the YAML, and never wrap the renderer under a `renderer:` key.** A file without a proper renderer block parses as an empty draft and renders nothing: it reports no error, so a misplaced block is silent.

```yaml
# viz_library/measure_trend.yaml
# viz/measure_trend.yaml
type: component
display_name: Measure trend
description: Line chart of any measure over a time dimension.
Expand Down Expand Up @@ -173,27 +173,27 @@ Small multiples: one panel per value of a dimension:
color: { field: "{{ .params.series }}" }
```

## Going beyond the chart spec: ejecting to Vega-Lite
## Going beyond the chart spec: converting to Vega-Lite

The chart spec is deliberately narrow: a chart type, six encoding keys, and `chartProperties`. When a
request needs something it cannot reach — a mark it has no chart type for, layered or dual-axis
composition, a data transform, an interaction, or specific control over an axis, legend, scale or
number format — the component **ejects**: `spec` is replaced by `vega_spec`, a Vega-Lite v5 spec
number format — the component is **converted to Vega-Lite**: `spec` is replaced by `vega_spec`, a Vega-Lite v5 spec
written as a string. The two are mutually exclusive; a component that declares both fails to parse.

Try the chart spec first. Ejecting is a one-way trade: Rill stops deriving scales, axes, formats,
Try the chart spec first. Converting is a one-way trade: Rill stops deriving scales, axes, formats,
sorting, stacking and layout, so the chart no longer adapts to its data, and everything the chart
spec used to compute becomes yours to write and maintain. Eject when the request genuinely cannot be
spec used to compute becomes yours to write and maintain. Convert when the request genuinely cannot be
met otherwise, not to gain incidental control. If a listed chart type does what was asked, use it.
Say plainly in your summary that the component was ejected and what it gave up.
Say plainly in your summary that the component was converted to Vega-Lite and what it gave up.

Nothing else about the component changes: keep the `params` and the `metrics_sql` exactly as they
were, so every dashboard already referencing the component keeps working. Only the renderer's chart
description changes.

### Keeping an ejected component parameterized
### Keeping a converted component parameterized

A component is reusable because its bindings are templated, and an ejected spec must stay that way.
A component is reusable because its bindings are templated, and a converted spec must stay that way.
Never write a field name, an axis title or a number format as a literal — bind each one back to the
param it came from:

Expand All @@ -217,7 +217,7 @@ Scalar params (`string`, `number`, `boolean`) are additionally injected as nativ
so a spec can reference one as a signal by name in an expression, predicate or extent — in addition
to substituting it textually as `{{ .params.<name> }}`.

### Vega-Lite rules for an ejected component
### Vega-Lite rules for a converted component

- Write a valid Vega-Lite v5 spec as a YAML block scalar (`vega_spec: |`).
- Read the query result from `{"data": {"name": "query1"}}`. A component issues exactly one query, so
Expand Down Expand Up @@ -276,7 +276,7 @@ custom_chart:
}
```

The component editor also has an **Eject to Vega-Lite** action, which compiles the current chart spec
The component editor also has a **Convert to Vega-Lite** action, which compiles the current chart spec
and writes the equivalent parameterized Vega-Lite for the user. It is a good starting point for
someone editing by hand, but it is not available to you: write the `vega_spec` directly.

Expand Down Expand Up @@ -304,4 +304,4 @@ If a visualization needs cross-row derivations (running sums, per-group offsets,
- **Do not set `type` or `aggregate` on an encoding** unless the derived behaviour is actually wrong; the semantics Rill supplies are usually right, and hardcoding them is what breaks a component when it is rebound to a different metrics view. A dimension typed `quantitative` renders a blank chart.
- New params on an already-used component should be optional or have a default, otherwise existing dashboards break.
- Components are validated leniently while they contain unresolved `{{ .params.* }}` placeholders; the fully-bound instance is validated in each canvas that references it.
- Inline `custom_chart` blocks inside a canvas are a different, Vega-Lite-based widget and always use `vega_spec`; they never accept `spec`. A component file authors `spec` by default and switches to `vega_spec` only when ejected (see "Going beyond the chart spec").
- Inline `custom_chart` blocks inside a canvas are a different, Vega-Lite-based widget and always use `vega_spec`; they never accept `spec`. A component file authors `spec` by default and switches to `vega_spec` only when converted (see "Going beyond the chart spec").
2 changes: 1 addition & 1 deletion runtime/parser/schema/project.schema.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -3296,7 +3296,7 @@ definitions:
type: string
description: |
A Vega-Lite spec, as an alternative to `spec` for a chart Rill cannot describe.
This is what ejecting a chart spec in the component editor produces: the Vega-Lite it
This is what converting a chart spec in the component editor produces: the Vega-Lite it
compiled to, with the param bindings turned back into template references. Rill no longer
derives scales, axes, formats, sorting or layout, so the chart stops adapting on its own.
Read the query result from the `query1` dataset, i.e. `"data": {"name": "query1"}`,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ export {
} from "./example-spec";

/**
* Imports the chart type as viz_library/<name>.yaml (deduped against existing names)
* Imports the chart type as viz/<name>.yaml (deduped against existing names)
* and returns the new file path.
*/
export async function importGalleryChart(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -167,7 +167,7 @@ describe("buildImportPrompt", () => {

it("hands the agent the query verbatim instead of describing the ordering", () => {
for (const entry of charts) {
const prompt = buildImportPrompt("/viz_library/chart.yaml", entry);
const prompt = buildImportPrompt("/viz/chart.yaml", entry);
expect(prompt, entry.name).toContain(buildMetricsSQL(entry));
expect(prompt, entry.name).not.toContain("Order a time series");
expect(prompt, entry.name).toContain(`default: ${entry.defaultLimit}`);
Expand Down
2 changes: 1 addition & 1 deletion web-common/src/features/custom-viz/params.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ import {
*/

/** Project folder where custom viz component files are created by convention. */
export const CUSTOM_VIZ_FOLDER = "viz_library";
export const CUSTOM_VIZ_FOLDER = "viz";

export type ComponentParamType =
| "string"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { deletePreviewArgsStore, getPreviewArgsStore } from "./preview-state";

describe("preview state", () => {
it("drops bindings when a file path is deleted or renamed", () => {
const path = "/viz_library/chart.yaml";
const path = "/viz/chart.yaml";
const original = getPreviewArgsStore(path);
original.set({ metrics_view: "mv1" });

Expand Down
16 changes: 8 additions & 8 deletions web-common/src/lib/i18n/messages/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -423,14 +423,14 @@
"component_fix_with_ai": "Fix with AI",
"component_imported_with_errors": "Imported {path}, but it still has errors — review it in the editor",
"component_importing_example_with_ai": "Importing example using AI…",
"component_eject": "Eject to Vega-Lite",
"component_eject_tooltip": "Replace this chart spec with the Vega-Lite it compiles to, so you can edit it directly",
"component_eject_confirm_title": "Eject to Vega-Lite?",
"component_eject_confirm_description": "This replaces the chart spec with the Vega-Lite it currently compiles to. Params keep working, but Rill stops deriving scales, sorting and layout from the data, so the chart no longer adapts on its own. There is no way back other than undoing the edit.",
"component_eject_confirm_action": "Eject",
"component_eject_in_progress": "Ejecting\u2026",
"component_eject_success": "Ejected to a Vega-Lite spec",
"component_eject_failed": "Could not eject this component: {error}",
"component_eject": "Convert to Vega-Lite",
"component_eject_tooltip": "Convert this chart spec into the Vega-Lite it compiles to, so you can edit it directly",
"component_eject_confirm_title": "Convert to Vega-Lite?",
"component_eject_confirm_description": "This converts the chart spec into the Vega-Lite it currently compiles to. Params keep working, but Rill stops deriving scales, sorting and layout from the data, so the chart no longer adapts on its own. There is no way back other than undoing the edit.",
"component_eject_confirm_action": "Convert",
"component_eject_in_progress": "Converting\u2026",
"component_eject_success": "Converted to a Vega-Lite spec",
"component_eject_failed": "Could not convert this component: {error}",
"component_copy_bindings": "Use this dashboard's values",
"component_no_params": "Declare params to make this component reusable across dashboards.",
"component_not_used": "Not used by any dashboards yet",
Expand Down
Loading
Loading