Conditionally change the opacity of all nodes and edges recursively (d3)

UPDATE: Here I created a JSFiddle . Send the updated script with your answer.

I have dynamic filters that the user can apply to the data, but they change the opacity of the nodes to indicate what is filtered in and out (the elements filtered "out" are still partially visible, and the actual function d3 filter() not used (intentionally) ) I also set a property for each node that is being filtered out (for example, node = {"name": "test", "isFilteredOut": true}; ). So, for the purposes of this question, although I use the word “filter”, this is really just a conditional style change (and I will try to put the word “filter” in quotation marks in this post as a reminder of this).

All this works fine, but now I want to recursively "filter out" all the child nodes and edges of the "filtered" nodes, as well as the edge connecting the original "filtered" node to the filtered parent node.

All the examples that I can find start with a click event and thus allow this to be used to get the data for the starting node. I do not have this luxury , because the filter is applied using a user interface element that is not inside the graph itself.

I am now filtering such nodes:

 node.style("opacity", function(n) { if (my_filter_conditions) { return 1; } else { n.isFilteredOut = true; return 0.1; } }); 

I need to do the following:

  • Recursively select all the child nodes of the currently “filtered” nodes and “filter” them as well (ie, change their opacity to 0.1 and set n.isFilteredOut = true; ).

  • Change the opacity of all the edges to 0.1, where the source node or target node is “filtered” (ie n.isFilteredOut = true; at each end of the edge)

What i tried

I do not know how to access the data of the source and target nodes, considering only the index of each of the edges (remember that I do not have this node to start with a click event). I tried passing the node index obtained from the edge to get the node data with:

 var node_data = d3.select(current_edge.source.index).datum(); 

However, this led to the d3 library errors related to this.node() being empty (therefore, index transfer did not work here).

I also tried to process the edges by nesting the function to handle the links inside the function passed to the node.style() function, but then it tries to figure out all the edges on each node, and I cannot get it to provide the desired result.

 link.style("opacity", function (e) { return ( (n.isFilteredOut) && (n.index==e.source.index | n.index==e.target.index) ) ? 0.1 : 1; }); 

It was my attempt to “filter” the edges on both sides of the “filtered” nodes, but none of the edges ever filtered out when I used this for some reason (it seemed like nothing happened at all).

UPDATE: Here I created a JSFiddle .

Script Notes:

  • I know that it is simplified (it should be a minimal working example)
  • The actual application contains filters that are applied within the types (even if you are just looking for a specific device / part / etc.), so it is important that the logic can conditionally follow the "chain" only for those nodes, node.isFilteredOut = true;
  • In this example, the correct answer will lead to a situation where the filtering devices will also filter out all parts
  • Solutions that do some sort of filtering using a dataSet will not work, because most of my data is dynamically populated from different JSON sources. Feel free to work with nodes , edges , links , node and / or link .
  • Please do not rewrite my filtering methodology. Yes, I know that eval() expressions are small. But this is not a question of how to best apply infinite conjunctive filters, but also of a recursive change in the opacity of nodes and edges based on the applied filters.
+8
javascript dictionary recursion filtering
source share
2 answers

Here is a possible approach that implements both recursive filtering (if the device is filtered, its parts are filtered) and filtering links based on filtering their nodes: http://jsfiddle.net/Lsr9c8nL/4/

I changed the way the filter is implemented. Using strings to create a filter and then eval() is considered pretty bad these days because tools can't do much with eval() , for example, to detect errors or optimize JS code in a browser.

I do the filtering directly on the dataSet , not on the nodes (where you had to query for the node type and compare strings that are slow). Doing this directly in the dataSet also makes it easy to find what a device is for a given part.

The trick is to redraw the entire chart each time and carefully use the d3 exit , enter and update parameters. It will also allow you to add animations if you want

+2
source share

I know that you strongly feel your use of eval is here, but you can simply reorganize your filtering functions to return a lambda:

 function filter_devices() { return n=>n.type != "mobile device"; } 

and then do something like this:

 function isNTrueForAll(arrayOFuncts,n){ return arrayOFuncts.every(i=>i(n)) } 

Or decreasing if you do not have Array.every:

 function isNTrueForAll(arrayOFuncts,n){ return arrayOFuncts.reduce((current,next)=>{return current && next(n)},true) } 

Or make a recursive iterator if this is bigger than your bag. Point, conditional logical evaluation, and deferred executions are not what you need to evaluate, and you are doing a great performance performing eval.

- # Edit -

It seems that the DOM does not actually reflect the relationship between your device and the nodes of the nodes, so you cannot use the D3 picker to bind a specific part of the node to its parent node.

Thus, recursively choosing a child is a kind of contentious issue.

Alternatively, you can simply configure the filter functions to reflect the relationship - IE, if we filter in parts, return false for the device or part.

This is a really reliable solution, if there are not so many filtering conditions, and you already know the business logic that you would like to use ahead of time to filter them out, but that might work for you.

0
source share

All Articles