删除无用代码
This commit is contained in:
@@ -33,13 +33,16 @@ function kernelDensityEstimator(kernel, range, samples) {
|
|||||||
|
|
||||||
// 预计算核函数值缓存(减少重复计算)
|
// 预计算核函数值缓存(减少重复计算)
|
||||||
const kernelCache = new Map();
|
const kernelCache = new Map();
|
||||||
const maxDistance = Math.ceil(bandwidth * 2 / gridSize); // 最大影响范围
|
const maxDistance = Math.ceil((bandwidth * 2) / gridSize); // 最大影响范围
|
||||||
|
|
||||||
for (let dx = -maxDistance; dx <= maxDistance; dx++) {
|
for (let dx = -maxDistance; dx <= maxDistance; dx++) {
|
||||||
for (let dy = -maxDistance; dy <= maxDistance; dy++) {
|
for (let dy = -maxDistance; dy <= maxDistance; dy++) {
|
||||||
const distance = Math.sqrt(dx * dx + dy * dy) * gridSize;
|
const distance = Math.sqrt(dx * dx + dy * dy) * gridSize;
|
||||||
if (distance <= bandwidth * 2) {
|
if (distance <= bandwidth * 2) {
|
||||||
kernelCache.set(`${dx},${dy}`, kernel([dx * gridSize, dy * gridSize]));
|
kernelCache.set(
|
||||||
|
`${dx},${dy}`,
|
||||||
|
kernel([dx * gridSize, dy * gridSize])
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -48,7 +51,7 @@ function kernelDensityEstimator(kernel, range, samples) {
|
|||||||
const affectedGridPoints = new Set();
|
const affectedGridPoints = new Set();
|
||||||
|
|
||||||
// 第一步:找出所有受影响的网格点
|
// 第一步:找出所有受影响的网格点
|
||||||
data.forEach(point => {
|
data.forEach((point) => {
|
||||||
const centerX = Math.round((point[0] - range[0]) / gridSize);
|
const centerX = Math.round((point[0] - range[0]) / gridSize);
|
||||||
const centerY = Math.round((point[1] - range[0]) / gridSize);
|
const centerY = Math.round((point[1] - range[0]) / gridSize);
|
||||||
|
|
||||||
@@ -65,8 +68,8 @@ function kernelDensityEstimator(kernel, range, samples) {
|
|||||||
});
|
});
|
||||||
|
|
||||||
// 第二步:只计算受影响的网格点
|
// 第二步:只计算受影响的网格点
|
||||||
affectedGridPoints.forEach(gridKey => {
|
affectedGridPoints.forEach((gridKey) => {
|
||||||
const [gridX, gridY] = gridKey.split(',').map(Number);
|
const [gridX, gridY] = gridKey.split(",").map(Number);
|
||||||
const x = range[0] + gridX * gridSize;
|
const x = range[0] + gridX * gridSize;
|
||||||
const y = range[0] + gridY * gridSize;
|
const y = range[0] + gridY * gridSize;
|
||||||
|
|
||||||
@@ -74,7 +77,7 @@ function kernelDensityEstimator(kernel, range, samples) {
|
|||||||
let validPoints = 0;
|
let validPoints = 0;
|
||||||
|
|
||||||
// 只考虑附近的点(空间分割优化)
|
// 只考虑附近的点(空间分割优化)
|
||||||
data.forEach(point => {
|
data.forEach((point) => {
|
||||||
const dx = (x - point[0]) / gridSize;
|
const dx = (x - point[0]) / gridSize;
|
||||||
const dy = (y - point[1]) / gridSize;
|
const dy = (y - point[1]) / gridSize;
|
||||||
const cacheKey = `${Math.round(dx)},${Math.round(dy)}`;
|
const cacheKey = `${Math.round(dx)},${Math.round(dy)}`;
|
||||||
@@ -102,32 +105,6 @@ function kernelDensityEstimator(kernel, range, samples) {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 生成随机射箭数据点
|
|
||||||
* @param {Number} centerCount 中心点数量
|
|
||||||
* @param {Number} pointsPerCenter 每个中心点的箭数
|
|
||||||
* @returns {Array} 箭矢坐标数组
|
|
||||||
*/
|
|
||||||
export function generateArcheryPoints(centerCount = 2, pointsPerCenter = 100) {
|
|
||||||
const points = [];
|
|
||||||
const range = 8; // 坐标范围 -4 到 4
|
|
||||||
const spread = 3; // 分散度
|
|
||||||
|
|
||||||
for (let i = 0; i < centerCount; i++) {
|
|
||||||
const centerX = Math.random() * range - range / 2;
|
|
||||||
const centerY = Math.random() * range - range / 2;
|
|
||||||
|
|
||||||
for (let j = 0; j < pointsPerCenter; j++) {
|
|
||||||
points.push([
|
|
||||||
centerX + (Math.random() - 0.5) * spread,
|
|
||||||
centerY + (Math.random() - 0.5) * spread,
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return points;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 颜色映射函数 - 将密度值映射到颜色
|
* 颜色映射函数 - 将密度值映射到颜色
|
||||||
* @param {Number} density 密度值 0-1
|
* @param {Number} density 密度值 0-1
|
||||||
@@ -144,7 +121,9 @@ function getHeatColor(density) {
|
|||||||
// 低密度:浅绿色
|
// 低密度:浅绿色
|
||||||
const green = Math.round(200 + 55 * intensity);
|
const green = Math.round(200 + 55 * intensity);
|
||||||
const blue = Math.round(50 + 100 * intensity);
|
const blue = Math.round(50 + 100 * intensity);
|
||||||
return `rgba(${Math.round(50 * intensity)}, ${green}, ${blue}, ${alpha * 0.7})`;
|
return `rgba(${Math.round(50 * intensity)}, ${green}, ${blue}, ${
|
||||||
|
alpha * 0.7
|
||||||
|
})`;
|
||||||
} else {
|
} else {
|
||||||
// 高密度:深绿色
|
// 高密度:深绿色
|
||||||
const red = Math.round(50 * (intensity - 0.5) * 2);
|
const red = Math.round(50 * (intensity - 0.5) * 2);
|
||||||
@@ -169,19 +148,11 @@ const heatmapCache = new Map();
|
|||||||
export function drawKDEHeatmap(canvasId, width, height, points, options = {}) {
|
export function drawKDEHeatmap(canvasId, width, height, points, options = {}) {
|
||||||
return new Promise(async (resolve, reject) => {
|
return new Promise(async (resolve, reject) => {
|
||||||
try {
|
try {
|
||||||
// iOS兼容性:限制canvas尺寸,避免内存问题
|
|
||||||
const maxCanvasSize = 1500; // iOS设备最大建议尺寸
|
|
||||||
if (width > maxCanvasSize || height > maxCanvasSize) {
|
|
||||||
const scale = Math.min(maxCanvasSize / width, maxCanvasSize / height);
|
|
||||||
width = Math.floor(width * scale);
|
|
||||||
height = Math.floor(height * scale);
|
|
||||||
console.log(`iOS兼容性:限制canvas尺寸为 ${width}x${height}`);
|
|
||||||
}
|
|
||||||
const {
|
const {
|
||||||
bandwidth = 0.8,
|
bandwidth = 0.8,
|
||||||
gridSize = 100,
|
gridSize = 100,
|
||||||
range = [-4, 4],
|
range = [-4, 4],
|
||||||
showPoints = true,
|
showPoints = false,
|
||||||
pointColor = "rgba(255, 255, 255, 0.9)",
|
pointColor = "rgba(255, 255, 255, 0.9)",
|
||||||
} = options;
|
} = options;
|
||||||
|
|
||||||
@@ -203,7 +174,7 @@ export function drawKDEHeatmap(canvasId, width, height, points, options = {}) {
|
|||||||
|
|
||||||
// iOS兼容性:设置全局合成操作,让颜色叠加更自然
|
// iOS兼容性:设置全局合成操作,让颜色叠加更自然
|
||||||
try {
|
try {
|
||||||
ctx.globalCompositeOperation = 'screen';
|
ctx.globalCompositeOperation = "screen";
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.warn("设置全局合成操作失败,使用默认设置:", error);
|
console.warn("设置全局合成操作失败,使用默认设置:", error);
|
||||||
}
|
}
|
||||||
@@ -222,7 +193,10 @@ export function drawKDEHeatmap(canvasId, width, height, points, options = {}) {
|
|||||||
|
|
||||||
const processChunk = () => {
|
const processChunk = () => {
|
||||||
// iOS兼容性:使用Date.now()作为performance.now的回退
|
// iOS兼容性:使用Date.now()作为performance.now的回退
|
||||||
const startTime = typeof performance !== 'undefined' && performance.now ? performance.now() : Date.now();
|
const startTime =
|
||||||
|
typeof performance !== "undefined" && performance.now
|
||||||
|
? performance.now()
|
||||||
|
: Date.now();
|
||||||
const endIndex = Math.min(index + chunkSize, data.length);
|
const endIndex = Math.min(index + chunkSize, data.length);
|
||||||
|
|
||||||
// 批量处理多个点,减少函数调用开销
|
// 批量处理多个点,减少函数调用开销
|
||||||
@@ -234,13 +208,16 @@ export function drawKDEHeatmap(canvasId, width, height, points, options = {}) {
|
|||||||
processPoint(point);
|
processPoint(point);
|
||||||
|
|
||||||
// 每处理50个点检查一次时间,避免超时
|
// 每处理50个点检查一次时间,避免超时
|
||||||
const currentTime = typeof performance !== 'undefined' && performance.now ? performance.now() : Date.now();
|
const currentTime =
|
||||||
|
typeof performance !== "undefined" && performance.now
|
||||||
|
? performance.now()
|
||||||
|
: Date.now();
|
||||||
if (i % 50 === 0 && currentTime - startTime > 8) {
|
if (i % 50 === 0 && currentTime - startTime > 8) {
|
||||||
// 如果处理时间超过8ms,保存状态并中断
|
// 如果处理时间超过8ms,保存状态并中断
|
||||||
index = i + 1;
|
index = i + 1;
|
||||||
ctx.restore();
|
ctx.restore();
|
||||||
// iOS兼容性:更安全的requestAnimationFrame检测
|
// iOS兼容性:更安全的requestAnimationFrame检测
|
||||||
if (typeof requestAnimationFrame === 'function') {
|
if (typeof requestAnimationFrame === "function") {
|
||||||
try {
|
try {
|
||||||
requestAnimationFrame(processChunk);
|
requestAnimationFrame(processChunk);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
@@ -260,12 +237,15 @@ export function drawKDEHeatmap(canvasId, width, height, points, options = {}) {
|
|||||||
|
|
||||||
if (index < data.length) {
|
if (index < data.length) {
|
||||||
// 动态调整延迟:如果处理时间超过16ms(一帧),使用更大延迟
|
// 动态调整延迟:如果处理时间超过16ms(一帧),使用更大延迟
|
||||||
const currentTime = typeof performance !== 'undefined' && performance.now ? performance.now() : Date.now();
|
const currentTime =
|
||||||
|
typeof performance !== "undefined" && performance.now
|
||||||
|
? performance.now()
|
||||||
|
: Date.now();
|
||||||
const processingTime = currentTime - startTime;
|
const processingTime = currentTime - startTime;
|
||||||
const delay = processingTime > 16 ? 8 : 1; // 根据处理时间动态调整
|
const delay = processingTime > 16 ? 8 : 1; // 根据处理时间动态调整
|
||||||
|
|
||||||
// iOS兼容性:更安全的requestAnimationFrame检测
|
// iOS兼容性:更安全的requestAnimationFrame检测
|
||||||
if (typeof requestAnimationFrame === 'function') {
|
if (typeof requestAnimationFrame === "function") {
|
||||||
try {
|
try {
|
||||||
requestAnimationFrame(processChunk);
|
requestAnimationFrame(processChunk);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
@@ -294,23 +274,33 @@ export function drawKDEHeatmap(canvasId, width, height, points, options = {}) {
|
|||||||
const color = getHeatColor(density);
|
const color = getHeatColor(density);
|
||||||
|
|
||||||
// iOS兼容性:确保数值有效
|
// iOS兼容性:确保数值有效
|
||||||
if (isNaN(canvasX) || isNaN(canvasY) || !isFinite(canvasX) || !isFinite(canvasY)) {
|
if (
|
||||||
|
isNaN(canvasX) ||
|
||||||
|
isNaN(canvasY) ||
|
||||||
|
!isFinite(canvasX) ||
|
||||||
|
!isFinite(canvasY)
|
||||||
|
) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx.setFillStyle(color);
|
ctx.setFillStyle(color);
|
||||||
ctx.beginPath();
|
ctx.beginPath();
|
||||||
const radius = Math.max(1, Math.min(width / gridSize, height / gridSize) * 0.6); // 确保半径至少为1
|
const radius = Math.max(
|
||||||
|
1,
|
||||||
|
Math.min(width / gridSize, height / gridSize) * 0.6
|
||||||
|
); // 确保半径至少为1
|
||||||
ctx.arc(canvasX, canvasY, radius, 0, 2 * Math.PI);
|
ctx.arc(canvasX, canvasY, radius, 0, 2 * Math.PI);
|
||||||
ctx.fill();
|
ctx.fill();
|
||||||
};
|
};
|
||||||
|
|
||||||
// 生成缓存key(基于参数和数据点的哈希)
|
// 生成缓存key(基于参数和数据点的哈希)
|
||||||
const cacheKey = `${bandwidth}-${gridSize}-${range.join(',')}-${points.length}-${JSON.stringify(points.slice(0, 10))}`;
|
const cacheKey = `${bandwidth}-${gridSize}-${range.join(",")}-${
|
||||||
|
points.length
|
||||||
|
}-${JSON.stringify(points.slice(0, 10))}`;
|
||||||
|
|
||||||
// 检查缓存
|
// 检查缓存
|
||||||
if (heatmapCache.has(cacheKey)) {
|
if (heatmapCache.has(cacheKey)) {
|
||||||
console.log('使用缓存的热力图数据');
|
console.log("使用缓存的热力图数据");
|
||||||
const cachedDensityData = heatmapCache.get(cacheKey);
|
const cachedDensityData = heatmapCache.get(cacheKey);
|
||||||
|
|
||||||
// 使用分片处理绘制缓存数据
|
// 使用分片处理绘制缓存数据
|
||||||
@@ -332,7 +322,12 @@ export function drawKDEHeatmap(canvasId, width, height, points, options = {}) {
|
|||||||
const canvasY = normalizedY * height;
|
const canvasY = normalizedY * height;
|
||||||
|
|
||||||
// iOS兼容性:确保坐标有效
|
// iOS兼容性:确保坐标有效
|
||||||
if (!isNaN(canvasX) && !isNaN(canvasY) && isFinite(canvasX) && isFinite(canvasY)) {
|
if (
|
||||||
|
!isNaN(canvasX) &&
|
||||||
|
!isNaN(canvasY) &&
|
||||||
|
isFinite(canvasX) &&
|
||||||
|
isFinite(canvasY)
|
||||||
|
) {
|
||||||
ctx.arc(canvasX, canvasY, 2.5, 0, 2 * Math.PI);
|
ctx.arc(canvasX, canvasY, 2.5, 0, 2 * Math.PI);
|
||||||
validPoints++;
|
validPoints++;
|
||||||
}
|
}
|
||||||
@@ -344,13 +339,17 @@ export function drawKDEHeatmap(canvasId, width, height, points, options = {}) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx.draw(false, () => {
|
ctx.draw(
|
||||||
|
false,
|
||||||
|
() => {
|
||||||
console.log("KDE热力图绘制完成(缓存)");
|
console.log("KDE热力图绘制完成(缓存)");
|
||||||
resolve();
|
resolve();
|
||||||
}, (error) => {
|
},
|
||||||
|
(error) => {
|
||||||
console.error("KDE热力图绘制失败(缓存):", error);
|
console.error("KDE热力图绘制失败(缓存):", error);
|
||||||
reject(new Error("Canvas绘制失败(缓存): " + error));
|
reject(new Error("Canvas绘制失败(缓存): " + error));
|
||||||
});
|
}
|
||||||
|
);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -389,7 +388,12 @@ export function drawKDEHeatmap(canvasId, width, height, points, options = {}) {
|
|||||||
const canvasY = normalizedY * height;
|
const canvasY = normalizedY * height;
|
||||||
|
|
||||||
// iOS兼容性:确保坐标有效
|
// iOS兼容性:确保坐标有效
|
||||||
if (!isNaN(canvasX) && !isNaN(canvasY) && isFinite(canvasX) && isFinite(canvasY)) {
|
if (
|
||||||
|
!isNaN(canvasX) &&
|
||||||
|
!isNaN(canvasY) &&
|
||||||
|
isFinite(canvasX) &&
|
||||||
|
isFinite(canvasY)
|
||||||
|
) {
|
||||||
ctx.arc(canvasX, canvasY, 2.5, 0, 2 * Math.PI);
|
ctx.arc(canvasX, canvasY, 2.5, 0, 2 * Math.PI);
|
||||||
validPoints++;
|
validPoints++;
|
||||||
}
|
}
|
||||||
@@ -402,13 +406,17 @@ export function drawKDEHeatmap(canvasId, width, height, points, options = {}) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 执行绘制 - iOS兼容性优化
|
// 执行绘制 - iOS兼容性优化
|
||||||
ctx.draw(false, () => {
|
ctx.draw(
|
||||||
|
false,
|
||||||
|
() => {
|
||||||
console.log("KDE热力图绘制完成");
|
console.log("KDE热力图绘制完成");
|
||||||
resolve();
|
resolve();
|
||||||
}, (error) => {
|
},
|
||||||
|
(error) => {
|
||||||
console.error("KDE热力图绘制失败:", error);
|
console.error("KDE热力图绘制失败:", error);
|
||||||
reject(new Error("Canvas绘制失败: " + error));
|
reject(new Error("Canvas绘制失败: " + error));
|
||||||
});
|
}
|
||||||
|
);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("KDE热力图绘制失败:", error);
|
console.error("KDE热力图绘制失败:", error);
|
||||||
reject(error);
|
reject(error);
|
||||||
@@ -459,39 +467,5 @@ export function generateKDEHeatmapImage(
|
|||||||
*/
|
*/
|
||||||
export function clearHeatmapCache() {
|
export function clearHeatmapCache() {
|
||||||
heatmapCache.clear();
|
heatmapCache.clear();
|
||||||
console.log('热力图缓存已清除');
|
console.log("热力图缓存已清除");
|
||||||
}
|
}
|
||||||
|
|
||||||
export const generateHeatMapData = (width, height, amount = 100) => {
|
|
||||||
const data = [];
|
|
||||||
const centerX = 0.5; // 中心点X坐标
|
|
||||||
const centerY = 0.5; // 中心点Y坐标
|
|
||||||
|
|
||||||
for (let i = 0; i < amount; i++) {
|
|
||||||
let x, y;
|
|
||||||
|
|
||||||
// 30%的数据集中在中心区域(高斯分布)
|
|
||||||
if (Math.random() < 0.3) {
|
|
||||||
// 使用正态分布生成中心区域的数据
|
|
||||||
const angle = Math.random() * 2 * Math.PI;
|
|
||||||
const radius = Math.sqrt(-2 * Math.log(Math.random())) * 0.15; // 标准差0.15
|
|
||||||
x = centerX + radius * Math.cos(angle);
|
|
||||||
y = centerY + radius * Math.sin(angle);
|
|
||||||
} else {
|
|
||||||
x = Math.random() * 0.8 + 0.1; // 0.1-0.9范围
|
|
||||||
y = Math.random() * 0.8 + 0.1;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 确保坐标在0-1范围内
|
|
||||||
x = Math.max(0.05, Math.min(0.95, x));
|
|
||||||
y = Math.max(0.05, Math.min(0.95, y));
|
|
||||||
|
|
||||||
data.push({
|
|
||||||
x: parseFloat(x.toFixed(3)),
|
|
||||||
y: parseFloat(y.toFixed(3)),
|
|
||||||
ring: Math.floor(Math.random() * 5) + 6, // 6-10环
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return data;
|
|
||||||
};
|
|
||||||
|
|||||||
Reference in New Issue
Block a user