D3.js add link with label - d3.js

I'm using a force layout and i want to add new node with link on an existing graph.
Adding node is not a problem, I have an issue with the links.
I put labels on link like this :
Here's the code I use to draw the link :
var link = svg.selectAll(".link");
var links = []; // link array with source and target
[...]
var force = d3.layout.force()
.nodes(nodes)
.links(links)
[...]
link = link.data(force.links(), function (d) {
return d.source.id + "-" + d.target.id;
});
var appendL = link.enter().append("g")
.attr("class", "link");
linkLine = appendL.append("line")
.attr("class", "linkLine")
.style("stroke-width", 1);
linkLabel = appendL.append("text")
.attr("class", "linkLabel")
.attr("dy", 5)
.attr("filter", "url(#solid)")
.text(function (d) {
return d.type;
});
link.exit().remove();
(The filter is use to put a white background on the label).
But when I change my link list, the result is this :
The new link can move but the old ones are fixed and not attach to the node.
I think D3 is having problem to detect if a link is already on the graph.
If you have any idea ... thanks !
PS : Sorry for my english.

Basically you weren't selecting all the lines to update them.
When you update them, you need to select what's already there to 'overwrite' it so to speak. So move this into your update function :
node = svg.selectAll(".node").data(nodes)
link = svg.selectAll(".linkLine").data(links);
Notice I use the selectAll on .linkLine. This is as you give the links a class of linkLine so we need to select them all. Also the way you are appending the line is wrong. This is the correct way :
linkLine = link.enter().append("line")
.attr("class", "linkLine")
.style("stroke-width", 1);
linkLabel = link.append("text")
.attr("class", "linkLabel")
.attr("dy", 5)
.attr("filter", "url(#solid)")
.text(function(d) {
return d.type;
});
link.exit().remove();
Here is your updated fiddle : https://jsfiddle.net/reko91/wdouvab8/1/
Also need to do the labels. As you cant append text to a line in svg, the best way I could think of was to do something similar with the labels as was done with the links.
Create the container with the link data :
linkLabelContainer = svg.selectAll(".linkLabel").data(links);
Create the actual text element :
linkLabel = linkLabelContainer.enter().append("text")
.attr("class", "linkLabel")
.attr("dy", 5)
.attr("filter", "url(#solid)")
.text(function(d) {
return d.type;
});
Don't forget to remove :
linkLabelContainer.exit().remove();
Now position :
linkLabelContainer.attr("transform", function(d) { //calcul de l'angle du label
var angle = Math.atan((d.source.y - d.target.y) / (d.source.x - d.target.x)) * 180 / Math.PI;
return 'translate(' + [((d.source.x + d.target.x) / 2), ((d.source.y + d.target.y) / 2)] + ')rotate(' + angle + ')';
});
Notice I am moving the container on the tick event not the actual text element.
Updated fiddle : https://jsfiddle.net/reko91/wdouvab8/3/

Related

d3 - sunburst partition. Different sizes for each node

I'm a newbie to the d3 library and javascript in general.
I'm trying to achieve something like
this, where I have a sunburst partition but each node has a different height with respect to the radial center - but the padding to its parent/child stays the same.
I've tried looking around and couldn't come up with any solutions.
(trying to change the innerRadius/outerRadius parameters didn't seem to work :( ).
Here is my code:
var vis = d3.select("#chart").append("svg")
.style("margin", "auto")
.style("position", "relative")
.attr("width", width)
.attr("height", height)
.append("svg:g")
.attr("id", "container")
.attr("transform", "translate(" + width / 2 + "," + height / 2 + ")");
var partition = d3.layout.partition()
.sort(function (a, b) { return d3.ascending(a.time, b.time); })
.size([2 * Math.PI, radius * radius])
.value(function(d) { return d.n_leaves+1; });
var arc = d3.svg.arc()
.startAngle(function(d) { return d.x; })
.endAngle(function(d) { return d.x + d.dx; })
.innerRadius(function(d) { return Math.sqrt(d.y); })
.outerRadius(function(d) { return Math.sqrt(d.y + d.dy); });
//read data from json file and visualize it
d3.text("5rrasx_out.json", function(text) {
var data = JSON.parse(text);
var json = buildHierarchy(data,'5rrasx');
createVisualization(json);
});
// Main function to draw and set up the visualization, once we have the data.
function createVisualization(json) {
// Bounding circle underneath the sunburst, to make it easier to detect
// when the mouse leaves the parent g.
vis.append("svg:circle")
.attr("r", radius)
.style("opacity", 0);
// For efficiency, filter nodes to keep only those large enough to see.
var nodes = partition.nodes(json);
var dataSummary = [{label: 'pos', count: totalPos}, {label: 'neg', count: totalNeg}];
//set title
$("#title").text(json.title.replace(/\[.*\]/g,""));
//set chart
var path = vis.data([json]).selectAll("path")
.data(nodes)
.enter().append("path")
.attr("class", "sunburst_node")
.attr("display", function(d) { return d.depth ? null : "none"; })
.attr("d", arc)
.attr("fill-rule", "evenodd")
.style("fill", function(d) { return (d.sentiment > 0) ? colors["pos"] : colors["neg"]; })
.style("opacity", 1)
.on("mouseover", mouseover)
.on("click", click);
};
Any help would be much appreciated.
Thanks!
I know this is not a proper answer to the question above, but in case someone needs a sunburst with different dimensions for each node, here I post how to do it in R using the ggsunburst package.
# install ggsunburst
if (!require("ggplot2")) install.packages("ggplot2")
if (!require("rPython")) install.packages("rPython")
install.packages("http://genome.crg.es/~didac/ggsunburst/ggsunburst_0.0.10.tar.gz", repos=NULL, type="source")
library(ggsunburst)
# one possible input for ggsunburst is newick format
# consider the following newick "(((A,B),C),D,E);"
# you can define the distance in node A with "A:0.5"
# you can define size in node E with "E[&&NHX:size=5]"
# adding both attributes to the newick
nw <- '(((A:0.5,B),C:3),D[&&NHX:size=5],E[&&NHX:size=5]);'
sb <- sunburst_data(nw)
sunburst(sb, rects.fill.aes = "name") + scale_fill_discrete(guide=F)
as you can see in the code, these attributes can be defined independently, and as you can see in the plot they affect the dimennsions of the correponding nodes:
node "A" is 0.5 times shorter than "B", which is defined by the attribute "distance"
E has an angle 5 times wider than C, which is defined by the attribute "size".
and here an attempt to resemble the example posted in the question with a newick tree
nw <- "(((.:0[&&NHX:support=1.0:dist=0.0:name=.:size=3],a3:1[&&NHX:color=2:support=1.0:dist=1.0:name=a3:size=1])1:1[&&NHX:color=-3:support=1.0:dist=1.0:name=a2])1:1[&&NHX:color=-1:support=1.0:dist=1.0:name=a1],b1:1.8[&&NHX:color=1:support=1.0:dist=1.8:name=b1:size=5],(((a4:1[&&NHX:color=1:support=1.0:dist=1.0:name=a4:size=1],b4:1.8[&&NHX:color=-1:support=1.0:dist=1.8:name=b4:size=1],c4:1.5[&&NHX:color=2:support=1.0:dist=1.5:name=c4:size=1],d4:0.8[&&NHX:color=-2:support=1.0:dist=0.8:name=d4:size=1])1:1[&&NHX:color=1:support=1.0:dist=1.0:name=b3:size=1])1:1[&&NHX:color=-3:support=1.0:dist=1.0:name=b2:size=1],(c3:1[&&NHX:color=1:support=1.0:dist=1.0:name=c3:size=1],(e4:1[&&NHX:color=-2:support=1.0:dist=1.0:name=e4:size=1])1:0.5[&&NHX:color=-1:support=1.0:dist=0.5:name=d3:size=1])1:0.5[&&NHX:color=1:support=1.0:dist=0.5:name=c2:size=1])1:1[&&NHX:color=-1:support=1.0:dist=1.0:name=c1:size=1],d1:0.8[&&NHX:color=3:support=1.0:dist=0.8:name=d1:size=20]);"
sb <- sunburst_data(nw, node_attributes = "color")
sunburst(sb, leaf_labels.size = 4, node_labels.size = 4, node_labels = T, node_labels.min = 1, rects.fill.aes = "color") +
scale_fill_gradient2(guide=F) + ylim(-8,NA)

Add text within links in d3.js sankey diagram

I'm trying to create a Sankey diagram based off the example shown here: https://bost.ocks.org/mike/sankey/
While I am able to recreate the diagram, I'd like to write the tooltip text directly onto the links themselves (so they can be read as a static image). I've attempted to add a .append("text") call to the links just like I saw done for the nodes, but it doesn't seem to be working. Below is the code I added to the example above:
link.append("text")
.attr("x", 25)
.attr("y", function(d) { return d.dy / 2; })
.attr("dy", ".35em")
.attr("text-anchor", "end")
.attr("transform", null)
.text(function(d) { return d.source.name + " → " + d.target.name + "\n" + format(d.value); });
.attr("text-anchor", "start");
Yet this doesn't show display anything in the diagram.
UPDATE: I pasted Mike Bostock's code into a gist to make recreating the issue easier: http://bl.ocks.org/almsuarez/50eab68a645d7fc12183384000ea8c82
As was said in the comments you can't append text to SVG elements. So say at the moment you append your link to a container. For example :
var link = container.append('path')....//and so on
You have to do the same for text. So, for example :
var linkText = container.append('text')
You say you can't keep track of the x and y positions ?
Basically as your link is in between two points, you need to find the centre. Here is how I would do it :
.attr("x", function(d) { console.log(d); return d.source.x + (d.target.x - d.source.x) / 2; })
.attr("y", function(d) { return d.source.y + (d.target.y - d.source.y) / 2; })
So you get the source' x position then add half the difference of targetX - sourceX and do the same for y.
I have put this in a fiddle for you to see : https://jsfiddle.net/thatOneGuy/8ayq5nwa/2/
You can hover over the links to see the title that way you can check it's all correct :)

How to move elements along with svg group

I have a circle that gets appended on drag drop. I want the circle to move along with group when I move the group around with mouse
Here's what I have tried which isn't working:
//targetG is the group element
targetG.append("rect")
.attr("fill", "none")
.style("stroke", "black")
.style("stroke-width", "2px")
.attr("width", 200)
.attr("height", 200)
.style("fill", "white")
.call(
d3.behavior.drag()
.on('drag', moveRect).origin(function () {
var t = d3.select(this);
return {x: t.attr("x"), y: t.attr("y")};
}));
Here's the full code in fiddle: http://jsfiddle.net/vk62y7un/
There's a couple of small problems that need to be fixed.
Your translate component split function is splitting by ,.
translate = (translate.substring(0, translate.indexOf(")"))).split(",");
While this works in Chrome, it should be splitting by space for IE.
translate = (translate.substring(0, translate.indexOf(")"))).split(",");
if (translate.length === 1)
translate = translate[0].split(' ');
The circle wasn't being attached to the g because of this.
Your (container) drag event is attached to the rectangle inside the g. This should be attached to the g instead. Correspondingly the drag functions should manipulate the transform (translate) attribute and not the x and y.
var targetG = svg.append("g")
.attr("transform", "translate(150,150)")
.call(
d3.behavior.drag()
.on('drag', moveRect).origin(function () {
...
and
function moveRect() {
d3.select(this)
.attr('transform', 'translate(' + d3.event.x + ' ' + d3.event.y +')');
}
The origin (for the g now) should be the (parsed) transform (translate) attribute at the start of the drag.
....
var tc = d3.select(this).attr('transform').replace(/[a-z()]/g, '').split(' ');
if (tc.length === 1)
tc = tc[0].split(',')
return { x: Number(tc[0]), y: Number(tc[1]) };
}));
Notice, the ===1 check and split - that's so that it works in IE and Chrome.
Fiddle (works in IE and Chrome) - http://jsfiddle.net/3hyu6om8/
The problem is when you try to drag rectangle you don't select circles. I made some changes and you could drag circles along rectangle.
Add this part to your code:
var groupAll = d3.behavior.drag()
.origin(Object)
.on("drag", function(d, i) {
var child = this;
var move = d3.transform(child.getAttribute("transform")).translate;
var x = d3.event.dx + move[0];
var y = d3.event.dy + move[1];
d3.select(child).attr("transform", "translate(" + x + "," + y + ")");
});
Complete Code here.

how to update specific node in D3

I'm living updating nodes and links. I also want to change the radius of existing nodes(circles). how can i update eg a specific circle?
When i do this below it updates all circles.
node.select("circle").attr("r", circleradius);
when i update the node array nothing changes in the viz.
nodes[index].size = 2000;
this is how the outputted html looks like
this is the function update code:
function update() {
// Restart the force layout.
force
.nodes(nodes)
.links(links)
.start();
// Update links.
link = link.data(links, function(d) { return d.target.id; });
link.exit().remove();
link.enter().insert("line", ".node")
.attr("class", "link");
// Update nodes.
node = node.data(nodes, function(d) { return d.id; });
node.exit().remove();
var nodeEnter = node.enter().append("g")
.attr("class", "node")
.on("click", click)
.call(force.drag);
nodeEnter.append("circle")
.attr("r", function(d) { return Math.sqrt(d.size) / 2 || 24.5; });
nodeEnter.append("text")
.attr("dy", ".35em")
.text(function(d) { return d.name; });
node.select("circle")
.style("fill", color);
}
Child elements are not automatically updated until you use d3.select. It's a bit of a hard thing to wrap your head around which I won't get into here.
But for practical purposes, what you need to do is, after you've created your force-directed layout and elements and such, if you want to change a data value used for size element 0 and see it update, the code needs to read like this:
nodes[0].size = 2000;
d3.selectAll("g").select("circle")
.attr("r", function(d) { return Math.sqrt(d.size) / 2 || 24.5; });

D3 how to make dynamic sequential transitions work using transform/translate and offset coordinates?

I am trying to create an interactive org chart such that when I click on a box that box is repositioned in the centre of the SVG container and all other elements transition as well but remain in the same relative position. So if you click the top box in the list, they all move down together. Then if you click one of the lower boxes they all move up together but always so the selected box is in the centre. If you click on a box which is already in the middle it should not move but at the moment they are flying all over the place.
I have got this working for the first click but on each subsequent click the boxes start flying all over the place. I am using the mouse listener to get the current position and calculate an offset to centre the selected box that I feed into transform/translate. I think this is where the strange behaviour is coming from because the offset is calculating correctly (viewed through console.log) but the applied transition is not equal to this calculation.
I have read many posts about transform/translate but they all seem to apply to a single transition, not multiple sequential transitions. I have tried using .attr(transform, null) before each new transition but this didn't work. I have also tried to dynamically extract the current x,y of the selected component and then update these attributes with the offset value but this didn't work either. Am really stuck with this and any help is greatly appreciated!
Thanks,
SD
<script type="text/javascript">
var cwidth = 1000;
var cheight = 500;
var bwidth = 100;
var bheight = 50;
// container definition
var svgContainer = d3.select("body").append("svg")
.attr("width",cwidth)
.attr("height",cheight)
.on("mousemove", mousemove);
// Background gray rectangle
svgContainer.append("svg:rect")
.attr("x",0)
.attr("y",0)
.attr("width",cwidth)
.attr("height",cheight)
.style("fill", "lightgrey");
// data
var secondData = [
{ "idx": 1, "name": "Commercial" },
{ "idx": 2, "name": "Finance" },
{ "idx": 3, "name": "Operations" },
{ "idx": 4, "name": "Business Services" }
];
var secondElements = secondData.length;
// group definition
var secondNodes = svgContainer.append("g")
.attr("class", "nodes")
.selectAll("rect")
.data(secondData)
.enter()
.append("g")
.attr("transform", function(d, i) {
d.x = 300;
d.y = ((cheight/secondElements)*d.idx)-bheight;
return "translate(" + d.x + "," + d.y + ")";
});
// Add elements to the previously added g element.
secondNodes.append("rect")
.attr("class", "node")
.attr("height", bheight)
.attr("width", bwidth)
.style("stroke", "gray")
.style("fill", "white")
.attr("y", function() {return -(bheight/2);})
.on("mouseover", function(){d3.select(this).style("fill", "aliceblue");})
.on("mouseout", function(){d3.select(this).style("fill", "white");})
.on("mousedown", center);
// Add a text element to the previously added g element.
secondNodes.append("text")
.attr("text-anchor", "left")
.attr("x", 15)
.attr("y",5)
.text(function(d) {return d.name;});
// gets current coordinates for transition
var current = [0,0];
var xshift = 0;
var yshift = 0;
// get offset to centre from current mouse location
function mousemove() {
//console.log(d3.mouse(this));
current = d3.mouse(this);
xshift = 500 - current[0];
yshift = 250 - current[1];
}
//applies transitions
function center(d) {
secondNodes.selectAll("rect")
.transition()
.delay(0)
.duration(500)
.attr("transform", "translate(" + xshift + "," + yshift + ")")
.each("end", function() {
secondNodes.selectAll("text")
.transition()
.delay(0)
.duration(0)
.attr("transform", null);
});
}
</script>
If you want everything to keep its relative position, it seems to me that something far easier to do would be to include everything in a g element that you can set the transform attribute one. That is, instead of moving many elements, you would have to move just the top-level container. The code you have for handling clicks would remain pretty much the same except that you only need to set the transform attribute on that one element.

Resources