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

最近在工作项目中遇到一个绘制地图轮廓,并且在地图上根据需求里的学校位置打点,从最初的无从下手到最后完成,中间是边看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!

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,233评论 4 360
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,013评论 1 291
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,030评论 0 241
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,827评论 0 204
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,221评论 3 286
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,542评论 1 216
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,814评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,513评论 0 198
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,225评论 1 241
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,497评论 2 244
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 31,998评论 1 258
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,342评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 32,986评论 3 235
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,055评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,812评论 0 194
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,560评论 2 271
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,461评论 2 266

推荐阅读更多精彩内容