Sector Charts¶
Sector charts divide a whole into angular parts, making the total-and-parts relationship visually explicit; this family includes pie charts, donut charts, and polar-area charts. They are most useful when showing simple composition with a small number of categories, especially when the main message is share of a whole rather than precise comparison between similar values.
Dataface sector charts use a small set of top-level shorthand fields. In most cases, you only need query, type: pie, theta, and color to get started.
A chart's control surface is the full set of authored properties available on a single chart. In Dataface, that surface is primarily top-level chart fields plus the typed style object.
This page demonstrates every sector-specific authored capability with at least one runnable example. For the field-level source of truth — default ownership and the shared style properties every chart family inherits (palette, border, padding, font, tooltip) — see the YAML Schema Reference.
A Complete Sector Chart¶
The full package: a donut that reports a formatted grand total in the center and labels every slice with its category, share of the whole, and value. The rest of the page breaks these capabilities apart field by field.
source: examples_db
style:
board:
width: 700
charts:
revenue_share_hero:
query:
sql: |
SELECT category, SUM(revenue) AS revenue FROM ecommerce_orders GROUP BY category ORDER BY revenue DESC
type: donut
title: Revenue by Category
theta: revenue
color: category
total:
label: Total Revenue
format: "$,.0f"
style:
marks:
slice:
labels:
template: "{{ '{:.0%}'.format(percent) }} {{ category }}\n{{ '${:,.0f}'.format(revenue) }}"
rows:
- revenue_share_hero
Minimum Required for a Sector Chart¶
These are the minimum fields required to render a basic sector chart in Dataface.
| Dataface field | Maps to Vega-Lite | Allowed values | Notes |
|---|---|---|---|
query |
data.values |
Query name or query reference | Supplies the dataset. |
type: pie |
mark.type: "arc" |
Literal pie |
Selects the standard pie form. |
theta |
encoding.theta.field |
Field name | Numeric field that sets wedge size. |
color |
encoding.color.field |
Field name | Category field that splits the whole into slices. |
Minimum Example¶
source: examples_db
style:
board:
width: 700
charts:
revenue_share_minimal:
query:
sql: |
SELECT category, SUM(revenue) AS revenue FROM ecommerce_orders GROUP BY category ORDER BY revenue DESC
type: pie
title: Revenue Share by Category
theta: revenue
color: category
rows:
- revenue_share_minimal
Top-Level Chart Fields¶
These are the top-level chart properties you set directly on a sector chart before you get into nested properties under style.
| Dataface field | Maps to Vega-Lite | Allowed values | Notes |
|---|---|---|---|
query |
data.values |
Query name or query reference | Query results become the plotted dataset. |
type |
mark.type: "arc" |
pie or donut |
donut is shorthand for a pie with an inner radius (see style.inner_radius). |
theta |
encoding.theta.field |
Field name | Required wedge-size field. |
color |
encoding.color.field |
Field name | Category field that splits the whole into slices. |
title |
title.text |
String | Chart title. |
subtitle |
title.subtitle |
String | Secondary line displayed under the title. |
description |
metadata | String | Available for tooling and context tooltips. |
link |
drill-down URL | URL template string | Click-through target; {{ column }} tokens interpolate per slice. |
total |
center total slot | {visible, label, format} |
Donut center total: the summed value with an optional caption and number format. |
labels |
per-slice text | {template, where} |
Direct per-slice labels. template is a Jinja string over each row's columns plus the injected percent, value, color, and total; where is a Jinja expression that filters which slices are labeled. Omitting labels applies the default two-line template — percent and the slice name on the first line, value on the second. |
conditional_formatting |
mark-fill condition | {column: {when: [...]}} |
Threshold rules that repaint slice fill by value. Use on a pie without color — the two both own the fill and cannot combine. |
Title, Subtitle, and Drill-Down Link¶
title and subtitle render above the chart. description and link are non-visual: description feeds context tooltips and tooling, and link turns each slice into a click-through (here into that category's page). (The center total and its formatting are shown in A Complete Sector Chart above.)
source: examples_db
style:
board:
width: 700
charts:
revenue_share_composition:
query:
sql: |
SELECT category, SUM(revenue) AS revenue FROM ecommerce_orders GROUP BY category ORDER BY revenue DESC
type: pie
title: Product Mix
subtitle: Fiscal year to date
description: Share of total revenue by product category.
link: "/category/{{ category }}"
theta: revenue
color: category
style:
marks:
slice:
labels:
template: "{{ category }}"
rows:
- revenue_share_composition
Slice Label Templates¶
Override the default template with style.marks.slice.labels.template. Following the default's convention, lead with percent. Three donuts on the same data: a compact percent-and-name line, a name over a currency-formatted value, and a where-filtered label that names only the largest slices.
source: examples_db
style:
board:
width: 700
queries:
by_category:
sql: |
SELECT category, SUM(revenue) AS revenue FROM ecommerce_orders GROUP BY category ORDER BY revenue DESC
charts:
labels_compact:
query: by_category
type: donut
title: Percent and Name
theta: revenue
color: category
total:
visible: false
style:
marks:
slice:
labels:
template: "{{ '{:.0%}'.format(percent) }} {{ category }}"
labels_currency:
query: by_category
type: donut
title: Name and Value
theta: revenue
color: category
total:
visible: false
style:
marks:
slice:
labels:
template: "{{ category }}\n{{ '${:,.0f}'.format(revenue) }}"
labels_filtered:
query: by_category
type: donut
title: Largest Slices Only
theta: revenue
color: category
total:
visible: false
style:
marks:
slice:
labels:
template: "{{ category }}"
where: "percent >= 0.3"
rows:
- labels_compact
- labels_currency
- labels_filtered
Conditional Formatting¶
conditional_formatting repaints slice fill by a per-column threshold rule. It replaces the color channel rather than layering on it — both own the fill — so use it on a pie with no color. Here slices above $150k are flagged in amber, the rest in grey.
source: examples_db
style:
board:
width: 700
charts:
revenue_share_threshold:
query:
sql: |
SELECT category, SUM(revenue) AS revenue FROM ecommerce_orders GROUP BY category ORDER BY revenue DESC
type: pie
title: Above $150k
theta: revenue
style:
marks:
slice:
labels:
template: "{{ category }}"
conditional_formatting:
revenue:
when:
- gte: 150000
background: "#f59e0b"
- lt: 150000
background: "#e5e7eb"
rows:
- revenue_share_threshold
Style Fields¶
Use style for the sector-specific presentational surface: ring geometry, slice paint, and the center-total paint. Shared style properties every chart inherits (palette, border, padding, font, tooltip) live in the YAML Schema Reference.
| Dataface field | Maps to Vega-Lite | Allowed values | Notes |
|---|---|---|---|
style.inner_radius |
mark.innerRadius |
0–1 |
Hole-to-disk ratio. 0 (or omitted) is a solid pie; type: donut defaults it to 0.6. Set it explicitly for any other ring thickness. |
style.aspect_ratio |
viewport ratio | Positive number | Width ÷ height of the chart viewport. |
style.marks.slice |
arc mark paint | {opacity, gap, corner_radius, stroke, labels} |
Per-slice mark paint and layout. |
style.total |
center total paint | {value, label} |
Typography of the donut center total's number (value) and caption (label). |
Ring Thickness¶
style.inner_radius sets any hole-to-disk ratio beyond the donut default of 0.6. 0.3 is a thick ring; values near 1 are a thin band.
source: examples_db
style:
board:
width: 700
charts:
revenue_share_ring:
query:
sql: |
SELECT category, SUM(revenue) AS revenue FROM ecommerce_orders GROUP BY category ORDER BY revenue DESC
type: pie
title: Thick Ring
theta: revenue
color: category
total:
visible: false
style:
inner_radius: 0.3
marks:
slice:
labels:
template: "{{ category }}"
rows:
- revenue_share_ring
Viewport¶
style.aspect_ratio reshapes the chart viewport — its width ÷ height. Above 1 is wider than tall.
source: examples_db
style:
board:
width: 700
charts:
revenue_share_viewport:
query:
sql: |
SELECT category, SUM(revenue) AS revenue FROM ecommerce_orders GROUP BY category ORDER BY revenue DESC
type: pie
title: Wide Viewport
theta: revenue
color: category
style:
aspect_ratio: 1.6
marks:
slice:
labels:
template: "{{ category }}"
rows:
- revenue_share_viewport
Slice Paint¶
style.marks.slice paints the arcs — here noticeably rounded corners and a wide angular gap between slices.
source: examples_db
style:
board:
width: 700
charts:
revenue_share_slice_paint:
query:
sql: |
SELECT category, SUM(revenue) AS revenue FROM ecommerce_orders GROUP BY category ORDER BY revenue DESC
type: donut
title: Rounded, Gapped Slices
theta: revenue
color: category
total:
visible: false
style:
marks:
slice:
corner_radius: 10
gap: 0.05
labels:
template: "{{ category }}"
rows:
- revenue_share_slice_paint
Center-Total Styling¶
style.total styles the center-total typography. Here the total value is painted in a distinct accent color.
source: examples_db
style:
board:
width: 700
charts:
revenue_share_total_style:
query:
sql: |
SELECT category, SUM(revenue) AS revenue FROM ecommerce_orders GROUP BY category ORDER BY revenue DESC
type: donut
title: Accented Total
theta: revenue
color: category
total:
label: Total
format: "$,.0f"
style:
total:
value:
font:
color: "#2563eb"
marks:
slice:
labels:
template: "{{ category }}"
rows:
- revenue_share_total_style
Slice Count and Density¶
How a sector chart labels itself is automatic and data-and-width aware — you do not author it. As slice count rises or the chart narrows, direct labels stop fitting, so the renderer switches presentation. Understanding the transitions is the difference between a chart that reads cleanly and one that collapses into unreadable callouts.
The rules, at a glance:
- Wedges under 8% of the whole keep their slice and hover tooltip but drop their direct label — small slices don't get to crowd the disk.
- Once the number of labelable wedges exceeds what fits around the disk (about 4 at a typical width, fewer when narrow), the chart renders as a donut plus an attached table that carries slice identity, color, and value.
- Very narrow charts always use the attached table.
Small Slices Drop Their Label¶
Four slices, one of them under 8%. The small wedge still renders (and its value is in the tooltip), but it gets no direct label rather than cluttering the disk.
source: examples_db
style:
board:
width: 700
queries:
long_tail:
type: values
columns: [segment, revenue]
values:
- ["Enterprise", 50]
- ["Mid-Market", 30]
- ["SMB", 15]
- ["Trial", 5]
charts:
small_slices:
query: long_tail
type: donut
title: Small Slices Suppressed
theta: revenue
color: segment
total:
visible: false
style:
marks:
slice:
labels:
template: "{{ segment }}"
rows:
- small_slices
High Cardinality Attaches a Table¶
Eight products is more than fits as direct callouts, so the chart renders the donut with an attached table instead — each row keyed by slice color, with the value alongside.
source: examples_db
style:
board:
width: 700
charts:
by_product:
query:
sql: |
SELECT product, SUM(units_sold) AS units FROM ecommerce_orders GROUP BY product ORDER BY units DESC
type: donut
title: Units Sold by Product
theta: units
color: product
rows:
- by_product
Authored Surface¶
Dataface sector charts are authored with type: pie or type: donut plus top-level channels such as theta and color, the top-level total/labels/conditional_formatting fields, and the typed style: object. Arbitrary Vega-Lite spec, mark, encoding, config, transform, params, and composition blocks are rejected on the authored surface.