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

前言

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

详细需求

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

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

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

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

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

最终效果图

先看一下图表的最终效果
在这里插入图片描述

实现步骤

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

基础简单散点图实现

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();
  });

在这里插入图片描述
使用的数据为

[
  { "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,辅助线,弧线。
在这里插入图片描述

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

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中加了一个图标,一个文字。 配置如下:

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 的配置参数

细分配置类型功能描述
xBaselinenumberx 方向上的象限分割基准线,默认为 0
yBaselinenumbery 方向上的象限分割基准线,默认为 0
lineStyleobject配置象限分割线的样式,详细配置参考绘图属性
regionStyleobject[]象限样式,详细配置参考绘图属性
labelsobject[]象限文本配置,详细配置参考绘图属性

我们的配置

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组件代码

 <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

  • 4
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 3
    评论
好的,下面是一个简单的使用函数方法在 React 中画一个带有 slider 的折柱图的示例代码: ```javascript import React, { useEffect, useRef } from 'react'; import { Line, Column } from '@antv/g2plot'; function G2PlotChart() { const containerRef = useRef(null); useEffect(() => { if (containerRef.current) { const data = [ { year: '1991', value: 3 }, { year: '1992', value: 4 }, { year: '1993', value: 3.5 }, { year: '1994', value: 5 }, { year: '1995', value: 4.9 }, { year: '1996', value: 6 }, { year: '1997', value: 7 }, { year: '1998', value: 9 }, { year: '1999', value: 13 }, ]; const linePlot = new Line(containerRef.current, { data, xField: 'year', yField: 'value', slider: { start: 0, end: 0.5, height: 26, xAxis: { type: 'time', grid: null, }, yAxis: { grid: null, }, backgroundStyle: { fill: '#f5f5f5', fillOpacity: 0.9, }, data: data.slice(0, 4), }, }); linePlot.render(); const columnPlot = new Column(containerRef.current, { data, xField: 'year', yField: 'value', slider: { start: 0.5, end: 1, height: 26, xAxis: { type: 'time', grid: null, }, yAxis: { grid: null, }, backgroundStyle: { fill: '#f5f5f5', fillOpacity: 0.9, }, data: data.slice(4), }, }); columnPlot.render(); } }, []); return <div ref={containerRef} />; } export default G2PlotChart; ``` 在上述代码中,我们使用 `useRef` 来创建一个容器的引用,然后在 `useEffect` 中使用 `new Line` 和 `new Column` 分别创建折线图和柱状图,并通过 `slider` 配置项来添加缩略轴。其中: - `data` 为图表的数据。 - `xField` 和 `yField` 分别为图表的 X 轴和 Y 轴字段。 - `slider` 配置项用来添加缩略轴,其中 `start` 和 `end` 分别为缩略轴的起始位置和结束位置,`height` 为缩略轴的高度,`xAxis` 和 `yAxis` 分别为缩略轴的 X 轴和 Y 轴配置项,`backgroundStyle` 为缩略轴的背景样式,`data` 为缩略轴的数据。 你可以根据实际需求修改以上配置项来自定义折柱图和缩略轴。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

拿我格子衫来

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值