项目最终的效果如图所示,最终效果涉及到6个图表, 5种图表类型,它们分别是折线图,柱状图,地图,散点图,饼图.。每个图表的数据都是从后端推送到前端来的, 不过在项目的初期,我们会先使用 ajax 由前端主动获取数 据, 后续会使用 WebSocket进行改造。整个项目的架构是基于 Vue 的, 所以我们需要创建 Vue项目, 然后在 Vue项目中开发各个图表组件。
1.前端项目的准备
1.1. vue-c1i脚手架创建项目
1.1.1 脚手架环境的安装
在全局环境中安装 vue-cli脚手架
npm install -g @vue/cli
1.1.2. 工程的创建
使用命令行执行
vue create 项目名
具体的配置项如下:
手动选择特性
集成 Router , Vuex , CSS Pre-processors
是否选用历史模式的路由
选择 Less作为 CSS 的预处理器
选择 ESLint 的配置
配置选择完之后, 就开始创建项目了, 这个过程需要一些时间:
当项目就创建完成了
cd vision
npm run serve
1.1.3.删除无关代码
将目录使用 vscode打开
修改 App.vue 中的代码,将布局和样式删除, 变成如下代码:
<template> <div id="app"> <router-view/> </div> </template> <style lang="less"> </style> |
删除 components/HelloWorld.vue 这个文件
删除 views/About.vue 和 views/Home.vue 这两个文件
修改 router/index.js 中的代码,去除路由配置和 Home组件导入的代码
import Vue from 'vue' import VueRouter from 'vue-router' Vue.use(VueRouter) const routes = [] const router = new VueRouter({ routes }) export default router |
1.2. 项目的基本配置
在项目根目录下创建 vue.config.js文件
在文件中增加代码
// 使用vue-cli创建出来的vue工程 , Webpack的配置是被隐藏起来了的 // 如果想覆盖Webpack中的默认配置 ,需要在项目的根路径下增加vue.config.js文件 module.exports = { devServer: { port: 8999, // 端口号的配置 open: true // 自动打开浏览器 } } |
1.3.全局echarts对象
1.3.1.引入 echarts包
将资料文件夹中的 static 目录复制到 public 目录之下
在 public/index.html 文件中引入 echarts.min.js 文件
1.3.2.挂载到Vue原型上
在 src/main.js文件中挂载
由于在 index.html 中已经通过script标签引入了 echarts.js文件夹, 故在 window全局对象中是 存在 echarts全局对象, 将其挂载到 Vue 的原型对象
...... // 将全局echarts对象挂载到Vue的原型对象上 Vue.prototype.$echarts = window.echarts ...... |
1.3.3.使用全局echarts对象
在其他组件中使用
this.$echarts |
1.4. axios的处理
1.4.1.安装 axios包
npm install axios |
1.4.2.封装 axios对象
在 src/main.js文件中配置 axios并且挂载到Vue的原型对象上
...... import axios from 'axios' axios.defaults.baseURL = 'http://127.0.0.1:8888/api/' // 将axios挂载到Vue的原型对象上 Vue.prototype.$http = axios ...... |
1.4.3.使用 axios对象
在其他组件中使用
this.$http
2.单独图表组件的开发
在项目的初期, 我们会每个图表单独的进行开发, 最后再将所有的图表合并到一个界面中. 在单独开发每个图表的时候, 一个图表会用一个单独的路径进行全屏展示, 他们分别是:
商家销售统计http://127.0.0.1:8999/#/sellerpage
销量趋势分析http://127.0.0.1:8999/#/trendpage
商家地图分布http://127.0.0.1:8999/#/mappage
地区销量排行http://127.0.0.1:8999/#/rankpage
热销商品占比http://127.0.0.1:8999/#/hotpage
库存销量分析http://127.0.0.1:8999/#/stockpage
2.1.商家销量排行
最终的效果如下图所示:
2.1.1.组件结构设计
在 src/components/ 目录下建立 Seller.vue , 这个组件是真实展示图表的组件
给外层div增加类样式 com-container
建立一个显示图表的div元素
给新增的这个div增加类样式 com-chart
在 src/views/ 目录下建立 SellerPage.vue ,这个组件是对应于路由 /seller 而展示的
给外层div元素增加样式 com-page
在 SellerPage 中引入 Seller组件,并且注册和使用
<!-- 这个组件是对应于路由规则中 /seller 这条路径的 在这个组件中 ,需要展示Seller.vue这个组件 Seller.vue才是真正显示图表的组件 --> <template> <div class="com-page"> <seller></seller> </div> </template> <script> import Seller from '@/components/Seller' export default { data () { return {} }, methods: {}, components: { seller:Seller } } </script> <style lang='less' scoped> </style> |
在 src/views/ 目录下建立 SellerPage.vue ,这个组件是对应于路由 /seller 而展示的
给外层div元素增加样式 com-page
在 SellerPage 中引入 Seller组件,并且注册和使用
<!-- 这个组件是对应于路由规则中 /seller 这条路径的 在这个组件中 ,需要展示Seller.vue这个组件 Seller.vue才是真正显示图表的组件 --> <template> <div class="com-page"> <seller></seller> </div> </template> <script> import Seller from '@/components/Seller' export default { data () { return {} }, methods: {}, components: { seller:Seller } } </script> <style lang='less' scoped> </style> |
增加路由规则, 在 src/router/index.js文件中修改
...... import SellerPage from '@/views/SellerPage' ...... const routes = [ { path: '/sellerpage', component: SellerPage } ] |
新建 src/assets/css/global.less 增加宽高样式
原则就是将所有的容器的宽度和高度设置为占满父容器
html,
body,
#app {
width: 100%;
height: 100%;
padding: 0;
margin: 0;
overflow: hidden;
}
.com-page {
width: 100%;
height: 100%;
overflow: hidden;
}
.com-container {
width: 100%;
height: 100%;
overflow: hidden;
}
.com-chart {
width: 100%;
height: 100%;
overflow: hidden;
}
在 main.js 中引入样式
import './assets/css/global.less' |
打开浏览器, 输入 http://127.0.0.1:8999/#/sellerpage 看Seller组件是否能够显示
2.1.2.图表 Seller .vue 基本功能的实现
1.在mounted生命周期中初始化 echartsInstance对象
2.在mounted中获取服务器的数据
3.将获取到的数据设置到图表上
<script>
export default {
data () {
return {
chartInstance: null, // echarts实例对象
allData: [] // 服务器获取的所有数据
}
},
mounted () {
// 由于初始化echarts实例对象需要使用到dom元素 ,因此必须要放到mounted中 , 而不是created
this.initChart()
this.getData()
},
methods: {
initChart () {
this.chartInstance = this.$echarts.init(this.$refs.seller_ref) // 初始化 echarts实例对象
},
async getData () {
const { data: res } = await this.$http.get('seller') // 获取数据 this.allData = res
// 对allData进行从大到小的排序
this.allData.sort((a, b) => {
return a.value - b.value
})
this.updateChart()
},
updateChart () {
// 处理数据并且更新界面图表
const sellerNames = this.allData.map((item) => {
return item.name
})
const sellerValues = this.allData.map((item) => {
return item.value
})
const option = {
xAxis: {
type: 'value'
},
yAxis: {
type: 'category',
data: sellerNames
},
series: [
{
type: 'bar',
data: sellerValues
}
]
}
this.chartInstance.setOption(option)
}
}
}
</script>
4.拆分配置项 option
初始化配置项
拥有数据之后的配置项
2.1.3.分页动画的实现
数据的处理, 每5个元素显示一页
数据的处理
动画的启动和停止
鼠标事件的处理
2.1.4. UI 效果调整
主题文件的导入
public/index.html 中引入
主题的指定,在初始化 echarts实例对象的时候指定
src/components/Seller.vue
边框圆角的设置
src/assets/css/global.less
canvas { border-radius: 20px; } |
其他图表样式的配置
标题的位置和颜色
const initOption = { title: { text: '▎ 商家销量排行', left: 20, top: 20, textStyle: { textStyle: { "color": "#fff" } } }, |
坐标轴的大小
const initOption = { ...... grid: { top: '20%', left: '3%', right: '6%', bottom: '3%', containLabel: true }, |
工具提示和背景
const initOption = { ...... tooltip: { trigger: 'axis', axisPointer: { type: 'line', z: 0, lineStyle: { width: 66, color: '#2D3443' } } }, |
const initOption = { ...... series: [ { ...... label: { show: true, position: 'right', textStyle: { color: '#fff' } }, |
柱宽度和柱圆角的实现
const initOption = { ...... series: [ { ...... barWidth: 66, itemStyle: { barBorderRadius: [0, 33, 33, 0] } } ] } |
柱颜色渐变的实现
线性渐变可以通过 LinearGradient进行实现
LinearGradient需要传递5个参数, 前四个代表两个点的相对位置,第五个参数代表颜色变化 的范围
0, 0, 1, 0 代表的是从左往右的方向
const initOption = {
series: [
{
......
itemStyle: {
barBorderRadius: [0, 33, 33, 0],
color: new this.$echarts.graphic.LinearGradient(0, 0, 1,
0, [
{
offset: 0,
color: '#5052EE'
},
{
offset: 1,
color: '#AB6EE5'
}
])
}
}
]
2.1.5.分辨率适配
对窗口大小变化的事件进行监听
mounted 时候监听
mounted () { this.initChart() this.getData() window.addEventListener('resize', this.screenAdapter) } |
组件销毁时取消监听
destroyed () { clearInterval(this.timerId) // 在组件销毁的时候 , 需要将监听器取消掉 window.removeEventListener('resize', this.screenAdapter) }, |
获取图表容器的宽度计算字体大小
// 当浏览器的大小发生变化的时候 , 会调用的方法 , 来完成屏幕的适配 screenAdapter () { // console.log(this.$refs.seller_ref.offsetWidth) const titleFontSize = this.$refs.seller_ref.offsetWidth / 100 * 3.6 |
2.1.6完整代码Seller.vue
<!-- 商家销量统计的横向柱状图 -->
<template>
<div class="com-container">
<div class="com-chart" ref="seller_ref"></div>
</div>
</template>
<script>
export default {
data () {
return {
chartInstance: null,
allData: null, // 服务器返回的数据
currentPage: 1, // 当前显示的页数
totalPage: 0, // 一共有多少页
timerId: null // 定时器的标识
}
},
mounted () {
this.initChart()
this.getData()
window.addEventListener('resize', this.screenAdapter)
// 在页面加载完成的时候, 主动进行屏幕的适配
this.screenAdapter()
},
destroyed () {
clearInterval(this.timerId)
// 在组件销毁的时候, 需要将监听器取消掉
window.removeEventListener('resize', this.screenAdapter)
},
methods: {
// 初始化echartInstance对象
initChart () {
this.chartInstance = this.$echarts.init(this.$refs.seller_ref, 'chalk')
// 对图表初始化配置的控制
const initOption = {
title: {
text: '▎商家销售统计',
left: 20,
top: 20
},
grid: {
top: '20%',
left: '3%',
right: '6%',
bottom: '3%',
containLabel: true // 距离是包含坐标轴上的文字
},
xAxis: {
type: 'value'
},
yAxis: {
type: 'category'
},
tooltip: {
trigger: 'axis',
axisPointer: {
type: 'line',
z: 0,
lineStyle: {
color: '#2D3443'
}
}
},
series: [
{
type: 'bar',
label: {
show: true,
position: 'right',
textStyle: {
color: 'white'
}
},
itemStyle: {
// 指明颜色渐变的方向
// 指明不同百分比之下颜色的值
color: new this.$echarts.graphic.LinearGradient(0, 0, 1, 0, [
// 百分之0状态之下的颜色值
{
offset: 0,
color: '#5052EE'
},
// 百分之100状态之下的颜色值
{
offset: 1,
color: '#AB6EE5'
}
])
}
}
]
}
this.chartInstance.setOption(initOption)
// 对图表对象进行鼠标事件的监听
this.chartInstance.on('mouseover', () => {
clearInterval(this.timerId)
})
this.chartInstance.on('mouseout', () => {
this.startInterval()
})
},
// 获取服务器的数据
async getData () {
// http://127.0.0.1:8888/api/seller
const { data: ret } = await this.$http.get('seller')
this.allData = ret
// 对数据排序
this.allData.sort((a, b) => {
return a.value - b.value // 从小到大的排序
})
// 每5个元素显示一页
this.totalPage = this.allData.length % 5 === 0 ? this.allData.length / 5 : this.allData.length / 5 + 1
this.updateChart()
// 启动定时器
this.startInterval()
},
// 更新图表
updateChart () {
const start = (this.currentPage - 1) * 5
const end = this.currentPage * 5
const showData = this.allData.slice(start, end)
const sellerNames = showData.map((item) => {
return item.name
})
const sellerValues = showData.map((item) => {
return item.value
})
const dataOption = {
yAxis: {
data: sellerNames
},
series: [
{
data: sellerValues
}
]
}
this.chartInstance.setOption(dataOption)
},
startInterval () {
if (this.timerId) {
clearInterval(this.timerId)
}
this.timerId = setInterval(() => {
this.currentPage++
if (this.currentPage > this.totalPage) {
this.currentPage = 1
}
this.updateChart()
}, 3000)
},
// 当浏览器的大小发生变化的时候, 会调用的方法, 来完成屏幕的适配
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()
}
}
}
</script>
<style lang="less" scoped>
</style>
2.2.销量趋势分析
最终的效果如下:
2.2.1.代码环境的准备
TrendPage.vue
<!--
针对于 /trendpage 这条路径而显示出来的
在这个组件中 , 通过子组件注册的方式 , 要显示出Trend.vue这个组件
-->
<template>
<div class="com-page">
<trend></trend>
</div>
</template>
<script>
import Trend from '@/components/Trend'
export default {
data () {
return {}
},
methods: {},
components: {
trend: Trend
}
}
</script>
<style lang="less" scoped>
</style>
Trend.vue
<template>
<div class="com-container">
<div class="title" :style="comStyle">
<span>{{ '▎ ' + showTitle }}</span>
<span class="iconfont title-icon" :style="comStyle" @click="showChoice = !showChoice"></span>
<div class="select-con" v-show="showChoice" :style="marginStyle">
<div class="select-item" v-for="item in selectTypes" :key="item.key" @click="handleSelect(item.key)">
{{ item.text }}
</div>
</div>
</div>
<div class="com-chart" ref="trend_ref"></div>
</div>
</template>
<script>
export default {
data () {
return {
chartInstane: null,
allData: null, // 从服务器中获取的所有数据
showChoice: false, // 是否显示可选项
choiceType: 'map', // 显示的数据类型
titleFontSize: 0 // 指明标题的字体大小
}
},
mounted () {
this.initChart()
this.getData()
window.addEventListener('resize', this.screenAdapter)
this.screenAdapter()
},
destroyed () {
window.removeEventListener('resize', this.screenAdapter)
},
computed: {
selectTypes () {
if (!this.allData) {
return []
} else {
return this.allData.type.filter(item => {
return item.key !== this.choiceType
})
}
},
showTitle () {
if (!this.allData) {
return ''
} else {
return this.allData[this.choiceType].title
}
},
// 设置给标题的样式
comStyle () {
return {
fontSize: this.titleFontSize + 'px'
}
},
marginStyle () {
return {
marginLeft: this.titleFontSize + 'px'
}
}
},
methods: {
initChart () {
this.chartInstane = this.$echarts.init(this.$refs.trend_ref, 'chalk')
const initOption = {
grid: {
left: '3%',
top: '35%',
right: '4%',
bottom: '1%',
containLabel: true
},
tooltip: {
trigger: 'axis'
},
legend: {
left: 20,
top: '15%',
icon: 'circle'
},
xAxis: {
type: 'category',
boundaryGap: false
},
yAxis: {
type: 'value'
}
}
this.chartInstane.setOption(initOption)
},
async getData () {
// await this.$http.get()
// 对allData进行赋值
const { data: ret } = await this.$http.get('trend')
this.allData = ret
console.log(this.allData)
this.updateChart()
},
updateChart () {
// 半透明的颜色值
const colorArr1 = [
'rgba(11, 168, 44, 0.5)',
'rgba(44, 110, 255, 0.5)',
'rgba(22, 242, 217, 0.5)',
'rgba(254, 33, 30, 0.5)',
'rgba(250, 105, 0, 0.5)'
]
// 全透明的颜色值
const colorArr2 = [
'rgba(11, 168, 44, 0)',
'rgba(44, 110, 255, 0)',
'rgba(22, 242, 217, 0)',
'rgba(254, 33, 30, 0)',
'rgba(250, 105, 0, 0)'
]
// 处理数据
// 类目轴的数据
const timeArr = this.allData.common.month
// y轴的数据 series下的数据
const valueArr = this.allData[this.choiceType].data
const seriesArr = valueArr.map((item, index) => {
return {
name: item.name,
type: 'line',
data: item.data,
stack: this.choiceType,
areaStyle: {
color: new this.$echarts.graphic.LinearGradient(0, 0, 0, 1, [
{
offset: 0,
color: colorArr1[index]
}, // %0的颜色值
{
offset: 1,
color: colorArr2[index]
} // 100%的颜色值
])
}
}
})
// 图例的数据
const legendArr = valueArr.map(item => {
return item.name
})
const dataOption = {
xAxis: {
data: timeArr
},
legend: {
data: legendArr
},
series: seriesArr
}
this.chartInstane.setOption(dataOption)
},
screenAdapter () {
this.titleFontSize = this.$refs.trend_ref.offsetWidth / 100 * 3.6
const adapterOption = {
legend: {
itemWidth: this.titleFontSize,
itemHeight: this.titleFontSize,
itemGap: this.titleFontSize,
textStyle: {
fontSize: this.titleFontSize / 2
}
}
}
this.chartInstane.setOption(adapterOption)
this.chartInstane.resize()
},
handleSelect (currentType) {
this.choiceType = currentType
this.updateChart()
this.showChoice = false
}
}
}
</script>
<style lang="less" scoped>
.title {
position: absolute;
left: 20px;
top: 20px;
z-index: 10;
color: white;
.title-icon {
margin-left: 10px;
cursor: pointer;
}
.select-con {
background-color: #222733;
}
}
</style>
router/index.js
......
import TrendPage from '@/views/TrendPage'
......
const routes = [
......
{
path: '/trendpage',
component: TrendPage
}
]
......
2.2.2.图表基本功能的实现
数据的获取
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 }, 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值相同 , 可以形成堆叠图效果 } }) ...... } |
图例效果
updateChart () {
......
const valueArrs = this.allData.map.data
const seriesArr = valueArrs.map((item, index) => {
return {
type: 'line',
name: item.name,
data: item.data,
stack: 'map'
}
})
// 准备图例数据 , 它需要和series下的每个对象的name属性保持一致
const legendArr = valueArrs.map(item => {
return item.name
})
const dataOption = {
......
legend: {
data: legendArr
}
......
}
this.chartInstance.setOption(dataOption) }
2.2.3. UI 效果的调整
主题的使用
initChart () { this.chartInstance = this.$echarts.init(this.$refs.trend_ref, 'chalk') } |
主题使用完之后, 发现折线图都变成了平滑折线图了, 这是因为在 chalk.js主题文件中, 设置了
smooth:true
.
2.2.4.切换图表
布局的实现
增加类样式为 title 的容器
<template> <div class='com-container'> <div class="title"> <span>我是标题</span> <span class="iconfont title-icon"></span> <div class="select-con"> <div class="select-item"> 标题选择1 </div> <div class="select-item"> 标题选择2 </div> <div class="select-item"> 标题选择3 </div> </div> </div> <div class='com-chart' ref='trend_ref'></div> </div> </template> |
字体文件的引入
将资料文件夹下的字体文件夹中的 font复制到 asset 目录下, 然后在 main.js 中引入字体样式文
件
在 Trend.vue 中的style标签中增加一些样式
<style lang='less' scoped> .title { position: absolute; left: 20px; top: 20px; z-index: 10; color: white; .title-icon { margin-left: 10px; cursor: pointer; } .select-item { cursor: pointer; } } </style> |
数据动态渲染
使用计算属性 title控制标题的内容和标题的可选择项
<script> 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 } } }, ...... |
点击三角控制显示隐藏
增加一项变量控制可选容器的显示与隐藏
export default { data () { return { showChoice: false // 控制可选面板的显示或者隐藏 } }, |
使用指令 v-if和点击事件的监听
<template> <div class='com-container'> <div class="title"> <span>{{ title }}</span> <span class="iconfont title-icon" @click="showChoice = !showChoice"></span> <div class="select-con" v-if="showChoice"> <div class="select-item" v-for="item in selectTypes" :key="item.key"> {{ item.text }} </div> </div> </div> <div class='com-chart' ref='trend_ref'></div> </div> </template> |
点击可选条目的控制
<template>
<div class='com-container'>
<div class="title">
<span>{{ title }}</span>
<span class="iconfont title-icon" @click="showChoice =
!showChoice"></span>
<div class="select-con" v-if="showChoice">
<div class="select-item" v-for="item in selectTypes" :key="item.key" @click="handleSelect(item.key)">
{{ item.text }}
</div>
</div>
</div>
<div class='com-chart' ref='trend_ref'></div>
</div>
</template>
<script>
export default {
......
methods: {
handleSelect (key) {
this.dataType = key
this.updateChart()
this.showChoice = false
}
}
}
</script>
将 updateChart 中, 之前写死的map变成 dataType
const valueArrs = this.allData[this.dataType].data const seriesArr = valueArrs.map((item, index) => { return { ...... stack: this.dataType } }) |
2.2.5.分辨率适配
分辨率适配主要就是在 screenAdapter方法中进行, 需要获取图表容器的宽度,计算出标题字体大小,
将字体的大小赋值给 titleFontSize
<script> export default { data () { return { titleFontSize: 0 } }, ...... screenAdapter () { this.titleFontSize = this.$refs.trend_ref.offsetWidth / 100 * 3.6 } |
通过 titleFontSize从而设置给标题文字的大小和图例的大小
标题文字的大小
增加计算属性 comStyle并设置给对应的 div ,如下:
<!-- 销量趋势图表 --> <template> <div class='com-container'> <div class="title" :style="comStyle"> <span>{{ title }}</span> <span class="iconfont title-icon" @click="showChoice = !showChoice" :style="comStyle"></span> ...... <script> export default { ...... computed: { ...... comStyle () { return { fontSize: this.titleFontSize + 'px' } } }, |
图例的大小
screenAdapter () {
this.titleFontSize = this.$refs.trend_ref.offsetWidth / 100 * 3.6 const adapterOption = {
legend: {
itemWidth: this.titleFontSize,
itemHeight: this.titleFontSize,
itemGap: this.titleFontSize,
textStyle: {
fontSize: this.titleFontSize / 2
}
}
}
this.chartInstance.setOption(adapterOption)
this.chartInstance.resize()
},
2.2.6.细节调整
可选条目的背景色
<style lang='less' scoped> .title { ...... .select-con { background-color: #222733; } .select-item { cursor: pointer; } } </style> |
增加标题左侧的小竖杆
<template> <div class='com-container'> <div class="title" :style="comStyle"> <span>{{'▎ ' + title }}</span> <span class="iconfont title-icon" @click="showChoice = !showChoice" :style="comStyle"></span> <div class="select-con" v-if="showChoice" :style="marginStyle"> ...... <script> export default { ...... computed: { marginStyle () { return { marginLeft: this.titleFontSize + 'px' } } }, |
2.3.商家地图分布
最终的效果如下:
2.3.1.代码环境的准备
MapPage.vue
<!-- 针对于 /mappage 这条路径而显示出来的 在这个组件中 , 通过子组件注册的方式 , 要显示出Map.vue这个组件 --> <template> <div class="com-page"> <single-map></single-map> </div> </template> <script> import Map from '@/components/Map' export default { data () { return {} }, methods: {}, components: { 'single-map': Map } } </script> <style lang="less" scoped> </style> |
Map.vue
<!-- 商家分布图表 -->
<template>
<div class='com-container' @dblclick="revertMap">
<div class='com-chart' ref='map_ref'></div>
</div>
</template>
<script>
import axios from 'axios'
import { getProvinceMapInfo } from '@/utils/map_utils'
export default {
data () {
return {
chartInstance: null,
allData: null,
mapData: {} // 所获取的省份的地图矢量数据
}
},
mounted () {
this.initChart()
this.getData()
window.addEventListener('resize', this.screenAdapter)
this.screenAdapter()
},
destroyed () {
window.removeEventListener('resize', this.screenAdapter)
},
methods: {
async initChart () {
this.chartInstance = this.$echarts.init(this.$refs.map_ref, 'chalk')
// 获取中国地图的矢量数据
// http://localhost:8999/static/map/china.json
// 由于我们现在获取的地图矢量数据并不是位于KOA2的后台, 所以咱们不能使用this.$http
const ret = await axios.get('http://localhost:8999/static/map/china.json')
this.$echarts.registerMap('china', ret.data)
const initOption = {
title: {
text: '▎ 商家分布',
left: 20,
top: 20
},
geo: {
type: 'map',
map: 'china',
top: '5%',
bottom: '5%',
itemStyle: {
areaColor: '#2E72BF',
borderColor: '#333'
}
},
legend: {
left: '5%',
bottom: '5%',
orient: 'vertical'
}
}
this.chartInstance.setOption(initOption)
this.chartInstance.on('click', async arg => {
// arg.name 得到所点击的省份, 这个省份他是中文
const provinceInfo = getProvinceMapInfo(arg.name)
console.log(provinceInfo)
// 需要获取这个省份的地图矢量数据
// 判断当前所点击的这个省份的地图矢量数据在mapData中是否存在
if (!this.mapData[provinceInfo.key]) {
const ret = await axios.get('http://localhost:8999' + provinceInfo.path)
this.mapData[provinceInfo.key] = ret.data
this.$echarts.registerMap(provinceInfo.key, ret.data)
}
const changeOption = {
geo: {
map: provinceInfo.key
}
}
this.chartInstance.setOption(changeOption)
})
},
async getData () {
// 获取服务器的数据, 对this.allData进行赋值之后, 调用updateChart方法更新图表
const { data: ret } = await this.$http.get('map')
this.allData = ret
console.log(this.allData)
this.updateChart()
},
updateChart () {
// 处理图表需要的数据
// 图例的数据
const legendArr = this.allData.map(item => {
return item.name
})
const seriesArr = this.allData.map(item => {
// return的这个对象就代表的是一个类别下的所有散点数据
// 如果想在地图中显示散点的数据, 我们需要给散点的图表增加一个配置, coordinateSystem:geo
return {
type: 'effectScatter',
rippleEffect: {
scale: 5,
brushType: 'stroke'
},
name: item.name,
data: item.children,
coordinateSystem: 'geo'
}
})
const dataOption = {
legend: {
data: legendArr
},
series: seriesArr
}
this.chartInstance.setOption(dataOption)
},
screenAdapter () {
const titleFontSize = this.$refs.map_ref.offsetWidth / 100 * 3.6
const adapterOption = {
title: {
textStyle: {
fontSize: titleFontSize
}
},
legend: {
itemWidth: titleFontSize / 2,
itemHeight: titleFontSize / 2,
itemGap: titleFontSize / 2,
textStyle: {
fontSize: titleFontSize / 2
}
}
}
this.chartInstance.setOption(adapterOption)
this.chartInstance.resize()
},
// 回到中国地图
revertMap () {
const revertOption = {
geo: {
map: 'china'
}
}
this.chartInstance.setOption(revertOption)
}
}
}
</script>
<style lang='less' scoped>
</style>
router/index.js
...... import MapPage from '@/views/MapPage' ...... const routes = [ ...... { path: '/mappage', component: MapPage } ] ...... |
2.3.2.显示地图
获取中国地图矢量数据
注册地图数据到 全局echarts对象 中
配置 geo
<script> // 获取的是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) }, |
2.3.3.显示散点图
获取散点数据
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)
},
2.3.4. UI 效果的调整
主题的使用
methods: { async initChart () { this.chartInstance = this.$echarts.init(this.$refs.map_ref, 'chalk') |
标题显示
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' }, ...... } }) |
2.3.5.分辨率适配
计算 titleFontSize
screenAdapter () { const titleFontSize = this.$refs.map_ref.offsetWidth / 100 * 3.6 const adapterOption = { } this.chartInstance.setOption(adapterOption) this.chartInstance.resize() } |
将 titleFontSize设置给图表的某些区域
标题的大小
图例大小
screenAdapter () {
const titleFontSize = this.$refs.map_ref.offsetWidth / 100 * 3.6 const adapterOption = {
title: {
textStyle: {
fontSize: titleFontSize
}
},
legend: {
itemWidth: titleFontSize / 2,
itemHeight: titleFontSize / 2,
itemGap: titleFontSize / 2,
textStyle: {
fontSize: titleFontSize / 2 }
}
}
this.chartInstance.setOption(adapterOption) this.chartInstance.resize()
2.3.6.地图点击事件
响应图表的点击事件, 并获取点击项相关的数据
async initChart () { ...... this.chartInstance.on('click', arg => { // arg.name 就是所点击的省份名称 , 是中文 }) |
将资料中的 map_utils.js复制到 src/utils/ 目录之下
得到地图所点击项的拼音和地图矢量数据的路径
<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>
2.4.地区销量排行
最终的效果如下:
2.4.1.代码环境的准备
RankPage.vue
<!--
针对于 /rankpage 这条路径而显示出来的
在这个组件中 , 通过子组件注册的方式 , 要显示出Rank.vue这个组件 -->
<template>
<div class="com-page">
<rank></rank>
</div>
</template>
<script>
import Rank from '@/components/Rank'
export default {
data () {
return {}
},
methods: {},
components: {
rank: Rank
}
}
</script>
<style lang="less" scoped>
</style>
Rank.vue
<!-- 地区销售排行 -->
<template>
<div class='com-container'>
<div class='com-chart' ref='rank_ref'></div>
</div>
</template>
<script>
export default {
data () {
return {
chartInstance: null,
allData: null,
startValue: 0, // 区域缩放的起点值
endValue: 9, // 区域缩放的终点值
timerId: null // 定时器的标识
}
},
mounted () {
this.initChart()
this.getData()
window.addEventListener('resize', this.screenAdapter)
this.screenAdapter()
},
destroyed () {
window.removeEventListener('resize', this.screenAdapter)
clearInterval(this.timerId)
},
methods: {
initChart () {
this.chartInstance = this.$echarts.init(this.$refs.rank_ref, 'chalk')
const initOption = {
title: {
text: '▎ 地区销售排行',
left: 20,
top: 20
},
grid: {
top: '40%',
left: '5%',
right: '5%',
bottom: '5%',
containLabel: true
},
tooltip: {
show: true
},
xAxis: {
type: 'category'
},
yAxis: {
type: 'value'
},
series: [
{
type: 'bar'
}
]
}
this.chartInstance.setOption(initOption)
this.chartInstance.on('mouseover', () => {
clearInterval(this.timerId)
})
this.chartInstance.on('mouseout', () => {
this.startInterval()
})
},
async getData () {
// 获取服务器的数据, 对this.allData进行赋值之后, 调用updateChart方法更新图表
const { data: ret } = await this.$http.get('rank')
this.allData = ret
// 对allData里面的每一个元素进行排序, 从大到小进行
this.allData.sort((a, b) => {
return b.value - a.value
})
console.log(this.allData)
this.updateChart()
this.startInterval()
},
updateChart () {
const colorArr = [
['#0BA82C', '#4FF778'],
['#2E72BF', '#23E5E5'],
['#5052EE', '#AB6EE5']
]
// 处理图表需要的数据
// 所有省份所形成的数组
const provinceArr = this.allData.map(item => {
return item.name
})
// 所有省份对应的销售金额
const valueArr = this.allData.map(item => {
return item.value
})
const dataOption = {
xAxis: {
data: provinceArr
},
dataZoom: {
show: false,
startValue: this.startValue,
endValue: this.endValue
},
series: [
{
data: valueArr,
itemStyle: {
color: arg => {
let targetColorArr = null
if (arg.value > 300) {
targetColorArr = colorArr[0]
} else if (arg.value > 200) {
targetColorArr = colorArr[1]
} else {
targetColorArr = colorArr[2]
}
return new this.$echarts.graphic.LinearGradient(0, 0, 0, 1, [
{
offset: 0,
color: targetColorArr[0]
},
{
offset: 1,
color: targetColorArr[1]
}
])
}
}
}
]
}
this.chartInstance.setOption(dataOption)
},
screenAdapter () {
const titleFontSize = this.$refs.rank_ref.offsetWidth / 100 * 3.6
const adapterOption = {
title: {
textStyle: {
fontSize: titleFontSize
}
},
series: [
{
barWidth: titleFontSize,
itemStyle: {
barBorderRadius: [titleFontSize / 2, titleFontSize / 2, 0, 0]
}
}
]
}
this.chartInstance.setOption(adapterOption)
this.chartInstance.resize()
},
startInterval () {
if (this.timerId) {
clearInterval(this.timerId)
}
this.timerId = setInterval(() => {
this.startValue++
this.endValue++
if (this.endValue > this.allData.length - 1) {
this.startValue = 0
this.endValue = 9
}
this.updateChart()
}, 2000)
}
}
}
</script>
<style lang='less' scoped>
</style>
router/index.js
...... import RankPage from '@/views/RankPage' ...... const routes = [ ...... { path: '/rankpage', component: RankPage } ] ...... |
2.4.2.图表基本功能的实现
数据的获取
async getData () { // 获取服务器的数据 , 对this.allData进行赋值之后 , 调用updateChart方法更新图表 const { data: ret } = await this.$http.get('rank') this.allData = ret // 对数据进行排序 , 从大到小排序 this.allData.sort((a, b) => { return b.value - a.value }) this.updateChart() }, |
数据的处理
updateChart () {
// 处理图表需要的数据
const provinceArr = this.allData.map(item => {
return item.name
})
const valueArr = this.allData.map(item => {
return item.value
})
const dataOption = {
xAxis: {
data: provinceArr
},
series: [
{
data: valueArr
}
]
}
this.chartInstance.setOption(dataOption)
},
初始化配置
initChart () { this.chartInstance = this.$echarts.init(this.$refs.rank_ref) const initOption = { xAxis: { type: 'category' }, yAxis: { type: 'value' }, series: [ { type: 'bar' } ] } this.chartInstance.setOption(initOption) } |
2.4.3. UI 效果调整
主题的使用
initChart () { this.chartInstance = this.$echarts.init(this.$refs.rank_ref, 'chalk') |
标题的设置
initChart () { this.chartInstance = this.$echarts.init(this.$refs.rank_ref, 'chalk') const initOption = { title: { text: '▎ 地区销售排行', left: 20, top: 20 } |
颜色的设置
不同柱显示不同颜色
渐变的控制
updateChart () {
// 处理图表需要的数据
const colorArr = [
['#0BA82C', '#4FF778'],
['#2E72BF', '#23E5E5'],
['#5052EE', '#AB6EE5']
]
......
const dataOption = {
xAxis: {
data: provinceArr
},
series: [
{
data: valueArr,
itemStyle: {
color: arg => {
let targetColorArr = colorArr[0]
if (arg.vaule >= 300) {
targetColorArr = colorArr[0]
} else if (arg.value >= 200) {
targetColorArr = colorArr[1]
} else {
targetColorArr = colorArr[2]
}
return new this.$echarts.graphic.LinearGradient(0,
1, 0, 0, [
{
offset: 0,
color: targetColorArr[0]
},
{
offset: 1,
color: targetColorArr[1] }
])
}
}
}
]
}
this.chartInstance.setOption(dataOption)
},
2.4.4.平移动画的实现
平移动画可以使用 dataZoom 中的 startValue和 endValue来实现
定义数据
<script> export default { data () { return { chartInstance: null, allData: null, startValue: 0, endValue: 9 } }, |
将 startValue 和 endValue 应用在 dataZoom 上, 并隐藏 dataZoom 的显示
updateChart () { ...... const dataOption = { xAxis: { data: provinceArr }, dataZoom: { show: false, startValue: this.startValue, endValue: this.endValue }, |
启动和停止定时器
增加 timerId 的变量, 并且增加一个方法 startInterval ,来控制 startValue和 endValue 的值
<script>
export default {
data () {
return {
chartInstance: null,
allData: null,
startValue: 0,
endValue: 9,
timerId: null
}
},
......
methods: {
......
startInterval () {
if (this.timerId) {
clearInterval(this.timerId)
}
this.timerId = setInterval(() => {
this.startValue++
this.endValue++
if (this.endValue > this.allData.length - 1) {
this.startValue = 0
this.endValue = 9
}
this.updateChart()
}, 3000)
}
}
}
获取数据之后启动
async getData () { ...... this.updateChart() this.startInterval() }, |
组件销毁停止
destroyed () { window.removeEventListener('resize', this.screenAdapter) clearInterval(this.timerId) }, |
鼠标移入停止
methods: { initChart () { ...... this.chartInstance.setOption(initOption) this.chartInstance.on('mouseover', () => { clearInterval(this.timerId) }) |
鼠标离开启动
methods: { initChart () { ...... this.chartInstance.on('mouseout', () => { this.startInterval() }) }, |
2.4.5.分辨率适配
计算 titleFontSize
将 titleFontSize设置给图表的某些区域
screenAdapter () { const titleFontSize = this.$refs.rank_ref.offsetWidth / 100 * 3.6 const adapterOption = { title: { textStyle: { fontSize: titleFontSize } }, series: [ { barWidth: titleFontSize, itemStyle: { barBorderRadius: [0.5 * titleFontSize, 0.5 * titleFontSize, 0, 0] } } ] } this.chartInstance.setOption(adapterOption) this.chartInstance.resize() }, |
2.5.热销商品占比
最终的效果如下:
2.5.1.代码环境的准备
HotPage.vue
<!--
针对于 /hotpage 这条路径而显示出来的
在这个组件中 , 通过子组件注册的方式 , 要显示出Hot.vue这个组件
-->
<template>
<div class="com-page">
<hot></hot>
</div>
</template>
<script>
import Hot from '@/components/Hot'
export default {
data () {
return {}
},
methods: {},
components: {
hot: Hot
}
}
</script>
<style lang="less" scoped>
</style>
Hot.vue
<!-- 热销商品图表 -->
<template>
<div class='com-container'>
<div class='com-chart' ref='hot_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.hot_ref)
const initOption = {}
this.chartInstance.setOption(initOption)
},
async getData () {
// 获取服务器的数据 , 对this.allData进行赋值之后 , 调用updateChart方法更新图表
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>
router/index.js
...... import HotPage from '@/views/HotPage' ...... const routes = [ ...... { path: '/hotpage', component: HotPage } ] ...... |
2.5.2.图表基本功能的实现
Hot.vue
<!-- 热销商品图表 -->
<template>
<div class='com-container'>
<div class='com-chart' ref='hot_ref'></div>
<span class="iconfont arr-left" @click="toLeft" :style="comStyle"></span>
<span class="iconfont arr-right" @click="toRight" :style="comStyle"></span>
<span class="cat-name" :style="comStyle">{{ catName }}</span>
</div>
</template>
<script>
export default {
data () {
return {
chartInstance: null,
allData: null,
currentIndex: 0, // 当前所展示出的一级分类数据
titleFontSize: 0
}
},
computed: {
catName () {
if (!this.allData) {
return ''
} else {
return this.allData[this.currentIndex].name
}
},
comStyle () {
return {
fontSize: this.titleFontSize + 'px'
}
}
},
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.hot_ref, 'chalk')
const initOption = {
title: {
text: '▎ 热销商品的占比',
left: 20,
top: 20
},
legend: {
top: '15%',
icon: 'circle'
},
tooltip: {
show: true,
formatter: arg => {
// console.log(arg)
const thirdCategory = arg.data.children
// 计算出所有三级分类的数值总和
let total = 0
thirdCategory.forEach(item => {
total += item.value
})
let retStr = ''
thirdCategory.forEach(item => {
retStr += `
${item.name}:${parseInt(item.value / total * 100) + '%'}
<br/>
`
})
return retStr
}
},
series: [
{
type: 'pie',
label: {
show: false
},
emphasis: {
label: {
show: true
},
labelLine: {
show: false
}
}
}
]
}
this.chartInstance.setOption(initOption)
},
async getData () {
// 获取服务器的数据, 对this.allData进行赋值之后, 调用updateChart方法更新图表
const { data: ret } = await this.$http.get('hotproduct')
this.allData = ret
console.log(this.allData)
this.updateChart()
},
updateChart () {
// 处理图表需要的数据
const legendData = this.allData[this.currentIndex].children.map(item => {
return item.name
})
const seriesData = this.allData[this.currentIndex].children.map(item => {
return {
name: item.name,
value: item.value,
children: item.children // 新增加children的原因是为了在tooltip中的formatter的回调函数中,来拿到这个二级分类下的三级分类数据
}
})
const dataOption = {
legend: {
data: legendData
},
series: [
{
data: seriesData
}
]
}
this.chartInstance.setOption(dataOption)
},
screenAdapter () {
this.titleFontSize = this.$refs.hot_ref.offsetWidth / 100 * 3.6
const adapterOption = {
title: {
textStyle: {
fontSize: this.titleFontSize
}
},
legend: {
itemWidth: this.titleFontSize / 2,
itemHeight: this.titleFontSize / 2,
itemGap: this.titleFontSize / 2,
textStyle: {
fontSize: this.titleFontSize / 2
}
},
series: [
{
radius: this.titleFontSize * 4.5,
center: ['50%', '60%']
}
]
}
this.chartInstance.setOption(adapterOption)
this.chartInstance.resize()
},
toLeft () {
this.currentIndex--
if (this.currentIndex < 0) {
this.currentIndex = this.allData.length - 1
}
this.updateChart()
},
toRight () {
this.currentIndex++
if (this.currentIndex > this.allData.length - 1) {
this.currentIndex = 0
}
this.updateChart()
}
}
}
</script>
<style lang='less' scoped>
.arr-left {
position:absolute;
left: 10%;
top: 50%;
transform: translateY(-50%);
cursor: pointer;
color: white;
}
.arr-right {
position:absolute;
right: 10%;
top: 50%;
transform: translateY(-50%);
cursor: pointer;
color: white;
}
.cat-name {
position:absolute;
left: 80%;
bottom: 20px;
color: white;
}
</style>
数据的处理
增加 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) }, |
2.5.3切换数据的实现
布局
<!-- 热销商品图表 --> <template> <div class='com-container'> <div class='com-chart' ref='hot_ref'></div> <span class="iconfont arr_left"></span> <span class="iconfont arr_right"></span> </div> </template> |
样式
<style lang='less' scoped> .arr_left { position: absolute; left: 10%; top: 50%; transform: translateY(-50%); cursor: pointer; } .arr_right { position: absolute; right: 10%; top: 50%; transform: translateY(-50%); cursor: pointer; } </style> |
点击事件
<span class="iconfont arr_left" @click="toLeft"></span> <span class="iconfont arr_right" @click="toRight"></span> methods: { toLeft () { this.currentIndex-- if (this.currentIndex < 0) { this.currentIndex = this.allData.length - 1 } this.updateChart() }, toRight () { this.currentIndex++ if (this.currentIndex > this.allData.length - 1) { this.currentIndex = 0 } this.updateChart() } } |
分类名称的显示
布局和样式
<template>
<div class='com-container'>
......
<span class="cat_name">分类名称</span>
</div>
</template>
<style lang='less' scoped>
.cat_name {
position: absolute;
left: 80%;
bottom: 20px;
font-weight: bold;
}
</style>
名称的改变
增加计算属性 catTitle
<script> export default { ...... computed: { catTitle () { if (!this.allData) { return '' } return this.allData[this.currentIndex].name } }, |
布局中使用计算属性
<!-- 热销商品图表 --> <template> <div class='com-container'> ...... <span class="cat_name">{{ catTitle }}</span> </div> </template> |
2.5.4. UI 效果的调整
主题的使用
methods: { initChart () { this.chartInstance = this.$echarts.init(this.$refs.hot_ref, 'chalk') |
分类名称和箭头的颜色
<style lang='less' scoped>
.arr_left {
......
color: white;
}
.arr_right {
......
color: white;
}
.cat_name {
.....
color: white;
}
</style>
默认隐藏文字, 高亮显示文字
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) }, |
图例形状和位置
methods: { initChart () { this.chartInstance = this.$echarts.init(this.$refs.hot_ref, 'chalk') const 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} ${parseInt((item.value / params.value) * 100) + '%'} ` tipArray.push(childStr) }) return tipArray.join('<br/>') } } |
2.5.5.分辨率适配
分辨率适配主要就是在 screenAdapter方法中进行, 需要获取图表容器的宽度,计算出标题字体大小,
将字体的大小赋值给 titleFontSize
<script> export default { data () { return { titleFontSize: 0 } }, ...... screenAdapter () { this.titleFontSize = this.$refs.hot_ref.offsetWidth / 100 * 3.6 } |
2.6.库存销量分析
最终的效果如下:
2.6.1.代码环境的准备
StockPage.vue
<!--
针对于 /stockpage 这条路径而显示出来的
在这个组件中 , 通过子组件注册的方式 , 要显示出Stock.vue这个组件
-->
<template>
<div class="com-page">
<stock></stock>
</div>
</template>
<script>
import Stock from '@/components/Stock' export default {
data () {
return {}
},
methods: {},
components: {
stock: Stock
}
}
</script>
<style lang="less" scoped>
</style>
Stock.vue
<!-- 库存销量分析 -->
<template>
<div class='com-container'>
<div class='com-chart' ref='stock_ref'></div>
</div>
</template>
<script>
export default {
data () {
return {
chartInstance: null,
allData: null,
currentIndex: 0, // 当前显示的数据
timerId: null // 定时器的标识
}
},
mounted () {
this.initChart()
this.getData()
window.addEventListener('resize', this.screenAdapter)
this.screenAdapter()
},
destroyed () {
window.removeEventListener('resize', this.screenAdapter)
clearInterval(this.timerId)
},
methods: {
initChart () {
this.chartInstance = this.$echarts.init(this.$refs.stock_ref, 'chalk')
const initOption = {
title: {
text: '▎库存和销量分析',
left: 20,
top: 20
}
}
this.chartInstance.setOption(initOption)
this.chartInstance.on('mouseover', () => {
clearInterval(this.timerId)
})
this.chartInstance.on('mouseout', () => {
this.startInterval()
})
},
async getData () {
// 获取服务器的数据, 对this.allData进行赋值之后, 调用updateChart方法更新图表
const { data: ret } = await this.$http.get('stock')
this.allData = ret
console.log(this.allData)
this.updateChart()
this.startInterval()
},
updateChart () {
const centerArr = [
['18%', '40%'],
['50%', '40%'],
['82%', '40%'],
['34%', '75%'],
['66%', '75%']
]
const colorArr = [
['#4FF778', '#0BA82C'],
['#E5DD45', '#E8B11C'],
['#E8821C', '#E55445'],
['#5052EE', '#AB6EE5'],
['#23E5E5', '#2E72BF']
]
// 处理图表需要的数据
const start = this.currentIndex * 5
const end = (this.currentIndex + 1) * 5
const showData = this.allData.slice(start, end)
const seriesArr = showData.map((item, index) => {
return {
type: 'pie',
radius: [110, 100],
center: centerArr[index],
hoverAnimation: false, // 关闭鼠标移入到饼图时的动画效果
labelLine: {
show: false // 隐藏指示线
},
label: {
position: 'center',
color: colorArr[index][0]
},
data: [
{
name: item.name + '\n' + item.sales,
value: item.sales,
itemStyle: {
color: new this.$echarts.graphic.LinearGradient(0, 1, 0, 0, [
{
offset: 0,
color: colorArr[index][0]
},
{
offset: 1,
color: colorArr[index][1]
}
])
}
},
{
value: item.stock,
itemStyle: {
color: '#333843'
}
}
]
}
})
const dataOption = {
series: seriesArr
}
this.chartInstance.setOption(dataOption)
},
screenAdapter () {
const titleFontSize = this.$refs.stock_ref.offsetWidth / 100 * 3.6
const innerRadius = titleFontSize * 2
const outterRadius = innerRadius * 1.125
const adapterOption = {
title: {
textStyle: {
fontSize: titleFontSize
}
},
series: [
{
type: 'pie',
radius: [outterRadius, innerRadius],
label: {
fontSize: titleFontSize / 2
}
},
{
type: 'pie',
radius: [outterRadius, innerRadius],
label: {
fontSize: titleFontSize / 2
}
},
{
type: 'pie',
radius: [outterRadius, innerRadius],
label: {
fontSize: titleFontSize / 2
}
},
{
type: 'pie',
radius: [outterRadius, innerRadius],
label: {
fontSize: titleFontSize / 2
}
},
{
type: 'pie',
radius: [outterRadius, innerRadius],
label: {
fontSize: titleFontSize / 2
}
}
]
}
this.chartInstance.setOption(adapterOption)
this.chartInstance.resize()
},
startInterval () {
if (this.timerId) {
clearInterval(this.timerId)
}
this.timerId = setInterval(() => {
this.currentIndex++
if (this.currentIndex > 1) {
this.currentIndex = 0
}
this.updateChart() // 在更改完currentIndex之后 , 需要更新界面
}, 5000)
}
}
}
</script>
<style lang='less' scoped>
</style>