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

news2025/5/24 19:44:16

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、grid
  series: [{                                          // ✨ 图表特定(完全不同)
    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、grid
  radar: {                                            // ✨ 图表特定(独有的坐标系)
    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.coloradmin.cn/o/2384836.html

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

相关文章

CSS:margin的塌陷与合并问题

文章目录 一、margin塌陷问题二、margin合并问题 一、margin塌陷问题 二、margin合并问题

探索服务网格(Service Mesh):云原生时代的网络新范式

文章目录 一、引言二、什么是服务网格基本定义形象比喻 三、服务网格解决了哪些问题微服务通信复杂性可观察性安全性 四、常见的服务网格实现IstioLinkerdConsul Connect 五、服务网格的应用场景大型微服务架构混合云环境 六、服务网格的未来发展与其他技术的融合标准化和行业规…

SQL SERVER中实现类似LEAST函数的功能,返回多列数据中的最小值

使用 LEAST()函数可以简洁地在一行SQL语句中找出多个值中的最小值,但在SQLServer数据库中,没有内置的LEAST函数。 我们可以使用values子句创建临时的数据集的办法,返回多列数据中的最小值。 创建表 CREATE TABLE stu…

SymPy | 获取表达式自由变量方法与因式分解

SymPy 是 Python 中强大的符号计算库,广泛应用于数学建模、公式推导和科学计算。本文将从两个核心功能展开:表达式中自由变量的获取与因式分解的实现,通过完整代码示例和深入分析,帮助读者掌握其使用方法。 第一部分:获…

深度剖析并发I/O模型select、poll、epoll与IOCP核心机制

核心概要:select、poll、epoll 和 IOCP 是四种用于提升服务器并发处理能力的I/O模型或机制。前三者主要属于I/O多路复用范畴,允许单个进程或线程监视多个I/O流的状态;而 IOCP 则是一种更为彻底的异步I/O模型。 一、引言:为何需要这…

数据结构 -- 交换排序(冒泡排序和快速排序)

冒泡排序 基于“交换”的排序&#xff1a;根据序列中两个元素关键字的比较结果来对换这两个记录在序列中的位置 //交换 void swap(int &a,int &b){int temp a;a b;b temp; }//冒泡排序 void BubbleSort(int A[],int n){for(int i0;i<n-1;i){bool flag false; …

【算法】: 前缀和算法(利用o(1)的时间复杂度快速求区间和)

前缀和算法&#xff1a;高效处理区间求和的利器 目录 引言什么是前缀和前缀和的基本实现前缀和的作用前缀和的典型应用场景前缀和的优缺点分析实战例题解析 引言 区间求和问题的普遍性暴力解法的时间复杂度问题前缀和算法的核心思想 什么是前缀和 前缀和的数学定义 通俗来…

macOS 安装 PostgreSQL

文章目录 安装安装信息 验证GUI 工具下载 安装 最简单的方式是通过 brew 安装 brew install postgresql17该版本在 brew 上的详情页&#xff1a;https://formulae.brew.sh/formula/postgresql17 你也可以根据需要&#xff0c;搜索 安装更新版本 如果你没有安装 brew&#xf…

Linux系统:基础命令之 ls~pwd~cd

文章目录 前言一、ls命令&#x1f4d8; 命令简介&#xff1a;&#x1f9e0; 基本语法&#xff1a;演示ls&#x1f527; 常用选项&#xff1a;-l选项-a选项-h选项 小结 ls 二、pwd命令&#x1f4d8; 命令简介&#xff1a;何为绝对路径&#xff01;何为相对路径&#xff01;&…

基于OAuth2-proxy和Keycloak为comfyui实现SSO

背景 comfyui无认证被漏扫后易被rce挖矿 攻击过程 https://www.oschina.net/news/340226 https://github.com/comfyanonymous/ComfyUI/discussions/5165 阿里云漏洞库关于comfyui的漏洞 https://avd.aliyun.com/search?qcomfyui&timestamp__1384n4%2BxBD0GitGQ0QD8ID%2F…

SmartSoftHelp 之 SQL Server 数据库安全备份与安全还原详解---深度优化版:SmartSoftHelp DeepCore XSuite

SmartSoftHelp 菜单之 DBMS 数据库备份与还原 (DBBackRest) 使用实例 SQL Server 数据库备份与还原详解 SQL Server 数据库的备份与还原是管理数据库的核心任务之一&#xff0c;涉及本地与远程操作、大小监控及目录管理等多个方面。以下是详细说明&#xff1a; 一、数据库…

Spring 代理与 Redis 分布式锁冲突:一次锁释放异常的分析与解决

Spring 代理与 Redis 分布式锁冲突&#xff1a;一次锁释放异常的分析与解决 Spring 代理与 Redis 分布式锁冲突&#xff1a;一次锁释放异常的分析与解决1. 问题现象与初步分析2 . 原因探究&#xff1a;代理机制对分布式锁生命周期的干扰3. 问题复现伪代码4. 解决方案&#xff1…

【数据结构】队列的完整实现

队列的完整实现 队列的完整实现github地址前言1. 队列的概念及其结构1.1 概念1.2 组织结构 2. 队列的实现接口一览结构定义与架构初始化和销毁入队和出队取队头队尾数据获取size和判空 完整代码与功能测试结语 队列的完整实现 github地址 有梦想的电信狗 前言 ​ 队列&…

根据YOLO数据集标签计算检测框内目标面积占比(YOLO7-10都适用)

程序&#xff1a; 路径改成自己的&#xff0c;阈值可以修改也可以默认 #zhouzhichao #25年5月17日 #计算时频图中信号面积占检测框面积的比值import os import numpy as np import pandas as pd from PIL import Image# Define the path to the directory containing the lab…

LLM笔记(九)KV缓存(2)

文章目录 1. 背景与动机2. 不使用 KV Cache 的情形2.1 矩阵形式展开2.2 计算复杂度 3. 使用 KV Cache 的优化3.1 核心思想3.2 矩阵形式展开3.3 计算复杂度对比 4. 总结5. GPT-2 中 KV 缓存的实现分析5.1 缓存的数据结构与类型5.2 在注意力机制 (GPT2Attention) 中使用缓存5.3 缓…

LVS 负载均衡集群应用实战

前提:三台虚拟机,有nginx,要做负载 1. LVS-server 安装lvs管理软件 [root@lvs-server ~]# yum -y install ipvsadm 程序包:ipvsadm(LVS管理工具) 主程序:/usr/sbin/ipvsadm 规则保存工具:/usr/sbin/ipvsadm-save > /path/to/file 配置文件:/etc/sysconfig/ipvsad…

MySQL——基本查询内置函数

目录 CRUD Create Retrieve where order by limit Update Delete 去重操作 聚合函数 聚合统计 内置函数 日期函数 字符函数 数学函数 其它函数 实战OJ 批量插入数据 找出所有员工当前薪水salary情况 查找最晚入职员工的所有信息 查找入职员工时间升序排…

Day34打卡 @浙大疏锦行

知识点回归&#xff1a; CPU性能的查看&#xff1a;看架构代际、核心数、线程数GPU性能的查看&#xff1a;看显存、看级别、看架构代际GPU训练的方法&#xff1a;数据和模型移动到GPU device上类的call方法&#xff1a;为什么定义前向传播时可以直接写作self.fc1(x) 作业 计算资…

AdGuard解锁高级版(Nightly)_v4.10.36 安卓去除手机APP广告

AdGuard解锁高级版(Nightly)_v4.10.36 安卓去除手机APP广告 AdGuard Nightly是AdGuard团队为及时更新软件而推出的最新测试版本&#xff0c;适合追求最新功能和愿意尝试新版本的用户。但使用时需注意其潜在的不稳定性和风险。…

C++修炼:红黑树的模拟实现

Hello大家好&#xff01;很高兴我们又见面啦&#xff01;给生活添点passion&#xff0c;开始今天的编程之路&#xff01; 我的博客&#xff1a;<但凡. 我的专栏&#xff1a;《编程之路》、《数据结构与算法之美》、《题海拾贝》、《C修炼之路》 欢迎点赞&#xff0c;关注&am…