点图指北

简单来讲,使用点或者小圆形来对数据进行可视化编码的图都可以称之为点图。到具体的应用场景,点图被抽象出了很多图表类型,这些图表的使用也常常让人迷惑。本文将具体的讲解点图的概念和使用。

本文你将学习到:

什么是点图?

点图的使用场景有哪些?

点图的优缺点有哪些?

如何创建点图?

  1. 常见点图

点图的常见应用领域和直方图有些类似,都是用来表达数据分布,点图在视觉上通过点的聚集形式来表现,而不是通过直接的频率数值。点图中的点和数据项是一一对应的关系。下面我们看几种常见的点图。

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 蜂群图

蜂群图从展现形式上来说和普通散点图没有什么区别,将每一个数据项展示成点。根本区别在于对数据的处理,蜂群图采用了一种逻辑,以确保所绘制的点彼此靠近且不会重叠,并能有效呈现出点分布的局部密度信息,直观而不失优雅。在反映密度分布的整体趋势上,蜂群图也类似箱线图或提琴图。相比之下,蜂群图本身由单个样本点组成,因此除了能够描述整体,还能比较局部,可以说内容更详细

  1. 点图的优缺点

与柱形图相比,点图更能节省“墨水( The data-ink ratio )”,可以留下更多的空间给数据注释。

点图关注的数据范围,使用宽、高、大小、颜色、相对位置来进行数据编码,不是很注重和坐标轴之间的关系,使得轴的使用更灵活。

虽然在实现上可以由柱图进行扩展,但是点图可以表达柱图表现不了的数据分布,以及可以展现多维数据。

但是在数值比对的场景下,点图是不如柱图直观的。

  1. 最佳实践

3.1 颜色

由于常规点图形状大小没有差别,颜色的使用就至关重要。

连续数据

在展示连续数据的时候,可以使用连续的颜色为点进行着色,同时配合连续颜色图例进行数据筛选。

分类数据

分类数据要求在颜色区分度上比较明显,当然我们可以辅助形状来加强用户对分类的识别。

时序数据

时序时间的颜色编码,通常按照时间由远到近逐渐加深颜色,以查看特定数据的时间分布。比如下图,不同颜色表示的是不同的年份。

3.2 组合应用

将点图与其他图表进行组合,可以很有效的展示复杂的数据。下图使用点图来展现范围,用柱图来表达值的大小。

下图使用点图和线图进行组合,展现数据范围和平均值的变化趋势。

下图组合散点图和和哑铃图,表示范围和某一范围内的数据波动情况。


数据平台前端团队,在公司内负责大数据相关产品的研发。我们在前端技术上保持着非常强的热情,除了数据产品相关的研发外,在数据可视化、海量数据处理优化、web excel、WebIDE、私有化部署、工程工具都方面都有很多的探索和积累,有兴趣可以与我们联系。 本文中的 ChartSpace 为我们内部自研图表框架。

猜你喜欢

转载自juejin.im/post/7031097812725022727