chartModule.js 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467
  1. /*
  2. //y轴显示数据
  3. Data:{
  4. type:number, 0(实线) 1虚线(预测) 2(填充)
  5. name:string,
  6. yIndex:number, //yName的下标索引(第几个y坐标轴)
  7. data:string[],
  8. }
  9. props:{
  10. closeTime:boolen //是否超过7天展示时间
  11. yName:string || string[], //y轴名称 一个或多个
  12. xData:string[], //x轴时间数据
  13. dataList:Data[], //折线的数据列表
  14. currentType:string, //当前选择那个tabs 需要外部更新当前选中type时传入
  15. typeList:string[], //图表下的选择类型列表
  16. onChange:(id:number)=>{}
  17. }
  18. */
  19. import { Tabs } from 'antd';
  20. import dayjs from 'dayjs';
  21. import * as echarts from 'echarts';
  22. import { useEffect, useRef, useState } from 'react';
  23. import styles from './index.less';
  24. const { TabPane } = Tabs;
  25. export const handleExportClick = () => {
  26. const canvas = document.getElementsByTagName('canvas');
  27. if (canvas && canvas.length > 0) {
  28. return canvas[0].toDataURL('image/png');
  29. }
  30. return null;
  31. };
  32. // 图表模块
  33. const ChartModule = (props) => {
  34. const chartDomRef = useRef();
  35. const chartRef = useRef();
  36. const {
  37. yName,
  38. closeTime = false,
  39. xData,
  40. dataList,
  41. typeList = [],
  42. onChange,
  43. currentType,
  44. chartType = 'line',
  45. legend = {},
  46. } = props;
  47. const [curType, setCurType] = useState(currentType);
  48. useEffect(() => {
  49. chartRef.current = echarts.init(chartDomRef.current);
  50. window.addEventListener('resize', resetChart);
  51. return () => window.removeEventListener('resize', resetChart);
  52. }, []);
  53. useEffect(() => {
  54. if (!chartRef.current || !dataList || !chartType === 'gauge') {
  55. return;
  56. }
  57. const option = { ...defaultOption };
  58. if (legend) {
  59. option.legend.textStyle = {
  60. ...option.legend.textStyle,
  61. ...legend.textStyle,
  62. };
  63. if (legend.right) {
  64. option.legend.right = legend.right;
  65. }
  66. }
  67. let series = [];
  68. switch (chartType) {
  69. case 'gauge':
  70. series = option.series[dataList.type];
  71. series.data = [dataList];
  72. for (const key in option) {
  73. delete option[key];
  74. }
  75. option.series = [series];
  76. break;
  77. default:
  78. // 超过7天x轴显示年月日,不到7天显示时分
  79. if (!closeTime && xData && xData.length > 1) {
  80. const timeFormat =
  81. Math.abs(
  82. dayjs(xData[xData?.length - 1]).diff(dayjs(xData[0]), 'days'),
  83. ) + 1;
  84. if (timeFormat > 150 || chartType === 'bar') {
  85. option.xAxis.data = xData.map((item) =>
  86. dayjs(item).format('YYYY-MM'),
  87. );
  88. } else if (timeFormat >= 7) {
  89. option.xAxis.data = xData.map((item) =>
  90. dayjs(item).format('YYYY-MM-DD'),
  91. );
  92. } else {
  93. option.xAxis.data = xData.map((item) =>
  94. dayjs(item).format('YYYY-MM-DD HH:mm'),
  95. );
  96. option.xAxis.axisLabel.formatter = (value) => value?.split(' ')[1];
  97. }
  98. } else {
  99. option.xAxis.data = xData || [];
  100. }
  101. if (Array.isArray(yName)) {
  102. if (yName.length > 2) {
  103. option.grid.right = yName.length * 60;
  104. }
  105. option.yAxis = yName.map((item, index) => {
  106. return { ...option.yAxis[index], name: item };
  107. });
  108. option.series = dataList.map((item) => {
  109. return {
  110. ...option.series[item.type],
  111. name: item.name,
  112. data: item.data,
  113. yAxisIndex: item.yIndex || 0,
  114. };
  115. });
  116. } else {
  117. option.grid.right = 60;
  118. // 重置grid left 用于比对计算合适宽度
  119. option.grid.left = 70;
  120. option.yAxis = { ...option.yAxis[0], name: yName };
  121. option.series = dataList.map((item) => {
  122. item.data.forEach((dataItem) => {
  123. if (String(dataItem).length * 10 + 40 > option.grid.left) {
  124. option.grid.left = String(dataItem).length * 10 + 45;
  125. }
  126. });
  127. return {
  128. ...option.series[item.type],
  129. name: item.name,
  130. data: item.data,
  131. // barWidth: dataList.length >= 4 ? 8 : 20,
  132. };
  133. });
  134. }
  135. // 柱状图需要开启boundaryGap避免和Y轴重合
  136. if (chartType === 'bar') {
  137. option.xAxis.boundaryGap = true;
  138. delete option.legend.itemHeight;
  139. } else {
  140. option.xAxis.boundaryGap = false;
  141. option.legend.itemHeight = 0;
  142. }
  143. option.legend.data = dataList.map((item) => item.name);
  144. break;
  145. }
  146. // console.log(props, JSON.stringify(option));
  147. chartRef.current.clear();
  148. chartRef.current.setOption(option);
  149. chartRef.current.resize();
  150. // if (typeList?.length > 0) setCurType(typeList[0]);
  151. }, [yName, xData, dataList, typeList, chartType]);
  152. useEffect(() => {
  153. setCurType(currentType);
  154. }, [currentType]);
  155. const resetChart = () => {
  156. if (chartRef.current) chartRef.current.resize();
  157. };
  158. return (
  159. <div className={styles.content}>
  160. <div
  161. style={{
  162. width: chartType === 'gauge' ? '60%' : '100%',
  163. marginLeft: chartType === 'gauge' ? '20%' : '0',
  164. height: typeList?.length <= 0 ? '100%' : 'calc(100% - 0.57rem)',
  165. }}
  166. ref={chartDomRef}
  167. />
  168. {typeList?.length > 0 && (
  169. <Tabs
  170. activeKey={curType || typeList[0]}
  171. onChange={(type) => {
  172. setCurType(type);
  173. onChange(type);
  174. }}
  175. >
  176. {typeList.map((item) => (
  177. <TabPane tab={item} key={item} />
  178. ))}
  179. </Tabs>
  180. )}
  181. </div>
  182. );
  183. };
  184. export default ChartModule;
  185. const colors = [
  186. '#F5A623',
  187. '#4B9FEC',
  188. '#91cc75',
  189. '#5470c6',
  190. '#fac858',
  191. '#ee6666',
  192. '#73c0de',
  193. '#3ba272',
  194. '#fc8452',
  195. '#9a60b4',
  196. '#ea7ccc',
  197. ];
  198. const defaultOption = {
  199. color: colors,
  200. tooltip: {
  201. trigger: 'axis',
  202. textStyle: {
  203. fontSize: 24,
  204. // color:
  205. },
  206. },
  207. grid: {
  208. bottom: 30,
  209. left: 70,
  210. right: 50,
  211. },
  212. xAxis: {
  213. type: 'category',
  214. boundaryGap: false,
  215. axisTick: { show: false },
  216. nameTextStyle: {
  217. fontSize: '0.24rem',
  218. },
  219. axisLabel: {
  220. fontSize: '0.24rem',
  221. },
  222. data: [
  223. '00:00',
  224. '01:15',
  225. '02:30',
  226. '03:45',
  227. '05:00',
  228. '06:15',
  229. '07:30',
  230. '08:45',
  231. ],
  232. },
  233. yAxis: [
  234. {
  235. type: 'value',
  236. name: '000',
  237. top: 20,
  238. nameTextStyle: {
  239. fontSize: '0.24rem',
  240. // align: 'left',
  241. padding: [0, 0, 15, 0],
  242. },
  243. axisLabel: {
  244. fontSize: '0.2rem',
  245. },
  246. axisLine: {
  247. show: false,
  248. // lineStyle: {
  249. // color: colors[0],
  250. // },
  251. },
  252. splitLine: {
  253. lineStyle: {
  254. type: 'dashed',
  255. },
  256. },
  257. },
  258. {
  259. type: 'value',
  260. name: '111',
  261. top: 20,
  262. position: 'right',
  263. nameTextStyle: {
  264. fontSize: '0.24rem',
  265. align: 'center',
  266. padding: [0, 0, 15, 0],
  267. },
  268. axisLabel: {
  269. fontSize: '0.2rem',
  270. },
  271. axisLine: {
  272. show: true,
  273. lineStyle: {
  274. color: colors[7],
  275. },
  276. },
  277. splitLine: {
  278. lineStyle: {
  279. type: 'dashed',
  280. },
  281. },
  282. },
  283. {
  284. type: 'value',
  285. name: '222',
  286. top: 20,
  287. position: 'right',
  288. offset: 80,
  289. nameTextStyle: {
  290. fontSize: '0.24rem',
  291. align: 'left',
  292. padding: [0, 0, 15, 0],
  293. },
  294. axisLabel: {
  295. fontSize: '0.2rem',
  296. },
  297. axisLine: {
  298. show: true,
  299. lineStyle: {
  300. color: colors[8],
  301. },
  302. },
  303. splitLine: {
  304. lineStyle: {
  305. type: 'dashed',
  306. },
  307. },
  308. },
  309. ],
  310. series: [
  311. {
  312. data: [820, 932, 901, 934, 1290, 1330, 1320],
  313. type: 'line',
  314. name: '进水水量',
  315. yAxisIndex: 0,
  316. smooth: 'true',
  317. // itemStyle:{
  318. // color:'#be7bbe',
  319. // },
  320. showSymbol: false, // 不展示拐点圆圈
  321. },
  322. {
  323. data: [130, 125, 828, 743, 1100],
  324. name: '预测出水量',
  325. yAxisIndex: 0,
  326. type: 'line',
  327. smooth: 'true',
  328. lineStyle: {
  329. normal: {
  330. width: 4,
  331. type: 'dashed',
  332. },
  333. },
  334. itemStyle: {
  335. color: '#be7bbe',
  336. },
  337. showSymbol: false, // 不展示拐点圆圈
  338. },
  339. {
  340. data: [820, 772, 901, 934, 1290, 1120, 1320],
  341. name: '实际出水量',
  342. yAxisIndex: 0,
  343. type: 'line',
  344. smooth: 'true',
  345. showSymbol: false, // 不展示拐点圆圈
  346. itemStyle: {
  347. color: '#50A3C8',
  348. },
  349. areaStyle: {
  350. origin: 'number',
  351. color: new echarts.graphic.LinearGradient(0, 1, 0, 0, [
  352. {
  353. offset: 0,
  354. color: 'rgba(31,114,150,1)',
  355. },
  356. {
  357. offset: 1,
  358. color: 'rgba(31,114,150,0.3)',
  359. },
  360. ]),
  361. },
  362. },
  363. {
  364. data: [120, 200, 150, 80, 70, 110, 130],
  365. name: '实际出水量',
  366. type: 'bar',
  367. // barGap: 0.2,
  368. // barWidth: 30,
  369. },
  370. {
  371. data: [
  372. {
  373. value: 19,
  374. name: '负荷率',
  375. },
  376. ],
  377. name: '负荷率',
  378. type: 'gauge',
  379. startAngle: 180,
  380. endAngle: 0,
  381. radius: '90%',
  382. min: 0,
  383. max: 100,
  384. splitNumber: 10,
  385. center: ['50%', '65%'],
  386. axisLine: {
  387. lineStyle: {
  388. width: 40,
  389. color: [
  390. [0.33, '#a4f428'],
  391. [0.66, '#f6a842'],
  392. [1, '#e02b42'],
  393. ],
  394. },
  395. },
  396. axisPointer: {
  397. show: true,
  398. },
  399. pointer: {
  400. icon: 'path://M12.8,0.7l12,40.1H0.7L12.8,0.7z',
  401. length: '50%',
  402. width: 8,
  403. offsetCenter: [0, 0],
  404. itemStyle: {
  405. color: 'auto',
  406. },
  407. },
  408. axisTick: {
  409. show: false,
  410. },
  411. splitLine: {
  412. show: false,
  413. },
  414. axisLabel: {
  415. show: false,
  416. },
  417. title: {
  418. offsetCenter: [0, '25%'],
  419. fontSize: '0.24rem',
  420. },
  421. detail: {
  422. show: false,
  423. },
  424. },
  425. ],
  426. legend: {
  427. // 图例配置
  428. // icon:'arrow',
  429. // width:'2',
  430. itemHeight: 0, // 远点宽度为0不显示原点
  431. // right: '30%',
  432. data: ['进水水量', '预测出水量', '实际出水量'],
  433. lineStyle: {},
  434. textStyle: {
  435. fontSize: '0.24rem',
  436. },
  437. },
  438. // toolbox: {
  439. // show: true,
  440. // feature: {
  441. // // dataZoom: {
  442. // // yAxisIndex: 'none'
  443. // // },
  444. // // dataView: { readOnly: false },
  445. // // magicType: { type: ['line', 'bar'] },
  446. // restore: {},
  447. // saveAsImage: {},
  448. // },
  449. // },
  450. };