3

我正在使用d3-force布局构建“弹簧”。我想通过用户输入来操纵它的属性,如“强度”和“距离”。为此,我目前正在使用“输入范围滑块”。为了更好地理解,我在 codepen 上设置了一个工作草案,这个问题与此相关:http ://codepen.io/bitHugger/pen/XNqGNE?editors=1010

的HTML:

<input id="strengthElem" step="0.1" type="range" min="0" max="2"/>

我想做这样的事件处理:

let strengthElem = window.document.getElementById('strengthElem');
let strength;

strengthElem.addEventListener('click', function(evt) {
  strength = strengthElem.value;
  console.log('strength', strength);
}, false);

现在,当范围滑块发生某些交互时,我想重新启动或重新计算 d3.simulation 对象。这是我目前的模拟:

let simulation = d3.forceSimulation().nodes(nodes)
    .force("link", d3.forceLink()
        .id(function(d) { return d.index; })
        .strength(function(d) { return 2; })
        .distance(function(d) { return 2; }))
    .force("charge", d3.forceManyBody());

对于强度和距离,这些值当前是硬编码的。我想将其更改为例如:

.strength(function(d) { return strength; })
.distance(function(d) { return distance; })

我试图设置一个 d3.call().on() 函数,但无法让它工作。我想知道如何根据 unser 输入来操纵模拟,这发生在 force() 函数之外/svg 容器之外。

遗憾的是,我无法正常工作,也不知道如何设置适当的 d3 事件侦听器,该侦听器对输入按钮做出反应,然后根据更改的值重新计算力布局。有任何想法吗?

4

2 回答 2

6

与其在原地创建连接力而不保留对力的参考,不如先创建力并将参考传递给模拟。这样,您以后就可以根据滑块的值来操纵力:

// Create as before, but keep a reference for later manipulations.
let linkForce = d3.forceLink()
  .id(function(d) { return d.index; })
  .strength(2)
  .distance(2);

let simulation = d3.forceSimulation().nodes(nodes)
  .force("link", linkForce)
  .force("charge", d3.forceManyBody());

在滑块上注册事件处理程序时,您可能还希望d3.select()使用方便,并使用selection.on().

d3.select('#strengthElem')
  .on('click', function() {
    // Set the slider's value. This will re-initialize the force's strenghts.
    linkForce.strength(this.value);   
    simulation.alpha(0.5).restart();  // Re-heat the simulation
  }, false);

d3.select('#distanceElem')
  .on('click', function(evt) {
    // Set the slider's value. This will re-initialize the force's strenghts
    linkForce.distance(this.value);
    simulation.alpha(0.5).restart();  // Re-heat the simulation
  }, false);

在处理函数内this指向实际的 DOM 元素,从而允许轻松访问滑块的值。现在可以使用先前保存的参考来更新链接力的参数。剩下要做的就是重新加热模拟以继续计算。

查看此代码段以获取工作演示:

'use strict';

var route = [[30, 30],[192, 172],[194, 170],[197, 167],[199, 164],[199, 161],[199, 157],[199, 154],[199, 150],[199, 147],[199, 143],[199, 140],[200, 137],[202, 134],[204, 132],[207, 129],[207, 126],[200, 200]];

let distance = 1;
let createNode = function(id, coords) {
  return {
    radius: 4,
    x: coords[0],
    y: coords[1],
  };
};

let getNodes = (route) => {
  let d = [];
  let i = 0;
  route.forEach(function(coord) {
    if(i === 0 || i === route.length-1) {
      d.push(createNode(i, coord));
      d[i].fx = coord[0];
      d[i].fy = coord[1];
    }
    else {
      d.push(createNode(i, coord));
    }
    ++i;
  });
  return d;
};

let getLinks = (nodes) => {
  let next = 1;
  let prev = 0;
  let obj = [];
  while(next < nodes.length) {
    obj.push({source: prev, target: next, value: 1});
    prev = next;
    ++next;
  }
  return obj;
};

let force = function(route) {
  let width = 900;
  let height = 700;
  let nodes = getNodes(route);
  let links = getLinks(nodes);

  d3.select('#strengthElem')
    .on('click', function() {
      linkForce.strength(this.value);   // Set the slider's value. This will re-initialize the force's strenghts
      simulation.alpha(0.5).restart();  // Re-heat the simulation
    }, false);

  d3.select('#distanceElem')
    .on('click', function(evt) {
      linkForce.distance(this.value);  // Set the slider's value. This will re-initialize the force's strenghts
      simulation.alpha(0.5).restart();  // Re-heat the simulation
    }, false);

  let linkForce = d3.forceLink()
    .id(function(d) { return d.index; })
    .strength(2)
    .distance(2);

  let simulation = d3.forceSimulation().nodes(nodes)
    .force("link", linkForce)
    .force("charge", d3.forceManyBody());

  let svg = d3.select('svg').append('svg')
    .attr('width', width)
    .attr('height', height);

  let link = svg.append("g")
      .attr('class', 'link')
    .selectAll('.link')
    .data(links)
    .enter().append('line')
      .attr("stroke-width", 1);

  let node = svg.append("g")
      .attr("class", "nodes")
    .selectAll("circle")
    .data(nodes)
    .enter().append("circle")
      .attr("r", function(d) { return d.radius; })
      .attr("fill", function(d) { return '#fabfab'; });

  simulation.nodes(nodes).on("tick", ticked);
  simulation.force("link").links(links);

  function ticked() {
    link
        .attr("x1", function(d) { return d.source.x; })
        .attr("y1", function(d) { return d.source.y; })
        .attr("x2", function(d) { return d.target.x; })
        .attr("y2", function(d) { return d.target.y; });
    node
        .attr("cx", function(d) { return d.x; })
        .attr("cy", function(d) { return d.y; });
  }
};

force(route);
.link {
    stroke: #777;
    stroke-width: 2px;
}

.links line {
  stroke: #999;
  stroke-opacity: 0.6;
}

.nodes circle {
  stroke: #fff;
  stroke-width: 1.5px;
}
<script src="https://d3js.org/d3.v4.js"></script>
<div>Strength <input id="strengthElem" step="0.1" type="range" min="0" max="2"/></div>
<div>Distance <input id="distanceElem" step="1" type="range" min="0" max="50"/></div>

<svg style="width: 900; height: 700;"></svg>

我也相应地更新了codepen

于 2016-12-07T22:24:48.700 回答
1

一种方法是删除 svg 的内容并使用所需的常量重新绘制它。

我不明白您在哪里卡住了,因为我只更改了几行,就像您在问题中所说的那样。

在您的点击处理程序中,我清除了 svg 的内容并调用了“draw”函数:

strengthElem.addEventListener('click', function(evt) {
  strength = strengthElem.value;
  console.log('strength', strength);
  d3.select('svg').selectAll("*").remove();
  force(route);
}, false);

将您的配置变量移动到全局范围:

var distance = 1;
let distElem = window.document.getElementById('distanceElem');
let strengthElem = window.document.getElementById('strengthElem');
var strength = strengthElem.value;
distance = distElem.value;

就像你说的我已经更改为返回参数:

 .strength(function(d) { return strength; })
 .distance(function(d) { return distance; }))

完整示例:http ://codepen.io/anon/pen/ObZYLo?editors=1010

于 2016-12-07T18:21:19.670 回答