Base on Collapsible Tree Search, I was able to search an element using the force directed graph. The problem is when I choose a search name, it will only hightlight the element and its first level of parent. How to hightlight the links and the nodes from the search element to the root?
Here's my code on Plunker Search on Force Directed Graph.
function searchTree (d) {
if (d.children)
d.children.forEach(searchTree);
else if (d._children)
d._children.forEach(searchTree);
var searchFieldValue = eval(searchField);
if (searchFieldValue && searchFieldValue.match(searchText)) {
// Walk parent chain
var ancestors = [];
var parent = d;
while (typeof(parent) !== "undefined") {
ancestors.push(parent);
// console.log(parent);
parent.class = "found";
parent = parent.parent;
}
// console.log(ancestors);
}
}
I've already compared everything on how it was being implemented on a tree but I was not able to find it out why.
Related
I'm trying to select all the child node of a parent node when the parent is clicked, but when I for each node set the Selected = true i only end up with the last one being selected.
MultiSelect is true and I can do it with the mouse, so the setup should be ok.
For testing I use this code:
TTreeNode *node = Tv->Items->GetFirstNode();
node->Selected = true;
node = node->GetNext();
node->Selected = true;
node = node->GetNext();
node->Selected = true;
node = node->GetNext();
node->Selected = true;
Any tricks to make this work?
The TTreeNode::Selected property does not support multiple selections when toggling the node's selection state. Internally, it will call the Win32 TreeView_SelectItem() API, which selects a single node only.
For multi-select, use the TTreeView::Select() method instead:
The select method selects one or more tree nodes.
That being said, your example is attempting to select (potentially) every node in the TreeView, not just the child nodes of a parent node, as you claim.
Try this:
void AddNodeAndChildrenToList(TList *List, TTreeNode *Node)
{
List->Add(Node);
TTreeNode *child = Node->getFirstChild();
while (child)
{
AddNodeAndChildrenToList(List, child);
child = child->getNextSibling();
}
}
...
TList *nodes = new TList;
try
{
TTreeNode *parent = ...;
AddNodeAndChildrenToList(nodes, parent);
Tv->Select(nodes);
}
__finally
{
delete nodes;
}
Staring from previously posted code I'd like check branch boxes when one clicks on the upper box. For instance, on the picture below, I would like that checking the BRE box, check the five checkboxes of the tree branch, and conversely, unchecking BRE would uncheck the five children.
To do so, I made a few changes in the code:
// On click, check the corresponding box, select it as well as its children (recursively)
function check(d) {
d.Selected = !d.Selected;
d3.select(this).style("opacity", boxStyle(d));
if (d.children) {
// mark children recursively
for(n in d.children) {
checkBox(d.children[n], d.Selected);
}
}
console.log(d);
update(d);
}
function checkBox(d, checked) {
d.Selected = checked;
}
As one can check in the console, items are properly Selected, the clicked box is properly checked, but children are not although I forced updating the drawing with the update function used to update after collapsing/expending.
Collapsing, hence expending the tree shows that everything is in place to properly draw the tree, it seems that it's not updated (redrawn from source) after the BRE box have been checked.
Is there something in the main code that prevent redrawing when branches have not been collapsed or expended?
You only apply the style:
.attr("style", function(d) { return "opacity: "+boxStyle(d) });
on the nodeEnter selection in your update. You need to apply on the node selection to update:
node.selectAll("path:nth-child(3)")
.attr("style", function(d) { return "opacity: "+boxStyle(d) });
The selector here is a little funny. Since you have multiple paths at in each node I had to find it by it's child position.
Here's an example.
Note, two things:
1.) This is not truly recursive and it only "checks" one child level down.
2.) It'll only check the children if the node is expanded.
Here's a new version which solves those issues:
// Toggle check box on click.
function check(d) {
d.Selected = !d.Selected;
if (d.children || d._children) {
// mark children recursively
for(var n in d.children) {
check(d.children[n]);
}
for(n in d._children) {
check(d._children[n]);
}
}
}
function recurCheck(d){
check(d);
update(d);
}
I'm trying to highlight all the connected links and links of their target nodes till the end of the layout.
The first level of highlighting can be easily achieved as follows -
On node click, call highlight_paths(1);
function highlight_paths(stroke_opacity) {
return function(d,i){
d.sourceLinks.forEach(function(srcLnk){
d3.select("#link"+srcLnk.id).style("stroke-opacity", stroke_opacity);
});
d.targetLinks.forEach(function(srcLnk){
d3.select("#link"+srcLnk.id).style("stroke-opacity", stroke_opacity);
});
}
}
But I'm not yet able to write correctly a recursive algorithm to get all the sourceLinks and targetLinks of each of the connected source & target nodes.
All thoughts are appreciated!
Thanks.
I was going through the sankey layout code and found a Breadth First Search implementation for traversing the layout nodes. Some knowledge on BFS here - http://www.cse.ohio-state.edu/~gurari/course/cis680/cis680Ch14.html
Purely based on that, here is the function to highlight all the paths from the clicked node in both the directions - Forward ( Target ) and Backward (Source)
Hope this helps someone!
Working examples -
http://bl.ocks.org/git-ashish/8959771
https://observablehq.com/#git-ashish/sankey-diagram
function highlight_node_links(node,i){
var remainingNodes=[],
nextNodes=[];
var stroke_opacity = 0;
if( d3.select(this).attr("data-clicked") == "1" ){
d3.select(this).attr("data-clicked","0");
stroke_opacity = 0.2;
}else{
d3.select(this).attr("data-clicked","1");
stroke_opacity = 0.5;
}
var traverse = [{
linkType : "sourceLinks",
nodeType : "target"
},{
linkType : "targetLinks",
nodeType : "source"
}];
traverse.forEach(function(step){
node[step.linkType].forEach(function(link) {
remainingNodes.push(link[step.nodeType]);
highlight_link(link.id, stroke_opacity);
});
while (remainingNodes.length) {
nextNodes = [];
remainingNodes.forEach(function(node) {
node[step.linkType].forEach(function(link) {
nextNodes.push(link[step.nodeType]);
highlight_link(link.id, stroke_opacity);
});
});
remainingNodes = nextNodes;
}
});
}
function highlight_link(id,opacity){
d3.select("#link-"+id).style("stroke-opacity", opacity);
}
I have a network diagram (force-directed graph), a scatterplot, and a table that are all interconnected (see jsFiddle). I have the interconnections working the way I want them for mouseover events. I would like to modify my code so that when I mouseover a node in the network diagram, not only is the moused-over node highlighted (and its connections in the scatterplot and table), but its immediate neighbor nodes are also highlighted (as well as their connections in the scatterplot and table).
I looked at the information in Highlight selected node, its links, and its children in a D3 force directed graph for help. Somewhere along the way (not exactly sure where) I found an example of a function that helps define the connected nodes, isConnected().
function isConnected(a, b) {
return linkedByIndex[a.index + "," + b.index] || linkedByIndex[b.index + "," + a.index] || a.index == b.index;
}
I'd like to incorporate this function into my mouseover events, perhaps with an if() statement, so that I can do all of the "highlighting" that I want. But, I'm new to D3 and js and am not sure how to set it up.
Below is the snippet of code (from the jsFiddle) that I would like to modify. I would appreciate any suggestions or pointers to other examples.
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");
//}
});
In another scenario I would put my visual objects into a graph data structure and navigate that to efficiently update the appropriate items. But this is d3, but so we will do the same thing but instead of a graph data structure that we create we will use d3 selections (which can be like graphs but for this they will look a lot more like arrays). Algorithmically this approach will not be as efficient, but our graphs are small.
So working backwards I will want a selection that includes only the picked node's neighboring
nodes. I will do this by selecting all the circles and then using the d3 selection filter method to reduce that to only those circles that are neighbors.
Of course then I need the list of neighbors, but a few nice js array methods make short work of that. The final relevant code (in mouseover) is not even that long - but I've added a bunch of comments:
// 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');
You can also try the fiddle
I think the important d3 concept relevant here is that when you associate data with an element (usually using the data() or datum() methods on selections) then that data sticks with that element and any future selections will always use it.
To link other aspects you can pull those attributes in a similar way and link them through d3. For example for the location rectangles you could add to mouseover:
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");
This thing I built does that with the Ego Network feature:
https://gist.github.com/emeeks/4588962
Add a .on("mouseover", findEgo) to your nodes and the following should work, as long as you have some kind of identifying uid attribute, which you could generate when you load the nodes if one isn't handy. It's a bit of overkill, since it allows for n-degree ego networks, and creates an aggregated table for other network analysis functions, but the basic functionality will give you what you want and you or other users might find that aspect useful:
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];
}
}
This is for a tennis draw and I need to be able to have a bye, so most parents have two children, ie. winner of each match goes through, but in some cases there is a bye, so there is only one child. See here as an example where some parent matches have no children and some have one: http://www.irtpa.com/index.php/realtennis/ladder/1246
I don't think this answer helps: How to remove node on tree layout D3.js?
As it is assuming that ALL children of a node are hidden/removed.
I've got this far based on the above stackoverflow answer but my brain cannot see the solution for removing/hiding the child:
function check_children(data, parent) {
// recurse through array and toggle/hide any Match Byes
if (typeof data.data != "undefined") {
if (data.data.bye == "byeM") {
toggle(parent);
}
}
if (data.children) {
check_children(data.children[0], data);
check_children(data.children[1], data);
}
}
function toggle(d) {
if (d.children) {
d._children = d.children;
d.children = null;
} else {
d.children = d._children;
d._children = null;
}
}
If you want to remove your "bye" children I would probably test before recursing. So something like:
function removeByeChildren(parent) {
if(!parent.children)
return;
var i = 0;
while(i < parent.children.length) {
var data = parent.children[i].data;
if (typeof data != "undefined" && data.bye == "byeM") {
// remove child - could save it in _children if you wanted to ever put it back
var child = parent.children.splice(i,1);
// length of child list reduced, i points to the next child
}
else {
// not a bye - recurse
removeByeChildren(parent.children[i]);
// all the child's bye's are removed so go to the next child
i++;
}
}
}
You can play with it here. The idea is to check each child if it is a bye or not. If it is we remove it from the list and if it is not we recurse and remove all the descendant child byes.