我正在 Angular 中使用 eCharts 创建一个交互式散点图,它可能会有一个非常大的数据集。我有两组点将显示在同一个图表上。
我想分离标签,将一组标签排列在顶部,另一组标签排列在数据点下方。它们有很多重叠,所以我使用 labellayout: { moveOverlap: 'shiftX' }.
但是,“shiftX”选项仅查找 x 轴重叠,因此如果一个标签位于图表顶部,另一个标签位于图表底部,它仍然会分隔它们的 x 位置。它看起来很奇怪,并且不是一种有效的数据显示方式。我不知道如何在标签的每一半上单独使用“shiftX” - 我尝试将它们放入单独的网格/轴/系列等中。
有什么想法吗?
这是我的代码 - 我修改了一个 eCharts 示例来说明我的困境。如果点实际上很密集,我可以打开 hideOverlap,但对于这个例子,如果您取消任一系列上的“shiftX”,则另一个看起来很完美。没有“shiftX”的系列是一团糟。
当两者都有“shiftX”时,组织更加混乱,但重叠程度超出了数据集大小所需的程度,因为它试图最小化所有标签之间的水平重叠,而不仅仅是顶行或底行。
const data = [
[[28604,77,17096869,'Australia',1990],[31163,77.4,27662440,'Canada',1990],[1516,68,1154605773,'China',1990],[13670,74.7,10582082,'Cuba',1990],[28599,75,4986705,'Finland',1990],[29476,77.1,56943299,'France',1990],[31476,75.4,78958237,'Germany',1990],[28666,78.1,254830,'Iceland',1990],[1777,57.7,870601776,'India',1990],[29550,79.1,122249285,'Japan',1990],[2076,67.9,20194354,'North Korea',1990],[12087,72,42972254,'South Korea',1990],[24021,75.4,3397534,'New Zealand',1990],[43296,76.8,4240375,'Norway',1990],[10088,70.8,38195258,'Poland',1990],[19349,69.6,147568552,'Russia',1990],[10670,67.3,53994605,'Turkey',1990],[26424,75.7,57110117,'United Kingdom',1990],[37062,75.4,252847810,'United States',1990]],
[[44056,81.8,23968973,'Australia',2015],[43294,81.7,35939927,'Canada',2015],[13334,76.9,1376048943,'China',2015],[21291,78.5,11389562,'Cuba',2015],[38923,80.8,5503457,'Finland',2015],[37599,81.9,64395345,'France',2015],[44053,81.1,80688545,'Germany',2015],[42182,82.8,329425,'Iceland',2015],[5903,66.8,1311050527,'India',2015],[36162,83.5,126573481,'Japan',2015],[1390,71.4,25155317,'North Korea',2015],[34644,80.7,50293439,'South Korea',2015],[34186,80.6,4528526,'New Zealand',2015],[64304,81.6,5210967,'Norway',2015],[24787,77.3,38611794,'Poland',2015],[23038,73.13,143456918,'Russia',2015],[19360,76.5,78665830,'Turkey',2015],[38225,81.4,64715810,'United Kingdom',2015],[53354,79.1,321773631,'United States',2015]]
];
option = {
xAxis: [{
splitLine: { show: false }
},
{
splitLine: { show: false }
}],
yAxis: [{
splitLine: { show: false },
scale: true,
},
{
splitLine: { show: false },
scale: true,
}],
grid: {
left: 40,
right: 130
},
series: [
{
name: '1990',
data: data[0],
xAxisIndex: 0,
yAxisIndex: 0,
type: 'scatter',
symbolSize: function (data) {
return Math.sqrt(data[2]) / 5e2;
},
emphasis: {
focus: 'self'
},
labelLayout: function () {
return {
y: myChart.getHeight() - 750,
moveOverlap: 'shiftX'
};
},
labelLine: {
show: true,
lineStyle: {
color: '#bbb'
}
},
label: {
show: true,
formatter: function (param: any) {
return param.data[3];
},
position: 'right',
minMargin: 2
}
},
{
name: '1990',
data: data[1],
xAxisIndex: 1,
yAxisIndex: 1,
type: 'scatter',
symbolSize: function (data) {
return Math.sqrt(data[2]) / 5e2;
},
emphasis: {
focus: 'self'
},
labelLayout: function () {
return {
y: myChart.getHeight() - 30,
moveOverlap: 'shiftX'
};
},
labelLine: {
show: true,
lineStyle: {
color: '#bbb'
}
},
label: {
show: true,
formatter: function (param: any) {
return param.data[3];
},
position: 'right',
minMargin: 2
}
}
]
};
确实
LabelManager
会获得所有标签
并分发所有具有 moveOverlap: 'shiftX'
的图表,无论它们属于哪个系列。
解决这个难题的一个棘手/hacky方法是为每个系列的不同阶段设置
moveOverlap: 'shiftX'
。更准确地说,您可以为第一个系列设置初始 shiftX
,而不是为其他系列设置。在图表 finished
之后,收集第一个系列标签的实际 x 位置,并用显式 shiftX
值替换 x
,同时为第二个系列添加 moveOverlap: 'shiftX'
并通过 .setOption
更新图表。如果有n
系列,可以重复此操作
n-1
次。
这是应用此技巧的代码:
const myChart = echarts.init(document.getElementById('chart'));
const data = [
[[28604,77,17096869,'Australia',1990],[31163,77.4,27662440,'Canada',1990],[1516,68,1154605773,'China',1990],[13670,74.7,10582082,'Cuba',1990],[28599,75,4986705,'Finland',1990],[29476,77.1,56943299,'France',1990],[31476,75.4,78958237,'Germany',1990],[28666,78.1,254830,'Iceland',1990],[1777,57.7,870601776,'India',1990],[29550,79.1,122249285,'Japan',1990],[2076,67.9,20194354,'North Korea',1990],[12087,72,42972254,'South Korea',1990],[24021,75.4,3397534,'New Zealand',1990],[43296,76.8,4240375,'Norway',1990],[10088,70.8,38195258,'Poland',1990],[19349,69.6,147568552,'Russia',1990],[10670,67.3,53994605,'Turkey',1990],[26424,75.7,57110117,'United Kingdom',1990],[37062,75.4,252847810,'United States',1990]],
[[44056,81.8,23968973,'Australia',2015],[43294,81.7,35939927,'Canada',2015],[13334,76.9,1376048943,'China',2015],[21291,78.5,11389562,'Cuba',2015],[38923,80.8,5503457,'Finland',2015],[37599,81.9,64395345,'France',2015],[44053,81.1,80688545,'Germany',2015],[42182,82.8,329425,'Iceland',2015],[5903,66.8,1311050527,'India',2015],[36162,83.5,126573481,'Japan',2015],[1390,71.4,25155317,'North Korea',2015],[34644,80.7,50293439,'South Korea',2015],[34186,80.6,4528526,'New Zealand',2015],[64304,81.6,5210967,'Norway',2015],[24787,77.3,38611794,'Poland',2015],[23038,73.13,143456918,'Russia',2015],[19360,76.5,78665830,'Turkey',2015],[38225,81.4,64715810,'United Kingdom',2015],[53354,79.1,321773631,'United States',2015]]
];
let labelsXBySeries = Array(data.length-1).fill(null); // may be attached to the chart instance if global variables are an issue
const option = {
xAxis: [{
splitLine: { show: false }
},
{
splitLine: { show: false }
}],
yAxis: [{
splitLine: { show: false },
scale: true,
},
{
splitLine: { show: false },
scale: true,
}],
grid: {
left: 40,
right: 130
},
series: [
{
name: '1990',
data: data[0],
xAxisIndex: 0,
yAxisIndex: 0,
type: 'scatter',
symbolSize: function (data) {
return Math.sqrt(data[2]) / 5e2;
},
emphasis: {
focus: 'self'
},
labelLayout: function (params) {
const layout = {y: 0}; // use the y value for this series
if(labelsXBySeries[params.seriesIndex]){ // if x positions were computed
layout.x = labelsXBySeries[params.seriesIndex][params.dataIndex];
}
else{ // if not, use `shiftX` to set the positions
layout.moveOverlap = 'shiftX';
}
return layout;
},
labelLine: {
show: true,
lineStyle: {
color: '#bbb'
}
},
label: {
show: true,
formatter: function (param) {
return param.data[3];
},
position: 'top',
minMargin: 2
}
},
{
name: '2015',
data: data[1],
xAxisIndex: 1,
yAxisIndex: 1,
type: 'scatter',
symbolSize: function (data) {
return Math.sqrt(data[2]) / 5e2;
},
emphasis: {
focus: 'self'
},
labelLayout: {
y: myChart.getHeight() - 30
},
labelLine: {
show: true,
lineStyle: {
color: '#bbb'
}
},
label: {
show: true,
formatter: function (param) {
return param.data[3];
},
position: 'bottom',
minMargin: 2
}
}
]
};
function collectSeriesLabelsX(chart, seriesIndex){
const labelManager = Object.entries(chart._api)
.filter(([prop])=>prop.match(/^__ec_/))
.find(([_, o])=>o.hasOwnProperty('labelManager'))[1].labelManager;
const labelsX = [];
labelManager._labelList.forEach(label=>{
if(label.seriesModel.seriesIndex === seriesIndex){
labelsX[label.dataIndex] = label.label.x;
}
});
return labelsX;
}
myChart.on('finished', () => {
const firstNull = labelsXBySeries.findIndex(labels => labels === null);
if(firstNull < 0){
labelsXBySeries.forEach((_, i, a) => {a[i] = null;});
return;
}
labelsXBySeries[firstNull] = collectSeriesLabelsX(myChart, firstNull);
if(firstNull === labelsXBySeries.length - 1){
// the last series
option.series[firstNull + 1].labelLayout.moveOverlap = 'shiftX';
}
//option.animation = false; // to speed up the process
myChart.setOption(option);
});
myChart.setOption(option);
<div id='chart' style='height: 250px; min-width: 1000px'></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/echarts/5.5.0/echarts.min.js"></script>