Skip to content
On this page

G2

G2官网:https://g2.antv.antgroup.com/manual/introduction/what-is-g2

语雀知源.致远:https://www.yuque.com/afx/blog/fpgdp5

林峰:Echarts的作者 G2 是面粉,Echarts 是面条,皆微小而美好,因小食材怀大梦想,助力共筹东方巨龙崛起之盛宴,迎四海饕客。

我们是蚂蚁集团数据可视化团队,一群有爱有梦的人,怀揣「让人们在数据世界里获得视觉化思考能力」的梦想前行,成就智能时代全球领先的数据可视化解决方案。

多年前第一次看到知源.致远这篇文章的时候真的是羡慕这样的团队。可即使是这么优秀、这么纯粹的一个研发团队,也最终会走向没落。技术人员的出路到底是什么?

ts
TODO:学了`webgl`的渲染以后,现在看G2的源码好像没有那么恐怖了,后面有时间还是要学习学习。也不需要学习它的底层代码,但是它作为一个这么好用的图表库,有时间还是需要好好研究研究。

G2学习

G2是一个简洁的渐进式语法,主要用于制作基于网页的可视化。它提供了一套函数风格式、声明形式的API和组件化的编程范式,希望能帮助用户能快速完成报表搭建、数据探索、可视化叙事等多样式的需求。

G2中的核心概念:

  • 标记(Mark): 绘制数据驱动的图形
  • 转换(Transform): 派生数据
  • 比例尺(Scale): 将抽象的数据映射为视觉数据
  • 坐标系(Coordinate): 对空间通道应用点变换
  • 视图复合(Composition): 管理和增强视图
  • 动画(Animation): 数据驱动的动画和连续的形变动画
  • 交互(Interaction): 操作视图并且展现详细信息

安装

ts
npm install @antv/g2

// 安装依赖时报错:
Fix the upstream dependency conflict, or retry
npm ERR! this command with --force, or --legacy-peer-deps
npm ERR! to accept an incorrect (and potentially broken) dependency resolution
// 当前的依赖项和之前安装的某些包需求的依赖项有冲突,在后面加上 --force 或 --legacy-peer-deps
// 然后按提示执行npm audit fix --force

// 后续又换了一个干净的vue3项目,没有上诉依赖冲突的报错问题

绘制第一张图表

vue
<script setup lang="ts">
import { onMounted } from 'vue'
import { Chart } from '@antv/g2';
// 准备数据
const data = [
  { genre: 'Sports', sold: 275 },
  { genre: 'Strategy', sold: 115 },
  { genre: 'Action', sold: 120 },
  { genre: 'Shooter', sold: 350 },
  { genre: 'Other', sold: 150 }
]

onMounted(() => {
    // 初始化图表实例
    const chart = new Chart({
        width: 800, // 图表高度
        height: 400, // 图表宽度
        container: 'container', // 挂载容器的ID
    })

    // 声明可视化
    chart
        .interval() // 创建一个 Interval 标记
        .data(data) // 绑定数据
        .encode('x', 'genre') // 编码 x 通道
        .encode('y', 'sold') // 编码 y 通道

    // 渲染可视化
    chart.render()
})
</script>

<template>
    <div id="container"></div>
</template>

使用复合节点在一个容器中绘制两张图表

可参考下面的使用复合Mark进行代码优化

vue
<script setup lang="ts">
import { onMounted } from 'vue'
import { Chart } from '@antv/g2';
// 准备数据
const data = [
  { genre: 'Sports', sold: 350 },
  { genre: 'Strategy', sold: 275 },
  { genre: 'Action', sold: 150 },
  { genre: 'Shooter', sold: 120 },
  { genre: 'Other', sold: 115 }
]

onMounted(() => {
    // 初始化图表实例
    const chart = new Chart({
        width: 800, // 图表高度
        height: 400, // 图表宽度
        container: 'container', // 挂载容器的ID
    })

    chart.options({
        type: 'spaceFlex', // 复合节点
        children: [
            { 
                type: 'interval',
                padding: 'auto',
                data: data,
                encode: {
                    x: 'genre', // 编码 x 通道
                    y: 'sold' // 编码 y 通道
                }
            },
            {
                type: 'interval',
                padding: 'auto',
                data: data,
                encode: {
                    color: 'genre', // 编码 x 通道
                    y: 'sold' // 编码 y 通道
                },
                transform: [{ type: 'stackY' }],
                coordinate: { type: 'theta' },
                legend: { color: false }
            }
        ]
    })

    // 渲染可视化
    chart.render()
})
</script>

<template>
    <div id="container"></div>
</template>

请求json文件作为数据源

vue
<script setup lang="ts">
import { onMounted } from 'vue'
import { Chart } from '@antv/g2';

onMounted(() => {
    // 初始化图表实例
    const chart = new Chart({
        width: 800, // 图表高度
        height: 400, // 图表宽度
        container: 'container', // 挂载容器的ID
    })

    chart.options({
        type: 'point',
        data: {
            type: 'fetch',
            value: 'https://gw.alipayobjects.com/os/basement_prod/6b4aa721-b039-49b9-99d8-540b3f87d339.json'
        },
        encode: {
            color: 'gender',
            x: 'height',
            y: 'weight',
        }
    })

    // 渲染可视化
    chart.render()
})
</script>

<template>
    <div id="container"></div>
</template>

图表数据叠加

vue
<script setup lang="ts">
import { onMounted } from 'vue'
import { Chart } from '@antv/g2';

onMounted(() => {
    // 初始化图表实例
    const chart = new Chart({
        width: 800, // 图表高度
        height: 400, // 图表宽度
        container: 'container', // 挂载容器的ID
    })

    chart.options({
        type: 'line',
        data: {
            type: 'fetch',
            value: 'https://assets.antv.antgroup.com/g2/indices.json'
        },
        transform: [{
            type: 'normalizeY',
            basis: 'first',
            groupBy: 'color'
        }],
        encode: {
            x: (d: any) => new Date(d.Date),
            color: 'Symbol',
            y: 'Close',
        },
        axis: {
            y: {
                title: '↑ Change in price (%)'
            }
        }
    })

    // 渲染可视化
    chart.render()
})
</script>

<template>
    <div id="container"></div>
</template>

使用复合 Mark

vue
<script setup lang="ts">
import { onMounted } from 'vue'
import { Chart } from '@antv/g2';

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 },
]
onMounted(() => {
    // 初始化图表实例
    const chart = new Chart({
        width: 800, // 图表高度
        height: 400, // 图表宽度
        container: 'container', // 挂载容器的ID
    })

    // 定义复合 Mark
    function PointLine({ encode, data }: any = {}) {
        return [
            { type: 'line', data, encode },
            { type: 'point', data, encode },
        ]
    }

    chart.options({
        type: PointLine, // 使用复合 Mark
        data,
        encode: {
            x: 'year',
            y: 'value'
        }
    })

    // 渲染可视化
    chart.render()
})
</script>

<template>
    <div id="container"></div>
</template>

桑基图绘制

桑基图最明显的特征就是:始末端的分支宽度总和相等,即所有主支宽度的总和应与所有分出去的分支宽度的总和相等,保持能量的平衡。 在这个案例中,json文件的数据格式:绘制桑基图来说,这种数据格式应该是固定的。

json
{
    "source": "Agricultural 'waste'", // 来源
    "target": "Bio-conversion", // 目标
    "value": 124.729 // 数值
},
vue
<script setup lang="ts">
import { onMounted } from 'vue'
import { Chart } from '@antv/g2';

onMounted(() => {
    // 初始化图表实例
    const chart = new Chart({
        width: 900, // 图表高度
        height: 600, // 图表宽度
        container: 'container', // 挂载容器的ID
    })

    chart.options({
        type: 'sankey',
        data: {
            type: 'fetch',
            value: 'https://assets.antv.antgroup.com/g2/energy.json',
            transform: [
                {
                    type: 'custom',
                    callback: (data: any) => ({ links: data }),
                }
            ]
        },
        layout: {
            nodeAlign: 'center',
            nodePadding: 0.03
        },
        style: {
            labelSpacing: 3,
            labelFontWeight: 'bold',
            nodeStrokeWidth: 1.2,
            linkFillOpacity: 0.4
        }
    })

    // 渲染可视化
    chart.render()
})
</script>

<template>
    <div id="container"></div>
</template>