charts.js 9.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265
  1. import { palette as C } from './data'
  2. const tipOpt = {
  3. backgroundColor: 'rgba(12,43,94,.94)',
  4. borderWidth: 0,
  5. textStyle: { color: '#fff', fontSize: 12 },
  6. padding: [8, 12],
  7. extraCssText: 'box-shadow:0 6px 18px rgba(12,43,94,.3);border-radius:6px;',
  8. }
  9. function axisStyle(opts = {}) {
  10. return {
  11. axisLine: { lineStyle: { color: '#E4E9F2' } },
  12. axisTick: { show: false },
  13. axisLabel: { color: C.mut, fontSize: 11 },
  14. splitLine: { lineStyle: { color: '#F2F5FA', type: 'dashed' } },
  15. ...opts,
  16. }
  17. }
  18. const sliceDate = (s) => s.map((x) => String(x[0]).slice(2, 7))
  19. const sliceDateFull = (s) => s.map((x) => String(x[0]).slice(0, 10))
  20. const upGrad = (color) => ({
  21. type: 'linear', x: 0, y: 0, x2: 0, y2: 1,
  22. colorStops: [{ offset: 0, color: '#3D7BE0' }, { offset: 1, color }],
  23. })
  24. const downGrad = (color) => ({
  25. type: 'linear', x: 0, y: 0, x2: 0, y2: 1,
  26. colorStops: [{ offset: 0, color: '#4FCB9A' }, { offset: 1, color }],
  27. })
  28. // ============== 月度累计值(柱图) ==============
  29. export function monthlyAccumulatedOption(series, { title = '累计值', color = C.brand, unit = '亿元' } = {}) {
  30. const cats = sliceDate(series)
  31. const values = series.map((x) => x[1])
  32. return {
  33. tooltip: { ...tipOpt, trigger: 'axis', axisPointer: { type: 'shadow' } },
  34. grid: { top: 18, left: 60, right: 24, bottom: 28 },
  35. xAxis: { type: 'category', data: cats, ...axisStyle({ axisLabel: { color: C.mut, fontSize: 10 } }) },
  36. yAxis: { type: 'value', name: unit, ...axisStyle() },
  37. series: [{
  38. name: title,
  39. type: 'bar',
  40. data: values,
  41. barWidth: 12,
  42. itemStyle: {
  43. color: (p) => (p.value >= 0 ? upGrad(color) : downGrad(C.down)),
  44. borderRadius: [3, 3, 0, 0],
  45. },
  46. label: { show: false },
  47. }],
  48. }
  49. }
  50. // ============== 月度累计同比(柱图,含 0 轴线) ==============
  51. export function monthlyYoYOption(series, { title = '累计同比', color = C.brand, unit = '%' } = {}) {
  52. const cats = sliceDate(series)
  53. const values = series.map((x) => x[1])
  54. return {
  55. tooltip: { ...tipOpt, trigger: 'axis', axisPointer: { type: 'shadow' } },
  56. grid: { top: 18, left: 50, right: 18, bottom: 28 },
  57. xAxis: { type: 'category', data: cats, ...axisStyle({ axisLabel: { color: C.mut, fontSize: 10 } }) },
  58. yAxis: { type: 'value', name: unit, ...axisStyle() },
  59. series: [{
  60. name: title,
  61. type: 'bar',
  62. data: values,
  63. barWidth: 12,
  64. itemStyle: {
  65. color: (p) => (p.value >= 0 ? upGrad(color) : downGrad(C.down)),
  66. borderRadius: [3, 3, 0, 0],
  67. },
  68. label: {
  69. show: true, position: 'top', fontSize: 10, color: C.mut,
  70. formatter: (p) => (typeof p.value === 'number' ? p.value.toFixed(2) : ''),
  71. },
  72. markLine: {
  73. symbol: ['none', 'none'],
  74. data: [{ yAxis: 0, lineStyle: { color: C.faint, type: 'dashed', width: 1 } }],
  75. label: { show: false },
  76. },
  77. }],
  78. }
  79. }
  80. // ============== 收入 vs 支出 双柱 ==============
  81. export function incomeVsExpenseOption(income, expense) {
  82. const cats = sliceDate(income)
  83. return {
  84. tooltip: { ...tipOpt, trigger: 'axis', axisPointer: { type: 'shadow' } },
  85. legend: {
  86. data: ['收入', '支出'],
  87. top: 4, textStyle: { color: C.mut, fontSize: 11 }, itemWidth: 14, itemHeight: 8,
  88. },
  89. grid: { top: 32, left: 60, right: 24, bottom: 28 },
  90. xAxis: { type: 'category', data: cats, ...axisStyle({ axisLabel: { color: C.mut, fontSize: 10 } }) },
  91. yAxis: { type: 'value', name: '亿元', ...axisStyle() },
  92. series: [
  93. {
  94. name: '收入',
  95. type: 'bar',
  96. data: income.map((x) => x[1]),
  97. barWidth: 8,
  98. itemStyle: { color: upGrad(C.brand), borderRadius: [2, 2, 0, 0] },
  99. },
  100. {
  101. name: '支出',
  102. type: 'bar',
  103. data: expense.map((x) => x[1]),
  104. barWidth: 8,
  105. itemStyle: { color: upGrad(C.warn), borderRadius: [2, 2, 0, 0] },
  106. },
  107. ],
  108. }
  109. }
  110. // ============== 政府性基金 收入 vs 支出 双柱 ==============
  111. export function fundIncVsExpOption(income, expense) {
  112. const cats = sliceDate(income)
  113. return {
  114. tooltip: { ...tipOpt, trigger: 'axis', axisPointer: { type: 'shadow' } },
  115. legend: {
  116. data: ['基金收入', '基金支出'],
  117. top: 4, textStyle: { color: C.mut, fontSize: 11 }, itemWidth: 14, itemHeight: 8,
  118. },
  119. grid: { top: 32, left: 60, right: 24, bottom: 28 },
  120. xAxis: { type: 'category', data: cats, ...axisStyle({ axisLabel: { color: C.mut, fontSize: 10 } }) },
  121. yAxis: { type: 'value', name: '亿元', ...axisStyle() },
  122. series: [
  123. {
  124. name: '基金收入',
  125. type: 'bar',
  126. data: income.map((x) => x[1]),
  127. barWidth: 8,
  128. itemStyle: { color: upGrad(C.purple), borderRadius: [2, 2, 0, 0] },
  129. },
  130. {
  131. name: '基金支出',
  132. type: 'bar',
  133. data: expense.map((x) => x[1]),
  134. barWidth: 8,
  135. itemStyle: { color: upGrad(C.brand), borderRadius: [2, 2, 0, 0] },
  136. },
  137. ],
  138. }
  139. }
  140. // ============== 赤字 / 专项债 年度(柱图) ==============
  141. export function yearlyBarOption(series, { title = '年度', color = C.warn, unit = '亿元' } = {}) {
  142. const cats = series.map((x) => String(x[0]).slice(0, 4))
  143. const values = series.map((x) => x[1])
  144. return {
  145. tooltip: { ...tipOpt, trigger: 'axis', axisPointer: { type: 'shadow' } },
  146. grid: { top: 18, left: 60, right: 24, bottom: 28 },
  147. xAxis: { type: 'category', data: cats, ...axisStyle({ axisLabel: { color: C.mut, fontSize: 10 } }) },
  148. yAxis: { type: 'value', name: unit, ...axisStyle() },
  149. series: [{
  150. name: title,
  151. type: 'bar',
  152. data: values,
  153. barWidth: 18,
  154. itemStyle: { color: upGrad(color), borderRadius: [3, 3, 0, 0] },
  155. label: {
  156. show: true, position: 'top', fontSize: 10, color: C.mut,
  157. formatter: (p) => (typeof p.value === 'number' ? p.value.toLocaleString() : ''),
  158. },
  159. }],
  160. }
  161. }
  162. // ============== 赤字率 折线 ==============
  163. export function yearlyLineOption(series, { title = '赤字率', color = C.up, unit = '%' } = {}) {
  164. const cats = series.map((x) => String(x[0]).slice(0, 4))
  165. const values = series.map((x) => x[1])
  166. return {
  167. tooltip: { ...tipOpt, trigger: 'axis' },
  168. grid: { top: 18, left: 50, right: 18, bottom: 28 },
  169. xAxis: { type: 'category', data: cats, ...axisStyle({ axisLabel: { color: C.mut, fontSize: 10 } }) },
  170. yAxis: { type: 'value', name: unit, ...axisStyle() },
  171. series: [{
  172. name: title,
  173. type: 'line',
  174. smooth: true,
  175. showSymbol: true,
  176. symbol: 'circle',
  177. symbolSize: 5,
  178. data: values,
  179. lineStyle: { color, width: 2.5 },
  180. itemStyle: { color },
  181. areaStyle: {
  182. type: 'linear', x: 0, y: 0, x2: 0, y2: 1,
  183. colorStops: [{ offset: 0, color: 'rgba(225,59,59,.16)' }, { offset: 1, color: 'rgba(225,59,59,0)' }],
  184. },
  185. label: {
  186. show: true, position: 'top', color: C.mut, fontSize: 10,
  187. formatter: (p) => (typeof p.value === 'number' ? p.value.toFixed(1) + '%' : ''),
  188. },
  189. }],
  190. }
  191. }
  192. // ============== 政府债余额 月度 折线 ==============
  193. export function monthlyLineOption(series, { title = '政府债余额', color = C.brand, unit = '万亿元' } = {}) {
  194. const cats = sliceDate(series)
  195. const values = series.map((x) => x[1])
  196. return {
  197. tooltip: { ...tipOpt, trigger: 'axis' },
  198. grid: { top: 18, left: 60, right: 24, bottom: 28 },
  199. xAxis: { type: 'category', data: cats, ...axisStyle({ axisLabel: { color: C.mut, fontSize: 10 } }) },
  200. yAxis: { type: 'value', name: unit, ...axisStyle() },
  201. series: [{
  202. name: title,
  203. type: 'line',
  204. smooth: true,
  205. showSymbol: false,
  206. symbol: 'circle',
  207. symbolSize: 4,
  208. data: values,
  209. lineStyle: { color, width: 2.5 },
  210. itemStyle: { color },
  211. areaStyle: {
  212. type: 'linear', x: 0, y: 0, x2: 0, y2: 1,
  213. colorStops: [{ offset: 0, color: 'rgba(21,82,208,.16)' }, { offset: 1, color: 'rgba(21,82,208,0)' }],
  214. },
  215. label: { show: false },
  216. }],
  217. }
  218. }
  219. // ============== 收支差 ==============
  220. export function gapOption(income, expense, { title = '财政收支差', color = C.up, unit = '亿元' } = {}) {
  221. const cats = sliceDate(income)
  222. const inc = new Map(income)
  223. const exp = new Map(expense)
  224. const gap = cats.map((_, i) => {
  225. const d = String(income[i][0])
  226. const iv = inc.get(d)?.[1]
  227. const ev = exp.get(d)?.[1]
  228. if (typeof iv !== 'number' || typeof ev !== 'number') return null
  229. return iv - ev
  230. })
  231. return {
  232. tooltip: { ...tipOpt, trigger: 'axis', axisPointer: { type: 'shadow' } },
  233. grid: { top: 18, left: 60, right: 24, bottom: 28 },
  234. xAxis: { type: 'category', data: cats, ...axisStyle({ axisLabel: { color: C.mut, fontSize: 10 } }) },
  235. yAxis: { type: 'value', name: unit, ...axisStyle() },
  236. series: [{
  237. name: title,
  238. type: 'bar',
  239. data: gap,
  240. barWidth: 12,
  241. itemStyle: {
  242. color: (p) => (p.value >= 0 ? upGrad(color) : downGrad(C.down)),
  243. borderRadius: [3, 3, 0, 0],
  244. },
  245. label: { show: false },
  246. markLine: {
  247. symbol: ['none', 'none'],
  248. data: [{ yAxis: 0, lineStyle: { color: C.faint, type: 'dashed', width: 1 } }],
  249. label: { show: false },
  250. },
  251. }],
  252. }
  253. }