简单来讲,使用点或者小圆形来对数据进行可视化编码的图都可以称之为点图。到具体的应用场景,点图被抽象出了很多图表类型,这些图表的使用也常常让人迷惑。本文将具体的讲解点图的概念和使用。
本文你将学习到:
什么是点图?
点图的使用场景有哪些?
点图的优缺点有哪些?
如何创建点图?
-
常见点图
点图的常见应用领域和直方图有些类似,都是用来表达数据分布,点图在视觉上通过点的聚集形式来表现,而不是通过直接的频率数值。点图中的点和数据项是一一对应的关系。下面我们看几种常见的点图。
1.1 克利夫兰点图(Cleveland dot plot)
克利夫兰点图通常用来展示分类数据,展现形式上,数值一般分布在横轴上。我们可以通过横向柱形图来理解克利夫兰点图。
首先我们基于ChartSpace 来创建一个条形图,输入如下spec-1:
{
"type": "horizontalBar",
"data": [
{
"name": "data1",
"values": [
{
"x": "折线图",
"y": 82
},
{
"x": "条形图",
"y": 50
},
{
"x": "饼图",
"y": 64
},
{
"x": "地图",
"y": 30
},
{
"x": "雷达图",
"y": 40
}
]
}
],
"xField": "y",
"yField": "x",
"bandPadding": 0.3
}
spec-1
效果如下:
下面我们每一条柱子的末端添加一个圆点。
在上面的spec的最外层添加如spec-2配置:
"extensionMarks": [
{
"name": "dot",
"type": "symbol",
"shape": "circle",
"from": { "mark": "bar" }, // spec中定义的mark 名字
"spec": {
"normal": {
"size": (item, scale, ctx) => {
// item是对symbol图元的描述
return item.height/2;
},
"fill": "red",
"x": (item, scale, ctx) => {
return item.x2;
},
"y": (item, scale, ctx) => {
return item.y+item.height/2;
},
}
}
}
]
spec-2
结果如下:
最后我们将柱子隐藏掉。
继续添加如下配置:
"barSpec": {
"visible": false
},
结果如下:
我们得到了一个标准的克利夫兰点图,这个过程是该点图的诞生过程,但是我们在ChartSpace 里有更加简便的生成方法——直接使用散点图类型。示例如spec-3:
{
"type": "scatter",
"direction":"horizontal",
"xField": "x",
"yField": "y",
"groupBy": "x",
"size": 25,
"data": [
{
"name": "data1",
"values": [
{
"x": "折线图",
"y": 82
},
{
"x": "条形图",
"y": 50
},
{
"x": "饼图",
"y": 64
},
{
"x": "地图",
"y": 30
},
{
"x": "雷达图",
"y": 40
}
]
}
]
}
spec-3
注意上面配置的加粗部分,我们使用了图表的 direction
****字段来控制x,y轴数据映射,默认情况下分类数据映射到x轴,效果如下:
1.2 棒棒糖图(Lollipop Plot)
棒棒糖图就像它的名字一样,由一个大的圆点和一条从坐标轴底部出发的直线连接而成,形成一个棒棒糖的形状。直线和圆点可以是同一组数据,也可以是不同的数据,从而可以进行多维数据的展示。
在ChartSpace 中我们有几种方案可以实现棒棒糖图,一个方案类似上面的推演过程,在柱形图的末端加上圆点;第二种方案是先创建散点图,在散点图的基础上添加竖线或者横线。
我们采用第二种方案,如下spec-4:
// 声明图表 spec
const spec = {
"type": "scatter",
"xField": "x",
"yField": "y",
"groupBy": "x",
"size": 25,
"data": [
{
"name": "data1",
"values": [
{
"x": "折线图",
"y": 82
},
{
"x": "条形图",
"y": 50
},
{
"x": "饼图",
"y": 64
},
{
"x": "地图",
"y": 30
},
{
"x": "雷达图",
"y": 40
}
]
}
],
"extensionMarks": [
{
"name": "markRule",
"type": "rule",
"from": {
"data": "data1"
},
"spec": {
"normal": {
x: (datum, _scale, ctx) => {
return ctx.chart.getPositionX(datum.x);
},
y: (datum, _scale, ctx) => {
const chart = ctx.chart;
return ctx.chart.getPositionY(datum.y);
},
x2: (datum, _scale, ctx) => {
const chart = ctx.chart;
return ctx.chart.getPositionX(datum.x);
},
y2: (datum, _scale, ctx) => {
const chart = ctx.chart;
return ctx.chart.getPositionY(0);
}
}
}
}
],
};
spec-4
结果如下图:
现实生活中,棒棒糖有很多不同的形状,我们也可以通过自定义散点图的 symbolSpec 来展现不同的形状。
在spec-4 的基础上,我们继续添加如下代码:
symbolSpec: {
"normal": {
"shape": {
"type": "ordinal",
"field": "x",
"range": ["circle", "diamond", "square", "triangle", "rect"]
}
}
}
spec-4
spec-4 使用的是ChartSpace 的视觉通道语法。我们来看一下效果。
更进一步的,我们可以使用图片来进行更有趣的自定义效果,增强故事性,比如展现不同的奥特曼的战力。
{
"type": "scatter",
"xField": "x",
"yField": "y",
"groupBy": "x",
"size": 25,
"data": [
{
"name": "data1",
"values": [
{
"x": "雷杰多",
"y": 900000
},
{
"x": "奥特之王",
"y": 509876
},
{
"x": "诺亚奥特曼",
"y": 1000000
},
{
"x": "银河奥特曼",
"y": 357890
},
{
"x": "赛迦奥特曼",
"y": 897656
}
]
}
],
"extensionMarks": [
{
"name": "markRule",
"type": "rule",
"from": {
"data": "data1"
},
"spec": {
"normal": {
x: (datum, _scale, ctx) => {
return ctx.chart.getPositionX(datum.x);
},
y: (datum, _scale, ctx) => {
const chart = ctx.chart;
return ctx.chart.getPositionY(datum.y);
},
x2: (datum, _scale, ctx) => {
const chart = ctx.chart;
return ctx.chart.getPositionX(datum.x);
},
y2: (datum, _scale, ctx) => {
const chart = ctx.chart;
return ctx.chart.getPositionY(0);
}
}
}
},
{
"name": "markImage",
"type": "image",
"from": {
"mark": "markRule"
},
"spec": {
"normal": {
x: (item, _scale, ctx) => {
return item.bounds.x1 - 25;
},
y: (item, _scale, ctx) => {
return item.bounds.y1
},
"width": 50,
"url": (item, _scale, ctx) => {
switch (item.datum.x) {
case "雷杰多": return "1.png"; break;
case "奥特之王": return "2.png"; break;
case "诺亚奥特曼": return "3.png"; break;
case "银河奥特曼": return "4.png"; break;
case "赛迦奥特曼": return "5.png"; break;
}
}
}
}
}
],
symbolSpec: {
"normal": {
"fillOpacity": 0
}
}
}
spec-5
在上面的例子中,我们充分使用了ChartSpace的扩展能力,首先我们通过
symbolSpec: {
"normal": {
"fillOpacity": 0
}
}
隐藏了默认的点,在 extensionMarks
配置中使用了两种自定义mark 的方式:
- 基于data,添加了
rule
类型的mark,名为 “markRule”,作为棒棒糖的“棍”; - 基于mark,随后基于 “markRule” 的mark,取得其坐标值,添加了图片类型的mark,名为 “markImage”;
效果如下图:
1.3 哑铃图(Dumbbell Plot)
哑铃图也很好理解,就是将两个圆点用一条线连接起来,通常用于多组数据之间的指标对比。和帮帮图类似,我们使用堆积柱形图来演示哑铃图的由来。
我们首先定义一个堆积分组柱形图
{
"type": "horizontalBar",
"data": [
{
"name": "data1",
"values": [
{
"x": "折线图",
"y": 82,
"type":"1"
},
{
"x": "条形图",
"y": 50,
"type":"1"
},
{
"x": "折线图",
"y": 64,
"type":"2"
},
{
"x": "条形图",
"y": 30,
"type":"2"
}
]
}
],
"type": "horizontalBar",
"xField": "y",
"yField": "x",
"groupBy": "type",
"stackBy": "x",
"bandPadding": 0.2
}
spec-6
效果如下:
然后利用spec-2 的方式转换为点图,
"extensionMarks": [
{
"name": "dot",
"type": "symbol",
"shape": "circle",
"from": { "mark": "bar" }, // spec中定义的mark 名字
"spec": {
"normal": {
"size": (item, scale, ctx) => {
// item是对symbol图元的描述
return item.height/2;
},
"fill": (item, scale, ctx) => {
return item.fill;
},
"x": (item, scale, ctx) => {
return item.x2;
},
"y": (item, scale, ctx) => {
return item.y+item.height/2;
},
}
}
}
],
"barSpec": {
"visible": false
},
spec-7
效果如下:
下面我们再添加一组矩形来进行连接:
"extensionMarks": [
(....略)
{
"name": "line",
"type": "rect",
"from": { "mark": "bar", }, // spec中定义的mark 名字
"spec": {
"normal": {
"height": 20,
"stroke":"#0000",
"visible":(item)=>{
return item.datum.type==="2"
},
"width":(item, scale, ctx) => {
return item.width;
},
"fill": (item, scale, ctx) => {
return item.fill;
},
"x": (item, scale, ctx) => {
return item.x2+item.height / 4;
},
"y": (item, scale, ctx) => {
return item.y + item.height / 2;
},
}
}
}
]
spec-8
效果如下:
从上面的变换过程来看,哑铃图和分组柱图一样,可以进行数据的分组和比对,同时又增加了数据变换的展示功能。
1.4 气泡图(Bubble Plot)
气泡图和普通散点图的区别在于,数据条目可以多一个字段来编码到点的大小(Size)上,使得气泡图比普通散点图可以映射更多维度的数据。
我们可以基于上面展示的扩展能力,在普通散点图的基础上,扩展点的Size 属性,来实现气泡图。
"symbolSpec": {
"normal": {
"size": {
"field": "size",
"range": [5, 20],
"type": "linear"
}
}
}
spec-9
效果如下:
1.5 散点地图(Scatter Map)
散点地图是将点的位置和地理位置相结合的一种数据可视化方法,通过颜色区间,点的大小来编码不同地理区域相关维度的数据,比如人口,国民生产总值,舆情信息等。比如下图:
1.6 范围图(Range Charts )
范围图并不属于点图,但是和点图有很强的关联关系,就像点图和柱形图的关系一样。范围图用来展示数据范围,是由哑铃图变种而来,将哑铃图的两个”哑铃“隐藏掉就是范围图。基于上面的实现,我们只需隐藏堆积柱形图下面的柱子即可。如下spec
{
"type": "bar",
"xField": "x",
"yField": "y",
"groupBy": "type",
"data": [
{
"name": "data",
"values": [
{
"x": "2:00",
"y": 82,
"type": "销售额"
},
(略......)
{
"x": "22:00",
"y": 78,
"type": "利润"
}
]
}
],
"bandPadding": 0.2,
"paddingInGroup": 0.2,
"barSpec": {
"visible": (item) => {
return item.type === "销售额"
}
}
}
spec-10
展示效果如下:
1.7 蜂群图
蜂群图从展现形式上来说和普通散点图没有什么区别,将每一个数据项展示成点。根本区别在于对数据的处理,蜂群图采用了一种逻辑,以确保所绘制的点彼此靠近且不会重叠,并能有效呈现出点分布的局部密度信息,直观而不失优雅。在反映密度分布的整体趋势上,蜂群图也类似箱线图或提琴图。相比之下,蜂群图本身由单个样本点组成,因此除了能够描述整体,还能比较局部,可以说内容更详细 。
-
点图的优缺点
与柱形图相比,点图更能节省“墨水( The data-ink ratio )”,可以留下更多的空间给数据注释。
点图关注的数据范围,使用宽、高、大小、颜色、相对位置来进行数据编码,不是很注重和坐标轴之间的关系,使得轴的使用更灵活。
虽然在实现上可以由柱图进行扩展,但是点图可以表达柱图表现不了的数据分布,以及可以展现多维数据。
但是在数值比对的场景下,点图是不如柱图直观的。
-
最佳实践
3.1 颜色
由于常规点图形状大小没有差别,颜色的使用就至关重要。
连续数据
在展示连续数据的时候,可以使用连续的颜色为点进行着色,同时配合连续颜色图例进行数据筛选。
分类数据
分类数据要求在颜色区分度上比较明显,当然我们可以辅助形状来加强用户对分类的识别。
时序数据
时序时间的颜色编码,通常按照时间由远到近逐渐加深颜色,以查看特定数据的时间分布。比如下图,不同颜色表示的是不同的年份。
3.2 组合应用
将点图与其他图表进行组合,可以很有效的展示复杂的数据。下图使用点图来展现范围,用柱图来表达值的大小。
下图使用点图和线图进行组合,展现数据范围和平均值的变化趋势。
下图组合散点图和和哑铃图,表示范围和某一范围内的数据波动情况。
数据平台前端团队,在公司内负责大数据相关产品的研发。我们在前端技术上保持着非常强的热情,除了数据产品相关的研发外,在数据可视化、海量数据处理优化、web excel、WebIDE、私有化部署、工程工具都方面都有很多的探索和积累,有兴趣可以与我们联系。 本文中的 ChartSpace 为我们内部自研图表框架。