I have created a dynamically built force graph in D3. When I click on one of the links I want to only show that and all the connected links/nodes.
This Plunkr is a simplified version of what I have: http://plnkr.co/edit/TiKKmvydqXNipe103juL?p=preview (EDIT: I have updated this and is now a complete solution).
As you can see there are 3 separate 'groups' of connected nodes, in my real data set there are several hundred nodes and I want to be able to isolate an individual 'group' and only show that when the link which is part of that group is clicked on (it can't be the node that is clicked on as this shows a pop-up with more info).
I have been able to colour/hide connected links by pre-processing my data and using a rather complicated and long winded algorithm to determine whether the links are in the same connected group.
I have also been able to change the node that is clicked on or all the nodes/links by changing the dragstart function, this feels like I am getting close, but I don't think it recognises any link relationships.
function dragstarted(d) {
if (!d3.event.active) simulation.alphaTarget(0.3).restart();
// Changes all nodes/links
d3.select("circle").classed("others",true).style("display","none");
// Trying different ways to hide only 'other' nodes
d3.selectAll("circle").classed("others",true).style("display","none");
d3.selectAll("line").classed("others",true).style("display","none");
d.fx = d.x;
d.fy = d.y;
}
(I am aware of this question/answer how to highlight(change color) of all connected(neighbours) nodes and links in a d3 force directed graph but I don't think it really helps).
Any help would be appreciated.
OK, this might be beyond me, but I think you somehow need to group your links into the same sorts of groups as your nodes. If you could somehow add the "group" attribute of the source node to the link, then when you click on the link something like this might work:
d3.selectAll("line")function fade(){if
(group = "selected", "opacity" = 1)
else
("opacity" = 0)
};
alternatively, if you can sort the links into groups before the data is loaded (in your PHP etc. script), then you could append 3 (or n) groups of lines with separate classes.
Sorry, JS isn't my strongsuit, hopefully someone more experienced will sort that out for us...
Related
I've got a behavior with d3.zoom whose solution I'm sure is to be found in something I'm obviously missing, but I can't seem to make sense of it. I've reviewed and reviewed examples, and seem to be following them precisely, but something is causing this particular function to not behave.
The following, rather than zoom to focusElement as intended, flips between zooming away from it, and then back to it. The values of -focusBBox['x'], for example, flip between the following two values on subsequent executions. 2500 is svgWidth/2
-208.586669921875
2500
function focusObject(focusElement) {
var focus = document.getElementById(focusElement);
var focusBBox = focus.getBoundingClientRect();
gridGroup.transition().duration(750).call(zoom.transform,d3.zoomIdentity.translate(-svgWidth / 2, -svgHeight / 2).translate(-focusBBox['x'], -focusBBox['y']));
}
Can someone just please take a moment to give a kind virtual slap to point out what it is that I'm missing?
Aha! From a previous incarnation of this particular endeavor, I was using a fixed-position SVG to capture mouse events and applying my transformations to a child SVG. The fact that getBbox() returns local coordinates and getBoundingClientRect() returns coordinates from the outer SVG coord system thus mucked things up.
I've included the revised snippet below. Note that focusBBox2 uses getBBox rather than getBoundingClientRect(), and that grid is the parent SVG as distinct from gridGroup in the original post.
function focusObject(focusElement) {
var focus = document.getElementById(focusElement);
var focusBBox2 = focus.getBBox();
grid.transition()
.duration(750)
.call(zoom.transform,d3.zoomIdentity.translate(focusBBox2.x, focusBBox2.y));
}
I'm backend developer for several years but a newbie in frontend issues.
I used "graphviz" (using d3.js) to draw an SVG graph from DOT notation.
Everything is working fine but one thing I don't get in my mind:
If I "open" another (or the same one) graph its starting position is the
same as this from the previous drawn graph even if I completely remove
the whole node content from the dom as follows:
var svg = d3.selectAll("svg");
var otherBelow = svg.selectAll("*");
otherBelow.remove();
// svg.remove();
Doing this and checking the page source the nodes below SVG are realy dropped
but drawing the new graph it has exactly the position of the previously
moved graph in "transform" attribute. Doing a work around by resetting the
position bevore solves this problem but then the problem remains for the
"moving on mousedown" capability. Then the graph immediately "jumps" to the old
ones position. But therefor I can't even get an information about somewhere
in the page source. Really the generated page code is 100% the same (with
diff tool) but has a different behaviour. Don't understand how this is possible.
So now my question: Is there a kind of caching? Or is there perhaps the
browser cache used somehow internally? How to fix this?
P.s. if I remove the SVG node itself I get a completely courious behaviour.
Then the newly drawn graph is not movable at all.
This (ugly) workaround does it for me.
// Snipped to render a new graph and reset its position
// #graph -> id of d3-graphviz div-container
document.getElementById('graph').innerHTML = ''
setTimeout(() => {
d3.select("#graph").graphviz()
.dot(yourDotData)
.render()
}, 50)
Explanation/Assumption why this works:
It seems that the deletion of the old graph with .innerHTML = ''and the creation of the new one should not happen in the same rendering phase/at the same time, therefore the timeout function.
The second part of the workaround is to use a new graphivz-instance to render the new graph, therefore the
d3.select(...) within the timeout function.
The graphviz renderer is still present on the element it was created on and has all its data still intact.
Removing the svg and then reuse the renderer is not a valid use case. Nico's answer is probably correct, but there are better ways to do it, but in order to tell you how you should do it instead I would need so see all of your code so I can understand what you really want to do.
Could some one please suggest how to do column-drilldown with D3 JS library,
below example is from Hightchart,
http://www.highcharts.com/demo/column-drilldown
A complete code example for this problem is probably quite extensive, so I'll mostly keep to how you would approach it and assume you know enough of D3 to turn the concept into code.
Lets assume you have the functionality for drawing a general bar chart.
Part of that functionality would priobably be things like
Setting up your svg element and containers
Setting up your scales (one for x and one for y)
Adding axes based on the scales you have created
Adding your bars to the svg container
4.1 Make sure you have your data set available as an array
4.2 Create an enter selection for the available data and append rectelements
4.3 Update attributes like x, y for all your available bar nodes
4.4 Remove any nodes on your exit selection
Voila you have a simple bar chart. Nothing new in that and you can have a look at the code details here -> https://bl.ocks.org/mbostock/3885304
Now in order to do the drill down:
In order to avoid lots of code repetition it probably makes sense to separate the above steps into functions. So for example a setup function that just creates your svg and containers as well as your scales.
Important about the setup function is that you do not need to rerun it on drill through.
Second you will want an update function. This contains steps 3+, which you will need to rerun in order to update your charts on drill through.
One addition here would be adding functionality for updating your scale domain in the beginning (as your data changes on drill through and you want to reflect that in your scales).
Now that you have those two functions all you really need to do is:
Add a click handler to your axis labels or your bars (click on bars might be easier for now). You cans use d3's .on() function for that.
In that event you will want to subset your data by the value of the clicked bar (or get a new data set for the bar value depending on how your data is structured) and then run the update function we created above with the new data.
It could look something like this:
d3.selectAll('.bar-nodes')
.on('click', function(d) {
var updatedData = updateData(d);
updateChart(updatedData);
});
If anything is unclear some more specific questions would be good.
Hope that helps.
I use a SELECT drop down box to choose a new JSON data source for a force network graph. The first graph draws correctly: Nodes draw on top of edges and mouseovers work as expected.
When I select the second data source, the edges draw on top of the nodes and I have lost my mouseovers. If I go back to selection "A", the same is true except for the last node.
A jsfiddle showing this problem is here:
http://jsfiddle.net/NovasTaylor/e6qjubaa/
Obligatory stack overflow code inclusion:
//EXIT
edges.exit().remove();
nodes.exit().remove();
I expect this is a problem with my ENTER/UPDATE/EXIT and perhaps the keys I am using to exit the elements? Please see the code in the fiddle.
Any advice would be greatly appreciated. My next step is to add edge labels so I want to ensure I get the nodes and edges and working first.
Tim
So just add some lines to the combobox:
d3.selectAll('#familytreecontentsvg .node')
.each(function (d) {
d.fixed = false;
})
.classed("fixed", false)
And I moved the svg object into the drawings while just initializing it empty above. Also I created an id for the SVG itself. Hope it helps. Please tell me if that is fixing your troubles.
Updated fiddle: http://jsfiddle.net/xg9fjze3/9/
I'm having a heck of a time with transitions in D3js. I posted a fiddle here: dynamic area graph. The problem that I run into is that when trying to follow this tutorial path transitions, I run into sync problems with the xAxis. Bostock indicates that the domain should be skewed slightly so that the data appears to "shift" in from the side. However, when I do that, the data reflected will be listed under a tick mark that is "2 minutes" behind the actual time it should be listed. If I just update the data as-is, without doing the tricky stuff with the clip-path, it works fine. All of the data is in sync. Just for reference, the xAxis is an integer, linear scale. Dealing with date strings was madding, even though d3 has great time manipulation, I just find dealing with epoch easier. If someone could check out the fiddle and let me know how to transition the entire drawing...I want it to be smooth like in the examples that bostock has.
Since SO requires some code, here's the data structure that I'm generating. The rest is in the fiddle:
setInterval(function(){
lastTime = lastTime.add('m',1);
var data = {"apikey":"FOO",
"description":"a dumb description",
"beg_effective_dt_tm":lastTime,
"data":{
"sum":getRandomInt(385,4000),
}
};
tick(data);
},1000)
I think this is close to what you are after: http://jsfiddle.net/JJ7Rj/1/
It is close because the graph is delayed by one value.
Most of the architecture was already there, and your concern that you might lose the sync between the xAxis and the data was correct. Mike gets around it by changing his range of the scales. The other (and better, IMO) way is to do it by changing the domains. I made the following two primary changes.
The domain of the axis
I have modified the minMax function such that it does not include the latest and the last point in the domain of the xAxis. Note that this means that the most recent value (as well as the oldest value) is actually displayed outside the visible region. This is an unfortunate limitation of using the monotone interpolation, which Mike talks about at the bottom of his post.
function minMax(pd) {
return [
d3.min(pd.slice(1).slice(0, -1),function(d){ return d.beg_effective_dt_tm; }),
d3.max(pd.slice(1).slice(0, -1),function(d){ return d.beg_effective_dt_tm; })
];
}
If you want to have all the values visible, then you'll get the wiggling effect of the discontinuous tangent suddenly forming when the new value comes in. You can obtain that by removing the .slice(0, -1).
The initial transform
For each element, I have initially placed the DOM element one step to the right.
var step = x(newVal.beg_effective_dt_tm) - x(pd[pd.length - 1].beg_effective_dt_tm);
// ...
.attr("transform", 'translate(' + step + ')');
// ...
Then finally, I have transitioned everything back to their rightful place:
clipPath.selectAll("path,circle,.dp").transition()
.ease("linear")
.attr("transform", "translate(" + 0 + ",0)");
Also, I have enabled the transition for the xAxis.