I have some hierarchical data which is being packed to form a bubble chart, this works fine however when I update the data and apply a new pack the wrong elements are updated so they transition to the wrong location within the circle. It seems to be updating the data based on its index but I can’t find any way to tell it which data applies to which existing element. Is there a mechanism to link new data with existing elements, for example an ID or anything?
This is noticeable if my hierarchy is one level and then later split into groups. Some of the existing circles become the parent group circles rather than creating new ones for it. I do have enter/exit functions but it just does a dumb update as there seems to be no way of linking the existing elements to the new data.
I’m sure there must be a way to bind the two together but for the life of me I can’t find it. Thanks!
Mehdi answered this in the comments but adding here for anybody else with the same issue.
I was missing a key in the data binding reference.
Instead of applying the data using .data(data) I needed to add an additional key so that it could be bound properly like .data(data, function(d) { return d.data.id; })
Related
I've been experimenting Compound Graph editor based on Cytoscape.js.
I created a function for adding a compound node for a set of selected nodes with the same parent.
Roughly, it is first tested if the selected nodes have the same parent. If the case, a new node is create, as a child of the parent, and all the selected nodes are moved to this new node.
It seems to work well.
E.g. I created a set of nodes, and grouped them with two levels of decomposition the following way.
A hierarchy of compound nodes created with the created functions
However, when trying to apply the function on a set of nodes at layer 2 as illustrated in the next figure:
Initiating grouping for a set of nodes layer 2
The result is quite surprising, as illustrated in the next figure. The hierarchy of compound nodes is fully changed, while applying exactly the same algorithm than previously.
result of using the function
So I'm wondering if somebody already faced this, and if any idea concerning the reason of this behavior.
In order to debug, I tried to identify the place where the move function is defined in the code (but I didn't succeed yet) in order to put a breakpoint on the call to the move function and to identify which part of the code could cause the unexpected change of parents.
So if someone know where I can find this piece of code, it will also be helpful for understanding where the issue is coming from.
I have nodes variable that is an array with all nodes from graph. Each node has a SVG g (group) SVG element in DOM.
To select the data having the DOM element I do d3.select(myElement).data()[0], but how can I get the DOM element having the data?
You can do this using D3's data matching. The idea is to have the data "identify" the correct element for you.
d3.selectAll("allMyElements").data([datumIwantElementFor], suitableKeyFunction);
Complete demo here.
To be clear, I do not recommend taking this approach. This is prone to breaking when you add more elements to the document and accidentally select some of those, use the same data in more than one place, or change the data format. It will lead to bugs that are really hard to spot unless you know what you're looking for.
A better approach is to assign an ID or class from that data to each element that you bind the data to so that you can select it directly when you have the data. This will also be more efficient.
I'm getting used to d3, but seem to be having trouble with more advanced structures. I'm fairly sure there's some subtlety or concept I am not fully appreciating. I want to be able to mirror a changing hierarchical data structure with a changing hierarchical element structure.
My data structure is 3 groups, each with 3 items. Each group and item has a unique key, extracted using a key function in the data() call.
I build the structure, and I can remove a top-level item; .exit().remove() works just fine on that selection. BUT, modifying or removing any sub-item is simply not reflected in the generated element structure.
Full (non-)working jsFiddle here!: http://jsfiddle.net/eu95R/2/, and the all-important enticingly beautiful screenshot:
The problem is that your definition of groups is using svg.enter() and the subselection is made on groups. That is, you're not seeing a change because groups in this case is empty (no enter selection for the SVGs) and therefore there's no subselection.
To fix, simply do the subselection based on e.g. svg (there are a number of ways to fix this -- not saying that this is necessarily the best one). As you are appending the elements to a g within the SVG, the selector would be svg.selectAll("g").selectAll("text.item")....
Complete demo here.
I'm playing around with different ways of integrating D3 and Angular, in the context of learning both of these frameworks, and am hoping for some input:
My client application receives a JSON array of nodes and an array of edges from my server.
The central component of the client is a graph visualization implemented as a D3 force-directed layout: For each node in the nodes array, a (SVG) circle element is added to this visualization, and lines between these circles are added for each edge in the edges array.
Of course, D3's selection.data( ) makes it trivial to add these elements and bind each to the data it represents, but the graph visualization is only part of a much larger application: I need to create different types of elements representing these same nodes and edges in different parts of the application (which D3 has nothing to do with), and I'd like to keep all of these elements bound to a single dataset.
My primary goal is minimizing code complexity, and - although I've fallen in love with the simplicity and elegance of D3's data-binding functionality - I've arrived at the tentative conclusion that using it in one part of the application while using Angular to do the same in other parts is creating unnecessary complexity, and that the simplest approach would be to use Angular to handle the data-binding/element-creation
In other words, instead of using selection.data( ).enter( ).append( ) to create SVG elements, I'm thinking I should do so using a ng-repeat="node in nodes", perhaps creating each as a custom directive to allow for custom functionality. Having done so, I would then need to get these elements "into" D3, i.e. managed by its force-directed layout.
Is my reasoning here sound? In particular, I'm worried that I'm overlooking complications this will create with regard to object constancy, which is an important requirement as nodes will be entering, exiting and moving about the screen constantly and I need these transitions to be smooth. How would you recommend I go about integrating my angular-created elements into D3 (more precisely, getting them into my force.nodes{ } and force.links( ) arrays) to avoid any such complications?
Finally, I'm also considering a strategy that I'm hoping might give me the best of both worlds: I could use D3 to create my SVG elements and bind them to their respective datum, but rather than executing that code in the link function of visualization directive (as I've been doing, and as all the Angular/D3 tutorials I've found do), I could run it in the compile function, and do something like this:
d3.select('SVG')
.selectAll('.node')
.data('nodeDataArray')
.enter( )
.append('circle')
.attr("class", "node-icon"); //...other attributes/styles etc
where node-icon is the normalized name of a directive with a restrict property that includes C. If this runs in the compile method, angular should then compile all of these directives as normal, adding any additional functionality / interfaces with other directives (etc.), the same way it does with any other type of element. Right?
This is the option I'm most attracted to intuitively - are there any pitfalls of it I might be overlooking, which might make the former strategy preferable?
I have been pondering pretty much the same problem for a while and come to the following conclusion.
The simplest way to integrate Angular created elements with d3 is to add the directives with .attr and then .call the compile service on the d3 generated elements. Like this:
mySvg.selectAll("circle")
.data(scope.nodes)
.enter()
.append("circle")
.attr("tooltip-append-to-body", true)
.attr("tooltip", function(d){
return d.name;
})
.call(function(){
$compile(this[0].parentNode)(scope);
});
Here is a Plunker.
I think the idea of generating elements with Angular ngRepeat rather than d3 is working against the frameworks. D3 does not expect to be handed a bunch of elements. It wants to be handed data - almost always an array. It then has a stack of excellent functions to convert that data into various SVG or HTML elements. Let it do that.
It seems from this quote...
D3 makes it trivial to add elements and bind each to the data it represents, but the graph visualization is only part of a much larger application: I need to create different types of elements representing these same nodes and edges in different parts of the application (which D3 has nothing to do with), and I'd like to keep all of these elements bound to a single dataset.
... you are implying that generating elements with d3 somehow prevents you from binding the same data to different parts of the application. I can't see why. Just have d3 generate elements from a scope array (as is done in the linked Plunker). Then use the same dataset wherever you want in the usual Angular way. Other parts of the application can update the dataset and a $watch callback can re-render the d3 graphic.
I am working on a graph which basically contains at least 150 nodes using D3 Javascript. The idea can be described as follows:
One node is connected to 100 nodes.
49 nodes of these 100 nodes are connected to other 49 nodes.
Let's clarify it. I know 100 people. 49 people of them have birthdays. Each of which represents a node.
In such a way, I could manage to link them together using source and target through some arrays in Javascript. I think I am missing something very important to understand in Force-Directed Graph in D3. As far as I am aware, the links can be implemented with source and target. My problem is I cant understand how I can link these links to these nodes. I do not know on what basis I can create an array to hold my name, peoples' names and their birthdays so they can be linked together. In my graph, it gives me the right person but the wrong birthday. Could anyone please guide me the right way how to do with this matter. I am following this tutorial: and could not understand how I can create an array to add the information such as: name, peoples names and birthday.
http://bl.ocks.org/mbostock/4062045
Your assistance would be very much appreciated.
The way the nodes and links are linked is by index. That is, if target: 0, it means that the target node of that link is the first one in the list of nodes, as passed to the force layout. You can add any data you need to both links and nodes -- for links, the nodes are also available through link.source and link.target.
Alternatively, you can specify the source and target of a link as the object itself -- this is actually what D3 does internally when you pass the links to the force layout. As it can't work with the indices as such, they are replaced with the resolved node objects (this is why you can access the actual objects through the links).
If you're specifying the graph structure through external data (e.g. JSON), then you should use indices to identify nodes in links. If you're generating the data structure in Javascript, you could use references to the actual objects instead.