项
目
最终的效果如图所示
,
最终效果涉及
到
6
个图表
, 5
种图表类型
,
它们分别是折线图
,
柱状图
,
地图
,
散点图
,
饼图.。每个图表的数据都是从后端推送到前端来的
,
不过在项目的初期
,
我们会先使用
ajax
由前端主动获取数
据
,
后续会使用
WebSocket
进行改造。整个项目的架构
是
基于
Vue
的
,
所以我们需要创建
Vue
项目
,
然后在
Vue
项目中开发各个图表组件
。
1.
前端项目的准备
1.1.
vue
–
c
1
i
脚手架创建项
目
1.1.
1
脚手架环境的安装
在全局环境中安装
vue
–
cli
脚手
架
npm install -g @vue/cli
1
.
1.2.
工程的创建
使用命令行执
行
vue create vision
具
体的配置项如下
:
手动选择特性
集成
Router
,
Vuex
,
CSS
Pre
–
processors
是否选用历史模式的路由
选择
Less
作
为
CSS
的预处理器
选择
ESLint
的配置
什
么时候进行
Lint
提示
如何存放
Babel
,
ESLint
等配置文
件
是
否保存以上配置以便下次创建项目时使用
配
置选择完之后
,
就开始创建项目了
,
这个过程需要一些时间
:
当项目就创建完成了
,
会看到这个提示
运行默认的项
目
cd vision
npm run serve
1.1.3.
删除无关代
码
将目录使用
vscode
打开
修
改
App
.
vue
中的代码
,
将布局和样式删除
,
变成如下代码
:
|
删除
components
/
HelloWorld
.
vue
这个文件
删除
views
/
About
.
vue
和
views
/
Home
.
vue
这两个文
件
修改
router
/
index
.
js
中的代码
,
去除路由配置和
Home
组件导入的代码
|
1
.2.
项目的基本配置
在项目根目录下创建
vue
.
config
.
js
文件
在文件中增加代码
|
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
的原型对象
|
1
.3.3.
使用全局
echarts
对象
在其他组件中使用
|
1
.4.
axios
的处理
1.4.1.
安装
axios
包
|
1
.4.2.
封装
axios
对象
在
src
/
main
.
js
文件中配置
axios
并且挂载到
Vue
的原型对象上
|
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
组件
,
并且注册和使用
|
在
src
/
views
/
目录下建立
SellerPage
.
vue
,
这个组件是对应于路由
/
seller
而展示的
给外层
div
元素增加样式
com
–
page
在
SellerPage
中引入
Seller
组件
,
并且注册和使用
|
增
加路由规则
,
在
src
/
router
/
index
.
js
文件中修改
|
新建
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
中引入样式
|
打开浏
览
器
,
输入
http
://127.0.0.1:8999/#/
sellerpage
看
Seller
组件是否能够显示
2.1.2.
图表
Se
ll
er
.
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
|
其
他
图表样式的配置
标题的位置和颜色
|
坐
标轴的大小
|
工
具提示和背景
|
|
柱宽度和柱圆角的实现
|
柱颜色渐变的实现
线
性渐变可以通过
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.
分辨率适配
对窗口大小变化的事件进行监听
mount
ed
时候监听
|
组
件
销毁时取消监听
|
获取图表容器的宽度计算字体大小
|
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.
图表基本功能的实现
数据的获取
|
数据的处理
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)
}
初始化配置
|
堆
叠图效果
要
实现堆叠图的效果
,
series
下的每个对象都需要配置上相同的
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
效果的调整
主题的使用
|
主题使用完之后
,
发现折线图都变成了平滑折线图了
,
这是因为在
chalk
.
js
主题文件中
,
设置
了
smooth
:
true
.
2
.2.4.
切换图表
布局的实现
增加类样式为
title
的
容器
|
字
体文件的引入
将资料文件夹下的字体文件
夹中的
font
复制到
asset
目录下
,
然后在
main
.
js
中引入字体样式文
件
在
Trend
.
vue
中的
style
标签中增加一些样式
|
数据动态渲染
使用计算属性
title
控制标题的内容和标题的可选择项
|
点
击
三角控制显示隐藏
增加一项变量控制可选容器的显示与隐
藏
|
使
用指令
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" @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
|
2
.2.5.
分辨率适配
分辨率适配主要就是在
screenAdapter
方法中进行
,
需要获取图表容器的宽度
,
计算出标题字体大小
,
将
字体的大小赋值给
titleFontSize
|
通
过
titleFontSize
从而设置给标题文字的大小和图例的大小
标题文字的大
小
增加计
算
属性
comStyle
并设置给对应的
div
,
如下
:
|
图
例的大小
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.
细节调整
可选条目的背景色
|
增加标题左侧的小竖杆
|
2
.3.
商家地图分布
最终的效果如下
:
2
.
3.1.
代码环境的准备
MapPage
.
vue
|
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
|
2
.3.2.
显示地图
获取中国地图矢量数据
注册地图数据到
全局
echarts
对象
中
配置
geo
|
2
.3.3.
显示散点图
获取散点数据
|
处理数据并且更新图表
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
效果的调整
主题的使用
|
标
题显示
|
地图位置和颜
色
|
图例控制
|
涟漪效果
|
2
.3.5.
分辨率适配
计算
titleFontSize
|
将
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.
地图点击事件
响应
图
表的点击事件
,
并获取点击项相关的数据
|
将
资料中的
map
_
utils
.
js
复制到
src
/
utils
/
目录之下
得到地图所点击项的拼音和地图矢量数据的路
径
|
回到
中国地图
<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
|
2.
4.2.
图表基本功能的实现
数据的获取
|
数据的处理
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)
},
初始化配置
|
2
.4.3.
UI
效果调整
主题的使用
|
标
题的设置
|
颜色的设置
不同柱显示不同颜色
渐变的控制
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
来实现
定义数据
|
将
startValue
和
endValue
应用在
dataZoom
上
,
并隐藏
dataZoom
的显示
|
启动和停止定时
器
增加
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)
}
}
}
获取数据之后启
动
|
组
件销毁停止
|
鼠标移入停
止
|
鼠标离开启
动
|
2
.4.5.
分辨率适配
计算
titleFontSize
将
titleFontSize
设置给图表的某些区域
|
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
|
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)
},
初
始化配置
|
2.5.3切换数据的实现
布
局
|
样
式
|
点
击事件
|
分类名称的显
示
布局和样式
<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
|
布
局
中使用计算属性
|
2
.5.4.
UI
效果的调整
主题的使用
|
分类名称和箭头的颜色
<style lang='less' scoped>
.arr_left {
......
color: white;
}
.arr_right {
......
color: white;
}
.cat_name {
.....
color: white;
}
</style>
默认隐藏文字
,
高亮显示文字
|
图例形状和位置
|
工
具提示
当鼠
标
移入某个扇区的时候
,
需要将该二级分类之下的三级分类数据进行展示
增
加
series
下饼图每一个扇区的数据
|
显示
tooltip
,
并控制显
示内容
|
2
.5.5.
分辨率适配
分辨率适配主要就是在
screenAdapter
方法中进行
,
需要获取图表容器的宽度
,
计算出标题字体大小
,
将
字体的大小赋值给
titleFontSize
|
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>