从范围滑块重新启动用户输入的d3模拟 [英] Restart d3 simulation on user input from range slider

查看:129
本文介绍了从范围滑块重新启动用户输入的d3模拟的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

I am building a "spring" using the d3-force layout. I want to manipulate it's properties like "strength" and "distance" via user input. For that I am currently using an "input range slider". For better understanding I set up a working draft on codepen where this question is related to: 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对象。这是我目前的模拟:

Now I would like to restart or recalculate the d3.simulation object when some interaction happens with the range slider. This is my current 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());

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

For the strength and the distance the values are currently hard coded.I would like to change it to e.g.:

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

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

I tried to setup a d3.call().on() function but could not get it working. I wonder how I can manipulate the simulation based on unser input, that happens outside of the force() function / outside of the svg container.

可悲的是我无法得到工作的东西,我不知道如何设置一个正确的d3事件监听器,对输入按钮做出反应,然后根据更改的值重新计算力布局。任何想法?

Sadly I can't get something working and I don't know how to setup a proper d3 event listener that reacts on the input button and then recalculates the force layout based on the changed values. Any ideas?

推荐答案

你应该首先创建一个而不是在不保持对力的引用的情况下就地创建链接力强制并将参考传递给模拟。这样,您稍后就可以根据滑块的值操纵力:

Instead of creating a link force in-place without keeping a reference to the force, you should first create the force and just pass the reference to the simulation. That way, you are later on able to manipulate the force according to your sliders' values:

// 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()易于使用,并使用 <分配函数code> selection.on()

When registering the event handlers on the sliders you may also want to use d3.select() for ease of use, and assign the functions using 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);

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

Within the handler functions this points to the actual DOM element, whereby allowing to easily access the slider's value. The link force's parameters may now be updated using the previously kept reference. All that is left to do, is to re-heat the simulation to continue its calculations.

看看这个工作演示的片段:

Have a look at this snippet for a working demo:

'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 因此。

这篇关于从范围滑块重新启动用户输入的d3模拟的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆