vue3实现活跃度(热力图)

本文介绍如何使用HTML和CSS实现一年的活动热力图,包括创建热力图布局、定义颜色等级、渲染数据及添加交互效果。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

在项目开发中遇到一个需求,制作一个展示一年信息的活跃度图,也就是热力图,如图:

 1.制作右上角的方格标识

<a-row>
    <a-col :span="1" :offset="19">
        <div style="height: 13px;"></div>
        <div>少</div>
    </a-col>
    <a-col :span="2">
      <div>
        <ul class="graph" style="border: 0;grid-template-rows: repeat(1, 15px)">
          <li :data-level="level0"  class="li-day"></li>
          <li :data-level="level1"  class="li-day"></li>
          <li :data-level="level2"  class="li-day"></li>
          <li :data-level="level3"  class="li-day"></li>
        </ul>
      </div>
    </a-col>
    <a-col :span="1">
      <div style="height: 13px;"></div>
      <div>多</div>
    </a-col>
</a-row>

2.制作热力图

<div class="container-card">
    <a-row>
      <a-col :span="2">
        <ul style="list-style: none;margin: 20px 0px 5px 0px;line-height: 15px">
          <li style="font-size: 13px">周日</li>
          <li>&nbsp;</li>
          <li>&nbsp;</li>
          <li style="font-size: 13px">周三</li>
          <li>&nbsp;</li>
          <li>&nbsp;</li>
          <li style="font-size: 13px">周六</li>
        </ul>
      </a-col>
      <a-col :span="22">
        <ul class="graph">
          <!-- 热力图左边的空格,去年的今天以前的日期不显示,没有格子 -->  
          <li v-for="n in today" style="background-color: white;list-style: none;margin: 1.5px;">&nbsp;</li>

          <a-popover class="item"
                     placement="top" v-for="(item, index) in infos" :key="index" >
            <template #content>
              {{item.year + '-' + item.month + '-' + item.date}}
            </template>
            <li :data-level="item.level"  class="li-day" ></li>
          </a-popover>
        </ul>
        <ul class="months">
          <!-- 热力图下面的月份 -->
          <li class="li-month" v-for="(item,index) in monthBar" :key="index">{{item}}</li>
        </ul><br>
      </a-col>
    </a-row> 
</div>

3.热力图需要的数据

const state = reactive({
    infos: [],  //存放365天每一天的数据(year,month,date,状态数量,isToday标记)
    current: {  //存放今天的年月日
      year: "",
      month: "",
      date: "",
    },
    level0: 0,     //等级1
    level1: 1,     //等级2
    level2: 2,     //等级3
    level3: 3,     //等级4
    today: 0,
    monthBar: ["", "", "", "", "", "", "", "", "","",
        "", "", "", "", "", "", "", "", "","",
        "", "", "", "", "", "", "", "", "","",
        "", "", "", "", "", "", "", "", "","",
        "", "", "", "", "", "", "", "", "","",
        "","",
    ],
})

4.判断传回的数值对应的等级

const judgeLevel = function (num){
  if (num === 0){
     return "0"
  }
  if (num>0&&num<=100){
    return "1"
  }
  if (num>100&&num<=200){
    return "2"
  }
  if (num>200&&num<=300){
    return "3"
  }
}

5.渲染数据(先渲染出来365天的空数据,然后判断遍历的天数是否为后端传回的数据中的天数,如果是,则替换掉原来的空数据。因为这里用到了字符串的截取,1-9月和10-12月截取的位置不一样,所以判断两次)

 const activeNumber = function (){
    let d = new Date();                    //

    let day = d.getDay();           //获取今天所在星期,以进行后续计算    5
    let today = d.getDate();        //获取今天的日数                   3
    // console.log(day)

    state.today = day

    state.current.year = d.getFullYear();   //初始化今日的年月日    2022
    state.current.month = d.getMonth();     //   8
    state.current.date = d.getDate();

    const dataLists =[                  //模拟后端接收到的数据
      {day: '2021-10-20',number: 200},
      {day: "2022-9-20",number: 300},
      {day: "2022-9-21",number: 100},
      {day: '2022-8-10',number: 150},
      {day: "2022-9-2",number: 50},
      {day: "2022-9-23",number: 150},
    ]

    let info = {};         //用来存放某一天的数据对象(年月日、isToday、level)
    let month = "";        //后续计算某月第一天在哪一列用,表示第几月
    let weekOfMonth = ""   //后续计算某月第一天在哪一列用,表示第几列

    for (let i = 0; i < 365; i++) {
      d.setFullYear(state.current.year);  //每次循环要重置年月日为今天否则会以上次循环结尾的年月日计算而计算错误
      d.setMonth(state.current.month);
      d.setDate(state.current.date);
      d.setDate(today - 364 + i);

      info = {
        year: d.getFullYear(),
        month: d.getMonth() + 1,
        date: d.getDate(),
        number: 0,
        level: 0,
        isToday: false,
      };
      state.infos.push(info);      //渲染上365天的空数据

      for(let j = 0;j < dataLists.length;j++){
        //判断遍历到的天数是否和后端传回的数据对应(0-9月份)
        if (d.getMonth()+1 == dataLists[j].day.substr(5,1)&&d.getDate() == dataLists[j].day.substr(7,2)){
            state.infos[i] = {                      //每个格子(天)的info对象
              year: d.getFullYear(),      //年月日
              month: d.getMonth() + 1,
              date: d.getDate(),
              number: dataLists[j].number,    //今日的数据量
              level: judgeLevel(dataLists[j].number),  //今日数据量对应的等级
              isToday: false, //是否是今天
            };
        //判断遍历到的天数是否和后端传回的数据对应(10-12月份)
        }else if(d.getMonth()+1 == dataLists[j].day.substr(5,2)&&d.getDate() == dataLists[j].day.substr(8,2)){
            state.infos[i] = {                      //每个格子(天)的info对象
              year: d.getFullYear(),      //年月日
              month: d.getMonth() + 1,
              date: d.getDate(),
              number: dataLists[j].number,    //今日的数据量
              level: judgeLevel(dataLists[j].number),  //今日数据量对应的等级
              isToday: false, //是否是今天
           };
        }
      }

      // // 判断每月第一天在12列种的哪一列
      if (d.getDate() === 1) { //date为1的肯定是某月第一天
        month = d.getMonth() + 1  //获取这一天对应的月份(0-11,所以还要+1)
        weekOfMonth = parseInt((i + 1) / 7)  //这个月的第一天的index(84天的第几天)除以7获得所在列的index(12列的第几列),作为下面monthBar的index,并把原来空的内容用替换为xx月
        state.monthBar[weekOfMonth] = month + "月"
      }
    }
};

6.CSS样式



.graph {
  display: grid;
  grid-template-columns: repeat(53, 15px);  /*竖向53列,21px宽*/
  grid-template-rows: repeat(7, 15px);     /*横向7列,21px宽*/
  padding-inline-start: 0px;
  grid-auto-flow: column;               /*生成7*53的格子后,设置为竖向排布*/
  margin: 20px 0px 5px 0px;
}

.months {
  display: grid;
  grid-template-columns: repeat(52, 15px);
  grid-template-rows: 21px;
  font-size: 8px;
  color: #aaa;
  padding-inline-start: 0px;
  margin: 5px 20px 5px 0px;
}

.li-month {
  display: inline-block;
}

.li-day {
  background-color: #eaeaea;
  list-style: none;             /*记得把list的圆点效果去掉*/
  margin: 1.5px;
  border-radius: 3px;
}

.li-day:hover {               /*添加hover强调效果*/
  box-shadow: 0px 0px 5px rgb(57, 120, 255);
}
.graph li[data-level="1"],.level1 {
  background-color: #d9ecff;
}

.graph li[data-level="2"],.level2 {
  background-color: #8cc5ff;
}

.graph li[data-level="3"],.level3 {
  background-color: #409eff;
}

src/views/activeNum.vue · 马飞祥/work1 - 码云 - 开源中国 (gitee.com)https://gitee.com/ma-feixiang/work1/blob/master/src/views/activeNum.vue

### Vue3实现力图的显示效果 在 Vue3实现力图的效果可以通过多种方式完成,具体取决于所使用的库和技术栈。以下是基于不同技术方案的实现方法: #### 使用 Plotly.js 绘制交互式 3D 力图 Plotly.js 是一个强大的可视化工具,支持创建复杂的图表和力图。通过集成到 Vue3 项目中,可以轻松实现交互式的力图。 1. 安装依赖项 需要安装 `plotly.js` 和其相关插件: ```bash npm install plotly.js-dist-min --save ``` 2. 创建组件并初始化力图 下面是一个简单的示例代码片段,用于展示如何在 Vue3 中使用 Plotly.js 来绘制力图[^1]。 ```vue <template> <div id="heatmap-container"></div> </template> <script> import * as Plotly from &#39;plotly.js-dist-min&#39;; export default { name: "HeatmapComponent", mounted() { const data = [ { z: [[1, null], [null, 2]], x: [&#39;Monday&#39;, &#39;Tuesday&#39;], y: [&#39;Morning&#39;, &#39;Evening&#39;], type: &#39;heatmap&#39;, colorscale: &#39;Viridis&#39; } ]; const layout = { title: &#39;Interactive Heatmap Example&#39; }; Plotly.newPlot(&#39;heatmap-container&#39;, data, layout); } }; </script> <style scoped> #heatmap-container { width: 600px; height: 400px; } </style> ``` 此代码展示了如何定义数据矩阵 (`z`) 并将其映射到二维坐标系中的位置 (`x`, `y`) 上,从而生成力图。 --- #### 基于 Leaflet 的力图实现 Leaflet 结合 heatmap.js 可以用来制作地理空间上的力图。这种类型的力图通常应用于地图背景之上,适合表示地理位置相关的分布情况。 1. 添加必要的 JavaScript 文件至 `public/index.html` 或者通过 NPM 进行管理: ```html <!-- 如果未通过NPM安装,则需手动引入 --> <link rel="stylesheet" href="https://unpkg.com/leaflet/dist/leaflet.css"> <script src="https://unpkg.com/leaflet/dist/leaflet.js"></script> <script src="https://cdn.jsdelivr.net/npm/heatmap.js@2.0.2/build/heatmap.min.js"></script> <script src="https://cdn.jsdelivr.net/npm/leaflet-heatmap@1.0.0-beta/src/HeatLayer.js"></script> ``` 2. 编写 Vue3 组件逻辑如下所示[^2]: ```vue <template> <div ref="mapContainer" class="map-container"></div> </template> <script> import L from &#39;leaflet&#39;; import &#39;leaflet-heatmap&#39;; // Ensure this is installed via npm or CDN. export default { name: "LeafletHeatmap", mounted() { const map = L.map(this.$refs.mapContainer).setView([51.505, -0.09], 13); L.tileLayer(&#39;https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png&#39;, { maxZoom: 18, }).addTo(map); const heatData = []; for (let i = 0; i < 100; i++) { heatData.push({ lat: Math.random() * 2 - 1 + 51.5, lng: Math.random() * 2 - 1 - 0.09, count: Math.floor(Math.random() * 50), }); } const heat = L.heatLayer(heatData, { radius: 25 }).addTo(map); }, }; </script> <style scoped> .map-container { width: 100%; height: 500px; } </style> ``` 上述代码实现了在一个 OpenStreetMap 地图上叠加力图的功能,其中随机生成了一些点作为模拟数据。 --- #### 利用高德地图 API 构建力图 如果目标是在中国范围内构建力图,推荐使用高德地图服务。它提供了丰富的接口文档以及良好的兼容性和性能优化能力。 1. 注册开发者账号获取 Key 后,在 HTML 文件头部加入 SDK 资源链接: ```html <script src="https://webapi.amap.com/maps?v=1.4.15&key=YOUR_API_KEY"></script> <script src="https://webapi.amap.com/ui/1.1/main.js?v=1.1"></script> ``` 2. 开发 Vue3 组件实例化地图对象,并加载力图层[^3]: ```vue <template> <div ref="amapRef" class="amap-box"></div> </template> <script> export default { name: "AmapHeatmap", mounted() { const AMap = window.AMap; let map = new AMap.Map(this.$refs.amapRef, { zoom: 11, center: [116.397428, 39.90923], }); var heatmap = new AMap.Heatmap(map, { radius: 25, opacity: [0, 0.8], }); var points = []; for (var i = 0; i < 100; i++) { points.push({ lng: 116.3 + Math.random() / 10, lat: 39.9 + Math.random() / 10, count: Math.ceil(Math.random() * 100), }); } heatmap.setDataSet({ data: points, max: 100 }); }, }; </script> <style scoped> .amap-box { width: 100%; height: 500px; } </style> ``` 这段脚本利用了高德地图提供的力图模块,能够快速部署适用于实际业务需求的地图应用。 ---
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值