You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何生成间距不小于指定值的随机数?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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.15 07:44:51