2.在 Vue 3 中使用 ECharts 实现动态时间轴效果

news2024/12/25 13:34:23

在前端开发中,时间轴(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 轴的时间范围是动态的,我们使用 currentMincurrentMax 变量来控制时间的范围,并通过前进(navigatePrev)和后退(navigateNext)按钮来更新范围。
  • 前进/后退按钮:按钮可以分别将时间范围前后移动三个月,通过 setMonth() 方法动态调整 minmax 的值。

3. 总结

在这篇文章中,我们展示了如何在 Vue 3 中结合 ECharts 实现一个动态的时间轴效果。通过 ECharts 的 custom 类型、X 轴动态范围的控制以及前进后退按钮的交互,我们能够为用户提供一个灵活且直观的时间轴展示方式。希望这篇教程能为你的项目提供帮助,并且你能够根据自己的需求进一步定制和扩展功能。


4. 参考链接

  • ECharts 官方文档
  • Vue 3 官方文档

以上就是关于如何在 Vue 3 中使用 ECharts 实现动态时间轴效果的详细教程。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2265308.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【EthIf-13】EthIfGeneral容器配置-01

1.EthIfGeneral类图结构 下面是EthIfGeneral配置参数的类图&#xff0c;比较重要的参数就是配置&#xff1a; 接收中断是否打开发送确认中断是否打开EthIf轮询周期 1.EthIfGeneral参数的含义

卓软计量业务管理平台 image.ashx 任意文件读取漏洞复现

0x01 产品简介 卓软计量业务管理平台是一款专为计量测试检定机构设计的信息化管理系统。随着社会经济的不断发展,计量测试检定机构面临的管理规范化、技术水平、检测效率、服务能力以及行业竞争等问题日益增多。卓软计量业务管理平台旨在通过信息化手段,帮助机构实现业务管理…

BCSP-X 2024 Scratch图形化编程 小学低年级组 真题

BCSP-X 2024-2 图形化编程 小学低年级组 真题 题目总数&#xff1a;40 总分数&#xff1a;100 选择题 第 1 题 单选题 在下面各世界顶级的奖项中&#xff0c;为计算机科学与技术领域做出杰出贡献的科学 家设立的奖项是?( ) A. 奥斯卡奖 B. 诺贝尔奖 C. 菲尔兹…

GCP GCA认证考试Case错题库1(JenciMart+Helicopter+EHR)

GCP GCA认证考试Case错题库1(JenciMartHelicopterEHR) 整理by Moshow郑锴https://zhengkai.blog.csdn.net/ JenciMart 在生产和开发资源之间进行管理职责分离的最小权限模型中&#xff0c;最佳实践是每个应用程序的每个阶段都有自己的项目。这种设置确保权限是细化的&#xf…

centos权限大集合,覆盖多种权限类型,解惑权限后有“. + t s”问题!

在 CentOS 系统中&#xff0c;权限管理是操作系统的核心功能之一&#xff0c;确保不同用户和进程对文件、目录以及设备的访问被合理控制。 权限系统主要包括传统的 Unix 权限模型、特殊权限&#xff08;SetUID、SetGID、Sticky 位&#xff09;和更精细的访问控制列表&#xff…

HarmonyOS NEXT 技术实践-基于基础视觉服务实现骨骼点识别

本示例展示了如何在HarmonyOS Next中实现基于基础视觉服务的骨骼点识别功能。骨骼点识别是计算机视觉中的一项重要技术&#xff0c;广泛应用于运动分析、健身监控和增强现实等领域。通过使用HarmonyOS Next提供的视觉API&#xff0c;开发者能够轻松地对人物图像进行骨骼点检测&…

【RAG实战】语言模型基础

语言模型赋予了计算机理解和生成人类语言的能力。它结合了统计学原理和深度神经网络技术&#xff0c;通过对大量的样本数据进行复杂的概率分布分析来学习语言结构的内在模式和相关性。具体地&#xff0c;语言模型可根据上下文中已出现的词序列&#xff0c;使用概率推断来预测接…

workman服务端开发模式-应用开发-vue-element-admin挂载websocket

一、项目根目录main.js添加全局引入 import /utils/websocket 二、在根目录app.vue 中初始化WebSocket连接 <template><div id"app"><router-view /></div> </template><script>import store from ./store export default {n…

2024-12-24 NO1. XR Interaction ToolKit 环境配置

文章目录 1 软件配置2 安装 XRToolKit3 配置 OpenXR4 安装示例场景5 运行测试 1 软件配置 Unity 版本&#xff1a;Unity6000.0.26 ​ 2 安装 XRToolKit 创建新项目&#xff08;URP 3D&#xff09;&#xff0c;点击进入 Asset Store。 进入“Unity Registry”页签&#xff0…

华为手机鸿蒙4.2连接不上adb

1、下载HiSuite华为手机助手 https://consumer.huawei.com/cn/support/hisuite/ 2、安装后点连接 3、就可以adb连接了

GitPuk安装配置指南

GitPuk是一款开源免费的代码管理工具&#xff0c;上篇文章已经介绍了Gitpuk的功能与优势&#xff0c;这篇文章将为大家讲解如何快速安装和配置GitPuk&#xff0c;助力你快速的启动GitPuk管理代码 1. 安装 支持 Windows、Mac、Linux、docker 等操作系统。 1.1 Windows安装 下载…

【从零开始入门unity游戏开发之——C#篇20】C#面向对象的封装——静态成员(`static`)(静态字段、静态方法、静态属性、静态构造函数、静态类)

文章目录 静态成员&#xff08;static&#xff09;1、静态成员的特点&#xff1a;2、为什么可以直接点出来使用&#xff1f;3、不同的静态成员介绍3.1. 静态字段3.2. 静态方法3.3. 静态属性3.4. 静态构造函数3.5. 静态类 4、静态成员的优缺点优点&#xff1a;缺点&#xff1a; …

【Yonghong 企业日常问题 06】上传的文件不在白名单,修改allow.jar.digest属性添加允许上传的文件SH256值?

文章目录 前言问题描述问题分析问题解决1.允许所有用户上传驱动文件2.如果是想只上传白名单的驱动 前言 该方法适合永洪BI系列产品&#xff0c;包括不限于vividime desktop&#xff0c;vividime z-suit&#xff0c;vividime x-suit产品。 问题描述 当我们连接数据源的时候&a…

我的JAVA-Web基础(2)

1.JDBC 防止sql注入 2.JSP JSP的基本语法 基本语法是 <% %> Java代码 <% %> 输出变量 可以转换成${变量}的EL表达式 <%! %>定义变量 JSP的基本语法包括以下几个主要部分&#xff1a; 1. 表达式&#xff08;Expression&#xff09; 表达式用于将…

新闻网站的个性化推荐:机器学习的应用

3.1可行性分析 开发者在进行开发系统之前&#xff0c;都需要进行可行性分析&#xff0c;保证该系统能够被成功开发出来。 3.1.1技术可行性 开发该新闻网站所采用的技术是vue和MYSQL数据库。计算机专业的学生在学校期间已经比较系统的学习了很多编程方面的知识&#xff0c;同时也…

IIC驱动EEPROM

代码参考正点原子 i2c_dri:主要是三段式状态机的编写 module iic_dri#(parameter SLAVE_ADDR 7b1010000 , //EEPROM从机地址parameter CLK_FREQ 26d50_000_000, //模块输入的时钟频率parameter I2C_FREQ 18d250_000 //IIC_SCL的时钟频率)( …

【动手学轨迹预测】2.3 场景表征方法

场景表征是指在所有可用的场景信息数据中, 提取出对于预测网络有用的数据, 并将其转换为易于模型学习的数据格式. 对于预测网络来说, 最重要的数据是交通参与者的历史轨迹和地图信息, 表达它们的常见方法有:栅格化和稀疏化 2.1.1 栅格化 多通道表达 如上图所示, 将历史轨迹和…

亚信安全举办“判大势 悟思想 强实践”主题党日活动

为深入学习和贯彻党的二十届三中全会精神&#xff0c;近日&#xff0c;亚信安全举办了 “学习贯彻党的二十届三中全会精神——‘判大势 悟思想 强实践’党日活动”&#xff0c;并取得圆满成功。 本次活动特邀南京市委宣讲团成员、南京市委党校市情研究中心主任王辉龙教授出席。…

医疗大模型威胁攻击下的医院AI安全:挑战与应对策略

一、引言 1.1 研究背景与意义 随着人工智能技术的迅猛发展,医疗大模型作为一种新兴的技术手段,正逐渐渗透到医疗领域的各个环节,为医疗服务的数字化转型带来了前所未有的机遇。从辅助诊断到疾病预测,从个性化治疗方案的制定到医疗资源的优化配置,医疗大模型展现出了巨大…

如何在谷歌浏览器中使用内置翻译功能

谷歌浏览器作为全球最受欢迎的网络浏览器之一&#xff0c;提供了强大且便捷的内置翻译功能。这一功能帮助用户轻松跨越语言障碍&#xff0c;浏览不同语言的网页内容。本文将详细介绍如何在谷歌浏览器中使用其内置翻译功能。 一、启用谷歌浏览器内置翻译功能 1、打开谷歌浏览器…