数据可视化的前端项目实践

前言

这个项目主要使用的技术为vue2+Echarts+Axios。
实现了五个图表的显示:

  • 地区销量趋势(折线图)
  • 商家销量图统计(柱状图)
  • 商户分布与销量排行(地图+散点图)
  • 地区销量排行(柱状图)
  • 热销商品金额占比统计(饼图)

前端项目的准备

  • 安装脚手架和创建项目
npm install -g @vue/cli
vue create web
  • 后续选择自定义安装,注意ESLint设置为如下图所示,并且在.eslintrc.js中添加如下代码不然很容易报格式上的错误。
    在这里插入图片描述
    在这里插入图片描述

  • 创建完成后使用如下代码启动项目

npm run serve

项目的基本配置

配置端口号和自动打开浏览器的设置

在vue.config.js文件里面添加如下代码

// 使用vue-cli创建出来的vue工程, Webpack的配置是被隐藏起来了的
// 如果想覆盖Webpack中的默认配置,需要在项目的根路径下增加vue.config.js文件
module.exports = {
devServer: {
port: 8080, // 端口号的配置
open: true // 自动打开浏览器
}
}

配置全局echarts 对象

  • 首先引入echarts 包,在 public/index.html 文件中引入 echarts.min.js 文件
    <!-- 通过script标签引入文件 -->
    <script src="static/lib/echarts.min.js"></script>
  • 然后挂载到 Vue 原型上,在 src/main.js 文件中挂载。
    由于在 index.html 中已经通过script标签引入了 echarts.js , 故在 window 全局对象中存在 echarts 全局对象
// 将全局的echarts对象挂载到Vue的原型对象上
// 别的组件中 this.$echarts
Vue.prototype.$echarts = window.echarts

这样在其他组件中可以直接用this.$echarts

axios 的配置和处理

  • 首先安装axios包
npm install axios
  • 然后在main.js中封装axios对象,便于使用
import axios from 'axios'
axios.defaults.baseURL = 'http://127.0.0.1:8888/api/'
// 将axios挂载到Vue的原型对象上
Vue.prototype.$http = axios

商家销量图统计(柱状图)

最终效果展示图

在这里插入图片描述

组件结构设计

在 src/components/ 目录下建立 Seller.vue , 这个组件是真实展示图表的组件,在 src/views/ 目录下建立 SellerPage.vue ,这个组件是对应于路由 /seller 而展示的。
另外说一嘴组件的注册和使用

<template>
	<div class="com-page">
		<!-- 第三步:使用 -->
		<seller></seller> 
	</div>
</template>
<script>
//第一步:引入组件
import Seller from '@/components/Seller'
export default {
	data () {
		return {}
	},
	methods: {},
	components: {
	//第二步:在compoents里面注册
	seller:Seller
	}
}
</script>

增加路由规则

在 src/router/index.js 文件中修改

......
import SellerPage from '@/views/SellerPage'
......
const routes = [
{
	path: '/sellerpage',
	component: SellerPage
}
]

图表基本结构的搭建

<!--  -->
<template>
  <div class='com-container'>
    <div class="com-chart" ref="seller_ref"></div>
  </div>
</template>

<script>

export default {
  data() {
    return {
      chartInstance: null, 
      allData: null
    };
  },
  mounted(){
    // 第四步使用方法
    this.initChart()
    this.getData()
    // 第五步:监听窗口缩放事件
    window.addEventListener('resize',this.screenAdapter)
    this.screenAdapter()
  },
  destroyed(){
    // 第六步:销毁监听器
    window.removeEventListener('resize',this.screenAdapter)
  },
  methods: {
    // 第一步引入
    initChart() {
      this.chartInstance = this.$echarts.init(this.$refs.seller_ref)
      const initOption = {}
      this.chartInstance.setOption(initOption)
    },
    // 第二步获取数据
    async getData() {
      // await this.$http.get()
      // 对allData赋值
      this.updateChart() //调用第三步的方法
    },
    // 第三步更新图表
    updateChart() {
      // 处理数据
      const dataOption = {}
      this.chartInstance.setOption(dataOption)
    },
    // 第五步:监听窗口缩放事件
    screenAdapter(){
      const adapterOption = {}
      this.chartInstance.setOption(adapterOption)
      this.chartInstance.resize()
    }
  },
}
</script>
<style lang='less' scoped></style>

初始化数据

获取服务器数据

在getData中获取数据

// 获取服务器数据
    async getData() {
      // 这里使用了一个解构赋值
      const { data: ret } = await this.$http.get('seller')
      this.allData = ret
      // console.log(ret);
      this.allData.sort((a, b) => {
        return a.value - b.value //从小到大的排序
      })
      this.updateChart()
      this.startInterval()
    },

初始化echartInstance对象

initChart() {
      this.chartInstance = this.$echarts.init(this.$refs.seller_ref, 'chalk')
      // 对图表初始化的控制
      const initOption = {
        xAxis: {
          type: 'value'
        },
        yAxis: {
          type: 'category',
        },
        series: [
          {
            type: 'bar',
            },
          }
        ]
      }
      this.chartInstance.setOption(initOption)
    },
updateChart() {
      const sellerNames = this.allData.map((item) => {
        return item.name
      })
      const sellerValues = this.allData.map((item) => {
        return item.value
      })
      const dataOption = {
        yAxis: {
          data: sellerNames
        },
        series: [
          {
            data: sellerValues,
          }
        ]

      }
      this.chartInstance.setOption(dataOption)
    },

分页动画的实现

1.使每页显示五个,然后设置定时器使每3s更换一次
2.使鼠标移上去时销毁定时器,移开时开启定时器

数据的处理

在这里插入图片描述
在这里插入图片描述

动画的启动与停止

在这里插入图片描述
在这里插入图片描述

鼠标事件的添加

在这里插入图片描述

ui效果的调整

//标题的设置
title: {
          text: '|商家销售统计',
          left: "9%",
          top: 10
        },
//坐标轴的设置
grid: {
          top:"16%",
          width: "80%",
          height: "80%",
        },
//工具提示和背景
tooltip: {
          trigger: 'axis',
          axisPointer: {
            type: 'line',
            z: 0,
            lineStyle: {
              color: '#2D3443'
            }
          }
        },
//文字显示和位置
series: [
          {
            type: 'bar',
            label:{
              show:true,
              position:'right',
              textStyle:{
                color:'#fff'
              }
            },
            //柱颜色渐变的实现
            itemStyle:{
              // 指明颜色渐变的方向
              // 指明不同百分比之下颜色的值
              color: new this.$echarts.graphic.LinearGradient(0, 0, 1, 0, [
                // 百分之0状态之下的颜色值
                {
                  offset: 0,
                  color: '#5052EE'
                },
                // 百分之100状态之下的颜色值
                {
                  offset: 1,
                  color: '#AB6EE5'
                }
              ])
            }
          }
        ]

分辨率适配

// 当浏览器的大小发生变化的时候, 会调用的方法, 来完成屏幕的适配
    screenAdapter () {
      // console.log(this.$refs.seller_ref.offsetWidth)
      const titleFontSize = this.$refs.seller_ref.offsetWidth / 100 * 3.6
      // 和分辨率大小相关的配置项
      const adapterOption = {
        title: {
          textStyle: {
            fontSize: titleFontSize
          }
        },
        tooltip: {
          axisPointer: {
            lineStyle: {
              width: titleFontSize
            }
          }
        },
        series: [
          {
            barWidth: titleFontSize,
            itemStyle: {
              barBorderRadius: [0, titleFontSize / 2, titleFontSize / 2, 0]
            }
          }
        ]
      }
      this.chartInstance.setOption(adapterOption)
      // 手动的调用图表对象的resize 才能产生效果
      this.chartInstance.resize()
    }

销量趋势分析图(折线图)

最终效果展示图

点击标题可以切换到不同的图
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

图表基本功能的实现

数据的获取和处理

async getData () {
// 获取服务器的数据, 对this.allData进行赋值之后, 调用updateChart方法更新图表
const { data: ret } = await this.$http.get('trend')
this.allData = ret
this.updateChart()
}
updateChart () {
// x轴的数据
const timeArrs = this.allData.common.month
// y轴的数据, 暂时先取出map这个节点的数据
// map代表地区销量趋势
// seller代表商家销量趋势
// commodity代表商品销量趋势
const valueArrs = this.allData.map.data
// 图表数据, 一个图表中显示5条折线图
const seriesArr = valueArrs.map((item, index) => {
return {
type: 'line', // 折线图
name: item.name,
data: item.data,
}
})
const dataOption = {
xAxis: {
data: timeArrs
},
legend: {
data: legendArr
},
series: seriesArr
}
this.chartInstance.setOption(dataOption)
}

初始化配置

const initOption = {
	xAxis: {
	type: 'category',
	boundaryGap: false //x坐标轴两边不留白
	},
	yAxis: {
	type: 'value'
	}
}

堆叠图效果和图例实现

要实现堆叠图的效果, series下的每个对象都需要配置上相同的stack属性

updateChart () {
	const timeArrs = this.allData.common.month
	const valueArrs = this.allData.map.data
	const seriesArr = valueArrs.map((item, index) => {
		return {
			type: 'line',
			name: item.name,
			data: item.data,
			stack: 'map' // stack值相同, 可以形成堆叠图效果
		}
	})
	// 准备图例数据, 它需要和series下的每个对象的name属性保持一致
	const legendArr = valueArrs.map(item => {
		return item.name
	})
	const dataOption = {
	......
		legend: {
		data: legendArr
		}
	}
}

UI效果的调整

坐标轴和工具提示

const initOption = {
	grid: {
		top: '35%',
		left: '3%',
		right: '4%',
		bottom: '1%',
		containLabel: true
	},
	tooltip: {
		trigger: 'axis'
	},
	legend: { //图例大小和位置的设置
		top: '15%',
		icon: 'circle',
		left: 20
	},
}

区域面积的颜色渐变的设置

区域面积只需要给series的每一个对象增加一个 areaStyle 即可,颜色渐变可以通过 LinearGradient 进行设置, 颜色渐变的方向从上往下

      const seriesArr = valueArr.map((item, index) => {
        return {
          name: item.name,
          type: 'line',
          data: item.data,
          stack: map,
          areaStyle: {
            color: new this.$echarts.graphic.LinearGradient(0, 0, 0, 1, [
              {
                offset: 0,
                color: colorArr1[index]
              }, // %0的颜色值
              {
                offset: 1,
                color: colorArr2[index]
              } // 100%的颜色值
            ])
          }
        }
      })

使用计算属性控制标题的内容和标题的可选择项

export default {
	data () {
		return {
			chartInstance: null,
			allData: null,
			dataType: 'map' // 这项数据代表目前选择的数据类型, 可选值有map seller commodity
		}
	},
	computed: {
		selectTypes () {
			if (!this.allData || ! this.allData.type) {
				return []
			} else {
				return this.allData.type.filter(item => {
				return item.key !== this.dataType
			})}
		},
		title () {
			if (!this.allData) {
				return ''
			} else {
				return this.allData[this.dataType].title
			}
		}
	},

ps:记得将 updateChart 中, 之前写死的map变成 dataType,还有一些细节我就不细说了

商家地图分布(地图+散点图)

最终效果展示图

在这里插入图片描述
在这里插入图片描述

地图的显示

基础的组件和路由配置以及初始化配置我不多提,直接写关键代码
在public/static/map下有对应地图的json资料

  • 获取中国地图矢量数据
  • 注册地图数据到 全局echarts对象 中
  • 配置 geo
// 获取的是Vue环境之下的数据, 而不是我们后台的数据
import axios from 'axios'
export default {
	......
	methods: {
		async initChart () {
			this.chartInstance = this.$echarts.init(this.$refs.map_ref)
			const { data: mapData } = await
			axios.get('http://127.0.0.1:8999/static/map/china.json')
			this.$echarts.registerMap('china', mapData)
			const initOption = {
				geo: {
				type: 'map',
				map: 'china'
				}
			}
			this.chartInstance.setOption(initOption)
	},
}

散点图的显示

  • 获取散点数据
async getScatterData () {
	// 获取服务器的数据, 对this.allData进行赋值之后, 调用updateChart方法更新图表
	const { data: ret} = await this.$http.get('map')
	this.allData = ret
	this.updateChart()
}
  • 更新图表
updateChart () {
	// 处理图表需要的数据
	// 图例数据
	const legendData = this.allData.map(item => {
		return item.name
	})
	// 散点数据
	const seriesArr = this.allData.map(item => {
		return {
			type: 'effectScatter',
			coordinateSystem: 'geo',
			name: item.name,
			data: item.children
		}
	})
	const dataOption = {
		legend: {
			data: legendData
		},
		series: seriesArr
	}
	this.chartInstance.setOption(dataOption)
},

UI效果的调整

标题显示

const initOption = {
	title: {
		text: '▎ 商家分布',
		left: 20,
		top: 20
},

地图位置和颜色

const initOption = {
	......
	geo: {
		type: 'map',
		map: 'china',
		top: '5%',
		bottom: '5%',
		itemStyle: {
			areaColor: '#2E72BF',
			borderColor: '#333'
		}
	}
}

图例控制

const initOption = {
	......
	legend: {
		left: '5%',
		bottom: '5%',
		orient: 'vertical'
	}
}

涟漪效果

updateChart () {
	......
	const seriesArr = this.allData.map(item => {
	return {
		type: 'effectScatter',
		rippleEffect: {
		scale: 5,
		brushType: 'stroke'
	},
	......
	}
})

地图点击事件

点击查看省份地图

响应图表的点击事件, 并获取点击项相关的数据,得到地图所点击项的拼音和地图矢量数据的路径

<script>
// 获取的是Vue环境之下的数据, 而不是我们后台的数据
import axios from 'axios'
import { getProvinceMapInfo } from '@/utils/map_utils'
export default {
	......
	methods: {
		async initChart () {
		......
		this.chartInstance.setOption(initOption)
		this.chartInstance.on('click', async arg => {
		// arg.name 就是所点击的省份名称, 是中文
			const provinceInfo = getProvinceMapInfo(arg.name)
			const { data: ret } = await axios.get('http://127.0.0.1:8999' +provinceInfo.path)
			this.$echarts.registerMap(provinceInfo.key, ret)
			this.chartInstance.setOption({
				geo: {
					map: provinceInfo.key
				}
			})
		})
		this.getScatterData()
		}
	}
}
</script>

双击回到中国地图

<template>
	<div class='com-container' @dblclick="revertMap"> //绑定双击事件
	<div class='com-chart' ref='map_ref'></div>
</div>
</template>
<script>
export default {
	......
	methods: {
	......
		revertMap () {
			this.chartInstance.setOption({
				geo: {
					map: 'china'
				}
			})
		}
	}
}
</script>

热销商品占比(饼图)

最终效果展示图

一共三张图,可以使用左右箭头进行切换
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

基本功能的实现

数据的处理

增加 currentIndex 索引代表当前显示的数据索引, 后期通过左右箭头改变 currentIndex 的值

<script>
export default {
	data () {
		return {
			chartInstance: null,
			allData: null,
			currentIndex: 0
		}
	},
	......
	updateChart () {
	// 处理图表需要的数据
	// 饼图数据
		const seriesData = this.allData[this.currentIndex].children.map(item=> {
			return {
				value: item.value,
				name: item.name
			}
		})
		// 图例数据
		const legendData = this.allData[this.currentIndex].children.map(item=> {
			return item.name
		})
		const dataOption = {
			legend: {
				data: legendData
			},
			series: [{data: seriesData}]
		}
		this.chartInstance.setOption(dataOption)
	},

初始化配置

methods: {
	initChart () {
		this.chartInstance = this.$echarts.init(this.$refs.hot_ref)
		const initOption = {
			title: {
				text: '▎ 热销商品销售金额占比统计',
				left: 20,
				top: 20
			},
			series: [
				{
					type: 'pie'
				}
			]
		}
		this.chartInstance.setOption(initOption)
},

切换数据的实现

点击事件

<span class="iconfont arr_left" @click="toLeft">&#xe6ef;</span>
<span class="iconfont arr_right" @click="toRight">&#xe6ed;</span>
	methods: {
		toLeft () {
			this.currentIndex--
			if (this.currentIndex < 0) {
				this.currentIndex = this.allData.length - 1 //若当前为0则点击左跳到最大值那
			}
			this.updateChart()
		},
		toRight () {
			this.currentIndex++
			if (this.currentIndex > this.allData.length - 1) {
				this.currentIndex = 0
			}
			this.updateChart()
		}
	}

名称的改变

增加计算属性 catTitle

<!-- 热销商品图表 -->
<template>
	<div class='com-container'>
	......
		<span class="cat_name">{{ catTitle }}</span>
	</div>
</template>


computed: {
	catTitle () {
		if (!this.allData) {
		return ''
		}
		return this.allData[this.currentIndex].name
	}
},

UI效果调整

高亮效果

默认隐藏文字, 高亮显示文字,就是说鼠标移动到上面才会显示标签

methods: {
	initChart () {
	this.chartInstance = this.$echarts.init(this.$refs.hot_ref, 'chalk')
	const initOption = {
		......
		series: [
			{
				type: 'pie',
				label: { // 隐藏文字
					show: false
				},
				labelLine: { // 隐藏线
					show: false
				},
				emphasis: {
					label: { // 高亮显示文字
						show: true
					}
				}
			}
		]
	}
	this.chartInstance.setOption(initOption)
},

图例形状和位置

legend: {
	top: '5%',
	icon: 'circle'
},

工具提示

当鼠标移入某个扇区的时候, 需要将该二级分类之下的三级分类数据进行展示
增加 series 下饼图每一个扇区的数据

updateChart () {
// 处理图表需要的数据
	const seriesData = this.allData[this.currentIndex].children.map(item =>
	{
		return {
		......
		children: item.children
		}
	})

显示 tooltip ,并控制显示内容

methods: {
	initChart () {
		this.chartInstance = this.$echarts.init(this.$refs.hot_ref, 'chalk')
	const initOption = {
	......
		tooltip: {
			trigger: 'item',
			formatter: function(params) {
			let tipArray = []
			params.data.children.forEach(function(item) {
			let childStr = `
			${item.name}&nbsp;&nbsp;&nbsp;
			${parseInt((item.value / params.value) * 100) + '%'}
			`
			tipArray.push(childStr)
			})
			return tipArray.join('<br/>')
		}
	},
  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值