Skip to content
On this page

更高维度的数据可视化图表


第 17 节 更高维度的数据可视化图表

我们在前面的 16 个章节中分别学习了如何使用 JavaScript 对各种类型的数据结构进行操作处理、通过结合不同的数据结构来进行简单的数据统计,以及各种数据可视化图表的创建使用。而在这一节内容正式开始之前,我们先来简单地复习和梳理一下数据统计的基本原理,以便我们在进入后面的章节时能够更好地理解实际问题。

我们在第 11 节中曾经学习过名义数据和计量数据的概念,而这些都是对于不同维度的数据进行定义。而数据分析最基本的前提条件便是找出所需要进行分析的数据维度,就比如一个商家销售记录中的订单价格、订单类型、下单时间都是各种不同的维度。

而多维数据(多个数据,两个及以上)的组合是数据分析的基础,因为在很多情况下只针对一维数据的分析(如平均值、方差等)是很难反应出实际情况也无法直接与目标问题相关联的。所以我们在进行实际的分析之前,首先要找出我们需要进行分析的维度组合,比如交易额与时间(计量数据与有序数据)、交易类型和订单金额(分类数据与计量数据)等等。这些二维数据我们在前面的数据图表学习中都有不同程度的学习和使用过,而在这一节中我们将学习比二维更高的多维数据可视化。

17.1 多维数据集

实际上我们在进行各种数据分析的工作时,所能遇到的数据集基本上都会是拥有多个维度的,比如我们在第 5 节中所使用的 crew 人员数据集,每一个人便有 4 个维度的数据。

但这个数据集并不适合用来演示本章节所需要学习的内容,所以我们还需要其他数据集来进行使用。

还记得我们这本小册的介绍页面中的这个数据图表吗?

这张图表是 ECharts 官方实例中的一个实例,光是在这张图表中就已经使用了 5 个不同的数据维度:

  1. X 轴:日期(时间)
  2. Y 轴:AQI 指数
  3. 散点颜色:城市(分类)
  4. 散点大小:PM2.5 指数
  5. 颜色明暗度:二氧化硫指数

而其实在实际的数据集中,除此以外还有 PM10 指数、一氧化碳、二氧化氮以及空气污染程度,足足 9 个维度的数据。且其中有 6 个维度的数据都是数值数据,所以这个数据集非常适合作为多维数据分析的用例。

17.1.1 组合并转换数据集

在 ECharts 的官方实例代码中,我们可以得到这个数据集。

const data = [
  ['北京',1,55,9,56,0.46,18,6,'良'],
  ['北京',2,25,11,21,0.65,34,9,'优'],
  ['北京',3,56,7,63,0.3,14,5,'良'],
  ['北京',4,33,7,29,0.33,16,6,'优'],
  ['北京',5,42,24,44,0.76,40,16,'优'],
  ['北京',6,82,58,90,1.77,68,33,'良'],
  ['北京',7,74,49,77,1.46,48,27,'良'],
  ['北京',8,78,55,80,1.29,59,29,'良'],
  ['北京',9,267,216,280,4.8,108,64,'重度污染'],
  ['北京',10,185,127,216,2.52,61,27,'中度污染'],
  ['北京',11,39,19,38,0.57,31,15,'优'],
  ['北京',12,41,11,40,0.43,21,7,'优'],
  ['北京',13,64,38,74,1.04,46,22,'良'],
  ['北京',14,108,79,120,1.7,75,41,'轻度污染'],
  ['北京',15,108,63,116,1.48,44,26,'轻度污染'],
  ['北京',16,33,6,29,0.34,13,5,'优'],
  ['北京',17,94,66,110,1.54,62,31,'良'],
  ['北京',18,186,142,192,3.88,93,79,'中度污染'],
  ['北京',19,57,31,54,0.96,32,14,'良'],
  ['北京',20,22,8,17,0.48,23,10,'优'],
  ['北京',21,39,15,36,0.61,29,13,'优'],
  ['北京',22,94,69,114,2.08,73,39,'良'],
  ['北京',23,99,73,110,2.43,76,48,'良'],
  ['北京',24,31,12,30,0.5,32,16,'优'],
  ['北京',25,42,27,43,1,53,22,'优'],
  ['北京',26,154,117,157,3.05,92,58,'中度污染'],
  ['北京',27,234,185,230,4.09,123,69,'重度污染'],
  ['北京',28,160,120,186,2.77,91,50,'中度污染'],
  ['北京',29,134,96,165,2.76,83,41,'轻度污染'],
  ['北京',30,52,24,60,1.03,50,21,'良'],
  ['北京',31,46,5,49,0.28,10,6,'优'],
  ['广州',1,26,37,27,1.163,27,13,'优'],
  ['广州',2,85,62,71,1.195,60,8,'良'],
  ['广州',3,78,38,74,1.363,37,7,'良'],
  ['广州',4,21,21,36,0.634,40,9,'优'],
  ['广州',5,41,42,46,0.915,81,13,'优'],
  ['广州',6,56,52,69,1.067,92,16,'良'],
  ['广州',7,64,30,28,0.924,51,2,'良'],
  ['广州',8,55,48,74,1.236,75,26,'良'],
  ['广州',9,76,85,113,1.237,114,27,'良'],
  ['广州',10,91,81,104,1.041,56,40,'良'],
  ['广州',11,84,39,60,0.964,25,11,'良'],
  ['广州',12,64,51,101,0.862,58,23,'良'],
  ['广州',13,70,69,120,1.198,65,36,'良'],
  ['广州',14,77,105,178,2.549,64,16,'良'],
  ['广州',15,109,68,87,0.996,74,29,'轻度污染'],
  ['广州',16,73,68,97,0.905,51,34,'良'],
  ['广州',17,54,27,47,0.592,53,12,'良'],
  ['广州',18,51,61,97,0.811,65,19,'良'],
  ['广州',19,91,71,121,1.374,43,18,'良'],
  ['广州',20,73,102,182,2.787,44,19,'良'],
  ['广州',21,73,50,76,0.717,31,20,'良'],
  ['广州',22,84,94,140,2.238,68,18,'良'],
  ['广州',23,93,77,104,1.165,53,7,'良'],
  ['广州',24,99,130,227,3.97,55,15,'良'],
  ['广州',25,146,84,139,1.094,40,17,'轻度污染'],
  ['广州',26,113,108,137,1.481,48,15,'轻度污染'],
  ['广州',27,81,48,62,1.619,26,3,'良'],
  ['广州',28,56,48,68,1.336,37,9,'良'],
  ['广州',29,82,92,174,3.29,0,13,'良'],
  ['广州',30,106,116,188,3.628,101,16,'轻度污染'],
  ['广州',31,118,50,0,1.383,76,11,'轻度污染'],
  ['上海',1,91,45,125,0.82,34,23,'良'],
  ['上海',2,65,27,78,0.86,45,29,'良'],
  ['上海',3,83,60,84,1.09,73,27,'良'],
  ['上海',4,109,81,121,1.28,68,51,'轻度污染'],
  ['上海',5,106,77,114,1.07,55,51,'轻度污染'],
  ['上海',6,109,81,121,1.28,68,51,'轻度污染'],
  ['上海',7,106,77,114,1.07,55,51,'轻度污染'],
  ['上海',8,89,65,78,0.86,51,26,'良'],
  ['上海',9,53,33,47,0.64,50,17,'良'],
  ['上海',10,80,55,80,1.01,75,24,'良'],
  ['上海',11,117,81,124,1.03,45,24,'轻度污染'],
  ['上海',12,99,71,142,1.1,62,42,'良'],
  ['上海',13,95,69,130,1.28,74,50,'良'],
  ['上海',14,116,87,131,1.47,84,40,'轻度污染'],
  ['上海',15,108,80,121,1.3,85,37,'轻度污染'],
  ['上海',16,134,83,167,1.16,57,43,'轻度污染'],
  ['上海',17,79,43,107,1.05,59,37,'良'],
  ['上海',18,71,46,89,0.86,64,25,'良'],
  ['上海',19,97,71,113,1.17,88,31,'良'],
  ['上海',20,84,57,91,0.85,55,31,'良'],
  ['上海',21,87,63,101,0.9,56,41,'良'],
  ['上海',22,104,77,119,1.09,73,48,'轻度污染'],
  ['上海',23,87,62,100,1,72,28,'良'],
  ['上海',24,168,128,172,1.49,97,56,'中度污染'],
  ['上海',25,65,45,51,0.74,39,17,'良'],
  ['上海',26,39,24,38,0.61,47,17,'优'],
  ['上海',27,39,24,39,0.59,50,19,'优'],
  ['上海',28,93,68,96,1.05,79,29,'良'],
  ['上海',29,188,143,197,1.66,99,51,'中度污染'],
  ['上海',30,174,131,174,1.55,108,50,'中度污染'],
  ['上海',31,187,143,201,1.39,89,53,'中度污染']
]

17.1.2 语义化代码

由于数据集中的每一行的承载方式都是一个数组而不是我们前面所习惯使用的对象字面量,所以为了能让我们接下来所编写的代码更具可读性,需要使用一个用于“翻译”维度信息的工具来帮助我们和这份代码的阅读者更好地进行理解。

我们可以通过配合 LoDash 库所提供的方法来实现一个用于描述实际维度与数组下标之间关系的字典或对象,标识为 d(维度,dimension)。

const d = _.mapValues(_.invert(
  [ 'city', 'date', 'aqi', 'pm25', 'pm10', 'co', 'no2', 'so2', 'pollution' ]
), parseInt)
//=> {
//   city: 0,
//   date: 1,
//   ...
// }

这样我们就可以比较好地获取到某一个维度的数组下标了。

data[0][d['aqi']] //=> 55

事实上在 ECharts 中可以直接把 [ 'city', 'date', ... ] 这个维度数据放在 dataset.source 的第一行来实现类似的效果。

另外由于在 ECharts 中分组的表现形式必须为多个数据系列(Series),而我们的数据集是混在一起的,所以我们可以通过再实现一个简单的小工具来完成数据集的分割。

function subDataset(dataset, property, targetValue) {
  return dataset.filter(function(row) {
    return row[property] === targetValue
  })
}

console.log(subDataset(data, 0, '广州'))
//=> [
//  [ '广州', 1, 26, 37, 27, 1.163, 27, 13, '优' ],
//  [ '广州', 2, 85, 62, 71, 1.195, 60, 8, '良' ],
//  [ '广州', 3, 78, 38, 74, 1.363, 37, 7, '良' ],
//  ...
// ]

17.2 ECharts 中的多维表现方式

在我们前面学习过的数据可视化图表中,绝大部分都是对一维或二维数据进行可视化的图表。而实际上我们在 ECharts 中可以通过各种不同的方式将各种组件组合在一维或二维图表上,以表达更高维度的信息。

就以我们上面的散点图作为例子,散点的颜色、大小、明暗程度都算是不同的维度,这些都是可以用来增加图表维度容量的组件,我们可以按需使用。

17.2.1 基础图表

在加入表示更高维度的组件之前,我们需要先将最基础的图表绘制出来。

这里我们选取数据集中的日期 date 以及 AQI 指数 aqi 作为直角坐标轴的二维数据。

const option = {
  dataset: [
    { source: subDataset(data, 0, '北京') },
    { source: subDataset(data, 0, '广州') },
    { source: subDataset(data, 0, '上海') }
  ],
  
  legend: {},
  xAxis: {
    type: 'category'
  },
  yAxis: {
    type: 'value'
  },
  
  series: [
    {
      name: '北京',
      datasetIndex: 0,
    
      type: 'scatter',
      encode: {
        x: d['date'],
        y: d['aqi']
      }
    },
    {
      name: '广州',
      datasetIndex: 1,
      
      type: 'scatter',
      encode: {
        x: d['date'],
        y: d['aqi']
      }
    },
    {
      name: '上海',
      datasetIndex: 2,
      
      type: 'scatter',
      encode: {
        x: d['date'],
        y: d['aqi']
      }
    }
  ]
}

可以不难地发现其实这个基础的图表中就已经包含了 3 个维度了,那么接下来就使用更多的数据组件将不同的数据维度表示出来。

17.2.2 添加组件

在 ECharts 中有一个非常强大的组件名为 visualMap,字面意思是“视觉映射组件”。它的作用是将维度数据中的数据与图表上的视觉元素进行映射,并依托 Web 应用的天然优势为可视化图表添加可操作特性,使得图表更具实用性。

我们在上一节中就曾经使用过该组件。

官方文档中说明 visualMap 支持绑定图表元素中的以下视觉元素:

  • symbol: 图元的图形类别。
  • symbolSize: 图元的大小。
  • color: 图元的颜色。
  • colorAlpha: 图元的颜色的透明度。
  • opacity: 图元以及其附属物(如文字标签)的透明度。
  • colorLightness: HSL 颜色的明暗度。
  • colorSaturation: HSL 颜色的饱和度。
  • colorHue: HSL 颜色的色调。

其中图元的类别、颜色(比如前面的按组分类)可以与离散型数据(如分类数据)相绑定,而其余的(包括颜色在内)可以与连续型数据(数值数据)相绑定。

作为例子我们先使用图元的大小和数据集中的 PM2.5 数据相绑定。

const option = {
  // ...
  
  visualMap: [
    {
      type: 'continuous',       // 定义为连续型数据
      text: [ 'PM2.5' ],
      dimension: d['pm25'],     // 绑定到 PM2.5 数据
      inRange: {                // 范围内的图元样式
        symbolSize: [ 10, 70 ]  // 图元大小范围
      },
      
      // 定义图例位置
      left: 'right',
      top: '10%'
    }
  ]
}

visualMap 还支持为范围内的内容设置可视区间,即在原本的范围内,图表的使用者还可以根据自己的需求自行调整所需要的区间大小。

const option = {
  // ...
  
  visualMap: [
    {
      // ...
      
      calculable: true  // 展示区间手柄
    }
  ]
}

17.2.3 多个 visualMap 组件

既然已经通过 visualMap 组件将第四个维度展示出来了,那么我们就趁热打铁把更多的维度展示在图表上吧,详细的使用方法可以自行查看 ECharts 的官方文档,这里不再赘述。

  • 图形类别 → 污染等级(pollution
  • 图元的颜色的透明度 → 二氧化硫浓度(so2
const option = {
  // ...
  
  visualMap: [
    // ...
    
    {
      type: 'piecewise',
      text: [ '污染等级' ],
      dimension: d['pollution'],
      pieces: [
        { value: '优', symbol: 'circle' },
        { value: '良', symbol: 'rect' },
        { value: '轻度污染', symbol: 'roundRect' },
        { value: '中度污染', symbol: 'triangle' },
        { value: '重度污染', symbol: 'diamond' },
      ],
      left: 'right'
    },
    {
      type: 'continuous',
      text: [ '二氧化硫浓度' ],
      dimension: d['so2'],
      min: 0,
      max: 50,
      inRange: {
        colorAlpha: [ 0.5, 1 ],
      },
      
      calculable: true,
      orient: 'horizontal',
      top: 'bottom'
    }
  ]
}

17.3 有趣的特殊维度——时间

时间是一个非常有趣的维度,因为时间维度在进行统计和可视化的过程中并没有一个标准的计量单位。比如在上面这个数据集中,时间的计量单位是天(day),总容量为 31。31 并不算是一个较大的基数,而若需要将时间维度的容量增大却有两种截然不同的方法:

  1. 计量单位不变,向单边或双边扩容,即扩大时间范围;
  2. 时间范围不变,缩小计量单位,即更细化维度颗粒度。

第一种方法不必作过多的说明,我们需要详细认识的是第二种方法。时间维度的计量单位有非常多:年、季、月、周、日、时、分、秒,更小的甚至还有毫秒、微秒。

一般来说,我们每向更细的方向修改一次时间计量单位,时间维度的总容量都会出现激增的情况。还记得我们在第 6 节中曾经学习和使用过的时间序列控制工具吗?通过不同的组合方式并加以统计方法,就可以运用到时间维度上并展示在数据图表上。

在 ECharts 中有一个非常厉害的组件叫 dataZoom,它的作用跟前面的 visualMap.calculable 有些类似。它可以在一个维度上设定一个范围值,并通过可自由操控的方式对数据的视野范围进行移动和缩放。

这个工具经常会被使用在时间维度上,因为它的使用方式和设计原理与时间维度实在是太相称了,话不多说我们直接看代码。

const option = {
  // ...
  
  dataZoom: [
    {
      type: 'slider' // 定义为独立的、可操作的滑动组件
    }
  ]
}

是的,就这么简单的几行代码就完成了对 dataZoom 的引入和配置。当图表上只有一个图表并且 X 坐标轴(当然也可以使用在 Y 坐标轴)唯一时,ECharts 内部会自动完成其配置。而更详细的配置方法可以自行参考官方文档

而若出现原本时间维度的总容量较大,而希望图表的初始展示范围只选取其中的一部分时,通过为 dataZoom 添加初始配置即可。

const option = {
  // ...
  
  dataZoom: [
    {
      type: 'slider',
      
      startValue: 15,
      endValue: 31
    }
  ]
}

小结

这一节中我们学习了如何充分利用 ECharts 所提供的各种工具来扩张我们在二维平面上数据图表的维度容量。同时我们也看到了 Web 为传统统计、数据可视化所带来的新变化,非常简单、直接、直观的可操作性使得数据可视化图表变得非常的生动和更加实用。

本节为本小册中,介绍关于 ECharts 使用的最后一节。从下一节开始,我们将正式进入开发 JavaScript 数据应用的环节,学习开发一个真正具有实际价值的数据应用。

习题

请自行收集身边的各种数据,并整理成多维度数据集。结合本节所学知识自行开发一个具有良好可用性的多维度数据图表。