From 7571ed148fd72e5d9837522e9fad10f6cecdc41a Mon Sep 17 00:00:00 2001 From: James Swirhun Date: Wed, 1 Apr 2026 10:26:06 -0600 Subject: [PATCH] Document visualization updates: dashboards, horizontal bars, hidden dimensions - Expand dashboard docs with KPI cards, grid spans, row breaks, labels, subtitles, borderless mode, equal columns, custom gap, responsive behavior, and default sizing - Add horizontal bar chart section with basic, grouped, and stacked examples - Add hidden dimension docs for bar and line charts (order_by without rendering) - Update overview quick reference with new properties Signed-off-by: James Swirhun --- .../visualizations/bar_charts.malloynb | 50 ++++ .../visualizations/charts_line_chart.malloynb | 17 ++ .../visualizations/dashboards.malloynb | 254 +++++++++++++++--- .../visualizations/overview.malloynb | 4 +- 4 files changed, 292 insertions(+), 33 deletions(-) diff --git a/src/documentation/visualizations/bar_charts.malloynb b/src/documentation/visualizations/bar_charts.malloynb index e691fb7a..8098f407 100644 --- a/src/documentation/visualizations/bar_charts.malloynb +++ b/src/documentation/visualizations/bar_charts.malloynb @@ -19,6 +19,7 @@ Malloy can create simple bar charts and bar charts with series breakdowns, eithe | `.series` | Field for grouping/coloring | `# bar_chart { series=category }` | | `.series.limit` | Max series shown (default: 20) | `# bar_chart { series.limit=5 }` | | `.series.independent` | Independent series colors in nested charts | `# bar_chart { series.independent }` | +| `.horizontal` | Render bars horizontally (categories on y-axis) | `# bar_chart { horizontal }` | | `.title` | Chart title | `# bar_chart { title='My Title' }` | | `.subtitle` | Chart subtitle | `# bar_chart { subtitle='Details' }` | @@ -137,6 +138,55 @@ run: flights -> { } >>>markdown +## Horizontal Bar Charts + +Use the `horizontal` property to render bars left-to-right instead of bottom-to-top. Categories appear on the y-axis and values on the x-axis: +>>>malloy +#(docs) size=large limit=5000 +# bar_chart { horizontal } +run: flights -> { + group_by: carriers.nickname + aggregate: flight_count +} +>>>markdown +Horizontal mode works with series breakdowns: +>>>malloy +#(docs) size=large limit=5000 +# bar_chart { horizontal } +run: flights -> { + where: destination ? 'SFO' | 'OAK' | 'SJC' + group_by: carriers.nickname, destination + aggregate: flight_count +} +>>>markdown +And with stacked bars: +>>>malloy +#(docs) size=large limit=5000 +# bar_chart.stack { horizontal } +run: flights -> { + where: destination ? 'SFO' | 'OAK' | 'SJC' + group_by: carriers.nickname, destination + aggregate: flight_count +} +>>>markdown + +## Hidden Dimensions + +Use `# hidden` on a dimension to exclude it from the chart's automatic channel assignment (x-axis, series) while keeping it in the query data. This is useful when you want to include a dimension for `order_by` without rendering it on the chart: +>>>malloy +#(docs) size=large limit=5000 +# bar_chart +run: flights -> { + group_by: + # hidden + carrier + carriers.nickname + aggregate: flight_count + order_by: carrier +} +>>>markdown +The `carrier` code is used to control sort order, but only `nickname` appears on the chart. A hidden field with an explicit channel tag (e.g., `# hidden # x`) will still be assigned to that channel. + ## Nested Bar Charts Bar charts can be used inside of nested queries: >>>malloy diff --git a/src/documentation/visualizations/charts_line_chart.malloynb b/src/documentation/visualizations/charts_line_chart.malloynb index 34498fd6..564b3135 100644 --- a/src/documentation/visualizations/charts_line_chart.malloynb +++ b/src/documentation/visualizations/charts_line_chart.malloynb @@ -79,6 +79,23 @@ run: flights -> { } >>>markdown +## Hidden Dimensions + +Use `# hidden` on a dimension to exclude it from automatic channel assignment while keeping it in the query data. This lets you include a dimension for `order_by` or other purposes without it appearing as an axis or series on the chart: +>>>malloy +#(docs) size=large limit=5000 +# line_chart +run: flights -> { + group_by: + # hidden + dep_month is dep_time.month + departure_month is dep_time.month::string + aggregate: flight_count + order_by: dep_month +} +>>>markdown +A hidden field with an explicit channel tag (e.g., `# hidden # x`) will still be assigned to that channel. + ## Line Charts nested in tables When line charts are nested in tables the size is reduced. Nested line charts increase the density of information provided by the result. diff --git a/src/documentation/visualizations/dashboards.malloynb b/src/documentation/visualizations/dashboards.malloynb index 28538ad0..582e2c56 100644 --- a/src/documentation/visualizations/dashboards.malloynb +++ b/src/documentation/visualizations/dashboards.malloynb @@ -1,14 +1,27 @@ >>>markdown # Dashboards -The `# dashboard` tag renders a query result as a dashboard layout. Dimensions appear at the top, while aggregates and nested views float within the dashboard. +The `# dashboard` tag renders a query result as a dashboard layout. Dimensions appear at the top as section headers, while aggregates render as KPI cards and nested views render as charts or tables within a responsive 12-column grid. ## Properties +### View-Level Properties + +| Property | Description | Example | +|----------|-------------|---------| +| `columns` | Force N equal columns instead of 12-col grid | `# dashboard { columns = 3 }` | +| `gap` | Gap spacing in pixels between items | `# dashboard { gap = 24 }` | +| `table.max_height` | Max pixel height for tables in tiles | `# dashboard { table.max_height = 400 }` | + +### Item-Level Properties + | Property | Description | Example | |----------|-------------|---------| -| `.table.max_height` | Max pixel height for tables in tiles | `# dashboard { table.max_height=400 }` | -| `# break` | Layout break (on a nested view) | Forces next item to a new row | +| `# span` | Grid column span (1–12) | `# span = 6` | +| `# break` | Force item to a new row | `# break` | +| `# label` | Custom display label | `# label = 'Total Revenue'` | +| `# subtitle` | Subtitle text below item title | `# subtitle = "Top 10 brands"` | +| `# borderless` | Remove card styling (shadow/border) | `# borderless` | >>>malloy source: airports is duckdb.table('../data/airports.parquet') extend { @@ -41,57 +54,178 @@ In such cases, the `# dashboard` renderer is useful for making the results easie run: airports -> by_state_and_county >>>markdown -## Custom Labels +## KPI Cards + +Aggregate measures in a dashboard automatically render as styled KPI cards with centered labels and values. Each measure gets a default width of 3 columns (fitting 4 per row): -Use `# label` on aggregates or nested views to customize how they appear in the dashboard: +>>>malloy +#(docs) size=large limit=5000 +source: flights is duckdb.table('../data/flights.parquet') extend { + join_one: carriers is duckdb.table('../data/carriers.parquet') + on carrier = carriers.code + measure: flight_count is count() + measure: total_distance is distance.sum() +} >>>malloy #(docs) size=large limit=5000 # dashboard -run: airports -> { - group_by: state - # currency +run: flights -> { + group_by: carriers.nickname aggregate: - # label='Total Airports' - airport_count + flight_count + total_distance + limit: 3 +} +>>>markdown + +### Borderless KPIs + +Use `# borderless` to remove card styling from individual items. This works on any item type — measures, tables, or charts: + +>>>malloy +#(docs) size=large limit=5000 +# dashboard +run: flights -> { + group_by: carriers.nickname + aggregate: + # borderless + flight_count + # borderless + total_distance + limit: 3 +} +>>>markdown + +## Custom Labels and Subtitles + +Use `# label` on aggregates or nested views to customize how they appear in the dashboard. Labels default to converting `snake_case` field names to Title Case. Use `# subtitle` to add descriptive text below a title: +>>>malloy +#(docs) size=large limit=5000 +# dashboard +run: flights -> { + group_by: carriers.nickname + aggregate: + # label='Total Flights' + flight_count nest: - # label='Facility Breakdown' - by_fac_type is { - group_by: fac_type - aggregate: airport_count + # subtitle="Top 10 destinations by flight count" + by_destination is { + group_by: destination + aggregate: flight_count + limit: 10 } - limit: 5 + limit: 3 } >>>markdown -## Layout Breaks +## Layout Control + +### Grid Spans + +Dashboards use a 12-column grid. Use `# span = N` to control how many columns an item occupies. Items without an explicit span get smart defaults based on their type: + +- **Measures (KPIs):** span 3 (4 per row) +- **Nested views (tables/charts):** span 4–12 depending on the number of columns -By default, nested views in a dashboard flow side by side. Use `# break` on a nested view to force it onto a new row: >>>malloy #(docs) size=large limit=5000 # dashboard -run: airports -> { - group_by: state - aggregate: airport_count +run: flights -> { + group_by: carriers.nickname + aggregate: + # span = 4 + flight_count + # span = 4 + total_distance + nest: + # span = 6 + # bar_chart + by_destination is { + group_by: destination + aggregate: flight_count + limit: 10 + } + # span = 6 + top_routes is { + group_by: destination + aggregate: + flight_count + total_distance + limit: 10 + } + limit: 3 +} +>>>markdown + +### Row Breaks + +By default, items flow left-to-right and wrap when they exceed 12 columns. Use `# break` to force an item onto a new row: +>>>malloy +#(docs) size=large limit=5000 +# dashboard +run: flights -> { + group_by: carriers.nickname + aggregate: + # span = 4 + flight_count + # span = 4 + total_distance nest: # break - by_fac_type is { - group_by: fac_type - aggregate: airport_count + # span = 12 + # bar_chart + by_destination is { + group_by: destination + aggregate: flight_count + limit: 10 } - limit: 5 + limit: 3 } >>>markdown -## Dashboards with Charts +### Equal Columns Mode + +Instead of the 12-column grid, you can force a fixed number of equal-width columns with `# dashboard { columns = N }`: -Nested views inside a dashboard can use any visualization tag. This lets you combine KPI cards, tables, and charts in a single view: >>>malloy #(docs) size=large limit=5000 -source: flights is duckdb.table('../data/flights.parquet') extend { - join_one: carriers is duckdb.table('../data/carriers.parquet') - on carrier = carriers.code - measure: flight_count is count() +# dashboard { columns = 3 } +run: flights -> { + group_by: carriers.nickname + aggregate: + flight_count + total_distance + nest: + by_destination is { + group_by: destination + aggregate: flight_count + limit: 5 + } + limit: 3 +} +>>>markdown + +### Custom Gap + +Use the `gap` property to control spacing between items: + +>>>malloy +#(docs) size=large limit=5000 +# dashboard { gap = 24 } +run: flights -> { + group_by: carriers.nickname + aggregate: + # span = 6 + flight_count + # span = 6 + total_distance + limit: 3 } +>>>markdown + +## Dashboards with Charts + +Nested views inside a dashboard can use any visualization tag. This lets you combine KPI cards, tables, and charts in a single view: >>>malloy #(docs) size=large limit=5000 # dashboard @@ -99,20 +233,78 @@ run: flights -> { group_by: carriers.nickname aggregate: flight_count nest: + # span = 6 # big_value kpis is { aggregate: # label="Flights" flight_count } - # break + # span = 6 # bar_chart + # subtitle="Top 10 destinations" by_destination is { group_by: destination aggregate: flight_count limit: 10 } + # break + # span = 12 + # subtitle="Top destinations by flight count and distance" + route_detail is { + group_by: destination + aggregate: + flight_count + total_distance + limit: 10 + } + limit: 3 +} +>>>markdown + +### Chart Height + +Control chart height using `size.height` on the chart tag: + +>>>malloy +#(docs) size=large limit=5000 +# dashboard +run: flights -> { + group_by: carriers.nickname + nest: + # span = 12 + # bar_chart { size.height = 400 } + # subtitle="Taller chart (400px)" + by_destination is { + group_by: destination + aggregate: flight_count + limit: 15 + } limit: 3 } >>>markdown +## Responsive Behavior + +Dashboards automatically adapt to container width: + +- **Below 600px:** All items collapse to a single column +- **Below 900px:** Items are capped at 6 columns (half width) +- **Above 900px:** Full 12-column grid + +This ensures dashboards look good on mobile, tablet, and desktop without any extra configuration. + +## Default Sizing + +When no explicit `# span` is set, the dashboard assigns intelligent defaults: + +| Item type | Default span | Rationale | +|-----------|-------------|-----------| +| Measure (KPI) | 3 | Fits 4 KPIs per row | +| Nest with 1–3 columns | 4 | Compact tables/charts | +| Nest with 4–5 columns | 6 | Medium tables | +| Nest with 6–8 columns | 8 | Wide tables | +| Nest with 9+ columns or nested charts | 8–12 | Weighted by content | + +Nested views that themselves contain sub-nests (e.g., a table with an embedded bar chart column) are weighted more heavily — each sub-nest counts as 3 columns in the span calculation, while scalar columns count as 1. + diff --git a/src/documentation/visualizations/overview.malloynb b/src/documentation/visualizations/overview.malloynb index 76fc867b..d7979f73 100644 --- a/src/documentation/visualizations/overview.malloynb +++ b/src/documentation/visualizations/overview.malloynb @@ -50,7 +50,7 @@ Malloy's rendering library uses [Vega-Lite](https://vega.github.io/vega-lite/) f | Tag | Description | Key Properties | |-----|-------------|----------------| -| [`# bar_chart`](bar_charts.malloynb) | Bar chart | `.stack`, `.size`, `.x`, `.y`, `.series`, `.title`, `.subtitle` | +| [`# bar_chart`](bar_charts.malloynb) | Bar chart | `.stack`, `.horizontal`, `.size`, `.x`, `.y`, `.series`, `.title`, `.subtitle` | | [`# line_chart`](charts_line_chart.malloynb) | Line chart | `.zero_baseline`, `.size`, `.interpolate`, `.x`, `.y`, `.series`, `.title`, `.subtitle` | | [`# scatter_chart`](scatter_charts.malloynb) | Scatter plot | Uses field order: x, y, color, size, shape | | [`# shape_map`](shape_maps.malloynb) | Choropleth map (US states) | Uses field order: state, value | @@ -61,7 +61,7 @@ Malloy's rendering library uses [Vega-Lite](https://vega.github.io/vega-lite/) f | Tag | Description | Key Properties | |-----|-------------|----------------| -| [`# dashboard`](dashboards.malloynb) | Dashboard layout | `.table.max_height`, `# break` on nested views | +| [`# dashboard`](dashboards.malloynb) | Dashboard layout | `.columns`, `.gap`, `.table.max_height`, `# span`, `# break`, `# subtitle`, `# borderless` | | [`# table`](tables.malloynb) | Table (default) | `.size=fill`, `# column { width, height, word_break }` | | [`# pivot`](pivots.malloynb) | Pivot nested query into columns | `.dimensions` | | [`# transpose`](transpose.malloynb) | Swap rows and columns | `.limit` |