feat: 泡泡玛特增加年度、半年数据区分
This commit is contained in:
+33
-5
@@ -123,6 +123,9 @@ button {
|
||||
.content { min-width: 0; }
|
||||
|
||||
.tabs {
|
||||
position: sticky;
|
||||
top: 20px;
|
||||
z-index: 5;
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 6px;
|
||||
@@ -142,7 +145,7 @@ button {
|
||||
background: var(--surface);
|
||||
border-radius: 10px;
|
||||
box-shadow: 0 1px 3px rgba(0, 0, 0, .06);
|
||||
scroll-margin-top: 20px;
|
||||
scroll-margin-top: 84px;
|
||||
}
|
||||
|
||||
.card-bar {
|
||||
@@ -198,14 +201,39 @@ button {
|
||||
}
|
||||
|
||||
@media (max-width: 880px) {
|
||||
.app-layout { display: block; padding: 16px 12px 40px; }
|
||||
.sidebar { position: static; margin-bottom: 12px; }
|
||||
.company-sidebar { padding: 7px; }
|
||||
.app-layout { display: block; padding: 0 12px 40px; }
|
||||
.sidebar { margin-bottom: 12px; }
|
||||
.company-sidebar {
|
||||
position: sticky;
|
||||
top: 0;
|
||||
z-index: 7;
|
||||
margin: 0 -12px;
|
||||
padding: 7px 12px;
|
||||
border-width: 0 0 1px;
|
||||
border-radius: 0;
|
||||
background: rgba(245, 244, 241, .96);
|
||||
backdrop-filter: blur(10px);
|
||||
}
|
||||
.company-sidebar .sidebar-title { display: none; }
|
||||
.company-list { display: flex; overflow-x: auto; }
|
||||
.company-button { flex: 0 0 auto; width: auto; }
|
||||
.tabs { overflow-x: auto; flex-wrap: nowrap; }
|
||||
.tabs {
|
||||
top: 50px;
|
||||
z-index: 6;
|
||||
overflow-x: auto;
|
||||
overflow-y: hidden;
|
||||
flex-wrap: nowrap;
|
||||
margin: 0 -12px 18px;
|
||||
padding: 5px 12px;
|
||||
border-width: 0 0 1px;
|
||||
border-radius: 0;
|
||||
background: rgba(245, 244, 241, .96);
|
||||
backdrop-filter: blur(10px);
|
||||
scrollbar-width: none;
|
||||
}
|
||||
.tabs::-webkit-scrollbar { display: none; }
|
||||
.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; }
|
||||
.chart { min-width: 560px; }
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
period,total_assets,cash_like,receivables,investment,operating_assets
|
||||
2020H1,17.89,8.22,0.41,0.46,8.8
|
||||
2020H2,69.71,56.8,0.78,0.67,11.45
|
||||
2021H1,76.41,57.67,1.35,1.78,15.6
|
||||
2020H1,17.89,8.22,0.41,0.46,8.80
|
||||
2020H2,69.71,56.80,0.78,0.67,11.45
|
||||
2021H1,76.41,57.67,1.35,1.78,15.60
|
||||
2021H2,83.24,52.65,1.71,4.11,24.77
|
||||
2022H1,85.9,51.63,1.32,5.53,27.42
|
||||
2022H2,85.8,50.42,1.94,5.55,27.89
|
||||
2023H1,90.65,55.4,2.09,5.75,27.42
|
||||
2022H1,85.90,51.63,1.32,5.53,27.42
|
||||
2022H2,85.80,50.42,1.94,5.55,27.89
|
||||
2023H1,90.65,55.40,2.09,5.75,27.42
|
||||
2023H2,99.69,59.63,3.21,5.87,30.97
|
||||
2024H1,110.17,70.1,2.64,5.5,31.93
|
||||
2024H2,148.71,96.2,4.78,5.6,42.13
|
||||
2025H1,213.57,137.66,9.72,5.4,60.79
|
||||
2024H1,110.17,70.10,2.64,5.50,31.93
|
||||
2024H2,148.71,96.20,4.78,5.60,42.13
|
||||
2025H1,213.57,137.66,9.72,5.40,60.79
|
||||
2025H2,321.01,172.25,9.21,4.95,132.04
|
||||
|
||||
|
@@ -0,0 +1,7 @@
|
||||
period,total_assets,cash_like,receivables,investment,operating_assets
|
||||
2020FY,69.71,56.80,0.78,0.67,11.45
|
||||
2021FY,83.24,52.65,1.71,4.11,24.77
|
||||
2022FY,85.80,50.42,1.94,5.55,27.89
|
||||
2023FY,99.69,59.63,3.21,5.87,30.97
|
||||
2024FY,148.71,96.20,4.78,5.60,42.13
|
||||
2025FY,321.01,172.25,9.21,4.95,132.04
|
||||
|
@@ -0,0 +1,13 @@
|
||||
period,total_assets,cash_like,receivables,investment,operating_assets
|
||||
2020H1,17.89,8.22,0.41,0.46,8.80
|
||||
2020H2,69.71,56.80,0.78,0.67,11.45
|
||||
2021H1,76.41,57.67,1.35,1.78,15.60
|
||||
2021H2,83.24,52.65,1.71,4.11,24.77
|
||||
2022H1,85.90,51.63,1.32,5.53,27.42
|
||||
2022H2,85.80,50.42,1.94,5.55,27.89
|
||||
2023H1,90.65,55.40,2.09,5.75,27.42
|
||||
2023H2,99.69,59.63,3.21,5.87,30.97
|
||||
2024H1,110.17,70.10,2.64,5.50,31.93
|
||||
2024H2,148.71,96.20,4.78,5.60,42.13
|
||||
2025H1,213.57,137.66,9.72,5.40,60.79
|
||||
2025H2,321.01,172.25,9.21,4.95,132.04
|
||||
|
@@ -1,7 +1,19 @@
|
||||
period,operating_cash_flow,net_profit,cash_conversion
|
||||
2020H1,1.22,1.41,86.5
|
||||
2020H2,5.81,3.82,152.1
|
||||
2020FY,7.03,5.23,134.4
|
||||
2021H1,4.42,3.59,123.1
|
||||
2021H2,3.37,4.96,68.1
|
||||
2021FY,7.79,8.55,91.2
|
||||
2022H1,3.74,3.33,112.2
|
||||
2022H2,5.17,1.43,362.2
|
||||
2022FY,8.91,4.76,187.3
|
||||
2023H1,10.92,4.77,228.9
|
||||
2023H2,8.98,6.12,146.9
|
||||
2023FY,19.91,10.89,182.8
|
||||
2024H1,19.05,9.64,197.6
|
||||
2024H2,30.49,23.44,130.1
|
||||
2024FY,49.54,33.08,149.7
|
||||
2025H1,59.77,46.82,127.7
|
||||
2025H2,48.88,83.30,58.7
|
||||
2025FY,108.65,130.12,83.5
|
||||
|
||||
|
@@ -0,0 +1,7 @@
|
||||
period,operating_cash_flow,net_profit,cash_conversion
|
||||
2020FY,7.03,5.23,134.4
|
||||
2021FY,7.79,8.55,91.2
|
||||
2022FY,8.91,4.76,187.3
|
||||
2023FY,19.91,10.89,182.8
|
||||
2024FY,49.54,33.08,149.7
|
||||
2025FY,108.65,130.12,83.5
|
||||
|
@@ -0,0 +1,13 @@
|
||||
period,operating_cash_flow,net_profit,cash_conversion
|
||||
2020H1,1.22,1.41,86.5
|
||||
2020H2,5.81,3.82,152.1
|
||||
2021H1,4.42,3.59,123.1
|
||||
2021H2,3.37,4.96,68.1
|
||||
2022H1,3.74,3.33,112.2
|
||||
2022H2,5.17,1.43,362.2
|
||||
2023H1,10.92,4.77,228.9
|
||||
2023H2,8.98,6.12,146.9
|
||||
2024H1,19.05,9.64,197.6
|
||||
2024H2,30.49,23.44,130.1
|
||||
2025H1,59.77,46.82,127.7
|
||||
2025H2,48.88,83.30,58.7
|
||||
|
@@ -1,7 +1,19 @@
|
||||
period,operating_cash_flow,investing_cash_flow,financing_cash_flow,net_cash_increase
|
||||
2020H1,1.22,-0.48,4.24,4.98
|
||||
2020H2,5.81,-1.69,44.47,48.59
|
||||
2020FY,7.03,-2.17,48.70,53.57
|
||||
2021H1,4.42,-44.98,-1.21,-41.78
|
||||
2021H2,3.37,38.50,-3.15,38.73
|
||||
2021FY,7.79,-6.48,-4.36,-3.05
|
||||
2022H1,3.74,-42.10,-4.81,-43.17
|
||||
2022H2,5.17,-4.89,-6.97,-6.68
|
||||
2022FY,8.91,-46.98,-11.77,-49.85
|
||||
2023H1,10.92,0.82,-5.58,6.16
|
||||
2023H2,8.98,1.52,-2.83,7.67
|
||||
2023FY,19.91,2.34,-8.42,13.83
|
||||
2024H1,19.05,3.34,-7.16,15.23
|
||||
2024H2,30.49,-3.25,-2.43,24.82
|
||||
2024FY,49.54,0.09,-9.59,40.05
|
||||
2025H1,59.77,12.34,-14.05,58.07
|
||||
2025H2,48.88,-22.23,-3.62,23.02
|
||||
2025FY,108.65,-9.89,-17.67,81.09
|
||||
|
||||
|
@@ -0,0 +1,7 @@
|
||||
period,operating_cash_flow,investing_cash_flow,financing_cash_flow,net_cash_increase
|
||||
2020FY,7.03,-2.17,48.70,53.57
|
||||
2021FY,7.79,-6.48,-4.36,-3.05
|
||||
2022FY,8.91,-46.98,-11.77,-49.85
|
||||
2023FY,19.91,2.34,-8.42,13.83
|
||||
2024FY,49.54,0.09,-9.59,40.05
|
||||
2025FY,108.65,-9.89,-17.67,81.09
|
||||
|
@@ -0,0 +1,13 @@
|
||||
period,operating_cash_flow,investing_cash_flow,financing_cash_flow,net_cash_increase
|
||||
2020H1,1.22,-0.48,4.24,4.98
|
||||
2020H2,5.81,-1.69,44.47,48.59
|
||||
2021H1,4.42,-44.98,-1.21,-41.78
|
||||
2021H2,3.37,38.50,-3.15,38.73
|
||||
2022H1,3.74,-42.10,-4.81,-43.17
|
||||
2022H2,5.17,-4.89,-6.97,-6.68
|
||||
2023H1,10.92,0.82,-5.58,6.16
|
||||
2023H2,8.98,1.52,-2.83,7.67
|
||||
2024H1,19.05,3.34,-7.16,15.23
|
||||
2024H2,30.49,-3.25,-2.43,24.82
|
||||
2025H1,59.77,12.34,-14.05,58.07
|
||||
2025H2,48.88,-22.23,-3.62,23.02
|
||||
|
@@ -1,6 +1,7 @@
|
||||
period,plush,figure,mega,other
|
||||
品类,毛绒,手办,MEGA,衍生品及其他
|
||||
2024H1,4.46105,26.56781,5.8613,8.68815
|
||||
2024H2,23.85973,42.79306,10.9765,7.16989
|
||||
2025H1,61.39236,51.75875,10.07147,15.54018
|
||||
2025H2,125.68838,68.46694,9.08935,29.19309
|
||||
2024H1,4.46,26.57,5.86,8.69
|
||||
2024H2,23.86,42.79,10.98,7.17
|
||||
2024FY,28.32,69.36,16.84,15.86
|
||||
2025H1,61.39,51.76,10.07,15.54
|
||||
2025H2,125.69,68.47,9.09,29.19
|
||||
2025FY,187.08,120.23,19.16,44.73
|
||||
|
||||
|
@@ -0,0 +1,3 @@
|
||||
period,plush,figure,mega,other
|
||||
2024FY,28.32,69.36,16.84,15.86
|
||||
2025FY,187.08,120.23,19.16,44.73
|
||||
|
@@ -0,0 +1,5 @@
|
||||
period,plush,figure,mega,other
|
||||
2024H1,4.46,26.57,5.86,8.69
|
||||
2024H2,23.86,42.79,10.98,7.17
|
||||
2025H1,61.39,51.76,10.07,15.54
|
||||
2025H2,125.69,68.47,9.09,29.19
|
||||
|
@@ -1,14 +1,20 @@
|
||||
period,retail,online,robot_store,wholesale,total_revenue,revenue_yoy
|
||||
2020H1,3.13,3.34,1.05,0.64,8.18,50.5
|
||||
2020H2,6.89,6.18,2.23,1.44,16.96,48.7
|
||||
2020FY,10.02,9.52,3.29,2.07,25.13,49.3
|
||||
2021H1,6.75,6.78,2.28,1.91,17.73,116.8
|
||||
2021H2,9.98,12.01,2.42,2.53,27.18,60.3
|
||||
2021FY,16.73,18.79,4.70,4.45,44.91,78.7
|
||||
2022H1,9.26,10.13,2.08,2.12,23.59,33.1
|
||||
2022H2,9.00,9.07,1.86,2.65,22.59,-16.9
|
||||
2022FY,18.27,19.20,3.94,4.77,46.17,2.8
|
||||
2023H1,13.44,8.74,2.96,2.99,28.14,19.3
|
||||
2023H2,17.18,9.92,3.10,4.68,34.87,54.4
|
||||
2023FY,30.62,18.66,6.06,7.67,63.01,36.5
|
||||
2024H1,23.65,13.43,3.68,4.82,45.58,62.0
|
||||
2024H2,44.01,28.10,4.63,8.06,84.80,143.2
|
||||
2024FY,67.66,41.53,8.31,12.88,130.38,106.9
|
||||
2025H1,69.08,54.98,8.21,6.50,138.76,204.4
|
||||
2025H2,103.46,109.36,8.69,10.93,232.44,174.1
|
||||
2025FY,172.54,164.34,16.89,17.42,371.20,184.7
|
||||
2026H1,91.12,62.12,9.51,8.98,171.73,23.8
|
||||
|
||||
|
@@ -0,0 +1,7 @@
|
||||
period,retail,online,robot_store,wholesale,total_revenue,revenue_yoy
|
||||
2020FY,10.02,9.52,3.29,2.07,25.13,49.3
|
||||
2021FY,16.73,18.79,4.70,4.45,44.91,78.7
|
||||
2022FY,18.27,19.20,3.94,4.77,46.17,2.8
|
||||
2023FY,30.62,18.66,6.06,7.67,63.01,36.5
|
||||
2024FY,67.66,41.53,8.31,12.88,130.38,106.9
|
||||
2025FY,172.54,164.34,16.89,17.42,371.20,184.7
|
||||
|
@@ -0,0 +1,14 @@
|
||||
period,retail,online,robot_store,wholesale,total_revenue,revenue_yoy
|
||||
2020H1,3.13,3.34,1.05,0.64,8.18,50.5
|
||||
2020H2,6.89,6.18,2.23,1.44,16.96,48.7
|
||||
2021H1,6.75,6.78,2.28,1.91,17.73,116.8
|
||||
2021H2,9.98,12.01,2.42,2.53,27.18,60.3
|
||||
2022H1,9.26,10.13,2.08,2.12,23.59,33.1
|
||||
2022H2,9.00,9.07,1.86,2.65,22.59,-16.9
|
||||
2023H1,13.44,8.74,2.96,2.99,28.14,19.3
|
||||
2023H2,17.18,9.92,3.10,4.68,34.87,54.4
|
||||
2024H1,23.65,13.43,3.68,4.82,45.58,62.0
|
||||
2024H2,44.01,28.10,4.63,8.06,84.80,143.2
|
||||
2025H1,69.08,54.98,8.21,6.50,138.76,204.4
|
||||
2025H2,103.46,109.36,8.69,10.93,232.44,174.1
|
||||
2026H1,91.12,62.12,9.51,8.98,171.73,23.8
|
||||
|
@@ -1,14 +1,20 @@
|
||||
period,core_margin
|
||||
2020H1,22.6
|
||||
2020H2,27.2
|
||||
2020H2,29.4
|
||||
2020FY,27.2
|
||||
2021H1,25.8
|
||||
2021H2,24.4
|
||||
2021H2,23.5
|
||||
2021FY,24.4
|
||||
2022H1,15.0
|
||||
2022H2,10.8
|
||||
2022H2,6.3
|
||||
2022FY,10.8
|
||||
2023H1,17.4
|
||||
2023H2,18.3
|
||||
2023H2,19.0
|
||||
2023FY,18.3
|
||||
2024H1,24.8
|
||||
2024H2,31.5
|
||||
2024H2,35.1
|
||||
2024FY,31.5
|
||||
2025H1,41.8
|
||||
2025H2,45.6
|
||||
2025H2,47.8
|
||||
2025FY,45.6
|
||||
2026H1,41.4
|
||||
|
||||
|
@@ -0,0 +1,7 @@
|
||||
period,core_margin
|
||||
2020FY,27.2
|
||||
2021FY,24.4
|
||||
2022FY,10.8
|
||||
2023FY,18.3
|
||||
2024FY,31.5
|
||||
2025FY,45.6
|
||||
|
@@ -0,0 +1,14 @@
|
||||
period,core_margin
|
||||
2020H1,22.6
|
||||
2020H2,29.4
|
||||
2021H1,25.8
|
||||
2021H2,23.5
|
||||
2022H1,15.0
|
||||
2022H2,6.3
|
||||
2023H1,17.4
|
||||
2023H2,19.0
|
||||
2024H1,24.8
|
||||
2024H2,35.1
|
||||
2025H1,41.8
|
||||
2025H2,47.8
|
||||
2026H1,41.4
|
||||
|
@@ -1,14 +1,20 @@
|
||||
period,core_profit,yoy
|
||||
2020H1,1.9,25.0
|
||||
2020H2,6.8,17.1
|
||||
2021H1,4.6,147.0
|
||||
2021H2,11.0,60.1
|
||||
2022H1,3.5,-22.5
|
||||
2022H2,5.0,-54.5
|
||||
2023H1,4.9,38.1
|
||||
2023H2,11.5,131.3
|
||||
2024H1,11.3,131.5
|
||||
2024H2,41.1,256.9
|
||||
2025H1,58.0,412.2
|
||||
2025H2,169.1,311.4
|
||||
2026H1,71.1,22.6
|
||||
2020H1,1.85,25.2
|
||||
2020H2,4.99,14.4
|
||||
2020FY,6.84,17.1
|
||||
2021H1,4.58,147.3
|
||||
2021H2,6.37,27.7
|
||||
2021FY,10.95,60.1
|
||||
2022H1,3.54,-22.5
|
||||
2022H2,1.43,-77.6
|
||||
2022FY,4.98,-54.6
|
||||
2023H1,4.89,37.9
|
||||
2023H2,6.63,363.8
|
||||
2023FY,11.52,131.6
|
||||
2024H1,11.31,131.5
|
||||
2024H2,29.79,349.1
|
||||
2024FY,41.10,256.8
|
||||
2025H1,57.98,412.4
|
||||
2025H2,111.14,273.1
|
||||
2025FY,169.12,311.5
|
||||
2026H1,71.08,22.6
|
||||
|
||||
|
@@ -0,0 +1,7 @@
|
||||
period,core_profit,yoy
|
||||
2020FY,6.84,17.1
|
||||
2021FY,10.95,60.1
|
||||
2022FY,4.98,-54.6
|
||||
2023FY,11.52,131.6
|
||||
2024FY,41.10,256.8
|
||||
2025FY,169.12,311.5
|
||||
|
@@ -0,0 +1,14 @@
|
||||
period,core_profit,yoy
|
||||
2020H1,1.85,25.2
|
||||
2020H2,4.99,14.4
|
||||
2021H1,4.58,147.3
|
||||
2021H2,6.37,27.7
|
||||
2022H1,3.54,-22.5
|
||||
2022H2,1.43,-77.6
|
||||
2023H1,4.89,37.9
|
||||
2023H2,6.63,363.8
|
||||
2024H1,11.31,131.5
|
||||
2024H2,29.79,349.1
|
||||
2025H1,57.98,412.4
|
||||
2025H2,111.14,273.1
|
||||
2026H1,71.08,22.6
|
||||
|
@@ -1,7 +1,20 @@
|
||||
period,sales_expense,admin_expense,financial_expense,four_expense_rate
|
||||
2020H1,2.23,1.25,0.04,43.1
|
||||
2020H2,4.07,1.55,0.05,33.4
|
||||
2020FY,6.30,2.80,0.09,36.6
|
||||
2021H1,4.20,2.40,-0.06,36.9
|
||||
2021H2,6.86,3.18,-0.02,36.9
|
||||
2021FY,11.06,5.58,-0.07,36.9
|
||||
2022H1,6.93,3.23,-0.07,42.8
|
||||
2022H2,7.78,3.64,-0.22,49.5
|
||||
2022FY,14.71,6.86,-0.29,46.1
|
||||
2023H1,8.78,3.31,-0.63,40.7
|
||||
2023H2,11.26,3.76,-0.88,40.5
|
||||
2023FY,20.05,7.07,-1.52,40.6
|
||||
2024H1,13.53,4.34,-0.84,37.4
|
||||
2024H2,22.97,5.13,-0.80,32.2
|
||||
2024FY,36.50,9.47,-1.63,34.0
|
||||
2025H1,31.93,7.70,-0.66,28.1
|
||||
2025H2,48.90,10.00,-0.11,25.3
|
||||
2025FY,80.82,17.70,-0.76,26.3
|
||||
2026H1,39.31,9.27,-0.11,28.2
|
||||
|
||||
|
@@ -0,0 +1,7 @@
|
||||
period,sales_expense,admin_expense,financial_expense,four_expense_rate
|
||||
2020FY,6.30,2.80,0.09,36.6
|
||||
2021FY,11.06,5.58,-0.07,36.9
|
||||
2022FY,14.71,6.86,-0.29,46.1
|
||||
2023FY,20.05,7.07,-1.52,40.6
|
||||
2024FY,36.50,9.47,-1.63,34.0
|
||||
2025FY,80.82,17.70,-0.76,26.3
|
||||
|
@@ -0,0 +1,14 @@
|
||||
period,sales_expense,admin_expense,financial_expense,four_expense_rate
|
||||
2020H1,2.23,1.25,0.04,43.1
|
||||
2020H2,4.07,1.55,0.05,33.4
|
||||
2021H1,4.20,2.40,-0.06,36.9
|
||||
2021H2,6.86,3.18,-0.02,36.9
|
||||
2022H1,6.93,3.23,-0.07,42.8
|
||||
2022H2,7.78,3.64,-0.22,49.5
|
||||
2023H1,8.78,3.31,-0.63,40.7
|
||||
2023H2,11.26,3.76,-0.88,40.5
|
||||
2024H1,13.53,4.34,-0.84,37.4
|
||||
2024H2,22.97,5.13,-0.80,32.2
|
||||
2025H1,31.93,7.70,-0.66,28.1
|
||||
2025H2,48.90,10.00,-0.11,25.3
|
||||
2026H1,39.31,9.27,-0.11,28.2
|
||||
|
+17
-11
@@ -1,14 +1,20 @@
|
||||
period,non_gaap_net_profit,gross_profit,revenue,cost
|
||||
2020H1,23.7,61.0,50.6,33.3
|
||||
2020H2,16.2,46.2,49.3,55.0
|
||||
2021H1,154.6,109.6,116.7,131.0
|
||||
2021H2,69.5,73.1,78.7,88.5
|
||||
2022H1,-13.6,22.6,33.1,50.6
|
||||
2022H2,-42.7,-3.8,2.8,13.3
|
||||
2020H1,24.4,61.4,50.5,33.6
|
||||
2020H2,33.1,39.6,48.7,67.0
|
||||
2020FY,30.9,46.2,49.3,55.0
|
||||
2021H1,208.0,109.4,116.8,130.5
|
||||
2021H2,26.1,54.8,60.3,69.5
|
||||
2021FY,69.6,73.1,78.7,88.4
|
||||
2022H1,-13.5,22.7,33.1,50.8
|
||||
2022H2,-65.2,-21.8,-16.9,-9.5
|
||||
2022FY,-42.7,-3.8,2.8,13.3
|
||||
2023H1,42.3,23.9,19.3,12.9
|
||||
2023H2,107.5,45.5,36.5,24.1
|
||||
2024H1,90.3,71.9,62.0,47.0
|
||||
2024H2,185.7,125.4,106.9,77.7
|
||||
2025H1,362.7,234.4,204.4,151.1
|
||||
2025H2,284.5,207.4,184.7,139.1
|
||||
2023H2,232.1,68.7,54.4,35.6
|
||||
2023FY,107.6,45.6,36.5,24.2
|
||||
2024H1,90.1,71.9,62.0,46.9
|
||||
2024H2,264.1,167.3,143.2,103.7
|
||||
2024FY,185.9,125.4,106.9,77.7
|
||||
2025H1,362.8,234.4,204.4,151.1
|
||||
2025H2,251.0,193.7,174.1,131.9
|
||||
2025FY,284.5,207.4,184.7,139.1
|
||||
2026H1,9.5,22.6,23.8,26.5
|
||||
|
||||
|
@@ -0,0 +1,7 @@
|
||||
period,non_gaap_net_profit,gross_profit,revenue,cost
|
||||
2020FY,30.9,46.2,49.3,55.0
|
||||
2021FY,69.6,73.1,78.7,88.4
|
||||
2022FY,-42.7,-3.8,2.8,13.3
|
||||
2023FY,107.6,45.6,36.5,24.2
|
||||
2024FY,185.9,125.4,106.9,77.7
|
||||
2025FY,284.5,207.4,184.7,139.1
|
||||
|
@@ -0,0 +1,14 @@
|
||||
period,non_gaap_net_profit,gross_profit,revenue,cost
|
||||
2020H1,24.4,61.4,50.5,33.6
|
||||
2020H2,33.1,39.6,48.7,67.0
|
||||
2021H1,208.0,109.4,116.8,130.5
|
||||
2021H2,26.1,54.8,60.3,69.5
|
||||
2022H1,-13.5,22.7,33.1,50.8
|
||||
2022H2,-65.2,-21.8,-16.9,-9.5
|
||||
2023H1,42.3,23.9,19.3,12.9
|
||||
2023H2,232.1,68.7,54.4,35.6
|
||||
2024H1,90.1,71.9,62.0,46.9
|
||||
2024H2,264.1,167.3,143.2,103.7
|
||||
2025H1,362.8,234.4,204.4,151.1
|
||||
2025H2,251.0,193.7,174.1,131.9
|
||||
2026H1,9.5,22.6,23.8,26.5
|
||||
|
@@ -1,9 +1,9 @@
|
||||
ip,2021H1,2021H2,2022H1,2022H2,2023H1,2023H2,2024H1,2024H2,2025H1,2025H2
|
||||
THE MONSTERS,1.45789,1.58642,1.34887,1.28115,1.59829,2.08042,6.26806,24.13858,48.14017,93.47047
|
||||
MOLLY,2.039,5.01198,4.04347,3.97808,4.11433,6.08872,7.8216,13.11012,13.57248,15.39836
|
||||
SKULLPANDA,1.83042,4.12214,4.61766,3.89845,5.26383,4.98405,5.74562,7.337,12.20504,23.19038
|
||||
CRYBABY,0,0,0,0,0.26603,0.44546,3.49382,8.15503,12.1817,17.10836
|
||||
DIMOO,2.04993,3.61742,2.98442,2.79506,3.62032,3.75634,3.77838,5.31723,11.05135,16.7156
|
||||
HIRONO-小野,0,0,0.37538,1.0323,1.09352,2.41849,2.45247,4.81278,7.2842,10.06815
|
||||
HACIPUPU,0,0,0,0.42797,0.69171,0.82587,0.94991,1.29297,3.32122,5.15899
|
||||
星星人,0,0,0,0,0,0,0,0,3.89363,16.66217
|
||||
ip,2021H1,2021H2,2021FY,2022H1,2022H2,2022FY,2023H1,2023H2,2023FY,2024H1,2024H2,2024FY,2025H1,2025H2,2025FY
|
||||
THE MONSTERS,1.45789,1.58642,3.04431,1.34887,1.28115,2.63002,1.59829,2.08042,3.67871,6.26806,24.13858,30.406640000000003,48.14017,93.47047,141.61064
|
||||
MOLLY,2.039,5.01198,7.050980000000001,4.04347,3.97808,8.02155,4.11433,6.08872,10.203050000000001,7.8216,13.11012,20.93172,13.57248,15.39836,28.970840000000003
|
||||
SKULLPANDA,1.83042,4.12214,5.95256,4.61766,3.89845,8.51611,5.26383,4.98405,10.247879999999999,5.74562,7.337,13.082619999999999,12.20504,23.19038,35.39542
|
||||
CRYBABY,0,0,0,0,0,0,0.26603,0.44546,0.71149,3.49382,8.15503,11.64885,12.1817,17.10836,29.29006
|
||||
DIMOO,2.04993,3.61742,5.66735,2.98442,2.79506,5.7794799999999995,3.62032,3.75634,7.376659999999999,3.77838,5.31723,9.09561,11.05135,16.7156,27.766949999999998
|
||||
HIRONO-小野,0,0,0,0.37538,1.0323,1.40768,1.09352,2.41849,3.51201,2.45247,4.81278,7.26525,7.2842,10.06815,17.35235
|
||||
HACIPUPU,0,0,0,0,0.42797,0.42797,0.69171,0.82587,1.5175800000000002,0.94991,1.29297,2.24288,3.32122,5.15899,8.48021
|
||||
星星人,0,0,0,0,0,0,0,0,0,0,0,0,3.89363,16.66217,20.555799999999998
|
||||
|
||||
|
@@ -0,0 +1,9 @@
|
||||
ip,2021FY,2022FY,2023FY,2024FY,2025FY
|
||||
THE MONSTERS,3.04431,2.63002,3.67871,30.406640000000003,141.61064
|
||||
MOLLY,7.050980000000001,8.02155,10.203050000000001,20.93172,28.970840000000003
|
||||
SKULLPANDA,5.95256,8.51611,10.247879999999999,13.082619999999999,35.39542
|
||||
CRYBABY,0,0,0.71149,11.64885,29.29006
|
||||
DIMOO,5.66735,5.7794799999999995,7.376659999999999,9.09561,27.766949999999998
|
||||
HIRONO-小野,0,1.40768,3.51201,7.26525,17.35235
|
||||
HACIPUPU,0,0.42797,1.5175800000000002,2.24288,8.48021
|
||||
星星人,0,0,0,0,20.555799999999998
|
||||
|
@@ -1,12 +1,17 @@
|
||||
period,revenue,yoy
|
||||
2021H1,16.27,
|
||||
2021H2,25.59,
|
||||
2021FY,41.86,
|
||||
2022H1,22.24,36.7
|
||||
2022H2,21.30,-16.8
|
||||
2022FY,43.54,4.0
|
||||
2023H1,26.54,19.3
|
||||
2023H2,32.79,53.9
|
||||
2023FY,59.33,36.3
|
||||
2024H1,39.31,48.1
|
||||
2024H2,60.66,85.0
|
||||
2024FY,99.97,68.5
|
||||
2025H1,90.62,130.5
|
||||
2025H2,138.97,129.1
|
||||
2025FY,229.59,129.7
|
||||
2026H1,127.19,40.4
|
||||
|
||||
|
@@ -0,0 +1,6 @@
|
||||
period,revenue,yoy
|
||||
2021FY,41.86,
|
||||
2022FY,43.54,4.0
|
||||
2023FY,59.33,36.3
|
||||
2024FY,99.97,68.5
|
||||
2025FY,229.59,129.7
|
||||
|
@@ -0,0 +1,12 @@
|
||||
period,revenue,yoy
|
||||
2021H1,16.27,
|
||||
2021H2,25.59,
|
||||
2022H1,22.24,36.7
|
||||
2022H2,21.30,-16.8
|
||||
2023H1,26.54,19.3
|
||||
2023H2,32.79,53.9
|
||||
2024H1,39.31,48.1
|
||||
2024H2,60.66,85.0
|
||||
2025H1,90.62,130.5
|
||||
2025H2,138.97,129.1
|
||||
2026H1,127.19,40.4
|
||||
|
@@ -0,0 +1,9 @@
|
||||
ip,2021H1,2021H2,2022H1,2022H2,2023H1,2023H2,2024H1,2024H2,2025H1,2025H2
|
||||
THE MONSTERS,1.45789,1.58642,1.34887,1.28115,1.59829,2.08042,6.26806,24.13858,48.14017,93.47047
|
||||
MOLLY,2.039,5.01198,4.04347,3.97808,4.11433,6.08872,7.8216,13.11012,13.57248,15.39836
|
||||
SKULLPANDA,1.83042,4.12214,4.61766,3.89845,5.26383,4.98405,5.74562,7.337,12.20504,23.19038
|
||||
CRYBABY,0,0,0,0,0.26603,0.44546,3.49382,8.15503,12.1817,17.10836
|
||||
DIMOO,2.04993,3.61742,2.98442,2.79506,3.62032,3.75634,3.77838,5.31723,11.05135,16.7156
|
||||
HIRONO-小野,0,0,0.37538,1.0323,1.09352,2.41849,2.45247,4.81278,7.2842,10.06815
|
||||
HACIPUPU,0,0,0,0.42797,0.69171,0.82587,0.94991,1.29297,3.32122,5.15899
|
||||
星星人,0,0,0,0,0,0,0,0,3.89363,16.66217
|
||||
|
@@ -1,12 +1,12 @@
|
||||
ip,2021H1,2021H2,2022H1,2022H2,2023H1,2023H2,2024H1,2024H2,2025H1,2025H2,2026H1
|
||||
THE MONSTERS,1.46,1.59,1.35,1.28,1.60,2.08,6.27,24.14,48.14,93.47,44.54
|
||||
星星人,0.00,0.00,0.00,0.00,0.00,0.00,0.00,0.00,3.89,16.66,26.50
|
||||
授权IP(非独家),2.88,3.33,3.01,2.50,4.22,6.14,6.95,9.06,15.25,18.57,17.71
|
||||
CRYBABY,0.00,0.00,0.00,0.00,0.27,0.45,3.49,8.16,12.18,17.11,16.33
|
||||
DIMOO,2.05,3.62,2.98,2.80,3.62,3.76,3.78,5.32,11.05,16.72,16.19
|
||||
SKULLPANDA,1.83,4.12,4.62,3.90,5.26,4.98,5.75,7.34,12.21,23.19,15.51
|
||||
其他艺术家IP,1.43,2.47,4.37,3.00,4.54,5.44,6.37,7.72,10.64,14.00,11.74
|
||||
HIRONO-小野,0.00,0.00,0.38,1.03,1.09,2.42,2.45,4.81,7.28,10.07,10.09
|
||||
MOLLY,2.04,5.01,4.04,3.98,4.11,6.09,7.82,13.11,13.57,15.40,9.01
|
||||
其他IP(含已停单列),4.14,4.51,0.81,1.86,1.15,1.37,0.95,3.74,3.32,5.16,2.98
|
||||
外采及其他,1.90,2.53,2.03,2.24,2.28,2.15,1.75,1.41,1.23,2.09,1.12
|
||||
ip,2021H1,2021H2,2021FY,2022H1,2022H2,2022FY,2023H1,2023H2,2023FY,2024H1,2024H2,2024FY,2025H1,2025H2,2025FY,2026H1
|
||||
THE MONSTERS,1.46,1.59,3.05,1.35,1.28,2.63,1.6,2.08,3.68,6.27,24.14,30.41,48.14,93.47,141.61,44.54
|
||||
星星人,0,0,0,0,0,0,0,0,0,0,0,0,3.89,16.66,20.55,26.5
|
||||
授权IP(非独家),2.88,3.33,6.21,3.01,2.5,5.51,4.22,6.14,10.36,6.95,9.06,16.01,15.25,18.57,33.82,17.71
|
||||
CRYBABY,0,0,0,0,0,0,0.27,0.45,0.72,3.49,8.16,11.65,12.18,17.11,29.29,16.33
|
||||
DIMOO,2.05,3.62,5.67,2.98,2.8,5.779999999999999,3.62,3.76,7.38,3.78,5.32,9.1,11.05,16.72,27.77,16.19
|
||||
SKULLPANDA,1.83,4.12,5.95,4.62,3.9,8.52,5.26,4.98,10.24,5.75,7.34,13.09,12.21,23.19,35.400000000000006,15.51
|
||||
其他艺术家IP,1.43,2.47,3.9000000000000004,4.37,3,7.37,4.54,5.44,9.98,6.37,7.72,14.09,10.64,14,24.64,11.74
|
||||
HIRONO-小野,0,0,0,0.38,1.03,1.4100000000000001,1.09,2.42,3.51,2.45,4.81,7.26,7.28,10.07,17.35,10.09
|
||||
MOLLY,2.04,5.01,7.05,4.04,3.98,8.02,4.11,6.09,10.2,7.82,13.11,20.93,13.57,15.4,28.97,9.01
|
||||
其他IP(含已停单列),4.14,4.51,8.649999999999999,0.81,1.86,2.67,1.15,1.37,2.52,0.95,3.74,4.69,3.32,5.16,8.48,2.98
|
||||
外采及其他,1.9,2.53,4.43,2.03,2.24,4.27,2.28,2.15,4.43,1.75,1.41,3.16,1.23,2.09,3.32,1.12
|
||||
|
||||
|
@@ -0,0 +1,12 @@
|
||||
ip,2021FY,2022FY,2023FY,2024FY,2025FY
|
||||
THE MONSTERS,3.05,2.63,3.68,30.41,141.61
|
||||
星星人,0,0,0,0,20.55
|
||||
授权IP(非独家),6.21,5.51,10.36,16.01,33.82
|
||||
CRYBABY,0,0,0.72,11.65,29.29
|
||||
DIMOO,5.67,5.779999999999999,7.38,9.1,27.77
|
||||
SKULLPANDA,5.95,8.52,10.24,13.09,35.400000000000006
|
||||
其他艺术家IP,3.9000000000000004,7.37,9.98,14.09,24.64
|
||||
HIRONO-小野,0,1.4100000000000001,3.51,7.26,17.35
|
||||
MOLLY,7.05,8.02,10.2,20.93,28.97
|
||||
其他IP(含已停单列),8.649999999999999,2.67,2.52,4.69,8.48
|
||||
外采及其他,4.43,4.27,4.43,3.16,3.32
|
||||
|
@@ -0,0 +1,12 @@
|
||||
ip,2021H1,2021H2,2022H1,2022H2,2023H1,2023H2,2024H1,2024H2,2025H1,2025H2,2026H1
|
||||
THE MONSTERS,1.46,1.59,1.35,1.28,1.6,2.08,6.27,24.14,48.14,93.47,44.54
|
||||
星星人,0,0,0,0,0,0,0,0,3.89,16.66,26.5
|
||||
授权IP(非独家),2.88,3.33,3.01,2.5,4.22,6.14,6.95,9.06,15.25,18.57,17.71
|
||||
CRYBABY,0,0,0,0,0.27,0.45,3.49,8.16,12.18,17.11,16.33
|
||||
DIMOO,2.05,3.62,2.98,2.8,3.62,3.76,3.78,5.32,11.05,16.72,16.19
|
||||
SKULLPANDA,1.83,4.12,4.62,3.9,5.26,4.98,5.75,7.34,12.21,23.19,15.51
|
||||
其他艺术家IP,1.43,2.47,4.37,3,4.54,5.44,6.37,7.72,10.64,14,11.74
|
||||
HIRONO-小野,0,0,0.38,1.03,1.09,2.42,2.45,4.81,7.28,10.07,10.09
|
||||
MOLLY,2.04,5.01,4.04,3.98,4.11,6.09,7.82,13.11,13.57,15.4,9.01
|
||||
其他IP(含已停单列),4.14,4.51,0.81,1.86,1.15,1.37,0.95,3.74,3.32,5.16,2.98
|
||||
外采及其他,1.9,2.53,2.03,2.24,2.28,2.15,1.75,1.41,1.23,2.09,1.12
|
||||
|
@@ -1,14 +1,14 @@
|
||||
period,cash,total_liabilities,net_working_capital
|
||||
2020H1,8.22,5.47,8.66
|
||||
2020FY,56.80,8.40,55.91
|
||||
2020H2,56.80,8.40,55.91
|
||||
2021H1,57.67,13.26,55.92
|
||||
2021FY,52.65,15.04,56.65
|
||||
2021H2,52.65,15.04,56.65
|
||||
2022H1,51.63,14.76,58.16
|
||||
2022FY,50.42,16.15,54.85
|
||||
2022H2,50.42,16.15,54.85
|
||||
2023H1,55.40,17.59,57.33
|
||||
2023FY,59.63,21.88,59.50
|
||||
2023H2,59.63,21.88,59.50
|
||||
2024H1,70.10,25.65,66.47
|
||||
2024FY,96.20,39.86,88.66
|
||||
2024H2,96.20,39.86,88.66
|
||||
2025H1,137.66,69.27,119.27
|
||||
2025FY,172.25,94.49,177.46
|
||||
2025H2,172.25,94.49,177.46
|
||||
2026H1,139.62,77.40,174.20
|
||||
|
||||
|
@@ -0,0 +1,7 @@
|
||||
period,cash,total_liabilities,net_working_capital
|
||||
2020FY,56.80,8.40,55.91
|
||||
2021FY,52.65,15.04,56.65
|
||||
2022FY,50.42,16.15,54.85
|
||||
2023FY,59.63,21.88,59.50
|
||||
2024FY,96.20,39.86,88.66
|
||||
2025FY,172.25,94.49,177.46
|
||||
|
@@ -0,0 +1,14 @@
|
||||
period,cash,total_liabilities,net_working_capital
|
||||
2020H1,8.22,5.47,8.66
|
||||
2020H2,56.80,8.40,55.91
|
||||
2021H1,57.67,13.26,55.92
|
||||
2021H2,52.65,15.04,56.65
|
||||
2022H1,51.63,14.76,58.16
|
||||
2022H2,50.42,16.15,54.85
|
||||
2023H1,55.40,17.59,57.33
|
||||
2023H2,59.63,21.88,59.50
|
||||
2024H1,70.10,25.65,66.47
|
||||
2024H2,96.20,39.86,88.66
|
||||
2025H1,137.66,69.27,119.27
|
||||
2025H2,172.25,94.49,177.46
|
||||
2026H1,139.62,77.40,174.20
|
||||
|
+17
-11
@@ -1,14 +1,20 @@
|
||||
period,gross_margin,sales_expense_rate,admin_expense_rate,non_gaap_net_margin
|
||||
2020H1,65.23,27.27,15.33,17.24
|
||||
2020H2,63.42,25.07,11.14,23.52
|
||||
2021H1,63.02,23.68,13.52,24.53
|
||||
2021H2,61.43,24.63,12.41,22.31
|
||||
2022H1,58.1,29.39,13.68,15.94
|
||||
2022H2,57.49,31.85,14.86,12.43
|
||||
2023H1,60.36,31.21,11.77,19.01
|
||||
2023H2,61.32,31.82,11.23,18.9
|
||||
2020H1,65.23,27.27,15.33,17.28
|
||||
2020H2,62.55,24.00,9.12,26.49
|
||||
2020FY,63.42,25.07,11.14,23.49
|
||||
2021H1,63.02,23.68,13.52,24.55
|
||||
2021H2,60.39,25.25,11.69,20.84
|
||||
2021FY,61.43,24.63,12.41,22.30
|
||||
2022H1,58.10,29.39,13.68,15.95
|
||||
2022H2,56.86,34.43,16.10,8.73
|
||||
2022FY,57.49,31.85,14.86,12.42
|
||||
2023H1,60.36,31.21,11.77,19.03
|
||||
2023H2,62.10,32.30,10.78,18.79
|
||||
2023FY,61.32,31.82,11.23,18.89
|
||||
2024H1,64.05,29.69,9.53,22.33
|
||||
2024H2,66.79,28.0,7.26,26.1
|
||||
2024H2,68.26,27.09,6.05,28.13
|
||||
2024FY,66.79,28.00,7.26,26.10
|
||||
2025H1,70.34,23.01,5.55,33.94
|
||||
2025H2,72.1,21.77,4.77,35.25
|
||||
2026H1,69.68,22.89,5.4,30.02
|
||||
2025H2,73.15,21.04,4.30,36.03
|
||||
2025FY,72.10,21.77,4.77,35.25
|
||||
2026H1,69.68,22.89,5.40,30.02
|
||||
|
||||
|
@@ -0,0 +1,7 @@
|
||||
period,gross_margin,sales_expense_rate,admin_expense_rate,non_gaap_net_margin
|
||||
2020FY,63.42,25.07,11.14,23.49
|
||||
2021FY,61.43,24.63,12.41,22.30
|
||||
2022FY,57.49,31.85,14.86,12.42
|
||||
2023FY,61.32,31.82,11.23,18.89
|
||||
2024FY,66.79,28.00,7.26,26.10
|
||||
2025FY,72.10,21.77,4.77,35.25
|
||||
|
@@ -0,0 +1,14 @@
|
||||
period,gross_margin,sales_expense_rate,admin_expense_rate,non_gaap_net_margin
|
||||
2020H1,65.23,27.27,15.33,17.28
|
||||
2020H2,62.55,24.00,9.12,26.49
|
||||
2021H1,63.02,23.68,13.52,24.55
|
||||
2021H2,60.39,25.25,11.69,20.84
|
||||
2022H1,58.10,29.39,13.68,15.95
|
||||
2022H2,56.86,34.43,16.10,8.73
|
||||
2023H1,60.36,31.21,11.77,19.03
|
||||
2023H2,62.10,32.30,10.78,18.79
|
||||
2024H1,64.05,29.69,9.53,22.33
|
||||
2024H2,68.26,27.09,6.05,28.13
|
||||
2025H1,70.34,23.01,5.55,33.94
|
||||
2025H2,73.15,21.04,4.30,36.03
|
||||
2026H1,69.68,22.89,5.40,30.02
|
||||
|
+18
-16
@@ -1,18 +1,20 @@
|
||||
period,revenue,gross_profit,gross_margin,net_profit
|
||||
2026H1,171.73,119.66,69.67999999999999,50.38
|
||||
2025H2,371.2,267.65,72.1,127.76
|
||||
2025H1,138.76,97.61,70.34,45.74
|
||||
2024H2,130.38,87.08,66.79,31.25
|
||||
2024H1,45.58,29.19,64.05,9.21
|
||||
2023H2,63.01,38.64,61.31999999999999,10.82
|
||||
2023H1,28.14,16.98,60.36,4.77
|
||||
2022H2,46.17,26.55,57.489999999999995,4.76
|
||||
2022H1,23.59,13.7,58.099999999999994,3.33
|
||||
2021H2,44.91,27.59,61.42999999999999,8.54
|
||||
2021H1,17.73,11.17,63.019999999999996,3.59
|
||||
2020H2,25.13,15.94,63.42,5.24
|
||||
2020H1,8.18,5.33,65.23,1.41
|
||||
2019H2,16.83,10.9,64.77000000000001,4.51
|
||||
2019H1,5.43,3.31,60.84,1.14
|
||||
2018H2,5.15,2.98,57.92,1
|
||||
2017H2,1.58,0.75,47.61,0.02
|
||||
2020H2,16.96,10.61,62.55,3.82
|
||||
2020FY,25.13,15.94,63.42,5.23
|
||||
2021H1,17.73,11.17,63.02,3.59
|
||||
2021H2,27.18,16.42,60.39,4.96
|
||||
2021FY,44.91,27.59,61.43,8.55
|
||||
2022H1,23.59,13.70,58.10,3.33
|
||||
2022H2,22.59,12.84,56.86,1.43
|
||||
2022FY,46.17,26.55,57.49,4.76
|
||||
2023H1,28.14,16.98,60.36,4.77
|
||||
2023H2,34.87,21.66,62.10,6.12
|
||||
2023FY,63.01,38.64,61.32,10.89
|
||||
2024H1,45.58,29.19,64.05,9.64
|
||||
2024H2,84.80,57.89,68.26,23.44
|
||||
2024FY,130.38,87.08,66.79,33.08
|
||||
2025H1,138.76,97.61,70.34,46.82
|
||||
2025H2,232.44,170.04,73.15,83.30
|
||||
2025FY,371.20,267.65,72.10,130.12
|
||||
2026H1,171.73,119.66,69.68,51.00
|
||||
|
||||
|
@@ -0,0 +1,7 @@
|
||||
period,revenue,gross_profit,gross_margin,net_profit
|
||||
2020FY,25.13,15.94,63.42,5.23
|
||||
2021FY,44.91,27.59,61.43,8.55
|
||||
2022FY,46.17,26.55,57.49,4.76
|
||||
2023FY,63.01,38.64,61.32,10.89
|
||||
2024FY,130.38,87.08,66.79,33.08
|
||||
2025FY,371.20,267.65,72.10,130.12
|
||||
|
@@ -0,0 +1,14 @@
|
||||
period,revenue,gross_profit,gross_margin,net_profit
|
||||
2020H1,8.18,5.33,65.23,1.41
|
||||
2020H2,16.96,10.61,62.55,3.82
|
||||
2021H1,17.73,11.17,63.02,3.59
|
||||
2021H2,27.18,16.42,60.39,4.96
|
||||
2022H1,23.59,13.70,58.10,3.33
|
||||
2022H2,22.59,12.84,56.86,1.43
|
||||
2023H1,28.14,16.98,60.36,4.77
|
||||
2023H2,34.87,21.66,62.10,6.12
|
||||
2024H1,45.58,29.19,64.05,9.64
|
||||
2024H2,84.80,57.89,68.26,23.44
|
||||
2025H1,138.76,97.61,70.34,46.82
|
||||
2025H2,232.44,170.04,73.15,83.30
|
||||
2026H1,171.73,119.66,69.68,51.00
|
||||
|
+3
-1
@@ -147,7 +147,9 @@ js/main.js + js/ui.js ← 解析引用、渲染三栏、导出和
|
||||
}
|
||||
```
|
||||
|
||||
页面仅在声明多个视图时显示切换按钮,并将选择写入 URL query。当前泡泡玛特图表没有强制启用这一功能;基础能力已保留,等真实数据需要时再登记。
|
||||
页面仅在声明多个视图时显示切换按钮,并将选择写入 URL query;尚未启用多视图的图表维持单一 CSV,不显示控制器。
|
||||
|
||||
泡泡玛特已在 15 张连续图启用该能力:盈利能力、营收到利润的增速对比、核心利润及同比增速、核心利润率、利润率与费用率拆解、资产结构演变、主要 IP 收入趋势、各 IP 收入占比、除 THE MONSTERS 外 IP 收入及同比、品类收入结构、经营现金流与净利润、现金/负债/净营运资本、三大现金流与现金净增加、费用结构与费用率、渠道收入结构与增速。默认显示“全部”,并提供“年度 / 半年”;其原始披露没有可靠 Q1/Q3 数据,故不展示“季度”。其中 `FY` 为全年、`H1/H2` 为单独半年,流量类指标的 H2 在数据归一化阶段由 `FY − H1` 生成,比率与同比也在数据层重算;资产负债表类则为期末余额,H2 对应年末时点而非累计金额。
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { col } from '../csv.js';
|
||||
import {
|
||||
PALETTE, FONT, axisCommon, periodAxis, titleStyle, legendStyle,
|
||||
PALETTE, FONT, axisCommon, standalonePeriodAxis, titleStyle, legendStyle,
|
||||
financialViewLabel,
|
||||
lineSeries, footnote, watermarks, LAYOUT,
|
||||
} from '../theme.js';
|
||||
|
||||
@@ -8,15 +9,15 @@ import {
|
||||
* 泡泡玛特资产结构演变图
|
||||
* 堆叠柱状图展示资产配置,折线图展示总资产增长
|
||||
*/
|
||||
export function assetsChart(rows, { width, height }) {
|
||||
export function assetsChart(rows, { width, height, view = 'all' }) {
|
||||
const periods = col(rows, 'period');
|
||||
|
||||
return {
|
||||
backgroundColor: '#fff',
|
||||
title: {
|
||||
...titleStyle,
|
||||
text: '泡泡玛特资产结构演变(2020H1-2026H1)',
|
||||
subtext: '单位:亿元',
|
||||
text: `泡泡玛特资产结构演变(${financialViewLabel(view)})`,
|
||||
subtext: '单位:亿元;资产负债表为期末余额,H2 为年末时点而非全年累计',
|
||||
},
|
||||
legend: {
|
||||
...legendStyle,
|
||||
@@ -29,7 +30,7 @@ export function assetsChart(rows, { width, height }) {
|
||||
axisPointer: { type: 'shadow' },
|
||||
textStyle: { fontFamily: FONT },
|
||||
},
|
||||
xAxis: { ...periodAxis, data: periods },
|
||||
xAxis: { ...standalonePeriodAxis, data: periods },
|
||||
yAxis: [
|
||||
{
|
||||
...axisCommon,
|
||||
|
||||
@@ -1,19 +1,23 @@
|
||||
import { col } from '../csv.js';
|
||||
import {
|
||||
FINANCIAL_COLORS, PALETTE, FONT, axisCommon, periodAxis, titleStyle, legendStyle,
|
||||
FINANCIAL_COLORS, PALETTE, FONT, axisCommon, standalonePeriodAxis, titleStyle, legendStyle,
|
||||
financialViewDisclosure, financialViewLabel,
|
||||
lineSeries, footnote, watermarks, LAYOUT,
|
||||
} from '../theme.js';
|
||||
|
||||
/** 泡泡玛特经营现金流与净利润:观察利润的现金兑现能力。 */
|
||||
export function cashConversionChart(rows, { width, height }) {
|
||||
export function cashConversionChart(rows, { width, height, view = 'all' }) {
|
||||
const periods = col(rows, 'period');
|
||||
const conversionMax = Math.max(...col(rows, 'cash_conversion').filter(Number.isFinite));
|
||||
const conversionAxisMax = Math.max(200, Math.ceil((conversionMax * 1.1) / 100) * 100);
|
||||
const barWidth = view === 'all' ? 14 : view === 'halfyear' ? 24.96 : 31.2;
|
||||
|
||||
return {
|
||||
backgroundColor: '#fff',
|
||||
title: {
|
||||
...titleStyle,
|
||||
text: '泡泡玛特经营现金流与净利润(年度)',
|
||||
subtext: '现金转换率 = 经营活动现金流净额 / 净利润;单位:亿元',
|
||||
text: `泡泡玛特经营现金流与净利润(${financialViewLabel(view)})`,
|
||||
subtext: ['现金转换率 = 经营活动现金流净额 / 净利润;单位:亿元', financialViewDisclosure(view)].filter(Boolean).join('\n'),
|
||||
},
|
||||
legend: {
|
||||
...legendStyle,
|
||||
@@ -27,7 +31,7 @@ export function cashConversionChart(rows, { width, height }) {
|
||||
textStyle: { fontFamily: FONT },
|
||||
valueFormatter: (value) => `${value.toFixed(1)}`,
|
||||
},
|
||||
xAxis: { ...periodAxis, data: periods },
|
||||
xAxis: { ...standalonePeriodAxis, data: periods },
|
||||
yAxis: [
|
||||
{
|
||||
...axisCommon,
|
||||
@@ -48,8 +52,8 @@ export function cashConversionChart(rows, { width, height }) {
|
||||
nameRotate: -90,
|
||||
nameTextStyle: { color: PALETTE.grey, fontSize: 13, fontFamily: FONT },
|
||||
min: 0,
|
||||
max: 200,
|
||||
interval: 50,
|
||||
max: conversionAxisMax,
|
||||
interval: conversionAxisMax / 4,
|
||||
axisLabel: { ...axisCommon.axisLabel, formatter: '{value}%' },
|
||||
splitLine: { show: false },
|
||||
},
|
||||
@@ -59,7 +63,7 @@ export function cashConversionChart(rows, { width, height }) {
|
||||
name: '经营活动现金流净额',
|
||||
type: 'bar',
|
||||
data: col(rows, 'operating_cash_flow'),
|
||||
barWidth: 31.2,
|
||||
barWidth,
|
||||
itemStyle: { color: FINANCIAL_COLORS.operatingCashFlow },
|
||||
label: { show: true, position: 'top', color: PALETTE.ink, fontFamily: FONT, fontSize: 11, formatter: (p) => p.value.toFixed(1) },
|
||||
},
|
||||
@@ -67,7 +71,7 @@ export function cashConversionChart(rows, { width, height }) {
|
||||
name: '净利润',
|
||||
type: 'bar',
|
||||
data: col(rows, 'net_profit'),
|
||||
barWidth: 31.2,
|
||||
barWidth,
|
||||
itemStyle: { color: FINANCIAL_COLORS.netProfit },
|
||||
label: { show: true, position: 'top', color: PALETTE.ink, fontFamily: FONT, fontSize: 11, formatter: (p) => p.value.toFixed(1) },
|
||||
},
|
||||
@@ -85,7 +89,7 @@ export function cashConversionChart(rows, { width, height }) {
|
||||
],
|
||||
graphic: [
|
||||
...watermarks(width, height),
|
||||
footnote('数据来源:泡泡玛特合并现金流量表、利润表(年度口径)| @思考的Joey'),
|
||||
footnote('数据来源:泡泡玛特合并现金流量表、利润表;H2为单独半年 | @思考的Joey'),
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,12 +1,14 @@
|
||||
import { col } from '../csv.js';
|
||||
import {
|
||||
FINANCIAL_COLORS, PALETTE, FONT, axisCommon, periodAxis, titleStyle, standardLegend,
|
||||
FINANCIAL_COLORS, PALETTE, FONT, axisCommon, standalonePeriodAxis, titleStyle, standardLegend,
|
||||
financialViewDisclosure, financialViewLabel,
|
||||
lineDataWithSafeLabels,
|
||||
lineSeries, footnote, watermarks, LAYOUT,
|
||||
} from '../theme.js';
|
||||
|
||||
/** 泡泡玛特三大现金流:展示经营造血、投资投入与筹资变化。 */
|
||||
export function cashflowCompositionChart(rows, { width, height }) {
|
||||
export function cashflowCompositionChart(rows, { width, height, view = 'all' }) {
|
||||
const barWidth = view === 'all' ? 10 : view === 'halfyear' ? 18.2 : 26;
|
||||
const netCashIncrease = lineDataWithSafeLabels(rows, {
|
||||
lineKey: 'net_cash_increase',
|
||||
barKeys: ['operating_cash_flow', 'investing_cash_flow', 'financing_cash_flow'],
|
||||
@@ -16,8 +18,8 @@ export function cashflowCompositionChart(rows, { width, height }) {
|
||||
backgroundColor: '#fff',
|
||||
title: {
|
||||
...titleStyle,
|
||||
text: '泡泡玛特三大现金流与现金净增加(年度)',
|
||||
subtext: '单位:亿元;正值为流入,负值为流出',
|
||||
text: `泡泡玛特三大现金流与现金净增加(${financialViewLabel(view)})`,
|
||||
subtext: ['单位:亿元;正值为流入,负值为流出', financialViewDisclosure(view)].filter(Boolean).join('\n'),
|
||||
},
|
||||
// 不手工指定 icon,让 ECharts 按系列类型生成:柱为矩形、折线为短线 + 空心圆。
|
||||
legend: standardLegend(['经营活动现金流', '投资活动现金流', '筹资活动现金流', '现金净增加']),
|
||||
@@ -28,7 +30,7 @@ export function cashflowCompositionChart(rows, { width, height }) {
|
||||
textStyle: { fontFamily: FONT },
|
||||
valueFormatter: (value) => `${value.toFixed(1)} 亿元`,
|
||||
},
|
||||
xAxis: { ...periodAxis, data: col(rows, 'period') },
|
||||
xAxis: { ...standalonePeriodAxis, data: col(rows, 'period') },
|
||||
yAxis: {
|
||||
...axisCommon,
|
||||
type: 'value',
|
||||
@@ -38,9 +40,9 @@ export function cashflowCompositionChart(rows, { width, height }) {
|
||||
nameTextStyle: { color: PALETTE.grey, fontSize: 13, fontFamily: FONT },
|
||||
},
|
||||
series: [
|
||||
{ name: '经营活动现金流', type: 'bar', data: col(rows, 'operating_cash_flow'), barWidth: 26, itemStyle: { color: FINANCIAL_COLORS.operatingCashFlow } },
|
||||
{ name: '投资活动现金流', type: 'bar', data: col(rows, 'investing_cash_flow'), barWidth: 26, itemStyle: { color: FINANCIAL_COLORS.investingCashFlow } },
|
||||
{ name: '筹资活动现金流', type: 'bar', data: col(rows, 'financing_cash_flow'), barWidth: 26, itemStyle: { color: FINANCIAL_COLORS.financingCashFlow } },
|
||||
{ name: '经营活动现金流', type: 'bar', data: col(rows, 'operating_cash_flow'), barWidth, itemStyle: { color: FINANCIAL_COLORS.operatingCashFlow } },
|
||||
{ name: '投资活动现金流', type: 'bar', data: col(rows, 'investing_cash_flow'), barWidth, itemStyle: { color: FINANCIAL_COLORS.investingCashFlow } },
|
||||
{ name: '筹资活动现金流', type: 'bar', data: col(rows, 'financing_cash_flow'), barWidth, itemStyle: { color: FINANCIAL_COLORS.financingCashFlow } },
|
||||
{
|
||||
...lineSeries({
|
||||
name: '现金净增加',
|
||||
@@ -55,7 +57,7 @@ export function cashflowCompositionChart(rows, { width, height }) {
|
||||
],
|
||||
graphic: [
|
||||
...watermarks(width, height),
|
||||
footnote('数据来源:泡泡玛特合并现金流量表(年度口径)| @思考的Joey'),
|
||||
footnote('数据来源:泡泡玛特合并现金流量表;H2为单独半年 | @思考的Joey'),
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { col } from '../csv.js';
|
||||
import {
|
||||
PALETTE, FONT, axisCommon, categoryAxis, titleStyle, legendStyle,
|
||||
PALETTE, FONT, axisCommon, standalonePeriodAxis, titleStyle, legendStyle,
|
||||
financialViewDisclosure, financialViewLabel,
|
||||
footnote, watermarks, LAYOUT,
|
||||
} from '../theme.js';
|
||||
|
||||
@@ -8,7 +9,7 @@ import {
|
||||
* 泡泡玛特品类收入结构图
|
||||
* 堆叠柱状图展示各品类的收入占比和增长
|
||||
*/
|
||||
export function categoryChart(rows, { width, height }) {
|
||||
export function categoryChart(rows, { width, height, view = 'all' }) {
|
||||
// 过滤掉表头行("品类"那一行)
|
||||
const dataRows = rows.filter(r => r.period && r.period !== '品类');
|
||||
const periods = col(dataRows, 'period');
|
||||
@@ -17,8 +18,8 @@ export function categoryChart(rows, { width, height }) {
|
||||
backgroundColor: '#fff',
|
||||
title: {
|
||||
...titleStyle,
|
||||
text: '泡泡玛特品类收入结构(2024H1-2025H2)',
|
||||
subtext: '单位:亿元',
|
||||
text: `泡泡玛特品类收入结构(${financialViewLabel(view)})`,
|
||||
subtext: ['单位:亿元;H2为下半年单期', financialViewDisclosure(view)].filter(Boolean).join('\n'),
|
||||
},
|
||||
legend: {
|
||||
...legendStyle,
|
||||
@@ -41,14 +42,7 @@ export function categoryChart(rows, { width, height }) {
|
||||
return result;
|
||||
},
|
||||
},
|
||||
xAxis: {
|
||||
...categoryAxis,
|
||||
data: periods,
|
||||
axisLabel: {
|
||||
...categoryAxis.axisLabel,
|
||||
formatter: (value) => String(value).replace(/^20/, ''),
|
||||
},
|
||||
},
|
||||
xAxis: { ...standalonePeriodAxis, data: periods },
|
||||
yAxis: {
|
||||
...axisCommon,
|
||||
type: 'value',
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
import { col } from '../csv.js';
|
||||
import {
|
||||
CHANNEL_COLORS, PALETTE, FONT, axisCommon, periodAxis, titleStyle, legendStyle,
|
||||
CHANNEL_COLORS, PALETTE, FONT, axisCommon, standalonePeriodAxis, titleStyle, legendStyle,
|
||||
financialViewDisclosure, financialViewLabel,
|
||||
lineSeries, footnote, watermarks, LAYOUT,
|
||||
} from '../theme.js';
|
||||
|
||||
/** 泡泡玛特渠道收入结构与增速。 */
|
||||
export function channelRevenueChart(rows, { width, height }) {
|
||||
export function channelRevenueChart(rows, { width, height, view = 'all' }) {
|
||||
const channels = [
|
||||
{ key: 'retail', name: '零售店', color: CHANNEL_COLORS.retail },
|
||||
{ key: 'online', name: '线上', color: CHANNEL_COLORS.online },
|
||||
@@ -17,8 +18,8 @@ export function channelRevenueChart(rows, { width, height }) {
|
||||
backgroundColor: '#fff',
|
||||
title: {
|
||||
...titleStyle,
|
||||
text: '泡泡玛特渠道收入结构与增速',
|
||||
subtext: '渠道收入为堆叠柱;折线为营业收入同比增速',
|
||||
text: `泡泡玛特渠道收入结构与增速(${financialViewLabel(view)})`,
|
||||
subtext: ['渠道收入为堆叠柱;折线为营业收入同比增速', financialViewDisclosure(view)].filter(Boolean).join('\n'),
|
||||
},
|
||||
legend: {
|
||||
...legendStyle,
|
||||
@@ -32,7 +33,7 @@ export function channelRevenueChart(rows, { width, height }) {
|
||||
textStyle: { fontFamily: FONT },
|
||||
valueFormatter: (value) => value.toFixed(1),
|
||||
},
|
||||
xAxis: { ...periodAxis, data: col(rows, 'period') },
|
||||
xAxis: { ...standalonePeriodAxis, data: col(rows, 'period') },
|
||||
yAxis: [
|
||||
{
|
||||
...axisCommon,
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { col } from '../csv.js';
|
||||
import {
|
||||
PALETTE, FONT, axisCommon, periodAxis, titleStyle,
|
||||
PALETTE, FONT, axisCommon, standalonePeriodAxis, titleStyle,
|
||||
financialViewDisclosure, financialViewLabel,
|
||||
lineSeries, standardLegend, footnote, watermarks, LAYOUT,
|
||||
} from '../theme.js';
|
||||
|
||||
@@ -15,15 +16,15 @@ import {
|
||||
*/
|
||||
const LINE_COLOR = '#7B2D42';
|
||||
|
||||
export function ppmtCoreMarginChart(rows, { width, height }) {
|
||||
export function ppmtCoreMarginChart(rows, { width, height, view = 'all' }) {
|
||||
const pct = (p) => `${p.value.toFixed(1)}%`;
|
||||
|
||||
return {
|
||||
backgroundColor: '#fff',
|
||||
title: {
|
||||
...titleStyle,
|
||||
text: '泡泡玛特核心利润率(20H1-26H1)',
|
||||
subtext: '核心利润率 = 毛利率 − 销售费用率 − 管理费用率,剔除汇兑与投资等非主业扰动',
|
||||
text: `泡泡玛特核心利润率(${financialViewLabel(view)})`,
|
||||
subtext: ['核心利润率 = 毛利率 − 销售费用率 − 管理费用率,剔除汇兑与投资等非主业扰动', financialViewDisclosure(view)].filter(Boolean).join('\n'),
|
||||
},
|
||||
legend: standardLegend(['核心利润率'], { itemGap: 32 }),
|
||||
grid: { left: 78, right: 60, top: LAYOUT.gridTop, bottom: 90 },
|
||||
@@ -32,7 +33,7 @@ export function ppmtCoreMarginChart(rows, { width, height }) {
|
||||
valueFormatter: (v) => `${v.toFixed(1)}%`,
|
||||
textStyle: { fontFamily: FONT },
|
||||
},
|
||||
xAxis: { ...periodAxis, data: col(rows, 'period') },
|
||||
xAxis: { ...standalonePeriodAxis, data: col(rows, 'period') },
|
||||
yAxis: {
|
||||
...axisCommon,
|
||||
type: 'value',
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { col } from '../csv.js';
|
||||
import {
|
||||
PALETTE, FINANCIAL_COLORS, FONT, axisCommon, periodAxis, titleStyle, legendStyle,
|
||||
PALETTE, FINANCIAL_COLORS, FONT, axisCommon, standalonePeriodAxis, titleStyle, legendStyle,
|
||||
financialViewDisclosure, financialViewLabel,
|
||||
footnote, watermarks, LAYOUT,
|
||||
} from '../theme.js';
|
||||
|
||||
@@ -9,7 +10,7 @@ import {
|
||||
* 核心利润 = 毛利 - 销售费用 - 管理费用,反映主业盈利能力去除营销和管理成本后的净额。
|
||||
* 柱状图展示绝对值(亿元),折线图展示同比增速(%)。
|
||||
*/
|
||||
export function ppmtCoreProfitChart(rows, { width, height }) {
|
||||
export function ppmtCoreProfitChart(rows, { width, height, view = 'all' }) {
|
||||
const periods = col(rows, 'period');
|
||||
const core = col(rows, 'core_profit');
|
||||
const yoy = col(rows, 'yoy');
|
||||
@@ -18,8 +19,8 @@ export function ppmtCoreProfitChart(rows, { width, height }) {
|
||||
backgroundColor: '#fff',
|
||||
title: {
|
||||
...titleStyle,
|
||||
text: '泡泡玛特核心利润及同比增速(20H1-26H1)',
|
||||
subtext: '核心利润 = 毛利 - 销售费用 - 管理费用,单位:亿元',
|
||||
text: `泡泡玛特核心利润及同比增速(${financialViewLabel(view)})`,
|
||||
subtext: ['核心利润 = 毛利 - 销售费用 - 管理费用,单位:亿元', financialViewDisclosure(view)].filter(Boolean).join('\n'),
|
||||
},
|
||||
legend: {
|
||||
...legendStyle,
|
||||
@@ -49,7 +50,7 @@ export function ppmtCoreProfitChart(rows, { width, height }) {
|
||||
return result;
|
||||
},
|
||||
},
|
||||
xAxis: { ...periodAxis, data: periods },
|
||||
xAxis: { ...standalonePeriodAxis, data: periods },
|
||||
yAxis: [
|
||||
{
|
||||
// 左轴:核心利润(亿元)
|
||||
|
||||
@@ -1,17 +1,18 @@
|
||||
import { col } from '../csv.js';
|
||||
import {
|
||||
PALETTE, FONT, axisCommon, periodAxis, titleStyle, legendStyle,
|
||||
PALETTE, FONT, axisCommon, standalonePeriodAxis, titleStyle, legendStyle,
|
||||
financialViewDisclosure, financialViewLabel,
|
||||
lineSeries, footnote, watermarks, LAYOUT,
|
||||
} from '../theme.js';
|
||||
|
||||
/** 泡泡玛特期间费用结构:观察销售投入、管理成本与费用率的规模效应。 */
|
||||
export function expenseStructureChart(rows, { width, height }) {
|
||||
export function expenseStructureChart(rows, { width, height, view = 'all' }) {
|
||||
return {
|
||||
backgroundColor: '#fff',
|
||||
title: {
|
||||
...titleStyle,
|
||||
text: '泡泡玛特费用结构与费用率(年度)',
|
||||
subtext: '销售、管理、财务费用;折线为四项费用率',
|
||||
text: `泡泡玛特费用结构与费用率(${financialViewLabel(view)})`,
|
||||
subtext: ['销售、管理、财务费用;折线为四项费用率', financialViewDisclosure(view)].filter(Boolean).join('\n'),
|
||||
},
|
||||
legend: {
|
||||
...legendStyle,
|
||||
@@ -25,7 +26,7 @@ export function expenseStructureChart(rows, { width, height }) {
|
||||
textStyle: { fontFamily: FONT },
|
||||
valueFormatter: (value) => value.toFixed(1),
|
||||
},
|
||||
xAxis: { ...periodAxis, data: col(rows, 'period') },
|
||||
xAxis: { ...standalonePeriodAxis, data: col(rows, 'period') },
|
||||
yAxis: [
|
||||
{
|
||||
...axisCommon,
|
||||
@@ -68,7 +69,7 @@ export function expenseStructureChart(rows, { width, height }) {
|
||||
],
|
||||
graphic: [
|
||||
...watermarks(width, height),
|
||||
footnote('数据来源:泡泡玛特合并利润表(年度口径)| @思考的Joey'),
|
||||
footnote('数据来源:泡泡玛特合并利润表;H2为单独半年 | @思考的Joey'),
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { col } from '../csv.js';
|
||||
import {
|
||||
PALETTE, FONT, axisCommon, periodAxis, titleStyle, standardLegend,
|
||||
PALETTE, FONT, axisCommon, standalonePeriodAxis, titleStyle, standardLegend,
|
||||
financialViewDisclosure, financialViewLabel,
|
||||
lineSeries, footnote, watermarks, LAYOUT,
|
||||
} from '../theme.js';
|
||||
|
||||
@@ -43,15 +44,15 @@ function labeledData(rows, key) {
|
||||
});
|
||||
}
|
||||
|
||||
export function ppmtGrowthChart(rows, { width, height }) {
|
||||
export function ppmtGrowthChart(rows, { width, height, view = 'all' }) {
|
||||
const pct = (p) => `${p.value}%`;
|
||||
|
||||
return {
|
||||
backgroundColor: '#fff',
|
||||
title: {
|
||||
...titleStyle,
|
||||
text: '泡泡玛特营收到利润的增速对比(2020H1-2026H1)',
|
||||
subtext: '同比增速(%),净利增速显著高于营收增速体现利润弹性',
|
||||
text: `泡泡玛特营收到利润的增速对比(${financialViewLabel(view)})`,
|
||||
subtext: ['同比增速(%),净利增速显著高于营收增速体现利润弹性', financialViewDisclosure(view)].filter(Boolean).join('\n'),
|
||||
},
|
||||
legend: standardLegend(['非GAAP归母净利', '毛利润', '营业收入', '营业成本'], { itemGap: 34 }),
|
||||
grid: { left: 84, right: 56, top: LAYOUT.gridTop, bottom: 80 },
|
||||
@@ -60,7 +61,7 @@ export function ppmtGrowthChart(rows, { width, height }) {
|
||||
valueFormatter: (v) => `${v}%`,
|
||||
textStyle: { fontFamily: FONT },
|
||||
},
|
||||
xAxis: { ...periodAxis, data: col(rows, 'period') },
|
||||
xAxis: { ...standalonePeriodAxis, data: col(rows, 'period') },
|
||||
yAxis: {
|
||||
...axisCommon,
|
||||
type: 'value',
|
||||
@@ -105,7 +106,7 @@ export function ppmtGrowthChart(rows, { width, height }) {
|
||||
})),
|
||||
graphic: [
|
||||
...watermarks(width, height),
|
||||
footnote('数据来源:泡泡玛特财报,2020H1/H2非GAAP净利用归母净利替代 | @思考的Joey'),
|
||||
footnote('数据来源:泡泡玛特财报;2020H1/H2非GAAP净利用归母净利替代 | @思考的Joey'),
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { col } from '../csv.js';
|
||||
import {
|
||||
PALETTE, FONT, axisCommon, categoryAxis, titleStyle, legendStyle,
|
||||
financialViewDisclosure, financialViewLabel, shortStandalonePeriod,
|
||||
lineSeries, footnote, watermarks, LAYOUT,
|
||||
} from '../theme.js';
|
||||
|
||||
@@ -8,7 +9,7 @@ import {
|
||||
* 泡泡玛特主要IP收入趋势图
|
||||
* 多条折线展示各IP的收入变化
|
||||
*/
|
||||
export function ipChart(rows, { width, height }) {
|
||||
export function ipChart(rows, { width, height, view = 'all' }) {
|
||||
// 提取时期(从第一行的列名)
|
||||
const periods = Object.keys(rows[0]).filter(k => k !== 'ip');
|
||||
|
||||
@@ -42,8 +43,8 @@ export function ipChart(rows, { width, height }) {
|
||||
backgroundColor: '#fff',
|
||||
title: {
|
||||
...titleStyle,
|
||||
text: '泡泡玛特主要IP收入趋势(2021H1-2025H2)',
|
||||
subtext: '单位:亿元',
|
||||
text: `泡泡玛特主要IP收入趋势(${financialViewLabel(view)})`,
|
||||
subtext: ['单位:亿元;H2为单独半年', financialViewDisclosure(view)].filter(Boolean).join('\n'),
|
||||
},
|
||||
legend: {
|
||||
...legendStyle,
|
||||
@@ -62,7 +63,7 @@ export function ipChart(rows, { width, height }) {
|
||||
data: periods,
|
||||
axisLabel: {
|
||||
...categoryAxis.axisLabel,
|
||||
formatter: (value) => String(value).replace(/^20/, ''),
|
||||
formatter: shortStandalonePeriod,
|
||||
},
|
||||
},
|
||||
yAxis: {
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { col } from '../csv.js';
|
||||
import {
|
||||
PALETTE, FONT, axisCommon, categoryAxis, titleStyle, labelStyle,
|
||||
PALETTE, FONT, axisCommon, standalonePeriodAxis, titleStyle, labelStyle,
|
||||
financialViewDisclosure, financialViewLabel,
|
||||
barSeries, lineSeries, standardLegend, footnote, watermarks, LAYOUT,
|
||||
} from '../theme.js';
|
||||
|
||||
@@ -34,13 +35,13 @@ const BAR_COLOR = '#176B52'; // 墨翡翠
|
||||
const LINE_COLOR = '#7A3FE0'; // 鲜亮紫
|
||||
const LINE_ON_BAR = '#DCC9FF'; // 压在翡翠柱面上时用的浅紫(对比度约 6:1)
|
||||
|
||||
const Y_MAX = 160; // 左轴上限
|
||||
const YOY_MIN = -50; // 右轴下限
|
||||
const YOY_MAX = 150; // 右轴上限
|
||||
|
||||
export function ppmtIpExMonstersChart(rows, { width, height }) {
|
||||
export function ppmtIpExMonstersChart(rows, { width, height, view = 'all' }) {
|
||||
const periods = col(rows, 'period');
|
||||
const revenue = col(rows, 'revenue');
|
||||
const yMax = Math.max(...revenue) > 160 ? 300 : 160;
|
||||
// 前两期无同比基数,CSV 里留空;parseCSV 会给出 '',统一转成 null
|
||||
const yoy = col(rows, 'yoy').map((v) => (v === '' ? null : v));
|
||||
|
||||
@@ -50,7 +51,7 @@ export function ppmtIpExMonstersChart(rows, { width, height }) {
|
||||
const labelGap = (8 + 13) / plotH; // labelStyle 的 distance + 字高,换成占比
|
||||
const yoyData = yoy.map((v, i) => {
|
||||
if (v === null) return null;
|
||||
const barFrac = revenue[i] / Y_MAX;
|
||||
const barFrac = revenue[i] / yMax;
|
||||
const lineFrac = (v - YOY_MIN) / (YOY_MAX - YOY_MIN);
|
||||
// 标签整体(底边到顶边)都落在柱子高度以内 → 压在柱面上
|
||||
const onBar = lineFrac + labelGap < barFrac;
|
||||
@@ -61,8 +62,8 @@ export function ppmtIpExMonstersChart(rows, { width, height }) {
|
||||
backgroundColor: '#fff',
|
||||
title: {
|
||||
...titleStyle,
|
||||
text: '泡泡玛特除 THE MONSTERS 外的IP收入及同比(21H1-26H1)',
|
||||
subtext: 'IP收入合计 − THE MONSTERS,单位:亿元;H2 为下半年单期,非全年累计',
|
||||
text: `泡泡玛特除 THE MONSTERS 外的IP收入及同比(${financialViewLabel(view)})`,
|
||||
subtext: ['IP收入合计 − THE MONSTERS,单位:亿元;H2为下半年单期,非全年累计', financialViewDisclosure(view)].filter(Boolean).join('\n'),
|
||||
},
|
||||
legend: standardLegend(['除MONSTERS外收入', '同比增速'], { itemGap: 32 }),
|
||||
grid: { left: 78, right: 128, top: LAYOUT.gridTop, bottom: 90 },
|
||||
@@ -82,12 +83,7 @@ export function ppmtIpExMonstersChart(rows, { width, height }) {
|
||||
return out;
|
||||
},
|
||||
},
|
||||
xAxis: {
|
||||
...categoryAxis,
|
||||
data: periods,
|
||||
// 只去世纪前缀:这张表的 H2 是单半年,不能简写成「全年」
|
||||
axisLabel: { ...categoryAxis.axisLabel, formatter: (v) => v.replace(/^20/, '') },
|
||||
},
|
||||
xAxis: { ...standalonePeriodAxis, data: periods },
|
||||
yAxis: [
|
||||
{
|
||||
...axisCommon,
|
||||
@@ -97,9 +93,8 @@ export function ppmtIpExMonstersChart(rows, { width, height }) {
|
||||
nameGap: 50,
|
||||
nameTextStyle: { color: PALETTE.grey, fontSize: 13, fontFamily: FONT },
|
||||
min: 0,
|
||||
// 定死 160:留出顶部余量,最高柱(139.0)不会顶到绘图区上沿
|
||||
max: Y_MAX,
|
||||
interval: 20,
|
||||
max: yMax,
|
||||
interval: yMax === 160 ? 20 : 50,
|
||||
},
|
||||
{
|
||||
type: 'value',
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import {
|
||||
PALETTE, FONT, axisCommon, categoryAxis, titleStyle, LEGEND_ITEM_WIDTH,
|
||||
financialViewDisclosure, financialViewLabel, shortStandalonePeriod,
|
||||
barSeries, footnote, watermarks, LAYOUT,
|
||||
} from '../theme.js';
|
||||
|
||||
@@ -38,7 +39,7 @@ const COLORS = {
|
||||
const LEGEND_WIDTH = 160;
|
||||
const LEGEND_RIGHT = 20;
|
||||
|
||||
export function ppmtIpShareHistoryChart(rows, { width, height }) {
|
||||
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));
|
||||
@@ -49,8 +50,8 @@ export function ppmtIpShareHistoryChart(rows, { width, height }) {
|
||||
backgroundColor: '#fff',
|
||||
title: {
|
||||
...titleStyle,
|
||||
text: '泡泡玛特各IP半年收入占比(21H1-26H1)',
|
||||
subtext: '占当期IP收入合计的比重;H2 为下半年单期,非全年累计',
|
||||
text: `泡泡玛特各IP收入占比(${financialViewLabel(view)})`,
|
||||
subtext: ['占当期IP收入合计的比重;H2为下半年单期,非全年累计', financialViewDisclosure(view)].filter(Boolean).join('\n'),
|
||||
},
|
||||
legend: {
|
||||
orient: 'vertical',
|
||||
@@ -89,7 +90,7 @@ export function ppmtIpShareHistoryChart(rows, { width, height }) {
|
||||
...categoryAxis,
|
||||
data: periods,
|
||||
// 只去世纪前缀:这张表的 H2 是单半年,不能简写成「全年」
|
||||
axisLabel: { ...categoryAxis.axisLabel, formatter: (v) => v.replace(/^20/, '') },
|
||||
axisLabel: { ...categoryAxis.axisLabel, formatter: shortStandalonePeriod },
|
||||
},
|
||||
yAxis: {
|
||||
...axisCommon,
|
||||
|
||||
@@ -1,19 +1,20 @@
|
||||
import { col } from '../csv.js';
|
||||
import {
|
||||
PALETTE, FINANCIAL_COLORS, FONT, axisCommon, periodAxis, titleStyle, legendStyle,
|
||||
PALETTE, FINANCIAL_COLORS, FONT, axisCommon, standalonePeriodAxis, titleStyle, legendStyle,
|
||||
financialViewLabel,
|
||||
lineDataWithSafeLabels, lineSeries, barSeries, footnote, watermarks, LAYOUT,
|
||||
} from '../theme.js';
|
||||
|
||||
/** 泡泡玛特现金、负债与净营运资本:观察流动性与资金占用。 */
|
||||
export function liquidityWorkingCapitalChart(rows, { width, height }) {
|
||||
export function liquidityWorkingCapitalChart(rows, { width, height, view = 'all' }) {
|
||||
const periods = col(rows, 'period');
|
||||
|
||||
return {
|
||||
backgroundColor: '#fff',
|
||||
title: {
|
||||
...titleStyle,
|
||||
text: '泡泡玛特现金、负债与净营运资本',
|
||||
subtext: '净营运资本 = 流动资产 − 流动负债;单位:亿元',
|
||||
text: `泡泡玛特现金、负债与净营运资本(${financialViewLabel(view)})`,
|
||||
subtext: '净营运资本 = 流动资产 − 流动负债;单位:亿元;H2 为年末时点',
|
||||
},
|
||||
legend: {
|
||||
...legendStyle,
|
||||
@@ -27,7 +28,7 @@ export function liquidityWorkingCapitalChart(rows, { width, height }) {
|
||||
textStyle: { fontFamily: FONT },
|
||||
valueFormatter: (value) => `${value.toFixed(1)} 亿元`,
|
||||
},
|
||||
xAxis: { ...periodAxis, data: periods },
|
||||
xAxis: { ...standalonePeriodAxis, data: periods },
|
||||
yAxis: {
|
||||
...axisCommon,
|
||||
type: 'value',
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { col } from '../csv.js';
|
||||
import {
|
||||
PALETTE, FONT, axisCommon, periodAxis, titleStyle, standardLegend,
|
||||
PALETTE, FONT, axisCommon, standalonePeriodAxis, titleStyle, standardLegend,
|
||||
financialViewDisclosure, financialViewLabel,
|
||||
lineSeries, footnote, watermarks, LAYOUT,
|
||||
} from '../theme.js';
|
||||
|
||||
@@ -54,15 +55,15 @@ function labeledData(rows, key) {
|
||||
});
|
||||
}
|
||||
|
||||
export function ppmtMarginsChart(rows, { width, height }) {
|
||||
export function ppmtMarginsChart(rows, { width, height, view = 'all' }) {
|
||||
const pct = (p) => `${p.value}%`;
|
||||
|
||||
return {
|
||||
backgroundColor: '#fff',
|
||||
title: {
|
||||
...titleStyle,
|
||||
text: '泡泡玛特利润率与费用率拆解(2020H1-2026H1)',
|
||||
subtext: '毛利率抬升叠加费用率下降,共同推动净利率走高(H1为半年,H2为全年累计)',
|
||||
text: `泡泡玛特利润率与费用率拆解(${financialViewLabel(view)})`,
|
||||
subtext: ['毛利率抬升叠加费用率下降,共同推动净利率走高', financialViewDisclosure(view)].filter(Boolean).join('\n'),
|
||||
},
|
||||
legend: standardLegend(SERIES.map((series) => series.name)),
|
||||
grid: { left: 78, right: 56, top: LAYOUT.gridTop, bottom: 80 },
|
||||
@@ -71,7 +72,7 @@ export function ppmtMarginsChart(rows, { width, height }) {
|
||||
valueFormatter: (v) => `${v}%`,
|
||||
textStyle: { fontFamily: FONT },
|
||||
},
|
||||
xAxis: { ...periodAxis, data: col(rows, 'period') },
|
||||
xAxis: { ...standalonePeriodAxis, data: col(rows, 'period') },
|
||||
yAxis: {
|
||||
...axisCommon,
|
||||
type: 'value',
|
||||
|
||||
+12
-11
@@ -1,6 +1,7 @@
|
||||
import { col } from '../csv.js';
|
||||
import {
|
||||
PALETTE, FONT, axisCommon, periodAxis, titleStyle, legendStyle,
|
||||
PALETTE, FONT, axisCommon, standalonePeriodAxis, titleStyle, legendStyle,
|
||||
financialViewDisclosure, financialViewLabel,
|
||||
lineSeries, footnote, watermarks, LAYOUT,
|
||||
} from '../theme.js';
|
||||
|
||||
@@ -8,19 +9,19 @@ import {
|
||||
* 泡泡玛特盈利能力分析图
|
||||
* 柱状图展示收入和利润,折线图展示毛利率
|
||||
*/
|
||||
export function profitChart(rows, { width, height }) {
|
||||
const periods = col(rows, 'period').reverse(); // 按时间正序
|
||||
const revenue = col(rows, 'revenue').reverse();
|
||||
const grossProfit = col(rows, 'gross_profit').reverse();
|
||||
const netProfit = col(rows, 'net_profit').reverse();
|
||||
const grossMargin = col(rows, 'gross_margin').reverse();
|
||||
export function profitChart(rows, { width, height, view = 'all' }) {
|
||||
const periods = col(rows, 'period');
|
||||
const revenue = col(rows, 'revenue');
|
||||
const grossProfit = col(rows, 'gross_profit');
|
||||
const netProfit = col(rows, 'net_profit');
|
||||
const grossMargin = col(rows, 'gross_margin');
|
||||
|
||||
return {
|
||||
backgroundColor: '#fff',
|
||||
title: {
|
||||
...titleStyle,
|
||||
text: '泡泡玛特盈利能力分析(2022H2-2026H1)',
|
||||
subtext: '营收、毛利、净利润及毛利率变化',
|
||||
text: `泡泡玛特盈利能力分析(${financialViewLabel(view)})`,
|
||||
subtext: ['营收、毛利、净利润及毛利率变化', financialViewDisclosure(view)].filter(Boolean).join('\n'),
|
||||
},
|
||||
legend: {
|
||||
...legendStyle,
|
||||
@@ -33,7 +34,7 @@ export function profitChart(rows, { width, height }) {
|
||||
axisPointer: { type: 'shadow' },
|
||||
textStyle: { fontFamily: FONT },
|
||||
},
|
||||
xAxis: { ...periodAxis, data: periods },
|
||||
xAxis: { ...standalonePeriodAxis, data: periods },
|
||||
yAxis: [
|
||||
{
|
||||
...axisCommon,
|
||||
@@ -122,7 +123,7 @@ export function profitChart(rows, { width, height }) {
|
||||
],
|
||||
graphic: [
|
||||
...watermarks(width, height),
|
||||
footnote('数据来源:泡泡玛特财报(累计数据)| @思考的Joey'),
|
||||
footnote('数据来源:泡泡玛特财报;H2为单独半年 | @思考的Joey'),
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
+35
-5
@@ -11,6 +11,7 @@ import {
|
||||
|
||||
const instances = new Map();
|
||||
const activeViews = new Map();
|
||||
const chartRenderVersions = new Map();
|
||||
let manifest;
|
||||
let shell;
|
||||
let state;
|
||||
@@ -106,25 +107,47 @@ function chartSource(chart) {
|
||||
return chart.views[view];
|
||||
}
|
||||
|
||||
async function renderChart(chart, version) {
|
||||
async function renderChart(chart, version, chartVersion) {
|
||||
const el = document.getElementById(`chart-${chart.instanceId}`);
|
||||
if (!el) return;
|
||||
const source = chartSource(chart);
|
||||
const view = chart.views
|
||||
? activeViews.get(chart.viewKey) || chart.defaultView
|
||||
: undefined;
|
||||
|
||||
try {
|
||||
const rows = await loadCSV(`${source}?t=${cacheToken}`);
|
||||
if (version !== renderVersion || !document.body.contains(el)) return;
|
||||
if (version !== renderVersion || chartVersion !== chartRenderVersions.get(chart.instanceId) || !document.body.contains(el)) return;
|
||||
|
||||
const instance = echarts.init(el, null, { renderer: 'canvas' });
|
||||
instances.set(chart.instanceId, instance);
|
||||
const { width, height } = el.getBoundingClientRect();
|
||||
instance.setOption(getChartBuilder(chart.key)(rows, { width, height }), true);
|
||||
instance.setOption(getChartBuilder(chart.key)(rows, { width, height, view }), true);
|
||||
} catch (error) {
|
||||
if (version === renderVersion) showChartError(chart.instanceId, error.message);
|
||||
console.error(`渲染图表失败:${chart.id}`, error);
|
||||
}
|
||||
}
|
||||
|
||||
function renderSingleChart(chart) {
|
||||
const previous = instances.get(chart.instanceId);
|
||||
previous?.dispose();
|
||||
instances.delete(chart.instanceId);
|
||||
const chartVersion = (chartRenderVersions.get(chart.instanceId) || 0) + 1;
|
||||
chartRenderVersions.set(chart.instanceId, chartVersion);
|
||||
renderChart(chart, renderVersion, chartVersion);
|
||||
}
|
||||
|
||||
function updateViewControls(chart, view) {
|
||||
const card = document.getElementById(`card-${chart.instanceId}`);
|
||||
if (!card) return;
|
||||
card.querySelectorAll('.view-button').forEach((button) => {
|
||||
button.dataset.active = String(button.dataset.view === view);
|
||||
});
|
||||
const source = card.querySelector('.src');
|
||||
if (source) source.textContent = chartSource(chart);
|
||||
}
|
||||
|
||||
function exportPNG(chartId, filename) {
|
||||
const instance = instances.get(chartId);
|
||||
if (!instance) return;
|
||||
@@ -167,12 +190,19 @@ function renderCurrent() {
|
||||
renderCards(shell.cards, charts, activeViews, {
|
||||
onExport: exportPNG,
|
||||
onSelectView: (chart, view) => {
|
||||
if (activeViews.get(chart.viewKey) === view) return;
|
||||
activeViews.set(chart.viewKey, view);
|
||||
updateHash({ companyId: company.id, tabId: tab.id, chartId: chart.id, view });
|
||||
state = normalizeState({ companyId: company.id, tabId: tab.id, chartId: chart.id, view });
|
||||
history.pushState(null, '', serializeHash(state));
|
||||
updateViewControls(chart, view);
|
||||
renderSingleChart(chart);
|
||||
},
|
||||
});
|
||||
renderAnchors(shell.chartNav, charts, selectAnchor);
|
||||
Promise.all(charts.map((chart) => renderChart(chart, version)));
|
||||
charts.forEach((chart) => {
|
||||
chartRenderVersions.set(chart.instanceId, (chartRenderVersions.get(chart.instanceId) || 0) + 1);
|
||||
});
|
||||
Promise.all(charts.map((chart) => renderChart(chart, version, chartRenderVersions.get(chart.instanceId))));
|
||||
}
|
||||
|
||||
async function start() {
|
||||
|
||||
+26
@@ -108,6 +108,32 @@ export const periodAxis = {
|
||||
},
|
||||
};
|
||||
|
||||
/**
|
||||
* 独立期间轴:FY 为全年,H1/H2 为单独半年。
|
||||
* 用于已拆分累计值的多时间维度图,避免把 H2 错写为全年。
|
||||
*/
|
||||
export function shortStandalonePeriod(label) {
|
||||
return String(label)
|
||||
.replace(/^20(\d{2})FY$/, '$1全年')
|
||||
.replace(/^20(\d{2})/, '$1');
|
||||
}
|
||||
|
||||
export const standalonePeriodAxis = {
|
||||
...categoryAxis,
|
||||
axisLabel: {
|
||||
...categoryAxis.axisLabel,
|
||||
formatter: shortStandalonePeriod,
|
||||
},
|
||||
};
|
||||
|
||||
export function financialViewLabel(view) {
|
||||
return ({ all: '全部期间', annual: '年度', halfyear: '半年', quarter: '季度' })[view] || '全部期间';
|
||||
}
|
||||
|
||||
export function financialViewDisclosure(view) {
|
||||
return '';
|
||||
}
|
||||
|
||||
export const titleStyle = {
|
||||
left: 'center',
|
||||
top: 18,
|
||||
|
||||
@@ -79,13 +79,15 @@ export function renderCards(container, charts, activeViews, { onExport, onSelect
|
||||
|
||||
if (viewNames.length > 1) {
|
||||
const switcher = element('div', 'view-switcher');
|
||||
const labels = { annual: '年度', halfyear: '半年', quarter: '季度' };
|
||||
const labels = { all: '全部', annual: '年度', halfyear: '半年', quarter: '季度' };
|
||||
viewNames.forEach((view) => {
|
||||
switcher.append(button(labels[view] || view, {
|
||||
const viewButton = button(labels[view] || view, {
|
||||
active: view === activeView,
|
||||
onClick: () => onSelectView(chart, view),
|
||||
className: 'view-button',
|
||||
}));
|
||||
});
|
||||
viewButton.dataset.view = view;
|
||||
switcher.append(viewButton);
|
||||
});
|
||||
bar.append(switcher);
|
||||
}
|
||||
|
||||
+15
-15
@@ -4,28 +4,28 @@
|
||||
"id": "ppmt",
|
||||
"name": "泡泡玛特",
|
||||
"chartCatalog": {
|
||||
"ppmt-profit": { "key": "ppmt-profit", "title": "盈利能力", "csv": "data/ppmt/ppmt_profit.csv", "height": 680, "filename": "泡泡玛特盈利能力" },
|
||||
"ppmt-profit": { "key": "ppmt-profit", "title": "盈利能力", "defaultView": "all", "views": { "all": "data/ppmt/ppmt_profit.csv", "annual": "data/ppmt/ppmt_profit_annual.csv", "halfyear": "data/ppmt/ppmt_profit_halfyear.csv" }, "height": 680, "filename": "泡泡玛特盈利能力" },
|
||||
"ppmt-sankey": { "key": "ppmt-sankey", "title": "利润流向", "csv": "data/ppmt/ppmt_sankey_2026h1.csv", "height": 800, "filename": "泡泡玛特利润流向2026H1" },
|
||||
"ppmt-growth": { "key": "ppmt-growth", "title": "营收到利润的增速对比", "csv": "data/ppmt/ppmt_growth.csv", "height": 700, "filename": "泡泡玛特营收到利润增速对比" },
|
||||
"ppmt-core-profit": { "key": "ppmt-core-profit", "title": "核心利润及同比增速", "csv": "data/ppmt/ppmt_core_profit.csv", "height": 720, "filename": "泡泡玛特核心利润及同比增速" },
|
||||
"ppmt-core-margin": { "key": "ppmt-core-margin", "title": "核心利润率", "csv": "data/ppmt/ppmt_core_margin.csv", "height": 680, "filename": "泡泡玛特核心利润率" },
|
||||
"ppmt-growth": { "key": "ppmt-growth", "title": "营收到利润的增速对比", "defaultView": "all", "views": { "all": "data/ppmt/ppmt_growth.csv", "annual": "data/ppmt/ppmt_growth_annual.csv", "halfyear": "data/ppmt/ppmt_growth_halfyear.csv" }, "height": 700, "filename": "泡泡玛特营收到利润增速对比" },
|
||||
"ppmt-core-profit": { "key": "ppmt-core-profit", "title": "核心利润及同比增速", "defaultView": "all", "views": { "all": "data/ppmt/ppmt_core_profit.csv", "annual": "data/ppmt/ppmt_core_profit_annual.csv", "halfyear": "data/ppmt/ppmt_core_profit_halfyear.csv" }, "height": 720, "filename": "泡泡玛特核心利润及同比增速" },
|
||||
"ppmt-core-margin": { "key": "ppmt-core-margin", "title": "核心利润率", "defaultView": "all", "views": { "all": "data/ppmt/ppmt_core_margin.csv", "annual": "data/ppmt/ppmt_core_margin_annual.csv", "halfyear": "data/ppmt/ppmt_core_margin_halfyear.csv" }, "height": 680, "filename": "泡泡玛特核心利润率" },
|
||||
"ppmt-bridge": { "key": "ppmt-bridge", "title": "净利率同比拆解", "csv": "data/ppmt/ppmt_margin_bridge.csv", "height": 700, "filename": "泡泡玛特净利率同比拆解" },
|
||||
"ppmt-margins": { "key": "ppmt-margins", "title": "利润率与费用率拆解", "csv": "data/ppmt/ppmt_margins.csv", "height": 680, "filename": "泡泡玛特利润率与费用率拆解" },
|
||||
"ppmt-assets": { "key": "ppmt-assets", "title": "资产结构演变", "csv": "data/ppmt/ppmt_assets.csv", "height": 680, "filename": "泡泡玛特资产结构" },
|
||||
"ppmt-ip": { "key": "ppmt-ip", "title": "主要 IP 收入趋势", "csv": "data/ppmt/ppmt_ip.csv", "height": 720, "filename": "泡泡玛特IP收入趋势" },
|
||||
"ppmt-margins": { "key": "ppmt-margins", "title": "利润率与费用率拆解", "defaultView": "all", "views": { "all": "data/ppmt/ppmt_margins.csv", "annual": "data/ppmt/ppmt_margins_annual.csv", "halfyear": "data/ppmt/ppmt_margins_halfyear.csv" }, "height": 680, "filename": "泡泡玛特利润率与费用率拆解" },
|
||||
"ppmt-assets": { "key": "ppmt-assets", "title": "资产结构演变", "defaultView": "all", "views": { "all": "data/ppmt/ppmt_assets.csv", "annual": "data/ppmt/ppmt_assets_annual.csv", "halfyear": "data/ppmt/ppmt_assets_halfyear.csv" }, "height": 680, "filename": "泡泡玛特资产结构" },
|
||||
"ppmt-ip": { "key": "ppmt-ip", "title": "主要 IP 收入趋势", "defaultView": "all", "views": { "all": "data/ppmt/ppmt_ip.csv", "annual": "data/ppmt/ppmt_ip_annual.csv", "halfyear": "data/ppmt/ppmt_ip_halfyear.csv" }, "height": 720, "filename": "泡泡玛特IP收入趋势" },
|
||||
"ppmt-ip-share": { "key": "ppmt-ip-share", "title": "主要 IP 收入占比", "csv": "data/ppmt/ppmt_ip_share_2026h1.csv", "height": 720, "filename": "泡泡玛特IP收入占比2026H1" },
|
||||
"ppmt-ip-share-history": { "key": "ppmt-ip-share-history", "title": "各 IP 半年收入占比", "csv": "data/ppmt/ppmt_ip_share_history.csv", "height": 700, "filename": "泡泡玛特各IP半年收入占比" },
|
||||
"ppmt-ip-ex-monsters": { "key": "ppmt-ip-ex-monsters", "title": "除 THE MONSTERS 外的 IP 收入及同比", "csv": "data/ppmt/ppmt_ip_ex_monsters.csv", "height": 700, "filename": "泡泡玛特除MONSTERS外IP收入及同比" },
|
||||
"ppmt-category": { "key": "ppmt-category", "title": "品类收入结构", "csv": "data/ppmt/ppmt_category.csv", "height": 680, "filename": "泡泡玛特品类结构" },
|
||||
"ppmt-ip-share-history": { "key": "ppmt-ip-share-history", "title": "各 IP 半年收入占比", "defaultView": "all", "views": { "all": "data/ppmt/ppmt_ip_share_history.csv", "annual": "data/ppmt/ppmt_ip_share_history_annual.csv", "halfyear": "data/ppmt/ppmt_ip_share_history_halfyear.csv" }, "height": 700, "filename": "泡泡玛特各IP半年收入占比" },
|
||||
"ppmt-ip-ex-monsters": { "key": "ppmt-ip-ex-monsters", "title": "除 THE MONSTERS 外的 IP 收入及同比", "defaultView": "all", "views": { "all": "data/ppmt/ppmt_ip_ex_monsters.csv", "annual": "data/ppmt/ppmt_ip_ex_monsters_annual.csv", "halfyear": "data/ppmt/ppmt_ip_ex_monsters_halfyear.csv" }, "height": 700, "filename": "泡泡玛特除MONSTERS外IP收入及同比" },
|
||||
"ppmt-category": { "key": "ppmt-category", "title": "品类收入结构", "defaultView": "all", "views": { "all": "data/ppmt/ppmt_category.csv", "annual": "data/ppmt/ppmt_category_annual.csv", "halfyear": "data/ppmt/ppmt_category_halfyear.csv" }, "height": 680, "filename": "泡泡玛特品类结构" },
|
||||
"ppmt-region-channel": { "key": "ppmt-region-channel", "title": "区域渠道分布", "csv": "data/ppmt/ppmt_region_channel_2026h1.csv", "height": 680, "filename": "泡泡玛特区域渠道分布2026H1" },
|
||||
"ppmt-region-yoy": { "key": "ppmt-region-yoy", "title": "区域渠道同比增速", "csv": "data/ppmt/ppmt_region_channel_yoy.csv", "height": 620, "filename": "泡泡玛特区域渠道同比增速" },
|
||||
"ppmt-region-yoy-timeseries": { "key": "ppmt-region-yoy-timeseries", "title": "区域渠道同比增速趋势", "csv": "data/ppmt/ppmt_region_channel_yoy_timeseries.csv", "height": 760, "filename": "泡泡玛特区域渠道同比增速趋势" },
|
||||
"ppmt-cash-conversion": { "key": "ppmt-cash-conversion", "title": "经营现金流与净利润(年度)", "csv": "data/ppmt/ppmt_cash_conversion.csv", "height": 700, "filename": "泡泡玛特经营现金流与净利润" },
|
||||
"ppmt-liquidity-working-capital": { "key": "ppmt-liquidity-working-capital", "title": "现金、负债与净营运资本", "csv": "data/ppmt/ppmt_liquidity_working_capital.csv", "height": 720, "filename": "泡泡玛特现金负债与净营运资本" },
|
||||
"ppmt-cashflow-composition": { "key": "ppmt-cashflow-composition", "title": "三大现金流与现金净增加(年度)", "csv": "data/ppmt/ppmt_cashflow_composition.csv", "height": 700, "filename": "泡泡玛特三大现金流与现金净增加" },
|
||||
"ppmt-cash-conversion": { "key": "ppmt-cash-conversion", "title": "经营现金流与净利润", "defaultView": "all", "views": { "all": "data/ppmt/ppmt_cash_conversion.csv", "annual": "data/ppmt/ppmt_cash_conversion_annual.csv", "halfyear": "data/ppmt/ppmt_cash_conversion_halfyear.csv" }, "height": 700, "filename": "泡泡玛特经营现金流与净利润" },
|
||||
"ppmt-liquidity-working-capital": { "key": "ppmt-liquidity-working-capital", "title": "现金、负债与净营运资本", "defaultView": "all", "views": { "all": "data/ppmt/ppmt_liquidity_working_capital.csv", "annual": "data/ppmt/ppmt_liquidity_working_capital_annual.csv", "halfyear": "data/ppmt/ppmt_liquidity_working_capital_halfyear.csv" }, "height": 720, "filename": "泡泡玛特现金负债与净营运资本" },
|
||||
"ppmt-cashflow-composition": { "key": "ppmt-cashflow-composition", "title": "三大现金流与现金净增加", "defaultView": "all", "views": { "all": "data/ppmt/ppmt_cashflow_composition.csv", "annual": "data/ppmt/ppmt_cashflow_composition_annual.csv", "halfyear": "data/ppmt/ppmt_cashflow_composition_halfyear.csv" }, "height": 700, "filename": "泡泡玛特三大现金流与现金净增加" },
|
||||
"ppmt-working-capital-days": { "key": "ppmt-working-capital-days", "title": "营运资本效率", "csv": "data/ppmt/ppmt_working_capital_days.csv", "height": 700, "filename": "泡泡玛特营运资本效率" },
|
||||
"ppmt-expense-structure": { "key": "ppmt-expense-structure", "title": "费用结构与费用率(年度)", "csv": "data/ppmt/ppmt_expense_structure.csv", "height": 700, "filename": "泡泡玛特费用结构与费用率" },
|
||||
"ppmt-channel-revenue": { "key": "ppmt-channel-revenue", "title": "渠道收入结构与增速", "csv": "data/ppmt/ppmt_channel_revenue.csv", "height": 720, "filename": "泡泡玛特渠道收入结构与增速" }
|
||||
"ppmt-expense-structure": { "key": "ppmt-expense-structure", "title": "费用结构与费用率", "defaultView": "all", "views": { "all": "data/ppmt/ppmt_expense_structure.csv", "annual": "data/ppmt/ppmt_expense_structure_annual.csv", "halfyear": "data/ppmt/ppmt_expense_structure_halfyear.csv" }, "height": 700, "filename": "泡泡玛特费用结构与费用率" },
|
||||
"ppmt-channel-revenue": { "key": "ppmt-channel-revenue", "title": "渠道收入结构与增速", "defaultView": "all", "views": { "all": "data/ppmt/ppmt_channel_revenue.csv", "annual": "data/ppmt/ppmt_channel_revenue_annual.csv", "halfyear": "data/ppmt/ppmt_channel_revenue_halfyear.csv" }, "height": 720, "filename": "泡泡玛特渠道收入结构与增速" }
|
||||
},
|
||||
"tabs": [
|
||||
{ "id": "overview", "name": "概览", "chartRefs": ["ppmt-growth", "ppmt-profit", "ppmt-ip-share", "ppmt-region-channel"] },
|
||||
|
||||
+1
-1
@@ -23,5 +23,5 @@
|
||||
"echarts": "5.5.1",
|
||||
"vite": "^8.2.2"
|
||||
},
|
||||
"packageManager": "pnpm@10.32.1+sha512.a706938f0e89ac1456b6563eab4edf1d1faf3368d1191fc5c59790e96dc918e4456ab2e67d613de1043d2e8c81f87303e6b40d4ffeca9df15ef1ad567348f2be"
|
||||
"packageManager": "pnpm@11.24.0"
|
||||
}
|
||||
|
||||
@@ -258,3 +258,389 @@ writeCSV('ppmt_channel_revenue.csv', [
|
||||
], channelRows);
|
||||
|
||||
console.log(`已生成现金转换 ${cashConversionRows.length}、流动性 ${liquidityRows.length}、现金流 ${cashflowCompositionRows.length}、营运资本 ${workingCapitalDaysRows.length}、费用 ${expenseRows.length}、渠道 ${channelRows.length} 行数据。`);
|
||||
|
||||
// 多维度图表:FY 为全年,H1/H2 为独立半年。所有拆分和同比均在数据层完成。
|
||||
const VIEW_YEARS = Array.from({ length: 8 }, (_, index) => 2019 + index);
|
||||
const OUTPUT_START_YEAR = 2020;
|
||||
const HUNDRED_MILLION = 100000000;
|
||||
|
||||
function isComplete(values) {
|
||||
return values && Object.values(values).every((value) => Number.isFinite(value));
|
||||
}
|
||||
|
||||
function subtractPeriod(fullYear, firstHalf) {
|
||||
if (!isComplete(fullYear) || !isComplete(firstHalf)) return null;
|
||||
return Object.fromEntries(Object.keys(fullYear).map((key) => [key, fullYear[key] - firstHalf[key]]));
|
||||
}
|
||||
|
||||
function buildViews(readPeriod) {
|
||||
const views = { all: [], annual: [], halfyear: [] };
|
||||
VIEW_YEARS.forEach((year) => {
|
||||
const h1 = readPeriod(year, 'H1');
|
||||
const fy = readPeriod(year, 'FY');
|
||||
const h2 = subtractPeriod(fy, h1);
|
||||
if (h1) {
|
||||
const row = { period: `${year}H1`, ...h1 };
|
||||
views.halfyear.push(row);
|
||||
views.all.push(row);
|
||||
}
|
||||
if (h2) {
|
||||
const row = { period: `${year}H2`, ...h2 };
|
||||
views.halfyear.push(row);
|
||||
views.all.push(row);
|
||||
}
|
||||
if (fy) {
|
||||
const row = { period: `${year}FY`, ...fy };
|
||||
views.annual.push(row);
|
||||
views.all.push(row);
|
||||
}
|
||||
});
|
||||
return views;
|
||||
}
|
||||
|
||||
function addPeriods(firstHalf, secondHalf) {
|
||||
if (!isComplete(firstHalf) || !isComplete(secondHalf)) return null;
|
||||
return Object.fromEntries(Object.keys(firstHalf).map((key) => [key, firstHalf[key] + secondHalf[key]]));
|
||||
}
|
||||
|
||||
// IP、品类等运营数据的 H2 已是单独半年,年度值应由 H1 + H2 汇总。
|
||||
function buildViewsFromHalfYears(readPeriod) {
|
||||
const views = { all: [], annual: [], halfyear: [] };
|
||||
VIEW_YEARS.forEach((year) => {
|
||||
const h1 = readPeriod(year, 'H1');
|
||||
const h2 = readPeriod(year, 'H2');
|
||||
const fy = addPeriods(h1, h2);
|
||||
if (h1) {
|
||||
const row = { period: `${year}H1`, ...h1 };
|
||||
views.halfyear.push(row);
|
||||
views.all.push(row);
|
||||
}
|
||||
if (h2) {
|
||||
const row = { period: `${year}H2`, ...h2 };
|
||||
views.halfyear.push(row);
|
||||
views.all.push(row);
|
||||
}
|
||||
if (fy) {
|
||||
const row = { period: `${year}FY`, ...fy };
|
||||
views.annual.push(row);
|
||||
views.all.push(row);
|
||||
}
|
||||
});
|
||||
return views;
|
||||
}
|
||||
|
||||
function yoyFor(rows, key, outputKey = key) {
|
||||
const byPeriod = new Map(rows.map((row) => [row.period, row]));
|
||||
return rows.map((row) => {
|
||||
const year = Number(row.period.slice(0, 4));
|
||||
const prior = byPeriod.get(`${year - 1}${row.period.slice(4)}`);
|
||||
return {
|
||||
...row,
|
||||
[outputKey]: prior && prior[key] !== 0 ? (row[key] / prior[key] - 1) * 100 : null,
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
function toOutputRows(rows, headers, digits = {}) {
|
||||
return rows
|
||||
.filter((row) => Number(row.period.slice(0, 4)) >= OUTPUT_START_YEAR)
|
||||
.map((row) => headers.map((header) => {
|
||||
if (header === 'period') return row.period;
|
||||
const value = row[header];
|
||||
return Number.isFinite(value) ? value.toFixed(digits[header] ?? 2) : '';
|
||||
}));
|
||||
}
|
||||
|
||||
function writeViewFiles(base, headers, views, digits) {
|
||||
const filenames = {
|
||||
all: `${base}.csv`,
|
||||
annual: `${base}_annual.csv`,
|
||||
halfyear: `${base}_halfyear.csv`,
|
||||
};
|
||||
Object.entries(filenames).forEach(([view, filename]) => {
|
||||
writeCSV(filename, headers, toOutputRows(views[view], headers, digits));
|
||||
});
|
||||
}
|
||||
|
||||
const incomeDateIndex = new Map(income.dates.map((date, index) => [date, index]));
|
||||
const incomeColumns = {
|
||||
revenue: row(income, '一、营业总收入'),
|
||||
cost: row(income, '营业成本'),
|
||||
sales_expense: row(income, '减:销售费用'),
|
||||
admin_expense: row(income, '管理费用'),
|
||||
net_profit: row(income, '三、净利润'),
|
||||
non_gaap_net_profit: row(income, '非国际公认会计准则净利润'),
|
||||
};
|
||||
|
||||
function incomePeriod(year, type) {
|
||||
const date = `${year}-${type === 'H1' ? '06-30' : '12-31'}`;
|
||||
const index = incomeDateIndex.get(date);
|
||||
if (index === undefined) return null;
|
||||
const result = Object.fromEntries(Object.entries(incomeColumns).map(([key, values]) => [key, values[index]]));
|
||||
// 2020 年非 GAAP 净利未披露,沿用现有图表的归母净利替代口径。
|
||||
result.non_gaap_net_profit ??= result.net_profit;
|
||||
return isComplete(result) ? result : null;
|
||||
}
|
||||
|
||||
const cashflowDateIndex = new Map(cashflow.dates.map((date, index) => [date, index]));
|
||||
const cashflowColumns = {
|
||||
operating_cash_flow: row(cashflow, '经营活动产生的现金流量净额'),
|
||||
investing_cash_flow: row(cashflow, '投资活动产生的现金流量净额'),
|
||||
financing_cash_flow: row(cashflow, '筹资活动产生的现金流量净额'),
|
||||
net_cash_increase: row(cashflow, '现金及现金等价物的净增加额'),
|
||||
};
|
||||
|
||||
function cashflowPeriod(year, type) {
|
||||
const date = `${year}-${type === 'H1' ? '06-30' : '12-31'}`;
|
||||
const index = cashflowDateIndex.get(date);
|
||||
if (index === undefined) return null;
|
||||
const result = Object.fromEntries(Object.entries(cashflowColumns).map(([key, values]) => [key, values[index]]));
|
||||
return isComplete(result) ? result : null;
|
||||
}
|
||||
|
||||
function inBillion(values) {
|
||||
return Object.fromEntries(Object.entries(values).map(([key, value]) => [key, value / HUNDRED_MILLION]));
|
||||
}
|
||||
|
||||
const growthViews = buildViews((year, type) => {
|
||||
const values = incomePeriod(year, type);
|
||||
return values && {
|
||||
non_gaap_net_profit: values.non_gaap_net_profit,
|
||||
gross_profit: values.revenue - values.cost,
|
||||
revenue: values.revenue,
|
||||
cost: values.cost,
|
||||
};
|
||||
});
|
||||
Object.keys(growthViews).forEach((view) => {
|
||||
['non_gaap_net_profit', 'gross_profit', 'revenue', 'cost'].forEach((key) => {
|
||||
growthViews[view] = yoyFor(growthViews[view], key);
|
||||
});
|
||||
});
|
||||
writeViewFiles('ppmt_growth', ['period', 'non_gaap_net_profit', 'gross_profit', 'revenue', 'cost'], growthViews, {
|
||||
non_gaap_net_profit: 1, gross_profit: 1, revenue: 1, cost: 1,
|
||||
});
|
||||
|
||||
const coreProfitViews = buildViews((year, type) => {
|
||||
const values = incomePeriod(year, type);
|
||||
return values && { core_profit: (values.revenue - values.cost - values.sales_expense - values.admin_expense) / HUNDRED_MILLION };
|
||||
});
|
||||
Object.keys(coreProfitViews).forEach((view) => {
|
||||
coreProfitViews[view] = yoyFor(coreProfitViews[view], 'core_profit', 'yoy');
|
||||
});
|
||||
writeViewFiles('ppmt_core_profit', ['period', 'core_profit', 'yoy'], coreProfitViews, { core_profit: 2, yoy: 1 });
|
||||
|
||||
const marginAmountViews = buildViews((year, type) => {
|
||||
const values = incomePeriod(year, type);
|
||||
return values && {
|
||||
revenue: values.revenue,
|
||||
gross_profit: values.revenue - values.cost,
|
||||
sales_expense: values.sales_expense,
|
||||
admin_expense: values.admin_expense,
|
||||
non_gaap_net_profit: values.non_gaap_net_profit,
|
||||
};
|
||||
});
|
||||
const marginsViews = Object.fromEntries(Object.entries(marginAmountViews).map(([view, rows]) => [
|
||||
view,
|
||||
rows.map((values) => {
|
||||
if (values.revenue === 0) return values;
|
||||
return {
|
||||
period: values.period,
|
||||
gross_margin: values.gross_profit / values.revenue * 100,
|
||||
sales_expense_rate: values.sales_expense / values.revenue * 100,
|
||||
admin_expense_rate: values.admin_expense / values.revenue * 100,
|
||||
non_gaap_net_margin: values.non_gaap_net_profit / values.revenue * 100,
|
||||
};
|
||||
}),
|
||||
]));
|
||||
writeViewFiles('ppmt_margins', ['period', 'gross_margin', 'sales_expense_rate', 'admin_expense_rate', 'non_gaap_net_margin'], marginsViews, {
|
||||
gross_margin: 2, sales_expense_rate: 2, admin_expense_rate: 2, non_gaap_net_margin: 2,
|
||||
});
|
||||
|
||||
const cashConversionViews = buildViews((year, type) => {
|
||||
const flow = cashflowPeriod(year, type);
|
||||
const incomeValues = incomePeriod(year, type);
|
||||
if (!flow || !incomeValues) return null;
|
||||
return inBillion({
|
||||
operating_cash_flow: flow.operating_cash_flow,
|
||||
net_profit: incomeValues.net_profit,
|
||||
});
|
||||
});
|
||||
Object.values(cashConversionViews).forEach((rows) => rows.forEach((row) => {
|
||||
row.cash_conversion = row.net_profit === 0 ? null : row.operating_cash_flow / row.net_profit * 100;
|
||||
}));
|
||||
writeViewFiles('ppmt_cash_conversion', ['period', 'operating_cash_flow', 'net_profit', 'cash_conversion'], cashConversionViews, {
|
||||
operating_cash_flow: 2, net_profit: 2, cash_conversion: 1,
|
||||
});
|
||||
|
||||
const cashflowCompositionViews = buildViews((year, type) => {
|
||||
const values = cashflowPeriod(year, type);
|
||||
return values && inBillion(values);
|
||||
});
|
||||
writeViewFiles('ppmt_cashflow_composition', ['period', 'operating_cash_flow', 'investing_cash_flow', 'financing_cash_flow', 'net_cash_increase'], cashflowCompositionViews, {
|
||||
operating_cash_flow: 2, investing_cash_flow: 2, financing_cash_flow: 2, net_cash_increase: 2,
|
||||
});
|
||||
|
||||
const channelKeysForViews = [
|
||||
['零售店銷售收益', 'retail'],
|
||||
['線上銷售收益', 'online'],
|
||||
['機器人商店銷售收益', 'robot_store'],
|
||||
['批發收益', 'wholesale'],
|
||||
];
|
||||
const revenueDateIndex = new Map(revenueComposition.dates.map((date, index) => [date, index]));
|
||||
function channelPeriod(year, type) {
|
||||
const index = revenueDateIndex.get(`${year}-Q${type === 'H1' ? 2 : 4}`);
|
||||
if (index === undefined) return null;
|
||||
const values = Object.fromEntries(channelKeysForViews.map(([name, key]) => [
|
||||
key, revenueComposition.channels.get(`${name}:收入`)?.[index] ?? null,
|
||||
]));
|
||||
const total = revenueComposition.channels.get('totalRevenue')?.[index] ?? null;
|
||||
return isComplete({ ...values, total }) ? inBillion({ ...values, total_revenue: total }) : null;
|
||||
}
|
||||
|
||||
const channelRevenueViews = buildViews(channelPeriod);
|
||||
Object.keys(channelRevenueViews).forEach((view) => {
|
||||
channelRevenueViews[view] = yoyFor(channelRevenueViews[view], 'total_revenue', 'revenue_yoy');
|
||||
});
|
||||
writeViewFiles('ppmt_channel_revenue', ['period', 'retail', 'online', 'robot_store', 'wholesale', 'total_revenue', 'revenue_yoy'], channelRevenueViews, {
|
||||
retail: 2, online: 2, robot_store: 2, wholesale: 2, total_revenue: 2, revenue_yoy: 1,
|
||||
});
|
||||
|
||||
console.log('已生成首批六图的全部、年度、半年 CSV。');
|
||||
|
||||
const profitViews = buildViews((year, type) => {
|
||||
const values = incomePeriod(year, type);
|
||||
return values && inBillion({
|
||||
revenue: values.revenue,
|
||||
gross_profit: values.revenue - values.cost,
|
||||
net_profit: values.net_profit,
|
||||
});
|
||||
});
|
||||
Object.values(profitViews).forEach((rows) => rows.forEach((item) => {
|
||||
item.gross_margin = item.revenue === 0 ? null : item.gross_profit / item.revenue * 100;
|
||||
}));
|
||||
writeViewFiles('ppmt_profit', ['period', 'revenue', 'gross_profit', 'gross_margin', 'net_profit'], profitViews, {
|
||||
revenue: 2, gross_profit: 2, gross_margin: 2, net_profit: 2,
|
||||
});
|
||||
|
||||
const coreMarginViews = buildViews((year, type) => {
|
||||
const values = incomePeriod(year, type);
|
||||
return values && {
|
||||
revenue: values.revenue,
|
||||
core_profit: values.revenue - values.cost - values.sales_expense - values.admin_expense,
|
||||
};
|
||||
});
|
||||
Object.values(coreMarginViews).forEach((rows) => rows.forEach((item) => {
|
||||
item.core_margin = item.revenue === 0 ? null : item.core_profit / item.revenue * 100;
|
||||
}));
|
||||
writeViewFiles('ppmt_core_margin', ['period', 'core_margin'], coreMarginViews, { core_margin: 1 });
|
||||
|
||||
const expenseAmountViews = buildViews((year, type) => {
|
||||
const values = incomePeriod(year, type);
|
||||
if (!values) return null;
|
||||
const financialExpense = row(income, '财务费用')[incomeDateIndex.get(`${year}-${type === 'H1' ? '06-30' : '12-31'}`)];
|
||||
return {
|
||||
revenue: values.revenue,
|
||||
sales_expense: values.sales_expense,
|
||||
admin_expense: values.admin_expense,
|
||||
financial_expense: financialExpense,
|
||||
};
|
||||
});
|
||||
const expenseViews = Object.fromEntries(Object.entries(expenseAmountViews).map(([view, rows]) => [
|
||||
view,
|
||||
rows.map((item) => ({
|
||||
...inBillion(item),
|
||||
period: item.period,
|
||||
four_expense_rate: (item.sales_expense + item.admin_expense + item.financial_expense) / item.revenue * 100,
|
||||
})),
|
||||
]));
|
||||
writeViewFiles('ppmt_expense_structure', ['period', 'sales_expense', 'admin_expense', 'financial_expense', 'four_expense_rate'], expenseViews, {
|
||||
sales_expense: 2, admin_expense: 2, financial_expense: 2, four_expense_rate: 1,
|
||||
});
|
||||
|
||||
function readRuntimeRows(filename) {
|
||||
const [header, ...lines] = fs.readFileSync(path.join(OUTPUT_DIR, filename), 'utf8').trim().split(/\r?\n/);
|
||||
const headers = header.split(',');
|
||||
return lines.map((line) => Object.fromEntries(line.split(',').map((cell, index) => {
|
||||
const value = Number(cell);
|
||||
return [headers[index], cell !== '' && Number.isFinite(value) ? value : cell];
|
||||
})));
|
||||
}
|
||||
|
||||
function writeRuntimeViews(base, headers, views, digits = {}) {
|
||||
writeViewFiles(base, headers, views, digits);
|
||||
}
|
||||
|
||||
function buildWideIpViews(filename) {
|
||||
const rows = readRuntimeRows(filename);
|
||||
const id = Object.keys(rows[0])[0];
|
||||
const sourcePeriods = Object.keys(rows[0]).slice(1).filter((period) => /H[12]$/.test(period));
|
||||
const years = [...new Set(sourcePeriods.map((period) => period.slice(0, 4)))];
|
||||
const halfyear = sourcePeriods;
|
||||
const annual = years.filter((year) => sourcePeriods.includes(`${year}H1`) && sourcePeriods.includes(`${year}H2`)).map((year) => `${year}FY`);
|
||||
const all = years.flatMap((year) => [
|
||||
...(sourcePeriods.includes(`${year}H1`) ? [`${year}H1`] : []),
|
||||
...(sourcePeriods.includes(`${year}H2`) ? [`${year}H2`, `${year}FY`] : []),
|
||||
]);
|
||||
const valuesFor = (periods) => rows.map((row) => [row[id], ...periods.map((period) => {
|
||||
if (!period.endsWith('FY')) return row[period];
|
||||
return row[`${period.slice(0, 4)}H1`] + row[`${period.slice(0, 4)}H2`];
|
||||
})]);
|
||||
writeCSV(filename, [id, ...all], valuesFor(all));
|
||||
writeCSV(filename.replace('.csv', '_annual.csv'), [id, ...annual], valuesFor(annual));
|
||||
writeCSV(filename.replace('.csv', '_halfyear.csv'), [id, ...halfyear], valuesFor(halfyear));
|
||||
}
|
||||
|
||||
buildWideIpViews('ppmt_ip.csv');
|
||||
buildWideIpViews('ppmt_ip_share_history.csv');
|
||||
|
||||
const ipExMonstersRows = readRuntimeRows('ppmt_ip_ex_monsters.csv');
|
||||
function ipExMonstersPeriod(year, type) {
|
||||
const row = ipExMonstersRows.find((item) => item.period === `${year}${type}`);
|
||||
return row ? { revenue: row.revenue } : null;
|
||||
}
|
||||
const ipExMonstersViews = buildViewsFromHalfYears(ipExMonstersPeriod);
|
||||
Object.keys(ipExMonstersViews).forEach((view) => {
|
||||
ipExMonstersViews[view] = yoyFor(ipExMonstersViews[view], 'revenue', 'yoy');
|
||||
});
|
||||
writeRuntimeViews('ppmt_ip_ex_monsters', ['period', 'revenue', 'yoy'], ipExMonstersViews, { revenue: 2, yoy: 1 });
|
||||
|
||||
const categoryRows = readRuntimeRows('ppmt_category.csv').filter((item) => item.period !== '品类');
|
||||
function categoryPeriod(year, type) {
|
||||
const row = categoryRows.find((item) => item.period === `${year}${type}`);
|
||||
return row && { plush: row.plush, figure: row.figure, mega: row.mega, other: row.other };
|
||||
}
|
||||
const categoryViews = buildViewsFromHalfYears(categoryPeriod);
|
||||
writeRuntimeViews('ppmt_category', ['period', 'plush', 'figure', 'mega', 'other'], categoryViews, {
|
||||
plush: 2, figure: 2, mega: 2, other: 2,
|
||||
});
|
||||
|
||||
// 资产负债表为时点指标:H2 是年末时点,而非“全年累计”。
|
||||
function buildSnapshotViews(base, headers, digits) {
|
||||
const source = readRuntimeRows(`${base}.csv`);
|
||||
const views = { all: [], annual: [], halfyear: [] };
|
||||
VIEW_YEARS.forEach((year) => {
|
||||
const h1 = source.find((row) => row.period === `${year}H1`);
|
||||
const yearEnd = source.find((row) => row.period === `${year}H2` || row.period === `${year}FY`);
|
||||
if (h1) {
|
||||
const row = { ...h1, period: `${year}H1` };
|
||||
views.all.push(row);
|
||||
views.halfyear.push(row);
|
||||
}
|
||||
if (yearEnd) {
|
||||
const halfyearRow = { ...yearEnd, period: `${year}H2` };
|
||||
views.all.push(halfyearRow);
|
||||
views.halfyear.push(halfyearRow);
|
||||
views.annual.push({ ...yearEnd, period: `${year}FY` });
|
||||
}
|
||||
});
|
||||
writeRuntimeViews(base, headers, views, digits);
|
||||
}
|
||||
|
||||
buildSnapshotViews('ppmt_assets', ['period', 'total_assets', 'cash_like', 'receivables', 'investment', 'operating_assets'], {
|
||||
total_assets: 2, cash_like: 2, receivables: 2, investment: 2, operating_assets: 2,
|
||||
});
|
||||
buildSnapshotViews('ppmt_liquidity_working_capital', ['period', 'cash', 'total_liabilities', 'net_working_capital'], {
|
||||
cash: 2, total_liabilities: 2, net_working_capital: 2,
|
||||
});
|
||||
|
||||
console.log('已生成第二批连续图表的全部、年度、半年 CSV。');
|
||||
|
||||
+1
-1
@@ -70,7 +70,7 @@ for (const entry of targets) {
|
||||
if (!csvPath.startsWith(`${ROOT}${path.sep}`)) throw new Error(`CSV 路径越过项目根目录:${entry.csv}`);
|
||||
const rows = parseCSV(fs.readFileSync(csvPath, 'utf8'));
|
||||
const chart = echarts.init(null, null, { renderer: 'svg', ssr: true, width: 1100, height: 720 });
|
||||
const option = getChartBuilder(entry.key)(rows, { width: 1100, height: 720 });
|
||||
const option = getChartBuilder(entry.key)(rows, { width: 1100, height: 720, view: entry.view || undefined });
|
||||
chart.setOption(option);
|
||||
const svg = chart.renderToSVGString();
|
||||
chart.dispose();
|
||||
|
||||
Reference in New Issue
Block a user