2.在 Vue 3 中使用 ECharts 实现动态时间轴效果
在前端开发中,时间轴(Timeline)是一种常见且有效的方式来展示时间相关的数据。在本篇文章中,我们将展示如何在 Vue 3 项目中使用 ECharts 创建一个具有动态时间范围的时间轴,并添加了今日时间的标记以及通过按钮来前进和后退调整时间范围的功能。
前言
时间轴的可视化是展示与时间相关信息的重要方式,常常用于展示事件、任务进度、数据变化等。在这篇文章中,我们将实现一个交互式的时间轴,支持动态的时间范围选择、显示今日时间,并且通过前后按钮来调整时间范围。
主要功能
- 显示具有时间段的自定义时间轴。
- 动态更新 X 轴的时间范围。
- 显示今日时间。
- 使用按钮进行前进和后退的时间范围调整。
效果图
1. 安装必要的依赖
首先,确保你的 Vue 3 项目已经安装了 ECharts。如果没有安装,可以通过以下命令安装:
npm install vue@next npm install echarts
如果你还没有创建 Vue 3 项目,可以使用 Vue CLI 创建一个新的项目:
npm install -g @vue/cli vue create vue-echarts-timeline
然后选择 Vue 3 配置。
2. 创建 Vue 3 组件
在组件中,我们将使用 ECharts 的 custom
类型来绘制时间轴。以下是实现的基本步骤和代码。
2.1 完整代码
在 script setup
中,我们将初始化 ECharts,并根据时间段数据渲染时间轴。X 轴使用动态的时间范围,并且会在图表中标记今日时间。
<!--
* @Author: 彭麒
* @Date: 2024/12/24
* @Email: 1062470959@qq.com
* @Description: 此源码版权归吉檀迦俐所有,可供学习和借鉴或商用。
-->
<template>
<div class="content">
<button class="back-button" @click="goBack">返回</button>
<div class="font-bold text-[24px]">在Vue3中使用Echarts实现时间轴效果</div>
<div class="mt-[202px] px-10">
<div class="w-full mx-auto p-4">
<div class="relative">
<!-- Timeline Bar -->
<div ref="chartContainer" class="w-full h-[200px]"></div>
<button
@click="navigatePrev"
class="absolute left-0 top-1/2 -translate-y-1/2 -translate-x-12 bg-white rounded-full p-2 shadow-lg hover:bg-gray-50"
>
<svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6 text-gray-600" fill="none" viewBox="0 0 24 24"
stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 19l-7-7 7-7"/>
</svg>
</button>
<button
@click="navigateNext"
class="absolute right-0 top-1/2 -translate-y-1/2 translate-x-12 bg-white rounded-full p-2 shadow-lg hover:bg-gray-50"
>
<svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6 text-gray-600" fill="none" viewBox="0 0 24 24"
stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5l7 7-7 7"/>
</svg>
</button>
</div>
</div>
</div>
</div>
</template>
<script setup lang="ts">
import {onMounted, ref, onUnmounted} from 'vue'
import {useRoute} from 'vue-router'
import * as echarts from 'echarts'
import type {EChartsOption} from 'echarts'
const chartContainer = ref<HTMLElement | null>(null)
let chart: echarts.ECharts | null = null
const route = useRoute()
const id = route.query.id
onUnmounted(() => {
chart?.dispose()
window.removeEventListener('resize', handleResize)
})
/**
* 处理窗口大小变化
*/
const handleResize = () => {
chart?.resize()
}
/**
* 循环颜色
*/
const colors = ['#ECECEC', '#C192EB', '#92D8EB', '#92EBB1', '#EB9692', '#EB92BA']
const getRandomColor = (num: number) => {
return colors[num % colors.length]
}
let clickedSegmentIndex: { seriesIndex: number; dataIndex: number } | null = null
let transformedMockList: { name: string; segments: { start: number; end: number; color: string }[] }[] = []
const currentMin = ref(new Date().getTime() - 90 * 24 * 60 * 60 * 1000) // 3 months ago
const currentMax = ref(new Date().getTime() + 90 * 24 * 60 * 60 * 1000) // 3 months from now
const initChart = () => {
if (!chartContainer.value) return
chart = echarts.init(chartContainer.value)
const todayTimestamp = Date.now()
const option: EChartsOption = {
tooltip: {
trigger: 'item',
formatter: (params: any) => {
const start = formatDate(params.data[0])
const end = formatDate(params.data[1])
return `时间段:${start} - ${end}`
},
},
dataZoom: [
{
show: true,
realtime: true,
bottom: 20,
},
],
grid: {
left: 50,
right: 50,
},
xAxis: {
type: 'time',
min: currentMin.value,
max: currentMax.value,
axisLabel: {
formatter: (value: number) => {
const date = new Date(value)
const year = date.getFullYear()
const month = String(date.getMonth() + 1).padStart(2, '0')
const day = String(date.getDate()).padStart(2, '0')
return `${year}-${month}-${day}`
},
},
},
yAxis: {
type: 'category',
data: transformedMockList.map((item) => item.name || '时间线'),
axisLine: {show: true},
axisTick: {show: true},
},
series: transformedMockList.map((category, seriesIndex) => ({
name: category.name || '时间线',
type: 'custom',
renderItem: (params: any, api: any) => {
const segment = category.segments[params.dataIndex]
const start = api.coord([segment.start, params.seriesIndex])
const end = api.coord([segment.end, params.seriesIndex])
const yOffset =
clickedSegmentIndex && clickedSegmentIndex.seriesIndex === seriesIndex && clickedSegmentIndex.dataIndex === params.dataIndex ? -10 : 0
return {
type: 'rect',
shape: {
x: start[0],
y: start[1] - 15 + yOffset,
width: Math.max(end[0] - start[0], 1),
height: 30,
},
style: {
fill: segment.color,
},
}
},
data: category.segments.map((segment) => [segment.start, segment.end]),
markLine: {
data: [
{
xAxis: todayTimestamp,
label: {
formatter: '今天',
position: 'end',
},
lineStyle: {
color: 'red',
type: 'solid',
},
tooltip: {
formatter: () => {
const date = new Date(todayTimestamp)
const year = date.getFullYear()
const month = String(date.getMonth() + 1).padStart(2, '0')
const day = String(date.getDate()).padStart(2, '0')
const hours = String(date.getHours()).padStart(2, '0')
const minutes = String(date.getMinutes()).padStart(2, '0')
const seconds = String(date.getSeconds()).padStart(2, '0')
return `时间段:${year}-${month}-${day} ${hours}:${minutes}:${seconds}`
},
},
},
],
},
})),
}
chart.setOption(option)
chart.on('click', (params: any) => {
clickedSegmentIndex = {seriesIndex: params.seriesIndex, dataIndex: params.dataIndex}
const segment = transformedMockList[params.seriesIndex].segments[params.dataIndex]
ruleForm.value.start = segment.start
ruleForm.value.end = segment.end
chart.setOption(option)
})
}
/**
* 获取数据
*/
const getList = async () => {
transformedMockList = [
{
name: '',
segments: [
{
id: 1,
start: 1733044053000,
end: 1733303253000,
color: getRandomColor(1),
},
{
id: 2,
start: 1734167253000,
end: 1735031253000,
color: getRandomColor(2),
},
{
id: 3,
start: 1735722453000,
end: 1736240853000,
color: getRandomColor(3),
},
{
id: 4,
start: 1736244453000,
end: 1736503653000,
color: getRandomColor(4),
},
{
id: 5,
start: 1736676453000,
end: 1739354853000,
color: getRandomColor(5),
},
{
id: 6,
start: 1739527653000,
end: 1771063653000,
color: getRandomColor(6),
},
]
},
]
updateChartView()
}
/**
* 更新图表视图
*/
const updateChartView = () => {
if (!chart) return
chart.setOption({
xAxis: {
min: currentMin.value,
max: currentMax.value,
},
series: transformedMockList.map((category) => ({
name: category.name || '时间线',
type: 'custom',
renderItem: (params: any, api: any) => {
const segment = category.segments[params.dataIndex]
const start = api.coord([segment.start, params.seriesIndex])
const end = api.coord([segment.end, params.seriesIndex])
return {
type: 'rect',
shape: {
x: start[0],
y: start[1] - 15,
width: Math.max(end[0] - start[0], 1), // Ensure width is at least 1 pixel
height: 30,
},
style: {
fill: segment.color,
},
}
},
data: category.segments.map((segment) => [segment.start, segment.end]),
})),
})
}
/**
* 格式化日期
* @param dateString
* @param type
*/
const formatDate = (timestamp: number) => {
const date = new Date(timestamp)
const year = date.getFullYear()
const month = String(date.getMonth() + 1).padStart(2, '0')
const day = String(date.getDate()).padStart(2, '0')
const hours = String(date.getHours()).padStart(2, '0')
const minutes = String(date.getMinutes()).padStart(2, '0')
const seconds = String(date.getSeconds()).padStart(2, '0')
return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`
}
/**
* 导航到上一个日期范围
*/
const navigatePrev = () => {
const newMin = new Date(currentMin.value)
newMin.setMonth(newMin.getMonth() - 3)
currentMin.value = newMin.getTime()
const newMax = new Date(currentMax.value)
newMax.setMonth(newMax.getMonth() - 3)
currentMax.value = newMax.getTime()
getList()
}
/**
* 导航到下一个日期范围
*/
const navigateNext = () => {
const newMin = new Date(currentMin.value)
newMin.setMonth(newMin.getMonth() + 3)
currentMin.value = newMin.getTime()
const newMax = new Date(currentMax.value)
newMax.setMonth(newMax.getMonth() + 3)
currentMax.value = newMax.getTime()
getList()
}
onMounted(() => {
getList().then(() => {
initChart()
})
window.addEventListener('resize', handleResize)
if (id) {
getMerInfo()
}
})
</script>
<style scoped lang="scss">
.blue {
color: #0177fb !important;
border-bottom: 2px solid #0177fb;
}
.btn {
@apply text-[#757575] text-[14px] cursor-pointer h-[28px];
}
</style>
2.2 功能说明
- 今日时间标记:我们通过
markLine
在图表上添加了一条红色的竖线,表示当前的日期。每次鼠标悬停在竖线时,会显示今天的具体时间。 - 动态时间范围:X 轴的时间范围是动态的,我们使用
currentMin
和currentMax
变量来控制时间的范围,并通过前进(navigatePrev
)和后退(navigateNext
)按钮来更新范围。 - 前进/后退按钮:按钮可以分别将时间范围前后移动三个月,通过
setMonth()
方法动态调整min
和max
的值。
3. 总结
在这篇文章中,我们展示了如何在 Vue 3 中结合 ECharts 实现一个动态的时间轴效果。通过 ECharts 的 custom
类型、X 轴动态范围的控制以及前进后退按钮的交互,我们能够为用户提供一个灵活且直观的时间轴展示方式。希望这篇教程能为你的项目提供帮助,并且你能够根据自己的需求进一步定制和扩展功能。
4. 参考链接
- ECharts 官方文档
- Vue 3 官方文档
以上就是关于如何在 Vue 3 中使用 ECharts 实现动态时间轴效果的详细教程。