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

news/2024/11/16 8:07:40/

使用 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.ppmy.cn/news/1547398.html

相关文章

面试经典 150 题:20、2、228

20. 有效的括号 参考代码 #include <stack>class Solution { public:bool isValid(string s) {if(s.size() < 2){ //特判&#xff1a;空字符串和一个字符的情况return false;}bool flag true;stack<char> st; //栈for(int i0; i<s.size(); i){if(s[i] ( |…

免押租赁系统的优势与应用前景分析

内容概要 免押租赁系统是一种新兴的租赁形式&#xff0c;它利用了信用大数据与区块链技术的优势&#xff0c;帮助用户摆脱了传统租赁中常见的押金烦恼。通过这种方式&#xff0c;用户不仅可以体验到更低的租用门槛&#xff0c;还能享受到更顺畅的交易过程。用户只需提供基本的…

Linux网络——网络初识

目录 1. 认识协议 2. 协议的分层 3. OSI 七层模型 && TCP/IP 五层(四层)模型 4. 网络传输的基本流程 5. 以太网的通信原理 6. 数据的跨网络传播 7. 认识 IP 地址 ① IP 是什么 ② IP 与 MAC 的关系 ③ 为什么需要 IP 在谈及网络之前&#xff0c;我们要先对学…

React Native 全栈开发实战班 - 第四部分:用户界面进阶之动画效果实现

在移动应用中&#xff0c;动画效果 是提升用户体验的重要手段。合理的动画设计可以增强应用的交互性、流畅性和视觉吸引力。React Native 提供了多种实现动画的方式&#xff0c;包括内置的 Animated API、LayoutAnimation 以及第三方库&#xff08;如 react-native-reanimated&…

酒店行业数据仓库

重要名词&#xff1a; PMS&#xff1a;酒店管理系统CRS&#xff1a;中央预定系统客户&#xff1a;可以分为会员、散客&#xff08;自行到店入住&#xff09;、协议&#xff08;与酒店长期合作&#xff0c;内部价&#xff09;、中介预定&#xff1a;可以分为线上预定、线下预定…

【Java Web】MVC与分层开发

文章目录 MVC分层开发 MVC MVC&#xff08;Model-View-Controller&#xff09;架构模式将应用程序分为三个核心组件&#xff1a;模型&#xff08;Model&#xff09;、视图&#xff08;View&#xff09;和控制器&#xff08;Controller&#xff09;&#xff0c;以实现清晰的责任…

鸿蒙学习生态应用开发能力全景图-鸿蒙生态伙伴 SDK 市场(4)

鸿蒙生态伙伴 SDK 市场帮助开发者获得更优质安全的闭源 SDK&#xff0c;与 SDK 伙伴、开发者共建一站式的 SDK 选用平台&#xff0c;实现开发者、SDK 伙伴和华为共赢。鸿蒙生态伙伴 SDK市场汇聚热门 SDK 助力开发者构建高品质鸿蒙原生应用。同时伙伴 SDK 市场通过 SDK 签名认证…

综合文化信息管理系统|基于java和小程序的综合文化信息管理系统设计与实现(源码+数据库+文档)

综合文化信息管理系统 目录 基于java和小程序的打印室预约系统设计与实现 一、前言 二、系统设计 三、系统功能设计 四、数据库设计 五、核心代码 六、论文参考 七、最新计算机毕设选题推荐 八、源码获取&#xff1a; 博主介绍&#xff1a;✌️大厂码农|毕设布道师&…