Files
2026-09-09 17:54:06 +08:00

287 lines
16 KiB
JavaScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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: '附件“经销商数量”工作表;单位为家',
});
}