数据可视化图表库
重要通知
数据可视化图表库,一个基于 JavaScript 的开源可视化图表库。
基本概况
ZRender 基本概况
- GitHub:https://github.com/ecomfe/zrender
- API文档:https://ecomfe.github.io/zrender-doc/public/
- ZRender:https://www.runoob.com/w3cnote/html5-canvas-zrender.html
架构设计
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 基本概况
- 官网:https://antv.antgroup.com/
- G2:https://g2.antv.antgroup.com/zh
- G2Plot: https://g2plot.antv.antgroup.com/
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: {}
}