【Vue+echarts】分周期统计在线时长热点图

262 阅读3分钟

“我正在参加「掘金·启航计划」”

最终效果如下;一开始准备自己使用div排版这种结构,后来发现实在太麻烦了,然后在echart官网,发现热点图,可以实现这种效果。每周使用了四个echart图,分为四个不同时间段。上面的点根据在线的时长越长,颜色越深。 image.png

vue中如何安装eachart 在这里我就不再做深层说明了,网上应该有很多博主都写过很详细的安装说明。

首先我们来看下我的数据结构,每一个data为一个周期。

{
  "code": 0,
  "data": {
    "y": [
      {
        "data": [
          { "week": 1, "date": "21:05", "value": 100 }, //week 周一  在线时长 100小时  
          { "week": 5, "value": 9, "date": "06:53" }  //week周五  在线时长9小时
        ]
      },
      {
        "data": [
          { "value": 22, "week": 7, "date": "04:16" },
          { "week": 3, "date": "17:35", "value": 92 },
          { "week": 6, "date": "14:26", "value": 11 },
          { "week": 2, "value": 68, "date": "21:20" },
          { "week": 5, "date": "01:28", "value": 83 }
        ]
      },
      {
        "data": [
          { "value": 38, "date": "18:57", "week": 4 },
          { "week": 4, "value": 35, "date": "11:02" }
        ]
      },
      { "data": [{ "week": 2, "date": "20:06", "value": 68 }] },
      {
        "data": [
          { "value": 74, "week": 5, "date": "11:20" },
          { "date": "01:32", "week": 5, "value": 13 },
          { "date": "19:54", "value": 39, "week": 5 },
          { "date": "17:23", "week": 4, "value": 73 },
          { "date": "14:36", "value": 75, "week": 5 }
        ]
      },
      {
        "data": [
          { "value": 15, "date": "07:36", "week": 5 },
          { "value": 80, "date": "06:55", "week": 1 },
          { "value": 93, "date": "07:51", "week": 2 },
          { "week": 6, "value": 79, "date": "23:31" }
        ]
      },
      { "data": [{ "week": 7, "value": 39, "date": "03:01" }] },
      {
        "data": [
          { "week": 4, "value": 50, "date": "19:49" },
          { "value": 12, "date": "07:21", "week": 2 },
          { "value": 10, "week": 2, "date": "06:38" },
          { "date": "00:50", "value": 47, "week": 7 },
          { "date": "21:39", "week": 4, "value": 12 }
        ]
      },
      {
        "data": [
          { "value": 40, "week": 2, "date": "08:28" },
          { "value": 42, "date": "09:06", "week": 7 }
        ]
      },
      {
        "data": [
          { "value": 96, "week": 7, "date": "05:43" },
          { "week": 1, "value": 50, "date": "15:14" },
          { "date": "22:55", "week": 1, "value": 16 },
          { "date": "11:54", "week": 4, "value": 93 }
        ]
      },
      {
        "data": [
          { "date": "08:17", "week": 1, "value": 26 },
          { "date": "17:43", "week": 7, "value": 3 },
          { "week": 6, "value": 22, "date": "07:58" },
          { "value": 32, "date": "19:35", "week": 6 },
          { "week": 3, "date": "01:36", "value": 7 }
        ]
      },
      {
        "data": [
          { "week": 6, "value": 19, "date": "20:51" },
          { "week": 2, "date": "09:24", "value": 79 },
          { "week": 3, "value": 66, "date": "09:05" },
          { "date": "00:37", "value": 13, "week": 6 },
          { "date": "06:43", "week": 4, "value": 33 }
        ]
      }
    ]
  }
}

timeEchart.vue 组件库

我这里可能封装的不太灵活,主要还是为了实现了这个效果。没有去做太多的灵活代码优化。大家如果有时间有更好的想法欢迎更新。

每一周期,需要有四个热点图,使用heatmap组件,没有做过多的封装,主要就是把echarts.apache.org/examples/zh… 这个封装了一个公用的库,方便四个重复使用。

<div class="time-box" v-if="time && time.length > 0">
    <a-row class="grid-demo" :wrap="false">
      <a-col flex="60px">
        <div class="time-label">00-05</div>
        <div class="time-label">06-11</div>
        <div class="time-label">12-17</div>
        <div class="time-label">18-23</div>
      </a-col>
      <a-col flex="auto" style="width:0">
        <a-row class="grid-demo" style="width:100%;overflow:auto;" :wrap="false">
          <a-col flex="86px" v-for="(item, index) in time" :key="index">
            <div class="heatmp-li">
              <div class="heatmap">
                 <heatmap :y="['00','01','02','03','04','05']"  :x="['1','2','3','4','5','6','7']"  :data= "time[index].heat"/>
              </div>
              <div class="heatmap">
                  <heatmap :y="['06','07','08','09','10','11']"  :x="['1','2','3','4','5','6','7']"  :data= "time[index].heat"/>
              </div>
              <div class="heatmap">
                <heatmap :y="['12','13','14','15','16','17']"  :x="['1','2','3','4','5','6','7']"  :data= "time[index].heat"/>
              </div>
              <div class="heatmap">
                 <heatmap :y="['18','19','20','21','22','23']"  :x="['1','2','3','4','5','6','7']"  :data= "time[index].heat"/>
              </div>
              <p>{{index + 1}}周</p>
            </div>
          </a-col>
        </a-row>
      </a-col>
    </a-row>
   
  </div>
  
  
<script lang="ts" setup>
import { ref, provide } from 'vue'
import  useLoading  from '@/hooks/loading'
import { onBeforeRouteLeave } from 'vue-router'
import { useAppStore } from '@/stores'
import Heatmap from '@/views/components/heatmap.vue'
import {getPianhaoTime} from '@/axios/api'
import { Notification } from '@arco-design/web-vue';

const {loading,setLoading} = useLoading(false)
const renderList = ref([])
const cacheStore = useAppStore()
 
const x = ref(['1周','2周','2周'])
const time = ref()
const api_getPianhaoTime = ()=>{
  setLoading(true)
  getPianhaoTime().then((res)=>{
    if(res.data.code == 0){
      time.value = res.data.data.y
      time.value.forEach((e,index) => {
        e.heat = []

        e.data.forEach((d,i)=>{
        
          if( Number(d.date.split(':')[0]) >= 0 && Number(d.date.split(':')[0]) <= 5){
          e.heat.push([d.date.split(':')[0],String(d.week),d.value])

          }else if( Number(d.date.split(':')[0]) >= 6 && Number(d.date.split(':')[0]) <= 11){
              e.heat.push([d.date.split(':')[0],String(d.week),d.value])


          }else if( Number(d.date.split(':')[0]) >= 12 && Number(d.date.split(':')[0]) <=17){
              e.heat.push([d.date.split(':')[0],String(d.week),d.value])


          }else if( Number(d.date.split(':')[0]) >= 18 && Number(d.date.split(':')[0]) <= 23){
              e.heat.push([d.date.split(':')[0],String(d.week),d.value])


          }
        })
      });

      
    }else{
      Notification.error(res.data.msg)
      
    }
    setLoading(false)
  }).catch((err)=>{
    setLoading(false)
    Notification.error(err)

  })
}

api_getPianhaoTime()
console.log(time)

 

</script>
 

heatmap.vue 组件

<template>
    <Chart   :option="chartOption"/>
</template>

<script setup lang="ts">
 import { ref, defineProps } from 'vue';
  import type { ToolTipFormatterParams } from '@/types/echarts';
  import useChartOption from '@/hooks/chart-option';
  const tooltipItemsHtmlString = (items: ToolTipFormatterParams[]) => {
    return items
      .map(
        (el) => `<div class="content-panel">
        <p>
          <span style="background-color: ${el.color}" class="tooltip-item-icon"></span>
          <span>${el.seriesName}</span>
        </p>
        <span class="tooltip-value">
        ${el.value}
        </span>
      </div>`
      )
      .join('');
  };
  const props = defineProps({
    data:{
      type:Array,
      default:()=>{
        return []
      }
    },
    y:{
      type:Array,
      default:()=>{
        return []
      }
    },
    x:{
      type:Array,
      default:()=>{
        return []
      }
    },
    color:{
      type:String,
      default:''
    },

    xname:{
      type:String,
      default:''
    },
    yname:{
      type:String,
      default:''
    },
    axisLabel:{
      type:String,
      default:''
    },
    type:{
      type:String,
      default:'bar'
    },
    series:{
      type:Object,
      default:()=>{
        return {}
      }
    },
    grid:{
      type:Object,
      default:()=>{ 
        return {
          left: '0',
          right: 0,
          top: '0',
          bottom: '0',
        }
      }
    }
  })

  const xAxis = ref<string[]>([]);
  const textChartsData = ref<number[]>([]);
// prettier-ignore
const data = props.data
    .map(function (item) {
    return [item[1], item[0], item[2] || '-'];
});
  const { chartOption } = useChartOption((isDark:any) => {
    return {
  //      tooltip: {
  //   position: 'top'
  // },
   grid:props.grid,
  xAxis: {
    type: 'category',
    data: props.x,
    axisLabel:{
      show:false
    },
     axisLine:{
      show:false
    },
    splitLine: {
      show: true,
       lineStyle:{
        color:isDark ? '#232324': '#fff',
        width:2
      }
    }
  },
  yAxis: {
    type: 'category',
    data: props.y,
    inverse:true,
    axisLabel:{
      show:false
    },
     axisLine:{
      show:false
    },
    splitLine: {
      show: true,
      lineStyle:{
        color:isDark ? '#232324': '#fff',
        width:2
      }
    }
  },
  visualMap: {
    show:false,
    min: 0,
    max: 100,
    calculable: false,
    orient: 'horizontal',
    inRange: {
      color: ['rgb(232, 243, 255)','rgb(0, 13, 77)'],
    },
    left: 'center',
    bottom: '15%'
  },
  series: [
    {
      name: 'Punch Card',
      type: 'heatmap',
      data: data,
      label: {
        show: false
      },
      emphasis: {
        itemStyle: {
          shadowBlur: 10,
          shadowColor: 'rgba(0, 0, 0, 0.5)'
        }
      }
    }
  ]
    };
  });
 

</script>

总结

最后我把我的代码已经上传到了gitcode.net/u013491704/… 欢迎大家一起讨论