diff --git a/docs/docs/developers/build/dashboards/canvas-widgets/canvas-widgets.md b/docs/docs/developers/build/dashboards/canvas-widgets/canvas-widgets.md index e696abac527..49a72a5e8a5 100644 --- a/docs/docs/developers/build/dashboards/canvas-widgets/canvas-widgets.md +++ b/docs/docs/developers/build/dashboards/canvas-widgets/canvas-widgets.md @@ -125,6 +125,19 @@ Rill Canvas dashboards are built using a variety of widgets that can display dat /> +## Custom Viz + +### Reusable Charts + +
+ } + /> +
+ ## Miscellaneous ### Markdown Text diff --git a/docs/docs/developers/build/dashboards/canvas-widgets/custom-viz.md b/docs/docs/developers/build/dashboards/canvas-widgets/custom-viz.md deleted file mode 100644 index 68246b6c21c..00000000000 --- a/docs/docs/developers/build/dashboards/canvas-widgets/custom-viz.md +++ /dev/null @@ -1,177 +0,0 @@ ---- -title: "Custom Viz Components" -sidebar_label: "Custom Viz" -sidebar_position: 15 ---- - -Custom viz components are reusable visualizations defined as standalone `type: component` files. A component declares typed **params** and renders a [custom chart](/reference/project-files/component) built from a Metrics SQL query and a chart spec. Canvas dashboards reference the component by name and bind values to its params, so one visualization can be reused across many dashboards with different metrics views, measures, and dimensions. - -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/`), 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/` by hand): - -```yaml -# viz/measure_trend.yaml -type: component -display_name: Measure trend - -params: - - name: metrics_view - type: metrics_view - required: true - - name: measure - type: measure - required: true - - name: time_dim - type: time_dimension - required: true - -custom_chart: - metrics_sql: | - SELECT {{ .params.time_dim }}, {{ .params.measure }} - FROM {{ .params.metrics_view }} - ORDER BY {{ .params.time_dim }} - spec: - chartType: Line Chart - encodings: - x: { field: "{{ .params.time_dim }}" } - y: { field: "{{ .params.measure }}" } -``` - -There is no schema, sizing, mark, or encoding type to write: `chartType` and `encodings` are the whole spec. An encoding accepts only `field`, `type`, `aggregate`, `sortOrder`, `sortBy`, and `scheme` — and `type` and `aggregate` are rarely needed, since Rill already knows that measures are quantitative and pre-aggregated. Presentation tuning that a chart type exposes (`innerRadius`, `stackMode`, `interpolate`, …) goes under an optional `chartProperties` mapping. - -The component editor shows a live preview. Use the **Test values** panel to bind preview-only values to the params; dashboards set their own values. The **Used by** panel lists every dashboard referencing the component. - -## Params - -Params are the component's contract with dashboards. Each param has a `name`, a `type`, and optionally `required`, `default`, `description`, and (for scalars) `options`: - -| Type | Bound value | Notes | -|---|---|---| -| `metrics_view` | A metrics view name | Must be named `metrics_view` or end with `_metrics_view` | -| `measure` | A measure of the bound metrics view | Validated at reconcile time | -| `dimension` | A dimension of the bound metrics view | Validated at reconcile time | -| `time_dimension` | A time dimension of the bound metrics view | The primary time dimension or a time-typed dimension | -| `string`, `number`, `boolean` | A scalar value | Useful for row limits and `chartProperties` | - -Params are substituted into `metrics_sql` and `spec` through templating: `{{ .params.measure }}`. A value that is exactly one placeholder keeps the param's type, so a `number` param bound to `innerRadius: "{{ .params.inner_radius }}"` arrives as a number rather than a string. Partial interpolations such as `LIMIT {{ .params.limit }}` are ordinary strings. - -When evolving a component that dashboards already use, prefer adding params with a `default` (or `optional`) so existing references keep working. Renaming or removing a param surfaces a validation error on every dashboard that binds it. - -## Referencing from a canvas - -In the canvas visual editor, open **Add widget → Your components** and pick the component: Rill pre-selects sensible bindings (a metrics view with a time dimension, its first measure, and so on), and the inspector shows a form generated from the declared params. In YAML, a reference looks like: - -```yaml -# dashboards/overview.yaml -type: canvas -rows: - - items: - - component: measure_trend - params: - metrics_view: bids_metrics - measure: total_bids - time_dim: __time - width: 8 - - component: measure_trend - params: - metrics_view: bids_metrics - measure: avg_bid_price - time_dim: __time - width: 4 -``` - -Bindings are validated when the canvas reconciles: unknown params, missing required params, and fields that don't exist in the bound metrics view are reported as errors on the dashboard. Canvas-level time and dimension filters apply to custom viz like any other widget. - -A time dimension selected by the `metrics_sql` is bucketed at the dashboard's time grain, so a time series re-buckets when the time controls change, just like a native chart. Write `date_trunc('', ) AS ` in the query only when a chart should stay at one fixed bucket regardless of the dashboard; the alias is required, or the column comes out named after the expression and the encodings can no longer reference it. The component editor's preview has no time controls to inherit from and renders at day grain. - -## Starting from a chart type - -**Add → Custom viz** opens a gallery of every chart type Rill can render, grouped by family (bars, lines and areas, distributions, circular, points, tables and maps) and labelled with the fields each one needs — for example a heatmap needs two categorical fields and a measure. Picking one writes a component with a required `metrics_view` param, one typed param per channel named after its chart role (`x_axis`, `color`, …), a templated `metrics_sql`, and a row limit sized to the chart's visual density. - -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. - -## Converting to Vega-Lite - -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. - -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 | -|---|---| -| A field name, wherever it appears (encodings, transforms, expressions) | `{{ .params. }}` | -| A field's display name, in an axis or legend title | `{{ .fields..display_name }}` | -| A measure's number formatter, in `formatType` | `{{ .fields..format_type }}` | - -Nothing about the component's contract changes: the params, the `metrics_sql` query, and every -dashboard that references it keep working, and re-binding a param in a dashboard still re-titles the -axes and re-formats the numbers. - -```yaml -# viz/measure_trend.yaml, after converting -custom_chart: - metrics_sql: | - SELECT {{ .params.time_dim }}, {{ .params.measure }} - FROM {{ .params.metrics_view }} - ORDER BY {{ .params.time_dim }} - vega_spec: | - { - "$schema": "https://vega.github.io/schema/vega-lite/v5.json", - "width": "container", - "height": "container", - "autosize": { "type": "fit" }, - "data": { "name": "query1" }, - "mark": "line", - "encoding": { - "x": { - "field": "{{ .params.time_dim }}", - "type": "temporal", - "title": "{{ .fields.time_dim.display_name }}" - }, - "y": { - "field": "{{ .params.measure }}", - "type": "quantitative", - "title": "{{ .fields.measure.display_name }}", - "formatType": "{{ .fields.measure.format_type }}" - } - } - } -``` - -The query result arrives as the `query1` dataset, which is why the spec reads its data from -`{"name": "query1"}` rather than declaring values inline. The container sizing is the same as the -native charts use: `autosize: fit` makes the rendered dimensions the chart's total size, so axes and -legends stay inside the widget instead of growing past it. - -Alongside `display_name` and `format_type`, a field-typed param also exposes `name` (the same value -as `{{ .params. }}`), `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. - -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. 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 - -A component issues exactly one Metrics SQL query, so charts that layer several datasets cannot be expressed. Cross-row derivations (running totals, rankings, per-group offsets) are not available in Metrics SQL either — use a chart type that computes them, such as `Waterfall Chart`, `Bump Chart`, or `ECDF Plot`. - -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 converted. - -:::note Feature flag -Custom viz is currently gated behind the `customComponents` feature flag. Enable it in `rill.yaml`: - -```yaml -features: - - customComponents -``` -::: diff --git a/docs/docs/developers/build/dashboards/canvas-widgets/custom-viz/_category_.yml b/docs/docs/developers/build/dashboards/canvas-widgets/custom-viz/_category_.yml new file mode 100644 index 00000000000..533b2022195 --- /dev/null +++ b/docs/docs/developers/build/dashboards/canvas-widgets/custom-viz/_category_.yml @@ -0,0 +1,4 @@ +position: 12 +label: Custom Viz +collapsible: true +collapsed: true diff --git a/docs/docs/developers/build/dashboards/canvas-widgets/custom-viz/chart-types.md b/docs/docs/developers/build/dashboards/canvas-widgets/custom-viz/chart-types.md new file mode 100644 index 00000000000..89c523c1f20 --- /dev/null +++ b/docs/docs/developers/build/dashboards/canvas-widgets/custom-viz/chart-types.md @@ -0,0 +1,262 @@ +--- +title: "Custom Viz Chart Types" +sidebar_label: "Chart Types" +sidebar_position: 10 +--- + +import ImageCodeToggle from '@site/src/components/ImageCodeToggle'; + +The chart spec of a [custom viz](/developers/build/dashboards/canvas-widgets/custom-viz) can draw 32 chart types, in the six families of the **Chart types** gallery. This page lists, for each type, what it is for, the channels it takes and the `chartProperties` it accepts. Channels in **bold** are required; the rest are optional. + +## The Gallery + +**Add → Custom viz** opens a gallery of up to three examples per chart type, each with a different channel layout: a scatter plot with and without bubble sizes, a line chart with and without series. The thumbnails are drawn from sample data that ships with Flint. Filter by family or chart type in the sidebar, or search by name. + +![The Chart types gallery, filtered to the Bars family](/img/build/dashboard/canvas/custom-viz/gallery-bars.png) + +Picking an example writes a component with the same layout. Only the chart type, the channels and any chart properties carry over, never the sample data. The component declares: + +- a required `metrics_view` param; +- one required param per channel, named for the channel's role (`x_axis`, `y_axis`, `x_axis_end`, `color`, `size`, `group`, `goal`, and so on) and typed `measure`, `dimension` or `time_dimension` to match the field the example draws there; +- for charts that plot individual observations, a required `detail` dimension, described in [Row Limits](/developers/build/dashboards/canvas-widgets/custom-viz#row-limits); +- a `limit` param, with a default suited to how many marks the chart draws; +- a `metrics_sql` query that selects those fields, ordered by time for a time series and by the measure otherwise. + +The examples below are bound to the `bids_metrics` metrics view of the [OpenRTB example project](https://github.com/rilldata/rill-examples/tree/main/rill-openrtb-prog-ads). Each shows the component's `custom_chart` block; the params it references are declared as described above. + +## Points + +Charts that draw a point per row: scatter plots and their variants for the relationship between two measures, and dot and strip plots for values per category. Scatter plots, regressions and strip plots plot individual observations, so their components select a `detail` dimension that sets what each point represents. + +| Chart type | Use it for | Channels | Chart properties | +|---|---|---|---| +| Scatter Plot | The relationship between two measures | **x**, **y**, color, size, shape, opacity, column, row | `opacity` | +| Regression | A scatter plot with a fitted trend line | **x**, **y**, color, size, column, row | `regressionMethod`, `polyOrder` | +| Connected Scatter Plot | How two measures move together over time | **x**, **y**, order, color, detail, column, row | | +| Ranged Dot Plot | The gap between two values per category, drawn as a dumbbell | **x**, **y**, **color** | | +| Strip Plot | The spread of individual values per category | **x**, **y**, color, size, column, row | `stepWidth`, `pointSize`, `opacity` | + +- **Regression**: `regressionMethod` is `linear` (the default), `log`, `exp`, `pow`, `quad` or `poly`; `polyOrder` sets the degree of a `poly` fit, from 2 to 10. +- **Connected Scatter Plot**: `order` is the sequence the points are connected in, usually a time dimension. +- **Ranged Dot Plot**: bind `color` to a dimension with exactly two values, one for each end of the dumbbell. +- **Strip Plot**: `stepWidth` sets the jitter spread, from 10 to 100. + +A bubble chart of advertisers: average bid price against win rate, sized by impressions and colored by creative type, with `detail` bound to `advertiser_name` so each point is one advertiser. + + + +## Bars + +Charts that compare a measure across categories. Put the category on `x` and the measure on `y` for vertical bars, or the other way around for horizontal bars. + +| Chart type | Use it for | Channels | Chart properties | +|---|---|---|---| +| Bar Chart | Comparing a measure across categories | **x**, **y**, color, opacity, column, row | `cornerRadius` | +| Grouped Bar Chart | Side-by-side bars for a second category | **x**, **y**, **group**, column, row | `dodge` | +| Stacked Bar Chart | The parts that make up each bar | **x**, **y**, **color**, column, row | `stackMode` | +| Lollipop Chart | A lighter alternative to a bar chart | **x**, **y**, color, column, row | `dotSize` | +| Waterfall Chart | How a sequence of increases and decreases adds up | **x**, **y**, color, column, row | `totals`, `cornerRadius`, `showTextLabels` | +| Gantt Chart | Intervals on a shared axis | **y**, **x**, **x2**, color, detail, column, row | `taskHeight`, `cornerRadius`, `intervalLabels` | +| Bullet Chart | A measure against a target | **y**, **x**, **goal**, color, column, row | | + +- **Grouped Bar Chart**: the second category goes on `group`, not `color`. `dodge` is `auto` (the default), `local` or `global`. +- **Stacked Bar Chart**: `stackMode: normalize` stacks to 100%, and `center` centers each stack. +- **Waterfall Chart**: each row is one step, and the bars float at the running total. `totals` decides which bars are drawn from zero as totals: `auto` (the default), `none`, `first`, `last` or `both`. Leave `color` unbound; increases and decreases are colored by sign. +- **Gantt Chart**: `x` is where an interval starts and `x2` where it ends. +- **Bullet Chart**: the `goal` measure is drawn as a tick on each bar, and each bar is colored by whether it reaches its goal. + +A bullet chart of average bid price by device type, with the average bid floor as the target. + + + +## Distributions + +Charts that summarize how values are spread. Histograms, density plots, ECDF plots, box plots and violin plots summarize individual observations, so their components select a `detail` dimension. + +| Chart type | Use it for | Channels | Chart properties | +|---|---|---|---| +| Histogram | The distribution of a measure, in bins | **x**, color, column, row | `binCount` | +| Density Plot | The distribution of a measure, as a smooth curve | **x**, color, column, row | `bandwidth` | +| ECDF Plot | The share of values at or below each value | **x**, color, detail, column, row | `showPoints` | +| Boxplot | Medians, quartiles and outliers per category | **x**, **y**, color, opacity, column, row | `whiskerMethod`, `showOutliers`, `dodge` | +| Violin Plot | The shape of a distribution per category | **x**, **y**, color, row | `bandwidth` | +| Pyramid Chart | Two groups compared side by side across categories | **x**, **y**, **color** | | +| Candlestick Chart | Open, high, low and close over time | **x**, **open**, **high**, **low**, **close**, column, row | | + +- **Histogram**: `binCount` caps the number of bins, from 5 to 50. +- **Density Plot** and **Violin Plot**: `bandwidth` sets the smoothing, from 0.05 to 2; leave it unset to pick one from the data. +- **Boxplot**: `whiskerMethod` is `iqr` (1.5 times the interquartile range, the default) or `minmax`. +- **Pyramid Chart**: bind `color` to a dimension with exactly two values, one for each side. + +The spread of average bid price across advertisers, per device type, with `detail` bound to `advertiser_name`. + + + +## Tables & Maps + +Grids that show a measure for each combination of two categories, or rank categories in a compact table. Maps are not offered here; use the [Map widget](/developers/build/dashboards/canvas-widgets/map) instead. + +| Chart type | Use it for | Channels | Chart properties | +|---|---|---|---| +| Heatmap | A measure across two categories, or a category over time | **x**, **y**, **color**, column, row | `showTextLabels` | +| Bar Table | A ranked list with inline bars and values | **y**, **x**, color, column, row | `maxRows`, `showPercent` | + +- **Heatmap**: the measure goes on `color`. +- **Bar Table**: `maxRows` caps the rows shown, from 5 to 100, and `showPercent` adds each row's share of the total. + +Daily spend by device type. The query truncates the time dimension to a day, so the heatmap stays daily whatever the dashboard's time grain. + + + +## Lines & Areas + +Charts that follow a measure along an ordered axis, usually time. + +| Chart type | Use it for | Channels | Chart properties | +|---|---|---|---| +| Line Chart | A measure over time, optionally split into series | **x**, **y**, color, strokeDash, detail, opacity, column, row | `interpolate`, `showPoints` | +| Sparkline | Compact trend lines, one row per series | **x**, **y**, color, detail, column, row | `interpolate`, `baseline`, `trendWidth` | +| Bump Chart | How the rank of each series changes over time | **x**, **y**, color, detail, column, row | | +| Slope Chart | Change between two periods, one line per category | **x**, **y**, color, detail, column, row | | +| Area Chart | A measure over time, stacked by series | **x**, **y**, color, opacity, column, row | `interpolate`, `opacity`, `stackMode` | +| Streamgraph | Stacked series around a center line | **x**, **y**, **color**, column, row | `interpolate` | +| Range Area Chart | A band between a low and a high measure | **x**, **y**, **y2**, color, column, row | `interpolate`, `opacity` | + +- `interpolate` is `linear` (the default), `monotone`, `step`, `step-before`, `step-after`, `basis`, `cardinal` or `catmull-rom`. +- **Sparkline**: `baseline` draws a reference line in each row at the `mean` (the default), `median` or `zero`, or `none`; `trendWidth` sets the width of each line in pixels. +- **Bump Chart**: `y` must already hold ranks, with 1 as the best. The chart reverses the axis to put rank 1 on top but doesn't rank values itself, and Metrics SQL can't compute ranks. +- **Slope Chart**: bind `x` to a dimension with exactly two values, the two periods. +- **Area Chart**: `stackMode` is `normalize` for 100% stacks, `center`, or `layered` to overlap the series instead of stacking them. + +The band between the average bid floor and the average bid price, by day. + + + +## Circular + +Charts drawn around a center. Pie and donut charts read best with a handful of slices; components from the gallery default to 10 rows. + +| Chart type | Use it for | Channels | Chart properties | +|---|---|---|---| +| Pie Chart | Shares of a total | **size**, **color**, column, row | `innerRadius`, `sortSlices` | +| Donut Chart | Shares of a total, with a hole in the middle | **size**, **color**, column, row | `innerRadius`, `sortSlices` | +| Rose Chart | A measure per category, as wedges of varying length | **x**, **y**, color, column, row | `alignment`, `padAngle`, `sortSlices` | +| Radar Chart | Several measures or categories compared as a polygon | **x**, **y**, color, column, row | `filled`, `fillOpacity`, `strokeWidth` | + +- **Pie Chart** and **Donut Chart**: the measure goes on `size` and the category on `color`. `innerRadius` is the size of the hole as a percentage of the radius, from 0 to 100; a `Pie Chart` with an `innerRadius` above 0 is a donut. `sortSlices` is `none` (the default), `descending` or `ascending`. +- **Rose Chart**: `padAngle` sets the gap between wedges, from 0 to 0.1, and `alignment` is `left` or `center`. +- **Radar Chart**: `filled` (on by default) fills each polygon, with `fillOpacity` from 0 to 0.5; `strokeWidth` sets the line width. + +Share of spend by player size. + + + +## Properties Shared Across Chart Types + +These properties are shared by many chart types; a chart type that doesn't support one ignores it. Set them only to override what Rill derives. + +| Property | Values | Effect | +|---|---|---| +| `logScale_x`, `logScale_y` | `true` or `false` | Use a logarithmic scale on the axis. | +| `includeZero_x`, `includeZero_y` | `true` or `false` | Force the axis to include zero, or not. | +| `independentYAxis` | `true` or `false` | With small multiples, give each panel its own y scale. | +| `xAxisType`, `yAxisType` | `temporal` or `nominal` | Draw a time axis as discrete bands, or the reverse. Bar, lollipop, line, area and heatmap charts. | diff --git a/docs/docs/developers/build/dashboards/canvas-widgets/custom-viz/custom-viz.md b/docs/docs/developers/build/dashboards/canvas-widgets/custom-viz/custom-viz.md new file mode 100644 index 00000000000..fcd4747ece8 --- /dev/null +++ b/docs/docs/developers/build/dashboards/canvas-widgets/custom-viz/custom-viz.md @@ -0,0 +1,338 @@ +--- +title: "Custom Viz Components" +sidebar_label: "Custom Viz" +sidebar_position: 15 +--- + +A custom viz is a chart you define once and reuse across canvas dashboards. Each one is a `type: component` file, kept under `viz/` by convention, with three parts: + +- **Params**: the inputs a dashboard binds, such as a metrics view, a measure and a dimension. +- **A query**: a [Metrics SQL](/developers/build/metrics-view/metrics-sql) query that selects the rows to draw. +- **A chart spec**: the chart type, and which field goes on which channel. + +Use a custom viz when the native [chart widgets](/developers/build/dashboards/canvas-widgets/chart) don't offer the chart you need, such as a box plot, a bullet chart, a waterfall or a radar chart, or when you want one chart definition that many dashboards bind to their own metrics views and fields. + +Rill compiles the chart spec to Vega-Lite with [Flint](https://github.com/microsoft/flint-chart), an open-source chart compiler from Microsoft, and describes each field to Flint using your metrics view: a currency measure is formatted as currency, a percentage as a percentage, and the time dimension is bucketed at the dashboard's time grain. Scales, axes, sorting, stacking, colors and layout are derived from the data. The spec stays short, and the chart keeps working when a dashboard binds it to a different metrics view or field. + +![A canvas dashboard built from custom viz components](/img/build/dashboard/canvas/custom-viz/overview.png) + +When a chart needs something the chart spec does not offer, such as a reference line, an annotation or a second axis, you can [convert it to Vega-Lite](/developers/build/dashboards/canvas-widgets/custom-viz/vega-lite) and edit the spec directly. + +:::note Feature flag +Custom viz authoring is behind a feature flag. Enable it in `rill.yaml`: + +```yaml +features: + custom_components: true +``` + +The flag adds the **Custom viz** entry to the **Add** menu, the component editor, and **Your components** in the canvas widget menu. Dashboards render the custom viz they reference whether or not the flag is set. +::: + +## Create a Custom Viz + +In Rill Developer, open the **Add** menu and choose **Custom viz**. + +![The Add menu with the Custom viz entry](/img/build/dashboard/canvas/custom-viz/add-menu.png) + +The **Chart types** gallery shows every chart type Rill can draw, grouped by family. Pick the example closest to the chart you want, or choose **Start blank** for an empty component. [Chart Types](/developers/build/dashboards/canvas-widgets/custom-viz/chart-types) describes each type and the fields it needs. + +![The Chart types gallery](/img/build/dashboard/canvas/custom-viz/chart-types-gallery.png) + +Picking a chart type writes a component to `viz/` and opens it in the component editor. The editor draws a live preview against **Test values**, shown in the panel on the right. Rill picks them from your project, choosing for each param a field that suits the channel it is drawn on: a dimension that splits the chart into colored series, for example, gets one with 2 to 12 values. Change them to preview the chart with other fields. Test values belong to the editor; each dashboard binds its own. + +![The component editor with a live preview and test values](/img/build/dashboard/canvas/custom-viz/component-editor.png) + +The toggle beside the file name switches between the preview and the YAML. The preview's **Data** tab lists the rows the query returned, and a notice above the chart tells you when the query returned as many rows as its `LIMIT` allows, which means some may be missing. + +:::tip Working with the AI assistant +When the AI assistant is enabled, picking a chart type hands it to the developer agent, which writes the same params, query and chart spec and checks that the query runs against your data before the editor opens. Until then the file shows **Importing example using AI…**. In the editor, type a change into the box at the top of the **Preview** panel and press Enter to have the agent make it, and use **Fix with AI** when the query or the chart fails. +::: + +## The Component File + +This is the file the gallery writes for a multi-series **Line Chart**, with the query split over several lines and the `description` rewritten. The gallery sets `display_name` to the chart type and copies the caption of the example you picked into `description`; replace both with text that describes your chart. The canvas widget menu and inspector list the component by its display name. + +```yaml +# viz/line_chart.yaml +type: component +display_name: Line Chart +description: A measure over time, one line per value of a dimension. + +params: + - name: metrics_view + type: metrics_view + required: true + - name: x_axis + type: time_dimension + required: true + description: Field shown on the x channel. + - name: y_axis + type: measure + required: true + description: Field shown on the y channel. + - name: color + type: dimension + required: true + description: Field shown on the color channel. + - name: limit + type: number + default: 1000 + description: Maximum rows returned. + +custom_chart: + metrics_sql: | + SELECT {{ .params.x_axis }}, {{ .params.y_axis }}, {{ .params.color }} + FROM {{ .params.metrics_view }} + ORDER BY {{ .params.x_axis }}, {{ .params.y_axis }} DESC + LIMIT {{ .params.limit }} + spec: + chartType: Line Chart + encodings: + x: { field: "{{ .params.x_axis }}" } + y: { field: "{{ .params.y_axis }}" } + color: { field: "{{ .params.color }}" } +``` + +- `params` declares what a dashboard binds. The rest of the file refers to each param as `{{ .params. }}`. +- `custom_chart` is the renderer. It holds the query, `metrics_sql`, and the chart spec, `spec`. +- The query selects every field the chart draws. Measures arrive aggregated, grouped by the selected dimensions. +- The chart spec names the chart type and binds each channel to a param. + +## Params + +Params are the contract between a component and the dashboards that use it. Each param has a `name` and a `type`, and optionally: + +| Property | Description | +|---|---| +| `required` | Every dashboard that uses the component must bind a value. Mutually exclusive with `default`. | +| `default` | The value used when a dashboard doesn't bind one. | +| `description` | Help text shown below the param's input in the component editor and the canvas inspector. | +| `options` | For `string` and `number` params, the allowed values. The input becomes a dropdown. | +| `metrics_view` | For field params, the name of the `metrics_view` param whose metrics view the field belongs to. Only needed when a component declares more than one `metrics_view` param. | + +The type determines what a param binds to: + +| Type | Binds | Notes | +|---|---|---| +| `metrics_view` | The name of a metrics view | The param must be named `metrics_view` or end with `_metrics_view`. | +| `measure` | A measure of the bound metrics view | | +| `dimension` | A dimension of the bound metrics view | | +| `time_dimension` | The primary time dimension, or a time-typed dimension | | +| `string`, `number`, `boolean` | A literal value | Row limits, thresholds, chart properties. | + +Inputs are labeled with the param's name in title case, so `x_axis` reads **X Axis**. Name params after their role in the chart rather than after a field, because the same component is bound to different fields. The names `title`, `description`, `datum`, `item`, `event` and `parent` are reserved. + +### Templating + +A param's value is substituted wherever `{{ .params. }}` appears in the renderer. Rill checks every reference against the declared params when it parses the file, so a misspelled name is reported as an error rather than drawing an empty chart. + +A value that consists of a single placeholder keeps the param's type. With `inner_radius` declared as a `number`, `innerRadius: "{{ .params.inner_radius }}"` reaches the chart as the number `50`, not the string `"50"`. A placeholder inside a longer string, such as `LIMIT {{ .params.limit }}`, is substituted as text. + +Field params also expose the field's metadata from the metrics view, such as `{{ .fields..display_name }}`. A chart spec doesn't need it, because Rill supplies titles and formats; a [converted Vega-Lite spec](/developers/build/dashboards/canvas-widgets/custom-viz/vega-lite#templating) uses it for axis titles and number formats. + +### Change a Component That Dashboards Use + +The component editor's **Used by** panel lists the dashboards that use the component. **Use this dashboard's values** under a dashboard loads its bindings as test values, so you can preview a change against a real use. + +Rill validates every use when a canvas reconciles: an unknown param, a missing required param, a value of the wrong type, or a field that doesn't exist in the bound metrics view is reported as an error on the dashboard. To change a component without breaking the dashboards that use it, add params with a `default` rather than `required: true`, and don't rename, remove or change the type of existing params. The editor warns you when a change would break the dashboards in **Used by**. + +## The Query + +`metrics_sql` treats the metrics view as a table: you select dimensions and measures, and Rill groups and aggregates them. `WHERE` and `HAVING` filter the rows. Keep the rest of the query to `SELECT`, `FROM`, `ORDER BY` and `LIMIT`: + +- Select every field the chart draws or the query orders by. Ordering by a field that isn't selected fails. +- Don't wrap measures in aggregate functions such as `SUM()`; they arrive aggregated. +- Don't rename fields with `AS`. The chart reads each column by its field name, and Metrics SQL rejects the alias. The exception is an expression aliased back to its field's name, such as a [truncated time dimension](#time-dimensions). +- `WITH`, joins, window functions and `CASE` are not supported, and subqueries only work inside an `IN` filter. The [Metrics SQL reference](/developers/build/metrics-view/metrics-sql) lists the full syntax. +- Order a time series by its time dimension, and anything else by its main measure descending, so that the `LIMIT` keeps the rows the chart needs. + +### Time Dimensions + +Select a time dimension as is and Rill buckets it at the dashboard's time grain, so the chart re-buckets when a viewer changes the grain. The component editor has no time controls, so its preview buckets by day. + +To keep a fixed bucket regardless of the dashboard, truncate the time dimension in the query and alias it back to its own name: + +```sql +SELECT date_trunc('day', {{ .params.x_axis }}) AS {{ .params.x_axis }}, {{ .params.y_axis }}, {{ .params.color }} +FROM {{ .params.metrics_view }} +``` + +The alias is required: without it, the column is named after the expression and the chart can't find it. In the dashboard at the top of this page, the heatmap is truncated this way and stays daily while the line chart above it follows the hourly grain. + +### Row Limits + +A metrics view returns one row per combination of the selected dimensions, and a chart draws one mark per row. Size the default `limit` to the marks the chart needs: about 25 rows for one mark per category, 10 for pie and donut charts, about 150 when categories are split into series, 500 for a time series and 1,000 for a time series split into series. A limit that is too low for a split chart fills up on the first categories or time buckets and leaves the rest of the chart empty. + +Charts that plot individual observations, such as box plots, histograms and scatter plots, need one row per observation, but a metrics view aggregates: selecting only the plotted fields returns one row per category. Gallery components for these charts declare a `detail` dimension param that the query selects and the chart never draws. It sets the grain of the rows, so binding it to `advertiser_name` gives one row, and one point, per advertiser. + +## The Chart Spec + +`spec` has three keys: + +| Key | Description | +|---|---| +| `chartType` | One of the [chart types](/developers/build/dashboards/canvas-widgets/custom-viz/chart-types), spelled exactly, such as `Line Chart` or `Heatmap`. | +| `encodings` | A mapping from channel to field. Each value is an encoding object, or a field name as shorthand for `{ field: }`. | +| `chartProperties` | Optional settings the chart type exposes, such as a donut's `innerRadius`. | + +Everything else is derived: sizes, axis titles, number formats, tick counts, fonts and legends. A chart spec has no title. + +### Encodings + +The channels are `x` and `y`; `x2` and `y2` for the end of a range; `color`, `size`, `shape`, `opacity` and `strokeDash`; `detail` and `order`; `group` for side-by-side bars; `goal` for a target; `column` and `row` for small multiples; and `open`, `high`, `low` and `close` for candlesticks. Each chart type takes a subset, listed in [Chart Types](/developers/build/dashboards/canvas-widgets/custom-viz/chart-types). + +An encoding object takes these keys: + +| Key | Values | Purpose | +|---|---|---| +| `field` | A field name, usually `"{{ .params. }}"` | The field to draw. | +| `sortBy` | A channel, such as `x`, or a field | Sort a category axis by another channel's values. | +| `sortOrder` | `ascending` or `descending` | The direction of that sort. | +| `scheme` | A [Vega color scheme](https://vega.github.io/vega/docs/schemes/) name, such as `tableau10` or `viridis` | The colors of the `color` channel. | +| `type` | `quantitative`, `nominal`, `ordinal` or `temporal` | Override how the field is encoded. | +| `aggregate` | `count`, `sum`, `average` or `mean` | Aggregate the channel's values. | + +For example, this draws horizontal bars, longest first: + +```yaml +spec: + chartType: Bar Chart + encodings: + y: { field: "{{ .params.dimension }}", sortBy: x, sortOrder: descending } + x: { field: "{{ .params.measure }}" } +``` + +Leave out `type` and `aggregate` unless the chart draws wrong without them. Rill already marks measures as quantitative, dimensions as nominal and time dimensions as temporal, and measures arrive aggregated; a hard-coded type can break the chart when a dashboard binds a different field. + +### Field Semantics + +Rill tells Flint what each selected field means, based on the metrics view: + +| Field | Treated as | +|---|---| +| A measure formatted as currency, by `format_preset` or by a `format_d3` with a currency symbol | An amount | +| A measure formatted as a percentage | A percentage | +| A measure with the `interval` format preset | A duration | +| Any other measure with an integer type | A count | +| Any other measure | A number | +| A time dimension | A date at the grain it was bucketed at, from year down to day, or a timestamp below a day | +| Any other dimension | A category | + +These decide zero baselines, tick spacing and the kind of color scale. Axis labels and tooltips format each measure with its own format, the same as everywhere else in Rill, and titles use display names. + +### Colors + +Series colors come from the `tableau10` scheme, and a measure bound to `color` is drawn with a sequential scheme such as `blues`. Set `scheme` on the `color` encoding to choose another. Charts drawn in a single color use the dashboard theme's primary color. To color series with the [theme's palette](/developers/build/dashboards/customization#changing-themes--colors) instead, [convert the component](/developers/build/dashboards/canvas-widgets/custom-viz/vega-lite#use-the-theme-palette) and remove the scheme. + +### Chart Properties + +`chartProperties` adjusts choices a chart type exposes: line interpolation, stacking, bar corner radius, the size of a donut's hole, and so on. [Chart Types](/developers/build/dashboards/canvas-widgets/custom-viz/chart-types) lists the properties of each type. Properties a chart type doesn't have are ignored, and an invalid value falls back to the property's default with a notice above the chart. + +```yaml +spec: + chartType: Line Chart + encodings: + x: { field: "{{ .params.x_axis }}" } + y: { field: "{{ .params.y_axis }}" } + chartProperties: + interpolate: monotone + showPoints: true +``` + +To let each dashboard choose a property, bind it to a param: + +```yaml +params: + # ... + - name: inner_radius + type: number + default: 50 + description: Size of the hole, as a percentage of the radius. Set to 0 for a pie. + +custom_chart: + # ... + spec: + chartType: Donut Chart + encodings: + size: { field: "{{ .params.size }}" } + color: { field: "{{ .params.color }}" } + chartProperties: + innerRadius: "{{ .params.inner_radius }}" +``` + +A donut's `innerRadius` is a percentage of the chart's radius, so the hole keeps its proportions at any widget size. + +### Several Measures on One Axis + +To draw several measures as series, give `x` or `y` a list of encodings: + +```yaml +spec: + chartType: Line Chart + encodings: + x: { field: "{{ .params.x_axis }}" } + y: + - field: "{{ .params.first_measure }}" + - field: "{{ .params.second_measure }}" +``` + +The measures share one axis, so pair measures of a similar scale and unit. The list can't be combined with a `color` encoding. Series are labeled with field names rather than display names, and values are not formatted with the measures' formats; for full control, convert the component and [give each measure its own axis](/developers/build/dashboards/canvas-widgets/custom-viz/vega-lite#a-second-axis). + +### Small Multiples + +Bind a dimension to `column` or `row` to draw one panel per value: + +```yaml +spec: + chartType: Area Chart + encodings: + x: { field: "{{ .params.x_axis }}" } + y: { field: "{{ .params.y_axis }}" } + column: { field: "{{ .params.column }}" } +``` + +Each panel shares the widget's space, so keep the dimension to a handful of values. + +## Add a Custom Viz to a Canvas + +In the canvas editor, open the widget menu, choose **Your components**, and pick the custom viz. Rill fills in the params with a metrics view and fields from your project, and the inspector shows an input for every param so you can rebind them. **Edit component** opens the component file; edits there apply to every dashboard that uses it. + +![Your components in the canvas widget menu](/img/build/dashboard/canvas/custom-viz/canvas-your-components.png) + +![A custom viz selected on a canvas, with its param bindings in the inspector](/img/build/dashboard/canvas/custom-viz/canvas-inspector.png) + +In YAML, a canvas item names the component and binds its params: + +```yaml +# dashboards/custom_viz_overview.yaml +type: canvas +display_name: Custom viz overview +defaults: + time_range: 7D as of latest/D +rows: + - items: + - component: line_chart + params: + metrics_view: bids_metrics + x_axis: __time + y_axis: overall_spend + color: app_or_site + width: 8 + - component: donut_chart + params: + metrics_view: bids_metrics + size: overall_spend + color: player_size + width: 4 + height: 360px +``` + +Bound values must be literals: strings, numbers or booleans. Params that have a default can be left out. A custom viz follows the canvas's time range, time grain and dimension filters like any other widget. + +## Limits + +- A component runs one query, so a chart can't combine two datasets. +- When a category axis has more values than fit in the widget, the chart drops the extra rows and says so above the chart. +- A custom viz doesn't draw time comparisons. +- Custom viz components are separate from the inline `custom_chart` widget that a canvas can define in its own YAML. That widget always takes a Vega-Lite `vega_spec`. A component file takes a chart `spec`, and carries a `vega_spec` only after it has been converted. diff --git a/docs/docs/developers/build/dashboards/canvas-widgets/custom-viz/vega-lite.md b/docs/docs/developers/build/dashboards/canvas-widgets/custom-viz/vega-lite.md new file mode 100644 index 00000000000..f6c583e3e8d --- /dev/null +++ b/docs/docs/developers/build/dashboards/canvas-widgets/custom-viz/vega-lite.md @@ -0,0 +1,490 @@ +--- +title: "Customize a Custom Viz with Vega-Lite" +sidebar_label: "Vega-Lite" +sidebar_position: 20 +--- + +A chart spec draws the [chart types](/developers/build/dashboards/canvas-widgets/custom-viz/chart-types) Flint knows, with the encodings and properties each one exposes. For anything beyond that, such as a reference line, an annotation, a second axis, a computed series, an interaction, or exact control over an axis, a legend or the colors, convert the component to [Vega-Lite](https://vega.github.io/vega-lite/) and edit the spec directly. + +Converting replaces the chart spec with the Vega-Lite it compiles to. It is a one-way change, and it gives up what makes a chart spec adapt: Rill no longer derives scales, axes, formats, sorting, stacking and layout from the data, and the Vega-Lite reflects the data it was compiled against. Check first whether a different chart type, `sortBy`, `scheme` or a chart property does what you need. + +## Convert a Component + +1. Open the component in the component editor, and set **Test values** that represent how dashboards use it. **Use this dashboard's values** in the **Used by** panel loads a dashboard's bindings. Rill compiles the chart against these values, so their data shapes the Vega-Lite it writes. +2. Click **Convert to Vega-Lite** and confirm. + +![The Convert to Vega-Lite confirmation in the component editor](/img/build/dashboard/canvas/custom-viz/convert-confirm.png) + +Rill replaces `spec` with `vega_spec`. The params and the query don't change, so every dashboard that uses the component keeps working, and the chart looks the same until you edit it. Once a component has a `vega_spec`, the button is no longer shown. To return to the chart spec, undo the edit or restore the file from version control. + +## What Converting Writes + +Converting a ranked bar chart, bound in the editor to `advertiser_name` and `overall_spend`: + +```yaml +custom_chart: + metrics_sql: | + SELECT {{ .params.dimension }}, {{ .params.measure }} + FROM {{ .params.metrics_view }} + ORDER BY {{ .params.measure }} DESC + LIMIT {{ .params.limit }} + spec: + chartType: Bar Chart + encodings: + y: { field: "{{ .params.dimension }}", sortBy: x, sortOrder: descending } + x: { field: "{{ .params.measure }}" } +``` + +writes: + +```yaml +custom_chart: + metrics_sql: | + SELECT {{ .params.dimension }}, {{ .params.measure }} + FROM {{ .params.metrics_view }} + ORDER BY {{ .params.measure }} DESC + LIMIT {{ .params.limit }} + vega_spec: |- + { + "$schema": "https://vega.github.io/schema/vega-lite/v5.json", + "width": "container", + "height": "container", + "autosize": { + "type": "fit", + "contains": "padding" + }, + "data": { + "name": "query1" + }, + "mark": "bar", + "encoding": { + "x": { + "field": "{{ .params.measure }}", + "type": "quantitative", + "title": "{{ .fields.measure.display_name }}", + "scale": { + "zero": true + }, + "axis": { + "formatType": "{{ .fields.measure.format_type }}" + }, + "formatType": "{{ .fields.measure.format_type }}" + }, + "y": { + "field": "{{ .params.dimension }}", + "type": "nominal", + "sort": "-x", + "title": "{{ .fields.dimension.display_name }}" + } + }, + "config": { + "axisX": { + "labelLimit": 100, + "labelFontSize": 10, + "titleFontSize": 11 + }, + "axisY": { + "labelFontSize": 10, + "titleFontSize": 11 + }, + "legend": { + "labelFontSize": 10, + "titleFontSize": 11 + }, + "facet": { + "spacing": 32 + }, + "mark": { + "tooltip": true + } + } + } +``` + +The compiled spec contained the test values: field names, display names and formatter names. Rill turns each back into a template reference before writing the file, so the spec keeps working for every binding: + +| In the compiled spec | Written as | +|---|---| +| A field name, in an encoding, a transform or a `datum` expression | `{{ .params. }}` | +| A field's display name, as an axis, legend or tooltip title | `{{ .fields..display_name }}` | +| A measure's formatter, in `formatType` | `{{ .fields..format_type }}` | + +The `config` block holds the label sizes and spacing the chart was compiled with, and `"tooltip": true` adds a tooltip for every encoded field. + +## Rules for a Component's Vega-Lite + +These apply to every `vega_spec` in a component file, converted or written by hand: + +- **Data**: read the query result from `"data": { "name": "query1" }`. A component runs one query, so there is no other dataset; don't inline `values` or load a `url`. +- **Size**: keep `"width": "container"`, `"height": "container"` and `"autosize": { "type": "fit", "contains": "padding" }` so the chart fills its widget. Without `autosize`, only the width is fitted, and axes and legends can run past the bottom of the widget. Vega-Lite can't size faceted or concatenated views to a container; give their panels fixed sizes. +- **Types**: set `type` on every encoding. Nothing is derived any more, and a missing type can draw the wrong scale. +- **Time**: encode a time dimension as `temporal`. The query still buckets it at the dashboard's time grain, as described in [Time Dimensions](/developers/build/dashboards/canvas-widgets/custom-viz#time-dimensions). Vega-Lite labels times in the viewer's browser time zone; on a dashboard that uses UTC, add `"scale": { "type": "utc" }` to the encoding so the labels match the buckets for every viewer. +- **Bindings**: template field names, titles and formats instead of writing them literally, so the component keeps working when a dashboard binds other fields. + +A component can't have both `spec` and `vega_spec`; Rill reports an error when it parses the file. + +## Templating + +| Reference | Value | +|---|---| +| `{{ .params. }}` | The value bound to any param. | +| `{{ .fields..name }}` | The field name, the same as `{{ .params. }}`. | +| `{{ .fields..display_name }}` | The field's display name from the metrics view; `Time` for the primary time dimension. | +| `{{ .fields..format_type }}` | The name of the formatter Rill registers for a measure, for use as `formatType`. | +| `{{ .fields..format_d3 }}` | The measure's `format_d3`, if it sets one. | +| `{{ .fields..format_preset }}` | The measure's `format_preset`, if it sets one. | +| `{{ .fields..type }}` | The param's type: `measure`, `dimension` or `time_dimension`. | + +`.fields` exists for `measure`, `dimension` and `time_dimension` params. Rill rejects a reference to `.fields` on another type of param, or to a property not listed here, when it parses the file. + +Values are JSON-escaped when they are substituted into `vega_spec`, so a display name or string param containing a quote or backslash is safe inside any JSON string. Keep every placeholder inside a JSON string, such as `"title": "{{ .fields.measure.display_name }}"`; a string param cannot carry a JSON fragment to splice in as structure. + +Format values with `"formatType": "{{ .fields..format_type }}"` rather than a `format` string. It formats a measure the way Rill formats it everywhere else, including currency, percentage and interval presets. Set it on an axis, on a `tooltip` or `text` encoding, or on a field you compute from the measure, such as a running total. The formatter is also a function you can call in an expression, as the [reference line](#a-reference-line) example does to format its label. + +### Scalar Params in Expressions + +`string`, `number` and `boolean` params are also added to the spec's top-level `params`, so Vega expressions, predicates and conditions can read them by name: a `number` param named `target` is available as `target`. If the spec already declares a top-level param with that name, Rill sets only its `value`. + +## Examples + +The examples below are converted components bound to the `bids_metrics` metrics view, each shown as its `vega_spec`. Each names the params its spec references, and its query selects those fields as described in [The Query](/developers/build/dashboards/canvas-widgets/custom-viz#the-query). The [Vega-Lite example gallery](https://vega.github.io/vega-lite/examples/) has many more patterns to adapt: replace the example's `data` with `{ "name": "query1" }`, its field names with param references, and its size settings with the container settings above. + +### A Reference Line + +Bars ranked by a measure, with a dashed line at their average. This is the converted bar chart above, with the same `dimension` and `measure` params and query. The second layer aggregates the rows to their mean and builds the label by calling the measure's formatter, so the average reads `$1.3k` for a currency measure and `12%` for a percentage. + +![A ranked bar chart with a dashed line and a label at the average](/img/build/dashboard/canvas/custom-viz/vl-average-line.png) + +```yaml +vega_spec: | + { + "$schema": "https://vega.github.io/schema/vega-lite/v5.json", + "width": "container", + "height": "container", + "autosize": { "type": "fit", "contains": "padding" }, + "data": { "name": "query1" }, + "layer": [ + { + "mark": "bar", + "encoding": { + "y": { + "field": "{{ .params.dimension }}", + "type": "nominal", + "sort": "-x", + "title": "{{ .fields.dimension.display_name }}" + }, + "x": { + "field": "{{ .params.measure }}", + "type": "quantitative", + "title": "{{ .fields.measure.display_name }}", + "axis": { "formatType": "{{ .fields.measure.format_type }}" } + }, + "tooltip": [ + { "field": "{{ .params.dimension }}", "title": "{{ .fields.dimension.display_name }}" }, + { + "field": "{{ .params.measure }}", + "type": "quantitative", + "title": "{{ .fields.measure.display_name }}", + "formatType": "{{ .fields.measure.format_type }}" + } + ] + } + }, + { + "transform": [ + { "aggregate": [{ "op": "mean", "field": "{{ .params.measure }}", "as": "average" }] }, + { "calculate": "'Average ' + {{ .fields.measure.format_type }}(datum.average)", "as": "label" } + ], + "encoding": { "x": { "field": "average", "type": "quantitative" } }, + "layer": [ + { "mark": { "type": "rule", "strokeDash": [4, 4], "color": "#475569" } }, + { + "mark": { "type": "text", "align": "left", "baseline": "bottom", "dx": 6, "dy": -6, "color": "#475569" }, + "encoding": { "y": { "value": "height" }, "text": { "field": "label" } } + } + ] + } + ] + } +``` + +The `y` encoding sits in the bar layer only. Placed at the top level, the rule layer would inherit it and draw one line per bar. + +### Highlight Values That Meet a Target + +Bars that reach a target in one color and the rest in grey, with the target drawn as a line. The target is a `number` param, so each dashboard sets its own: + +```yaml +params: + # metrics_view, dimension, measure and limit as before + - name: target + type: number + default: 0 + description: Bars at or above this value are highlighted. +``` + +The color condition and the line read the param by name, `target`, because Rill adds scalar params to the spec's `params`. The chart below binds `avg_bid_price` with a target of `5`. + +![Bars at or above a target highlighted, with a dashed target line](/img/build/dashboard/canvas/custom-viz/vl-target.png) + +```yaml +vega_spec: | + { + "$schema": "https://vega.github.io/schema/vega-lite/v5.json", + "width": "container", + "height": "container", + "autosize": { "type": "fit", "contains": "padding" }, + "data": { "name": "query1" }, + "layer": [ + { + "mark": "bar", + "encoding": { + "x": { + "field": "{{ .params.dimension }}", + "type": "nominal", + "sort": "-y", + "title": "{{ .fields.dimension.display_name }}" + }, + "y": { + "field": "{{ .params.measure }}", + "type": "quantitative", + "title": "{{ .fields.measure.display_name }}", + "axis": { "formatType": "{{ .fields.measure.format_type }}" } + }, + "color": { + "condition": { "test": "datum['{{ .params.measure }}'] >= target", "value": "#4f46e5" }, + "value": "#cbd5e1" + }, + "tooltip": [ + { "field": "{{ .params.dimension }}", "title": "{{ .fields.dimension.display_name }}" }, + { + "field": "{{ .params.measure }}", + "type": "quantitative", + "title": "{{ .fields.measure.display_name }}", + "formatType": "{{ .fields.measure.format_type }}" + } + ] + } + }, + { + "transform": [ + { "aggregate": [{ "op": "count", "as": "rows" }] }, + { "calculate": "target", "as": "target_value" }, + { "calculate": "'Target ' + {{ .fields.measure.format_type }}(target)", "as": "label" } + ], + "encoding": { "y": { "field": "target_value", "type": "quantitative" } }, + "layer": [ + { "mark": { "type": "rule", "strokeDash": [4, 4], "color": "#475569" } }, + { + "mark": { "type": "text", "align": "right", "baseline": "bottom", "dy": -4, "color": "#475569" }, + "encoding": { "x": { "value": "width" }, "text": { "field": "label" } } + } + ] + } + ] + } +``` + +The `aggregate` transform reduces the rows to one, so the line and its label are drawn once. + +### A Second Axis + +A volume measure as an area on the left axis and a rate as a line on the right. The params are a time dimension, `x_axis`, and two measures, `area_measure` and `line_measure`. `resolve` gives each layer its own y scale, and each axis title takes the color of its series. The time axis is formatted explicitly, with one tick per day. + +![Impressions as an area and click-through rate as a line, on two axes](/img/build/dashboard/canvas/custom-viz/vl-dual-axis.png) + +```yaml +vega_spec: | + { + "$schema": "https://vega.github.io/schema/vega-lite/v5.json", + "width": "container", + "height": "container", + "autosize": { "type": "fit", "contains": "padding" }, + "data": { "name": "query1" }, + "encoding": { + "x": { + "field": "{{ .params.x_axis }}", + "type": "temporal", + "title": "{{ .fields.x_axis.display_name }}", + "axis": { "format": "%b %d", "tickCount": "day" } + }, + "tooltip": [ + { "field": "{{ .params.x_axis }}", "type": "temporal", "title": "{{ .fields.x_axis.display_name }}" }, + { + "field": "{{ .params.area_measure }}", + "type": "quantitative", + "title": "{{ .fields.area_measure.display_name }}", + "formatType": "{{ .fields.area_measure.format_type }}" + }, + { + "field": "{{ .params.line_measure }}", + "type": "quantitative", + "title": "{{ .fields.line_measure.display_name }}", + "formatType": "{{ .fields.line_measure.format_type }}" + } + ] + }, + "layer": [ + { + "mark": { "type": "area", "color": "#818cf8", "opacity": 0.4 }, + "encoding": { + "y": { + "field": "{{ .params.area_measure }}", + "type": "quantitative", + "title": "{{ .fields.area_measure.display_name }}", + "axis": { "formatType": "{{ .fields.area_measure.format_type }}", "titleColor": "#4f46e5" } + } + } + }, + { + "mark": { "type": "line", "color": "#ea580c", "strokeWidth": 2 }, + "encoding": { + "y": { + "field": "{{ .params.line_measure }}", + "type": "quantitative", + "title": "{{ .fields.line_measure.display_name }}", + "axis": { + "orient": "right", + "grid": false, + "formatType": "{{ .fields.line_measure.format_type }}", + "titleColor": "#ea580c" + } + } + } + } + ], + "resolve": { "scale": { "y": "independent" } } + } +``` + +The query selects the time dimension and both measures, ordered by time. A daily tick suits ranges of a few weeks; for longer ranges, use a coarser `tickCount`, such as `week` or `month`. + +### A Running Total + +The cumulative sum of a measure over time, from `x_axis` and `measure` params and a query that selects both, ordered by time. Metrics SQL can't compute values across rows, but a Vega-Lite `window` transform can, over the rows the query returns. The same approach gives moving averages, ranks and shares of a total. + +![The cumulative sum of advertising spend over a week](/img/build/dashboard/canvas/custom-viz/vl-running-total.png) + +```yaml +vega_spec: | + { + "$schema": "https://vega.github.io/schema/vega-lite/v5.json", + "width": "container", + "height": "container", + "autosize": { "type": "fit", "contains": "padding" }, + "data": { "name": "query1" }, + "transform": [ + { + "window": [{ "op": "sum", "field": "{{ .params.measure }}", "as": "running_total" }], + "sort": [{ "field": "{{ .params.x_axis }}" }] + } + ], + "mark": { "type": "area", "line": true, "opacity": 0.3 }, + "encoding": { + "x": { + "field": "{{ .params.x_axis }}", + "type": "temporal", + "title": "{{ .fields.x_axis.display_name }}" + }, + "y": { + "field": "running_total", + "type": "quantitative", + "title": "Cumulative {{ .fields.measure.display_name }}", + "axis": { "formatType": "{{ .fields.measure.format_type }}" } + }, + "tooltip": [ + { "field": "{{ .params.x_axis }}", "type": "temporal", "title": "{{ .fields.x_axis.display_name }}" }, + { + "field": "{{ .params.measure }}", + "type": "quantitative", + "title": "{{ .fields.measure.display_name }}", + "formatType": "{{ .fields.measure.format_type }}" + }, + { + "field": "running_total", + "type": "quantitative", + "title": "Cumulative", + "formatType": "{{ .fields.measure.format_type }}" + } + ] + } + } +``` + +The running total is computed in the browser, over the rows within the query's `LIMIT` and the dashboard's time range. It starts at zero at the beginning of the time range, not at the beginning of the data. + +### Use the Theme Palette + +A converted chart that splits into series keeps the scheme Flint chose on its `color` encoding: + +```json +"color": { + "field": "{{ .params.color }}", + "type": "nominal", + "sort": null, + "scale": { "scheme": "tableau10" }, + "title": "{{ .fields.color.display_name }}" +} +``` + +Remove the `scale` and the series take the qualitative colors of the dashboard's [theme](/developers/build/dashboards/customization#changing-themes--colors) instead. + +### Highlight a Series from the Legend + +A point selection bound to the legend: clicking a legend entry highlights its series and fades the others. This is the gallery's multi-series line chart after converting, with the same `x_axis`, `y_axis` and `color` params. It has no `scale` on `color`, so it also uses the theme's palette, and its time axis is formatted as in [A Second Axis](#a-second-axis). + +![A line chart with one series highlighted from the legend](/img/build/dashboard/canvas/custom-viz/vl-legend-selection.png) + +```yaml +vega_spec: | + { + "$schema": "https://vega.github.io/schema/vega-lite/v5.json", + "width": "container", + "height": "container", + "autosize": { "type": "fit", "contains": "padding" }, + "data": { "name": "query1" }, + "params": [ + { + "name": "segment", + "select": { "type": "point", "fields": ["{{ .params.color }}"] }, + "bind": "legend" + } + ], + "mark": "line", + "encoding": { + "color": { + "field": "{{ .params.color }}", + "type": "nominal", + "title": "{{ .fields.color.display_name }}" + }, + "opacity": { + "condition": { "param": "segment", "value": 1 }, + "value": 0.15 + }, + "x": { + "field": "{{ .params.x_axis }}", + "type": "temporal", + "title": "{{ .fields.x_axis.display_name }}", + "axis": { "format": "%b %d", "tickCount": "day" } + }, + "y": { + "field": "{{ .params.y_axis }}", + "type": "quantitative", + "title": "{{ .fields.y_axis.display_name }}", + "axis": { "formatType": "{{ .fields.y_axis.format_type }}" } + }, + "tooltip": [ + { "field": "{{ .params.x_axis }}", "type": "temporal", "title": "{{ .fields.x_axis.display_name }}" }, + { "field": "{{ .params.color }}", "title": "{{ .fields.color.display_name }}" }, + { + "field": "{{ .params.y_axis }}", + "type": "quantitative", + "title": "{{ .fields.y_axis.display_name }}", + "formatType": "{{ .fields.y_axis.format_type }}" + } + ] + } + } +``` + +The selection lives in the chart only. It doesn't filter the dashboard or other widgets. diff --git a/docs/docs/developers/build/dashboards/canvas-widgets/misc.md b/docs/docs/developers/build/dashboards/canvas-widgets/misc.md index 21e182d614e..8cf13b30014 100644 --- a/docs/docs/developers/build/dashboards/canvas-widgets/misc.md +++ b/docs/docs/developers/build/dashboards/canvas-widgets/misc.md @@ -107,4 +107,16 @@ Reference a reusable component created outside of the canvas dashboard. imageAlt="Table showing detailed data columns" code={`- component: my_component`} codeLanguage="yaml" -/> \ No newline at end of file +/> + +To reuse one chart with different metrics views and fields, define it as a [custom viz](/developers/build/dashboards/canvas-widgets/custom-viz): a component that declares params, which each canvas item binds. + +```yaml +- component: bullet_chart + params: + metrics_view: bids_metrics + y_axis: device_type + x_axis: avg_bid_price + goal: avg_bid_floor + width: 6 +``` \ No newline at end of file diff --git a/docs/docs/developers/build/dashboards/canvas.md b/docs/docs/developers/build/dashboards/canvas.md index 329b70f8fe5..60cf020a27b 100644 --- a/docs/docs/developers/build/dashboards/canvas.md +++ b/docs/docs/developers/build/dashboards/canvas.md @@ -12,11 +12,12 @@ Canvas dashboards are built using various components that can display data, crea ## Available Components -Canvas dashboards support four main types of components: +Canvas dashboards support five main types of components: - **[Data components](/developers/build/dashboards/canvas-widgets/data)** - Display structured data and key metrics (KPIs, leaderboards, tables) - **[Chart components](/developers/build/dashboards/canvas-widgets/chart)** - Create rich visualizations (bar charts, line charts, heat maps, etc.) - **[Map components](/developers/build/dashboards/canvas-widgets/map)** - Plot measures by location as points or shaded regions +- **[Custom viz components](/developers/build/dashboards/canvas-widgets/custom-viz)** - Reusable charts with params, built from 32 chart types or a Vega-Lite spec - **[Miscellaneous components](/developers/build/dashboards/canvas-widgets/misc)** - Add text, images, and other content Each component page shows both the visual result and the corresponding YAML code, making it easy to understand how to implement them in your own dashboards. diff --git a/docs/docs/developers/build/project-configuration.md b/docs/docs/developers/build/project-configuration.md index 72411e4afb7..58372f3d17d 100644 --- a/docs/docs/developers/build/project-configuration.md +++ b/docs/docs/developers/build/project-configuration.md @@ -279,6 +279,7 @@ features: - `chat`: Enables chat functionality (default: `true`) - `dashboardChat`: Enables chat features within dashboards (default: `false`) - `cloud_editing`: Enables [editing the project in Rill Cloud](/developers/deploy/cloud-editing) (default: `false`; included as `true` in new projects) +- `custom_components`: Enables authoring [custom viz](/developers/build/dashboards/canvas-widgets/custom-viz) components in Rill Developer (default: `false`) **How to enable**: Add the `features` section to your `rill.yaml` diff --git a/docs/docs/reference/project-files/component.md b/docs/docs/reference/project-files/component.md index 1302510b8a6..af12da96723 100644 --- a/docs/docs/reference/project-files/component.md +++ b/docs/docs/reference/project-files/component.md @@ -505,6 +505,7 @@ _[object]_ - (no description) _[object]_ - A custom visualization: a Metrics SQL query plus a chart spec describing how to draw it. Rill derives scales, axes, formats, sorting, stacking and layout from the data and from the metrics view's semantics, so the spec only states the chart type and which field goes on which channel. +See [Custom Viz](/developers/build/dashboards/canvas-widgets/custom-viz). - **`metrics_sql`** - _[string]_ - Metrics SQL query selecting the dimensions and measures to plot, e.g. @@ -514,9 +515,9 @@ metrics view's semantics, so the spec only states the chart type and which field - **`spec`** - _[object]_ - (no description) - - **`chartType`** - _[string]_ - The chart type, e.g. `Line Chart`, `Bar Chart`, `Heatmap`, `Waterfall Chart`. + - **`chartType`** - _[string]_ - The chart type, e.g. `Line Chart`, `Bar Chart`, `Heatmap`, `Waterfall Chart`. See [Chart Types](/developers/build/dashboards/canvas-widgets/custom-viz/chart-types) for the full list. - - **`encodings`** - _[object]_ - Maps a visual channel (`x`, `y`, `color`, `size`, `column`, `row`, `group`, `angle`, `goal`, ...) + - **`encodings`** - _[object]_ - Maps a visual channel (`x`, `y`, `x2`, `y2`, `color`, `size`, `column`, `row`, `group`, `goal`, ...) to a field. Each value is either a field name or an encoding object. @@ -528,7 +529,9 @@ metrics view's semantics, so the spec only states the chart type and which field 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"}`, and size the chart to its container with `"width": "container"`, `"height": "container"` - and `"autosize": {"type": "fit"}`. Mutually exclusive with `spec`. + and `"autosize": {"type": "fit", "contains": "padding"}`. Mutually exclusive with `spec`. + Template values are JSON-escaped, so place placeholders inside JSON strings. + See [Vega-Lite](/developers/build/dashboards/canvas-widgets/custom-viz/vega-lite). ### `map` diff --git a/docs/static/img/build/dashboard/canvas/custom-viz/add-menu.png b/docs/static/img/build/dashboard/canvas/custom-viz/add-menu.png new file mode 100644 index 00000000000..d2a160d797a Binary files /dev/null and b/docs/static/img/build/dashboard/canvas/custom-viz/add-menu.png differ diff --git a/docs/static/img/build/dashboard/canvas/custom-viz/canvas-inspector.png b/docs/static/img/build/dashboard/canvas/custom-viz/canvas-inspector.png new file mode 100644 index 00000000000..e84232a3e96 Binary files /dev/null and b/docs/static/img/build/dashboard/canvas/custom-viz/canvas-inspector.png differ diff --git a/docs/static/img/build/dashboard/canvas/custom-viz/canvas-your-components.png b/docs/static/img/build/dashboard/canvas/custom-viz/canvas-your-components.png new file mode 100644 index 00000000000..23fc176bb06 Binary files /dev/null and b/docs/static/img/build/dashboard/canvas/custom-viz/canvas-your-components.png differ diff --git a/docs/static/img/build/dashboard/canvas/custom-viz/chart-boxplot.png b/docs/static/img/build/dashboard/canvas/custom-viz/chart-boxplot.png new file mode 100644 index 00000000000..0f325f46212 Binary files /dev/null and b/docs/static/img/build/dashboard/canvas/custom-viz/chart-boxplot.png differ diff --git a/docs/static/img/build/dashboard/canvas/custom-viz/chart-bullet.png b/docs/static/img/build/dashboard/canvas/custom-viz/chart-bullet.png new file mode 100644 index 00000000000..5c8b378e142 Binary files /dev/null and b/docs/static/img/build/dashboard/canvas/custom-viz/chart-bullet.png differ diff --git a/docs/static/img/build/dashboard/canvas/custom-viz/chart-donut.png b/docs/static/img/build/dashboard/canvas/custom-viz/chart-donut.png new file mode 100644 index 00000000000..f855a906a38 Binary files /dev/null and b/docs/static/img/build/dashboard/canvas/custom-viz/chart-donut.png differ diff --git a/docs/static/img/build/dashboard/canvas/custom-viz/chart-heatmap.png b/docs/static/img/build/dashboard/canvas/custom-viz/chart-heatmap.png new file mode 100644 index 00000000000..693c9081f18 Binary files /dev/null and b/docs/static/img/build/dashboard/canvas/custom-viz/chart-heatmap.png differ diff --git a/docs/static/img/build/dashboard/canvas/custom-viz/chart-range-area.png b/docs/static/img/build/dashboard/canvas/custom-viz/chart-range-area.png new file mode 100644 index 00000000000..6c57b39e112 Binary files /dev/null and b/docs/static/img/build/dashboard/canvas/custom-viz/chart-range-area.png differ diff --git a/docs/static/img/build/dashboard/canvas/custom-viz/chart-scatter-plot.png b/docs/static/img/build/dashboard/canvas/custom-viz/chart-scatter-plot.png new file mode 100644 index 00000000000..7559183445a Binary files /dev/null and b/docs/static/img/build/dashboard/canvas/custom-viz/chart-scatter-plot.png differ diff --git a/docs/static/img/build/dashboard/canvas/custom-viz/chart-types-gallery.png b/docs/static/img/build/dashboard/canvas/custom-viz/chart-types-gallery.png new file mode 100644 index 00000000000..0d58aab00f0 Binary files /dev/null and b/docs/static/img/build/dashboard/canvas/custom-viz/chart-types-gallery.png differ diff --git a/docs/static/img/build/dashboard/canvas/custom-viz/component-editor.png b/docs/static/img/build/dashboard/canvas/custom-viz/component-editor.png new file mode 100644 index 00000000000..41fe88d5f71 Binary files /dev/null and b/docs/static/img/build/dashboard/canvas/custom-viz/component-editor.png differ diff --git a/docs/static/img/build/dashboard/canvas/custom-viz/convert-confirm.png b/docs/static/img/build/dashboard/canvas/custom-viz/convert-confirm.png new file mode 100644 index 00000000000..7dbee5cfdad Binary files /dev/null and b/docs/static/img/build/dashboard/canvas/custom-viz/convert-confirm.png differ diff --git a/docs/static/img/build/dashboard/canvas/custom-viz/gallery-bars.png b/docs/static/img/build/dashboard/canvas/custom-viz/gallery-bars.png new file mode 100644 index 00000000000..c11763dd739 Binary files /dev/null and b/docs/static/img/build/dashboard/canvas/custom-viz/gallery-bars.png differ diff --git a/docs/static/img/build/dashboard/canvas/custom-viz/overview.png b/docs/static/img/build/dashboard/canvas/custom-viz/overview.png new file mode 100644 index 00000000000..9eb687e2eda Binary files /dev/null and b/docs/static/img/build/dashboard/canvas/custom-viz/overview.png differ diff --git a/docs/static/img/build/dashboard/canvas/custom-viz/vl-average-line.png b/docs/static/img/build/dashboard/canvas/custom-viz/vl-average-line.png new file mode 100644 index 00000000000..91b5c6410ed Binary files /dev/null and b/docs/static/img/build/dashboard/canvas/custom-viz/vl-average-line.png differ diff --git a/docs/static/img/build/dashboard/canvas/custom-viz/vl-dual-axis.png b/docs/static/img/build/dashboard/canvas/custom-viz/vl-dual-axis.png new file mode 100644 index 00000000000..ecf229d4116 Binary files /dev/null and b/docs/static/img/build/dashboard/canvas/custom-viz/vl-dual-axis.png differ diff --git a/docs/static/img/build/dashboard/canvas/custom-viz/vl-legend-selection.png b/docs/static/img/build/dashboard/canvas/custom-viz/vl-legend-selection.png new file mode 100644 index 00000000000..e7db6e80e6f Binary files /dev/null and b/docs/static/img/build/dashboard/canvas/custom-viz/vl-legend-selection.png differ diff --git a/docs/static/img/build/dashboard/canvas/custom-viz/vl-running-total.png b/docs/static/img/build/dashboard/canvas/custom-viz/vl-running-total.png new file mode 100644 index 00000000000..257fb84c4b9 Binary files /dev/null and b/docs/static/img/build/dashboard/canvas/custom-viz/vl-running-total.png differ diff --git a/docs/static/img/build/dashboard/canvas/custom-viz/vl-target.png b/docs/static/img/build/dashboard/canvas/custom-viz/vl-target.png new file mode 100644 index 00000000000..220a429937b Binary files /dev/null and b/docs/static/img/build/dashboard/canvas/custom-viz/vl-target.png differ diff --git a/runtime/ai/instructions/data/resources/component.md b/runtime/ai/instructions/data/resources/component.md index 88ff06a5145..3c5d700fc03 100644 --- a/runtime/ai/instructions/data/resources/component.md +++ b/runtime/ai/instructions/data/resources/component.md @@ -231,6 +231,8 @@ to substituting it textually as `{{ .params. }}`. so encode it as `"type": "temporal"` and let the query drive the bucket. - Include a tooltip covering the encoded fields, titled with `display_name` and formatted with `format_type`. +- Put every placeholder inside a JSON string. Values are JSON-escaped on substitution, so quotes in + display names are safe, and a string param cannot inject a JSON fragment. ```yaml custom_chart: diff --git a/runtime/parser/schema/project.schema.yaml b/runtime/parser/schema/project.schema.yaml index c05a6f1a953..da8bf5235b6 100644 --- a/runtime/parser/schema/project.schema.yaml +++ b/runtime/parser/schema/project.schema.yaml @@ -3419,6 +3419,7 @@ definitions: A custom visualization: a Metrics SQL query plus a chart spec describing how to draw it. Rill derives scales, axes, formats, sorting, stacking and layout from the data and from the metrics view's semantics, so the spec only states the chart type and which field goes on which channel. + See [Custom Viz](/developers/build/dashboards/canvas-widgets/custom-viz). properties: metrics_sql: type: string @@ -3437,7 +3438,9 @@ definitions: 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"}`, and size the chart to its container with `"width": "container"`, `"height": "container"` - and `"autosize": {"type": "fit"}`. Mutually exclusive with `spec`. + and `"autosize": {"type": "fit", "contains": "padding"}`. Mutually exclusive with `spec`. + Template values are JSON-escaped, so place placeholders inside JSON strings. + See [Vega-Lite](/developers/build/dashboards/canvas-widgets/custom-viz/vega-lite). additionalProperties: false # Flint Chart Spec @@ -3446,11 +3449,11 @@ definitions: properties: chartType: type: string - description: The chart type, e.g. `Line Chart`, `Bar Chart`, `Heatmap`, `Waterfall Chart`. + description: The chart type, e.g. `Line Chart`, `Bar Chart`, `Heatmap`, `Waterfall Chart`. See [Chart Types](/developers/build/dashboards/canvas-widgets/custom-viz/chart-types) for the full list. encodings: type: object description: | - Maps a visual channel (`x`, `y`, `color`, `size`, `column`, `row`, `group`, `angle`, `goal`, ...) + Maps a visual channel (`x`, `y`, `x2`, `y2`, `color`, `size`, `column`, `row`, `group`, `goal`, ...) to a field. Each value is either a field name or an encoding object. additionalProperties: oneOf: