diff --git a/src/documentation/visualizations/about_rendering.malloynb b/src/documentation/visualizations/about_rendering.malloynb index 5e6d8efb..3763859e 100644 --- a/src/documentation/visualizations/about_rendering.malloynb +++ b/src/documentation/visualizations/about_rendering.malloynb @@ -1,25 +1,24 @@ >>>markdown -# Rendering Documentation -The latest and most up to date documentation for the Malloy renderer are now found in GitHub: +# Rendering Results -- [Renderer Tag Documentation](https://github.com/malloydata/malloy/blob/main/packages/malloy-render/docs/renderer_tags_overview.md) -- [Renderer Tag Cheatsheet](https://github.com/malloydata/malloy/blob/main/packages/malloy-render/docs/renderer_tag_cheatsheet.md) +Malloy's rendering library interprets tags to control how query results are displayed. -For developers wishing to implement custom renderers, a plugin system is available: -- [Plugin System Overview](https://github.com/malloydata/malloy/blob/main/packages/malloy-render/docs/plugin-system.md) -- [Plugin Quick Start](https://github.com/malloydata/malloy/blob/main/packages/malloy-render/docs/plugin-quick-start.md) -- [Plugin API Reference](https://github.com/malloydata/malloy/blob/main/packages/malloy-render/docs/plugin-api-reference.md) ->>>markdown - # Using Render Tags +## Documentation + +- [Renderer Tags](renderer_tags.malloynb) - Complete guide to all visualization and formatting tags +- [Tag Cheatsheet](renderer_cheatsheet.malloynb) - Quick reference for all tags + +## Plugin System -When Malloy runs a query, it returns two things. The *results* of the query and *metadata* about the results. The metadata are the schema for the results, including type information. Malloy also provides a mechanism to tag things in the source code and return tags with this meta data. +- [Custom Plugins](plugins.malloynb) - Build your own renderers for specialized visualizations -In Malloy, anything that can be named can be tagged. A tag starts with a `#`. Tags that start on a new line attach the tag the thing on the following line. For more details about how tagging works, see the [Tags](../language/tags.malloynb) section. +--- -Malloy's rendering library interprets these tags to change how results are rendered. +## How Tags Work -## Tagging individual elements -In the query below, the measure `percent_of_total` is tagged as a percentage. Any time `percent_of_total` is used in a query, Malloy's rendering library will be displayed as a percentage. +When Malloy runs a query, it returns results and metadata including schema and tags. Tags start with `#` and attach to the following item. + +For details on tag syntax, see [Tags](../language/tags.malloynb). >>>malloy source: flights is duckdb.table('../data/flights.parquet') extend { measure: @@ -31,23 +30,18 @@ source: flights is duckdb.table('../data/flights.parquet') extend { #(docs) size=small limit=5000 run: flights -> { group_by: carrier - aggregate: - flight_count + aggregate: + flight_count percent_of_flights } ->>>malloy -#(docs) size=small limit=5000 -run: duckdb.table('../data/flights.parquet') -> { - group_by: carrier - aggregate: flight_count is count() -} >>>markdown -Simply adding `# bar_chart` before the query tags it and tells the rendering library to show the result as a bar chart. See the docs on the [Bar Chart tag](./bar_charts.malloynb) for more information. +## Quick Examples >>>malloy #(docs) size=large limit=5000 # bar_chart -run: duckdb.table('../data/flights.parquet') -> { +run: duckdb.table('../data/flights.parquet') -> { group_by: carrier aggregate: flight_count is count() -} \ No newline at end of file + limit: 10 +} diff --git a/src/documentation/visualizations/plugins.malloynb b/src/documentation/visualizations/plugins.malloynb new file mode 100644 index 00000000..992ada27 --- /dev/null +++ b/src/documentation/visualizations/plugins.malloynb @@ -0,0 +1,46 @@ +>>>markdown +# Custom Renderer Plugins + +The Malloy Render plugin system lets you create custom visualizations for specific field types or data patterns. + +## Use Cases + +- Custom chart types not included in the standard renderer +- Domain-specific visualizations (maps, diagrams, etc.) +- Branded or styled components +- Interactive visualizations with custom behavior + +## Getting Started + +Plugins can be built using SolidJS (recommended) or direct DOM manipulation. They match fields based on tags and field types. + +```typescript +const MyPluginFactory: RenderPluginFactory = { + name: 'my_plugin', + + matches: (field, fieldTag, fieldType) => fieldTag.has('my_plugin'), + + create: (field) => ({ + name: 'my_plugin', + field, + renderMode: 'solidjs', + sizingStrategy: 'fixed', + renderComponent: (props) =>
{props.dataColumn.value}
, + getMetadata: () => ({ type: 'my_plugin' }) + }) +}; +``` + +Then tag fields in Malloy to use your plugin: + +```malloy +source: data extend { + dimension: status # my_plugin +} +``` + +## Documentation + +- [Plugin System Overview](https://github.com/malloydata/malloy/blob/main/packages/malloy-render/docs/plugin-system.md) - Architecture and concepts +- [Plugin Quick Start](https://github.com/malloydata/malloy/blob/main/packages/malloy-render/docs/plugin-quick-start.md) - Minimal examples +- [Plugin API Reference](https://github.com/malloydata/malloy/blob/main/packages/malloy-render/docs/plugin-api-reference.md) - Complete type definitions diff --git a/src/documentation/visualizations/renderer_cheatsheet.malloynb b/src/documentation/visualizations/renderer_cheatsheet.malloynb new file mode 100644 index 00000000..e0629748 --- /dev/null +++ b/src/documentation/visualizations/renderer_cheatsheet.malloynb @@ -0,0 +1,72 @@ +>>>markdown +# Renderer Tag Cheatsheet + +| Tag | Description | Details | Example | +| :-------------------------------- | :----------------------------------------- | :------------------------------------------------------------------------------------------------ | :---------------------------------------------------------------------------------------------------------------------------- | +| **Charts & Visualization** | | | | +| `# bar_chart` | Render data as a bar chart. | Base tag for bar chart configuration. Axes/series often inferred. | `view: my_view is { # bar_chart ... }` | +| `# bar_chart.stack` | Stack bars in a series. | Boolean property. Use when a `series` field is defined. | `view: my_view is { # bar_chart.stack ... }` | +| `# bar_chart.size` | Set preset chart size. | Values: `spark`, `xs`, `sm`, `md`, `lg`, `xl`, `2xl`. | `view: my_view is { # bar_chart { size=lg } ... }` | +| `# bar_chart.size.width` | Set specific chart width. | Value is in pixels. Overrides preset width. | `view: my_view is { # bar_chart { size.width=450 } ... }` | +| `# bar_chart.size.height` | Set specific chart height. | Value is in pixels. Overrides preset height. | `view: my_view is { # bar_chart { size.height=300 } ... }` | +| `# bar_chart.x` | Define x-axis field. | Assigns a field to the category axis. | `view: my_view is { # bar_chart { x=category } ... }` | +| `# bar_chart.x.limit` | Limit categories on x-axis. | Numeric value. Limits bars shown. Auto-calculated if not set. | `view: my_view is { # bar_chart { x.limit=10 } ... }` | +| `# bar_chart.x.independent` | Control x-axis scale sharing. | Boolean (`true`/`false`). Overrides auto-sharing based on cardinality (default shares if <=20). | `view: my_nest is { # bar_chart { x.independent } ...}` | +| `# bar_chart.y` | Define y-axis field(s). | Assigns field(s) to the value axis. Use array `['m1', 'm2']` for measure series. | `view: my_view is { # bar_chart { y=total_sales } ... }`
`view: my_view is { # bar_chart { y=['Sales', 'Cost'] } ... }` | +| `# bar_chart.y.independent` | Control y-axis scale sharing. | Boolean (`true`/`false`). Default is shared. Use `true` for independent scales in nested charts. | `view: my_nest is { # bar_chart { y.independent } ... }` | +| `# bar_chart.series` | Define series field. | Assigns a field for grouping/coloring bars. | `view: my_view is { # bar_chart { series=department } ... }` | +| `# bar_chart.series.limit` | Limit number of series shown. | Numeric value or `auto`. Default `auto` uses 20. Series ranked by sum of Y-values. | `view: my_view is { # bar_chart { series.limit=5 } ... }`
`view: my_view is { # bar_chart { series.limit=auto } ... }` | +| `# bar_chart.series.independent` | Control series legend/color sharing. | Boolean (`true`/`false`). Overrides auto-sharing based on cardinality (default shares if <=20). | `view: my_nest is { # bar_chart { series.independent } ... }` | +| `# bar_chart.title` | Set chart title. | String value. | `view: my_view is { # bar_chart { title='Sales Distribution' } ... }` | +| `# bar_chart.subtitle` | Set chart subtitle. | String value. | `view: my_view is { # bar_chart { subtitle='Q1 Data' } ... }` | +| `# line_chart` | Render data as a line chart. | Base tag for line chart configuration. Axes/series often inferred. | `view: my_view is { # line_chart ... }` | +| `# line_chart.zero_baseline` | Control if y-axis includes zero. | Boolean (`true`/`false`). Default `false` (unlike bar charts). `true` forces axis to start at zero. | `view: my_view is { # line_chart { zero_baseline=true } ... }` | +| `# line_chart.size` | Set preset chart size. | Values: `spark`, `xs`...`2xl`. | `view: my_view is { # line_chart { size=spark } ... }` | +| `# line_chart.interpolate` | Set line interpolation mode. | E.g., `step`. | `view: my_view is { # line_chart { interpolate=step } ... }` | +| `# line_chart.x` | Define x-axis field. | Assigns a field (often time-based) to the x-axis. | `view: my_view is { # line_chart { x=sale_date } ... }` | +| `# line_chart.x.independent` | Control x-axis scale sharing. | Boolean (`true`/`false`). Overrides auto-sharing (default shares if <=20). | `view: my_nest is { # line_chart { x.independent } ... }` | +| `# line_chart.y` | Define y-axis field(s). | Assigns field(s) to the value axis. Use array `['m1', 'm2']` for multiple lines from measures. | `view: my_view is { # line_chart { y=value } ... }`
`view: my_view is { # line_chart { y=['MetricA', 'MetricB'] } ... }` | +| `# line_chart.y.independent` | Control y-axis scale sharing. | Boolean (`true`/`false`). Default is shared, unless `series.limit` is used. | `view: my_nest is { # line_chart { y.independent } ... }` | +| `# line_chart.series` | Define series field. | Assigns a field for multiple lines. | `view: my_view is { # line_chart { series=category } ... }` | +| `# line_chart.series.limit` | Limit number of series lines. | Numeric value or `auto`. Default `auto` uses 12. Series ranked by sum of Y-values. Implies `y.independent=true`. | `view: my_view is { # line_chart { series.limit=5 } ... }`
`view: my_view is { # line_chart { series.limit=auto } ... }` | +| `# line_chart.series.independent` | Control series legend/color sharing. | Boolean (`true`/`false`). Overrides auto-sharing (default shares if <=20). | `view: my_nest is { # line_chart { series.independent } ... }` | +| `# line_chart.title` | Set chart title. | String value. | `view: my_view is { # line_chart { title='Trend Over Time' } ... }` | +| `# line_chart.subtitle` | Set chart subtitle. | String value. | `view: my_view is { # line_chart { subtitle='Daily Values' } ... }` | +| `# scatter_chart` | Render as scatter plot. | Legacy renderer. Uses field order (x, y, color, size, shape). | `view: my_view is { # scatter_chart ... }` | +| **Layout & Structure** | | | | +| `# dashboard` | Render nested views as a dashboard. | Base tag for dashboard layout. | `view: my_dashboard is { # dashboard nest: ... }` | +| `# dashboard.table.max_height` | Set max height for tables in dashboard. | Numeric pixel value or `'none'`. | `view: my_dashboard is { # dashboard { table.max_height=400 } ... }` | +| `# break` | Insert layout break in dashboard. | Applied to a `nest:` definition within a dashboard. | `view: my_dashboard is { nest: chart1 is {...} # break nest: chart2 is {...} }` | +| `# table` | Render data as a table. | Often the default for nested data. | `view: my_table is { # table ... }` | +| `# table.pivot` | Pivot table dimensions into columns. | Use `# pivot` for automatic, or `# pivot { dimensions=["d1"] }` for specific dimensions. | `nest: my_pivot is { # pivot ... }`
`nest: my_pivot is { # pivot { dimensions=["country"] } ... }` | +| `# table.flatten` | Flatten nested record into columns. | Applied to a `nest:` definition. Nested query must not have `group_by`. | `nest: metrics is { # flatten ... }` | +| `# table.size=fill` | Make table fill container width. | Boolean property applied via value. | `view: full_width_table is { # table.size=fill ... }` | +| `# table.column.width` | Set specific column width. | Apply to a field. Value: `xs`, `sm`, `md`, `lg`, `xl`, `2xl` or pixels. | `dimension: my_col is ... # column { width=lg }`
`dimension: my_col is ... # column { width=150 }` | +| `# table.column.height` | Set specific row height for column cells. | Apply to a field. Value in pixels. | `dimension: my_col is ... # column { height=50 }` | +| `# table.column.word_break` | Control word breaking in cells. | Value: `break_all`. Apply to a field. | `dimension: long_text is ... # column { word_break=break_all }` | +| `# list` | Render first field as list. | Comma-separated values from the first non-hidden field of a nest. | `nest: top_items is { # list ... }` | +| `# list_detail` | Render first two fields as list. | Comma-separated `value (detail)` from first two non-hidden fields. | `nest: item_details is { # list_detail ... }` | +| `# transpose` | Transpose table rows/columns. | Applied at the view level. | `view: metrics_row is { # transpose ... }` | +| **Field Formatting/Rendering** | | | | +| `# currency` | Format number as currency. | Default is USD ($). Specify units like `euro` or `pound`. | `measure: revenue is ... # currency`
`measure: revenue_eur is ... # currency=euro` | +| `# percent` | Format number as percentage. | Multiplies by 100, adds '%'. | `measure: margin is ... # percent` | +| `# number` | Format number with `ssf` string. | Provide format string as value. | `measure: my_num is ... # number="#,##0.0"` | +| `# duration` | Format number as duration. | Default input unit: `seconds`. Specify units (`nanoseconds`..`days`). | `measure: avg_time is ... # duration`
`measure: compute is ... # duration=milliseconds` | +| `# duration.terse` | Use abbreviated duration units. | E.g., `ns`, `s`, `m`, `h`, `d`. | `measure: short_time is ... # duration.terse` | +| `# duration.number` | Format numeric parts of duration. | Uses `ssf` format string. | `measure: precise_duration is ... # duration { number="0.00" }` | +| `# image` | Render string as image URL. | Applied to a string field containing a URL. | `dimension: logo_url is ... # image` | +| `# image.height` | Set image height. | CSS value (e.g., `40px`). | `dimension: logo_url is ... # image { height=40px }` | +| `# image.width` | Set image width. | CSS value (e.g., `100px`). | `dimension: logo_url is ... # image { width=100px }` | +| `# image.alt` | Set image alt text. | Literal string value. | `dimension: logo_url is ... # image { alt='Company Logo' }` | +| `# image.alt.field` | Use another field for alt text. | Value is relative path to field (e.g., `field_name`, `'../parent_field'`). | `dimension: logo_url is ... # image { alt.field=product_name }` | +| `# link` | Render field as hyperlink. | Applied to a field whose value is the link text. | `dimension: url is ... # link` | +| `# link.url_template` | Template for link href. | String where `$$` is replaced by value (from this field or `.field`). Appends if `$$` is missing. | `dimension: name is ... # link { url_template="https://example.com/$$" }` | +| `# link.field` | Use another field's value for href. | Value is relative path to the field containing the href data. | `dimension: link_text is 'Search' # link { field=query_term url_template="https://google.com/search?q=$$" }` | +| **Utilities & Configuration** | | | | +| `# hidden` | Hide field from rendering. | Field remains in data, just not displayed in tables/dashboards. | `dimension: internal_id is ... # hidden` | +| `# label` | Override display name/title. | String value. Applied to fields or dashboard items. | `measure: total_revenue is ... # label="Total Sales ($)"` | +| `# tooltip` | Include nested view in tooltip. | Applied to a `nest:`. Can contain render hints for the tooltip itself. | `nest: details is { # tooltip ... }`
`nest: chart_tip is { # tooltip bar_chart.size=xs ... }` | +| `# size` | Set preset size (legacy). | Prefer `.size` on specific renderer tags. Values: `spark`, `xs`...`2xl`. Applied to view/nest. | `view: my_view is { # size=lg ... }` | +| `# theme` | Apply theme style overrides (View level). | Contains CSS-like properties (e.g., `tableBodyColor`, `tableRowHeight`). | `view: my_view is { # theme { tableBodyColor=red } ... }` | +| `## theme` | Apply theme style overrides (Model level). | Contains CSS-like properties. Sets defaults for the model. | `## theme { tableBodyColor=blue }` | +| `## renderer_legacy` | Use legacy HTML renderer for model. | Model-level tag. No properties. | `## renderer_legacy` | diff --git a/src/documentation/visualizations/renderer_tags.malloynb b/src/documentation/visualizations/renderer_tags.malloynb new file mode 100644 index 00000000..e886764c --- /dev/null +++ b/src/documentation/visualizations/renderer_tags.malloynb @@ -0,0 +1,547 @@ +>>>markdown +# Renderer Tags + +Control how Malloy renders query results using tags. Tags are annotations that tell the renderer how to display data. + +## Basic Syntax + +- Tags are prefixed with `#`: `# tag_name` +- Tags with properties: `# tag_name { property=value }` +- Boolean properties use dot notation: `# tag_name.property` +- Nested properties: `# bar_chart { y.independent }` + +--- + +## Chart Tags + +### Bar Chart + +Renders data as a bar chart. The renderer infers x (first dimension), y (first measure), and series (second dimension) from query structure. +>>>malloy +source: flights is duckdb.table('../data/flights.parquet') extend { + measure: flight_count is count() +} +>>>malloy +#(docs) size=large +# bar_chart +run: flights -> { + group_by: carrier + aggregate: flight_count + limit: 10 +} +>>>markdown + +**Properties:** + +| Property | Description | Example | +|----------|-------------|---------| +| `.stack` | Stack bars when series present | `# bar_chart.stack` | +| `.size` | Size preset: `spark`, `xs`, `sm`, `md`, `lg`, `xl`, `2xl` | `# bar_chart { size=lg }` | +| `.size.width` | Pixel width | `# bar_chart { size.width=300 }` | +| `.size.height` | Pixel height | `# bar_chart { size.height=220 }` | +| `.x` | Field for x-axis | `# bar_chart { x=category }` | +| `.x.limit` | Limit number of bars | `# bar_chart { x.limit=10 }` | +| `.y` | Field(s) for y-axis | `# bar_chart { y=total }` or `{ y=['sales', 'cost'] }` | +| `.y.independent` | Independent y-axis per nested chart | `# bar_chart { y.independent }` | +| `.series` | Field for grouping/coloring | `# bar_chart { series=category }` | +| `.series.limit` | Limit series shown (default 20) | `# bar_chart { series.limit=5 }` | +| `.title` | Chart title | `# bar_chart { title='Sales' }` | + +#### Stacked Bar Chart + +Add a second dimension to create stacked bars: +>>>malloy +#(docs) size=large +# bar_chart.stack +run: flights -> { + group_by: origin, carrier + aggregate: flight_count + limit: 25 +} +>>>markdown + +### Line Chart + +Renders data as a line chart. Similar inference to bar charts. +>>>malloy +#(docs) size=large +# line_chart +run: flights -> { + group_by: dep_time.month + aggregate: flight_count + order_by: 1 +} +>>>markdown + +**Properties:** + +| Property | Description | Example | +|----------|-------------|---------| +| `.zero_baseline` | Force y-axis to start at zero | `# line_chart { zero_baseline=true }` | +| `.interpolate` | Line interpolation mode | `# line_chart { interpolate=step }` | +| `.series.limit` | Limit lines shown (default 12) | `# line_chart { series.limit=5 }` | + +#### Multi-Series Line Chart + +Use explicit axis tags to control which fields map to x, y, and series: + +```malloy +# line_chart { series.limit=5 } +run: flights -> { + group_by: + # x + departure_month is dep_time.month + aggregate: + # y + flight_count + group_by: + # series + carrier + order_by: departure_month +} +``` + +--- + +## Embedded Field Tags + +Tag fields directly to control chart axis mapping, overriding automatic inference. + +### `# x` - X-Axis Field + +Mark a dimension for the X-axis: +>>>malloy +#(docs) size=large +# bar_chart +run: flights -> { + # x + group_by: carrier + aggregate: flight_count + limit: 10 +} +>>>markdown + +### `# y` - Y-Axis Field(s) + +Mark aggregates for the Y-axis. Multiple `# y` fields create a measure series: +>>>malloy +#(docs) size=large +# bar_chart +run: flights -> { + group_by: carrier + # y + aggregate: flight_count + # y + aggregate: total_distance is distance.sum() / 1000000 + limit: 10 +} +>>>markdown + +### `# series` - Series Grouping + +Mark a dimension for series grouping (different colors): +>>>malloy +#(docs) size=large +# line_chart { series.limit=5 } +run: flights -> { + # x + group_by: dep_time.month + # series + group_by: carrier + aggregate: flight_count + order_by: 1 +} +>>>markdown + +--- + +## Advanced Chart Features + +### Understanding Axis Independence + +By default, nested charts **share axes** when there are ≤20 unique values. This helps compare across charts. + +**Shared axes (default):** All nested charts use the same scale: +>>>malloy +#(docs) size=large +run: flights -> { + group_by: origin + aggregate: flight_count + # bar_chart + nest: by_carrier is { + group_by: carrier + aggregate: flight_count + limit: 5 + } + limit: 3 +} +>>>markdown + +**Independent axes:** Each nested chart scales to its own data: +>>>malloy +#(docs) size=large +run: flights -> { + group_by: origin + aggregate: flight_count + # bar_chart { y.independent } + nest: by_carrier is { + group_by: carrier + aggregate: flight_count + limit: 5 + } + limit: 3 +} +>>>markdown + +### Dimension Series vs Measure Series + +**Dimension Series:** A second dimension creates grouped or stacked bars: +>>>malloy +#(docs) size=large +# bar_chart +run: flights -> { + where: origin ? 'SFO' | 'LAX' | 'JFK' + group_by: carrier, origin + aggregate: flight_count + limit: 30 +} +>>>markdown + +**Measure Series:** Multiple `# y` aggregates create series from different measures: +>>>malloy +#(docs) size=large +# bar_chart +run: flights -> { + group_by: carrier + # y + aggregate: flight_count + # y + aggregate: total_distance is distance.sum() / 1000000 + limit: 10 +} +>>>markdown + +### Sparklines + +Use `size="spark"` for compact inline charts: +>>>malloy +#(docs) size=large +run: flights -> { + group_by: carrier + aggregate: flight_count + # bar_chart size="spark" + nest: trend is { + group_by: dep_time.month + aggregate: flight_count + order_by: 1 + limit: 12 + } + limit: 5 +} +>>>markdown + +### Handling Many Lines + +Use `series.limit` to show only the top N series by total Y-value: +>>>malloy +#(docs) size=large +# line_chart { series.limit=5 } +run: flights -> { + # x + group_by: dep_time.month + # series + group_by: carrier + aggregate: flight_count + order_by: 1 +} +>>>markdown + +--- + +## Tooltip Interactions + +Tooltips can contain simple values, nested tables, or nested charts. + +### Simple Value Tooltips +>>>malloy +#(docs) size=large +# bar_chart +run: flights -> { + group_by: carrier + aggregate: flight_count + # tooltip + aggregate: total_distance is distance.sum() + limit: 10 +} +>>>markdown + +### Chart Tooltips + +Add `# tooltip` with a chart tag to show mini-charts on hover: +>>>malloy +#(docs) size=large +# bar_chart +run: flights -> { + group_by: carrier + aggregate: flight_count + # tooltip bar_chart.size=xs + nest: by_month is { + group_by: dep_time.month + aggregate: flight_count + order_by: 1 + limit: 6 + } + limit: 10 +} +>>>markdown + +--- + +## Layout Tags + +### Dashboard + +Arranges nested views into a dashboard layout. Put chart tags ABOVE the `nest:` keyword: +>>>malloy +#(docs) size=large +# dashboard +run: flights -> { + nest: total is { + aggregate: flight_count + } + # bar_chart + nest: by_carrier is { + group_by: carrier + aggregate: flight_count + limit: 5 + } + # line_chart + nest: by_month is { + group_by: dep_time.month + aggregate: flight_count + order_by: 1 + } +} +>>>markdown + +**Properties:** + +| Property | Description | Example | +|----------|-------------|---------| +| `.table.max_height` | Max height for tables in tiles | `# dashboard { table.max_height=400 }` | +| `# break` | Force new row (on nested view) | See example below | + +### Table + +Explicitly render as a table (often the default). + +**Properties:** + +| Property | Description | Example | +|----------|-------------|---------| +| `# pivot` | Pivot dimensions to columns | `# pivot` | +| `# flatten` | Flatten single-row nested record | `# flatten` | +| `.size=fill` | Fill container width | `# table.size=fill` | +| `# column { width }` | Column width | `# column { width=lg }` | + +#### Pivot Syntax + +```malloy +run: flights -> { + group_by: carrier + # pivot + nest: by_year is { + group_by: dep_time.year + aggregate: flight_count + } +} +``` + +### List and List Detail + +Render nested results as comma-separated lists. +>>>malloy +#(docs) size=small +run: flights -> { + group_by: origin + # list + nest: carriers is { + group_by: carrier + limit: 3 + } + limit: 5 +} +>>>markdown + +`# list_detail` shows value with detail in parentheses: +>>>malloy +#(docs) size=small +run: flights -> { + group_by: origin + # list_detail + nest: carriers is { + group_by: carrier + aggregate: flight_count + limit: 3 + } + limit: 5 +} +>>>markdown + +### Transpose + +Swap rows and columns. Useful for single-row results with many measures. + +```malloy +# transpose +run: flights -> { + aggregate: + flight_count + total_distance is distance.sum() + avg_distance is distance.avg() +} +``` + +--- + +## Field Formatting Tags + +### Currency +>>>malloy +#(docs) size=small +run: duckdb.table('../data/order_items.parquet') -> { + aggregate: + # currency + total_sales is sale_price.sum() +} +>>>markdown + +#### Currency Scaling + +Scale large currency values for readability: +>>>malloy +#(docs) size=small +run: duckdb.table('../data/order_items.parquet') -> { + aggregate: + # currency { scale=thousands } + sales_k is sale_price.sum() + # currency { scale=millions decimals=2 } + sales_m is sale_price.sum() + # currency { scale=thousands no_suffix } + sales_raw is sale_price.sum() +} +>>>markdown + +| Property | Description | +|----------|-------------| +| `scale=thousands` | Divide by 1000, add "K" suffix | +| `scale=millions` | Divide by 1M, add "M" suffix | +| `decimals=2` | Control decimal places | +| `no_suffix` | Show scaled number without suffix | + +### Percent +>>>malloy +#(docs) size=small +run: flights -> { + group_by: carrier + aggregate: + flight_count + # percent + percent_of_total is flight_count / all(flight_count) + limit: 5 +} +>>>markdown + +### Number Format + +Use `ssf` format strings (Excel/Sheets style): +>>>malloy +#(docs) size=small +run: flights -> { + aggregate: + # number="#,##0" + total_flights is count() +} +>>>markdown + +### Duration + +Format numbers as human-readable durations: + +| Property | Description | +|----------|-------------| +| `# duration` | Default (seconds) | +| `# duration=milliseconds` | Input is milliseconds | +| `# duration.terse` | Abbreviated (s, m, h, d) | + +### Image + +Render string as image: + +```malloy +dimension: product_image is image_url # image { height=50px } +``` + +### Link + +Render as hyperlink: + +```malloy +dimension: product_page is name # link { url_template="https://example.com/$$" } +``` + +--- + +## Utility Tags + +| Tag | Description | Example | +|-----|-------------|---------| +| `# hidden` | Hide field from display | `dimension: id # hidden` | +| `# label` | Override display name | `measure: total # label="Total Sales"` | +| `# tooltip` | Include in chart tooltip | `nest: details is { # tooltip ... }` | + +### Label Limitations + +`# label` overrides display names for: +- Table column headers ✓ +- Dashboard card titles ✓ + +**Note:** `# label` does **not** work for chart axis labels. For chart titles, use the `.title` and `.subtitle` properties: +>>>malloy +#(docs) size=large +# bar_chart { title='Custom Title' subtitle='With subtitle' } +run: flights -> { + group_by: carrier + aggregate: flight_count + limit: 5 +} +>>>markdown + +--- + +## Theme Configuration + +Apply theme overrides at model level (`##`) or view level (`#`): + +```malloy +## theme { tableBodyColor=blue } + +source: my_source is ... extend { + view: styled is { + # theme { tableRowHeight=40 } + select: * + } +} +``` + +--- + +## Legacy Renderer + +Use the older HTML renderer for an entire model: + +```malloy +## renderer_legacy + +source: my_source is ... +``` diff --git a/src/table_of_contents.json b/src/table_of_contents.json index 943bdb04..771a130f 100644 --- a/src/table_of_contents.json +++ b/src/table_of_contents.json @@ -291,6 +291,18 @@ { "title": "Overview", "link": "/visualizations/about_rendering.malloynb" + }, + { + "title": "Renderer Tags", + "link": "/visualizations/renderer_tags.malloynb" + }, + { + "title": "Tag Cheatsheet", + "link": "/visualizations/renderer_cheatsheet.malloynb" + }, + { + "title": "Custom Plugins", + "link": "/visualizations/plugins.malloynb" } ] },