繁琐实用系列(一):vue实现github活跃图

under 默认分类  tag     Published on November 3rd , 2021 at 04:35 pm

根据一些实用、不难但是繁琐的功能,推出一个这个系列,方便大家以后直接拿来就用。

缘由

最新有老师看见其他网站有仿github活跃图的效果,让我安排一下。

去github仔细看了看,以一个想法是看看echarts有没有类似的,找了一会发现有个类似的,但是修改成本较大,果断造轮子。

效果图

请输入图片描述

依赖

使用node.js的package管理器安装iview

npm install iview --save

在webpack项目的main.js中加入

import iView from 'iview';                    //引入iview
import 'iview/dist/styles/iview.css';        //引入iview的css样式文件,后面如果需要自己配置也可以新建文件进行修改
Vue.use(iView);                                //use使用这个插件

思路

1.先排除坐标说明,绘制最里面的每个小格子。

第一个格子的时间:上一年今天如果不是周一,那么推到下一个周一。

最后一个格子的时间:今天。

格子的数量:最后一个格子时间 - 第一个格子时间 。

最后一个列的个数:今天的星期数 或 格子总数量除以7取余。

格子的颜色:格子是用for循环变量的数组数据,设置每个格子data时候,带上提交次数属性,然后写个根据数量获取不同颜色的方法。

鼠标移入的内容:组件采用的iview的tooltip。根据项目集成的ui库可更改,或者手写一个也不难。

2.绘制x,y轴坐标值。

y坐标代表星期数,比较简单,直接添加格子容器节点的上面就行。

x坐标代表月份, 并非每列都有月份,只会在当前月份的第一列显示月份。

月份第一个列判断依据:每个格子我都赋予了具体的日期,判断前一列是否有不相同月份的日期,如果有不同的日期设置一个变量为true,如果没有重新设置成false,然后根据这个变量来决定本列是不是月份第一列。

3.绘制legend。

位置使用flex布局就行,不做过多解释。

左侧的slider用的iview。

代码

<template>
  <div class="submission-chart">
    <div class="calendar">
      <div class="weeks">
        <div class="week">周二</div>
        <div class="week">周四</div>
        <div class="week">周六</div>
      </div>
      <div class="column" v-for="(columnData, columnIndex) in dateData" :key="columnIndex">
        <div class="title">{{columnData.title}}</div>
        <div 
          class="date-wrapper" 
          v-for="(dateData, dateIndex) in columnData.data" 
          :key="dateIndex" 
          :style="`background:${getColor(dateData.number)};`" 
        >
        <Tooltip placement="top" :delay="300" :content="`${dateData.date}:${dateData.number}次通过`">
          <div class="date"></div>
        </Tooltip>
        </div>
      </div>
    </div>
    <div class="operation">
      <div class="slider">
        <div class="slider-desc">0</div>
        <div style="width:120px;">
         <Slider :value="sliderValue" :max="12" range :tip-format="sliderFormat" @on-change="sliderChange"></Slider>
        </div>
        <div class="slider-desc">12+</div>
      </div>
      <div class="legend">
        <div class="level-desc">少</div>
        <div class="level level-1"></div>
        <div class="level level-2"></div>
        <div class="level level-3"></div>
        <div class="level level-4"></div>
        <div class="level level-5"></div>
        <div class="level-desc">多</div>
      </div>
    </div>
  </div>
</template>
 
<script>
  import moment from 'moment'
  export default {
    name: 'submission-chart',
    data () {
      return {
        dateData: [],
        submissionRecord: {},
        sliderValue: [0, 12]
      }
    },
    props: {
      profile: {
        default: {},
        type: Object
      }
    },
    mounted () {
      this.formatProblemData()
      this.init()
    },
    methods: {
      init () {
        // 上一年信息
        let prevYear = moment().format('YYYY') - 1
        let prevTodayFormatStr = prevYear + '-' + moment().format('MM-DD')
        let prevToday = moment(prevTodayFormatStr).format('YYYY-MM-DD')
        // 上年今日的是星期几
        let prevTodayWeekNum = moment(prevToday).weekday() || 7
        // 初始日期(上年临近的星期一)
        let firstMondayDate = prevTodayWeekNum > 1 ? moment(prevToday).add(8 - prevTodayWeekNum, 'days').format('YYYY-MM-DD') : prevToday
        // 初始日期至今日的天数,包括今日
        let days = moment().diff(moment(firstMondayDate), 'days') + 1
        // 每周天数
        let columns = 7
        // 最大列数(周数)
        let lineNums = Math.ceil(days / columns)
        // 绘制图表的源数据
        let dateData = []
        let monthCN = ['一月', '二月', '三月', '四月', '五月', '六月', '七月', '八月', '九月', '十月', '十一月', '十二月']
        let startSliderNum = this.sliderValue[0]
        let endSliderNum = this.sliderValue[1]
        for (let i = 0; i < lineNums; i++) {
          // 最近一星期不一定满的
          let weekColumn = (i === lineNums - 1 ? days % columns ? days % columns : columns : columns)
          // 开始计算title(月份的图例)
          // 思路:第一列直接根据第一天的月份
          //      之后的嘛列数根据上一周的最后一天减去第一天的月份,如果大于1代表月份发生了改变,下一列的title显示最新的月份
          let theWeekStartMonth = moment(firstMondayDate).add(i * 7, 'days').format('M')
          let theWeekEndMonth = moment(firstMondayDate).add(i * 7 + weekColumn, 'days').format('M')
          let title = (i === 0) ? monthCN[theWeekStartMonth - 1] : ''
          let ifSwitchMonth = false
          if (theWeekEndMonth - theWeekStartMonth) {
            ifSwitchMonth = true
          }
          if (i && dateData[i - 1].ifSwitchMonth) {
            title = monthCN[theWeekEndMonth - 1]
          }
          // 图表源数据格式:columns:列数,title:列标题,ifSwitchMonth:月份是否发生改变,data:每格数据
          dateData.push({
            columns: weekColumn,
            title: title,
            ifSwitchMonth: ifSwitchMonth,
            data: []
          })
          for (let j = 0; j < dateData[i].columns; j++) {
            let date = moment(firstMondayDate).add(i * 7 + j, 'days').format('YYYY-MM-DD')
            let number = 0
            // 提交次数在slider范围内再进行次数赋值
            if ((this.submissionRecord[date] >= startSliderNum && this.submissionRecord[date] < endSliderNum) ||
             (this.submissionRecord[date] > 12 && endSliderNum === 12)) {
              number = this.submissionRecord[date]
            }
            // number:提交次数,date:提交日期
            dateData[i].data.push({
              number: number,
              date: date
            })
          }
        }
        this.dateData = dateData
      },
      formatProblemData () {
        let submissionRecord = {}
        // let OIProblems = this.profile.oi_problems_status.problems || {}
        // // 格式化profile中oi的提交记录数据,创建submissionRecord对象,将create_time作为key进行存储
        // Object.keys(OIProblems).forEach(problemID => {
        //   if (OIProblems[problemID]['status'] === 0) {
        //     let date = moment(OIProblems[problemID]['create_time']).format('YYYY-MM-DD')
        //     // 第一次出现提交次数设置1,之后每次出现提交次数+1
        //     submissionRecord[date.toString()] = submissionRecord[date] ? ((submissionRecord[date])) + 1 : 1
        //   }
        // })
 
        // 处理你的业务逻辑
        // submissionRecord 最后的格式应为 {'2020-01-01':10, '2020-01-02': 11}
        this.submissionRecord = submissionRecord
      },
      getColor (number) {
        // level color
        // 左闭右开
        let color = '#EBEDF0'
        if (number >= 12) {
          color = '#196127'
        } else if (number >= 8) {
          color = '#239A3B'
        } else if (number >= 4) {
          color = '#7BC96F'
        } else if (number >= 1) {
          color = '#C6E48B'
        } else {
          color = '#EBEDF0'
        }
        return color
      },
      sliderFormat (val) {
        return '提交次数: ' + val
      },
      sliderChange (val) {
        // 没有使用v-model绑定sliderValue而是采用回调的原因
        // 1.拖拽1px sliderValue都会引起组件重绘,此组件计算嵌套了2个for循环,导致页面出现卡顿slider不流畅的情况
        this.sliderValue = val
        this.init()
      }
    }
  }
</script>
<style lang="less" scoped>
  .submission-chart {
    width: 820px;
    height: 180px;
    background-color: #fff;
    margin: auto;
    margin-top: 20px;
    padding: 0px 0;
    font-size: 12px;
 
    .calendar {
      margin-left: 16px;
      margin-right: 16px;
      display: flex;
 
      .weeks {
        width: 30px;
        margin-right: 3px;
        margin-top: 22px;
 
        .week {
          margin-top: 13px;
          width: 30px;
          height: 14px;
        }
      }
 
      .column {
        width: 11px;
        margin-right: 3px;
 
        .title {
          width: 14px;
          height: 14px;
          margin-bottom: 8px;
          text-align: left;
          overflow: visible;
          white-space: nowrap;
        }
 
        .date-wrapper { 
          width: 11px;
          height: 11px;
          background: #EBEDF0;
          margin-bottom: 3px;
 
          .date {
            width: 11px;
            height: 11px;
 
            :hover {
              width: 13px;
              height: 13px;
            }
          }
        }
      }
    }
    
    .operation {
      display: flex;
      justify-content: space-between;
      align-items: center;
      margin-top: 10px;
 
      .slider {
        display: flex;
        justify-content: center;
        align-items: center;
        width: 200px;
 
        .slider-desc {
          width: 11px;
          margin: 0 8px;
        }
      }
 
      .legend {
        display: flex;
        justify-content:center;
        align-items: center;
 
        .level-desc {
          margin-right: 6px;
          margin-left: 3px;
        }
 
        .level {
          margin-right: 3px;
          width: 11px;
          height: 11px;
        }
 
        .level-1 {
          background: #EBEDF0;
        }
 
        .level-2 {
          background: #C6E48B;
        }
 
        .level-3 {
          background: #7BC96F;
        }
 
        .level-4 {
          background: #239A3B;
        }
 
        .level-5 {
          background: #196127;
        }
      }
    }
  }
</style>

使用

<submission-chart :profile="profile"/>

本文由simyng创作, 采用知识共享署名4.0 国际许可协议进行许可,转载前请务必署名
  文章最后更新时间为:November 3rd , 2021 at 08:35 am