Skip to content

Latest commit

 

History

History
847 lines (765 loc) · 17.2 KB

File metadata and controls

847 lines (765 loc) · 17.2 KB

echarts.js ECharts工具文档

概述

echarts.js是ECharts图表工具库,提供常用图表配置、主题管理、响应式处理等功能,简化图表开发和使用。

文件位置: src/utils/echarts.js

主要功能

1. 图表配置生成

  • 柱状图配置
  • 折线图配置
  • 饼图配置
  • 散点图配置
  • 雷达图配置
  • 仪表盘配置

2. 核心方法

基础配置生成器

/**
 * 生成基础图表配置
 * @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
          }
        ]
      }
    ]
  }
}

3. 主题管理

/**
 * 预定义主题
 */
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
  }
}

4. 响应式处理

/**
 * 图表响应式处理类
 */
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)
  }
}

5. 工具函数

/**
 * 格式化数值
 * @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
      }
    })
  }
}

使用示例

1. 基础柱状图

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)

2. 多系列折线图

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
})

3. 响应式图表

import { ChartResponsive } from '@/utils/echarts'

const chart = echarts.init(container)
chart.setOption(option)

// 启用响应式
const responsive = new ChartResponsive(chart, container)

// 组件销毁时清理
onBeforeUnmount(() => {
  responsive.destroy()
  chart.dispose()
})

注意事项

  1. 性能优化: 大数据量时使用数据采样和虚拟滚动
  2. 内存管理: 及时销毁图表实例,避免内存泄漏
  3. 主题适配: 根据系统主题动态切换图表主题
  4. 响应式: 确保图表在不同设备上正常显示
  5. 数据格式: 确保数据格式符合ECharts要求

相关文档


最后更新时间:2025-09-19