logo

G2

  • Chart Gallery
  • Docs
  • Examples
  • Theme
  • Ecosystem
  • Productsantv logo arrow
  • 5.3.3
  • Get Started
  • Introduction
    • What is G2
    • Using in Frontend Frameworks
    • Experimental Spec API
  • Chart API
  • Core Concepts
    • Chart
      • Components of G2 Charts
      • How to Use Charts
    • Mark
      • Overview
      • area
      • box
      • boxplot
      • cell
      • chord
      • density
      • gauge
      • heatmap
      • image
      • interval
      • line
      • lineX
      • lineY
      • link
      • liquid
      • sunburst
      • point
      • polygon
      • range
      • rangeX
      • rangeY
      • rect
      • shape
      • text
      • vector
      • connector
      • wordCloud
    • View
    • Data
      • Overview
      • custom
      • ema
      • fetch
      • filter
      • fold
      • inline
      • join
      • kde
      • log
      • map
      • pick
      • rename
      • slice
      • sort
      • sortBy
    • Encode
    • Scale
      • Overview
      • band
      • linear
      • log
      • ordinal
      • point
      • quantile
      • quantize
      • sqrt
      • threshold
      • time
      • pow
    • Transform
      • Overview
      • bin
      • binX
      • diffY
      • dodgeX
      • flexX
      • group
      • groupColor
      • groupX
      • groupY
      • jitter
      • jitterX
      • jitterY
      • normalizeY
      • pack
      • sample
      • select
      • selectX
      • selectY
      • sortColor
      • sortX
      • sortY
      • stackEnter
      • stackY
      • symmetryY
    • Coordinate
      • Overview
      • fisheye
      • parallel
      • polar
      • theta
      • transpose
      • radial
      • cartesian3D
      • helix
    • Style
    • Animate
      • Overview
      • fadeIn
      • fadeOut
      • growInX
      • growInY
      • morphing
      • pathIn
      • scaleInX
      • scaleInY
      • scaleOutX
      • scaleOutY
      • waveIn
      • zoomIn
      • zoomOut
    • State
    • Interaction
      • Overview
      • brushAxisHighlight
      • brushHighlight
      • brushXHighlight
      • brushYHighlight
      • brushFilter
      • brushXFilter
      • brushYFilter
      • chartIndex
      • elementHighlight
      • elementHighlightByColor
      • elementHighlightByX
      • elementSelect
      • elementSelectByColor
      • elementSelectByX
      • legendFilter
      • legendHighlight
      • poptip
      • scrollbarFilter
      • sliderFilter
      • fisheye
    • Composition
      • Overview
      • facetCircle
      • facetRect
      • repeatMatrix
      • spaceFlex
      • spaceLayer
      • timingKeyframe
    • Theme
      • Overview
      • academy
      • classic
      • classicDark
    • Events
    • Color Mapping
  • Chart Component
    • Title
    • Axis
    • Legend
    • Scrollbar
    • Slider
    • Tooltip
    • Data Label
  • Extra Topics
    • Graph
      • forceGraph
      • pack
      • sankey
      • tree
      • treemap
    • Geo
      • geoPath
      • geoView
    • 3D
      • Draw 3D Chart
      • point3D
      • line3D
      • interval3D
      • surface3D
    • Plugin
      • renderer
      • rough
      • lottie
      • a11y
    • Package on demand
    • Set pattern
    • Server-Side Rendering (SSR)
    • Spec Function Expression Support (Available in 5.3.0)
  • Whats New
    • New Version Features
    • Migration from v4 to v5
  • Frequently Asked Questions (FAQ)

Legend

Previous
Axis
Next
Scrollbar

Resource

Ant Design
Galacea Effects
Umi-React Application Framework
Dumi-Component doc generator
ahooks-React Hooks Library

Community

Ant Financial Experience Tech
seeconfSEE Conf-Experience Tech Conference

Help

GitHub
StackOverflow

more productsMore Productions

Ant DesignAnt Design-Enterprise UI design language
yuqueYuque-Knowledge creation and Sharing tool
EggEgg-Enterprise-class Node development framework
kitchenKitchen-Sketch Tool set
GalaceanGalacean-Interactive solution
xtechLiven Experience technology
© Copyright 2025 Ant Group Co., Ltd..备案号:京ICP备15032932号-38

Loading...

Overview

In G2, Legend is an auxiliary element of the chart that uses color, size, and shape to distinguish different data types and is used for data filtering in charts. It can be understood as the visualization of scales corresponding to non-spatial channels (color, opacity, size, shape). G2 automatically generates different legends based on the set graphic attribute mappings and data types. When a variable corresponds to multiple graphic attributes, G2 merges the legends to achieve simplification.

The four non-spatial channels color, opacity, size, and shape automatically generate different legends when they receive parameters that are determined to be fields from the data source:

Visual ChannelDescription
colorGenerate legend based on different colors
opacityGenerate legend based on different opacity
sizeGenerate legend based on different sizes
shapeGenerate legend based on different shapes

Components

legend-overview

Usage

There are two ways to configure legends:

First, pass a boolean to set whether to display the legend.

({
type: 'interval',
legend: false; // Hide all legends
})
({
type: 'interval',
legend: {color: false}; // Hide the legend for the color channel
})

Second, pass legendOption to configure the legend as a whole.

({
type: 'interval',
legend: {
color: {},
size: {},
},
});

Configuration Levels

Legends can be configured at the Mark level. In G2, each mark has its own legend. If the scales corresponding to marks are synchronized, the legends are also merged.

({
type: 'interval',
legend: {
color: {},
size: {},
},
});

Legends can also be configured at the View level. Legends have transitivity. Legends declared on a view are passed to the marks declared in children. If the mark has a legend for the corresponding channel, they are merged; otherwise, they don't affect each other.

({
type: 'view',
legend: {
color: {},
size: {},
},
});

Configuration Options

Legends in G2 are divided into continuous legends and categorical legends. Due to the different structures of these two types of legends, their configuration options also differ.

Some configuration options are scoped to categorical legends and continuous legends: Categorical LegendContinuous Legend

PropertyDescriptionTypeDefault ValueRequired
orientationLegend orientation, which is the scroll direction for categorical legendshorizontal | verticalhorizontal
positionPosition of the legendtop | right | left | bottomtop
layoutAdjust the flex layout of the legendlayoutSee layout
sizeSize of the legend componentnumber-
lengthLength of the legend componentnumber-
crossPaddingDistance from legend to chart areanumber12
orderOrder of legend in layoutnumber1
titleConfigure legend titletitleSee title
cols Categorical LegendSpecify the number of legend items displayed per row; empty means no limit on columnsnumber-
colPadding Categorical LegendSpecify horizontal spacing between legend itemsnumber12
rowPadding Categorical LegendSpecify vertical spacing between legend itemsnumber8
maxRows Categorical LegendSpecify maximum number of rows for legendnumber3
maxCols Categorical LegendSpecify maximum number of columns for legendnumber3
itemMarker Categorical LegendConfigure legend item markeritemMarkerSee itemMarker
itemLabel Categorical LegendConfigure legend item label textitemLabelSee itemLabel
itemValue Categorical LegendConfigure legend item valueitemValueSee itemValue
itemBackground Categorical LegendConfigure legend item backgrounditemBackgroundSee itemBackground
itemWidth Categorical LegendConfigure legend item widthnumber-
itemSpan Categorical LegendConfigure space allocation for legend item marker, label, and valuenumber | number[][1, 1, 1]
itemSpacing Categorical LegendConfigure spacing between legend item marker, label, and valuenumber | number[][8, 8]
nav Categorical LegendConfigure legend navigatornavSee nav
color Continuous LegendConfigure color band colors for continuous legendstring[] | d3-interpolate-
block Continuous LegendWhether continuous legend displays by intervalsbooleanfalse
type Continuous LegendConfigure type of continuous legendsize |colorcolor
ribbon Continuous LegendConfigure color band of continuous legendribbonSee ribbon
handle Continuous LegendConfigure slider handle of continuous legendhandleSee handle
label Continuous LegendConfigure labels/tick values of continuous legendlabelSee label
indicator Continuous LegendConfigure indicator of continuous legendindicatorSee indicator

orientation

optional horizontal | vertical

Legend orientation, which is the scroll direction for categorical legends. Default is horizontal.

position

optional "top" | "right" | "left" | "bottom"

Position of the legend. Default is top.

const { Chart } = G2;
const chart = new Chart({
container: 'container',
});
const container = chart.getContainer();
const positionList = ['top', 'right', 'left', 'bottom'];
const positionMap = positionList.map((p) => {
return {
label: p,
value: p,
};
});
chart.options({
type: 'interval',
data: [
{ name: 'London', month: 'Jan.', avgRainfall: 18.9 },
{ name: 'London', month: 'Feb.', avgRainfall: 28.8 },
{ name: 'London', month: 'Mar.', avgRainfall: 39.3 },
{ name: 'London', month: 'Apr.', avgRainfall: 81.4 },
{ name: 'London', month: 'May', avgRainfall: 47 },
{ name: 'London', month: 'Jun.', avgRainfall: 20.3 },
{ name: 'London', month: 'Jul.', avgRainfall: 24 },
{ name: 'London', month: 'Aug.', avgRainfall: 35.6 },
{ name: 'Berlin', month: 'Jan.', avgRainfall: 12.4 },
{ name: 'Berlin', month: 'Feb.', avgRainfall: 23.2 },
{ name: 'Berlin', month: 'Mar.', avgRainfall: 34.5 },
{ name: 'Berlin', month: 'Apr.', avgRainfall: 99.7 },
{ name: 'Berlin', month: 'May', avgRainfall: 52.6 },
{ name: 'Berlin', month: 'Jun.', avgRainfall: 35.5 },
{ name: 'Berlin', month: 'Jul.', avgRainfall: 37.4 },
{ name: 'Berlin', month: 'Aug.', avgRainfall: 42.4 },
],
encode: { x: 'month', y: 'avgRainfall', color: 'name' },
transform: [{ type: 'dodgeX' }],
});
const handleSetPosition = (position) => {
chart.options({
legend: {
color: {
position,
},
},
});
chart.render(); // Re-render the chart
};
// Insert Position selector
const selectorContainer = document.createElement('div');
selectorContainer.textContent = 'Select legend position ';
const selector = document.createElement('select');
selector.innerHTML = positionMap.map(
(position, index) =>
`<option value="${position.value}" ${index === 0 ? 'selected' : ''}>${
position.label
}</option>`,
);
selector.onchange = (e) => {
handleSetPosition(e.target.value);
};
selectorContainer.appendChild(selector);
container.insertBefore(selectorContainer, container.childNodes[0]);
chart.render();

layout

LegendLayoutCfg optional

Legend component supports adjusting its position on the canvas through the layout property. Legend layout uses the Flexbox layout model, where position determines the legend's basic position on the canvas, and layout further controls the precise alignment within the legend.

Concept of Layout Axes

The key to understanding legend layout is mastering the concepts of main axis and cross axis:

positionDefault flexDirectionMain Axis DirectionCross Axis DirectionMain Axis MeaningCross Axis Meaning
toprowHorizontal →Vertical ↓Legend items left-rightLegend area top-bottom
bottomrowHorizontal →Vertical ↑Legend items left-rightLegend area top-bottom
leftcolumnVertical ↓Horizontal →Legend items top-bottomLegend area left-right
rightcolumnVertical ↓Horizontal ←Legend items top-bottomLegend area left-right

Configuration Properties

LegendLayoutCfg configuration:

PropertyDescriptionTypeDefault ValueAffectsRequired
justifyContentMain axis alignflex-start | flex-end | centerflex-startMain
alignItemsCross axis alignflex-start | flex-end | centerflex-startCross
flexDirectionMain axis directionrow | columnrow when position is top and bottom, column otherwise-

position + layout Combination Configuration

Through the combination of position and layout, precise positioning of legends can be achieved:

// 1. Top center legend
({
legend: {
color: {
position: 'top', // Legend at top
layout: {
justifyContent: 'center', // Main axis (horizontal) center
},
},
},
});
// 2. Right vertical center legend
({
legend: {
color: {
position: 'right', // Legend at right
layout: {
justifyContent: 'center', // Main axis (vertical) center
},
},
},
});
// 3. Bottom right-aligned legend
({
legend: {
color: {
position: 'bottom', // Legend at bottom
layout: {
justifyContent: 'flex-end', // Main axis (horizontal) right-align
},
},
},
});
// 4. Left bottom-aligned legend
({
legend: {
color: {
position: 'left', // Legend at left
layout: {
justifyContent: 'flex-end', // Main axis (vertical) bottom-align
},
},
},
});

Interactive Example

Through the interactive example below, you can intuitively see the effects of different position and layout combinations:

import { Chart } from '@antv/g2';
const chart = new Chart({
container: 'container',
height: 400,
width: 600,
});
const container = chart.getContainer();
const data = [
{ genre: 'Sports', sold: 50 },
{ genre: 'Strategy', sold: 115 },
{ genre: 'Action', sold: 120 },
{ genre: 'Shooter', sold: 350 },
{ genre: 'Other', sold: 150 },
];
chart.options({
type: 'interval',
data,
encode: { x: 'genre', y: 'sold', color: 'genre' },
legend: {
color: {
position: 'top',
layout: {
justifyContent: 'flex-start',
alignItems: 'flex-start',
},
},
},
});
const positionOptions = [
{ value: 'top', label: 'Top (top)' },
{ value: 'bottom', label: 'Bottom (bottom)' },
{ value: 'left', label: 'Left (left)' },
{ value: 'right', label: 'Right (right)' },
];
const justifyOptions = [
{ value: 'flex-start', label: 'Start align (flex-start)' },
{ value: 'center', label: 'Center align (center)' },
{ value: 'flex-end', label: 'End align (flex-end)' },
];
const alignOptions = [
{ value: 'flex-start', label: 'Start align (flex-start)' },
{ value: 'center', label: 'Center align (center)' },
{ value: 'flex-end', label: 'End align (flex-end)' },
];
// Create control panel
const controlPanel = document.createElement('div');
controlPanel.style.cssText = `
margin-bottom: 16px;
padding: 16px;
background: #f5f5f5;
border-radius: 8px;
display: grid;
grid-template-columns: 1fr 1fr 1fr;
gap: 16px;
`;
// Position selector
const positionContainer = document.createElement('div');
positionContainer.innerHTML = `
<label style="display: block; margin-bottom: 8px; font-weight: bold;">
Legend Position (position):
</label>
`;
const positionSelect = document.createElement('select');
positionSelect.style.cssText = 'width: 100%; padding: 4px;';
positionSelect.innerHTML = positionOptions
.map(
(option, index) =>
`<option value="${option.value}" ${index === 0 ? 'selected' : ''}>${
option.label
}</option>`,
)
.join('');
positionContainer.appendChild(positionSelect);
// JustifyContent selector
const justifyContainer = document.createElement('div');
justifyContainer.innerHTML = `
<label style="display: block; margin-bottom: 8px; font-weight: bold;">
Main Axis Align (justifyContent):
</label>
`;
const justifySelect = document.createElement('select');
justifySelect.style.cssText = 'width: 100%; padding: 4px;';
justifySelect.innerHTML = justifyOptions
.map(
(option, index) =>
`<option value="${option.value}" ${index === 0 ? 'selected' : ''}>${
option.label
}</option>`,
)
.join('');
justifyContainer.appendChild(justifySelect);
// AlignItems selector
const alignContainer = document.createElement('div');
alignContainer.innerHTML = `
<label style="display: block; margin-bottom: 8px; font-weight: bold;">
Cross Axis Align (alignItems):
</label>
`;
const alignSelect = document.createElement('select');
alignSelect.style.cssText = 'width: 100%; padding: 4px;';
alignSelect.innerHTML = alignOptions
.map(
(option, index) =>
`<option value="${option.value}" ${index === 0 ? 'selected' : ''}>${
option.label
}</option>`,
)
.join('');
alignContainer.appendChild(alignSelect);
controlPanel.appendChild(positionContainer);
controlPanel.appendChild(justifyContainer);
controlPanel.appendChild(alignContainer);
// Status display
const statusDiv = document.createElement('div');
statusDiv.style.cssText = `
margin-bottom: 16px;
padding: 12px;
background: #e6f7ff;
border: 1px solid #91d5ff;
border-radius: 4px;
font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', monospace;
font-size: 12px;
`;
const updateChart = () => {
const position = positionSelect.value;
const justifyContent = justifySelect.value;
const alignItems = alignSelect.value;
chart.options({
legend: {
color: {
position,
layout: {
justifyContent,
alignItems,
},
},
},
});
chart.render();
// Update status display
const isHorizontal = position === 'top' || position === 'bottom';
const mainAxis = isHorizontal ? 'Horizontal' : 'Vertical';
const crossAxis = isHorizontal ? 'Vertical' : 'Horizontal';
statusDiv.innerHTML = `
Current Config: position: "${position}", justifyContent: "${justifyContent}", alignItems: "${alignItems}"<br>
Main Axis Direction: ${mainAxis} | Cross Axis Direction: ${crossAxis}<br>
Main Axis Align: ${justifyContent} | Cross Axis Align: ${alignItems}
`;
};
// Bind events
positionSelect.addEventListener('change', updateChart);
justifySelect.addEventListener('change', updateChart);
alignSelect.addEventListener('change', updateChart);
// Insert control panel
container.insertBefore(controlPanel, container.firstChild);
container.insertBefore(statusDiv, container.lastChild);
// Initial render
updateChart();

Common Layout Scenarios

Here are some common legend layout requirements and their configuration methods:

// 🎯 Scenario 1: Top center display, suitable for dashboards
({
legend: {
color: {
position: 'top',
layout: {
justifyContent: 'center', // Horizontal center
},
},
},
});
// 🎯 Scenario 2: Right vertical center, suitable for detailed charts
({
legend: {
color: {
position: 'right',
layout: {
justifyContent: 'center', // Vertical center
},
},
},
});
// 🎯 Scenario 3: Bottom left-aligned, space-saving
({
legend: {
color: {
position: 'bottom',
layout: {
justifyContent: 'flex-start', // Left-aligned
},
},
},
});
// 🎯 Scenario 4: Right top-aligned, compact layout
({
legend: {
color: {
position: 'right',
layout: {
justifyContent: 'flex-start', // Top-aligned
},
},
},
});
// 🎯 Scenario 5: Left bottom-aligned, aligned with chart bottom
({
legend: {
color: {
position: 'left',
layout: {
justifyContent: 'flex-end', // Bottom-aligned
},
},
},
});

💡 Layout Tips

  • Horizontal center: position: 'top' + justifyContent: 'center'
  • Vertical center: position: 'right' + justifyContent: 'center'
  • Compact layout: Use flex-start to keep legend close to chart

size

number optional

Size of the Legend component, used for G2 internal layout calculation and space allocation. Affects the component size on the cross axis:

  • For horizontal layout legends (position: 'top' or 'bottom'), controls the legend height
  • For vertical layout legends (position: 'left' or 'right'), controls the legend width

Manual configuration will cause G2's internal calculation logic to fail, requiring you to configure margin, padding, inset, etc. yourself. See Chart Layout. Not recommended for configuration unless customization scenarios are needed.

length

number optional

Length of the Legend component, used for G2 internal layout calculation and space allocation. Affects the component size on the main axis:

  • For horizontal layout legends (position: 'top' or 'bottom'), controls the legend width
  • For vertical layout legends (position: 'left' or 'right'), controls the legend height
legend-overviewlegend-overview

crossPadding

number optional

Distance between the Legend component and the chart. Default is 12.

order

number optional

Sort order of the Legend component during layout. Default is 1. All components in G2 have default sort sizes, with smaller values closer to the chart area. For example, the Title component has a default sort of 2, which is farther from the chart area than the Legend component with default sort of 1.

title

LegendTitleCfg optional

Legend title configuration. LegendTitleCfg configuration:

PropertyDescriptionTypeDefault ValueRequired
titleClose title or set title contentfalse | stringtrue for continuous legend, false for categorical
titleSpacingSpacing from title to content areanumber | number[]4
titleInsetTitle text inner paddingnumber | number[]0
titlePositionTitle position. Can use abbreviated form, e.g., i for inner, lt for left-toptop | bottom |left |right |left-top |left-bottom |right-top |right-bottom |inner-
titleFontSizeTitle text sizenumber | (datum, index, data) => number12
titleFontFamilyTitle text fontstring | (datum, index, data) => string-
titleFontWeightTitle font weightnumber | (datum, index, data) => numbernormal
titleLineHeightTitle text line heightnumber | (datum, index, data) => number-
titleTextAlignSet current text alignment for title contentcenter | end | left | right | startstart
titleTextBaselineSet current text baseline used when drawing title texttop | middle | bottom | alphabetic | hangingbottom
titleFillTitle text colorstring | (datum, index, data) => string#1D2129
titleFillOpacityTitle text color opacitynumber | (datum, index, data) => number0.65
titleStrokeTitle text stroke colorstring | (datum, index, data) => string-
titleStrokeOpacityTitle text stroke color opacitynumber | (datum, index, data) => number-
titleLineWidthTitle stroke widthnumber | (datum, index, data) => number-
titleLineDashTitle text stroke dash configuration. First value is dash segment length, second is gap length. Setting lineDash to [0,0] means no stroke.[number,number] | (datum, index, data) => [number , number]-
titleOpacityTitle text overall opacitynumber | (datum, index, data) => number-
titleShadowColorTitle text shadow colorstring | (datum, index, data) => string-
titleShadowBlurTitle text shadow Gaussian blur coefficientnumber | (datum, index, data) => number-
titleShadowOffsetXTitle shadow horizontal offsetnumber | (datum, index, data) => number-
titleShadowOffsetYTitle shadow vertical offsetnumber | (datum, index, data) => number-
titleCursorTitle cursor style. Same as CSS cursor style.string | (datum, index, data) => stringdefault

In the Legend component, when configuring the title, it's not configured as an object, but with the title prefix plus property:

({
legend: {
size: {
title: 'Legend Title',
titleSpacing: 0,
titleInset: 0,
titlePosition: 't',
titleFontSize: 16,
titleFontFamily: 'sans-serif',
titleFontWeight: 500,
titleLineHeight: 20,
titleTextAlign: 'center',
titleTextBaseline: 'middle',
titleFill: '#000',
titleFillOpacity: 0.9,
titleStroke: '#DAF5EC',
titleStrokeOpacity: 0.9,
titleLineWidth: 2,
titleLineDash: [4, 8],
titleOpacity: 1,
titleShadowColor: '#d3d3d3',
titleShadowBlur: 10,
titleShadowOffsetX: 10,
titleShadowOffsetY: 10,
titleCursor: 'pointer',
},
},
});

Try it:

import { Chart } from '@antv/g2';
const chart = new Chart({ container: 'container', height: 300 });
chart.options({
type: 'legends',
title: 'Legend Title',
titleSpacing: 0,
titleInset: 0,
titlePosition: 't',
titleFontSize: 16,
titleFontFamily: 'sans-serif',
titleFontWeight: 500,
titleLineHeight: 20,
titleTextAlign: 'center',
titleTextBaseline: 'middle',
titleFill: '#000',
titleFillOpacity: 0.9,
titleStroke: '#DAF5EC',
titleStrokeOpacity: 0.9,
titleLineWidth: 2,
titleLineDash: [4, 8],
titleOpacity: 1,
titleShadowColor: '#d3d3d3',
titleShadowBlur: 10,
titleShadowOffsetX: 10,
titleShadowOffsetY: 10,
titleCursor: 'pointer',
scale: {
size: {
type: 'linear',
domain: [0, 10],
range: [0, 100],
},
},
});
chart.render();

cols

number optional

Applicable to Categorical Legend. Specifies the number of legend items displayed per row. When empty, it means the number of columns is unlimited.

Legend layout uses flow layout by default.

flow layout

When cols is specified, grid layout is used.

grid layout 1grid layout 2

colPadding

number optional

Applicable to Categorical Legend. Specifies horizontal spacing between legend items. Default is 12.

rowPadding

number optional

Applicable to Categorical Legend. Specifies vertical spacing between legend items. Default is 8.

maxRows

number optional

Applicable to Categorical Legend. Specifies maximum number of rows for legend. Default is 3.

⚠️ Note: This configuration only takes effect in horizontal layout (position: 'top' or 'bottom'). When legend position is 'left' or 'right', G2 automatically calculates the number of rows based on container height, and maxRows configuration will be ignored.

maxCols

number optional

Applicable to Categorical Legend. Specifies maximum number of columns for legend. Default is 3.

⚠️ Note: This configuration only takes effect in vertical layout (position: 'left' or 'right'). When legend position is 'top' or 'bottom', G2 automatically calculates the number of columns based on container width, and maxCols configuration will be ignored.

💡 How do maxRows and maxCols affect legend layout?

maxRows and maxCols are used to limit the maximum number of rows and columns in legend layout, but they have different effects under different layout directions:

ParameterHorizontal Layout (top/bottom)Vertical Layout (left/right)
maxRows✅ Limits rows, controls legend height❌ No effect, rows auto-calculated by height
maxCols❌ No effect, rows auto-calculated by width✅ Limits columns, controls legend width

⚠️ Note: When using maxRows and maxCols, avoid manually configuring legend container's size and length.

itemMarker

LegendItemMarkerCfg optional

Applicable to Categorical Legend. Configure legend item markers. LegendItemMarkerCfg configuration:

PropertyDescriptionTypeDefault ValueRequired
itemMarkerLegend item markerSymbols |(datum, index, data)=>Symbolscircle
itemMarkerSizeLegend item marker sizenumber | (datum, index, data) => number8
itemMarkerFillLegend item marker fill colorstring | (datum, index, data) => string-
itemMarkerFillOpacityLegend item marker fill opacitynumber | (datum, index, data) => number1
itemMarkerStrokeLegend item marker strokestring | (datum, index, data) => string-
itemMarkerStrokeOpacityLegend item marker stroke opacitynumber | (datum, index, data) => number-
itemMarkerLineWidthLegend item marker stroke widthnumber | (datum, index, data) => number4 for line markers
itemMarkerLineDashLegend item marker stroke dash configuration. First value is dash segment length, second is gap length. Setting lineDash to [0,0] means no stroke.[number,number] | (datum, index, data) => [number , number]-
itemMarkerOpacityLegend item marker overall opacitynumber | (datum, index, data) => number-
itemMarkerShadowColorLegend item marker shadow colorstring | (datum, index, data) => string-
itemMarkerShadowBlurLegend item marker shadow Gaussian blur coefficientnumber | (datum, index, data) => number-
itemMarkerShadowOffsetXSet horizontal distance of shadow from legend item markernumber | (datum, index, data) => number-
itemMarkerShadowOffsetYSet vertical distance of shadow from legend item markernumber | (datum, index, data) => number-
itemMarkerCursorLegend item marker cursor style. Same as CSS cursor style.string | (datum, index, data) => stringdefault

Available Symbol Types

PropertyDescription
bowtieShape type: Bowtie
crossShape type: Cross
dashShape type: Dash
diamondShape type: Diamond
dotShape type: Dot
hexagonShape type: Hexagon
hollowBowtieShape type: Hollow Bowtie
hollowDiamondShape type: Hollow Diamond
hollowHexagonShape type: Hollow Hexagon
hollowPointShape type: Hollow Point
hollowSquareShape type: Hollow Square
hollowTriangleShape type: Hollow Triangle
hollowTriangleDownShape type: Hollow Triangle Down
hvStyle type: HV Path
hvhStyle type: HVH Path
hyphenShape type: Hyphen
lineShape type: Line
plusShape type: Plus
pointShape type: Solid Point
rectShape type: Rectangle
smoothStyle type: Smooth Curve
squareShape type: Square
tickShape type: Tick
triangleDownShape type: Triangle Down
triangleShape type: Triangle
vhStyle type: VH Path
vhvStyle type: VHV Path

Try it:

const { Chart } = G2;
const chart = new Chart({
container: 'container',
});
const container = chart.getContainer();
// Available itemMarker shapes
const shapeList = [
'bowtie',
'cross',
'dash',
'diamond',
'dot',
'hexagon',
'hollowBowtie',
'hollowDiamond',
'hollowHexagon',
'hollowPoint',
'hollowSquare',
'hollowTriangle',
'hollowTriangleDown',
'hv',
'hvh',
'hyphen',
'line',
'plus',
'point',
'rect',
'smooth',
'square',
'tick',
'triangleDown',
'triangle',
'vh',
'vhv',
];
const shapeMap = shapeList.map((p) => {
return {
label: p,
value: p,
};
});
chart.options({
type: 'legends',
height: 60,
itemMarker: 'bowtie',
scale: {
color: {
type: 'ordinal',
domain: ['a', 'b'],
range: ['steelblue', 'orange'],
},
},
});
const handleSetShape = (shape) => {
chart.options({
itemMarker: shape,
});
chart.render(); // Re-render the chart
};
const selectorContainer = document.createElement('div');
selectorContainer.textContent = 'Select legend item marker shape ';
const selector = document.createElement('select');
selector.innerHTML = shapeMap.map(
(shape, index) =>
`<option value="${shape.value}" ${index === 0 ? 'selected' : ''}>${
shape.label
}</option>`,
);
selector.onchange = (e) => {
handleSetShape(e.target.value);
};
selectorContainer.appendChild(selector);
container.insertBefore(selectorContainer, container.childNodes[0]);
chart.render();

In the Legend component, when configuring legend item markers, it's not configured as an object, but with the itemMarker prefix plus property:

({
legend: {
color: {
itemMarker: 'rect',
itemMarkerSize: 20,
itemMarkerFill: '#000',
itemMarkerFillOpacity: 0.9,
itemMarkerStroke: '#DAF5EC',
itemMarkerStrokeOpacity: 0.9,
itemMarkerLineWidth: 2,
itemMarkerLineDash: [4, 8],
itemMarkerOpacity: 1,
itemMarkerShadowColor: '#d3d3d3',
itemMarkerShadowBlur: 10,
itemMarkerShadowOffsetX: 10,
itemMarkerShadowOffsetY: 10,
itemMarkerCursor: 'pointer',
},
},
});

itemLabel

LegendItemLabelCfg optional

Applicable to Categorical Legend. Configure legend item labels. LegendItemLabelCfg configuration:

PropertyDescriptionTypeDefault ValueRequired
itemLabelTextLegend item label contentstring | (datum, index, data) => string-
itemLabelFontSizeLegend item label text sizenumber | (datum, index, data) => number12
itemLabelFontFamilyLegend item label text fontstring | (datum, index, data) => string-
itemLabelFontWeightLegend item label font weightnumber | (datum, index, data) => numbernormal
itemLabelLineHeightLegend item label text line heightnumber | (datum, index, data) => number-
itemLabelTextAlignSet current text alignment for legend item label contentcenter | end | left | right | startstart
itemLabelTextBaselineSet current text baseline used when drawing legend item label texttop | middle | bottom | alphabetic | hangingbottom
itemLabelFillLegend item label text colorstring | (datum, index, data) => string#1D2129
itemLabelFillOpacityLegend item label text color opacitynumber | (datum, index, data) => number0.9
itemLabelStrokeLegend item label text stroke colorstring | (datum, index, data) => string-
itemLabelStrokeOpacityLegend item label text stroke color opacitynumber | (datum, index, data) => number-
itemLabelLineWidthLegend item label text stroke widthnumber | (datum, index, data) => number-
itemLabelLineDashLegend item label text stroke dash configuration. First value is dash segment length, second is gap length. Setting lineDash to [0,0] means no stroke.[number,number] | (da tu m, index, data) => [number , number]-
itemLabelOpacityLegend item label text overall opacitynumber | (datum, index, data) => number-
itemLabelShadowColorLegend item label text shadow colorstring | (datum, index, data) => string-
itemLabelShadowBlurLegend item label text shadow Gaussian blur coefficientnumber | (datum, index, data) => number-
itemLabelShadowOffsetXSet horizontal distance of shadow from legend item label textnumber | (datum, index, data) => number-
itemLabelShadowOffsetYSet vertical distance of shadow from legend item label textnumber | (datum, index, data) => number-
itemLabelCursorLegend item label cursor style. Same as CSS cursor style.string | (datum, index, data) => stringdefault

In the Legend component, when configuring legend item labels, it's not configured as an object, but with the itemLabel prefix plus property:

({
legend: {
color: {
itemLabelText: 'Legend Item Label',
itemLabelFontSize: 16,
itemLabelFontFamily: 'sans-serif',
itemLabelFontWeight: 500,
itemLabelLineHeight: 20,
itemLabelTextAlign: 'center',
itemLabelTextBaseline: 'middle',
itemLabelFill: '#000',
itemLabelFillOpacity: 0.9,
itemLabelStroke: '#DAF5EC',
itemLabelStrokeOpacity: 0.9,
itemLabelLineWidth: 2,
itemLabelLineDash: [4, 8],
itemLabelOpacity: 1,
itemLabelShadowColor: '#d3d3d3',
itemLabelShadowBlur: 10,
itemLabelShadowOffsetX: 10,
itemLabelShadowOffsetY: 10,
itemLabelCursor: 'pointer',
},
},
});

itemValue

LegendItemValueCfg optional

Applicable to Categorical Legend. Configure legend item values. LegendItemValueCfg configuration:

PropertyDescriptionTypeDefault ValueRequired
itemValueTextLegend item value contentstring | (datum, index, data) => string-
itemValueFontSizeLegend item value text sizenumber | (datum, index, data) => number12
itemValueFontFamilyLegend item value text fontstring | (datum, index, data) => string-
itemValueFontWeightLegend item value font weightnumber | (datum, index, data) => numbernormal
itemValueLineHeightLegend item value text line heightnumber | (datum, index, data) => number-
itemValueTextAlignSet current text alignment for legend item value contentcenter | end | left | right | startstart
itemValueTextBaselineSet current text baseline used when drawing legend item value texttop | middle | bottom | alphabetic | hangingbottom
itemValueFillLegend item value text colorstring | (datum, index, data) => string#1D2129
itemValueFillOpacityLegend item value text color opacitynumber | (datum, index, data) => number0.65
itemValueStrokeLegend item value text stroke colorstring | (datum, index, data) => string-
itemValueStrokeOpacityLegend item value text stroke color opacitynumber | (datum, index, data) => number-
itemValueLineWidthLegend item value text stroke widthnumber | (datum, index, data) => number-
itemValueLineDashLegend item value text stroke dash configuration. First value is dash segment length, second is gap length. Setting lineDash to [0,0] means no stroke.[number,number] | (datum, index, data) => [number , number]-
itemValueOpacityLegend item value text overall opacitynumber | (datum, index, data) => number-
itemValueShadowColorLegend item value text shadow colorstring | (datum, index, data) => string-
itemValueShadowBlurLegend item value text shadow Gaussian blur coefficientnumber | (datum, index, data) => number-
itemValueShadowOffsetXSet horizontal distance of shadow from legend item value textnumber | (datum, index, data) => number-
itemValueShadowOffsetYSet vertical distance of shadow from legend item value textnumber | (datum, index, data) => number-
itemValueCursorLegend item value cursor style. Same as CSS cursor style.string | (datum, index, data) => stringdefault

In the Legend component, when configuring legend item values, it's not configured as an object, but with the itemValue prefix plus property:

({
legend: {
color: {
itemValueText: 'Legend Item Value',
itemValueFontSize: 16,
itemValueFontFamily: 'sans-serif',
itemValueFontWeight: 500,
itemValueLineHeight: 20,
itemValueTextAlign: 'center',
itemValueTextBaseline: 'middle',
itemValueFill: '#000',
itemValueFillOpacity: 0.9,
itemValueStroke: '#DAF5EC',
itemValueStrokeOpacity: 0.9,
itemValueLineWidth: 2,
itemValueLineDash: [4, 8],
itemValueOpacity: 1,
itemValueShadowColor: '#d3d3d3',
itemValueShadowBlur: 10,
itemValueShadowOffsetX: 10,
itemValueShadowOffsetY: 10,
itemValueCursor: 'pointer',
},
},
});

itemBackground

LegendItemBackgroundCfg optional

Applicable to Categorical Legend. Configure legend item backgrounds. LegendItemBackgroundCfg configuration:

PropertyDescriptionTypeDefault ValueRequired
itemBackgroundRadiusLegend item background border radiusnumber | (datum, index, data) => number0
itemBackgroundFillLegend item background fill colorstring | (datum, index, data) => stringtransparent
itemBackgroundFillOpacityLegend item background fill opacitynumber | (datum, index, data) => number-
itemBackgroundStrokeLegend item background strokestring | (datum, index, data) => string-
itemBackgroundStrokeOpacityLegend item background stroke opacitynumber | (datum, index, data) => number-
itemBackgroundLineWidthLegend item background stroke widthnumber | (datum, index, data) => number-
itemBackgroundLineDashLegend item background stroke dash configuration. First value is dash segment length, second is gap length. Setting lineDash to [0,0] means no stroke.[number,number] | (datum, index, data) => [number , number]-
itemBackgroundOpacityLegend item background overall opacitynumber | (datum, index, data) => number-
itemBackgroundShadowColorLegend item background shadow colorstring | (datum, index, data) => string-
itemBackgroundShadowBlurLegend item background shadow Gaussian blur coefficientnumber | (datum, index, data) => number-
itemBackgroundShadowOffsetXSet horizontal distance of shadow from legend item backgroundnumber | (datum, index, data) => number-
itemBackgroundShadowOffsetYSet vertical distance of shadow from legend item backgroundnumber | (datum, index, data) => number-
itemBackgroundCursorLegend item background cursor style. Same as CSS cursor style.string | (datum, index, data) => stringdefault

In the Legend component, when configuring legend item backgrounds, it's not configured as an object, but with the itemBackground prefix plus property:

({
legend: {
color: {
itemBackgroundRadius: 50,
itemBackgroundFill: '#000',
itemBackgroundFillOpacity: 0.9,
itemBackgroundStroke: '#DAF5EC',
itemBackgroundStrokeOpacity: 0.9,
itemBackgroundLineWidth: 2,
itemBackgroundLineDash: [4, 8],
itemBackgroundOpacity: 1,
itemBackgroundShadowColor: '#d3d3d3',
itemBackgroundShadowBlur: 10,
itemBackgroundShadowOffsetX: 10,
itemBackgroundShadowOffsetY: 10,
itemBackgroundCursor: 'pointer',
},
},
});

Next, try combining various properties of itemMarker, itemLabel, itemValue, and itemBackground to configure a custom legend:

import { Chart } from '@antv/g2';
const chart = new Chart({ container: 'container', height: 350 });
const shapeList = ['bowtie', 'smooth', 'hv', 'rect', 'hollowPoint'];
const data = [
{ genre: 'Sports', sold: 50 },
{ genre: 'Strategy', sold: 115 },
{ genre: 'Action', sold: 120 },
{ genre: 'Shooter', sold: 350 },
{ genre: 'Other', sold: 150 },
];
chart.options({
type: 'interval',
data,
encode: { x: 'genre', y: 'sold', color: 'genre' },
legend: {
color: {
size: 100,
itemWidth: 120,
// itemMarker
itemMarker: (d, index) => shapeList[index],
// itemLabel
itemLabelFill: 'red',
// itemValue
itemValueText: (d, index) => data[index]['sold'],
// itemBackground
itemBackgroundFill: (d) => d.color,
itemBackgroundFillOpacity: 0.2,
},
},
});
chart.render();

itemWidth

number optional

Applicable to Categorical Legend. Configure legend item width.

itemSpan

number|number[] optional

Applicable to Categorical Legend. Configure space allocation for legend item marker, label, and value. Default is [1, 1, 1].

itemSpacing

number|number[] optional

Applicable to Categorical Legend. Configure spacing between legend item marker, label, and value. Default is [8, 8].

nav

LegendNavCfg optional

Applicable to Categorical Legend. Configure legend navigator. In grid layout, page capacity = gridRow × gridCol. If the number of categorical items exceeds this capacity, pagination occurs. In flex layout, page capacity is calculated dynamically, limited by container width and height. When categorical items exceed container height or width, pagination occurs and the navigator component is shown.

💡 Vertical Layout Legend Pagination Configuration

For vertical layout legends (position: 'right' or 'left'), since maxRows doesn't take effect, it's recommended to enable pagination through the following methods:

legend: {
color: {
position: 'right',
size: 100, // Limit legend width, trigger pagination
length: 200, // Limit legend height, trigger pagination
navOrientation: 'vertical', // Vertical navigator
navDefaultPage: 0, // Default to first page
navLoop: true, // Enable loop pagination
}
}

LegendNavCfg configuration:

legend-nav
PropertyDescriptionTypeDefault ValueRequired
navEffectPage transition effectSee Web Animations APIlinear
navDurationSingle page transition animation durationnumber200
navOrientationPagination scroll direction
- Horizontal horizontal
- Vertical vertical
horizontal |verticalhorizontal
navDefaultPageDefault page number to displaynumber0
navLoopWhether to enable loop paginationbooleanfalse
navPageWidthNavigator page widthnumber-
navPageHeightNavigator page heightnumber-
navControllerPaddingSpacing between navigator buttons and page numbersnumber | number[]5
navControllerSpacingSpacing between navigator buttons and pages, can configure spacing between navigator and legendnumber | number[]5
navPageNumFontSizeNavigator page number text sizenumber | (datum, index, data) => number12
navPageNumFontFamilyNavigator page number text fontstring | (datum, index, data) => string-
navPageNumFontWeightNavigator page number font weightnumber | (datum, index, data) => numbernormal
navPageNumLineHeightNavigator page number text line heightnumber | (datum, index, data) => number-
navPageNumTextAlignSet current text alignment for navigator page number contentcenter | end | left | right | startstart
navPageNumTextBaselineSet current text baseline used when drawing navigator page number texttop | middle | bottom | alphabetic | hangingbottom
navPageNumFillNavigator page number text colorstring | (datum, index, data) => string#1D2129
navPageNumFillOpacityNavigator page number text color opacitynumber | (datum, index, data) => number0.45
navPageNumStrokeNavigator page number text stroke colorstring | (datum, index, data) => string-
navPageNumStrokeOpacityNavigator page number text stroke color opacitynumber | (datum, index, data) => number-
navPageNumLineWidthNavigator page number text stroke widthnumber | (datum, index, data) => number-
navPageNumLineDashNavigator page number text stroke dash configuration. First value is dash segment length, second is gap length. Setting lineDash to [0,0] means no stroke.[number,number] | (datum, index, data) => [number , number]-
navPageNumOpacityNavigator page number text overall opacitynumber | (datum, index, data) => number-
navPageNumShadowColorNavigator page number text shadow colorstring | (datum, index, data) => string-
navPageNumShadowBlurNavigator page number text shadow Gaussian blur coefficientnumber | (datum, index, data) => number-
navPageNumShadowOffsetXSet horizontal distance of shadow from navigator page number textnumber | (datum, index, data) => number-
navPageNumShadowOffsetYSet vertical distance of shadow from navigator page number textnumber | (datum, index, data) => number-
navPageNumCursorNavigator page number cursor style. Same as CSS cursor style.string | (datum, index, data) => stringdefault
navButtonSizeNavigator button sizenumber | (datum, index, data) => number-
navButtonFillNavigator button fill colorstring | (datum, index, data) => string#1D2129
navButtonFillOpacityNavigator button fill opacitynumber | (datum, index, data) => number0.65
navButtonStrokeNavigator button strokestring | (datum, index, data) => string-
navButtonStrokeOpacityNavigator button stroke opacitynumber | (datum, index, data) => number-
navButtonLineWidthNavigator button stroke widthnumber | (datum, index, data) => number-
navButtonLineDashNavigator button stroke dash configuration. First value is dash segment length, second is gap length. Setting lineDash to [0,0] means no stroke.[number,number] | (datum, index, data) => [number , number]-
navButtonOpacityNavigator button overall opacitynumber | (datum, index, data) => number-
navButtonShadowColorNavigator button shadow colorstring | (datum, index, data) => string-
navButtonShadowBlurNavigator button shadow Gaussian blur coefficientnumber | (datum, index, data) => number-
navButtonShadowOffsetXSet horizontal distance of shadow from navigator buttonnumber | (datum, index, data) => number-
navButtonShadowOffsetYSet vertical distance of shadow from navigator buttonnumber | (datum, index, data) => number-
navButtonCursorNavigator button cursor style. Same as CSS cursor style.string | (datum, index, data) => stringdefault
navFormatterPage number text formatter(current: number, total: number) => string-

In the Legend component, when configuring navigator properties, it's not configured as an object, but with the nav prefix plus property:

({
legend: {
color: {
navEffect: 'cubic-bezier',
navDuration: 400,
navOrientation: 'vertical',
navDefaultPage: 1,
navLoop: true,
navPageWidth: 10,
navPageHeight: 10,
navControllerPadding: 10,
navControllerSpacing: 10,
// Configure navPageNum drawing properties
navPageNumFontSize: 16,
navPageNumFontFamily: 'sans-serif',
navPageNumFontWeight: 500,
navPageNumLineHeight: 20,
navPageNumTextAlign: 'center',
navPageNumTextBaseline: 'middle',
navPageNumFill: '#000',
navPageNumFillOpacity: 0.9,
navPageNumStroke: '#DAF5EC',
navPageNumStrokeOpacity: 0.9,
navPageNumLineWidth: 2,
navPageNumLineDash: [4, 8],
navPageNumOpacity: 1,
navPageNumShadowColor: '#d3d3d3',
navPageNumShadowBlur: 10,
navPageNumShadowOffsetX: 10,
navPageNumShadowOffsetY: 10,
navPageNumCursor: 'pointer',
// Configure navButton drawing properties
navButtonFill: '#000',
navButtonFillOpacity: 0.9,
navButtonStroke: '#DAF5EC',
navButtonStrokeOpacity: 0.9,
navButtonLineWidth: 2,
navButtonLineDash: [4, 8],
navButtonOpacity: 1,
navButtonShadowColor: '#d3d3d3',
navButtonShadowBlur: 10,
navButtonShadowOffsetX: 10,
navButtonShadowOffsetY: 10,
navButtonCursor: 'pointer',
},
},
});

Try it:

import { Chart } from '@antv/g2';
const chart = new Chart({ container: 'container', height: 350 });
chart.options({
type: 'interval',
data: [
{ genre: 'Sports', sold: 50 },
{ genre: 'Strategy', sold: 115 },
{ genre: 'Action', sold: 120 },
{ genre: 'Shooter', sold: 350 },
{ genre: 'Other', sold: 150 },
],
encode: { x: 'genre', y: 'sold', color: 'genre' },
legend: {
color: {
itemWidth: 160,
navEffect: 'cubic-bezier',
navDuration: 400,
navOrientation: 'vertical',
navDefaultPage: 2,
navLoop: true,
// Configure navPageNum drawing properties
navPageNumFontSize: 16,
navPageNumFontFamily: 'sans-serif',
navPageNumFontWeight: 500,
navPageNumLineHeight: 20,
navPageNumTextAlign: 'center',
navPageNumTextBaseline: 'middle',
navPageNumFill: '#2989FF',
navPageNumFillOpacity: 0.9,
navPageNumStroke: '#DAF5EC',
navPageNumStrokeOpacity: 0.9,
navPageNumLineWidth: 2,
navPageNumLineDash: [4, 8],
navPageNumOpacity: 1,
navPageNumShadowColor: '#d3d3d3',
navPageNumShadowBlur: 10,
navPageNumShadowOffsetX: 10,
navPageNumShadowOffsetY: 10,
navPageNumCursor: 'pointer',
// Configure navButton drawing properties
navButtonFill: '#2989FF',
navButtonFillOpacity: 0.7,
navButtonStroke: '#DAF5EC',
navButtonStrokeOpacity: 0.9,
navButtonLineWidth: 2,
navButtonLineDash: [4, 8],
navButtonOpacity: 0.9,
navButtonShadowColor: '#d3d3d3',
navButtonShadowBlur: 10,
navButtonShadowOffsetX: 10,
navButtonShadowOffsetY: 10,
navButtonCursor: 'pointer',
navFormatter: (current, total) => `Page ${current}/Total ${total}`,
},
},
});
chart.render();

color

string[] | d3-interpolate optional

Applicable to Continuous Legend. Configure color band colors for continuous legend. When it's an array, colors are taken sequentially.

block

boolean optional

Applicable to Continuous Legend. Whether continuous legend displays by intervals. Default is false.

type

size | color optional

Applicable to Continuous Legend. Configure type of continuous legend. Default is color.

Combining block and type properties can configure different styles of continuous legends. Continuous data legends have 4 styles:

  • Continuous representation Default

    ribbon-color
  • Range representation block=true

    ribbon-color
  • Size representation type='size'

    ribbon-color
  • Size, range representation type='size' block=true

    ribbon-color

Events

Legend component exposes some special events to get component interaction information.

Event TypeDescriptionType
click Categorical LegendTriggered when clicking legend item(item:Selection)=>void
mouseenter Categorical LegendTriggered when mouse enters legend item(item: Selection)=>void
mouseleave Categorical LegendTriggered when mouse leaves legend item(item:Selection)=>void
valuechange Continuous LegendTriggered when sliding window range changes(range: [number, number])=>void
indicate Continuous LegendTriggered when indicator value changes(value: number, range: [number, number])=>void

Component appears only after chart rendering, so component events should be mounted after afterrender. Here's an example:

chart.on('afterrender', () => {
const { canvas } = chart.getContext();
const { document } = canvas;
document
.querySelector('.component')
.addEventListener('valuechange', (range) => {});
});

Examples

Default Display of Partial Legend Items on First Render

Currently there's no built-in API, you need to manually trigger the legendFilter interaction to achieve this.

import { Chart, ChartEvent } from '@antv/g2';
const chart = new Chart({ container: 'container' });
chart.options({
type: 'interval',
data: [
{ genre: 'Sports', sold: 100 },
{ genre: 'Strategy', sold: 115 },
{ genre: 'Action', sold: 120 },
{ genre: 'Shooter', sold: 350 },
{ genre: 'Other', sold: 150 },
],
encode: { x: 'genre', y: 'sold', color: 'genre' },
});
chart.render();
chart.on(ChartEvent.AFTER_RENDER, () => {
chart.emit('legend:filter', {
data: { channel: 'color', values: ['Sports', 'Strategy', 'Action'] },
});
});

You can set animate: false to avoid triggering update animations, but there will still be flickering. This will be handled internally through configuration options in the future to achieve better filtering effects.

Vertical Layout Legend Pagination

When legend position is right or left, since maxRows doesn't take effect, you need to control legend height through the length property to achieve pagination.

import { Chart } from '@antv/g2';
const chart = new Chart({
container: 'container',
height: 350,
width: 600,
});
// Create data with multiple legend items
const data = [
{ category: 'Category A', value: 40 },
{ category: 'Category B', value: 35 },
{ category: 'Category C', value: 30 },
{ category: 'Category D', value: 25 },
{ category: 'Category E', value: 20 },
{ category: 'Category F', value: 18 },
{ category: 'Category G', value: 15 },
{ category: 'Category H', value: 12 },
{ category: 'Category I', value: 10 },
{ category: 'Category J', value: 8 },
{ category: 'Category K', value: 6 },
{ category: 'Category L', value: 4 },
];
chart.options({
type: 'interval',
data,
encode: { x: 'category', y: 'value', color: 'category' },
legend: {
color: {
position: 'right',
length: 150, // Limit legend height to trigger pagination
size: 120, // Control legend width
navOrientation: 'vertical', // Vertical pagination
navDefaultPage: 0, // Default to first page
navLoop: true, // Enable loop navigation
navButtonFill: '#1890ff', // Pagination button color
navPageNumFill: '#1890ff', // Page number color
navFormatter: (current, total) => `${current + 1}/${total}`, // Page number format
},
},
});
chart.render();

Custom Legend Item Marker (itemMarker)

In actual development, the built-in legend item markers may not meet your requirements. Don't worry, G2 provides powerful customization features.

Custom Symbol

Each symbol can be customized, mainly in three steps:

  • Define symbol path.
  • Register symbol.
  • Use symbol.

First, let's see how to define a symbol path. A symbol path is a function that takes the starting point coordinates x, y and drawing radius, and returns a path.

import { type SymbolFactor } from '@antv/g2';
const triangle: SymbolFactor = (x, y, r) => {
const diffY = r * Math.sin((1 / 3) * Math.PI);
return [
['M', x - r, y + diffY],
['L', x, y - diffY],
['L', x + r, y + diffY],
['Z'],
];
};
triangle.style = ['fill'];

Next is registering the symbol, by calling G2.register('symbol.${symbol}', Symbol). Where symbol is the symbol name and Symbol is the defined symbol path. For example, registering a triangle symbol:

import { register } from '@antv/g2';
register('symbol.customTriangle', triangle);

Finally, use the symbol:

legend: {
color: {
itemMarker: 'customTriangle';
}
}

Using Images

import { Chart } from '@antv/g2';
const chart = new Chart({
container: 'container',
});
const logo = [
[
'TikTok',
'https://mdn.alipayobjects.com/huamei_qa8qxu/afts/img/A*8IXHQLvx9QkAAAAAAAAAAAAADmJ7AQ/original',
],
[
'Kuaishou',
'https://mdn.alipayobjects.com/huamei_qa8qxu/afts/img/A*swueRrrKvbcAAAAAAAAAAAAADmJ7AQ/original',
],
[
'Xiaomi',
'https://mdn.alipayobjects.com/huamei_qa8qxu/afts/img/A*79G3TIt3mBoAAAAAAAAAAAAADmJ7AQ/original',
],
[
'WeChat',
'https://mdn.alipayobjects.com/huamei_qa8qxu/afts/img/A*_ELBTJLp0dQAAAAAAAAAAAAADmJ7AQ/original',
],
[
'Keep',
'https://mdn.alipayobjects.com/huamei_qa8qxu/afts/img/A*JzbKRpFhR14AAAAAAAAAAAAADmJ7AQ/original',
],
[
'Chrome',
'https://mdn.alipayobjects.com/huamei_qa8qxu/afts/img/A*xLnYTaZfdh8AAAAAAAAAAAAADmJ7AQ/original',
],
[
'QQ',
'https://mdn.alipayobjects.com/huamei_qa8qxu/afts/img/A*AbGNTpA5JLwAAAAAAAAAAAAADmJ7AQ/original',
],
[
'Youku',
'https://mdn.alipayobjects.com/huamei_qa8qxu/afts/img/A*UL6lS4jw9lUAAAAAAAAAAAAADmJ7AQ/original',
],
[
'Baidu Maps',
'https://mdn.alipayobjects.com/huamei_qa8qxu/afts/img/A*I6nrTITAxcoAAAAAAAAAAAAADmJ7AQ/original',
],
[
'Tencent Video',
'https://mdn.alipayobjects.com/huamei_qa8qxu/afts/img/A*zwVvT5OFnuYAAAAAAAAAAAAADmJ7AQ/original',
],
[
'Bilibili',
'https://mdn.alipayobjects.com/huamei_qa8qxu/afts/img/A*6jkAQayTiMMAAAAAAAAAAAAADmJ7AQ/original',
],
[
'Word',
'https://mdn.alipayobjects.com/huamei_qa8qxu/afts/img/A*FbkXT6K6mVEAAAAAAAAAAAAADmJ7AQ/original',
],
[
'Excel',
'https://mdn.alipayobjects.com/huamei_qa8qxu/afts/img/A*CKb-R6ZAFpYAAAAAAAAAAAAADmJ7AQ/original',
],
[
'PowerPoint',
'https://mdn.alipayobjects.com/huamei_qa8qxu/afts/img/A*K7-FT4RYRqIAAAAAAAAAAAAADmJ7AQ/original',
],
[
'Tencent Meeting',
'https://mdn.alipayobjects.com/huamei_qa8qxu/afts/img/A*xbPXR7snu44AAAAAAAAAAAAADmJ7AQ/original',
],
[
'NetEase Cloud Music',
'https://mdn.alipayobjects.com/huamei_qa8qxu/afts/img/A*upKlRJ9QB4cAAAAAAAAAAAAADmJ7AQ/original',
],
[
'Safari',
'https://mdn.alipayobjects.com/huamei_qa8qxu/afts/img/A*kjDHRbiW734AAAAAAAAAAAAADmJ7AQ/original',
],
[
'Maps',
'https://mdn.alipayobjects.com/huamei_qa8qxu/afts/img/A*tl-2QIB8LKIAAAAAAAAAAAAADmJ7AQ/original',
],
[
'Docker',
'https://mdn.alipayobjects.com/huamei_qa8qxu/afts/img/A*iJ4dS49yrJ4AAAAAAAAAAAAADmJ7AQ/original',
],
[
'VSCode',
'https://mdn.alipayobjects.com/huamei_qa8qxu/afts/img/A*rR6nRInEcz4AAAAAAAAAAAAADmJ7AQ/original',
],
[
'Baidu Netdisk',
'https://mdn.alipayobjects.com/huamei_qa8qxu/afts/img/A*futaTbIAkG4AAAAAAAAAAAAADmJ7AQ/original',
],
[
'Evernote',
'https://mdn.alipayobjects.com/huamei_qa8qxu/afts/img/A*Skh1S4BfL9oAAAAAAAAAAAAADmJ7AQ/original',
],
];
chart
.interval()
.data(logo)
.encode('x', (d) => d[0])
.encode('y', () => Math.random())
.encode('color', (d) => d[1])
.scale('y', { nice: true })
.legend({
color: {
itemMarker: (_, index) => () => {
const { document } = chart.getContext().canvas;
const image = document.createElement('image', {
style: {
width: 20,
height: 20,
transform: `translate(-10, -10)`,
src: logo[index][1],
},
});
return image;
},
itemMarkerSize: 40,
itemLabelText: (_, index) => logo[index][0],
maxRows: 1,
},
})
.tooltip(false);
chart.render();

Custom Legend

G2's built-in legends are drawn with canvas or svg. If you want to render legends with HTML, you can customize legends by following these steps:

  • Turn off built-in legends and render the chart.
  • Wait for chart rendering to complete, then render HTML legend based on scale data.
  • Add interactions (if needed).

First, turn off built-in legends and render the chart.

chart.options({ legend: false });

Then wait for chart rendering to complete and call legendColor to render HTML legend:

chart.render().then(legendColor);

In legendColor, we first need to draw the legend. The following example draws the legend and adds it in front of the canvas:

function legendColor(chart) {
const node = chart.getContainer();
const legend = document.createElement('div');
node.insertBefore(legend, node.childNodes[0]);
// ...
}

After drawing the legend, we need to draw legend items. This data comes from the corresponding channel's scale: chart.getScale().color, and get corresponding names and values through the scale's domain and range.

function legendColor(chart) {
// ...
const scale = chart.getScale().color;
const { domain } = scale.getOptions();
const items = domain.map(() => {});
// ...
}

After drawing legend items, we should add interactions to each legend item through item.onclick, collect currently selected values, and add Filter transforms to the chart declaration based on these values, then re-render the chart. The complete implementation is as follows:

import { Chart } from '@antv/g2';
// Add legend
function legendColor(chart) {
// Create Legend and mount legend
const node = chart.getContainer();
const legend = document.createElement('div');
legend.style.display = 'flex';
node.insertBefore(legend, node.childNodes[0]);
// Create and mount Items
const { color: scale } = chart.getScale();
const { domain } = scale.getOptions();
const items = domain.map((value) => {
const item = document.createElement('div');
const color = scale.map(value);
item.style.marginLeft = '1em';
item.innerHTML = `
<span style="
background-color:${color};
display:inline-block;
width:10px;
height:10px;"
></span>
<span>${value}</span>
`;
return item;
});
items.forEach((d) => legend.append(d));
// Listen to events
const selectedValues = [...domain];
const options = chart.options();
for (let i = 0; i < items.length; i++) {
const item = items[i];
const value = domain[i];
item.style.cursor = 'pointer';
item.onclick = () => {
const index = selectedValues.indexOf(value);
if (index !== -1) {
selectedValues.splice(index, 1);
item.style.opacity = 0.5;
} else {
selectedValues.push(value);
item.style.opacity = 1;
}
changeColor(selectedValues);
};
}
// Re-render view
function changeColor(value) {
const { transform = [] } = options;
const newTransform = [{ type: 'filter', color: { value } }, ...transform];
chart.options({
...options,
transform: newTransform, // Specify new transform
scale: { color: { domain } },
});
chart.render(); // Re-render chart
}
}
// Draw chart
const container = document.createElement('div');
const chart = new Chart({
container: 'container',
container,
});
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' },
legend: false,
});
chart.render().then(legendColor);