日历热力图,月度数据可视化图表(日活跃图、格子图)vue组件

日历热力图,月度数据可视化图表,vue组件

先看效果👇
在这里插入图片描述
在这里插入图片描述
在线体验https://www.guetzjb.cn/calanderViewGraph/

日历图简单划分为近一年时间,开始时间是 上一年的今天,例如2024/01/01 —— 2025/01/01,跨度刚好一年,依次从上到下看一排真好七个小方格,分别对应着 周日、周一、周二、周三、周四、周五、周六。

PC端、移动端支持良好

方法颜色支持自定义,可根据数据大小规定颜色深度。

实现方式简单易懂~用到element plus和moment,用前请安装到项目

yarn add element-plus moment
# or
npm i element-plus moment

show code

calanderViewGraph.vue

<script setup lang='ts'>
import moment from 'moment'
import 'moment/dist/locale/zh-cn'

moment.locale('zh-cn')

const props = withDefaults(defineProps<{
  size: number
}>(), {
  size: 10
})

const ansRes: any = []
const date = new Date()
const today = {
  year: date.getFullYear(),
  month: date.getMonth(),
  day: date.getDate()
}
let endTime = moment(date, 'YYYY/MM/DD').format('L')
let startTime = moment((today.year - 1) + '/' + (today.month + 1) + '/' + today.day, 'YYYY/MM/DD').format('L')
const visibleList = ref<Record<string, boolean>>({})
let days = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]

function isLeapYear(year: number) {
  return (year % 4 === 0 && year % 100 !== 0) || year % 400 === 0;
}

function init() {
  //上一年开始遍历
  //下标从0开始
  for (let month = today.month; month <= 12 + today.month; month++) {
    let year = Math.floor(month / 12); //0->前一年 1->今年
    let m = month % 12;
    let remainDay
    let week
    if (month != today.month && month != 12 + today.month) {
      remainDay = days[m] + (isLeapYear(today.year - (year == 0 ? 1 : 0)) && m == 1 ? 1 : 0) // 闰年补1
      week = new Date(today.year - (year == 0 ? 1 : 0) + '/' + (m + 1) + '/1').getDay()
    } else {
      if (month == today.month) {
        remainDay = days[m] - today.day + 1 + (isLeapYear(today.year - (year == 0 ? 1 : 0)) && m == 1 ? 1 : 0) // 例如 1/1 ~ 1/2 日期相差2天,相减+1
        week = new Date(today.year - (year == 0 ? 1 : 0) + '/' + (m + 1) + '/' + today.day).getDay()
      } else {
        remainDay = today.day + (isLeapYear(today.year - (year == 0 ? 1 : 0)) && m == 1 ? 1 : 0)
        week = new Date(today.year - (year == 0 ? 1 : 0) + '/' + (m + 1) + '/1').getDay()
      }
    }

    ansRes.push({
      year,
      month: m,
      remainDay,
      week,
      rev: month == today.month // 第一个日期必须反转 例如2024/1/20 剩余11天,应该显示2024/1/20 ~ 2024/1/31
    })
  }
}

const viewsList = ref<any>({
  totalCnt: 0,
  views: {
    // '2025/01/20': 10 //格式 —— (key:日期,value:数量)
  },
  colors: {
    // '2025/01/20': '#fff000' //格式 —— (key:日期,value:色值)
  }
})

// 自定义颜色
const colorArr = ["#E0F8E0", "#C6E0C6", "#AEE6AE", "#96EA96", "#7EF07E", "#66F566", "#4EF94E", "#36FD36", "#1EFF1E", "#00CC00"]
function getColorFunc(value: number): string {
  let i = 0
  while (value > 10 && i < colorArr.length) {
    value -= 10
    i += 1
  }
  return colorArr[i]
}

function generateData() {
  let startTimeStamp = new Date(startTime).getTime()
  let endTimeStamp = new Date(endTime).getTime()
  // 随机生成365个数据
  for (let i = 0; i < 365; i++) {
    let randomTimeStamp: number = (endTimeStamp - Math.random() * (endTimeStamp - startTimeStamp)) //  随机减一个随机时间戳,相当于在今天的时间戳基础上减
    let dateStr: string = moment(randomTimeStamp).format('YYYY/MM/DD')
    if (!viewsList.value.views[dateStr]) {
      viewsList.value.views[dateStr] = 0
    }
    let curCnt = Math.random() * 100 | 0 // |0去除小数点 
    viewsList.value.views[dateStr] += curCnt
    viewsList.value.totalCnt += curCnt
    viewsList.value.colors[dateStr] = getColorFunc(viewsList.value.views[dateStr])
  }
}

const formatDate = (year: number, month: number, day: number) => {
  return moment(today.year + (year == 0 ? -1 : 0) + '/' + (month + 1) + '/' + (day + 1), 'YYYY/MM/DD').format('L')
}

init()
onMounted(() => {
  generateData()
})

</script>

<template>
  <div class="calander_box">
    <p class="view_title">
      近一年共浏览
      <span style="font-weight: bold;padding: 0 5px;">{{ viewsList?.totalCnt != null ?
        viewsList?.totalCnt : '...' }}
      </span></p>
    <el-scrollbar>
      <div class="mobile_wrap">
        <div class="calander_view_g_wrap">
          <div class="views_wrap" v-for="month in ansRes" v-show="month.remainDay > 0">
            <!-- 一排 7个 加边距(20px) -->
            <div class="views_month" :style="{ height: props.size * 7 + 20 + 'px' }">
              <!-- 伪装的格子 -->
              <div class="views_day" :style="{
                width: props.size + 'px',
                height: props.size + 'px'
              }" v-for="_offset in month.week" style="background: transparent;cursor: auto;">
              </div>
              <!-- 真正显示的格子 -->
              <div v-for="(_day, index) in month.remainDay">
                <el-tooltip effect="dark" :visible="visibleList[formatDate(month.year, month.month, index)]"
                  :content="`${formatDate(month.year, month.month, !month.rev ? index : (days[month.month] - (month.remainDay - index)))}  ${viewsList?.views[formatDate(month.year, month.month, index)] || 0}次浏览`"
                  placement="top-start">
                  <div class="views_day" @mouseenter="visibleList[formatDate(month.year, month.month, index)] = true"
                    @mouseleave="visibleList[formatDate(month.year, month.month, index)] = false" :style="{
                      background: viewsList?.colors[formatDate(month.year, month.month, index)],
                      width: props.size + 'px',
                      height: props.size + 'px'
                    }">
                  </div>
                </el-tooltip>
              </div>
            </div>
            <p style="color: #a2a2a2;">{{ month.month + 1 + '月' }}</p>
          </div>
        </div>
      </div>
    </el-scrollbar>
  </div>
</template>

<style lang='scss' scoped>
.calander_box {
  width: 100%;
  padding: 20px;

  .view_title {
    font-size: 18px;
    padding-left: 10px;
    margin-bottom: 20px;
  }

  .mobile_wrap {
    width: fit-content;

    @media screen and (max-width:480px) {
      width: 800px;
      white-space: nowrap;
      overflow-anchor: auto;
    }

    .calander_view_g_wrap {
      display: flex;
      justify-content: space-between;

      .views_wrap {
        width: 100%;
        margin-right: 8px;
        margin-left: 8px;

        p {
          text-align: center;
          margin-top: 10px;
        }

        .views_month {
          width: calc(100% / 12);
          height: 90px;
          display: flex;
          flex-direction: column;
          flex-wrap: wrap;

          @media screen and (max-width:1200px) {
            height: 50px;
          }

          .views_day {
            margin: 0 2px 2px 0;
            border-radius: 2px;
            background: #F7F7F8;
            cursor: pointer;

            @media screen and (max-width:1200px) {
              width: 5px;
              height: 5px;
            }
          }
        }
      }
    }
  }

}
</style>

使用方法:

传入size表示方格的宽度和高度,

如果不想要方形,可以自己改样式实现(注意调整外部div高度,必须一排七个,否则周(日、一……六)的顺序会错乱)

<calanderViewGraph :size="10"/>

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/957123.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【Linux】常见指令(三)

Linux常见指令 01.nano02.cat03.cp04.mv 我的Linux专栏&#xff1a;【Linux】 本节Linux指令讲解的基本框架如下&#xff1a; 大家可以根据自己的需求&#xff0c;自行进行跳转和学习&#xff01; 01.nano nano Linux 系统中一款简单易用的命令行文本编辑器&#xff0c;适合…

学习ASP.NET Core的身份认证(基于JwtBearer的身份认证6)

重新创建WebApi项目&#xff0c;安装Microsoft.AspNetCore.Authentication.JwtBearer包&#xff0c;将之前JwtBearer测试项目中的初始化函数&#xff0c;jwt配置类、token生成类全部挪到项目中。   重新编写login函数&#xff0c;之前测试Cookie和Session认证时用的函数适合m…

图解Git——分布式Git《Pro Git》

分布式工作流程 Centralized Workflow&#xff08;集中式工作流&#xff09; 所有开发者都与同一个中央仓库同步代码&#xff0c;每个人通过拉取、提交来合作。如果两个开发者同时修改了相同的文件&#xff0c;后一个开发者必须在推送之前合并其他人的更改。 Integration-Mana…

小白爬虫——selenium入门超详细教程

目录 一、selenium简介 二、环境安装 2.1、安装Selenium 2.2、浏览器驱动安装 三、基本操作 3.1、对页面进行操作 3.1.1、初始化webdriver 3.1.2、打开网页 3.1.3、页面操作 3.1.4、页面数据提取 3.1.5、关闭页面 ?3.1.6、综合小案例 3.2、对页面元素进行操作 3…

Flutter鸿蒙化中的Plugin

Flutter鸿蒙化中的Plugin 前言鸿蒙项目内PluginFlutter端实现鸿蒙端实现创建Plugin的插件类注册Plugin 开发纯Dart的package为现有插件项目添加ohos平台支持创建插件配置插件编写插件内容 参考资料 前言 大家知道Flutter和鸿蒙通信方式和Flutter和其他平台通信方式都是一样的&…

【Docker】搭建一个功能强大的自托管虚拟浏览器 - n.eko

前言 本教程基于群晖的NAS设备DS423的docker功能进行搭建&#xff0c;DSM版本为 DSM 7.2.2-72806 Update 2。 n.eko 支持多种类型浏览器在其虚拟环境中运行&#xff0c;本次教程使用 Chromium​ 浏览器镜像进行演示&#xff0c;支持访问内网设备和公网地址。 简介 n.eko 是…

AIGC视频生成国产之光:ByteDance的PixelDance模型

大家好&#xff0c;这里是好评笔记&#xff0c;公主号&#xff1a;Goodnote&#xff0c;专栏文章私信限时Free。本文详细介绍ByteDance的视频生成模型PixelDance&#xff0c;论文于2023年11月发布&#xff0c;模型上线于2024年9月&#xff0c;同时期上线的模型还有Seaweed&…

《keras 3 内卷神经网络》

keras 3 内卷神经网络 作者&#xff1a;Aritra Roy Gosthipaty 创建日期&#xff1a;2021/07/25 最后修改时间&#xff1a;2021/07/25 描述&#xff1a;深入研究特定于位置和通道无关的“内卷”内核。 &#xff08;i&#xff09; 此示例使用 Keras 3 在 Colab 中查看 GitHub …

【json_object】mysql中json_object函数过长,显示不全

问题&#xff1a;json只显示部分 解决&#xff1a; SET GLOBAL group_concat_max_len 1000000; -- 设置为1MB&#xff0c;根据需要调整如果当前在navicat上修改&#xff0c;只有效本次连接和后续会话&#xff0c;重新连接还是会恢复默认值1024 在my.ini配置文件中新增或者修…

云消息队列 Kafka 版 V3 系列荣获信通院“云原生技术创新标杆案例”

2024 年 12 月 24 日&#xff0c;由中国信息通信研究院&#xff08;以下简称“中国信通院”&#xff09;主办的“2025 中国信通院深度观察报告会&#xff1a;算力互联网分论坛”&#xff0c;在北京隆重召开。本次论坛以“算力互联网 新质生产力”为主题&#xff0c;全面展示中国…

2024 年度学习总结

目录 1. 前言 2. csdn 对于我的意义 3. 写博客的初衷 3.1 现在的想法 4. 写博客的意义 5. 关于生活和博客创作 5.1 写博客较于纸质笔记的优势 6. 致 2025 1. 前言 不知不觉, 来到 csdn 已经快一年了, 在这一年中, 我通过 csdn 学习到了很多知识, 结识了很多的良师益友…

Spring Boot自动配置原理:如何实现零配置启动

引言 在现代软件开发中&#xff0c;Spring 框架已经成为 Java 开发领域不可或缺的一部分。而 Spring Boot 的出现&#xff0c;更是为 Spring 应用的开发带来了革命性的变化。Spring Boot 的核心优势之一就是它的“自动配置”能力&#xff0c;它极大地简化了 Spring 应用的配置…

1.2.神经网络基础

目录 1.2.神经网络基础 1.2.1.Logistic回归 1.2.2 梯度下降算法 1.2.3 导数 1.2.4 向量化编程 1.2.5 正向传播与反向传播 1.2.6.练习 1.2.神经网络基础 1.2.1.Logistic回归 1.2.1.1.Logistic回归 逻辑回归是一个主要用于二分分类类的算法。那么逻辑回归是给定一个x ,…

Matlab总提示内存不够用,明明小于电脑内存

目录 前言情况1&#xff08;改matlab最大内存限制&#xff09;情况2&#xff08;重启电脑&#xff09;情况3 前言 在使用matlab中&#xff0c;有时候需要占用的内存并没有超过电脑内存依旧会报错&#xff0c;提示内存不够用&#xff0c;可以尝试下面几种方法&#xff0c;总有一…

使用AI生成金融时间序列数据:解决股市场的数据稀缺问题并提升信噪比

“GENERATIVE MODELS FOR FINANCIAL TIME SERIES DATA: ENHANCING SIGNAL-TO-NOISE RATIO AND ADDRESSING DATA SCARCITY IN A-SHARE MARKET” 论文地址&#xff1a;https://arxiv.org/pdf/2501.00063 摘要 金融领域面临的数据稀缺与低信噪比问题&#xff0c;限制了深度学习在…

深入解析人工智能中的协同过滤算法及其在推荐系统中的应用与优化

目录 什么是协同过滤算法核心原理基本步骤相似度计算代码实现详解1.流程图2.创建基础的数据结构存储用户评分数据3.计算用户相似度4.获取相似用户5.推荐方法 算法优化建议1. 数据预处理优化去除异常值和噪声数据进行数据标准化使用稀疏矩阵优化存储 2. 相似度计算优化使用局部敏…

react install

react 安装 React 是一个用于构建用户界面的 JavaScript 库。以下是安装 React 的步骤&#xff1a; 使用 Create React App Create React App 是一个官方支持的命令行工具&#xff0c;用于快速搭建 React 应用。 安装 Node.js 和 npm 确保你的计算机上安装了 Node.js 和 npm…

程序员不可能不知道的常见锁策略

前面我们学习过线程不安全问题&#xff0c;我们通过给代码加锁来解决线程不安全问题&#xff0c;在生活中我们也知道有很多种类型的锁&#xff0c;同时在代码的世界当中&#xff0c;也对应着很多类型的锁&#xff0c;今天我们对锁一探究竟&#xff01; 1. 常见的锁策略 注意: …

智启未来,AI筑梦科技新星”------华清远见成都中心2025冬令营圆满结束

2025年1月11日-16日&#xff0c;华清远见成都中心为期6天的“智启未来&#xff0c;AI筑梦科技新星”2025冬令营活动圆满结束。此次活动吸引了众多对人工智能和无人驾驶技术充满热情的学生参与&#xff0c;共同开启了一段点燃科技梦想的精彩旅程。 报道接待 以AI无人驾驶小车为核…

Debezium日常分享系列之:对于从Oracle数据库进行快照的性能优化

Debezium日常分享系列之&#xff1a;对于从Oracle数据库进行快照的性能优化 源数据库Kafka Connect监控测试结果 源数据库 Oracle 19c&#xff0c;本地&#xff0c;CDB数据库主机的I/O带宽为6 GB/s&#xff0c;由此主机上运行的所有数据库共享临时表空间由42个文件组成&#x…