Hi I would like to use Fisheye Distortion plugin for my force-directed graph in d3.js, but when I want to apply this plugin, behaviour of graph is weird. I am new in d3.js and not good at computer graphics.
complete sample in jsfiddle
var fisheye = d3.fisheye.circular()
.radius(200)
.distortion(2);
// graph - variable which represents whole graph
graph.svg.on("mousemove", function() {
fisheye.focus(d3.mouse(this));
d3.select("svg").selectAll("circle").each(function(d) { d.fisheye = fisheye(d); })
.attr("cx", function(d) { return d.fisheye.x; })
.attr("cy", function(d) { return d.fisheye.y; })
.attr("r", function(d) { return d.fisheye.z * 4.5; });
d3.select("svg").selectAll("line").attr("x1", function(d) { return d.source.fisheye.x; })
.attr("y1", function(d) { return d.source.fisheye.y; })
.attr("x2", function(d) { return d.target.fisheye.x; })
.attr("y2", function(d) { return d.target.fisheye.y; });
});
Weird behaviour I mean the nodes of graph disappear (are hidden) after mouseover action.
The problem is that you were using the code to add cx and cy to circles, but your circles were actually enclosed inside nodeElements which were transformed into place.
Hence, changing the fisheye code to the following solves the problem:
graph.svg.on("mousemove", function() {
fisheye.focus(d3.mouse(this));
// Change transform on the .node
d3.select("svg").selectAll(".node")
.each(function(d) { d.fisheye = fisheye({ x: graph.x(d.x), y: graph.y(d.y) }); console.log(d.fisheye, d); })
.attr("transform", function (d) { return "translate(" + d.fisheye.x + "," + d.fisheye.y + ")"; })
// Now change the 'r'adius on the circles within
// One can also scale the font of the text inside nodeElements here
.select("circle")
.attr("r", function(d) { return 15 * graph.nodeSizeFactor * d.fisheye.z; });
d3.select("svg").selectAll("line")
.attr("x1", function(d) { return d.source.fisheye.x; })
.attr("y1", function(d) { return d.source.fisheye.y; })
.attr("x2", function(d) { return d.target.fisheye.x; })
.attr("y2", function(d) { return d.target.fisheye.y; });
});
Note that I have also applied the proper scales graph.x and graph.y for the transform attribute and 15 * graph.nodeSizeFactor for the radius of the circles (instead of 4.5).
Working Demo: http://jsfiddle.net/90u4sjzm/23/
Related
I need to convert a forceDirected layout to use stepped path curves instead of point to point straight lines. Typical links connecting nodes use this method.
function ticked() {
link
.attr("x1", function(d) { return d.source.x; })
.attr("y1", function(d) { return d.source.y; })
.attr("x2", function(d) { return d.target.x; })
.attr("y2", function(d) { return d.target.y; });
node
.attr("cx", function (d) { return d.x+6; })
.attr("cy", function(d) { return d.y-6; });
}
Here is an example of a forceDirected as a use case
https://www.d3-graph-gallery.com/graph/network_basic.html
Here a rough idea of how I want the links to be rendered shown in red.
Here is a method I am trying to use as a solution
https://www.geeksforgeeks.org/d3-js-curvestep-method/
and here is a snippet of code I am testing based on the linked example above without much success
var line = d3.line()
.x((d) => d.x)
.y((d) => d.y)
.curve(d3.curveStep);
function addConnections(o){
// o is link.d from existing links between nodes
var ldata = []
var d = {x: o.source.x,y:o.source.y}
ldata.push(d)
var d = {x: o.target.x,y:o.target.y}
ldata.push(d)
var steps = svg
.selectAll("path")
.data(ldata)
.enter()
.append("path")
.attr("d",function(d){
console.log(d)
line(d)
})
.classed("steps",true)
}
Here is the final simple solution I came up with that works for generating stepped curves as links between nodes in forceDirected layout
line = d3.line()
.x((d) => d.x)
.y((d) => d.y + 5)
.curve(d3.curveStepAfter)
function ticked(){
link
.attr("d", function (d) {
a.push({x: d.source.x, y: d.source.y});
a.push({x: d.target.x, y: d.target.y});
return line(a)
})
}
This is my first question here so bear with me...
I've made a Force-Directed graph using the d3 inbuilt force layout. This works find on the initial load, but when I try to reload with new data (using onchange with a select element), all the links disappear from the force layout.
The data seems to be updating correctly, in that the correct links are still present in the data, I just can't understand why the links aren't coming through as lines in the svg element.
I've made a js fiddle to show how I'm doing it:
https://jsfiddle.net/onlyskin/f5a4uxje/
(run and then select either 'size' or 'colour' from the dropdown - the expected behaviour is for the appropriate nodes and links to disappear).
The update function looks like this:
function update() {
var pointGroup = nodeGroup.selectAll('.node')
.data(data.nodes, function(d) { return d ? d.id : this.id; });
pointGroup.exit().remove();
var newNodeGroups = pointGroup.enter().append('g')
.attr('class', 'node');
newNodeGroups.append('circle')
.attr('r', function(d) { return circleRadius[d.type] });
newNodeGroups.append('text')
.text(function(d) { return d.id; })
.attr('class', function(d) { return d.type; });
var allLinks = linkGroup.selectAll('line')
.data(data.links, function(d) { return d.source+'-'+d.target; });
allLinks.exit().remove();
newLinks = allLinks.enter().append('line')
.attr('class', 'link');
var circles = nodeGroup.selectAll('circle');
var text = nodeGroup.selectAll('text');
var lines = linkGroup.selectAll('line');
function ticked() {
lines
.attr("x1", function(d) { return d.source.x; })
.attr("y1", function(d) { return d.source.y; })
.attr("x2", function(d) { return d.target.x; })
.attr("y2", function(d) { return d.target.y; });
circles
.attr("cx", function(d) { return d.x; })
.attr("cy", function(d) { return d.y; });
text
.attr("x", function(d) { return d.x; })
.attr("y", function(d) { return d.y; });
}
simulation
.on('tick', ticked)
.restart();
};
I am making grouped bar chart based on Mike Bostock's tutorial.
I can't figure out how to put circles on top of my bars to act as tooltip when hovering, just like in this tutorial except it's on bars and not on a line.
I tried appending the circles like this :
svg.selectAll("dot")
.data(data)
.enter().append("circle")
.attr("r", 5)
.attr("cx", function(d) { return x1(d.name); })
.attr("cy", function(d) { return y(d.value); })
});
But I get NaN values. I am very confused about which variable I should use to get the right cx and cy.
Here is my code.
Any ideas ?
Thank you
You will get NaN values since your data join is not correct, you are trying to get values that are not currently present in your data. In order to get those values you would need to make a reference to data.years.
Here is my approach:
// Inheriting data from parent node and setting it up,
// add year to each object so we can make use for our
// mouse interactions.
year.selectAll('.gender-circles')
.data(function(data) {
return data.years.map(function(d) {
d.year = data.year;
return d;
})
})
.enter().append('circle')
.attr("class", function(d) {
return "gender-circles gender-circles-" + d.year;
})
.attr("r", 10)
.attr('cx', function(d) {
console.log(d)
return x1(d.name) + 6.5;
})
.attr('cy', function(d) {
return y(d.value) - 15;
})
.style('display', 'none'); // default display
// ....
// Using an invisible rect for mouseover interactions
year.selectAll('.gender-rect-interaction')
.data(function(d) { // Inheriting data from parent node and setting it up
return [d];
})
.enter().append('rect')
.attr("width", x0.rangeBand()) // full width of x0 rangeband
.attr("x", function(d) {
return 0;
})
.attr("y", function(d) {
return 0;
})
.attr("height", function(d) { // full height
return height;
})
.style('opacity', 0) // invisible!
.on('mousemove', function(d) { // show all our circles by class
d3.selectAll('.gender-circles-' + d.year)
.style('display', 'block');
})
.on('mouseout', function(d) { // hide all our circles by class
d3.selectAll('.gender-circles-' + d.year)
.style('display', 'none');
});
Working plnkr: https://plnkr.co/edit/oH4KXdxdIW82nLGv46NI?p=preview
I am using D3's forced layout to display a graph. Now, I require that the nodes change their positions when any node is clicked.
I looked up other related StackOverflow questions but that didn't help me.
The code for render is as follows :
var render = function(graph){
/* var loading = svg.append("text")
.attr("x", width / 2)
.attr("y", height / 2)
.attr("dy", ".35em")
.style("text-anchor", "middle")
.text("Simulating. One moment pleaseā¦");*/
force
.nodes(graph.nodes)
.links(graph.links)
.start();
var link = svg.selectAll(".link")
.data(graph.links);
//Enter phase for links.
link.enter().append("line");
//Update phase for links
link
.attr("class", "link")
.style("stroke-width", function(d) { return Math.sqrt(d.value); });
var node = svg.selectAll(".node")
.data(graph.nodes,function(d){return d.name;});
//Enter phase for nodes
var node_g = node.enter()
.append("g")
.attr("class","node")
.on("dblclick",nodeClick)
.call(force.drag);
//Update phase for nodes
node_g.append("text")
.attr("class","NodeLabel")
.text(function(d){
return d.name;
});
var nodeCirlce = node_g.append("circle");
nodeCirlce
.attr("r", 5)
.style("fill", function(d) { return color(d.group); })
node_g.append("title")
.text(function(d) { return d.name; });
force.on("tick", function() {
link.attr("x1", function(d) { return d.source.x; })
.attr("y1", function(d) { return d.source.y; })
.attr("x2", function(d) { return d.target.x; })
.attr("y2", function(d) { return d.target.y; });
node_g.attr("transform",function(d){
return "translate("+ d.x+","+ d.y+")";
});
//TODO : Add attr change for node text as well.
});
And the code for the node click handler looks like this :
var nodeClick = function(d,i){
//Translate the graph to center around the selected node.
var x_trans = x_cent - d.x;
var y_trans = y_cent - d.y;
var nodes = oldGraph.nodes;
for(var i=0;i<nodes.length;i++){
var node = nodes[i];
node.x = node.x + 1000;
node.y = node.y + 1000;
node.fixed = true;
}
//oldGraph.nodes = updateNodes(nodes,oldGraph.links);
render(oldGraph);
//setTimeout(function(){layout("json/HUMAN-1g.json");},000);
};
However, the node positions don't get updated.
After changing the data, you need to run the code that updates the positions in the DOM. This is exactly what you have in the tick event handler function:
link.attr("x1", function(d) { return d.source.x; })
.attr("y1", function(d) { return d.source.y; })
.attr("x2", function(d) { return d.target.x; })
.attr("y2", function(d) { return d.target.y; });
node_g.attr("transform",function(d){
return "translate("+ d.x+","+ d.y+")";
I recommend pulling this out into a separate function and then setting it as the tick handler function and calling it from your nodeClick() function. To be clear, you don't need to call render() from the nodeClick() function.
I have network data for different days which I plot as a force-directed graph for each single day. When I press a button, the network is partially (leaving nodes are removed, new nodes are drawn) updated to the following day. Everything is working fine except for one thing.
For every new day I update some attributes of my nodes-array from my data (e.g. degree of the node). This also works fine, since I can see that the attributes have been updated correctly when I just look at my nodes-array after switching to the next day. However the command
`.append("circle").attr("r", function(d) { return 2*d.Degree+10; })`
is not conducted with the new attributes and the radius of the nodes do not represent by the degree of the node at the date the graph show.
How can I update my graph such that the new values for Degree are used to define the radius of the nodes?
Here is my function start(), which I call after manipulating my data to plot the graph:
function start() {
var force = d3.layout.force()
.charge(-130)
.linkDistance(230)
.size([width, height]);
force.nodes(nodes)
.links(edges)
linkOP = linkOP.data(edges, function(d) { return d.source.id + "-" + d.target.id; });
linkOP.enter().insert("line", ".node1")
.attr("class", "link1")
.style("stroke-width", function(d) {
return 2*d.weight.weight;
});
linkOP.exit()
.remove();
nodeOP = nodeOP.data(nodes, function(d) { return d.id;});
nodeOP.enter()
.append("g")
.append("circle")
.attr("r", function(d) { return 2*d.Degree+10; })
.attr("class", "node")
.style("fill", function(d) { return color(d.bipartite); });
nodeOP.append("title").text(function(d) { return d.name; });
nodeOP.call(force.drag);
nodeOP.append("text")
.text(function(d) { return d.id; });
nodeOP.moveToFront();
nodeOP.exit().remove();
force.start();
clean();
force.on("tick", function() {
linkOP.attr("x1", function(d) { return d.source.x; })
.attr("y1", function(d) { return d.source.y; })
.attr("x2", function(d) { return d.target.x; })
.attr("y2", function(d) { return d.target.y; });
nodeOP.attr("cx", function(d) { return d.x; })
.attr("cy", function(d) { return d.y; });
nodeOP.attr("transform", function (d) {
return "translate(" + d.x + "," + d.y + ")";
});
});
}
I know this is a very simple question... Thank you very much for your help!
Your code doesn't have an update to the radius. Everything called by .enter() only occur for new items. So because you only have the line
.append("circle").attr("r", function(d) { return 2*d.Degree+10; })`
Inside the .enter that portion only occurs on the new nodes.
I built a jsfiddle on enter, update, exit. Here: http://jsfiddle.net/TheMcMurder/H3HTe/
I hope that helps