import {
FONT, PALETTE, categoryAxis, footnote, titleStyle, watermarks,
} from '../theme.js';
const REGION_ORDER = ['中国', '海外', '亚太', '美洲', '欧洲及其他'];
const CHANNEL_ORDER = ['线下渠道', '线上渠道', '批发及其他'];
function orderedValues(values, order) {
return [...values].sort((a, b) => {
const aIndex = order.indexOf(a);
const bIndex = order.indexOf(b);
return (aIndex === -1 ? order.length : aIndex) - (bIndex === -1 ? order.length : bIndex)
|| a.localeCompare(b, 'zh-CN');
});
}
function orderedPeriods(rows) {
return [...new Set(rows.map((row) => row.period))].sort((a, b) => {
const [yearA, halfA] = String(a).match(/(\d{4})H(\d)/).slice(1).map(Number);
const [yearB, halfB] = String(b).match(/(\d{4})H(\d)/).slice(1).map(Number);
return yearA - yearB || halfA - halfB;
});
}
/** 泡泡玛特区域 × 渠道同比增速热力图。 */
export function regionChannelYoyTimeseriesChart(rows, { width, height }) {
const periods = orderedPeriods(rows);
const regions = orderedValues(new Set(rows.map((row) => row.region)), REGION_ORDER);
const channels = orderedValues(new Set(rows.map((row) => row.channel)), CHANNEL_ORDER);
const dimensions = regions.flatMap((region) => channels.map((channel) => ({ region, channel })));
const periodIndex = new Map(periods.map((period, index) => [period, index]));
const dimensionIndex = new Map(dimensions.map(({ region, channel }, index) => [`${region}_${channel}`, index]));
const data = rows.map((row) => [
periodIndex.get(row.period),
dimensionIndex.get(`${row.region}_${row.channel}`),
row.yoy,
]);
return {
backgroundColor: '#fff',
title: {
...titleStyle,
text: '泡泡玛特区域渠道同比增速趋势',
subtext: '颜色越深代表同比变化幅度越大;空白表示当期未单列披露',
},
tooltip: {
position: 'top',
textStyle: { fontFamily: FONT },
formatter: (params) => {
const [periodIndexValue, dimensionIndexValue, value] = params.value;
const { region, channel } = dimensions[dimensionIndexValue];
const sign = value >= 0 ? '+' : '';
return `${periods[periodIndexValue]}
${region} · ${channel}
同比:${sign}${value.toFixed(1)}%`;
},
},
grid: {
left: 178,
right: 42,
top: 112,
// 底部依次预留:横轴标签 → visualMap 控制器 → 数据来源。
bottom: 155,
},
xAxis: {
...categoryAxis,
data: periods,
axisLabel: {
...categoryAxis.axisLabel,
formatter: (value) => String(value).replace(/^20/, ''),
},
},
yAxis: {
...categoryAxis,
type: 'category',
inverse: true,
data: dimensions.map(({ region, channel }) => `${region} · ${channel}`),
axisLabel: {
...categoryAxis.axisLabel,
fontSize: 12,
margin: 12,
},
},
visualMap: {
min: -100,
max: 200,
calculable: true,
orient: 'horizontal',
left: 'center',
bottom: 64,
text: ['较高增长', '较低增长'],
textStyle: { color: PALETTE.grey, fontFamily: FONT, fontSize: 12 },
// 红色系代表较高增长;两端均采用浅色,保证数值标签清晰。
inRange: { color: ['#9cc4d0', '#f3eee6', PALETTE.coral] },
},
series: [{
name: '同比增速',
type: 'heatmap',
data,
label: {
show: true,
fontFamily: FONT,
fontSize: 11,
color: PALETTE.ink,
formatter: (params) => {
const value = params.value[2];
return `${value >= 0 ? '+' : ''}${value.toFixed(0)}%`;
},
},
emphasis: {
itemStyle: { borderColor: PALETTE.ink, borderWidth: 1 },
},
}],
graphic: [
...watermarks(width, height),
footnote('数据来源:泡泡玛特财报;早期海外为合并口径 | @思考的Joey'),
],
};
}