feat: 更新泡泡玛特图表,加入单店收入
This commit is contained in:
@@ -0,0 +1,110 @@
|
||||
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: '#3F7FBE' },
|
||||
{ key: 'apac', name: '亚太', color: '#36A28F' },
|
||||
{ key: 'americas', name: '美洲', color: '#E4A349' },
|
||||
{ key: 'europe_other', 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 retailStoreRevenueChart(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) / 1000) * 1000;
|
||||
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) => `<b>${params[0].axisValue}</b><br/>${params
|
||||
.filter((item) => Number.isFinite(item.value))
|
||||
.map((item) => `${item.marker}${item.seriesName}: ${Number(item.value).toFixed(1)}${item.seriesName.endsWith('同比') ? '%' : ' 万元/店'}`)
|
||||
.join('<br/>')}`,
|
||||
},
|
||||
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' ? 17 : 34,
|
||||
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\n2024年海外各区域缺少期初店数,相关单店收入留空 | @思考的Joey'),
|
||||
],
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user