当前位置: 首页 > news >正文

vue中通过heatmap.js实现热力图(多个热力点)热区展示(带鼠标移入弹窗)

直接上完整代码!记录实现方式

注意heatmap.min.js需要通过heatmap.js提供的下载地址进行下载,地址放在下边
url:heatmap GIT地址
实现效果1
实现效果2

<template><div class="heatmap-view" ref="heatmapContainer"></div>
</template><script lang="ts" setup>import { ref, onMounted } from 'vue';const heatmapContainer = ref<HTMLElement>();let heatmapInstance: any = null;let tooltipTimer: NodeJS.Timeout | null = null;// 固定的热力图数据 - 6行,每行17个点const fixedHeatmapData = [// 第1行数据{ x: 80, y: 80, value: 25 },{ x: 160, y: 80, value: 22 },{ x: 240, y: 80, value: 28 },{ x: 320, y: 80, value: 24 },{ x: 400, y: 80, value: 26 },{ x: 480, y: 80, value: 13 },{ x: 560, y: 80, value: 27 },{ x: 640, y: 80, value: 25 },{ x: 720, y: 80, value: 12 },{ x: 800, y: 80, value: 29 },{ x: 880, y: 80, value: 24 },{ x: 960, y: 80, value: 16 },{ x: 1040, y: 80, value: 23 },{ x: 1120, y: 80, value: 28 },{ x: 1200, y: 80, value: 15 },{ x: 1280, y: 80, value: 27 },{ x: 1360, y: 80, value: 24 },// 第2行数据{ x: 80, y: 200, value: 23 },{ x: 160, y: 200, value: 26 },{ x: 240, y: 200, value: 22 },{ x: 320, y: 200, value: 25 },{ x: 400, y: 200, value: 24 },{ x: 480, y: 200, value: 17 },{ x: 560, y: 200, value: 23 },{ x: 640, y: 200, value: 26 },{ x: 720, y: 200, value: 25 },{ x: 800, y: 200, value: 12 },{ x: 880, y: 200, value: 18 },{ x: 960, y: 200, value: 24 },{ x: 1040, y: 200, value: 26 },{ x: 1120, y: 200, value: 23 },{ x: 1200, y: 200, value: 17 },{ x: 1280, y: 200, value: 25 },{ x: 1360, y: 200, value: 24 },// 第3行数据{ x: 80, y: 320, value: 24 },{ x: 160, y: 320, value: 27 },{ x: 240, y: 320, value: 23 },{ x: 320, y: 320, value: 16 },{ x: 400, y: 320, value: 25 },{ x: 480, y: 320, value: 22 },{ x: 560, y: 320, value: 18 },{ x: 640, y: 320, value: 24 },{ x: 720, y: 320, value: 16 },{ x: 800, y: 320, value: 23 },{ x: 880, y: 320, value: 27 },{ x: 960, y: 320, value: 25 },{ x: 1040, y: 320, value: 24 },{ x: 1120, y: 320, value: 16 },{ x: 1200, y: 320, value: 23 },{ x: 1280, y: 320, value: 8 },{ x: 1360, y: 320, value: 25 },// 第4行数据{ x: 80, y: 440, value: 26 },{ x: 160, y: 440, value: 23 },{ x: 240, y: 440, value: 27 },{ x: 320, y: 440, value: 14 },{ x: 400, y: 440, value: 25 },{ x: 480, y: 440, value: 18 },{ x: 560, y: 440, value: 22 },{ x: 640, y: 440, value: 26 },{ x: 720, y: 440, value: 24 },{ x: 800, y: 440, value: 17 },{ x: 880, y: 440, value: 23 },{ x: 960, y: 440, value: 25 },{ x: 1040, y: 440, value: 16 },{ x: 1120, y: 440, value: 24 },{ x: 1200, y: 440, value: 18 },{ x: 1280, y: 440, value: 23 },{ x: 1360, y: 440, value: 26 },// 第5行数据{ x: 80, y: 560, value: 25 },{ x: 160, y: 560, value: 28 },{ x: 240, y: 560, value: 24 },{ x: 320, y: 560, value: 17 },{ x: 400, y: 560, value: 23 },{ x: 480, y: 560, value: 26 },{ x: 560, y: 560, value: 15 },{ x: 640, y: 560, value: 22 },{ x: 720, y: 560, value: 9 },{ x: 800, y: 560, value: 24 },{ x: 880, y: 560, value: 26 },{ x: 960, y: 560, value: 23 },{ x: 1040, y: 560, value: 17 },{ x: 1120, y: 560, value: 25 },{ x: 1200, y: 560, value: 24 },{ x: 1280, y: 560, value: 26 },{ x: 1360, y: 560, value: 18 },// 第6行数据{ x: 80, y: 680, value: 27 },{ x: 160, y: 680, value: 24 },{ x: 240, y: 680, value: 26 },{ x: 320, y: 680, value: 23 },{ x: 400, y: 680, value: 18 },{ x: 480, y: 680, value: 25 },{ x: 560, y: 680, value: 24 },{ x: 640, y: 680, value: 27 },{ x: 720, y: 680, value: 16 },{ x: 800, y: 680, value: 23 },{ x: 880, y: 680, value: 15 },{ x: 960, y: 680, value: 28 },{ x: 1040, y: 680, value: 24 },{ x: 1120, y: 680, value: 16 },{ x: 1200, y: 680, value: 25 },{ x: 1280, y: 680, value: 12 },{ x: 1360, y: 680, value: 14 },];// 生成密集的热力图数据,形成连片效果const generateHeatmapData = () => {const data: Array<{ x: number; y: number; value: number }> = [];// 为每个固定数据点生成多个数据点,形成连片效果fixedHeatmapData.forEach((fixedPoint, index) => {const baseValue = fixedPoint.value; // 使用固定值const pointsCount = 6; // 增加点数让分布更密集const spreadRadius = 120; // 减小扩散半径,让中心更突出// 首先添加中心点本身 - 确保中心点有最高值data.push({x: fixedPoint.x,y: fixedPoint.y,value: baseValue * 1.2, // 中心点值稍微提高,确保最亮});// 为中心点周围生成渐变填充点,让整体显示更自然for (let ring = 1; ring <= 3; ring++) {const ringRadius = (spreadRadius / 3) * ring;const ringPoints = ring * 4; // 每圈点数递增for (let i = 0; i < ringPoints; i++) {const angle = (i / ringPoints) * Math.PI * 2;const x = fixedPoint.x + Math.cos(angle) * ringRadius;const y = fixedPoint.y + Math.sin(angle) * ringRadius;// 确保点在容器范围内if (x >= 0 && x <= 1440 && y >= 0 && y <= 800) {// 根据距离中心的远近决定热力值 - 距离越远,热力值越低const distanceFromCenter = Math.sqrt(Math.pow(x - fixedPoint.x, 2) + Math.pow(y - fixedPoint.y, 2));const value = Math.max(1, baseValue * (1 - distanceFromCenter / spreadRadius) * 0.6);data.push({x: Math.floor(x),y: Math.floor(y),value: Math.floor(value),});}}}// 添加随机点增加自然感for (let i = 0; i < pointsCount; i++) {const angle = Math.random() * Math.PI * 2;const distance = Math.random() * spreadRadius;const x = fixedPoint.x + Math.cos(angle) * distance;const y = fixedPoint.y + Math.sin(angle) * distance;// 确保点在容器范围内if (x >= 0 && x <= 1440 && y >= 0 && y <= 800) {// 根据距离中心的远近决定热力值 - 距离越远,热力值越低const distanceFromCenter = Math.sqrt(Math.pow(x - fixedPoint.x, 2) + Math.pow(y - fixedPoint.y, 2));const value = Math.max(1, baseValue * (1 - distanceFromCenter / spreadRadius) * 0.4);data.push({x: Math.floor(x),y: Math.floor(y),value: Math.floor(value),});}}});// 添加连接点,让不同区域的热力图更好地融合for (let i = 0; i < fixedHeatmapData.length - 1; i++) {const currentPos = fixedHeatmapData[i];const nextPos = fixedHeatmapData[i + 1];// 在相邻位置之间添加一些连接点const midX = (currentPos.x + nextPos.x) / 2;const midY = (currentPos.y + nextPos.y) / 2;// const midValue = Math.floor((currentPos.value + nextPos.value) / 2); // 使用中间值for (let j = 0; j < 10; j++) {const angle = Math.random() * Math.PI * 2;const distance = Math.random() * 50; // 连接点范围const x = midX + Math.cos(angle) * distance;const y = midY + Math.sin(angle) * distance;if (x >= 0 && x <= 1440 && y >= 0 && y <= 800) {// 计算到两个相邻热力点的距离,取较小值const distToCurrent = Math.sqrt(Math.pow(x - currentPos.x, 2) + Math.pow(y - currentPos.y, 2));const distToNext = Math.sqrt(Math.pow(x - nextPos.x, 2) + Math.pow(y - nextPos.y, 2));const minDistance = Math.min(distToCurrent, distToNext);// 根据距离最近的热力点计算值,距离越远值越低const nearestValue = minDistance === distToCurrent ? currentPos.value : nextPos.value;const value = Math.max(1, nearestValue * (1 - minDistance / 120) * 0.5);data.push({x: Math.floor(x),y: Math.floor(y),value: Math.floor(value),});}}}return data;};// 添加热力点交互区域const addInteractiveAreas = () => {if (!heatmapContainer.value) return;// 为每个热力中心添加交互区域fixedHeatmapData.forEach((point, index) => {const interactiveArea = document.createElement('div');interactiveArea.style.position = 'absolute';interactiveArea.style.left = `${point.x - 20}px`; // 扩大交互区域interactiveArea.style.top = `${point.y - 20}px`;interactiveArea.style.width = '40px';interactiveArea.style.height = '40px';interactiveArea.style.borderRadius = '50%';interactiveArea.style.zIndex = '5';interactiveArea.style.cursor = 'pointer';interactiveArea.style.transition = 'all 0.3s ease';// 添加数据属性interactiveArea.setAttribute('data-index', index.toString());interactiveArea.setAttribute('data-value', point.value.toString());interactiveArea.setAttribute('data-x', point.x.toString());interactiveArea.setAttribute('data-y', point.y.toString());// 鼠标移入事件 - 添加延时interactiveArea.addEventListener('mouseenter', (e) => {// 清除之前的定时器if (tooltipTimer) {clearTimeout(tooltipTimer);tooltipTimer = null;}// 设置延时显示弹窗tooltipTimer = setTimeout(() => {showTooltip(e, point, index);}, 300); // 300ms延时});// 鼠标移出事件 - 立即隐藏interactiveArea.addEventListener('mouseleave', () => {// 清除定时器if (tooltipTimer) {clearTimeout(tooltipTimer);tooltipTimer = null;}hideTooltip();});heatmapContainer.value?.appendChild(interactiveArea);});};// 显示毛玻璃弹窗const showTooltip = (event: MouseEvent, point: any, index: number) => {// 移除已存在的弹窗hideTooltip();const tooltip = document.createElement('div');tooltip.id = 'heatmap-tooltip';tooltip.style.position = 'absolute';tooltip.style.zIndex = '1000';tooltip.style.pointerEvents = 'none';tooltip.style.transition = 'all 0.3s ease';tooltip.style.opacity = '0';tooltip.style.transform = 'translateY(10px)';// 毛玻璃效果tooltip.style.background = 'rgba(255, 255, 255, 0.15)';tooltip.style.backdropFilter = 'blur(10px)';tooltip.style.border = '1px solid rgba(255, 255, 255, 0.2)';tooltip.style.borderRadius = '12px';tooltip.style.padding = '12px 16px';tooltip.style.boxShadow = '0 8px 32px rgba(0, 0, 0, 0.1)';tooltip.style.minWidth = '120px';// 内容tooltip.innerHTML = `<div style="color: #333; font-size: 14px; font-weight: 600; margin-bottom: 4px;">点位 ${index + 1}</div><div style="color: #666; font-size: 12px; margin-bottom: 2px;">坐标: (${point.x}, ${point.y})</div><div style="color: #ff6b6b; font-size: 16px; font-weight: 700;">热力值: ${point.value}</div>`;if (heatmapContainer.value) {heatmapContainer.value.appendChild(tooltip);// 获取容器和弹窗尺寸const containerRect = heatmapContainer.value.getBoundingClientRect();const tooltipRect = tooltip.getBoundingClientRect();// 智能定位逻辑let left = point.x + 30;let top = point.y - 30;// 检查右边界if (left + tooltipRect.width > containerRect.width) {left = point.x - tooltipRect.width - 30; // 显示在左侧}// 检查左边界if (left < 0) {left = 10; // 贴左边}// 检查上边界if (top < 0) {top = point.y + 30; // 显示在下方}// 检查下边界if (top + tooltipRect.height > containerRect.height) {top = point.y - tooltipRect.height - 30; // 显示在上方}// 应用计算后的位置tooltip.style.left = `${left}px`;tooltip.style.top = `${top}px`;// 动画显示setTimeout(() => {tooltip.style.opacity = '1';tooltip.style.transform = 'translateY(0)';}, 10);}};// 隐藏弹窗const hideTooltip = () => {// 清除定时器if (tooltipTimer) {clearTimeout(tooltipTimer);tooltipTimer = null;}const existingTooltip = document.getElementById('heatmap-tooltip');if (existingTooltip) {existingTooltip.style.opacity = '0';existingTooltip.style.transform = 'translateY(10px)';setTimeout(() => {existingTooltip.remove();}, 300);}};// 初始化热力图const initHeatmap = () => {if (!heatmapContainer.value) return;console.log('开始初始化热力图...');// 动态加载本地的heatmap.min.jsconst script = document.createElement('script');script.src = '/src/utils/heatmap.min.js';script.onload = () => {console.log('heatmap.js 加载成功');// 创建热力图实例const h337 = (window as any).h337;if (!h337) {console.error('h337 未找到');return;}try {heatmapInstance = h337.create({container: heatmapContainer.value,radius: 18, // 增大半径,让热力扩散更广maxOpacity: 0.5, // 降低最大透明度,让整体颜色更淡minOpacity: 0,blur: 0.9, // 增加模糊度,让低值点更接近背景色gradient: {'0.0': 'rgba(75, 0, 130, 0.1)', // 蓝紫色 - 最冷,更淡'0.1': 'rgba(0, 0, 235, 0.2)', // 蓝紫色 - 稍微深一点'0.2': 'rgba(0, 0, 255, 0.3)', // 蓝色,更淡'0.3': 'rgba(0, 255, 255, 0.4)', // 青色,更淡'0.4': 'rgba(0, 255, 255, 0.5)', // 青色,更淡'0.5': 'rgba(0, 255, 0, 0.6)', // 绿色,更淡'0.6': 'rgba(0, 255, 0, 0.7)', // 绿色,更淡'0.7': 'rgba(255, 255, 0, 0.8)', // 黄色,更淡'0.8': 'rgba(255, 255, 0, 0.9)', // 黄色,更淡'0.9': 'rgba(255, 140, 0, 0.9)', // 橙色,更淡'1.0': 'rgba(255, 69, 0, 1)', // 橙红色 - 最热,稍微淡一点},});console.log('热力图实例创建成功');// 设置数据const data = generateHeatmapData();console.log('生成数据点数量:', data.length);// 找到最大热力值,确保中心点最亮const maxValue = Math.max(...data.map((d) => d.value));console.log('最大热力值:', maxValue);heatmapInstance.setData({max: maxValue, // 使用实际的最大值作为基准data: data,});console.log('热力图数据设置完成');// 添加交互区域addInteractiveAreas();} catch (error) {console.error('创建热力图时出错:', error);}};script.onerror = () => {console.error('heatmap.js 加载失败');};document.head.appendChild(script);};onMounted(() => {// 延迟一下确保DOM完全渲染setTimeout(() => {console.log('容器元素:', heatmapContainer.value);initHeatmap();}, 100);});
</script><style lang="less" scoped>.heatmap-view {margin: 30px auto;width: 1440px;height: 800px;border: 1px solid lightgray;position: relative;background-color: #e0eaf9;overflow: hidden;}/* 毛玻璃弹窗样式 */#heatmap-tooltip {font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;backdrop-filter: blur(10px);-webkit-backdrop-filter: blur(10px);}/* 交互区域悬停效果 */.heatmap-view div[data-index] {transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);}.heatmap-view div[data-index]:hover {background-color: rgba(255, 255, 255, 0.1);transform: scale(1.1);}
</style>

文章转载自:

http://giWhuczU.cgntj.cn
http://YNCwk2ic.cgntj.cn
http://ZbiqNSPi.cgntj.cn
http://DVklzrUn.cgntj.cn
http://alMQzjeG.cgntj.cn
http://2asqLNTr.cgntj.cn
http://xd8dLPYd.cgntj.cn
http://hPdWUmkN.cgntj.cn
http://tErLL3Qn.cgntj.cn
http://62vVZYeK.cgntj.cn
http://8lltIxmo.cgntj.cn
http://k18AaJnV.cgntj.cn
http://nJs6WJII.cgntj.cn
http://G2GPglPz.cgntj.cn
http://TwcnKr6a.cgntj.cn
http://JRUydRBB.cgntj.cn
http://J7PR5t0Z.cgntj.cn
http://99dO2H5C.cgntj.cn
http://NsJzz9PZ.cgntj.cn
http://zGHACXKB.cgntj.cn
http://NUdIvND9.cgntj.cn
http://PtbmsbJ0.cgntj.cn
http://Owka1DNl.cgntj.cn
http://5DPRHEYw.cgntj.cn
http://p5tcg0kL.cgntj.cn
http://V3qaeHvJ.cgntj.cn
http://nQSneTXF.cgntj.cn
http://VQSgtEei.cgntj.cn
http://n7652nzB.cgntj.cn
http://OQ3LRp2j.cgntj.cn
http://www.dtcms.com/a/377715.html

相关文章:

  • Java基础 9.10
  • 绿色算力技术栈:AI集群功耗建模与动态调频系统
  • 从零搭建网站(第五天)
  • MySQL 8.4.6 安装
  • 前端架构知识体系:Web Worker 使用与优化指南
  • 嵌入式 - ARM4
  • Linux 的权限详解
  • 研究生开题答辩全攻略!老学姐教你轻松过关,再也不用担心被老师刁难!
  • Angr符号执行初步学习
  • Shell编程之正则表达式与文本处理工具
  • 软考系统架构设计师之UML统一建模语言
  • malloc概述
  • Nginx 实战系列(九)—— LVS负载均衡集群与DR模式部署指南
  • 利用美团龙猫用libxml2编写XML转CSV文件C程序
  • NJet支持使用json格式的配置文件了
  • 平时只会CRUD,没有高质量项目经验,我该怎么办
  • Vue项目创建方式(多种)
  • PMM:一款开源的数据库监控与管理工具
  • pyinstaller打包多个文件成一个exe
  • JavaScript逆向非对称加密算法
  • 基于EB_S32K3XX芯片的EMIOS_PWM的输出_1
  • 计算机毕设 java 高校会议信息管理系统的设计与实现 基于 SSM 框架的高校会议服务平台 Java+MySQL 的会议室预约与管理系统
  • Git 初识
  • 安全用电管理系统助力银行以全链路监测
  • 3.远程控制网络编程的设计上
  • 存算一体芯片生态评估:从三星PIM到知存科技WTM2101
  • 智联未来 点赋科技
  • SpringBoot项目健康检查端点返回为down排查思路
  • yolov8(yolov11)目标识别使用Tensorrt推理-2.0(C++Api)
  • 《Java中的IO流》