d3 force graph: sticky nodes - javascript

I would like to add a behavior to a force directed graph layout in D3 in such a way that once dropped, a dragged-and-dropped svg node sticks in its place, no longer changing position no matter what else happens in the graph. I have done some reading about this API but I can't figure out a way to get that one working.
The problem I am trying to solve is allowing a user to "pick apart" a complex force graph.

Set the fixed property of the node to true on mousedown.
node.on("mousedown", function(d) { d.fixed = true; });
For example: http://bl.ocks.org/3750558

Related

D3-Force-Quicker stabilization of nodes & transition of elements in projectiles

I have implemented the following version of the force diagram to show inter-cluster movement of nodes.
https://jsfiddle.net/Edwig_Noronha/67ey5rz0/
The nodes are grouped into four clusters. After the first initialization of the force diagram ends I call a function to transition the nodes from source to destination clusters.
function moveNodes() {
Object.keys(inputdata).forEach(function(key, index) {
svg.selectAll("circle.viewernodes" + index)
.each(function(d) {
d.type = d.destination;
});
});
viewersTransitioned = true;
force.start();
}
However, The stabilization of the first initialization of the force diagram takes about 35 seconds. Hence the transition happens after that much time.
Q1) is it possible to achieve a quicker stabilization of the force diagram with collision detection?
The transition of the nodes from source to destination clusters happens along a linear path.
Q2) Is it possible to make the nodes move along projectile paths?
To achieve quicker stabilization you can do one of two things in my experience.
Initialize the nodes X and Y values to be near to their end/goal state
Such as nodes[i].x = 500 etc, then calling the simulation start.
This would somewhat defeat the purpose of what you're trying to show in your example, unless you don't want the nodes to be shown moving to the groups and just be in them to begin with...
Stronger force
Have the force moving/pulling the nodes be stronger. This would require an essentially fundamental change to your approach to this example. Instead of just transitioning their positions, create custom forces within your force-layout that affect the appropriate nodes only based on their attributes. Place these forces in the center of your 'sorting circles' and they would attract the nodes appropriately.
See here for what something like this would look like: https://bl.ocks.org/mbostock/1021841

how to improve the drilling performance of d3 sunburst chart when there are too many paths? like dynamically show partial levels?

I'm new to d3 and now trying to render a sunburst chart with a really big set of data. But I find out that with the number of paths goes up, the drilling of chart becomes dull, like stops for one sec and then drills.
Is there any way to improve this? I'm thinking to display a limited number of levels but how to make showing levels dynamically change when the center changes?
function click(d) {
var duration = config.animationDuration || config.animationDuration === 0 ? config.animationDuration : 1000;
path.transition()
.duration(duration)
.attrTween("d", arcTween(d))
path.attr("display", function(d){
if(d.depth>config.drillDownPath.length + data.levelLimit-1) return "none";
}); }
In the code above, I tracked the chart drilldown path to decide if a path is shown or not. But this didn't solve the question because the the path still exists in the DOM.
Can anyone help me with this? Thanks!
I know this is a bit late, but I might try this example for displaying a large hierarchical dataset allowing for the user to drill down to any level.
By only rendering and transitioning 2-3 levels of arcs at a time, your transitions will probably lag less and the chart would probably be more readable. However, as mentioned by Lars, it's really hard to give you a concrete demo without seeing more code...

D3 radial force layout

I am working on a d3 force layout which requires the nodes to be placed in such a way that there is a central node according to which all the other nodes are radially placed.The nodes are linked to each other like a normal force layout with appropriate source and target. The central node is dictating the position of all the other nodes, so essentially it is the source of all the nodes. Right now all I have been able to manage to do is to place them in a linear fashion using the linkDistance property with one node as the reference, but I need it in a radial manner. I could have shown an image but apparently my reputation is too low and I am not being allowed to post one.Can someone help me out with this?
Take a look at this example:
link to jsfiddle
Central (root) node has special treatment that makes it always remain in the center of the graph. On initialization, central node's property fixed is set to true, so that d3 force layout simulation doesn't move it. Also, it is placed in the center of rectangle containing layout:
root.fixed = true;
root.x = width / 2;
root.y = height / 2;
Hope this helps.

Disable node drag in force layout

Implemented force layout, here is the fiddle. However I want to disable the drag behaviour for the nodes, drag event will move the nodes across and set the nodes center to cursor position until mouseup event. Is there a way we can disable this? I tried removing the callback from the nodes as follows:
node.append("svg:circle").attr("r",5).style("fill", "#FE9A2E").on("mousedown.drag", null)
This didn't work. not sure if it will remove the callback from the node. also tried fixing the node position on drag event by setting fixed property of the node to true. But that fixes the node after it has been dragged. how can I stop the nodes from dragging?
The default drag behaviour is added to the nodes in the last part of this statement:
var node = vis.selectAll("g.node").data(nodeSet).enter()
.append("svg:g").attr("class", "node").call(force.drag);
Just remove the .call(force.drag) and you can no longer move around individual nodes. If you also want to get rid of the behaviour where you can move around the entire graph, it is part of the "zoom" behaviour added in the last line of this statement:
var vis = d3.select(".journalGraph").append("svg:svg")
.attr("width", w).attr("height", h)
.attr("pointer-events", "all").append('svg:g')
.call(d3.behavior.zoom().on("zoom", redraw)).append('svg:g');
Removing the call statement would get rid of both the pan and zoom functionality. See https://github.com/mbostock/d3/wiki/Zoom-Behavior for more on zooming.

D3 graph with svg nodes - How to move nodes to arbitrary positions

I want to make a D3 graph, which should be as follows:
When the html page is loaded, there will be a single node at a fixed location. Let us say top left. Let us call it template node and this node is non-movable.
When the user does mouse down on the template node, a new node is created at the same location as the template node and the user should be able to drag the new node to where he wants. The new node should remain exactly where the user moves it to.
At any time user should be able to move a node. Again the node should remain where the user leaves it.
User should be able to draw link between any two nodes. Let us assume that if he drags from one node to another without holding down ctrl key, then a link is drawn and if he drags while holding down the control key, then the node moves.
When a link is drawn between two nodes, then the nodes should not change positions.
When two nodes are linked and one of them is moved by dragging it, then the link should change in size and orientation as needed.
I am using force layout.
I am able to create a template node but it always goes to the center of the container - I think it is because the center of the container is the center of gravity. But not sure how to fix its position to the top left through code.
I can create links and new nodes. But the nodes move and links resize. May be it is because force layout tries to make link lengths equal to the link distance in the force layout. But I do not know how to use a function for link distance? I am even not sure if that will really help.
So what method should I use? Any idea?
For force layout, you can set the 'fixed' property of a node to true in order to prevent it from being affected by the simulation. After that, you should be able to set it's position manually. You might choose to do this in a function call:
function pinNode(node) {
node.fixed = true;
}
function unpinNode(node) {
node.fixed = false;
}
I believe you could get a node to the upper left corner with a call like this: pinNode(node, 0, 0). As long as the node has its fixed property set to true, it should remain unaffected by the sim. You might find this snippet from the documentation helpful; it describes how the fixed property is affected by force.drag:
Bind a behavior to nodes to allow interactive dragging, either using
the mouse or touch. Use this in conjunction with the call operator on
the nodes; for example, say node.call(force.drag) on initialization.
The drag event sets the fixed attribute of nodes on mouseover, such
that as soon as the mouse is over a node, it stops moving. Fixing on
mouseover, rather than on mousedown, makes it easier to catch moving
nodes. When a mousedown event is received, and on each subsequent
mousemove until mouseup, the node center is set to the current mouse
position. In addition, each mousemove triggers a resume of the force
layout, reheating the simulation. If you want dragged nodes to remain
fixed after dragging, set the fixed attribute to true on dragstart, as
in the sticky force layout example.
force.drag
Also see here: force layout nodes
If you want to use a function for link distance, include it when you create the force layout:
var force = d3.layout.force()
.size(width, height)
.linkStrength(0.5) // how much can link distance be overridedn by the simulation
.linkDistance(function() {return /* some evaluation */;});
// ...
// You might need to defer the calculation of linkDistance until later,
// such as in update(), since nodes might not have the properties
// that you need to check until that point:
function update() {
force
.nodes(nodes)
.links(links)
.linkDistance(function(link) {
// The function gets called for each link in the simulation.
// Each link will be connected to two nodes, source and target,
// which may be useful in determining link distance.
if (link.source.someProperty || link.target.somePropery) {
return /* something */;
} else {
return /* something else */;
}
});
}

Categories