Chart.vue 8.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256
  1. <script lang="ts" setup>
  2. import {getParametricEquation} from "@/utils/chart";
  3. import * as echarts from "echarts";
  4. import 'echarts-gl' // 3d图表库
  5. import {waterQualitys} from "@/utils/unit.js";
  6. import {nextTick, onMounted, onUnmounted, ref} from "vue";
  7. defineExpose({loadChart, carousel, bindListen, markLineChange})
  8. const chartRef = ref(null)
  9. let chart = null
  10. let timer = undefined
  11. let selectedIndex = ''
  12. let hoveredIndex = ''
  13. async function loadChart(option, type = null) {
  14. if (!chart) {
  15. await nextTick(); // 确保DOM已经渲染完成
  16. chart = echarts.init(chartRef.value);
  17. }
  18. chart.setOption(option, true);
  19. setTimeout(() => chart.resize(), 1000)
  20. if (type && type === 'bindListen') {
  21. bindListen();
  22. }
  23. }
  24. function markLineChange(index) {
  25. if (chart) {
  26. const option = chart.getOption()
  27. option.series[0].markLine.data = [{xAxis: index}]
  28. chart.setOption(option, true);
  29. }
  30. }
  31. function reloadChart() {
  32. const checkAndResize = () => {
  33. if (chart) {
  34. chart.resize();
  35. } else {
  36. setTimeout(checkAndResize, 1000);
  37. }
  38. };
  39. checkAndResize();
  40. }
  41. /* 轮播 */
  42. function carousel(timeout = 5000, yAxisChange = false) {
  43. const checkAndCarousel = () => {
  44. if (chart) {
  45. // 获取legend的data
  46. const legendData = chart.getOption().legend[0].data
  47. // 首次总是从0开始的
  48. let i = 0
  49. // 开始轮播
  50. timer = setInterval(() => {
  51. // 激活
  52. chart.dispatchAction({
  53. type: 'legendToggleSelect',
  54. name: legendData[++i % legendData.length]
  55. })
  56. }, 10000)
  57. if (yAxisChange) {
  58. chart.on('legendselectchanged', function (params) {
  59. let selected = undefined;
  60. for (let key in params.selected) {
  61. if (params.selected[key] === true) {
  62. selected = key;
  63. break
  64. }
  65. }
  66. let field = waterQualitys.find(i => i.label === selected)
  67. if (field) {
  68. chart.setOption({yAxis: {name: field.unit,}});
  69. }
  70. });
  71. }
  72. } else {
  73. setTimeout(checkAndCarousel, 1000);
  74. }
  75. }
  76. checkAndCarousel();
  77. }
  78. // 监听鼠标事件,实现饼图选中效果(单选),近似实现高亮(放大)效果。
  79. function bindListen() {
  80. // 监听点击事件,实现选中效果(单选)
  81. chart.on('click', (params) => {
  82. const option = chart.getOption()
  83. // 从 option.series 中读取重新渲染扇形所需的参数,将是否选中取反。
  84. const isSelected = !option.series[params.seriesIndex].pieStatus.selected
  85. const isHovered = option.series[params.seriesIndex].pieStatus.hovered
  86. const k = option.series[params.seriesIndex].pieStatus.k
  87. const startRatio = option.series[params.seriesIndex].pieData.startRatio
  88. const endRatio = option.series[params.seriesIndex].pieData.endRatio
  89. // 如果之前选中过其他扇形,将其取消选中(对 option 更新)
  90. if (selectedIndex !== '' && selectedIndex !== params.seriesIndex) {
  91. option.series[selectedIndex].parametricEquation = getParametricEquation(
  92. option.series[selectedIndex].pieData.startRatio,
  93. option.series[selectedIndex].pieData.endRatio,
  94. false,
  95. false,
  96. k,
  97. option.series[selectedIndex].pieData.value
  98. )
  99. option.series[selectedIndex].pieStatus.selected = false
  100. }
  101. // 对当前点击的扇形,执行选中/取消选中操作(对 option 更新)
  102. option.series[params.seriesIndex].parametricEquation = getParametricEquation(
  103. startRatio,
  104. endRatio,
  105. isSelected,
  106. isHovered,
  107. k,
  108. option.series[params.seriesIndex].pieData.value
  109. )
  110. option.series[params.seriesIndex].pieStatus.selected = isSelected
  111. // 如果本次是选中操作,记录上次选中的扇形对应的系列号 seriesIndex
  112. selectedIndex = isSelected ? params.seriesIndex : null
  113. // 使用更新后的 option,渲染图表
  114. chart.setOption(option)
  115. })
  116. // 监听 mouseover,近似实现高亮(放大)效果
  117. chart.on('mouseover', (params) => {
  118. const option = chart.getOption()
  119. // 准备重新渲染扇形所需的参数
  120. let isSelected
  121. let isHovered
  122. let startRatio
  123. let endRatio
  124. let k
  125. // 如果触发 mouseover 的扇形当前已高亮,则不做操作
  126. if (hoveredIndex === params.seriesIndex) {
  127. // 否则进行高亮及必要的取消高亮操作
  128. } else {
  129. // 如果当前有高亮的扇形,取消其高亮状态(对 option 更新)
  130. if (hoveredIndex !== '') {
  131. // 从 option.series 中读取重新渲染扇形所需的参数,将是否高亮设置为 false。
  132. isSelected = option.series[hoveredIndex].pieStatus.selected
  133. isHovered = false
  134. startRatio = option.series[hoveredIndex].pieData.startRatio
  135. endRatio = option.series[hoveredIndex].pieData.endRatio
  136. k = option.series[hoveredIndex].pieStatus.k
  137. // 对当前点击的扇形,执行取消高亮操作(对 option 更新)
  138. option.series[
  139. hoveredIndex
  140. ].parametricEquation = getParametricEquation(
  141. startRatio,
  142. endRatio,
  143. isSelected,
  144. isHovered,
  145. k,
  146. option.series[hoveredIndex].pieData.value
  147. )
  148. option.series[hoveredIndex].pieStatus.hovered = isHovered
  149. // 将此前记录的上次选中的扇形对应的系列号 seriesIndex 清空
  150. hoveredIndex = ''
  151. }
  152. // 如果触发 mouseover 的扇形不是透明圆环,将其高亮(对 option 更新)
  153. if (
  154. params.seriesName !== 'mouseoutSeries' &&
  155. params.seriesName !== 'pie2d'
  156. ) {
  157. // 从 option.series 中读取重新渲染扇形所需的参数,将是否高亮设置为 true。
  158. isSelected =
  159. option.series[params.seriesIndex].pieStatus.selected
  160. isHovered = true
  161. startRatio =
  162. option.series[params.seriesIndex].pieData.startRatio
  163. endRatio = option.series[params.seriesIndex].pieData.endRatio
  164. k = option.series[params.seriesIndex].pieStatus.k
  165. // 对当前点击的扇形,执行高亮操作(对 option 更新)
  166. option.series[
  167. params.seriesIndex
  168. ].parametricEquation = getParametricEquation(
  169. startRatio,
  170. endRatio,
  171. isSelected,
  172. isHovered,
  173. k,
  174. option.series[params.seriesIndex].pieData.value * 1.3
  175. )
  176. option.series[
  177. params.seriesIndex
  178. ].pieStatus.hovered = isHovered
  179. // 记录上次高亮的扇形对应的系列号 seriesIndex
  180. hoveredIndex = params.seriesIndex
  181. }
  182. // 使用更新后的 option,渲染图表
  183. chart.setOption(option)
  184. }
  185. })
  186. // 修正取消高亮失败的 bug
  187. chart.on('globalout', () => {
  188. const option = chart.getOption()
  189. // 准备重新渲染扇形所需的参数
  190. let isSelected
  191. let isHovered
  192. let startRatio
  193. let endRatio
  194. let k
  195. if (hoveredIndex !== '') {
  196. // 从 option.series 中读取重新渲染扇形所需的参数,将是否高亮设置为 true。
  197. isSelected = option.series[hoveredIndex].pieStatus.selected
  198. isHovered = false
  199. k = option.series[hoveredIndex].pieStatus.k
  200. startRatio = option.series[hoveredIndex].pieData.startRatio
  201. endRatio = option.series[hoveredIndex].pieData.endRatio
  202. // 对当前点击的扇形,执行取消高亮操作(对 option 更新)
  203. option.series[
  204. hoveredIndex
  205. ].parametricEquation = getParametricEquation(
  206. startRatio,
  207. endRatio,
  208. isSelected,
  209. isHovered,
  210. k,
  211. option.series[hoveredIndex].pieData.value
  212. )
  213. option.series[hoveredIndex].pieStatus.hovered = isHovered
  214. // 将此前记录的上次选中的扇形对应的系列号 seriesIndex 清空
  215. hoveredIndex = ''
  216. }
  217. // 使用更新后的 option,渲染图表
  218. chart.setOption(option)
  219. })
  220. }
  221. onMounted(() => chartRef.value?.addEventListener("resize", reloadChart))
  222. onUnmounted(() => {
  223. if (timer) {
  224. clearInterval(timer)
  225. timer = null
  226. }
  227. chartRef.value?.removeEventListener("resize", reloadChart)
  228. if (chart && chart.dispose) {
  229. chart.dispose();
  230. }
  231. })
  232. </script>
  233. <template>
  234. <div ref="chartRef" class="chart-wrapper"></div>
  235. </template>
  236. <style lang="scss" scoped>
  237. .chart-wrapper {
  238. width: 100%;
  239. height: 100%;
  240. }
  241. </style>