ECharts图表工厂,完整代码+思路逻辑

Echart工厂支持柱状图(bar)折线图(line)散点图(scatter)饼图(pie)雷达图(radar)极坐标柱状图(polarBar)极坐标折线图(polarLine)等多种图表,及其对应扩展图表:

git链接:sq/UI/src/components/Echarts at main · afigzb/sq (github.com)https://github.com/afigzb/sq/tree/main/UI/src/components/Echarts

展示页面,后续附带详细的说明: 


引言

ECharts 是一个功能强大的图表库,广泛应用于数据可视化场景。然而,其复杂的配置项和高学习曲线常常让开发者望而却步。本文将介绍一个精心设计的图表工厂系统,通过封装 ECharts 的复杂性,提供简洁的 API 和统一的开发体验,帮助开发者快速构建图表,提高效率和代码可维护性。本文将全面介绍其设计背景、架构、功能特性及使用方法,带你了解如何利用它简化图表开发。


设计背景:为什么我要重新封装一个图表工厂?

见此代码:

const option = {color: ['#5470c6', '#91cc75', '#fac858', '#ee6666'],backgroundColor: '#ffffff',xAxis: {type: 'category',data: ['A', 'B', 'C'],axisLine: { lineStyle: { color: '#cccccc' } },axisLabel: { color: '#666666' },splitLine: { lineStyle: { color: '#cccccc', opacity: 0.4 } }},yAxis: {type: 'value',axisLine: { lineStyle: { color: '#cccccc' } },axisLabel: { color: '#666666' },splitLine: { lineStyle: { color: '#cccccc', opacity: 0.4 } }},series: [{type: 'bar',data: [120, 200, 150]}],tooltip: {backgroundColor: '#333333',textStyle: { color: '#ffffff' }},grid: { left: '5%', right: '5%', bottom: '15%', top: '5%' }
};

这是一个标准的Echart 配置项,在实际开发过程中项目中往往不止一个Echart图表,同时图表的配置项也远比这负责,这就导致了:

  1. 配置重复:每个图表都需要重复设置颜色、背景、提示框等通用配置。
  2. 维护困难:修改主题或样式时,需要逐一调整每个图表的配置。
  3. 类型散乱:不同图表类型的配置差异大,缺乏统一抽象。
  4. 维护成本高:ECharts 的 API 庞大,写好的配置项难以更改。

基于这些问题,EChartFactory2 的设计目标是:

  • 简化配置:从繁琐的手动配置转为简单的数据输入。
  • 统一接口:让所有图表类型共享一致的调用方式。
  • 集中管理:通过主题系统统一管理样式,支持动态切换。
  • 易于扩展:方便添加新图表类型和功能。

架构设计:分层抽象的思考过程

核心设计思想:分离通用与特定

为了探寻Echart图表的设计规律我收集了项目中常见的图表配置,进行对比分析,如下:

// 柱状图配置示例
const barOption = {color: ['#5470c6', '#91cc75', '#fac858'],          // 🔄 重复出现backgroundColor: '#ffffff',                         // 🔄 重复出现grid: { left: '5%', right: '5%', top: '5%', bottom: '15%' }, // 🔄 重复出现tooltip: {                                          // 🔄 重复出现backgroundColor: '#333333',textStyle: { color: '#ffffff' }},xAxis: {                                            // ✨ 图表特定type: 'category',data: ['销售', '市场', '研发'],axisLine: { lineStyle: { color: '#cccccc' } },axisLabel: { color: '#666666' }},yAxis: {                                            // ✨ 图表特定  type: 'value',axisLine: { lineStyle: { color: '#cccccc' } },axisLabel: { color: '#666666' }},series: [{                                          // ✨ 图表特定type: 'bar',data: [320, 280, 450]}]
};// 折线图配置示例
const lineOption = {color: ['#5470c6', '#91cc75', '#fac858'],          // 🔄 重复出现backgroundColor: '#ffffff',                         // 🔄 重复出现grid: { left: '5%', right: '5%', top: '5%', bottom: '15%' }, // 🔄 重复出现tooltip: {                                          // 🔄 重复出现backgroundColor: '#333333',textStyle: { color: '#ffffff' }},xAxis: {                                            // ✨ 图表特定(和柱状图相似)type: 'category', data: ['1月', '2月', '3月'],axisLine: { lineStyle: { color: '#cccccc' } },axisLabel: { color: '#666666' }},yAxis: {                                            // ✨ 图表特定(和柱状图相似)type: 'value',axisLine: { lineStyle: { color: '#cccccc' } },axisLabel: { color: '#666666' }},series: [{                                          // ✨ 图表特定(配置差异大)type: 'line',data: [820, 932, 901],smooth: true,symbol: 'circle'}]
};// 饼图配置示例
const pieOption = {color: ['#5470c6', '#91cc75', '#fac858'],          // 🔄 重复出现backgroundColor: '#ffffff',                         // 🔄 重复出现  tooltip: {                                          // 🔄 重复出现backgroundColor: '#333333',textStyle: { color: '#ffffff' }},// ❌ 注意:饼图没有 xAxis、yAxis、gridseries: [{                                          // ✨ 图表特定(完全不同)type: 'pie',radius: '50%',data: [{ value: 1048, name: '搜索引擎' },{ value: 735, name: '直接访问' },{ value: 580, name: '邮件营销' }]}]
};// 雷达图配置示例:
const radarOption = {color: ['#5470c6', '#91cc75', '#fac858'],          // 🔄 重复出现backgroundColor: '#ffffff',                         // 🔄 重复出现tooltip: {                                          // 🔄 重复出现backgroundColor: '#333333',textStyle: { color: '#ffffff' }},// ❌ 注意:雷达图没有 xAxis、yAxis、gridradar: {                                            // ✨ 图表特定(独有的坐标系)indicator: [{ name: '销售', max: 100 },{ name: '管理', max: 100 },{ name: '技术', max: 100 }]},series: [{                                          // ✨ 图表特定(又是不同的结构)type: 'radar',data: [{value: [60, 73, 85],name: '预算分配'}]}]
};

通过对比分析,我们可以发现这些图标基本可以划分成以下几部分:

// 通用配置(所有图表都需要,配置内容基本相同)
const universalConfig = {color: [],           // 调色板 - 所有图表都需要backgroundColor: '', // 背景色 - 所有图表都需要tooltip: {},        // 提示框 - 所有图表都需要,但触发方式可能不同legend: {},         // 图例 - 大部分图表需要toolbox: {}         // 工具箱 - 看项目需求,但配置方式变化很小
};// 特定配置(每种图表独有,配置内容差异很大)
const specificConfig = {// 直角坐标系图表(柱状图、折线图、散点图)xAxis: {},          // X轴配置yAxis: {},          // Y轴配置  grid: {},           // 网格配置// 极坐标图表polar: {},          // 极坐标配置angleAxis: {},      // 角度轴radiusAxis: {},     // 径向轴// 雷达图radar: {},          // 雷达图配置(带指示器)// 所有图表都有,但配置差异巨大series: []          // 系列配置(每种图表类型完全不同)
};

如果能自动生成通用配置,只让用户关心数据和特定需求,Echart代码将得到极大程度的简化。

配置映射系统的设计

有了这个思路后,我开始思考:如果每种图表类型都有一个"配置生成器",那么我只需要告诉它图表类型和数据,它就能自动生成完整的配置。

最初的想法很简单,只要吧需要配置的东西单独抽象出来统一配置不就可以了:

const CHART_TYPE_CONFIGS = {bar: {series: (data) => ({ type: 'bar', data: data.data })},line: {series: (data) => ({ type: 'line', data: data.data })}// ...
};

但很快我就发现问题了——不同图表需要的坐标系完全不同!

第一个难题:坐标系的差异

当我试图处理饼图时,发现它根本不需要 xAxis 和 yAxis,而雷达图需要的是 radar 配置。如果还是用传统思路,我又要写很多 if-else:

// 这样写太丑了...
if (chartType === 'pie') {// 不要坐标轴
} else if (chartType === 'radar') {// 要雷达配置
} else {// 要直角坐标系
}

这时我意识到,坐标系才是图表的核心差异。于是我重新整理思路:

| 坐标系类型 | 适用图表 | 需要的配置 |

|-----------|---------|-----------|

| 直角坐标系 (cartesian) | 柱状图、折线图、散点图 | xAxis + yAxis + grid |

| 极坐标系 (polar) | 极坐标柱状图、极坐标折线图 | polar + angleAxis + radiusAxis |

| 雷达坐标系 (radar) | 雷达图 | radar (带indicator) |

| 无坐标系 (none) | 饼图 | 隐藏所有坐标轴 |

这样一来,我的配置映射就变成了两层结构:

const CHART_TYPE_CONFIGS = {bar: {coordinateSystem: 'cartesian',  // 👈 指定用哪种坐标系series: (data, theme, config) => ({ /* 系列配置 */ })},pie: {coordinateSystem: 'none',       // 👈 饼图不需要坐标系series: (data, theme, config) => ({ /* 系列配置 */ })}
};

第二个难题:主题样式的统一

有了坐标系分类,我又遇到新问题:每次创建图表都要设置颜色、背景色、字体等样式,这些重复工作能否自动化?

我回顾了之前写的图表,发现比较常见的是几种风格:

  • 默认风格:白底黑字,全给Echart自动化
  • 科技风格:黑底彩色,大屏常用
  • 简约风格:浅色背景,较为正式

与其每次都手写这些样式,不如做成主题系统:

const themes = {default: {colors: {series: ['#5470c6', '#91cc75', '#fac858'],background: { chart: '#ffffff', tooltip: '#333333' },text: { primary: '#333333', secondary: '#666666' }}},futuristic: {colors: {series: ['#00d4ff', '#ff6b9d', '#7fff00'],background: { chart: '#0a0a0a', tooltip: 'rgba(0,0,0,0.8)' },text: { primary: '#ffffff', secondary: '#cccccc' }}}
};

这样我们就能一键切换整个图表的视觉风格了。

第三个难题:如何合并配置?

现在有了图表类型配置、坐标系配置、主题配置,但我们的逻辑是把Echart拆解成一个个独立部分,最终要合并在一起才是我们需要的ECharts 配置

显然简单的 Object.assign 不可行,因为Echarts配置是多层嵌套的:

const config1 = { series: [{ itemStyle: { color: 'red' } }] };
const config2 = { series: [{ itemStyle: { borderWidth: 2 } }] };// Object.assign 会直接覆盖,丢失 color 配置
Object.assign(config1, config2); 
// 结果:{ series: [{ itemStyle: { borderWidth: 2 } }] } ❌// 我需要的是深度合并
// 结果:{ series: [{ itemStyle: { color: 'red', borderWidth: 2 } }] } ✅

所以我写了一个深度合并函数,确保所有配置都能正确合并。

整合:EChartFactory2 的诞生

有了这些基础设施,我开始设计核心的工厂类。我的设计原则是:

  1. 使用简单:简单调用函数就能创建
  1. 配置灵活:支持自定义配置覆盖默认值
  1. 功能完整:支持主题切换、类型切换、动态更新

于是有了这样的 API:

// 创建图表
const factory = new EChartFactory2(container, 'bar', 'default');// 更新数据
factory.update({xAxis: ['产品A', '产品B', '产品C'],series: { data: [120, 200, 150] }
});// 切换主题
factory.switchTheme('futuristic');// 切换类型
factory.switchType('line');

实际效果:还算让人满意

我们做一个简单的对比,假设用户的需求是:

创建一个销售数据的柱状图,要求科技风格,支持堆叠显示。

传统写法:

const option = {color: ['#00d4ff', '#ff6b9d', '#7fff00', '#ffaa00'],backgroundColor: '#0a0a0a',grid: {left: '3%', right: '4%', bottom: '3%', top: '4%',containLabel: true,borderColor: '#333333'},tooltip: {trigger: 'axis',backgroundColor: 'rgba(0, 0, 0, 0.8)',borderColor: '#00d4ff',borderWidth: 1,textStyle: { color: '#ffffff', fontSize: 12 },axisPointer: {type: 'shadow',shadowStyle: { color: 'rgba(0, 212, 255, 0.2)' }}},legend: {textStyle: { color: '#ffffff' },icon: 'rect',itemHeight: 8,itemGap: 20},xAxis: {type: 'category',data: ['1月', '2月', '3月', '4月'],axisLine: { lineStyle: { color: '#333333', width: 1 } },axisLabel: { color: '#cccccc', fontSize: 11 },splitLine: { show: false }},yAxis: {type: 'value',axisLine: { lineStyle: { color: '#333333', width: 1 } },axisLabel: { color: '#cccccc', fontSize: 11 },splitLine: {lineStyle: { color: '#333333', width: 0.5, opacity: 0.6 }}},series: [{name: '销售额',type: 'bar',stack: 'total',data: [120, 132, 101, 134],itemStyle: {color: '#00d4ff',borderRadius: [2, 2, 0, 0]}},{name: '利润',type: 'bar', stack: 'total',data: [220, 182, 191, 234],itemStyle: {color: '#ff6b9d',borderRadius: [2, 2, 0, 0]}}]
};const chart = echarts.init(document.getElementById('chart'));
chart.setOption(option);

用工厂后:

const chart = createChart(document.getElementById('chart'), 'bar', 'futuristic');
chart.update({xAxis: ['1月', '2月', '3月', '4月'],series: [{ name: '销售额', data: [120, 132, 101, 134] },{ name: '利润', data: [220, 182, 191, 234] }]
}, { stack: 'total' });

代码量从 60 行减少到 5 行,减少了 92%!

更重要的是,假设现在客户说"把这个图改成折线图",我只需要把代码中的bar改成line即可:

const chart = createChart(document.getElementById('chart'), 'line', 'futuristic');
chart.update({xAxis: ['1月', '2月', '3月', '4月'],series: [{ name: '销售额', data: [120, 132, 101, 134] },{ name: '利润', data: [220, 182, 191, 234] }]
}, { stack: 'total' });

而且越复杂的配置,我这边修改起来就越简单,越统一。

详细代码可以从git中获取:

sq/UI/src/components/Echarts at main · afigzb/sq (github.com)https://github.com/afigzb/sq/tree/main/UI/src/components/Echarts

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

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

相关文章

如何制作令人印象深刻的UI设计?

1. 规划用户旅程 规划用户旅程是创建高效且吸引人的UI设计的第一步。设计师需要深入了解目标用户群体的需求和行为模式,这通常涉及用户调研、创建用户角色(Personas)和绘制用户旅程图(User Journey Maps)。通过这种方…

k8s 离线安装 kube-prometheus-stack

配置共享存储 Prometheus 需要配置持久化存储,防止数据丢失 服务端 服务端安装 NFS 服务 sudo apt install nfs-kernel-server 创建共享目录,在服务器端创建 /nfs 目录。 mkdir /nfs chmod -R 777 /nfs # 设置文件权限 nfs目录下只给了默认权限&#xff…

ceph osd 磁盘分区对齐

分区对齐可以提高读写速度的原理是什么 分区对齐可以提高磁盘读写速度的原理主要在于 磁盘的物理扇区大小与操作系统发起的读写请求之间是否对齐。如果不对齐,每次读写操作可能会跨越多个物理扇区,造成额外的 I/O 操作,从而降低性能。 🔧 原理详解 1. 物理扇区(Physica…

Simon J.D. Prince《Understanding Deep Learning》

学习神经网络和深度学习推荐这本书,这本书站位非常高,且很多问题都深入剖析了,甩其他同类书籍几条街。 多数书,不深度分析、没有知识体系,知识点零散、章节之间孤立。还有一些人Tian所谓的权威,醒醒吧。 …

【泛微系统】后端开发Action常用方法

后端开发Action常用方法 代码实例经验分享:代码实例 经验分享: 本文分享了后端开发中处理工作流Action的常用方法,主要包含以下内容:1) 获取工作流基础信息,如流程ID、节点ID、表单ID等;2) 操作请求信息,包括请求紧急程度、操作类型、用户信息等;3) 表单数据处理,展示…

SSH的screen方法

创建一个screen窗口,(在需要运行程序的文件夹内)使用 screen -S name 命令,其中 name 是窗口的名字。 在窗口中执行需要的命令。 当需要临时离开时,使用快捷键 ctrlA D 回来时,使用 screen -r name 恢复…

无法访问org.springframework.boot.SpringApplication

无法访问org.springframework.boot.SpringApplication 检查springboot和jdk的版本是否适配检查jdk的设置是否统一 主要检查下面几处地方

洛谷 P1800 software(DP+二分)【提高+/省选−】

题目链接 https://www.luogu.com.cn/problem/P1800 思路 对于大于等于最优解的天数,一定能使公司交付软件。对于小于最优解的天数,一定无法使公司交付软件。所以考虑二分答案 x x x。 定义 f [ i ] [ j ] f[i][j] f[i][j]表示前 i i i个人做了 j j j…

C++性能测试工具——sysprof的使用

一、sysprof sysprof相对于前面的一些性能测试工具来说,要简单不少。特别是其图形界面的操作,非常容易上手,它还支持分析文件的保存和导入功能,这是一个非常不错的功能。做为一款系统性能测试工具,它支持多种硬件平台…

redis数据持久化和配置-15(备份和还原 Redis 数据)

备份和还原 Redis 数据 备份和恢复数据是管理任何数据库系统(包括 Redis)的关键方面。数据丢失可能是由于硬件故障、软件错误、意外删除甚至恶意攻击而发生的。因此,拥有强大的备份和恢复策略对于确保数据持久性和业务连续性至关重要。本课将…

【上位机——WPF】布局控件

布局控件 常用布局控件Panel基类Grid(网格)UniformGrid(均匀分布)StackPanel(堆积面板)WrapPanel(换行面板)DockerPanel(停靠面板)Canvas(画布布局)Border(边框)GridSplitter(分割窗口)常用布局控件 Grid:网格,根据自定义行和列来设置控件的布局StackPanel:栈式面板,包含的…

打卡Day33

简单的神经网络 数据的准备 # 仍然用4特征,3分类的鸢尾花数据集作为我们今天的数据集 from sklearn.datasets import load_iris from sklearn.model_selection import train_test_split import numpy as np# 加载鸢尾花数据集 iris load_iris() X iris.data # …

python开发环境管理和包管理

在 Python 开发中,环境管理 和 包管理 是两个非常重要的概念。它们帮助开发者: 这里写目录标题 一、什么是 Python 环境管理?二、什么是 Python 包管理?三、常见文件说明(用于包管理和环境配置)四、典型流程…

Mybatis面向接口编程

添加与Mapper接口的映射 <!--UserMapper.xml--> <?xml version"1.0" encoding"UTF-8" ?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> …

GMP模型入门

go的并发实现采用的是M:N的线程模型&#xff0c;落地就是gmp模型。 M:N模型如下图&#xff1a; gmp模型如下图&#xff1a; --- Go 的 GMP 模型是其 高效并发调度机制的核心。GMP 代表&#xff1a; G&#xff1a;Goroutine&#xff08;用户态线程&#xff09; M&#xff1a;…

达梦数据库-报错-01-[-3205]:全文索引词库加载出错

目录 一、环境信息 二、说点什么 三、模拟实验 1、前台启动数据库 2、重建全文索引报错 3、日志信息 4、查找SYSWORD.UTF8.LIB 5、想一想加做一做 6、重启数据库 7、重建全文索引 8、总结 一、环境信息 名称值CPU12th Gen Intel(R) Core(TM) i7-12700H操作系统CentO…

经典密码学和现代密码学的结构及其主要区别(1)维吉尼亚密码—附py代码

Vigenre cipher 维吉尼亚密码 维吉尼亚密码由布莱斯德维吉尼亚在 16 世纪发明&#xff0c;是凯撒密码的一个更复杂的扩展。它是一种多字母替换密码&#xff0c;使用一个关键字来确定明文中不同字母的多个移位值。 与凯撒密码不同&#xff0c;凯撒密码对所有字母都有固定的偏移…

Ubuntu部署私有Gitlab

这个东西安装其实挺简单的&#xff0c;但是因为我这边迁移了数据目录和使用自己安装的 nginx 代理还是踩了几个坑&#xff0c;所以大家可以注意下 先看下安装 # 先安装必要组件 sudo apt update sudo apt install -y curl openssh-server ca-certificates tzdata perl# 添加gi…

【JVM 02-JVM内存结构之-程序计数器】

程序计数器 笔记记录 1. 定义2. 作用3. 特点4. 拓展理解4.1 PC寄存器存储字节码指令地址有什么用&#xff1f;4.2 PC寄存器为什么被设定为线程私有的&#xff1f;4.3 为什么执行native方法时&#xff0c;是undefined&#xff1f; 学习资料来源-b站黑马JVM& 尚硅谷JVM精讲与…

【node.js】数据库与存储

个人主页&#xff1a;Guiat 归属专栏&#xff1a;node.js 文章目录 1. 数据库概述1.1 数据库在Node.js中的作用1.2 Node.js支持的数据库类型 2. 关系型数据库集成2.1 MySQL与Node.js2.1.1 安装MySQL驱动2.1.2 建立连接2.1.3 执行CRUD操作 2.2 PostgreSQL与Node.js2.2.1 安装pg驱…