开发环境
编辑器:VsCode
Vue:2.6.14
Less:4.0.0
DataV(用于构建大屏,数据展示页面即数据可视化):2.10.0
AntV:
- g2plot(一套简单、易用、并具备一定扩展能力和组合能力的统计图表库):2.4.31
- l7(地理空间数据可视分析引擎):2.17.2
- l7polt(基于l7的地理空间可视化图表库):0.5.5
ThreeJS(实现3D效果的JS库):0.153.0
作者前言:之前我有出过一期ThreeJS的入门教程,既然写可视化大屏,那就搞炫酷一点,这才加了ThreeJS
灵感来源(来自百度智能云-Sugar BI案例):
依赖安装
1、DataV:
npm install @jiaminghi/data-view --save
2、AntV g2plot:
npm install @antv/g2plot --save
3、AntV l7 + l7polt:
npm install @antv/l7 @antv/l7plot --save
4、ThreeJS:
npm install three --save
效果展示
静态资源获取
阿里云盘:点击打开
提取码:r4l5文件目录预览:
- 根目录(按照我的专栏分类)
- 二级目录(按照文章标题分类)
- 三级目录(文章所有静态资源)
随着文章的增加,静态资源占用云盘的容量会越来越大,作者自掏腰包为大家带来便捷的资源和原创文章
很荣幸您能找到我的文章,劳烦动动您的小手,点点赞、收藏、关注一下吧!您的支持是我最大的动力
完整代码
vue文件
<template>
<div class="bodys">
<!-- 顶部部分 -->
<div class="topStyle">
<div>2023-07-04 08:12:30</div>
<div>{{ title }}</div>
<div>湖南-长沙 星期二 晴 31-36℃</div>
</div>
<!-- 主体部分 -->
<div class="mainStyle">
<!-- 左边数据表格 -->
<div>
<dv-border-box-1>
<div>各地工厂产量</div>
<div id="container01"></div>
</dv-border-box-1>
<dv-border-box-1>
<div>综合分析统计图</div>
<div id="container02"></div>
</dv-border-box-1>
<dv-border-box-1>
<div>生成车型</div>
<div id="container03"></div>
</dv-border-box-1>
</div>
<!-- 中间部分 -->
<div>
<!-- 地图 -->
<div>
<div id="containerMap" style="width: 100%; height: 100%"></div>
</div>
<!-- 三维跑车模型 -->
<div>
<div id="modelCar"></div>
</div>
</div>
<!-- 右边数据表格 -->
<div>
<dv-border-box-1>
<div>实时销量</div>
<div id="container04"></div>
</dv-border-box-1>
<dv-border-box-1>
<div>车型销量占比</div>
<div id="container05"></div>
</dv-border-box-1>
<dv-border-box-1>
<div>历史销量</div>
<div id="container06"></div>
</dv-border-box-1>
</div>
</div>
</div>
</template>
<script>
import * as g2plot from '@antv/g2plot';
import * as l7plot from '@antv/l7plot';
import * as THREE from 'three';
import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader';
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls';
export default {
name: 'DemoOne',
data() {
return {
// 页面标题
title: '汽车智能工厂大屏',
// 场景
scene: null,
// 环境光
ambienLight: null,
// 加载的场景
loaderScene: null,
// 渲染器
renderer: null,
// 相机
camera: null
};
},
mounted() {
// 地图加载
this.loadMap();
// 跑车模型加载
this.init();
// 数据表格加载
setTimeout(() => {
this.loadContainer01();
this.loadContainer02();
this.loadContainer03();
this.loadContainer04();
this.loadContainer05();
this.loadContainer06();
}, 500);
},
methods: {
clearScene() {
if (this.scene) {
this.scene.traverse(function (v) {
if (v.type === 'Mesh') {
v.geometry.dispose();
v.material.dispose();
}
});
while (this.scene.children.length > 0) {
this.scene.remove(this.scene.children[0]);
}
this.renderer.dispose();
this.renderer.forceContextLoss();
this.renderer.domElement = null;
this.renderer = null;
this.scene.clear();
this.scene = null;
this.camera = null;
this.ambienLight = null;
this.loaderScene = null;
}
},
init() {
this.clearScene();
this.scene = new THREE.Scene();
this.ambienLight = new THREE.AmbientLight('#ffffff', 1);
this.scene.add(this.ambienLight);
let loader = new GLTFLoader();
loader.load('/gltf/scene.gltf', (gltf) => {
let boundingBox = new THREE.Box3().setFromObject(gltf.scene);
let boundingBoxCenter = new THREE.Vector3();
boundingBox.getCenter(boundingBoxCenter);
const width = document.getElementById('modelCar').clientWidth;
const height = document.getElementById('modelCar').clientHeight;
this.camera = new THREE.PerspectiveCamera(45, width / height, 1, 10000);
let sceneSize = new THREE.Vector3();
boundingBox.getSize(sceneSize);
this.sceneMeshHeight = sceneSize.y;
this.sceneMeshWidth = sceneSize.x;
let distance = Math.max(sceneSize.x, sceneSize.y, sceneSize.z) / 2 / Math.tan(((this.camera.fov / 2) * Math.PI) / 180);
this.camera.position.set(distance / 2.5, sceneSize.y, distance / 2.5);
this.camera.lookAt(0, 0, 0);
this.renderer = new THREE.WebGLRenderer({ alpha: true });
this.renderer.setSize(width, height);
document.getElementById('modelCar').appendChild(this.renderer.domElement);
this.renderer.outputEncoding = THREE.sRGBEncoding;
this.control = new OrbitControls(this.camera, this.renderer.domElement);
this.control.zoomSpeed = 0.5;
this.control.enableDamping = true;
this.control.dampingFactor = 0.02;
this.control.minPolarAngle = 0;
this.control.maxPolarAngle = 1.5;
let envMap = new THREE.CubeTextureLoader().load([
'/sky/st/posx.jpg',
'/sky/st/negx.jpg',
'/sky/st/posy.jpg',
'/sky/st/negy.jpg',
'/sky/st/posz.jpg',
'/sky/st/negz.jpg'
]);
gltf.scene.traverse(function (item) {
if (item instanceof THREE.Mesh) {
item.material.envMap = envMap;
item.material.envMapIntensity = 1;
item.material.needsUpdate = true;
}
});
this.scene.add(gltf.scene);
this.loaderScene = gltf.scene;
this.animat();
this.autoCanvas();
});
},
animat() {
if (this.loaderScene != null) {
this.loaderScene.rotateY(0.01);
}
this.renderer.render(this.scene, this.camera);
this.control.update();
requestAnimationFrame(this.animat);
},
autoCanvas() {
let app = this;
window.onresize = function () {
app.renderer.setSize(window.innerWidth, window.innerHeight);
app.camera.aspect = window.innerWidth / window.innerHeight;
app.camera.updateProjectionMatrix();
};
},
loadMap() {
fetch('https://gw.alipayobjects.com/os/alisis/geo-data-v0.1.2/administrative-data/area-list.json')
.then((response) => response.json())
.then((list) => {
const data = list
.filter(({ level }) => level === 'district')
.map((item) => Object.assign({}, item, { value: Math.random() * 5000 }));
new l7plot.Choropleth('containerMap', {
map: {
type: 'map',
center: [120.19382669582967, 30.258134],
zoom: 3,
pitch: 0
},
source: {
data: data,
joinBy: {
sourceField: 'adcode',
geoField: 'adcode'
}
},
viewLevel: {
level: 'province',
adcode: 430000,
granularity: 'district'
},
autoFit: true,
color: {
field: 'value',
value: ['#B8E1FF', '#7DAAFF', '#3D76DD', '#0047A5', '#001D70'],
scale: { type: 'quantize' }
},
style: {
opacity: 1,
stroke: '#ccc',
lineWidth: 0.6,
lineOpacity: 1
},
label: {
visible: true,
field: 'name',
style: {
fill: '#fff',
opacity: 0.8,
fontSize: 10,
stroke: '#000',
strokeWidth: 1.5,
textAllowOverlap: false,
padding: [5, 5]
}
},
state: {
active: { stroke: 'black', lineWidth: 1 }
},
tooltip: {
items: ['name', 'adcode', 'value']
}
});
});
},
loadContainer06() {
fetch('https://gw.alipayobjects.com/os/bmw-prod/b21e7336-0b3e-486c-9070-612ede49284e.json')
.then((res) => res.json())
.then((data) => {
const area = new g2plot.Area('container06', {
data,
xField: 'date',
yField: 'value',
seriesField: 'country',
slider: {
start: 0.1,
end: 0.9,
textStyle: {
fill: '#fff'
}
},
xAxis: {
label: {
style: {
fill: '#fff'
}
}
},
yAxis: {
label: {
style: {
fill: '#fff'
}
}
},
legend: {
itemName: {
style: {
fill: '#fff'
}
}
}
});
area.render();
});
},
loadContainer05() {
const data = [
{ type: '奥迪', value: 27 },
{ type: '宝马', value: 25 },
{ type: '奔驰', value: 18 },
{ type: '五菱', value: 15 },
{ type: '大众', value: 10 },
{ type: '其他', value: 5 }
];
const piePlot = new g2plot.Pie('container05', {
appendPadding: 10,
data,
legend: {
itemName: {
style: {
fill: '#fff'
}
}
},
angleField: 'value',
colorField: 'type',
radius: 0.9,
label: {
type: 'inner',
offset: '-30%',
content: ({ percent }) => `${(percent * 100).toFixed(0)}%`,
style: {
fontSize: 14,
textAlign: 'center'
}
},
interactions: [{ type: 'element-active' }, { type: 'element-selected' }]
});
piePlot.render();
// 如果业务中还有单选联动,可以考虑使用按住某个键来区分交互 (或者多选之后,让用户自己去触发查询)
document.addEventListener('keyup', (evt) => {
if (evt.key === 'Shift') {
console.info(evt);
const states = piePlot.getStates();
console.info(states.filter((d) => d.state === 'selected'));
// 获取选中元素
// states.filter(d => d.state === 'selected')
}
});
},
loadContainer04() {
fetch('https://gw.alipayobjects.com/os/antfincdn/mor%26R5yBI9/stack-group-column.json')
.then((data) => data.json())
.then((data) => {
const column = new g2plot.Column('container04', {
data,
xField: 'product_type',
yField: 'order_amt',
isGroup: true,
isStack: true,
seriesField: 'product_sub_type',
groupField: 'sex',
xAxis: {
label: {
style: {
fill: '#fff'
}
}
},
yAxis: {
label: {
style: {
fill: '#fff'
}
}
},
legend: {
itemName: {
style: {
fill: '#fff'
}
}
}
});
column.render();
});
},
loadContainer03() {
fetch('https://gw.alipayobjects.com/os/antvdemo/assets/data/antv-keywords.json')
.then((res) => res.json())
.then((data) => {
const wordCloud = new g2plot.WordCloud('container03', {
data,
wordField: 'name',
weightField: 'value',
colorField: 'name',
wordStyle: {
fontFamily: 'Verdana',
fontSize: [8, 32],
rotation: 0
},
random: () => 0.5
});
wordCloud.render();
});
},
loadContainer02() {
const averageData = [
{ date: '2015-02', value: 21168 },
{ date: '2015-08', value: 21781 },
{ date: '2016-01', value: 23818 },
{ date: '2017-02', value: 25316 },
{ date: '2018-01', value: 26698 },
{ date: '2018-08', value: 27890 }
];
const plot = new g2plot.Mix('container02', {
appendPadding: 8,
tooltip: { shared: true },
syncViewPadding: true,
plots: [
{
type: 'column',
options: {
data: [
{ date: '2015-02', value: 160 },
{ date: '2015-08', value: 245 },
{ date: '2016-01', value: 487 },
{ date: '2017-02', value: 500 },
{ date: '2018-01', value: 503 },
{ date: '2018-08', value: 514 }
],
xField: 'date',
yField: 'value',
yAxis: {
type: 'log',
max: 100000,
label: {
style: {
fill: '#fff'
}
}
},
meta: {
date: {
sync: true
},
value: {
alias: '店数(间)'
}
},
label: {
position: 'middle',
style: {
fill: '#fff'
}
}
}
},
{
type: 'line',
options: {
data: averageData,
xField: 'date',
yField: 'value',
xAxis: false,
yAxis: {
type: 'log',
max: 100000,
label: {
style: {
fill: '#fff'
}
}
},
label: {
offsetY: -8,
style: {
fill: '#fff'
}
},
meta: {
value: {
alias: '平均租金(元)'
}
},
color: '#FF6B3B',
annotations: averageData.map((d) => {
return {
type: 'dataMarker',
position: d,
point: {
style: {
stroke: '#FF6B3B',
lineWidth: 1.5
}
}
};
})
}
},
{
type: 'line',
options: {
data: [
{ date: '2015-02', value: null },
{ date: '2015-08', value: 0.029 },
{ date: '2016-01', value: 0.094 },
{ date: '2017-02', value: 0.148 },
{ date: '2018-01', value: 0.055 },
{ date: '2018-08', value: 0.045 }
],
xField: 'date',
yField: 'value',
xAxis: {
label: {
style: {
fill: '#fff'
}
}
},
yAxis: {
line: null,
grid: null,
position: 'right',
max: 0.16,
tickCount: 8,
label: {
style: {
fill: '#fff'
}
}
},
meta: {
date: {
sync: 'date'
},
value: {
alias: '递增',
formatter: (v) => `${(v * 100).toFixed(1)}%`
}
},
smooth: true,
label: {
callback: (value) => {
return {
offsetY: value === 0.148 ? 36 : value === 0.055 ? 0 : 20,
style: {
fill: '#1AAF8B',
fontWeight: 700,
stroke: '#fff',
lineWidth: 1
}
};
}
},
color: '#1AAF8B'
}
}
]
});
plot.render();
},
loadContainer01() {
fetch('https://gw.alipayobjects.com/os/antfincdn/PC3daFYjNw/column-data.json')
.then((data) => data.json())
.then((data) => {
const column = new g2plot.Column('container01', {
data,
xField: 'city',
yField: 'value',
seriesField: 'type',
isGroup: 'true',
columnStyle: {
radius: [20, 20, 0, 0]
},
xAxis: {
label: {
style: {
fill: '#fff'
}
}
},
yAxis: {
label: {
style: {
fill: '#fff'
}
}
},
legend: {
itemName: {
style: {
fill: '#fff'
}
}
}
});
column.render();
});
}
}
};
</script>
<style lang="less" scoped>
.mainStyle {
color: #ffffff;
margin: 2% 5%;
height: 85%;
display: flex;
> div:nth-child(1),
div:nth-child(3) {
width: 25%;
> div {
width: 100%;
height: 30%;
margin: 4% 0;
> div {
> div {
display: flex;
justify-content: center;
width: 94%;
margin: 0 3%;
}
> div:nth-child(1) {
padding-top: 2%;
font-size: 20px;
font-family: 'FZY4JW';
color: #35f3fd;
}
> div:nth-child(2) {
height: 79%;
}
}
}
}
> div:nth-child(2) {
width: 50%;
> div {
padding: 0 2%;
}
> div:nth-child(1) {
margin-top: 1%;
height: 58%;
> div > div {
position: absolute;
top: 2.5%;
height: 95% !important;
}
}
> div:nth-child(2) {
margin-top: 1%;
height: 35%;
> div {
width: 100%;
height: 100%;
}
}
}
}
.topStyle {
color: #fff;
display: flex;
justify-content: space-between;
align-items: center;
margin: 1.6% 6% 0 6%;
font-size: 18px;
> div:nth-child(2) {
font-size: 32px;
font-family: 'FZZiYHJW';
letter-spacing: 3px;
margin-left: 4%;
}
}
.bodys {
overflow: hidden;
width: 100vw;
height: 100vh;
background-image: url('@/assets/background.png');
background-size: 100% 100%;
}
@font-face {
font-family: 'FZZiYHJW';
src: url('@/assets/font/FZZiYHJW.TTF');
}
@font-face {
font-family: 'FZY4JW';
src: url('@/assets/font/FZY4JW.TTF');
}
</style>
main.js文件
import Vue from 'vue';
import App from './App.vue';
import router from './router';
import store from './store';
// DataV依赖
import dataV from '@jiaminghi/data-view';
// 全局使用DataV
Vue.use(dataV);
Vue.config.productionTip = false;
new Vue({
router,
store,
render: (h) => h(App)
}).$mount('#app');
静态资源目录问题
如果你的静态资源存放目录与我的不同,记得要修改vue文件中的静态资源路径,否则报错
作者目录参考:
结语
如有不懂或者疑问,可私信我,看到必回
大屏中所有数据表格、地图展示内容的代码,都是复制官方示例的代码,具体内容并未修改,仅供演示
具体使用,请参考官方文档