暂无图片
暂无图片
1
暂无图片
暂无图片
暂无图片

vue中,整合AntV + DataV实现可视化大屏

原创 雨笛 2023-07-08
1866

开发环境

编辑器: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案例):

Snipaste_2023-07-04_09-45-38.png

依赖安装

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

效果展示

动画.gif

静态资源获取

阿里云盘:点击打开
提取码:r4l5

文件目录预览:

  • 根目录(按照我的专栏分类)

image.png

  • 二级目录(按照文章标题分类)

image.png

  • 三级目录(文章所有静态资源)

image.png

随着文章的增加,静态资源占用云盘的容量会越来越大,作者自掏腰包为大家带来便捷的资源和原创文章
很荣幸您能找到我的文章,劳烦动动您的小手,点点赞、收藏、关注一下吧!您的支持是我最大的动力

完整代码

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文件中的静态资源路径,否则报错
作者目录参考:

image.png

image.png

结语

如有不懂或者疑问,可私信我,看到必回
大屏中所有数据表格、地图展示内容的代码,都是复制官方示例的代码,具体内容并未修改,仅供演示
具体使用,请参考官方文档

「喜欢这篇文章,您的关注和赞赏是给作者最好的鼓励」
关注作者
【版权声明】本文为墨天轮用户原创内容,转载时必须标注文章的来源(墨天轮),文章链接,文章作者等基本信息,否则作者和墨天轮有权追究责任。如果您发现墨天轮中有涉嫌抄袭或者侵权的内容,欢迎发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。

评论