vue使用echarts實(shí)現(xiàn)柱狀圖動態(tài)排序效果
前言
echarts在前端開發(fā)中實(shí)屬必不可缺的大數(shù)據(jù)可視化工具,在前段時(shí)間搬磚時(shí)遇到這樣一個(gè)需求,需要實(shí)現(xiàn)一個(gè)動態(tài)排序的柱狀圖,用來展示不同部門在不同月份的績效收益情況,能夠直觀地看到每個(gè)月各個(gè)部門的排名變化情況。并能夠隨時(shí)暫停,手動切換來展示某個(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
},
讓圖表動起來
其實(shí)讓圖表動起來,只需要?jiǎng)討B(tài)改變echarts的數(shù)據(jù)即可實(shí)現(xiàn),但是需要在echarts配置中增加動畫效果
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)柱狀圖動態(tài)排序效果的詳細(xì)內(nèi)容,更多關(guān)于vue echarts動態(tài)柱狀圖的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
vue3 setup中defineEmits與defineProps的使用案例詳解
在父組件中定義String、Number、Boolean、Array、Object、Date、Function、Symbol這些類型的數(shù)據(jù),使用defineEmits會返回一個(gè)方法,使用一個(gè)變量emits(變量名隨意)去接收,本文給大家介紹vue3 setup中defineEmits與defineProps的使用案例,感興趣的朋友一起看看吧2023-10-10
Vue.js仿Metronic高級表格(一)靜態(tài)設(shè)計(jì)
這篇文章主要為大家詳細(xì)介紹了Vue.js仿Metronic高級表格的靜態(tài)設(shè)計(jì),文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-04-04
VUE側(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-07
vue3項(xiàng)目中各個(gè)文件的作用詳細(xì)介紹
在Vue3項(xiàng)目中,通常會有以下一些常見的目錄和文件,下面這篇文章主要給大家介紹了關(guān)于vue3項(xiàng)目中各個(gè)文件的作用,文中通過代碼介紹的非常詳細(xì),需要的朋友可以參考下2024-09-09

