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 和统一的开发体验,帮助开发者快速构建图表,提高效率和代码可维护性。本文将全面介绍其设计背景、架构、功能特性及使用方法,带你了解如何利用它简化图表开发。


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

见此代码:

javascript 复制代码
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图表的设计规律我收集了项目中常见的图表配置,进行对比分析,如下:

javascript 复制代码
// 柱状图配置示例
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: '预算分配'
    }]
  }]
};

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

javascript 复制代码
// 通用配置(所有图表都需要,配置内容基本相同)
const universalConfig = {
  color: [],           // 调色板 - 所有图表都需要
  backgroundColor: '', // 背景色 - 所有图表都需要
  tooltip: {},        // 提示框 - 所有图表都需要,但触发方式可能不同
  legend: {},         // 图例 - 大部分图表需要
  toolbox: {}         // 工具箱 - 看项目需求,但配置方式变化很小
};

// 特定配置(每种图表独有,配置内容差异很大)
const specificConfig = {
  // 直角坐标系图表(柱状图、折线图、散点图)
  xAxis: {},          // X轴配置
  yAxis: {},          // Y轴配置  
  grid: {},           // 网格配置
  
  // 极坐标图表
  polar: {},          // 极坐标配置
  angleAxis: {},      // 角度轴
  radiusAxis: {},     // 径向轴
  
  // 雷达图
  radar: {},          // 雷达图配置(带指示器)
  
  // 所有图表都有,但配置差异巨大
  series: []          // 系列配置(每种图表类型完全不同)
};

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

配置映射系统的设计

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

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

javascript 复制代码
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:

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

javascript 复制代码
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配置是多层嵌套的:

javascript 复制代码
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:

javascript 复制代码
// 创建图表
const factory = new EChartFactory2(container, 'bar', 'default');

// 更新数据
factory.update({
  xAxis: ['产品A', '产品B', '产品C'],
  series: { data: [120, 200, 150] }
});

// 切换主题
factory.switchTheme('futuristic');

// 切换类型
factory.switchType('line');

实际效果:还算让人满意

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

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

传统写法:

javascript 复制代码
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);

用工厂后:

javascript 复制代码
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即可:

javascript 复制代码
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

相关推荐
玖釉-1 分钟前
解决PowerShell执行策略导致的npm脚本无法运行问题
前端·npm·node.js
Larcher35 分钟前
新手也能学会,100行代码玩AI LOGO
前端·llm·html
徐子颐1 小时前
从 Vibe Coding 到 Agent Coding:Cursor 2.0 开启下一代 AI 开发范式
前端
小月鸭1 小时前
如何理解HTML语义化
前端·html
jump6801 小时前
url输入到网页展示会发生什么?
前端
诸葛韩信1 小时前
我们需要了解的Web Workers
前端
brzhang2 小时前
我觉得可以试试 TOON —— 一个为 LLM 而生的极致压缩数据格式
前端·后端·架构
yivifu2 小时前
JavaScript Selection API详解
java·前端·javascript
这儿有一堆花2 小时前
告别 Class 组件:拥抱 React Hooks 带来的函数式新范式
前端·javascript·react.js
十二春秋2 小时前
场景模拟:基础路由配置
前端