如何生成间距不小于指定值的随机数?d3.js不使用force()实现无触碰圆
嘿,这个问题我刚好有思路!咱们分两部分来解决——先搞定满足间距要求的随机数生成,再把这个逻辑延伸到D3.js里绘制互不触碰的圆,而且完全不用force布局~
一、生成区间[a, b]内满足最小间距y的x个随机数
首先得明确一个前提:*必须满足 (x-1)y ≤ b - a,不然区间里根本塞不下x个间距至少为y的数,第一步一定要先做可行性校验。
我推荐用「空间分配法」,效率比拒绝采样高得多,步骤如下:
- 先计算出除去强制间距后的「自由分配空间」:
freeSpace = b - a - (x-1)*y - 生成x个0到freeSpace之间的随机数,排序后,每个数依次加上
i*y(i从0到x-1),最后再加上区间起点a,这样就保证了每个数之间的间距至少为y,同时所有数都落在[a, b]内。
直接上JavaScript代码:
function generateSpacedRandomNumbers(a, b, x, y) { // 先校验可行性 const minRequiredLength = (x - 1) * y; if (b - a < minRequiredLength) { throw new Error(`无法生成${x}个间距≥${y}的数,区间长度不足!需要至少${minRequiredLength}的长度,当前只有${b - a}`); } const freeSpace = b - a - minRequiredLength; // 生成x个0~freeSpace的随机数 const randoms = Array.from({ length: x }, () => Math.random() * freeSpace); // 排序后添加偏移量 randoms.sort((numA, numB) => numA - numB); return randoms.map((num, index) => a + num + index * y); } // 测试示例:生成0~1000之间的3个间距≥200的随机数 console.log(generateSpacedRandomNumbers(0, 1000, 3, 200));
二、用D3.js绘制互不触碰的圆(无需force布局)
你提到y是最大圆的半径,那要保证圆不触碰,任意两个圆心之间的距离至少为2*y(因为两个最大圆的半径之和是2y)。这里给你两种靠谱的实现方式:
方法1:拒绝采样法(灵活不规则布局)
适合圆的数量不多的场景,每次生成一个新圆心,检查它和所有已存在圆心的距离是否≥2y,同时保证圆完全在画布内:
const width = 800; const height = 600; const maxRadius = 30; // 对应问题中的y const numCircles = 10; // 对应问题中的x // 生成互不重叠的圆心 function generateNonOverlappingCenters(width, height, num, minCenterDistance) { const centers = []; const maxAttempts = 1000; // 防止无限循环 let attempts = 0; while (centers.length < num && attempts < maxAttempts) { attempts++; // 随机生成圆心坐标 const cx = Math.random() * width; const cy = Math.random() * height; // 检查两个条件:1. 和已有圆心距离足够;2. 圆完全在画布内 const isValid = centers.every(center => { const dx = cx - center.cx; const dy = cy - center.cy; return Math.sqrt(dx * dx + dy * dy) >= minCenterDistance; }) && cx >= maxRadius && cx <= width - maxRadius && cy >= maxRadius && cy <= height - maxRadius; if (isValid) { centers.push({ cx, cy, r: maxRadius }); // 如果需要不同半径,可以改成:r: Math.random() * maxRadius + 5(最小半径5) } } if (centers.length < num) { console.warn(`只成功生成了${centers.length}个圆,已达最大尝试次数`); } return centers; } // 创建SVG画布 const svg = d3.select("body") .append("svg") .attr("width", width) .attr("height", height) .style("border", "1px solid #eee"); // 生成圆心数据并绘制圆 const circles = generateNonOverlappingCenters(width, height, numCircles, 2 * maxRadius); svg.selectAll("circle") .data(circles) .enter() .append("circle") .attr("cx", d => d.cx) .attr("cy", d => d.cy) .attr("r", d => d.r) .style("fill", "steelblue") .style("opacity", 0.8);
方法2:网格分配法(高效规则布局)
如果需要生成大量圆,拒绝采样效率太低,推荐用网格法:把画布分成边长为2*y的网格,每个网格内随机放一个圆心,这样天然保证了所有圆心的距离≥2y,不会重叠:
function generateGridBasedCenters(width, height, num, gridSize) { const cols = Math.floor(width / gridSize); const rows = Math.floor(height / gridSize); const gridPoints = []; // 遍历所有网格,生成每个网格内的随机点 for (let i = 0; i < rows; i++) { for (let j = 0; j < cols; j++) { const cx = j * gridSize + Math.random() * gridSize; const cy = i * gridSize + Math.random() * gridSize; gridPoints.push({ cx, cy }); } } // 随机打乱网格点,取前num个 const shuffledPoints = d3.shuffle(gridPoints); return shuffledPoints.slice(0, num).map(d => ({ ...d, r: maxRadius })); } // 使用方法: // const circles = generateGridBasedCenters(width, height, numCircles, 2 * maxRadius);
两种方法各有优势:拒绝采样能得到更自然的不规则布局,网格法效率更高,适合批量生成。
内容的提问来源于stack exchange,提问作者MagTun
相关产品推荐
相关产品推荐

