SunburstChart 旭日图
多层径向树形分区图,从内圈到外圈代表父→子层级,扇形面积 ∝ 值。
何时使用
- 多层级占比:流量来源(渠道 → 平台)、成本归因(事业部 → 项目 → 任务)、文件系统大小(目录 → 子目录 → 文件)。
- 与
CfTreemap的差异:Treemap 是矩形 slice-and-dice,单层;Sunburst 是径向,支持任意深度。 - 与
CfDonutChart的差异:DonutChart 是单环;Sunburst 是多环嵌套。 - 不建议层级 > 4:外圈扇形过小、标签放不下,转用 Treemap 或 ScatterPlot 更可读。
基础用法
数据是一棵 SunburstNode 树。叶子节点提供 value,父节点的值=子节点求和。colorIndex 从内圈向外圈继承,外圈只能看不到时给 root 的孩子上色就够了。
背景 视口
<script setup lang="ts">
import { CfSunburstChart } from '@chufix-design/vue';
const root = {
name: '总流量',
children: [
{
name: '搜索',
colorIndex: 0,
children: [
{ name: 'Google', value: 420 },
{ name: 'Bing', value: 80 },
{ name: 'Baidu', value: 60 },
],
},
{
name: '社交',
colorIndex: 1,
children: [
{ name: 'Twitter', value: 180 },
{ name: 'LinkedIn', value: 90 },
{ name: 'Reddit', value: 60 },
],
},
{
name: '直访',
colorIndex: 2,
children: [
{ name: 'App', value: 240 },
{ name: 'Bookmark', value: 120 },
],
},
{
name: '引荐',
colorIndex: 3,
children: [
{ name: 'Blog', value: 70 },
{ name: 'Partner', value: 50 },
{ name: 'Other', value: 30 },
],
},
],
};
</script>
<template>
<CfSunburstChart :root="root" :size="320" />
</template> <script setup>
import { CfSunburstChart } from '@chufix-design/vue';
const root = {
name: '总流量',
children: [
{
name: '搜索',
colorIndex: 0,
children: [
{ name: 'Google', value: 420 },
{ name: 'Bing', value: 80 },
{ name: 'Baidu', value: 60 },
],
},
{
name: '社交',
colorIndex: 1,
children: [
{ name: 'Twitter', value: 180 },
{ name: 'LinkedIn', value: 90 },
{ name: 'Reddit', value: 60 },
],
},
{
name: '直访',
colorIndex: 2,
children: [
{ name: 'App', value: 240 },
{ name: 'Bookmark', value: 120 },
],
},
{
name: '引荐',
colorIndex: 3,
children: [
{ name: 'Blog', value: 70 },
{ name: 'Partner', value: 50 },
{ name: 'Other', value: 30 },
],
},
],
};
</script>
<template>
<CfSunburstChart :root="root" :size="320" />
</template> import { CfSunburstChart } from '@chufix-design/react';
export default function Demo() {
const root = {
name: '总流量',
children: [
{
name: '搜索',
colorIndex: 0,
children: [
{ name: 'Google', value: 420 },
{ name: 'Bing', value: 80 },
{ name: 'Baidu', value: 60 },
],
},
{
name: '社交',
colorIndex: 1,
children: [
{ name: 'Twitter', value: 180 },
{ name: 'LinkedIn', value: 90 },
{ name: 'Reddit', value: 60 },
],
},
{
name: '直访',
colorIndex: 2,
children: [
{ name: 'App', value: 240 },
{ name: 'Bookmark', value: 120 },
],
},
{
name: '引荐',
colorIndex: 3,
children: [
{ name: 'Blog', value: 70 },
{ name: 'Partner', value: 50 },
{ name: 'Other', value: 30 },
],
},
],
};
return (
<>
<CfSunburstChart root={root} size={320} />
</>
);
} import { CfSunburstChart } from '@chufix-design/react';
export default function Demo() {
const root = {
name: '总流量',
children: [
{
name: '搜索',
colorIndex: 0,
children: [
{ name: 'Google', value: 420 },
{ name: 'Bing', value: 80 },
{ name: 'Baidu', value: 60 },
],
},
{
name: '社交',
colorIndex: 1,
children: [
{ name: 'Twitter', value: 180 },
{ name: 'LinkedIn', value: 90 },
{ name: 'Reddit', value: 60 },
],
},
{
name: '直访',
colorIndex: 2,
children: [
{ name: 'App', value: 240 },
{ name: 'Bookmark', value: 120 },
],
},
{
name: '引荐',
colorIndex: 3,
children: [
{ name: 'Blog', value: 70 },
{ name: 'Partner', value: 50 },
{ name: 'Other', value: 30 },
],
},
],
};
return (
<>
<CfSunburstChart root={root} size={320} />
</>
);
} 钻取 / drill-down
drillable 默认为 true。点击任意有子节点的扇形 → 该子树成为新根重新渲染;面包屑显示当前路径,中心圆形按钮(↑)或顶部面包屑都能回到上层。监听 @drill 拿到当前焦点节点 + 完整路径。
焦点切换时整层做一次淡出 + 微缩动画(200ms),新焦点淡入 + 放大,营造”逐层钻取”的视觉过渡。prefers-reduced-motion: reduce 时动效自动关闭。
背景 视口
点击外环任意分类 → 该子树成为新焦点;中心 ↑ 或顶部面包屑回到上层。
焦点路径 (顶层)
<script setup lang="ts">
import { ref } from 'vue';
import { CfSunburstChart, CfTag } from '@chufix-design/vue';
const root = {
name: '全公司',
children: [
{
name: '产品',
colorIndex: 0,
children: [
{ name: '设计', value: 12 },
{ name: '前端', value: 18 },
{ name: '后端', value: 22 },
{ name: '移动端', value: 8 },
],
},
{
name: '增长',
colorIndex: 1,
children: [
{ name: '增长黑客', value: 6 },
{ name: '内容', value: 10 },
{ name: 'BD', value: 8 },
],
},
{
name: '后台',
colorIndex: 2,
children: [
{ name: 'HR', value: 4 },
{ name: '财务', value: 5 },
{ name: '法务', value: 3 },
{ name: '行政', value: 4 },
],
},
],
};
const path = ref<string[]>([]);
</script>
<template>
<p style="margin: 0 0 8px; color: var(--fg-3); font-size: 12px;">
点击外环任意分类 → 该子树成为新焦点;中心 ↑ 或顶部面包屑回到上层。
</p>
<CfSunburstChart
:root="root"
:size="340"
@drill="(p: { pathNames: string[] }) => path = p.pathNames"
/>
<p style="margin-top: 8px; font-size: 12px;">
<CfTag tone="info" size="sm">焦点路径</CfTag>
{{ path.length ? path.join(' / ') : '(顶层)' }}
</p>
</template> <script setup>
import { ref } from 'vue';
import { CfSunburstChart, CfTag } from '@chufix-design/vue';
const root = {
name: '全公司',
children: [
{
name: '产品',
colorIndex: 0,
children: [
{ name: '设计', value: 12 },
{ name: '前端', value: 18 },
{ name: '后端', value: 22 },
{ name: '移动端', value: 8 },
],
},
{
name: '增长',
colorIndex: 1,
children: [
{ name: '增长黑客', value: 6 },
{ name: '内容', value: 10 },
{ name: 'BD', value: 8 },
],
},
{
name: '后台',
colorIndex: 2,
children: [
{ name: 'HR', value: 4 },
{ name: '财务', value: 5 },
{ name: '法务', value: 3 },
{ name: '行政', value: 4 },
],
},
],
};
const path = ref<string[]>([]);
</script>
<template>
<p style="margin: 0 0 8px; color: var(--fg-3); font-size: 12px;">
点击外环任意分类 → 该子树成为新焦点;中心 ↑ 或顶部面包屑回到上层。
</p>
<CfSunburstChart
:root="root"
:size="340"
@drill="(p: { pathNames: string[] }) => path = p.pathNames"
/>
<p style="margin-top: 8px; font-size: 12px;">
<CfTag tone="info" size="sm">焦点路径</CfTag>
{{ path.length ? path.join(' / ') : '(顶层)' }}
</p>
</template> import { useState } from 'react';
import { CfSunburstChart, CfTag } from '@chufix-design/react';
export default function Demo() {
const root = {
name: '全公司',
children: [
{
name: '产品',
colorIndex: 0,
children: [
{ name: '设计', value: 12 },
{ name: '前端', value: 18 },
{ name: '后端', value: 22 },
{ name: '移动端', value: 8 },
],
},
{
name: '增长',
colorIndex: 1,
children: [
{ name: '增长黑客', value: 6 },
{ name: '内容', value: 10 },
{ name: 'BD', value: 8 },
],
},
{
name: '后台',
colorIndex: 2,
children: [
{ name: 'HR', value: 4 },
{ name: '财务', value: 5 },
{ name: '法务', value: 3 },
{ name: '行政', value: 4 },
],
},
],
};
const [path, setPath] = useState<string[]>([]);
return (
<>
<p style={{ margin: "0 0 8px", color: "var(--fg-3)", fontSize: 12 }}>
点击外环任意分类 → 该子树成为新焦点;中心 ↑ 或顶部面包屑回到上层。
</p>
<CfSunburstChart root={root} size={340} onDrill={(p: { pathNames: string[] }) => setPath(p.pathNames)}
/>
<p style={{ marginTop: 8, fontSize: 12 }}>
<CfTag tone="info" size="sm">焦点路径</CfTag>
{path.length ? path.join(' / ') : '(顶层)'}
</p>
</>
);
} import { useState } from 'react';
import { CfSunburstChart, CfTag } from '@chufix-design/react';
export default function Demo() {
const root = {
name: '全公司',
children: [
{
name: '产品',
colorIndex: 0,
children: [
{ name: '设计', value: 12 },
{ name: '前端', value: 18 },
{ name: '后端', value: 22 },
{ name: '移动端', value: 8 },
],
},
{
name: '增长',
colorIndex: 1,
children: [
{ name: '增长黑客', value: 6 },
{ name: '内容', value: 10 },
{ name: 'BD', value: 8 },
],
},
{
name: '后台',
colorIndex: 2,
children: [
{ name: 'HR', value: 4 },
{ name: '财务', value: 5 },
{ name: '法务', value: 3 },
{ name: '行政', value: 4 },
],
},
],
};
const [path, setPath] = useState<string[]>([]);
return (
<>
<p style={{ margin: "0 0 8px", color: "var(--fg-3)", fontSize: 12 }}>
点击外环任意分类 → 该子树成为新焦点;中心 ↑ 或顶部面包屑回到上层。
</p>
<CfSunburstChart root={root} size={340} onDrill={(p: { pathNames: string[] }) => setPath(p.pathNames)}
/>
<p style={{ marginTop: 8, fontSize: 12 }}>
<CfTag tone="info" size="sm">焦点路径</CfTag>
{path.length ? path.join(' / ') : '(顶层)'}
</p>
</>
);
} API
| 属性 | 类型 | 默认值 | 说明 |
|---|---|---|---|
root | SunburstNode | — | 根节点(其本身不渲染,孩子构成第 1 环) |
size | number | 240 | 直径 |
innerRadiusRatio | 0..1 | 0.2 | 中心留白半径占总半径的比例 |
showLabels | boolean | true | 大扇形上画标签 |
labelMinAngle | number | 12 | 小于该角度(度)的扇形不画标签 |
drillable | boolean | true | 点击有子节点的扇形钻取到该子树 |
showBreadcrumb | boolean | true | 钻取时显示顶部面包屑(受控钻取也用得到) |
ariaLabel | string | '旭日图' |
Events
| Vue 事件 | React 回调 | 载荷 | 说明 |
|---|---|---|---|
item-enter | onItemEnter | SunburstChartInteractionPayload | 鼠标进入某扇形 |
item-leave | onItemLeave | SunburstChartInteractionPayload | 鼠标离开 |
drill | onDrill | SunburstDrillPayload | 钻取后新焦点 + 路径 |
类型
interface SunburstNode {
name: string;
/** 叶子节点必填;父节点会从孩子求和。 */
value?: number;
/** 0..7,对应 --viz-1..8。父节点的色相会被孩子继承。 */
colorIndex?: number;
children?: SunburstNode[];
}
interface SunburstChartInteractionPayload {
node: SunburstNode;
/** 1 = 第一环;2 = 第二环;以此类推。 */
depth: number;
/** 从 root 到该节点的 name 路径(含 root.name)。 */
pathNames: string[];
/** node 及其后代的累计值。 */
totalValue: number;
nativeEvent?: PointerEvent;
}
反馈与讨论
SunburstChart 旭日图 的讨论