import { col } from '../csv.js'; import { CHANNEL_COLORS, PALETTE, FONT, axisCommon, standalonePeriodAxis, titleStyle, standardLegend, financialViewLabel, lineSeries, footnote, watermarks, LAYOUT, } from '../theme.js'; /** * 泡泡玛特分地区机器人商店数量与同比增速。 * 堆叠柱为各期末机器人数,折线为全球合计数量的同比。 * 口径:2023年及以前"海外合计"含港澳台;2024年起港澳台单列并计入中国。 * 机器人商店仅披露内地/港澳台/海外三层,无亚太/美洲/欧洲分列。 */ // 门店数是计数而非金额,保留整数。中国内地沿用渠道语义色(机器人商店=绿)。 const SEGMENT_COLORS = { mainland: CHANNEL_COLORS.robotStore, hkmtw: '#AECBE8', overseas: '#C2726B', }; const regions = [ { key: 'mainland', name: '中国内地' }, { key: 'hkmtw', name: '港澳台' }, { key: 'overseas', name: '海外合计(含港澳台)' }, ]; // 段内标签文字色:浅色底(港澳台)用深字,深色底(内地绿、海外玫红)用白字 const INSIDE_LABEL_COLORS = { mainland: '#fff', hkmtw: PALETTE.ink, overseas: '#fff', }; const num = (value) => (Number.isFinite(value) ? value : null); // 绘图区高约 435px(grid top 145 / bottom 100,画布 680),轴上限 3000。 // 低于约 13px 的段放不下段内 10px 标签:柱顶段标到柱顶上方, // 上方还有其它段的标到柱体右侧,避免与上段段内标签互相挤压。 const PX_PER_UNIT = 435 / 3000; const MIN_INSIDE_PX = 13; function segmentData(row, key) { const value = num(row[key]); if (value === null) return null; if (value * PX_PER_UNIT >= MIN_INSIDE_PX) { return { value, label: { position: 'inside', color: INSIDE_LABEL_COLORS[key] } }; } const keyIndex = regions.findIndex((item) => item.key === key); const hasSegmentAbove = regions .slice(keyIndex + 1) .some((item) => num(row[item.key]) !== null); return { value, label: hasSegmentAbove ? { position: 'right', color: PALETTE.ink, distance: 4 } : { position: 'top', color: PALETTE.ink }, }; } export function storesRobotChart(rows, { width, height, view = 'all' }) { return { backgroundColor: '#fff', title: { ...titleStyle, text: `泡泡玛特分地区机器人商店数量与同比增速(${financialViewLabel(view)})`, subtext: '柱状为各期末机器人商店数(堆叠);折线为全球合计数量同比', }, legend: standardLegend([...regions.map((item) => item.name), '全球合计同比']), grid: { left: 78, right: 82, top: LAYOUT.gridTop, bottom: 100 }, tooltip: { trigger: 'axis', axisPointer: { type: 'shadow' }, textStyle: { fontFamily: FONT }, }, xAxis: { ...standalonePeriodAxis, data: col(rows, 'period') }, yAxis: [ { ...axisCommon, type: 'value', name: '机器人商店数量(台)', nameLocation: 'middle', nameGap: 52, nameTextStyle: { color: PALETTE.grey, fontSize: 13, fontFamily: FONT }, min: 0, max: 3000, interval: 500, }, { ...axisCommon, type: 'value', name: '全球合计同比(%)', nameLocation: 'middle', nameGap: 60, nameRotate: -90, nameTextStyle: { color: PALETTE.grey, fontSize: 13, fontFamily: FONT }, min: 0, max: 600, interval: 100, axisLabel: { ...axisCommon.axisLabel, formatter: '{value}%' }, splitLine: { show: false }, }, ], series: [ ...regions.map((item) => ({ name: item.name, type: 'bar', stack: 'stores-robot', data: rows.map((row) => segmentData(row, item.key)), itemStyle: { color: SEGMENT_COLORS[item.key] }, label: { show: true, fontSize: 10, fontWeight: 500, fontFamily: FONT, overflow: 'none', }, // 相邻矮段标签贴近时沿 y 向微移避让 labelLayout: { moveOverlap: 'shiftY' }, emphasis: { focus: 'series' }, })), { ...lineSeries({ name: '全球合计同比', data: col(rows, 'global_yoy'), color: '#8256B2', formatter: (p) => `${p.value.toFixed(1)}%`, }), yAxisIndex: 1, z: 10, }, ], graphic: [ ...watermarks(width, height), footnote( [ '数据来源:泡泡玛特历年公告;门店数为各期末时点数', view === 'annual' ? '海外合计2023年及以前含港澳台、其后不含 | @Joey知行记' : '海外合计2023年及以前含港澳台、其后不含;2024H1海外数量未披露 | @Joey知行记', ].join('\n'), ), ], }; }