2015-05-01 5 views
4

Я собрал следующий jfiddle, основанный на некотором коде, который я видел в книге - http://jsfiddle.net/hiwilson1/o3gwejbx/2. В общем, я следую тому, что происходит, но есть несколько аспектов, за которыми я не следую.d3 добавление и удаление узлов с силой

svg.on("mousemove", function() { 

    var point = d3.mouse(this), 
     node = {x: point[0], y: point[1]}; 

    svg.append("circle") 
     .data([node]) 
      .attr("r", 1e-6) 
      .transition() 
      .attr("r", 4.5) 
      .transition() 
      .delay(1000) 
      .attr("r", 1e-6) 
      .remove(); 
     force.nodes().push(node); 
     force.start(); 
}); 

Здесь мы строим нашу новую точку данных и добавляем круг с атрибутами x и y этой точки данных. Я перехожу к радиусу узлов, а затем удаляю() его. Вот бит, который я не соблюдаю - перед удалением его точка данных добавляется в массив force.nodes(), а не сам круг, только точка данных. Затем я начинаю() силу.

  1. Почему мы удаляем круг, прежде чем нажимать точку данных в массив force.nodes().
  2. Почему мы нажимаем только точку данных в массив force.nodes(), не нужна ли ссылка на круг? Или круг как-то просто визуальное представление точки данных и манипулирование точкой данных манипулирует кругом?
  3. Почему мы запускаем силу() при каждом перемещении мыши? Я думал, что сила() зациклилась на заднем плане и не нужно перезапускать после добавления каждого узла?

UPDATE: Я думаю, что я в конечном счете ищу ясность, это то, что на самом деле делает маска force() на капоте.

Теория: вы даете силовой макете массив узлов. Для каждого элемента данных x и y либо предоставляются, либо произвольно назначаются. Как только сила запускается, массив постоянно пересчитывается для перемещения этих компонентов x и y в соответствии с применяемыми дополнительными свойствами силы, такими как гравитация и заряд. Силовая компоновка не имеет ничего общего с визуализацией самих кругов - вам нужно продолжать рисовать их/обновлять свои местоположения x и y, чтобы отражать позиции значений массива, которые манипулирует силой.

Правильно ли это?

ответ

2

Я думаю, что это просто компактный способ сделать это, но не очень хороший пример для обучения ... Во-первых, данные узлов никогда не удаляются, а во-вторых, этот метод является немного императивом и на самом деле не управляется данными ,

Узлы число в этой демонстрации длина свойство массива узлов

\t \t var w = 900, h = 400, nodes = [], 
 
\t \t \t \t \t \t indx = 0, show = false, 
 

 
\t \t svg = d3.select("body").append("svg") 
 
\t \t .attr("width", w) 
 
\t \t .attr("height", h), 
 

 
\t \t force = d3.layout.force() 
 
\t \t .nodes(nodes) 
 
\t \t .size([w, h]) 
 
\t \t .gravity(0) 
 
\t \t .charge(1) 
 
\t \t .friction(0.7), 
 

 
\t \t outputDiv = d3.select("body").insert("div", "svg").attr("id", "output"); 
 

 
\t \t $("#toggleShow").click(function (e) { 
 
\t \t \t d3.selectAll(".dead").attr("opacity", (show = !show) ? 0.2 : 0) 
 
\t \t \t $(this).text((show ? "don't " : "") + "show dead nodes") 
 
\t \t }); 
 
\t \t $("#clear").click(function (e) { 
 
\t \t \t nodes.length = 0; 
 
\t \t \t d3.selectAll("circle").remove(); 
 
\t \t }); 
 

 

 
\t \t force.on("tick", function (e) { 
 
\t \t \t outputDiv.text("alpha:\t" + d3.format(".3f")(force.alpha()) 
 
\t \t \t \t + "\tnodes:\t" + force.nodes().length) 
 
\t \t \t var circles = svg.selectAll("circle").data(nodes, function (d) { return d.id }) 
 

 
\t \t \t //ENTER 
 
\t \t \t // direct 
 
\t \t \t // data is there but the circle has been deleted by completion of transition 
 
\t \t \t // replace the previously live node with a dead one 
 
\t \t \t // idiomatic 
 
\t \t \t // always zero size 
 
\t \t \t circles.enter().append("circle") 
 
\t \t \t \t .attr("r", 4.5) 
 
\t \t \t \t .attr("class", "dead") 
 
\t \t \t .attr("opacity", show ? 0.2 : 0); 
 
\t \t \t //UPDATE+ENTER 
 
\t \t \t circles 
 
\t \t \t \t .attr("cx", function (d) { return d.x; }) 
 
\t \t \t \t .attr("cy", function (d) { return d.y; }); 
 
\t \t }); 
 

 
\t \t svg.on("mousemove", onMove) 
 
\t \t .on("touchmove", onMove) 
 
\t \t .on("touchstart", onMove); 
 

 
\t \t function onMove() { 
 
\t \t d3.event.preventDefault(); 
 
\t \t d3.event.stopPropagation(); 
 
\t \t updateMethod.call(this) 
 
\t \t } 
 

 
\t \t function direct() { 
 
\t \t \t return function() { 
 
\t \t \t var pointM = d3.mouse(this), pointT = d3.touches(this), 
 
\t \t \t \t \t \t point = pointT.length ? pointT[0] : pointM, 
 
\t \t \t \t \t \t node = { x: point[0], y: point[1], id: indx++ }; 
 

 
\t \t \t \t svg.append("circle") 
 
\t \t \t \t \t \t \t .data([node]) 
 
\t \t \t \t \t \t \t \t \t .attr("class", "alive") 
 
\t \t \t \t \t \t \t \t \t .attr("r", 1e-6) 
 
\t \t \t \t \t \t \t \t \t .transition() 
 
\t \t \t \t \t \t \t \t \t .attr("r", 4.5) 
 
\t \t \t \t \t \t \t \t \t .transition() 
 
\t \t \t \t \t \t \t \t \t .delay(1000) 
 
\t \t \t \t \t \t \t \t \t .attr("r", 1e-6) 
 
\t \t \t \t \t \t \t \t \t .remove(); 
 
\t \t \t \t force.nodes().push(node); 
 
\t \t \t \t force.start(); 
 
\t \t \t } 
 
\t \t } /*direct*/ 
 

 

 
\t \t updateMethod = direct();
\t body, html { 
 
\t \t \t width:100%; 
 
\t \t \t height:100%; 
 
\t } 
 
\t \t #vizcontainer { 
 
\t \t \t width: 100%; 
 
\t \t \t height: 100%; 
 
\t \t } 
 

 
\t svg { 
 
\t \t \t outline: 1px solid red; 
 
\t \t \t width: 100%; 
 
\t \t \t height: 100%; 
 
\t \t } 
 

 
\t \t #output { 
 
\t \t \t pointer-events: none; 
 
\t \t \t display: inline-block; 
 
\t \t \t z-index: 1; 
 
\t \t \t margin: 10px; 
 
\t \t } 
 

 
\t \t button { 
 
\t \t \t display: inline-block; 
 
\t \t \t margin: 10px; 
 
\t \t } 
 
\t \t .dead { 
 
\t \t \t fill: white; 
 
\t \t \t stroke: black; 
 
\t \t \t stroke-width: 1px; 
 
\t \t }
<button id="toggleShow" name="">show dead nodes</button> 
 
\t <button id="clear" name="clear">clear</button> 
 
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script> 
 
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>

Даже если узлы были удалены переходами, они по-прежнему существует в nodes массиве и, следовательно, все еще действует в расчете силы. Вы можете видеть это как своего рода эффект черной дыры, когда растет количество (данных) узлов: раковина начинает развиваться из-за растущего скопления невидимых узлов.

В ответ на ваши вопросы ...

  1. Почему нет? Он работает в любом случае ...
  2. Как вы отметили в своем обновлении, макет предоставляет только положения объектов и не имеет ссылки на сами объекты. Да, это вам решать.
  3. Если вы используете инструменты разработчика браузера, чтобы посмотреть на элементы массива, возвращенные force.nodes(), вы увидите, что есть много состояний, добавленных над исходными x и y членами, есть также состояние, закрытое в d3.force объект, такой как расстояния, силы и заряды. Все это нужно настроить где-то и не удивительно, это делается в force.start(). Вот почему вы должны звонить force.start() каждый раз, когда вы меняете структуру данных. На самом деле нетрудно отследить этот материал, если вы RTFC, вот как вы узнаете, что находится под капотом.

с точки зрения моделей, это было бы более идиоматических для d3 ...

;(function() { 
 
     var w = 900, h = 400, nodes = [], touch, 
 

 
      svg = d3.select("#vizcontainer").append("svg") 
 
      .attr("width", w) 
 
      .attr("height", h), 
 

 
      force = d3.layout.force() 
 
      .size([w, h]) 
 
      .gravity(0) 
 
      .charge(1) 
 
      .friction(0.7), 
 

 
      outputDiv = d3.select("body").insert("div", "#vizcontainer").attr("id", "output").attr("class", "output"), 
 
      touchesDiv = d3.select("body").insert("div", "#output").attr("id", "touches") 
 
      .style("margin-right", "10px").attr("class", "output"); 
 

 

 
     force.on("tick", function (e) { 
 

 
     outputDiv.text("alpha:\t" + d3.format(".3f")(force.alpha()) 
 
      + "\tnodes:\t" + force.nodes().length) 
 

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

 
     svg.on("mousemove", onMove); 
 
     svg.on("touchmove", onTouch); 
 
     svg.on("touchstart", onTouch); 
 

 
     function onMove() { 
 
     updateMethod.call(this) 
 
     } 
 
     function onTouch() { 
 
     d3.event.preventDefault(); 
 
     d3.event.stopPropagation(); 
 
     updateMethod.call(this) 
 
     } 
 

 
     function idiomatic() { 
 
     force.nodes(nodes); 
 
     return function() { 
 
      var pointM = d3.mouse(this), pointT = d3.touches(this), 
 
       point = pointT.length ? pointT[0] : pointM, 
 
      node = { x: point[0], y: point[1] }; 
 

 
      //touchesDiv.text(pointT.length ? pointT : "mouse"); 
 

 
      nodes.push(node); 
 

 
      svg.selectAll("circle") 
 
      .data(nodes) 
 
      .enter().append("circle") 
 
      .attr("r", 1e-6) 
 
      .transition("in") 
 
      .attr("r", 4.5) 
 
      .transition("out") 
 
      .delay(1000) 
 
      .attr("r", 1e-6) 
 
      .remove() 
 
      .each("end.out", (function (n) { 
 
      return function (d, i) { 
 
       //console.log("length: " + nodes.length + "\tdeleting " + i) 
 
       var i = nodes.indexOf(n); 
 
       nodes.splice(i, 1) 
 
      } 
 
      })(node)); 
 

 
      force.start(); 
 
     } 
 
     } /*idiomatic*/ 
 

 

 
     updateMethod = idiomatic(); 
 
    })()
body, html { 
 
     width:100%; 
 
     height:100%; 
 
    } 
 
    #vizcontainer { 
 
     width: 100%; 
 
     height: 100%; 
 
    } 
 

 
    svg { 
 
     outline: 1px solid red; 
 
     width: 100%; 
 
     height: 100%; 
 
    } 
 

 
    .output { 
 
     pointer-events: none; 
 
     display: inline-block; 
 
     z-index: 1; 
 
     margin: 10px; 
 
    }
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script> 
 
    <div id="vizcontainer"></div>

Это подмножество часто упоминается общей схеме обновления. В этом случае, однако, рассматривается только выбор ввода, поскольку данные ведут только эту фазу. Поведение выхода предварительно запрограммировано на переходы, поэтому это особый случай, и очистка данных должна определяться временем переходов. Использование события end - один из способов сделать это. Важно отметить, что каждый узел имеет свой индивидуальный переход, так что в этом случае он отлично работает.

И да, ваша теория верна.

0

Первый: Remove() вызывается, когда переход завершен (включая задержку). В этом примере круги растут и снова становятся маленькими. В этот момент он удаляется .remove().

Второе: Этот кусок кода, что выбирает все круги снова каждый клещ силы и движется кругами:

force.on("tick", function() { 
    svg.selectAll("circle") 
    .attr("cx", function (d) {return d.x;}) 
    .attr("cy", function (d) {return d.y;}); 
}); 

Третье: новый круг создается на каждом движении мыши, и добавлено к существующему force.

+0

RE Первое: я имел в виду раньше, а не после. Почему мы удаляем узел, прежде чем добавлять его в массив force.nodes()? Во-вторых: Истинно, он отбирает все круги, тогда зачем нам нужно выражение force.nodes(). Push (node)? В-третьих: Что это значит, «добавлено к существующей силе»? Можете ли вы рассказать об этом? – hwilson1

Смежные вопросы