Loading...
The band scale is a special type of ordinal scale that maps discrete domains (such as categories, names, etc.) to continuous numerical ranges, allocating equal-width intervals (bands) for each category. Unlike regular ordinal scales, band scales not only focus on point positions but also consider the interval width occupied by each category.
Key characteristics of band scales:
In G2, the band scale is the default x-axis scale for bar charts (interval marks), automatically handling the mapping and layout of categorical data.
Property | Description | Type | Default | Required |
---|---|---|---|---|
domain | Sets the domain array, i.e., possible values of input data | number[] | string[] | Date[] | [] | |
range | Sets the range of data mapping, i.e., the output range | number[] | string[] | [0, 1] | |
unknown | Return value for undefined , NaN , null empty values | any | undefined | |
round | Whether to round the output values | boolean | false | |
paddingInner | Sets inner spacing between categories, in range [0, 1], larger values mean larger spacing | number | 0 | |
paddingOuter | Sets outer spacing at both ends, in range [0, 1], larger values mean larger spacing | number | 0 | |
padding | Shortcut to set both paddingInner and paddingOuter | number | 0 | |
align | Alignment, in range [0, 1], 0 means left-aligned, 0.5 means centered, 1 means right-aligned | number | 0.5 | |
compare | Sorting function for domain mapping | (a: string or number, b: string or number) => number | undefined | |
flex | Sets width allocation ratio for each category | number[] | [] |
The band scale divides the continuous range into equal-width intervals, with each interval corresponding to a discrete value in the domain. The following diagram illustrates the layout principles of the band scale:
|<------------------------------------------- range ------------------------------------------->|| | | | | | ||<--step*PO-->|<----bandWidth---->|<--step*PI-->|<----bandWidth---->|<--step*PI-->|<--step*PO-->|| | ***************** | | ***************** | | || | ******* A ******* | | ******* B ******* | | || | ***************** | | ***************** | | || |<--------------step------------->| ||-----------------------------------------------------------------------------------------------|
Where:
The most common application of band scales is in bar charts. By setting padding
, you can control the spacing between bars:
import { Chart } from '@antv/g2';const chart = new Chart({container: 'container',autoFit: true,});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: {x: {type: 'band',padding: 0.5 // Set spacing between bars}}});chart.render();
In grouped bar charts, band scales work together with dodgeX transform to create grouping effects:
import { Chart } from '@antv/g2';const chart = new Chart({container: 'container',autoFit: true,});chart.options({type: 'interval',data: [{ month: 'Jan', value: 86.5, type: 'Precipitation' },{ month: 'Feb', value: 86.5, type: 'Precipitation' },{ month: 'Mar', value: 86.5, type: 'Precipitation' },{ month: 'Apr', value: 86.5, type: 'Precipitation' },{ month: 'May', value: 86.5, type: 'Precipitation' },{ month: 'Jun', value: 86.5, type: 'Precipitation' },{ month: 'Jan', value: 30.5, type: 'Evaporation' },{ month: 'Feb', value: 30.5, type: 'Evaporation' },{ month: 'Mar', value: 30.5, type: 'Evaporation' },{ month: 'Apr', value: 30.5, type: 'Evaporation' },{ month: 'May', value: 30.5, type: 'Evaporation' },{ month: 'Jun', value: 30.5, type: 'Evaporation' },],encode: {x: 'month',y: 'value',color: 'type'},transform: [{ type: 'dodgeX' }],scale: {x: {type: 'band',padding: 0.2 // Set spacing between groups}}});chart.render();
Using the flex
property allows setting different width ratios for different categories:
import { Chart } from '@antv/g2';const chart = new Chart({container: 'container',autoFit: true,});chart.options({type: 'interval',data: [{ country: 'USA', value: 12394, gdp: 21.4 },{ country: 'China', value: 9608, gdp: 14.7 },{ country: 'Japan', value: 4731, gdp: 5.0 },{ country: 'Germany', value: 3861, gdp: 4.2 },{ country: 'UK', value: 2678, gdp: 2.9 },],encode: {x: 'country',y: 'value',color: 'country'},scale: {x: {type: 'band',padding: 0.4,flex: [2.14, 1.47, 0.5, 0.42, 0.29] // Set different widths based on GDP}}});chart.render();
By transposing the coordinate system, you can create horizontal bar charts where band scales still apply:
import { Chart } from '@antv/g2';const chart = new Chart({container: 'container',autoFit: true,});chart.options({type: 'interval',coordinate: { transform: [{ type: 'transpose' }] },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: {x: {type: 'band',padding: 0.5}}});chart.render();
Using stackY
transform creates stacked bar charts that show cumulative effects of different parts:
import { Chart } from '@antv/g2';const chart = new Chart({container: 'container',autoFit: true,});chart.options({type: 'interval',data: [{ quarter: 'Q1', department: 'Sales', value: 120 },{ quarter: 'Q1', department: 'Marketing', value: 100 },{ quarter: 'Q1', department: 'Engineering', value: 80 },{ quarter: 'Q2', department: 'Sales', value: 140 },{ quarter: 'Q2', department: 'Marketing', value: 110 },{ quarter: 'Q2', department: 'Engineering', value: 90 },{ quarter: 'Q3', department: 'Sales', value: 160 },{ quarter: 'Q3', department: 'Marketing', value: 95 },{ quarter: 'Q3', department: 'Engineering', value: 120 },{ quarter: 'Q4', department: 'Sales', value: 180 },{ quarter: 'Q4', department: 'Marketing', value: 100 },{ quarter: 'Q4', department: 'Engineering', value: 130 },],encode: {x: 'quarter',y: 'value',color: 'department'},transform: [{ type: 'stackY' }],scale: {x: {type: 'band',padding: 0.3}}});chart.render();
Automatically adjusts bar width based on specified field values, suitable for representing weight or proportional relationships:
import { Chart } from '@antv/g2';const chart = new Chart({container: 'container',autoFit: true,});chart.options({type: 'interval',data: [{ region: 'East', sales: 8500, population: 2.4 },{ region: 'South', sales: 6200, population: 1.8 },{ region: 'North', sales: 7800, population: 2.1 },{ region: 'Southwest', sales: 4500, population: 1.2 },{ region: 'Northeast', sales: 3200, population: 0.9 },{ region: 'Northwest', sales: 2800, population: 0.7 },],encode: {x: 'region',y: 'sales',color: 'region'},transform: [{ type: 'flexX', field: 'population' }], // Adjust bar width based on population datascale: {x: {type: 'band',padding: 0.2}}});chart.render();
When handling time data, band scales can effectively handle the visualization of time intervals:
import { Chart } from '@antv/g2';const chart = new Chart({container: 'container',autoFit: true,});chart.options({type: 'interval',data: [{ month: '2024-01', sales: 1200 },{ month: '2024-02', sales: 1100 },{ month: '2024-03', sales: 1350 },{ month: '2024-04', sales: 1280 },{ month: '2024-05', sales: 1400 },{ month: '2024-06', sales: 1520 },{ month: '2024-07', sales: 1680 },{ month: '2024-08', sales: 1590 },{ month: '2024-09', sales: 1450 },{ month: '2024-10', sales: 1380 },{ month: '2024-11', sales: 1250 },{ month: '2024-12', sales: 1600 }],encode: {x: 'month',y: 'sales',color: (d) => d.sales > 1500 ? 'high' : d.sales > 1300 ? 'medium' : 'low'},scale: {x: {type: 'band',padding: 0.1},color: {domain: ['low', 'medium', 'high'],range: ['#faad14', '#1890ff', '#52c41a']}}});chart.render();
Displaying categorical data with hierarchical structure:
import { Chart } from '@antv/g2';const chart = new Chart({container: 'container',autoFit: true,});chart.options({type: 'interval',data: [{ category: 'Clothing-Men', subcategory: 'Shirts', value: 850 },{ category: 'Clothing-Men', subcategory: 'Pants', value: 750 },{ category: 'Clothing-Men', subcategory: 'Jackets', value: 650 },{ category: 'Clothing-Women', subcategory: 'Dresses', value: 950 },{ category: 'Clothing-Women', subcategory: 'Tops', value: 800 },{ category: 'Clothing-Women', subcategory: 'Skirts', value: 700 },{ category: 'Electronics-Phones', subcategory: 'iPhone', value: 1200 },{ category: 'Electronics-Phones', subcategory: 'Huawei', value: 1100 },{ category: 'Electronics-Phones', subcategory: 'Xiaomi', value: 900 },{ category: 'Electronics-Computers', subcategory: 'Laptops', value: 1500 },{ category: 'Electronics-Computers', subcategory: 'Desktops', value: 800 },{ category: 'Electronics-Computers', subcategory: 'Tablets', value: 600 }],encode: {x: 'category',y: 'value',color: 'subcategory'},transform: [{ type: 'dodgeX' }],scale: {x: {type: 'band',padding: 0.4, // Larger spacing to distinguish different main categoriespaddingInner: 0.3, // Inner spacingpaddingOuter: 0.1 // Outer spacing}}});chart.render();
Using paddingInner and paddingOuter to precisely control spacing, suitable for comparative analysis:
import { Chart } from '@antv/g2';const chart = new Chart({container: 'container',autoFit: true,});chart.options({type: 'interval',data: [{ product: 'Product A', current: 320, target: 400 },{ product: 'Product B', current: 280, target: 350 },{ product: 'Product C', current: 410, target: 450 },{ product: 'Product D', current: 180, target: 250 },{ product: 'Product E', current: 350, target: 380 }].flatMap(d => [{ product: d.product, type: 'Current Sales', value: d.current },{ product: d.product, type: 'Target Sales', value: d.target }]),encode: {x: 'product',y: 'value',color: 'type'},transform: [{ type: 'dodgeX' }],scale: {x: {type: 'band',paddingInner: 0.2, // Smaller spacing within groupspaddingOuter: 0.3 // Larger spacing between groups},color: {domain: ['Current Sales', 'Target Sales'],range: ['#1890ff', '#52c41a']}}});chart.render();
Combining compare function to sort data with different bar width strategies:
import { Chart } from '@antv/g2';const data = [{ brand: 'Apple', market_share: 23.4, revenue: 365 },{ brand: 'Samsung', market_share: 20.1, revenue: 220 },{ brand: 'Huawei', market_share: 15.8, revenue: 180 },{ brand: 'Xiaomi', market_share: 12.3, revenue: 120 },{ brand: 'OPPO', market_share: 8.9, revenue: 95 },{ brand: 'vivo', market_share: 7.2, revenue: 85 },{ brand: 'Others', market_share: 12.3, revenue: 150 }];const chart = new Chart({container: 'container',autoFit: true,});chart.options({type: 'interval',data,encode: {x: 'brand',y: 'market_share',color: 'brand'},scale: {x: {type: 'band',padding: 0.2,compare: (a, b) => {// Sort by market share in descending orderconst dataA = data.find(d => d.brand === a);const dataB = data.find(d => d.brand === b);return (dataB?.market_share || 0) - (dataA?.market_share || 0);},flex: [2.34, 2.01, 1.58, 1.23, 0.89, 0.72, 1.23] // Set width based on market share}}});chart.render();
Displaying step-by-step cumulative changes in values:
import { Chart } from '@antv/g2';// Waterfall chart data processingconst rawData = [{ name: 'Initial Balance', value: 1000, type: 'start' },{ name: 'Revenue Increase', value: 500, type: 'positive' },{ name: 'Cost Expense', value: -200, type: 'negative' },{ name: 'Tax Expense', value: -150, type: 'negative' },{ name: 'Other Income', value: 100, type: 'positive' },{ name: 'Final Balance', value: 1250, type: 'end' }];// Calculate cumulative valueslet cumulative = 0;const data = rawData.map((d, i) => {if (d.type === 'start' || d.type === 'end') {const result = { ...d, start: 0, end: d.value };cumulative = d.value;return result;} else {const start = cumulative;cumulative += d.value;return { ...d, start, end: cumulative };}});const chart = new Chart({container: 'container',autoFit: true,});chart.options({type: 'interval',data: data.flatMap(d => [{ name: d.name, value: d.end - d.start, position: d.start, type: d.type }]),encode: {x: 'name',y: ['position', (d) => d.position + d.value],color: 'type'},scale: {x: {type: 'band',padding: 0.4},color: {domain: ['start', 'positive', 'negative', 'end'],range: ['#722ed1', '#52c41a', '#ff4d4f', '#1890ff']}}});chart.render();
Using band scales with faceted layout to display multi-dimensional data:
import { Chart } from '@antv/g2';const chart = new Chart({container: 'container',autoFit: true,});chart.options({type: 'facetRect',data: [{ region: 'North', quarter: 'Q1', product: 'Product A', sales: 120 },{ region: 'North', quarter: 'Q1', product: 'Product B', sales: 100 },{ region: 'North', quarter: 'Q1', product: 'Product C', sales: 80 },{ region: 'North', quarter: 'Q2', product: 'Product A', sales: 140 },{ region: 'North', quarter: 'Q2', product: 'Product B', sales: 110 },{ region: 'North', quarter: 'Q2', product: 'Product C', sales: 90 },{ region: 'South', quarter: 'Q1', product: 'Product A', sales: 150 },{ region: 'South', quarter: 'Q1', product: 'Product B', sales: 130 },{ region: 'South', quarter: 'Q1', product: 'Product C', sales: 110 },{ region: 'South', quarter: 'Q2', product: 'Product A', sales: 170 },{ region: 'South', quarter: 'Q2', product: 'Product B', sales: 140 },{ region: 'South', quarter: 'Q2', product: 'Product C', sales: 120 }],encode: { x: 'region', y: 'quarter' },children: [{type: 'interval',encode: {x: 'product',y: 'sales',color: 'product'},scale: {x: {type: 'band',padding: 0.3}}}]});chart.render();
You can adjust the spacing between bars by setting the padding
property, which indirectly adjusts the width of the bars. The larger the padding
value, the narrower the bars; the smaller the value, the wider the bars.
chart.interval().encode('x', 'type').encode('y', 'sale').scale('x', {type: 'band',padding: 0.5, // Value range is [0, 1]});
bandWidth = 0
, suitable for scatter plots and other charts that only need point positionsThere are two methods:
flex
property to set different width ratios for different categoriesflexX
transform to automatically set bar width based on specified field values// Method 1: Using flex propertychart.interval().encode('x', 'country').encode('y', 'value').scale('x', {type: 'band',flex: [2, 1, 3, 1.5] // Manually set width ratios});// Method 2: Using flexX transformchart.interval().encode('x', 'country').encode('y', 'value').transform({ type: 'flexX', field: 'gdp' }); // Automatically set width based on gdp field