目录

Vue3 + hook + Apexcharts实现可视化图表渲染的封装 - 第一节 - 基础封装 (当前所在)
Vue3 + hook + Apexcharts实现可视化图表渲染的封装 - 第二节 - 图表的全屏功能(Vuex)

摘要

可视化是切图仔的必备技能,大部分需求我都是通过现有工具,不必自己实现。比如 EchartsD3Antv 等可视化工具,但是我们今天的主角都是这些,是一个叫 Apexcharts 的可视化工具。Apexchartsjs+svg 实现的(ps: Echarts 同时支持 canvassvg 渲染图表)。
因为工作偶然接触到 Apexcharts ,但是该工具的社区环境是真的差,如果遇到什么问题在网上基本上搜不到信息,只有官方文档。这里对开发使用过程的封装进行整理保存,后续针对其他的问题进行整理。

官方文档纯英文
中文文档非官方

正文

安装
npm i -D apexcharts
npm i -D vue3-apexcharts
基础使用

基础使用具体请查看 vue3-apexcharts 文档,

<template>
  <div>
    <ApexChart
      width="500"
      type="bar"
      :options="chartOptions"
      :series="series"
    />
  </div>
</template>

<script setup>
import ApexChart from 'vue3-apexcharts'

const chartOptions =  {
   chart: {
     id: "vuechart-example",
   },
   xaxis: {
     categories: [1991, 1992, 1993, 1994, 1995, 1996, 1997, 1998],
   },
},
const series = [
    {
      name: "series-1",
      data: [30, 40, 35, 50, 49, 60, 70, 91],
    },
],
</script>

在前端开发过程中图表的数据都是通过接口获取的,在数据更新后需要动态触发 Apexcharts 的更新,当我们在 ApexChart 中绑定一个ref时: <ApexChart ref="chartRef" /> ,可以通过 chartRef 访问图表的 DOM 元素,然后通过 chartRef.updateOptions(最新的数据) 函数去触发图表的重新渲染。注意:必须在 ApexChart DOM元素更新之后才能获取到 updateOptions 所以最好提前渲染一个数据为空的图表。如果无法获取,可以通过 nextTieck() 去渲染。

图表渲染的 hook 封装

当我们通过 v-for 去渲染多个 <Apexchart /> 时,我们可以通过动态创建 ref 正确绑定到对应的图表项中。下面直接放封装的hook

/*
**	useRenderChart.js
**	chartList 为传入的需要渲染的图表数组,具体格式见下面使用过程
*/
const useRenderChart = (chartList) => {
	// 对scatter数据进行做截取,如果
	const getScatterData = (data, intercept) => {
		const obj = {}
		Object.keys(data).forEach(key => {
			obj[key] = data[key].slice(0, intercept)
		})
		return obj
	}
	
	// 触发图表的重新渲染,chartObj.updateOptions(data)返回的是一个对象包含新的series|options
	const updateChart = (data, chartRef, chartObj) => {
		chartRef.updateOptions(chartObj.updateOptions(data))
	}

	// 统一渲染函数,触发所有图表的重新更新
	const renderCharts = (data) => {
		chartList.forEach(item => {
			const { key, refName, chartObj, intercept, type, interceptFn } = item
			item.data = data[key]
			let chartData = []
			if (type === 'scatter' && !interceptFn) {
				chartData = getScatterData(data[key], intercept)
			} else if (intercept) {
				chartData = interceptFn ? interceptFn(data[key], intercept) : data[key].slice(0, intercept)
			} else {
				chartData = data[key]
			}

			if (!chartData) return
			if (Array.isArray(chartData)) {
				chartData.length && updateChart(chartData, refName, chartObj)
			} else {
				updateChart(chartData, refName, chartObj)
			}
		})
	}

	// 动态设置图表的ref
	const setRefMap = (el, index) => {
		chartList[index].refName = el
	}

	return {
		getScatterData,
		renderCharts,
		setRefMap,
	}
}

export default useRenderChart

使用
<template>
	<div v-for="(item, index) in chartList" :key="item.key" :id="item.key" class="chart-item">
		<div class="title">{{ item.title }}</div>
		<ApexChart
			:ref="el => setRefMap(el, index)"
			:type="item.type"
			:options="item.chartObj.options"
			:series="item.chartObj.series"
		/>
	</div>
</template>

<script setup>
import ApexChart from 'vue3-apexcharts'
import { BoxPlotChart, KMChart } from '@/utils/chartsInit'
import useRenderChart from '@/hook/useRenderChart'

/**
 * 图表数据处理
 * @param {Array} chartList 需要渲染的图表列表
 * @param {Object} chartObj 图表对象 - 可做统一封装
 * @param {String} key 图表key,用于渲染图表时找到对应的数据
 * @param {String} title 图表标题
 * @param {String} type 图表类型
 * @param {Number} intercept 图表数据截取长度
 * @param {Function} interceptFn 自定义图表数据截取函数
 */
const chartList = reactive([
	{
		key: 'coef_data',
		title: 'Coefficient',
		type: 'boxPlot',
		chartObj: BoxPlotChart,
	},
	{
		key: 'train',
		title: 'KM plot (train)',
		type: 'line',
		chartObj: KMChart,
		intercept: 1000,
		interceptFn: (data, intercept) => {
			return data.map(item => {
				const obj = {}
				obj.prob = item.prob?.slice(0, intercept)
				obj.time = item.time?.slice(0, intercept)
				return obj
			})
		},
	},
])
const { renderCharts, setRefMap } = useRenderChart(chartList)

// 请求数据后触发图表重新渲染
async function handleGetData() {
	// ...
	// data数据一个对象{},包含chartList的每个key,对应值为图表的新数据
	renderCharts(data)
}

chartsInit.js 的图表封装,以上面的 BoxPlotChart 为例子

export const BoxPlotChart = {
	series: [],
	options: {
		xaxis: {
			type: 'category',
		},
		// ...
	},
	updateOptions: data => {
		return {
			series: [],
			xaxis: {},
			// ...等等options的配置
		}
	},
}

总结

对于上面的统一封装的 useRenderChart 这里去掉了 全屏查看图表功能 的,因为涉及到其他一部分逻辑,以及对上面所有代码的更改。将在下一篇文章补充实现。

Logo

瓜分20万奖金 获得内推名额 丰厚实物奖励 易参与易上手

更多推荐