使用 Vue 和 ECharts 创建交互式图表

使用 Vue 和 ECharts 创建交互式图表

引言

在现代 Web 应用中,数据可视化是一个重要的组成部分。它不仅能够帮助用户更好地理解复杂的数据,还能提升用户体验。

技术背景

Vue.js

Vue.js 是一个渐进式 JavaScript 框架,用于构建用户界面。它易于上手,同时提供了强大的功能来构建复杂的单页应用。Vue 的响应式系统使得数据绑定变得简单高效。

ECharts

ECharts 是一个基于 JavaScript 的开源可视化库,由百度前端技术部开发。它提供了丰富的图表类型和高度可定制的配置选项,适用于各种数据可视化需求。

项目搭建

首先,需要创建一个新的 Vue 项目。如果还没有安装 Vue CLI,可以通过以下命令进行安装:

npm install -g @vue/cli

然后,创建一个新的 Vue 项目:

vue create my-chart-app
cd my-chart-app

接下来,安装 ECharts:

npm install echarts

代码说明

  1. 图表容器

    • 使用 ref 获取图表容器的 DOM 元素。
    • onMounted 生命周期钩子中初始化 ECharts 实例并调用 updateChart 方法更新图表配置。
  2. 图表类型选择

    • 使用 v-model 绑定图表类型,并在选择改变时调用 updateChart 方法更新图表。
  3. 数据编辑

    • 提供两个模态对话框,一个用于编辑单个数据点,另一个用于编辑所有数据点。
    • 使用计算属性 selectedXAxisValueselectedSeriesValue 来同步选中的数据点的 X 轴值和系列数据值。
    • 提供 addDataPointdeleteDataPoint 方法来添加和删除数据点,并在操作后调用 updateChart 方法更新图表。
  4. 图表配置

    • 根据不同的图表类型(折线图、柱状图、饼图、散点图),设置不同的图表配置。
    • 使用 label 属性常驻显示数值标签,并在饼图中使用 labelLine 属性设置连接线的样式。

代码实现

<script setup lang="ts">
import { defineComponent, onMounted, ref, computed } from 'vue'
import * as echarts from 'echarts'// 定义图表容器引用
const chartRef = ref<HTMLDivElement | null>(null)
let chartInstance: echarts.ECharts | null = null// 定义图表数据
const xAxisData = ref(["初始阶段", "开发阶段", "完成阶段"])
const seriesData = ref([10, 50, 80])const chartType = ref('line')// 初始化图表
const initChart = () => {if (!chartRef.value) returnchartInstance = echarts.init(chartRef.value)updateChart()
}// 更新图表配置
const updateChart = () => {if (!chartInstance) returnlet option;switch (chartType.value) {case 'line':case 'bar':option = {tooltip: {trigger: 'axis',formatter: '{b}: {c}'},legend: {orient: 'vertical',left: 'left',textStyle: { color: '#666' }},xAxis: {show: true,type: 'category',data: xAxisData.value,axisLine: { lineStyle: { color: '#999' } },axisLabel: { color: '#666' }},yAxis: {show: true,type: 'value',axisLine: { lineStyle: { color: '#999' } },splitLine: { lineStyle: { color: ['#eaeaea'], width: 1, type: 'dashed' } },axisLabel: { color: '#666' }},series: [{data: seriesData.value,type: chartType.value,itemStyle: { color: '#5470c6' },label: { // 常驻显示数值标签show: true,position: 'top', // 标签位置color: '#666'},...(chartType.value === 'line' ? { areaStyle: { color: 'rgba(84, 112, 198, 0.3)' } } : {})}],grid: { left: '5%', right: '5%', bottom: '10%' }};break;case 'pie':option = {tooltip: {trigger: 'item',formatter: '{a} <br/>{b}: {c} ({d}%)'},legend: {orient: 'vertical',left: 'left',textStyle: { color: '#666' }},xAxis: {show: false // 明确禁用 X 轴},yAxis: {show: false // 明确禁用 Y 轴},series: [{name: '数据',type: 'pie',radius: ['40%', '70%'],avoidLabelOverlap: false,label: {show: true, // 常驻显示数值标签position: 'outside', // 标签位置formatter: '{b}: {c} ({d}%)', // 自定义标签格式color: '#666'},emphasis: {label: { show: true, fontSize: '20', fontWeight: 'bold' }},data: xAxisData.value.map((name, index) => ({name,value: seriesData.value[index],itemStyle: { color: ['#5470c6', '#91cc75', '#fac858'][index % 3] }}))}]};break;case 'scatter':option = {tooltip: {trigger: 'item',formatter: '{b}: {c}'},legend: {orient: 'vertical',left: 'left',textStyle: { color: '#666' }},xAxis: {show: true,type: 'category',data: xAxisData.value,axisLine: { lineStyle: { color: '#999' } },axisLabel: { color: '#666' }},yAxis: {show: true,type: 'value',axisLine: { lineStyle: { color: '#999' } },splitLine: { lineStyle: { color: ['#eaeaea'], width: 1, type: 'dashed' } },axisLabel: { color: '#666' }},series: [{symbolSize: 20,data: xAxisData.value.map((name, index) => [index, seriesData.value[index]]),type: 'scatter',label: { // 常驻显示数值标签show: true,position: 'top', // 标签位置color: '#666'},itemStyle: { color: '#5470c6' }}]};break;default:option = {};}chartInstance.setOption(option)console.log('option',option)
}// 监听图表点击事件
onMounted(() => {initChart()chartInstance?.on('click', (params) => {showModalSingle.value = true;selectedDataIndex.value = params.dataIndex ?? -1;});
})// 处理 X 轴数据变化
const handleXAxisChange = (index: number, value: string) => {xAxisData.value[index] = valueupdateChart()
}// 处理系列数据变化
const handleSeriesChange = (index: number, value: string) => {seriesData.value[index] = parseFloat(value)updateChart()
}// 模态对话框状态
const showModalSingle = ref(false);
const showModalAll = ref(false);
const selectedDataIndex = ref(-1);// 计算属性:获取选中的 X 轴值
const selectedXAxisValue = computed({get: () => xAxisData.value[selectedDataIndex.value],set: (newValue) => handleXAxisChange(selectedDataIndex.value, newValue)
});// 计算属性:获取选中的系列数据值
const selectedSeriesValue = computed({get: () => seriesData.value[selectedDataIndex.value].toString(),set: (newValue) => handleSeriesChange(selectedDataIndex.value, newValue)
});// 添加数据点
const addDataPoint = () => {xAxisData.value.push(`新数据点 ${xAxisData.value.length + 1}`);seriesData.value.push(0);updateChart(); // 更新图表以反映新增的数据点
};// 删除数据点
const deleteDataPoint = (index: number) => {xAxisData.value.splice(index, 1);seriesData.value.splice(index, 1);updateChart();
};
</script><template><!-- 图表容器 --><div ref="chartRef" :style="{ width: '100%', height: '400px', backgroundColor: '#fff', borderRadius: '8px', boxShadow: '0 2px 4px rgba(0, 0, 0, 0.1)' }"></div><!-- 图表类型选择 --><select v-model="chartType" @change="updateChart" style="margin-top: 20px; padding: 8px; border: 1px solid #ccc; border-radius: 4px;"><option value="line">折线图</option><option value="bar">柱状图</option><option value="pie">饼图</option><option value="scatter">散点图</option></select><!-- 编辑所有数据按钮 --><button @click="showModalAll = true" style="margin-top: 20px; margin-left: 10px; padding: 8px 16px; background-color: #5470c6; color: #fff; border: none; border-radius: 4px; cursor: pointer;">编辑所有数据</button><!-- 单个数据点模态对话框 --><div v-if="showModalSingle" style="position: fixed; top: 0; left: 0; right: 0; bottom: 0; background-color: rgba(0, 0, 0, 0.5); display: flex; justify-content: center; align-items: center;"><div style="background-color: white; padding: 20px; border-radius: 8px; box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);"><h3>编辑数据点 {{ selectedDataIndex + 1 }}</h3><div><label>X轴数据:</label><input :value="selectedXAxisValue" @input="selectedXAxisValue = ($event.target as HTMLInputElement).value" style="width: 100%; padding: 8px; margin-top: 5px; border: 1px solid #ccc; border-radius: 4px;"/></div><div><label>系列数据:</label><input :value="selectedSeriesValue" @input="selectedSeriesValue = ($event.target as HTMLInputElement).value" style="width: 100%; padding: 8px; margin-top: 5px; border: 1px solid #ccc; border-radius: 4px;"/></div><button @click="showModalSingle = false" style="margin-top: 10px; padding: 8px 16px; background-color: #5470c6; color: #fff; border: none; border-radius: 4px; cursor: pointer;">关闭</button></div></div><!-- 所有数据模态对话框 --><div v-if="showModalAll" style="position: fixed; top: 0; left: 0; right: 0; bottom: 0; background-color: rgba(0, 0, 0, 0.5); display: flex; justify-content: center; align-items: center;"><div style="background-color: white; padding: 20px; border-radius: 8px; box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); width: 80%; max-width: 600px;"><h3>编辑所有数据</h3><table style="width: 100%; border-collapse: collapse;"><thead><tr><th style="padding: 8px; text-align: left; background-color: #f2f2f2; color: #333;">序号</th><th style="padding: 8px; text-align: left; background-color: #f2f2f2; color: #333;">X轴数据</th><th style="padding: 8px; text-align: left; background-color: #f2f2f2; color: #333;">系列数据</th><th style="padding: 8px; text-align: left; background-color: #f2f2f2; color: #333;">操作</th></tr></thead><tbody><tr v-for="(item, index) in xAxisData" :key="index"><td style="border-bottom: 1px solid #ddd; padding: 8px;">{{ index + 1 }}</td><td style="border-bottom: 1px solid #ddd; padding: 8px;"><input :value="xAxisData[index]" @input="handleXAxisChange(index, ($event.target as HTMLInputElement).value)" style="width: 100%; padding: 8px; border: 1px solid #ccc; border-radius: 4px;"/></td><td style="border-bottom: 1px solid #ddd; padding: 8px;"><input :value="seriesData[index]" @input="handleSeriesChange(index, ($event.target as HTMLInputElement).value)" style="width: 100%; padding: 8px; border: 1px solid #ccc; border-radius: 4px;"/></td><td style="border-bottom: 1px solid #ddd; padding: 8px;"><button @click="deleteDataPoint(index)" style="padding: 8px 16px; background-color: #ff4d4f; color: #fff; border: none; border-radius: 4px; cursor: pointer;">删除</button></td></tr></tbody></table><button @click="addDataPoint" style="margin-top: 10px; padding: 8px 16px; background-color: #5470c6; color: #fff; border: none; border-radius: 4px; cursor: pointer;">添加数据点</button><button @click="showModalAll = false" style="margin-top: 10px; padding: 8px 16px; background-color: #5470c6; color: #fff; border: none; border-radius: 4px; cursor: pointer;">关闭</button></div></div>
</template>

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

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

小熊派Nano接入华为云

一、华为云IoTDA创建产品 创建如下服务&#xff0c;并添加对应的属性和命令。 二、小熊派接入 根据小熊派官方示例代码D6完成了小熊派接入华为云并实现属性上传命令下发。源码&#xff1a;小熊派开源社区/BearPi-HM_Nano 1. MQTT连接代码分析 这部分代码在oc_mqtt.c和oc_mq…

Hbuilder X/Uniapp 关于app运行调试及mumu模拟器运行问题

Hbuilder X 关于app调试问题及mumu模拟器链接问题 Hbuilder 关于app调试问题1. app运行配置2. adb路径配置3. 模拟器端口查询4. 运行 Hbuilder 关于app调试问题 1. app运行配置 Hbuilder > 工具 > 设置 > 运行配置 adb路径配置&#xff08;见2&#xff09; Android模…

MySQL-关键字执行顺序

&#x1f496;简介 在MySQL中&#xff0c;SQL查询语句的执行遵循一定的逻辑顺序&#xff0c;即使这些关键字在SQL语句中的物理排列可能有所不同。 &#x1f31f;语句顺序 (8) SELECT (9) DISTINCT<select_list> (1) FROM <left_table> (3) <join_type> JO…

【SpringBoot】26 实体映射工具(MapStruct)

Gitee 仓库 https://gitee.com/Lin_DH/system 介绍 现状 为了让应用程序的代码更易于维护&#xff0c;通常会将项目进行分层。在《阿里巴巴 Java 开发手册》中&#xff0c;推荐分层如下图所示&#xff1a; 每层都有对应的领域模型&#xff0c;即不同类型的 Bean。 DO&…

RPC-健康检测机制

什么是健康检测&#xff1f; 在真实环境中服务提供方是以一个集群的方式提供服务&#xff0c;这对于服务调用方来说&#xff0c;就是一个接口会有多个服务提供方同时提供服务&#xff0c;调用方在每次发起请求的时候都可以拿到一个可用的连接。 健康检测&#xff0c;能帮助从连…

Enterprise Architect 16 下载、安装与无限30天操作

文章目录 Enterprise Architect 16 简介&#xff08;一&#xff09;支持多种建模语言和标准&#xff08;二&#xff09;强大的版本控制、协作和文档管理功能&#xff08;三&#xff09;增强的技术和用户体验&#xff08;四&#xff09;高级功能和扩展性 一&#xff0c;下载软件…

小程序租赁系统开发为企业提供高效便捷的租赁服务解决方案

内容概要 在这个数字化飞速发展的时代&#xff0c;小程序租赁系统应运而生&#xff0c;成为企业管理租赁业务的一种新选择。随着移动互联网的普及&#xff0c;越来越多的企业开始关注如何利用小程序来提高租赁服务的效率和便捷性。小程序不仅可以为用户提供一个快速、易用的平…

定时器的小应用

第一个项目 第一步&#xff0c;RCC开启时钟&#xff0c;这个基本上每个代码都是第一步&#xff0c;不用多想&#xff0c;在这里打开时钟后&#xff0c;定时器的基准时钟和整个外设的工作时钟就都会同时打开了 RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE);第二步&…

JVM--内存结构

目录 1. PC Register&#xff08;程序计数器&#xff09; 1.1 定义 1.2 工作原理 1.3 特点 1.4 应用 2.虚拟机栈 2.1定义与特性 2.2内存模型 2.3工作原理 2.4异常处理 2.5应用场景 2.6 Slot 复用 2.7 动态链接详解 1. 栈帧与动态链接 动态链接的作用&#xff1a…

一文读懂Redis6的--bigkeys选项源码以及redis-bigkey-online项目介绍

一文读懂Redis6的--bigkeys选项源码以及redis-bigkey-online项目介绍 本文分为两个部分&#xff0c;第一是详细讲解Redis6的--bigkeys选项相关源码是怎样实现的&#xff0c;第二部分为自己对--bigkeys源码的优化项目redis-bigkey-online的介绍。redis-bigkey-online是自己开发的…

Go语言跨平台桌面应用开发新纪元:LCL、CEF与Webview全解析

开篇寄语 在Go语言的广阔生态中&#xff0c;桌面应用开发一直是一个备受关注的领域。今天&#xff0c;我将为大家介绍三款基于Go语言的跨平台桌面应用开发框架——LCL、CEF与Webview&#xff0c;它们分别拥有独特的魅力和广泛的应用场景。通过这三款框架&#xff0c;你将能够轻…

音视频入门基础:MPEG2-TS专题(5)——FFmpeg源码中,判断某文件是否为TS文件的实现

一、引言 通过FFmpeg命令&#xff1a; ./ffmpeg -i XXX.ts 可以判断出某个文件是否为TS文件&#xff1a; 所以FFmpeg是怎样判断出某个文件是否为TS文件呢&#xff1f;它内部其实是通过mpegts_probe函数来判断的。从《FFmpeg源码&#xff1a;av_probe_input_format3函数和AVI…

C++初阶学习第十一弹——list的用法和模拟实现

目录 一、list的使用 二.list的模拟实现 三.总结 一、list的使用 list的底层是双向链表结构&#xff0c;双向链表中每个元素存储在互不相关的独立节点中&#xff0c;在节点中通过指针指向 其前一个元素和后一个元素。 常见的list的函数的使用 std::list<int> It {1,…

Qlik Sense QVD 文件

QVD 文件 QVD (QlikView Data) 文件是包含从 Qlik Sense 或 QlikView 中所导出数据的表格的文件。QVD 是本地 Qlik 格式&#xff0c;只能由 Qlik Sense 或 QlikView 写入和读取。当从 Qlik Sense 脚本中读取数据时&#xff0c;该文件格式可提升速度&#xff0c;同时又非常紧凑…

攻防世界 Web新手练习区

GFSJ0475 get_post 获取在线场景后&#xff0c;点开网址 依据提示在搜索框输入信息 给出第二条提示信息 打开hackbar&#xff0c;将网址Load下来&#xff0c;勾选Post data&#xff0c;在下方输入框输入b2 点击Execute 出现flag值 GFSJ0476 robots 打开御剑扫描域名&#…

MySQL —— explain 查看执行计划与 MySQL 优化

文章目录 explain 查看执行计划explain 的作用——查看执行计划explain 查看执行计划返回信息详解表的读取顺序&#xff08;id&#xff09;查询类型&#xff08;select_type&#xff09;数据库表名&#xff08;table&#xff09;联接类型&#xff08;type&#xff09;可用的索引…

前端研发高德地图,如何根据经纬度获取地点名称和两点之间的距离?

地理编码与逆地理编码 引入插件&#xff0c;此示例采用异步引入&#xff0c;更多引入方式 https://lbs.amap.com/api/javascript-api-v2/guide/abc/plugins AMap.plugin("AMap.Geocoder", function () {var geocoder new AMap.Geocoder({city: "010", /…

React(二)

文章目录 项目地址七、数据流7.1 子组件传递数据给父组件7.1.1 方式一:給父设置回调函数,传递给子7.1.2 方式二:直接将父的setState传递给子7.2 给props传递jsx7.2.1 方式一:直接传递组件给子类7.2.2 方式二:传递函数给子组件7.3 props类型验证7.4 props的多层传递7.5 cla…

SpringBootTest常见错误解决

1.启动类所在包错误 问题 由于启动类所在包与需要自动注入的类的包不在一个包下&#xff1a; 启动类所在包&#xff1a; com.exmaple.test_02 但是对于需要注入的类却不在com.exmaple.test_02下或者其子包下&#xff0c;就会导致启动类无法扫描到该类&#xff0c;从而无法对…

Redis面试篇笔记(持续更新)

一、redis主从集群 单节点redis的并发能力是由上限的&#xff0c;要进一步提高redis的并发能力可以搭建主从集群&#xff0c;实现读写分离&#xff0c;一主多从&#xff0c;主节点写数据&#xff0c;从节点读数据 部署redis主从节点的docker-compose文件命令解析 version: &q…