数据可视化避坑指南:为什么你的南丁格尔玫瑰图总是不协调?

南丁格尔玫瑰图,这个以“提灯女神”命名的数据可视化图表,以其优雅的放射状花瓣形态,在各类数据报告中频频亮相。它本应是数据故事中最具视觉冲击力的讲述者,能将枯燥的数字转化为直观的面积对比。然而,很多开发者,尤其是已经熟悉ECharts基础操作的中高级用户,在实际项目中却常常陷入一种困境:代码跑通了,图表也出来了,但怎么看都觉得别扭——要么是内圈被挤压得不成样子,要么是外圈的花瓣长得“张牙舞爪”,数据之间的比例关系在视觉上完全失真。这种不协调感,不仅让图表的专业性大打折扣,更可能误导决策者做出错误判断。

问题的根源,往往不在于你是否知道roseType: 'area'这个参数,而在于对玫瑰图底层视觉编码逻辑的深层理解不足。玫瑰图本质上是一种极坐标下的堆叠柱状图,它将每个数据项映射为一段具有相同角度、但半径(或面积)不同的扇形。这里的“映射规则”——即radius(半径)模式与area(面积)模式的选择,以及数据预处理、半径区间设置等细节——共同决定了最终图表的“协调感”。本文将从一个资深数据可视化工程师的视角,带你深入这些细节,拆解那些让玫瑰图“翻车”的隐形陷阱,并提供一套从数据到视觉的完整调优方案。

1. 核心症结:半径模式与面积模式的本质差异

很多教程会告诉你,在ECharts中绘制南丁格尔玫瑰图,只需在饼图系列中加入roseType参数。这没错,但危险也恰恰隐藏于此。roseType有两个可选值:'radius'和'area'。大多数人在初次使用时,可能并未深究这两者的区别,随便选一个能用就行。但正是这个选择,从根本上决定了你的图表是“科学”还是“艺术”(这里指失真的艺术)。

半径模式 (roseType: 'radius') 是“视觉失真”的重灾区。在此模式下,每个扇形的半径长度与数据值成正比。听上去很合理,对吧?但别忘了,扇形的面积公式是 面积 ∝ 半径²。这意味着,一个数值是另一个2倍的数据项,其半径是√2倍,但视觉上我们感知的面积却是4倍!这种平方放大的效应,会极度夸大数值之间的差异。

让我们用代码来直观感受一下这种失真。假设我们有三类产品A、B、C,其销售额分别为100、200、400。

option = {
  dataset: [{
    source: [
      ['Product', 'Sales'],
      ['A', 100],
      ['B', 200],
      ['C', 400]
    ]
  }],
  series: [{
    type: 'pie',
    radius: [0, '70%'],
    roseType: 'radius', // 半径模式
    datasetIndex: 0
  }]
};

运行这段代码,你会发现代表C产品的扇形(数值是A的4倍)在视觉上占据了压倒性的面积,几乎挤占了其他扇形的空间。这种呈现方式,对于需要精确对比数据大小的场景是极具误导性的。

面积模式 (roseType: 'area') 则是为了纠正这种失真而设计的。在此模式下,每个扇形的面积与数据值成正比。由于面积与半径的平方相关,ECharts内部会自动计算,使得半径与数据值的平方根成正比。这样一来,数值为4倍的数据项,其半径仅为2倍,面积才是4倍,这更符合人类的视觉感知习惯。

将上面代码中的roseType改为'area',再看效果。C产品的扇形依然最大,但A和B产品获得了更合理的展示空间,三者之间的视觉比例关系更接近真实的数值比例(100:200:400 ≈ 1:2:4)。

关键提示:除非你有意要制造强烈的视觉冲击来突出最大值(并且明确告知观众这种失真),否则在绝大多数需要客观对比数据的业务场景中,请务必使用 roseType: 'area'。这是让你的玫瑰图迈向协调的第一步,也是最关键的一步。

为了更清晰地对比,我们用一个表格总结两种模式的核心区别:

特性维度半径模式 (roseType: 'radius')面积模式 (roseType: 'area')
映射关系半径 r ∝ 数据值 value面积 S ∝ 数据值 value
视觉感知严重夸大差异(平方放大)相对均衡,接近真实比例
适用场景艺术化表达、极端强调最大值绝大多数数据分析、业务报告
协调性低,易导致内圈拥挤、外圈膨胀高,是制作协调玫瑰图的基础

2. 数据预处理:决定玫瑰图“骨架”的关键步骤

即使选对了面积模式,如果你的原始数据“先天不足”,生成的玫瑰图依然会显得头重脚轻或布局怪异。玫瑰图对数据分布有其内在要求,未经处理的数据直接灌入,好比用畸形的骨架去支撑华美的外衣。

首先,警惕极端值(Outliers)。这是破坏玫瑰图协调性的头号杀手。想象一下,你的数据集中大部分值在100-300之间,但突然有一个值是5000。在面积模式下,这个超大值对应的扇形半径会远大于其他扇形,导致两个糟糕的结果:1) 这个“巨无霸”扇形会占据画布中心到外缘的绝大部分半径区间;2) 其他所有正常值对应的扇形会被挤压在非常狭窄的内圈半径范围内,密密麻麻挤在一起,根本无法区分。

处理极端值没有银弹,需要根据业务逻辑判断:

  • 分组建模:如果这个超大值是一个合理的特殊类别(如“其他”或“汇总”),可以考虑将其单独作为一个系列,或者用其他图表类型(如条形图)辅助展示。
  • 数据转换:考虑对数据取对数(log scale)。这在数据跨度极大的场景(如用户收入分布、城市人口)中很常见。ECharts本身不直接支持在饼图中进行对数转换,但我们可以预处理数据。
// 原始数据
const rawData = [
  { name: '中小客户', value: 150 },
  { name: '重点客户', value: 850 },
  { name: '战略客户', value: 5000 }
];

// 对数转换预处理(以10为底)
const processedData = rawData.map(item => ({
  name: item.name,
  value: Math.log10(item.value + 1) // +1 防止对0取对数
}));

// 此时 processedData 中的值差异变小,更适合用玫瑰图展示比例关系
// 注意:需要在图表标题或提示框中说明数据经过了对数处理

其次,进行数据排序。一个杂乱无章、数据项随机排列的玫瑰图,就像一首音符错乱的乐曲,毫无美感与逻辑。将数据按照值的大小进行升序或降序排列,能让玫瑰图的花瓣呈现出有规律的、渐变的弧度,极大地提升可读性和美观度。

ECharts提供了两种排序方式:

  1. 数据源排序:在准备数据时,就使用数组的sort方法进行排序。这是最直接的方式。
  2. 使用ECharts Transform(ECharts 5+):这是一种更声明式、更强大的方法,尤其适合数据来自动态数据集时。
option = {
  dataset: [{
    source: [
      ['Product', 'Sales'],
      ['Cake', 123],
      ['Tofu', 235],
      ['Latte', 231],
      ['Milk Tea', 341] // 原始数据未排序
    ]
  }, {
    // 使用 transform 进行排序
    transform: {
      type: 'sort',
      config: { dimension: 'Sales', order: 'desc' } // 按Sales维度降序
    }
  }],
  series: [{
    type: 'pie',
    roseType: 'area',
    datasetIndex: 1 // 引用排序后的数据集
  }]
};

最后,考虑数据聚合。玫瑰图不适合展示过多分类(例如超过15个)。过多的花瓣会导致每个扇形的角度变得非常小,看起来像一根根尖刺,失去了“花瓣”的柔和感,同时也会让图例变得冗长。如果分类确实很多,可以考虑:

  • 将较小的、次要的类别合并为“其他”项。
  • 使用嵌套环形图,将大类放在内环(普通饼图),子类放在外环(玫瑰图),形成层次结构。这正是你提供的原始场景中“学院-专业”的展示方式。

3. 半径与布局:精细控制视觉空间的艺术

解决了数据和模式问题,我们来到了“微调”阶段。radius和center这两个配置项,就像雕刻家手中的刻刀,直接决定了玫瑰图在画布上的最终形态和比例。

radius 的设置是门学问。它通常是一个数组,如 ['10%', '60%'],分别代表内半径和外半径。很多人在这里踩坑:

  • 内半径过小或为0:当roseType启用时,即使内半径设为'0%',ECharts也不会真的从圆心开始绘制,而是会留出一个默认的最小空间,否则中心点会因扇形角度太小而显得杂乱。但如果你显式地设置一个过小的内半径(比如'1%'),对于数值差异大的数据集,小值对应的扇形可能会被压缩成一条几乎看不见的细线。
  • 外半径过大:将外半径设为'90%甚至'100%',试图填满整个容器。这会导致最长的花瓣尖端紧贴容器边缘,甚至被裁剪,视觉上非常压抑,也失去了留白的美感。

一个经验性的法则是:为玫瑰图预留足够的“呼吸空间”。外半径通常设置在容器宽高较小值的50%-70%之间。例如,对于一个600x400的容器,设置 radius: ['15%', '65%'] 通常会得到一个比较舒适的布局。

多系列嵌套时的半径规划(嵌套环形图)。这是高级用法,也是容易产生不协调感的重灾区。你的原始内容展示了学院与专业的嵌套。关键在于合理规划各系列之间的半径区间,避免重叠或间隙过大。

series: [
  { // 内环:学院汇总(普通饼图)
    type: 'pie',
    radius: ['0%', '30%'], // 占据最内圈0-30%的半径范围
    label: { position: 'inner' },
    data: [ /* 学院数据 */ ]
  },
  { // 外环:专业分布(南丁格尔玫瑰图)
    type: 'pie',
    radius: ['40%', '70%'], // 占据40%-70%的半径范围
    roseType: 'area',
    data: [ /* 专业数据 */ ]
  }
]

注意上面代码中两个radius数组的关系:内环的外半径是'30%',外环的内半径是'40%',中间留下了10%的空白间隙。这个间隙至关重要:

  1. 视觉分离:清晰地区分两个不同的数据层次。
  2. 避免干扰:防止内环的标签与外环的图形重叠。
  3. 美学需要:让图表更有层次感和设计感。

如果内外环之间没有间隙(例如内环['0%', '50%'],外环['50%', '80%']),它们会在半径50%处紧密相连,在视觉上会粘连成一个整体,难以区分。

center 的动态调整。当图表容器不是正方形,或者你需要为图例、标题留出更多空间时,调整圆心位置center就很有必要。center可以是像['50%', '50%']这样的百分比,也可以是[300, 200]这样的像素值。在多系列嵌套且数据量不同的情况下,你甚至可能需要微调不同系列的center,让它们在同一圆心对齐,这需要一些耐心和反复预览。

4. 视觉增强与交互:从“能用”到“好用”的进阶

一个协调的玫瑰图,不仅在于静态布局的合理,更在于动态交互的清晰。以下是一些提升专业度的细节技巧。

标签(Label)的智能放置。玫瑰图的扇形大小不一,将标签统一放在内部或外部都不合适。ECharts提供了丰富的标签布局策略:

  • position: 'outside':标签在外部,用引导线连接。适合扇形较大的情况,但花瓣多时引导线会像蜘蛛网一样杂乱。
  • position: 'inside':标签在内部。适合扇形足够大的情况,但小扇形里可能放不下文字。
  • position: 'center':标签在扇形中心。比较通用。
  • 使用富文本标签和自定义格式化:这是高级玩法。你可以根据扇形面积动态决定是否显示标签,或者显示简化后的文字。
label: {
  position: 'center',
  formatter: function(params) {
    // 如果数据占比太小,则不显示名称,只显示百分比
    if (params.percent < 5) {
      return `{b|${params.percent.toFixed(1)}%}`;
    }
    return `{a|${params.name}}\n{b|${params.value} (${params.percent.toFixed(1)}%)}`;
  },
  rich: {
    a: { fontSize: 12, lineHeight: 16 },
    b: { fontSize: 10, color: '#999' }
  }
}

颜色映射的讲究。不要使用默认的颜色数组,它可能在多个系列间重复,导致关联性混乱。对于嵌套环形图,内环(父类别)和外环(子类别)的颜色应该建立关联。

  • 方案一:同色系渐变。为每个内环类别分配一个主色,其对应的外环子类别使用该主色的不同明度或饱和度变体。
  • 方案二:使用ECharts的visualMap组件,将数据值映射到连续的颜色带上,这样颜色本身就携带了数据大小的信息。

高亮与选中状态的优化。在emphasis(高亮)状态下,默认的扇形放大效果在玫瑰图上可能因为半径变化而导致重叠。更好的做法是调整itemStyle的阴影和边框,而不是改变radius。

itemStyle: {
  emphasis: {
    shadowBlur: 10,
    shadowOffsetX: 0,
    shadowColor: 'rgba(0, 0, 0, 0.5)'
  }
}

利用 dataset 和 transform 实现数据驱动。对于中高级开发者,我强烈推荐拥抱ECharts 5引入的dataset和transform范式。它将数据与图表配置分离,使得数据过滤、排序、转换等操作可以声明式地完成,代码更清晰,也更容易实现动态数据更新。例如,你可以轻松地创建一个下拉框,让用户选择不同的年份,图表通过transform: { type: 'filter', config: { dimension: 'Year', '=': selectedYear } }自动过滤并重绘,而无需重写整个series数据。

5. 实战案例:调试一个“不协调”的玫瑰图

让我们模拟一个真实场景。假设我们要可视化公司各产品线在不同地区的销售额,数据差异较大,初始图表效果很差。

初始问题图表特征:

  • 使用了roseType: 'radius',导致明星产品线的扇形巨大无比。
  • 数据未排序,扇形排列杂乱。
  • 外半径设置为'85%',花瓣顶到边缘。
  • 多个小销售额地区在内圈挤成一团,无法识别。

分步调试过程:

  1. 切换模式:首先将roseType从'radius'改为'area'。立刻,图表整体的比例关系正常了许多,极端值的视觉压迫感降低。

  2. 数据排序与筛选:

    // 假设原始数据源存储在 dataset[0].source 中
    dataset: [{
      source: rawData // 原始数据
    }, {
      transform: [
        {
          type: 'filter',
          config: { dimension: 'Sales', '>': 100 } // 过滤掉销售额过小的项,归入“其他”
        },
        {
          type: 'sort',
          config: { dimension: 'Sales', order: 'desc' }
        }
      ]
    }],
    series: [{
      type: 'pie',
      roseType: 'area',
      datasetIndex: 1, // 引用处理后的数据
      radius: ['15%', '65%'], // 调整半径,留出空间
      // ... 其他配置
    }]
    
  3. 调整视觉参数:

    • 设置一个柔和的、按顺序变化的颜色主题。
    • 配置标签,对小扇形使用更紧凑的格式化方式。
    • 添加一个副标题,说明“销售额小于100的区域已合并显示”。
  4. 引入交互:添加一个toolbox组件,提供“切换为条形图”的魔法类型按钮。因为当用户真的需要精确比较所有数据大小时,条形图仍然是更佳选择。玫瑰图的价值在于展示整体构成和突出主要部分。

经过以上步骤,一个原本扭曲、难以解读的玫瑰图,被重塑为层次清晰、重点突出、视觉协调的有效数据可视化作品。记住,调试玫瑰图的过程,是一个在数据真实性、视觉感知和业务表达之间不断寻求平衡的过程。没有一劳永逸的配置,最好的图表永远是经过深思熟虑和反复调试后,最适配当前数据故事的那一个。下次当你的玫瑰图看起来不对劲时,别急着怀疑ECharts,不妨沿着“模式->数据->布局->视觉”这条路径系统性地排查一遍,你会发现,让图表“协调”起来的钥匙,一直就在你手中。

Logo

北京人形旗下天工造物具身智能开源社区,聚焦具身天工与慧思开物两大平台

更多推荐