D3 Force Network - 如何在模拟后应用功能而不仅仅是在拖动后

问题描述 投票:0回答:1

我有一个用力网络绘制的简单网络,我希望用户在绘制后单独拖动节点。目前,这仅在用户拖动每个节点后才起作用。第一次拖动时,拖动一个节点将导致其他节点移动。我希望当拖动另一个节点时所有节点都保持在原位。我尝试在

simulation.stop()
之后添加拖动功能,但它不起作用。 下面的例子:

const networkData = {
  "nodes": [{
      "id": 1,
      "name": "A",
      isSource: true,
      "sourceX": 100,
      "sourceY": 100
    },
    {
      "id": 2,
      "name": "B",
      isSource: false
    },
    {
      "id": 3,
      "name": "C",
      isSource: true,
      "sourceX": 150,
      "sourceY": 150
    },
    {
      "id": 4,
      "name": "D",
      isSource: false
    },
    {
      "id": 5,
      "name": "E",
      isSource: true,
      "sourceX": 200,
      "sourceY": 200
    },
    {
      "id": 6,
      "name": "F",
      isSource: false
    }
  ],
  "links": [{
      "source": 1,
      "target": 2
    },
    {
      "source": 3,
      "target": 4
    },
    {
      "source": 5,
      "target": 6
    }
  ]
}

const svg = d3.select("#my_dataviz");

const sourceNodes = networkData.nodes.filter(node => {
  return node.isSource === true;
});

const targetNodes = networkData.nodes.filter(node => {
  return !node.isSource;
});

const link = svg
  .selectAll(".link")
  .data(networkData.links)
  .enter()
  .append("line")
  .attr("class", "link")
  .attr("stroke-width", 2)
  .attr("stroke", 'grey');

const targetNodeSelection = svg
  .selectAll(".node")
  .data(targetNodes)
  .enter()
  .append("circle")
  .attr("class", "node")
  .attr("r", 10)
  .attr("fill", 'blue');

// Separate selections for source nodes
const sourceNodeSelection = svg
  .selectAll(".source-node")
  .data(sourceNodes)
  .enter()
  .append("circle")
  .attr("class", "source-node")
  .attr("r", 1)
  .attr("fill", 'white')
  .attr("cx", d => d.sourceX) // Set fixed X position for source nodes
  .attr("cy", d => d.sourceY); // Set fixed Y position for source nodes

const maxIterations = 10;
let iterationCount = 0; // Initialize the iteration counter


// Create a simulation for the target nodes only
const simulation = d3.forceSimulation(networkData.nodes)
  .force("link", d3.forceLink(networkData.links)
    .id(function(d) {
      return d.id;
    })
    .links(networkData.links)
  )
  .force("center", d3.forceCenter(400 / 2, 400 / 2))
  .force('collision', d3.forceCollide().radius(20))
  .on("tick", function() {
    tick();

    iterationCount++;
    if (iterationCount >= maxIterations) {

      simulation.stop();


    }
  });



function tick() {
  link
    .attr("x1", function(d) {
      return d.source.sourceX;
    })
    .attr("y1", function(d) {
      return d.source.sourceY;
    })
    .attr("x2", function(d) {
      return d.target.x;
    })
    .attr("y2", function(d) {
      return d.target.y;
    });

  targetNodeSelection
    .attr("cx", function(d) {
      return d.x;
    })
    .attr("cy", function(d) {
      return d.y;
    });

  sourceNodeSelection
    .attr("cx", function(d) {
      return d.sourceX;
    })
    .attr("cy", function(d) {
      return d.sourceY;
    });

}

function clamp(x, lo, hi) {
  return x < lo ? lo : x > hi ? hi : x;
}


function dragged(event, d) {
  d.fx = clamp(event.x, 0, 400);
  d.fy = clamp(event.y, 0, 400);
  simulation.alpha(0).restart();
}

const drag = d3
  .drag()
  .on("drag", dragged);

targetNodeSelection.call(drag);
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/6.7.0/d3.min.js" integrity="sha512-cd6CHE+XWDQ33ElJqsi0MdNte3S+bQY819f7p3NUHgwQQLXSKjE4cPZTeGNI+vaxZynk1wVU3hoHmow3m089wA==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
<svg id="my_dataviz" height='400' width='400'></svg>

javascript d3.js
1个回答
0
投票

拖动时,您的模拟仍然遵循您的

center
collision
力。您可以在初始模拟完成后简单地将它们归零。

const networkData = {
  "nodes": [{
      "id": 1,
      "name": "A",
      isSource: true,
      "sourceX": 100,
      "sourceY": 100
    },
    {
      "id": 2,
      "name": "B",
      isSource: false
    },
    {
      "id": 3,
      "name": "C",
      isSource: true,
      "sourceX": 150,
      "sourceY": 150
    },
    {
      "id": 4,
      "name": "D",
      isSource: false
    },
    {
      "id": 5,
      "name": "E",
      isSource: true,
      "sourceX": 200,
      "sourceY": 200
    },
    {
      "id": 6,
      "name": "F",
      isSource: false
    }
  ],
  "links": [{
      "source": 1,
      "target": 2
    },
    {
      "source": 3,
      "target": 4
    },
    {
      "source": 5,
      "target": 6
    }
  ]
}

const svg = d3.select("#my_dataviz");

const sourceNodes = networkData.nodes.filter(node => {
  return node.isSource === true;
});

const targetNodes = networkData.nodes.filter(node => {
  return !node.isSource;
});

const link = svg
  .selectAll(".link")
  .data(networkData.links)
  .enter()
  .append("line")
  .attr("class", "link")
  .attr("stroke-width", 2)
  .attr("stroke", 'grey');

const targetNodeSelection = svg
  .selectAll(".node")
  .data(targetNodes)
  .enter()
  .append("circle")
  .attr("class", "node")
  .attr("r", 10)
  .attr("fill", 'blue');

// Separate selections for source nodes
const sourceNodeSelection = svg
  .selectAll(".source-node")
  .data(sourceNodes)
  .enter()
  .append("circle")
  .attr("class", "source-node")
  .attr("r", 1)
  .attr("fill", 'white')
  .attr("cx", d => d.sourceX) // Set fixed X position for source nodes
  .attr("cy", d => d.sourceY); // Set fixed Y position for source nodes

const maxIterations = 10;
let iterationCount = 0; // Initialize the iteration counter


// Create a simulation for the target nodes only
const simulation = d3.forceSimulation(networkData.nodes)
  .force("link", d3.forceLink(networkData.links)
    .id(function(d) {
      return d.id;
    })
    .links(networkData.links)
  )
  .force("center", d3.forceCenter(400 / 2, 400 / 2))
  .force('collision', d3.forceCollide().radius(20))
  .on("tick", function() {
    tick();

    iterationCount++;
    if (iterationCount >= maxIterations) {

      simulation.stop();
      
      simulation
        .force("center", null)
        .force('collision', null);

    }
  });



function tick() {
  link
    .attr("x1", function(d) {
      return d.source.sourceX;
    })
    .attr("y1", function(d) {
      return d.source.sourceY;
    })
    .attr("x2", function(d) {
      return d.target.x;
    })
    .attr("y2", function(d) {
      return d.target.y;
    });

  targetNodeSelection
    .attr("cx", function(d) {
      return d.x;
    })
    .attr("cy", function(d) {
      return d.y;
    });

  sourceNodeSelection
    .attr("cx", function(d) {
      return d.sourceX;
    })
    .attr("cy", function(d) {
      return d.sourceY;
    });

}

function clamp(x, lo, hi) {
  return x < lo ? lo : x > hi ? hi : x;
}


function dragged(event, d) {
  d.fx = clamp(event.x, 0, 400);
  d.fy = clamp(event.y, 0, 400);
  simulation.alpha(0).restart();
}

const drag = d3
  .drag()
  .on("drag", dragged);

targetNodeSelection.call(drag);
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/6.7.0/d3.min.js" integrity="sha512-cd6CHE+XWDQ33ElJqsi0MdNte3S+bQY819f7p3NUHgwQQLXSKjE4cPZTeGNI+vaxZynk1wVU3hoHmow3m089wA==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
<svg id="my_dataviz" height='400' width='400'></svg>

© www.soinside.com 2019 - 2024. All rights reserved.