Vue全家桶+Echarts数据可视化实践

写在前面

很久没有更新文章了,并不是偷懒,因为最近一直在学习Vue这个框架,数据可视化这个东西如果离开技术,总感觉有点纸上谈兵。一年前,刚开始玩数据可视化的时候,结合单位的项目和自己对数据可视化的认识,使用HTML+CSS+JS+D3 做了一个小项目

在线展示地址:http://xuxiaoyang.site/isee/
Github地址:https://github.com/xuxiaoyang/isee

最近学习了Vue后,为了练练手,小试牛刀,对这个小项目进行了重构,下面分享一下这个项目的实现。

技术栈

  • Vue 2.x
  • Vue-Cli Vue的脚手架工具
  • Vue-Router 前端路由
  • Vuex 状态管理
  • Echarts 数据可视化库
  • Element 组件库
  • Axios 数据请求

项目初始构建

首先安装Vue-cli 脚手架,并使用它创建一个项目

1
2
npm install -g vue-cli
vue create isee

执行完这两个命令后,会出现一系列的cmd命令选择项,根据自己的需求选择即可,本项目需要Babel、VueRouter、Vuex、Css Pre-processors、EsLint,路由模式选择history mode,css预处理语言使用Less

项目结构目录如下:

  • node_modules 各种依赖模块
  • public 放置静态资源包括平台首页入口index.html、数据文件等
  • src文件夹 这个目录下写代码,包括程序入口、vue组件、route、store等
  • package.json 项目配置文件
  • 其余文件就是Babel.Eslint等一系列配置文件,一般不需要修改

这样,我们的项目架子就搭起来了,下面开始正式整项目

安装模块依赖

完成这个项目,还需要Element做ui组件,Axios进行数据请求,Echarts做数据可视化图表

1
yarn add  element-ui axios echarts

页面实现

本项目是完全采用组件化的思想进行开发的响应式SPA单页面应用,即所有的交互都在一个页面完成,兼容PC端与移动端,使用的时候你会发现第一次加载页面相对比较慢,一旦加载完毕交互时就会很快。
src文件下开始写项目,包括路由组件的顶层路由App.vue,vue的入口文件main.js,路由配置文件route.js,vuex相关配置文件store.js,公共组件components,页面组件

App.Vue 路由组件的顶层路由

所有的路由组件都将挂载到这个页面
App.vue页面

main.js

vue的入口文件,注册路由配置文件router.js,vuex配置文件store.js,Element组件库以及挂载App.vue

main.js

route.js

前端路由配置文件

route.js

store.js

vuex状态管理相关配置文件

store.js

components放置公共组件

包括Nav,Footer等,以Nav为例

Nav.vue页面

views放置页面组件

项目中的6个功能模块以及指南模块独立成为一个页面组件,页面组件中可能会用到公共组件,以Industry.vue为例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
<template>
<div class="module">
<el-button
type="primary"
:icon="unfold?'el-icon-s-unfold':'el-icon-s-fold'"
plain
style="padding:10px 12px;font-size:20px"
class="hidden-lg-and-up"
:class="[unfold?'unfold':'fold']"
@click="selectUnfold"
></el-button>
<el-container>
<el-aside class="hidden-md-and-down">
<el-autocomplete
class="inline-input"
placeholder="请输入名称"
prefix-icon="el-icon-search"
:fetch-suggestions="querySearch"
v-model="name"
@select="handleSelect"
></el-autocomplete>
<el-table
ref="listTable"
:data="nameList"
highlight-current-row
height="calc(100vh - 60px)"
@current-change="handleCurrentChange"
>
<el-table-column property="id" label="#" align="center" width="60" sortable></el-table-column>
<el-table-column property="value" label="高校名称" align="center" sortable></el-table-column>
</el-table>
</el-aside>
<el-drawer
:visible.sync="elDrawer"
direction="ltr"
title="高校列表"
:show-close="false"
:wrapperClosable="false"
class="hidden-lg-and-up"
id="drawer"
size = "320px"
>
<el-autocomplete
class="inline-input"
placeholder="请输入名称"
prefix-icon="el-icon-search"
:fetch-suggestions="querySearch"
v-model="name"
style="width:100%"
@select="handleSelect"
></el-autocomplete>
<el-table
ref="listTable"
:data="nameList"
highlight-current-row
height="calc(100vh - 45px)"
@current-change="handleCurrentChange"
>
<el-table-column property="id" label="#" align="center" width="60" sortable></el-table-column>
<el-table-column property="value" label="高校名称" align="center" sortable></el-table-column>
</el-table>
</el-drawer>
<el-container>
<el-header height="auto">
<el-row v-if="currentRow">
<el-col :lg="8" :xs="24">
<h2></h2>
</el-col>
<el-col :lg="8" :xs="24">
<span>
排名
<br />
<strong></strong>
</span>
</el-col>
<el-col :lg="8" :xs="24">
<span>
排行榜内高校数量
<br />
<strong></strong>
</span>
</el-col>
</el-row>
</el-header>
<el-main height="auto">
<div id="canvas"></div>
</el-main>
<el-footer height="auto">
<el-table
ref="indexTable"
:data="currentTableData"
highlight-current-row
>
<el-table-column type="index" align="center"></el-table-column>
<el-table-column property="indicatorName" label="指标" align="left"></el-table-column>
<el-table-column property="indicatorvalue" label="数值" align="center"></el-table-column>
</el-table>
</el-footer>
<CopyRight></CopyRight>
</el-container>

</el-container>
</div>
</template>

<script>
import { mapGetters } from "vuex";
import * as echarts from "echarts/lib/echarts";
import "echarts/lib/chart/radar";
import "echarts/lib/component/tooltip";
import CopyRight from "@/components/CopyRight.vue";
export default {
data() {
return {
name: "",
elDrawer: false,
unfold: true,
currentRow: null,
currentData: [],
};
},
components:{
CopyRight
},
created() {
this.$store.dispatch("initData", { name: "industry" });
//在页面加载时读取localStorage里的状态信息
localStorage.getItem("dataset") &&
this.$store.replaceState(
Object.assign(
this.$store.state,
JSON.parse(localStorage.getItem("dataset"))
)
);
//在页面刷新时将vuex里的信息保存到localStorage里
window.addEventListener("beforeunload", () => {
localStorage.setItem("dataset", JSON.stringify(this.$store.state));
});
},
updated() {
let id = this.currentRow ? this.currentRow.id - 1 : 0;
this.$refs.listTable.setCurrentRow(this.nameList[id]);
},
methods: {
selectUnfold() {
this.elDrawer = !this.elDrawer;
this.unfold = !this.unfold;
},
handleCurrentChange(val) {
this.currentRow = val;
this.currentData = this.dataList[val.id - 1];
this.drawRadar();
},
handleSelect(val) {
this.$refs.listTable.setCurrentRow(this.nameList[val.id - 1]);
},
querySearch(queryString, cb) {
var results = queryString
? this.nameList.filter(item => item.value.indexOf(queryString) === 0)
: this.nameList;
cb(results);
},
drawRadar() {
let radarChart = echarts.init(document.getElementById("canvas"));
radarChart.setOption({
tooltip: {},
radar: {
splitNumber: 10,
startAngle: 90,
name: {
color: "#303133"
},
axisLine: {
symbolOffset: [0, 10]
},
splitLine: {
lineStyle: {
type: "dashed"
}
},
splitArea: {
show: false
},
indicator: this.indicatorsList.slice(1).map((d, index) => {
return {
name: d,
max: this.maxData[index]
};
})
},
series: [
{
type: "radar",
symbolSize: 7,
label: {
show: true,
color: "#1890ff"
},
lineStyle: {
color: "#1890ff",
width: 2
},
itemStyle: {
color: "#1890ff"
},
data: [
{
name: this.currentData[0],
value: this.currentData.slice(1)
}
]
}
]
});
}
},
computed: {
...mapGetters(["indicatorsList", "dataList", "nameList"]),
currentTableData() {
let that = this;
return this.currentData.map((d, index) => {
return {
indicatorName: that.indicatorsList[index],
indicatorvalue: d
};
});
},
maxData() {
let init = new Array(this.dataList[0].length - 1).fill(0);
return this.dataList.reduce((accumulator, cur) => {
let idata = cur.slice(1);
return accumulator.map((d, index) =>
d > +idata[index] ? d : +idata[index]
);
}, init);
}
}
};
</script>

<style lang="less" scoped>
//css部分这里省略...
</style>


这样,整个项目就构建好了,项目预览地址:

http://xuxiaoyang.site/isee-vue

欢迎体验~
欢迎批评指正~