智慧水务管理系统 - 精河县供水工程综合管理平台

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510
  1. /**
  2. * 图表绘制模块
  3. * 使用ECharts绘制各种图表
  4. */
  5. class ChartRenderer {
  6. constructor() {
  7. this.charts = new Map(); // 存储图表实例
  8. }
  9. // 创建图表容器
  10. createChartContainer(chartId, title) {
  11. const container = document.createElement('div');
  12. container.className = 'chart-container';
  13. container.innerHTML = `
  14. <h3>${title}</h3>
  15. <div class="chart" id="chart-${chartId}"></div>
  16. `;
  17. return container;
  18. }
  19. // 初始化图表
  20. initChart(chartId, options = {}) {
  21. const chartDom = document.getElementById(`chart-${chartId}`);
  22. if (!chartDom) {
  23. console.error(`图表容器 ${chartId} 不存在`);
  24. return null;
  25. }
  26. // 清除之前的图表实例
  27. if (this.charts.has(chartId)) {
  28. this.charts.get(chartId).dispose();
  29. }
  30. const chart = echarts.init(chartDom);
  31. this.charts.set(chartId, chart);
  32. // 应用配置
  33. chart.setOption(options);
  34. // 响应式处理
  35. const resizeHandler = () => {
  36. chart.resize();
  37. };
  38. window.addEventListener('resize', resizeHandler);
  39. // 保存resize处理函数以便后续清理
  40. chartDom._resizeHandler = resizeHandler;
  41. return chart;
  42. }
  43. // 销毁图表
  44. destroyChart(chartId) {
  45. const chart = this.charts.get(chartId);
  46. if (chart) {
  47. chart.dispose();
  48. this.charts.delete(chartId);
  49. // 移除resize监听器
  50. const chartDom = document.getElementById(`chart-${chartId}`);
  51. if (chartDom && chartDom._resizeHandler) {
  52. window.removeEventListener('resize', chartDom._resizeHandler);
  53. }
  54. }
  55. }
  56. // 绘制折线图
  57. renderLineChart(chartId, data, options = {}) {
  58. const defaultOptions = {
  59. title: {
  60. text: options.title || '趋势图',
  61. left: 'center'
  62. },
  63. tooltip: {
  64. trigger: 'axis',
  65. axisPointer: {
  66. type: 'cross'
  67. }
  68. },
  69. legend: {
  70. data: options.legendData || ['数据'],
  71. top: 'bottom'
  72. },
  73. xAxis: {
  74. type: 'category',
  75. data: data.map(item => item[options.xAxis || 'timestamp']),
  76. axisLabel: {
  77. rotate: 45
  78. }
  79. },
  80. yAxis: {
  81. type: 'value',
  82. name: options.yAxisName || '数值'
  83. },
  84. series: options.seriesData || [{
  85. name: '数据',
  86. type: 'line',
  87. data: data.map(item => item.value),
  88. smooth: true,
  89. symbol: 'circle',
  90. symbolSize: 6,
  91. lineStyle: {
  92. width: 2
  93. },
  94. areaStyle: {
  95. opacity: 0.1
  96. }
  97. }],
  98. grid: {
  99. left: '3%',
  100. right: '4%',
  101. bottom: '15%',
  102. containLabel: true
  103. }
  104. };
  105. const mergedOptions = this.mergeOptions(defaultOptions, options);
  106. this.initChart(chartId, mergedOptions);
  107. }
  108. // 绘制柱状图
  109. renderBarChart(chartId, data, options = {}) {
  110. const defaultOptions = {
  111. title: {
  112. text: options.title || '柱状图',
  113. left: 'center'
  114. },
  115. tooltip: {
  116. trigger: 'axis',
  117. axisPointer: {
  118. type: 'shadow'
  119. }
  120. },
  121. xAxis: {
  122. type: 'category',
  123. data: data.map(item => item[options.xAxis || 'category']),
  124. axisLabel: {
  125. rotate: 45
  126. }
  127. },
  128. yAxis: {
  129. type: 'value',
  130. name: options.yAxisName || '数值'
  131. },
  132. series: [{
  133. name: options.seriesName || '数据',
  134. type: 'bar',
  135. data: data.map(item => item.value),
  136. itemStyle: {
  137. color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
  138. { offset: 0, color: '#83bff6' },
  139. { offset: 0.5, color: '#188df0' },
  140. { offset: 1, color: '#188df0' }
  141. ])
  142. },
  143. emphasis: {
  144. itemStyle: {
  145. color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
  146. { offset: 0, color: '#2378f7' },
  147. { offset: 0.7, color: '#2378f7' },
  148. { offset: 1, color: '#83bff6' }
  149. ])
  150. }
  151. }
  152. }],
  153. grid: {
  154. left: '3%',
  155. right: '4%',
  156. bottom: '15%',
  157. containLabel: true
  158. }
  159. };
  160. const mergedOptions = this.mergeOptions(defaultOptions, options);
  161. this.initChart(chartId, mergedOptions);
  162. }
  163. // 绘制饼图
  164. renderPieChart(chartId, data, options = {}) {
  165. const defaultOptions = {
  166. title: {
  167. text: options.title || '饼图',
  168. left: 'center'
  169. },
  170. tooltip: {
  171. trigger: 'item',
  172. formatter: '{b}: {c} ({d}%)'
  173. },
  174. legend: {
  175. orient: 'vertical',
  176. left: 'left',
  177. top: 'middle'
  178. },
  179. series: [{
  180. name: options.seriesName || '数据',
  181. type: 'pie',
  182. radius: options.radius || ['40%', '70%'],
  183. avoidLabelOverlap: false,
  184. itemStyle: {
  185. borderRadius: 10,
  186. borderColor: '#fff',
  187. borderWidth: 2
  188. },
  189. label: {
  190. show: false,
  191. position: 'center'
  192. },
  193. emphasis: {
  194. label: {
  195. show: true,
  196. fontSize: '20',
  197. fontWeight: 'bold'
  198. }
  199. },
  200. labelLine: {
  201. show: false
  202. },
  203. data: data.map(item => ({
  204. name: item.name,
  205. value: item.value
  206. }))
  207. }]
  208. };
  209. const mergedOptions = this.mergeOptions(defaultOptions, options);
  210. this.initChart(chartId, mergedOptions);
  211. }
  212. // 绘制散点图
  213. renderScatterChart(chartId, data, options = {}) {
  214. const defaultOptions = {
  215. title: {
  216. text: options.title || '散点图',
  217. left: 'center'
  218. },
  219. tooltip: {
  220. trigger: 'item'
  221. },
  222. xAxis: {
  223. type: 'value',
  224. name: options.xAxisName || 'X轴'
  225. },
  226. yAxis: {
  227. type: 'value',
  228. name: options.yAxisName || 'Y轴'
  229. },
  230. series: [{
  231. name: options.seriesName || '数据',
  232. type: 'scatter',
  233. data: data.map(item => [item.x, item.y]),
  234. symbolSize: function (data) {
  235. return Math.sqrt(data[2]) || 10;
  236. },
  237. itemStyle: {
  238. color: new echarts.graphic.RadialGradient(0.5, 0.5, 1, [
  239. { offset: 0, color: 'rgba(58,77,233,0.8)' },
  240. { offset: 1, color: 'rgba(58,77,233,0.1)' }
  241. ])
  242. }
  243. }]
  244. };
  245. const mergedOptions = this.mergeOptions(defaultOptions, options);
  246. this.initChart(chartId, mergedOptions);
  247. }
  248. // 绘制面积图
  249. renderAreaChart(chartId, data, options = {}) {
  250. const defaultOptions = {
  251. title: {
  252. text: options.title || '面积图',
  253. left: 'center'
  254. },
  255. tooltip: {
  256. trigger: 'axis',
  257. axisPointer: {
  258. type: 'cross'
  259. }
  260. },
  261. legend: {
  262. data: options.legendData || ['数据'],
  263. top: 'bottom'
  264. },
  265. xAxis: {
  266. type: 'category',
  267. data: data.map(item => item[options.xAxis || 'timestamp']),
  268. axisLabel: {
  269. rotate: 45
  270. }
  271. },
  272. yAxis: {
  273. type: 'value',
  274. name: options.yAxisName || '数值'
  275. },
  276. series: [{
  277. name: options.seriesName || '数据',
  278. type: 'line',
  279. data: data.map(item => item.value),
  280. smooth: true,
  281. symbol: 'none',
  282. areaStyle: {
  283. opacity: 0.3,
  284. color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
  285. { offset: 0, color: 'rgba(128, 255, 165, 0.3)' },
  286. { offset: 1, color: 'rgba(1, 191, 236, 0.1)' }
  287. ])
  288. }
  289. }],
  290. grid: {
  291. left: '3%',
  292. right: '4%',
  293. bottom: '15%',
  294. containLabel: true
  295. }
  296. };
  297. const mergedOptions = this.mergeOptions(defaultOptions, options);
  298. this.initChart(chartId, mergedOptions);
  299. }
  300. // 绘制仪表盘
  301. renderGaugeChart(chartId, data, options = {}) {
  302. const defaultOptions = {
  303. title: {
  304. text: options.title || '仪表盘',
  305. left: 'center'
  306. },
  307. tooltip: {
  308. formatter: '{b}: {c}%'
  309. },
  310. series: [{
  311. name: options.seriesName || '数据',
  312. type: 'gauge',
  313. min: options.min || 0,
  314. max: options.max || 100,
  315. splitNumber: options.splitNumber || 10,
  316. radius: options.radius || '80%',
  317. axisLine: {
  318. lineStyle: {
  319. width: 10,
  320. color: [
  321. [0.3, '#ff6e76'],
  322. [0.7, '#fddd60'],
  323. [1, '#7cffb2']
  324. ]
  325. }
  326. },
  327. pointer: {
  328. itemStyle: {
  329. color: 'auto'
  330. }
  331. },
  332. axisTick: {
  333. distance: -15,
  334. length: 8,
  335. lineStyle: {
  336. color: '#999',
  337. width: 2
  338. }
  339. },
  340. splitLine: {
  341. distance: -20,
  342. length: 15,
  343. lineStyle: {
  344. color: '#999',
  345. width: 3
  346. }
  347. },
  348. axisLabel: {
  349. color: '#999',
  350. distance: 30,
  351. fontSize: 12
  352. },
  353. detail: {
  354. valueAnimation: true,
  355. fontSize: 20,
  356. offsetCenter: [0, '60%']
  357. },
  358. data: [{
  359. value: data.value || 0,
  360. name: options.name || '数据'
  361. }]
  362. }]
  363. };
  364. const mergedOptions = this.mergeOptions(defaultOptions, options);
  365. this.initChart(chartId, mergedOptions);
  366. }
  367. // 绘制表格
  368. renderTableChart(chartId, data, options = {}) {
  369. const defaultOptions = {
  370. title: {
  371. text: options.title || '表格',
  372. left: 'center'
  373. },
  374. tooltip: {
  375. show: false
  376. },
  377. grid: {
  378. top: '10%',
  379. left: '3%',
  380. right: '4%',
  381. bottom: '3%',
  382. containLabel: true
  383. },
  384. xAxis: {
  385. type: 'category',
  386. data: data.map(item => item[options.xAxis || 'name']),
  387. axisLabel: {
  388. interval: 0,
  389. rotate: 45
  390. }
  391. },
  392. yAxis: {
  393. type: 'value'
  394. },
  395. series: [{
  396. name: options.seriesName || '数据',
  397. type: 'bar',
  398. data: data.map(item => item.value),
  399. itemStyle: {
  400. color: '#5470c6'
  401. }
  402. }]
  403. };
  404. const mergedOptions = this.mergeOptions(defaultOptions, options);
  405. this.initChart(chartId, defaultOptions); // 表格不需要复杂配置
  406. }
  407. // 合并配置选项
  408. mergeOptions(defaultOptions, customOptions) {
  409. return {
  410. ...defaultOptions,
  411. ...customOptions,
  412. series: customOptions.series || defaultOptions.series
  413. };
  414. }
  415. // 根据图表类型渲染图表
  416. renderChartByType(chartId, chartData) {
  417. const { chart_type: chartType, data, options = {} } = chartData;
  418. switch (chartType) {
  419. case 'line':
  420. this.renderLineChart(chartId, data, options);
  421. break;
  422. case 'bar':
  423. this.renderBarChart(chartId, data, options);
  424. break;
  425. case 'pie':
  426. this.renderPieChart(chartId, data, options);
  427. break;
  428. case 'scatter':
  429. this.renderScatterChart(chartId, data, options);
  430. break;
  431. case 'area':
  432. this.renderAreaChart(chartId, data, options);
  433. break;
  434. case 'gauge':
  435. this.renderGaugeChart(chartId, data, options);
  436. break;
  437. case 'table':
  438. this.renderTableChart(chartId, data, options);
  439. break;
  440. default:
  441. // 默认使用折线图
  442. this.renderLineChart(chartId, data, options);
  443. }
  444. }
  445. // 批量渲染图表
  446. renderCharts(chartsData) {
  447. chartsData.forEach(chartData => {
  448. const { chart_id: chartId, chart_name: chartName, chart_type: chartType, data, options = {} } = chartData;
  449. const chartContainer = this.createChartContainer(chartId, chartName);
  450. document.getElementById('chartsContainer').appendChild(chartContainer);
  451. this.renderChartByType(chartId, {
  452. chart_type: chartType,
  453. data,
  454. options: {
  455. ...options,
  456. title: chartName
  457. }
  458. });
  459. });
  460. }
  461. // 清理所有图表
  462. clearAllCharts() {
  463. this.charts.forEach((chart, chartId) => {
  464. this.destroyChart(chartId);
  465. });
  466. const chartsContainer = document.getElementById('chartsContainer');
  467. if (chartsContainer) {
  468. chartsContainer.innerHTML = '';
  469. }
  470. }
  471. }
  472. // 创建全局图表渲染器实例
  473. const chartRenderer = new ChartRenderer();
  474. export default chartRenderer;