基于Echarts+百度地图+Three.js的数据可视化系统

6,457 阅读4分钟

上周重构项目数据统计部分写了几篇echarts相关的文章,重构的后台前端框架改用Vue,关于Vue中引用Echarts及可能遇到的问题的文章如下所示:


其中提到大学的时候echarts教程较少,想要做一些理想效果比较麻烦,正好记得大三的时候写了一个,基于Echarts+Three.js+百度地图的数据可视化系统,当时这个系统参加了计算机设计大赛,是拿了安徽省一等奖,全国二等奖,里面有写了几个关于Echarts的样式,当时的技术还比较菜,系统里面的代码仅供参考。

系统介绍视频【比赛演示视频】

下面就介绍下系统用到的Echarts+百度地图+Three.js:

一、Echarts开发介绍

关于echarts图表部分以下面这个图表进行介绍

在这里插入图片描述

这个图表使用的是带有涟漪特效动画的散点(气泡)图,利用动画特效可以将某些想要突出的数据进行视觉突出,在图表配置文件series中将type值设置成effectScatter就可以了。

上面效果的话,首先是把图表自带的一些样式给隐藏,主要在xAxis和yAxis更改一些属性,然后在series设置气泡的样式。关于鼠标在图表内移动展示的提示部分,主要是修改默认的提示框组件样式,将formatter方法返回的内容自定义成自己开发的组件样式,提示框组件部分如下所示:


// 该部分只是布局代码,样式写在上面的样式内
tooltip: {
    trigger: 'axis',
    axisPointer: {
        type: 'cross'
    },
        formatter: function (params) { // 当鼠标在图表的区域,设置提示栏的CSS样式
        return '<div class="echarts"><table><tr><td colspan=2""><img src="../libs/img/p.gif"></td></tr><tr><td>消防员姓名</td><td>' + params[0].name + '</td></tr><tr><td>所在楼层</td><td>' + params[0].value + '</td></tr><tr><td>当前温度</td><td>30</td></tr><tr><td>当前气压</td><td>20</td></tr><tr><td>状态</td><td>移动</td></tr></table> <img src="../libs/img/bj-1.png" alt="" class="bj-1"><img src="../libs/img/bj-2.png" alt="" class="bj-2"> <img src="../libs/img/bj-3.png" alt="" class="bj-3"><img src="../libs/img/bj-4.png" alt="" class="bj-4"></div>';
    }
}

// 其中params参数里面包含了当前位置X轴、Y轴的数据

该图表代码如下所示【系统源码下载:h5-simple-visualization】:

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8" />
    <meta content="width=device-width,initial-scale=1,minimum-scale=1,maximum-scale=1,user-scalable=no" name="viewport"/>
    <title></title>

    <!--  引入echarts  -->
    <script src="../libs/js/echarts.js"></script>

    <style type="text/css">
        html, body {
            overflow: hidden;
            height: 100%;
            background: linear-gradient(#104BAA, #000000);
            margin: 0;
            padding: 0;
        }
        p, ul, li {
            margin: 0;
            padding: 0;
            list-style-type: none;
        }
        .index {
            margin-top: 135px;
        }
        .index .tit {
            width: 100%;
            height: 50px;
            font-size: 25px;
            text-align: center;
            font-family: Arial, 华文新魏;
            font-weight: bold;
            line-height: 50px;
            border-radius: 20px;
            color: #00FFCC;
            border-bottom: 1px solid #0099ff;
            box-shadow: 0 0 15px 1px #20558b;
        }
        .index .box {
            width: 96%;
            height: 1200px;
            margin-left: 2%;
            margin-top: 120px;
            border: 1px solid #0099ff;
            box-shadow: 0 0 15px 1px #20558b;
            border-radius: 25px;
        }
        .index .echart {
            width: 96%;
            height: 575px;
            margin-top: 20px;
            margin-left: 2%;
            padding-top: 20px;
            background-color: rgba(0, 24, 106, 0.5);
            border: 1px solid #20558b;
            box-shadow: 0 0 15px 1px #20558b;
            position: relative;
        }
        .index #main {
            width: 100%;
            height: 600px;
        }
        .echarts {
            width: 200px;
            height: 250px;
            color: #00FFCC;
            font-size: 20px;
            font-family: Arial, 华文新魏;
            background-color: rgba(0, 24, 106, 0.5);
            border: 1px solid #20558b;
            box-shadow: 0 0 15px 1px #20558b;
            position: relative;
        }
        .moddle {
            width: 96%;
            height: 500px;
            margin-top: 10px;
            margin-left: 2%;
            background-color: rgba(0, 24, 106, 0.5);
            border: 1px solid #20558b;
            box-shadow: 0 0 15px 1px #20558b;
            position: relative;
        }
        .moddle .ifra {
            width: 96%;
            height: 470px;
            margin-left: 2%;
            margin-top: 12px;
        }
        table {
            width: 100%;
            height: 250px;
            text-align: center;
            padding-top: 10px;
        }
        table img {
            width: 100%;
            height: 80px;
        }
        .bj-1 {
            position: absolute;
            left: -1px;
            top: -1px;
        }
        .bj-2 {
            position: absolute;
            right: -1px;
            top: -1px;
        }
        .bj-3 {
            position: absolute;
            right: -1px;
            bottom: -1px;
        }
        .bj-4 {
            position: absolute;
            left: -1px;
            bottom: -1px;
        }
    </style>
</head>
<body>
<div class="index">
    <div class="box">
        <div class="tit">
            消防员信息实时监控
        </div>
        <div class="moddle">
            <iframe class="ifra" src="../libs/model/showModel.html">
            </iframe>
            <img alt="" class="bj-1" src="../libs/img/bj-1.png">
            <img alt="" class="bj-2" src="../libs/img/bj-2.png">
            <img alt="" class="bj-3" src="../libs/img/bj-3.png">
            <img alt="" class="bj-4" src="../libs/img/bj-4.png">
        </div>
        <div class="echart">
            <div id="main">
            </div>
            <img alt="" class="bj-1" src="../libs/img/bj-1.png">
            <img alt="" class="bj-2" src="../libs/img/bj-2.png">
            <img alt="" class="bj-3" src="../libs/img/bj-3.png">
            <img alt="" class="bj-4" src="../libs/img/bj-4.png">
        </div>
    </div>
</div>
</body>
</html>

<script>
    
    // 获取图表的ID值,并设置图表的主题风格
    let myChart = echarts.init(document.getElementById('main'), 'walden');

    let option = {
        title: {
            text: '所在楼层/层',
            left: '5%',
            textStyle: {
                color: '#00FFCC',
                fontFamily: '华文新魏',
                fontWeight: 'bold',
                fontSize: '25',
                align: 'center'
            }
        },
        tooltip: {
            trigger: 'axis',
            axisPointer: {
                type: 'cross'
            },
                formatter: function (params) { // 当鼠标在图表的区域,设置提示栏的CSS样式
                return '<div class="echarts"><table><tr><td colspan=2""><img src="../libs/img/p.gif"></td></tr><tr><td>消防员姓名</td><td>' + params[0].name + '</td></tr><tr><td>所在楼层</td><td>' + params[0].value + '</td></tr><tr><td>当前温度</td><td>30</td></tr><tr><td>当前气压</td><td>20</td></tr><tr><td>状态</td><td>移动</td></tr></table> <img src="../libs/img/bj-1.png" alt="" class="bj-1"><img src="../libs/img/bj-2.png" alt="" class="bj-2"> <img src="../libs/img/bj-3.png" alt="" class="bj-3"><img src="../libs/img/bj-4.png" alt="" class="bj-4"></div>';
            }
        },
        toolbox: {
            show: true,
            right: '100',
            itemGap: 20,
            itemSize: '20',
            feature: {
                dataZoom: {
                    yAxisIndex: 'none'
                },
                dataView: {
                    readOnly: false
                },
                magicType: {
                    type: ['pictorialBar', 'bar']
                },
                restore: {},
                saveAsImage: {}
            },
            iconStyle: {
                borderColor: '#00FFCC'
            }
        },
        xAxis: {
            data: ['消1号', '消2号', '消3号', '消4号', '消5号'],
            axisTick: {
                show: true
            },
            axisLine: {
                show: false
            },
            axisLabel: {
                textStyle: {
                    color: '#00FFCC',
                    fontFamily: '华文新魏',
                    fontWeight: 'bold',
                    fontSize: '20',
                }
            }
        },
        yAxis: {
            splitLine: {
                show: false
            },
            axisTick: {
                show: true
            },
            axisLine: {
                show: false
            },
            axisLabel: {
                show: true,
                textStyle: {
                    color: '#00FFCC',
                    fontFamily: '华文新魏',
                    fontWeight: 'bold',
                    fontSize: '20',
                }
            }

        },
        color: ['#ffffff'],
        series: [{
            name: '海拔高度',
            type: 'effectScatter',
            rippleEffect: {
                brushType: 'stroke'
            },
            symbolSize: 25,
            hoverAnimation: true,
            barCategoryGap: '-130%',
            symbol: 'circle',
            itemStyle: {
                normal: {
                    color: '#FF00FF'
                },
                emphasis: {
                    color: '#FF00FF'
                }
            },
            data: [5, 0, 1, 5, 3],
            z: 10
        }],
    };

    myChart.setOption(option);

    // 设置点击跳转方法,并传送图表X属性的值
    myChart.on('click', function (params) {
        if (params.name == '') {
            window.open('new_data.html?a=' + encodeURIComponent(params.name) + '');
        }
    });
</script>

二、百度地图开发介绍

系统使用百度地图主要是展示在地图上添加一些覆盖物,然后点击覆盖物到其详情页介绍,下面介绍接入流程:

在这里插入图片描述

因为这里用的是百度地图,首先的话要去百度地图开发者中心注册成为开发者,然后再控制台应用管理新建一个应用,应用类型选择浏览器端,白名单填写*即可,然后就能拿到应用的key,根据key的话就能调用百度地图。

在这里插入图片描述

因为我这个是两年前的系统,用到的地图代码和现在的不同,首先new的地图实例都改变了,具体的可以看下最新的文档。

主要就介绍下面几个方法:

  1. createMap内Point里面是想要进入地图展示的位置,如下的经纬度和setCurrentCity方法设置的是滁州,进入系统地图就会缩进到滁州那里。

  2. addMapOverlay方法就是设置地图上面的覆盖物,也就是想要在地图上面展示的自己的内容如上面的提示框组件,这里就是循环覆盖物数组,根据经纬度逐次展示覆盖物。

  3. styleMap方法就是改变地图的样式,这里的话官方提供了工具个性化地图编辑器,我用的是官方的【眼眸】样式,在官网生成JSON文件,然后在styleJson设置下就行

百度地图代码如下所示【项目源码下载:h5-simple-visualization】:

<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
    <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
    <title></title>

    <!--  引入百度地图,这里的key填写自己开发者创建应用的key -->
    <script src="http://api.map.baidu.com/api?v=2.0&ak=" type="text/javascript"></script>

    <!--  加载鼠标绘制工具  -->
    <script src="http://api.map.baidu.com/library/DrawingManager/1.4/src/DrawingManager_min.js" type="text/javascript"></script>
    <link href="http://api.map.baidu.com/library/DrawingManager/1.4/src/DrawingManager_min.css" rel="stylesheet"/>

    <!--  加载检索信息窗口  -->
    <script src="http://api.map.baidu.com/library/SearchInfoWindow/1.4/src/SearchInfoWindow_min.js" type="text/javascript"></script>
    <link href="http://api.map.baidu.com/library/SearchInfoWindow/1.4/src/SearchInfoWindow_min.css" rel="stylesheet"/>

</head>
<body>
<div class="v_box">
    <div class="v_map" id="map" style="width: 100%;height: 500px;">
    </div>
</div>
</body>
<script type="text/javascript">

    // 初始化地图
    let map;
    initMap();

    // 创建和初始化地图函数:
    function initMap() {
        createMap();      //创建地图
        setMapEvent();    //设置地图事件
        addMapControl();  //向地图添加控件
        addMapOverlay();  //向地图添加覆盖物
        styleMap();     //地图个性化模板
    }

    function createMap() {
        map = new BMap.Map("map");
        map.centerAndZoom(new BMap.Point(118.339637, 32.305602), 15);
        map.addControl(new BMap.MapTypeControl({
            mapTypes: [
                BMAP_NORMAL_MAP,
                BMAP_HYBRID_MAP
            ]
        }));

        // 设置地图显示的城市 此项是必须设置的
        map.setCurrentCity("滁州");
        map.enableScrollWheelZoom(true);

        //添加城市列表
        map.enableInertialDragging();
        map.enableContinuousZoom();

        let size = new BMap.Size(10, 10);
        map.addControl(new BMap.CityListControl({
            anchor: BMAP_ANCHOR_TOP_LEFT,
            offset: size,
        }));
    }

    // 添加地图类型和缩略图
    function add_control() {
        map.addControl(mapType1);      // 2D图,卫星图
        map.addControl(mapType2);      // 左上角,默认地图控件
        map.addControl(overView);      // 添加默认缩略地图控件
        map.addControl(overViewOpen);  // 右下角,打开
    }

    function setMapEvent() {
        map.enableScrollWheelZoom();
        map.enableKeyboard();
        map.enableDragging();
        map.enableDoubleClickZoom()
    }

    function addClickHandler(target, window) {
        target.addEventListener("click", function () {
        });
    }

    function addMapOverlay() {
        let data_info = [[118.34264, 32.30297, "滁州市琅琊区消防支队"],
            [118.325967, 32.3142, "滁州市琅琊区消防支队"],
            [118.357013, 32.291738, "滁州市琅琊区消防支队"],
            [118.328842, 32.296133, "滁州市琅琊区消防支队"],
            [118.371961, 32.312735, "滁州市琅琊区消防支队"],
            [118.351264, 32.325429, "滁州市琅琊区消防支队"]
        ];
        let opts = {
            width: 250,          // 信息窗口宽度
            height: 80,          // 信息窗口高度
            title: "",           // 信息窗口标题
            enableMessage: true  //设置允许信息窗发送短息
        };
        for (let i = 0; i < data_info.length; i++) {
            let myIcon = new BMap.Icon("../libs/img/x_1.png", new BMap.Size(50, 50), {
                offset: new BMap.Size(10, 25)// 设置图片偏移
            });

            let marker = new BMap.Marker(new BMap.Point(data_info[i][0], data_info[i][1]), {icon: myIcon});  // 创建标注
            marker.setAnimation(BMAP_ANIMATION_BOUNCE);

            let content = data_info[i][2];
            map.addOverlay(marker); // 将标注添加到地图中

            addClickHandler(content, marker);
            marker.addEventListener('dbclick', function () {
                window.location.href = './detail.html';
            });
        }

        function addClickHandler(content, marker) {
            marker.addEventListener("click", function (e) {
                    openInfo(content, e)
                }
            );
        }

        function openInfo(content, e) {
            let p = e.target;
            let point = new BMap.Point(p.getPosition().lng, p.getPosition().lat);
            let infoWindow = new BMap.InfoWindow(content, opts);  // 创建信息窗口对象

            map.openInfoWindow(infoWindow, point); // 开启信息窗口
        }
    }

    // 向地图添加控件
    function addMapControl() {
        let navControl = new BMap.NavigationControl({
            anchor: BMAP_ANCHOR_BOTTOM_RIGHT,
            type: BMAP_NAVIGATION_CONTROL_LARGE
        });

        map.addControl(navControl);
    }

    // 个性化地图
    function styleMap() {
        let myStyleJson = [
            {
                "featureType": "road",
                "elementType": "geometry.stroke",
                "stylers": {
                    "color": "#ff0000"
                }
            }];

        map.setMapStyle({styleJson: myStyleJson});

		// 这里放自己再样式生成器生成的json
        map.setMapStyle({
            styleJson: []
        });

    }

</script>
</html>

三、Three.js开发介绍

系统关于展示模型的部分,使用的是three.js技术,里面使用iframe将这个模型文件引入进来。这个部分比较麻烦些,因为不会自己建模,所以从网上一个模型网站下载了这个模型,然后使用3dmax将模型转成mtl和obj类型的文件,在PrepDataFile内设置下上述两个文件就行。

在这里插入图片描述

该部分three.js部分代码使用了学长在网上找的代码,消防车模型代码就是在系统里面建筑部分代码基础上修改的,这里代码太长了可以去项目看下libs/model/max_1.html文件。

three.js代码如下所示【项目源码下载:h5-simple-visualization】:

	// 这里代码大概在433行,可以看下libs/model/max_1.html文件
    var prepData = new THREE.OBJLoader2.WWOBJLoader2.PrepDataFile(
        '1',
        '',
        '1.obj',  // 在这里设置自己的模型文件,就可以将模型引进来
        '',
        '1.mtl'   // 在这里设置自己的模型文件,就可以将模型引进来
    );
    
    app.loadFiles(prepData);

下周的话准备出一篇《让CSS3中Transform属性带你一文实现炫酷的转盘抽奖效果》博文,解析转盘抽奖系统的前后端开发过程。