feat: 增加贵州茅台图表

This commit is contained in:
R524809
2026-09-09 17:54:06 +08:00
parent 2a38778fa3
commit a6387f8220
27 changed files with 1190 additions and 0 deletions
+286
View File
@@ -0,0 +1,286 @@
import { col } from '../csv.js';
import {
PALETTE, FONT, axisCommon, categoryAxis, titleStyle, standardLegend,
lineSeries, footnote, watermarks, LAYOUT,
} from '../theme.js';
const amountAxis = (name = '金额(亿元)', extra = {}) => ({
...axisCommon,
type: 'value',
name,
nameLocation: 'middle',
nameGap: 52,
nameTextStyle: { color: PALETTE.grey, fontSize: 13, fontFamily: FONT },
...extra,
});
const percentageAxis = (name = '比例(%)') => ({
...amountAxis(name),
axisLabel: { ...axisCommon.axisLabel, formatter: '{value}%' },
splitLine: { show: false },
});
const periodAxis = (rows) => ({
...categoryAxis,
data: col(rows, 'period'),
axisLabel: {
...categoryAxis.axisLabel,
formatter: (value) => String(value).replace(/^20(\d{2})/, '$1'),
},
});
function option({ title, subtitle, rows, legend, series, yAxis, width, height, source }) {
return {
backgroundColor: '#fff',
title: { ...titleStyle, text: title, subtext: subtitle },
legend: standardLegend(legend),
grid: { left: 78, right: 82, top: LAYOUT.gridTop, bottom: 90 },
tooltip: {
trigger: 'axis',
axisPointer: { type: 'shadow' },
textStyle: { fontFamily: FONT },
valueFormatter: (value) => (Number.isFinite(value) ? value.toFixed(1) : '—'),
},
xAxis: periodAxis(rows),
yAxis,
series,
graphic: [...watermarks(width, height), footnote(`数据来源:贵州茅台定期报告及附件底稿;${source} | @Joey知行记`)],
};
}
function bar(name, rows, key, color, extras = {}) {
return {
name,
type: 'bar',
data: col(rows, key),
itemStyle: { color },
emphasis: { focus: 'series' },
...extras,
};
}
function profitChart(rows, { width, height, label }) {
return option({
title: `贵州茅台盈利能力(${label})`,
subtitle: '营业收入、毛利、归母净利润与毛利率',
rows,
legend: ['营业收入', '毛利', '归母净利润', '毛利率'],
yAxis: [amountAxis(), percentageAxis('毛利率(%)')],
series: [
bar('营业收入', rows, 'revenue', PALETTE.navy),
bar('毛利', rows, 'gross_profit', PALETTE.sage),
bar('归母净利润', rows, 'net_profit', PALETTE.orange),
{ ...lineSeries({ name: '毛利率', data: col(rows, 'gross_margin').map((value) => (Number.isFinite(value) ? value * 100 : null)), color: PALETTE.red, formatter: (point) => `${point.value.toFixed(1)}%` }), yAxisIndex: 1, z: 10 },
],
width, height, source: `${label}口径;金额单位为亿元`,
});
}
export const moutaiProfitabilityAnnualChart = (rows, context) => profitChart(rows, { ...context, label: '年度' });
export const moutaiProfitabilityQuarterlyChart = (rows, context) => profitChart(rows, { ...context, label: '单季' });
function expenseChart(rows, { width, height, label }) {
const rates = rows.map((row) => {
const value = (row.sales_expense_rate || 0) + (row.admin_expense_rate || 0) + (row.rd_expense_rate || 0);
return Number.isFinite(value) ? value * 100 : null;
});
return option({
title: `贵州茅台费用结构(${label})`,
subtitle: '税金及附加单列展示;费用率为销售、管理和研发费用占营业收入比例',
rows,
legend: ['销售费用', '管理费用', '研发费用', '税金及附加', '三项费用率'],
yAxis: [amountAxis(), percentageAxis('三项费用率(%)')],
series: [
bar('销售费用', rows, 'sales_expense', PALETTE.blue),
bar('管理费用', rows, 'admin_expense', PALETTE.orange),
bar('研发费用', rows, 'rd_expense', PALETTE.purple),
bar('税金及附加', rows, 'tax_surcharge', PALETTE.sage),
{ ...lineSeries({ name: '三项费用率', data: rates, color: PALETTE.red, formatter: (point) => `${point.value.toFixed(1)}%` }), yAxisIndex: 1, z: 10 },
],
width, height, source: `${label}口径;金额单位为亿元`,
});
}
export const moutaiExpenseAnnualChart = (rows, context) => expenseChart(rows, { ...context, label: '年度' });
export const moutaiExpenseQuarterlyChart = (rows, context) => expenseChart(rows, { ...context, label: '单季' });
function balanceChart(rows, { width, height, label }) {
return option({
title: `贵州茅台资产负债结构(${label}期末)`,
subtitle: '资产总计、所有者权益、负债与类现金余额',
rows,
legend: ['资产总计', '所有者权益', '负债合计', '类现金'],
yAxis: amountAxis('期末余额(亿元)'),
series: [
bar('资产总计', rows, 'total_assets', PALETTE.navy),
bar('所有者权益', rows, 'equity', PALETTE.sage),
bar('负债合计', rows, 'liabilities', PALETTE.orange),
{ ...lineSeries({ name: '类现金', data: col(rows, 'cash_like'), color: PALETTE.red, formatter: (point) => point.value.toFixed(0) }), z: 10 },
],
width, height, source: '资产负债表;金额单位为亿元',
});
}
export const moutaiBalanceAnnualChart = (rows, context) => balanceChart(rows, { ...context, label: '年度' });
export const moutaiBalanceQuarterlyChart = (rows, context) => balanceChart(rows, { ...context, label: '季度' });
function cashflowChart(rows, { width, height, label }) {
return option({
title: `贵州茅台三大现金流(${label})`,
subtitle: label === '单季' ? '季度单期值由披露的累计现金流量表相邻期间相减得出' : '年度现金流量表披露值',
rows,
legend: ['经营现金流', '投资现金流', '筹资现金流', '现金净增加'],
yAxis: amountAxis('现金流量(亿元)'),
series: [
bar('经营现金流', rows, 'operating_cash_flow', '#36A28F'),
bar('投资现金流', rows, 'investing_cash_flow', '#4D83D1'),
bar('筹资现金流', rows, 'financing_cash_flow', '#EEA64F'),
{ ...lineSeries({ name: '现金净增加', data: col(rows, 'net_cash_increase'), color: '#D95567', formatter: (point) => point.value.toFixed(0) }), z: 10 },
],
width, height, source: `${label}口径;金额单位为亿元`,
});
}
export const moutaiCashflowAnnualChart = (rows, context) => cashflowChart(rows, { ...context, label: '年度' });
export const moutaiCashflowQuarterlyChart = (rows, context) => cashflowChart(rows, { ...context, label: '单季' });
function revenueMixChart(rows, { width, height, title, subtitle, fields, source }) {
const [first, second] = fields;
return option({
title,
subtitle,
rows,
legend: [first.name, second.name, first.shareName],
yAxis: [amountAxis(), percentageAxis(`${first.shareName}(%)`)],
series: [
{ ...bar(first.name, rows, first.key, first.color), stack: 'revenue' },
{ ...bar(second.name, rows, second.key, second.color), stack: 'revenue' },
{ ...lineSeries({ name: first.shareName, data: col(rows, first.shareKey).map((value) => (Number.isFinite(value) ? value * 100 : null)), color: PALETTE.red, formatter: (point) => `${point.value.toFixed(1)}%` }), yAxisIndex: 1, z: 10 },
],
width, height, source,
});
}
export const moutaiProductAnnualChart = (rows, context) => revenueMixChart(rows, {
...context, title: '贵州茅台产品档次营收(年度)', subtitle: '茅台酒与系列酒营收及茅台酒营收占比',
fields: [{ name: '茅台酒', key: 'moutai', shareName: '茅台酒占比', shareKey: 'moutai_share', color: PALETTE.navy }, { name: '系列酒', key: 'series', color: PALETTE.orange }], source: '年度产品档次口径;金额单位为亿元',
});
export const moutaiChannelAnnualChart = (rows, context) => revenueMixChart(rows, {
...context, title: '贵州茅台销售渠道营收(年度)', subtitle: '直销与批发代理营收及直销营收占比',
fields: [{ name: '直销', key: 'direct', shareName: '直销占比', shareKey: 'direct_share', color: PALETTE.blue }, { name: '批发代理', key: 'wholesale', color: PALETTE.orange }], source: '年度销售渠道口径;金额单位为亿元',
});
function classifiedMixChart(rows, { width, height, label, kind }) {
const isProduct = kind === 'product';
return revenueMixChart(rows, {
width, height,
title: `贵州茅台${isProduct ? '产品档次' : '销售渠道'}营收(${label})`,
subtitle: isProduct ? '茅台酒与系列酒营收及茅台酒营收占比' : '直销与批发代理营收及直销营收占比',
fields: isProduct
? [{ name: '茅台酒', key: 'moutai', shareName: '茅台酒占比', shareKey: 'moutai_share', color: PALETTE.navy }, { name: '系列酒', key: 'series', color: PALETTE.orange }]
: [{ name: '直销', key: 'direct', shareName: '直销占比', shareKey: 'direct_share', color: PALETTE.blue }, { name: '批发代理', key: 'wholesale', color: PALETTE.orange }],
source: `${label}分类营收口径;金额单位为亿元`,
});
}
export const moutaiClassifiedProductQuarterlyChart = (rows, context) => classifiedMixChart(rows, { ...context, label: '单季', kind: 'product' });
export const moutaiClassifiedChannelQuarterlyChart = (rows, context) => classifiedMixChart(rows, { ...context, label: '单季', kind: 'channel' });
export const moutaiClassifiedProductCumulativeChart = (rows, context) => classifiedMixChart(rows, { ...context, label: '累计', kind: 'product' });
export const moutaiClassifiedChannelCumulativeChart = (rows, context) => classifiedMixChart(rows, { ...context, label: '累计', kind: 'channel' });
export function moutaiRevenueCompositionHalfyearChart(rows, { width, height }) {
return option({
title: '贵州茅台半年度累计营收构成',
subtitle: '实线为产品档次口径,虚线为经营地区口径;两组口径不作加总',
rows,
legend: ['茅台酒', '系列酒', '国内', '国外'],
yAxis: amountAxis(),
series: [
{ ...lineSeries({ name: '茅台酒', data: col(rows, 'moutai'), color: PALETTE.navy, formatter: (point) => point.value.toFixed(0) }), z: 10 },
{ ...lineSeries({ name: '系列酒', data: col(rows, 'series'), color: PALETTE.orange, formatter: (point) => point.value.toFixed(0) }), z: 10 },
{ ...lineSeries({ name: '国内', data: col(rows, 'domestic'), color: PALETTE.green, formatter: (point) => point.value.toFixed(0) }), lineStyle: { color: PALETTE.green, width: 2, type: 'dashed' }, z: 9 },
{ ...lineSeries({ name: '国外', data: col(rows, 'overseas'), color: PALETTE.purple, formatter: (point) => point.value.toFixed(1) }), lineStyle: { color: PALETTE.purple, width: 2, type: 'dashed' }, z: 9 },
],
width, height, source: '半年度累计营收构成;金额单位为亿元',
});
}
export function moutaiLiquorIndustryOutputChart(rows, { width, height }) {
return option({
title: '全国白酒产量与同比变化', subtitle: '行业产量趋势,用于观察白酒行业供给环境', rows,
legend: ['白酒产量', '同比变化'],
yAxis: [amountAxis('白酒产量(万千升)'), percentageAxis('同比变化(%)')],
series: [bar('白酒产量', rows, 'output', PALETTE.sage), { ...lineSeries({ name: '同比变化', data: col(rows, 'yoy').map((value) => (Number.isFinite(value) ? value * 100 : null)), color: PALETTE.red, formatter: (point) => `${point.value.toFixed(1)}%` }), yAxisIndex: 1, z: 10 }],
width, height, source: '附件“全国白酒产量”工作表',
});
}
export function moutaiBaseWineVolumeChart(rows, { width, height }) {
return option({
title: '茅台基酒产能、产量与商品酒销量', subtitle: '基酒产量与商品酒销售年份存在生产周期差异', rows,
legend: ['设计产能', '已释放产能', '基酒产量', '商品酒销量'],
yAxis: amountAxis('数量(吨)'),
series: [
bar('设计产能', rows, 'planned_capacity', PALETTE.sand),
bar('基酒产量', rows, 'base_wine_output', PALETTE.navy),
{ ...lineSeries({ name: '已释放产能', data: col(rows, 'released_capacity'), color: PALETTE.orange, formatter: (point) => point.value.toFixed(0) }), z: 10 },
{ ...lineSeries({ name: '商品酒销量', data: col(rows, 'commercial_wine_sales'), color: PALETTE.green, formatter: (point) => point.value.toFixed(0) }), z: 10 },
],
width, height, source: '附件“茅台基酒产销量”工作表;单位为吨',
});
}
export function moutaiFlyingPriceChart(rows, { width, height }) {
return option({
title: '飞天茅台年度批价', subtitle: '年度批价为附件记录值,单位为元/瓶', rows,
legend: ['飞天茅台批价'], yAxis: amountAxis('批价(元/瓶)'),
series: [{ ...lineSeries({ name: '飞天茅台批价', data: col(rows, 'wholesale_price'), color: PALETTE.red, formatter: (point) => point.value.toFixed(0) }), z: 10 }],
width, height, source: '附件“飞天茅台历年批价”工作表',
});
}
export function moutaiPriceAdjustmentChart(rows, { width, height }) {
return option({
title: '飞天茅台出厂价调整记录', subtitle: '折线为调整后价格,柱形为本次调价幅度', rows,
legend: ['调整后价格', '提价幅度'], yAxis: [amountAxis('出厂价(元/瓶)'), percentageAxis('提价幅度(%)')],
series: [
{ ...lineSeries({ name: '调整后价格', data: col(rows, 'after_price'), color: PALETTE.navy, formatter: (point) => point.value.toFixed(0) }), z: 10 },
{ ...bar('提价幅度', rows, 'increase_rate', PALETTE.orange), yAxisIndex: 1, data: col(rows, 'increase_rate').map((value) => (Number.isFinite(value) ? value * 100 : null)) },
], width, height, source: '附件“提价记录”工作表',
});
}
export function moutaiTonPriceChart(rows, { width, height }) {
return option({
title: '茅台酒与系列酒平均吨价', subtitle: '由各产品收入除以商品酒销量计算,单位为万元/吨', rows,
legend: ['茅台酒平均吨价', '系列酒平均吨价'], yAxis: amountAxis('万元/吨'),
series: [
{ ...lineSeries({ name: '茅台酒平均吨价', data: col(rows, 'moutai_price_per_ton'), color: PALETTE.navy, formatter: (point) => point.value.toFixed(1) }), z: 10 },
{ ...lineSeries({ name: '系列酒平均吨价', data: col(rows, 'series_price_per_ton'), color: PALETTE.orange, formatter: (point) => point.value.toFixed(1) }), z: 10 },
], width, height, source: '附件“吨价”工作表;由收入与销量计算',
});
}
export function moutaiDividendChart(rows, { width, height }) {
return option({
title: '贵州茅台分红与归母净利润', subtitle: '分红总金额与归母净利润;折线为分红率', rows,
legend: ['分红总金额', '归母净利润', '分红率'], yAxis: [amountAxis(), percentageAxis('分红率(%)')],
series: [
bar('分红总金额', rows, 'dividend', PALETTE.orange),
bar('归母净利润', rows, 'net_profit', PALETTE.navy),
{ ...lineSeries({ name: '分红率', data: col(rows, 'payout_ratio').map((value) => (Number.isFinite(value) ? value * 100 : null)), color: PALETTE.red, formatter: (point) => `${point.value.toFixed(1)}%` }), yAxisIndex: 1, z: 10 },
], width, height, source: '附件“历年分红”工作表;金额单位为亿元',
});
}
export function moutaiDistributorsChart(rows, { width, height }) {
return option({
title: '贵州茅台经销商数量', subtitle: '国内与国外经销商期末数量', rows,
legend: ['国内经销商', '国外经销商', '合计'], yAxis: amountAxis('数量(家)'),
series: [
{ ...bar('国内经销商', rows, 'domestic', PALETTE.navy), stack: 'dealer' },
{ ...bar('国外经销商', rows, 'overseas', PALETTE.orange), stack: 'dealer' },
{ ...lineSeries({ name: '合计', data: col(rows, 'total'), color: PALETTE.red, formatter: (point) => point.value.toFixed(0) }), z: 10 },
], width, height, source: '附件“经销商数量”工作表;单位为家',
});
}