I am trying to make my tree have a straight links between the parent node and the children nodes.
I have straight links now but the links are not connecting to the right places.
I think this may be because there is a transformation of rotation and translate to the nodes and the x and y didn't change somehow.
I have tried following the answer in this question but result is the same. D3: Substituting d3.svg.diagonal() with d3.svg.line()
var lines = svg.selectAll('line')
.data(links)
.enter()
.append('line')
.attr('stroke','#000')
lines.attr('x1',function(d){return d.source.x })
.attr('y1',function(d){return d.source.x})
.attr('x2',function(d){return d.target.x })
.attr('y2',function(d){return d.target.y })
Here is the full code:
var diameter = 1000;
var tree = d3.layout.tree()
.size([360, diameter / 2 - 100])
.separation(function(a, b) { return (a.parent == b.parent ? 1 : 2) / a.depth; });
// var diagonal = d3.svg.diagonal.radial()
// .projection(function(d) {
// return [d.y, d.x ]; })
var svg = d3.select("body").append("svg")
.attr("width", diameter)
.attr("height", diameter )
.append("g")
.attr("transform", "translate(" + diameter / 2 + "," + diameter / 2 + ")");
d3.json("flare.json", function(error, root) {
var nodes = tree.nodes(root),
links = tree.links(nodes);
var link = svg.selectAll(".link")
.data(links)
.enter().append("path")
.attr("class", "link")
var lines = svg.selectAll('line')
.data(links)
.enter()
.append('line')
.attr('stroke','#000')
lines.attr('x1',function(d){return d.source.x })
.attr('y1',function(d){return d.source.x})
.attr('x2',function(d){return d.target.x })
.attr('y2',function(d){return d.target.y })
var node = svg.selectAll(".node")
.data(nodes)
.enter()
.append("g")
.attr("class", "node")
.attr("transform", function(d) { return "rotate(" + (d.x - 90 ) + ")translate(" + d.y + ")"; })
node.append("circle")
.attr("r", 10);
node.append("text")
.attr("dy", ".81em")
.attr("text-anchor", function(d) {
return d.x < 180 ? "start" : "end"; })
.attr("transform", function(d) { return d.x < 180 ? "translate(20)" : "rotate(180)translate(-20)"; })
.text(function(d) { return d.name; });
});
d3.select(self.frameElement).style("height", diameter - 150 + "px");
screenshots
I finally got it to work.. The solution is quite bizarre.
There is no projection method for line as there is for diagonal.
So when resetting the positions of x1,x2,y1,y2 needs a little bit tuning just like the diagonal projection.
Also I have to apply the transformation like how the nodes are applied but without the translation.
var link = svg.selectAll("link")
.data(links)
.enter().append("path")
.attr("class", "link")
var lines = svg.selectAll('line')
.data(links)
.enter()
.append('line')
.attr('stroke','#000')
lines.attr('x1',function(d){return d.source.y})
.attr('y1',function(d){return d.source.x/180*Math.PI})
.attr('x2',function(d){return d.target.y })
.attr('y2',function(d){return d.target.x/180*Math.PI})
// lines.attr("transform", function(d) {
// return "rotate(" + (d.source.x - 90 ) + ")translate(" + d.source.y + ")"; })
lines.attr("transform", function(d) {
return "rotate(" + (d.target.x - 90 ) + ")"; })
Related
I have a problem when there is two graph on same page, the first graph has a normal size but the second which is populated by fixed data is anormaly large. The two graph are superimposed. Strange thing, if I use the code of the second graph, it works on my local website test.
This is the code of the second graph :
// http://blog.thomsonreuters.com/index.php/mobile-patent-suits-graphic-of-the-day/
var links = [
{"source":"TEST111","target":"TEST222","level":"1","life":"1","test":"1","type":"licensing"},
{"source":"TEST222","target":"TEST3333","level":"2","life":"2","test":"2","type":"licensing"},
{"source":"TEST3333","target":"TEST4444","level":"3","life":"3","test":"3","type":"licensing"}
];
var nodes = {};
// Compute the distinct nodes from the links.
links.forEach(function(link) {
link.source = nodes[link.source] || (nodes[link.source] = {name: link.source, level:link.level, life:link.life});
link.target = nodes[link.target] || (nodes[link.target] = {name: link.target, level:link.level, life:link.life});
});
var width = 960,
height = 500;
var force = d3.layout.force()
.nodes(d3.values(nodes))
.links(links)
.size([width, height])
.linkDistance(200)
.charge(-400)
.on("tick", tick)
.start();
var svg = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height);
// Per-type markers, as they don't inherit styles.
svg.append("defs").selectAll("marker")
.data(["suit", "licensing", "resolved"])
.enter().append("marker")
.attr("id", function(d) { return d; })
.attr("viewBox", "0 -5 10 10")
.attr("refX", 15)
.attr("refY", -1.5)
.attr("markerWidth", 20)
.attr("markerHeight", 20)
.attr("orient", "auto")
.append("path")
.attr("d", "M0,-5L10,0L0,5");
var path = svg.append("g").selectAll("path")
.data(force.links())
.enter().append("path")
.attr("class", function(d) { return "link " + d.type; })
.attr("marker-end", function(d) { return "url(#" + d.type + ")"; });
var circle = svg.append("g").selectAll("circle")
.data(force.nodes())
.enter().append("circle")
.attr("r", 20)
.call(force.drag)
.style("fill","red")
.on("click", click);
function click(d){
}
var text = svg.append("g").selectAll("text")
.data(force.nodes())
.enter().append("text")
.attr("x", 8)
.attr("y", ".31em")
.text(function(d) { return d.name; });
// Use elliptical arc path segments to doubly-encode directionality.
function tick() {
path.attr("d", linkArc);
circle.attr("transform", transform);
text.attr("transform", transform)
}
function linkArc(d) {
var dx = d.target.x - d.source.x,
dy = d.target.y - d.source.y,
dr = Math.sqrt(dx * dx + dy * dy);
return "M" + d.source.x + "," + d.source.y + "A" + dr + "," + dr + " 0 0,1 " + d.target.x + "," + d.target.y;
}
function transform(d) {
return "translate(" + d.x + "," + d.y + ")";
}
Also, on the Pluker, the two graph are bunk but it's not the problem (It works in local). You can delete the data load if you want (line 34). This is an online Plunker to see the problem : https://plnkr.co/edit/ewoi6wao97tXAKr1QxIm?p=preview
Thanks.
Basically your problem was you were filling the path rather than just giving it a stroke.
So when you create the path just add the following :
.style("fill","none").style("stroke","red")
So now it looks like :
var path = svg.append("g").selectAll("path")
.data(force.links())
.enter().append("path")
.attr("class", function(d) { return "link " + d.type; })
.style("fill","none").style("stroke","red")
.attr("marker-end", function(d) { return "url(#" + d.type + ")"; });
Updated plnkr : https://plnkr.co/edit/JgHwC4zyolj0hsg8ib4w?p=preview
I am creating an arc diagram where I'd like to, hopefully, find a way to prevent the overlap of arcs. There's an example of the working bl.ock here.
The darker lines in this case are overlapping lines where multiple nodes share the same edge. I'd like to prevent that, perhaps by doing two passes: the first would alternate the arc to go above the nodes rather than below, giving a sort of helix appearance; the second would draw a slightly larger arc if an arc already exists above/below to help differentiate the links.
var width = 1000,
height = 500,
margin = 20,
pad = margin / 2,
radius = 6,
yfixed = pad + radius;
var color = d3.scale.category10();
// Main
//-----------------------------------------------------
function arcDiagram(graph) {
var radius = d3.scale.sqrt()
.domain([0, 20])
.range([0, 15]);
var svg = d3.select("#chart").append("svg")
.attr("id", "arc")
.attr("width", width)
.attr("height", height);
// create plot within svg
var plot = svg.append("g")
.attr("id", "plot")
.attr("transform", "translate(" + pad + ", " + pad + ")");
// fix graph links to map to objects
graph.links.forEach(function(d,i) {
d.source = isNaN(d.source) ? d.source : graph.nodes[d.source];
d.target = isNaN(d.target) ? d.target : graph.nodes[d.target];
});
linearLayout(graph.nodes);
drawLinks(graph.links);
drawNodes(graph.nodes);
}
// layout nodes linearly
function linearLayout(nodes) {
nodes.sort(function(a,b) {
return a.uniq - b.uniq;
})
var xscale = d3.scale.linear()
.domain([0, nodes.length - 1])
.range([radius, width - margin - radius]);
nodes.forEach(function(d, i) {
d.x = xscale(i);
d.y = yfixed;
});
}
function drawNodes(nodes) {
var gnodes = d3.select("#plot").selectAll("g.node")
.data(nodes)
.enter().append('g');
var nodes = gnodes.append("circle")
.attr("class", "node")
.attr("id", function(d, i) { return d.name; })
.attr("cx", function(d, i) { return d.x; })
.attr("cy", function(d, i) { return d.y; })
.attr("r", 5)
.style("stroke", function(d, i) { return color(d.gender); });
nodes.append("text")
.attr("dx", function(d) { return 20; })
.attr("cy", ".35em")
.text(function(d) { return d.name; })
}
function drawLinks(links) {
var radians = d3.scale.linear()
.range([Math.PI / 2, 3 * Math.PI / 2]);
var arc = d3.svg.line.radial()
.interpolate("basis")
.tension(0)
.angle(function(d) { return radians(d); });
d3.select("#plot").selectAll(".link")
.data(links)
.enter().append("path")
.attr("class", "link")
.attr("transform", function(d,i) {
var xshift = d.source.x + (d.target.x - d.source.x) / 2;
var yshift = yfixed;
return "translate(" + xshift + ", " + yshift + ")";
})
.attr("d", function(d,i) {
var xdist = Math.abs(d.source.x - d.target.x);
arc.radius(xdist / 2);
var points = d3.range(0, Math.ceil(xdist / 3));
radians.domain([0, points.length - 1]);
return arc(points);
});
}
Any pointers on how I might start approaching the problem?
Here is a bl.ock for reference. It shows your original paths in gray, and the proposed paths in red.
First store the counts for how many times a given path occurs:
graph.links.forEach(function(d,i) {
var pathCount = 0;
for (var j = 0; j < i; j++) {
var otherPath = graph.links[j];
if (otherPath.source === d.source && otherPath.target === d.target) {
pathCount++;
}
}
d.pathCount = pathCount;
});
Then once you have that data, I would use an ellipse instead of a radial line since it appears the radial line can only draw a curve for a circle:
d3.select("#plot").selectAll(".ellipse-link")
.data(links)
.enter().append("ellipse")
.attr("fill", "transparent")
.attr("stroke", "gray")
.attr("stroke-width", 1)
.attr("cx", function(d) {
return (d.target.x - d.source.x) / 2 + radius;
})
.attr("cy", pad)
.attr("rx", function(d) {
return Math.abs(d.target.x - d.source.x) / 2;
})
.attr("ry", function(d) {
return 150 + d.pathCount * 20;
})
.attr("transform", function(d,i) {
var xshift = d.source.x - radius;
var yshift = yfixed;
return "translate(" + xshift + ", " + yshift + ")";
});
Note that changing the value for ry above will change the heights of different curves.
Finally you'll have to use a clippath to restrict the area of each ellipse that's actually shown, so that they only display below the nodes. (This is not done in the bl.ock)
Good ...
I am using this example from the following url (http://bl.ocks.org/mbostock/7607535
):
My problem is that when inserting many circles, the main circle is not filled correctly as a kind of exaltation is created, my boss wants princial fills the circle.
I leave the code and a screenshot!
var margin = 10,
outerDiameter = 1000,
innerDiameter = outerDiameter - margin - margin;
var x = d3.scale.linear()
.range([0, innerDiameter]);
var y = d3.scale.linear()
.range([0, innerDiameter]);
var color = d3.scale.linear()
.domain([-1, 5])
.range(["hsl(152,80%,80%)", "hsl(228,30%,40%)"])
.interpolate(d3.interpolateHcl);
var pack = d3.layout.pack()
.padding(0.5)
.size([innerDiameter, innerDiameter])
.value(function(d) { return d.size; })
var svg = d3.select("#chart4").append("svg")
.attr("width", outerDiameter)
.attr("height", outerDiameter)
.append("g")
.attr("transform", "translate(" + margin + "," + margin + ")");
nodes = pack.nodes(testHeatMap);
svg.append("g").selectAll("circle")
.data(nodes)
.enter().append("circle")
//.attr("class", function(d) { return d.parent ? d.children ? "node" : "node node--leaf" : "node node--root"; })
.attr("transform", function(d) { return "translate(" + d.x + "," + d.y + ")"; })
.attr("r", function(d) { return d.r; })
.style("fill", function(d,i){
if(d.color)
return d.color
else if(d.children[0].color)
return d.children[0].color
else return d.children[0].children[i].color
})
.style("fill-opacity", "0.25")
.style("stroke", function (){ return "rgb(31, 119, 180)"})
.style("stroke-width", "1px")
d3.select(self.frameElement).style("height", outerDiameter + "px");
I just started getting involved in web visualizations, so I'm totally novice. My goal is to display a family tree where a root node would have both multiple parents and children. While looking for a solution I found this example: http://bl.ocks.org/jdarling/2503502
It's great because it seems to have the feature I need. However, I would like to alter the orientation (top-to-bottom). I tried to do so using this example: http://bl.ocks.org/mbostock/3184089 but failed.
My code:
var tree = d3.layout.tree()
.size([height, width]);
var diagonal = d3.svg.diagonal()
.projection(function(d) {
return [d.x, d.y];
});
var elbow = function (d, i){
var source = calcTop(d.source);
var target = calcTop(d.target);
var hx = (target.x-source.x)/2;
if(d.isRight)
hx = -hx;
return "M" + source.x + "," + source.y
+ "H" + (source.x+hx)
+ "V" + target.y + "H" + target.x;
};
var connector = elbow;
var calcTop = function(d){
var top = d.x;
if(!d.isRight){
top = d.x-halfHeight;
top = halfHeight - top;
}
return {x : top, y : d.y};
};
var vis = d3.select("#chart")
.append("svg")
.attr("height", height + margin.top + margin.bottom)
.attr("width", width + margin.right + margin.left)
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
d3.json("tree.json", function(json) {
root = json;
root.x0 = height / 2;
root.y0 = width / 2;
var t1 = d3.layout.tree()
.size([halfHeight, width])
.children(function(d){
return d.winners;
});
var t2 = d3.layout.tree()
.size([halfHeight, width])
.children(function(d){
return d.challengers;
});
t1.nodes(root);
t2.nodes(root);
var rebuildChildren = function(node){
node.children = getChildren(node);
if(node.children)
node.children.forEach(rebuildChildren);
}
rebuildChildren(root);
root.isRight = false;
update(root);
});
var toArray = function(item, arr){
arr = arr || [];
var i = 0, l = item.children?item.children.length:0;
arr.push(item);
for(; i < l; i++){
toArray(item.children[i], arr);
}
return arr;
};
function update(source) {
// Compute the new tree layout.
var nodes = toArray(source);
// Normalize for fixed-depth.
nodes.forEach(function(d) { d.x = d.depth * 180 + halfHeight; });
// Update the nodes…
var node = vis.selectAll("g.node")
.data(nodes, function(d) { return d.id || (d.id = ++i); });
// Enter any new nodes at the parent's previous position.
var nodeEnter = node.enter().append("g")
.attr("class", "node")
.attr("transform", function(d) {
return "translate(" + source.x0 + "," + source.y0 + ")";
})
.on("click", click);
nodeEnter.append("circle")
.attr("r", 1e-6)
.style("fill", function(d) {
return d._children ? "lightsteelblue" : "#fff";
});
nodeEnter.append("text")
.attr("dy", function(d) { return d.isRight?14:-8;})
.attr("text-anchor", "middle")
.text(function(d) { return d.name; })
.style("fill-opacity", 1e-6);
// Transition nodes to their new position.
var nodeUpdate = node.transition()
.duration(duration)
.attr("transform", function(d) {
p = calcTop(d);
return "translate(" + p.x + "," + p.y + ")";
});
nodeUpdate.select("circle")
.attr("r", 4.5)
.style("fill", function(d) {
return d._children ? "lightsteelblue" : "#fff";
});
nodeUpdate.select("text")
.style("fill-opacity", 1);
// Transition exiting nodes to the parent's new position.
var nodeExit = node.exit().transition()
.duration(duration)
.attr("transform", function(d) {
p = calcTop(d.parent||source);
return "translate(" + p.x + "," + p.y + ")";
})
.remove();
nodeExit.select("circle")
.attr("r", 1e-6);
nodeExit.select("text")
.style("fill-opacity", 1e-6);
// Update the links...
var link = vis.selectAll("path.link")
.data(tree.links(nodes), function(d) { return d.target.id; });
// Enter any new links at the parent's previous position.
link.enter().insert("path", "g")
.attr("class", "link")
.attr("d", function(d) {
var o = {x: source.x0, y: source.y0};
return connector({source: o, target: o});
});
// Transition links to their new position.
link.transition()
.duration(duration)
.attr("d", connector);
// Transition exiting nodes to the parent's new position.
link.exit()
.transition()
.duration(duration)
.attr("d", function(d) {
var o = calcTop(d.source||source);
if(d.source.isRight)
o.x -= halfHeight - (d.target.x - d.source.x);
else
o.x += halfHeight - (d.target.x - d.source.x);
return connector({source: o, target: o});
})
.remove();
// Stash the old positions for transition.
nodes.forEach(function(d) {
var p = calcTop(d);
d.x0 = p.x;
d.y0 = p.y;
});
// Toggle children on click.
function click(d) {
if (d.children) {
d._children = d.children;
d.children = null;
} else {
d.children = d._children;
d._children = null;
}
update(source);
}
}
Would really appreciate the help!
The example you're looking at is actually already flipped - this might be causing you some confusion. Trees in D3 are naturally top-down trees, and the code does a lot of x-y flipping to make the tree sideways.
Changing
var nodeUpdate = node.transition()
.duration(duration)
.attr("transform", function(d) { p = calcLeft(d); return "translate(" + p.y + "," + p.x + ")"; })
;
to
var nodeUpdate = node.transition()
.duration(duration)
.attr("transform", function(d) { p = calcLeft(d); return "translate(" + p.x + "," + p.y + ")"; })
;
will get the nodes displaying in the right position. Doing a similar change with any instance of swapped x-y coordinates inside update() fixes most of the positioning issues. One last thing is the elbow function/variable/whatever you want to call it, where
return "M" + source.y + "," + source.x
+ "H" + (source.y+hy)
+ "V" + target.x + "H" + target.y;
should be changed to
return "M" + source.x + "," + source.y
+ "V" + (source.y+hy)
+ "H" + target.x + "V" + target.y;
This changes the connector shape from horizontal vertical horizontal to vertical horizontal vertical. Note that this is a raw SVG line, not d3 at all. The changes I made (plus swapping width and height, and changing the AJAX JSON request to hardcoding the data - AJAX is hard to get working in fiddle) are all at http://jsfiddle.net/Zj3th/2/.
If you have no experience with d3 and SVG, I would definitely take a look and fully understand a simple example like http://blog.pixelingene.com/2011/07/building-a-tree-diagram-in-d3-js/ before you go further in modifying the code.
Here is an example of bottom-top diagram that I made. I gather it might be useful for you, maybe you'll spot some idea etc.
Link to code on codepen. Feel free to ask any question.
its very easy to make rotation in d3 collapsible tree TOP-DOWN..
step 1: specify top,bottom,left,right part of our drawing pan and also the width and
height.
Step 2: Make the orientation as top to bottom
step 3: Reading the json and make the orientation
step 4: Appending the link and circle to the body
<script>
var jdata='${data}'
var margin = {top: 20, right: 120, bottom: 20, left: 120},
width = 980 - margin.left - margin.right,
height = 500 - margin.top - margin.bottom;
var orientations = {
"top-to-bottom": {
size: [width, height],
x: function(d) { return d.x; },
y: function(d) { return d.y; }
}
};
var svg = d3.select("body").selectAll("svg")
.data(d3.entries(orientations))
.enter().append("svg")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
d3.json("resources/grap.json", function(json) {
root = JSON.parse(jdata);
svg.each(function(orientation) {
var svg = d3.select(this),
o = orientation.value;
// Compute the layout.
var tree = d3.layout.tree().size(o.size),
nodes = tree.nodes(root),
links = tree.links(nodes);
// Create the link lines.
svg.selectAll(".link")
.data(links)
.enter().append("path")
.attr("class", "link")
.attr("d", d3.svg.diagonal().projection(function(d) { return [o.x(d), o.y(d)]; }));
// Create the node circles.
svg.selectAll(".node")
.data(nodes)
.enter().append("circle")
.attr("r",1.5)
.attr("x", o.x)
.attr("y", o.y)
});
});
</script>
I am trying to implement the zooming feature on a dendrogram in the simplest, most basic way and have gotten it to work. The only issue is that the zoom event only works when the cursor is over an edge, a node, or text. How do I allow zooming when the cursor is on any portion of the svg?
var margin = {top: 20, right: 120, bottom: 20, left: 120},
width = 2000 - margin.right - margin.left,
height = 2000 - margin.top - margin.bottom;
var x = d3.scale.linear()
.domain([0, width])
.range([0, width]);
var y = d3.scale.linear()
.domain([0, height])
.range([height, 0]);
var tree = d3.layout.tree()
.size([height, width])
.separation(function(a, b) { return (a.parent == b.parent ? 1 : 2) / a.depth; });
var diagonal = d3.svg.diagonal()
.projection(function(d) { return [d.y, d.x]; });
var svg = d3.select("body").append("svg")
.attr("width", width + margin.right + margin.left)
.attr("height", height + margin.top + margin.bottom)
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")")
.attr("pointer-events", "all")
.append('svg:g')
.call(d3.behavior.zoom()
.x(x)
.y(y)
.scaleExtent([1,8])
.on("zoom", zoom))
.append('svg:g');
function zoom(d) {
svg.attr("transform",
"translate(" + d3.event.translate + ")"
+ " scale(" + d3.event.scale + ")");
}
d3.json("flare.json", function(error, root) {
var nodes = tree.nodes(root),
links = tree.links(nodes);
var link = svg.selectAll(".link")
.data(links)
.enter().append("path")
.attr("class", "link")
.attr("d", diagonal);
var node = svg.selectAll(".node")
.data(nodes)
.enter().append("g")
.attr("class", "node")
// .attr("transform", function(d) { return "rotate(" + (d.x - 90) + ")translate(" + d.y + ")"; })
.attr("transform", function(d) { return "translate(" + d.y + "," + d.x + ")"; })
node.append("circle")
.attr("r", 4.5);
node.append("text")
.attr("dy", ".31em")
.attr("text-anchor", function(d) { return d.children || d._children ? "end" : "start"; })
// .attr("transform", function(d) { return d.x < 180 ? "translate(8)" : "rotate(180)translate(-8)"; })
.text(function(d) { return d.name; });
});
d3.select(self.frameElement).style("height", "800px");
I have been using the following jsfiddle as a guide and cannot see where the difference is: http://jsfiddle.net/nrabinowitz/QMKm3/
Thanks in advance.
the jsfiddle you point to in your question has this...
vis.append('svg:rect')
.attr('width', w)
.attr('height', h)
.attr('fill', 'white');
This makes sure there's always something drawn no matter where you are. You need to adjust your code accordingly. You can make it opacity 0 if you don't like white and then you won't see it, but it does need to be there.