HomeAnalysisLine.vue 6.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290
  1. <script setup lang="ts">
  2. import type { EChartsOption, SeriesOption } from 'echarts'
  3. import type { LegendInfo } from '@/types/echarts/homeAnalysisChart'
  4. import { ref, shallowRef, watch } from 'vue'
  5. import { cloneDeep } from 'lodash'
  6. import { nextTick } from 'vue'
  7. import { debounceFunc } from '@/utils/common'
  8. import echarts from './index'
  9. import type { YAXisOption } from 'echarts/types/dist/shared'
  10. interface Props {
  11. loading: boolean
  12. legend: Array<LegendInfo>
  13. data: Array<any>
  14. xAxisDataField: string // 用于标明x轴数据字段是来自数据中的哪个字段
  15. }
  16. const props = defineProps<Props>()
  17. const MAX_INTERVAL = 5 // y轴最大间隔数
  18. /**
  19. * @description: 格式化tooltip
  20. * @param {*} params 表格数据
  21. * @return {*}
  22. */
  23. const formatterTooltip = (params: Object | Array<any>) => {
  24. if (Array.isArray(params)) {
  25. let circle = `<span style="display:inline-block;margin-right:5px;border-radius:50%;
  26. width:10px;height:10px;left:5px;background-color:`
  27. let result = `<span style="font-weight:bold;">${params[0].axisValueLabel}</span>`
  28. params.map((item, index) => {
  29. let data = `${circle}${props.legend[index].color}"></span>
  30. <span >
  31. <span style="display: inline-block; box-sizing: border-box;
  32. padding-right: 50px;">${props.legend[index].cnName}</span><span>${item['value']}<span>`
  33. result += `<br/>${data}`
  34. })
  35. return result
  36. } else {
  37. return `{b0}: {c0}<br />{b1}: {c1}`
  38. }
  39. }
  40. // 颜色
  41. // const colorList = [
  42. // '#00e070',
  43. // '#1495eb',
  44. // '#993333', // Dark Red
  45. // '#FFD700', // Bright Yellow
  46. // '#FF00FF', // Magenta
  47. // '#FFA500', // Orange
  48. // '#800080', // Dark Purple
  49. // '#A52A2A', // Brown
  50. // '#FF4500', // Orange Red
  51. // '#FF6347', // Tomato
  52. // '#B22222', // Firebrick
  53. // '#FF1493', // Deep Pink
  54. // ]
  55. let baseOptions: EChartsOption = {
  56. grid: {
  57. left: '3%',
  58. right: '4%',
  59. bottom: '3%',
  60. containLabel: true,
  61. },
  62. xAxis: {
  63. type: 'category',
  64. axisLabel: {
  65. showMaxLabel: true,
  66. },
  67. axisTick: {
  68. alignWithLabel: true,
  69. },
  70. },
  71. yAxis: {
  72. type: 'value',
  73. minInterval: 1,
  74. },
  75. tooltip: {
  76. trigger: 'axis',
  77. formatter: (params: Object | Array<any>) => formatterTooltip(params),
  78. },
  79. series: [],
  80. }
  81. let baseSeries: SeriesOption = {
  82. symbol: 'circle',
  83. symbolSize: 5,
  84. showSymbol: false,
  85. // itemStyle要在lineStyle前面,这个item会把line的样式覆盖掉,并且必须要有line,不然也会被item替换掉
  86. itemStyle: {
  87. color: 'rgb(255,255,255)',
  88. borderColor: '#1495eb', // symbol边框颜色
  89. borderWidth: 2, // symbol边框宽度
  90. },
  91. lineStyle: {
  92. color: '#1495eb',
  93. },
  94. name: '',
  95. data: [],
  96. type: 'line',
  97. smooth: false,
  98. }
  99. // 图表dom对象
  100. const chartRef = ref<HTMLElement>()
  101. // 图表实例
  102. const chartInstance = shallowRef<echarts.ECharts>()
  103. // 图表尺寸变化的观察者
  104. let chartSizeOb: ResizeObserver | null = null
  105. /**
  106. * @description: 图表和图表实例是否都存在
  107. * @return {*}
  108. */
  109. const isExistChart = (): boolean => {
  110. if (chartRef.value && chartInstance.value) return true
  111. return false
  112. }
  113. /**
  114. * @description: 更改图的加载状态
  115. * @param {*} newState
  116. * @return {*}
  117. */
  118. const changeChartsLoading = (newState: boolean) => {
  119. if (!isExistChart()) return
  120. if (newState) chartInstance.value!.showLoading()
  121. else chartInstance.value!.hideLoading()
  122. }
  123. /**
  124. * @description: 更新图表大小
  125. * @return {*}
  126. */
  127. const chartResize = () => {
  128. nextTick(() => {
  129. chartInstance.value?.resize()
  130. })
  131. }
  132. /**
  133. * @description: 初始化图表的信息
  134. * @return {*}
  135. */
  136. const initChart = () => {
  137. if (!chartRef.value) return
  138. chartInstance.value = echarts.init(chartRef.value)
  139. // 只监听window会导致当侧边栏缩放时,dom大小变化但无法resize
  140. // 所以需要使用ovserver对整个dom进行监听
  141. const debounceResize = debounceFunc(chartResize, 300)
  142. chartSizeOb = new ResizeObserver(debounceResize)
  143. chartSizeOb.observe(chartRef.value!)
  144. }
  145. /**
  146. * @description: 创建Yaxis
  147. * @return {*}
  148. */
  149. const createYAxis = (): Array<YAXisOption> => {
  150. let yAxis: Array<YAXisOption> = []
  151. props.legend.forEach(item => {
  152. yAxis.push({
  153. name: item.cnName,
  154. nameTextStyle: {
  155. fontWeight: 'bold',
  156. },
  157. alignTicks: true, // 开启轴线对齐
  158. type: 'value',
  159. minInterval: 1,
  160. })
  161. })
  162. return yAxis
  163. }
  164. /**
  165. * @description: 创建一条series数据
  166. * @return {*}
  167. */
  168. const createSeries = (): SeriesOption[] => {
  169. let finalSeriesData: SeriesOption[] = []
  170. // 图例中包含了图例的name和颜色,那么根据这个name,
  171. // 去data中找到对应字段的值,形成一个series需要的data
  172. props.legend.forEach((item, index) => {
  173. let newSeries: SeriesOption = cloneDeep(baseSeries)
  174. let cData = cloneDeep(props.data)
  175. // 取出一个legend的里面的name,找到data中每条数据对应字段的值
  176. let newData = cData.map((data: any) => data[item.value])
  177. newSeries.name = item.value
  178. newSeries.data = newData
  179. newSeries.yAxisIndex = index
  180. newSeries.lineStyle!.color = item.color // 这里是线的颜色
  181. newSeries.itemStyle!.color = item.color
  182. finalSeriesData.push(newSeries)
  183. })
  184. return finalSeriesData
  185. }
  186. /**
  187. * @description: 创建xAxis的data
  188. * @return {*}
  189. */
  190. const createXAxis = (): string[] => {
  191. if (props.data.length > 0) {
  192. let result: string[] = props.data.map(
  193. (item: any) => item[props.xAxisDataField],
  194. )
  195. return result
  196. }
  197. return []
  198. }
  199. /**
  200. * @description: 初始化选项
  201. * @return {*}
  202. */
  203. const initOptions = () => {
  204. if (!isExistChart()) return
  205. chartInstance.value!.clear()
  206. if (props.data.length > 0) {
  207. let finalSeriesData = createSeries()
  208. let xAxisData = createXAxis()
  209. let yAxisData = createYAxis()
  210. baseOptions.series = finalSeriesData
  211. baseOptions.yAxis = yAxisData
  212. ;(baseOptions.xAxis as any).data = xAxisData
  213. } else {
  214. baseOptions = {
  215. title: {
  216. text: '暂无数据',
  217. textStyle: {
  218. fontSize: 16,
  219. fontWeight: 'normal',
  220. },
  221. },
  222. }
  223. }
  224. chartInstance.value!.setOption(baseOptions)
  225. }
  226. /**
  227. * @description: 观察loading状态,如果变为false证明加载完毕,需要重新设置一次数据
  228. * @return {*}
  229. */
  230. watch(
  231. () => props.loading,
  232. (newState: boolean) => {
  233. changeChartsLoading(newState)
  234. if (newState === false) {
  235. if (!chartInstance.value) initChart()
  236. initOptions()
  237. }
  238. },
  239. { deep: true },
  240. )
  241. /**
  242. * @description: 当图例的信息变化的时候也需要重新设置选项
  243. * @return {*}
  244. */
  245. watch(
  246. () => props.legend,
  247. () => {
  248. initOptions()
  249. },
  250. {
  251. deep: true,
  252. },
  253. )
  254. </script>
  255. <template>
  256. <div class="chart" ref="chartRef" style="width: 100%; height: 365px"></div>
  257. </template>
  258. <style scoped></style>