Files
shoot-miniprograms/src/heatmap.js
2025-09-28 18:28:49 +08:00

106 lines
3.1 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/**
* 在 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})`;
}