echarts Is there a better way of handling data groupings (Hierarchical Data) and centering their category labels on xAxis

emeijp43  于 2022-12-31  发布在  Echarts
关注(0)|答案(9)|浏览(139)

Version

4.9.0

https://codepen.io/ChrisMash/pen/ExgXZLv

Steps to reproduce

Centering labels on second and third xAxis cannot be done in an intuitive manner when the number of items in each hierarchy or grouping is not even. The idea of hierarchies and grouped data (as to my understanding) is not fully supported?

What is expected?

What Im trying to achieve here is to produce a simple bar chart that has 2 series and 3 hierarchies. The code example i provided has this already implemented. The issue though (and what is expected) is a way to properly center the values on the second and third xAxis without having to add or remove empty strings in my xAxis data array.

What is actually happening?

The labels for second and third axes are always to the left since i have to provide empty strings always for my axes to work properly.

I used this issue to better understand how to use hierarchical data on a bar chart, please let me know if there is a better way to generate charts from such data.

#4902

Here is the example data i used and the expected result in excel.

Thank you very much for this great library 👌

nzkunb0c

nzkunb0c1#

Hi! We've received your issue and please be patient to get responded. 🎉
The average response time is expected to be within one day for weekdays.

In the meanwhile, please make sure that you have posted enough image to demo your request. You may also check out the API and chart option to get the answer.

If you don't get helped for a long time (over a week) or have an urgent question to ask, you may also send an email to dev@echarts.apache.org . Please attach the issue link if it's a technical question.

If you are interested in the project, you may also subscribe our mailing list .

Have a nice day! 🍵

sshcrbum

sshcrbum2#

An example to workaround.

option = {
  	color: ['#3e6591', '#eb7d22', '#d73f45'],
  	grid: {
      	left: 250
    },
    xAxis: {
      	axisLine: {
          	lineStyle: {color: '#ccc'}
        },
      	axisLabel: {
          	textStyle: {color: '#777'}
        }
    },
    yAxis: [{
      	inverse: true,
      	splitLine: {
          	show: true
        },
      	axisTick: {
          	length: 100,
          	lineStyle: {color: '#ccc'}          
        },
      	axisLine: {
          	lineStyle: {color: '#ccc'}
        },
        data: ['-', '-', '-', '-', '-']      
    }, {
      	name: '                     所属行业',
      	nameLocation: 'start',      
      	nameTextStyle: {
          	fontWeight: 'bold'
        },
	    position: 'left',
      	offset: 130,
      	axisLine: {
          	onZero: false,
          	show: false          
        },
      	axisTick: {
          	length: 70,
          	inside: true,
          	lineStyle: {color: '#ccc'}
        },      
      	axisLabel: {
          	inside: true
        },      
      	inverse: true,      
      	data: ['电商', '游戏', '金融', '旅游', '直播']
    }, {
      	name: '                产品名',
      	nameLocation: 'start',
      	nameTextStyle: {
          	fontWeight: 'bold'
        },      
		position: 'left',
      	offset: 220,
      	axisLine: {
          	onZero: false,
          	show: false
        },
      	axisTick: {
          	length: 100,
          	inside: true,
          	lineStyle: {color: '#ccc'}          
        },
      	axisLabel: {
          	inside: true
        },
      	inverse: true,
      	data: ['APP数据分析', 'DMP', '企业版', '移动广告鉴别', '']      
    }],
    series: [{
        type: 'bar',
        data:[220, 182, 191, 234, 290],
        label: {
         	normal: {
              	show: true,
              	position: 'left',
              	textStyle: {color: '#000'},
              	formatter: '合同金额',              
            }
        }
    }, {
        type: 'bar',
        data:[210, 132, 91, 204, 220],
        label: {
         	normal: {
              	show: true,
              	position: 'left',
              	textStyle: {color: '#000'},
              	formatter: '已收款',              
            }
        }      
    }, {
        type: 'bar',
        data:[120, 132, 131, 254, 278],
        label: {
         	normal: {
              	show: true,
              	position: 'left',
              	textStyle: {color: '#000'},
              	formatter: '应收款',              
            }
        }      
    }, {      
        type: 'bar',
        data:['-', '-', '-', '-', '-'],
      	yAxisIndex: 1
    }, {
        type: 'bar',
        data:['-', '-', '-', '-', '-'],
      	yAxisIndex: 2
    }]
};

uxh89sit

uxh89sit3#

Hello, I have question regarding this type of chart. If there is no data for all bars, then the chart still allocated space for the bar which leaves a gap. See the image below:

Is there any way to prevent creating this gap?

nsc4cvqm

nsc4cvqm4#

@mortenamby where you able to figure something out?

I guess you want to achieve something like this:

It would be great if we could have grouppings with different lengths.

@pissang any idea if this is possible right now?

n7taea2i

n7taea2i5#

I was able to imporve it:

I basically do the same as #4902 (comment) but when I use a dummy separator to indicate the sections and then I put the caption in the bar in the middle. This means that for pair groups it will still be a bit off (but at least it won't be in the first columns) and that for even groups it will be placed perfectly in the middle. To give you an idea:

xAxis: [
...,
{
  axisLabel: {
          inside: true,
          interval: (index, value) => {
            return value !== "" && value !== dummySeparator;
          },
        },
        offset: 80,
        axisTick: {
          length: 70,
          lineStyle: {
            color: "#CCC"
          },
          inside: true,
          interval: (index, value) => {
            return value === dummySeparator;
          }
        },
        splitArea: {
          show: true,
          interval: (index, value) => {
            return value === dummySeparator;
          }
        },
        data: groupCategrories.flatMap(c => {

          const spaces = new Array(c.length).fill("");
          spaces[0] = dummySeparator;
          spaces[Math.max(0, Math.trunc((spaces.length-1) / 2))] = c.caption; 
          return spaces;  
        })
}
}
z8dt9xmd

z8dt9xmd6#

@DavidMarquezF
I didn't manage to find a solution myself. I worked around it by choosing a different chart design, so it is not really relevant to me anymore. It may still be relevant to others as I think the gap is an issue if you have many empty bars that will never get data, which was the issue I was facing.

But nice to see that you found a way to do it :D

sc4hvdpw

sc4hvdpw7#

I was able to imporve it:

I basically do the same as #4902 (comment) but when I use a dummy separator to indicate the sections and then I put the caption in the bar in the middle. This means that for pair groups it will still be a bit off (but at least it won't be in the first columns) and that for even groups it will be placed perfectly in the middle. To give you an idea:

xAxis: [
...,
{
  axisLabel: {
          inside: true,
          interval: (index, value) => {
            return value !== "" && value !== dummySeparator;
          },
        },
        offset: 80,
        axisTick: {
          length: 70,
          lineStyle: {
            color: "#CCC"
          },
          inside: true,
          interval: (index, value) => {
            return value === dummySeparator;
          }
        },
        splitArea: {
          show: true,
          interval: (index, value) => {
            return value === dummySeparator;
          }
        },
        data: groupCategrories.flatMap(c => {

          const spaces = new Array(c.length).fill("");
          spaces[0] = dummySeparator;
          spaces[Math.max(0, Math.trunc((spaces.length-1) / 2))] = c.caption; 
          return spaces;  
        })
}
}

Could you help to share the full code? Thanks.

anhgbhbe

anhgbhbe8#

In my project I build the config dynamically so I don't have a single config for all available right now. However, the only important part you need is what i posted (the xAxis). The rest should be quite easy to lay down.

I actually improved the design a bit more so that the labels from the second groups are offset enough so that they don't overlap with the first row of categories. I will share my code but it's not copy paste material, you will need to figure it out a bit.

let groupCategrories: { caption: string, length: number }[] = // Here you should put the ordered list of the second level of categories. Caption is the name it should have and length is how many subcategories it holds;
 const labels: (string | number)[] = //Here put the list of labels from the first row of labels

 const longestLabel: string = labels.reduce<string>((a: string, b): string => {
        const strB = b?.toString();
        return a.length > strB?.length ? a : strB;
   }, "") ?? "";

// We want to calculate the longest label in order to offset the second row of labels 
const maxPixelLengthStr = getTextWidth(longestLabel, DEFAULT_FONT);
const dummySeparator = "***";

const xAxis: XAXisOption [] = [{
      name: this.xLabel,
      nameLocation: "middle",
      type: "category",
      axisLine: {
          show: false
       },
       axisTick: {
          show: false
       },
      axisLabel: {
        interval: 0,
        rotate: 90,
      },
      splitArea: {
          show: false
        },
    },
   {
        type: "category",
        position: "bottom",
        axisLine: {
          show: false,
          onZero: false
        },
        axisLabel: {
          inside: true,
          interval: (index, value) => {
            return value !== "" && value !== dummySeparator;
          },
        },
        offset: maxPixelLengthStr + 20,
        axisTick: {
          length: maxPixelLengthStr + 15,
          lineStyle: {
            color: "#CCC"
          },
          inside: true,
          interval: (index, value) => {
            return value === dummySeparator ||
              (value !== "" && groupCategrories.find(c => c.caption === value).length <= 2);
          }
        },
        splitArea: {
          show: true,
          interval: (index, value) => {
            return value === dummySeparator;
          }
        },
        data: groupCategrories.flatMap(c => {

          const spaces: string[] = new Array<string>(c.length).fill("");
          spaces[0] = dummySeparator;
          spaces[Math.max(0, Math.trunc((spaces.length - 1) / 2))] = c.caption;
          return spaces;
        })
      }
];
   

//The getTextWidth helper function
function getTextWidth(text, font): number {
    // re-use canvas object for better performance
    const canvas = (getTextWidth as any).canvas || ((getTextWidth as any).canvas = document.createElement("canvas"));
    const context = canvas.getContext("2d");
    context.font = font;
    const metrics = context.measureText(text);
    return metrics.width;
  }
const DEFAULT_FONT = "400 14px \"Inter var\", ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, \"Segoe UI\", Roboto, \"Helvetica Neue\", Arial, \"Noto Sans\", sans-serif, \"Apple Color Emoji\", \"Segoe UI Emoji\", \"Segoe UI Symbol\", \"Noto Color Emoji\"";

Then, for the rest of the config, just build a normal bar chart with a single series. You just use a single series and then the axis config is actually what does all of the work.

I'm sorry I can't share a full copy-paste example, I just don't have the time to do that right now. However, with this info it should be quite straight forward since the only complicated part about this is the xAxis itself and in the snippet I've shared that should work right of the bat.

zazmityj

zazmityj9#

Any chance that this will be officially supported? Tibco's Spotfire has a nice hierarchical axis representation that would be great to see in e-charts someday, even if it weren't interactive like the following example.

相关问题