import { col } from '../csv.js';
import {
PALETTE, FONT, axisCommon, standalonePeriodAxis, titleStyle, standardLegend,
financialViewLabel, barSeries, lineSeries, footnote, watermarks, LAYOUT,
} from '../theme.js';
// 同比虚线用柱色的同色系深色变体:压在柱体和彼此之间都能区分,色相仍与柱对应
const REGIONS = [
{ key: 'china', name: '中国(含港澳台)', color: '#71AD81', lineColor: PALETTE.green },
// 色板没有深棕,按 brown 加深
{ key: 'overseas', name: '海外', color: PALETTE.brown, lineColor: '#4A3117' },
];
function yoyByPeriod(rows, key) {
const values = new Map(rows.map((row) => [row.period, row[key]]));
return rows.map((row) => {
const year = Number(row.period.slice(0, 4));
const prior = values.get(`${year - 1}${row.period.slice(4)}`);
return Number.isFinite(row[key]) && Number.isFinite(prior) && prior !== 0
? (row[key] / prior - 1) * 100
: null;
});
}
/** 中国与海外机器人商店单店收入:收入 ÷ 期初、期末平均机器人商店数。 */
export function robotStoreRevenueChart(rows, { width, height, view = 'all' }) {
const values = REGIONS.flatMap((region) => col(rows, `${region.key}_per_store`)).filter(Number.isFinite);
const yMax = Math.ceil(Math.max(...values) / 50) * 50;
const yoySeries = REGIONS.map((region) => ({ ...region, data: yoyByPeriod(rows, `${region.key}_per_store`) }));
const yoyValues = yoySeries.flatMap((region) => region.data).filter(Number.isFinite);
const yoyMin = Math.min(0, Math.floor(Math.min(...yoyValues) / 50) * 50);
const yoyMax = Math.max(50, Math.ceil(Math.max(...yoyValues) / 50) * 50);
const legendItems = REGIONS.flatMap((region) => [region.name, `${region.name}同比`]);
return {
backgroundColor: '#fff',
title: {
...titleStyle,
text: `泡泡玛特机器人商店单店收入(${financialViewLabel(view)})`,
subtext: '柱状为单店收入;深色虚线为对应柱的单店收入同比;H2为下半年单期',
},
legend: standardLegend(legendItems, { itemGap: 18 }),
grid: { left: 78, right: 42, top: LAYOUT.gridTop, bottom: 90 },
tooltip: {
trigger: 'axis',
axisPointer: { type: 'shadow' },
textStyle: { fontFamily: FONT },
formatter: (params) => `${params[0].axisValue}
${params
.filter((item) => Number.isFinite(item.value))
.map((item) => `${item.marker}${item.seriesName}: ${Number(item.value).toFixed(1)}${item.seriesName.endsWith('同比') ? '%' : ' 万元/店'}`)
.join('
')}`,
},
xAxis: { ...standalonePeriodAxis, data: col(rows, 'period') },
yAxis: [
{
...axisCommon,
type: 'value',
name: '单店收入(万元/店)',
nameLocation: 'middle',
nameGap: 58,
nameTextStyle: { color: PALETTE.grey, fontSize: 13, fontFamily: FONT },
min: 0,
max: yMax,
interval: yMax / 5,
},
{
...axisCommon,
type: 'value',
name: '单店收入同比(%)',
nameLocation: 'middle',
nameGap: 60,
nameRotate: -90,
nameTextStyle: { color: PALETTE.grey, fontSize: 13, fontFamily: FONT },
min: yoyMin,
max: yoyMax,
interval: 50,
axisLabel: { ...axisCommon.axisLabel, formatter: '{value}%' },
splitLine: { show: false },
},
],
series: [
...REGIONS.map((region) => ({
...barSeries({
name: region.name,
data: col(rows, `${region.key}_per_store`),
color: region.color,
barWidth: view === 'all' ? 28 : 52,
formatter: (point) => point.value.toFixed(0),
}),
emphasis: { focus: 'series' },
})),
...yoySeries.map((region) => ({
...lineSeries({
name: `${region.name}同比`,
data: region.data,
color: region.lineColor,
width: 2,
formatter: (point) => `${point.value.toFixed(0)}%`,
}),
yAxisIndex: 1,
lineStyle: { color: region.lineColor, width: 2, type: 'dashed' },
z: 10,
})),
],
graphic: [
...watermarks(width, height),
footnote('数据来源:泡泡玛特历年公告;海外收入=亚太+美洲+欧洲及其他的机器人商店收入;H2收入=全年−H1\n2024H1海外期末机器人商店数未披露,相关单店收入留空 | @Joey知行记'),
],
};
}