AntV G2 图表:自定义图例和交互式注释
import { Chart } from '@antv/g2';
const data = [ { item: '事例一', count: 40, percent: 0.4 }, { item: '事例二', count: 21, percent: 0.21 }, { item: '事例三', count: 17, percent: 0.17 }, { item: '事例四', count: 13, percent: 0.13 }, { item: '事例五', count: 9, percent: 0.09 }, ];
const chart = new Chart({ container: 'container', autoFit: true, height: 500, }); // 新建一个 view 用来单独渲染Annotation const innerView = chart.createView(); chart.coordinate('theta', { radius: 0.75, innerRadius: 0.5, });
chart.data(data);
chart.scale('percent', { formatter: val => { val = val * 100 + '%'; return val; }, });
chart.tooltip(false);
// 声明需要进行自定义图例字段: 'item'
chart.legend('item', {
position: 'right', // 配置图例显示位置
custom: true, // 关键字段,告诉 G2,要使用自定义的图例
items: data.map((obj, index) => {
return {
name: obj.item, // 对应 itemName
value: obj.percent, // 对应 itemValue
marker: {
symbol: 'square', // marker 的形状
style: {
r: 5, // marker 图形半径
fill: chart.getTheme().colors10[index], // marker 颜色,使用默认颜色,同图形对应
},
},
};
}),
itemValue: {
style: {
fill: '#999',
},
formatter: val => ${val * 100}%
},
});
chart .interval() .adjust('stack') .position('percent') .color('item') .style({ fillOpacity: 1, }) .state({ active: { style: element => { const shape = element.shape; return { lineWidth: 10, stroke: shape.attr('fill'), strokeOpacity: shape.attr('fillOpacity'), }; }, }, });
// 移除图例点击过滤交互 chart.removeInteraction('legend-filter'); chart.interaction('element-active');
let firstPoint1; let firstPoint2; chart.on('afterrender', () => { // label 绘制图层 let labelGroup = chart.foregroundGroup.findById('customLabels'); if (labelGroup) { labelGroup.clear(); } else { labelGroup = chart.foregroundGroup.addGroup({ capture: false, id: 'customLabels' }); } const offset = 30; // 拐点折线的长度 const textOffset = 8; const elements = chart.geometries[0].elements; const coordinate = chart.getCoordinateByIndex(0); const center = coordinate.getCenter(); const radius = coordinate.getRadius();
const count = elements.length; let preWidth = 0;
for (let i = 0; i < count; i++) { const label = labelGroup.addGroup(); const element = elements[i]; const originData = element.data; const mappingData = element.getModel(); if (i === count - 1) { // 最后一个图形 label 横着长 label.addShape('path', { attrs: { path: [ ['M', center.x, center.y], ['L', center.x + radius + offset, center.y], ], stroke: mappingData.color, lineWidth: 1, }, }); label.addShape('text', { attrs: { x: center.x + radius + offset + textOffset, y: center.y, text: originData.count + originData.count, textBaseline: 'middle', fill: '#000', }, }); } else { const nextElement = elements[i + 1]; const nextBBox = nextElement.getBBox(); const bbox = element.getBBox(); // 第一个点 const width = bbox.maxX - nextBBox.maxX; const pointRadius = radius - preWidth - (width / 2); const point1 = Util.polarToCartesian(center.x, center.y, pointRadius, - 3 * Math.PI / 8 + (Math.PI / 8) * i); let point2; if (i === 0) { point2 = { x: bbox.maxX, y: bbox.minY }; firstPoint2 = point2; firstPoint1 = point1; } else { point2 = { x: Math.min(firstPoint2.x + point1.x - firstPoint1.x, elements[0].getBBox().maxX), y: firstPoint2.y + point1.y - firstPoint1.y, }; }
const point3 = {
x: point2.x + offset,
y: point2.y
};
label.addShape('path', {
attrs: {
path: [
['M', point1.x, point1.y],
['L', point2.x, point2.y],
['L', point3.x, point3.y],
],
stroke: mappingData.color,
lineWidth: 1,
},
});
label.addShape('text', {
attrs: {
x: point3.x + textOffset, // 加个偏移量
y: point3.y,
text: originData.count + ': ' + originData.count,
textBaseline: 'middle',
fill: '#000',
},
});
preWidth += width;
}
} });
chart.render();
// 监听 element 上状态的变化来动态更新 Annotation 信息 chart.on('element:statechange', (ev) => { const { state, stateStatus, element } = ev.gEvent.originalEvent;
// 本示例只需要监听 active 的状态变化 if (state === 'active') { const data = element.data; if (stateStatus === 'active') { // 更新 Annotation updateAnnotation(data); } else { // 隐藏 Annotation clearAnnotation(); } } });
// 绘制 annotation let lastItem; function updateAnnotation(data) { if (data.item !== lastItem) { innerView.annotation().clear(true); innerView .annotation() .text({ // 第一部分: 显示 item 名称 position: ['50%', '50%'], content: data.item, style: { fontSize: 20, fill: '#8c8c8c', textAlign: 'center', }, offsetY: -20, }) .text({ // 第二部分: 显示 count 数据 position: ['50%', '50%'], content: data.count, style: { fontSize: 28, fill: '#8c8c8c', textAlign: 'center', }, offsetX: -10, offsetY: 20, }) .text({ // 第三部分: 显示 '台' 字样 position: ['50%', '50%'], content: '台', style: { fontSize: 20, fill: '#8c8c8c', textAlign: 'center', }, offsetY: 20, offsetX: 20, }); innerView.render(); lastItem = data.item; } }
// 清空 annotation function clearAnnotation() { innerView.annotation().clear(true); innerView.render(); lastItem = null; }
原文地址: https://www.cveoy.top/t/topic/qmwn 著作权归作者所有。请勿转载和采集!