前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >「AntV」使用 AntV G2Plot 实现一个复杂的带有四象限自定义标注的统计散点图

「AntV」使用 AntV G2Plot 实现一个复杂的带有四象限自定义标注的统计散点图

作者头像
拿我格子衫来
发布2023-08-24 10:44:05
4970
发布2023-08-24 10:44:05
举报
文章被收录于专栏:TopFETopFE

前言

作为一名CSDN的前端领域优质创作者,时常有一些读者向我咨询前端问题。最近就有一个读者看了一些我之前写的数据可视化文章,向我请教如何制作一个比较复杂的散点图,由于目前做的是大数据项目,在数据可视化也做过一些成绩,尤其是数据分析,数据血缘链路。最常用的是AntV图表库和Echarts。 于是我就用AntV实现了他的需求,由于这个图表比较复杂,借着这次AntV的案例征文来给大家详细分享一下。

详细需求

先说一下需求背景 某个学校需要统计本区域内学校的成绩,并显示自己在该区域中的位置,设计了这样一个散点图,以x轴为学校成绩的标准差,y轴为学校的平均成绩,两个轴都是数值。点的类型一共有四类。 在图表的四个角分别有辅助注释,分别是

  • 高水平高均衡
  • 高水平低均衡
  • 低水平高均衡
  • 低水平低均衡

除此之外在图表中有两个特殊的点,这两个点附近使用特殊的图标显示。 以达到快速区分,寻找的效果 一个是“本校”, 一个是“全体”,其中"全体"这个点,以该点的(x,y) 画两条蓝色线,两条蓝线将图表划分成了四象限。

总结而言,相对于一般最基础的散点图,该图表有以下难点

  • 四个方位的辅助文本
  • “本校”,“全体”点的特殊图标
  • “全体”点的的两条蓝线

最终效果图

先看一下图表的最终效果

在这里插入图片描述
在这里插入图片描述

实现步骤

这个图我是使用G2Plot来实现的,官网地址:https://g2plot.antv.antgroup.com/。 它是一个开箱即用的图表库, 并且易于配置、并且定位是一个通用统计图表库。 由于是散点图,所以使用的是G2Plot中Scatter 模块。

基础简单散点图实现

代码语言:javascript
复制
import { Scatter } from '@antv/g2plot';

fetch('https://gw.alipayobjects.com/os/antfincdn/j5ADHaMsZx/scatter.json')
  .then(data => data.json())
  .then(data => {
    const scatterPlot = new Scatter('container', {
      data,
      xField: 'x',
      yField: 'y',
      size: 5,
      pointStyle: {
        fill: '#5B8FF9',
      },
    });
    scatterPlot.render();
  });
在这里插入图片描述
在这里插入图片描述

使用的数据为

代码语言:javascript
复制
[
  { "x": 1, "y": 4.181 },
  { "x": 2, "y": 4.665 },
  { "x": 3, "y": 5.296 },
  { "x": 4, "y": 5.365 },
  { "x": 5, "y": 5.448 },
  .....
]

其中有几个比较关键的配置dataxFieldyField

  • data 数组或对象, 设置图表数据源。数据源为对象集合,例如:[{ time: ‘1991’,value: 20 }, { time: ‘1992’,value: 20 }]。
  • xField 一个字符串, 图形在 x 方向对应的数据字段名,一般是横向的坐标轴对应的字段。比如:要看不同班级的人数情况,那么班级字段就是对应的 xField。
  • yField 一个字符串, 图形在 y 方向对应的数据字段名,一般是纵向的坐标轴对应的字段。比如:要看不同班级的人数情况,那么人数字段就是对应的 yField。

四个方位的标注文本

四个方位的图表标注是使用 Annotations 来实现的, 图形标注,Annotation,作为图表的辅助元素,主要用于在图表上标识额外的标记注解。https://g2plot.antv.antgroup.com/api/components/annotations

在这里插入图片描述
在这里插入图片描述

在一个图表中你可以添加多个图表标注,而且标注的类型也是多种多样的。可以是文字,图片,html,辅助线,弧线。

在这里插入图片描述
在这里插入图片描述

在该例子中,我们只需要这样配置就可以在四个角添加标注

代码语言:javascript
复制
annotations: [
  {
    type: 'text',
    position: ['18%', '5%'],
    content: '高水平高均衡',
    style: {
      textAlign: 'right',
      fontWeight: '500',
      fill: 'rgb(92, 92, 92)',
    },
  },
  {
    type: 'text',
    position: ['18%', '95%'],
    content: '低水平高均衡',
    style: {
      textAlign: 'right',
      fontWeight: '500',
      fill: 'rgb(92, 92, 92)',
    },
  },
  {
    type: 'text',
    position: ['83%', '5%'],
    content: '高水平低均衡',
    style: {
      textAlign: 'left',
      fontWeight: '500',
      fill: 'rgb(92, 92, 92)',
    },
  },
  {
    type: 'text',
    position: ['83%', '95%'],
    content: '低水平低均衡',
    style: {
      textAlign: 'left',
      fontWeight: '500',
      fill: 'rgb(92, 92, 92)',
    },
  },
],

其中position表示标注的位置,可以使用百分比,也可以使用一些特殊位置的枚举值,如position: ['median', 'median'],

某个点的特殊图标及文字

在这个散点图的统计图中,有两个特殊的点,就是“本校”和“全体”

在这里插入图片描述
在这里插入图片描述

这是为了实现区分,方便自我定位,按照上面这个图,本校的点,更接近“高水平低均衡” 所以说这说明这个学校的成绩基本是高水平,但并不太稳定。比全体水平好一些。

在特殊点这里,使用的是label 配置项。使用label 可以定义某个点的文本图形属性样式。 官方配置文档 https://g2plot.antv.antgroup.com/api/plots/scatter

在这里插入图片描述
在这里插入图片描述

由于图标上还要显示文字,嫌麻烦的话可以直接将文字放到图片上,我这里是拆开的,在label中加了一个图标,一个文字。 配置如下:

代码语言:javascript
复制
label:{
  formatter: item => {
    return labels.includes(item.type) ? item.type : ''
  },
  offsetY: 0,
  content: item => {
    if (labels.includes(item.type)) {
      const group = new G.Group({})
      group.addShape({
        type: 'image',
        attrs: {
          x: 0,
          y: 0,
          width: 40,
          height: 50,
          img: 'https://gw.alipayobjects.com/zos/rmsportal/oeCxrAewtedMBYOETCln.png',
        },
      })

      group.addShape({
        type: 'text',
        attrs: {
          x: 20,
          y: 20,
          text: item.type,
          textAlign: 'center',
          textBaseline: 'top',
          fill: '#ffffff',
        },
      })
      return group
    } else {
      return ''
    }
  },
  labelLine: true,
},

首先过滤一些点,点的属性中有type属性的 才需要显示label。content属性是用来配置label的内容,样式,及定位的。

以某个点为中心划分四象限

这个图表细节要使用散点图的quadrant属性来实现,在散点图中给一个y值和x值就能以该点画出一个四象限,并且能够配置每个区域的颜色,和线的颜色。 quadrant 的配置参数

细分配置

类型

功能描述

xBaseline

number

x 方向上的象限分割基准线,默认为 0

yBaseline

number

y 方向上的象限分割基准线,默认为 0

lineStyle

object

配置象限分割线的样式,详细配置参考绘图属性

regionStyle

object[]

象限样式,详细配置参考绘图属性

labels

object[]

象限文本配置,详细配置参考绘图属性

我们的配置

代码语言:javascript
复制
quadrant: {
  xBaseline: item.standardDeviation,
  yBaseline: item.average,
  lineStyle: {
    stroke: '#1890ff',
    opacity: 0.8,
    lineWidth: 3,
  },
  regionStyle: [
    { fill: '#ffffff', opacity: 0.2 },
    { fill: '#ffffff', opacity: 0.2 },
    { fill: '#ffffff', opacity: 0.2 },
    { fill: '#ffffff', opacity: 0.2 },
  ],
},

item 是"全体"点。配置线的颜色为蓝色 lineStyle.stroke = '#1890ff'

至此该图表所有的细节都已实现。

在这里插入图片描述
在这里插入图片描述

完整Vue组件代码

代码语言:javascript
复制
 <template>
  <div>
    <div id="container" style="height: 500px; width: 500px"></div>
  </div>
</template>
<script>
import { Scatter, G2 } from '@antv/g2plot'
const G = G2.getEngine('canvas')
export default {
  data() {
    return {}
  },
  methods: {},
  mounted() {
    const data = [
      { standardDeviation: 8, average: 95, category: 'I类', type: '本校' },
      { standardDeviation: 8, average: 25, category: 'I类' },
      { standardDeviation: 3, average: 26, category: 'I类' },
      { standardDeviation: 0, average: 98, category: 'I类' },
      { standardDeviation: 7, average: 58, category: 'I类' },
      { standardDeviation: 6, average: 7, category: 'I类' },
      { standardDeviation: 10, average: 54, category: 'I类' },
      { standardDeviation: 5, average: 84, category: 'I类' },
      { standardDeviation: 6, average: 21, category: 'I类' },
      { standardDeviation: 9, average: 93, category: 'I类' },
      { standardDeviation: 9, average: 47, category: 'I类' },
      { standardDeviation: 5, average: 26, category: 'I类' },
      { standardDeviation: 10, average: 17, category: 'I类' },
      { standardDeviation: 2, average: 50, category: 'I类' },
      { standardDeviation: 12, average: 3, category: 'I类' },
      { standardDeviation: 10, average: 75, category: 'II类', type: '全体' },
      { standardDeviation: 3, average: 95, category: 'II类' },
      { standardDeviation: 2, average: 75, category: 'II类' },
      { standardDeviation: 10, average: 27, category: 'II类' },
      { standardDeviation: 7, average: 40, category: 'II类' },
      { standardDeviation: 3, average: 81, category: 'II类' },
      { standardDeviation: 2, average: 94, category: 'II类' },
      { standardDeviation: 0, average: 93, category: 'II类' },
      { standardDeviation: 9, average: 68, category: 'II类' },
      { standardDeviation: 10, average: 12, category: 'II类' },
      { standardDeviation: 12, average: 30, category: 'II类' },
      { standardDeviation: 5, average: 10, category: 'II类' },
      { standardDeviation: 4, average: 60, category: 'II类' },
      { standardDeviation: 7, average: 24, category: 'II类' },
      { standardDeviation: 6, average: 28, category: 'II类' },
      { standardDeviation: 11, average: 14, category: 'III类' },
      { standardDeviation: 7, average: 10, category: 'III类' },
      { standardDeviation: 0, average: 13, category: 'III类' },
      { standardDeviation: 4, average: 74, category: 'III类' },
      { standardDeviation: 1, average: 24, category: 'III类' },
      { standardDeviation: 10, average: 4, category: 'III类' },
      { standardDeviation: 11, average: 90, category: 'III类' },
      { standardDeviation: 0, average: 90, category: 'III类' },
      { standardDeviation: 2, average: 99, category: 'III类' },
      { standardDeviation: 11, average: 24, category: 'III类' },
      { standardDeviation: 6, average: 65, category: 'III类' },
      { standardDeviation: 8, average: 0, category: 'III类' },
      { standardDeviation: 5, average: 19, category: 'III类' },
      { standardDeviation: 12, average: 7, category: 'III类' },
      { standardDeviation: 2, average: 69, category: 'III类' },
      { standardDeviation: 12, average: 37, category: 'IV类' },
      { standardDeviation: 8, average: 56, category: 'IV类' },
      { standardDeviation: 5, average: 70, category: 'IV类' },
      { standardDeviation: 5, average: 1, category: 'IV类' },
      { standardDeviation: 0, average: 37, category: 'IV类' },
      { standardDeviation: 4, average: 9, category: 'IV类' },
      { standardDeviation: 11, average: 69, category: 'IV类' },
      { standardDeviation: 7, average: 20, category: 'IV类' },
      { standardDeviation: 9, average: 77, category: 'IV类' },
      { standardDeviation: 1, average: 83, category: 'IV类' },
      { standardDeviation: 5, average: 68, category: 'IV类' },
      { standardDeviation: 3, average: 39, category: 'IV类' },
      { standardDeviation: 1, average: 8, category: 'IV类' },
      { standardDeviation: 10, average: 38, category: 'IV类' },
      { standardDeviation: 11, average: 18, category: 'IV类' },
    ]

    const cateMap = {
      I类: { color: '#299999', shape: 'circle' },
      II类: { color: '#f6c022', shape: 'triangle' },
      III类: { color: '#ff99c3', shape: 'square' },
      IV类: { color: '#74cbed', shape: 'diamond' },
    }

    const labels = ['本校', '全体']

    const item = data.filter(x => x.type === '全体')[0]

    const scatterPlot = new Scatter('container', {
      padding: [30, 30, 50, 50],
      data,
      xField: 'standardDeviation',
      yField: 'average',
      colorField: 'category',
      color: ({ category }) => {
        return cateMap[category].color
      },
      size: 5,
      legend: {
        layout: 'horizontal',
        position: 'top',
      },
      shapeField: 'category',
      shape: ({ category }) => {
        return cateMap[category].shape
      },
      pointStyle: {
        fillOpacity: 1,
      },
      label: {
        formatter: item => {
          return labels.includes(item.type) ? item.type : ''
        },
        offsetY: 0,
        content: item => {
          if (labels.includes(item.type)) {
            const group = new G.Group({})
            group.addShape({
              type: 'image',
              attrs: {
                x: 0,
                y: 0,
                width: 40,
                height: 50,
                img: 'https://gw.alipayobjects.com/zos/rmsportal/oeCxrAewtedMBYOETCln.png',
              },
            })

            group.addShape({
              type: 'text',
              attrs: {
                x: 20,
                y: 20,
                text: item.type,
                textAlign: 'center',
                textBaseline: 'top',
                fill: '#ffffff',
              },
            })
            return group
          } else {
            return ''
          }
        },
        labelLine: true,
      },
      annotations: [
        {
          type: 'text',
          position: ['18%', '5%'],
          content: '高水平高均衡',
          style: {
            textAlign: 'right',
            fontWeight: '500',
            fill: 'rgb(92, 92, 92)',
          },
        },
        {
          type: 'text',
          position: ['18%', '95%'],
          content: '低水平高均衡',
          style: {
            textAlign: 'right',
            fontWeight: '500',
            fill: 'rgb(92, 92, 92)',
          },
        },
        {
          type: 'text',
          position: ['83%', '5%'],
          content: '高水平低均衡',
          style: {
            textAlign: 'left',
            fontWeight: '500',
            fill: 'rgb(92, 92, 92)',
          },
        },
        {
          type: 'text',
          position: ['83%', '95%'],
          content: '低水平低均衡',
          style: {
            textAlign: 'left',
            fontWeight: '500',
            fill: 'rgb(92, 92, 92)',
          },
        },
      ],
      meta: {
        average: {
          alias: '平均分',
        },
        standardDeviation: {
          alias: '标准差',
        },
        category: {
          alias: '类别',
        },
      },
      yAxis: {
        nice: true,
        line: {
          style: {
            stroke: '#aaa',
          },
        },
        title: {
          text: '平均分',
          position: 'end',
          offset: 30,
          // autoRotate: false,
        },
      },
      xAxis: {
        title: {
          text: '标准差',
          position: 'end',
        },
        grid: {
          line: {
            style: {
              stroke: '#eee',
            },
          },
        },
        line: {
          style: {
            stroke: '#aaa',
          },
        },
      },
      quadrant: {
        xBaseline: item.standardDeviation,
        yBaseline: item.average,
        lineStyle: {
          stroke: '#1890ff',
          opacity: 0.8,
          lineWidth: 3,
        },
        regionStyle: [
          { fill: '#ffffff', opacity: 0.2 },
          { fill: '#ffffff', opacity: 0.2 },
          { fill: '#ffffff', opacity: 0.2 },
          { fill: '#ffffff', opacity: 0.2 },
        ],
      },
    })
    scatterPlot.render()
  },
}
</script>

附录

代码简洁解释

具体来说,使用了 Scatter 组件创建了一个散点图,并传入了以下配置项:

  • padding:设置图表的内边距。
  • data:传入的数据源。
  • xField:指定 x 轴所对应的数据字段。
  • yField:指定 y 轴所对应的数据字段。
  • colorField:指定颜色所对应的数据字段。
  • color:为传入的数据分类设置颜色,使用了一个函数来返回对应分类的颜色。
  • size:设置散点的大小。
  • legend:设置图例的位置和布局。
  • shapeField:指定形状所对应的数据字段。
  • shape:为传入的数据分类设置形状,使用了一个函数来返回对应分类的形状。
  • pointStyle:设置散点的样式,这里设置了填充不透明度为 1。
  • label:设置散点的标签,使用了一个函数来返回标签的内容和样式。
  • annotations:添加文本注释,用于标识四个象限的位置。
  • meta:设置数据字段的别名。
  • yAxis:设置 y 轴的样式和标题。
  • xAxis:设置 x 轴的样式和标题。
  • quadrant:设置四象限的样式,包括基线坐标、线条样式和区域样式。

最后,调用 render() 方法将散点图渲染到 HTML 元素上。

相关链接

AntV G2Plot 散点图API

AntV G2Plot 散点图四象限示例I

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2023-06-14,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 前言
  • 详细需求
  • 最终效果图
  • 实现步骤
    • 基础简单散点图实现
      • 四个方位的标注文本
        • 某个点的特殊图标及文字
          • 以某个点为中心划分四象限
          • 完整Vue组件代码
          • 附录
            • 代码简洁解释
            • 相关链接
            领券
            问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档