看啥推荐读物
专栏名称: 龙小胖
菜鸡
目录
相关文章推荐
今天看啥  ›  专栏  ›  龙小胖

数据可视化,它不香吗

龙小胖  · 掘金  ·  · 2019-12-09 06:19
阅读 97

数据可视化,它不香吗

ECharts

最近2个月没有写东西,因为准备了个考试,周六刚刚解脱,回过头来把这两个月做的东西发一下。主要把用到的一部分Echarts的可视化展示,结合之前用G2做的来把做过的图形化的东西分享一下。(本文取自实际代码结合重点注释)

Echarts

ECharts部分的话大概有4部分:

  1. ECharts地图使用;
  2. 柱状图滚动和点击选中事件;
  3. 平行坐标系使用;
  4. 外加一个得分条;

地图

在这里插入图片描述
首先看一下一个简单的测试数据(地区得分为0会是深蓝色,所以地图有几块深蓝色不好看,数据比较简单)

1、首先看一看地图,先说在Echarts中地图的使用,有注释的代码一定很舒服~

<div id="echartMap" style="width: 100%;height:100%"></div> //宽高100%是必要的


methods:{
initMapCharts(data, init) {
      const that = this
      that.chartMap = echarts.init(document.getElementById('echartMap'))
      that.chartMap.clear() //用来清除图像示例,无论是初始化或者是切换项目都会清除map示例,不清楚示例多次setoption你的地图会有问题的
      
      let sum = 0//这是右上角所有地区的一个总分,直接绑定在了html模板上
      for (let i of that.regionScore.scores) {
        sum += i.score
      }
      
      that.regionScore.scores.forEach(e => {
        let num = e.score / sum
        e.itemStyle = {}
        e.itemStyle.areaColor = `rgba(24,${num * 500}, 255,1)`
        if (that.isActiveCity == e.regionCode) {
          e.selected = true
        } else {
          e.selected = false
        }
      })//这个方法很重要,根据不同地区的得分,做一个颜色值的比例得出一个基于得分百分比的蓝色rgba,同事为每个地区对象添加一个selected属性
      
      axios.get('/echarts/chengdu.json').then(function(res) { //这里调用了本地的成都地区json文件用来渲染地图
        echarts.registerMap('chengdu', res.data) 
        that.chartMap.setOption({ //地图配置项
          backgroundColor: 'rgba(182,216,255,0)',
          selectedMode: 'single', //单击模式
          //对移动到地区上显示当前地区名字得分和排名的tooltip使用formatter来return字符串改造
          tooltip: {
            trigger: 'item',
            backgroundColor: 'rgba(4,35,134,0.38)',
            borderWidth: 1,
            borderColor: '#0e6de9',
            showDelay: 0,
            hideDelay: 0,
            enterable: true,
            transitionDuration: 0,
            extraCssText: 'z-index:100',
            formatter: function(params) {
              let res = `<div style="padding: 5px">
															<span>${params.name}</span><br/>
                              <span style="font-size: 10px;line-height: 30px">综合排名 </span>
                              <span style="color: #00FFED;font-size: 18px;line-height: 30px">NO.${that.regionScore.scores[params.dataIndex].rank}</span><br/>
                              <span style="font-size: 10px;line-height: 30px">综合得分 </span>
                              <span style="color: #00FFED;font-size: 18px;line-height: 30px">${that.regionScore.scores[params.dataIndex].score}</span>
													 </div>`
              return res
            }
          },
          
          geo: {
              …………
              //省略,配置项比较简单
          },
          series: [
            {
              type: 'map',
              mapType: 'chengdu',
              roam: true,
              scaleLimit: {
                min: '1'
              },
              zoom: 1.2,
              label: {
                normal: {
                  show: true,
                  color: '#fff',
                  fontWeight: '100',
                  fontSize: '8',
                  formatter: text => {
                    if (text.name === '崇州市') {
                      return '                         崇州市'
                    }
                    //崇州市坐标有问题,通过格式化加空行,把文字向右推解决,感觉是json里面的文字坐标不太准
                  }
                },
                emphasis: {
                  show: true
                }
              },
              itemStyle: {
                normal: {
                  areaColor: '#999',
                  borderColor: '#0a003e',
                  borderWidth: 0.4,
                  shadowColor: 'rgba(19,57,108,0.45)'
                },
                emphasis: {
                  areaColor: '#05C5FF'
                }
              },
              data: that.regionScore.scores
            }
          ]
        })
      })
      if (init) {//init代表着重置地图,初始化的时候使用,切换项目不会重新执行,为什么呢?
        that.chartMap.on('click', function(params) { //因为这里面是给地图设置了监听事件,假设你第二次渲染再执行一次这个对map的监听,你的所有点击事件会触发两遍
          // 点击地图事件,其他3部分数据联动
          if (that.regionCode == that.regionScore.scores[params.dataIndex].regionCode) {
            that.regionCode = that.regionScore.regionCode
            that.isActiveCity = null
            that.isActiveDepart = null
            that.title = ''
          } else {
            that.regionCode = that.regionScore.scores[params.dataIndex].regionCode
            that.isActiveCity = that.regionScore.scores[params.dataIndex].regionCode
            that.isActiveDepart = null
            let item = that.regionScore.scores.find(
              v => v.regionCode == that.regionScore.scores[params.dataIndex].regionCode
            )
            if (item) {
              that.title = item.name
            } else {
              that.title = ''
            }
          }
          that.componentIndex = null
          that.dataIndex = null
          that.getRegionIndicatorScore()
          that.getDepartScore()
        })
      }
    },
}
复制代码

看完带有注释的代码应该就很清晰明了,然后再加几个小tip:

首先,地图json数据,可以在datav.aliyun.com/tools/atlas…中获取到,这里也可以写一个接口用来调用获取不同区域的json,当然我只用到了成都,所以把成都本地存储了一份。

第二点,地图最后的data,data在setoption到地图期间我对对象处理了一下,先看一格处理完的对象示例:

	{
		itemStyle: {areaColor: "rgba(24,290.4191616766467, 255,1)"} //单独定义每个地区区块的颜色值
		name: "青羊区" //地图比较特殊,其而地图通过name的中文地区名来判断渲染
		rank: 1
		regionCode: "510104"
		score: 97
		scores: null
		selected: false //这个传入为true会让当前地区默认选中状态,用来做地图下面的城市排名点击选中,地图也切换到对应地区选中状态
	}
复制代码

所以在设置selected的时候做了判断 if (that.isActiveCity == e.regionCode),isActiveCity 就是我选中的下方的城市排名里的城市code。

但是这个问题是当你每点击一次下方的城市排名,要想做到上方的地图也选中对应城市就只能刷新一遍地图,地图会闪烁(求教别的更完美的方法……)

然后,因为地图通过name的中文地区名来判断渲染,也就要求后台传过来的地区名字一定要和你的地区中文名统一,我还真碰上了后台传过来一个地区,我地图无法渲染进去数据,后来一查这个地区早就改名了,后台专门改库……

哦,对,还有放大缩小是roam,这个是配置项,很简单自己配。

柱状图滚动加点击

在这里插入图片描述
这个柱状图我就上一下滚动的配置项和点击事件吧:

{
	that.chartBar.setOption({
        backgroundColor: 'rgba(61,93,255,0)',
        legend: {
          bottom: 20,
          right: 20,
          textStyle: {
            color: '#fff'
          },
          itemHeight: 10,
          itemWidth: 10,
          selectedMode: true, //图例选择的模式,控制是否可以通过点击图例改变系列的显示状态。(打开样式有bug)
          data: ['政府填答', '企业填答', '系统接入'] //图例数组,每条数据的3种类新
        },
        grid: {
       		……
       		//省略
        },
        tooltip: {
          show: true,
			……
			//省略一些,跟上一个差不多
          formatter: function(params) {
            let res = `<div style="padding: 5px">
															<span>${params.name} -- ${params.seriesName}</span><br/>
															<span style="font-size: 10px;line-height: 30px">指标得分 </span><span style="color: #00FFED;font-size: 18px;line-height: 30px">${params.value}</span>
												 </div>`
            return res
          }
        },
        yAxis: {
         	……
         	//省略
        },
        xAxis: [
          {
            ……
            //省略
            axisLabel: {
              inside: false,
              interval: '0',
              showMinLabel: 'false',
              textStyle: {
                color: function(value, index) {
                //判断type,来设置字体选中颜色
                  if (that.cityData[0].type) {
                    return that.cityData[index].type == 1 ? 'rgb(255, 39, 124)' : 'rgb(0, 193, 170)'
                  } else {
                    return index == that.dataIndex ? '#00fff0' : 'rgb(224,224,224)'
                  }
                },
                fontWeight: that.cityData[0].type ? 'bold' : 'normal',
                fontSize: '12'
              },
              formatter: function(value) {
                //解决x轴单位名称太长,每4个字换一下行
                if (value.length > 4) {
                  return value.substring(0, 4) + '\n' + value.substring(4, value.length)
                } else {
                  return value
                }
              }
            },
            data: that.cityData.map(i => i.indicatorName) //x轴背景数组
          },
          {
            type: 'category',
            ……
            //省略
            data: that.cityData.map(i => i.indicatorName) //x轴显示数组
          }
        ],
        dataZoom:
          that.cityData.length > 9 //数据量多余9条设置滚动
            ? {
                show: true,
                realtime: true,
                fillerColor: 'rgba(6, 123, 201)',
                backgroundColor: '#020254',
                borderColor: 'rgba(197,197,197,0.3)',
                left: 150,
                bottom: 30,
                showDetail: false,
                height: 8,
                width: '60%',
                start: 0,
                end: 60
              }
            : {
                show: false
              },
        series: [
          {
            name: '政府填答',
            type: 'bar',
            ……
            //省略
            data: that.cityData.map(i => i.governmentScore)
          },
          {
            name: '企业填答',
            type: 'bar',
             ……
            //省略
            data: that.cityData.map(i => i.businessScore)
          },
          {
            name: '系统接入',
            type: 'bar',
             ……
            //省略
            data: that.cityData.map(i => i.otherScore)
          }
        ]
      })
}
复制代码

这个重点就没有太多,代码几乎都看清楚了,设置滚动,和点击变色。 然后点击时事件:

if (init) {
        that.chartBar.on('click', function(params) {
          if (that.isActiveDepart) {
            // 选中部门的时候不能点击指标
          } else {
            if (that.componentIndex == params.componentIndex && that.dataIndex == params.dataIndex) {
              that.componentIndex = null
              that.dataIndex = null
              return
            } else {
              that.componentIndex = params.componentIndex //选中的数据分类 0政府/1企业/2外部
              that.dataIndex = params.dataIndex //选中的x轴指标选项index
              that.indicatorTitle = params.name//这是标题名字
              that.getDepartScore(that.cityData[params.dataIndex].indicatorId)
              that.getIndicatorRank(that.cityData[params.dataIndex].indicatorId)
            }
            that.initBarCharts()
            myChart.off('click')
          }
        })
}
复制代码

没有太多花哨,主要通过缩放dataZoom吧宽度固定一定比例,去掉detail来达到柱状图滚动效果。代码很详细了~

进度条

进度条不是用的echarts是Ant Design进度条组件,小技巧: 通过判断值来切换进度条颜色达到不同排名不同颜色

<a-progress
    v-if="item.score!==null"
     :strokeColor="(item.score < 60 ? '#FF5543' : '') || (item.score > 80 ? '#00C277' : '')"
     :strokeWidth="4"
     status="active"
     :percent="item.score"
     :format="percent => `${percent}`"
/>
复制代码

平行坐标系

在这里插入图片描述
这个图算基本图形里面比较异类的,数据结构上也很异类:

    initParallelCharts() {
      const that = this
      var data = []
      let dataItem = that.parallelData.find(v => {
        let objLength = Object.keys(v.data)
        if (objLength.length > 0) {
          return v
        }
      }) //对象标准,用来补充data为空的情况
      if (dataItem == undefined) {
        that.noData = true
        return
      } else {
        that.noData = false
      }
      //没有数据不渲染
      
      let lineStyle = {
          width: 2,
          opacity: 1
      } //线条样式配置
      
      var series = [] //series 配置项
      for (let i of that.parallelData) {
        let arr = [[]]
        let seriesObj = {}
        let hasData = JSON.stringify(i.data) != '{}'
        // 该区域存在数据,不存在数据后台返回空对象,前台默认添加对象属性,值为0
        if (hasData) {
          for (let j in i.data) {
            arr[0].push(i.data[j])
          }
        } else {
          for (let j in dataItem.data) {
            arr[0].push(0)
          }
        }
        seriesObj = { name: i.region.name, type: 'parallel', lineStyle, data: arr }
        series.push(seriesObj)
        data.push(arr)
      }

      var schema = []
      let num = 0
      for (let i in dataItem.data) {
        let obj = { name: i, text: i, index: num }
        num += 1
        schema.push(obj)
      }


      let parallelAxis = [] //parallelAxis配置项
      for (let i = 0; i < schema.length; i++) {
        parallelAxis.push({
          dim: i,
          name: schema[i].text
        })
      }

      let legendName = []
      for (let i of that.parallelData) {
        legendName.push(i.region.name)
      }
      that.echartParallel = echarts.init(document.getElementById('echartParallel'))
      that.echartParallel.setOption({
        legend: {
          bottom: 0,
          data: legendName,
          itemGap: 15,
          itemWidth: 5,
          itemHeight: 5,
          textStyle: {
            color: '#F0F2F5',
            fontSize: 13
          }
        },
        parallelAxis: parallelAxis,

        parallel:……,//省略
        series
      })
    },
复制代码

看一下处理完的数据示例:

legendName:["金牛区","武侯区","青羊区","锦江区"],
parallelAxis:[{dim: 0, name: "成本"},{dim: 1, name: "时间"},{dim: 2, name: "环节"}],
series:[{
	data: [[89.2, 70, 70]]//这个地方要是二维数组,可以存在多数值线
	lineStyle: {width: 2, opacity: 1}
	name: "青羊区"
	type: "parallel"
} ………]
复制代码

这个平行坐标系主要特殊在他是用数组对应的index来取值渲染,legendName存放纯字符串,parallelAxis存放对应的Y轴坐标单位名称,series的数组每个对象对应一个legend分类。

假设series的data里面存放一个数组,那就是一个legend对应一条线,data里面存放多个数组就是一个legend对应多条数据,而data里面的数组存放的是y轴的值按parallelAxis的index对应。其实看看也就懂了~

G2

G2 是蚂蚁金服数据可视化解决方案 AntV 的一个子产品,是一套数据驱动的、高交互的可视化图形语法。

看起来更好看一些,用法类似,但是概念有差距,社区略鸡肋,具体可以自己了解一下。 然后主要分享一下之前记录的几个G2问题:

  1. legend图例使用滚动条;
  2. 南丁格尔图添加中心文字以及legend百分比;
  3. G2封装组件重载问题;

legend图例使用滚动条

先看效果:

在这里插入图片描述

this.chart.legend({
          // marker: 'square',
          position: 'right-center',
          // textStyle: { fill: '#a3a9c1' },
          // offsetX: 20,
          itemFormatter(val) {
            let labe = data.find(v => v.content == val).percent
            return `${val} ${parseInt(labe / sum * 100)}%` // val 为每个图例项的文本值
            }else{
          },
          useHtml: true,//使用Html绘制图例
          flipPage: true,//图例超出容器是否滚动
          containerTpl: '<div class="g2-legend" style="position:absolute;top:20px;right:60px;width:auto;">'
            + '<h4 class="g2-legend-title"></h4>'
            + '<ul class="g2-legend-list" style="list-style-type:none;margin:0;padding:0;"></ul>'
            + '</div>',//图例容器
          itemTpl: '<li class="g2-legend-list-item item-{index} {checked}" style="margin-right: 24px" data-color="{originColor}" data-value="{originValue}">' +
            '<i class="g2-legend-marker" style="background-color:{color};"></i>' +
            '<span class="g2-legend-text">{value}</span></li>'//图例
})

复制代码

南丁格尔图添加中心文字以及legend百分比

先看效果:

在这里插入图片描述

  • 中心文字设置
this.chart.guide().html({
          position: ['50%', '50%'],
          html: `<div style="color:#fff;font-size: 25px;text-align: center;width: 10em;">${total}<br><div style="color:#fff;font-size:14px;text-align: center">总得分</div></div>`,
          alignX: 'middle',
          alignY: 'middle'
})

复制代码
  • 百分比设置
//通过legend的itemFormatter参数进行格式化
this.chart.legend({
          marker: 'square',
          position: 'right-center',
          textStyle: { fill: '#a3a9c1', fontSize: '12' },
          itemFormatter(val) {
            let labe = data.find(v => v.title == val).population
            return `${val}
 ${parseInt(labe / sum * 100)}%` // val 为每个图例项的文本值
          }
})

复制代码

chart.guide().html(cfg)

辅助 html。

chart.guide().html({
  position: { object } | { function } | { array }, // html 的中心位置
  htmlContent: { string }, // html 代码
  alignX: { string }, // html 水平方向的布局,可取值为 'left','middle','right'
  alignY: { string }, // html 垂直方向的布局,可取值为 'top','middle','bottom'
  offsetX: { number },
  offsetY: { number },
  zIndex: { number },
});
复制代码

G2封装组件重载问题

G2 更新数据的方式主要有三种:

  • 仅仅是更新图表的数据
  • 清理所有,重新绘制
  • 使用 DataView 时的更新

如果需要马上更新图表,使用 chart.changeData(data) 即可:

chart.changeData(newData);
复制代码

view 也支持 view.changeData(data),如果仅仅是更新数据,而不需要马上更新图表,可以调用 chart.source(data),需要更新图表时调用 chart.repaint()

chart.source(newData);

chart.guide().clear();// 清理guide
chart.repaint();
复制代码

清理图形语法: 更新数据时还可以清除图表上的所有元素,重新定义图形语法,重新绘制

chart.line().position('x*y');

chart.render();

chart.clear(); // 清理所有
chart.source(newData); // 重新加载数据
chart.interval().position('x*y').color('z');
chart.render();

复制代码

关键点在于:

  1. 不要将chart实例化在初始化方法里
  2. 将这个实例保存下来
  3. 重载方法不要去new Chart
  4. 直接执行chart.changeData()

本人写文章一般都是分享自己学习的探索路程,可能对更多刚入门前端或者正从入门到进阶前端开发者更有用些,欢迎点赞鼓励和评论指正:v::v:

觉得有用求赞 哈哈哈~~




原文地址:访问原文地址
快照地址: 访问文章快照