echarts.js是ECharts图表工具库,提供常用图表配置、主题管理、响应式处理等功能,简化图表开发和使用。
文件位置: src/utils/echarts.js
- 柱状图配置
- 折线图配置
- 饼图配置
- 散点图配置
- 雷达图配置
- 仪表盘配置
/**
* 生成基础图表配置
* @param {Object} options - 配置选项
* @returns {Object} ECharts配置对象
*/
export function createBaseConfig(options = {}) {
const {
title = '',
subtitle = '',
theme = 'default',
backgroundColor = 'transparent',
animation = true,
animationDuration = 1000
} = options
return {
backgroundColor,
animation,
animationDuration,
title: {
text: title,
subtext: subtitle,
left: 'center',
textStyle: {
fontSize: 18,
fontWeight: 'bold',
color: '#333'
},
subtextStyle: {
fontSize: 12,
color: '#666'
}
},
tooltip: {
trigger: 'axis',
backgroundColor: 'rgba(0, 0, 0, 0.8)',
borderColor: 'transparent',
textStyle: {
color: '#fff'
},
axisPointer: {
type: 'cross',
crossStyle: {
color: '#999'
}
}
},
legend: {
top: '10%',
left: 'center',
textStyle: {
color: '#333'
}
},
grid: {
left: '3%',
right: '4%',
bottom: '3%',
containLabel: true
},
toolbox: {
feature: {
saveAsImage: {
title: '保存为图片'
},
dataView: {
title: '数据视图',
readOnly: false
},
magicType: {
title: {
line: '切换为折线图',
bar: '切换为柱状图'
},
type: ['line', 'bar']
},
restore: {
title: '还原'
}
}
}
}
}/**
* 生成柱状图配置
* @param {Object} data - 数据对象
* @param {Object} options - 配置选项
* @returns {Object} 柱状图配置
*/
export function createBarChart(data, options = {}) {
const {
title = '柱状图',
xAxisData = [],
series = [],
colors = ['#5470c6', '#91cc75', '#fac858', '#ee6666', '#73c0de'],
showDataZoom = false,
stack = false
} = options
const baseConfig = createBaseConfig({ title, ...options })
return {
...baseConfig,
color: colors,
xAxis: {
type: 'category',
data: xAxisData,
axisLabel: {
color: '#666',
rotate: xAxisData.length > 10 ? 45 : 0
},
axisLine: {
lineStyle: {
color: '#ddd'
}
}
},
yAxis: {
type: 'value',
axisLabel: {
color: '#666'
},
axisLine: {
lineStyle: {
color: '#ddd'
}
},
splitLine: {
lineStyle: {
color: '#f0f0f0'
}
}
},
series: series.map((item, index) => ({
name: item.name,
type: 'bar',
data: item.data,
stack: stack ? 'total' : undefined,
itemStyle: {
borderRadius: [4, 4, 0, 0],
color: colors[index % colors.length]
},
emphasis: {
itemStyle: {
shadowBlur: 10,
shadowOffsetX: 0,
shadowColor: 'rgba(0, 0, 0, 0.5)'
}
},
...item
})),
dataZoom: showDataZoom ? [
{
type: 'slider',
start: 0,
end: 100
},
{
type: 'inside',
start: 0,
end: 100
}
] : undefined
}
}/**
* 生成折线图配置
* @param {Object} data - 数据对象
* @param {Object} options - 配置选项
* @returns {Object} 折线图配置
*/
export function createLineChart(data, options = {}) {
const {
title = '折线图',
xAxisData = [],
series = [],
colors = ['#5470c6', '#91cc75', '#fac858', '#ee6666', '#73c0de'],
smooth = true,
showArea = false,
showDataZoom = false
} = options
const baseConfig = createBaseConfig({ title, ...options })
return {
...baseConfig,
color: colors,
xAxis: {
type: 'category',
data: xAxisData,
boundaryGap: false,
axisLabel: {
color: '#666'
},
axisLine: {
lineStyle: {
color: '#ddd'
}
}
},
yAxis: {
type: 'value',
axisLabel: {
color: '#666'
},
axisLine: {
lineStyle: {
color: '#ddd'
}
},
splitLine: {
lineStyle: {
color: '#f0f0f0'
}
}
},
series: series.map((item, index) => ({
name: item.name,
type: 'line',
data: item.data,
smooth,
symbol: 'circle',
symbolSize: 6,
lineStyle: {
width: 3,
color: colors[index % colors.length]
},
itemStyle: {
color: colors[index % colors.length],
borderColor: '#fff',
borderWidth: 2
},
areaStyle: showArea ? {
opacity: 0.3,
color: colors[index % colors.length]
} : undefined,
emphasis: {
scale: true,
scaleSize: 8
},
...item
})),
dataZoom: showDataZoom ? [
{
type: 'slider',
start: 0,
end: 100
}
] : undefined
}
}/**
* 生成饼图配置
* @param {Array} data - 数据数组
* @param {Object} options - 配置选项
* @returns {Object} 饼图配置
*/
export function createPieChart(data, options = {}) {
const {
title = '饼图',
colors = ['#5470c6', '#91cc75', '#fac858', '#ee6666', '#73c0de', '#9a60b4', '#ea7ccc'],
radius = ['40%', '70%'],
center = ['50%', '50%'],
showLabel = true,
showLabelLine = true,
roseType = false
} = options
const baseConfig = createBaseConfig({ title, ...options })
return {
...baseConfig,
color: colors,
tooltip: {
trigger: 'item',
formatter: '{a} <br/>{b}: {c} ({d}%)'
},
series: [
{
name: title,
type: 'pie',
radius,
center,
roseType: roseType ? 'area' : false,
data: data.map((item, index) => ({
...item,
itemStyle: {
color: colors[index % colors.length]
}
})),
label: {
show: showLabel,
formatter: '{b}: {d}%',
color: '#333'
},
labelLine: {
show: showLabelLine
},
emphasis: {
itemStyle: {
shadowBlur: 10,
shadowOffsetX: 0,
shadowColor: 'rgba(0, 0, 0, 0.5)'
}
}
}
]
}
}/**
* 生成仪表盘配置
* @param {Number} value - 当前值
* @param {Object} options - 配置选项
* @returns {Object} 仪表盘配置
*/
export function createGaugeChart(value, options = {}) {
const {
title = '仪表盘',
min = 0,
max = 100,
unit = '%',
splitNumber = 10,
color = '#5470c6'
} = options
const baseConfig = createBaseConfig({ title, ...options })
return {
...baseConfig,
series: [
{
name: title,
type: 'gauge',
min,
max,
splitNumber,
radius: '80%',
center: ['50%', '55%'],
startAngle: 225,
endAngle: -45,
axisLine: {
lineStyle: {
width: 30,
color: [
[0.3, '#67e0e3'],
[0.7, '#37a2da'],
[1, '#fd666d']
]
}
},
pointer: {
itemStyle: {
color: color
}
},
axisTick: {
distance: -30,
length: 8,
lineStyle: {
color: '#fff',
width: 2
}
},
splitLine: {
distance: -30,
length: 30,
lineStyle: {
color: '#fff',
width: 4
}
},
axisLabel: {
color: '#666',
distance: 40,
fontSize: 12
},
detail: {
valueAnimation: true,
formatter: `{value}${unit}`,
color: color,
fontSize: 20,
offsetCenter: [0, '70%']
},
data: [
{
value,
name: title
}
]
}
]
}
}/**
* 预定义主题
*/
export const themes = {
default: {
backgroundColor: 'transparent',
textStyle: {
color: '#333'
},
title: {
textStyle: {
color: '#333'
}
},
legend: {
textStyle: {
color: '#333'
}
}
},
dark: {
backgroundColor: '#1e1e1e',
textStyle: {
color: '#fff'
},
title: {
textStyle: {
color: '#fff'
}
},
legend: {
textStyle: {
color: '#fff'
}
}
},
blue: {
backgroundColor: '#f0f8ff',
color: ['#1890ff', '#52c41a', '#faad14', '#f5222d', '#722ed1'],
textStyle: {
color: '#333'
}
}
}
/**
* 应用主题
* @param {Object} config - 图表配置
* @param {String} themeName - 主题名称
* @returns {Object} 应用主题后的配置
*/
export function applyTheme(config, themeName = 'default') {
const theme = themes[themeName] || themes.default
return {
...config,
...theme,
color: theme.color || config.color
}
}/**
* 图表响应式处理类
*/
export class ChartResponsive {
constructor(chart, container) {
this.chart = chart
this.container = container
this.resizeObserver = null
this.init()
}
init() {
// 监听窗口大小变化
window.addEventListener('resize', this.handleResize.bind(this))
// 使用ResizeObserver监听容器大小变化
if (window.ResizeObserver) {
this.resizeObserver = new ResizeObserver(this.handleResize.bind(this))
this.resizeObserver.observe(this.container)
}
}
handleResize() {
if (this.chart && !this.chart.isDisposed()) {
// 延迟执行resize,避免频繁调用
clearTimeout(this.resizeTimer)
this.resizeTimer = setTimeout(() => {
this.chart.resize()
this.updateResponsiveConfig()
}, 100)
}
}
updateResponsiveConfig() {
const containerWidth = this.container.clientWidth
const containerHeight = this.container.clientHeight
// 根据容器大小调整配置
const option = this.chart.getOption()
// 调整字体大小
if (containerWidth < 600) {
option.title[0].textStyle.fontSize = 14
option.legend[0].textStyle.fontSize = 10
} else {
option.title[0].textStyle.fontSize = 18
option.legend[0].textStyle.fontSize = 12
}
// 调整网格大小
if (containerWidth < 400) {
option.grid[0].left = '10%'
option.grid[0].right = '10%'
} else {
option.grid[0].left = '3%'
option.grid[0].right = '4%'
}
this.chart.setOption(option)
}
destroy() {
window.removeEventListener('resize', this.handleResize.bind(this))
if (this.resizeObserver) {
this.resizeObserver.disconnect()
}
clearTimeout(this.resizeTimer)
}
}/**
* 格式化数值
* @param {Number} value - 数值
* @param {String} type - 格式类型
* @returns {String} 格式化后的字符串
*/
export function formatValue(value, type = 'number') {
switch (type) {
case 'percent':
return `${(value * 100).toFixed(1)}%`
case 'currency':
return `¥${value.toLocaleString()}`
case 'number':
return value.toLocaleString()
case 'decimal':
return value.toFixed(2)
default:
return value.toString()
}
}
/**
* 生成颜色数组
* @param {Number} count - 颜色数量
* @param {String} baseColor - 基础颜色
* @returns {Array} 颜色数组
*/
export function generateColors(count, baseColor = '#5470c6') {
const colors = []
const hsl = hexToHsl(baseColor)
for (let i = 0; i < count; i++) {
const hue = (hsl.h + (360 / count) * i) % 360
colors.push(hslToHex(hue, hsl.s, hsl.l))
}
return colors
}
/**
* 十六进制颜色转HSL
* @param {String} hex - 十六进制颜色
* @returns {Object} HSL对象
*/
function hexToHsl(hex) {
const r = parseInt(hex.slice(1, 3), 16) / 255
const g = parseInt(hex.slice(3, 5), 16) / 255
const b = parseInt(hex.slice(5, 7), 16) / 255
const max = Math.max(r, g, b)
const min = Math.min(r, g, b)
let h, s, l = (max + min) / 2
if (max === min) {
h = s = 0
} else {
const d = max - min
s = l > 0.5 ? d / (2 - max - min) : d / (max + min)
switch (max) {
case r: h = (g - b) / d + (g < b ? 6 : 0); break
case g: h = (b - r) / d + 2; break
case b: h = (r - g) / d + 4; break
}
h /= 6
}
return { h: h * 360, s: s * 100, l: l * 100 }
}
/**
* HSL转十六进制颜色
* @param {Number} h - 色相
* @param {Number} s - 饱和度
* @param {Number} l - 亮度
* @returns {String} 十六进制颜色
*/
function hslToHex(h, s, l) {
h /= 360
s /= 100
l /= 100
const hue2rgb = (p, q, t) => {
if (t < 0) t += 1
if (t > 1) t -= 1
if (t < 1/6) return p + (q - p) * 6 * t
if (t < 1/2) return q
if (t < 2/3) return p + (q - p) * (2/3 - t) * 6
return p
}
let r, g, b
if (s === 0) {
r = g = b = l
} else {
const q = l < 0.5 ? l * (1 + s) : l + s - l * s
const p = 2 * l - q
r = hue2rgb(p, q, h + 1/3)
g = hue2rgb(p, q, h)
b = hue2rgb(p, q, h - 1/3)
}
const toHex = (c) => {
const hex = Math.round(c * 255).toString(16)
return hex.length === 1 ? '0' + hex : hex
}
return `#${toHex(r)}${toHex(g)}${toHex(b)}`
}
/**
* 数据处理工具
*/
export const dataUtils = {
/**
* 数据分组
* @param {Array} data - 原始数据
* @param {String} key - 分组键
* @returns {Object} 分组后的数据
*/
groupBy(data, key) {
return data.reduce((groups, item) => {
const group = item[key]
if (!groups[group]) {
groups[group] = []
}
groups[group].push(item)
return groups
}, {})
},
/**
* 数据聚合
* @param {Array} data - 数据数组
* @param {String} valueKey - 值键名
* @param {String} method - 聚合方法
* @returns {Number} 聚合结果
*/
aggregate(data, valueKey, method = 'sum') {
const values = data.map(item => item[valueKey]).filter(v => typeof v === 'number')
switch (method) {
case 'sum':
return values.reduce((sum, val) => sum + val, 0)
case 'avg':
return values.reduce((sum, val) => sum + val, 0) / values.length
case 'max':
return Math.max(...values)
case 'min':
return Math.min(...values)
case 'count':
return values.length
default:
return 0
}
},
/**
* 数据排序
* @param {Array} data - 数据数组
* @param {String} key - 排序键
* @param {String} order - 排序顺序
* @returns {Array} 排序后的数据
*/
sortBy(data, key, order = 'asc') {
return [...data].sort((a, b) => {
const aVal = a[key]
const bVal = b[key]
if (order === 'desc') {
return bVal > aVal ? 1 : bVal < aVal ? -1 : 0
} else {
return aVal > bVal ? 1 : aVal < bVal ? -1 : 0
}
})
}
}import { createBarChart } from '@/utils/echarts'
const chartData = {
xAxisData: ['1月', '2月', '3月', '4月', '5月'],
series: [
{
name: '销售额',
data: [120, 200, 150, 80, 70]
}
]
}
const option = createBarChart(chartData, {
title: '月度销售统计',
colors: ['#409EFF']
})
// 使用ECharts渲染
const chart = echarts.init(document.getElementById('chart'))
chart.setOption(option)import { createLineChart } from '@/utils/echarts'
const option = createLineChart({
xAxisData: ['周一', '周二', '周三', '周四', '周五'],
series: [
{
name: '访问量',
data: [820, 932, 901, 934, 1290]
},
{
name: '用户数',
data: [220, 182, 191, 234, 290]
}
]
}, {
title: '网站流量统计',
smooth: true,
showArea: true
})import { ChartResponsive } from '@/utils/echarts'
const chart = echarts.init(container)
chart.setOption(option)
// 启用响应式
const responsive = new ChartResponsive(chart, container)
// 组件销毁时清理
onBeforeUnmount(() => {
responsive.destroy()
chart.dispose()
})- 性能优化: 大数据量时使用数据采样和虚拟滚动
- 内存管理: 及时销毁图表实例,避免内存泄漏
- 主题适配: 根据系统主题动态切换图表主题
- 响应式: 确保图表在不同设备上正常显示
- 数据格式: 确保数据格式符合ECharts要求
最后更新时间:2025-09-19