# Sunburst Chart > A sunburst chart shows hierarchical composition as concentric rings, with each level of the hierarchy radiating outward. A sunburst chart shows hierarchical composition as concentric rings: the inner ring breaks the total into categories, and the outer ring splits each category into its subcategories. Use it to answer "what makes up the total, and what makes up each part?" in one view, such as revenue by product line and product, or tickets by team and ticket type. ### Two-level hierarchy ```aml CustomChartDef sunburst_chart { label: 'Sunburst Chart' description: "A sunburst chart shows hierarchical composition as concentric rings, with each level of the hierarchy radiating outward." fields { field category { type: 'dimension' label: 'Category (inner ring)' } field subcategory { type: 'dimension' label: 'Subcategory (outer ring)' } field value { type: 'measure' label: 'Value' } } options { option color_scheme { type: 'select' label: 'Color scheme' options: ['tableau10', 'category10', 'accent', 'dark2', 'paired', 'set2'] default_value: 'tableau10' } option donut_hole { type: 'select' label: 'Center hole size' options: [0, 0.2, 0.35, 0.5] default_value: 0.35 } } template: @vg { "$schema": "https://vega.github.io/schema/vega/v5.json", "description": "Sunburst chart of a two-level hierarchy, built from stacked angles.", "autosize": "none", "padding": 0, "signals": [ { "name": "width", "init": "containerSize()[0] - 5", "on": [{ "events": "window:resize", "update": "containerSize()[0] - 5" }] }, { "name": "height", "init": "containerSize()[1] - 5", "on": [{ "events": "window:resize", "update": "containerSize()[1] - 5" }] }, {"name": "radius", "update": "min(width, height) / 2"}, {"name": "holeR", "update": "radius * @{options.donut_hole.value}"}, {"name": "ringSplit", "update": "holeR + (radius - holeR) * 0.55"}, {"name": "grandTotal", "update": "length(data('cats')) ? data('cats')[0].total : 0"}, { "name": "hovered", "value": null, "on": [ { "events": "@catArc:mouseover", "update": "{kind: 'category', category: datum.category, label: datum.category, amount: datum.amount, share: datum.amount / datum.total}" }, { "events": "@leafArc:mouseover", "update": "{kind: 'subcategory', category: datum.category, label: datum.subcategory, amount: datum.amount, share: datum.amount / datum.total}" }, {"events": "@catArc:mouseout, @leafArc:mouseout", "update": "null"} ] } ], "data": [ { "name": "leaves", "values": @{values}, "transform": [ {"type": "formula", "expr": "datum['@{fields.category.name}']", "as": "category"}, {"type": "formula", "expr": "datum['@{fields.subcategory.name}']", "as": "subcategory"}, {"type": "formula", "expr": "datum['@{fields.value.name}']", "as": "amount"}, {"type": "filter", "expr": "datum.amount != null && datum.amount > 0"}, { "type": "aggregate", "groupby": ["category", "subcategory"], "fields": ["amount"], "ops": ["sum"], "as": ["amount"] }, {"type": "collect", "sort": {"field": ["category", "subcategory"]}}, { "type": "stack", "field": "amount", "as": ["s0", "s1"] }, { "type": "joinaggregate", "fields": ["amount"], "ops": ["sum"], "as": ["total"] }, { "type": "joinaggregate", "fields": ["amount"], "ops": ["sum"], "as": ["cat_total"], "groupby": ["category"] }, {"type": "formula", "expr": "datum.s0 / datum.total * 2 * PI", "as": "a0"}, {"type": "formula", "expr": "datum.s1 / datum.total * 2 * PI", "as": "a1"} ] }, { "name": "cats", "source": "leaves", "transform": [ { "type": "aggregate", "groupby": ["category"], "fields": ["amount"], "ops": ["sum"], "as": ["amount"] }, {"type": "collect", "sort": {"field": "category"}}, { "type": "stack", "field": "amount", "as": ["s0", "s1"] }, { "type": "joinaggregate", "fields": ["amount"], "ops": ["sum"], "as": ["total"] }, {"type": "formula", "expr": "datum.s0 / datum.total * 2 * PI", "as": "a0"}, {"type": "formula", "expr": "datum.s1 / datum.total * 2 * PI", "as": "a1"} ] } ], "scales": [ { "name": "color", "type": "ordinal", "domain": {"data": "cats", "field": "category"}, "range": {"scheme": @{options.color_scheme.value}} } ], "marks": [ { "type": "arc", "name": "catArc", "from": {"data": "cats"}, "encode": { "update": { "x": {"signal": "width / 2"}, "y": {"signal": "height / 2"}, "startAngle": {"field": "a0"}, "endAngle": {"field": "a1"}, "innerRadius": {"signal": "holeR"}, "outerRadius": {"signal": "ringSplit"}, "fill": {"scale": "color", "field": "category"}, "fillOpacity": { "signal": "hovered === null || hovered.category === datum.category ? 1 : 0.3" }, "stroke": {"value": "white"}, "strokeWidth": {"value": 1.5}, "tooltip": { "signal": "{'Category': datum.category, 'Value': format(datum.amount, ','), 'Share of Total': format(datum.amount / datum.total, '.1%')}" } } } }, { "type": "arc", "name": "leafArc", "from": {"data": "leaves"}, "encode": { "update": { "x": {"signal": "width / 2"}, "y": {"signal": "height / 2"}, "startAngle": {"field": "a0"}, "endAngle": {"field": "a1"}, "innerRadius": {"signal": "ringSplit + 1"}, "outerRadius": {"signal": "radius"}, "fill": {"scale": "color", "field": "category"}, "fillOpacity": { "signal": "hovered === null ? 0.7 : (hovered.category !== datum.category ? 0.2 : (hovered.kind === 'subcategory' && hovered.label === datum.subcategory ? 1 : 0.75))" }, "stroke": {"value": "white"}, "strokeWidth": {"value": 1}, "tooltip": { "signal": "{'Subcategory': datum.subcategory, 'Category': datum.category, 'Value': format(datum.amount, ','), 'Share of Total': format(datum.amount / datum.total, '.1%'), 'Share of Category': format(datum.amount / datum.cat_total, '.1%')}" } } } }, { "type": "text", "interactive": false, "encode": { "update": { "x": {"signal": "width / 2"}, "y": {"signal": "height / 2 - 14"}, "align": {"value": "center"}, "text": {"signal": "hovered === null ? 'Total' : hovered.label"}, "limit": {"signal": "holeR * 1.7"}, "fontSize": {"value": 13}, "fontWeight": {"value": 600}, "fill": {"value": "#374151"}, "opacity": {"signal": "holeR > 30 ? 1 : 0"} } } }, { "type": "text", "interactive": false, "encode": { "update": { "x": {"signal": "width / 2"}, "y": {"signal": "height / 2 + 8"}, "align": {"value": "center"}, "text": {"signal": "format(hovered === null ? grandTotal : hovered.amount, ',')"}, "fontSize": {"value": 16}, "fontWeight": {"value": 700}, "fill": {"value": "#111827"}, "opacity": {"signal": "holeR > 30 ? 1 : 0"} } } }, { "type": "text", "interactive": false, "encode": { "update": { "x": {"signal": "width / 2"}, "y": {"signal": "height / 2 + 28"}, "align": {"value": "center"}, "text": {"signal": "hovered === null ? '' : format(hovered.share, '.1%') + ' of total'"}, "fontSize": {"value": 11}, "fill": {"value": "#6b7280"}, "opacity": {"signal": "holeR > 30 ? 1 : 0"} } } } ] };; } ``` ### Three-level hierarchy The same chart with one more ring: category, subcategory, then item (for example, product line, product, then variant). Works best when the outer ring has a manageable number of slices; beyond a few dozen items the tooltips remain usable but the slices get thin. ```aml CustomChartDef sunburst_chart_three_level { label: 'Sunburst Chart (3 levels)' description: "A sunburst chart with three rings showing a category, subcategory, and item hierarchy as nested composition." fields { field category { type: 'dimension' label: 'Category (inner ring)' } field subcategory { type: 'dimension' label: 'Subcategory (middle ring)' } field item { type: 'dimension' label: 'Item (outer ring)' } field value { type: 'measure' label: 'Value' } } options { option color_scheme { type: 'select' label: 'Color scheme' options: ['tableau10', 'category10', 'accent', 'dark2', 'paired', 'set2'] default_value: 'tableau10' } option donut_hole { type: 'select' label: 'Center hole size' options: [0, 0.2, 0.35, 0.5] default_value: 0.35 } } template: @vg { "$schema": "https://vega.github.io/schema/vega/v5.json", "description": "Three-ring sunburst built from stacked angles.", "autosize": "none", "padding": 0, "signals": [ { "name": "width", "init": "containerSize()[0] - 5", "on": [{ "events": "window:resize", "update": "containerSize()[0] - 5" }] }, { "name": "height", "init": "containerSize()[1] - 5", "on": [{ "events": "window:resize", "update": "containerSize()[1] - 5" }] }, {"name": "radius", "update": "min(width, height) / 2"}, {"name": "holeR", "update": "radius * @{options.donut_hole.value}"}, {"name": "ring1End", "update": "holeR + (radius - holeR) * 0.38"}, {"name": "ring2End", "update": "holeR + (radius - holeR) * 0.7"}, {"name": "grandTotal", "update": "length(data('cats')) ? data('cats')[0].total : 0"}, { "name": "hovered", "value": null, "on": [ { "events": "@catArc:mouseover", "update": "{kind: 'category', category: datum.category, subcategory: null, label: datum.category, amount: datum.amount, share: datum.amount / datum.total}" }, { "events": "@subArc:mouseover", "update": "{kind: 'subcategory', category: datum.category, subcategory: datum.subcategory, label: datum.subcategory, amount: datum.amount, share: datum.amount / datum.total}" }, { "events": "@leafArc:mouseover", "update": "{kind: 'item', category: datum.category, subcategory: datum.subcategory, label: datum.item, amount: datum.amount, share: datum.amount / datum.total}" }, {"events": "@catArc:mouseout, @subArc:mouseout, @leafArc:mouseout", "update": "null"} ] } ], "data": [ { "name": "leaves", "values": @{values}, "transform": [ {"type": "formula", "expr": "datum['@{fields.category.name}']", "as": "category"}, {"type": "formula", "expr": "datum['@{fields.subcategory.name}']", "as": "subcategory"}, {"type": "formula", "expr": "datum['@{fields.item.name}']", "as": "item"}, {"type": "formula", "expr": "datum['@{fields.value.name}']", "as": "amount"}, {"type": "filter", "expr": "datum.amount != null && datum.amount > 0"}, { "type": "aggregate", "groupby": ["category", "subcategory", "item"], "fields": ["amount"], "ops": ["sum"], "as": ["amount"] }, {"type": "collect", "sort": {"field": ["category", "subcategory", "item"]}}, {"type": "stack", "field": "amount", "as": ["s0", "s1"]}, {"type": "joinaggregate", "fields": ["amount"], "ops": ["sum"], "as": ["total"]}, { "type": "joinaggregate", "fields": ["amount"], "ops": ["sum"], "as": ["sub_total"], "groupby": ["category", "subcategory"] }, {"type": "formula", "expr": "datum.s0 / datum.total * 2 * PI", "as": "a0"}, {"type": "formula", "expr": "datum.s1 / datum.total * 2 * PI", "as": "a1"} ] }, { "name": "subs", "source": "leaves", "transform": [ { "type": "aggregate", "groupby": ["category", "subcategory"], "fields": ["amount"], "ops": ["sum"], "as": ["amount"] }, {"type": "collect", "sort": {"field": ["category", "subcategory"]}}, {"type": "stack", "field": "amount", "as": ["s0", "s1"]}, {"type": "joinaggregate", "fields": ["amount"], "ops": ["sum"], "as": ["total"]}, { "type": "joinaggregate", "fields": ["amount"], "ops": ["sum"], "as": ["cat_total"], "groupby": ["category"] }, {"type": "formula", "expr": "datum.s0 / datum.total * 2 * PI", "as": "a0"}, {"type": "formula", "expr": "datum.s1 / datum.total * 2 * PI", "as": "a1"} ] }, { "name": "cats", "source": "leaves", "transform": [ { "type": "aggregate", "groupby": ["category"], "fields": ["amount"], "ops": ["sum"], "as": ["amount"] }, {"type": "collect", "sort": {"field": "category"}}, {"type": "stack", "field": "amount", "as": ["s0", "s1"]}, {"type": "joinaggregate", "fields": ["amount"], "ops": ["sum"], "as": ["total"]}, {"type": "formula", "expr": "datum.s0 / datum.total * 2 * PI", "as": "a0"}, {"type": "formula", "expr": "datum.s1 / datum.total * 2 * PI", "as": "a1"} ] } ], "scales": [ { "name": "color", "type": "ordinal", "domain": {"data": "cats", "field": "category"}, "range": {"scheme": @{options.color_scheme.value}} } ], "marks": [ { "type": "arc", "name": "catArc", "from": {"data": "cats"}, "encode": { "update": { "x": {"signal": "width / 2"}, "y": {"signal": "height / 2"}, "startAngle": {"field": "a0"}, "endAngle": {"field": "a1"}, "innerRadius": {"signal": "holeR"}, "outerRadius": {"signal": "ring1End"}, "fill": {"scale": "color", "field": "category"}, "fillOpacity": { "signal": "hovered === null || hovered.category === datum.category ? 1 : 0.3" }, "stroke": {"value": "white"}, "strokeWidth": {"value": 1.5}, "tooltip": { "signal": "{'Category': datum.category, 'Value': format(datum.amount, ','), 'Share of Total': format(datum.amount / datum.total, '.1%')}" } } } }, { "type": "arc", "name": "subArc", "from": {"data": "subs"}, "encode": { "update": { "x": {"signal": "width / 2"}, "y": {"signal": "height / 2"}, "startAngle": {"field": "a0"}, "endAngle": {"field": "a1"}, "innerRadius": {"signal": "ring1End + 1"}, "outerRadius": {"signal": "ring2End"}, "fill": {"scale": "color", "field": "category"}, "fillOpacity": { "signal": "hovered === null ? 0.85 : (hovered.category !== datum.category ? 0.2 : (hovered.subcategory === null ? 0.9 : (hovered.subcategory === datum.subcategory ? 1 : 0.5)))" }, "stroke": {"value": "white"}, "strokeWidth": {"value": 1.2}, "tooltip": { "signal": "{'Subcategory': datum.subcategory, 'Category': datum.category, 'Value': format(datum.amount, ','), 'Share of Total': format(datum.amount / datum.total, '.1%'), 'Share of Category': format(datum.amount / datum.cat_total, '.1%')}" } } } }, { "type": "arc", "name": "leafArc", "from": {"data": "leaves"}, "encode": { "update": { "x": {"signal": "width / 2"}, "y": {"signal": "height / 2"}, "startAngle": {"field": "a0"}, "endAngle": {"field": "a1"}, "innerRadius": {"signal": "ring2End + 1"}, "outerRadius": {"signal": "radius"}, "fill": {"scale": "color", "field": "category"}, "fillOpacity": { "signal": "hovered === null ? 0.65 : (hovered.category !== datum.category ? 0.15 : (hovered.kind === 'item' ? (hovered.label === datum.item ? 1 : 0.45) : (hovered.kind === 'subcategory' ? (hovered.subcategory === datum.subcategory ? 0.95 : 0.4) : 0.8)))" }, "stroke": {"value": "white"}, "strokeWidth": {"value": 1}, "tooltip": { "signal": "{'Item': datum.item, 'Subcategory': datum.subcategory, 'Category': datum.category, 'Value': format(datum.amount, ','), 'Share of Total': format(datum.amount / datum.total, '.1%'), 'Share of Subcategory': format(datum.amount / datum.sub_total, '.1%')}" } } } }, { "type": "text", "interactive": false, "encode": { "update": { "x": {"signal": "width / 2"}, "y": {"signal": "height / 2 - 14"}, "align": {"value": "center"}, "text": {"signal": "hovered === null ? 'Total' : hovered.label"}, "limit": {"signal": "holeR * 1.7"}, "fontSize": {"value": 13}, "fontWeight": {"value": 600}, "fill": {"value": "#374151"}, "opacity": {"signal": "holeR > 30 ? 1 : 0"} } } }, { "type": "text", "interactive": false, "encode": { "update": { "x": {"signal": "width / 2"}, "y": {"signal": "height / 2 + 8"}, "align": {"value": "center"}, "text": {"signal": "format(hovered === null ? grandTotal : hovered.amount, ',')"}, "fontSize": {"value": 16}, "fontWeight": {"value": 700}, "fill": {"value": "#111827"}, "opacity": {"signal": "holeR > 30 ? 1 : 0"} } } }, { "type": "text", "interactive": false, "encode": { "update": { "x": {"signal": "width / 2"}, "y": {"signal": "height / 2 + 28"}, "align": {"value": "center"}, "text": {"signal": "hovered === null ? '' : format(hovered.share, '.1%') + ' of total'"}, "fontSize": {"value": 11}, "fill": {"value": "#6b7280"}, "opacity": {"signal": "holeR > 30 ? 1 : 0"} } } } ] };; } ```