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' }, { key: 'overseas', name: '海外', color: PALETTE.brown }, ]; 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.color, width: 2, formatter: (point) => `${point.value.toFixed(0)}%`, }), yAxisIndex: 1, lineStyle: { color: region.color, width: 2, type: 'dashed' }, z: 10, })), ], graphic: [ ...watermarks(width, height), footnote('数据来源:泡泡玛特历年公告;海外收入=亚太+美洲+欧洲及其他的机器人商店收入;H2收入=全年−H1\n2024H1海外期末机器人商店数未披露,相关单店收入留空 | @Joey知行记'), ], }; }