├── .gitignore
├── babel.config.js
├── public
├── bg.jpg
├── favicon.ico
├── index.html
└── css
│ └── normalize.min.css
├── src
├── assets
│ └── logo.png
├── main.js
├── views
│ ├── Home.vue
│ ├── grid1.vue
│ └── grid2.vue
├── router.js
├── libs
│ └── util.js
├── App.vue
└── components
│ └── dashboard
│ ├── charts
│ ├── gauge
│ │ └── index.vue
│ ├── bar
│ │ └── index.vue
│ ├── scatter
│ │ └── index.vue
│ ├── line
│ │ └── index.vue
│ ├── dataset
│ │ └── index.vue
│ ├── pie
│ │ └── index.vue
│ ├── radar
│ │ └── index.vue
│ ├── graph
│ │ └── index.vue
│ └── bar2
│ │ └── index.vue
│ ├── index.vue
│ ├── index-grid2.vue
│ └── index-grid.vue
├── package.json
├── LICENSE
├── vue.config.js
└── README.md
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
--------------------------------------------------------------------------------
/babel.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | presets: [
3 | '@vue/app'
4 | ]
5 | }
6 |
--------------------------------------------------------------------------------
/public/bg.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MengFangui/vue-data-visualization/HEAD/public/bg.jpg
--------------------------------------------------------------------------------
/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MengFangui/vue-data-visualization/HEAD/public/favicon.ico
--------------------------------------------------------------------------------
/src/assets/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MengFangui/vue-data-visualization/HEAD/src/assets/logo.png
--------------------------------------------------------------------------------
/src/main.js:
--------------------------------------------------------------------------------
1 | import Vue from 'vue'
2 | import App from './App.vue'
3 | import router from './router'
4 | import echarts from 'echarts'
5 | import { debounce } from './libs/util'
6 | Vue.prototype.$echarts = echarts
7 | Vue.prototype.$debounce = debounce
8 |
9 | Vue.config.productionTip = false
10 |
11 | new Vue({
12 | router,
13 | render: h => h(App)
14 | }).$mount('#app')
15 |
--------------------------------------------------------------------------------
/src/views/Home.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
17 |
23 |
24 |
--------------------------------------------------------------------------------
/src/views/grid1.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
17 |
23 |
24 |
--------------------------------------------------------------------------------
/src/views/grid2.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
17 |
23 |
24 |
--------------------------------------------------------------------------------
/src/router.js:
--------------------------------------------------------------------------------
1 | import Vue from 'vue'
2 | import Router from 'vue-router'
3 | import Home from './views/Home.vue'
4 | import grid1 from './views/grid1.vue'
5 | import grid2 from './views/grid2.vue'
6 |
7 | Vue.use(Router)
8 |
9 | export default new Router({
10 | routes: [
11 | {
12 | path: '/',
13 | name: 'home',
14 | component: Home
15 | },
16 | {
17 | path: '/grid1',
18 | name: 'grid1',
19 | component: grid1
20 | },
21 | {
22 | path: '/grid2',
23 | name: 'grid2',
24 | component: grid2
25 | }
26 | ]
27 | })
28 |
--------------------------------------------------------------------------------
/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 | 数据可视化
9 |
10 |
11 |
14 |
15 |
16 |
17 |
18 |
--------------------------------------------------------------------------------
/src/libs/util.js:
--------------------------------------------------------------------------------
1 | // 函数防抖
2 | export function debounce(fn, wait, immediate) {
3 | let timer;
4 | return function () {
5 | if (timer) clearTimeout(timer);
6 | if (immediate) {
7 | // 如果已经执行过,不再执行
8 | var callNow = !timer;
9 | timer = setTimeout(() => {
10 | timer = null;
11 | }, wait)
12 | if (callNow) {
13 | fn.apply(this, arguments)
14 | }
15 | } else {
16 | timer = setTimeout(() => {
17 | fn.apply(this, arguments)
18 | }, wait);
19 | }
20 | }
21 | }
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "vue-data-visualization",
3 | "version": "1.0.0",
4 | "private": false,
5 | "scripts": {
6 | "serve": "vue-cli-service serve",
7 | "build": "vue-cli-service build"
8 | },
9 | "dependencies": {
10 | "core-js": "^2.6.5",
11 | "echarts": "^4.2.1",
12 | "vue": "^2.6.10",
13 | "vue-router": "^3.0.3"
14 | },
15 | "devDependencies": {
16 | "@vue/cli-plugin-babel": "^3.10.0",
17 | "@vue/cli-service": "^3.10.0",
18 | "vue-template-compiler": "^2.6.10"
19 | },
20 | "postcss": {
21 | "plugins": {
22 | "autoprefixer": {}
23 | }
24 | },
25 | "browserslist": [
26 | "> 1%",
27 | "last 2 versions"
28 | ]
29 | }
30 |
--------------------------------------------------------------------------------
/src/App.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
10 |
11 |
38 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2019 MengFangui
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/vue.config.js:
--------------------------------------------------------------------------------
1 | const path = require('path')
2 |
3 | const resolve = dir => {
4 | return path.join(__dirname, dir)
5 | }
6 |
7 | const BASE_URL = process.env.NODE_ENV === 'production'
8 | ? './'
9 | : './'
10 |
11 | module.exports = {
12 | baseUrl: BASE_URL,
13 | outputDir: 'dist',
14 | pages: {
15 | index: {
16 | // page 的入口
17 | entry: 'src/main.js',
18 | // 模板来源
19 | template: 'public/index.html',
20 | filename: 'index.html',
21 | title: "大屏可视化",
22 | // 在这个页面中包含的块,默认情况下会包含
23 | // 提取出来的通用 chunk 和 vendor chunk。
24 | chunks: ['chunk-vendors', 'chunk-common', 'index']
25 | }
26 | },
27 | lintOnSave: process.env.NODE_ENV === 'development',
28 | parallel: require('os').cpus().length > 1,
29 | chainWebpack: config => {
30 | config.resolve.alias
31 | .set('@', resolve('src'))
32 | .set('_c', resolve('src/components'))
33 | },
34 |
35 | // 设为false打包时不生成.map文件
36 | productionSourceMap: false,
37 | devServer: {
38 | quiet: false,
39 | watchOptions: {
40 | poll: true
41 | },
42 | // 在浏览器上全屏显示编译的errors或warnings。
43 | overlay: {
44 | warnings: false,
45 | errors: true
46 | }
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/src/components/dashboard/charts/gauge/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
55 |
--------------------------------------------------------------------------------
/src/components/dashboard/charts/bar/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
63 |
--------------------------------------------------------------------------------
/public/css/normalize.min.css:
--------------------------------------------------------------------------------
1 | /*! normalize.css v8.0.1 | MIT License | github.com/necolas/normalize.css */html{line-height:1.15;-webkit-text-size-adjust:100%}body{margin:0}main{display:block}h1{font-size:2em;margin:.67em 0}hr{box-sizing:content-box;height:0;overflow:visible}pre{font-family:monospace,monospace;font-size:1em}a{background-color:transparent}abbr[title]{border-bottom:none;text-decoration:underline;text-decoration:underline dotted}b,strong{font-weight:bolder}code,kbd,samp{font-family:monospace,monospace;font-size:1em}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}img{border-style:none}button,input,optgroup,select,textarea{font-family:inherit;font-size:100%;line-height:1.15;margin:0}button,input{overflow:visible}button,select{text-transform:none}[type=button],[type=reset],[type=submit],button{-webkit-appearance:button}[type=button]::-moz-focus-inner,[type=reset]::-moz-focus-inner,[type=submit]::-moz-focus-inner,button::-moz-focus-inner{border-style:none;padding:0}[type=button]:-moz-focusring,[type=reset]:-moz-focusring,[type=submit]:-moz-focusring,button:-moz-focusring{outline:1px dotted ButtonText}fieldset{padding:.35em .75em .625em}legend{box-sizing:border-box;color:inherit;display:table;max-width:100%;padding:0;white-space:normal}progress{vertical-align:baseline}textarea{overflow:auto}[type=checkbox],[type=radio]{box-sizing:border-box;padding:0}[type=number]::-webkit-inner-spin-button,[type=number]::-webkit-outer-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}[type=search]::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}details{display:block}summary{display:list-item}template{display:none}[hidden]{display:none}
2 | /*# sourceMappingURL=normalize.min.css.map */
--------------------------------------------------------------------------------
/src/components/dashboard/charts/scatter/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
73 |
--------------------------------------------------------------------------------
/src/components/dashboard/charts/line/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
75 |
--------------------------------------------------------------------------------
/src/components/dashboard/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
55 |
--------------------------------------------------------------------------------
/src/components/dashboard/index-grid2.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
52 |
--------------------------------------------------------------------------------
/src/components/dashboard/index-grid.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
52 |
--------------------------------------------------------------------------------
/src/components/dashboard/charts/dataset/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
73 |
--------------------------------------------------------------------------------
/src/components/dashboard/charts/pie/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
77 |
--------------------------------------------------------------------------------
/src/components/dashboard/charts/radar/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
81 |
--------------------------------------------------------------------------------
/src/components/dashboard/charts/graph/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
139 |
--------------------------------------------------------------------------------
/src/components/dashboard/charts/bar2/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
149 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # 基于VUE、echarts和Grid的大屏数据可视化实现技术
2 |
3 | ## 简介
4 |
5 | 数据可视化技术是将把比较复杂、抽象的数据通过可视的技术以人们更易理解的形式展示出来,数据可视化技术促进了数据信息的传播和应用。 数据可视化技术是抽象数据的具象表达。
6 |
7 | 大屏数据可视化是以大屏为主要展示载体的数据可视化。目前市场上大屏设备有1280*768的笔记本,也有7680*4320的8K显示屏,设备分辨率宽泛。“面积大、炫酷动效、丰富色彩、可交互”是大屏数据可视化的特点。大屏数据可视化技术主要应用场景有:信息展示、数据分析和监控预警三类。
8 |
9 | 本文阐述基于VUE.js、echarts图表和Grid布局的大屏数据可视化技术。
10 |
11 | ## 技术栈
12 |
13 | * vue
14 | * echarts
15 | * Grid布局
16 |
17 | ## echarts图标库使用
18 |
19 | echarts官网:https://www.echartsjs.com/zh/index.html
20 |
21 | 1. echarts导入VUE项目
22 |
23 | ```
24 | import echarts from 'echarts'
25 | Vue.prototype.$echarts = echarts
26 | ```
27 |
28 | 2. echarts 使用性能优化
29 |
30 | 当window resize时,echart需要重新绘制。若window resize实时对echart重绘,页面会卡顿,页面性能会降低,因此需要考虑dom渲染的性能,加入函数防抖。 下面简单解释一下函数防抖和函数节流。
31 |
32 | ### 函数节流throttle
33 |
34 | 函数节流throttle通俗解释:假设你正在乘电梯上楼,当电梯门关闭之前发现有人也要乘电梯,礼貌起见,你会按下开门开关,然后等他进电梯; 但是,你是个没耐心的人,你最多只会等待电梯停留一分钟; 在这一分钟内,你会开门让别人进来,但是过了一分钟之后,你就会关门,让电梯上楼。
35 |
36 | 所以函数节流throttle的作用是,预先设定一个执行周期,当调用动作的时刻大于等于执行周期则执行该动作,然后进入下一个新的时间周期。
37 |
38 | 函数节流throttle应用:在指定时间,事件最多触发一次。
39 |
40 | ### 函数防抖debounce
41 |
42 | 函数防抖debounce通俗解释假设你正在乘电梯上楼,当电梯门关闭之前发现有人也要乘电梯,礼貌起见,你会按下开门开关,然后等他进电梯; 如果在电梯门关闭之前,又有人来了,你会继续开门; 这样一直进行下去,你可能需要等待几分钟,最终没人进电梯了,才会关闭电梯门,然后上楼。
43 |
44 | 所以函数防抖debounce的作用是,当调用动作触发一段时间后,才会执行该动作,若在这段时间间隔内又调用此动作则将重新计算时间间隔。
45 |
46 | 函数防抖debounce应用:百度首页的搜索按钮。
47 |
48 | 函数节流throttle和函数防抖debounce在函数式编程,如lodash库都有实现。
49 |
50 | 函数防抖debounce的封装:
51 |
52 | ```
53 | // 函数防抖
54 | export function debounce(fn, wait, immediate) {
55 | let timer;
56 | return function () {
57 | if (timer) clearTimeout(timer);
58 | if (immediate) {
59 | // 如果已经执行过,不再执行
60 | var callNow = !timer;
61 | timer = setTimeout(() => {
62 | timer = null;
63 | }, wait)
64 | if (callNow) {
65 | fn.apply(this, arguments)
66 | }
67 | } else {
68 | timer = setTimeout(() => {
69 | fn.apply(this, arguments)
70 | }, wait);
71 | }
72 | }
73 | }
74 | ```
75 |
76 | Vue项目main.js中引入函数防抖:
77 |
78 | ```
79 | import { debounce } from './libs/util'
80 | Vue.prototype.$debounce = debounce
81 |
82 | ```
83 |
84 | echarts图表组件初始化函数防抖:
85 |
86 | ```
87 | mounted() {
88 | // 窗口改变时重新绘制
89 | window.addEventListener("resize", this.$debounce(myChart.resize, 500));
90 | }
91 | ```
92 |
93 | ## echarts 图表使用时细节优化处理
94 |
95 | 1. 图例区域太大导致遮挡住图表
96 |
97 | 在option中设置grid,主要设置top值。
98 |
99 | ```
100 | grid: {
101 | left: "3%",
102 | right: "3%",
103 | top: "3%",
104 | containLabel: true
105 | },
106 | ```
107 |
108 | 2. 防止坐标轴标签显示空间不全
109 |
110 | 在option中设置interval和rotate:
111 |
112 | ```
113 | axisLabel: {
114 | // 防止坐标轴标签显示空间不全
115 | rotate: -30
116 | },
117 | // 防止坐标轴标签显示空间不全
118 | interval: 0
119 | ```
120 |
121 | ## Grid布局
122 |
123 | 首先说明的是grid布局不是bootstrap框架,element ui等UI的栅格布局。目前CSS布局方式有table,浮动,定位,flex和grid布局。
124 |
125 | Grid布局说明:https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Grid_Layout
126 |
127 | Grid和flex布局十分相似,但却有极大的不同。Flex 布局是一维布局,即轴线布局,只能指定"项目"针对轴线的位置。Grid 布局则是将容器划分成"行"和"列",产生单元格,然后指定"项目所在"的单元格,因此是二维布局。
128 | 在大屏数据可视化技术需要解决屏幕的响应式布局。常见的响应式布局有媒体查询,flex布局、百分比布局和Grid布局。相比于媒体查询,flex布局和百分比布局,Grid布局在实现大屏数据可视化技术响应式布局更便捷。有一点需要注意的是Grid存在浏览器兼容,请参考:https://caniuse.com/#search=grid,即在IE浏览器不兼容,对于Edge、Fifefox和Chrome主流浏览器兼容性良好。
129 |
130 | 1. Grid布局三行三列布局核心配置示例
131 |
132 | ```
133 | /* 网格布局 */
134 | display: grid;
135 | grid-template-columns: 29% 40% 29%;
136 | grid-template-rows: 32.333% 33.333% 32.333%;
137 | /* 行与行的间隔(行间距) */
138 | /* grid-row-gap: 20px; */
139 | /* 列与列的间隔(列间距) */
140 | /* grid-column-gap: 20px; */
141 | /* 行间距和列间距均是1% */
142 | grid-gap: 1% 1%;
143 | ```
144 |
145 | 2. Grid布局中行合并示例配置
146 |
147 | html:
148 |
149 | ```
150 |
151 |
152 |
153 |
154 |
155 |
156 |
157 |
158 |
159 |
160 |
163 |
164 |
165 |
166 |
167 |
168 |
169 |
170 |
171 |
172 |
173 |
174 |
175 |
176 |
177 |
178 |
179 |
180 |
181 | ```
182 | css:
183 |
184 | ```
185 |
186 |
212 |
213 | ```
214 |
215 | 2. Grid布局中列合并示例配置
216 |
217 | html:
218 |
219 | ```
220 |
221 |
222 |
223 |
224 |
225 |
226 |
227 |
228 |
229 |
230 |
233 |
234 |
235 |
236 |
237 |
238 |
239 |
240 |
241 |
242 |
243 |
244 |
245 |
246 |
247 |
248 |
249 |
250 |
251 | ```
252 | css:
253 |
254 | ```
255 |
256 |
282 |
283 | ```
284 |
285 | 4. Grid布局左侧固定,右侧自适应布局示例配置
286 |
287 | ```
288 |
289 | display: grid;
290 | /* 左侧固定,右侧自适应布局 */
291 | grid-template-columns: 150px 1fr;
292 | /* grid-template-columns: 150px auto; */
293 |
294 | ```
295 |
296 | 5. Grid布局repeat属性
297 |
298 | 将页面水平和垂直方向均分为100分。
299 |
300 | ```
301 |
302 | .grid {
303 |
304 | height: 100%;
305 | display: grid;
306 | grid-template-columns: repeat(100, 1%);
307 | grid-template-rows: repeat(100, 1%);
308 |
309 | }
310 |
311 | .item {
312 |
313 | border: 1px solid red;
314 |
315 | }
316 |
317 | .item-1 {
318 | /* item-1 dom元素在水平和垂直页面占据左上角的20% */
319 |
320 | grid-column-start: 1;
321 | grid-column-end: 21;
322 | grid-row-start: 1;
323 | grid-row-end: 21;
324 |
325 | }
326 |
327 | ```
328 |
329 | grid布局参考[grid](http://www.ruanyifeng.com/blog/2019/03/grid-layout-tutorial.html)
330 |
331 | ## VUe项目引入normalize 库初始化浏览器默认样式
332 |
333 | ```
334 |
335 |
338 | ```
339 |
340 | ## 项目代码:https://github.com/MengFangui/vue-data-visualization
341 |
342 | ## 效果图
343 |
344 | 
345 |
346 |
347 |
348 | 
349 |
350 | 
351 |
352 |
--------------------------------------------------------------------------------