如何在散点图上显示两个不相关的组中的eCharts标签

问题描述 投票:0回答:1

我正在 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
      }
    }
  ]
};
angular scatter-plot echarts ngecharts
1个回答
0
投票

确实

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>

© www.soinside.com 2019 - 2024. All rights reserved.