Как изменить макет силы d3 с полигонами Вороного, чтобы инициировать события для сгруппированных элементов?

Цель состоит в том, чтобы объединить симуляцию силы d3, элементы g и полигоны Вороного, чтобы упростить запуск событий на узлах, таких как перетаскивание, наведение курсора мыши, всплывающие подсказки и т. д., с графиком, который может быть динамически измененный. Это соответствует примеру d3 Перетаскивание по кругу IV.

В следующем коде при добавлении атрибута clip path к элементу g и элементам clippath:

  • Почему перетаскивание не срабатывает в ячейках?
  • Почему узлы становятся скрытыми, а пути теряют свой стиль на краях?
  • Как это можно исправить, чтобы перетаскивать узлы и запускать на них события, такие как наведение мыши?

var data = [
  {
    "index" : 0,
      "vx" : 0,
        "vy" : 0,
          "x" : 842,
            "y" : 106
  },
    {
      "index" : 1,
        "vx" : 0,
          "vy" : 0,
            "x" : 839,
              "y" : 56
    },
     {
        "index" : 2,
          "vx" : 0,
            "vy" : 0,
              "x" : 771,
                "y" : 72
      }
]

var svg = d3.select("svg"),
    width = +svg.attr("width"),
    height = +svg.attr("height");
  
var simulation = d3.forceSimulation(data)
	.force("charge", d3.forceManyBody())
	.force("center", d3.forceCenter(width / 2, height / 2))
	.on("tick", ticked);
  
var nodes = svg.append("g").attr("class", "nodes"),
    node = nodes.selectAll("g"),
    paths = svg.append("g").attr("class", "paths"),
    path = paths.selectAll("path");

var voronoi = d3.voronoi()
	.x(function(d) { return d.x; })
	.y(function(d) { return d.y; })
	.extent([[0, 0], [width, height]]);
  
var update = function() {

  node = nodes.selectAll("g").data(data);
    var nodeEnter = node.enter()
  	.append("g")
  	.attr("class", "node")
    .attr("clip-path", function(d, i) { return "url(#clip-" + i + ")"; });
  nodeEnter.append("circle");
  nodeEnter.append("text")
    .text(function(d, i) { return i; });  
  node.merge(nodeEnter); 
  
  path = paths.selectAll(".path")
  .data(data)
  .enter().append("clipPath")
    .attr("id", function(d, i) { return "clip-" + i; })
    .append("path")
    .attr("class", "path");
  
  simulation.nodes(data);
  simulation.restart();

}();
  
function ticked() {
	var node = nodes.selectAll("g");
  var diagram = voronoi(node.data()).polygons();
  
  paths.selectAll("path")
    .data(diagram)
    .enter()
    .append("clipPath")
    .attr("id", function(d, i) { return "clip-" + i; })
    .append("path")
    .attr("class", "path");

  paths.selectAll("path")
    .attr("d", function(d) { return d == null ? null : "M" + d.join("L") + "Z"; });
  
  node.call(d3.drag()
    .on("start", dragstarted)
    .on("drag", dragged)
    .on("end", dragended));  

  node
    .attr("transform", function(d) { return "translate(" + d.x + "," + d.y + ")" });
}

function dragstarted(d) {
  if (!d3.event.active) simulation.alphaTarget(0.3).restart();
  d.fx = d.x;
  d.fy = d.y;
}

function dragged(d) {
  d.fx = d3.event.x;
  d.fy = d3.event.y;
}

function dragended(d) {
  if (!d3.event.active) simulation.alphaTarget(0);
  d.fx = null;
  d.fy = null;
}
svg {
  border: 1px solid #888888;  
}

circle {
  r: 3;
  cursor: move;
  fill: black;
}

.node {
  pointer-events: all;
}

path {
  fill: none;
  stroke: #999;
  pointer-events: all;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/4.5.1/d3.js"></script>
<svg width="400" height="400"></svg>

(Отдельный вопрос, но вложение путей в элементы g, как в элементе Circle Drag IV, приводит к нежелательному расположению путей в стороне от графика.)

В вопросе, связанном с , используя многоугольники вместо путей и путей отсечения, я могу заставить перетаскивание работать, но я пытаюсь использовать версию пути отсечения в качестве сравнения и не уверен, в чем различия, кроме того, что путь отсечения, кажется, предпочитает Майк Босток (создатель d3).


person Cliff Coulter    schedule 19.02.2017    source источник


Ответы (2)


Если цель:

состоит в том, чтобы объединить симуляцию силы d3, элементы g и полигоны Вороного, чтобы упростить триггерные события на узлах, такие как перетаскивание, наведение курсора мыши, всплывающие подсказки и т. д. с графиком, который может динамически обновляться.

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

Первый источник: пример перетаскивания блочного круга Майка Бостока.

Второй источник: пример графика, ориентированного на Силу, Майка Бостока.

Я надеюсь, что этот подход, по крайней мере, поможет достичь вашей цели (я воспользовался им частично, так как у меня возникли трудности с вашим фрагментом). Это должно быть полезно в качестве минимального примера и доказательства концепции.


Как и вы, я буду использовать пример с перетаскиванием круга в качестве основы, а затем попытаюсь включить пример с направленной силой.

Ключевые части графа направленной силы, которые необходимо импортировать, определяют симуляцию:

var simulation = d3.forceSimulation()

Назначение узлов:

 simulation
      .nodes(circle)
      .on("tick", ticked);

( .nodes(graph.nodes) в оригинале )

Инструктирование, что делать в тик:

force.nodes(circles)
 .on('tick',ticked);

Отмеченная функция:

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

( нам не нужна часть ссылки, и мы хотим обновить круги (а не переменную с именем node )

И части, которые попадают в события перетаскивания.

Если мы импортируем все это в сниппет (объединив события перетаскивания, добавив отмеченную функцию, мы получим:

var svg = d3.select("svg"),
    width = +svg.attr("width"),
    height = +svg.attr("height"),
    radius = 32;
    
var simulation = d3.forceSimulation()
    .force("charge", d3.forceManyBody())

var circles = d3.range(20).map(function() {
  return {
    x: Math.round(Math.random() * (width - radius * 2) + radius),
    y: Math.round(Math.random() * (height - radius * 2) + radius)
  };
});

var color = d3.scaleOrdinal()
    .range(d3.schemeCategory20);

var voronoi = d3.voronoi()
    .x(function(d) { return d.x; })
    .y(function(d) { return d.y; })
    .extent([[-1, -1], [width + 1, height + 1]]);

var circle = svg.selectAll("g")
  .data(circles)
  .enter().append("g")
    .call(d3.drag()
        .on("start", dragstarted)
        .on("drag", dragged)
        .on("end", dragended));

var cell = circle.append("path")
  .data(voronoi.polygons(circles))
    .attr("d", renderCell)
    .attr("id", function(d, i) { return "cell-" + i; });

circle.append("clipPath")
    .attr("id", function(d, i) { return "clip-" + i; })
  .append("use")
    .attr("xlink:href", function(d, i) { return "#cell-" + i; });

circle.append("circle")
    .attr("clip-path", function(d, i) { return "url(#clip-" + i + ")"; })
    .attr("cx", function(d) { return d.x; })
    .attr("cy", function(d) { return d.y; })
    .attr("r", radius)
    .style("fill", function(d, i) { return color(i); });
    
simulation
    .nodes(circles)
    .on("tick", ticked);

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

function dragstarted(d) {
  d3.select(this).raise().classed("active", true);
  if (!d3.event.active) simulation.alphaTarget(0.3).restart();
  d.fx = d.x;
  d.fy = d.y;
}

function dragged(d) {
  d3.select(this).select("circle").attr("cx", d.x = d3.event.x).attr("cy", d.y = d3.event.y);
  cell = cell.data(voronoi.polygons(circles)).attr("d", renderCell);
  d.fx = d3.event.x;
  d.fy = d3.event.y;
}

function dragended(d, i) {
  d3.select(this).classed("active", false);
  if (!d3.event.active) simulation.alphaTarget(0);
  d.fx = null;
  d.fy = null;
}

function renderCell(d) {
  return d == null ? null : "M" + d.join("L") + "Z";
}
path {
  pointer-events: all;
  fill: none;
  stroke: #666;
  stroke-opacity: 0.2;
}

.active circle {
  stroke: #000;
  stroke-width: 2px;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/4.5.0/d3.min.js"></script>
<svg width="600" height="400"></svg>

Очевидная проблема заключается в том, что ячейки не обновляются, если нет перетаскивания. Чтобы решить эту проблему, нам просто нужно взять строку, которая обновляет ячейки при перетаскивании, и поместить ее в отмеченную функцию, чтобы она обновлялась по тиканию:

var svg = d3.select("svg"),
    width = +svg.attr("width"),
    height = +svg.attr("height"),
    radius = 32;
    
var simulation = d3.forceSimulation()
    .force("charge", d3.forceManyBody())

var circles = d3.range(20).map(function() {
  return {
    x: Math.round(Math.random() * (width - radius * 2) + radius),
    y: Math.round(Math.random() * (height - radius * 2) + radius)
  };
});

var color = d3.scaleOrdinal()
    .range(d3.schemeCategory20);

var voronoi = d3.voronoi()
    .x(function(d) { return d.x; })
    .y(function(d) { return d.y; })
    .extent([[-1, -1], [width + 1, height + 1]]);

var circle = svg.selectAll("g")
  .data(circles)
  .enter().append("g")
    .call(d3.drag()
        .on("start", dragstarted)
        .on("drag", dragged)
        .on("end", dragended));

var cell = circle.append("path")
  .data(voronoi.polygons(circles))
    .attr("d", renderCell)
    .attr("id", function(d, i) { return "cell-" + i; });

circle.append("clipPath")
    .attr("id", function(d, i) { return "clip-" + i; })
  .append("use")
    .attr("xlink:href", function(d, i) { return "#cell-" + i; });

circle.append("circle")
    .attr("clip-path", function(d, i) { return "url(#clip-" + i + ")"; })
    .attr("cx", function(d) { return d.x; })
    .attr("cy", function(d) { return d.y; })
    .attr("r", radius)
    .style("fill", function(d, i) { return color(i); });

circle.append("text")
    .attr("clip-path", function(d, i) { return "url(#clip-" + i + ")"; })
    .attr("x", function(d) { return d.x; })
    .attr("y", function(d) { return d.y; })
    .attr("dy", '0.35em')
    .attr("text-anchor", function(d) { return 'middle'; })
    .attr("opacity", 0.6)
    .style("font-size", "1.8em")
    .style("font-family", "Sans-Serif")
    .text(function(d, i) { return i; })
    
simulation
    .nodes(circles)
    .on("tick", ticked);

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

  circle.selectAll('text')
    .attr("x", function(d) { return d.x; })
    .attr("y", function(d) { return d.y; });
    
  cell = cell.data(voronoi.polygons(circles)).attr("d", renderCell);
}

function dragstarted(d) {
  d3.select(this).raise().classed("active", true);
  if (!d3.event.active) simulation.alphaTarget(0.3).restart();
  d.fx = d.x;
  d.fy = d.y;
}

function dragged(d) {
  d3.select(this).select("circle").attr("cx", d.x = d3.event.x).attr("cy", d.y = d3.event.y);
  cell = cell.data(voronoi.polygons(circles)).attr("d", renderCell);
  d.fx = d3.event.x;
  d.fy = d3.event.y;
}

function dragended(d, i) {
  d3.select(this).classed("active", false);
  if (!d3.event.active) simulation.alphaTarget(0);
  d.fx = null;
  d.fy = null;
}

function renderCell(d) {
  return d == null ? null : "M" + d.join("L") + "Z";
}
path {
  pointer-events: all;
  fill: none;
  stroke: #666;
  stroke-opacity: 0.2;
}

.active circle {
  stroke: #000;
  stroke-width: 2px;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/4.5.0/d3.min.js"></script>
<svg width="600" height="400"></svg>

update: обновление узлов:

По крайней мере, для меня добавление и удаление узлов усложнилось. Основная проблема заключалась в том, что приведенный выше код переупорядочивал группы svg с помощью d3.selection.raise() в событиях перетаскивания, что могло испортить мой порядок пути клипа, если использовать только приращение элемента данных. Точно так же удаление элементов из середины массива может вызвать проблемы с сопряжением между ячейками, группами и кругами. Это сопряжение было основной проблемой — наряду с обеспечением того, чтобы все добавленные узлы находились в правильном родительском элементе и в правильном порядке.

Чтобы решить проблемы с сопряжением, я использовал новое свойство в данных для использования в качестве идентификатора, а не приращения. Во-вторых, я делаю пару конкретных манипуляций с ячейками при добавлении: убеждаюсь, что они находятся в правильном родительском элементе и что ячейка отображается над кругом в DOM (используя d3.selection.lower()).

Примечание. Мне не удалось удалить круг и сохранить работу вороного с типичным циклом обновления, поэтому я просто воссоздал его для каждого удаления, и поскольку, насколько мне известно, вороной пересчитывается каждый тик, это не должно быть проблемой.

Результат (нажмите, чтобы удалить/добавить, нажмите кнопку, чтобы переключить удаление/добавление):

var svg = d3.select("svg"),
    width = +svg.attr("width"),
    height = +svg.attr("height"),
    radius = 32;

var n = 0;
var circles = d3.range(15).map(function() {
  return {
	n: n++,
    x: Math.round(Math.random() * (width - radius * 2) + radius),
    y: Math.round(Math.random() * (height - radius * 2) + radius)
  };
});

// control add/remove
var addNew = false;
d3.select('#control').append('input')
	.attr('type','button')
	.attr('value', addNew ? "Add" : "Remove")
	.on('click', function(d) {
		addNew = !addNew;
		d3.select(this).attr('value', addNew ? "Add" : "Remove")
		d3.selectAll('g').on('click', (addNew) ? add : remove);
	});
	

var color = d3.scaleOrdinal()
    .range(d3.schemeCategory20);

var voronoi = d3.voronoi()
    .x(function(d) { return d.x; })
    .y(function(d) { return d.y; })
    .extent([[-1, -1], [width + 1, height + 1]]);

var circle = svg.selectAll("g")
  .data(circles)
  .enter().append("g")
  .attr('id',function(d) { return 'g-'+d.n })
  .call(d3.drag()
        .on("start", dragstarted)
        .on("drag", dragged)
        .on("end", dragended))
  .on('click', (addNew) ? add : remove);

var cell = circle.append("path")
  .data(voronoi.polygons(circles))
    .attr("d", renderCell)
	.attr("class","cell")
    .attr("id", function(d) {  return "cell-" + d.data.n; });

circle.append("clipPath")
    .attr("id", function(d) { return "clip-" + d.n; })
  .append("use")
    .attr("xlink:href", function(d) { return "#cell-" + d.n; });


circle.append("circle")
    .attr("clip-path", function(d) { return "url(#clip-" + d.n + ")"; })
    .attr("cx", function(d) { return d.x; })
    .attr("cy", function(d) { return d.y; })
    .attr("r", radius)
    .style("fill", function(d) { return color(d.n); });
	
circle.append("text")
    .attr("clip-path", function(d, i) { return "url(#clip-" + i + ")"; })
    .attr("x", function(d) { return d.x; })
    .attr("y", function(d) { return d.y; })
    .attr("dy", '0.35em')
    .attr("text-anchor", function(d) { return 'middle'; })
    .attr("opacity", 0.6)
    .style("font-size", "1.8em")
    .style("font-family", "Sans-Serif")
    .text(function(d) { return d.n; })
	
var simulation = d3.forceSimulation()
   .nodes(circles)
   .force('charge', d3.forceManyBody());
   	

simulation.nodes(circles)
 .on('tick',ticked);
 
     
function ticked() {
circle.selectAll('circle')
  .attr("cx", function(d) { return d.x; })
  .attr("cy", function(d) { return d.y; })
  
circle.selectAll('text')
  .attr("x", function(d) { return d.x; })
  .attr("y", function(d) { return d.y; });  

cell = cell.data(voronoi.polygons(circles)).attr("d", renderCell);

}

function dragstarted(d) {
  if (!d3.event.active) simulation.alphaTarget(0.3).restart();
	d.fx = d.x;
	d.fy = d.y;
}

function dragged(d) {
  d3.select(this).select("circle").attr("cx", d.x = d3.event.x).attr("cy", d.y = d3.event.y);
  cell = cell.data(voronoi.polygons(circles)).attr("d", renderCell);
  d.fx = d3.event.x;
  d.fy = d3.event.y;
  
  
}

function dragended(d) {
 if (!d3.event.active) simulation.alphaTarget(0);
  d.fx = null;
  d.fy = null;
}




function remove () {

	d3.select(this).raise(); 
	var id = d3.select(this).attr('id').split('-')[1];
	id = +id;
	
	// Get the clicked item:
	var index = circles.map(function(d) {
		return d.n;
	}).indexOf(id);
	
	circles.splice(index,1);
	
	// Update circle data:
	var circle = svg.selectAll("g")
	  .data(circles);
	  
	circle.exit().remove();
	circle.selectAll("clipPath").exit().remove();
	circle.selectAll("circle").exit().remove();
	circle.selectAll("text").exit().remove();

	//// Update voronoi:
	d3.selectAll('.cell').remove();
	cell = circle.append("path")
	  .data(voronoi.polygons(circles))
	  .attr("d", renderCell)
	  .attr("class","cell")
	  .attr("id", function(d) {  return "cell-" + d.data.n; });
	
	simulation.nodes(circles)
		.on('tick',ticked);
}

function add() {
	// Add circle to circles:
	var coord = d3.mouse(this);
	var newIndex = d3.max(circles, function(d) { return d.n; }) + 1;
	circles.push({x: coord[0], y: coord[1], n: newIndex });
	
	// Enter and Append:	
	circle = svg.selectAll("g").data(circles).enter()
	
	var newCircle = circle.append("g")
	  .attr('id',function(d) { return 'g-'+d.n })
	  .call(d3.drag()
			.on("start", dragstarted)
			.on("drag", dragged)
			.on("end", dragended))
	  .on('click',add)

	cell = circle.selectAll("path")
		.data(voronoi.polygons(circles)).enter();
		
	cell.select('#g-'+newIndex).append('path')
	  .attr("d", renderCell)
	  .attr("class","cell")
	  .attr("id", function(d) { return "cell-" + d.data.n; });

	newCircle.data(circles).enter();
	
	newCircle.append("clipPath")
		.attr("id", function(d) { return "clip-" + d.n; })
	    .append("use")
		.attr("xlink:href", function(d) { return "#cell-" + d.n; });

	newCircle.append("circle")
		.attr("clip-path", function(d) { return "url(#clip-" + d.n + ")"; })
		.attr("cx", function(d) { return d.x; })
		.attr("cy", function(d) { return d.y; })
		.attr("r", radius)
		.style("fill", function(d) { return color(d.n); });
		
	newCircle.append("text")
      .attr("clip-path", function(d) { return "url(#clip-" + d.n + ")"; })
      .attr("x", function(d) { return d.x; })
      .attr("y", function(d) { return d.y; })
      .attr("dy", '0.35em')
      .attr("text-anchor", function(d) { return 'middle'; })
      .attr("opacity", 0.6)
      .style("font-size", "1.8em")
      .style("font-family", "Sans-Serif")
      .text(function(d) { return d.n; })
	  
	cell = d3.selectAll('.cell');
		
	d3.select("#cell-"+newIndex).lower(); // ensure the path is above the circle in svg.
	
	simulation.nodes(circles)
	  .on('tick',ticked);

}

function renderCell(d) {
  return d == null ? null : "M" + d.join("L") + "Z";
}
.cell {
  pointer-events: all;
  fill: none;
  stroke: #666;
  stroke-opacity: 0.2;
}

.active circle {
  stroke: #000;
  stroke-width: 2px;
}

svg {
  background: #eeeeee;
}
<script src="https://d3js.org/d3.v4.min.js"></script>

<div id="control"> </div>
<svg width="960" height="500"></svg>

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

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

person Andrew Reid    schedule 25.02.2017
comment
Спасибо за вашу работу, вложенную в этот и связанный с ним вопрос, Эндрю (никогда бы не заподозрил, что css fill:none part). Одной из необходимых частей головоломки является функция обновления — будут узлы (и ссылки), которые динамически вставляются и удаляются; извините, если это было не ясно. Благодаря тому, как вы решили проблему пути клипа, функция обновления может стать неактуальной. Часть путаницы для меня заключается в том, как привязывать и определять узлы внутри/снаружи функций тика и обновления. Собираюсь пройти через это в эти выходные и, надеюсь, найти новый взгляд на проблему из вашей работы. - person Cliff Coulter; 25.02.2017
comment
Я думал, что это может быть немного не так, но я решил, что это не повредит. Это отличный вопрос, и его забавная игра с возможными решениями. Я посмотрю, что я могу добавить к нему с точки зрения ссылок или обновлений. - person Andrew Reid; 25.02.2017
comment
Я думаю, вы решили эту проблему, просто пока не знаете, как ;) У вас есть сгруппированные элементы, так что это должно быть с дополнительным текстовым братом. Преобразование, описанное выше, заключалось в перемещении группы элементов, которые, казалось, не работали должным образом, когда перетаскивание было на круге. Работая с тем, что вы сделали, чтобы использовать его с обновлением/вводом/удалением. Надеясь получить график обновления в реальном времени, который будет отображать текст или всплывающее окно с данными при наведении курсора и перетаскивать их, используя voronoi для простоты. Извините, я знаю, что объединить все отдельные функции в одну сложно. - person Cliff Coulter; 25.02.2017
comment
Вот модифицированная скрипта вашего решения с добавленным текстовым элементом: jsfiddle.net/dunderjeep/56x7z2yy/3 — текстовые элементы располагаются вне svg. Идея состоит в том, что они будут «следовать» за кругами. Но если сгруппированные элементы трансформируются с элементами клипа, то вороной вычисляется относительно позиции группы, и все перепуталось: jsfiddle.net/dunderjeep/56x7z2yy/5. Таким образом, он пытается синтезировать эти две функции. Прозрачный как грязь? - person Cliff Coulter; 25.02.2017
comment
Получил текст для «следования», выбрав и поместив его в отдельный вызов: circle.selectAll('text') .attr("transform", function(d) { return "translate(" + d.x + "," + d.y + ")" }); ' здесь< /а> - person Cliff Coulter; 26.02.2017
comment
Теперь просто добавим функцию обновления, чтобы завершить ее, и я думаю, что у нас все хорошо. - person Cliff Coulter; 26.02.2017
comment
@CliffCoulter Я отредактировал отличный ответ Эндрю, чтобы показать, как добавлять текст. Андрей, надеюсь, ты не против. - person meetamit; 27.02.2017
comment
Мои попытки обновить функцию частично увенчались успехом, я был немного занят, но должен что-то получить сегодня вечером. - person Andrew Reid; 27.02.2017
comment
У меня есть функция обновления для добавления/удаления в ответе. Удивительно сложный, он, вероятно, не настолько элегантен или эффективен, насколько это возможно, но должен быть полезным, может быть? Мне еще слишком рано думать здраво, поэтому я надеюсь, что объяснение работает, я отредактирую его позже сегодня, если это необходимо, но сегодня я немного в дороге. - person Andrew Reid; 27.02.2017
comment
В конце удаления может быть один сбой, я не могу достоверно воспроизвести его, сегодня проверю повторно. - person Andrew Reid; 27.02.2017
comment
Отличная работа! С облегчением я не единственный, кто нашел это сложным. И очень полезно - вмятина на лбу благодарна, что не бьюсь об стену :) У вас есть это; Я просто собираюсь оставить это, чтобы инкубировать немного дольше, и мне нужно больше времени, чтобы проработать это. - person Cliff Coulter; 28.02.2017
comment
Спасибо, у меня тоже немного вмятина на лбу, пытаясь понять, что казалось бессмысленными изменениями в визуализации. Как только я, наконец, начал понимать, что происходит не так в DOM, все стало обретать смысл. Конечно, это сложно, но это полезный и инновационный тип визуализации. Я хотел бы увидеть, что другие думают о проблеме, поэтому, пожалуйста, оставьте это. Вот почему я сначала опубликовал частичный ответ, чтобы посмотреть, присоединятся ли другие и попробуют. Я думаю, что принудительные ссылки могут быть довольно прямолинейными, я посмотрю, что я могу сделать, но я не буду пользоваться своим компьютером в течение нескольких дней. - person Andrew Reid; 28.02.2017
comment
У меня есть еще один рабочий пример здесь с использованием источника: Изменение принудительного макета и ссылки на идентификаторы узлов устраняют проблему индексации. Позже будет развернутый пример. - person Cliff Coulter; 04.03.2017
comment
Вот пример с событиями наведения мыши, делающими группу узлов активной, чтобы видимость или атрибуты вложенных элементов могли определяться в css. Очевидно, что clip path не является необходимым для достижения цели. Спасибо, что помогли мне понять, как выбирать правильные элементы, и указали на события указателя, которые я бы никогда не проверил. - person Cliff Coulter; 04.03.2017
comment
Выглядит красиво, рад видеть ссылки там. И эта проблема с индексацией — из-за нее столько проблем. - person Andrew Reid; 04.03.2017
comment
Да, извините, что индексы и пути клипа ввели в заблуждение (они меня тоже смутили). Я собираюсь отредактировать вопрос и ответить на него, чтобы приблизить его к истинным намерениям, чтобы, надеюсь, сделать его более ясным, доступным для поиска и полезным для других с аналогичными намерениями, но награда и кредит достаются вам за то, что вы разместили его на правильном пути. - person Cliff Coulter; 04.03.2017
comment
Не извиняйтесь, было полезно отследить это и найти решение - person Andrew Reid; 04.03.2017

Заблокировать версию.

  • Why does dragging not trigger on the cells?
    • Because if the cell attribute has fill:none, then it must have pointer-events:all.
  • Why do the nodes become obscured and the paths lose their styles on edges?
    • Because the clip path was targeting the g elements position instead of the circles position.
  • How can this be fixed to drag the nodes and trigger events on them like mouseovers?
    • use path attr pointer-events: all, path { pointer-events: all; }
    • выберите нужный дочерний элемент, например круг или текст, в событии перетаскивания или галочки для позиционирования parent.select(child).attr('d' function(d) { ..do stuff.. });
    • используйте идентификаторы узлов для ссылок, чтобы упростить обновление или удаление массива данных node.data(data, function(d) { return d.id; })

Спасибо Эндрю Рид за вашу помощь.

var svg = d3.select("svg"),
    width = +svg.attr("width"),
    height = +svg.attr("height"),
    color = d3.scaleOrdinal(d3.schemeCategory10);

var a = {id: "a"},
    b = {id: "b"},
    c = {id: "c"},
    data = [a, b, c],
    links = [];

var simulation = d3.forceSimulation(data)
    .force("charge", d3.forceManyBody().strength(-10))
    .force("link", d3.forceLink(links).distance(200))
		.force("center", d3.forceCenter(width / 2, height / 2))
    .alphaTarget(1)
    .on("tick", ticked);

var link = svg.append("g").attr("class", "links").selectAll(".link"),
    node = svg.append("g").attr("class", "nodes").selectAll(".node");
    
var voronoi = d3.voronoi()
	.x(function(d) { return d.x; })
	.y(function(d) { return d.y; })
	.extent([[-1, 1], [width + 1, height + 1]]);

update();

d3.timeout(function() {
  links.push({source: a, target: b}); // Add a-b.
  links.push({source: b, target: c}); // Add b-c.
  links.push({source: c, target: a}); // Add c-a.
  update();
}, 1000);

d3.interval(function() {
  data.pop(); // Remove c.
  links.pop(); // Remove c-a.
  links.pop(); // Remove b-c.
  update();
}, 5000, d3.now());

d3.interval(function() {
  data.push(c); // Re-add c.
  links.push({source: b, target: c}); // Re-add b-c.
  links.push({source: c, target: a}); // Re-add c-a.
  update();
}, 5000, d3.now() + 1000);

function update() {

  node = node.data(data, function(d) { return d.id; });
  node.exit().remove();
  var nodeEnter = node.enter().append("g")
  	.attr("class", "node")
    .on("mouseover", mouseover)
    .on("mouseout", mouseout);
  nodeEnter.append("circle").attr("fill", function(d) { return color(d.id); }).attr("r", 8);
  nodeEnter.append("text")
    .attr("dx", 12)
    .attr("dy", ".35em")
  	.text(function(d) { return d.id; });
  nodeEnter.append("path").attr("class", "path");
  nodeEnter.call(d3.drag()
                 .on("start", dragstarted)
                 .on("drag", dragged)
                 .on("end", dragended));
  node = node.merge(nodeEnter);


  // Apply the general update pattern to the links.
  link = link.data(links, function(d) { return d.source.id + "-" + d.target.id; });
  link.exit().remove();
  link = link.enter().append("line").merge(link);

  // Update and restart the simulation.
  simulation.nodes(data);
  simulation.force("link").links(links);
  simulation.alpha(1).restart();
}

function mouseover(d) {
	d3.select(this).raise().classed("active", true);
}

function mouseout(d) {
	d3.select(this).raise().classed("active", false);
}

function dragstarted(d) {
  if (!d3.event.active) simulation.alphaTarget(0.3).restart();
  d.fx = d.x;
  d.fy = d.y;
}

function dragged(d) {
  d3.select(this).select("circle").attr("cx", d.fx = d3.event.x).attr("cy", d.fy = d3.event.y);
}

function dragended(d) {
  if (!d3.event.active) simulation.alphaTarget(0);
  d.fx = null;
  d.fy = null;
}

function ticked() {
  node.select("circle")
  	.attr("cx", function(d) { return d.x; })
    .attr("cy", function(d) { return d.y; });
    
  node.select("path")
  	.data(voronoi.polygons(data))
    .attr("d", function(d) { return d == null ? null : "M" + d.join("L") + "Z"; });
    
  node.select("text")
  	.attr("transform", function(d) { return "translate(" + d.x + "," + d.y + ")" });

  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; });
}
path {
  pointer-events: all;
  fill: none;
  stroke: #666;
  stroke-opacity: 0.2;
}

.active path {
  fill: #111;  
  opacity: 0.05;
}

.active text {
  visibility: visible;
}

.active circle {
  stroke: #000;
  stroke-width: 1.5px;
}

svg {
  border: 1px solid #888;  
}

.links {
  stroke: #000;
  stroke-width: 1.5;
}

.nodes {
  stroke-width: 1.5;
}

text {
  pointer-events: none;
  font: 1.8em sans-serif;
  visibility: hidden;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/4.5.0/d3.min.js"></script>
<svg width="400" height="400"></svg>

person Cliff Coulter    schedule 04.03.2017