echart热力图项目

本文详细介绍了在Vue项目中使用Echarts实现热力图和排行柱状图的交互功能,包括柱状图的y轴翻转、自定义宽度、颜色配置,热力图的设置,以及两者之间的悬停、点击交互。此外,还分享了截图功能的实现和解决图表更新卡顿的容器销毁重建策略。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

之前项目中运用echart热力图,在这里记录一下。

项目中如何实践echart

echart应用到项目起来很简单,大概分为以下几步:

  1. vue项目中下载echart:npm install echarts --save
  2. 在组件中导入:import echarts from ‘echarts’
  3. 给定一个有宽高的容器作为echart的画布如:
    <div class="echart-map"></div>
    
  4. echart的配置文件,是一个对象,一般会在echart官方实例网址上尝试调试好,再放到代码里。实际使用上,先通过init方法初始化echart,再通过setOption方法设置option对象。
    	let myArea = echarts.init(document.getElementsByClassName('echart-map')[0])
    	myArea.setOption(this.optionArea)
    
    至此已经完成echart画图。关于option,我的实际经验,会分成两部分:将静态的,不会发生变化的,或者是被不同的图通用的配置放到一个公共的js文件里;对于需要根据接口调用后动态生成的,或者同一种图,细微不同呈现方式的部分,放到实际的组件代码里,最后在setOption之前组装。这样既能满足公共配置封装,又能保持灵活性。
  5. 如果echart容器大小是需要动态变化的,比如使用了百分比,还需要在窗口大小变化时,调用resize方法实现适配
    window.onresize = () => {
    	let myRank = echarts.init(document.getElementsByClassName('echart-rank')[0])
    	myRank.resize()
    }
    
核心功能简介

项目中实现的功能,这里抽取出来核心功能并对实际业务进行抽离,主要要做一个如下的可视化展示功能:
在这里插入图片描述
可以看到,左上角可以切换展示的指标(ind1 - ind9)。切换时,按当前指标在左侧排名区域从大到小展示柱状图,排名前三的省用了一个高亮的渐变色表示;右侧热力地图按照当前指标数据展示。此外,当悬停左侧一个柱子时,进行黄色高亮,同时右侧地图相应省市也高亮为黄色,当点击一个柱子时,当前柱子变为橙色,同时右侧地图也将该省标橙色;反之在热力地图上操作也是类似更新柱状图区域。在柱状图上部有一个将整个可视化区域导出为图片的功能

排行柱状图

柱状图默认是横向的,首先这里要将y轴翻转,option中设置

  yAxis: {
	...
    inverse: true,
	...
  }

在布局方面,运用到了grid,并做了如下设置:

  grid: {
    top: 0,
    left: 92,
    right: 0,
    bottom: 0
  }

采用这个设置的原因,首先我们的柱状排行图需要在纵向上排满整个画布区域,如果不采用grid布局并将top、bottom设置为0,echart将默认将顶部和底部留空,这样达不到我们的目的。在运用grid的基础之上,这里我们就有机会通过设置画布的高度控制每个柱字整体所占据的宽度。

设置柱子的宽度
echart的柱状图是不能够直接控制柱子的宽度的,要精确控制每个柱子所占的宽度,我们可以变相思维,通过控制echart的高度,并将grid的top、bottom设置为0,然后设置画布的高度来实现我们期望的柱子宽度。
以本例来说,我们希望每个柱字整体占据的宽度为20px(并不是柱子本身的宽度,而是echart为每一个柱子区域实际分配的宽度,包含柱子本身以及上下空白部分,如下图所示部分)
在这里插入图片描述
这样我们只需要计算出我们有多少个省,然后将画布高度设置为:省市个数 * 20(px)即可。本例中首先在css设置画布为680px(34个省 * 20),然后根据当前接口返回数据个数进行动态调整:

	if (this.rankData.length < 34) {
    	// 如果数据不足34项排行重新设置echart高度为数据个数*20
        document.getElementsByClassName('echart-rank')[0].style.height = this.rankData.length * 20 + 'px'
    }

再画布外层包了一个固定高度的容器,并设置为overflow-y: scroll,当画布超出外层高度时,出现滚动条。这样设置以期在大屏上,我们能够完美展示34个省,没有滚动条,这时顶部的截图功能也将完美截取到所有省;而在小屏上,能够通过滚动展示所有省。

grid设置中left: 92的设置,是为了留给前面展示y轴内容的,就是排行数字加上省名字。
关于从大到小的排序,只要做好切换指标时,将相应指标省市数据重新做好排序,放到柱状图的数据里就好,这个无需赘述。
悬停文字,这个比较简单,如果需要定制,tooltip中增加一个formatter函数即可

  tooltip: {
    show: true,
    formatter: function (params) {
      return params.name + ':' + formatData(params.data['value'])
    }
  }
热力地图

热力地图除了要引入echarts的公共包,还需要引入专门中国地图的包
import ‘echarts/map/js/china’

option设置里都很简单,热力区域在visualMap中配置,其中颜色范围inRange中配置:

  visualMap: {
  	...
    inRange: {
      color: ['#CFE6FE', '#60ACFC']
    }
    ...
  }

具体数值,在拉接口后将省市的最小值和最大值分别赋给visualMap的min和max:

this.optionArea.visualMap.min = this.rankData[0][currentInd]
this.optionArea.visualMap.max = this.rankData[this.rankData.length - 1][currentInd]
排行柱状图与热力地图交互

这个交互主要指的是悬停和点击的高亮设置,体现在两个图中,包含自身颜色设置,以及一些配置项。
echart事件
echart的事件,使用起来跟dom事件没有啥区别,echart暴露了一般会用到的比如点击、悬停这种事件在它的图形上,比如以下调用:

	let myRank = echarts.init(document.getElementsByClassName('echart-rank')[0])
	myRank.setOption(this.optionRank)
	myRank.on('mouseover', (params) => {
		...
    })

这个params中包含了所有关于当前悬停到的柱子的信息,我们可以在组件代码中结合别的数据加以利用
排行柱状图的颜色
在option中有如下关于颜色的配置项

  series: [{
  	type: 'bar',
	...
    itemStyle: {
      normal: {
        color: function (params) {
          var colorList = [
            '#FFF464',
            '#FF9933',
            {
              colorStops: [
                {
                  offset: 0,
                  color: '#FFD119'
                },
                {
                  offset: 1,
                  color: '#FFAC4C'
                }
              ]
            },
            {
              colorStops: [
                {
                  offset: 0,
                  color: '#00C0FA'
                },
                {
                  offset: 1,
                  color: '#2F95FA'
                }
              ]
            }
          ]
          if (params.data.barH) {
            return colorList[0]
          } else if (params.data.barC) {
            return colorList[1]
          } else if (params.dataIndex < 3) {
            return colorList[2]
          } else {
            return colorList[3]
          }
        },
		...
      }
    },
    emphasis: {
      itemStyle: {
        color: '#FFF464'
      }
    }
  }]

先看emphasis属性,这个是关于悬停行为的设置,在itemStyle设置颜色,那么当我们鼠标悬停柱子之后就会显示相应颜色
在bar本身的itemStyle里,我们配置一个设置颜色的方法,其中有4种颜色的一个数组,根据当前数据的不同状态我们设置当前柱子为不同颜色。4种颜色依次是:悬停时的颜色、点击时的颜色、排行top3的颜色、排行top3以外的颜色,同时优先级也是从前往后的。这样当我们进行悬停和点击操作时,更新数据状态即可。
这里为什么emphasis里面的itemStyle和本身itemStyle的颜色数组都有#FFF464这一项呢?这是悬停柱状图和悬停热力图的不同行为,悬停柱状图本身时,我们可以直接借助echart本身emphasis设置进行悬停高亮,而当悬停热力图时,需要将柱状图相关省的数据更新(该数据项barH设置为true),然后刷新柱状图来实现。
热力地图的颜色
再来看热力图的颜色:

const option = {
  ...
  visualMap: {
    ...
    inRange: {
      color: ['#CFE6FE', '#60ACFC']
    },
	...
  },
  geo: {
    map: 'china',
    itemStyle: {
      emphasis: {
        areaColor: '#fff464'
      }
    },
    regions: []
  },
  series: [
    {
      type: 'map'
      ...
    }
  ]
}

这里的静态配置项体现不出过多内容,我们看到热力图的配置项inRange和emphasis我们已经分别介绍过作用。这里主要看regions属性和我们后续在组件代码里会给series第一项里添加的data属性。
在热力图中,visualMap已经帮助我们完成按照数据大小的地图着色,那么,当点击和悬停时,怎么改变颜色呢?当悬停热力图本身时好说,添加的emphasis可以自动帮我们完成高亮,而其他场景,我们需要添加额外属性帮助我们绕过visualMap里面的颜色设置

  1. 配置geo里面的regions属性,设置地图区块颜色
  2. 在series[0].data绑定的地图数据中设置visualMap:false
    如下为在初始化时的设置:
for (let i = 0; i < this.areaData.length; i++) {
	if (this.areaData[i].visualMap === false) {
    	this.areaData[i].visualMap = true
    }
    if (i === 0) {
    	this.areaData[i].visualMap = false
    }
}
this.optionArea.geo.regions = [
	{
    	name: this.rankData[0].PROVINCE,
        itemStyle: {
        	areaColor: '#FF9933'
        }
	}
]
this.areaData.forEach(item => {
	this.mapData.push({ name: item.PROVINCE, value: item[currentInd], visualMap: item.visualMap })
})
this.optionArea.series[0].data = this.mapData

for循环中,this.areaData是预先同柱状图一个逻辑排序好的从大到小的省份数据,将所有项的visualMap属性先置true,然后将第一项的visualMap置false(初始时候点击高亮排名第一的省),来标识绕过通用visualMap设置
然后我们设置了geo的regions属性,放入第一个省,颜色给好
this.mapData是实际绑定到地图中的数据,带上了每一个省份的visualMap属性开关,从而最终完成特殊颜色的设置
在geo.regions中同样可以添加selected属性来标识当前地图区域的选中状态,这个选中状态实际上应用了emphasis,当我们悬停柱状图时,对地图相应省份进行高亮,只需借助以下方式:

let myRank = echarts.init(document.getElementsByClassName('echart-rank')[0])
myRank.setOption(this.optionRank)

myRank.on('mouseover', (params) => {
	const regionToHighlight = this.optionArea.geo.regions.find(item => {
    	return item.name === params.data.name
    })
	if (regionToHighlight) {
    	regionToHighlight.selected = true
    } else {
    	const newRegion = {
        	name: params.data.name,
            selected: true
        }
        this.optionArea.geo.regions.push(newRegion)
    }
	...
})
截图功能

单个echart有自带的截图功能按钮,但是我们这里需要将两个echart图截到一起,采用了html2canvas工具,它能帮助我们截取指定容器的渲染结果为图片。在指定div中添加了截图方法

    toImage () {
      html2canvas(this.$refs.imageWrapper, {
        backgroundColor: null
      }).then((canvas) => {
        let imgData = canvas.toDataURL('image/png')
        this.fileDownload(imgData)
      })
    },
    // 下载图片
    fileDownload (downloadUrl) {
      let aLink = document.createElement('a')
      aLink.style.display = 'none'
      aLink.href = downloadUrl
      aLink.download = '区域分布.png'
      document.body.appendChild(aLink)
      aLink.click()
      document.body.removeChild(aLink)
    },
容器强制销毁重建

看似大功告成的功能,往往在实际自测过程中并不完美。在不断尝试切换别的指标时,发现echart图的悬停点击联动越来越慢,尤其是在对柱状排行图操作时尤其明显。具体原因不是特别明确,有可能是因为给它添加了事件后,同一个图又要根据数据不断重新渲染。但是对于这类问题,往往有一个根本解决方法,就是强制容器销毁重建,保证每次数据更新时,容器都是新建的。
方法是给容器绑定一个v-if属性,在需要重建容器时,调用如下方法:

this.hasData = false
this.$nextTick(() => {
	this.hasData = true
})

之前笔者在撰文提到过vue的异步更新队列,vue会在一次更新时更新所有属性变化,这是一个完整的更新队列,如果不加nextTick,vue对于hasData连续置false和true的操作认为是不更新,这样容器不会销毁重建,nextTick会在下一次队列更新时,再将hasData置true。我们把这种机制引入到当前项目,发现当不断切换指标后,悬停点击操作都不会卡顿了,完美!
这个方法不仅可以应用到echart上,对于其他vue需要的场景,都可以使用。

这次项目的总结到这里结束,以上所有代码读者都可以在https://github.com/tanglingjia/exp/tree/master/src/components/EchartGeoMap 中找到

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值