2013-05-31 13 views
14

Ich habe ein Netzwerkdiagramm (Force-directed Graph), ein Streudiagramm und eine Tabelle, die alle miteinander verbunden sind (siehe jsFiddle). Ich habe die Verbindungen funktioniert so, wie ich sie für Mouseover-Ereignisse will. Ich möchte meinen Code so ändern, dass, wenn ich über einen Knoten im Netzwerkdiagramm mouseovere, nicht nur der Mouseover-Knoten hervorgehoben wird (und seine Verbindungen im Streudiagramm und der Tabelle), sondern auch seine unmittelbaren Nachbarknoten hervorgehoben werden wie ihre Verbindungen im Streudiagramm und in der Tabelle).Mehrere Mouseover-Ereignisse auf benachbarte (verbundene) Knoten anwenden

Ich schaute auf die Informationen in Highlight selected node, its links, and its children in a D3 force directed graph für Hilfe. Irgendwo auf dem Weg (nicht genau wo), fand ich ein Beispiel für eine Funktion, die hilft, die verbundenen Knoten zu definieren, isConnected().

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

Ich möchte diese Funktion in meine Mouseover Ereignisse zu übernehmen, vielleicht mit einer if() Aussage, so dass ich alle „Hervorhebung“ tun kann, was ich will. Aber ich bin neu in D3 und js und bin mir nicht sicher, wie ich es einrichten soll.

Unten ist das Codeschnipsel (von jsFiddle), die ich gerne ändern würde. Ich würde mich über Anregungen oder Hinweise auf andere Beispiele freuen.

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"); 
      //} 
     }); 

Antwort

5

In einem anderen Szenario würde ich meine visuellen Objekte in eine Diagrammdatenstruktur einfügen und navigieren, um die entsprechenden Elemente effizient zu aktualisieren. Aber das ist d3, aber wir werden dasselbe tun, aber anstelle einer Diagrammdatenstruktur, die wir erstellen, werden wir d3-Selektionen verwenden (die wie Diagramme aussehen können, aber dafür werden sie mehr wie Arrays aussehen). Algorithmisch wird dieser Ansatz nicht so effizient sein, aber unsere Graphen sind klein.

Also rückwärts arbeiten werde ich eine Auswahl wollen, die nur die benachbarten Knoten Knoten enthält. Ich werde das tun, indem ich alle Kreise auswähle und dann die d3-Auswahlfiltermethode verwende, um diese auf nur die Kreise zu reduzieren, die Nachbarn sind.

Natürlich dann brauche ich die Liste der Nachbarn, aber ein paar nette js Array-Methoden machen kurze Arbeit davon. Der endgültige entsprechende Code (in Mouseover) ist nicht einmal so lange - aber ich habe ein paar Kommentare hinzugefügt:

// 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'); 

Sie können auch die versuchen fiddle

Ich denke, die wichtig d3 Konzept hier relevant ist Wenn Sie Daten mit einem Element verknüpfen (normalerweise mit den Methoden "data()" oder "datum()"), bleiben diese Daten bei diesem Element haften und zukünftige Selektionen verwenden es immer.

Um andere Aspekte zu verknüpfen, können Sie diese Attribute auf ähnliche Weise ziehen und durch d3 verknüpfen.Zum Beispiel für die Standortrechtecke, die Sie zum Mouseover hinzufügen könnten:

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"); 
+0

Dies ist hilfreich, aber es gilt nur die Nachbarhervorhebung auf das Netzwerkdiagramm. Ich möchte auch die entsprechenden Bits in der Tabelle und der Karte hervorheben. Wenn ich zum Beispiel GroupA übergebe, möchte ich, dass Jim, Sally und Tom (1) Kreise im Netzwerkdiagramm markiert haben (Ihr Code adressiert dies), (2) markierte Zeilen in der Tabelle und (3) hervorgehoben Rechtecke in der Karte. Kannst du mir mit den Teilen 2 und 3 helfen? –

+0

Es sieht so aus, als ob Ihre Rechtecke und vielleicht Ihre Tabelle Daten nicht wie D3 assoziieren. Aber es gibt keinen Grund, warum Sie die d3-Auswahl nicht verwenden können. Der Trick wäre, das Array der Nachbarknotenindizes zu verwenden, um tatsächlich eine Liste von Knoten oder von korrekten Attributen zu erhalten, um die anderen Elemente hervorzuheben. dann sollten Sie in der Lage sein, dieselbe Logik anzuwenden. – Superboggly

+0

Kannst du mir einen Hinweis geben, wie das geht? Ich habe versucht, \t \t \t \t 'd3.selectAll ("rect." + D.location) .filter (function (Knoten) { return nodeNeighbors.indexOf (node.index)> -1; }) .style (“ stroke "," cyan ");' aber offensichtlich hat das nicht funktioniert. –

0

Das, was ich gebaut hat das mit dem Ego-Netzwerk-Funktion:

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

ein .on hinzufügen ("Mouseover", findEgo), um Ihre Knoten und die folgenden sollte funktionieren, so lange da Sie eine Art von UID-Attribut haben, das Sie generieren können, wenn Sie die Knoten laden, wenn einer nicht handlich ist. Es ist ein wenig Overkill, da es N-Grad-Ego-Netzwerke ermöglicht und eine aggregierte Tabelle für andere Netzwerkanalysefunktionen erstellt, aber die grundlegende Funktionalität gibt Ihnen, was Sie wollen und Sie oder andere Benutzer finden diesen Aspekt nützlich:

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]; 
} 
}