Malloy supports model-level annotations (using ##) that configure rendering defaults across an entire model. These are set at the top of a file, outside any source or query.
Chart Defaults
Use ## viz.<chart_type>.defaults.* to set default properties for all charts of a given type in the model. Individual charts can still override these defaults.
In this example, all line charts default to independent y-axes. The third nested chart explicitly overrides back to shared axes:
## viz.line_chart.defaults.y.independent=true 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() aircraft_count is count(tail_num) }
run: flights -> { group_by: carriers.nickname aggregate: flight_count // This chart inherits y.independent=true from the model default # line_chart nest: default_independent is { group_by: dep_month is dep_time.month aggregate: flight_count limit: 12 } // This chart explicitly overrides back to shared axes # line_chart { y.independent=false } nest: shared_axes is { group_by: dep_month is dep_time.month aggregate: flight_count limit: 12 } limit: 3 }
[ { "nickname": "Southwest", "flight_count": 88751, "default_independent": [ { "dep_month": "2005-12-01T00:00:00.000Z", "flight_count": 1606 }, { "dep_month": "2005-11-01T00:00:00.000Z", "flight_count": 1448 }, { "dep_month": "2005-10-01T00:00:00.000Z", "flight_count": 1543 }, { "dep_month": "2005-09-01T00:00:00.000Z", "flight_count": 1466 }, { "dep_month": "2005-08-01T00:00:00.000Z", "flight_count": 1582 }, { "dep_month": "2005-07-01T00:00:00.000Z", "flight_count": 1564 }, { "dep_month": "2005-06-01T00:00:00.000Z", "flight_count": 1383 }, { "dep_month": "2005-05-01T00:00:00.000Z", "flight_count": 1384 }, { "dep_month": "2005-04-01T00:00:00.000Z", "flight_count": 1432 }, { "dep_month": "2005-03-01T00:00:00.000Z", "flight_count": 1439 }, { "dep_month": "2005-02-01T00:00:00.000Z", "flight_count": 1281 }, { "dep_month": "2005-01-01T00:00:00.000Z", "flight_count": 1421 } ], "shared_axes": [ { "dep_month": "2005-12-01T00:00:00.000Z", "flight_count": 1606 }, { "dep_month": "2005-11-01T00:00:00.000Z", "flight_count": 1448 }, { "dep_month": "2005-10-01T00:00:00.000Z", "flight_count": 1543 }, { "dep_month": "2005-09-01T00:00:00.000Z", "flight_count": 1466 }, { "dep_month": "2005-08-01T00:00:00.000Z", "flight_count": 1582 }, { "dep_month": "2005-07-01T00:00:00.000Z", "flight_count": 1564 }, { "dep_month": "2005-06-01T00:00:00.000Z", "flight_count": 1383 }, { "dep_month": "2005-05-01T00:00:00.000Z", "flight_count": 1384 }, { "dep_month": "2005-04-01T00:00:00.000Z", "flight_count": 1432 }, { "dep_month": "2005-03-01T00:00:00.000Z", "flight_count": 1439 }, { "dep_month": "2005-02-01T00:00:00.000Z", "flight_count": 1281 }, { "dep_month": "2005-01-01T00:00:00.000Z", "flight_count": 1421 } ] }, { "nickname": "USAir", "flight_count": 37683, "default_independent": [ { "dep_month": "2005-12-01T00:00:00.000Z", "flight_count": 333 }, { "dep_month": "2005-11-01T00:00:00.000Z", "flight_count": 470 }, { "dep_month": "2005-10-01T00:00:00.000Z", "flight_count": 651 }, { "dep_month": "2005-09-01T00:00:00.000Z", "flight_count": 652 }, { "dep_month": "2005-08-01T00:00:00.000Z", "flight_count": 723 }, { "dep_month": "2005-07-01T00:00:00.000Z", "flight_count": 686 }, { "dep_month": "2005-06-01T00:00:00.000Z", "flight_count": 645 }, { "dep_month": "2005-05-01T00:00:00.000Z", "flight_count": 663 }, { "dep_month": "2005-04-01T00:00:00.000Z", "flight_count": 686 }, { "dep_month": "2005-03-01T00:00:00.000Z", "flight_count": 667 }, { "dep_month": "2005-02-01T00:00:00.000Z", "flight_count": 578 }, { "dep_month": "2005-01-01T00:00:00.000Z", "flight_count": 692 } ], "shared_axes": [ { "dep_month": "2005-12-01T00:00:00.000Z", "flight_count": 333 }, { "dep_month": "2005-11-01T00:00:00.000Z", "flight_count": 470 }, { "dep_month": "2005-10-01T00:00:00.000Z", "flight_count": 651 }, { "dep_month": "2005-09-01T00:00:00.000Z", "flight_count": 652 }, { "dep_month": "2005-08-01T00:00:00.000Z", "flight_count": 723 }, { "dep_month": "2005-07-01T00:00:00.000Z", "flight_count": 686 }, { "dep_month": "2005-06-01T00:00:00.000Z", "flight_count": 645 }, { "dep_month": "2005-05-01T00:00:00.000Z", "flight_count": 663 }, { "dep_month": "2005-04-01T00:00:00.000Z", "flight_count": 686 }, { "dep_month": "2005-03-01T00:00:00.000Z", "flight_count": 667 }, { "dep_month": "2005-02-01T00:00:00.000Z", "flight_count": 578 }, { "dep_month": "2005-01-01T00:00:00.000Z", "flight_count": 692 } ] }, { "nickname": "American", "flight_count": 34577, "default_independent": [ { "dep_month": "2005-12-01T00:00:00.000Z", "flight_count": 428 }, { "dep_month": "2005-11-01T00:00:00.000Z", "flight_count": 401 }, { "dep_month": "2005-10-01T00:00:00.000Z", "flight_count": 406 }, { "dep_month": "2005-09-01T00:00:00.000Z", "flight_count": 492 }, { "dep_month": "2005-08-01T00:00:00.000Z", "flight_count": 529 }, { "dep_month": "2005-07-01T00:00:00.000Z", "flight_count": 493 }, { "dep_month": "2005-06-01T00:00:00.000Z", "flight_count": 466 }, { "dep_month": "2005-05-01T00:00:00.000Z", "flight_count": 507 }, { "dep_month": "2005-04-01T00:00:00.000Z", "flight_count": 491 }, { "dep_month": "2005-03-01T00:00:00.000Z", "flight_count": 464 }, { "dep_month": "2005-02-01T00:00:00.000Z", "flight_count": 467 }, { "dep_month": "2005-01-01T00:00:00.000Z", "flight_count": 399 } ], "shared_axes": [ { "dep_month": "2005-12-01T00:00:00.000Z", "flight_count": 428 }, { "dep_month": "2005-11-01T00:00:00.000Z", "flight_count": 401 }, { "dep_month": "2005-10-01T00:00:00.000Z", "flight_count": 406 }, { "dep_month": "2005-09-01T00:00:00.000Z", "flight_count": 492 }, { "dep_month": "2005-08-01T00:00:00.000Z", "flight_count": 529 }, { "dep_month": "2005-07-01T00:00:00.000Z", "flight_count": 493 }, { "dep_month": "2005-06-01T00:00:00.000Z", "flight_count": 466 }, { "dep_month": "2005-05-01T00:00:00.000Z", "flight_count": 507 }, { "dep_month": "2005-04-01T00:00:00.000Z", "flight_count": 491 }, { "dep_month": "2005-03-01T00:00:00.000Z", "flight_count": 464 }, { "dep_month": "2005-02-01T00:00:00.000Z", "flight_count": 467 }, { "dep_month": "2005-01-01T00:00:00.000Z", "flight_count": 399 } ] } ]
WITH __stage0 AS ( SELECT group_set, carriers_0."nickname" as "nickname__0", CASE WHEN group_set=0 THEN COUNT(1) END as "flight_count__0", CASE WHEN group_set=1 THEN DATE_TRUNC('month', base."dep_time") END as "dep_month__1", CASE WHEN group_set=1 THEN COUNT(1) END as "flight_count__1", CASE WHEN group_set=2 THEN DATE_TRUNC('month', base."dep_time") END as "dep_month__2", CASE WHEN group_set=2 THEN COUNT(1) END as "flight_count__2" FROM '../data/flights.parquet' as base LEFT JOIN '../data/carriers.parquet' AS carriers_0 ON base."carrier"=carriers_0."code" CROSS JOIN (SELECT UNNEST(GENERATE_SERIES(0,2,1)) as group_set ) as group_set GROUP BY 1,2,4,6 ) , __stage1 AS ( SELECT *, CASE WHEN GROUP_SET=0 THEN ROW_NUMBER() OVER (PARTITION BY group_set ORDER BY "flight_count__0" desc) END as __row_number__0, CASE WHEN GROUP_SET=1 THEN ROW_NUMBER() OVER (PARTITION BY CAST("nickname__0" as VARCHAR), group_set ORDER BY "dep_month__1" desc) END as __row_number__1, CASE WHEN GROUP_SET=2 THEN ROW_NUMBER() OVER (PARTITION BY CAST("nickname__0" as VARCHAR), group_set ORDER BY "dep_month__2" desc) END as __row_number__2 FROM __stage0 ) , __stage2 AS ( SELECT *, MAX(CASE WHEN group_set IN (0,1,2) THEN CASE WHEN (GROUP_SET = 0 AND __row_number__0 > 3) THEN 1 ELSE 0 END END) OVER(PARTITION BY CAST("nickname__0" as VARCHAR)) as __shaving__0 FROM __stage1 WHERE NOT ((GROUP_SET = 1 AND __row_number__1 > 12) OR (GROUP_SET = 2 AND __row_number__2 > 12)) ) , __stage3 AS ( SELECT * FROM __stage2 WHERE NOT ((group_set IN (0,1,2) AND __shaving__0 > 0) ) ) SELECT "nickname__0" as "nickname", MAX(CASE WHEN group_set=0 THEN "flight_count__0" END) as "flight_count", COALESCE(LIST({ "dep_month": "dep_month__1", "flight_count": "flight_count__1"} ORDER BY "dep_month__1" DESC NULLS LAST) FILTER (WHERE group_set=1),[]) as "default_independent", COALESCE(LIST({ "dep_month": "dep_month__2", "flight_count": "flight_count__2"} ORDER BY "dep_month__2" DESC NULLS LAST) FILTER (WHERE group_set=2),[]) as "shared_axes" FROM __stage3 GROUP BY 1 ORDER BY 2 desc NULLS LAST LIMIT 3
The same pattern works for other chart types:
## viz.bar_chart.defaults.stack ## viz.bar_chart.defaults.y.independent=true
The # viz= Shorthand
Instead of # bar_chart or # line_chart, you can use the # viz= shorthand. All chart properties work the same way:
# viz=bar { title='Flights by Carrier' subtitle='Top 10' } run: flights -> { group_by: carriers.nickname aggregate: flight_count limit: 10 }
[ { "nickname": "Southwest", "flight_count": 88751 }, { "nickname": "USAir", "flight_count": 37683 }, { "nickname": "American", "flight_count": 34577 }, { "nickname": "Northwest", "flight_count": 33580 }, { "nickname": "United", "flight_count": 32757 }, { "nickname": "Delta", "flight_count": 32130 }, { "nickname": "Continental Express", "flight_count": 16074 }, { "nickname": "American Eagle", "flight_count": 15869 }, { "nickname": "Atlantic Southeast", "flight_count": 15769 }, { "nickname": "America West", "flight_count": 9750 } ]
SELECT carriers_0."nickname" as "nickname", COUNT(1) as "flight_count" FROM '../data/flights.parquet' as base LEFT JOIN '../data/carriers.parquet' AS carriers_0 ON base."carrier"=carriers_0."code" GROUP BY 1 ORDER BY 2 desc NULLS LAST LIMIT 10
Theme Properties
Use ## theme.* at the model level to customize the appearance of rendered results. Individual views can override with # theme.*.
## theme.tableHeaderColor=darkblue source: airports is duckdb.table('../data/airports.parquet') extend { measure: airport_count is count() }
run: airports -> { group_by: state aggregate: airport_count limit: 5 }
[ { "state": "TX", "airport_count": 1845 }, { "state": "CA", "airport_count": 984 }, { "state": "IL", "airport_count": 890 }, { "state": "FL", "airport_count": 856 }, { "state": "PA", "airport_count": 804 } ]
SELECT base."state" as "state", COUNT(1) as "airport_count" FROM '../data/airports.parquet' as base GROUP BY 1 ORDER BY 2 desc NULLS LAST LIMIT 5
A view can override the model-level theme:
# theme.tableHeaderColor=darkred run: airports -> { group_by: state aggregate: airport_count limit: 5 }
[ { "state": "TX", "airport_count": 1845 }, { "state": "CA", "airport_count": 984 }, { "state": "IL", "airport_count": 890 }, { "state": "FL", "airport_count": 856 }, { "state": "PA", "airport_count": 804 } ]
SELECT base."state" as "state", COUNT(1) as "airport_count" FROM '../data/airports.parquet' as base GROUP BY 1 ORDER BY 2 desc NULLS LAST LIMIT 5
Available Theme Properties
| Property | Description | Example values |
|---|---|---|
tableBodyColor |
Text color for table body cells | blue, #333, rgb(0,0,0) |
tableHeaderColor |
Text color for table headers | gray, #666 |
tableBodyWeight |
Font weight for table body | normal, bold, 400 |
tableHeaderWeight |
Font weight for table headers | bold, 600 |
tableFontSize |
Font size for tables | 12px, 0.9em |
tableRowHeight |
Row height for tables | 24px, 2em |
tableBackground |
Background color for tables | white, #f5f5f5 |
tableBorder |
Border style for table cells | 1px solid #ccc |
tableGutterSize |
Spacing between table sections | 8px |
tablePinnedBackground |
Background for pinned columns | #fafafa |
tablePinnedBorder |
Border for pinned column edges | 2px solid #ddd |
fontFamily |
Font family for all rendered output | monospace, 'Helvetica Neue' |
background |
Overall background color | white, transparent |
Viewing these models in Malloy Publisher? It depends on which surface you are looking at, and the two behave in opposite ways.
On a model view, a notebook cell, or a saved result, Publisher supplies its own instance theme, and a caller-supplied theme takes precedence over both # theme.* and ## theme.*. So most of the properties above have no effect there: tableBodyColor, tableHeaderColor, tableFontSize, tableBackground, tableBorder, tablePinnedBackground, tablePinnedBorder, fontFamily and background are all supplied by Publisher and win. Only tableBodyWeight, tableHeaderWeight, tableRowHeight and tableGutterSize fall through. For those surfaces Publisher has its own vocabulary, namespaced under theme.palette and theme.font, where a per-chart annotation overrides the instance theme rather than losing to it. See Theming Publisher.
In the Explore panel on a model page, results render with no instance theme at all, so the properties above work exactly as documented here, and the namespaced theme.palette vocabulary does nothing. Both panels sit on the same page, so a view can look different in the explorer than it does once saved.