美文网首页
vue-echarts在绘制区域轮廓地图时的应用

vue-echarts在绘制区域轮廓地图时的应用

作者: 折原女乔君 | 来源:发表于2019-05-11 10:24 被阅读0次

    最近在工作项目中遇到一个绘制地图轮廓,并且在地图上根据需求里的学校位置打点,从最初的无从下手到最后完成,中间是边看echarts文档边尝试,开了一个好头后面越写越顺利,因此谨以此篇文章记录下我本次学习的过程。

    首先上图看效果:

    XX教育局大数据平台

    然后说说地图的实现思路:

    1.首先需要绘制出地图轮廓;

    2.请求接口数据,然后在地图上打点;

    3.地图上的学校点击时异步加载学校数据

    现在开始说正经的:


    第一步:绘制地图轮廓

    在绘制地图的准备工作中,需要事先准备好需要绘制的地区的json文件(主要是区域轮廓图的一些重要拐点经纬度,地图就是依靠它一个点一个点连线绘制出来滴),大概长这样:

    地区的json文件

    然后在代码导入并中注册:

    import echarts from 'echarts'

    var config = require('../../../../static/mock/conf')

    var dingnan = require(`../../../../static/mock/${config.map}`)

    echarts.registerMap('dingnan', dingnan)

    前两句代码基本上可以合并成一句,其意思是说给这个地图Json起了个名字,引入的时候要对号入座(还有其他地域地图)。。。

    这一步完成之后,相当于地图已经注册了,但是需要一个载体来呈现它,这时候echarts就出场了:

    地图容器vue

    各种数据初始化准备:

    数据初始化准备

    另外我封装了一个方法makeMapOption用来装载echarts图表所需的各种参数(主要为了方便后面接口请求数据后传到这个方法中地图打点):

    方法makeMapOption

    当程序解析到:

    mounted() {

            this.mapChart = this.$refs.map.chart

            this.mapOption = makeMapOption()

        },

    的时候,地图轮廓已经加载出来了:

    地图轮廓

    第二步:地图打点

    这里就需要异步请求接口了,根据后台返回的数据看看该在哪里打点,打点最重要的两个参数就是经度和纬度啦,接口返回的数据大概长这样:

    服务端接口返回数据

    这个latitude和longitude就是每个学校的经纬度了,而schoolCode则是我们在第三步需要用到的重要参数,接下来就是拿着这些数据封装成我们地图中所需要的结构:

    请求需要打点的学校列表 封装成地图所需数据格式

    分别是打点的学校的名字,学校id,经纬度,学校类别...

    之后传到刚才说的方法中去,echaerts图表参数主要部分series中其中一个项大概就是这样滴(这个截图对应的是文章开头第一张图中的黄色点学校,该地区高中中职在我们系统中的就这两...,同理小学初中,完全中学,教育局也是一样滴,不同就是经纬度和颜色啦,看到这里如果懵了的话,不要捉急,代码被我截图拆的四零八散,最后我会附上完整代码滴~~~~):

    接口返回的数据

    到这地图打点算是完成了,效果就是图一的样子,但是只有地图和点,没有其他信息,看官也不知道这些点到底是干嘛滴不是吗,所以再看

    第三步:异步获取地图上某个点的信息

    其实本来是很简单的一个echarts图表中的tooltip,就是像echarts官网中的许多例子一样,鼠标放上去出现一个小tips,有什么系统名啊,本item的值啊,百分比啊云云,but我们的需求不按常理出牌啊,tips上的文字是自定义滴,需要展示的数据还是需要请求滴,一开始我还是不会滴...

    地图上点的信息tooltip提示

    就是图上红框框里那个玩意儿~~

    于是我又去看了看echarts官网关于formatter的部分,

    echarts配置项中formatter部分说明截图

    划重点aaaaaa

    第二个参数 ticket 是异步回调标识,配合第三个参数 callback 使用。 第三个参数 callback 是异步回调,在提示框浮层内容是异步获取的时候,可以通过 callback 传入上述的 ticket 和 html 更新提示框浮层内容。


    然后怎么办?--硬着头皮写啊,异步请求啊,连接字符串啊,显示啊,上代码:

    tooltip: {

        show: true,

        triggerOn: 'click',

        backgroundColor: 'rgba(10, 17, 64, 0.8)',

        formatter(value, ticket, callback) {

            let _this = this

            fetchSchoolDeatail({ unitCode: value.data.schoolCode }).then(({ data, headers }) => {

              let info = data.data

              let str = `<div style="padding: 10px;">

                    <h4>${value.name}</h4>

                    <p>${info.bxTypeName}</p>

                    <p style="display: flex; line-height: 200%">

                        <span style="flex: 1 1 50%;margin-right: 30px;">在册学生<br/><b style="color: #62b2e2; font-size: 200%">${info.studentNum}</b>人 </span>

                        <span style="flex: 1 1 50%;">在册职工数<br/><b style="color: #65db8a; font-size: 200%">${info.teacherNum}</b>人</span>

                    </p>

                    <p style="display: flex; line-height: 200%">

                        <span style="flex: 1 1 50%;margin-right: 30px;">今日到校学生数<br/><b style="color: #62b2e2; font-size: 200%;">${info.studentArriveSchoolNum}</b> 人</span>

                        <span style="flex: 1 1 50%;">今日到校职工数<br/><b style="color: #65db8a; font-size: 200%">${info.teacherArriveSchoolNum}</b>人</span>

                    </p>

                </div>`

                callback(ticket, str)

          }).catch(err => {

                console.dir(err)

                _this.$message.error(err.message || '获取学校信息失败,请稍后再试')

          }).finally(() => {

                console.log(1)

          })

              return '加载中...'

            }

        },

    有几点说明:

    1. 为了避免鼠标划过地图点就请求接口导致接口卡顿,触发tooltip的方法改成了click,也就是点击一下学校点才会加载学校信息接口数据;

    2. 由于接口请求发送到成功返回需要一点时间,刚一点上去可能是空白或者tips没出来,所以点击那一瞬间tips显示的是“加载中...”,这就是最外层的return;

    3. 当数据加载完成之后必须要写callback(ticket, str),这是人家formatter回调函数规定滴,ticket异步标识不能改,否则不返回任何东西。


    至此呢,整个地图绘制,打点,加载点信息就完成了,下面补充几个我在写代码时的注意项:

    1. 绘制图表的点的样式可以是一般的ciecle圆点或者rect方形,也可以自定义,比如我地图中的教育局图表,就是引入了一张图片

    `const starImg = require('../../../assets/images/secondBg/star.png')`

    引入以后vue自己转化成了base64的编码,然后我们在需要使用的地方是“image://http://xxx.xxx.xxx/a/b.png”这种格式,我们自己需要加上'image://'这段字符,

    ```

    icon: `image://${starImg}`

    ```

    2. 为了方便操作地图上的点,不和页面上其他部分重叠,我设置了地图鼠标缩放和平移漫游(geo坐标系中的roam),也就是鼠标在地图区域内的时候可以拖拽地图移动位置,也可以通过滑轮放大缩小地图面积,有个小瑕疵是有时候拖拽完之后鼠标已经没有左键右键控制了,但是鼠标一挪动,地图还是会跟着走,只有在地图之外的空白处点一下才会丢下拖拽。。。

    3. 本地图series中共有4个不同类型的数据组合,有的点经纬度离得近,地图缩小时会看上去重合,这时候就需要设置好每个类型的zlevel值,这是为了给每个类型绘制canvas时分层,不至于累在一起看不到别的点。


    最后附上我的全部代码:

    <template>

        <div class="map-wrapper">

            <v-chart :option="mapOption" ref="map" style="height: 100% !important;" auto-resize></v-chart>

        </div>

    </template>

    <script>

    import { allSchools, fetchSchoolDeatail } from '@/services/showView'

    import echarts from 'echarts'

    var config = require('../../../../static/mock/conf')

    var dingnan = require(`../../../../static/mock/${config.map}`)

    const starImg = require('../../../assets/images/secondBg/star.png')

    echarts.registerMap('dingnan', dingnan)

    const makeMapOption = (data) => {

        return {

            legend: {

                show: true,

                orient: 'vertical',

                left: '22%',

                top: '15%',

                symbolKeepAspect: false,

                itemWidth: 15,

                itemHeight: 15,

                data: [

                    {

                        name: '小学初中',

                        icon: 'circle',

                        textStyle: {

                            color: '#75eef5'

                        }

                    },

                    {

                        name: '高中中职',

                        icon: 'circle',

                        textStyle: {

                            color: '#f0ea75'

                        }

                    },

                    {

                        name: '完全中学',

                        icon: 'circle',

                        textStyle: {

                            color: '#887bff'

                        }

                    },

                    {

                        name: '县教育局',

                        icon: `image://${starImg}`,

                        textStyle: {

                            color: '#da4800'

                        }

                    }

                ]

            },

            tooltip: {

                show: true,

                triggerOn: 'click',

                backgroundColor: 'rgba(10, 17, 64, 0.8)',

                formatter(value, ticket, callback) {

                    let _this = this

                    fetchSchoolDeatail({ unitCode: value.data.schoolCode }).then(({ data, headers }) => {

                        let info = data.data

                        let str = `<div style="padding: 10px;">

                        <h4>${value.name}</h4>

                        <p>${info.bxTypeName}</p>

                        <p style="display: flex; line-height: 200%">

                            <span style="flex: 1 1 50%;margin-right: 30px;">在册学生<br/><b style="color: #62b2e2; font-size: 200%">${info.studentNum}</b>人 </span>

                            <span style="flex: 1 1 50%;">在册职工数<br/><b style="color: #65db8a; font-size: 200%">${info.teacherNum}</b>人</span>

                        </p>

                        <p style="display: flex; line-height: 200%">

                            <span style="flex: 1 1 50%;margin-right: 30px;">今日到校学生数<br/><b style="color: #62b2e2; font-size: 200%;">${info.studentArriveSchoolNum}</b> 人</span>

                            <span style="flex: 1 1 50%;">今日到校职工数<br/><b style="color: #65db8a; font-size: 200%">${info.teacherArriveSchoolNum}</b>人</span>

                        </p>

                    </div>`

                        callback(ticket, str)

                    }).catch(err => {

                        console.dir(err)

                        _this.$message.error(err.message || '获取学校信息失败,请稍后再试')

                    }).finally(() => {

                        console.log(1)

                    })

                    return '加载中...'

                }

            },

            geo: {

                map: 'dingnan',

                label: {

                    emphasis: {

                        show: false

                    }

                },

                roam: true,

                itemStyle: {

                    normal: {

                        color: 'rgba(43,58,113, 0.2)',

                        borderColor: '#0c6cd2'

                    },

                    emphasis: {

                        color: 'rgba(43,58,113, 0.2)'

                    }

                }

            },

            series: [{

                name: '县教育局', // 地图中心点

                type: 'scatter',

                coordinateSystem: 'geo',

                zlevel: 10,

                symbol: `image://${starImg}`,

                data: [{

                    name: '县教育局',

                    value: data[3]

                }],

                symbolSize: [20, 20],

                label: {

                    normal: {

                        formatter: '{b}',

                        position: 'top',

                        show: false

                    },

                    emphasis: {

                        show: false

                    }

                },

                itemStyle: {

                    normal: {

                        color: '#96edfe'

                    },

                    emphasis: {

                        show: false

                    }

                }

            },

            {

                name: '高中中职',

                type: 'effectScatter', // 涟漪

                coordinateSystem: 'geo',

                zlevel: 2,

                rippleEffect: {

                    brushType: 'stroke',

                    scale: 3

                },

                label: {

                    normal: {

                        fontSize: 16,

                        offset: [12, 0],

                        show: false,

                        position: 'right',

                        formatter: '{b}'

                    },

                    emphasis: {

                        show: false

                    }

                },

                symbolSize: 12,

                itemStyle: {

                    normal: {

                        color: '#f0ea75'

                    }

                },

                data: data[1]

            },

            {

                name: '小学初中',

                type: 'effectScatter', //带有涟漪特效动画的散点

                coordinateSystem: 'geo', // 该系统所使用的坐标系,geo地理坐标系

                zlevel: 2, // zlevel用于 Canvas 分层

                rippleEffect: {

                    brushType: 'stroke', // 波纹的绘制方式

                    scale: 3 // 动画中波纹的最大缩放比例

                },

                label: {

                    normal: {

                        fontSize: 16,

                        offset: [10, 0],

                        show: false,

                        position: 'right',

                        formatter: '{b}'

                    },

                    emphasis: {

                        show: false

                    }

                },

                symbolSize: 10, // 标记的大小

                itemStyle: { // 图形样式

                    normal: {

                        color: '#75eef5'

                    }

                },

                data: data[0]

            },

            {

                name: '完全中学',

                type: 'effectScatter', // 涟漪

                coordinateSystem: 'geo',

                zlevel: 2,

                rippleEffect: {

                    brushType: 'stroke',

                    scale: 3

                },

                label: {

                    normal: {

                        fontSize: 16,

                        offset: [10, 0],

                        show: false,

                        position: 'right',

                        formatter: '{b}'

                    },

                    emphasis: {

                        show: false

                    }

                },

                symbolSize: 8,

                itemStyle: {

                    normal: {

                        color: '#887bff'

                    }

                },

                data: data[2]

            }]

        }

    }

    export default {

        name: 'dingnan-map',

        data() {

            return {

                mapname: config.mapname,

                mapOption: {

                    series: [

                    ]

                },

                mapChart: null,

                coordData: {

                    name: '',

                    coord: [],

                    id: '',

                    children: [],

                    url3D: ''

                },

                geoCoordMap: {},

                noticeList: [],

                searchDeviceList: [],

                allSecuityList: [],

                isPlay: false,

                playInt: '',

                playCount: 1,

                schoolName: ''

            }

        },

        mounted() {

            this.mapChart = this.$refs.map.chart

            this.mapChart.showLoading({

                text: '正在加载',

                color: '#25D7FB',

                textColor: '#25D7FB',

                maskColor: 'rgba(19, 25, 83, 0.4)'

            })

            allSchools().then(({ data, headers }) => {

                // 地图中的学校

                if (data.resultCode === 0) {

                    let map = new Map()

                    for (let item of data.data) {

                        this.coordData.children.push({

                            schoolName: item.schoolName,

                            schoolId: item.schoolCode,

                            url3D: '',

                            coord: [item.longitude, item.latitude],

                            showColor: item.showColor

                        })

                        this.coordData.name = '定南县教体局'

                        this.coordData.coord = [115.049, 24.759872]

                        this.coordData.id = '3607280000'

                        this.coordData.url3D = ''

                        map.set(item.schoolName, [item.longitude, item.latitude])

                    }

                    this.geoCoordMap = map

                    this.mapOption = makeMapOption([this.convertEffectData(1), this.convertEffectData(2), this.convertEffectData(3), this.coordData.coord])

                }

                return { data: null }

            }).catch(err => {

                console.dir(err)

                this.$message.error(err.message || '查询失败,请稍后再试')

            }).finally(() => {

                this.mapChart.hideLoading()

            })

        },

        methods: {

            // 涟漪点

            convertEffectData(colorType) {

                var res = [];

                for (var i = 0; i < this.coordData.children.length; i++) {

                    var dataItem = this.coordData.children[i];

                    if (dataItem.showColor === colorType) {

                        res.push({

                            name: dataItem.schoolName,

                            value: dataItem.coord,

                            schoolCode: dataItem.schoolId

                        });

                    }

                }

                return res;

            }

        }

    }

    </script>

    <style lang="scss" scoped>

    .map-wrapper {

        width: 98%;

        height: 100%;

        position: absolute;

        left: 1%;

        right: 1%;

        margin: auto;

        z-index: 1;

    }

    </style>

    最后再啰嗦一下,我的地图小点点是一颗颗闪亮的小星星,这个主要设置好散点的颜色,涟漪点的圈圈数量,闪动的时间,就很好看啦,我的代码里都有注释。

    第一次写这么多字,有点小鸡冻,分享就这么多了,如果您有幸看到了我的文章,希望能对您遇到的问题有所帮助,与君共勉,fighting!

    相关文章

      网友评论

          本文标题:vue-echarts在绘制区域轮廓地图时的应用

          本文链接:https://www.haomeiwen.com/subject/iugtaqtx.html