子弹图
子弹图的简介
子弹图(Bullet Chart)是一种线性的图表类型,最初由数据可视化专家 Stephen Few 设计,旨在替代传统的仪表盘而提供更加紧凑和信息丰富的可视化形式。子弹图能够在有限的空间内同时展示实际值、目标值和表现区间,通过对比的方式清晰地显示指标的完成度和表现情况。
子弹图的设计理念是最大化数据墨水比(data-ink ratio),在最小的空间内展示最多的有用信息,特别适合用于仪表板和绩效监控场景。
英文名:Bullet Chart
其他名称:进度条图、目标比较图、弹丸图
子弹图的构成
基础子弹图
| 图表类型 | 子弹图 |
|---|---|
| 适合的数据 | 一个分类数据字段、一个连续数据字段(实际值)、一个目标值、可选的表现区间 |
| 功能 | 展示实际值与目标值的对比,评估表现等级 |
| 数据与图形的映射 | 分类数据字段映射到纵轴位置 连续数据字段映射到条形长度 目标值映射到标记线 表现区间映射到背景色带 |
| 适合的数据条数 | 单个或多个指标,建议不超过 10 个 |
子弹图的主要组成部分包括:
- 实际值条形:用短粗的深色条形表示,展示当前实际达到的数值
- 目标值标记:用垂直线或其他标记表示,代表需要达成的目标
- 表现区间:背景使用不同深浅的色带表示,通常分为差、良、优等区间
- 刻度轴:提供数值参考,帮助读者理解具体的数值大小
import { Chart } from '@antv/g2';
const chart = new Chart({
container: 'container',
theme: 'classic',
});
const data = [
{
title: '销售完成率',
ranges: 100,
measures: 80,
target: 85,
},
];
chart.options({
type: 'view',
coordinate: { transform: [{ type: 'transpose' }] },
children: [
{
type: 'interval',
data,
encode: { x: 'title', y: 'ranges', color: '#f0efff' },
style: { maxWidth: 30 },
axis: {
y: {
grid: true,
gridLineWidth: 2,
title: '完成率 (%)',
},
x: {
title: false,
},
},
},
{
type: 'interval',
data,
encode: { x: 'title', y: 'measures', color: '#5B8FF9' },
style: { maxWidth: 20 },
label: {
text: 'measures',
position: 'right',
textAlign: 'left',
dx: 5,
formatter: (d) => `${d}%`,
},
},
{
type: 'point',
data,
encode: {
x: 'title',
y: 'target',
shape: 'line',
color: '#3D76DD',
size: 8,
},
tooltip: {
title: false,
items: [
{ channel: 'y', name: '目标值', valueFormatter: (d) => `${d}%` },
],
},
},
],
});
chart.render();
子弹图的应用场景
适合的场景
场景 1:业绩指标监控
子弹图是展示业绩指标完成情况的理想工具,能够清晰地对比实际表现与目标要求。
import { Chart } from '@antv/g2';
const chart = new Chart({
container: 'container',
theme: 'classic',
});
const colors = {
ranges: ['#ffebee', '#fff3e0', '#e8f5e8'],
measures: '#1890ff',
target: '#ff4d4f',
};
// 转换数据结构
const transformedData = [
{ title: '项目进度', value: 40, level: '差' },
{ title: '项目进度', value: 30, level: '良' },
{ title: '项目进度', value: 30, level: '优' },
];
const data = [
{ title: '项目进度', value: 60, type: '实际进度' },
{ title: '项目进度', value: 80, type: '目标进度' },
];
chart.options({
type: 'view',
coordinate: { transform: [{ type: 'transpose' }] },
children: [
{
type: 'interval',
data: transformedData,
encode: { x: 'title', y: 'value', color: 'level' },
transform: [{ type: 'stackY' }],
scale: {
color: {
domain: ['差', '良', '优'],
range: colors.ranges,
},
},
style: { maxWidth: 30 },
},
{
type: 'interval',
data: {
value: data,
transform: [{ type: 'filter', callback: (d) => d.type === '实际进度' }],
},
encode: { x: 'title', y: 'value', color: colors.measures },
style: { maxWidth: 16 },
},
{
type: 'point',
data: {
value: data,
transform: [{ type: 'filter', callback: (d) => d.type === '目标进度' }],
},
encode: {
x: 'title',
y: 'value',
shape: 'line',
color: colors.target,
size: 8,
},
axis: { y: { grid: true, title: '进度 (%)' }, x: { title: false } },
},
],
});
chart.render();
场景 2:预算执行跟踪
子弹图能够有效地显示预算的执行情况,包括实际支出、预算目标和预警区间。
场景 3:资源利用率监控
通过子弹图可以直观地了解各类资源的使用情况,识别过度使用或利用不足的资源。
import { Chart } from '@antv/g2';
const chart = new Chart({
container: 'container',
theme: 'classic',
});
const resourceData = [
{
resource: 'CPU使用率',
ranges: 100,
measures: 65,
target: 80,
},
{
resource: '内存使用率',
ranges: 100,
measures: 45,
target: 70,
},
{
resource: '磁盘使用率',
ranges: 100,
measures: 88,
target: 85,
},
{
resource: '网络带宽',
ranges: 100,
measures: 72,
target: 75,
},
];
chart.options({
type: 'view',
coordinate: { transform: [{ type: 'transpose' }] },
children: [
{
type: 'interval',
data: resourceData,
encode: { x: 'resource', y: 'ranges', color: '#f5f5f5' },
style: { maxWidth: 30 },
},
{
type: 'interval',
data: resourceData,
encode: {
x: 'resource',
y: 'measures',
color: (d) => (d.measures > d.target ? '#ff7875' : '#52c41a'),
},
style: { maxWidth: 20 },
label: {
text: 'measures',
position: 'right',
textAlign: 'left',
dx: 5,
formatter: (d) => `${d}%`,
},
},
{
type: 'point',
data: resourceData,
encode: {
x: 'resource',
y: 'target',
shape: 'line',
color: '#1890ff',
size: 6,
},
axis: {
y: {
grid: true,
title: '使用率 (%)',
},
x: {
title: '系统资源',
},
},
},
],
});
chart.render();
不适合的场景
场景 1:时间趋势分析
子弹图主要展示某个时点的状态对比,不适合展示随时间变化的趋势,此时应使用折线图。
场景 2:部分与整体关系
如果需要展示各部分占整体的比例关系,饼图或环形图更为合适。
场景 3:数据量过大
当需要展示大量指标时,子弹图会导致视觉混乱,建议使用分组展示或其他图表类型。
子弹图的扩展
多维子弹图
通过分组显示多个相关指标的表现情况,便于横向对比。
import { Chart } from '@antv/g2';
const chart = new Chart({
container: 'container',
theme: 'classic',
});
const multiData = [
{
department: '销售部',
indicator: '销售额',
ranges: 100,
measures: 85,
target: 90,
},
{
department: '销售部',
indicator: '客户数',
ranges: 100,
measures: 92,
target: 85,
},
{
department: '市场部',
indicator: '品牌知名度',
ranges: 100,
measures: 78,
target: 80,
},
{
department: '市场部',
indicator: '营销投入ROI',
ranges: 100,
measures: 88,
target: 85,
},
];
chart.options({
type: 'view',
coordinate: { transform: [{ type: 'transpose' }] },
children: [
{
type: 'interval',
data: multiData,
encode: { x: 'indicator', y: 'ranges', color: '#f0f0f0' },
style: { maxWidth: 30 },
},
{
type: 'interval',
data: multiData,
encode: { x: 'indicator', y: 'measures', color: 'department' },
style: { maxWidth: 20 },
label: {
text: 'measures',
position: 'right',
textAlign: 'left',
dx: 5,
},
},
{
type: 'point',
data: multiData,
encode: {
x: 'indicator',
y: 'target',
shape: 'line',
color: '#666',
size: 6,
},
axis: {
y: {
grid: true,
title: '完成度',
},
x: {
title: '关键指标',
},
},
},
],
});
chart.render();
分层表现区间
通过不同颜色深浅的背景区间,提供更细致的表现评估标准。
import { Chart } from '@antv/g2';
const chart = new Chart({
container: 'container',
theme: 'classic',
});
const colors = {
ranges: ['#ffebee', '#fff3e0', '#e8f5e8'],
measures: '#1890ff',
target: '#ff4d4f',
};
// 转换数据结构
const transformedData = [
{ title: '项目进度', value: 40, level: '差' },
{ title: '项目进度', value: 30, level: '良' },
{ title: '项目进度', value: 30, level: '优' },
];
const data = [
{ title: '项目进度', value: 60, type: '实际进度' },
{ title: '项目进度', value: 80, type: '目标进度' },
];
chart.options({
type: 'view',
coordinate: { transform: [{ type: 'transpose' }] },
children: [
{
type: 'interval',
data: transformedData,
encode: { x: 'title', y: 'value', color: 'level' },
transform: [{ type: 'stackY' }],
scale: {
color: {
domain: ['差', '良', '优'],
range: colors.ranges,
},
},
style: { maxWidth: 30 },
},
{
type: 'interval',
data: {
value: data,
transform: [{ type: 'filter', callback: (d) => d.type === '实际进度' }],
},
encode: { x: 'title', y: 'value', color: colors.measures },
style: { maxWidth: 16 },
},
{
type: 'point',
data: {
value: data,
transform: [{ type: 'filter', callback: (d) => d.type === '目标进度' }],
},
encode: {
x: 'title',
y: 'value',
shape: 'line',
color: colors.target,
size: 8,
},
axis: { y: { grid: true, title: '进度 (%)' }, x: { title: false } },
},
],
});
chart.render();
垂直子弹图
在空间受限或需要特殊布局时,可以使用垂直方向的子弹图。
import { Chart } from '@antv/g2';
const chart = new Chart({
container: 'container',
theme: 'classic',
});
const verticalData = [
{
metric: 'Q1销售',
ranges: 100,
measures: 80,
target: 85,
},
{
metric: 'Q2销售',
ranges: 100,
measures: 92,
target: 88,
},
{
metric: 'Q3销售',
ranges: 100,
measures: 76,
target: 90,
},
];
chart.options({
type: 'view',
children: [
{
type: 'interval',
data: verticalData,
encode: { x: 'metric', y: 'ranges', color: '#f0f0f0' },
style: { maxWidth: 30 },
},
{
type: 'interval',
data: verticalData,
encode: { x: 'metric', y: 'measures', color: '#52c41a' },
style: { maxWidth: 20 },
label: {
text: 'measures',
position: 'top',
textAlign: 'center',
dy: -5,
},
},
{
type: 'point',
data: verticalData,
encode: {
x: 'metric',
y: 'target',
shape: 'line',
color: '#ff4d4f',
size: 6,
},
axis: {
y: {
grid: true,
title: '销售额 (万元)',
},
x: {
title: '季度',
},
},
},
],
});
chart.render();
子弹图与其他图表的对比
子弹图和条形图
- 子弹图在条形图的基础上增加了目标值和表现区间,提供更丰富的对比维度
- 条形图主要用于比较不同分类的数值大小,而子弹图侧重于实际值与目标值的对比
子弹图和仪表盘
- 子弹图比仪表盘更节省空间,能在相同空间内展示更多信息
- 仪表盘更直观地展示单个指标的状态,子弹图更适合多指标对比
子弹图和进度条
- 子弹图提供了更多的上下文信息,包括目标值和表现区间
- 进度条主要展示完成进度,子弹图能展示表现的优劣程度