/** * 在 uni-app 小程序里画弓箭热力图 * @param {String} canvasId 画布 id * @param {Number} width 画布宽(px) * @param {Number} height 画布高(px) * @param {Array} arrowData [{x, y, count}, ...] */ export function generateHeatmapImage(canvasId, width, height, arrowData) { return new Promise((resolve, reject) => { // 1. 创建绘图上下文 const ctx = uni.createCanvasContext(canvasId); // 3. 计算最大 count,用于归一化 const maxCount = Math.max(...arrowData.map((p) => p.count), 1); // 4. 热点半径:可按实际靶子大小调,这里取画布短边的 6% const radius = Math.min(width, height) * 0.12; // 5. 按count从小到大排序,count越大越后面 arrowData.sort((a, b) => a.count - b.count); // 6. 画每个点 arrowData.forEach((item) => { const intensity = item.count / maxCount; // 0-1 // console.log(item.count, maxCount, intensity); const r = radius * (1.2 - intensity * 0.8); // 创建径向渐变 const grd = ctx.createCircularGradient( item.x * width, item.y * height, r ); grd.addColorStop(0, heatColor(intensity, 1)); grd.addColorStop(0.5, heatColor(intensity, 0.6)); grd.addColorStop(1, "rgba(0,0,0,0)"); ctx.save(); ctx.fillStyle = grd; ctx.globalCompositeOperation = "screen"; // 叠加变亮 ctx.beginPath(); ctx.arc(item.x * width, item.y * height, r, 0, Math.PI * 2); ctx.fill(); ctx.restore(); }); // 6. 可选:整体蒙版,让非热点区域暗下去 // ctx.save() // ctx.fillStyle = 'rgba(0,0,0,0.35)' // ctx.fillRect(0, 0, width, height) // ctx.restore() // 7. 把指令一次性推送到 canvas ctx.draw(false, () => { // Canvas绘制完成后生成图片 uni.canvasToTempFilePath({ canvasId: "heatMapCanvas", width: width, height: height, destWidth: width * 2, // 提高图片质量 destHeight: height * 2, success: (res) => { console.log("热力图图片生成成功:", res.tempFilePath); resolve(res.tempFilePath); }, fail: (error) => { console.error("热力图图片生成失败:", error); reject(error); }, }); }); }); } /** * 把强度 0-1 映射成红-黄-绿渐变,返回 rgba 字符串 * @param {Number} v 0-1 * @param {Number} a 透明度 0-1 */ function heatColor(v, a) { // v 从 0→1,重新映射:极低值绿色,低值黄色,中到高值红色 let red, green; if (v < 0.2) { // 极低值:纯绿色 red = 0; green = 200; // 柔和的绿 } else if (v < 0.4) { // 低值:绿色到黄色 const t = (v - 0.2) / 0.2; red = Math.round(255 * t); green = 255; } else if (v < 0.6) { // 中低值:黄色到橙色 const t = (v - 0.4) / 0.2; red = 255; green = Math.round(255 * (1 - t * 0.5)); } else { // 中到高值:橙色到红色 const t = (v - 0.6) / 0.4; red = 255; green = Math.round(128 * (1 - t)); } const blue = 0; return `rgba(${red}, ${green}, ${blue}, ${a})`; }