Chart.vue 8.1 KB

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