DoughnutCharts.vue 9.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414
  1. <template>
  2. <div class="doughnut_charts">
  3. <div class="operation" v-if="props.showLegend && props.showLegendOperationAll">
  4. <el-checkbox
  5. :model-value="showAll"
  6. :indeterminate="indeterminate"
  7. @change="(val)=>HandleCheckAllOrNot(val)"
  8. >
  9. 显示全部
  10. </el-checkbox>
  11. </div>
  12. <div class="render_doughnut_charts" :style="{height:chartHeight}" ref="chartsRef">
  13. </div>
  14. </div>
  15. </template>
  16. <script setup lang="ts">
  17. import {ref,onMounted, watch,computed} from 'vue'
  18. import * as echarts from 'echarts';
  19. import type { PropType } from 'vue'
  20. interface PieItm {
  21. name:String,
  22. value:String
  23. }
  24. interface SeiresLabelFormatter {
  25. formatter:Function | String,
  26. rich:Object
  27. }
  28. const props = defineProps({
  29. chartHeight:{
  30. type:String,
  31. default:'270px'
  32. },
  33. //饼图数据
  34. chartData:{
  35. required:true,
  36. type:Array<PieItm>
  37. },
  38. // 饼图扇形弧度
  39. rotateAngle:{
  40. type:[Number,null],
  41. default:360
  42. },
  43. // 是否显示图例
  44. showLegend:{
  45. type:Boolean,
  46. default:true
  47. },
  48. // 图例的位置
  49. legendTop:{
  50. type:[Number,String],
  51. default:'auto'
  52. },
  53. legendBottom:{
  54. type:[Number,String],
  55. default:'auto'
  56. },
  57. legendLeft:{
  58. type:[Number,String],
  59. default:'auto'
  60. },
  61. legendRight:{
  62. type:[Number,String],
  63. default:'auto'
  64. },
  65. // 图例布局朝向
  66. legendOrient:{
  67. type:String,
  68. default:'vertical'
  69. },
  70. // 是否一键隐藏图例或展示图例
  71. showLegendOperationAll:{
  72. type:Boolean,
  73. default:false
  74. },
  75. // 图例展示文本
  76. legendFormatter:{
  77. type:Function
  78. },
  79. // 扇形图位置
  80. pieTop:{
  81. type:[Number,String],
  82. default:0
  83. },
  84. pieLeft:{
  85. type:[Number,String],
  86. default:0
  87. },
  88. pieBottom:{
  89. type:[Number,String],
  90. default:0
  91. },
  92. pieRight:{
  93. type:[Number,String],
  94. default:0
  95. },
  96. pieRadius:{
  97. type:Array,
  98. required:true
  99. },
  100. //展示扇区信息
  101. showSeriesLabel:{
  102. type:Boolean,
  103. default:true
  104. },
  105. // 扇形的信息展示位置
  106. seriesLabelPosition:{
  107. type:String,
  108. },
  109. // 扇区信息的格式
  110. seiresLabelFormatter: {
  111. type:Object as PropType<SeiresLabelFormatter>
  112. },
  113. // 展示鼠标悬浮提示
  114. showTooltip:{
  115. type:Boolean,
  116. default:true,
  117. },
  118. // 鼠标悬浮提示 展示的位置
  119. tooltipPosition:{
  120. type:[Array,Function],
  121. default:()=>{
  122. return function (point, params, dom, rect, size) {
  123. // 固定在鼠标右侧
  124. return [point[0], point[1]];
  125. }
  126. }
  127. },
  128. // 图表鼠标悬浮提示信息
  129. tooltipFormatter:{
  130. type:[Function,String]
  131. },
  132. //页面动态变化的标识
  133. randomIndex:{
  134. type:Number,
  135. required:true
  136. },
  137. })
  138. const chartsRef = ref(null)
  139. const myChart = ref()
  140. /**
  141. * 图例全选/取消全选
  142. */
  143. const showAll = ref(true)
  144. const indeterminate = ref(false)
  145. const selectedLegendList = ref([])
  146. const HandleCheckAllOrNot = (val)=>{
  147. console.log('当前的变更值',val)
  148. showAll.value = val
  149. indeterminate.value = false
  150. if(val){
  151. myChart.value.dispatchAction({
  152. type: 'legendAllSelect',
  153. });
  154. }else{
  155. // 第一次点击没有图例值 需要调用反选
  156. if(selectedLegendList.value?.length > 0){
  157. selectedLegendList.value.map(item=>{
  158. myChart.value.dispatchAction({
  159. type: 'legendUnSelect',
  160. name:item
  161. })
  162. })
  163. }else{
  164. // 反选
  165. myChart.value.dispatchAction({
  166. type: 'legendInverseSelect',
  167. })
  168. }
  169. }
  170. }
  171. // 基础配置
  172. const getBaseOption = () => ({
  173. tooltip: {
  174. trigger: 'item',
  175. borderWidth:0,
  176. textStyle:{
  177. color:'#666'
  178. },
  179. },
  180. legend: {
  181. itemGap:15,
  182. itemWidth:20,
  183. padding:1,
  184. itemHeight:10,
  185. textStyle:{
  186. fontSize:12,
  187. },
  188. },
  189. color:['#5470C6','#3BA272','#FAC858','#995FB3','#EE6666','#72C0DD','#90CB75','#EA7ACB','#FC8451','#65789B',
  190. '#178BD3','#26A616','#A6129E','#D3A141','#234098','#047640','#B43535','#848BDC','#D6AF83','#84313D'],
  191. series: [
  192. {
  193. name: '',
  194. type: 'pie',
  195. startAngle:90,
  196. avoidLabelOverlap: false,
  197. labelLine: {
  198. length:15,
  199. length2:10,
  200. },
  201. emphasis: {
  202. scale:false,
  203. },
  204. data: [],
  205. },
  206. {
  207. name: '',
  208. type: 'pie',
  209. startAngle:90,
  210. endAngle:'auto',
  211. avoidLabelOverlap: false,
  212. label: {
  213. show: false,
  214. },
  215. labelLine: {
  216. show: false,
  217. },
  218. emphasis: {
  219. scale:false,
  220. label: {
  221. show: false,
  222. }
  223. },
  224. data:[],
  225. emptyCircleStyle:{
  226. color:'#F4F5F8',
  227. opacity:1
  228. },
  229. }
  230. ]
  231. });
  232. // 动态配置
  233. const getDynamicOption = ()=>({
  234. tooltip: {
  235. showContent:props.showTooltip,
  236. formatter: props.tooltipFormatter,
  237. position:props.tooltipPosition
  238. },
  239. legend: {
  240. show: props.showLegend,
  241. top: props.legendTop,
  242. right:props.legendRight,
  243. left:props.legendLeft,
  244. bottom:props.legendBottom,
  245. orient:props.legendOrient,
  246. // 自定义图例展示内容:名称:占比xx%
  247. formatter: props.legendFormatter,
  248. },
  249. series: [
  250. {
  251. name: '',
  252. radius: props.pieRadius,
  253. endAngle:endAngle.value,
  254. top:props.pieTop,
  255. left:props.pieLeft,
  256. right:props.pieRight,
  257. bottom:props.pieBottom,
  258. label: {
  259. show: props.showSeriesLabel,
  260. position:props.seriesLabelPosition,
  261. ...props.seiresLabelFormatter,
  262. },
  263. labelLine: {
  264. show: props.showSeriesLabel,
  265. },
  266. emphasis: {
  267. disabled: !props.showTooltip,
  268. label: {
  269. show: props.showSeriesLabel,
  270. }
  271. },
  272. data: props?.chartData || [],
  273. },
  274. {
  275. name: '',
  276. radius: props.pieRadius,
  277. top:props.pieTop,
  278. left:props.pieLeft,
  279. right:props.pieRight,
  280. bottom:props.pieBottom,
  281. }
  282. ]
  283. })
  284. // 饼图的扇区结束位置
  285. const endAngle = computed(()=>{
  286. console.log('旋转角度',props.rotateAngle)
  287. if(props.rotateAngle && props.rotateAngle !== 360){
  288. if(props.rotateAngle <= 90){
  289. return ( 90 - props.rotateAngle)
  290. }else{
  291. return ( 90 - props.rotateAngle + 360)
  292. }
  293. }else{
  294. return 'auto'
  295. }
  296. })
  297. const UpdateChart = () =>{
  298. if(!myChart.value) return
  299. console.log('labelformater值',props.seiresLabelFormatter)
  300. myChart.value.setOption(getBaseOption());
  301. myChart.value.setOption(getDynamicOption());
  302. }
  303. const HandleInitChart = ()=>{
  304. console.log('准备初始化了--')
  305. if(myChart.value){
  306. myChart.value?.dispose();
  307. myChart.value = null
  308. }
  309. // 基于准备好的dom,初始化echarts实例
  310. myChart.value = echarts.init(chartsRef.value);
  311. UpdateChart()
  312. myChart.value.on('legendselectchanged', function (params) {
  313. console.log('图例状态切换了',params);
  314. selectedLegendList.value = Object.entries(params.selected).map(itm=>itm[0])
  315. let selectedStatus = Object.values(params.selected)
  316. let length = selectedStatus.length
  317. let selectedLegendStatus = selectedStatus.filter(item=>item === true)
  318. let unSelectedLegend = selectedStatus.filter(item=>item === false)
  319. if(selectedLegendStatus.length === length || unSelectedLegend.length === length){
  320. indeterminate.value = false
  321. if(selectedLegendStatus.length === length){
  322. showAll.value = true
  323. }else{
  324. showAll.value = false
  325. }
  326. }else{
  327. indeterminate.value = true
  328. }
  329. });
  330. }
  331. watch([()=>props.randomIndex,()=>props.rotateAngle],([newRandom,newRotate],[oldRandom,oldRotate])=>{
  332. if(!myChart.value) return
  333. // rotateAngle 实际发生变化时需要重建以应用 endAngle 变更
  334. if(newRotate !== oldRotate){
  335. HandleInitChart()
  336. return
  337. }
  338. console.log('执行到了--')
  339. // 仅容器尺寸/布局变化时调整大小
  340. try{ myChart.value.resize() }catch(e){}
  341. })
  342. watch(()=>props.chartData,(newVal)=>{
  343. // 数据变换后只更新 series.data
  344. if(!myChart.value) return
  345. try{
  346. // 同步更新 series 数据并刷新 legend 和 label 的展示(使百分比同步)
  347. myChart.value.setOption(getDynamicOption())
  348. }catch(e){
  349. HandleInitChart()
  350. }
  351. },{deep:true})
  352. onMounted(()=>{
  353. HandleInitChart()
  354. })
  355. </script>
  356. <style lang="scss" scoped>
  357. .render_doughnut_charts{
  358. position: relative;
  359. width: 100%;
  360. }
  361. .operation{
  362. position: absolute;
  363. :deep(.el-checkbox){
  364. display: flex;
  365. align-items: center;
  366. height: auto !important;
  367. .el-checkbox__label{
  368. font-size: 12px !important;
  369. }
  370. color:#333333;
  371. }
  372. }
  373. </style>