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

14,238 阅读2分钟

开发环境

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

结语

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