Treemap 矩形树图
层级矩形面积图,支持任意深度嵌套 + 点击下钻聚焦。
基础用法
数据通过 props 传入,纯 SVG 渲染,无第三方图表库依赖。
配色取自 --viz-1..8 token,色盲友好。
背景 视口
<script setup lang="ts">
import { CfTreemap } from '@chufix-design/vue';
const nodes = [
{ name: 'react-dom', value: 1200 },
{ name: 'react', value: 720 },
{ name: 'lodash', value: 540 },
{ name: 'moment', value: 480 },
{ name: 'rxjs', value: 380 },
{ name: 'd3', value: 320 },
{ name: 'three', value: 280 },
{ name: 'others', value: 200 },
];
</script>
<template>
<CfTreemap :nodes="nodes" />
</template> <script setup>
import { CfTreemap } from '@chufix-design/vue';
const nodes = [
{ name: 'react-dom', value: 1200 },
{ name: 'react', value: 720 },
{ name: 'lodash', value: 540 },
{ name: 'moment', value: 480 },
{ name: 'rxjs', value: 380 },
{ name: 'd3', value: 320 },
{ name: 'three', value: 280 },
{ name: 'others', value: 200 },
];
</script>
<template>
<CfTreemap :nodes="nodes" />
</template> import { CfTreemap } from '@chufix-design/react';
export default function Demo() {
const nodes = [
{ name: 'react-dom', value: 1200 },
{ name: 'react', value: 720 },
{ name: 'lodash', value: 540 },
{ name: 'moment', value: 480 },
{ name: 'rxjs', value: 380 },
{ name: 'd3', value: 320 },
{ name: 'three', value: 280 },
{ name: 'others', value: 200 },
];
return (
<>
<CfTreemap nodes={nodes} />
</>
);
} import { CfTreemap } from '@chufix-design/react';
export default function Demo() {
const nodes = [
{ name: 'react-dom', value: 1200 },
{ name: 'react', value: 720 },
{ name: 'lodash', value: 540 },
{ name: 'moment', value: 480 },
{ name: 'rxjs', value: 380 },
{ name: 'd3', value: 320 },
{ name: 'three', value: 280 },
{ name: 'others', value: 200 },
];
return (
<>
<CfTreemap nodes={nodes} />
</>
);
} label 显隐
showLabels=false 时只展示色块,适合微缩组合。仅当矩形足够大(w>50, h>20)时才显示文字。
背景 视口
5 个节点(label 显示)
label 隐藏
<script setup lang="ts">
import { CfTreemap } from '@chufix-design/vue';
const small = [
{ name: '依赖 A', value: 280 },
{ name: '依赖 B', value: 220 },
{ name: '依赖 C', value: 180 },
{ name: '依赖 D', value: 120 },
{ name: '其他', value: 80 },
];
</script>
<template>
<div style="display: grid; grid-template-columns: 1fr 1fr; gap: 16px;">
<div>
<div style="font-size: 11px; color: var(--fg-3); margin-bottom: 4px;">5 个节点(label 显示)</div>
<CfTreemap :nodes="small" :height="180" />
</div>
<div>
<div style="font-size: 11px; color: var(--fg-3); margin-bottom: 4px;">label 隐藏</div>
<CfTreemap :nodes="small" :height="180" :show-labels="false" />
</div>
</div>
</template> <script setup>
import { CfTreemap } from '@chufix-design/vue';
const small = [
{ name: '依赖 A', value: 280 },
{ name: '依赖 B', value: 220 },
{ name: '依赖 C', value: 180 },
{ name: '依赖 D', value: 120 },
{ name: '其他', value: 80 },
];
</script>
<template>
<div style="display: grid; grid-template-columns: 1fr 1fr; gap: 16px;">
<div>
<div style="font-size: 11px; color: var(--fg-3); margin-bottom: 4px;">5 个节点(label 显示)</div>
<CfTreemap :nodes="small" :height="180" />
</div>
<div>
<div style="font-size: 11px; color: var(--fg-3); margin-bottom: 4px;">label 隐藏</div>
<CfTreemap :nodes="small" :height="180" :show-labels="false" />
</div>
</div>
</template> import { CfTreemap } from '@chufix-design/react';
export default function Demo() {
const small = [
{ name: '依赖 A', value: 280 },
{ name: '依赖 B', value: 220 },
{ name: '依赖 C', value: 180 },
{ name: '依赖 D', value: 120 },
{ name: '其他', value: 80 },
];
return (
<>
<div style={{ display: "grid", gridTemplateColumns: "1fr 1fr", gap: 16 }}>
<div>
<div style={{ fontSize: 11, color: "var(--fg-3)", marginBottom: 4 }}>5 个节点(label 显示)</div>
<CfTreemap nodes={small} height={180} />
</div>
<div>
<div style={{ fontSize: 11, color: "var(--fg-3)", marginBottom: 4 }}>label 隐藏</div>
<CfTreemap nodes={small} height={180} showLabels={false} />
</div>
</div>
</>
);
} import { CfTreemap } from '@chufix-design/react';
export default function Demo() {
const small = [
{ name: '依赖 A', value: 280 },
{ name: '依赖 B', value: 220 },
{ name: '依赖 C', value: 180 },
{ name: '依赖 D', value: 120 },
{ name: '其他', value: 80 },
];
return (
<>
<div style={{ display: "grid", gridTemplateColumns: "1fr 1fr", gap: 16 }}>
<div>
<div style={{ fontSize: 11, color: "var(--fg-3)", marginBottom: 4 }}>5 个节点(label 显示)</div>
<CfTreemap nodes={small} height={180} />
</div>
<div>
<div style={{ fontSize: 11, color: "var(--fg-3)", marginBottom: 4 }}>label 隐藏</div>
<CfTreemap nodes={small} height={180} showLabels={false} />
</div>
</div>
</>
);
} 嵌套层级 + 下钻
给任意节点加 children,组件会递归排版,父矩形顶部留 headerHeight 像素当作分组标题。点击有子节点的顶层矩形 → 该子树成为新焦点;面包屑、↑ 上一层、底部 footer 都跟着切换。drillable={false} 即关掉钻取行为退回静态视图。
默认 layout="squarify" 用经典的 Squarified Treemap 算法 (Bruls et al. 2000),贪心地让每个矩形的长宽比接近 1,长宽悬殊的数据集也能保持可读;需要传统纵横交替切分时传 layout="slice-and-dice" 退回旧行为。
背景 视口
顶层方块直接展示子分类的嵌套排版;单击有子项的方块即可下钻聚焦到该子树。
焦点 全部
<script setup lang="ts">
import { ref } from 'vue';
import { CfTag, CfTreemap } from '@chufix-design/vue';
const nodes = [
{
name: '亚太',
children: [
{ name: '中国', value: 4280 },
{ name: '日本', value: 1820 },
{ name: '东南亚', value: 1240 },
{ name: '澳洲', value: 760 },
],
},
{
name: '欧洲',
children: [
{ name: '英国', value: 1420 },
{ name: '德国', value: 1380 },
{ name: '法国', value: 980 },
{ name: '北欧', value: 820 },
],
},
{
name: '北美',
children: [
{ name: '美国', value: 3920 },
{ name: '加拿大', value: 1080 },
],
},
{
name: '其他',
children: [
{ name: '南美', value: 620 },
{ name: '中东', value: 540 },
{ name: '非洲', value: 380 },
],
},
];
const focus = ref<string[]>([]);
</script>
<template>
<p style="margin: 0 0 8px; color: var(--fg-3); font-size: 12px;">
顶层方块直接展示子分类的嵌套排版;单击有子项的方块即可下钻聚焦到该子树。
</p>
<CfTreemap
:nodes="nodes"
:width="640"
:height="320"
@drill="(p: { pathNames: string[] }) => focus = p.pathNames"
/>
<p style="margin-top: 8px; font-size: 12px;">
<CfTag tone="info" size="sm">焦点</CfTag>
{{ focus.length ? focus.join(' / ') : '全部' }}
</p>
</template> <script setup>
import { ref } from 'vue';
import { CfTag, CfTreemap } from '@chufix-design/vue';
const nodes = [
{
name: '亚太',
children: [
{ name: '中国', value: 4280 },
{ name: '日本', value: 1820 },
{ name: '东南亚', value: 1240 },
{ name: '澳洲', value: 760 },
],
},
{
name: '欧洲',
children: [
{ name: '英国', value: 1420 },
{ name: '德国', value: 1380 },
{ name: '法国', value: 980 },
{ name: '北欧', value: 820 },
],
},
{
name: '北美',
children: [
{ name: '美国', value: 3920 },
{ name: '加拿大', value: 1080 },
],
},
{
name: '其他',
children: [
{ name: '南美', value: 620 },
{ name: '中东', value: 540 },
{ name: '非洲', value: 380 },
],
},
];
const focus = ref<string[]>([]);
</script>
<template>
<p style="margin: 0 0 8px; color: var(--fg-3); font-size: 12px;">
顶层方块直接展示子分类的嵌套排版;单击有子项的方块即可下钻聚焦到该子树。
</p>
<CfTreemap
:nodes="nodes"
:width="640"
:height="320"
@drill="(p: { pathNames: string[] }) => focus = p.pathNames"
/>
<p style="margin-top: 8px; font-size: 12px;">
<CfTag tone="info" size="sm">焦点</CfTag>
{{ focus.length ? focus.join(' / ') : '全部' }}
</p>
</template> import { useState } from 'react';
import { CfTag, CfTreemap } from '@chufix-design/react';
export default function Demo() {
const nodes = [
{
name: '亚太',
children: [
{ name: '中国', value: 4280 },
{ name: '日本', value: 1820 },
{ name: '东南亚', value: 1240 },
{ name: '澳洲', value: 760 },
],
},
{
name: '欧洲',
children: [
{ name: '英国', value: 1420 },
{ name: '德国', value: 1380 },
{ name: '法国', value: 980 },
{ name: '北欧', value: 820 },
],
},
{
name: '北美',
children: [
{ name: '美国', value: 3920 },
{ name: '加拿大', value: 1080 },
],
},
{
name: '其他',
children: [
{ name: '南美', value: 620 },
{ name: '中东', value: 540 },
{ name: '非洲', value: 380 },
],
},
];
const [focus, setFocus] = useState<string[]>([]);
return (
<>
<p style={{ margin: "0 0 8px", color: "var(--fg-3)", fontSize: 12 }}>
顶层方块直接展示子分类的嵌套排版;单击有子项的方块即可下钻聚焦到该子树。
</p>
<CfTreemap nodes={nodes} width={640} height={320} onDrill={(p: { pathNames: string[] }) => setFocus(p.pathNames)}
/>
<p style={{ marginTop: 8, fontSize: 12 }}>
<CfTag tone="info" size="sm">焦点</CfTag>
{focus.length ? focus.join(' / ') : '全部'}
</p>
</>
);
} import { useState } from 'react';
import { CfTag, CfTreemap } from '@chufix-design/react';
export default function Demo() {
const nodes = [
{
name: '亚太',
children: [
{ name: '中国', value: 4280 },
{ name: '日本', value: 1820 },
{ name: '东南亚', value: 1240 },
{ name: '澳洲', value: 760 },
],
},
{
name: '欧洲',
children: [
{ name: '英国', value: 1420 },
{ name: '德国', value: 1380 },
{ name: '法国', value: 980 },
{ name: '北欧', value: 820 },
],
},
{
name: '北美',
children: [
{ name: '美国', value: 3920 },
{ name: '加拿大', value: 1080 },
],
},
{
name: '其他',
children: [
{ name: '南美', value: 620 },
{ name: '中东', value: 540 },
{ name: '非洲', value: 380 },
],
},
];
const [focus, setFocus] = useState<string[]>([]);
return (
<>
<p style={{ margin: "0 0 8px", color: "var(--fg-3)", fontSize: 12 }}>
顶层方块直接展示子分类的嵌套排版;单击有子项的方块即可下钻聚焦到该子树。
</p>
<CfTreemap nodes={nodes} width={640} height={320} onDrill={(p: { pathNames: string[] }) => setFocus(p.pathNames)}
/>
<p style={{ marginTop: 8, fontSize: 12 }}>
<CfTag tone="info" size="sm">焦点</CfTag>
{focus.length ? focus.join(' / ') : '全部'}
</p>
</>
);
} API
| 属性 | 类型 | 默认值 | 说明 |
|---|---|---|---|
nodes | TreemapNode[] | — | { name, value?, colorIndex?, children? }[] |
width | number | 480 | SVG 宽度 |
height | number | 240 | SVG 高度 |
showLabels | boolean | true | 在矩形内显示 name / value(足够大时) |
childPadding | number | 4 | 父矩形内子节点四周留白 |
headerHeight | number | 16 | 父矩形顶部留给标题的高度 |
drillable | boolean | true | 点击有子节点的顶层矩形钻取 |
showBreadcrumb | boolean | true | 钻取时显示面包屑 + ↑上一层按钮 |
layout | 'squarify' | 'slice-and-dice' | 'squarify' | 排版算法,默认 squarify 优化长宽比 |
ariaLabel | string | — | 透传给根 <svg> 的 aria-label |
Events
| Vue 事件 | React 回调 | 载荷类型 | 说明 |
|---|---|---|---|
item-enter | onItemEnter | TreemapInteractionPayload | 鼠标进入某矩形时触发 |
item-leave | onItemLeave | TreemapInteractionPayload | 鼠标离开矩形时触发 |
drill | onDrill | TreemapDrillPayload | 钻取后的新焦点 + 完整路径 |
类型
interface TreemapNode {
name: string;
value?: number;
colorIndex?: number;
/** 嵌套子节点;父节点的值会被自动计算为子节点之和。 */
children?: TreemapNode[];
}
interface TreemapInteractionPayload {
node: TreemapNode;
/** 当前焦点的直接子节点中的索引。 */
dataIndex: number;
/** 0 = 顶层矩形;1+ = 嵌套子矩形。 */
depth: number;
/** 从原始 root 到该节点的 name 路径。 */
pathNames: string[];
nativeEvent?: PointerEvent;
}
interface TreemapDrillPayload {
/** 当前焦点,回到顶层时为 null。 */
node: TreemapNode | null;
pathNames: string[];
}
反馈与讨论
Treemap 矩形树图 的讨论