Table presentation lives under the chart’s existing style key, nested so it stays separate from generic chart chrome (background, border_radius, …) and matches how Vega groups mark-specific options.
charts:
my_table:
type: table
query: my_query
style:
background: null
table:
density: compact
zebra: true
rowBackgroundColor: row_highlight_color
columns:
revenue:
hidden: false
header:
title: Revenue
align: right
value:
number:
style: currency
currency: USD
decimals: 0
link: "https://wiki.internal/metrics/revenue"
Every key under style.table and style.columns is optional; omitted keys use product defaults (renderer/theme). Generic style keys (background, etc.) follow existing chart behavior.
Naming note: Vega-Lite uses the field name format (and formatType) inside axis/tooltip/value specs for number/date display. That is unrelated to a top-level format key — those leaves still appear under style.columns.<id>.value.* per Vega-Lite parity.
style.cells, row indices, or per-cell format objects. There is no YAML path like “format the cell at row 7, column revenue.”style.columns.<id> apply to every cell in that column. Per-row differences come only from row values (strings that resolve to another column’s value, URL templates with {% raw %}{{ col }}{% endraw %}, or the cell’s own raw field vs presentation adornments).So: we dropped cell as a config concept, not cell as a rendered thing.
style.table (table-wide)Shared defaults affecting whole table behavior. All fields optional. Only meaningful for type: table (compiler errors or ignores on other chart types — pick one and document).
style:
background: "#fafafa"
table:
density: compact
zebra: true
defaultRowHeight: 32
gridLines: horizontal
Terminology: density controls row padding / vertical rhythm (e.g. compact vs comfortable). zebra enables alternating row backgrounds for readability.
Row-level rules are still out of scope (no rules engine). One explicit table-level exception: a single background for all cells in the same data row, driven by a column (query computes #ffcccc, null, etc., per row).
style:
table:
rowBackgroundColor: row_highlight_color
rowBackgroundColor uses column-ID-first resolution: if row_highlight_color is a query column ID, each row uses that row’s value as the row background (invalid/null → skip, warn if appropriate).style.columns.<id>.style.backgroundColor is renderer-defined — document precedence (recommend: column cell background paints on top of row background when both set).Workaround if a row key is not implemented yet: set the same source column on style.columns.<col>.style.backgroundColor for every visible body column (verbose but equivalent visually).
style.columnsPer-column overrides keyed by stable query column ID.
style:
columns:
revenue:
hidden: false
header:
title: Revenue
align: right
value:
number:
style: currency
currency: USD
decimals: 0
link: "https://wiki.internal/metrics/revenue"
hidden is column-level (not cell-level)hidden belongs directly under the column definition (style.columns.<id>.hidden).For scalar string values under style.columns.<id> where the field means “display / URL / CSS-like value for this cell,” resolution is always:
One key per concern — never parallel *FromColumn keys.
Use column-ID-first for any of these when the YAML value is a plain string (not a nested mapping):
| Area | Examples |
|---|---|
Column style object (style.columns.<id>.style) |
textColor, backgroundColor, fontWeight, borderColor, … |
link |
Scalar link: … (static URL, column ID, or template string — see Links) |
visual |
series; color strings such as positiveColor, negativeColor if they are plain strings |
value |
String adornments that are allowed as strings, e.g. prefix, suffix on number display, if present as scalars |
Templates: A string containing curly-brace placeholders (for example, <code>{{ column_id }}</code>) or the product’s one canonical placeholder syntax is a template, not a column ID lookup on the whole string. Placeholders refer to column IDs inside the template; the outer string is still a template literal.
| Area | Why |
|---|---|
style.table.* |
Defaults are literals/enums except explicitly row-scoping keys (currently rowBackgroundColor), which use column-ID-first like column style strings. |
Generic chart style keys |
background, border_radius, … — literals/enums for chart chrome, not column-ID-first (unless explicitly documented otherwise). |
| Booleans / numbers / enums | hidden, visual.type, visual.mode, density, gridLines, align enums — not column-ID strings. |
| Nested value specs | Objects under value.number, value.date, etc. follow Vega-Lite format parity (see below); individual string leaves inside those objects still use column-ID-first if the field is defined as row-varying in schema. |
header.title |
Literal only for M2 (display name for the column). Avoid accidental resolution if a column ID equals the intended title text; authors can use query metadata for titles when needed. |
If a literal would be valid but matches a column ID, column wins. Authors who must force a literal that equals an ID use an explicit escape documented in schema (for example a reserved prefix) or rename the column in the query — pick one rule in the compiler and test it.
style.columns.<id> apply to all body cells in that column (and header treatment as defined by schema).Only plain strings in these places participate in column-ID-first resolution (see exceptions above). Below, placeholder names must be query column IDs on the same query as the table.
style.table (row-scoping exception)| Attribute | Example YAML |
|---|---|
rowBackgroundColor |
under style.table: rowBackgroundColor: row_highlight_color |
style object (style.columns.<id>.style)| Attribute | Example YAML |
|---|---|
textColor |
textColor: status_text_color |
backgroundColor |
backgroundColor: row_bg_hex |
fontWeight |
fontWeight: emphasis_flag (cell must contain valid weight token, e.g. bold) |
borderColor |
borderColor: border_hex_col |
style:
columns:
amount:
style:
textColor: amount_text_color
backgroundColor: amount_bg_color
fontWeight: amount_weight
borderColor: amount_border_color
link (scalar on the displayed column)| Form | Example YAML |
|---|---|
| URL from column | link: profile_url |
| Static URL | link: "https://example.com/docs" |
| Template | {% raw %}link: "https://app.example/u/{{ user_id }}"{% endraw %} |
style:
columns:
name:
link: profile_url
visual.* (in-cell sparkline / bar on the target column)| Attribute | Example YAML |
|---|---|
series |
series: revenue_series (column holds series data per row) |
positiveColor |
positiveColor: pos_hex_col |
negativeColor |
negativeColor: neg_hex_col |
style:
columns:
trend:
visual:
type: sparkline
series: spark_values
margin_pct:
visual:
type: bar
mode: diverging
positiveColor: bar_pos_color
negativeColor: bar_neg_color
value.* string leaves (Vega-Lite-shaped; only listed string fields)String fields under value that the schema exposes as scalars may use column-ID-first (e.g. per-row unit glyph or suffix). Exact leaf names follow Vega-Lite parity—commonly includes adornments such as:
| Attribute (illustrative) | Example YAML |
|---|---|
value.number.prefix |
prefix: currency_symbol_col |
value.number.suffix |
suffix: unit_suffix_col |
style:
columns:
revenue:
value:
number:
style: decimal
prefix: currency_glyph
suffix: scale_suffix
If a string equals a column ID, the cell shows that row’s value from the source column; otherwise it is a fixed literal. Non-string value fields (decimals, enums like value.number.style, nested spec objects, etc.) do not use column-ID-first unless explicitly documented as row-varying strings.
link.type enum unless multiple link kinds need different validation or UX; default is “navigate to URL” with safe URL validation (reject dangerous schemes).link.style:
columns:
profile_url:
hidden: true
name:
link: profile_url
Behavior:
- name is displayed; URL comes from profile_url in the same row.
- If URL value is null/invalid, row renders plain text (no link).
style:
columns:
status_text:
style:
backgroundColor: status_color
textColor: status_text_color
fontWeight: status_font_weight
style:
columns:
margin_pct:
visual:
type: bar
mode: diverging
positiveColor: pos_color_column
negativeColor: neg_color_column
If pos_color_column is a column ID, per-row colors apply; if not, fall back to literal hex/CSS.
Rule: Sparklines, data bars, and related micro-charts are cell presentation, not a separate chart type. They live under style.columns.<id>.visual, use query-owned data shape (series array / numeric value in row data), and use the same column-ID-first rule for any string field that can be row-sourced (e.g. series, optional color strings).
Supported in M2:
- visual.type: sparkline
- visual.type: bar
style:
columns:
revenue_trend:
visual:
type: sparkline
series: revenue_series
margin_pct:
visual:
type: bar
mode: diverging
positiveColor: "#1B9E5A"
negativeColor: "#D64545"
Number and date display under style.columns.<id>.value should mirror Vega-Lite’s format and formatType fields (same names, same meaning) so authors learn one model across charts and tables. Implementation may live in shared Python formatting used by SVG charts and table renderers; see initiative task Vega-Lite format parity for tables and Python formatters.
This is intentionally not a bespoke mini-i18n engine in YAML: advanced locale behavior follows whatever Vega-Lite’s format model supports once implemented.
style for table charts (vs other keys)Table charts (M2): under type: table, style may include:
background, border_radius, …).style.table — table-wide defaults and row-scoping keys (rowBackgroundColor, zebra, …).style.columns — map of query column ID → column config (header, value, style, link, visual, hidden, …).Other chart types: they do not use style.table / style.columns. They keep using style for chrome, plus settings, mark / encoding passthrough, KPI format, etc. — see CompiledChart / chart defaults.
mark, encoding, data) and nests under encoding.<channel> (field, type, axis, format, formatType, …). Tables are not a single mark with one encoding channel per column, so some nesting is unavoidable.style.columns.<id>.…) so authors think “this column’s display,” which matches mental models from BI tools.format, formatType, …) inside value.* (already planned).columns: [{ field: revenue, … }]) trades shorter paths for weaker keyed lookup and worse merge with includes; column-ID map stays the default for M2.For table-specific presentation:
style.tablestyle.columns.<id>Generic chart style keys (e.g. chart background) compose with table rendering as documented (container vs table body).
Duplicate keys in a single YAML mapping are invalid — compile error. This policy applies to all dashboard/face YAML the compiler loads. Do not rely on parser-specific “last key wins.”
Implementation note: Centralize YAML loading on a path that rejects duplicates (loader option or post-parse check). This is a small, one-time wiring change if parsing already goes through one module; no need for a heavy validator framework.
null for the style source column.Convenience exceptions: header.title (literal) and value.* objects (Vega-Lite-shaped, including VL’s format / formatType leaves) are allowed in YAML so authors are not forced to push every display concern into SQL — document precedence when both query metadata and formatter settings exist.
*_text_color, *_bg_color, *_link_url, etc.).
2. Reference that column ID in style.columns.<target>.style.*, link, visual.*, style.table.rowBackgroundColor, etc. (column-ID-first).Example:
{% raw %}
{% macro signed_text_color(expr) -%}
case
when {{ expr }} < 0 then '#D64545'
when {{ expr }} > 0 then '#111111'
else '#666666'
end
{%- endmacro %}
select
metric_value,
{{ signed_text_color('metric_value') }} as metric_value_text_color
from my_table
{% endraw %}
style:
columns:
metric_value:
style:
textColor: metric_value_text_color
Everything below ships in this milestone. Work may land in small implementation PRs, but there is no staged product rollout (“half the model in prod”) — the released behavior matches this spec.
style.table and style.columns on table charts (optional keys; defaults documented), including rowBackgroundColor column-ID-first.header.title literal-only; collision rule implemented and tested.style.columns.<id>.visual for sparkline and bar, query-owned data, formatting-owned rendering.style.table / style.columns (breaking pre-launch).hidden is column-level and works predictably.style, scalar link, applicable visual / value string fields, and style.table.rowBackgroundColor; header.title remains literal-only.style.columns.<id>.visual with the spark rule above.style.table / style.columns.