применить несколько событий наведения мыши к соседним (подключенным) узлам

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

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

function isConnected(a, b) {
    return linkedByIndex[a.index + "," + b.index] || linkedByIndex[b.index + "," + a.index] || a.index == b.index;
    }

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

Ниже приведен фрагмент кода (из jsFiddle), который я хотел бы изменить. Я был бы признателен за любые предложения или указатели на другие примеры.

var node = svg.selectAll(".node")
    .data(graph.nodes)
    .enter().append("g")
    .attr("class", function(d) { return "node " + d.name + " " + d.location; })
    .call(force.drag)
    .on("mouseover", function(d) { 
        // I would like to insert an if statement to do all of these things to the connected nodes
        // if(isConnected(d, o)) {
            d3.select(this).select("circle").style("stroke-width", 6); 
            d3.select(this).select("circle").style("stroke", "orange"); 
            d3.select(this).select("text").style("font", "20px sans-serif");
            d3.selectAll("rect." + d.location).style("stroke-width", 6);
            d3.selectAll("rect." + d.location).style("stroke", "orange");
            d3.selectAll("text." + d.location).style("font", "20px sans-serif");
            d3.selectAll("tr." + d.name).style("background-color", "orange");
            //}
        })
    .on("mouseout",  function(d) { 
        // if(isConnected(d, o)) {
            d3.select(this).select("circle").style("stroke-width", 1.5); 
            d3.select(this).select("circle").style("stroke", "gray"); 
            d3.select(this).select("text").style("font", "12px sans-serif");
            d3.selectAll("rect." + d.location).style("stroke-width", 1.5);
            d3.selectAll("rect." + d.location).style("stroke", "gray");
            d3.selectAll("text." + d.location).style("font", "12px sans-serif");
            d3.selectAll("tr." + d.name).style("background-color", "white");
            //}
        });

person Jean V. Adams    schedule 31.05.2013    source источник


Ответы (2)


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

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

Конечно, тогда мне нужен список соседей, но несколько хороших методов массива js быстро справятся с этим. Окончательный соответствующий код (при наведении курсора) даже не такой длинный, но я добавил кучу комментариев:

// Figure out the neighboring node id's with brute strength because the graph is small
var nodeNeighbors = graph.links.filter(function(link) {
    // Filter the list of links to only those links that have our target 
    // node as a source or target
    return link.source.index === d.index || link.target.index === d.index;})
.map(function(link) {
    // Map the list of links to a simple array of the neighboring indices - this is
    // technically not required but makes the code below simpler because we can use         
    // indexOf instead of iterating and searching ourselves.
    return link.source.index === d.index ? link.target.index : link.source.index; });

// Reset all circles - we will do this in mouseout also
svg.selectAll('circle').style('stroke', 'gray');

// now we select the neighboring circles and apply whatever style we want. 
// Note that we could also filter a selection of links in this way if we want to 
// Highlight those as well
svg.selectAll('circle').filter(function(node) {
    // I filter the selection of all circles to only those that hold a node with an
    // index in my listg of neighbors
    return nodeNeighbors.indexOf(node.index) > -1;
})
.style('stroke', 'orange');

Вы также можете попробовать скрипту.

Я думаю, что важная концепция d3, уместная здесь, заключается в том, что когда вы связываете данные с элементом (обычно используя методы data() или datum() для выборок), эти данные остаются с этим элементом, и любые будущие выборки всегда будут использовать его.

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

var nodeLocations = graph.links.filter(function(link) {
        return link.source.index === d.index || link.target.index === d.index;})
    .map(function(link) {
        return link.source.index === d.index ? link.target.location : link.source.location; });

d3.selectAll("rect").filter(function(node) { return nodeLocations.indexOf(node.location) > -1; }) .style("stroke", "cyan");
person Superboggly    schedule 21.06.2013
comment
Это полезно, но только применяет выделение соседей к сетевой диаграмме. Я также хочу выделить соответствующие биты в таблице и на карте. Так, например, когда я наведу указатель мыши на группу A, я хочу, чтобы у Джима, Салли и Тома были (1) выделенные кружки на сетевой диаграмме (ваш код решает эту проблему), (2) выделенные строки в таблице и (3) выделенные прямоугольники на карте. Не могли бы вы помочь мне с частями 2 и 3? - person Jean V. Adams; 24.06.2013
comment
Похоже, ваши прямоугольники и, возможно, ваша таблица не связывают данные так же, как D3. Но нет никаких причин, по которым вы все равно не можете использовать выбор d3. Хитрость заключается в том, чтобы использовать массив индексов соседних узлов, чтобы фактически получить список узлов или правильных атрибутов, чтобы выделить другие элементы. тогда вы сможете применить ту же логику. - person Superboggly; 24.06.2013
comment
Можете ли вы дать мне подсказку, как это сделать? Я попробовал d3.selectAll("rect." + d.location).filter(function(node) { return nodeNeighbors.indexOf(node.index) > -1; }) .style("stroke", "cyan");, но, очевидно, это не сработало. - person Jean V. Adams; 24.06.2013
comment
Я внес правку, вы также можете посмотреть здесь Хитрость в том, что на ваших узлах группы я не думайте, что ваш d.location возвращает правильную вещь. И я знаю, что ваши сопоставленные прямоугольники не содержат индекса. Мне нравится добавлять такие вещи, как console.log(node); в функции обратного вызова выбора, чтобы увидеть, что в итоге получится. Вы всегда можете открыть консоль шоу в инструментах разработчика браузера, чтобы посмотреть, что она выдает. ИТ значительно упрощает просмотр того, что происходит. - person Superboggly; 24.06.2013
comment
Прохладный. Спасибо. Я попытался воспроизвести этот подход с таблицей (используя имена вместо местоположений), но это не работает, и я не вижу никаких ошибок в браузере. d3.selectAll("tr").filter(function(node) { return nodeNames.indexOf(node.name) > -1; }).style("background-color", "cyan"); Вы видите, что я упускаю? Кроме того, я не понимаю, куда поместить оператор console.log(node): . Я пытался прямо перед возвратом в фигурных скобках .filter(), {}, но это не сработало. - person Jean V. Adams; 24.06.2013
comment
Да, я обычно помещаю console.log именно туда, куда вы сказали. Попробуйте это (в том же наведении курсора): d3.selectAll(tr).filter(function(node) { console.log(this,node); return node ? nodeLocations.indexOf(node.location) › -1 : false; } ).style(шрифт, полужирный); В консоли вы увидите, что первый выбор не имеет элемента данных (это ваша строка заголовка), поэтому вызов node.location выдает ошибку и прерывает все. - person Superboggly; 24.06.2013

Эта вещь, которую я построил, делает это с функцией Ego Network:

https://gist.github.com/emeeks/4588962

Добавьте .on("mouseover", findEgo) к своим узлам, и следующее должно работать, если у вас есть какой-то идентифицирующий атрибут uid, который вы можете сгенерировать при загрузке узлов, если он не удобен. Это немного излишне, так как он позволяет использовать эго-сети n-степеней и создает агрегированную таблицу для других функций сетевого анализа, но базовая функциональность даст вам то, что вы хотите, и вы или другие пользователи можете найти этот аспект полезным:

 function findEgo(d) {  
  var computedEgoArray = findEgoNetwork(d.id, 1, false,"individual");
  d3.selectAll("circle.node").style("fill", function(p) {return p.id == d.id ? "purple" : computedEgoArray.indexOf(p.id) > -1 ? "blue" : "pink"})
 }

 function findEgoNetwork(searchNode, egoNetworkDegree, isDirected, searchType) {
  var egoNetwork = {};
  for (x in nodes) {
  if (nodes[x].id == searchNode || searchType == "aggregate") {
   egoNetwork[nodes[x].id] = [nodes[x].id];
   var z = 0;
   while (z < egoNetworkDegree) {
    var thisEgoRing = egoNetwork[nodes[x].id].slice(0);
    for (y in links) {
     if (thisEgoRing.indexOf(links[y].source.id) > -1 && thisEgoRing.indexOf(links[y].target.id) == -1) {
     egoNetwork[nodes[x].id].push(links[y].target.id)
     }
    else if (isDirected == false && thisEgoRing.indexOf(links[y].source.id) == -1 && thisEgoRing.indexOf(links[y].target.id) > -1) {
    egoNetwork[nodes[x].id].push(links[y].source.id)
    }
 }
 z++;
 }
 }
 }
 if (searchType == "aggregate") {
 //if it's checking the entire network, pass back the entire object of arrays
 return egoNetwork;
 }
 else {
 //Otherwise only give back the array that corresponds with the search node
 return egoNetwork[searchNode];
 }
 }
person Elijah    schedule 08.06.2013