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圖表,同時圖表的配置項也遠比這負責,這就導致了:
- 配置重復:每個圖表都需要重復設置顏色、背景、提示框等通用配置。
- 維護困難:修改主題或樣式時,需要逐一調整每個圖表的配置。
- 類型散亂:不同圖表類型的配置差異大,缺乏統一抽象。
- 維護成本高: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?的誕生
有了這些基礎設施,我開始設計核心的工廠類。我的設計原則是:
- 使用簡單:簡單調用函數就能創建
- 配置靈活:支持自定義配置覆蓋默認值
- 功能完整:支持主題切換、類型切換、動態更新
于是有了這樣的 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