Loading...
G2 中比例尺(Scale) 是可视化很重要的一个抽象:将抽象数据映射为视觉数据,它是抽象数据和视觉数据的桥梁。如果说编码决定了标记的哪些通道需要被可视化,那么比例尺决定了这些通道该如何被可视化。
G2 提供了丰富的比例尺类型,可以根据数据类型和使用场景进行分类:
处理 连续
数值数据,保持数据间的比例关系:
比如下面的散点图的 x 和 y 通道都是使用了 linear
比例尺。
import { Chart } from '@antv/g2';const chart = new Chart({container: 'container',theme: 'classic',});chart.options({type: 'point',data: {type: 'fetch',value: 'https://gw.alipayobjects.com/os/antvdemo/assets/data/scatter.json',},encode: { x: 'weight', y: 'height', color: 'gender' },});chart.render();
当我们尝试改变 x 通道和 y 通道的比例尺:
import { Chart } from '@antv/g2';const chart = new Chart({container: 'container',theme: 'classic',});chart.options({type: 'point',data: {type: 'fetch',value: 'https://gw.alipayobjects.com/os/antvdemo/assets/data/scatter.json',},encode: { x: 'weight', y: 'height', color: 'gender' },scale: {x: {type: 'point',},y: {type: 'point',range: [1, 0],},},});chart.render();
对于密集的数据,更建议使用连续比例尺而非分类比例尺。
处理 离散
的分类数据:
比如下面的条形图的 color 通道就是用了 ordinal
比例尺。
import { Chart } from '@antv/g2';const chart = new Chart({container: 'container',theme: 'classic',});chart.options({type: 'interval',data: [{ genre: 'Sports', sold: 275 },{ genre: 'Strategy', sold: 115 },{ genre: 'Action', sold: 120 },{ genre: 'Shooter', sold: 350 },{ genre: 'Other', sold: 150 },],encode: { x: 'genre', y: 'sold', color: 'genre' },scale: {color: { range: ['#1f77b4', '#ff7f0e', '#2ca02c', '#d62728', '#c564be'] },},});chart.render();
我们可以通过以下的例子看出 band
比例尺和 point
比例尺的区别:
point
比例尺
import { Chart } from '@antv/g2';const chart = new Chart({container: 'container',theme: 'classic',});chart.options({type: 'cell',height: 640,data: {type: 'fetch',value:'https://gw.alipayobjects.com/os/bmw-prod/bd287f2c-3e2b-4d0a-8428-6a85211dce33.json',},encode: { x: 'x', y: 'y', color: 'index' },scale: { x: { type: 'point' } },style: { stroke: '#000', inset: 2 },animate: { enter: { type: 'fadeIn' } },});chart.render();
band
比例尺
import { Chart } from '@antv/g2';const chart = new Chart({container: 'container',theme: 'classic',});chart.options({type: 'cell',height: 640,data: {type: 'fetch',value:'https://gw.alipayobjects.com/os/bmw-prod/bd287f2c-3e2b-4d0a-8428-6a85211dce33.json',},encode: { x: 'x', y: 'y', color: 'index' },scale: { x: { type: 'band' } },style: { stroke: '#000', inset: 2 },animate: { enter: { type: 'fadeIn' } },});chart.render();
将 连续
数据 离散化
为有限类别:
以下是同一份数据分别应用 quantile
比例尺和quantize
比例尺的效果,前者按照数据分布分位数分段,每段数据量相等,后者按数值范围等宽分段。
quantile
比例尺
import { Chart } from '@antv/g2';const chart = new Chart({container: 'container',theme: 'classic',});chart.options({type: 'cell',data: {type: 'fetch',value:'https://gw.alipayobjects.com/os/bmw-prod/89c20fe8-0c6f-46c8-b36b-4cb653dba8ed.json',transform: [{type: 'map',callback: (d) => ({salary: d,}),},],},encode: {y: (_, i) => (i % 5) + 1,x: (_, i) => ((i / 5) | 0) + 1,color: 'salary',},scale: { color: { type: 'quantile', range: ['#eee', 'pink', 'red'] } },style: { stroke: '#000', inset: 2 },animate: { enter: { type: 'fadeIn' } },legend: { color: { length: 400, labelFormatter: '.0s' } },});chart.render();
quantize
比例尺
import { Chart } from '@antv/g2';const chart = new Chart({container: 'container',theme: 'classic',});chart.options({type: 'cell',data: {type: 'fetch',value:'https://gw.alipayobjects.com/os/bmw-prod/89c20fe8-0c6f-46c8-b36b-4cb653dba8ed.json',transform: [{type: 'map',callback: (d) => ({salary: d,}),},],},encode: {y: (_, i) => (i % 5) + 1,x: (_, i) => ((i / 5) | 0) + 1,color: 'salary',},scale: { color: { type: 'quantize', range: ['#eee', 'pink', 'red'] } },style: { stroke: '#000', inset: 2 },animate: { enter: { type: 'fadeIn' } },legend: { color: { length: 400, labelFormatter: '.0s' } },});chart.render();
主要用于 x、y 坐标轴:
主要用于颜色、大小、形状等视觉通道:
例如一个基础的柱状图,x 通道的比例尺默认为 band
,用于实现柱状图分类型坐标轴,y 通道比例尺默认为 linear
,将 y 通道对应的数据列的连续数据映射到柱子的长度,具有视觉属性。
总结一下:
比例尺类型 | 数据类型 | 映射函数 | 主要用途 | 适用场景 |
---|---|---|---|---|
linear | 连续数值 | y = mx + b | 位置、颜色、大小 | 数值型数据的基础映射 |
log | 连续数值 | y = logbase(x) + b | 位置、颜色 | 跨度很大的指数增长数据 |
pow | 连续数值 | y = xk + b | 位置、颜色、大小 | 需要调节数据差异强度 |
sqrt | 连续数值 | y = x0.5 + b | 大小、颜色 | 压缩大数值差异(如面积映射) |
time | 时间数据 | 自动计算时间间隔和刻度 | 时间轴 | 时间序列数据可视化,支持 UTC 和本地时间 |
ordinal | 分类数据 | 一对一映射 | 颜色、形状 | 分类数据的视觉属性映射 |
band | 分类数据 | 等宽区间分配 | x/y 轴位置 | 柱状图、条形图 |
point | 分类数据 | 点位置分配 | x/y 轴位置 | 点图、折线图 |
quantize | 连续数值 | 等宽分段 | 颜色分段 | 数据分布均匀的分段着色 |
quantile | 连续数值 | 等频分段 | 颜色分段 | 数据分布不均的分段着色 |
threshold | 连续数值 | 自定义阈值分段 | 颜色分段 | 按特定阈值分组(如及格线) |
数值型数据
linear
log
pow
(exponent > 1)sqrt
或 pow
(exponent < 1)时间数据
time
分类数据
ordinal
band
point
连续数据离散化
quantize
quantile
threshold
{type: 'linear', // 或 log, pow, sqrt, timedomain: [min, max], // 定义域range: [0, 1], // 值域unknown: undefined, // 未知值的映射值tickMethod: (min, max, count) => [1,2,3,4], // 刻度计算方法round: false, // 是否对输出值进行取整interpolate: (a, b) => (t) => a * (1 - t) + b * t, // 插值方法nice: true, // 是否优化刻度显示}
{type: 'ordinal', // 或 band, pointdomain: ['A', 'B', 'C'], // 类别列表range: ['red', 'green', 'blue'], // 映射值列表unknown: undefined, // 未知值的映射值compare: (a, b) => a.localeCompare(b), // 排序方法}
{type: 'quantize', // 或 quantile, thresholddomain: [0, 100], // 连续数据范围range: ['low', 'medium', 'high'], // 离散类别unknown: undefined, // 未知值的映射值}
G2 内部会根据数据类型以及标记的类型,去推断比例尺的类型、定义域和值域,但是仍然可以指定对应配置。比例尺可以配置在 Mark 层级:
({type: 'interval',scale: {x: { padding: 0.5 },y: {type: 'log', // 指定类型domain: [10, 100], // 指定定义域range: [0, 1], // 指定值域},},});
// API// 第一种方式chart.interval().scale('x', { padding: 0.5 }).scale('y', {type: 'log', // 指定类型domain: [10, 100], // 指定定义域range: [0, 1], // 指定值域});// 第二种方式chart.interval().scale({x: { padding: 0.5 },y: {type: 'log', // 指定类型domain: [10, 100], // 指定定义域range: [0, 1], // 指定值域},});
比例尺也可以配置在 View 层级:
({type: 'view',scale: {x: { padding: 0.5 },y: {type: 'log', // 指定类型domain: [10, 100], // 指定定义域range: [0, 1], // 指定值域},},});
// API 形式// 第一种方式chart.scale('x', { padding: 0.5 }).scale('y', {type: 'log', // 指定类型domain: [10, 100], // 指定定义域range: [0, 1], // 指定值域});// 第二种方式chart.scale({x: { padding: 0.5 },y: {type: 'log', // 指定类型domain: [10, 100], // 指定定义域range: [0, 1], // 指定值域},});
标记的每一个通道都绑定了一个比例尺。该比例尺会对该通道绑定的列数据进行转换,将其从数据范围:定义域(Domain) 转换到视觉范围:值域(Range)。不同类型的比例尺针对不同类型的数据和使用场景。
同一个视图中的标记相同通道的比例尺会默认是同步的:会去同步比例尺的类型,定义域和值域以及其他配置。这意味一个视图中所有的标记都会按照一个同样的尺度去绘制。比如下图中的 LineX 标记虽然没有完整的数据,但是也绘制到了准确的位置,就是因为比例尺同步。
import { Chart } from '@antv/g2';const chart = new Chart({container: 'container',theme: 'classic',});chart.options({type: 'view',children: [{type: 'line',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 },],encode: { x: 'year', y: 'value' },},{ type: 'lineX', data: ['1996'], style: { stroke: 'red', strokeWidth: 2 } },],});chart.render();
如果希望不同步(比如绘制双轴图的时候),就需要设置 scale.independent
为 true
,设置了该属性的比例尺不会和任何比例尺同步。下面的例子中的 interval 和 line 的 y 通道会使用两个不同的比例尺,从而会生成两个不同的坐标轴。
import { Chart } from '@antv/g2';const chart = new Chart({container: 'container',theme: 'classic',});chart.options({type: 'view',data: [{ time: '10:10', call: 4, waiting: 2, people: 2 },{ time: '10:15', call: 2, waiting: 6, people: 3 },{ time: '10:20', call: 13, waiting: 2, people: 5 },{ time: '10:25', call: 9, waiting: 9, people: 1 },{ time: '10:30', call: 5, waiting: 2, people: 3 },{ time: '10:35', call: 8, waiting: 2, people: 1 },{ time: '10:40', call: 13, waiting: 1, people: 2 },],children: [{type: 'interval',encode: { x: 'time', y: 'waiting' },axis: { y: { title: 'Waiting', titleFill: '#5B8FF9' } },},{type: 'line',encode: { x: 'time', y: 'people', shape: 'smooth' },scale: { y: { independent: true } }, // 设置 y 方向比例尺不同步style: { stroke: '#fdae6b', lineWidth: 2 },axis: {y: {position: 'right',grid: null,title: 'People',titleFill: '#fdae6b',},},},],});chart.render();
如果希望比例尺分组同步,可以声明 scale.key
,拥有相同 key 的 scale 会同步。比如下面的 Line 和 Point Mark y 通道的比例尺因为 key 都是 line 所以会同步。
import { Chart } from '@antv/g2';const chart = new Chart({container: 'container',theme: 'classic',});chart.options({type: 'view',data: [{ time: '10:10', call: 4, waiting: 2, people: 2 },{ time: '10:15', call: 2, waiting: 6, people: 3 },{ time: '10:20', call: 13, waiting: 2, people: 5 },{ time: '10:25', call: 9, waiting: 9, people: 1 },{ time: '10:30', call: 5, waiting: 2, people: 3 },{ time: '10:35', call: 8, waiting: 2, people: 1 },{ time: '10:40', call: 13, waiting: 1, people: 2 },],children: [{type: 'interval',encode: { x: 'time', y: 'waiting' },axis: { y: { title: 'Waiting', titleFill: '#5B8FF9' } },},{type: 'line',encode: { x: 'time', y: 'people', shape: 'smooth' },scale: { y: { key: 'line' } }, // 设置 key 为 linestyle: { stroke: '#fdae6b', lineWidth: 2 },axis: {y: {position: 'right',grid: null,title: 'People',titleFill: '#fdae6b',},},},{type: 'point',encode: { x: 'time', y: 'people' },scale: { y: { key: 'line' } }, // 设置 key 为 linestyle: { stroke: '#fdae6b', lineWidth: 2 },},],});chart.render();
比例尺会可以配置在视图层级,并且会传递给 children
指定的标记,如果该标记对应的通道没有设置比例尺,就设置,否则没有影响。在不绘制多轴图的情况下,比例尺是可以设置在视图层级的。
import { Chart } from '@antv/g2';const chart = new Chart({container: 'container',theme: 'classic',});chart.options({type: 'view',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 },],encode: { x: 'year', y: 'value' },scale: { y: { nice: true } },children: [{ type: 'line' }, { type: 'point' }],});chart.render();