数据可视化图表库

重要通知

数据可视化图表库,一个基于 JavaScript 的开源可视化图表库。

基本概况

ZRender 基本概况

架构设计

ZRender 核心实现

ZRender 源码解析

Echarts 基本概况

代码示例

注意,Echarts容器尺寸必须设置明确的大小,否则不生效。如果自适应,请如此设置,容器尺寸设置为width:100%;height:100%,也可生效,即如下配置。

<div :id="id" class="com-frame chart-container" :class="{ visibility: !tableData.length }"></div>

<style lang="scss" scoped>
	div.chart-container {
		position: relative;

		&.visibility {
			visibility: hidden;
			opacity: 0;
			pointer-events: none;
		}
	}
</style>
import * as echarts from 'echarts';

const echartDOM = document.getElementById('main');
const defineEcharts = echarts.init(echartDOM);

defineEcharts.showLoading();
defineEcharts.hideLoading();

// 配置项
const configOptions = {};

defineEcharts.setOption(configOptions);


// echarts获取双y轴自动生成的最大值。    要在setOption之后才能获取
console.log(charts.getModel().getComponent('yAxis').axis.scale._extent);
console.log(charts.getModel().getComponent('yAxis', 1).axis.scale._extent);

完整配置项

// 配置项
const configOptions = {
  // 标题组件
  title: {},


  // 图例 配置项
  legend: {
    show: true,
    icon: '' // 标记类型 'circle', 'rect', 'roundRect', 'triangle', 'diamond', 'pin', 'arrow', 'none'
  },


  // 提示框组件
  tooltip: {
    show: true,
    trigger: '',

    // 坐标轴指示器配置项
    axisPointer: {},
    formatter(params: Record<string, any>) {
      return params
    },
    valueFormatter(value: string) {
      return value + ' %'
    }
  },


  // 坐标轴指示器 | 全局公用设置
  axisPointer: {
    show: true, // 默认不显示
  },


  // 工具栏
  toolbox: {
    show: true,
    orient: 'horizontal', // 工具栏 icon 的布局朝向: 'horizontal' | 'vertical'
    itemSize: 10, // 工具栏 icon 的大小
    itemGap: 5, // 工具栏 icon 每项之间的间隔。横向布局时为水平间隔,纵向布局时为纵向间隔。
    showTitle: true, // 是否在鼠标 hover 的时候显示每个工具 icon 的标题
    feature: { // 各工具配置项
      // 数据视图工具
      dataView: { show: true, readOnly: false },
      // 动态类型切换
      magicType: { show: true, type: ['line', 'bar'] },
      // 重置
      restore: { show: true },
      // 导出图片
      saveAsImage: { show: true },
      // 数据区域缩放
      dataZoom: { show: true }
    }
  },


  // X 轴
  xAxis: {
    // 坐标轴刻度相关设置
    axisTick: {},

    // 坐标轴轴线相关设置
    axisLine: {
      lineStyle: {
        width: 2,
        color: '#f00' // 轴线颜色
      }
    },
    axisLabel: {
      color: '#000' // 文字颜色
    }
  },


  // Y 轴
  yAxis: [
    {
      name: '',
      type: '',
      min: 0,
      max: 250,
      interval: 50,
      axisLabel: []
    }
  ],


  // 全局调色盘
  color: [],


  // 数据集,见文档:https://echarts.apache.org/zh/option.html#series
  series: [
    {
      name: '', // 系列名称,用于tooltip的显示,legend 的图例筛选,在 setOption 更新数据和配置项时用于指定对应的系列
      type: '',
      // 普通样式
      itemStyle: {},
      label: {},
      emphasis: { // 高亮样式
        itemStyle: {}
      }
      // 系列调色盘
      color: []
    },

    // 柱状图
    {
      type: 'bar',
      data: [],
      barWidth: '', // 柱条宽度和高度
      barMinWidth: '',
      barMaxWidth: '',
      barHeight: ''
      barMinHeight: '',
      barMaxHeight: '',
      barGap: '', // 不同系列的柱间距离,为百分比
      barCategoryGap: '', // 同一系列的柱间距离,默认为类目间距的20%
      itemStyle: {
        // 渐变
        color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
          { offset: 0, color: '#83bff6' },
          { offset: 0.5, color: '#188df0' },
          { offset: 1, color: '#188df0' }
        ])
      },
      stack: '' // 堆叠
    }
  ],


  // 直角坐标系内绘图网格
  grid: {
    top: 0, // grid 组件离容器上侧的距离
    right: 0, // grid 组件离容器右侧的距离
    bottom: 0, // grid 组件离容器下侧的距离
    left: 0, // grid 组件离容器左侧的距离
  }


  // 滚动条
  dataZoom: [
    {
      type: 'inside', // 内置型数据区域缩放组件
      show: true,
      xAxisIndex: [], // 控制多个X轴
      yAxisIndex: [] // 控制多个Y轴
    },
    {
      type: 'slider', // 滑动条型数据区域缩放组件
      show: true,
      xAxisIndex: [], // 控制多个X轴
      yAxisIndex: [], // 控制多个Y轴
      dataBackground: { // 数据阴影的样式
      },
      width: 10, // 组件宽度
      height: 35, // 组件高度
			maxValueSpan: 20, // 用于限制窗口大小的最大值(实际数值)
    }
  ]
};

交互示例

<template>
	<section ref="echartsDOM" class="com-frame"></section>
</template>

<script setup lang="ts">
import { ref, reactive, onMounted } from 'vue'
import * as echarts from 'echarts'

import { getStoreRankAPI } from '../api'
import { queryCondition } from '../config'
import { initEchartsConfig } from '../data/store-rank'

const echartsDOM = ref(null)
const autoPlayToolTip = ref(null)
let defineEcharts = reactive(null)
const stat_list = reactive([] as Record<string, any>[])

const initPageData = async function () {
	const result = await getStoreRankAPI({
		page: 0,
		limit: 100,
		...queryCondition(),
		sort_object: 'fee_total',
		sort_type: 2,
		order_type: 1,
		stat_object: 3
	})
	// console.info('实时订单店铺统计: ', result)

	if (result?.data?.stat_list) {
		stat_list.splice(0, stat_list.length)
		stat_list.push.apply(stat_list, result.data.stat_list)
	}
}

async function updateRender() {
	await initPageData()
	window.clearInterval(autoPlayToolTip.value)
	const datalist = [...stat_list.slice(0, 15)]
	defineEcharts.setOption(initEchartsConfig(defineEcharts, datalist, echarts))

	const length = datalist.length
	let index = 0
	autoPlayToolTip.value = window.setInterval(() => {
		defineEcharts.dispatchAction({
			type: 'showTip',
			seriesIndex: 0,
			dataIndex: index
		})

		index++
		if (index >= length) {
			index = 0
		}
	}, 3000)
}

onMounted(() => {
	defineEcharts = echarts.init(echartsDOM.value)
	updateRender()
})

window.addEventListener(
	'resize',
	() => {
		defineEcharts && defineEcharts.resize()
	},
	false
)

window.setInterval(() => {
	updateRender()
}, 120000)
</script>

<style lang="scss" scoped>
section {
	position: relative;
	pointer-events: none;
}
</style>
export function initEchartsConfig(myChart: any, data: Record<string, any>[] = [], echarts: any) {
	const datalist = data

	const nameList = datalist.map((item) => item.store_info.name)
	nameList.forEach((item, index) => {
		nameList[index] = '***'
	})
	const nameColors: Record<string, any> = {}

	for (let i = 0; i < nameList.length; i++) {
		nameColors[nameList[i]] = '#fff'
	}

	const sale_count_list = datalist.map((item) => item.sale_sum.sale_fee.replace(/[¥,]/g, ''))

	const seriesData: string | number[] = sale_count_list

	const echartsOptions = {
		...echartsConfig.global,
		title: {
			text: '累计实时订单店铺销售额统计(Top 15)',
			...echartsConfig.title
		},
		tooltip: {
			trigger: 'axis'
		},
		grid: {
			...echartsConfig.grid,
			top: '50',
			left: '3%',
			right: '8%',
			bottom: '6%',
			containLabel: true
		},
		xAxis: {
			...echartsConfig.xAxis,
			max: 'dataMax'
		},
		yAxis: {
			...echartsConfig.yAxis,
			type: 'category',
			data: nameList,
			inverse: true,
			animationDuration: 300,
			animationDurationUpdate: 300,
			max: nameList.length - 1,
			axisLabel: {
				...echartsConfig.yAxis.axisLabel,
				fontSize: 12
			}
		},
		series: [
			{
				realtimeSort: true,
				name: '销售额',
				type: 'bar',
				barWidth: '12',
				data: seriesData,
				label: {
					show: true,
					position: 'right',
					valueAnimation: true
				},
				itemStyle: {
					color: function (param: Record<string, any>) {
						const length = datalist.length
						const { dataIndex } = param

						if (dataIndex < 3) {
							return echartsConfig.global.color[2]
						} else if (dataIndex >= length - 5) {
							return '#A1C5FF'
						} else {
							return echartsConfig.global.color[0]
						}
					}
				}
			}
		],
		animationDuration: 0,
		animationDurationUpdate: 3000,
		animationEasing: 'linear',
		animationEasingUpdate: 'linear'
	}

	return echartsOptions
}

Tooltip 提示框

// 清空数据,否则Tooltip数据容易存在缓存
dualAxes.clear()
dualAxes.setOption(mergeOptions)

轮询提示框动效

// const length = xAxisList.length
const length = stat_list.length
let index = 0
autoPlayToolTip.value = window.setInterval(() => {
  defineEcharts.dispatchAction({
    type: 'showTip',
    seriesIndex: 0,
    dataIndex: index
  })

  index++
  if (index >= length) {
    index = 0
  }		
}, 3000)

Echarts 核心实现

defineEcharts.on('click', function(params) {});

Echarts 源码解析

AntV 基本概况

AntV 核心实现

G2Plot

开箱即用的图表库

<div id="container" style="width:1180px;height:500px;"></div>
import { 
  Line, // 折线图
  Column, // 柱状图
  Bar, // 条形图
  Area, // 面积图
  , // 色块图 / 热力图
  Pie, // 饼图
  Funnel, // 漏斗图
  Gauge, // 仪表盘
  Histogram, // 直方图
  Waterfall, // 瀑布图
  Bullet, // 子弹图
  Liquid, // 水波图
  Radar, // 雷达图
  Scatter, // 散点图
  Violin, // 小提琴图
  Facet, // 分面图
  Venn, // 韦恩图
} from '@antv/g2plot';


const data = [
  { year: '1991', value: 3 },
  { year: '1992', value: 4 },
  { year: '1993', value: 3.5 },
  { year: '1994', value: 5 },
  { year: '1995', value: 4.9 },
  { year: '1996', value: 6 },
  { year: '1997', value: 7 },
  { year: '1998', value: 9 },
  { year: '1999', value: 13 },
];


const line = new Line('container', {
  data, // 数据源
  data: [],

  // 图表内边距
  autoPadding: {
    top: 
    right: 
    bottom: 
    left: 
  },

  // X 坐标轴
  xField: 'year', // string | string[]

  // Y 坐标轴
  yField: 'value', // string | string[]


  // X 坐标轴 配置项
  xAxis: false; // 隐藏 x 轴
  xAxis: {
    text: '',
    line: {
      style: {}
    }
  },

  // Y 坐标轴 配置项
  yAxis: false; // 隐藏 x 轴
  yAxis: {
    text: '',
    style: {
      fill: '#FE740C',
    },

    // 双轴图,yAxis 类型是以 yField 中的字段作为 key 值的object
    value: {
      label: {
        formatter: (text: string, item: Record<string, any>, index: number) => {
          return transThousandth(text)
        }
      }
    },
    count: {
      label: {
        formatter: (text: string, item: Record<string, any>, index: number) => {
          return text + '%'
        }
      }
    }
  },

  meta: {
    //
  },

  smooth: true,
  point: {},

  // 图例 配置项
  legend: false, // 关闭图例
  legend: {
    layout: 'horizontal',
    position: 'right' // 'top', 'top-left', 'top-right', 'left', 'left-top', 'left-bottom', 'right', 'right-top', 'right-bottom', 'bottom', 'bottom-left', 'bottom-right'
  },

  // 图表主题 https://g2plot.antv.antgroup.com/api/options/theme
  theme: 'default', // 'dark',
  theme: {
    //
  },


  // 滚动条
  scrollbar: {},


  // 添加辅助文本、辅助线
  annotations: [
    {
      type: 'text'
    },
    {
      type: 'line'
    }
  ],

  /**
   * 双轴图 图形配置项 https://g2plot.antv.antgroup.com/api/plots/dual-axes#geometryoptions
   * 双轴折线图: [Line, Line] 
   * 柱线混合图: [Column, Line]
   */
  geometryOptions: [
    // 图形一
    {
      geometry: 'column', // 图形类型
      isGroup: true, // 是否分组
      seriesField: 'type', // 区分字段
      columnWidthRatio: 0.4
    },

    // 图形二
    {
      geometry: 'line',
      seriesField: 'name',
      lineStyle: ({ name }) => {
        if (name === 'a') {
          return {
            lineDash: [1, 4],
            opacity: 1
          }
        }
        return {
          opacity: 0.5
        }
      }
    }
  ],

  // 悬浮提示 - Tooltip
  tooltip: {
    formatter: (datum: Datum) => {
      return { name: datum.x, value: datum.y + '%' };
    },
  },

  // 统计文本 - Statistic
  label: {
    type: 'inner',
    offset: '-50%',
    content: '{value}',
    style: {
      textAlign: 'center',
      fontSize: 14,
    },
  },

  // 图表交互 https://g2plot.antv.antgroup.com/api/options/interactions
  interactions: [{ type: 'element-active' }],

  // 贴图图案 https://g2plot.antv.antgroup.com/api/options/pattern
  pattern: {}
});

// element 添加点击事件
line.on('element:click', (e) => {
  console.log(e);
});

// annotation 添加点击事件
line.on('annotation:click', (e) => {
  console.log(e);
});

// axis-label 添加点击事件
line.on('axis-label:click', (e) => {
  console.log(e);
});


// 渲染
line.render();

// 更新
line.update(options: Partial<PlotOptions>);

// 修改图表的数据,并自动重新渲染
line.changeData(data: object[] | number);

plot.changeSize(width: number, height: number);

plot.destroy();

plot.once(event: string, callback: Function);

plot.off(event?: string, callback?: Function);

plot.setState(state?: 'active' | 'inactive' | 'selected', condition?: Function, status: boolean = true);

plot.getStates();

plot.addAnnotations(annotations: Annotation[], view?: View) => void;

plot.removeAnnotations(annotations: { id: string }[]) => void;


/**
 * 图表事件 https://g2plot.antv.antgroup.com/api/options/events
 */
plot.on(event: string, callback: Function);


/**
 * new Line对象
 */
{
  chart: {}
  container: HTMLElement
  options: {}
  type: ''
  unbind: Function
  _events: {}
}

AntV 源码分析

Last Updated:
Contributors: 709992523