原始效果:柱状图
二开效果1:
核心逻辑
同时显示百分比和原始值
label: {
show: true,
position: 'inside',
formatter: (params) => {
const rawValue = rawData[params.seriesIndex][params.dataIndex];
const percentage = Math.round(params.value * 1000) / 10;
return `${rawValue} \n(${percentage}%)`;
}
},
显示汇总值
// Add a new series for displaying total values
series.push({
name: 'Total',
type: 'bar',
stack: 'total',
itemStyle: {
color: 'rgba(0,0,0,0)' // 透明颜色
},
label: {
show: true,
position: 'top',
formatter: params => `Total: ${totalData[params.dataIndex]}`
},
data: totalData.map(value => 0.01) // 微小的值以便能显示标签但不影响图形
});
代码解释
新增显示总值的系列:
- 您添加了一个名为
'Total'
的新系列到series
数组中。- 这个系列使用
type: 'bar'
,并且堆叠在名为'total'
的堆栈中,这与其他系列使用的堆栈名称一致。这确保了柱状图的对齐,即使该系列是不可见的。透明的柱状图:
itemStyle
被设置为color: 'rgba(0,0,0,0)'
,使得该系列的柱状图完全透明。这是一个巧妙的方法,可以确保这些柱状图不增加任何可见的元素到图表中,但仍然可以在它们上面放置标签。标签配置:
label
对象中的show: true
确保显示标签。position
设置为'top'
,因此标签显示在每个柱状图堆栈的顶部。formatter
函数自定义了标签的文本。它使用params.dataIndex
获取totalData
中对应的值,并显示为Total: {value}
。这提供了关于每个类别(星期几)中所有堆叠元素的总值的清晰信息。带有微小值的数据:
- 该系列的
data
数组被设置为totalData.map(value => 0.01)
。这将每个数据点设置为一个非常小的值(0.01)。这些微小的值的目的是为标签创建一个占位符,而不影响图表的实际可视化。由于柱状图本身是透明的,这个值确保了标签可以正确地定位和显示,而不会为柱状图增加任何视觉重量。分析:
- 使用透明的柱状图来显示标签:通过使用透明的柱状图,您可以在柱状图堆栈的顶部放置标签,而不会改变图表的视觉外观。这是一种常见的技术,当您希望添加额外的信息而不影响数据的可视化时。
- 数据中的微小值:使用微小值(0.01)确保标签与柱状图相关联,但不会显著地影响堆叠柱状图的高度。这在ECharts中尤其有用,因为标签是与特定的数据点相关联的。
- 堆叠配置:使用相同的堆叠标识符('total')使透明柱状图与其余堆叠柱状图完美对齐,确保标签位置的一致性。
这种方法对于突出显示总值,同时保持数据可视化的完整性非常有效。这是一个为图表提供额外信息而不使其变得混乱或扭曲的巧妙解决方案。
完整版代码
// There should not be negative values in rawData
const rawData = [
[100, 302, 301, 334, 390, 330, 320],
[320, 132, 101, 134, 90, 230, 210],
[220, 182, 191, 234, 290, 330, 310],
[150, 212, 201, 154, 190, 330, 410],
[820, 832, 901, 934, 1290, 1330, 1320]
];
const totalData = [];
for (let i = 0; i < rawData[0].length; ++i) {
let sum = 0;
for (let j = 0; j < rawData.length; ++j) {
sum += rawData[j][i];
}
totalData.push(sum);
}
const grid = {
left: 100,
right: 100,
top: 50,
bottom: 50
};
const series = [
'Direct',
'Mail Ad',
'Affiliate Ad',
'Video Ad',
'Search Engine'
].map((name, sid) => {
return {
name,
type: 'bar',
stack: 'total',
barWidth: '60%',
label: {
show: true,
position: 'inside',
formatter: (params) => {
const rawValue = rawData[params.seriesIndex][params.dataIndex];
const percentage = Math.round(params.value * 1000) / 10;
return `${rawValue} \n(${percentage}%)`;
}
},
data: rawData[sid].map((d, did) =>
totalData[did] <= 0 ? 0 : d / totalData[did]
)
};
});
// Add a new series for displaying total values
series.push({
name: 'Total',
type: 'bar',
stack: 'total',
itemStyle: {
color: 'rgba(0,0,0,0)' // 透明颜色
},
label: {
show: true,
position: 'top',
formatter: params => `Total: ${totalData[params.dataIndex]}`
},
data: totalData.map(value => 0.01) // 微小的值以便能显示标签但不影响图形
});
option = {
legend: {
selectedMode: false
},
grid,
yAxis: {
type: 'value'
},
xAxis: {
type: 'category',
data: ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun']
},
series
};
二开效果2:
完整版代码
// There should not be negative values in rawData
const rawData = [
[100, 302, 301, 334, 390, 330, 320],
[320, 132, 101, 134, 90, 230, 210],
[220, 182, 191, 234, 290, 330, 310],
[150, 212, 201, 154, 190, 330, 410],
[820, 832, 901, 934, 1290, 1330, 1320]
];
const totalData = [];
for (let i = 0; i < rawData[0].length; ++i) {
let sum = 0;
for (let j = 0; j < rawData.length; ++j) {
sum += rawData[j][i];
}
totalData.push(sum);
}
const grid = {
left: 100,
right: 100,
top: 50,
bottom: 50
};
const series = [
'Direct',
'Mail Ad',
'Affiliate Ad',
'Video Ad',
'Search Engine'
].map((name, sid) => {
return {
name,
type: 'bar',
stack: 'total',
barWidth: '60%',
label: {
show: true,
position: 'inside', // Position the labels on top of the bars
formatter: (params) => {
const originalValue = rawData[sid][params.dataIndex];
const percentage = (originalValue / totalData[params.dataIndex] * 100).toFixed(2);
return `${originalValue} \n(${percentage}%)`;
},
},
data: rawData[sid].map((d, did) =>
totalData[did] <= 0 ? 0 : d / totalData[did]
)
};
});
option = {
tooltip: {
trigger: 'axis',
axisPointer: {
type: 'shadow'
},
formatter: (params) => {
const total = totalData[params[0].dataIndex];
const header = `<div style="font-weight:bold">${params[0].axisValue}</div>
<div>Total: ${total}</div>`;
const body = params.map(param => {
const originalValue = rawData[param.seriesIndex][param.dataIndex];
const percentage = (originalValue / total * 100).toFixed(2);
return `<div>${param.seriesName}: ${originalValue} (${percentage}%)</div>`;
}).join('');
return header + body;
}
},
legend: {
selectedMode: false
},
grid,
yAxis: {
type: 'value'
},
xAxis: {
type: 'category',
data: ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun']
},
series
};
实现思路与修改:
计算每天的总访问数:首先遍历
rawData
并计算每一天所有来源的总访问数。这些总数被存储在totalData
数组中。配置每个数据源系列:为每一个数据源创建一个
series
对象。每个系列代表一种访问来源,并包含一些配置选项,如类型、堆叠设置、标签显示方式等。配置标签显示:为了让用户在图表上直观地看到原始值和占比,我们需要在每个柱形上添加标签。标签的内容包括原始值和百分比。
配置提示框(Tooltip):为了提供更丰富的信息,我们配置了一个提示框,当用户悬停在柱形上时会显示当天的总访问数和各个来源的具体数值及占比。
二开效果3:
完整版代码
// There should not be negative values in rawData
const rawData = [
[100, 302, 301, 334, 390, 330, 320],
[320, 132, 101, 134, 90, 230, 210],
[220, 182, 191, 234, 290, 330, 310],
[150, 212, 201, 154, 190, 330, 410],
[820, 832, 901, 934, 1290, 1330, 1320]
];
const totalData = [];
for (let i = 0; i < rawData[0].length; ++i) {
let sum = 0;
for (let j = 0; j < rawData.length; ++j) {
sum += rawData[j][i];
}
totalData.push(sum);
}
const grid = {
left: 100,
right: 100,
top: 50,
bottom: 50
};
const series = [
'Direct',
'Mail Ad',
'Affiliate Ad',
'Video Ad',
'Search Engine'
].map((name, sid) => {
return {
name,
type: 'bar',
stack: 'total',
barWidth: '60%',
label: {
show: true,
position: 'inside', // Position the labels on top of the bars
formatter: (params) => {
const originalValue = rawData[sid][params.dataIndex];
const percentage = (originalValue / totalData[params.dataIndex] * 100).toFixed(2);
return `${originalValue} (${percentage}%)`;
},
},
itemStyle: {
emphasis: {
// focus : 'series',
label: {
show: true,
position: 'top',
fontSize: 12,
color: 'red',
formatter: (params) => totalData[params.dataIndex]
}
}
},
data: rawData[sid].map((d, did) =>
totalData[did] <= 0 ? 0 : d / totalData[did]
)
};
});
option = {
legend: {
selectedMode: false
},
grid,
yAxis: {
type: 'value'
},
xAxis: {
type: 'category',
data: ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun']
},
series
};
解释:
- 添加了
itemStyle
选项,其中包含了emphasis
子项。- 在
emphasis
中设置了label
,用于在鼠标悬停时显示总值。emphasis.label.show
设为true
,表示在鼠标悬停时显示标签。emphasis.label.position
设为'bottom'
,使标签显示在柱子底部。emphasis.label.fontSize
设为12
,调整字体大小。emphasis.label.formatter
使用totalData[params.dataIndex]
显示当前柱子对应的总值
柱状图转换为条形图
核心代码修改,变更xAxis,yAxis 中的 x y 即可
条形图同时展示百分比、原始值、汇总值功能
// There should not be negative values in rawData
const rawData = [
[100, 302, 301, 334, 390, 330, 320],
[320, 132, 101, 134, 90, 230, 210],
[220, 182, 191, 234, 290, 330, 310],
[150, 212, 201, 154, 190, 330, 410],
[820, 832, 901, 934, 1290, 1330, 1320]
];
const totalData = [];
for (let i = 0; i < rawData[0].length; ++i) {
let sum = 0;
for (let j = 0; j < rawData.length; ++j) {
sum += rawData[j][i];
}
totalData.push(sum);
}
const grid = {
left: 100,
right: 100,
top: 50,
bottom: 50
};
const series = [
'Direct',
'Mail Ad',
'Affiliate Ad',
'Video Ad',
'Search Engine'
].map((name, sid) => {
return {
name,
type: 'bar',
stack: 'total',
barWidth: '60%',
label: {
show: true,
position: 'inside',
formatter: (params) => {
const rawValue = rawData[params.seriesIndex][params.dataIndex];
const percentage = Math.round(params.value * 1000) / 10;
return `${rawValue} \n(${percentage}%)`;
}
},
data: rawData[sid].map((d, did) =>
totalData[did] <= 0 ? 0 : d / totalData[did]
)
};
});
series.push({
name: 'Total',
type: 'bar',
stack: 'total',
itemStyle: {
color: 'red' // 透明颜色
},
label: {
show: true,
// position: 'middle',
formatter: params => `Total: ${totalData[params.dataIndex]}`
},
data: totalData.map(value => 0.0) // 微小的值以便能显示标签但不影响图形
});
option = {
legend: {
selectedMode: false
},
grid,
xAxis: {
type: 'value'
},
yAxis: {
type: 'category',
data: ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun']
},
series
};