Javascript 如何设置气泡的边界并按命令触发移动?

Javascript 如何设置气泡的边界并按命令触发移动?,javascript,d3.js,Javascript,D3.js,我在D3中有一个气泡图,我用它来显示每组有多少个气泡。这个版本一开始大约有500个气泡,而我的完整版本大约有3000个 我在二维空间中挣扎。我试图让气泡在不在状态之间转换的情况下保持不动,我还试图让气泡创建一个矩形 这是气泡图的一部分。我将添加代码,然后检查我尝试过的内容 这是我的泡泡代码 // Initial time and quarter let time_so_far = 0; let quarter = 0; const tick_time = 100 // Forces const

我在D3中有一个气泡图,我用它来显示每组有多少个气泡。这个版本一开始大约有500个气泡,而我的完整版本大约有3000个

我在二维空间中挣扎。我试图让气泡在不在状态之间转换的情况下保持不动,我还试图让气泡创建一个矩形

这是气泡图的一部分。我将添加代码,然后检查我尝试过的内容

这是我的泡泡代码

// Initial time and quarter
let time_so_far = 0;
let quarter = 0;
const tick_time = 100

// Forces
const radius = 1.5
const padding1 = 10;
const padding2 = 2;
const strength = 50
const veloc_decay = .99
const alpha = .05
const alpha_decay = 0
const alpha_min = 0.001
const alpha_Collision = .08;
const charge_strength = -.5
const charge_theta = .9

// Load data
Promise.all([
    d3.tsv("stages.tsv", d3.autoType),
    d3.tsv("customers.tsv", d3.autoType),
])

   // Once data is loaded...
   .then(function(files){
  // Prepare the data...

  const stage_data = files[0]
  const customer_data = files[1]

    // Consolidate stages by id.
    stage_data.forEach(d => {
        if (d3.keys(stakeholders).includes(d.id+"")) {
            stakeholders[d.id+""].push(d);
        } else {
            stakeholders[d.id+""] = [d];
        }
    });

    // Consolidate customers by week.
    customer_data.forEach(d => {
        if (d3.keys(customers).includes(d.week+"")) {
            customers[d.week+""].push(d);
        } else {
            customers[d.week+""] = [d];
        }
    });

    // Create node data.
    var nodes = d3.keys(stakeholders).map(function(d) {
        // Initialize count for each group.

        groups[stakeholders[d][0].stage].cnt += 1;

        return {
            id: "node"+d,
            x: groups[stakeholders[d][0].stage].x + Math.random(),
            y: groups[stakeholders[d][0].stage].y + Math.random(),
            r: radius,
            color: groups[stakeholders[d][0].stage].color,
            group: stakeholders[d][0].stage,
            timeleft: stakeholders[d][0].weeks,
            istage: 0,
            stages: stakeholders[d]
        }
    });

    // Circle for each node.
    const circle = svg.append("g")
        .selectAll("circle")
        .data(nodes)
        .join("circle")
          .attr("cx", d => d.x)
          .attr("cy", d => d.y)
          .attr("fill", d => d.color)
          .attr("r", d => d.r);

     // Forces
     const simulation = d3.forceSimulation(nodes)
         // .force("bounds", boxingForce)
         .force("x", d => d3.forceX(d.x))
         .force("y", d => d3.forceY(d.y))
         .force("cluster", forceCluster())
         .force("collide", forceCollide())
         .force("charge", d3.forceManyBody().strength(charge_strength).theta(charge_theta))
         // .force('center', d3.forceCenter(center_x, center_y))
         .alpha(alpha)
         .alphaDecay(alpha_decay)
         .alphaMin(alpha_min)
         .velocityDecay(veloc_decay)

     // Adjust position of circles.
      simulation.on("tick", () => {
          circle
          .attr("cx", d => Math.max(r, Math.min(500 - r, d.x)))
          .attr("cy", d => Math.max(r, Math.min(500 - r, d.y)))
          .attr("fill", d => groups[d.group].color);
          });

      // Force to increment nodes to groups.
      function forceCluster() {
        let nodes;

        function force(alpha) {
          const l = alpha * strength;
          for (const d of nodes) {
            d.vx -= (d.x - groups[d.group].x) * l;
            d.vy -= (d.y - groups[d.group].y) * l;
          }
        }
        force.initialize = _ => nodes = _;

        return force;
      }

      // Force for collision detection.
      function forceCollide() {
        let nodes;
        let maxRadius;

        function force() {
          const quadtree = d3.quadtree(nodes, d => d.x, d => d.y);
          for (const d of nodes) {
            const r = d.r + maxRadius;
            const nx1 = d.x - r, ny1 = d.y - r;
            const nx2 = d.x + r, ny2 = d.y + r;
            quadtree.visit((q, x1, y1, x2, y2) => {

              if (!q.length) do {
                if (q.data !== d) {
                  const r = d.r + q.data.r + (d.group === q.data.group ? padding1 : padding2);
                  let x = d.x - q.data.x, y = d.y - q.data.y, l = Math.hypot(x, y);
                  if (l < r) {
                    l = (l - r) / l * alpha_Collision;
                    d.x -= x *= l, d.y -= y *= l;
                    q.data.x += x, q.data.y += y;
                  }
                }
              } while (q = q.next);
              return x1 > nx2 || x2 < nx1 || y1 > ny2 || y2 < ny1;
            });
          }
        }

        force.initialize = _ => maxRadius = d3.max(nodes = _, d => d.r) + Math.max(padding1, padding2);

        return force;
      }

      // Make time pass. Adjust node stage as necessary.
      function timer() {
          // Ticker...

          nodes.forEach(function(o,i) {

              o.timeleft -= 1;
              if (o.timeleft == 0 && o.istage < o.stages.length-1) {
                  // Decrease counter for previous group.
                  groups[o.group].cnt -= 1;

                  // Update current node to new group.
                  o.istage += 1;
                  o.group = o.stages[o.istage].stage;
                  o.timeleft = o.stages[o.istage].weeks;

                  // Increment counter for new group.
                  groups[o.group].cnt += 1;
              }
          });

          // Previous quarter
          quarter = Math.floor(time_so_far / 12)

          // Increment time.
          time_so_far += 1;

          // goes by week, timer updates every quarter
          var current_quarter = Math.floor(time_so_far / 13) + 1

          // stop on the last quarter
          if(time_so_far == d3.keys(customers).length) { return }

          d3.select("#timecount .cnt").text(quarters[current_quarter]);


           // update counter
           d3.selectAll(".counter")
             .text(d => d.cnt)

           // Define length of a tick
           d3.timeout(timer, tick_time);

      } // @end timer()

      timer()


}); // end TSV
这使圆圈在需要移动时有三个刻度的能量。否则,他们就得不到任何好处。(上一次编辑时,此变通方法仅适用于少量节点,但随着节点数量的增加,它将失败)

             for (var i = 0, n = nodes.length, node, k = alpha * strength; i < n; ++i) {
                node = nodes[i];
                if(node.statechange <= 0) { continue }
                node.vx -= (node.x - groups[node.group].x) * k;
                node.vy -= (node.y - groups[node.group].y) * k;
}