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

news/2024/7/10 2:35:06 标签: git, 前端, vue

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

前言

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

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

布局结构

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

<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悬浮展示具体时间,需要的可以自定义修改展示内容和样式

<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)贡献图例,可自定义实现点击筛选等功能

 <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来获取。计算上年今日最接近的周一的时间,该时间为起始时间。

 // 上年今日
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、计算从开始时间到结束时间之间有多少个周,计算列数

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

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

 // 绘制图表的源数据
    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)根据活跃度或数据量,展示不同节点的背景颜色

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)监听数据的变化,动态响应,更新组件数据

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

使用示例

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

npm install @fcli/vue-calendar-map --save-dev 来安装

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

测试demo
<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最大值Number0

最后

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


http://www.niftyadmin.cn/n/5220866.html

相关文章

C++——vector插入与删除和数据存取

一. vector插入和删除 功能描述:对vector容器进行插入、删除操作 函数原型: push back(ele); //尾部插入元素ele pop_back(); //删除最后一个元素 insert(const iterator pos, el…

CentOS添加开机启动

1.编写项目启动脚本&#xff08;run.sh&#xff09; #!/bin/bash-切换到程序所在路径 cd /home/cavs_install/app/cavs-admin/target/ # 等待其他组件启动完毕后再启动本项目&#xff08;如果不需要等待&#xff0c;本步骤可省略&#xff09; sleep 300 # 实际启动命令 nohup …

【数据库】MongoDB 简介:一种文档数据库

MongoDB 简介&#xff1a;一种文档数据库 1.MongoDB 介绍2.MongoDB 数据存储方式3.MongoDB 概念与操作3.1 Create Operations3.2 Read Operations3.3 Update Operations3.4 Delete Operations 4.MongoDB 特点5.MongoDB 应用场景 1.MongoDB 介绍 MongoDB 是一个基于分布式文件存…

表单项样式1(输入框,选择框)

样式均写在全局文件下&#xff08;src/assets/style/home.scss&#xff09; 1、选择框 效果&#xff1a; 选择时&#xff1a; 代码&#xff1a; //选择框 .el-select{.el-input__inner{border-radius: 0px;font-size: 14px;color: #CBEEFF;background: rgba(23,111,172,0.12…

python:傅里叶分析,傅里叶变换 FFT

使用python进行傅里叶分析&#xff0c;傅里叶变换 FFT 的一些关键概念的引入&#xff1a; 1.1.离散傅里叶变换&#xff08;DFT&#xff09; 离散傅里叶变换(discrete Fourier transform) 傅里叶分析方法是信号分析的最基本方法&#xff0c;傅里叶变换是傅里叶分析的核心&…

matplotlib,DLL load failed: 找不到指定的模块

问题&#xff1a;import matplotlib mportError: DLL load failed: 找不到指定的模块 &#xff08;2023年11月28日&#xff09; 解决方法&#xff1a;具体是matplotlib版本不匹配&#xff0c;而且在线pip install numpy时因为在线下载numpy库中缺少DLL。 应该下载带有mkl的num…

第十七章 处理空字符串和 Null 值 - XMLIGNORENULL、XMLNIL 和 XMLUSEMPTYELEMENT 的详细信息

文章目录 第十七章 处理空字符串和 Null 值 - XMLIGNORENULL、XMLNIL 和 XMLUSEMPTYELEMENT 的详细信息XMLIGNORENULL、XMLNIL 和 XMLUSEMPTYELEMENT 的详细信息XMLIGNORENULLXMLNILXMLUSEEMPTYELEMENT 导入值 第十七章 处理空字符串和 Null 值 - XMLIGNORENULL、XMLNIL 和 XML…

【开源】基于JAVA的海南旅游景点推荐系统

项目编号&#xff1a; S 023 &#xff0c;文末获取源码。 \color{red}{项目编号&#xff1a;S023&#xff0c;文末获取源码。} 项目编号&#xff1a;S023&#xff0c;文末获取源码。 目录 一、摘要1.1 项目介绍1.2 项目录屏 二、功能模块2.1 用户端2.2 管理员端 三、系统展示四…