import {
PALETTE, FONT, axisCommon, categoryAxis, titleStyle,
barSeries, footnote, watermarks, LAYOUT,
} from '../theme.js';
/**
* 泡泡玛特各 IP 半年收入占比(2021H1-2026H1)
* 归一化堆叠柱状图:每根柱固定 100%,看的是收入结构的变化而非规模。
* 源:ppmt.xlsx「IP收入和占比」的收入区;该表占比区未填,占比由收入算出。
*
* ⚠ 这张表的 H2 是【单半年】,不是全年累计——已核对 2025H2 的 232.44
* = 利润表 371.2 − 138.76。所以轴标签只去世纪前缀(25H2),
* 不能用 theme 的 `periodAxis`(它会渲染成「25全年」,对这张图是错的)。
*
* CSV 存两位小数:归一化占比对小值敏感,一位小数会让早期小 IP 失真
* (如 CRYBABY 2023H1 的 0.27 记成 0.3,相对误差 13%)。
*
* 18 行源数据里 8 个已停单列或体量极小的 IP 合并成「其他IP(含已停单列)」,
* 合并后仍是 11 个系列、逐期加总恰好 100%。
*
* 配色按 IP 名与同页饼图 ppmt_ip_share.js 一一对应,便于两图互相对照;
* 合并桶沿用 Zsiga 原色(Zsiga 已并入其中)。
*/
const COLORS = {
'THE MONSTERS': '#D64545',
'星星人': '#E8913C',
'授权IP(非独家)': '#D9C043',
'CRYBABY': '#7FA83C',
'DIMOO': '#3E9E6E',
'SKULLPANDA': '#3FB6D0',
'其他艺术家IP': '#2E5FA8',
'HIRONO-小野': '#8E86E0',
'MOLLY': '#8A3FA8',
'其他IP(含已停单列)': '#D081C4',
'外采及其他': '#8C8C8C',
};
const LEGEND_WIDTH = 160;
const LEGEND_RIGHT = 20;
export function ppmtIpShareHistoryChart(rows, { width, height }) {
const periods = Object.keys(rows[0]).filter((k) => k !== 'ip');
const names = rows.map((r) => r.ip);
const totals = periods.map((p) => rows.reduce((s, r) => s + r[p], 0));
// 归一化:各 IP 占当期合计的比重,逐期加总恰为 100%
const toPct = (r) => periods.map((p, i) => (r[p] / totals[i]) * 100);
return {
backgroundColor: '#fff',
title: {
...titleStyle,
text: '泡泡玛特各IP半年收入占比(21H1-26H1)',
subtext: '占当期IP收入合计的比重;H2 为下半年单期,非全年累计',
},
legend: {
orient: 'vertical',
right: LEGEND_RIGHT,
width: LEGEND_WIDTH,
top: 'middle',
itemGap: 12,
itemWidth: 14,
itemHeight: 14,
data: names,
textStyle: { color: PALETTE.ink, fontSize: 12, fontFamily: FONT },
},
tooltip: {
trigger: 'axis',
axisPointer: { type: 'shadow' },
textStyle: { fontFamily: FONT },
formatter: (ps) => {
const i = periods.indexOf(ps[0].axisValue);
const head = `${ps[0].axisValue} 合计 ${totals[i].toFixed(1)} 亿元
`;
return head + ps
.slice()
.reverse()
.filter((p) => p.value > 0)
.map((p) => `${p.marker}${p.seriesName}: ${p.value.toFixed(1)}%`
+ `(${((p.value / 100) * totals[i]).toFixed(1)} 亿元)`)
.join('
');
},
},
grid: {
left: 78,
right: LEGEND_RIGHT + LEGEND_WIDTH + 20,
top: LAYOUT.gridTop,
bottom: 70,
},
xAxis: {
...categoryAxis,
data: periods,
// 只去世纪前缀:这张表的 H2 是单半年,不能简写成「全年」
axisLabel: { ...categoryAxis.axisLabel, formatter: (v) => v.replace(/^20/, '') },
},
yAxis: {
...axisCommon,
type: 'value',
min: 0,
max: 100,
interval: 20,
axisLabel: { ...axisCommon.axisLabel, formatter: '{value}%' },
},
series: rows.map((r) => barSeries({
name: r.ip,
data: toPct(r),
color: COLORS[r.ip] ?? PALETTE.grey,
barWidth: 40,
stack: 'share',
label: {
show: true,
position: 'inside',
color: '#fff',
fontSize: 11,
fontFamily: FONT,
fontWeight: 500,
// 低于 4% 的段塞不进字,留空靠 tooltip 读数
formatter: (p) => (p.value >= 4 ? p.value.toFixed(1) : ''),
},
emphasis: { focus: 'series' },
})),
graphic: [
...watermarks(width, height),
footnote('其他IP(含已停单列)= Zsiga、HACIPUPU、BUNNY、PUCKY、小甜豆等 | @思考的Joey'),
],
};
}