git日历坐标系? 手动实现github活跃/贡献图

git日历坐标系? 手动实现github活跃/贡献图

前言

在使用github或gitlab时,我们总能发现,我们一年内的活跃度能够通过一张图直观地展现出来,那么你是否好奇它是如何实现的,最近工作中也遇到这样类似的需求,刚开始打算使用echarts的日历坐标系,但demo测试下来结果不尽人意,除了配置麻烦,自定义自由度不够,以及对于业务需求支持度也不够,因此,痛定思痛,决定动手手写一个自由度更高的组件。而且对于后端数据结构要求很低,除了能展示近一年数据,还能自定义时间范围,有需要的小伙伴可以直接查看源码:gitee.com/fcli/vue-ca...

话不多说,先上实现效果图:

布局结构

本组件主要分为三个部分: (1)最左侧展示周一至周日部分,每周开始时间从周日或周一开始可自定义修改

ini 复制代码
<div class="weeks">
    <div class="week">周一</div>
    <div class="week">周二</div>
    <div class="week">周三</div>
    <div class="week">周四</div>
    <div class="week">周五</div>
    <div class="week">周六</div>
    <div class="week">周日</div>
</div>

(2)每一列表示一周的始末时间,使用el-tooltip悬浮展示具体时间,需要的可以自定义修改展示内容和样式

ini 复制代码
<div class="column" v-for="(columnData, columnIndex) in allDateData" :key="columnIndex">
    <div class="my-title">{{ columnData.title }}</div>
    <div class="date-wrapper" v-for="(dateItem, dateIndex) in columnData.data" :key="dateIndex"
        :style="{ background: getColor(dateItem.number).color }"
        :class="{ hiddenDate: (columnIndex == 0 && dateIndex < prevTodayWeek - 1), active: getColor(dateItem.number).level == hoverLevel }">
        <el-tooltip class="box-item" effect="dark" :content="dateItem.date" placement="top">
            <div class="date"></div>
        </el-tooltip>
    </div>
</div>

(3)贡献图例,可自定义实现点击筛选等功能

ini 复制代码
 <div class="operation">
    <div class="legend">
        <div class="level-desc">少</div>
        <div class="level level-1" @mouseover="hoverLevel = i" @mouseout="hoverLevel = 0" v-for="i in 5" :key="i"
            :class="['level-' + i]"></div>
        <div class="level-desc">多</div>
    </div>
</div>

具体实现

1、首先计算和获取上一年的时间,以及是星期几,我这里采用dayjs来获取。计算上年今日最接近的周一的时间,该时间为起始时间。

scss 复制代码
 // 上年今日
let prevToday = dayjs().subtract(1, 'year').format('YYYY-MM-DD')
// 上年今日的是星期几,dayjs获取的为0-6,0是星期日
let prevTodayWeekNum = dayjs(prevToday).format('d') || 7;
prevTodayWeek.value = prevTodayWeekNum;
// 初始日期(上年临近的星期一)
let firstMondayDate =
prevTodayWeekNum > 1
    ? dayjs().subtract(1, 'year')
        .subtract(prevTodayWeekNum - 1, 'days')
        .format('YYYY-MM-DD')
    : prevToday;

2、计算从开始时间到结束时间之间有多少个周,计算列数

ini 复制代码
 // 初始日期至今日的天数,包括今日
let days = dayjs().diff(dayjs(firstMondayDate), 'days') + 1;
// 每周天数
let columns = 7;
// 最大列数(周数)
let lineNums = Math.ceil(days / columns);

3、绘制图表数据,判断月份需要展示的位置,可往dateData中自定义添加需要的数据字段。第一列直接根据第一天的月份,之后的每列数根据上一周的最后一天减去第一天的月份,如果大于1代表月份发生了改变,下一列的title显示最新的月份。

ini 复制代码
 // 绘制图表的源数据
    let dateData: any = [];
    let monthCN = ['一月', '二月', '三月', '四月', '五月', '六月', '七月', '八月', '九月', '十月', '十一月', '十二月'];
    for (let i = 0; i < lineNums; i++) {
        // 最近一星期不一定满的
        let weekColumn = i === lineNums - 1 ? (days % columns ? days % columns : columns) : columns;
        // 开始计算title(月份的图例)
        let theWeekStartMonth = dayjs(firstMondayDate)
            .add(i * 7, 'days')
            .format('M');
        let day = dayjs(firstMondayDate)
            .add(i * 7, 'days')
            .format('DD');
        //防止开始时月份挤在一起
        if (i == 0 && 30 - day < 15) {
            theWeekStartMonth = theWeekStartMonth + 1;
        }

        let theWeekEndMonth = dayjs(firstMondayDate)
            .add(i * 7 + weekColumn, 'days')
            .format('M');
        let title = i === 0 ? monthCN[theWeekStartMonth - 1] : '';
        let isSwitchMonth = false;
        if (theWeekEndMonth - theWeekStartMonth) {
            isSwitchMonth = true;
        }
        if (i && dateData[i - 1].isSwitchMonth) {
            title = monthCN[theWeekEndMonth - 1];
        }
        // 图表源数据格式:columns:列数,title:列标题,isSwitchMonth:月份是否发生改变,data:每格数据
        dateData.push({
            columns: weekColumn,
            title: title,
            isSwitchMonth: isSwitchMonth,
            data: []
        });
        for (let j = 0; j < dateData[i].columns; j++) {
            let date = dayjs(firstMondayDate)
                .add(i * 7 + j, 'days')
                .format('YYYY-MM-DD');
            // 提交次数在slider范围内再进行次数赋值
            let number = submissionRecord.value[date];
            // number:提交次数,date:提交日期
            dateData[i].data.push({
                number: number,
                date: date
            });
        }
    }

(4)根据活跃度或数据量,展示不同节点的背景颜色

ini 复制代码
const getColor = (number: number) => {
    let num = number / props.maxData;
    let level = 1;
    if (num == 0) {
        num = 0.1;
        level = 1
    } else if (num > 0 && num < 0.3) {
        num = 0.3;
        level = 2
    } else if (num > 0.3 && num < 0.6) {
        num = 0.6;
        level = 3
    } else if (num < 0.9) {
        num = 0.8;
        level = 4
    } else {
        num = 1;
        level = 5
    }
    if (props.maxData == 0) {
        num = 0.1;
        level = 1
    }
    return { color: 'rgba(55,126,259,' + num + ')', level };
}

(5)监听数据的变化,动态响应,更新组件数据

javascript 复制代码
    watch(() => props.timeData, (newValue) => {
        submissionRecord.value = newValue;
        init();
    }, { immediate: true, deep: true })

使用示例

已经打包上传npm,有需要的小伙伴可以直接安装使用,如果需要自定义修改组件内容,可访问git源码自行修改。git地址:gitee.com/fcli/vue-ca...

bash 复制代码
npm install @fcli/vue-calendar-map --save-dev 来安装

在项目中使用
import VueCalendarMap from '@fcli/vue-calendar-map';
const app=createApp(App)
app.use(VueCalendarMap);

测试demo

ini 复制代码
<template>
  <div class="content">
    <vue-calendar-map :timeData="timeData" :maxData="maxData"></vue-calendar-map>
  </div>
</template>

<script setup lang="ts">
import dayjs from 'dayjs';
import VueCalendarMap from './plugin/index.vue';
import {ref} from 'vue';
components:{
  VueCalendarMap
}
const maxData=ref(190);
const timeData=ref<any>({})
let tempDate:any={}
for(let i=0;i<366;i++){
  let date=dayjs().subtract(1, 'year').add(i, 'days').format('YYYY-MM-DD')
  tempDate[date]=(Math.random() * 200).toFixed(0);
}
timeData.value=tempDate;
</script>

参数说明

属性 属性名称 类型 可选值
timeData 日期和数量对应对象 Object {}
maxData 最大值 Number 0

最后

本人长期分享一些实用干货和学习总结之类的开源技术文章,欢迎有需要的小伙伴点赞收藏和点个关注🙏。

相关推荐
腾讯TNTWeb前端团队4 小时前
helux v5 发布了,像pinia一样优雅地管理你的react状态吧
前端·javascript·react.js
范文杰8 小时前
AI 时代如何更高效开发前端组件?21st.dev 给了一种答案
前端·ai编程
拉不动的猪8 小时前
刷刷题50(常见的js数据通信与渲染问题)
前端·javascript·面试
拉不动的猪8 小时前
JS多线程Webworks中的几种实战场景演示
前端·javascript·面试
FreeCultureBoy9 小时前
macOS 命令行 原生挂载 webdav 方法
前端
uhakadotcom9 小时前
快速开始使用 n8n
后端·面试·github
uhakadotcom9 小时前
Astro 框架:快速构建内容驱动型网站的利器
前端·javascript·面试
uhakadotcom9 小时前
了解Nest.js和Next.js:如何选择合适的框架
前端·javascript·面试
uhakadotcom9 小时前
React与Next.js:基础知识及应用场景
前端·面试·github
uhakadotcom9 小时前
Remix 框架:性能与易用性的完美结合
前端·javascript·面试