更新一版热力图
This commit is contained in:
105
src/heatmap.js
Normal file
105
src/heatmap.js
Normal file
@@ -0,0 +1,105 @@
|
||||
/**
|
||||
* 在 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})`;
|
||||
}
|
||||
Reference in New Issue
Block a user