echarts 图表二次封装(多色 + 正负轴+图表添加点击事件)

140 阅读1分钟

携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第1天,点击查看活动详情

参照最原始的echarts,官网地址:echarts.apache.org/examples/zh…

如果项目中数据可视化图表比较多,若每个图表都重新写一遍,代码又臭又长,修改的时候自己都要找半天才能对应上

所以,图表二次封装为组件,每一次用直接引用就行,代码瞬间减少80% 话不多说,直接上代码:

子组件:

html:

<template>
	<div class="chartContent" :style="{ height: height, width: width, position: 'relative' }">
		<div :id="id" :class="className" style="height: 100%; width: 100%"></div>
		<no-data v-if="noData" class="noDataCls"></no-data>
	</div>
</template>

js: NoData、resize是对图表暂无数据和窗口变化的组件方法,后面会把文件放上

<script>
import NoData from '@/components/NoData'
import resize from './mixins/resize'

export default {
	mixins: [resize],
	components: { NoData },
	props: {
		className: {
			type: String,
			default: 'chart'
		},
		id: {
			type: String,
			default: 'chart'
		},
		width: {
			type: String,
			default: '200px'
		},
		height: {
			type: String,
			default: '300px'
		}
	},
	data() {
		return {
			chart: null,
			noData: null
		}
	},
	beforeDestroy() {
		this.destoryChart()
	},
	methods: {
		destoryChart() {
			if (this.chart) {
				this.chart.clear()
				this.chart.dispose()
				this.chart = null
			}
		},
		initChart(data) {
			this.destoryChart()
			if (data.noData) {
				this.noData = true
				return
			}
			this.noData = false
			let { emitName, seriesData, yAxisData } = data
			this.chart = echarts.init(document.getElementById(this.id))
			var res = []
			if (seriesData && seriesData.length) {
				res = seriesData.map(item => {
					if (item.actualDays > item.targetDays) {
						return {
							value: item.actualDays,
							itemStyle: {
								color: '#F17373',
								borderRadius: 5 //圆角
							}
						}
					} else {
						return {
							value: item.actualDays,
							itemStyle: {
								color: '#3ed473',
								borderRadius: 5 //圆角
							}
						}
					}
				})
			}

			this.chart.setOption(
				{
					grid: {
						top: '30',
						left: '20',
						right: '40',
						bottom: '10',
						containLabel: true
					},
					tooltip: {
						trigger: 'axis'
					},
                                        // Y轴数据太多时滑动(看自己需求要不要保留)
					dataZoom: [
						{
                                                    start: 0, //默认为0
                                                    end: 100, //默认为100
                                                    type: 'slider',
                                                    maxValueSpan: 4, //显示数据的条数(默认显示10个)
                                                    // show: true,
                                                    show: yAxisData && yAxisData.length > 5 ? true : false,
                                                    yAxisIndex: [0],
                                                    handleSize: 20, //滑动条的 左右2个滑动条的大小
                                                    width: 12,
                                                    height: '76%', //组件高度
                                                    // left: 600, //左边的距离
                                                    right: 38, //右边的距离
                                                    top: 40, //上边边的距离
                                                    // borderColor: 'rgba(43,48,67,0.8)',
                                                    fillerColor: '#F5F5F5', //滑动块的颜色
                                                    // backgroundColor: 'rgba(13,33,117,0.5)', //两边未选中的滑动条区域的颜色
                                                    showDataShadow: false, //是否显示数据阴影 默认auto
                                                    showDetail: true, //即拖拽时候是否显示详细数值信息 默认true
                                                    realtime: true, //是否实时更新
                                                    filterMode: 'filter',
                                                    yAxisIndex: [0, 1] //控制的y轴
						},
						//滑块的属性
						{
                                                    type: 'inside',
                                                    show: true,

                                                    yAxisIndex: [0, 1],
                                                    start: 1, //默认为1
                                                    end: 100 //默认为100
						}
					],
					yAxis: {
						type: 'category',
						data: yAxisData
					},
					xAxis: {
						type: 'value'
					},
					series: [
					{
                                                data: res,
                                                barWidth: 16,
                                                label: {
                                                        show: true,
                                                        position: 'insideRight',
                                                        formatter: '{c}' + ' 天',
                                                        color: '#fff',
                                                        fontSize: 12
                                                },
                                                type: 'bar'
                                        }
                                ]
                        },
                        true
			)
			this.chart.on('click', param => {
				// 点击当前图表 触发父组件方法 并传参
				emitName && this.$emit(emitName, param.name)
			})
			this.chart.off('click', () => {
				console.log('解决多次调用接口问题')
			})
		}
	}
}
</script>

css:

<style lang="scss" scoped>
.noDataCls {
	position: absolute;
	left: 0px;
	top: 0px;
	right: 0px;
	bottom: 0px;
}
</style>

父组件: html:组件使用,id必须,ref必须且唯一

	<zhu-left-rights
            id="productionId2"
            @showProductionDetail2="showProductionDetail2"
            width="100%"
            height="200px"
            ref="zhuLeftRight2"
    ></zhu-left-rights>

js:获取数据源

// 引入组件,别忘了注册
import ZhuLeftRights from '@/components/Charts/ZhuLeftRights' 
		// 交付周期偏差
		showDetail2(val) {
			this.$Api.orderManage.getFinishOrderDetails(val).then(res => {
                        let { code, data } = res
                        if (code == 0) {
                                if (data.length) {
                                const productionDataY2 = new Array(data.length).fill(null)
                                const seriesData = []
                                for (let i = 0; i < data.length; i++) {
                                        productionDataY2[i] = data[i].name
                                        seriesData.push(data[i])
                                }
                                this.productionDataY2 = productionDataY2
                                this.$nextTick(() => {
                                        this.$refs.zhuLeftRight2.initChart({
                                                emitName: 'showProductionDetail2',
                                                yAxisData: this.productionDataY2,
                                                seriesData: seriesData,
                                                noData: !(this.productionDataY2 && this.productionDataY2.length > 0)
                                        })
                                })
                        } else {
                                this.$nextTick(() => {
                                        this.$refs.zhuLeftRight2.initChart({
                                                emitName: 'showProductionDetail2',
                                                yAxisData: [],
                                                seriesData: [],
                                                noData: true
                                        })
                                })
                            }
                        } else {
                              this.$message.error(res.msg)
                        }
                    })
		},

效果图:

image.png

image.png

说明:

子组件中:

循环seriesData的目的就是根据条件,显示不同颜色的柱状图, 如果想要正负轴显示的,及把seriesData里的value的值变成负数即可

父组件中: zhuLeftRight2: 通过ref来渲染调用子组件(单页面名称不可重复) emitName : 控制子组件图表点击后触发父组件事件 yAxisData: Y轴显示数据(内容过多可以加datazoom) seriesData:x轴数据源 noData:图表无数据是显示暂无数据图片

附件代码:

**// 新建一个js文件名为 debounce**
export function debounce(func, wait, immediate) {
	let timeout, args, context, timestamp, result

	const later = function () {
		// 据上一次触发时间间隔
		const last = +new Date() - timestamp

		// 上次被包装函数被调用时间间隔 last 小于设定时间间隔 wait
		if (last < wait && last > 0) {
			timeout = setTimeout(later, wait - last)
		} else {
			timeout = null
			// 如果设定为immediate===true,因为开始边界已经调用过了此处无需调用
			if (!immediate) {
				result = func.apply(context, args)
				if (!timeout) context = args = null
			}
		}
	}

	return function (...args) {
		context = this
		timestamp = +new Date()
		const callNow = immediate && !timeout
		// 如果延时不存在,重新设定延时
		if (!timeout) timeout = setTimeout(later, wait)
		if (callNow) {
			result = func.apply(context, args)
			context = args = null
		}

		return result
	}
}


**// 新建一个js文件名为resize 文件 并引入debounce文件**
import { debounce } from '@/common/utils/debounce'

export default {
	data() {
		return {
			$_sidebarElm: null,
			$_resizeHandler: null
		}
	},
	mounted() {
		this.initListener()
	},
	activated() {
		if (!this.$_resizeHandler) {
			// avoid duplication init
			this.initListener()
		}

		// when keep-alive chart activated, auto resize
		this.resize()
	},
	beforeDestroy() {
		this.destroyListener()
	},
	deactivated() {
		this.destroyListener()
	},
	methods: {
		// use $_ for mixins properties
		// https://vuejs.org/v2/style-guide/index.html#Private-property-names-essential
		$_sidebarResizeHandler(e) {
			if (e.propertyName === 'width') {
				this.$_resizeHandler()
			}
		},
		initListener() {
			this.$_resizeHandler = debounce(() => {
				this.resize()
			}, 10)
			window.addEventListener('resize', this.$_resizeHandler)

			this.$_sidebarElm = document.getElementsByClassName('sidebar-container')[0]
			this.$_sidebarElm && this.$_sidebarElm.addEventListener('transitionend', this.$_sidebarResizeHandler)
		},
		destroyListener() {
			window.removeEventListener('resize', this.$_resizeHandler)
			this.$_resizeHandler = null

			this.$_sidebarElm && this.$_sidebarElm.removeEventListener('transitionend', this.$_sidebarResizeHandler)
		},
		resize() {
			const { chart, treeGraph } = this
			chart && chart.resize()
			if (treeGraph) {
				const container = document.getElementById('antvOrzTree')
				const width = container.clientWidth
				const height = container.clientHeight
				treeGraph.changeSize(width, height) //修改画布的大小
				treeGraph.fitCenter() //将图移动到画布中心位置
				treeGraph.fitView() // 让画布内容适应视口
			}
		}
	}
}


// 暂无数据组件样式代码 (图片自己加想要的)
<template>
	<div class="noData">
		<div class="noData-neir">
			<img class="img" src="@/assets/imgs/noData.png" alt="" />
			<div class="tit">暂无数据</div>
		</div>
	</div>
</template>

<script>
export default {
	name: 'NoData',
	data() {
		return {}
	},
	props: {},
	mounted() {},
	methods: {}
}
</script>

<style lang="scss" scoped>
.noData {
	width: 100%;
	height: 100%;
	display: flex;
	align-items: center;
	justify-content: center;
	.noData-neir {
		text-align: center;
		.img {
			height: 60px;
		}
		.tit {
			font-size: 13px;
			font-family: Source Han Sans SC;
			font-weight: 400;
			color: rgba(0, 0, 0, 0.3);
			line-height: 46px;
		}
	}
}
</style>