Compare commits
10 Commits
main
..
feat-1.1.0
| Author | SHA1 | Date | |
|---|---|---|---|
| a15b3f8027 | |||
| 417116b928 | |||
| a6387f8220 | |||
| 2a38778fa3 | |||
| bab02e1efd | |||
| 44c6437b64 | |||
| 9605228c3f | |||
| d093a322d3 | |||
| 04105ad6eb | |||
| 143cd1fbf8 |
@@ -45,6 +45,10 @@ pnpm test # 全量,只在改了 t
|
||||
|
||||
## 数据口径
|
||||
|
||||
**底稿与图表数据分层。** `data/{company}/rawData/` 是唯一可维护的数据层。当前泡泡玛特的维护入口是其中的 `泡泡玛特_财报分析底稿.xlsx`:人工或 AI 从 PDF、截图、XLSX 提取的基础值先写入这份底稿,并保留来源文件、工作表和定位信息。`data/{company}/*.csv` 是图表运行时投影,只能由脚本生成,禁止手工编辑。
|
||||
|
||||
**维护明细,不维护可推导结果。** 收入、门店数、财报科目等基础值应保存;占比、同比、利润率、H2 单半年和分析分类小计应由脚本生成。若财报披露了占比,可作为核对值保留,但不是图表的唯一输入。资产负债表的“类现金/经营类资产/有息负债”等分类以独立规则映射明细科目,不复制成第二套人工数字。
|
||||
|
||||
**数值默认保留一位小数。** CSV 数据列与图表展示(柱/点标签、tooltip)统一保留一位小数(如 `71.1`、`311.4%`),四舍五入;确实需要其他精度时必须在 `footnote()` 或代码注释里说明原因。
|
||||
|
||||
**CSV 存四位年份**(`2026H1`),简写只在显示层做。核对数据时无歧义。
|
||||
@@ -91,6 +95,6 @@ pnpm test # 全量,只在改了 t
|
||||
|
||||
## 脚注
|
||||
|
||||
`footnote()` 单行约 60 字符封顶(1200px 画布),超了用 `\n` 手动断行。结尾统一 `| @思考的Joey`。
|
||||
`footnote()` 单行约 60 字符封顶(1200px 画布),超了用 `\n` 手动断行。结尾统一 `| @Joey知行记`。
|
||||
|
||||
水印文案在 `theme.js` 的 `WATERMARK_TEXT`,改一处全局生效。
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# 财报图表 · Joey 谨制
|
||||
|
||||
基于 **ECharts 5.5** 的财报数据可视化项目,模仿"方木博制"风格。
|
||||
基于 **ECharts 5.5** 的财报数据可视化项目,采用“Joey 风格”。
|
||||
|
||||
修改 CSV 数据,刷新页面即可更新图表;每张图表都可独立导出为高清 PNG。
|
||||
|
||||
|
||||
+187
-2
@@ -28,7 +28,10 @@ button {
|
||||
.sidebar-title { margin: 0; }
|
||||
|
||||
.reload-button,
|
||||
.export-button {
|
||||
.export-button,
|
||||
.cart-button,
|
||||
.cart-export-button,
|
||||
.primary-button {
|
||||
padding: 7px 13px;
|
||||
border: 1px solid var(--line);
|
||||
border-radius: 6px;
|
||||
@@ -57,6 +60,186 @@ button {
|
||||
.reload-button:hover { opacity: .88; transform: rotate(-45deg); }
|
||||
.export-button:hover { color: var(--accent); border-color: var(--accent); }
|
||||
|
||||
/* ── 合并队列(购物车)────────────────────────────── */
|
||||
|
||||
.cart-button { font-size: 12px; }
|
||||
.cart-button:hover { color: var(--accent); border-color: var(--accent); }
|
||||
.cart-button[data-in-cart="true"] {
|
||||
color: #fff;
|
||||
background: var(--accent);
|
||||
border-color: var(--accent);
|
||||
}
|
||||
|
||||
.cart-bar[hidden] { display: none; }
|
||||
|
||||
.cart-bar {
|
||||
position: fixed;
|
||||
left: 50%;
|
||||
bottom: 18px;
|
||||
transform: translateX(-50%);
|
||||
z-index: 20;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
max-width: min(1160px, calc(100vw - 32px));
|
||||
padding: 9px 12px;
|
||||
background: rgba(255, 255, 255, .97);
|
||||
border: 1px solid var(--line);
|
||||
border-radius: 12px;
|
||||
box-shadow: 0 6px 24px rgba(0, 0, 0, .12);
|
||||
}
|
||||
|
||||
.cart-label {
|
||||
color: var(--muted);
|
||||
font-size: 12px;
|
||||
font-weight: 600;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.cart-chips {
|
||||
display: flex;
|
||||
gap: 6px;
|
||||
max-width: 44vw;
|
||||
overflow-x: auto;
|
||||
scrollbar-width: none;
|
||||
}
|
||||
.cart-chips::-webkit-scrollbar { display: none; }
|
||||
|
||||
.cart-chip {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex: 0 0 auto;
|
||||
gap: 2px;
|
||||
padding: 3px 4px 3px 10px;
|
||||
background: var(--accent-soft);
|
||||
border-radius: 6px;
|
||||
}
|
||||
|
||||
.cart-chip-name {
|
||||
max-width: 150px;
|
||||
overflow: hidden;
|
||||
color: var(--ink);
|
||||
font-size: 12px;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.cart-chip-button {
|
||||
padding: 2px 4px;
|
||||
border: 0;
|
||||
border-radius: 4px;
|
||||
background: transparent;
|
||||
color: var(--muted);
|
||||
font-size: 12px;
|
||||
line-height: 1.4;
|
||||
}
|
||||
.cart-chip-button:hover:not(:disabled) { color: var(--accent); }
|
||||
.cart-chip-button:disabled { opacity: .3; cursor: default; }
|
||||
|
||||
.cart-divider { width: 1px; height: 22px; background: var(--line); }
|
||||
|
||||
.cart-modes {
|
||||
display: flex;
|
||||
flex: 0 0 auto;
|
||||
padding: 2px;
|
||||
background: #f4f2ee;
|
||||
border-radius: 6px;
|
||||
}
|
||||
|
||||
.cart-mode-button {
|
||||
padding: 4px 8px;
|
||||
border: 0;
|
||||
border-radius: 5px;
|
||||
background: transparent;
|
||||
color: var(--muted);
|
||||
font-size: 12px;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.cart-mode-button[data-active="true"] {
|
||||
color: var(--accent);
|
||||
background: var(--surface);
|
||||
box-shadow: 0 1px 2px rgba(0, 0, 0, .08);
|
||||
}
|
||||
|
||||
.cart-export-button { color: #fff; background: var(--accent); border-color: var(--accent); white-space: nowrap; }
|
||||
.cart-export-button:hover { opacity: .9; }
|
||||
.cart-clear-button { padding: 7px 8px; border: 0; background: transparent; color: var(--muted); font-size: 12px; white-space: nowrap; }
|
||||
.cart-clear-button:hover { color: #a63f32; }
|
||||
|
||||
/* ── 合并预览弹窗 ────────────────────────────────── */
|
||||
|
||||
.modal-overlay {
|
||||
position: fixed;
|
||||
inset: 0;
|
||||
z-index: 30;
|
||||
display: grid;
|
||||
place-items: center;
|
||||
padding: 24px;
|
||||
background: rgba(51, 51, 51, .45);
|
||||
}
|
||||
|
||||
.modal {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
max-width: min(860px, 100%);
|
||||
max-height: calc(100vh - 48px);
|
||||
background: var(--surface);
|
||||
border-radius: 12px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.modal-head {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
padding: 12px 16px;
|
||||
border-bottom: 1px solid var(--line);
|
||||
}
|
||||
|
||||
.modal-title { margin: 0; font-size: 14px; }
|
||||
.modal-notice { color: #a67c2b; font-size: 12px; }
|
||||
|
||||
.modal-close {
|
||||
margin-left: auto;
|
||||
padding: 2px 8px;
|
||||
border: 0;
|
||||
background: transparent;
|
||||
color: var(--muted);
|
||||
font-size: 16px;
|
||||
}
|
||||
.modal-close:hover { color: var(--ink); }
|
||||
|
||||
.modal-body {
|
||||
display: grid;
|
||||
gap: 20px;
|
||||
overflow-y: auto;
|
||||
padding: 16px;
|
||||
}
|
||||
|
||||
.preview-item {
|
||||
display: grid;
|
||||
gap: 10px;
|
||||
justify-items: center;
|
||||
}
|
||||
|
||||
.preview-canvas {
|
||||
max-width: 100%;
|
||||
height: auto;
|
||||
border: 1px solid var(--line);
|
||||
border-radius: 6px;
|
||||
}
|
||||
|
||||
.modal-foot {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
gap: 10px;
|
||||
padding: 12px 16px;
|
||||
border-top: 1px solid var(--line);
|
||||
}
|
||||
|
||||
.primary-button { color: #fff; background: var(--accent); border-color: var(--accent); }
|
||||
.primary-button:hover { opacity: .9; }
|
||||
|
||||
.app-layout {
|
||||
display: grid;
|
||||
grid-template-columns: 184px minmax(0, 1fr) 184px;
|
||||
@@ -235,7 +418,9 @@ button {
|
||||
.tab-button { flex: 0 0 auto; }
|
||||
.card { scroll-margin-top: 120px; }
|
||||
.card-bar { align-items: flex-start; flex-wrap: wrap; }
|
||||
.export-button { margin-left: auto; }
|
||||
.cart-button { margin-left: auto; }
|
||||
.cart-bar { left: 12px; right: 12px; transform: none; flex-wrap: wrap; }
|
||||
.cart-chips { max-width: none; }
|
||||
.chart { min-width: 560px; }
|
||||
.card { overflow-x: auto; }
|
||||
}
|
||||
|
||||
@@ -0,0 +1,76 @@
|
||||
year,company,shares
|
||||
1988,大都会/ABC(Capital Cities/ABC),3000000
|
||||
1988,房地美(Freddie Mac),2400000
|
||||
1988,盖可保险(GEICO),6850000
|
||||
1988,华盛顿邮报(Washington Post),1727765
|
||||
1988,可口可乐(Coca-Cola),14172500
|
||||
1989,大都会/ABC(Capital Cities/ABC),3000000
|
||||
1989,房地美(Freddie Mac),2400000
|
||||
1989,盖可保险(GEICO),6850000
|
||||
1989,华盛顿邮报(Washington Post),1727765
|
||||
1989,可口可乐(Coca-Cola),23350000
|
||||
1990,大都会/ABC(Capital Cities/ABC),3000000
|
||||
1990,房地美(Freddie Mac),2400000
|
||||
1990,富国银行(Wells Fargo),5000000
|
||||
1990,盖可保险(GEICO),6850000
|
||||
1990,华盛顿邮报(Washington Post),1727765
|
||||
1990,可口可乐(Coca-Cola),46700000
|
||||
1991,大都会/ABC(Capital Cities/ABC),3000000
|
||||
1991,房地美(Freddie Mac),2495200
|
||||
1991,富国银行(Wells Fargo),5000000
|
||||
1991,盖可保险(GEICO),6850000
|
||||
1991,华盛顿邮报(Washington Post),1727765
|
||||
1991,吉列(Gillette),24000000
|
||||
1991,健力士(Guinness),31247000
|
||||
1991,可口可乐(Coca-Cola),46700000
|
||||
1992,大都会/ABC(Capital Cities/ABC),3000000
|
||||
1992,房地美(Freddie Mac),16196700
|
||||
1992,富国银行(Wells Fargo),6358418
|
||||
1992,盖可保险(GEICO),34250000
|
||||
1992,华盛顿邮报(Washington Post),1727765
|
||||
1992,吉列(Gillette),24000000
|
||||
1992,健力士(Guinness),38335000
|
||||
1992,可口可乐(Coca-Cola),93400000
|
||||
1992,通用动力(General Dynamics),4350000
|
||||
1993,大都会/ABC(Capital Cities/ABC),2000000
|
||||
1993,房地美(Freddie Mac),13654600
|
||||
1993,富国银行(Wells Fargo),6791218
|
||||
1993,盖可保险(GEICO),34250000
|
||||
1993,华盛顿邮报(Washington Post),1727765
|
||||
1993,吉列(Gillette),24000000
|
||||
1993,健力士(Guinness),38335000
|
||||
1993,可口可乐(Coca-Cola),93400000
|
||||
1993,通用动力(General Dynamics),4350000
|
||||
1994,大都会/ABC(Capital Cities/ABC),20000000
|
||||
1994,房地美(Freddie Mac),12761200
|
||||
1994,富国银行(Wells Fargo),6791218
|
||||
1994,盖可保险(GEICO),34250000
|
||||
1994,甘尼特(Gannett),6854500
|
||||
1994,华盛顿邮报(Washington Post),1727765
|
||||
1994,吉列(Gillette),24000000
|
||||
1994,可口可乐(Coca-Cola),100000000
|
||||
1994,美国运通(American Express),27759941
|
||||
1994,PNC 银行(PNC Bank),19453300
|
||||
1995,大都会/ABC(Capital Cities/ABC),20000000
|
||||
1995,房地美(Freddie Mac),12502500
|
||||
1995,富国银行(Wells Fargo),6791218
|
||||
1995,盖可保险(GEICO),34250000
|
||||
1995,吉列(Gillette),48000000
|
||||
1995,可口可乐(Coca-Cola),100000000
|
||||
1995,美国运通(American Express),49456900
|
||||
1996,房地美(Freddie Mac),64246000
|
||||
1996,富国银行(Wells Fargo),7291418
|
||||
1996,华盛顿邮报(Washington Post),1727765
|
||||
1996,华特迪士尼(Walt Disney),24614214
|
||||
1996,吉列(Gillette),48000000
|
||||
1996,可口可乐(Coca-Cola),200000000
|
||||
1996,麦当劳(McDonald's),30156600
|
||||
1996,美国运通(American Express),49456900
|
||||
1997,房地美(Freddie Mac),63977600
|
||||
1997,富国银行(Wells Fargo),6690218
|
||||
1997,华盛顿邮报(Washington Post),1727765
|
||||
1997,华特迪士尼(Walt Disney),21563414
|
||||
1997,吉列(Gillette),48000000
|
||||
1997,可口可乐(Coca-Cola),200000000
|
||||
1997,旅行家集团(Travelers Group),23733198
|
||||
1997,美国运通(American Express),49456900
|
||||
|
@@ -0,0 +1,105 @@
|
||||
1997/12/31,,,,,,,,,,,
|
||||
公司名称,股份数量,成本(截至1997/12/31)(百万美元)*,市值(截至1997/12/31)(百万美元),,,,,,,,
|
||||
美国运通 (American Express),"49,456,900 ","1,392.70 ","4,414.00 ",,,,,,,,
|
||||
可口可乐 (The Coca-Cola Company),"200,000,000 ","1,298.90 ","13,337.50 ",,,,,,,,
|
||||
迪士尼 (The Walt Disney Company),"21,563,414 ",381,"2,134.80 ",,,,,,,,
|
||||
房地美 (Freddie Mac),"63,977,600 ",329,"2,683.10 ",,,,,,,,
|
||||
吉列 (The Gillette Company),"48,000,000 ",600,"4,821.00 ",,,,,,,,
|
||||
旅行家集团 (Travelers Group Inc.),"23,733,198 ",604,"1,278.60 ",,,,,,,,
|
||||
华盛顿邮报 (The Washington Post Company),"1,727,765 ",11,841,,,,,,,,
|
||||
富国银行 (Wells Fargo & Company),"6,690,218 ",413,"2,270.90 ",,,,,,,,
|
||||
,,,,,,,,,,,
|
||||
1996/12/31,,,,,,,,,,,
|
||||
公司,股数,成本\*(百万美元,截至1996/12/31),市值(百万美元,截至1996/12/31),,,,,,,,
|
||||
美国运通,"49,456,900 ","1,392.70 ","2,794.30 ",,,,,,,,
|
||||
可口可乐,"200,000,000 ","1,298.90 ","10,525.00 ",,,,,,,,
|
||||
华特迪士尼,"24,614,214 ",577,"1,716.80 ",,,,,,,,
|
||||
房地美,"64,246,000 ",333,"1,772.80 ",,,,,,,,
|
||||
吉列,"48,000,000 ",600,"3,732.00 ",,,,,,,,
|
||||
麦当劳,"30,156,600 ","1,265.30 ","1,368.40 ",,,,,,,,
|
||||
华盛顿邮报,"1,727,765 ",11,579,,,,,,,,
|
||||
富国银行,"7,291,418 ",498,"1,966.90 ",,,,,,,,
|
||||
,,,,,,,,,,,
|
||||
1995/12/31,,,,,,,,,,,
|
||||
公司,股数,成本 1995/12/31(百万美元),市值 1995/12/31(百万美元),,,,,,,,
|
||||
美国运通 (American Express),"49,456,900 ","1,392.70 ","2,046.30 ",,,,,,,,
|
||||
大都会/ABC (Capital Cities/ABC),"20,000,000 ",345,"2,467.50 ",,,,,,,,
|
||||
可口可乐 (The Coca-Cola Company),"100,000,000 ","1,298.90 ","7,425.00 ",,,,,,,,
|
||||
房地美 (Freddie Mac),"12,502,500 ",260,"1,044.00 ",,,,,,,,
|
||||
盖可保险 (GEICO Corp.),"34,250,000 ",46,"2,393.20 ",,,,,,,,
|
||||
吉列 (The Gillette Company),"48,000,000 ",600,"2,502.00 ",,,,,,,,
|
||||
富国银行 (Wells Fargo & Company),"6,791,218 ",424,"1,466.90 ",,,,,,,,
|
||||
,,,,,,,,,,,
|
||||
1994/12/31,,,,,,,,,,,
|
||||
公司,股份数量,成本 (千美元),市值(千美元),,,,,,,,
|
||||
美国运通 ,"27,759,941 ","US$723,919 ","US$818,918 ",,,,,,,,
|
||||
大都会/ABC,"20,000,000 ","345,000 ","1,705,000 ",,,,,,,,
|
||||
可口可乐,"100,000,000 ","1,298,888 ","5,150,000 ",,,,,,,,
|
||||
房地美,"12,761,200 ","270,468 ","644,441 ",,,,,,,,
|
||||
甘尼特公司,"6,854,500 ","335,216 ","365,002 ",,,,,,,,
|
||||
盖可保险 (GEICO),"34,250,000 ","45,713 ","1,678,250 ",,,,,,,,
|
||||
吉列 (Gillette),"24,000,000 ","600,000 ","1,797,000 ",,,,,,,,
|
||||
PNC银行 (PNC Bank),"19,453,300 ","503,046 ","410,951 ",,,,,,,,
|
||||
华盛顿邮报,"1,727,765 ","9,731 ","418,983 ",,,,,,,,
|
||||
富国银行 ,"6,791,218 ","423,680 ","984,727 ",,,,,,,,
|
||||
,,,,,,,,,,,
|
||||
,,,,,,,,,,,
|
||||
1993/12/31,,,,,,,,,,,
|
||||
公司名称,股份数量,成本(千美元),市值(千美元),,,,,,,,
|
||||
大都会/ABC ,"2,000,000 ","US$345,000 ","US$1,239,000 ",,,,,,,,
|
||||
可口可乐 ,"93,400,000 ","1,023,920 ","4,167,975 ",,,,,,,,
|
||||
房地美 ,"13,654,600 ","307,505 ","681,023 ",,,,,,,,
|
||||
盖可保险 ,"34,250,000 ","45,713 ","1,759,594 ",,,,,,,,
|
||||
通用动力 ,"4,350,000 ","94,938 ","401,287 ",,,,,,,,
|
||||
吉列 ,"24,000,000 ","600,000 ","1,431,000 ",,,,,,,,
|
||||
健力士,"38,335,000 ","333,019 ","270,822 ",,,,,,,,
|
||||
华盛顿邮报 ,"1,727,765 ","9,731 ","440,148 ",,,,,,,,
|
||||
富国银行,"6,791,218 ","423,680 ","878,614 ",,,,,,,,
|
||||
,,,,,,,,,,,
|
||||
1992/12/31,,,,,,,,,,,
|
||||
公司名称,股份数量,成本 (千美元),市值(千美元),,,,,,,,
|
||||
大都会/ABC,"3,000,000 ","US$517,500 ","US$1,523,500 ",,,,,,,,
|
||||
可口可乐,"93,400,000 ","1,023,920 ","3,911,125 ",,,,,,,,
|
||||
房地美 ,"16,196,700 ","414,257 ","783,515 ",,,,,,,,
|
||||
盖可保险 ,"34,250,000 ","45,713 ","2,226,250 ",,,,,,,,
|
||||
通用动力,"4,350,000 ","312,438 ","450,769 ",,,,,,,,
|
||||
吉列 ,"24,000,000 ","600,000 ","1,365,000 ",,,,,,,,
|
||||
健力士,"38,335,000 ","333,019 ","299,581 ",,,,,,,,
|
||||
华盛顿邮报 ,"1,727,765 ","9,731 ","396,954 ",,,,,,,,
|
||||
富国银行,"6,358,418 ","380,983 ","485,624 ",,,,,,,,
|
||||
,,,,,,,,,,,
|
||||
1991/12/31,,,,,,,,,,,
|
||||
公司,股数,成本(千美元),市值(千美元),,,,,,,,
|
||||
大都会/ABC,"3,000,000 ","US$517,500 ","US$1,300,500 ",,,,,,,,
|
||||
可口可乐,"46,700,000 ","1,023,920 ","3,747,675 ",,,,,,,,
|
||||
房地美,"2,495,200 ","77,245 ","343,090 ",,,,,,,,
|
||||
盖可保险,"6,850,000 ","45,713 ","1,363,150 ",,,,,,,,
|
||||
吉列,"24,000,000 ","600,000 ","1,347,000 ",,,,,,,,
|
||||
健力士 ,"31,247,000 ","264,782 ","296,755 ",,,,,,,,
|
||||
华盛顿邮报,"1,727,765 ","9,731 ","336,050 ",,,,,,,,
|
||||
富国银行,"5,000,000 ","289,431 ","290,000 ",,,,,,,,
|
||||
,,,,,,,,,,,
|
||||
1990/12/31,,,,,,,,,,,
|
||||
公司,持股数量,成本(千美元),市值(千美元),,,,,,,,
|
||||
大都会/ABC公司,"3,000,000 ","US$517,500 ","US$1,377,375 ",,,,,,,,
|
||||
可口可乐公司,"46,700,000 ","1,023,920 ","2,171,550 ",,,,,,,,
|
||||
联邦住房贷款抵押公司,"2,400,000 ","71,729 ","117,000 ",,,,,,,,
|
||||
盖可保险公司,"6,850,000 ","45,713 ","1,110,556 ",,,,,,,,
|
||||
华盛顿邮报公司,"1,727,765 ","9,731 ","342,097 ",,,,,,,,
|
||||
富国银行,"5,000,000 ","289,431 ","289,375 ",,,,,,,,
|
||||
,,,,,,,,,,,
|
||||
1989/12/31,,,,,,,,,,,
|
||||
股份数量,公司名称,成本(千美元),市值(千美元),,,,,,,,
|
||||
"3,000,000 ",大都会/ABC,"US$517,500 ","US$1,692,375 ",,,,,,,,
|
||||
"23,350,000 ",可口可乐,"1,023,920 ","1,803,787 ",,,,,,,,
|
||||
"2,400,000 ",房地美优先股,"71,729 ","161,100 ",,,,,,,,
|
||||
"6,850,000 ",盖可保险,"45,713 ","1,044,625 ",,,,,,,,
|
||||
"1,727,765 ",华盛顿邮报 ,"9,731 ","486,366 ",,,,,,,,
|
||||
,,,,,,,,,,,
|
||||
1988/12/31,,,,,,,,,,,
|
||||
股份数量,公司名称,成本(千美元),市值(千美元),,,,,,,,
|
||||
"3,000,000 ",大都会/ABC,"US$517,500 ","US$1,086,750 ",,,,,,,,
|
||||
"14,172,500 ",可口可乐公司,"592,540 ","632,448 ",,,,,,,,
|
||||
"2,400,000 ",联邦住宅贷款抵押公司优先股\*,"71,729 ","121,200 ",,,,,,,,
|
||||
"6,850,000 ",盖可保险,"45,713 ","849,400 ",,,,,,,,
|
||||
"1,727,765 ",华盛顿邮报公司,"9,731 ","364,126 ",,,,,,,,
|
||||
|
Binary file not shown.
@@ -0,0 +1,11 @@
|
||||
period,total_assets,equity,liabilities,cash_like,cash,receivables,inventory
|
||||
2016,,,,,,,
|
||||
2017,,,,,,,
|
||||
2018,,,,,,,
|
||||
2019,,,,,,,
|
||||
2020,,,,,,,
|
||||
2021,,,,,,,
|
||||
2022,,,,,,,
|
||||
2023,,,,,,,
|
||||
2024,,,,,,,
|
||||
2025,,,,,,,
|
||||
|
@@ -0,0 +1,22 @@
|
||||
period,total_assets,equity,liabilities,cash_like,cash,receivables,inventory
|
||||
2021Q2,,,,,,,
|
||||
2021Q3,,,,,,,
|
||||
2021Q4,,,,,,,
|
||||
2022Q1,,,,,,,
|
||||
2022Q2,,,,,,,
|
||||
2022Q3,,,,,,,
|
||||
2022Q4,,,,,,,
|
||||
2023Q1,,,,,,,
|
||||
2023Q2,,,,,,,
|
||||
2023Q3,,,,,,,
|
||||
2023Q4,,,,,,,
|
||||
2024Q1,,,,,,,
|
||||
2024Q2,,,,,,,
|
||||
2024Q3,,,,,,,
|
||||
2024Q4,,,,,,,
|
||||
2025Q1,,,,,,,
|
||||
2025Q2,,,,,,,
|
||||
2025Q3,,,,,,,
|
||||
2025Q4,,,,,,,
|
||||
2026Q1,,,,,,,
|
||||
2026Q2,,,,,,,
|
||||
|
@@ -0,0 +1,31 @@
|
||||
period,planned_capacity,released_capacity,base_wine_output,commercial_wine_sales
|
||||
2000,,,5397.000000,
|
||||
2001,,,7317.000000,
|
||||
2002,,,8640.000000,
|
||||
2003,,,9757.000000,
|
||||
2004,,,11522.000000,6500.000000
|
||||
2005,,,12500.000000,7356.000000
|
||||
2006,,,13839.000000,7300.000000
|
||||
2007,,,16865.000000,8640.000000
|
||||
2008,,,20431.000000,9558.000000
|
||||
2009,,,23004.000000,10380.000000
|
||||
2010,,,26284.000000,10141.000000
|
||||
2011,,,30026.000000,11250.000000
|
||||
2012,,,33600.000000,14800.000000
|
||||
2013,,,38452.000000,15500.000000
|
||||
2014,,,38745.000000,18000.000000
|
||||
2015,,,32179.000000,19798.000000
|
||||
2016,32592.000000,31360.000000,39313.000000,22918.000000
|
||||
2017,35952.000000,32592.000000,42829.000000,30206.000000
|
||||
2018,37408.000000,35952.000000,49672.000000,32464.000000
|
||||
2019,38528.000000,37408.000000,49923.000000,34562.000000
|
||||
2020,42560.000000,38528.000000,50235.000000,34313.000000
|
||||
2021,42743.000000,42743.000000,56472.250000,36261.000000
|
||||
2022,42743.000000,42743.000000,56810.440000,37901.000000
|
||||
2023,42795.000000,42743.000000,57204.110000,42111.000000
|
||||
2024,44595.000000,,56271.990000,46412.950000
|
||||
2025,46395.000000,,58473.160000,46750.660000
|
||||
2026,,,,
|
||||
2027,,,,
|
||||
2028,,,,
|
||||
2029,,,,
|
||||
|
@@ -0,0 +1,11 @@
|
||||
period,operating_cash_flow,investing_cash_flow,financing_cash_flow,net_cash_increase
|
||||
2016,374.510000,-11.030000,-83.350000,280.140000
|
||||
2017,221.530000,-11.210000,-88.990000,121.330000
|
||||
2018,413.850000,-16.290000,-164.410000,233.150000
|
||||
2019,452.110000,-31.660000,-192.840000,227.610000
|
||||
2020,516.690000,-18.050000,-241.280000,257.370000
|
||||
2021,640.290000,-55.620000,-265.640000,319.000000
|
||||
2022,366.990000,-55.370000,-574.250000,-262.620000
|
||||
2023,665.930000,-97.240000,-588.890000,-20.190000
|
||||
2024,924.640000,-17.850000,-710.680000,196.100000
|
||||
2025,615.220000,-316.420000,-734.270000,-435.440000
|
||||
|
@@ -0,0 +1,22 @@
|
||||
period,operating_cash_flow,investing_cash_flow,financing_cash_flow,net_cash_increase
|
||||
2021Q2,217.190000,-38.720000,-264.080000,-85.610000
|
||||
2021Q3,150.330000,-3.940000,-1.200000,145.180000
|
||||
2021Q4,272.770000,-12.960000,-0.360000,259.430000
|
||||
2022Q1,-68.760000,-12.400000,-0.040000,-81.200000
|
||||
2022Q2,68.650000,-4.650000,-283.150000,-219.160000
|
||||
2022Q3,94.160000,-9.660000,-15.460000,69.050000
|
||||
2022Q4,272.940000,-28.660000,-275.600000,-31.310000
|
||||
2023Q1,52.450000,2.180000,-0.230000,54.400000
|
||||
2023Q2,251.420000,-6.980000,-325.740000,-81.290000
|
||||
2023Q3,196.150000,-78.360000,-21.280000,96.500000
|
||||
2023Q4,165.910000,-14.080000,-241.640000,-89.800000
|
||||
2024Q1,91.870000,-7.670000,-0.120000,84.080000
|
||||
2024Q2,274.350000,-21.320000,-388.040000,-135.010000
|
||||
2024Q3,77.990000,22.940000,-1.730000,99.200000
|
||||
2024Q4,480.430000,-11.800000,-320.790000,147.830000
|
||||
2025Q1,88.090000,-6.240000,-16.070000,65.780000
|
||||
2025Q2,43.100000,3.100000,-382.860000,-336.630000
|
||||
2025Q3,250.780000,-51.090000,-33.510000,166.170000
|
||||
2025Q4,233.250000,-262.190000,-301.830000,-330.760000
|
||||
2026Q1,269.100000,256.390000,-10.060000,515.430000
|
||||
2026Q2,437.810000,0.020000,-369.380000,68.420000
|
||||
|
@@ -0,0 +1,11 @@
|
||||
period,direct,wholesale,direct_share,wholesale_share
|
||||
2016,34.850000,353.560000,0.089725,0.910275
|
||||
2017,,,,
|
||||
2018,43.760000,691.890000,0.059485,0.940515
|
||||
2019,72.490000,780.960000,0.084938,0.915062
|
||||
2020,132.400000,815.820000,0.139630,0.860370
|
||||
2021,240.290000,820.300000,0.226563,0.773437
|
||||
2022,493.790000,743.940000,0.398948,0.601052
|
||||
2023,672.330000,799.860000,0.456687,0.543313
|
||||
2024,748.433270,957.685110,0.438676,0.561324
|
||||
2025,845.430319,842.315533,0.500923,0.499077
|
||||
|
@@ -0,0 +1,31 @@
|
||||
period,moutai,series,direct,wholesale,moutai_share,direct_share
|
||||
2019Q1,194.980000,21.320000,10.920000,205.380000,0.901433,0.050485
|
||||
2019Q2,347.950000,46.550000,16.020000,378.480000,0.882003,0.040608
|
||||
2019Q3,538.320000,70.380000,31.030000,577.670000,0.884377,0.050977
|
||||
2019Q4,758.020000,95.420000,72.490000,780.960000,0.888194,0.084938
|
||||
2020Q1,222.220000,21.690000,19.380000,224.530000,0.911074,0.079456
|
||||
2020Q2,392.610000,46.500000,51.530000,387.590000,0.894104,0.117348
|
||||
2020Q3,601.450000,70.000000,84.330000,587.130000,0.895748,0.125592
|
||||
2020Q4,848.310000,99.910000,132.400000,815.820000,0.894634,0.139630
|
||||
2021Q1,245.920000,26.430000,47.770000,224.570000,0.902956,0.175406
|
||||
2021Q2,429.490000,60.600000,95.040000,395.050000,0.876349,0.193924
|
||||
2021Q3,649.920000,95.400000,146.850000,598.470000,0.872001,0.197029
|
||||
2021Q4,934.650000,125.950000,240.290000,820.300000,0.881246,0.226563
|
||||
2022Q1,288.600000,34.280000,108.870000,214.010000,0.893831,0.337184
|
||||
2022Q2,499.650000,75.980000,209.490000,366.140000,0.868005,0.363932
|
||||
2022Q3,744.000000,125.400000,318.820000,550.590000,0.855763,0.366708
|
||||
2022Q4,1078.340000,159.390000,493.790000,743.940000,0.871224,0.398948
|
||||
2023Q1,337.220000,50.140000,178.070000,209.290000,0.870560,0.459702
|
||||
2023Q2,592.790000,100.740000,314.200000,379.330000,0.854743,0.453045
|
||||
2023Q3,872.700000,155.940000,462.070000,566.570000,0.848402,0.449205
|
||||
2023Q4,1265.890000,206.300000,672.330000,799.860000,0.859869,0.456687
|
||||
2024Q1,397.070000,59.360000,193.190000,263.240000,0.869947,0.423263
|
||||
2024Q2,685.670000,131.470000,337.280000,479.860000,0.839110,0.412757
|
||||
2024Q3,1011.260000,193.930000,519.890000,685.290000,0.839088,0.431380
|
||||
2024Q4,1459.280000,246.840000,748.430000,957.690000,0.855321,0.438674
|
||||
2025Q1,435.570000,70.220000,232.200000,273.600000,0.861168,0.459075
|
||||
2025Q2,755.900000,137.630000,400.100000,493.430000,0.845970,0.447775
|
||||
2025Q3,1105.140000,178.840000,555.550000,728.420000,0.860714,0.432681
|
||||
2025Q4,1465.000000,222.750000,845.430000,842.320000,0.868020,0.500921
|
||||
2026Q1,460.048686,78.808783,295.040339,243.817130,0.853748,0.547529
|
||||
2026Q2,777.240000,129.340000,519.620000,386.970000,0.857332,0.573159
|
||||
|
@@ -0,0 +1,31 @@
|
||||
period,moutai,series,direct,wholesale,moutai_share,direct_share
|
||||
2019Q1,194.980000,21.320000,10.920000,205.380000,0.901433,0.050485
|
||||
2019Q2,152.970000,25.230000,5.100000,173.100000,0.858418,0.028620
|
||||
2019Q3,190.380000,23.830000,15.010000,199.190000,0.888754,0.070075
|
||||
2019Q4,219.700000,25.050000,41.460000,203.290000,0.897651,0.169397
|
||||
2020Q1,222.220000,21.690000,19.380000,224.530000,0.911074,0.079456
|
||||
2020Q2,170.390000,24.820000,32.140000,163.060000,0.872855,0.164652
|
||||
2020Q3,208.840000,23.500000,32.800000,199.540000,0.898855,0.141172
|
||||
2020Q4,246.860000,29.910000,48.070000,228.690000,0.891932,0.173688
|
||||
2021Q1,245.920000,26.430000,47.770000,224.570000,0.902956,0.175406
|
||||
2021Q2,183.570000,34.180000,47.270000,170.480000,0.843031,0.217084
|
||||
2021Q3,220.430000,34.790000,51.810000,203.410000,0.863686,0.203001
|
||||
2021Q4,284.730000,30.550000,93.440000,221.830000,0.903102,0.296381
|
||||
2022Q1,288.600000,34.280000,108.870000,214.010000,0.893831,0.337184
|
||||
2022Q2,211.050000,41.700000,100.620000,152.130000,0.835015,0.398101
|
||||
2022Q3,244.350000,49.420000,109.320000,184.450000,0.831773,0.372128
|
||||
2022Q4,334.340000,33.980000,174.970000,193.350000,0.907743,0.475049
|
||||
2023Q1,337.220000,50.140000,178.070000,209.290000,0.870560,0.459702
|
||||
2023Q2,255.570000,50.600000,136.130000,170.040000,0.834732,0.444622
|
||||
2023Q3,279.910000,55.200000,147.870000,187.240000,0.835278,0.441258
|
||||
2023Q4,393.200000,50.360000,210.260000,233.290000,0.886464,0.474039
|
||||
2024Q1,397.070000,59.360000,193.190000,263.240000,0.869947,0.423263
|
||||
2024Q2,288.600000,72.110000,144.090000,216.620000,0.800089,0.399462
|
||||
2024Q3,325.590000,62.460000,182.610000,205.430000,0.839041,0.470596
|
||||
2024Q4,448.020000,52.910000,228.540000,272.390000,0.894376,0.456231
|
||||
2025Q1,435.570000,70.220000,232.200000,273.600000,0.861168,0.459075
|
||||
2025Q2,320.320000,67.400000,167.890000,219.830000,0.826163,0.433019
|
||||
2025Q3,349.240000,41.220000,155.460000,235.000000,0.894432,0.398146
|
||||
2025Q4,359.860000,43.900000,289.880000,113.890000,0.891272,0.717933
|
||||
2026Q1,460.048686,78.808783,295.040339,243.817130,0.853748,0.547529
|
||||
2026Q2,317.190000,50.530000,224.580000,143.150000,0.862586,0.610720
|
||||
|
@@ -0,0 +1,10 @@
|
||||
period,domestic,overseas,total
|
||||
2022年报,2084.000000,105.000000,2189.000000
|
||||
2023年报,2080.000000,106.000000,2186.000000
|
||||
2024年报,2143.000000,104.000000,2247.000000
|
||||
2025Q1,2174.000000,109.000000,2283.000000
|
||||
2025Q2,2280.000000,115.000000,2395.000000
|
||||
2025Q3,2325.000000,121.000000,2446.000000
|
||||
2025年报,2353.000000,126.000000,2479.000000
|
||||
2026Q1,2098.000000,124.000000,2222.000000
|
||||
2026Q2,2307.000000,119.000000,2426.000000
|
||||
|
@@ -0,0 +1,27 @@
|
||||
period,dividend,net_profit,payout_ratio
|
||||
2001,1.500000,3.282907,0.456912
|
||||
2002,0.550000,3.767985,0.145967
|
||||
2003,0.907500,5.867478,0.154666
|
||||
2004,1.966250,8.205540,0.239625
|
||||
2005,1.415700,11.577382,0.122282
|
||||
2006,12.184458,15.448124,0.788734
|
||||
2007,7.890168,28.308316,0.278723
|
||||
2008,10.910328,37.994806,0.287153
|
||||
2009,11.184030,43.081146,0.259604
|
||||
2010,21.707400,50.511942,0.429748
|
||||
2011,41.496055,87.631459,0.473529
|
||||
2012,66.640774,133.080796,0.500754
|
||||
2013,45.409993,151.366398,0.300000
|
||||
2014,49.950993,153.498043,0.325418
|
||||
2015,77.519966,155.030903,0.500029
|
||||
2016,85.258145,167.183627,0.509967
|
||||
2017,138.169196,270.793603,0.510238
|
||||
2018,182.638598,352.036253,0.518806
|
||||
2019,213.867676,412.064710,0.519015
|
||||
2020,242.358242,466.972854,0.518999
|
||||
2021,272.280873,525.811027,0.517830
|
||||
2022,547.513811,627.164437,0.872999
|
||||
2023,627.872784,747.340716,0.840143
|
||||
2024,646.720000,862.281464,0.750010
|
||||
2025,650.330000,823.200671,0.790002
|
||||
2025,300.006375,646.267467,0.464214
|
||||
|
@@ -0,0 +1,23 @@
|
||||
period,sales_expense,admin_expense,rd_expense,tax_surcharge,revenue,sales_expense_rate,admin_expense_rate,rd_expense_rate,tax_surcharge_rate
|
||||
2004,,,,,,,,,
|
||||
2005,,,,,,,,,
|
||||
2006,,,,,,,,,
|
||||
2007,,,,,,,,,
|
||||
2008,,,,,,,,,
|
||||
2009,,,,,,,,,
|
||||
2010,,,,,,,,,
|
||||
2011,,,,,,,,,
|
||||
2012,,,,,,,,,
|
||||
2013,,,,,,,,,
|
||||
2014,,,,,,,,,
|
||||
2015,,,,,,,,,
|
||||
2016,,,,,,,,,
|
||||
2017,,,,,,,,,
|
||||
2018,,,,,,,,,
|
||||
2019,,,,,,,,,
|
||||
2020,,,,,,,,,
|
||||
2021,,,,,,,,,
|
||||
2022,,,,,,,,,
|
||||
2023,,,,,,,,,
|
||||
2024,,,,,,,,,
|
||||
2025,,,,,,,,,
|
||||
|
@@ -0,0 +1,27 @@
|
||||
period,sales_expense,admin_expense,rd_expense,tax_surcharge,revenue,gross_margin,sales_expense_rate,admin_expense_rate,rd_expense_rate,tax_surcharge_rate
|
||||
2020Q1,,,,,,,,,,
|
||||
2020Q2,,,,,,,,,,
|
||||
2020Q3,,,,,,,,,,
|
||||
2020Q4,,,,,,,,,,
|
||||
2021Q1,,,,,,,,,,
|
||||
2021Q2,,,,,,,,,,
|
||||
2021Q3,,,,,,,,,,
|
||||
2021Q4,,,,,,,,,,
|
||||
2022Q1,,,,,,,,,,
|
||||
2022Q2,,,,,,,,,,
|
||||
2022Q3,,,,,,,,,,
|
||||
2022Q4,,,,,,,,,,
|
||||
2023Q1,,,,,,,,,,
|
||||
2023Q2,,,,,,,,,,
|
||||
2023Q3,,,,,,,,,,
|
||||
2023Q4,,,,,,,,,,
|
||||
2024Q1,,,,,,,,,,
|
||||
2024Q2,,,,,,,,,,
|
||||
2024Q3,,,,,,,,,,
|
||||
2024Q4,,,,,,,,,,
|
||||
2025Q1,,,,,,,,,,
|
||||
2025Q2,,,,,,,,,,
|
||||
2025Q3,,,,,,,,,,
|
||||
2025Q4,,,,,,,,,,
|
||||
2026Q1,,,,,,,,,,
|
||||
2026Q2,,,,,,,,,,
|
||||
|
@@ -0,0 +1,17 @@
|
||||
period,wholesale_price
|
||||
2010,1000.000000
|
||||
2011,1200.000000
|
||||
2012,2300.000000
|
||||
2013,1500.000000
|
||||
2014,1000.000000
|
||||
2015,850.000000
|
||||
2016,1100.000000
|
||||
2017,1800.000000
|
||||
2018,2000.000000
|
||||
2019,2600.000000
|
||||
2020,2600.000000
|
||||
2021,3100.000000
|
||||
2022,3000.000000
|
||||
2023,2900.000000
|
||||
2024,2350.000000
|
||||
2025,1485.000000
|
||||
|
@@ -0,0 +1,21 @@
|
||||
period,output,yoy
|
||||
2006,397.100000,
|
||||
2007,493.900000,
|
||||
2008,569.300000,
|
||||
2009,706.900000,
|
||||
2010,890.800000,
|
||||
2011,1025.600000,
|
||||
2012,1153.200000,
|
||||
2013,1226.200000,
|
||||
2014,1257.100000,
|
||||
2015,1312.800000,
|
||||
2016,1358.400000,
|
||||
2017,1198.100000,
|
||||
2018,871.200000,
|
||||
2019,786.000000,
|
||||
2020,740.700000,
|
||||
2021,715.600000,
|
||||
2022,671.200000,
|
||||
2023,449.200000,
|
||||
2024,414.500000,
|
||||
2025,354.900000,
|
||||
|
@@ -0,0 +1,13 @@
|
||||
period,before_price,after_price,increase_rate
|
||||
2001-08,185.000000,218.000000,0.178000
|
||||
2003-10,218.000000,268.000000,0.229000
|
||||
2006-02,268.000000,308.000000,0.149000
|
||||
2007-03,308.000000,358.000000,0.162000
|
||||
2008-01,358.000000,439.000000,0.226000
|
||||
2010-01,439.000000,499.000000,0.137000
|
||||
2011-01,499.000000,619.000000,0.240000
|
||||
2012-09,619.000000,819.000000,0.323000
|
||||
2018-01,819.000000,969.000000,0.183000
|
||||
2023-11,969.000000,1169.000000,0.206000
|
||||
2026-03,1169.000000,1269.000000,0.086000
|
||||
2026-07,1269.000000,1369.000000,0.079000
|
||||
|
@@ -0,0 +1,11 @@
|
||||
period,moutai,series,moutai_share,series_share
|
||||
2016,367.140000,21.270000,0.945238,0.054762
|
||||
2017,523.940000,57.740000,0.900736,0.099264
|
||||
2018,654.870000,80.770000,0.890204,0.109796
|
||||
2019,758.020000,95.420000,0.888194,0.111806
|
||||
2020,848.310000,99.910000,0.894634,0.105366
|
||||
2021,934.650000,125.950000,0.881246,0.118754
|
||||
2022,1078.340000,159.390000,0.871224,0.128776
|
||||
2023,1265.890000,206.300000,0.859869,0.140131
|
||||
2024,1459.280760,246.837621,0.855322,0.144678
|
||||
2025,1464.999065,222.746787,0.868021,0.131979
|
||||
|
@@ -0,0 +1,22 @@
|
||||
period,revenue,gross_profit,net_profit,gross_margin,revenue_yoy,net_profit_yoy
|
||||
2005,39.310000,,11.190000,,,
|
||||
2006,48.960000,,15.040000,,0.245485,0.344057
|
||||
2007,72.370000,,28.310000,,0.478145,0.882314
|
||||
2008,82.420000,,37.990000,,0.138870,0.341929
|
||||
2009,96.700000,,43.120000,,0.173259,0.135036
|
||||
2010,116.330000,,50.510000,,0.202999,0.171382
|
||||
2011,184.020000,,87.630000,,0.581879,0.734904
|
||||
2012,264.550000,,133.080000,,0.437615,0.518658
|
||||
2013,309.220000,,151.370000,,0.168853,0.137436
|
||||
2014,315.740000,,153.500000,,0.021085,0.014071
|
||||
2015,326.600000,,155.030000,,0.034395,0.009967
|
||||
2016,388.620000,,167.180000,0.912300,0.189896,0.078372
|
||||
2017,582.180000,,270.790000,0.898000,0.498070,0.619751
|
||||
2018,736.390000,,352.040000,0.911400,0.264884,0.300048
|
||||
2019,854.300000,,412.060000,0.913000,0.160119,0.170492
|
||||
2020,949.150000,867.390000,466.970000,0.914100,0.111027,0.133257
|
||||
2021,1061.900000,972.070000,524.600000,0.915400,0.118790,0.123413
|
||||
2022,1241.000000,1140.060000,627.160000,0.918700,0.168660,0.195501
|
||||
2023,1476.940000,1358.260000,747.340000,0.919600,0.190121,0.191626
|
||||
2024,1708.990000,1571.100000,862.280000,0.919300,0.157115,0.153799
|
||||
2025,1688.380000,1539.460000,823.200000,0.911800,-0.012060,-0.045322
|
||||
|
@@ -0,0 +1,53 @@
|
||||
period,revenue,gross_profit,net_profit,gross_margin,revenue_yoy,net_profit_yoy
|
||||
2020Q1,244.050000,223.730000,130.940000,0.916700,,
|
||||
2020Q1,20.320000,,,,-0.916738,
|
||||
2020Q2,195.470000,178.280000,95.080000,0.912000,8.619587,
|
||||
2020Q2,17.200000,,,,-0.912007,
|
||||
2020Q3,232.620000,211.840000,112.250000,0.910600,12.524419,
|
||||
2020Q3,20.790000,,,,-0.910627,
|
||||
2020Q4,277.000000,253.770000,128.700000,0.916100,12.323713,
|
||||
2020Q4,23.230000,,,,-0.916137,
|
||||
2021Q1,272.710000,250.010000,139.540000,0.916800,10.739561,
|
||||
2021Q1,22.700000,,,,-0.916761,
|
||||
2021Q2,218.160000,198.540000,107.000000,0.910100,8.610573,
|
||||
2021Q2,19.620000,,,,-0.910066,
|
||||
2021Q3,255.550000,232.120000,126.120000,0.908300,12.024975,
|
||||
2021Q3,23.430000,,,,-0.908315,
|
||||
2021Q4,315.480000,291.400000,151.940000,0.923700,12.464789,
|
||||
2021Q4,24.080000,,,,-0.923672,
|
||||
2022Q1,322.960000,298.310000,172.450000,0.923700,12.411960,
|
||||
2022Q1,24.650000,,,,-0.923675,
|
||||
2022Q2,253.210000,232.400000,125.490000,0.917800,9.272211,
|
||||
2022Q2,20.810000,,,,-0.917815,
|
||||
2022Q3,295.430000,270.080000,146.060000,0.914200,13.196540,
|
||||
2022Q3,25.360000,,,,-0.914159,
|
||||
2022Q4,369.400000,339.280000,183.170000,0.918500,13.566246,
|
||||
2022Q4,30.110000,,,,-0.918489,
|
||||
2023Q1,387.560000,358.860000,207.950000,0.926000,11.871471,
|
||||
2023Q1,28.700000,,,,-0.925947,
|
||||
2023Q2,308.200000,279.830000,151.860000,0.908000,9.738676,
|
||||
2023Q2,28.370000,,,,-0.907949,
|
||||
2023Q3,336.920000,308.350000,168.960000,0.915200,10.875925,
|
||||
2023Q3,28.570000,,,,-0.915202,
|
||||
2023Q4,444.250000,411.220000,218.580000,0.925600,14.549527,
|
||||
2023Q4,33.030000,,,,-0.925650,
|
||||
2024Q1,457.760000,423.940000,240.650000,0.926100,12.858916,
|
||||
2024Q1,33.810000,,,,-0.926140,
|
||||
2024Q2,361.550000,327.860000,176.300000,0.906800,9.693582,
|
||||
2024Q2,33.700000,,,,-0.906790,
|
||||
2024Q3,388.450000,353.680000,191.320000,0.910500,10.526706,
|
||||
2024Q3,34.770000,,,,-0.910490,
|
||||
2024Q4,501.230000,465.620000,254.010000,0.928900,13.415588,
|
||||
2024Q4,35.610000,,,,-0.928955,
|
||||
2025Q1,506.010000,465.400000,268.470000,0.919700,13.209773,
|
||||
2025Q1,40.610000,,,,-0.919745,
|
||||
2025Q2,387.880000,350.720000,185.550000,0.904200,8.551342,
|
||||
2025Q2,37.160000,,,,-0.904197,
|
||||
2025Q3,390.640000,356.580000,192.240000,0.912800,9.512379,
|
||||
2025Q3,34.070000,,,,-0.912784,
|
||||
2025Q4,403.840000,366.760000,176.930000,0.908000,10.853243,
|
||||
2025Q4,37.080000,,,,-0.908181,
|
||||
2026Q1,539.090000,483.890000,272.430000,0.908000,13.538565,
|
||||
2026Q1,55.210000,,,,-0.897587,
|
||||
2026Q2,367.940000,328.400000,172.740000,,5.664372,
|
||||
2026Q2,39.530000,,,,-0.892564,
|
||||
|
@@ -0,0 +1,21 @@
|
||||
period,moutai,series,domestic,overseas
|
||||
2016H2,367.140000,21.270000,367.820000,20.590000
|
||||
2017H1,216.270000,25.490000,233.340000,8.420000
|
||||
2017H2,523.940000,57.740000,558.980000,22.700000
|
||||
2018H1,293.830000,39.930000,323.450000,10.310000
|
||||
2018H2,654.870000,80.770000,706.720000,28.930000
|
||||
2019H1,347.950000,46.550000,383.680000,10.810000
|
||||
2019H2,758.020000,95.420000,824.240000,29.200000
|
||||
2020H1,392.610000,46.500000,428.100000,11.010000
|
||||
2020H2,848.310000,99.910000,923.900000,24.320000
|
||||
2021H1,429.490000,60.600000,478.100000,12.000000
|
||||
2021H2,934.650000,125.950000,1034.410000,26.180000
|
||||
2022H1,499.650000,75.980000,554.970000,20.660000
|
||||
2022H2,1078.340000,159.390000,1195.330000,42.400000
|
||||
2023H1,592.790000,100.740000,673.110000,20.420000
|
||||
2023H2,1265.890000,206.300000,1428.690000,43.500000
|
||||
2024H1,685.670000,131.470000,797.180000,22.130000
|
||||
2024H2,1459.280000,246.840000,1654.230000,51.890000
|
||||
2025H1,755.900000,137.630000,864.900000,28.990000
|
||||
2025H2,1465.000000,222.750000,1639.240000,48.500000
|
||||
2026H1,777.240000,129.340000,896.660000,10.370000
|
||||
|
@@ -0,0 +1,9 @@
|
||||
period,moutai_revenue,series_revenue,moutai_volume,series_volume,moutai_price_per_ton,series_price_per_ton
|
||||
2016,367.140000,21.270000,22918.000000,14027.000000,160.197225,15.163613
|
||||
2017,523.940000,57.740000,30206.000000,29903.000000,173.455605,19.309099
|
||||
2018,654.870000,80.770000,32464.000000,29774.000000,201.721907,27.127695
|
||||
2019,758.020000,95.420000,34562.000000,30083.000000,219.321799,31.718911
|
||||
2020,848.310000,99.910000,34313.000000,29743.000000,247.226999,33.591097
|
||||
2021,934.650000,125.950000,36261.000000,30177.000000,257.756267,41.737085
|
||||
2022,1078.340000,159.390000,37901.000000,30275.000000,284.514920,52.647399
|
||||
2023,1265.890000,206.300000,42111.000000,31165.000000,300.607917,66.196053
|
||||
|
@@ -0,0 +1,5 @@
|
||||
# 贵州茅台图表数据
|
||||
|
||||
本目录保留原始附件:年度与季度工作簿均不作编辑。图表使用的 `data/moutai/*.csv` 由 `scripts/normalize_moutai_financials.py` 生成。
|
||||
|
||||
口径说明:金额默认单位为人民币亿元;季度现金流由各披露累计值与同年度上一季度累计值相减得到单季金额;“分类营收”中的单季与累计数据分别保留,图表不混用两种口径。
|
||||
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,8 @@
|
||||
period,retail,robot_store,own_app,tiktok,amazon,online_other,wholesale,total_revenue
|
||||
2024H1,0.9,0.1,0.3,0.1,0.0,0.2,0.2,1.83
|
||||
2024H2,2.5,0.3,1.6,0.9,0.0,0.5,0.4,6.19
|
||||
2024FY,3.4,0.4,1.9,1.1,0.0,0.7,0.6,8.02
|
||||
2025H1,7.4,1.1,8.9,3.1,0.0,1.3,1.0,22.65
|
||||
2025H2,12.7,1.3,19.8,7.3,0.0,3.2,1.2,45.42
|
||||
2025FY,20.0,2.3,28.7,10.4,0.0,4.4,2.2,68.07
|
||||
2026H1,9.0,1.0,4.9,0.0,1.7,0.6,1.6,18.91
|
||||
|
@@ -0,0 +1,3 @@
|
||||
period,retail,robot_store,own_app,tiktok,amazon,online_other,wholesale,total_revenue
|
||||
2024FY,3.4,0.4,1.9,1.1,0.0,0.7,0.6,8.02
|
||||
2025FY,20.0,2.3,28.7,10.4,0.0,4.4,2.2,68.07
|
||||
|
@@ -0,0 +1,6 @@
|
||||
period,retail,robot_store,own_app,tiktok,amazon,online_other,wholesale,total_revenue
|
||||
2024H1,0.9,0.1,0.3,0.1,0.0,0.2,0.2,1.83
|
||||
2024H2,2.5,0.3,1.6,0.9,0.0,0.5,0.4,6.19
|
||||
2025H1,7.4,1.1,8.9,3.1,0.0,1.3,1.0,22.65
|
||||
2025H2,12.7,1.3,19.8,7.3,0.0,3.2,1.2,45.42
|
||||
2026H1,9.0,1.0,4.9,0.0,1.7,0.6,1.6,18.91
|
||||
|
@@ -0,0 +1,8 @@
|
||||
period,retail,robot_store,own_app,shopee,online_other,wholesale,total_revenue
|
||||
2024H1,4.8,0.2,0.4,0.4,0.8,1.3,7.97
|
||||
2024H2,11.8,0.2,2.6,2.4,3.0,3.0,23.13
|
||||
2024FY,16.7,0.4,3.0,2.9,3.8,4.3,31.10
|
||||
2025H1,14.9,0.3,3.1,2.8,4.8,2.6,28.51
|
||||
2025H2,28.3,0.6,11.4,2.9,5.5,2.9,51.60
|
||||
2025FY,43.2,0.9,14.6,5.7,10.3,5.4,80.11
|
||||
2026H1,17.2,0.6,3.4,1.1,2.0,1.6,25.76
|
||||
|
@@ -0,0 +1,3 @@
|
||||
period,retail,robot_store,own_app,shopee,online_other,wholesale,total_revenue
|
||||
2024FY,16.7,0.4,3.0,2.9,3.8,4.3,31.10
|
||||
2025FY,43.2,0.9,14.6,5.7,10.3,5.4,80.11
|
||||
|
@@ -0,0 +1,6 @@
|
||||
period,retail,robot_store,own_app,shopee,online_other,wholesale,total_revenue
|
||||
2024H1,4.8,0.2,0.4,0.4,0.8,1.3,7.97
|
||||
2024H2,11.8,0.2,2.6,2.4,3.0,3.0,23.13
|
||||
2025H1,14.9,0.3,3.1,2.8,4.8,2.6,28.51
|
||||
2025H2,28.3,0.6,11.4,2.9,5.5,2.9,51.60
|
||||
2026H1,17.2,0.6,3.4,1.1,2.0,1.6,25.76
|
||||
|
@@ -0,0 +1,8 @@
|
||||
period,retail,robot_store,roboshop,tmall,douyin,online_other,wholesale,total_revenue
|
||||
2024H1,20.0,3.4,4.0,2.0,2.1,1.3,2.4,35.22
|
||||
2024H2,26.0,4.1,7.2,4.3,3.9,3.0,5.2,53.65
|
||||
2024FY,46.0,7.5,11.1,6.3,6.0,4.3,7.6,88.87
|
||||
2025H1,44.1,6.8,11.3,6.6,5.6,5.9,2.6,82.82
|
||||
2025H2,56.7,6.7,23.0,11.3,10.3,11.3,6.5,125.69
|
||||
2025FY,100.8,13.5,34.2,17.9,15.9,17.2,9.1,208.51
|
||||
2026H1,60.9,7.8,20.6,9.0,9.8,8.4,5.5,122.00
|
||||
|
@@ -0,0 +1,3 @@
|
||||
period,retail,robot_store,roboshop,tmall,douyin,online_other,wholesale,total_revenue
|
||||
2024FY,46.0,7.5,11.1,6.3,6.0,4.3,7.6,88.87
|
||||
2025FY,100.8,13.5,34.2,17.9,15.9,17.2,9.1,208.51
|
||||
|
@@ -0,0 +1,6 @@
|
||||
period,retail,robot_store,roboshop,tmall,douyin,online_other,wholesale,total_revenue
|
||||
2024H1,20.0,3.4,4.0,2.0,2.1,1.3,2.4,35.22
|
||||
2024H2,26.0,4.1,7.2,4.3,3.9,3.0,5.2,53.65
|
||||
2025H1,44.1,6.8,11.3,6.6,5.6,5.9,2.6,82.82
|
||||
2025H2,56.7,6.7,23.0,11.3,10.3,11.3,6.5,125.69
|
||||
2026H1,60.9,7.8,20.6,9.0,9.8,8.4,5.5,122.00
|
||||
|
@@ -0,0 +1,8 @@
|
||||
period,retail,robot_store,own_app,online_other,wholesale,total_revenue
|
||||
2024H1,0.4,0.0,0.0,0.1,0.1,0.58
|
||||
2024H2,1.2,0.0,0.2,0.2,0.2,1.81
|
||||
2024FY,1.6,0.0,0.2,0.3,0.3,2.39
|
||||
2025H1,2.7,0.0,0.8,0.8,0.4,4.77
|
||||
2025H2,5.8,0.1,2.3,1.1,0.4,9.74
|
||||
2025FY,8.6,0.2,3.2,1.9,0.7,14.51
|
||||
2026H1,4.0,0.1,0.5,0.1,0.2,5.06
|
||||
|
@@ -0,0 +1,3 @@
|
||||
period,retail,robot_store,own_app,online_other,wholesale,total_revenue
|
||||
2024FY,1.6,0.0,0.2,0.3,0.3,2.39
|
||||
2025FY,8.6,0.2,3.2,1.9,0.7,14.51
|
||||
|
@@ -0,0 +1,6 @@
|
||||
period,retail,robot_store,own_app,online_other,wholesale,total_revenue
|
||||
2024H1,0.4,0.0,0.0,0.1,0.1,0.58
|
||||
2024H2,1.2,0.0,0.2,0.2,0.2,1.81
|
||||
2025H1,2.7,0.0,0.8,0.8,0.4,4.77
|
||||
2025H2,5.8,0.1,2.3,1.1,0.4,9.74
|
||||
2026H1,4.0,0.1,0.5,0.1,0.2,5.06
|
||||
|
@@ -0,0 +1,8 @@
|
||||
period,retail,online,robot_store,wholesale,total_revenue
|
||||
2024H1,57.4,25.9,8.1,8.6,45.59
|
||||
2024H2,49.0,35.2,5.4,10.4,84.78
|
||||
2024FY,51.9,31.9,6.4,9.8,130.37
|
||||
2025H1,49.8,39.6,5.9,4.7,138.75
|
||||
2025H2,44.5,47.0,3.7,4.7,232.45
|
||||
2025FY,46.5,44.3,4.6,4.7,371.20
|
||||
2026H1,53.1,36.2,5.5,5.2,171.74
|
||||
|
@@ -0,0 +1,8 @@
|
||||
period,retail,robot_store,own_app,tiktok,amazon,online_other,wholesale,total_revenue
|
||||
2024H1,47.5,7.1,15.8,8.2,0.0,10.9,10.4,1.83
|
||||
2024H2,41.0,4.0,26.3,15.0,0.0,7.3,6.3,6.19
|
||||
2024FY,42.5,4.7,23.9,13.5,0.0,8.1,7.2,8.02
|
||||
2025H1,32.5,4.6,39.2,13.9,0.0,5.5,4.2,22.65
|
||||
2025H2,27.9,2.8,43.7,16.0,0.0,7.0,2.7,45.42
|
||||
2025FY,29.4,3.4,42.2,15.3,0.0,6.5,3.2,68.07
|
||||
2026H1,47.8,5.5,26.0,0.0,8.9,3.3,8.6,18.91
|
||||
|
@@ -0,0 +1,3 @@
|
||||
period,retail,robot_store,own_app,tiktok,amazon,online_other,wholesale,total_revenue
|
||||
2024FY,42.5,4.7,23.9,13.5,0.0,8.1,7.2,8.02
|
||||
2025FY,29.4,3.4,42.2,15.3,0.0,6.5,3.2,68.07
|
||||
|
@@ -0,0 +1,6 @@
|
||||
period,retail,robot_store,own_app,tiktok,amazon,online_other,wholesale,total_revenue
|
||||
2024H1,47.5,7.1,15.8,8.2,0.0,10.9,10.4,1.83
|
||||
2024H2,41.0,4.0,26.3,15.0,0.0,7.3,6.3,6.19
|
||||
2025H1,32.5,4.6,39.2,13.9,0.0,5.5,4.2,22.65
|
||||
2025H2,27.9,2.8,43.7,16.0,0.0,7.0,2.7,45.42
|
||||
2026H1,47.8,5.5,26.0,0.0,8.9,3.3,8.6,18.91
|
||||
|
@@ -0,0 +1,3 @@
|
||||
period,retail,online,robot_store,wholesale,total_revenue
|
||||
2024FY,51.9,31.9,6.4,9.8,130.37
|
||||
2025FY,46.5,44.3,4.6,4.7,371.20
|
||||
|
@@ -0,0 +1,8 @@
|
||||
period,retail,robot_store,own_app,shopee,online_other,wholesale,total_revenue
|
||||
2024H1,60.9,2.1,5.0,5.1,10.7,16.2,7.97
|
||||
2024H2,51.1,1.0,11.5,10.5,12.8,13.0,23.13
|
||||
2024FY,53.6,1.3,9.8,9.2,12.3,13.8,31.10
|
||||
2025H1,52.4,1.2,11.0,9.9,16.7,9.0,28.51
|
||||
2025H2,54.8,1.2,22.2,5.6,10.7,5.6,51.60
|
||||
2025FY,53.9,1.2,18.2,7.1,12.8,6.8,80.11
|
||||
2026H1,66.6,2.2,13.1,4.2,7.7,6.2,25.76
|
||||
|
@@ -0,0 +1,3 @@
|
||||
period,retail,robot_store,own_app,shopee,online_other,wholesale,total_revenue
|
||||
2024FY,53.6,1.3,9.8,9.2,12.3,13.8,31.10
|
||||
2025FY,53.9,1.2,18.2,7.1,12.8,6.8,80.11
|
||||
|
@@ -0,0 +1,6 @@
|
||||
period,retail,robot_store,own_app,shopee,online_other,wholesale,total_revenue
|
||||
2024H1,60.9,2.1,5.0,5.1,10.7,16.2,7.97
|
||||
2024H2,51.1,1.0,11.5,10.5,12.8,13.0,23.13
|
||||
2025H1,52.4,1.2,11.0,9.9,16.7,9.0,28.51
|
||||
2025H2,54.8,1.2,22.2,5.6,10.7,5.6,51.60
|
||||
2026H1,66.6,2.2,13.1,4.2,7.7,6.2,25.76
|
||||
|
@@ -0,0 +1,8 @@
|
||||
period,retail,robot_store,roboshop,tmall,douyin,online_other,wholesale,total_revenue
|
||||
2024H1,56.9,9.6,11.3,5.7,5.9,3.8,6.8,35.22
|
||||
2024H2,48.4,7.7,13.3,8.0,7.3,5.5,9.8,53.65
|
||||
2024FY,51.8,8.5,12.5,7.1,6.8,4.8,8.6,88.87
|
||||
2025H1,53.2,8.2,13.6,7.9,6.8,7.2,3.2,82.82
|
||||
2025H2,45.1,5.3,18.3,9.0,8.2,9.0,5.1,125.69
|
||||
2025FY,48.3,6.5,16.4,8.6,7.6,8.3,4.4,208.51
|
||||
2026H1,49.9,6.4,16.9,7.4,8.0,6.9,4.5,122.00
|
||||
|
@@ -0,0 +1,3 @@
|
||||
period,retail,robot_store,roboshop,tmall,douyin,online_other,wholesale,total_revenue
|
||||
2024FY,51.8,8.5,12.5,7.1,6.8,4.8,8.6,88.87
|
||||
2025FY,48.3,6.5,16.4,8.6,7.6,8.3,4.4,208.51
|
||||
|
@@ -0,0 +1,6 @@
|
||||
period,retail,robot_store,roboshop,tmall,douyin,online_other,wholesale,total_revenue
|
||||
2024H1,56.9,9.6,11.3,5.7,5.9,3.8,6.8,35.22
|
||||
2024H2,48.4,7.7,13.3,8.0,7.3,5.5,9.8,53.65
|
||||
2025H1,53.2,8.2,13.6,7.9,6.8,7.2,3.2,82.82
|
||||
2025H2,45.1,5.3,18.3,9.0,8.2,9.0,5.1,125.69
|
||||
2026H1,49.9,6.4,16.9,7.4,8.0,6.9,4.5,122.00
|
||||
|
@@ -0,0 +1,8 @@
|
||||
period,retail,robot_store,own_app,online_other,wholesale,total_revenue
|
||||
2024H1,70.7,1.7,1.7,17.2,8.6,0.58
|
||||
2024H2,64.1,0.6,11.0,13.3,11.0,1.81
|
||||
2024FY,65.7,0.8,8.8,14.2,10.5,2.39
|
||||
2025H1,57.0,0.8,17.8,16.8,7.5,4.77
|
||||
2025H2,60.0,1.3,23.7,11.2,3.8,9.74
|
||||
2025FY,59.0,1.2,21.8,13.0,5.0,14.51
|
||||
2026H1,79.6,2.2,10.5,3.0,4.7,5.06
|
||||
|
@@ -0,0 +1,3 @@
|
||||
period,retail,robot_store,own_app,online_other,wholesale,total_revenue
|
||||
2024FY,65.7,0.8,8.8,14.2,10.5,2.39
|
||||
2025FY,59.0,1.2,21.8,13.0,5.0,14.51
|
||||
|
@@ -0,0 +1,6 @@
|
||||
period,retail,robot_store,own_app,online_other,wholesale,total_revenue
|
||||
2024H1,70.7,1.7,1.7,17.2,8.6,0.58
|
||||
2024H2,64.1,0.6,11.0,13.3,11.0,1.81
|
||||
2025H1,57.0,0.8,17.8,16.8,7.5,4.77
|
||||
2025H2,60.0,1.3,23.7,11.2,3.8,9.74
|
||||
2026H1,79.6,2.2,10.5,3.0,4.7,5.06
|
||||
|
@@ -0,0 +1,6 @@
|
||||
period,retail,online,robot_store,wholesale,total_revenue
|
||||
2024H1,57.4,25.9,8.1,8.6,45.59
|
||||
2024H2,49.0,35.2,5.4,10.4,84.78
|
||||
2025H1,49.8,39.6,5.9,4.7,138.75
|
||||
2025H2,44.5,47.0,3.7,4.7,232.45
|
||||
2026H1,53.1,36.2,5.5,5.2,171.74
|
||||
|
@@ -1,16 +0,0 @@
|
||||
region,channel,value
|
||||
中国,线下渠道,68.69
|
||||
中国,线上渠道,47.79
|
||||
中国,批发及其他,5.53
|
||||
海外,线下渠道,31.94
|
||||
海外,线上渠道,14.34
|
||||
海外,批发及其他,3.45
|
||||
亚太,线下渠道,17.73
|
||||
亚太,线上渠道,6.44
|
||||
亚太,批发及其他,1.59
|
||||
美洲,线下渠道,10.07
|
||||
美洲,线上渠道,7.22
|
||||
美洲,批发及其他,1.62
|
||||
欧洲及其他,线下渠道,4.14
|
||||
欧洲及其他,线上渠道,0.68
|
||||
欧洲及其他,批发及其他,0.24
|
||||
|
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,22 @@
|
||||
# 泡泡玛特财报分析底稿
|
||||
|
||||
本目录是泡泡玛特的唯一维护数据层。日常只维护 [泡泡玛特_财报分析底稿.xlsx](泡泡玛特_财报分析底稿.xlsx):图表使用的 `data/ppmt/*.csv` 由脚本生成,不直接编辑。
|
||||
|
||||
## 工作簿结构
|
||||
|
||||
- `说明`:维护入口、口径和颜色约定。
|
||||
- `资产负债表`:期末时点的财报明细。
|
||||
- `利润表`:半年累计与全年累计的财报明细。
|
||||
- `现金流量表`:现金流量表披露的原始明细。
|
||||
- `资产分类规则`:把财报明细映射为“类现金、投资资产、有息负债”等分析分类的规则;不维护分类金额。
|
||||
- `检查`:资产负债、现金流量的勾稽差额。出现红色警告时,应回到来源核对,不能为了消除警告而改数。
|
||||
|
||||
## 更新流程
|
||||
|
||||
1. 将新财报 PDF、截图或原始表格保留在 `data/ppmt/raw/`,作为证据文件。
|
||||
2. 由 AI 读取证据后,将直接披露的科目、期间和金额录入本工作簿的蓝色数字区域,并记录来源定位。
|
||||
3. 检查期间、单位和勾稽关系;确认后再生成图表 CSV 与图表。
|
||||
|
||||
只维护基础值:收入、门店数、财报科目等。同比、占比、利润率、H2 单半年和分析分类小计都由后续脚本计算。若财报另有披露占比,可作为核对依据,不应成为第二套手工维护的数据。
|
||||
|
||||
期间约定:`FY` 是全年累计/年末,`H1` 是上半年累计/六月末;不得把来源的 `H2` 直接当作下半年单期。金额单位为人民币亿元。
|
||||
@@ -0,0 +1,146 @@
|
||||
region,channel,period,revenue
|
||||
中国,零售店,2024H1,20.04
|
||||
中国,零售店,2024FY,46.01
|
||||
中国,零售店,2025H1,44.06
|
||||
中国,零售店,2025FY,100.75
|
||||
中国,零售店,2026H1,60.90
|
||||
中国,机器人商店,2024H1,3.38
|
||||
中国,机器人商店,2024FY,7.51
|
||||
中国,机器人商店,2025H1,6.78
|
||||
中国,机器人商店,2025FY,13.46
|
||||
中国,机器人商店,2026H1,7.79
|
||||
中国,线上渠道,2024H1,9.41
|
||||
中国,线上渠道,2024FY,27.72
|
||||
中国,线上渠道,2025H1,29.37
|
||||
中国,线上渠道,2025FY,85.22
|
||||
中国,线上渠道,2026H1,47.79
|
||||
中国,泡泡玛特抽盒机,2024H1,3.99
|
||||
中国,泡泡玛特抽盒机,2024FY,11.14
|
||||
中国,泡泡玛特抽盒机,2025H1,11.25
|
||||
中国,泡泡玛特抽盒机,2025FY,34.23
|
||||
中国,泡泡玛特抽盒机,2026H1,20.63
|
||||
中国,天猫旗舰店,2024H1,1.99
|
||||
中国,天猫旗舰店,2024FY,6.28
|
||||
中国,天猫旗舰店,2025H1,6.58
|
||||
中国,天猫旗舰店,2025FY,17.85
|
||||
中国,天猫旗舰店,2026H1,9.01
|
||||
中国,抖音平台,2024H1,2.09
|
||||
中国,抖音平台,2024FY,6.01
|
||||
中国,抖音平台,2025H1,5.61
|
||||
中国,抖音平台,2025FY,15.90
|
||||
中国,抖音平台,2026H1,9.76
|
||||
中国,其他线上渠道,2024H1,1.34
|
||||
中国,其他线上渠道,2024FY,4.29
|
||||
中国,其他线上渠道,2025H1,5.93
|
||||
中国,其他线上渠道,2025FY,17.24
|
||||
中国,其他线上渠道,2026H1,8.38
|
||||
中国,批发及其他,2024H1,2.39
|
||||
中国,批发及其他,2024FY,7.63
|
||||
中国,批发及其他,2025H1,2.61
|
||||
中国,批发及其他,2025FY,9.08
|
||||
中国,批发及其他,2026H1,5.53
|
||||
亚太,零售店,2024H1,4.85
|
||||
亚太,零售店,2024FY,16.68
|
||||
亚太,零售店,2025H1,14.93
|
||||
亚太,零售店,2025FY,43.19
|
||||
亚太,零售店,2026H1,17.16
|
||||
亚太,机器人商店,2024H1,0.17
|
||||
亚太,机器人商店,2024FY,0.40
|
||||
亚太,机器人商店,2025H1,0.33
|
||||
亚太,机器人商店,2025FY,0.95
|
||||
亚太,机器人商店,2026H1,0.57
|
||||
亚太,线上渠道,2024H1,1.65
|
||||
亚太,线上渠道,2024FY,9.72
|
||||
亚太,线上渠道,2025H1,10.69
|
||||
亚太,线上渠道,2025FY,30.53
|
||||
亚太,线上渠道,2026H1,6.44
|
||||
亚太,自研APP及官方网站,2024H1,0.40
|
||||
亚太,自研APP及官方网站,2024FY,3.05
|
||||
亚太,自研APP及官方网站,2025H1,3.13
|
||||
亚太,自研APP及官方网站,2025FY,14.58
|
||||
亚太,自研APP及官方网站,2026H1,3.38
|
||||
亚太,Shopee,2024H1,0.41
|
||||
亚太,Shopee,2024FY,2.85
|
||||
亚太,Shopee,2025H1,2.81
|
||||
亚太,Shopee,2025FY,5.69
|
||||
亚太,Shopee,2026H1,1.07
|
||||
亚太,其他线上渠道,2024H1,0.85
|
||||
亚太,其他线上渠道,2024FY,3.82
|
||||
亚太,其他线上渠道,2025H1,4.75
|
||||
亚太,其他线上渠道,2025FY,10.26
|
||||
亚太,其他线上渠道,2026H1,1.99
|
||||
亚太,批发及其他,2024H1,1.29
|
||||
亚太,批发及其他,2024FY,4.30
|
||||
亚太,批发及其他,2025H1,2.56
|
||||
亚太,批发及其他,2025FY,5.44
|
||||
亚太,批发及其他,2026H1,1.59
|
||||
美洲,零售店,2024H1,0.87
|
||||
美洲,零售店,2024FY,3.41
|
||||
美洲,零售店,2025H1,7.37
|
||||
美洲,零售店,2025FY,20.04
|
||||
美洲,零售店,2026H1,9.03
|
||||
美洲,机器人商店,2024H1,0.13
|
||||
美洲,机器人商店,2024FY,0.38
|
||||
美洲,机器人商店,2025H1,1.05
|
||||
美洲,机器人商店,2025FY,2.32
|
||||
美洲,机器人商店,2026H1,1.04
|
||||
美洲,线上渠道,2024H1,0.64
|
||||
美洲,线上渠道,2024FY,3.64
|
||||
美洲,线上渠道,2025H1,13.27
|
||||
美洲,线上渠道,2025FY,43.54
|
||||
美洲,线上渠道,2026H1,7.22
|
||||
美洲,自研APP及官方网站,2024H1,0.29
|
||||
美洲,自研APP及官方网站,2024FY,1.92
|
||||
美洲,自研APP及官方网站,2025H1,8.87
|
||||
美洲,自研APP及官方网站,2025FY,28.70
|
||||
美洲,自研APP及官方网站,2026H1,4.91
|
||||
美洲,TikTok,2024H1,0.15
|
||||
美洲,TikTok,2024FY,1.08
|
||||
美洲,TikTok,2025H1,3.15
|
||||
美洲,TikTok,2025FY,10.40
|
||||
美洲,TikTok,2026H1,0.00
|
||||
美洲,亚马逊,2024H1,0.00
|
||||
美洲,亚马逊,2024FY,0.00
|
||||
美洲,亚马逊,2025H1,0.00
|
||||
美洲,亚马逊,2025FY,0.00
|
||||
美洲,亚马逊,2026H1,1.68
|
||||
美洲,其他线上渠道,2024H1,0.20
|
||||
美洲,其他线上渠道,2024FY,0.65
|
||||
美洲,其他线上渠道,2025H1,1.25
|
||||
美洲,其他线上渠道,2025FY,4.44
|
||||
美洲,其他线上渠道,2026H1,0.63
|
||||
美洲,批发及其他,2024H1,0.19
|
||||
美洲,批发及其他,2024FY,0.58
|
||||
美洲,批发及其他,2025H1,0.96
|
||||
美洲,批发及其他,2025FY,2.17
|
||||
美洲,批发及其他,2026H1,1.62
|
||||
欧洲及其他,零售店,2024H1,0.41
|
||||
欧洲及其他,零售店,2024FY,1.57
|
||||
欧洲及其他,零售店,2025H1,2.72
|
||||
欧洲及其他,零售店,2025FY,8.56
|
||||
欧洲及其他,零售店,2026H1,4.03
|
||||
欧洲及其他,机器人商店,2024H1,0.01
|
||||
欧洲及其他,机器人商店,2024FY,0.02
|
||||
欧洲及其他,机器人商店,2025H1,0.04
|
||||
欧洲及其他,机器人商店,2025FY,0.17
|
||||
欧洲及其他,机器人商店,2026H1,0.11
|
||||
欧洲及其他,线上渠道,2024H1,0.11
|
||||
欧洲及其他,线上渠道,2024FY,0.55
|
||||
欧洲及其他,线上渠道,2025H1,1.65
|
||||
欧洲及其他,线上渠道,2025FY,5.05
|
||||
欧洲及其他,线上渠道,2026H1,0.68
|
||||
欧洲及其他,自研APP及官方网站,2024H1,0.01
|
||||
欧洲及其他,自研APP及官方网站,2024FY,0.21
|
||||
欧洲及其他,自研APP及官方网站,2025H1,0.85
|
||||
欧洲及其他,自研APP及官方网站,2025FY,3.16
|
||||
欧洲及其他,自研APP及官方网站,2026H1,0.53
|
||||
欧洲及其他,其他线上渠道,2024H1,0.10
|
||||
欧洲及其他,其他线上渠道,2024FY,0.34
|
||||
欧洲及其他,其他线上渠道,2025H1,0.80
|
||||
欧洲及其他,其他线上渠道,2025FY,1.89
|
||||
欧洲及其他,其他线上渠道,2026H1,0.15
|
||||
欧洲及其他,批发及其他,2024H1,0.05
|
||||
欧洲及其他,批发及其他,2024FY,0.25
|
||||
欧洲及其他,批发及其他,2025H1,0.36
|
||||
欧洲及其他,批发及其他,2025FY,0.73
|
||||
欧洲及其他,批发及其他,2026H1,0.24
|
||||
|
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,172 @@
|
||||
# 财报图表项目数据架构改造方案
|
||||
|
||||
> 状态:建议实施
|
||||
>
|
||||
> 目标:保持静态图表站点的低复杂度,同时建立可追溯、可重复生成的数据链路。
|
||||
|
||||
## 1. 决策
|
||||
|
||||
当前不建设在线数据库和后端 API。
|
||||
|
||||
本项目目前是静态数据展示工具:浏览器按需读取 CSV,数据量很小,没有多人并发写入、权限控制、运行时查询或服务端计算的需求。此时引入 Postgres、API、鉴权和迁移机制会增加运维成本,但不能解决当前的数据正确性与可维护性问题。
|
||||
|
||||
CSV 保留为图表的运行时交付格式;需要改造的是 CSV 之前的数据生产链路。
|
||||
|
||||
## 2. 当前问题
|
||||
|
||||
### 2.1 权威数据源不一致
|
||||
|
||||
设计文档和 `data/ppmt/rawData/README.md` 将 `财报分析底稿.xlsx` 定义为唯一维护入口,但 `scripts/normalize_ppmt_financials.mjs` 实际读取的是 `data/ppmt/raw/*.csv`。底稿更新不会稳定地传递到图表。
|
||||
|
||||
### 2.2 派生产物被当作输入
|
||||
|
||||
归一化脚本的一部分通过 `readRuntimeRows()` 读取 `data/ppmt/*.csv`,再生成年度、半年等视图。IP、品类、资产等数据不能完全从上游重新构建;运行时 CSV 被删除、手改或过期时,会影响后续结果。
|
||||
|
||||
### 2.3 数据更新不完整
|
||||
|
||||
已出现底稿含有新期间、但相应图表 CSV 尚未更新的情况。问题不在某个数值,而在于链路没有可重复的全量构建保证。
|
||||
|
||||
### 2.4 发布边界不清晰
|
||||
|
||||
Vite 目前递归复制 `data/` 下的全部 CSV,因此原始披露 CSV 也会进入 `dist/`。原始证据、维护输入和公开运行时数据应有物理边界。
|
||||
|
||||
### 2.5 现有测试只验证展示
|
||||
|
||||
`pnpm test` 已能验证 manifest、CSV 可读性、图表渲染和部分视觉规范;还没有验证期间、单位、勾稽、同比口径、来源定位或派生逻辑。
|
||||
|
||||
## 3. 目标架构
|
||||
|
||||
```text
|
||||
原始披露文件(PDF / XLSX / 原始导出 CSV,只读证据)
|
||||
│
|
||||
▼
|
||||
人工录入与复核的规范化基础事实层(唯一权威)
|
||||
│ 数据构建:映射、同比、期间拆分、勾稽、图表数据整形
|
||||
▼
|
||||
图表运行时 CSV(完全可重建,不允许手工修改)
|
||||
│
|
||||
▼
|
||||
静态站点:manifest + Vanilla JS + ECharts
|
||||
```
|
||||
|
||||
### 3.1 各层职责
|
||||
|
||||
| 层 | 内容 | 是否人工编辑 | 发布到网站 |
|
||||
| --- | --- | --- | --- |
|
||||
| 原始证据 | 财报 PDF、公司 XLSX、原始导出 CSV、截图 | 否 | 否 |
|
||||
| 基础事实层 | 直接披露的数值、期间、单位、来源定位、科目映射 | 是,受控编辑 | 否 |
|
||||
| 派生层 | H2、同比、利润率、分类汇总、年度/半年视图 | 否,脚本生成 | 否 |
|
||||
| 图表 CSV | 仅包含图表需要的字段和布局 | 否,脚本生成 | 是 |
|
||||
|
||||
## 4. 基础事实层如何维护
|
||||
|
||||
基础事实层需要人工维护,但人工只编辑直接披露且可追溯的事实。
|
||||
|
||||
允许人工编辑的内容:
|
||||
|
||||
- 原始财报直接披露的金额、门店数等基础数值;
|
||||
- 期间、币种、单位、合并口径;
|
||||
- 来源文件、页码、表格、行号或单元格定位;
|
||||
- 科目映射与分类规则,例如“类现金”“投资资产”“经营资产”。
|
||||
|
||||
禁止人工编辑的内容:
|
||||
|
||||
- 同比、环比、占比、利润率;
|
||||
- `H2 = FY - H1` 等期间拆分;
|
||||
- 分析分类小计;
|
||||
- 任何图表运行时 CSV。
|
||||
|
||||
现有 `财报分析底稿.xlsx` 可以继续作为人工录入界面。改造后的要求是:构建脚本必须从这份工作簿或其一次性导出的规范化输入文件读取,不再并行从其他位置读取同一事实。工作簿是编辑器,不是与其他 CSV 并列的第二权威来源。
|
||||
|
||||
基础事实建议采用“长表”结构,便于复用、校验和未来接入多家公司:
|
||||
|
||||
| company_id | metric_id | period_end | period_type | value | unit | source_file | source_locator | revision |
|
||||
| --- | --- | --- | --- | ---: | --- | --- | --- | --- |
|
||||
| ppmt | revenue | 2026-06-30 | H1_YTD | 171.73 | CNY_100M | 2026H1.pdf | p.12 / 利润表 / 营业收入 | 1 |
|
||||
|
||||
运营类事实可使用相同结构,或按需要增加 `dimension_type`、`dimension_value`,例如地区、渠道、IP 和品类。
|
||||
|
||||
## 5. 目录与构建边界
|
||||
|
||||
建议将数据目录拆分,避免将证据文件误发布:
|
||||
|
||||
```text
|
||||
data/
|
||||
ppmt/
|
||||
evidence/ # 原始披露文件,只读,不发布
|
||||
input/ # 基础事实与映射规则,受控维护,不发布
|
||||
generated/ # 构建中间产物,可删除并重建,不发布
|
||||
public/
|
||||
data/
|
||||
ppmt/ # 仅图表运行时 CSV,发布
|
||||
scripts/
|
||||
import_ppmt_facts.* # 从 XLSX 或受控输入导入基础事实
|
||||
build_ppmt_datasets.* # 从基础事实完整生成图表 CSV
|
||||
check_ppmt_data.* # 质量与勾稽校验
|
||||
```
|
||||
|
||||
Vite 仅复制 `public/`;不再以“复制整个 `data/` 再过滤扩展名”的方式发布文件。
|
||||
|
||||
## 6. 数据构建与校验
|
||||
|
||||
推荐提供三个明确命令:
|
||||
|
||||
```bash
|
||||
pnpm data:import # 可选:从人工维护的 XLSX 导出/读取基础事实
|
||||
pnpm data:build # 从唯一权威源全量生成 generated 与 public/data
|
||||
pnpm data:check # 校验输入、派生结果与勾稽关系
|
||||
pnpm test # 图表渲染和展示回归
|
||||
pnpm build # 静态站点构建
|
||||
```
|
||||
|
||||
`data:build` 必须满足幂等性:在清空 `generated/` 和 `public/data/` 后,仍能只凭 `input/` 与 `evidence/` 完整生成相同的图表数据。构建脚本不得读取自身输出目录作为输入。
|
||||
|
||||
`data:check` 至少覆盖:
|
||||
|
||||
- 必填字段、数值类型、单位和币种;
|
||||
- 期间格式、排序、重复与缺失;
|
||||
- 流量类 `H2 = FY - H1`,时点类 H2/年末口径;
|
||||
- 同比仅比较同口径上一年期间;
|
||||
- 资产 = 负债 + 权益、现金流勾稽等适用检查;
|
||||
- 图表字段 schema、空值约束和合理数值范围;
|
||||
- 每条人工事实都有来源定位。
|
||||
|
||||
## 7. 分阶段实施
|
||||
|
||||
### P0:先恢复单一真相源
|
||||
|
||||
1. 决定以现有底稿 XLSX 作为人工维护入口。
|
||||
2. 为底稿建立稳定导入器,输出规范化基础事实;或将其导出为版本化输入 CSV。
|
||||
3. 将现有脚本中读取运行时 CSV 的逻辑改为读取基础事实。
|
||||
4. 将图表 CSV 移到仅发布目录,原始证据移出发布路径。
|
||||
5. 增加 `data:check` 的最小校验与全量重建测试。
|
||||
|
||||
完成标准:删除全部图表 CSV 后,仍能从唯一输入源重建所有图表;底稿新增期间后,数据检查能明确指出哪些图表应更新。
|
||||
|
||||
### P1:第二家公司接入后抽象共性
|
||||
|
||||
1. 固化跨公司通用的基础事实 schema、期间模型、来源定位格式和校验规则。
|
||||
2. 仅在两个公司确实复用同一字段映射与口径时,抽取通用图表或数据模板。
|
||||
3. 为 manifest 增加可选的数据契约版本,避免图表代码默默依赖 CSV 列名。
|
||||
|
||||
### P2:需要分析型查询时引入本地数据库
|
||||
|
||||
当需要跨公司指标筛选、较多事实数据查询、复杂聚合或批量分析时,引入 DuckDB + Parquet 作为本地构建和分析层。它可以替代或补强 `generated/`,但前端仍然发布静态 CSV 或预聚合 JSON。
|
||||
|
||||
### P3:满足以下条件才建设在线数据库
|
||||
|
||||
仅在出现多人在线录入、权限分级、审计版本、自动数据抓取、实时更新或面向用户的任意组合查询时,再建设 Postgres + 后端 API。
|
||||
|
||||
在线数据库只替换“基础事实层的存储与协作方式”;图表运行时仍应优先使用缓存后的静态数据集或受版本控制的 API 响应,不应让每张图直接查询底层事实表。
|
||||
|
||||
## 8. 数据库选择原则
|
||||
|
||||
“使用数据库”不是替代数据治理的方案。无论存储介质是 XLSX、CSV、DuckDB 还是 Postgres,都必须先满足:
|
||||
|
||||
1. 只有一个权威输入源;
|
||||
2. 派生产物可从该输入源完整重建;
|
||||
3. 每个数值可回溯到披露来源;
|
||||
4. 口径、单位和期间语义经过自动校验;
|
||||
5. 前端只消费稳定的数据契约。
|
||||
|
||||
在当前规模下,最优路线是“XLSX 受控人工录入 + 规范化事实 + 自动生成 CSV + 静态发布”,而不是立即迁移在线数据库。
|
||||
+8
-4
@@ -23,10 +23,13 @@
|
||||
## 2. 当前架构
|
||||
|
||||
```
|
||||
原始财报 / PDF / XLSX(仓库外保存)
|
||||
│ 人工、AI 或脚本:抽取、归一化、口径确认
|
||||
原始财报 / PDF / XLSX / 截图(证据文件)
|
||||
│ 人工或 AI 提取、复核并记录来源定位
|
||||
▼
|
||||
data/{company}/*.csv ← 运行时数据
|
||||
data/{company}/rawData/财报分析底稿.xlsx ← 财报分析底稿:唯一可维护的数据层
|
||||
│ 脚本:分类、同比/占比、年度/半年视图、勾稽
|
||||
▼
|
||||
data/{company}/*.csv ← 图表运行时数据,可随时重建,禁止手工编辑
|
||||
▼
|
||||
manifest.json ← 展示编排与图表配置的唯一事实来源
|
||||
├─ chartCatalog ← 每张图只定义一次
|
||||
@@ -69,7 +72,8 @@ js/main.js + js/ui.js ← 解析引用、渲染三栏、导出和
|
||||
- `js/charts/index.js`:注册 `key → build 函数`。
|
||||
- `js/charts/*.js`:只处理该图的业务字段与 ECharts 表达。
|
||||
- `js/theme.js`:字体、配色、图例、坐标轴、脚注和水印等统一视觉规范。
|
||||
- `data/{company}/`:图表需要的规范化 CSV。
|
||||
- `data/{company}/rawData/`:财报分析底稿。每家公司以一份可读、可维护的工作簿作为入口;基础科目、期间、单位、来源文件/工作表/定位信息在此维护,分析分类通过独立规则映射,不复制维护分类金额。
|
||||
- `data/{company}/*.csv`:图表需要的规范化 CSV,由底稿生成,不是维护入口。
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -0,0 +1,189 @@
|
||||
# Smart Fin Chart 视觉与图表风格规范
|
||||
|
||||
> 用途:把本项目的页面和图表复刻成同一种视觉语言,或把本规范直接交给其他 AI 生成新页面、图表或 ECharts 配置。
|
||||
>
|
||||
> 本文以现有实现为准:`css/style.css`、`js/theme.js` 与各图表配置。它不替代数据口径或交互需求。
|
||||
|
||||
## 1. 风格定位
|
||||
|
||||
**正式主题名:轻快消费(Light Consumer Finance)**。它建立在“Joey 风格”的克制财经媒体底子上:
|
||||
|
||||
- 页面:暖灰白底、白色内容卡、深棕作为品牌锚点,安静、清爽、有纸张感;
|
||||
- 图表:低饱和莫兰迪基础色,叠加清晰但不荧光的青绿、蓝、琥珀、珊瑚与紫;
|
||||
- 气质:专业、可亲近、信息密度高但不冰冷。更像编辑过的消费公司研究报告,而不是交易终端、科技 SaaS 或奢侈品画册;
|
||||
- 色彩策略:**页面保持中性,数据才使用颜色;一张图只设置一个视觉主角。**
|
||||
|
||||
不要把它误做成两种极端:不是厚重老钱/深棕复古风,也不是高饱和糖果色的消费品牌海报。
|
||||
|
||||
## 2. 设计令牌(直接复用)
|
||||
|
||||
### 页面基础色
|
||||
|
||||
| 角色 | 色值 | 用法 |
|
||||
| --- | --- | --- |
|
||||
| 页面背景 | `#F5F4F1` | 大面积暖灰白,不用纯白铺满全页 |
|
||||
| 内容表面 | `#FFFFFF` | 图表卡片、弹窗、选中态 |
|
||||
| 主文字 | `#333333` | 标题、正文、坐标轴重点文字 |
|
||||
| 次级文字 | `#8C8C8C` | 副标题、数据来源、脚注、弱提示 |
|
||||
| 分隔/网格 | `#E8E8E8` | 卡片分割线、图表横向网格 |
|
||||
| 坐标轴线 | `#D0D0D0` | 仅分类横轴等必要位置 |
|
||||
| 品牌强调/交互 | `#6B4A2B` | 选中 Tab、主要按钮、hover 文字 |
|
||||
| 品牌浅底 | `#F0E9E1` | 选中 Tab、筛选 chip、浅强调块 |
|
||||
|
||||
### 通用莫兰迪扩展色
|
||||
|
||||
用于非固定财务语义的分类图、桑基图或适量的次级系列。它们是备用色,不是要求一张图全用完。
|
||||
|
||||
| 色名 | 色值 | 视觉感觉 |
|
||||
| --- | --- | --- |
|
||||
| 棕 | `#6B4A2B` | 温暖、稳定、品牌锚点 |
|
||||
| 沙 | `#D5C7B4` | 最弱的暖中性色 |
|
||||
| 珊瑚 | `#EC7B73` | 柔和警示或重点 |
|
||||
| 绿 | `#2F8A5B` | 稳健正向 |
|
||||
| 橙 | `#D98324` | 温暖重点 |
|
||||
| 紫 | `#9B4FA8` | 指标/同比强调 |
|
||||
| 蓝 | `#3B6EA5` | 常规数据类别 |
|
||||
| 海军蓝 | `#1F4E79` | 深层级、强对比类别 |
|
||||
| 红 | `#B8342A` | 显著负向/风险,慎用 |
|
||||
| 鼠尾草绿 | `#8AAB7E` | 柔和的正向或过渡类别 |
|
||||
|
||||
## 3. 图表语义色(优先级高于通用色)
|
||||
|
||||
同一业务语义跨图必须保持一致;不要在单张图内重写近似颜色。
|
||||
|
||||
### 财务与经营指标
|
||||
|
||||
| 语义 | 色值 | 推荐图形角色 |
|
||||
| --- | --- | --- |
|
||||
| 经营现金流/正向贡献 | `#36A28F` | 柱、瀑布正向项 |
|
||||
| 投资现金流/基准值/现金 | `#4D83D1` | 柱、瀑布起点和终点 |
|
||||
| 筹资现金流/负债 | `#EEA64F` | 柱 |
|
||||
| 现金净增加/关键压力项/负向贡献 | `#D95567` | 高辨识度折线、瀑布负向项 |
|
||||
| 核心/净利润 | `#F08B5A` | 主柱 |
|
||||
| 同比、转化率等唯一强调指标 | `#8256B2` | 主折线 |
|
||||
|
||||
**瀑布图固定规则:**总计/基准 `#4D83D1`,正向 `#36A28F`,负向 `#D95567`。透明占位柱不着色。
|
||||
|
||||
### 渠道业务
|
||||
|
||||
| 语义 | 色值 | 推荐图形角色 |
|
||||
| --- | --- | --- |
|
||||
| 零售店 | `#3F7FBE` | 堆叠柱、区域/渠道分类 |
|
||||
| 线上 | `#E36D63` | 堆叠柱、渠道分类 |
|
||||
| 机器人商店 | `#71AD81` | 堆叠柱、渠道分类 |
|
||||
| 批发 | `#E4A349` | 堆叠柱、渠道分类 |
|
||||
| 营收增速 | `#8256B2` | 柱线组合中的唯一折线 |
|
||||
|
||||
### 多类别 IP/占比图(10+ 类)
|
||||
|
||||
按色相顺序使用,残差项始终为中性灰:
|
||||
|
||||
`#D64545` → `#E8913C` → `#D9C043` → `#7FA83C` → `#3E9E6E` → `#3FB6D0` → `#2E5FA8` → `#8E86E0` → `#8A3FA8` → `#D081C4` → `#8C8C8C`
|
||||
|
||||
规则:红—橙—金—橄榄—翡翠—青—蓝—紫的连续色相谱;冷色相邻时交替深浅;“其他”“外采”“未单列”等残差类别固定用灰。该谱只用于高类别数的占比/构成图,不应用于普通两三系列柱线图。
|
||||
|
||||
## 4. 页面样式语言
|
||||
|
||||
### 布局和容器
|
||||
|
||||
- 外层是暖灰白画布,内容居中,桌面端采用“窄侧栏 + 主内容 + 窄目录”的研究报告式三栏;
|
||||
- 白色图表卡片圆角 `10px`,仅有极浅阴影 `0 1px 3px rgba(0,0,0,.06)`,不使用厚重浮层;
|
||||
- 卡片之间留出约 `28px` 空白。卡片顶部工具栏高度约 `52px`,下方用 `#E8E8E8` 细线分隔;
|
||||
- 侧栏和 Tab 容器为半透明白(约 58%–60%),一像素浅边框、`9–10px` 圆角;移动端转为贴顶的半透明暖灰条,并带轻微 backdrop blur;
|
||||
- 主操作按钮为深棕底白字;次操作为白底浅灰边框,hover 时变深棕字/边。选中态使用深棕字加 `#F0E9E1` 浅底,不要大面积实色块。
|
||||
|
||||
### 字体与文字层级
|
||||
|
||||
- 字体:`PingFang SC, Hiragino Sans GB, Microsoft YaHei, sans-serif`;
|
||||
- 页面卡片标题:`14px / 600`;侧栏标题:`12px / 600`;来源文件:`11px` 等宽字体;
|
||||
- 图表主标题:居中、`19px / bold`、`#333333`;副标题:`13px`、`#8C8C8C`、`line-height: 20px`;
|
||||
- 图例:`14px`、`#333333`;坐标轴文字:重点分类 `14px`,数值刻度 `13px`;数据标签通常 `11–13px`;脚注 `12px`、灰色;
|
||||
- 文案不使用全大写、超粗黑体或夸张字号。层级靠字重、灰度、间距建立,不靠多种彩色文字。
|
||||
|
||||
## 5. 所有图表共享的画法
|
||||
|
||||
### 留白、坐标轴与网格
|
||||
|
||||
- 图表白底;标题顶边距约 `18px`,图例在约 `85px`,绘图区从约 `145px` 开始,形成稳定的“标题 → 副标题 → 图例 → 数据”节奏;
|
||||
- 数值轴隐藏轴线和刻度,仅保留 `#E8E8E8`、`1px` 的浅色横向网格;分类横轴可保留 `#D0D0D0` 细轴线,但不显示刻度;
|
||||
- 不做渐变背景、斑马条纹(矩阵/热力图除外)、3D、强阴影或粗黑边框;
|
||||
- 图表不依赖 hover 才展示关键数据:数据标签默认可见;密集处通过上下错位、外置、白色描边和避让解决。
|
||||
|
||||
### 图例、标签与 tooltip
|
||||
|
||||
- 图例优先置于副标题和绘图区之间,横排居中,项目间距约 `28px`;让图表库按类型生成图例图标:柱=矩形,线=短线+空心圆;
|
||||
- 折线:不平滑(直线),线宽通常 `3px`;白心圆点,直径 `9px`,彩色描边 `2.5px`;标签同线色,必要时加 `3px` 白描边;
|
||||
- 柱:无渐变、无厚边框、圆角克制或不加;主图中柱宽按密度保持适中;柱顶 `11px` 深灰数值标签。很矮的柱也尽量显示标签;
|
||||
- 堆叠占比:足够宽的段内白字/深字;过窄段把标签置于右侧,文字变深灰并加白描边;
|
||||
- tooltip 使用系统默认浅底即可,字体遵循中文无衬线;指示器用 shadow/cross,不渲染为夸张的彩色悬浮卡;
|
||||
- 每张图底部保留灰色脚注,解释口径与来源。背景可铺低干扰水印:深暖灰 `rgba(120,100,80,.10)`、`20px bold`、约 `20°` 斜向、300×200px 网格重复。
|
||||
|
||||
## 6. 关键图表配方
|
||||
|
||||
| 图形 | 推荐结构 | 色彩与样式规则 |
|
||||
| --- | --- | --- |
|
||||
| 现金流/利润 + 同比 | 多柱 + 单条双轴折线 | 柱用固定财务色;只保留一条紫色或珊瑚色重点折线;折线置顶,白心圆点、`3px` 线、显示标签 |
|
||||
| 渠道收入结构 + 增速 | 堆叠柱 + 单条折线 | 渠道色按零售蓝、线上珊瑚、机器人绿、批发琥珀固定;增速只用紫线;不要再引入第二条抢眼折线 |
|
||||
| 瀑布图/同比拆解 | 蓝色起终点 + 绿/红贡献柱 | 正负值同时显示 `+/-` 标签;透明占位列;分类横轴可斜 32°,其余保持克制 |
|
||||
| 多条经营比率线 | 4 条以内为宜 | 选深青、芥黄、茄紫、玫红等明度与色相均有区分的低饱和色;靠实/虚线、线宽、标签上下位置进一步区分 |
|
||||
| IP 占比饼图 | 非环图 + 两侧外标签 + 右侧纵向图例 | 扇区之间 `2px` 白边;名称按扇区同色,数值灰色;右侧图例使用“名称 + 次行数值/占比”;hover 阴影轻微 |
|
||||
| 100% 堆叠/区域份额 | 归一化堆叠柱 | 颜色固定映射业务类别,不能随年份换色;小段标签外置并避让;不要因标签多删去数据 |
|
||||
| 热力图 | 低增长蓝 → 暖白 → 高增长珊瑚 | 色阶为 `#9CC4D0 → #F3EEE6 → #EC7B73`;格中深灰数值;水平色尺说明高低;缺失值留白 |
|
||||
| 桑基图 | 低饱和节点 + 平滑渐变流向 | 节点取主题分类色,连线取源到目标的渐变;文字深灰、重点节点可用珊瑚/绿色,避免彩虹化 |
|
||||
|
||||
## 7. 配色决策规则
|
||||
|
||||
1. 先判断语义:财务、渠道、瀑布先使用固定语义色;只有无固定业务含义的分类才从扩展色选。
|
||||
2. 一图只有一个主角:柱负责区分类别,折线只留一个高辨识度强调色。避免红、橙、紫同时作为抢眼元素。
|
||||
3. 颜色不是唯一编码:折线还用线宽、实虚线、白心圆点、标签优先级区分;不要仅依赖色差。
|
||||
4. 2–4 个系列人工挑色即可;5–9 个系列保证相邻/同图的色相与明度明显不同;10+ 类使用预设色相谱。不要为全页所有图强制颜色互不重复。
|
||||
5. “其他、未披露、残差、外采”优先灰色/沙色;负向或风险优先珊瑚红,普通分类不要随意占用红色。
|
||||
|
||||
## 8. 可直接交给 AI 的提示词
|
||||
|
||||
将方括号内容替换成具体业务。若目标 AI 能生成代码,也可要求它输出 CSS 变量和 ECharts/图表库配置。
|
||||
|
||||
```text
|
||||
请设计一个中文消费公司财报数据页面,视觉风格为“轻快消费(Light Consumer Finance)”:
|
||||
以克制财经媒体/编辑研究报告为底子,而非交易终端或科技 SaaS。页面用暖灰白 #F5F4F1 背景、白色 #FFFFFF 图表卡片、10px 圆角、极浅阴影;主文字 #333333,辅助文字 #8C8C8C,细分隔和网格 #E8E8E8。品牌和选中态只使用深棕 #6B4A2B,浅强调底 #F0E9E1。
|
||||
|
||||
中文字体使用 PingFang SC / Hiragino Sans GB / Microsoft YaHei。图表标题居中、19px bold;副标题 13px 灰;图例 14px;数值轴浅灰横网格、弱化轴线和刻度。保留充足留白,画面安静、专业、可亲近。
|
||||
|
||||
为 [图表主题] 生成 [图表类型]。关键数据默认显示,不依赖 hover:柱顶显示深灰数字;折线是直线、3px、9px 白心彩边圆点,并显示同色标签(必要时白色描边)。图例放在副标题和绘图区之间,柱图例为矩形、线图例为短线加空心圆。
|
||||
|
||||
按固定业务语义配色:[在此选择财务色或渠道色]。一张图只保留一个视觉主角;柱承载分类,最多一条高辨识度折线。禁止霓虹色、渐变填充、3D 图表、粗黑边框、厚阴影、深色背景、过多鲜艳色和无意义装饰。
|
||||
|
||||
卡片底部添加简短灰色脚注说明数据口径和来源;可选地铺很淡的 20° 斜向暖灰水印,但绝不影响读数。
|
||||
```
|
||||
|
||||
### 财务色替换段
|
||||
|
||||
```text
|
||||
财务语义固定为:经营现金流/正向贡献 #36A28F,投资现金流/基准/现金 #4D83D1,筹资现金流/负债 #EEA64F,现金净增加/关键压力/负向贡献 #D95567,净利润 #F08B5A,同比或转化率的唯一强调折线 #8256B2。瀑布图总计蓝、正向青绿、负向珊瑚红。
|
||||
```
|
||||
|
||||
### 渠道色替换段
|
||||
|
||||
```text
|
||||
渠道语义固定为:零售店 #3F7FBE,线上 #E36D63,机器人商店 #71AD81,批发 #E4A349;若有营收增速,仅用一条 #8256B2 紫色折线。跨年度或跨图保持同一渠道不换色。
|
||||
```
|
||||
|
||||
## 9. 实施检查清单
|
||||
|
||||
- [ ] 页面背景不是纯白,卡片是白色且阴影很淡。
|
||||
- [ ] 主文字/次文字/网格分别使用深灰、灰色、浅灰,品牌深棕没有滥用。
|
||||
- [ ] 图表使用业务语义色,且同一语义跨图一致。
|
||||
- [ ] 一张图没有多个同时抢眼的红橙紫强调系列。
|
||||
- [ ] 折线为直线、白心圆点;柱和折线的图例图标能区分。
|
||||
- [ ] 网格以浅色横线为主,轴线、边框和阴影都很轻。
|
||||
- [ ] 标签、图例、脚注不重叠;很小的堆叠段有可读的外置标签。
|
||||
- [ ] 视觉表达是“轻快、克制的消费公司研究报告”,不是金融终端或营销海报。
|
||||
|
||||
## 10. 来源与维护
|
||||
|
||||
- 页面令牌:`css/style.css`
|
||||
- 通用色板、字体、坐标轴、图例、系列样式、水印:`js/theme.js`
|
||||
- 正式方向与使用规则:`CLAUDE.md` 的“配色”部分、`docs/交接说明_2026-08-28.md`
|
||||
- 关键图表示例:`js/charts/ppmt_cashflow_composition.js`、`ppmt_core_profit.js`、`ppmt_bridge.js`、`ppmt_ip_share.js`、`ppmt_region_channel_yoy_timeseries.js`
|
||||
|
||||
如果修改正式色板,应同步更新 `js/theme.js` 与本文,并以语义映射的稳定性优先于单张图的局部好看。
|
||||
@@ -0,0 +1,174 @@
|
||||
import {
|
||||
PALETTE, FONT, categoryAxis, titleStyle,
|
||||
standardLegend, footnote, watermarks, LAYOUT,
|
||||
} from '../theme.js';
|
||||
|
||||
const STATE_LABELS = {
|
||||
initial: '期初持有',
|
||||
added: '新建仓',
|
||||
held: '持续持有',
|
||||
sold: '清仓',
|
||||
};
|
||||
|
||||
const STATE_COLORS = {
|
||||
initial: PALETTE.sand,
|
||||
added: PALETTE.green,
|
||||
sold: PALETTE.coral,
|
||||
};
|
||||
|
||||
const SHORT_NAMES = {
|
||||
'美国运通(American Express)': '美国运通',
|
||||
'可口可乐(Coca-Cola)': '可口可乐',
|
||||
'华特迪士尼(Walt Disney)': '迪士尼',
|
||||
'房地美(Freddie Mac)': '房地美',
|
||||
'吉列(Gillette)': '吉列',
|
||||
'旅行家集团(Travelers Group)': '旅行家集团',
|
||||
'华盛顿邮报(Washington Post)': '华盛顿邮报',
|
||||
'富国银行(Wells Fargo)': '富国银行',
|
||||
'大都会/ABC(Capital Cities/ABC)': '大都会/ABC',
|
||||
'盖可保险(GEICO)': '盖可保险',
|
||||
'甘尼特(Gannett)': '甘尼特',
|
||||
'PNC 银行(PNC Bank)': 'PNC 银行',
|
||||
'通用动力(General Dynamics)': '通用动力',
|
||||
'健力士(Guinness)': '健力士',
|
||||
"麦当劳(McDonald's)": '麦当劳',
|
||||
};
|
||||
|
||||
function formatShares(value) {
|
||||
return Number(value).toLocaleString('en-US');
|
||||
}
|
||||
|
||||
function quantityChange(current, previous) {
|
||||
if (!Number.isFinite(previous)) return '无可比上年数据';
|
||||
const change = current - previous;
|
||||
const sign = change > 0 ? '+' : '';
|
||||
return `${sign}${formatShares(change)} 股`;
|
||||
}
|
||||
|
||||
export function brkHoldingsChart(rows, { width, height }) {
|
||||
const years = [...new Set(rows.map((row) => row.year))].sort((a, b) => a - b);
|
||||
const holdingsByYear = new Map(years.map((year) => [year, new Map()]));
|
||||
const firstYearByCompany = new Map();
|
||||
const maxSharesByCompany = new Map();
|
||||
|
||||
rows.forEach((row) => {
|
||||
holdingsByYear.get(row.year).set(row.company, row.shares);
|
||||
firstYearByCompany.set(row.company, Math.min(firstYearByCompany.get(row.company) ?? row.year, row.year));
|
||||
maxSharesByCompany.set(row.company, Math.max(maxSharesByCompany.get(row.company) ?? 0, row.shares));
|
||||
});
|
||||
|
||||
const companies = [...firstYearByCompany.keys()].sort((a, b) => (
|
||||
firstYearByCompany.get(a) - firstYearByCompany.get(b) || a.localeCompare(b, 'zh-CN')
|
||||
));
|
||||
const dataByState = { initial: [], added: [], held: [], sold: [] };
|
||||
|
||||
years.forEach((year, yearIndex) => {
|
||||
const current = holdingsByYear.get(year);
|
||||
const previous = yearIndex > 0 ? holdingsByYear.get(years[yearIndex - 1]) : new Map();
|
||||
|
||||
companies.forEach((company, companyIndex) => {
|
||||
const shares = current.get(company);
|
||||
const priorShares = previous.get(company);
|
||||
let state;
|
||||
if (Number.isFinite(shares)) {
|
||||
state = yearIndex === 0 ? 'initial' : (Number.isFinite(priorShares) ? 'held' : 'added');
|
||||
} else if (yearIndex > 0 && Number.isFinite(priorShares)) {
|
||||
state = 'sold';
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
|
||||
const relativeSize = Number.isFinite(shares)
|
||||
? Math.sqrt(shares / maxSharesByCompany.get(company))
|
||||
: 1;
|
||||
const color = state === 'held'
|
||||
? `rgba(59, 110, 165, ${(0.38 + relativeSize * 0.62).toFixed(2)})`
|
||||
: STATE_COLORS[state];
|
||||
const labelColor = state === 'initial' || (state === 'held' && relativeSize < 0.55)
|
||||
? PALETTE.ink
|
||||
: '#fff';
|
||||
dataByState[state].push({
|
||||
value: [yearIndex, companyIndex, shares ?? 0],
|
||||
shares,
|
||||
priorShares,
|
||||
state,
|
||||
shortName: SHORT_NAMES[company] || company,
|
||||
itemStyle: { color, borderColor: '#fff', borderWidth: 3 },
|
||||
label: { color: labelColor },
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
return {
|
||||
backgroundColor: '#fff',
|
||||
title: {
|
||||
...titleStyle,
|
||||
text: '伯克希尔主要持仓变动(1988–1997)',
|
||||
subtext: '每格标注公司简称;持续持有的蓝色越深,表示相对持股数量越大',
|
||||
},
|
||||
legend: standardLegend([
|
||||
STATE_LABELS.initial,
|
||||
STATE_LABELS.added,
|
||||
STATE_LABELS.held,
|
||||
STATE_LABELS.sold,
|
||||
], { selectedMode: false, itemGap: 20 }),
|
||||
tooltip: {
|
||||
trigger: 'item',
|
||||
textStyle: { fontFamily: FONT },
|
||||
formatter: (params) => {
|
||||
const data = params.data;
|
||||
const company = companies[data.value[1]];
|
||||
const year = years[data.value[0]];
|
||||
const lines = [`<b>${year} · ${company}</b>`, `状态:${STATE_LABELS[data.state]}`];
|
||||
if (Number.isFinite(data.shares)) {
|
||||
lines.push(`年末持股:${formatShares(data.shares)} 股`);
|
||||
if (data.state !== 'initial') lines.push(`较上年:${quantityChange(data.shares, data.priorShares)}`);
|
||||
} else {
|
||||
lines.push(`上年末持股:${formatShares(data.priorShares)} 股`);
|
||||
}
|
||||
return lines.join('<br/>');
|
||||
},
|
||||
},
|
||||
// ECharts 的 heatmap 组件要求声明 visualMap;实际颜色由每个状态单元格的 itemStyle 控制。
|
||||
visualMap: {
|
||||
show: false,
|
||||
min: 0,
|
||||
max: Math.max(...rows.map((row) => row.shares)),
|
||||
dimension: 2,
|
||||
inRange: { color: ['#ffffff'] },
|
||||
},
|
||||
grid: { left: 258, right: 48, top: LAYOUT.gridTop, bottom: 72 },
|
||||
xAxis: {
|
||||
...categoryAxis,
|
||||
type: 'category',
|
||||
data: years.map(String),
|
||||
splitArea: { show: true, areaStyle: { color: ['#fbfbfa'] } },
|
||||
},
|
||||
yAxis: {
|
||||
...categoryAxis,
|
||||
type: 'category',
|
||||
inverse: true,
|
||||
data: companies,
|
||||
axisLabel: { ...categoryAxis.axisLabel, fontSize: 12, margin: 14 },
|
||||
splitArea: { show: true, areaStyle: { color: ['#fbfbfa'] } },
|
||||
},
|
||||
series: Object.entries(dataByState).map(([state, data]) => ({
|
||||
name: STATE_LABELS[state],
|
||||
type: 'heatmap',
|
||||
data,
|
||||
label: {
|
||||
show: true,
|
||||
position: 'inside',
|
||||
fontFamily: FONT,
|
||||
fontSize: 12,
|
||||
fontWeight: 600,
|
||||
formatter: (params) => params.data.shortName,
|
||||
},
|
||||
emphasis: { itemStyle: { borderColor: PALETTE.ink, borderWidth: 2 } },
|
||||
})),
|
||||
graphic: [
|
||||
...watermarks(width, height),
|
||||
footnote('绿色=相对上一年新增;红色=相对上一年清仓;蓝色深浅=相对持股数量 | @Joey知行记'),
|
||||
],
|
||||
};
|
||||
}
|
||||
+1
-1
@@ -72,7 +72,7 @@ export function growthChart(rows, { width, height }) {
|
||||
],
|
||||
graphic: [
|
||||
...watermarks(width, height),
|
||||
footnote('示例数据 | @思考的Joey'),
|
||||
footnote('示例数据 | @Joey知行记'),
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
+47
-2
@@ -8,7 +8,6 @@ import { profitChart } from './ppmt_profit.js';
|
||||
import { ipChart } from './ppmt_ip.js';
|
||||
import { categoryChart } from './ppmt_category.js';
|
||||
import { sankeyChart } from './ppmt_sankey.js';
|
||||
import { regionChannelChart } from './ppmt_region_channel.js';
|
||||
import { regionChannelYoyChart } from './ppmt_region_yoy.js';
|
||||
import { regionChannelYoyTimeseriesChart } from './ppmt_region_channel_yoy_timeseries.js';
|
||||
import { ppmtGrowthChart } from './ppmt_growth.js';
|
||||
@@ -25,6 +24,8 @@ import { cashflowCompositionChart } from './ppmt_cashflow_composition.js';
|
||||
import { workingCapitalDaysChart } from './ppmt_working_capital_days.js';
|
||||
import { expenseStructureChart } from './ppmt_expense_structure.js';
|
||||
import { channelRevenueChart } from './ppmt_channel_revenue.js';
|
||||
import { channelShareChart } from './ppmt_channel_share.js';
|
||||
import { makeRegionChannelShareChart, makeRegionChannelAmountChart } from './ppmt_channel_share_region.js';
|
||||
import { storesRetailChart } from './ppmt_stores_retail.js';
|
||||
import { storesRobotChart } from './ppmt_stores_robot.js';
|
||||
import { regionShareChart } from './ppmt_region_share.js';
|
||||
@@ -32,6 +33,19 @@ import { inventoryBalanceChart } from './ppmt_inventory.js';
|
||||
import { storesRetailShareChart } from './ppmt_stores_retail_share.js';
|
||||
import { retailStoreRevenueChart } from './ppmt_retail_store_revenue.js';
|
||||
import { robotStoreRevenueChart } from './ppmt_robot_store_revenue.js';
|
||||
import { brkHoldingsChart } from './brk_holdings.js';
|
||||
import {
|
||||
moutaiProfitabilityAnnualChart, moutaiProfitabilityQuarterlyChart,
|
||||
moutaiExpenseAnnualChart, moutaiExpenseQuarterlyChart,
|
||||
moutaiBalanceAnnualChart, moutaiBalanceQuarterlyChart,
|
||||
moutaiCashflowAnnualChart, moutaiCashflowQuarterlyChart,
|
||||
moutaiProductAnnualChart, moutaiChannelAnnualChart,
|
||||
moutaiClassifiedProductQuarterlyChart, moutaiClassifiedChannelQuarterlyChart,
|
||||
moutaiClassifiedProductCumulativeChart, moutaiClassifiedChannelCumulativeChart,
|
||||
moutaiRevenueCompositionHalfyearChart, moutaiLiquorIndustryOutputChart,
|
||||
moutaiBaseWineVolumeChart, moutaiFlyingPriceChart, moutaiPriceAdjustmentChart,
|
||||
moutaiTonPriceChart, moutaiDividendChart, moutaiDistributorsChart,
|
||||
} from './moutai.js';
|
||||
|
||||
/** 图表构建函数注册表。manifest 只引用这里的 key,不直接引用 JS 函数。 */
|
||||
export const chartRegistry = {
|
||||
@@ -45,7 +59,6 @@ export const chartRegistry = {
|
||||
'ppmt-ip': ipChart,
|
||||
'ppmt-category': categoryChart,
|
||||
'ppmt-sankey': sankeyChart,
|
||||
'ppmt-region-channel': regionChannelChart,
|
||||
'ppmt-region-yoy': regionChannelYoyChart,
|
||||
'ppmt-region-yoy-timeseries': regionChannelYoyTimeseriesChart,
|
||||
'ppmt-growth': ppmtGrowthChart,
|
||||
@@ -62,6 +75,15 @@ export const chartRegistry = {
|
||||
'ppmt-working-capital-days': workingCapitalDaysChart,
|
||||
'ppmt-expense-structure': expenseStructureChart,
|
||||
'ppmt-channel-revenue': channelRevenueChart,
|
||||
'ppmt-channel-share': channelShareChart,
|
||||
'ppmt-channel-share-china': makeRegionChannelShareChart('china'),
|
||||
'ppmt-channel-share-apac': makeRegionChannelShareChart('apac'),
|
||||
'ppmt-channel-share-americas': makeRegionChannelShareChart('americas'),
|
||||
'ppmt-channel-share-europe': makeRegionChannelShareChart('europe'),
|
||||
'ppmt-channel-amount-china': makeRegionChannelAmountChart('china'),
|
||||
'ppmt-channel-amount-apac': makeRegionChannelAmountChart('apac'),
|
||||
'ppmt-channel-amount-americas': makeRegionChannelAmountChart('americas'),
|
||||
'ppmt-channel-amount-europe': makeRegionChannelAmountChart('europe'),
|
||||
'ppmt-stores-retail': storesRetailChart,
|
||||
'ppmt-stores-robot': storesRobotChart,
|
||||
'ppmt-region-share': regionShareChart,
|
||||
@@ -69,6 +91,29 @@ export const chartRegistry = {
|
||||
'ppmt-stores-retail-share': storesRetailShareChart,
|
||||
'ppmt-retail-store-revenue': retailStoreRevenueChart,
|
||||
'ppmt-robot-store-revenue': robotStoreRevenueChart,
|
||||
'brk-holdings': brkHoldingsChart,
|
||||
'moutai-profitability-annual': moutaiProfitabilityAnnualChart,
|
||||
'moutai-profitability-quarterly': moutaiProfitabilityQuarterlyChart,
|
||||
'moutai-expense-annual': moutaiExpenseAnnualChart,
|
||||
'moutai-expense-quarterly': moutaiExpenseQuarterlyChart,
|
||||
'moutai-balance-annual': moutaiBalanceAnnualChart,
|
||||
'moutai-balance-quarterly': moutaiBalanceQuarterlyChart,
|
||||
'moutai-cashflow-annual': moutaiCashflowAnnualChart,
|
||||
'moutai-cashflow-quarterly': moutaiCashflowQuarterlyChart,
|
||||
'moutai-product-annual': moutaiProductAnnualChart,
|
||||
'moutai-channel-annual': moutaiChannelAnnualChart,
|
||||
'moutai-classified-product-quarterly': moutaiClassifiedProductQuarterlyChart,
|
||||
'moutai-classified-channel-quarterly': moutaiClassifiedChannelQuarterlyChart,
|
||||
'moutai-classified-product-cumulative': moutaiClassifiedProductCumulativeChart,
|
||||
'moutai-classified-channel-cumulative': moutaiClassifiedChannelCumulativeChart,
|
||||
'moutai-revenue-composition-halfyear': moutaiRevenueCompositionHalfyearChart,
|
||||
'moutai-liquor-industry-output': moutaiLiquorIndustryOutputChart,
|
||||
'moutai-base-wine-volume': moutaiBaseWineVolumeChart,
|
||||
'moutai-flying-price': moutaiFlyingPriceChart,
|
||||
'moutai-price-adjustment': moutaiPriceAdjustmentChart,
|
||||
'moutai-ton-price': moutaiTonPriceChart,
|
||||
'moutai-dividend': moutaiDividendChart,
|
||||
'moutai-distributors': moutaiDistributorsChart,
|
||||
};
|
||||
|
||||
export function getChartBuilder(key) {
|
||||
|
||||
@@ -103,7 +103,7 @@ export function inventoryChart(rows, { width, height }) {
|
||||
...watermarks(width, height),
|
||||
footnote(
|
||||
'周转天数=平均存货÷当期销售成本×期间天数(半年181/年度365,期初=上一年末,日历天数口径);' +
|
||||
'与公司披露一致:23年133/24年102/25年123/26H1 201天 | @思考的Joey'
|
||||
'与公司披露一致:23年133/24年102/25年123/26H1 201天 | @Joey知行记'
|
||||
),
|
||||
],
|
||||
};
|
||||
|
||||
+1
-1
@@ -74,7 +74,7 @@ export function marginChart(rows, { width, height }) {
|
||||
],
|
||||
graphic: [
|
||||
...watermarks(width, height),
|
||||
footnote('示例数据 | @思考的Joey'),
|
||||
footnote('示例数据 | @Joey知行记'),
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
@@ -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: '附件“经销商数量”工作表;单位为家',
|
||||
});
|
||||
}
|
||||
+55
-36
@@ -1,16 +1,45 @@
|
||||
import { col } from '../csv.js';
|
||||
import {
|
||||
PALETTE, FONT, axisCommon, standalonePeriodAxis, titleStyle, legendStyle,
|
||||
financialViewLabel,
|
||||
financialViewLabel, niceAxisMax, fanTinyLabelOffsets, stackedSharePoint,
|
||||
lineSeries, footnote, watermarks, LAYOUT,
|
||||
} from '../theme.js';
|
||||
|
||||
const KEYS = ['cash_like', 'receivables', 'investment', 'operating_assets'];
|
||||
const NAMES = {
|
||||
cash_like: '类现金',
|
||||
receivables: '应收账款',
|
||||
investment: '投资资产',
|
||||
operating_assets: '经营类资产',
|
||||
};
|
||||
const COLORS = {
|
||||
cash_like: PALETTE.blue,
|
||||
receivables: PALETTE.orange,
|
||||
investment: PALETTE.purple,
|
||||
operating_assets: PALETTE.sage,
|
||||
};
|
||||
// 段内标签文字色:浅色段(经营类资产灰绿)用深字,深色段用白字
|
||||
const INSIDE_LABEL_COLORS = {
|
||||
cash_like: '#fff',
|
||||
receivables: '#fff',
|
||||
investment: '#fff',
|
||||
operating_assets: PALETTE.ink,
|
||||
};
|
||||
|
||||
/**
|
||||
* 泡泡玛特资产结构演变图
|
||||
* 堆叠柱状图展示资产配置,折线图展示总资产增长
|
||||
* 堆叠柱状图展示资产配置(每段标数值),折线图展示总资产增长
|
||||
*/
|
||||
export function assetsChart(rows, { width, height, view = 'all' }) {
|
||||
const periods = col(rows, 'period');
|
||||
const totalMax = Math.max(...rows.map((r) => r.total_assets));
|
||||
// 轴上限随数据走(2025H2 总资产已 321 亿),矮段像素判定才有确定基准
|
||||
const axisMax = niceAxisMax(totalMax);
|
||||
const interval = axisMax % 7 === 0 ? axisMax / 7 : axisMax / 5;
|
||||
// 绘图区像素高:画布高 − grid top 145 − grid bottom 90
|
||||
const pxPerUnit = (height - LAYOUT.gridTop - 90) / axisMax;
|
||||
// 早期年份应收/投资不足 1 亿,矮段标签外置到柱体右侧扇形排开
|
||||
const rowOffsets = fanTinyLabelOffsets(rows, KEYS, pxPerUnit);
|
||||
|
||||
return {
|
||||
backgroundColor: '#fff',
|
||||
@@ -22,7 +51,7 @@ export function assetsChart(rows, { width, height, view = 'all' }) {
|
||||
legend: {
|
||||
...legendStyle,
|
||||
top: LAYOUT.legendTop,
|
||||
data: ['类现金', '应收账款', '投资资产', '经营类资产', '资产总计'],
|
||||
data: [...KEYS.map((key) => NAMES[key]), '资产总计'],
|
||||
},
|
||||
grid: { left: 78, right: 82, top: LAYOUT.gridTop, bottom: 90 },
|
||||
tooltip: {
|
||||
@@ -40,8 +69,8 @@ export function assetsChart(rows, { width, height, view = 'all' }) {
|
||||
nameGap: 52,
|
||||
nameTextStyle: { color: PALETTE.grey, fontSize: 13, fontFamily: FONT },
|
||||
min: 0,
|
||||
max: 350,
|
||||
interval: 50,
|
||||
max: axisMax,
|
||||
interval,
|
||||
},
|
||||
{
|
||||
...axisCommon,
|
||||
@@ -52,44 +81,34 @@ export function assetsChart(rows, { width, height, view = 'all' }) {
|
||||
nameRotate: -90,
|
||||
nameTextStyle: { color: PALETTE.grey, fontSize: 13, fontFamily: FONT },
|
||||
min: 0,
|
||||
max: 350,
|
||||
interval: 50,
|
||||
max: axisMax,
|
||||
interval,
|
||||
splitLine: { show: false },
|
||||
},
|
||||
],
|
||||
series: [
|
||||
{
|
||||
name: '类现金',
|
||||
...KEYS.map((key, ci) => ({
|
||||
name: NAMES[key],
|
||||
type: 'bar',
|
||||
stack: 'assets',
|
||||
data: col(rows, 'cash_like'),
|
||||
itemStyle: { color: PALETTE.blue },
|
||||
emphasis: { focus: 'series' },
|
||||
},
|
||||
{
|
||||
name: '应收账款',
|
||||
type: 'bar',
|
||||
stack: 'assets',
|
||||
data: col(rows, 'receivables'),
|
||||
itemStyle: { color: PALETTE.orange },
|
||||
emphasis: { focus: 'series' },
|
||||
},
|
||||
{
|
||||
name: '投资资产',
|
||||
type: 'bar',
|
||||
stack: 'assets',
|
||||
data: col(rows, 'investment'),
|
||||
itemStyle: { color: PALETTE.purple },
|
||||
emphasis: { focus: 'series' },
|
||||
},
|
||||
{
|
||||
name: '经营类资产',
|
||||
type: 'bar',
|
||||
stack: 'assets',
|
||||
data: col(rows, 'operating_assets'),
|
||||
itemStyle: { color: PALETTE.sage },
|
||||
// 每段都标数值:矮段塞不进字的,外置到柱体右侧(机制同渠道金额图)
|
||||
data: rows.map((row, ri) => stackedSharePoint(row[key], {
|
||||
pxPerUnit,
|
||||
labelColor: INSIDE_LABEL_COLORS[key],
|
||||
dy: rowOffsets[ri][ci] ?? 0,
|
||||
})),
|
||||
itemStyle: { color: COLORS[key] },
|
||||
emphasis: { focus: 'series' },
|
||||
label: {
|
||||
show: true,
|
||||
position: 'inside',
|
||||
color: INSIDE_LABEL_COLORS[key],
|
||||
fontSize: 11,
|
||||
fontFamily: FONT,
|
||||
overflow: 'none',
|
||||
formatter: (p) => `${p.value.toFixed(1)}`,
|
||||
},
|
||||
})),
|
||||
{
|
||||
...lineSeries({
|
||||
name: '资产总计',
|
||||
@@ -105,7 +124,7 @@ export function assetsChart(rows, { width, height, view = 'all' }) {
|
||||
],
|
||||
graphic: [
|
||||
...watermarks(width, height),
|
||||
footnote('数据来源:泡泡玛特财报 | @思考的Joey'),
|
||||
footnote('数据来源:泡泡玛特财报 | @Joey知行记'),
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
@@ -109,7 +109,7 @@ export function ppmtBridgeChart(rows, { width, height }) {
|
||||
...watermarks(width, height),
|
||||
footnote(
|
||||
'各项为占营收百分点,已折算为对净利率影响(×税盾系数)\n' +
|
||||
'其他收益净额:26H1净损失6.89亿,主要为汇兑损失7.20亿(25H1为汇兑收益1.20亿) | @思考的Joey'
|
||||
'其他收益净额:26H1净损失6.89亿,主要为汇兑损失7.20亿(25H1为汇兑收益1.20亿) | @Joey知行记'
|
||||
),
|
||||
],
|
||||
};
|
||||
|
||||
@@ -89,7 +89,7 @@ export function cashConversionChart(rows, { width, height, view = 'all' }) {
|
||||
],
|
||||
graphic: [
|
||||
...watermarks(width, height),
|
||||
footnote('数据来源:泡泡玛特合并现金流量表、利润表;H2为单独半年 | @思考的Joey'),
|
||||
footnote('数据来源:泡泡玛特合并现金流量表、利润表;H2为单独半年 | @Joey知行记'),
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
@@ -57,7 +57,7 @@ export function cashflowCompositionChart(rows, { width, height, view = 'all' })
|
||||
],
|
||||
graphic: [
|
||||
...watermarks(width, height),
|
||||
footnote('数据来源:泡泡玛特合并现金流量表;H2为单独半年 | @思考的Joey'),
|
||||
footnote('数据来源:泡泡玛特合并现金流量表;H2为单独半年 | @Joey知行记'),
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
+122
-69
@@ -1,49 +1,113 @@
|
||||
import { col } from '../csv.js';
|
||||
import {
|
||||
PALETTE, FONT, axisCommon, standalonePeriodAxis, titleStyle, legendStyle,
|
||||
financialViewDisclosure, financialViewLabel,
|
||||
footnote, watermarks, LAYOUT,
|
||||
financialViewDisclosure, financialViewLabel, niceAxisMax,
|
||||
fanTinyLabelOffsets, stackedSharePoint,
|
||||
lineSeries, footnote, watermarks, LAYOUT,
|
||||
} from '../theme.js';
|
||||
|
||||
/**
|
||||
* 泡泡玛特品类收入结构图
|
||||
* 堆叠柱状图展示各品类的收入占比和增长
|
||||
* 堆叠柱展示各品类收入;折线(右轴)展示各品类收入同比,折线与柱同色便于对应。
|
||||
* 同比为衍生指标:当期 ÷ 上年同期 − 1(H2 对 H2 单半年、FY 对 FY 累计,同期对同期),
|
||||
* 2024 年各期无上年基数,不计算。
|
||||
*/
|
||||
// 同比折线用柱色的同色系深色变体:压在柱体上能区分开,又靠色相保持与柱的对应
|
||||
const CATEGORIES = [
|
||||
{ name: '毛绒', key: 'plush', color: PALETTE.coral, lineColor: PALETTE.red },
|
||||
{ name: '手办', key: 'figure', color: PALETTE.blue, lineColor: PALETTE.navy },
|
||||
// 色板没有深紫,按 MEGA 紫加深
|
||||
{ name: 'MEGA', key: 'mega', color: PALETTE.purple, lineColor: '#5E2A79' },
|
||||
{ name: '衍生品及其他', key: 'other', color: PALETTE.sage, lineColor: PALETTE.green },
|
||||
];
|
||||
|
||||
export function categoryChart(rows, { width, height, view = 'all' }) {
|
||||
// 过滤掉表头行("品类"那一行)
|
||||
const dataRows = rows.filter(r => r.period && r.period !== '品类');
|
||||
const periods = col(dataRows, 'period');
|
||||
const num = (r, k) => (Number.isFinite(r[k]) ? r[k] : null);
|
||||
|
||||
// 上年同期的期间下标(H1↔H1、H2↔H2、FY↔FY),无则 -1
|
||||
const priorIndex = periods.map((p) => {
|
||||
if (!/^20\d{2}(H1|H2|FY)$/.test(p)) return -1;
|
||||
const prev = p.replace(/^20(\d{2})/, (_, yy) => `20${String(Number(yy) - 1).padStart(2, '0')}`);
|
||||
return periods.indexOf(prev);
|
||||
});
|
||||
const yoyByCat = Object.fromEntries(CATEGORIES.map((c) => [
|
||||
c.key,
|
||||
periods.map((_, i) => {
|
||||
const j = priorIndex[i];
|
||||
const cur = num(dataRows[i], c.key);
|
||||
const prev = j >= 0 ? num(dataRows[j], c.key) : null;
|
||||
if (cur == null || prev == null || prev === 0) return null;
|
||||
return (cur / prev - 1) * 100;
|
||||
}),
|
||||
]));
|
||||
const yoyValues = CATEGORIES.flatMap((c) => yoyByCat[c.key]).filter(Number.isFinite);
|
||||
const yoyMax = Math.max(100, Math.ceil(Math.max(...yoyValues) / 100) * 100);
|
||||
const yoyMin = Math.min(0, Math.floor(Math.min(...yoyValues) / 100) * 100);
|
||||
// 右轴固定整百刻度:范围 ≤500 时 100% 一步,否则 200% 一步,刻度数控制在 7 以内
|
||||
const yoyInterval = yoyMax - yoyMin <= 500 ? 100 : 200;
|
||||
|
||||
// 多折线标签避让(同 ppmt_margins labeledData):比较上下间隙,朝更大的一侧放
|
||||
const yoyLabelPos = periods.map((_, i) => {
|
||||
const ranked = CATEGORIES
|
||||
.map((c) => yoyByCat[c.key][i])
|
||||
.filter(Number.isFinite)
|
||||
.sort((a, b) => b - a);
|
||||
return Object.fromEntries(CATEGORIES.map((c) => {
|
||||
const v = yoyByCat[c.key][i];
|
||||
if (!Number.isFinite(v)) return [c.key, 'top'];
|
||||
const rank = ranked.indexOf(v);
|
||||
const gapUp = rank > 0 ? ranked[rank - 1] - v : Infinity;
|
||||
const gapDown = rank < ranked.length - 1 ? v - ranked[rank + 1] : Infinity;
|
||||
return [c.key, gapUp >= gapDown ? 'top' : 'bottom'];
|
||||
}));
|
||||
});
|
||||
|
||||
const totals = periods.map((_, i) => CATEGORIES.reduce((s, c) => s + (num(dataRows[i], c.key) ?? 0), 0));
|
||||
// 轴上限随数据走:2025FY 合计已 371.2 亿,写死 250 会把柱子顶出绘图区
|
||||
const axisMax = niceAxisMax(Math.max(...totals));
|
||||
const amountInterval = axisMax % 4 === 0 ? axisMax / 4 : axisMax / 5;
|
||||
// 绘图区像素高:画布高 − grid top 145 − grid bottom 90
|
||||
const pxPerUnit = (height - LAYOUT.gridTop - 90) / axisMax;
|
||||
const rowOffsets = fanTinyLabelOffsets(dataRows, CATEGORIES.map((c) => c.key), pxPerUnit);
|
||||
|
||||
return {
|
||||
backgroundColor: '#fff',
|
||||
title: {
|
||||
...titleStyle,
|
||||
text: `泡泡玛特品类收入结构(${financialViewLabel(view)})`,
|
||||
subtext: ['单位:亿元;H2为下半年单期', financialViewDisclosure(view)].filter(Boolean).join('\n'),
|
||||
subtext: ['柱为收入(亿元,左轴);折线为各品类收入同比(右轴);H2为下半年单期',
|
||||
financialViewDisclosure(view)].filter(Boolean).join('\n'),
|
||||
},
|
||||
// 图例柱与同比线交错排列,同品类的两项相邻
|
||||
legend: {
|
||||
...legendStyle,
|
||||
top: LAYOUT.legendTop,
|
||||
data: ['毛绒', '手办', 'MEGA', '衍生品及其他'],
|
||||
data: CATEGORIES.flatMap((c) => [c.name, `${c.name}同比`]),
|
||||
},
|
||||
grid: { left: 78, right: 56, top: LAYOUT.gridTop, bottom: 90 },
|
||||
grid: { left: 78, right: 82, top: LAYOUT.gridTop, bottom: 90 },
|
||||
tooltip: {
|
||||
trigger: 'axis',
|
||||
axisPointer: { type: 'shadow' },
|
||||
textStyle: { fontFamily: FONT },
|
||||
formatter: (params) => {
|
||||
const bars = params.filter((p) => p.seriesType === 'bar' && Number.isFinite(p.value));
|
||||
const lines = params.filter((p) => p.seriesType === 'line' && Number.isFinite(p.value));
|
||||
const total = bars.reduce((s, p) => s + p.value, 0);
|
||||
let result = `<b>${params[0].axisValue}</b><br/>`;
|
||||
let total = 0;
|
||||
params.forEach(p => {
|
||||
total += p.value;
|
||||
result += `${p.marker}${p.seriesName}: ${p.value.toFixed(2)} 亿元<br/>`;
|
||||
bars.forEach((p) => { result += `${p.marker}${p.seriesName}: ${p.value.toFixed(2)} 亿元<br/>`; });
|
||||
result += `<b>合计: ${total.toFixed(2)} 亿元</b><br/>`;
|
||||
lines.forEach((p) => {
|
||||
result += `${p.marker}${p.seriesName}: ${p.value > 0 ? '+' : ''}${p.value.toFixed(1)}%<br/>`;
|
||||
});
|
||||
result += `<b>合计: ${total.toFixed(2)} 亿元</b>`;
|
||||
return result;
|
||||
},
|
||||
},
|
||||
xAxis: { ...standalonePeriodAxis, data: periods },
|
||||
yAxis: {
|
||||
yAxis: [
|
||||
{
|
||||
...axisCommon,
|
||||
type: 'value',
|
||||
name: '收入(亿元)',
|
||||
@@ -51,16 +115,36 @@ export function categoryChart(rows, { width, height, view = 'all' }) {
|
||||
nameGap: 52,
|
||||
nameTextStyle: { color: PALETTE.grey, fontSize: 13, fontFamily: FONT },
|
||||
min: 0,
|
||||
max: 250,
|
||||
interval: 50,
|
||||
max: axisMax,
|
||||
interval: amountInterval,
|
||||
},
|
||||
{
|
||||
...axisCommon,
|
||||
type: 'value',
|
||||
name: '收入同比(%)',
|
||||
nameLocation: 'middle',
|
||||
nameGap: 56,
|
||||
nameRotate: -90,
|
||||
nameTextStyle: { color: PALETTE.grey, fontSize: 13, fontFamily: FONT },
|
||||
min: yoyMin,
|
||||
max: yoyMax,
|
||||
interval: yoyInterval,
|
||||
axisLabel: { ...axisCommon.axisLabel, formatter: '{value}%' },
|
||||
splitLine: { show: false },
|
||||
},
|
||||
],
|
||||
series: [
|
||||
{
|
||||
name: '毛绒',
|
||||
...CATEGORIES.map((c, ci) => ({
|
||||
name: c.name,
|
||||
type: 'bar',
|
||||
stack: 'category',
|
||||
data: col(dataRows, 'plush'),
|
||||
itemStyle: { color: PALETTE.coral },
|
||||
// 每段都标数值:矮段外置到柱体右侧,机制同渠道金额图
|
||||
data: dataRows.map((row, ri) => stackedSharePoint(row[c.key], {
|
||||
pxPerUnit,
|
||||
labelColor: '#fff',
|
||||
dy: rowOffsets[ri][ci] ?? 0,
|
||||
})),
|
||||
itemStyle: { color: c.color },
|
||||
emphasis: { focus: 'series' },
|
||||
label: {
|
||||
show: true,
|
||||
@@ -68,61 +152,30 @@ export function categoryChart(rows, { width, height, view = 'all' }) {
|
||||
color: '#fff',
|
||||
fontSize: 12,
|
||||
fontFamily: FONT,
|
||||
formatter: (p) => (p.value > 15 ? p.value.toFixed(1) : ''),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: '手办',
|
||||
type: 'bar',
|
||||
stack: 'category',
|
||||
data: col(dataRows, 'figure'),
|
||||
itemStyle: { color: PALETTE.blue },
|
||||
emphasis: { focus: 'series' },
|
||||
label: {
|
||||
show: true,
|
||||
position: 'inside',
|
||||
color: '#fff',
|
||||
fontSize: 12,
|
||||
fontFamily: FONT,
|
||||
formatter: (p) => (p.value > 15 ? p.value.toFixed(1) : ''),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'MEGA',
|
||||
type: 'bar',
|
||||
stack: 'category',
|
||||
data: col(dataRows, 'mega'),
|
||||
itemStyle: { color: PALETTE.purple },
|
||||
emphasis: { focus: 'series' },
|
||||
label: {
|
||||
show: true,
|
||||
position: 'inside',
|
||||
color: '#fff',
|
||||
fontSize: 12,
|
||||
fontFamily: FONT,
|
||||
formatter: (p) => (p.value > 8 ? p.value.toFixed(1) : ''),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: '衍生品及其他',
|
||||
type: 'bar',
|
||||
stack: 'category',
|
||||
data: col(dataRows, 'other'),
|
||||
itemStyle: { color: PALETTE.sage },
|
||||
emphasis: { focus: 'series' },
|
||||
label: {
|
||||
show: true,
|
||||
position: 'inside',
|
||||
color: '#fff',
|
||||
fontSize: 12,
|
||||
fontFamily: FONT,
|
||||
formatter: (p) => (p.value > 8 ? p.value.toFixed(1) : ''),
|
||||
},
|
||||
overflow: 'none',
|
||||
formatter: (p) => `${p.value.toFixed(1)}`,
|
||||
},
|
||||
})),
|
||||
...CATEGORIES.map((c) => ({
|
||||
...lineSeries({
|
||||
name: `${c.name}同比`,
|
||||
data: yoyByCat[c.key],
|
||||
color: c.lineColor,
|
||||
formatter: (p) => `${p.value.toFixed(1)}%`,
|
||||
width: 2.5,
|
||||
}),
|
||||
yAxisIndex: 1,
|
||||
z: 10,
|
||||
// label.position 不接受函数,逐点覆盖(避让规则见 yoyLabelPos)
|
||||
data: yoyByCat[c.key].map((v, i) => (Number.isFinite(v)
|
||||
? { value: v, label: { position: yoyLabelPos[i][c.key], distance: 10 } }
|
||||
: null)),
|
||||
labelLayout: { hideOverlap: true },
|
||||
})),
|
||||
],
|
||||
graphic: [
|
||||
...watermarks(width, height),
|
||||
footnote('数据来源:泡泡玛特财报 | @思考的Joey'),
|
||||
footnote('数据来源:泡泡玛特财报;同比=当期÷上年同期−1\n2024年各期无上年基数,不计算同比 | @Joey知行记'),
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { col } from '../csv.js';
|
||||
import {
|
||||
CHANNEL_COLORS, PALETTE, FONT, axisCommon, standalonePeriodAxis, titleStyle, legendStyle,
|
||||
financialViewDisclosure, financialViewLabel,
|
||||
financialViewDisclosure, financialViewLabel, niceAxisMax,
|
||||
lineSeries, footnote, watermarks, LAYOUT,
|
||||
} from '../theme.js';
|
||||
|
||||
@@ -23,14 +23,12 @@ const INSIDE_LABEL_COLORS = {
|
||||
|
||||
const num = (value) => (Number.isFinite(value) ? value : null);
|
||||
|
||||
// 轴上限取整齐刻度,柱高比例确定后,段标签的段内/外置可按像素判断
|
||||
const NICE_MAX = [20, 25, 50, 60, 80, 100, 120, 150, 200, 250, 300, 400, 500, 600, 800, 1000];
|
||||
// 轴上限按数据最大值取整齐刻度,柱高比例确定后,段标签的段内/外置可按像素判断
|
||||
const MIN_INSIDE_PX = 13; // 低于约 13px 的段放不下 10px 段内标签
|
||||
|
||||
export function channelRevenueChart(rows, { width, height, view = 'all' }) {
|
||||
const totalMax = Math.max(...rows.map((row) => num(row.total_revenue) || 0));
|
||||
const axisMax = NICE_MAX.find((max) => max >= totalMax * 1.02)
|
||||
?? Math.ceil((totalMax * 1.2) / 100) * 100;
|
||||
const axisMax = niceAxisMax(totalMax);
|
||||
const yoyValues = col(rows, 'revenue_yoy').filter(Number.isFinite);
|
||||
// 全部/半年视图存在 2022H2 的负增长,右轴需包含负区间,避免折线被 0 轴裁切。
|
||||
const yoyMin = Math.min(0, Math.floor(Math.min(...yoyValues) / 50) * 50);
|
||||
@@ -170,7 +168,7 @@ export function channelRevenueChart(rows, { width, height, view = 'all' }) {
|
||||
],
|
||||
graphic: [
|
||||
...watermarks(width, height),
|
||||
footnote('数据来源:泡泡玛特营收构成表;渠道口径按财报披露 | @思考的Joey'),
|
||||
footnote('数据来源:泡泡玛特营收构成表;渠道口径按财报披露 | @Joey知行记'),
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
@@ -0,0 +1,80 @@
|
||||
import { col } from '../csv.js';
|
||||
import {
|
||||
CHANNEL_COLORS, PALETTE, FONT, axisCommon, standalonePeriodAxis, titleStyle, standardLegend,
|
||||
financialViewLabel, barSeries, stackedSharePoint, fanTinyLabelOffsets, footnote, watermarks, LAYOUT,
|
||||
} from '../theme.js';
|
||||
|
||||
const CHANNELS = [
|
||||
{ key: 'retail', name: '零售店', color: CHANNEL_COLORS.retail, labelColor: '#fff' },
|
||||
{ key: 'online', name: '线上渠道', color: CHANNEL_COLORS.online, labelColor: '#fff' },
|
||||
{ key: 'robot_store', name: '机器人商店', color: CHANNEL_COLORS.robotStore, labelColor: '#fff' },
|
||||
{ key: 'wholesale', name: '批发及其他', color: CHANNEL_COLORS.wholesale, labelColor: PALETTE.ink },
|
||||
];
|
||||
|
||||
/** 泡泡玛特全渠道收入占比:全部地区合并,每根柱按当期总收入归一化为 100%。 */
|
||||
export function channelShareChart(rows, { width, height, view = 'all' }) {
|
||||
const periods = col(rows, 'period');
|
||||
const barWidth = view === 'all' ? 48 : 80;
|
||||
// 绘图区像素高:画布高 − grid top 145 − grid bottom 100;轴固定 0–100%
|
||||
const pxPerUnit = (height - LAYOUT.gridTop - 100) / 100;
|
||||
const seriesKeys = CHANNELS.map((item) => item.key);
|
||||
const rowOffsets = fanTinyLabelOffsets(rows, seriesKeys, pxPerUnit);
|
||||
|
||||
return {
|
||||
backgroundColor: '#fff',
|
||||
title: {
|
||||
...titleStyle,
|
||||
text: `泡泡玛特渠道收入占比(${financialViewLabel(view)})`,
|
||||
subtext: '全部地区合并;归一化堆叠:每根柱为当期总收入的 100%',
|
||||
},
|
||||
legend: standardLegend(CHANNELS.map((item) => item.name)),
|
||||
tooltip: {
|
||||
trigger: 'axis',
|
||||
axisPointer: { type: 'shadow' },
|
||||
textStyle: { fontFamily: FONT },
|
||||
formatter: (params) => {
|
||||
const row = rows[periods.indexOf(params[0].axisValue)];
|
||||
return `<b>${params[0].axisValue}</b> 总收入 ${row.total_revenue.toFixed(2)} 亿元<br/>${params
|
||||
.filter((item) => item.value !== null)
|
||||
.slice()
|
||||
.reverse()
|
||||
.map((item) => `${item.marker}${item.seriesName}: ${item.value.toFixed(1)}%`)
|
||||
.join('<br/>')}`;
|
||||
},
|
||||
},
|
||||
grid: { left: 78, right: 42, top: LAYOUT.gridTop, bottom: 100 },
|
||||
xAxis: { ...standalonePeriodAxis, data: periods },
|
||||
yAxis: {
|
||||
...axisCommon,
|
||||
type: 'value',
|
||||
min: 0,
|
||||
max: 100,
|
||||
interval: 20,
|
||||
axisLabel: { ...axisCommon.axisLabel, formatter: '{value}%' },
|
||||
},
|
||||
series: CHANNELS.map((item, ci) => barSeries({
|
||||
name: item.name,
|
||||
data: rows.map((row, ri) => stackedSharePoint(row[item.key], {
|
||||
pxPerUnit,
|
||||
labelColor: item.labelColor,
|
||||
dy: rowOffsets[ri][ci] ?? 0,
|
||||
})),
|
||||
color: item.color,
|
||||
barWidth,
|
||||
stack: 'channel-share',
|
||||
label: {
|
||||
show: true,
|
||||
position: 'inside',
|
||||
fontSize: 11,
|
||||
fontFamily: FONT,
|
||||
fontWeight: 500,
|
||||
formatter: (point) => `${point.value.toFixed(1)}%`,
|
||||
},
|
||||
emphasis: { focus: 'series' },
|
||||
})),
|
||||
graphic: [
|
||||
...watermarks(width, height),
|
||||
footnote('数据来源:泡泡玛特分地区渠道收入披露(中国含港澳台);H2为单半年(全年−H1)\n线上渠道含抽盒机、天猫、抖音、自研APP及官网等 | @Joey知行记'),
|
||||
],
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,205 @@
|
||||
import { col } from '../csv.js';
|
||||
import {
|
||||
CHANNEL_COLORS, PALETTE, FONT, axisCommon, standalonePeriodAxis, titleStyle, standardLegend,
|
||||
financialViewLabel, barSeries, stackedSharePoint, fanTinyLabelOffsets, footnote, watermarks, LAYOUT,
|
||||
} from '../theme.js';
|
||||
|
||||
/**
|
||||
* 四地区共用同一套渠道语义色:零售店/机器人商店/批发及其他沿用渠道色板;
|
||||
* 线上细分逐平台一色,抖音与 TikTok 同属一条生态线共用青色,其他线上为中性灰。
|
||||
* 各图 5~7 个系列,图内两两 ΔE 已验算 > 25(最紧:抖音青 vs 中性灰 25.1)。
|
||||
*/
|
||||
const SERIES = {
|
||||
retail: { name: '零售店', color: CHANNEL_COLORS.retail, labelColor: '#fff' },
|
||||
robot_store: { name: '机器人商店', color: CHANNEL_COLORS.robotStore, labelColor: '#fff' },
|
||||
wholesale: { name: '批发及其他', color: CHANNEL_COLORS.wholesale, labelColor: PALETTE.ink },
|
||||
online_other: { name: '其他线上', color: PALETTE.grey, labelColor: '#fff' },
|
||||
roboshop: { name: '抽盒机', color: CHANNEL_COLORS.online, labelColor: '#fff' },
|
||||
tmall: { name: '天猫旗舰店', color: '#8256B2', labelColor: '#fff' },
|
||||
douyin: { name: '抖音平台', color: '#4E9EA7', labelColor: '#fff' },
|
||||
own_app: { name: '自研APP及官网', color: '#C05B76', labelColor: '#fff' },
|
||||
shopee: { name: 'Shopee', color: PALETTE.brown, labelColor: '#fff' },
|
||||
tiktok: { name: 'TikTok', color: '#4E9EA7', labelColor: '#fff' },
|
||||
amazon: { name: '亚马逊', color: PALETTE.red, labelColor: '#fff' },
|
||||
};
|
||||
|
||||
const REGIONS = {
|
||||
china: {
|
||||
title: '中国区',
|
||||
source: '中国(含港澳台)',
|
||||
series: ['retail', 'robot_store', 'roboshop', 'tmall', 'douyin', 'online_other', 'wholesale'],
|
||||
footnote: '数据来源:泡泡玛特中国区(含港澳台)分渠道收入披露;H2为单半年(全年−H1)\n线上渠道按财报披露拆分为抽盒机、天猫旗舰店、抖音平台与其他线上 | @Joey知行记',
|
||||
},
|
||||
apac: {
|
||||
title: '亚太区',
|
||||
source: '亚太区',
|
||||
series: ['retail', 'robot_store', 'own_app', 'shopee', 'online_other', 'wholesale'],
|
||||
footnote: '数据来源:泡泡玛特亚太区分渠道收入披露;H2为单半年(全年−H1)\n线上渠道按财报披露拆分为自研APP及官网、Shopee与其他线上 | @Joey知行记',
|
||||
},
|
||||
americas: {
|
||||
title: '美洲区',
|
||||
source: '美洲区',
|
||||
series: ['retail', 'robot_store', 'own_app', 'tiktok', 'amazon', 'online_other', 'wholesale'],
|
||||
footnote: '数据来源:泡泡玛特美洲区分渠道收入披露;H2为单半年(全年−H1)\n2026H1起TikTok无收入,亚马逊为2026年新开渠道 | @Joey知行记',
|
||||
},
|
||||
europe: {
|
||||
title: '欧洲及其他区',
|
||||
source: '欧洲及其他地区',
|
||||
series: ['retail', 'robot_store', 'own_app', 'online_other', 'wholesale'],
|
||||
footnote: '数据来源:泡泡玛特欧洲及其他地区分渠道收入披露;H2为单半年(全年−H1)\n线上渠道按财报披露拆分为自研APP及官网与其他线上 | @Joey知行记',
|
||||
},
|
||||
};
|
||||
|
||||
/** 轴上限取整齐刻度,柱高比例确定后,段标签的段内/外置可按像素判断(同 ppmt_channel_revenue)。 */
|
||||
const AMOUNT_NICE_MAX = [10, 20, 25, 50, 60, 80, 100, 120, 150, 200, 250, 300, 400, 500];
|
||||
|
||||
/**
|
||||
* 分地区渠道收入金额堆叠柱:与占比图同源同色,直接使用披露金额(亿元),
|
||||
* 不做占比×总收入还原。矮段标签外置到柱体右侧的机制同全局渠道金额图。
|
||||
*/
|
||||
export function makeRegionChannelAmountChart(regionKey) {
|
||||
const region = REGIONS[regionKey];
|
||||
if (!region) throw new Error(`未配置的地区渠道金额图:${regionKey}`);
|
||||
return function regionChannelAmountChart(rows, { width, height, view = 'all' }) {
|
||||
const periods = col(rows, 'period');
|
||||
const barWidth = view === 'all' ? 48 : 80;
|
||||
const totalMax = Math.max(...rows.map((row) => row.total_revenue));
|
||||
const axisMax = AMOUNT_NICE_MAX.find((max) => max >= totalMax * 1.02)
|
||||
?? Math.ceil((totalMax * 1.2) / 100) * 100;
|
||||
// 绘图区像素高:画布高 − grid top 145 − grid bottom 100(两行脚注)
|
||||
const pxPerUnit = (height - LAYOUT.gridTop - 100) / axisMax;
|
||||
const rowOffsets = fanTinyLabelOffsets(rows, region.series, pxPerUnit);
|
||||
|
||||
return {
|
||||
backgroundColor: '#fff',
|
||||
title: {
|
||||
...titleStyle,
|
||||
text: `泡泡玛特${region.title}渠道收入金额(${financialViewLabel(view)})`,
|
||||
subtext: '堆叠柱为各渠道收入(亿元);矮段数值标注在柱体右侧,线上渠道按披露细分拆分',
|
||||
},
|
||||
legend: standardLegend(region.series.map((key) => SERIES[key].name)),
|
||||
tooltip: {
|
||||
trigger: 'axis',
|
||||
axisPointer: { type: 'shadow' },
|
||||
textStyle: { fontFamily: FONT },
|
||||
formatter: (params) => {
|
||||
const row = rows[periods.indexOf(params[0].axisValue)];
|
||||
return `<b>${params[0].axisValue}</b> ${region.source}总收入 ${row.total_revenue.toFixed(2)} 亿元<br/>${params
|
||||
.filter((item) => item.value !== null)
|
||||
.slice()
|
||||
.reverse()
|
||||
.map((item) => `${item.marker}${item.seriesName}: ${item.value.toFixed(1)} 亿元`)
|
||||
.join('<br/>')}`;
|
||||
},
|
||||
},
|
||||
grid: { left: 78, right: 82, top: LAYOUT.gridTop, bottom: 100 },
|
||||
xAxis: { ...standalonePeriodAxis, data: periods },
|
||||
yAxis: {
|
||||
...axisCommon,
|
||||
type: 'value',
|
||||
name: '渠道收入(亿元)',
|
||||
nameLocation: 'middle',
|
||||
nameGap: 56,
|
||||
nameTextStyle: { color: PALETTE.grey, fontSize: 13, fontFamily: FONT },
|
||||
min: 0,
|
||||
max: axisMax,
|
||||
interval: axisMax % 4 === 0 ? axisMax / 4 : axisMax / 5,
|
||||
},
|
||||
series: region.series.map((key, ci) => barSeries({
|
||||
name: SERIES[key].name,
|
||||
data: rows.map((row, ri) => stackedSharePoint(row[key], {
|
||||
pxPerUnit,
|
||||
labelColor: SERIES[key].labelColor,
|
||||
dy: rowOffsets[ri][ci] ?? 0,
|
||||
})),
|
||||
color: SERIES[key].color,
|
||||
barWidth,
|
||||
stack: `channel-amount-${regionKey}`,
|
||||
label: {
|
||||
show: true,
|
||||
position: 'inside',
|
||||
fontSize: 11,
|
||||
fontFamily: FONT,
|
||||
fontWeight: 500,
|
||||
overflow: 'none',
|
||||
formatter: (point) => point.value.toFixed(1),
|
||||
},
|
||||
emphasis: { focus: 'series' },
|
||||
})),
|
||||
graphic: [
|
||||
...watermarks(width, height),
|
||||
footnote(region.footnote),
|
||||
],
|
||||
};
|
||||
};
|
||||
}
|
||||
/** 分地区渠道收入占比:线上渠道拆分披露的细分平台,每根柱按当期该地区总收入归一化。 */
|
||||
export function makeRegionChannelShareChart(regionKey) {
|
||||
const region = REGIONS[regionKey];
|
||||
if (!region) throw new Error(`未配置的地区渠道占比图:${regionKey}`);
|
||||
return function regionChannelShareChart(rows, { width, height, view = 'all' }) {
|
||||
const periods = col(rows, 'period');
|
||||
const barWidth = view === 'all' ? 48 : 80;
|
||||
// 绘图区像素高:画布高 − grid top 145 − grid bottom 100;轴固定 0–100%
|
||||
const pxPerUnit = (height - LAYOUT.gridTop - 100) / 100;
|
||||
const rowOffsets = fanTinyLabelOffsets(rows, region.series, pxPerUnit);
|
||||
|
||||
return {
|
||||
backgroundColor: '#fff',
|
||||
title: {
|
||||
...titleStyle,
|
||||
text: `泡泡玛特${region.title}渠道收入占比(${financialViewLabel(view)})`,
|
||||
subtext: '归一化堆叠:每根柱为当期该地区总收入的 100%;线上渠道按披露细分拆分',
|
||||
},
|
||||
legend: standardLegend(region.series.map((key) => SERIES[key].name)),
|
||||
tooltip: {
|
||||
trigger: 'axis',
|
||||
axisPointer: { type: 'shadow' },
|
||||
textStyle: { fontFamily: FONT },
|
||||
formatter: (params) => {
|
||||
const row = rows[periods.indexOf(params[0].axisValue)];
|
||||
return `<b>${params[0].axisValue}</b> ${region.source}总收入 ${row.total_revenue.toFixed(2)} 亿元<br/>${params
|
||||
.filter((item) => item.value !== null)
|
||||
.slice()
|
||||
.reverse()
|
||||
.map((item) => `${item.marker}${item.seriesName}: ${item.value.toFixed(1)}%`)
|
||||
.join('<br/>')}`;
|
||||
},
|
||||
},
|
||||
grid: { left: 78, right: 42, top: LAYOUT.gridTop, bottom: 100 },
|
||||
xAxis: { ...standalonePeriodAxis, data: periods },
|
||||
yAxis: {
|
||||
...axisCommon,
|
||||
type: 'value',
|
||||
min: 0,
|
||||
max: 100,
|
||||
interval: 20,
|
||||
axisLabel: { ...axisCommon.axisLabel, formatter: '{value}%' },
|
||||
},
|
||||
series: region.series.map((key, ci) => barSeries({
|
||||
name: SERIES[key].name,
|
||||
data: rows.map((row, ri) => stackedSharePoint(row[key], {
|
||||
pxPerUnit,
|
||||
labelColor: SERIES[key].labelColor,
|
||||
dy: rowOffsets[ri][ci] ?? 0,
|
||||
})),
|
||||
color: SERIES[key].color,
|
||||
barWidth,
|
||||
stack: `channel-share-${regionKey}`,
|
||||
label: {
|
||||
show: true,
|
||||
position: 'inside',
|
||||
fontSize: 11,
|
||||
fontFamily: FONT,
|
||||
fontWeight: 500,
|
||||
formatter: (point) => `${point.value.toFixed(1)}%`,
|
||||
},
|
||||
emphasis: { focus: 'series' },
|
||||
})),
|
||||
graphic: [
|
||||
...watermarks(width, height),
|
||||
footnote(region.footnote),
|
||||
],
|
||||
};
|
||||
};
|
||||
}
|
||||
@@ -59,7 +59,7 @@ export function ppmtCoreMarginChart(rows, { width, height, view = 'all' }) {
|
||||
...watermarks(width, height),
|
||||
footnote(
|
||||
'核心利润率由绝对值口径直接计算(核心利润÷营收),比三个比率行相减更准\n' +
|
||||
'——后者各只存4位小数,累加误差可达0.045pct | @思考的Joey'
|
||||
'——后者各只存4位小数,累加误差可达0.045pct | @Joey知行记'
|
||||
),
|
||||
],
|
||||
};
|
||||
|
||||
@@ -137,7 +137,7 @@ export function ppmtCoreProfitChart(rows, { width, height, view = 'all' }) {
|
||||
...watermarks(width, height),
|
||||
footnote(
|
||||
'核心利润反映主业盈利能力(扣除营销与管理成本);' +
|
||||
'2025H1/H2同比增速受益于费用率优化和规模效应 | @思考的Joey'
|
||||
'2025H1/H2同比增速受益于费用率优化和规模效应 | @Joey知行记'
|
||||
),
|
||||
],
|
||||
};
|
||||
|
||||
@@ -1,12 +1,49 @@
|
||||
import { col } from '../csv.js';
|
||||
import {
|
||||
PALETTE, FONT, axisCommon, standalonePeriodAxis, titleStyle, legendStyle,
|
||||
financialViewDisclosure, financialViewLabel,
|
||||
financialViewDisclosure, financialViewLabel, niceAxisMax, stackedSharePoint,
|
||||
lineSeries, footnote, watermarks, LAYOUT,
|
||||
} from '../theme.js';
|
||||
|
||||
/**
|
||||
* 混合正负堆叠的矮段标签外置偏移。
|
||||
* ECharts 对负值段自 0 向下堆叠,theme 的 fanTinyLabelOffsets 按全正累计
|
||||
* 算不出正确中心,这里按符号分两摞累计(屏幕坐标 y 向下为正),
|
||||
* 矮段(塞不进段内文字的)标签扇形排开在柱体右侧。
|
||||
*/
|
||||
function expenseFanOffsets(rows, keys, pxPerUnit, { minInsidePx = 15, gap = 13 } = {}) {
|
||||
return rows.map((row) => {
|
||||
let posCum = 0;
|
||||
let negCum = 0;
|
||||
const centers = {};
|
||||
keys.forEach((k) => {
|
||||
const v = Number.isFinite(row[k]) ? row[k] : 0;
|
||||
const mid = v >= 0 ? posCum + v / 2 : negCum + v / 2;
|
||||
centers[k] = -mid * pxPerUnit;
|
||||
if (v >= 0) posCum += v; else negCum += v;
|
||||
});
|
||||
const tiny = keys.filter((k) => Number.isFinite(row[k]) && row[k] !== 0
|
||||
&& Math.abs(row[k]) * pxPerUnit < minInsidePx);
|
||||
if (!tiny.length) return {};
|
||||
const ys = tiny.map((k) => centers[k]);
|
||||
for (let i = 1; i < ys.length; i += 1) ys[i] = Math.max(ys[i], ys[i - 1] + gap);
|
||||
const offsets = {};
|
||||
tiny.forEach((k, i) => { offsets[k] = centers[k] - ys[i]; });
|
||||
return offsets;
|
||||
});
|
||||
}
|
||||
|
||||
/** 泡泡玛特期间费用结构:观察销售投入、管理成本与费用率的规模效应。 */
|
||||
export function expenseStructureChart(rows, { width, height, view = 'all' }) {
|
||||
const keys = ['sales_expense', 'admin_expense', 'financial_expense'];
|
||||
const posStackMax = Math.max(...rows.map((r) => keys.reduce((s, k) => s + Math.max(r[k] ?? 0, 0), 0)));
|
||||
const negMin = Math.min(...rows.map((r) => keys.reduce((s, k) => s + Math.min(r[k] ?? 0, 0), 0)));
|
||||
// 轴范围随数据走并取整刻度,保证矮段像素判定(pxPerUnit)有确定基准
|
||||
const axisMax = niceAxisMax(posStackMax);
|
||||
const axisMin = Math.min(0, Math.floor(negMin / 10) * 10);
|
||||
const pxPerUnit = (height - LAYOUT.gridTop - 90) / (axisMax - axisMin);
|
||||
const rowOffsets = expenseFanOffsets(rows, keys, pxPerUnit);
|
||||
|
||||
return {
|
||||
backgroundColor: '#fff',
|
||||
title: {
|
||||
@@ -35,6 +72,9 @@ export function expenseStructureChart(rows, { width, height, view = 'all' }) {
|
||||
nameLocation: 'middle',
|
||||
nameGap: 52,
|
||||
nameTextStyle: { color: PALETTE.grey, fontSize: 13, fontFamily: FONT },
|
||||
min: axisMin,
|
||||
max: axisMax,
|
||||
interval: 10,
|
||||
},
|
||||
{
|
||||
...axisCommon,
|
||||
@@ -52,9 +92,34 @@ export function expenseStructureChart(rows, { width, height, view = 'all' }) {
|
||||
},
|
||||
],
|
||||
series: [
|
||||
{ name: '销售费用', type: 'bar', stack: 'expenses', data: col(rows, 'sales_expense'), itemStyle: { color: PALETTE.orange } },
|
||||
{ name: '管理费用', type: 'bar', stack: 'expenses', data: col(rows, 'admin_expense'), itemStyle: { color: PALETTE.purple } },
|
||||
{ name: '财务费用(净额)', type: 'bar', stack: 'expenses', data: col(rows, 'financial_expense'), itemStyle: { color: PALETTE.blue } },
|
||||
...keys.map((key, ci) => ({
|
||||
name: { sales_expense: '销售费用', admin_expense: '管理费用', financial_expense: '财务费用(净额)' }[key],
|
||||
type: 'bar',
|
||||
stack: 'expenses',
|
||||
// 每段都标数值:矮段(含负值段)外置到柱体右侧,见 expenseFanOffsets
|
||||
data: rows.map((row, ri) => stackedSharePoint(row[key], {
|
||||
pxPerUnit,
|
||||
labelColor: '#fff',
|
||||
dy: rowOffsets[ri][key] ?? 0,
|
||||
})),
|
||||
itemStyle: { color: [PALETTE.orange, PALETTE.purple, PALETTE.blue][ci] },
|
||||
emphasis: { focus: 'series' },
|
||||
label: {
|
||||
show: true,
|
||||
position: 'inside',
|
||||
color: '#fff',
|
||||
fontSize: 11,
|
||||
fontFamily: FONT,
|
||||
fontWeight: 500,
|
||||
overflow: 'none',
|
||||
// 财务费用净额多在 ±0.1 亿内,一位小数会显示成 ±0.0,此时退到两位小数
|
||||
formatter: (p) => {
|
||||
const t = p.value.toFixed(1);
|
||||
if (t === '0.0' || t === '-0.0') return Math.abs(p.value) < 0.005 ? '0.0' : p.value.toFixed(2);
|
||||
return t;
|
||||
},
|
||||
},
|
||||
})),
|
||||
{
|
||||
...lineSeries({
|
||||
name: '四项费用率',
|
||||
@@ -69,7 +134,7 @@ export function expenseStructureChart(rows, { width, height, view = 'all' }) {
|
||||
],
|
||||
graphic: [
|
||||
...watermarks(width, height),
|
||||
footnote('数据来源:泡泡玛特合并利润表;H2为单独半年 | @思考的Joey'),
|
||||
footnote('数据来源:泡泡玛特合并利润表;H2为单独半年 | @Joey知行记'),
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
@@ -106,7 +106,7 @@ export function ppmtGrowthChart(rows, { width, height, view = 'all' }) {
|
||||
})),
|
||||
graphic: [
|
||||
...watermarks(width, height),
|
||||
footnote('数据来源:泡泡玛特财报;2020H1/H2非GAAP净利用归母净利替代 | @思考的Joey'),
|
||||
footnote('数据来源:泡泡玛特财报;2020H1/H2非GAAP净利用归母净利替代 | @Joey知行记'),
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
@@ -76,7 +76,7 @@ export function inventoryBalanceChart(rows, { width, height, view = 'all' }) {
|
||||
],
|
||||
graphic: [
|
||||
...watermarks(width, height),
|
||||
footnote('数据来源:泡泡玛特历年合并资产负债表;存货为报告期末时点数 | @思考的Joey'),
|
||||
footnote('数据来源:泡泡玛特历年合并资产负债表;存货为报告期末时点数 | @Joey知行记'),
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { col } from '../csv.js';
|
||||
import {
|
||||
PALETTE, FONT, axisCommon, categoryAxis, titleStyle, legendStyle,
|
||||
financialViewDisclosure, financialViewLabel, shortStandalonePeriod,
|
||||
financialViewDisclosure, financialViewLabel, shortStandalonePeriod, niceAxisMax,
|
||||
lineSeries, footnote, watermarks, LAYOUT,
|
||||
} from '../theme.js';
|
||||
|
||||
@@ -39,6 +39,10 @@ export function ipChart(rows, { width, height, view = 'all' }) {
|
||||
});
|
||||
});
|
||||
|
||||
// 轴上限随数据走(THE MONSTERS 2025 已超 140 亿),写死会把爆款折线顶出绘图区
|
||||
const values = rows.flatMap((row) => periods.map((p) => row[p])).filter(Number.isFinite);
|
||||
const axisMax = niceAxisMax(Math.max(...values));
|
||||
|
||||
return {
|
||||
backgroundColor: '#fff',
|
||||
title: {
|
||||
@@ -74,13 +78,13 @@ export function ipChart(rows, { width, height, view = 'all' }) {
|
||||
nameGap: 52,
|
||||
nameTextStyle: { color: PALETTE.grey, fontSize: 13, fontFamily: FONT },
|
||||
min: 0,
|
||||
max: 100,
|
||||
interval: 20,
|
||||
max: axisMax,
|
||||
interval: axisMax % 4 === 0 ? axisMax / 4 : axisMax / 5,
|
||||
},
|
||||
series,
|
||||
graphic: [
|
||||
...watermarks(width, height),
|
||||
footnote('数据来源:泡泡玛特财报 | @思考的Joey'),
|
||||
footnote('数据来源:泡泡玛特财报 | @Joey知行记'),
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
@@ -143,7 +143,7 @@ export function ppmtIpExMonstersChart(rows, { width, height, view = 'all' }) {
|
||||
],
|
||||
graphic: [
|
||||
...watermarks(width, height),
|
||||
footnote('除 THE MONSTERS 外其余 IP 合计;同比对去年同半年 | @思考的Joey'),
|
||||
footnote('除 THE MONSTERS 外其余 IP 合计;同比对去年同半年 | @Joey知行记'),
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@ import {
|
||||
/**
|
||||
* 泡泡玛特主要 IP 收入占比(2026H1)
|
||||
* 饼图(非环图),图例放在右侧:每项上行 IP 名、下行小字显示收入和占比。
|
||||
* 扇区外直接标注 IP 名 + 数值(两行)。源:ppmt.xlsx「IP收入和占比」2026H1 列,
|
||||
* 扇区外直接标注 IP 名 + 数值/占比(两行),IP 名与扇区同色。源:ppmt.xlsx「IP收入和占比」2026H1 列,
|
||||
* HACIPUPU、BUNNY、PUCKY 等该期无数据的 IP 不列入。
|
||||
* 占比为衍生指标:各 IP 收入 ÷ 全部列入 IP 收入合计(源表占比列未填)。
|
||||
*
|
||||
@@ -29,18 +29,28 @@ const COLORS = [
|
||||
];
|
||||
|
||||
export function ppmtIpShareChart(rows, { width, height }) {
|
||||
const data = rows.map((row, i) => ({
|
||||
const data = rows.map((row, i) => {
|
||||
const color = COLORS[i % COLORS.length];
|
||||
return {
|
||||
name: row.ip,
|
||||
value: row.revenue,
|
||||
itemStyle: { color: COLORS[i % COLORS.length] },
|
||||
}));
|
||||
itemStyle: { color },
|
||||
// 逐扇区覆盖标签 rich:IP 名与所在区块同色,其余样式沿用系列级配置
|
||||
label: {
|
||||
rich: {
|
||||
name: { fontFamily: FONT, fontSize: 12, fontWeight: 600, color, lineHeight: 16 },
|
||||
value: { fontFamily: FONT, fontSize: 11, color: PALETTE.grey, lineHeight: 15 },
|
||||
},
|
||||
},
|
||||
};
|
||||
});
|
||||
const total = data.reduce((s, d) => s + d.value, 0);
|
||||
const byName = Object.fromEntries(data.map((d) => [d.name, d]));
|
||||
|
||||
// 把「饼图 + 两侧外标签」当成一个整体,与图例并成一组居中摆放:
|
||||
// 图例紧跟在右侧标签外缘之后,不会被推到画布边缘留下大片空白
|
||||
const LEGEND_WIDTH = 152; // 最宽一行「17.7 亿元 · 10.3%」约 120px + 色块
|
||||
const LEGEND_GAP = 24; // 右侧标签外缘 → 图例
|
||||
const LEGEND_GAP = 56; // 右侧标签外缘 → 图例;最长数值行会侵入该间隙,实测 24px 太挤
|
||||
// 左右标签占位不对称:左侧文字向左延伸(text-anchor: end)要占满,
|
||||
// 右侧只需容下引导线 + 首行文字起段,留太多会在图例前空出一大块
|
||||
const LABEL_LEFT = 135;
|
||||
@@ -105,7 +115,7 @@ export function ppmtIpShareChart(rows, { width, height }) {
|
||||
data,
|
||||
label: {
|
||||
show: true,
|
||||
formatter: (p) => `{name|${p.name}}\n{value|${p.value.toFixed(1)} 亿元}`,
|
||||
formatter: (p) => `{name|${p.name}}\n{value|${p.value.toFixed(1)} 亿元 / ${p.percent.toFixed(1)}%}`,
|
||||
rich: {
|
||||
name: { fontFamily: FONT, fontSize: 12, color: PALETTE.ink, fontWeight: 600, lineHeight: 16 },
|
||||
value: { fontFamily: FONT, fontSize: 11, color: PALETTE.grey, lineHeight: 15 },
|
||||
@@ -120,7 +130,7 @@ export function ppmtIpShareChart(rows, { width, height }) {
|
||||
],
|
||||
graphic: [
|
||||
...watermarks(width, height),
|
||||
footnote('HACIPUPU、BUNNY、PUCKY 等 IP 2026H1 无收入数据,未列入 | @思考的Joey'),
|
||||
footnote('HACIPUPU、BUNNY、PUCKY 等 IP 2026H1 无收入数据,未列入 | @Joey知行记'),
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import {
|
||||
PALETTE, FONT, axisCommon, categoryAxis, titleStyle,
|
||||
financialViewDisclosure, financialViewLabel, shortStandalonePeriod,
|
||||
standardLegend, barSeries, footnote, watermarks, LAYOUT,
|
||||
standardLegend, barSeries, fanTinyLabelOffsets, stackedSharePoint, footnote, watermarks, LAYOUT,
|
||||
} from '../theme.js';
|
||||
|
||||
/**
|
||||
@@ -40,8 +40,14 @@ export function ppmtIpShareHistoryChart(rows, { width, height, view = 'all' }) {
|
||||
const periods = Object.keys(rows[0]).filter((k) => k !== 'ip');
|
||||
const names = rows.map((r) => r.ip);
|
||||
const totals = periods.map((p) => rows.reduce((s, r) => s + r[p], 0));
|
||||
// 归一化:各 IP 占当期合计的比重,逐期加总恰为 100%
|
||||
const toPct = (r) => periods.map((p, i) => (r[p] / totals[i]) * 100);
|
||||
// 归一化:各 IP 占当期合计的比重,逐期加总恰为 100%。
|
||||
// 转成「期间 × 系列」的行结构,供 fanTinyLabelOffsets 逐柱计算矮段偏移。
|
||||
const pctRows = periods.map((p, i) => Object.fromEntries(
|
||||
rows.map((r) => [r.ip, (r[p] / totals[i]) * 100]),
|
||||
));
|
||||
// 绘图区像素高:画布高 − grid top 145 − grid bottom 70;轴固定 0–100%
|
||||
const pxPerUnit = (height - LAYOUT.gridTop - 70) / 100;
|
||||
const rowOffsets = fanTinyLabelOffsets(pctRows, names, pxPerUnit);
|
||||
|
||||
return {
|
||||
backgroundColor: '#fff',
|
||||
@@ -83,9 +89,14 @@ export function ppmtIpShareHistoryChart(rows, { width, height, view = 'all' }) {
|
||||
interval: 20,
|
||||
axisLabel: { ...axisCommon.axisLabel, formatter: '{value}%' },
|
||||
},
|
||||
series: rows.map((r) => barSeries({
|
||||
// 每个非零占比都要展示:塞得进段内的段内白字,矮段外置到柱体右侧(深字白描边)
|
||||
series: rows.map((r, ci) => barSeries({
|
||||
name: r.ip,
|
||||
data: toPct(r),
|
||||
data: pctRows.map((pr, ri) => stackedSharePoint(pr[r.ip], {
|
||||
pxPerUnit,
|
||||
labelColor: '#fff',
|
||||
dy: rowOffsets[ri][ci] ?? 0,
|
||||
})),
|
||||
color: COLORS[r.ip] ?? PALETTE.grey,
|
||||
barWidth: 40,
|
||||
stack: 'share',
|
||||
@@ -96,14 +107,14 @@ export function ppmtIpShareHistoryChart(rows, { width, height, view = 'all' }) {
|
||||
fontSize: 11,
|
||||
fontFamily: FONT,
|
||||
fontWeight: 500,
|
||||
// 低于 4% 的段塞不进字,留空靠 tooltip 读数
|
||||
formatter: (p) => (p.value >= 4 ? `${p.value.toFixed(1)}%` : ''),
|
||||
overflow: 'none',
|
||||
formatter: (p) => `${p.value.toFixed(1)}%`,
|
||||
},
|
||||
emphasis: { focus: 'series' },
|
||||
})),
|
||||
graphic: [
|
||||
...watermarks(width, height),
|
||||
footnote('其他IP(含已停单列)= Zsiga、HACIPUPU、BUNNY、PUCKY、小甜豆等 | @思考的Joey'),
|
||||
footnote('其他IP(含已停单列)= Zsiga、HACIPUPU、BUNNY、PUCKY、小甜豆等 | @Joey知行记'),
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
@@ -77,7 +77,7 @@ export function liquidityWorkingCapitalChart(rows, { width, height, view = 'all'
|
||||
],
|
||||
graphic: [
|
||||
...watermarks(width, height),
|
||||
footnote('数据来源:泡泡玛特合并资产负债表 | @思考的Joey'),
|
||||
footnote('数据来源:泡泡玛特合并资产负债表 | @Joey知行记'),
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
@@ -110,7 +110,7 @@ export function ppmtMarginsChart(rows, { width, height, view = 'all' }) {
|
||||
...watermarks(width, height),
|
||||
footnote(
|
||||
'毛利率/费用率取自利润表;非国际归母净利率=非GAAP净利÷营业收入,' +
|
||||
'2020H1/H2非GAAP缺失用归母净利替代 | @思考的Joey'
|
||||
'2020H1/H2非GAAP缺失用归母净利替代 | @Joey知行记'
|
||||
),
|
||||
],
|
||||
};
|
||||
|
||||
@@ -123,7 +123,7 @@ export function profitChart(rows, { width, height, view = 'all' }) {
|
||||
],
|
||||
graphic: [
|
||||
...watermarks(width, height),
|
||||
footnote('数据来源:泡泡玛特财报;H2为单独半年 | @思考的Joey'),
|
||||
footnote('数据来源:泡泡玛特财报;H2为单独半年 | @Joey知行记'),
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,126 +0,0 @@
|
||||
import {
|
||||
PALETTE, FONT, titleStyle, footnote, watermarks, legendStyle,
|
||||
LAYOUT, } from '../theme.js';
|
||||
|
||||
/**
|
||||
* 泡泡玛特区域渠道分布图(2026H1)
|
||||
* 使用交错正负轴展示不同区域和渠道的收入
|
||||
*/
|
||||
export function regionChannelChart(rows, { width, height }) {
|
||||
// 数据结构重组:按区域分组
|
||||
const regions = ['中国', '海外', '亚太', '美洲', '欧洲及其他'];
|
||||
const channels = ['线下渠道', '线上渠道', '批发及其他'];
|
||||
|
||||
// 构建数据映射
|
||||
const dataMap = {};
|
||||
rows.forEach(row => {
|
||||
const key = `${row.region}_${row.channel}`;
|
||||
dataMap[key] = row.value;
|
||||
});
|
||||
|
||||
// 为每个渠道构建系列数据
|
||||
const seriesData = channels.map(channel => {
|
||||
return regions.map(region => {
|
||||
const key = `${region}_${channel}`;
|
||||
const value = dataMap[key] || 0;
|
||||
// 线上渠道显示为负值(左侧),其他为正值(右侧)
|
||||
return channel === '线上渠道' ? -value : value;
|
||||
});
|
||||
});
|
||||
|
||||
// 配色
|
||||
const channelColors = {
|
||||
'线下渠道': PALETTE.navy,
|
||||
'线上渠道': PALETTE.coral,
|
||||
'批发及其他': PALETTE.sage,
|
||||
};
|
||||
|
||||
return {
|
||||
backgroundColor: '#fff',
|
||||
title: {
|
||||
...titleStyle,
|
||||
text: '泡泡玛特区域渠道分布(2026H1)',
|
||||
subtext: '左侧:线上渠道 | 右侧:线下渠道、批发及其他,单位:亿元',
|
||||
top: 18,
|
||||
},
|
||||
legend: {
|
||||
...legendStyle,
|
||||
data: channels,
|
||||
top: LAYOUT.legendTop,
|
||||
itemGap: 32,
|
||||
textStyle: { fontFamily: FONT, fontSize: 13 },
|
||||
},
|
||||
tooltip: {
|
||||
trigger: 'axis',
|
||||
axisPointer: { type: 'shadow' },
|
||||
textStyle: { fontFamily: FONT },
|
||||
formatter: (params) => {
|
||||
let result = `<b>${params[0].axisValue}</b><br/>`;
|
||||
params.forEach(p => {
|
||||
const value = Math.abs(p.value);
|
||||
result += `${p.marker}${p.seriesName}: ${value.toFixed(2)} 亿元<br/>`;
|
||||
});
|
||||
return result;
|
||||
},
|
||||
},
|
||||
grid: {
|
||||
left: 100,
|
||||
right: 100,
|
||||
top: 145,
|
||||
bottom: 90,
|
||||
containLabel: false,
|
||||
},
|
||||
xAxis: {
|
||||
type: 'value',
|
||||
position: 'top',
|
||||
splitLine: {
|
||||
show: true,
|
||||
lineStyle: { color: '#e8e8e8', width: 1 }
|
||||
},
|
||||
axisLine: { show: false },
|
||||
axisTick: { show: false },
|
||||
axisLabel: {
|
||||
formatter: (value) => Math.abs(value),
|
||||
fontFamily: FONT,
|
||||
fontSize: 12,
|
||||
color: '#8c8c8c',
|
||||
},
|
||||
},
|
||||
yAxis: {
|
||||
type: 'category',
|
||||
data: regions,
|
||||
axisLine: { show: false },
|
||||
axisTick: { show: false },
|
||||
axisLabel: {
|
||||
fontFamily: FONT,
|
||||
fontSize: 13,
|
||||
color: '#333',
|
||||
fontWeight: '500',
|
||||
},
|
||||
inverse: false,
|
||||
},
|
||||
series: channels.map((channel, idx) => ({
|
||||
name: channel,
|
||||
type: 'bar',
|
||||
stack: channel === '线上渠道' ? 'left' : 'right',
|
||||
data: seriesData[idx],
|
||||
itemStyle: { color: channelColors[channel] },
|
||||
label: {
|
||||
show: true,
|
||||
position: channel === '线上渠道' ? 'left' : 'right',
|
||||
formatter: (params) => {
|
||||
const value = Math.abs(params.value);
|
||||
return value > 0 ? value.toFixed(1) : '';
|
||||
},
|
||||
fontFamily: FONT,
|
||||
fontSize: 11,
|
||||
color: '#333',
|
||||
},
|
||||
barWidth: 22,
|
||||
})),
|
||||
graphic: [
|
||||
...watermarks(width, height),
|
||||
footnote('数据来源:泡泡玛特财报 | @思考的Joey'),
|
||||
],
|
||||
};
|
||||
}
|
||||
@@ -111,7 +111,7 @@ export function regionChannelYoyTimeseriesChart(rows, { width, height }) {
|
||||
}],
|
||||
graphic: [
|
||||
...watermarks(width, height),
|
||||
footnote('数据来源:泡泡玛特财报;早期海外为合并口径 | @思考的Joey'),
|
||||
footnote('数据来源:泡泡玛特财报;早期海外为合并口径 | @Joey知行记'),
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
@@ -105,7 +105,7 @@ export function regionShareChart(rows, { width, height, view = 'all' }) {
|
||||
})),
|
||||
graphic: [
|
||||
...watermarks(width, height),
|
||||
footnote('数据来源:泡泡玛特财报;中国含港澳台\n2024H1为按2025年中报披露的分地区同比还原;H2为单半年 | @思考的Joey'),
|
||||
footnote('数据来源:泡泡玛特财报;中国含港澳台\n2024H1为按2025年中报披露的分地区同比还原;H2为单半年 | @Joey知行记'),
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
@@ -120,7 +120,7 @@ export function regionChannelYoyChart(rows, { width, height }) {
|
||||
})),
|
||||
graphic: [
|
||||
...watermarks(width, height),
|
||||
footnote('数据来源:泡泡玛特财报(2026H1 vs 2025H1同比)| @思考的Joey'),
|
||||
footnote('数据来源:泡泡玛特财报(2026H1 vs 2025H1同比)| @Joey知行记'),
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
@@ -4,11 +4,13 @@ import {
|
||||
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 },
|
||||
{ key: 'china', name: '中国(含港澳台)', color: '#3F7FBE', lineColor: PALETTE.navy },
|
||||
// 亚太青、美洲金、深棕在色板无对应深色,按柱色加深或取邻近深色
|
||||
{ key: 'apac', name: '亚太', color: '#36A28F', lineColor: '#1F6E5F' },
|
||||
{ key: 'americas', name: '美洲', color: '#E4A349', lineColor: PALETTE.orange },
|
||||
{ key: 'europe_other', name: '欧洲及其他', color: PALETTE.brown, lineColor: '#4A3117' },
|
||||
];
|
||||
|
||||
function yoyByPeriod(rows, key) {
|
||||
@@ -37,7 +39,7 @@ export function retailStoreRevenueChart(rows, { width, height, view = 'all' }) {
|
||||
title: {
|
||||
...titleStyle,
|
||||
text: `泡泡玛特各地区零售店单店收入(${financialViewLabel(view)})`,
|
||||
subtext: '柱状为单店收入;同色虚线为单店收入同比;H2为下半年单期',
|
||||
subtext: '柱状为单店收入;深色虚线为对应柱的单店收入同比;H2为下半年单期',
|
||||
},
|
||||
legend: standardLegend(legendItems, { itemGap: 18 }),
|
||||
grid: { left: 78, right: 42, top: LAYOUT.gridTop, bottom: 90 },
|
||||
@@ -93,18 +95,18 @@ export function retailStoreRevenueChart(rows, { width, height, view = 'all' }) {
|
||||
...lineSeries({
|
||||
name: `${region.name}同比`,
|
||||
data: region.data,
|
||||
color: region.color,
|
||||
color: region.lineColor,
|
||||
width: 2,
|
||||
formatter: (point) => `${point.value.toFixed(0)}%`,
|
||||
}),
|
||||
yAxisIndex: 1,
|
||||
lineStyle: { color: region.color, width: 2, type: 'dashed' },
|
||||
lineStyle: { color: region.lineColor, width: 2, type: 'dashed' },
|
||||
z: 10,
|
||||
})),
|
||||
],
|
||||
graphic: [
|
||||
...watermarks(width, height),
|
||||
footnote('数据来源:泡泡玛特历年公告;收入仅含“零售店”渠道;H2收入=全年−H1\n2024年海外各区域缺少期初店数,相关单店收入留空 | @思考的Joey'),
|
||||
footnote('数据来源:泡泡玛特历年公告;收入仅含“零售店”渠道;H2收入=全年−H1\n2024年海外各区域缺少期初店数,相关单店收入留空 | @Joey知行记'),
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
@@ -4,9 +4,11 @@ import {
|
||||
financialViewLabel, barSeries, lineSeries, footnote, watermarks, LAYOUT,
|
||||
} from '../theme.js';
|
||||
|
||||
// 同比虚线用柱色的同色系深色变体:压在柱体和彼此之间都能区分,色相仍与柱对应
|
||||
const REGIONS = [
|
||||
{ key: 'china', name: '中国(含港澳台)', color: '#71AD81' },
|
||||
{ key: 'overseas', name: '海外', color: PALETTE.brown },
|
||||
{ key: 'china', name: '中国(含港澳台)', color: '#71AD81', lineColor: PALETTE.green },
|
||||
// 色板没有深棕,按 brown 加深
|
||||
{ key: 'overseas', name: '海外', color: PALETTE.brown, lineColor: '#4A3117' },
|
||||
];
|
||||
|
||||
function yoyByPeriod(rows, key) {
|
||||
@@ -35,7 +37,7 @@ export function robotStoreRevenueChart(rows, { width, height, view = 'all' }) {
|
||||
title: {
|
||||
...titleStyle,
|
||||
text: `泡泡玛特机器人商店单店收入(${financialViewLabel(view)})`,
|
||||
subtext: '柱状为单店收入;同色虚线为单店收入同比;H2为下半年单期',
|
||||
subtext: '柱状为单店收入;深色虚线为对应柱的单店收入同比;H2为下半年单期',
|
||||
},
|
||||
legend: standardLegend(legendItems, { itemGap: 18 }),
|
||||
grid: { left: 78, right: 42, top: LAYOUT.gridTop, bottom: 90 },
|
||||
@@ -91,18 +93,18 @@ export function robotStoreRevenueChart(rows, { width, height, view = 'all' }) {
|
||||
...lineSeries({
|
||||
name: `${region.name}同比`,
|
||||
data: region.data,
|
||||
color: region.color,
|
||||
color: region.lineColor,
|
||||
width: 2,
|
||||
formatter: (point) => `${point.value.toFixed(0)}%`,
|
||||
}),
|
||||
yAxisIndex: 1,
|
||||
lineStyle: { color: region.color, width: 2, type: 'dashed' },
|
||||
lineStyle: { color: region.lineColor, width: 2, type: 'dashed' },
|
||||
z: 10,
|
||||
})),
|
||||
],
|
||||
graphic: [
|
||||
...watermarks(width, height),
|
||||
footnote('数据来源:泡泡玛特历年公告;海外收入=亚太+美洲+欧洲及其他的机器人商店收入;H2收入=全年−H1\n2024H1海外期末机器人商店数未披露,相关单店收入留空 | @思考的Joey'),
|
||||
footnote('数据来源:泡泡玛特历年公告;海外收入=亚太+美洲+欧洲及其他的机器人商店收入;H2收入=全年−H1\n2024H1海外期末机器人商店数未披露,相关单店收入留空 | @Joey知行记'),
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
@@ -133,7 +133,7 @@ export function sankeyChart(rows, { width, height }) {
|
||||
],
|
||||
graphic: [
|
||||
...watermarks(width, height),
|
||||
footnote('数据来源:泡泡玛特财报 | @思考的Joey'),
|
||||
footnote('数据来源:泡泡玛特财报 | @Joey知行记'),
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
@@ -128,7 +128,7 @@ export function storesRetailChart(rows, { width, height, view = 'all' }) {
|
||||
],
|
||||
graphic: [
|
||||
...watermarks(width, height),
|
||||
footnote('数据来源:泡泡玛特历年公告;门店数为各期末时点数\n2023年及以前海外合计含港澳台;2024年起港澳台单列,海外=亚太+美洲+欧洲 | @思考的Joey'),
|
||||
footnote('数据来源:泡泡玛特历年公告;门店数为各期末时点数\n2023年及以前海外合计含港澳台;2024年起港澳台单列,海外=亚太+美洲+欧洲 | @Joey知行记'),
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user