vue使用echarts實(shí)現(xiàn)柱狀圖動(dòng)態(tài)排序效果
前言
echarts在前端開發(fā)中實(shí)屬必不可缺的大數(shù)據(jù)可視化工具,在前段時(shí)間搬磚時(shí)遇到這樣一個(gè)需求,需要實(shí)現(xiàn)一個(gè)動(dòng)態(tài)排序的柱狀圖,用來展示不同部門在不同月份的績效收益情況,能夠直觀地看到每個(gè)月各個(gè)部門的排名變化情況。并能夠隨時(shí)暫停,手動(dòng)切換來展示某個(gè)月的具體情況。經(jīng)過一下午一頓敲擊鍵盤后,完美完成本次任務(wù),總結(jié)記錄一下,以后就可以愉快地摸魚了。完整代碼看文末,先上圖看下實(shí)現(xiàn)效果:
安裝使用
1、demo基于vue3框架,首先安裝echarts。
yarn add echarts //我使用的版本:"echarts": "^5.2.2"
2、全局注冊echarts,在app.vue 中引入echarts,使用provide,使所有的子組件都可以直接使用echarts
//app.vue import * as echarts from 'echarts'; provide('$echarts', echarts);
demo實(shí)現(xiàn)
生成一組測試數(shù)據(jù)
首先隨機(jī)生成一組測試數(shù)據(jù),包含1-10月的數(shù)據(jù)count,平均數(shù)avg和y軸的label。timeLineList用于用于播放進(jìn)度條展示。
const generateRandomData = () => { const data = []; for (let i = 0; i < 100; i++) { let month = '2023-'; month = month + (Math.floor(i / 10) + 1); const count = Math.floor(Math.random() * 300) + 100; // 生成100到300的隨機(jī)整數(shù) const name = 'count' + i % 10; const avg = Math.floor(Math.random() * 300) + 100; data.push({ month, count, name, avg }); //生成月度集合,用于播放條展示 if(!timeLineList.value.includes(month)){ timeLineList.value.push(month) } } return data; }
編寫模板部分
包含播放和暫停按鈕、播放條和echart部分。
<template> <div class="chart-dialog-content"> <div class="opt-bar"> <button size="mini" v-if="playChart == false" @click="(playChart = true), initChart(0)"><i class="el-icon-video-play"></i>輪播</button> <button size="mini" v-if="playChart == true" @click="(playChart = false), initChart(activeIndex)"><i class="el-icon-video-pause"></i>暫停</button> </div> <section class="bottom"> <div class="time-line"> <div class="line-item" @click="changeChart(index)" v-for="(i, index) in timeLineList" :key="i" :class="{ active: index <= activeIndex, hover: index == activeIndex }"> <div class="line"></div> <div class="point"> <div class="text">{{ i }}</div> <div class="icon el-icon-caret-top" v-show="index == activeIndex"></div> </div> </div> </div> <div class="chart-content" id="chartContent"></div> </section> </div> </template>
echarts配置
echarts幾個(gè)比要重要配置實(shí)現(xiàn):
1、配置x軸的最大最小值,為了讓變化看上去更明顯,配置min值不從0開始。
xAxis: { max: 'dataMax', min: function (value: any) { return Math.round(value.min) - 10; }, axisLabel: { formatter: function (n: any) { return n; } } },
2、配置dataSet,從數(shù)據(jù)集合中取出某個(gè)月的數(shù)據(jù)
dataset: { source: data.filter(function (item) { return item[0] === startMonth; }) },
detaset需要的數(shù)據(jù)樣例:
[ [ "2023-2", 322, "count0", 205 ], [ "2023-2", 218, "count1", 203 ], [ "2023-2", 206, "count2", 194 ], [ "2023-2", 220, "count3", 297 ], [ "2023-2", 349, "count4", 101 ], [ "2023-2", 247, "count5", 357 ], ... ]
3、配置平均線,取dataset數(shù)組中的數(shù)據(jù)
markLine: { symbolSize: 0, data: [ { name: '平均分', xAxis: Number( data.filter(function (item) { return item[0] === startMonth; })[0][3] ), lineStyle: { color: '#eb7e65', type: 'dashed' }, label: { show: true, color: '#fe4852', formatter: '{ll|平均值:' + Number( data.filter(function (item) { return item[0] === startMonth; })[0][3] ) + '\n}', rich: { ll: { fontSize: 16, lineHeight: 22, padding: [40, 0, 0, 0] } } } } ], animation: false },
讓圖表動(dòng)起來
其實(shí)讓圖表動(dòng)起來,只需要?jiǎng)討B(tài)改變echarts的數(shù)據(jù)即可實(shí)現(xiàn),但是需要在echarts配置中增加動(dòng)畫效果
animationDuration: 0, animationDurationUpdate: updateFrequency, animationEasing: 'linear', animationEasingUpdate: 'linear',
定時(shí)從數(shù)據(jù)集合data中取出對應(yīng)月份的數(shù)據(jù)
if (playChart.value) { for (let i = startIndex; i < month.length - 1; ++i) { let myTimeout = setTimeout(() => { updateMonth(month[i + 1]); activeIndex.value = i + 1; //播放結(jié)束 if (i == month.length - 2) { setTimeout(() => { playChart.value = false; }, updateFrequency); } }, (i + 1 - startIndex) * updateFrequency); myTimeoutArr.value.push(myTimeout); } } function updateMonth(month: any) { let source = data.filter(function (item) { return item[0] === month; }); console.log(source) option.series[0].data = source; option.series[0].markLine = { symbolSize: 0, data: [ { name: '平均分', xAxis: Number(source[0][3]), lineStyle: { color: '#eb7e65', type: 'dashed' }, label: { show: true, color: '#fe4852', formatter: '{ll|平均值:' + Number(source[0][3]) + '\n}', rich: { ll: { fontSize: 16, lineHeight: 22, padding: [40, 0, 0, 0] } } } } ], animation: false }; myChart.value.setOption(option); }
完整代碼
demo完整代碼如下,可直接復(fù)制使用
<template> <div class="chart-dialog-content"> <div class="opt-bar"> <button size="mini" v-if="playChart == false" @click="(playChart = true), initChart(0)"><i class="el-icon-video-play"></i>輪播</button> <button size="mini" v-if="playChart == true" @click="(playChart = false), initChart(activeIndex)"><i class="el-icon-video-pause"></i>暫停</button> </div> <section class="bottom"> <div class="time-line"> <div class="line-item" @click="changeChart(index)" v-for="(i, index) in timeLineList" :key="i" :class="{ active: index <= activeIndex, hover: index == activeIndex }"> <div class="line"></div> <div class="point"> <div class="text">{{ i }}</div> <div class="icon el-icon-caret-top" v-show="index == activeIndex"></div> </div> </div> </div> <div class="chart-content" id="chartContent"></div> </section> </div> </template> <script setup lang="ts"> import { onMounted, ref, inject } from 'vue'; const activeIndex = ref(0); const myChart = ref<any>(null); const playChart = ref(true); const rspData = ref<any>([]); const timeLineList = ref<any>([]); const myTimeoutArr = ref<any>([]); const $echarts: any = inject('$echarts'); const generateRandomData = () => { const data = []; for (let i = 0; i < 100; i++) { let month = '2023-'; month = month + (Math.floor(i / 10) + 1); const count = Math.floor(Math.random() * 300) + 100; // 生成100到300的隨機(jī)整數(shù) const name = 'count' + i % 10; const avg = Math.floor(Math.random() * 300) + 100; data.push({ month, count, name, avg }); if(!timeLineList.value.includes(month)){ timeLineList.value.push(month) } } return data; } onMounted(() => { playChart.value = false; getData(); }); const getData = () => { timeLineList.value = []; rspData.value = generateRandomData(); initChart(0); } const changeChart = (index: number, flag = false) => { playChart.value = flag; initChart(index); } const initChart = (index = 0) => { myChart.value?.dispose(); //清理定時(shí) myTimeoutArr.value.forEach((item: any) => { clearTimeout(item); }); myTimeoutArr.value = []; // 獲取$echarts實(shí)例 myChart.value = $echarts.init(document.querySelector('#chartContent')); const updateFrequency = 3000; const dimension = 1; const data: any[] = []; rspData.value.forEach((item: any) => { data.push([item.month, item.count, item.name, item.avg]); }); const month: any[] = []; data.forEach((item) => { if (month.length === 0 || month[month.length - 1] !== item[0]) { month.push(item[0]); } }); let startIndex = index; let startMonth = month[startIndex]; let option = { grid: { top: 10, bottom: 30, left: 20, right: 40, containLabel: true }, xAxis: { max: 'dataMax', min: function (value: any) { return Math.round(value.min) - 10; }, axisLabel: { formatter: function (n: any) { return n; } } }, dataset: { source: data.filter(function (item) { return item[0] === startMonth; }) }, yAxis: { type: 'category', inverse: true, axisTick: { show: false }, axisLine: { show: true, lineStyle: { color: '#e2e2e2' } }, axisLabel: { show: true, fontSize: 14, formatter: function (value: any) { return value; }, color: '#8c8c8d', rich: { flag: { fontSize: 25, padding: 5 } } }, animationDuration: 300, animationDurationUpdate: 300 }, series: [ { realtimeSort: true, seriesLayoutBy: 'column', type: 'bar', itemStyle: { color: function () { return '#7ea2f4'; } }, markLine: { symbolSize: 0, data: [ { name: '平均分', xAxis: Number( data.filter(function (item) { return item[0] === startMonth; })[0][3] ), lineStyle: { color: '#eb7e65', type: 'dashed' }, label: { show: true, color: '#fe4852', formatter: '{ll|平均值:' + Number( data.filter(function (item) { return item[0] === startMonth; })[0][3] ) + '\n}', // distance: [-135, 150], // padding: [-50, 0, 50, 0], rich: { ll: { fontSize: 16, lineHeight: 22, padding: [40, 0, 0, 0] } } } } ], animation: false }, barMaxWidth: 20, encode: { x: dimension, y: 2 }, label: { show: true, // precision: 1, position: 'right', valueAnimation: true, fontFamily: 'monospace' } } ], // Disable init animation. animationDuration: 0, animationDurationUpdate: updateFrequency, animationEasing: 'linear', animationEasingUpdate: 'linear', // graphic: { // elements: [ // { // type: 'text', // right: 0, // bottom: 0, // style: { // text: startMonth, // font: 'bolder 24px monospace', // fill: 'rgba(100, 100, 100, 0.25)' // }, // z: 100 // } // ] // } }; myChart.value.setOption(option); activeIndex.value = index; if (playChart.value) { for (let i = startIndex; i < month.length - 1; ++i) { let myTimeout = setTimeout(() => { updateMonth(month[i + 1]); activeIndex.value = i + 1; //播放結(jié)束 if (i == month.length - 2) { setTimeout(() => { playChart.value = false; }, updateFrequency); } }, (i + 1 - startIndex) * updateFrequency); myTimeoutArr.value.push(myTimeout); } } function updateMonth(month: any) { let source = data.filter(function (item) { return item[0] === month; }); console.log(source) option.series[0].data = source; option.series[0].markLine = { symbolSize: 0, data: [ { name: '平均分', xAxis: Number(source[0][3]), lineStyle: { color: '#eb7e65', type: 'dashed' }, label: { show: true, color: '#fe4852', formatter: '{ll|平均值:' + Number(source[0][3]) + '\n}', rich: { ll: { fontSize: 16, lineHeight: 22, padding: [40, 0, 0, 0] } } } } ], animation: false }; // option.graphic.elements[0].style.text = month; myChart.value.setOption(option); } } </script> <style lang="less" scoped> .chart-dialog-content { .chart-content { height: 550px; width: 100%; margin: 0 auto; } .opt-bar { padding: 12px 16px; display: flex; align-items: center; >div { margin-right: 16px; } i { margin-right: 4px; } .desc { font-weight: bold; margin-right: 8px; } .el-date-editor { width: 170px; } } .bottom { padding: 16px; background: #f2f4f7; .time-line { display: flex; width: 80%; height: 36px; margin: 0 auto; .line-item { display: flex; align-items: center; width: 100%; cursor: pointer; &:first-child { width: 10px; .line { width: 0; } } &.hover { .text { color: #467ff1; } } &.active { .point { border: 2px solid #477ff0; .text { font-weight: 500; } } .line { background: #477ff0; } } .point { position: relative; width: 6px; height: 6px; border-radius: 50%; background: #fff; .text { position: absolute; white-space: nowrap; transform: translate(-50%, -135%); } .icon { font-size: 32px; position: absolute; color: #fff; transform: translate(-50%, 0); } } .line { width: 100%; height: 3px; background: #efefef; } } } .chart-content { background: #fff; } .type-radio { padding: 12px; background: #fff; .el-radio { font-weight: 400; color: rgba(0, 0, 0, 0.65); margin-right: 12px; } } } } </style>
以上就是vue使用echarts實(shí)現(xiàn)柱狀圖動(dòng)態(tài)排序效果的詳細(xì)內(nèi)容,更多關(guān)于vue echarts動(dòng)態(tài)柱狀圖的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
vue3 setup中defineEmits與defineProps的使用案例詳解
在父組件中定義String、Number、Boolean、Array、Object、Date、Function、Symbol這些類型的數(shù)據(jù),使用defineEmits會(huì)返回一個(gè)方法,使用一個(gè)變量emits(變量名隨意)去接收,本文給大家介紹vue3 setup中defineEmits與defineProps的使用案例,感興趣的朋友一起看看吧2023-10-10如何利用vue實(shí)現(xiàn)css過渡和動(dòng)畫
過渡Vue在插入、更新或者移除 DOM 時(shí),提供多種不同方式的應(yīng)用過渡效果這篇文章主要給大家介紹了關(guān)于如何利用vue實(shí)現(xiàn)css過渡和動(dòng)畫的相關(guān)資料,需要的朋友可以參考下2021-11-11Vue.js仿Metronic高級表格(一)靜態(tài)設(shè)計(jì)
這篇文章主要為大家詳細(xì)介紹了Vue.js仿Metronic高級表格的靜態(tài)設(shè)計(jì),文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-04-04VUE側(cè)邊導(dǎo)航欄實(shí)現(xiàn)篩選過濾的示例代碼
本文主要介紹了VUE側(cè)邊導(dǎo)航欄實(shí)現(xiàn)篩選過濾的示例代碼,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2023-05-05如何通過Vue自定義指令實(shí)現(xiàn)前端埋點(diǎn)詳析
埋點(diǎn)分析是網(wǎng)站分析的一種常用的數(shù)據(jù)采集方法,下面這篇文章主要給大家介紹了關(guān)于如何通過Vue自定義指令實(shí)現(xiàn)前端埋點(diǎn)的相關(guān)資料,文中通過實(shí)例代碼介紹的非常詳細(xì),需要的朋友可以參考下2022-07-07vue3項(xiàng)目中各個(gè)文件的作用詳細(xì)介紹
在Vue3項(xiàng)目中,通常會(huì)有以下一些常見的目錄和文件,下面這篇文章主要給大家介紹了關(guān)于vue3項(xiàng)目中各個(gè)文件的作用,文中通過代碼介紹的非常詳細(xì),需要的朋友可以參考下2024-09-09vue實(shí)現(xiàn)列表無縫動(dòng)態(tài)滾動(dòng)
要想實(shí)現(xiàn)列表的動(dòng)態(tài)無縫滾動(dòng),本文為大家推薦兩款組件,vue-seamless-scroll和vue3-seamless-scroll,組件的用法也非常簡單,下面就跟隨小編一起來學(xué)習(xí)一下吧2024-11-11