Label over arc paths bent - d3.js

I am trying to add labels properly over arcs. Because the arc angles are so small, the labels can't fit over the arcs and "bend". How can i make the labels fit over the arcs keeping the same font size, same radius and same number of arcs?
<script src="http://d3js.org/d3.v4.min.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<style>
.layer_1 {
stroke: none;
}
.layer_o {
stroke: none;
}
</style>
</head>
<body>
<script>
//build test data
var dict = []; // create an empty array
var angle = 0;
var nb_arcs = 255;
var max_radius = 200;
var colorScale = d3.scaleOrdinal(d3["schemeCategory10"])
for (var i = 0; i < nb_arcs; i++) {
var outer_radius_1 = Math.random() * max_radius;
dict.push({
id: i,
layer: 1,
inner_radius: 0,
outer_radius: outer_radius_1,
start_angle: angle,
end_angle: angle + 2 * Math.PI / nb_arcs
});
dict.push({
id: nb_arcs + i,
layer: 2,
inner_radius: outer_radius_1,
outer_radius: max_radius,
end_angle: 2,
start_angle: angle,
end_angle: angle + 2 * Math.PI / nb_arcs
});
angle = angle + 2 * Math.PI / nb_arcs;
}
var svg = d3.select("body").append("svg")
.attr("width", 960)
.attr("height", 500)
.append("g")
.attr("transform", "translate(480,250)");
var g = svg.selectAll("g").enter().append("g");
var path = svg.selectAll("path")
.data(dict)
.enter().append("path")
.attr("id", function(d) {
return d.id;
})
.attr("class", function(d) {
if (d.layer == 1) {
return "layer_1";
} else {
return "layer_o";
}
})
.attr("d", d3.arc()
.innerRadius(function(d) {
return d.inner_radius;
})
.outerRadius(function(d) {
return d.outer_radius;
})
.startAngle(function(d) {
return d.start_angle;
})
.endAngle(function(d) {
return d.end_angle;
}))
.attr("fill", function(d) {
return colorScale(d.layer);
})
.style('stroke', function(d) {
return colorScale(d.layer);
})
.style('stroke-width', '0.3');
svg.selectAll("text")
.data(dict)
.enter()
.append("text")
.attr('dy', function(d) {
if (d.id % 2 == 0) {
return -13;
} else {
return -5;
}
})
.attr("class", "labelText")
.append("textPath") //append a textPath to the text element
.attr("xlink:href", function(d) {
return "#" + d.id;
})
.style("font-size", "5px")
.text(function(d) {
if (d.layer == 2) {
return "#" + d.id;
} else {
return "";
}
})
.style("text-anchor", "middle")
.attr('startOffset', function(d) {
if (d.id % 2 == 0) {
return 3;
} else {
return 0;
}
})
.attr("fill", "black");
</script>
</body>
</html>

Related

D3 tree with boxes

With my limited D3 knowledge, I've combined some tree layout examples to make a collapsible tree with branch labels. Here is a functional extract:
var root = {
children:[
{
title:"Node title",
children:[
{
type:"end",
items:[],
optionTitle:"Branch 1"
},
{
type:"end",
items:[],
optionTitle:"Branch 2"
}
]
}
]
}
var maxLabelLength = 23;
var i = 0;
var duration = 750;
// Define the root
root.x0 = viewerHeight / 2;
root.y0 = 0;
var viewerWidth = 800;
var viewerHeight = 300;
var tree = d3.layout.tree();
var diagonal = d3.svg.diagonal()
.projection(function(d) {
return [d.y, d.x];
});
function visit(parent, visitFn, childrenFn) {
if (!parent) return;
visitFn(parent);
var children = childrenFn(parent);
if (children) {
var count = children.length;
for (var i = 0; i < count; i++) {
visit(children[i], visitFn, childrenFn);
}
}
}
var baseSvg = d3.select('.tree').append("svg")
.attr("width", viewerWidth)
.attr("height", viewerHeight)
.attr("class", "tree-container");
// Helper functions for collapsing and expanding nodes.
function collapse(d) {
if (d.children) {
d._children = d.children;
d._children.forEach(collapse);
d.children = null;
}
}
function centerNode(source) {
var scale = 1;
var x = 20;
var y = -source.x0;
y = y * scale + viewerHeight / 2;
d3.select('g').transition()
.duration(duration)
.attr("transform", "translate(" + x + "," + y + ")scale(" + scale + ")");
}
function toggleChildren(d) {
if (d.children) {
d._children = d.children;
d.children = null;
} else if (d._children) {
expand(d);
}
return d;
}
function expand(d) {
if (d._children) {
d.children = d._children;
d._children = null;
if (d.children.length == 1) {
expand(d.children[0])
}
}
}
function click(d) {
if (d._children) {
if (d.type!='end') {
expandCollapse(d);
}
} else {
expandCollapse(d);
}
}
function expandCollapse(d) {
d = toggleChildren(d);
update(d);
centerNode(d);
}
function update(source) {
var levelWidth = [1];
var childCount = function(level, n) {
if (n.children && n.children.length > 0) {
if (levelWidth.length <= level + 1) levelWidth.push(0);
levelWidth[level + 1] += n.children.length;
n.children.forEach(function(d) {
childCount(level + 1, d);
});
}
};
childCount(0, root);
var newHeight = d3.max(levelWidth) * 25;
tree = tree.size([newHeight, viewerWidth]);
// Compute the new tree layout.
var nodes = tree.nodes(root).reverse(),
links = tree.links(nodes);
// Set widths between levels based on maxLabelLength.
nodes.forEach(function(d) {
d.y = (d.depth * (maxLabelLength * 8));
if (d.x<root.x) {
d.x -= (root.x-d.x)*3;
} else if (d.x>root.x) {
d.x += (d.x-root.x)*3;
}
});
// Update the nodes…
var node = svgGroup.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.y0 + "," + source.x0 + ")";
})
.on('click', click);
nodeEnter.append("circle")
.attr('class', 'nodeCircle');
// Change the circle fill depending on whether it has children and is collapsed
node.select("circle.nodeCircle")
.attr("r", 6)
.style("fill", function(d) {
return getNodeFill(d);
});
nodeEnter.append("text")
.attr("x", function(d) {
return d.children || d._children ? -10 : 10;
})
.attr("dy", ".35em")
.attr('class', 'nodeText')
.attr("text-anchor", function(d) {
return d.children || d._children ? "end" : "start";
})
.text(function(d) {
return d.title;
})
.style("fill-opacity", 0);
// Update the text to reflect whether node has children or not.
node.select('text')
.attr("x", function(d) {
return d.children || d._children ? -10 : 10;
})
.text(function(d) {
if (d.type!='end') {
return d.title
} else {
return 'End node'
}
});
// Transition nodes to their new position.
var nodeUpdate = node.transition()
.duration(duration)
.attr("transform", function(d) {
return "translate(" + d.y + "," + d.x + ")";
});
// Fade the text in
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) {
return "translate(" + source.y + "," + source.x + ")";
})
.remove();
nodeExit.select("circle")
.attr("r", 0);
nodeExit.select("text")
.style("fill-opacity", 0);
// Update the links…
var link = svgGroup.selectAll("path.link")
.data(links, 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 diagonal({
source: o,
target: o
});
});
// Transition links to their new position.
link.transition()
.duration(duration)
.attr("d", diagonal);
// Transition exiting nodes to the parent's new position.
link.exit().transition()
.duration(duration)
.attr("d", function(d) {
var o = {
x: source.x,
y: source.y
};
return diagonal({
source: o,
target: o
});
})
.remove();
// Update the link text
var linktext = svgGroup.selectAll("g.link")
.data(links, function (d) {
return d.target.id;
});
linktext.enter()
.insert("g")
.attr("class", "link")
.append("text")
.attr("dy", ".35em")
.attr("text-anchor", "middle")
.text(function (d) {
return d.target.optionTitle;
});
// Transition link text to their new positions
linktext.transition()
.duration(duration)
.attr("transform", function (d) {
return "translate(" + ((d.source.y + d.target.y) / 2) + "," + ((d.source.x + d.target.x) / 2) + ")";
})
//Transition exiting link text to the parent's new position.
linktext.exit().transition().remove();
// Stash the old positions for transition.
nodes.forEach(function(d) {
d.x0 = d.x;
d.y0 = d.y;
});
}
var svgGroup = baseSvg.append("g");
// Layout the tree initially and center on the root node.
update(root);
centerNode(root);
svgGroup
.append('defs')
.append('pattern')
.attr('id', function(d,i){
return 'pic_plus';
})
.attr('height',60)
.attr('width',60)
.attr('x',0)
.attr('y',0)
.append('image')
.attr('xlink:href',function(d,i){
return 'https://s3-eu-west-1.amazonaws.com/eq-static/app/images/common/plus.png';
})
.attr('height',12)
.attr('width',12)
.attr('x',0)
.attr('y',0);
function getNodeFill(d) {
if (isFinal(d)) {
return '#0f0';
} else if (d._children || (!d._children&&!d.children)) {
return 'url(#pic_plus)'
} else {
return '#fff'
}
}
function isFinal(node) {
return node.type=='end';
}
body {
background-color: #ddd;
}
.tree-custom,
.tree {
width:100%;
height: 100%;
background-color: #fff;
}
.holder {
margin: 0 auto;
width: 1000px;
height: 800px;
background-color: #fff;
}
.node {
cursor: pointer;
}
.node circle {
fill: #fff;
stroke: steelblue;
stroke-width: 1.5px;
}
.node text {
font: 10px sans-serif;
}
path.link {
fill: none;
stroke: #ccc;
stroke-width: 1.5px;
}
.link text {
font: 10px sans-serif;
fill: #666;
}
<html>
<head>
<script src="https://d3js.org/d3.v3.min.js"></script>
</head>
<body>
<div class="tree"></div>
<script src="code.js"></script>
</body>
</html>
In my code, the nodes are circles with the node labels next to them:
When I collapse a node, a plussign shows up.
Now I'm trying to put the node labels in boxes as it's shown in this example code.
I know I have to change the circles by foreignObjectsas in the example code but when I do it, paths aren't adjusted to the boxes.
How could I change the circles by foreignObjects and maintain the same functionality expand/collapse/plus?
The changes that you need to make to your current layout are:
move the blue boxes to be vertically aligned with the circles and with the right edge adjacent to the left side of each circle;
alter the paths to terminate at the left side of each blue box
The first can be done using a transform to alter the position of the blue boxes, and the second by altering the coordinates for the target point of each line.
Looking at the sample you linked to, there are rect elements behind the foreignObject elements that provide the colour, and the foreignObject is offset from the position of the rect elements. I have taken the liberty of adding the rect elements to your code and grouping the rect and foreignObject elements together so they can be moved in a single transform:
var rectGrpEnter = nodeEnter.append('g')
.attr('class', 'node-rect-text-grp');
rectGrpEnter.append('rect')
.attr('rx', 6)
.attr('ry', 6)
.attr('width', rectNode.width)
.attr('height', rectNode.height)
.attr('class', 'node-rect');
rectGrpEnter.append('foreignObject')
.attr('x', rectNode.textMargin)
.attr('y', rectNode.textMargin)
.attr('width', function() {
return (rectNode.width - rectNode.textMargin * 2) < 0 ? 0 :
(rectNode.width - rectNode.textMargin * 2)
})
.attr('height', function() {
return (rectNode.height - rectNode.textMargin * 2) < 0 ? 0 :
(rectNode.height - rectNode.textMargin * 2)
})
.append('xhtml').html(function(d) {
return '<div style="width: ' +
(rectNode.width - rectNode.textMargin * 2) + 'px; height: ' +
(rectNode.height - rectNode.textMargin * 2) + 'px;" class="node-text wordwrap">' +
'<b>' + d.title + '</b>' +
'</div>';
});
If you look at the tree this produces, the rect/foreignObject group needs to be translated the length of the rect element + the circle radius along the x axis, and by half the height of the rect element along the y axis. So, first let's add a variable to represent the circle radius and replace the hard-coded number with that variable:
var circleRadius = 6;
// a bit further on
node.select("circle.nodeCircle")
.attr("r", circleRadius)
.style("fill", function(d) {
return getNodeFill(d);
});
Now write the transform:
var rectGrpEnter = nodeEnter.append('g')
.attr('class', 'node-rect-text-grp')
.attr('transform', 'translate('
+ -(rectNode.width + circleRadius) + ',' // note the transform is negative
+ -(rectNode.height/2) + ')' );
Check the resulting tree:
var rectNode = {
width: 120,
height: 45,
textMargin: 5
};
var root = {
slideId: 100,
children: [{
title: "Node title",
children: [{
type: "end",
items: [],
optionTitle: "Branch 1"
},
{
type: "end",
items: [],
optionTitle: "Branch 2"
}
]
}]
}
var maxLabelLength = 23;
var i = 0;
var duration = 750;
// Define the root
root.x0 = viewerHeight / 2;
root.y0 = 0;
var viewerWidth = 800;
var viewerHeight = 300;
var tree = d3.layout.tree();
var diagonal = d3.svg.diagonal()
.projection(function(d) {
return [d.y, d.x];
});
function visit(parent, visitFn, childrenFn) {
if (!parent) return;
visitFn(parent);
var children = childrenFn(parent);
if (children) {
var count = children.length;
for (var i = 0; i < count; i++) {
visit(children[i], visitFn, childrenFn);
}
}
}
var baseSvg = d3.select('.tree').append("svg")
.attr("width", viewerWidth)
.attr("height", viewerHeight)
.attr("class", "tree-container");
// Helper functions for collapsing and expanding nodes.
function collapse(d) {
if (d.children) {
d._children = d.children;
d._children.forEach(collapse);
d.children = null;
}
}
function centerNode(source) {
var scale = 1;
var x = 20;
var y = -source.x0;
y = y * scale + viewerHeight / 2;
d3.select('g').transition()
.duration(duration)
.attr("transform", "translate(" + x + "," + y + ")scale(" + scale + ")");
}
function toggleChildren(d) {
if (d.children) {
d._children = d.children;
d.children = null;
} else if (d._children) {
expand(d);
}
return d;
}
function expand(d) {
if (d._children) {
d.children = d._children;
d._children = null;
if (d.children.length == 1) {
expand(d.children[0])
}
}
}
function click(d) {
if (d._children) {
if (d.type != 'end') {
expandCollapse(d);
}
} else {
expandCollapse(d);
}
}
function expandCollapse(d) {
d = toggleChildren(d);
update(d);
centerNode(d);
}
function update(source) {
var levelWidth = [1];
var childCount = function(level, n) {
if (n.children && n.children.length > 0) {
if (levelWidth.length <= level + 1) levelWidth.push(0);
levelWidth[level + 1] += n.children.length;
n.children.forEach(function(d) {
childCount(level + 1, d);
});
}
};
childCount(0, root);
var newHeight = d3.max(levelWidth) * 25;
var circleRadius = 6;
tree = tree.size([newHeight, viewerWidth]);
// Compute the new tree layout.
var nodes = tree.nodes(root).reverse(),
links = tree.links(nodes);
// Set widths between levels based on maxLabelLength.
nodes.forEach(function(d) {
d.y = (d.depth * (maxLabelLength * 8));
if (d.x < root.x) {
d.x -= (root.x - d.x) * 3;
} else if (d.x > root.x) {
d.x += (d.x - root.x) * 3;
}
});
// Update the nodes…
var node = svgGroup.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.y0 + "," + source.x0 + ")";
})
.on('click', click);
var rectGrpEnter = nodeEnter.append('g')
.attr('class', 'node-rect-text-grp')
.attr('transform', 'translate('
+ -(rectNode.width + circleRadius) + ',' // note the transform is negative
+ -(rectNode.height/2) + ')' );
rectGrpEnter.append('rect')
.attr('rx', 6)
.attr('ry', 6)
.attr('width', rectNode.width)
.attr('height', rectNode.height)
.attr('class', 'node-rect');
rectGrpEnter.append('foreignObject')
.attr('x', rectNode.textMargin)
.attr('y', rectNode.textMargin)
.attr('width', function() {
return (rectNode.width - rectNode.textMargin * 2) < 0 ? 0 :
(rectNode.width - rectNode.textMargin * 2)
})
.attr('height', function() {
return (rectNode.height - rectNode.textMargin * 2) < 0 ? 0 :
(rectNode.height - rectNode.textMargin * 2)
})
.append('xhtml').html(function(d) {
return '<div style="width: ' +
(rectNode.width - rectNode.textMargin * 2) + 'px; height: ' +
(rectNode.height - rectNode.textMargin * 2) + 'px;" class="node-text wordwrap">' +
'<b>' + d.title + '</b>' +
'</div>';
});
nodeEnter.append("circle")
.attr('class', 'nodeCircle');
// Change the circle fill depending on whether it has children and is collapsed
node.select("circle.nodeCircle")
.attr("r", circleRadius)
.style("fill", function(d) {
return getNodeFill(d);
});
nodeEnter.append("text")
.attr("x", function(d) {
return d.children || d._children ? -10 : 10;
})
.attr("dy", ".35em")
.attr('class', 'nodeText')
.attr("text-anchor", function(d) {
return d.children || d._children ? "end" : "start";
})
.text(function(d) {
return d.title;
})
.style("fill-opacity", 0);
// Update the text to reflect whether node has children or not.
node.select('text')
.attr("x", function(d) {
return d.children || d._children ? -10 : 10;
})
.text(function(d) {
if (d.type != 'end') {
return d.title
} else {
return 'End node'
}
});
// Transition nodes to their new position.
var nodeUpdate = node.transition()
.duration(duration)
.attr("transform", function(d) {
return "translate(" + d.y + "," + d.x + ")";
});
// Fade the text in
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) {
return "translate(" + source.y + "," + source.x + ")";
})
.remove();
nodeExit.select("circle")
.attr("r", 0);
nodeExit.select("text")
.style("fill-opacity", 0);
// Update the links…
var link = svgGroup.selectAll("path.link")
.data(links, 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 diagonal({
source: o,
target: o
});
});
// Transition links to their new position.
link.transition()
.duration(duration)
.attr("d", diagonal);
// Transition exiting nodes to the parent's new position.
link.exit().transition()
.duration(duration)
.attr("d", function(d) {
var o = {
x: source.x,
y: source.y
};
return diagonal({
source: o,
target: o
});
})
.remove();
// Update the link text
var linktext = svgGroup.selectAll("g.link")
.data(links, function(d) {
return d.target.id;
});
linktext.enter()
.insert("g")
.attr("class", "link")
.append("text")
.attr("dy", ".35em")
.attr("text-anchor", "middle")
.text(function(d) {
return d.target.optionTitle;
});
// Transition link text to their new positions
linktext.transition()
.duration(duration)
.attr("transform", function(d) {
return "translate(" + ((d.source.y + d.target.y) / 2) + "," + ((d.source.x + d.target.x) / 2) + ")";
})
//Transition exiting link text to the parent's new position.
linktext.exit().transition().remove();
// Stash the old positions for transition.
nodes.forEach(function(d) {
d.x0 = d.x;
d.y0 = d.y;
});
}
var svgGroup = baseSvg.append("g");
// Layout the tree initially and center on the root node.
update(root);
centerNode(root);
svgGroup
.append('defs')
.append('pattern')
.attr('id', function(d, i) {
return 'pic_plus';
})
.attr('height', 60)
.attr('width', 60)
.attr('x', 0)
.attr('y', 0)
.append('image')
.attr('xlink:href', function(d, i) {
return 'https://s3-eu-west-1.amazonaws.com/eq-static/app/images/common/plus.png';
})
.attr('height', 12)
.attr('width', 12)
.attr('x', 0)
.attr('y', 0);
function getNodeFill(d) {
if (isFinal(d)) {
return '#0f0';
} else if (d._children || (!d._children && !d.children)) {
return 'url(#pic_plus)'
} else {
return '#fff'
}
}
function isFinal(node) {
return node.type == 'end';
}
function isCollapsed(node) {
return d._children || (!d._children && !d.children);
}
body {
background-color: #ddd;
}
.tree-custom,
.tree {
width: 100%;
height: 100%;
background-color: #fff;
}
.holder {
margin: 0 auto;
width: 1000px;
height: 800px;
background-color: #fff;
}
.node {
cursor: pointer;
}
.node circle {
fill: #fff;
stroke: steelblue;
stroke-width: 1.5px;
}
.node text {
font: 10px sans-serif;
}
path.link {
fill: none;
stroke: #ccc;
stroke-width: 1.5px;
}
.link text {
font: 10px sans-serif;
fill: #666;
}
.node-rect {
fill: #00f;
}
.node-text {
color: #fff;
}
<html>
<head>
<script src="https://d3js.org/d3.v3.min.js"></script>
</head>
<body>
<div class="tree"></div>
</body>
</html>
The next task is to alter the links to terminate at the edge of the rect boxes. If you check the code that covers the link positions and transitions, the enter and exit selections both use the position of the source node. The code we are interested in is this:
link.transition()
.duration(duration)
.attr("d", diagonal);
where the diagonal function is
var diagonal = d3.svg.diagonal()
.projection(function(d) {
return [d.y, d.x];
});
d3.svg.diagonal() takes an object of the form
{ source: { x: 10, y: 10 }, target: { x: 20, y: 50 } }
and if you look at each item in the tree.links array, you'll see it is in the form
{ source: { /* source node coordinates */ }, target: { /* target node coords */ }
so to alter the position of the link target, we need to create a new object with the target coordinates altered. Once again, the x axis alteration should be -(rectNode.width + circleRadius); the y axis is OK. Note that the diagonal function switches over the x and y values, though, so we need to alter the target's y value, not the x value. Thus, we have:
// Transition links to their new position.
link.transition()
.duration(duration)
.attr("d", function(d) {
return diagonal({
source: d.source, // this is the same
target: { x: d.target.x, y: d.target.y - (rectNode.width + circleRadius) }
});
});
Add that to our code:
var rectNode = {
width: 120,
height: 45,
textMargin: 5
};
var root = {
slideId: 100,
children: [{
title: "Node title",
children: [{
type: "end",
items: [],
optionTitle: "Branch 1"
},
{
type: "end",
items: [],
optionTitle: "Branch 2"
}
]
}]
}
var maxLabelLength = 23;
var i = 0;
var duration = 750;
// Define the root
root.x0 = viewerHeight / 2;
root.y0 = 0;
var viewerWidth = 800;
var viewerHeight = 300;
var tree = d3.layout.tree();
var diagonal = d3.svg.diagonal()
.projection(function(d) {
return [d.y, d.x];
});
function visit(parent, visitFn, childrenFn) {
if (!parent) return;
visitFn(parent);
var children = childrenFn(parent);
if (children) {
var count = children.length;
for (var i = 0; i < count; i++) {
visit(children[i], visitFn, childrenFn);
}
}
}
var baseSvg = d3.select('.tree').append("svg")
.attr("width", viewerWidth)
.attr("height", viewerHeight)
.attr("class", "tree-container");
// Helper functions for collapsing and expanding nodes.
function collapse(d) {
if (d.children) {
d._children = d.children;
d._children.forEach(collapse);
d.children = null;
}
}
function centerNode(source) {
var scale = 1;
var x = 20;
var y = -source.x0;
y = y * scale + viewerHeight / 2;
d3.select('g').transition()
.duration(duration)
.attr("transform", "translate(" + x + "," + y + ")scale(" + scale + ")");
}
function toggleChildren(d) {
if (d.children) {
d._children = d.children;
d.children = null;
} else if (d._children) {
expand(d);
}
return d;
}
function expand(d) {
if (d._children) {
d.children = d._children;
d._children = null;
if (d.children.length == 1) {
expand(d.children[0])
}
}
}
function click(d) {
if (d._children) {
if (d.type != 'end') {
expandCollapse(d);
}
} else {
expandCollapse(d);
}
}
function expandCollapse(d) {
d = toggleChildren(d);
update(d);
centerNode(d);
}
function update(source) {
var levelWidth = [1];
var childCount = function(level, n) {
if (n.children && n.children.length > 0) {
if (levelWidth.length <= level + 1) levelWidth.push(0);
levelWidth[level + 1] += n.children.length;
n.children.forEach(function(d) {
childCount(level + 1, d);
});
}
};
childCount(0, root);
var newHeight = d3.max(levelWidth) * 25;
var circleRadius = 6;
tree = tree.size([newHeight, viewerWidth]);
// Compute the new tree layout.
var nodes = tree.nodes(root).reverse(),
links = tree.links(nodes);
// Set widths between levels based on maxLabelLength.
nodes.forEach(function(d) {
d.y = (d.depth * (maxLabelLength * 8));
if (d.x < root.x) {
d.x -= (root.x - d.x) * 3;
} else if (d.x > root.x) {
d.x += (d.x - root.x) * 3;
}
});
// Update the nodes…
var node = svgGroup.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.y0 + "," + source.x0 + ")";
})
.on('click', click);
var rectGrpEnter = nodeEnter.append('g')
.attr('class', 'node-rect-text-grp')
.attr('transform', 'translate(' +
-(rectNode.width + circleRadius) + ',' // note the transform is negative
+
-(rectNode.height / 2) + ')');
rectGrpEnter.append('rect')
.attr('rx', 6)
.attr('ry', 6)
.attr('width', rectNode.width)
.attr('height', rectNode.height)
.attr('class', 'node-rect');
rectGrpEnter.append('foreignObject')
.attr('x', rectNode.textMargin)
.attr('y', rectNode.textMargin)
.attr('width', function() {
return (rectNode.width - rectNode.textMargin * 2) < 0 ? 0 :
(rectNode.width - rectNode.textMargin * 2)
})
.attr('height', function() {
return (rectNode.height - rectNode.textMargin * 2) < 0 ? 0 :
(rectNode.height - rectNode.textMargin * 2)
})
.append('xhtml').html(function(d) {
return '<div style="width: ' +
(rectNode.width - rectNode.textMargin * 2) + 'px; height: ' +
(rectNode.height - rectNode.textMargin * 2) + 'px;" class="node-text wordwrap">' +
'<b>' + d.title + '</b>' +
'</div>';
});
nodeEnter.append("circle")
.attr('class', 'nodeCircle');
// Change the circle fill depending on whether it has children and is collapsed
node.select("circle.nodeCircle")
.attr("r", circleRadius)
.style("fill", function(d) {
return getNodeFill(d);
});
nodeEnter.append("text")
.attr("x", function(d) {
return d.children || d._children ? -10 : 10;
})
.attr("dy", ".35em")
.attr('class', 'nodeText')
.attr("text-anchor", function(d) {
return d.children || d._children ? "end" : "start";
})
.text(function(d) {
return d.title;
})
.style("fill-opacity", 0);
// Update the text to reflect whether node has children or not.
node.select('text')
.attr("x", function(d) {
return d.children || d._children ? -10 : 10;
})
.text(function(d) {
if (d.type != 'end') {
return d.title
} else {
return 'End node'
}
});
// Transition nodes to their new position.
var nodeUpdate = node.transition()
.duration(duration)
.attr("transform", function(d) {
return "translate(" + d.y + "," + d.x + ")";
});
// Fade the text in
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) {
return "translate(" + source.y + "," + source.x + ")";
})
.remove();
nodeExit.select("circle")
.attr("r", 0);
nodeExit.select("text")
.style("fill-opacity", 0);
// Update the links…
var link = svgGroup.selectAll("path.link")
.data(links, 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 diagonal({
source: o,
target: o
});
});
// Transition links to their new position.
link.transition()
.duration(duration)
.attr("d", function(d) {
return diagonal({
source: d.source,
target: { x: d.target.x, y: d.target.y - (rectNode.width + circleRadius) }
});
});
// Transition exiting nodes to the parent's new position.
link.exit().transition()
.duration(duration)
.attr("d", function(d) {
var o = {
x: source.x,
y: source.y
};
return diagonal({
source: o,
target: o
});
})
.remove();
// Update the link text
var linktext = svgGroup.selectAll("g.link")
.data(links, function(d) {
return d.target.id;
});
linktext.enter()
.insert("g")
.attr("class", "link")
.append("text")
.attr("dy", ".35em")
.attr("text-anchor", "middle")
.text(function(d) {
return d.target.optionTitle;
});
// Transition link text to their new positions
linktext.transition()
.duration(duration)
.attr("transform", function(d) {
return "translate(" + ((d.source.y + d.target.y) / 2) + "," + ((d.source.x + d.target.x) / 2) + ")";
})
//Transition exiting link text to the parent's new position.
linktext.exit().transition().remove();
// Stash the old positions for transition.
nodes.forEach(function(d) {
d.x0 = d.x;
d.y0 = d.y;
});
}
var svgGroup = baseSvg.append("g");
// Layout the tree initially and center on the root node.
update(root);
centerNode(root);
svgGroup
.append('defs')
.append('pattern')
.attr('id', function(d, i) {
return 'pic_plus';
})
.attr('height', 60)
.attr('width', 60)
.attr('x', 0)
.attr('y', 0)
.append('image')
.attr('xlink:href', function(d, i) {
return 'https://s3-eu-west-1.amazonaws.com/eq-static/app/images/common/plus.png';
})
.attr('height', 12)
.attr('width', 12)
.attr('x', 0)
.attr('y', 0);
function getNodeFill(d) {
if (isFinal(d)) {
return '#0f0';
} else if (d._children || (!d._children && !d.children)) {
return 'url(#pic_plus)'
} else {
return '#fff'
}
}
function isFinal(node) {
return node.type == 'end';
}
function isCollapsed(node) {
return d._children || (!d._children && !d.children);
}
body {
background-color: #ddd;
}
.tree-custom,
.tree {
width: 100%;
height: 100%;
background-color: #fff;
}
.holder {
margin: 0 auto;
width: 1000px;
height: 800px;
background-color: #fff;
}
.node {
cursor: pointer;
}
.node circle {
fill: #fff;
stroke: steelblue;
stroke-width: 1.5px;
}
.node text {
font: 10px sans-serif;
}
path.link {
fill: none;
stroke: #ccc;
stroke-width: 1.5px;
}
.link text {
font: 10px sans-serif;
fill: #666;
}
.node-rect {
fill: #00f;
}
.node-text {
color: #fff;
}
<html>
<head>
<script src="https://d3js.org/d3.v3.min.js"></script>
</head>
<body>
<div class="tree"></div>
</body>
</html>
You can check that the links are terminating in the correct place by removing the fill on the rect elements.
There are now a number of other fixes to be made, but that should have proved your proof of concept; if you are unclear about any parts of the new code, please just ask.

Wrapping multi line labels

I'm building a responsive zoomable treemap based on this project. The problem is that the labels I've are longer than the original visualization and end up not showing:
function text(text) {
text.selectAll("tspan")
.attr("x", function(d) { return x(d.x) + 6; })
text.attr("x", function(d) { return x(d.x) + 6; })
.attr("y", function(d) { return y(d.y) + 6; })
.style("opacity", function(d) { return this.getComputedTextLength() < x(d.x + d.dx) - x(d.x) ? 1 : 0; });
}
The problem I see is that the text needs to be showed completely in the first line, but I'd like to show it in multiple lines (inside the rect) instead.
There's a code by Mike Bostock which seems to solve this issue but I don't know how to apply it to the treemap.
Here's a quick a modification which wraps the parent text in that example:
<!DOCTYPE html>
<!--
Generic treemap, based on http://bost.ocks.org/mike/treemap/
-->
<html>
<head>
<meta charset="utf-8">
<title>Zoomable treemap template</title>
<style>
#chart {
background: #fff;
font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
}
.title {
font-weight: bold;
font-size: 24px;
text-align: center;
margin-top: 6px;
margin-bottom: 6px;
}
text {
pointer-events: none;
}
.grandparent text {
font-weight: bold;
}
rect {
fill: none;
stroke: #fff;
}
rect.parent,
.grandparent rect {
stroke-width: 2px;
}
rect.parent {
pointer-events: none;
}
.grandparent rect {
fill: orange;
}
.grandparent:hover rect {
fill: #ee9700;
}
.children rect.parent,
.grandparent rect {
cursor: pointer;
}
.children rect.parent {
fill: #bbb;
fill-opacity: .5;
}
.children:hover rect.child {
fill: #bbb;
}
</style>
</head>
<body>
<div id="chart"></div>
<script src="http://code.jquery.com/jquery-1.7.2.min.js"></script>
<script src="http://d3js.org/d3.v3.min.js"></script>
<script>
window.addEventListener('message', function(e) {
var opts = e.data.opts,
data = e.data.data;
return main(opts, data);
});
var defaults = {
margin: {
top: 24,
right: 0,
bottom: 0,
left: 0
},
rootname: "TOP",
format: ",d",
title: "",
width: 960,
height: 500
};
function main(o, data) {
var root,
opts = $.extend(true, {}, defaults, o),
formatNumber = d3.format(opts.format),
rname = opts.rootname,
margin = opts.margin,
theight = 36 + 16;
$('#chart').width(opts.width).height(opts.height);
var width = opts.width - margin.left - margin.right,
height = opts.height - margin.top - margin.bottom - theight,
transitioning;
var color = d3.scale.category20c();
var x = d3.scale.linear()
.domain([0, width])
.range([0, width]);
var y = d3.scale.linear()
.domain([0, height])
.range([0, height]);
var treemap = d3.layout.treemap()
.children(function(d, depth) {
return depth ? null : d._children;
})
.sort(function(a, b) {
return a.value - b.value;
})
.ratio(height / width * 0.5 * (1 + Math.sqrt(5)))
.round(false);
var svg = d3.select("#chart").append("svg")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.bottom + margin.top)
.style("margin-left", -margin.left + "px")
.style("margin.right", -margin.right + "px")
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")")
.style("shape-rendering", "crispEdges");
var grandparent = svg.append("g")
.attr("class", "grandparent");
grandparent.append("rect")
.attr("y", -margin.top)
.attr("width", width)
.attr("height", margin.top);
grandparent.append("text")
.attr("x", 6)
.attr("y", 6 - margin.top)
.attr("dy", ".75em");
if (opts.title) {
$("#chart").prepend("<p class='title'>" + opts.title + "</p>");
}
if (data instanceof Array) {
root = {
key: rname,
values: data
};
} else {
root = data;
}
initialize(root);
accumulate(root);
layout(root);
console.log(root);
display(root);
if (window.parent !== window) {
var myheight = document.documentElement.scrollHeight || document.body.scrollHeight;
window.parent.postMessage({
height: myheight
}, '*');
}
function initialize(root) {
root.x = root.y = 0;
root.dx = width;
root.dy = height;
root.depth = 0;
}
// Aggregate the values for internal nodes. This is normally done by the
// treemap layout, but not here because of our custom implementation.
// We also take a snapshot of the original children (_children) to avoid
// the children being overwritten when when layout is computed.
function accumulate(d) {
return (d._children = d.values) ? d.value = d.values.reduce(function(p, v) {
return p + accumulate(v);
}, 0) : d.value;
}
// Compute the treemap layout recursively such that each group of siblings
// uses the same size (1×1) rather than the dimensions of the parent cell.
// This optimizes the layout for the current zoom state. Note that a wrapper
// object is created for the parent node for each group of siblings so that
// the parent’s dimensions are not discarded as we recurse. Since each group
// of sibling was laid out in 1×1, we must rescale to fit using absolute
// coordinates. This lets us use a viewport to zoom.
function layout(d) {
if (d._children) {
treemap.nodes({
_children: d._children
});
d._children.forEach(function(c) {
c.x = d.x + c.x * d.dx;
c.y = d.y + c.y * d.dy;
c.dx *= d.dx;
c.dy *= d.dy;
c.parent = d;
layout(c);
});
}
}
function display(d) {
grandparent
.datum(d.parent)
.on("click", transition)
.select("text")
.text(name(d));
var g1 = svg.insert("g", ".grandparent")
.datum(d)
.attr("class", "depth");
var g = g1.selectAll("g")
.data(d._children)
.enter().append("g");
g.filter(function(d) {
return d._children;
})
.classed("children", true)
.on("click", transition);
var children = g.selectAll(".child")
.data(function(d) {
return d._children || [d];
})
.enter().append("g");
children.append("rect")
.attr("class", "child")
.call(rect)
.append("title")
.text(function(d) {
return d.key + " (" + formatNumber(d.value) + ")";
});
children.append("text")
.attr("class", "ctext")
.text(function(d) {
return d.key;
})
.call(text2);
g.append("rect")
.attr("class", "parent")
.call(rect);
var t = g.append("text")
.attr("class", "ptext")
.attr("dy", ".75em")
t.append("tspan")
.text(function(d) {
return d.key;
});
t.append("tspan")
.attr("dy", "1.0em")
.text(function(d) {
return formatNumber(d.value);
});
t.call(text);
g.selectAll("rect")
.style("fill", function(d) {
return color(d.key);
});
function transition(d) {
if (transitioning || !d) return;
transitioning = true;
var g2 = display(d),
t1 = g1.transition().duration(750),
t2 = g2.transition().duration(750);
// Update the domain only after entering new elements.
x.domain([d.x, d.x + d.dx]);
y.domain([d.y, d.y + d.dy]);
// Enable anti-aliasing during the transition.
svg.style("shape-rendering", null);
// Draw child nodes on top of parent nodes.
svg.selectAll(".depth").sort(function(a, b) {
return a.depth - b.depth;
});
// Fade-in entering text.
g2.selectAll("text").style("fill-opacity", 0);
// Transition to the new view.
t1.selectAll(".ptext").call(text).style("fill-opacity", 0);
t1.selectAll(".ctext").call(text2).style("fill-opacity", 0);
t2.selectAll(".ptext").call(text).style("fill-opacity", 1);
t2.selectAll(".ctext").call(text2).style("fill-opacity", 1);
t1.selectAll("rect").call(rect);
t2.selectAll("rect").call(rect);
// Remove the old node when the transition is finished.
t1.remove().each("end", function() {
svg.style("shape-rendering", "crispEdges");
transitioning = false;
});
}
return g;
}
function text(text) {
text.selectAll("tspan")
.attr("x", function(d) {
return x(d.x) + 6;
})
text.attr("x", function(d) {
return x(d.x) + 6;
})
.attr("y", function(d) {
return y(d.y) + 6;
})
.each(function(d) {
var tspan = this.childNodes[0];
var w = x(d.x + d.dx) - x(d.x);
wrap(tspan, w, x(d.x) + 6);
})
}
function text2(text) {
text.attr("x", function(d) {
return x(d.x + d.dx) - this.getComputedTextLength() - 6;
})
.attr("y", function(d) {
return y(d.y + d.dy) - 6;
})
.style("opacity", function(d) {
return this.getComputedTextLength() < x(d.x + d.dx) - x(d.x) ? 1 : 0;
});
}
function rect(rect) {
rect.attr("x", function(d) {
return x(d.x);
})
.attr("y", function(d) {
return y(d.y);
})
.attr("width", function(d) {
return x(d.x + d.dx) - x(d.x);
})
.attr("height", function(d) {
return y(d.y + d.dy) - y(d.y);
});
}
function name(d) {
return d.parent ? name(d.parent) + " / " + d.key + " (" + formatNumber(d.value) + ")" : d.key + " (" + formatNumber(d.value) + ")";
}
}
if (window.location.hash === "") {
d3.json("https://jsonblob.com/api/7c30e101-da91-11e6-90ab-11c211a4b3d5", function(err, res) {
if (!err) {
console.log(res);
var data = d3.nest().key(function(d) {
return d.region;
}).key(function(d) {
return d.subregion;
}).entries(res);
main({
title: "World Population"
}, {
key: "World",
values: data
});
}
});
}
function wrap(tspan, width, x) {
var text = d3.select(tspan),
words = text.text().split(/\s+/).reverse(),
word,
line = [],
y = text.attr("y"),
dy = parseFloat(text.attr("dy")) || 0.4,
tspan = text.text(null).append("tspan").attr("x", x).attr("y", y).attr("dy", "0.75em");
while (word = words.pop()) {
line.push(word);
tspan.text(line.join(" "));
if (tspan.node().getComputedTextLength() > width) {
line.pop();
tspan.text(line.join(" "));
line = [word];
tspan = text.append("tspan").attr("x", x).attr("y", y).attr("dy", "1em").text(word);
}
}
}
</script>
</body>
</html>

How to control the coordinates of the nodes of d3

I have created a d3 force layout, and it works very well. Now I will add a group of data to my graph. I hope I could control the center of my new nodes. For example, supposed the center is (100,100), I hope the new nodes lay out into rectangle area like [(50,50) to (150,150)] as a whole.
var width = 500,
height = 500;
var nodes = [{id:0, n:'Tom'}, {id:1, n:'Join'}, {id:2, n:'John'}, {id:3, n:'Bob'}, {id:4, n:'4'}, {id:5, n:'5'}, {id:6, n:'6'}];
var links = [{source:0,target:1},{source:0,target:2},{source:0,target:3},{source:0,target:4},{source:0,target:5},{source:1,target:5},{source:1,target:6}];
// init force
var force = d3.layout.force()
.charge(-120)
.linkDistance(120)
.size([width, height]);
// init svg
var svg = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height);
// set tick function
force.on("tick", function () {
d3.selectAll(".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;
});
// controll the coordinates here
d3.selectAll(".node").attr("transform", function(d){
if(d.flag == 1){
d.x = Math.max(50, Math.min(150, d.x));
d.y = Math.max(50, Math.min(150, d.y));
}
return "translate("+d.x+","+d.y+")";
});
}).on('end', function(){
svg.selectAll(".node").each(function(d){d.fixed=true;});
});
function setData(ns, ls){
var update = svg.selectAll(".link").data(ls);
update.enter().append("line")
.attr("class", "link")
.style("stroke-width", 1);
update.exit().remove();
update = svg.selectAll(".node").data(ns);
update.enter().append("g")
.attr("class", "node")
.attr("id", function(d){return d.id})
.call(force.drag)
.call(function(p){
p.append("image")
.attr("class", "nodeimage")
.attr("width", "30px")
.attr("height", "30px")
.attr("x", "-15px")
.attr("y", "-15px");
p.append("text")
.attr("class", "nodetext")
.attr("dx", "-10px")
.attr("dy", "20px")
.style("font-size", "15px")
.text(function(d){return d.n});
});
update.exit().remove();
update.selectAll(".nodeimage")
.each(function() {
d3.select(this).datum(d3.select(this.parentNode).datum());
})
.attr("xlink:href", function(d){
var img;
if(d.flag == 1){
img = "http://www.gravatar.com/avatar/1eccef322f0beef11e0e47ed7963189b/?default=&s=80"
}else{
img = "http://www.gravatar.com/avatar/a1338368fe0b4f3d301398a79c171987/?default=&s=80";
}
return img;
});
force.nodes(ns)
.links(ls)
.start();
}
//init
setData(nodes, links);
setTimeout(function(){
//generate new data and merge to old data
nodes = nodes.concat(generateNewData());
setData(nodes, links);
//how do i control the coordinate of new nodes?
}, 3000);
function generateNewData(){
var ns = [];
for(var i = 0; i < 10; i++){
ns.push({id:i+100,n:'n'+i,flag:1});
}
return ns;
}
Here is my demo of jsfiddle:http://jsfiddle.net/cs4xhs7s/4/
The latest demo shows that the nodes can display in the rectangle, however, their coordinates are the same. I hope it is an available force layout.
https://jsfiddle.net/wpnq15mf/1/
var width = 500,
height = 500;
var nodes = [{id:0, n:'Tom'}, {id:1, n:'Join'}, {id:2, n:'John'}, {id:3, n:'Bob'}, {id:4, n:'4'}, {id:5, n:'5'}, {id:6, n:'6'}];
var links = [{source:0,target:1},{source:0,target:2},{source:0,target:3},{source:0,target:4},{source:0,target:5},{source:1,target:5},{source:1,target:6}];
// init force
var force = d3.layout.force()
.charge(-500)
.linkDistance(120)
.gravity(0.1)
.size([width, height]);
// init svg
var svg = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height);
// set tick function
force.on("tick", function () {
d3.selectAll(".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;
});
// controll the coordinates here
d3.selectAll(".node").attr("transform", function(d){
if(d.flag == 1){
d.x = Math.max(50, Math.min(150, d.x));
d.y = Math.max(50, Math.min(150, d.y));
}
return "translate("+d.x+","+d.y+")";
});
}).on('end', function(){
svg.selectAll(".node").each(function(d){d.fixed=true;});
});
function setData(ns, ls){
var update = svg.selectAll(".link").data(ls);
update.enter().append("line")
.attr("class", "link")
.style("stroke-width", 1);
update.exit().remove();
update = svg.selectAll(".node").data(ns);
update.enter().append("g")
.attr("class", "node")
.attr("id", function(d){return d.id})
.call(force.drag)
.call(function(p){
p.append("image")
.attr("class", "nodeimage")
.attr("width", "30px")
.attr("height", "30px")
.attr("x", "-15px")
.attr("y", "-15px");
p.append("text")
.attr("class", "nodetext")
.attr("dx", "-10px")
.attr("dy", "20px")
.style("font-size", "15px")
.text(function(d){return d.n});
});
update.exit().remove();
update.selectAll(".nodeimage")
.each(function() {
d3.select(this).datum(d3.select(this.parentNode).datum());
})
.attr("xlink:href", function(d){
var img;
if(d.flagx == 1){
img = "http://www.gravatar.com/avatar/1eccef322f0beef11e0e47ed7963189b/?default=&s=80"
}else{
img = "http://www.gravatar.com/avatar/a1338368fe0b4f3d301398a79c171987/?default=&s=80";
}
return img;
});
force.nodes(ns)
.links(ls)
.start();
}
//init
setData(nodes, links);
setTimeout(function(){
//generate new data and merge to old data
nodes = nodes.concat(generateNewData());
links = links.concat(generateNewLinks());
setData(nodes, links);
//how do i control the coordinate of new nodes?
}, 3000);
function generateNewData(){
var ns = [];
ns.push({id:6,n:'n'+i,flag:1, flagx:1});
for(var i = 1; i < 10; i++){
ns.push({id:i+6,n:'n'+i, flagx:1});
}
return ns;
}
function generateNewLinks(){
var ns = [];
ns.push({source:7,target:8});
ns.push({source:7,target:9});
ns.push({source:7,target:10});
ns.push({source:7,target:11});
ns.push({source:7,target:12});
ns.push({source:7,target:13});
ns.push({source:7,target:14});
ns.push({source:7,target:15});
ns.push({source:7,target:16});
return ns;
}
.node {
stroke: #fff;
stroke-width: 1.5px;
}
.link {
stroke: #999;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>

Zoomable Sunburst with % Share labeled

I want show percentage share of each block in labelled zoomable sunburst chart. I am referring http://bl.ocks.org/metmajer/5480307 this example.
here I want add (%x) share of each block. Please help.
Below is my index.html
<!DOCTYPE html>
<meta charset="utf-8"><style>
path {
stroke: #fff;
fill-rule: evenodd;
}
text {
font-family: Arial, sans-serif;
font-size: 12px;
}
</style> <body>
<script src="http://d3js.org/d3.v3.min.js">
</script> <script>
var width = 960,
height = 700,
radius = Math.min(width, height) / 2;
var x = d3.scale.linear()
.range([0, 2 * Math.PI]);
var y = d3.scale.linear()
.range([0, radius]);
var color = d3.scale.category20c();
var svg = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height)
.append("g")
.attr("transform", "translate(" + width / 2 + "," + (height / 2 + 10) + ")");
var partition = d3.layout.partition()
.value(function(d) { return d.size; });
var arc = d3.svg.arc()
.startAngle(function(d) { return Math.max(0, Math.min(2 * Math.PI, x(d.x))); })
.endAngle(function(d) { return Math.max(0, Math.min(2 * Math.PI, x(d.x + d.dx))); })
.innerRadius(function(d) { return Math.max(0, y(d.y)); })
.outerRadius(function(d) { return Math.max(0, y(d.y + d.dy)); });
d3.json("atmLeads.json", function(error, root) {
var g = svg.selectAll("g")
.data(partition.nodes(root))
.enter().append("g");
var path = g.append("path")
.attr("d", arc).attr("class",function(d){return "ring_"+ d.depth;})
.style("fill", function(d) { return color((d.children ? d : d.parent).name); })
.on("click", click);
var text = g.append("text")
.attr("transform", function(d) { return "rotate(" + computeTextRotation(d) + ")"; })
.attr("x", function(d) { return y(d.y); })
.attr("dx", "6") // margin
.attr("dy", ".35em") // vertical-align
.text(function(d) { return d.name; });
function click(d) {
// fade out all text elements
text.transition().attr("opacity", 0);
path.transition()
.duration(750)
.attrTween("d", arcTween(d))
.each("end", function(e, i) {
// check if the animated element's data e lies within the visible angle span given in d
if (e.x >= d.x && e.x < (d.x + d.dx)) {
// get a selection of the associated text element
var arcText = d3.select(this.parentNode).select("text");
// fade in the text element and recalculate positions
arcText.transition().duration(750)
.attr("opacity", 1)
.attr("transform", function() { return "rotate(" + computeTextRotation(e) + ")" })
.attr("x", function(d) { return y(d.y); });
}
});
}
});
d3.select(self.frameElement).style("height", height + "px");
// Interpolate the scales!
function arcTween(d) {
var xd = d3.interpolate(x.domain(), [d.x, d.x + d.dx]),
yd = d3.interpolate(y.domain(), [d.y, 1]),
yr = d3.interpolate(y.range(), [d.y ? 20 : 0, radius]);
return function(d, i) {
return i
? function(t) { return arc(d); }
: function(t) { x.domain(xd(t)); y.domain(yd(t)).range(yr(t)); return arc(d); };
};
}
function computeTextRotation(d) {
return (x(d.x + d.dx / 2) - Math.PI / 2) / Math.PI * 180;
}
</script>
Try this code.
var path = g.append("path")
.attr("d", arc).attr("class", function(d) {
return "ring_" + d.depth;
})
.style("fill", function(d) {
return color((d.children ? d : d.parent).name);
})
.on("click", click);
var totalSize = path.node().__data__.value;
var text = g.append("text")
.attr("transform", function(d) {
return "rotate(" + computeTextRotation(d) + ")";
})
.attr("x", function(d) {
return y(d.y);
})
.attr("dx", "6") // margin
.attr("dy", ".35em") // vertical-align
.text(function(d) {
var percentage = (100 * d.value / totalSize).toPrecision(3);
var percentageString = percentage + "%";
if (percentage < 0.1) {
percentageString = "< 0.1%";
}
return d.name +" "+percentageString;
});
var width = 960,
height = 700,
radius = Math.min(width, height) / 2;
var x = d3.scale.linear()
.range([0, 2 * Math.PI]);
var y = d3.scale.linear()
.range([0, radius]);
var color = d3.scale.category20c();
var svg = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height)
.append("g")
.attr("transform", "translate(" + width / 2 + "," + (height / 2 + 10) + ")");
var partition = d3.layout.partition()
.value(function(d) {
return d.size;
});
var arc = d3.svg.arc()
.startAngle(function(d) {
return Math.max(0, Math.min(2 * Math.PI, x(d.x)));
})
.endAngle(function(d) {
return Math.max(0, Math.min(2 * Math.PI, x(d.x + d.dx)));
})
.innerRadius(function(d) {
return Math.max(0, y(d.y));
})
.outerRadius(function(d) {
return Math.max(0, y(d.y + d.dy));
});
var root = {
"name": "ATM Leads Converted",
"size": 34752,
"children": [
{
"name": "Converted",
"size": 417
}, {
"name": "Failure",
"size": 1366
}, {
"name": "Interested",
"size": 916
}, {
"name": "No Value",
"size": 48932
}, {
"name": "Not Interested",
"size": 14479
}, {
"name": "Not contactable",
"size": 2961
},
{
"name": "Success",
"size": 1142
}, {
"name": "Will Get Back",
"size": 1564
}, {
"name": "Wrong Number",
"size": 358
}
]
};
var g = svg.selectAll("g")
.data(partition.nodes(root))
.enter().append("g");
var path = g.append("path")
.attr("d", arc).attr("class", function(d) {
return "ring_" + d.depth;
})
.style("fill", function(d) {
return color((d.children ? d : d.parent).name);
})
.on("click", click);
var totalSize = path.node().__data__.value;
var text = g.append("text")
.attr("transform", function(d) {
return "rotate(" + computeTextRotation(d) + ")";
})
.attr("x", function(d) {
return y(d.y);
})
.attr("dx", "6") // margin
.attr("dy", ".35em") // vertical-align
.text(function(d) {
var percentage = (100 * d.value / totalSize).toPrecision(3);
var percentageString = percentage + "%";
if (percentage < 0.1) {
percentageString = "< 0.1%";
}
return d.name +" "+percentageString;
});
function click(d) {
// fade out all text elements
text.transition().attr("opacity", 0);
path.transition()
.duration(750)
.attrTween("d", arcTween(d))
.each("end", function(e, i) {
// check if the animated element's data e lies within the visible angle span given in d
if (e.x >= d.x && e.x < (d.x + d.dx)) {
// get a selection of the associated text element
var arcText = d3.select(this.parentNode).select("text");
// fade in the text element and recalculate positions
arcText.transition().duration(750)
.attr("opacity", 1)
.attr("transform", function() {
return "rotate(" + computeTextRotation(e) + ")"
})
.attr("x", function(d) {
return y(d.y);
});
}
});
}
d3.select(self.frameElement).style("height", height + "px");
// Interpolate the scales!
function arcTween(d) {
var xd = d3.interpolate(x.domain(), [d.x, d.x + d.dx]),
yd = d3.interpolate(y.domain(), [d.y, 1]),
yr = d3.interpolate(y.range(), [d.y ? 20 : 0, radius]);
return function(d, i) {
return i ? function(t) {
return arc(d);
} : function(t) {
x.domain(xd(t));
y.domain(yd(t)).range(yr(t));
return arc(d);
};
};
}
function computeTextRotation(d) {
return (x(d.x + d.dx / 2) - Math.PI / 2) / Math.PI * 180;
}
path {
stroke: #fff;
fill-rule: evenodd;
}
text {
font-family: Arial, sans-serif;
font-size: 12px;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>

Change class of one element when hover over another element d3

I have a list of images and a list of image titles. I want to be able to show a hover state (change css) for the title when I mouse over its corresponding image, but I cannot figure out how to connect the two pieces of data. My code is below. I have it so that when you click on the top number the information appears beneath.
<!DOCTYPE html>
<html>
<head>
<script src="d3.v2.js"></script>
<title>Untitled</title>
</head>
<body>
<script type="text/javascript" >
function barStack(d) {
var l = d[0].length
while (l--) {
var posBase = 0, negBase = 0;
d.forEach(function(d) {
d=d[l]
d.size = Math.abs(d.y)
if (d.y<0) {
d.y0 = negBase
negBase-=d.size
} else
{
d.y0 = posBase = posBase + d.size
}
})
}
d.extent= d3.extent(d3.merge(d3.merge(d.map(function(e) { return e.map(function(f) { return [f.y0,f.y0-f.size]})}))))
return d
}
var ratiodata = [[{y:3.3}],[{y:-1.5}]]
var imageList = [
[3.3, 28, -1.5, 13, 857, 3, 4, 7, [{paintingfile:"676496.jpg", title:"Dessert1"}, {paintingfile:"676528.jpg", title: "Dessert2"}]
]]
var h=400
var w=1350
var margin=25
var color = d3.scale.category10()
var div = d3.select("body").append("div")
.attr("class", "imgtooltip")
.style("opacity", 0);
var x = d3.scale.ordinal()
.domain(['255'])
.rangeRoundBands([margin,w-margin], .1)
var y = d3.scale.linear()
.range([h-margin,0+margin])
var xAxis = d3.svg.axis().scale(x).orient("bottom").tickSize(6, 0)
var yAxis = d3.svg.axis().scale(y).orient("left")
barStack(ratiodata)
y.domain(ratiodata.extent)
var svg = d3.select("body")
.append("svg")
.attr("height",h)
.attr("width",w)
svg.selectAll(".series")
.data(ratiodata)
.enter()
.append("g")
.classed("series",true)
.style("fill","orange")
.selectAll("rect").data(Object)
.enter()
.append("rect")
.attr("x",function(d,i) { return x(x.domain()[i])})
.attr("y",function(d) { return y(d.y0)})
.attr("height",function(d) { return y(0)-y(d.size)})
.attr("width",x.rangeBand());
svg.selectAll("text")
.data(imageList)
.enter()
.append("text")
.text(function(d) {
return d[0];
})
.attr("x", function(d, i) {
return x(x.domain()[i]) + x.rangeBand() / 2;
})
.attr("y", function(d) {
return h - (32.1100917431*d[0] +150);
})
.attr("font-size", "16px")
.attr("fill", "#000")
.attr("text-anchor", "middle")
//.on("click", function(d) {console.log(d[1]);})
.on("click", function(d) {
//Update the tooltip position and value
d3.selectAll("ul")
.remove();
d3.selectAll("li")
.remove();
d3.select("#PaintingDetails")
.append("ul")
.selectAll("li")
.data(d[8])
.enter()
.append("li")
.text(function(d){
return (d.title);
});
d3.select("#imageBox")
.append("ul")
.selectAll("li")
.data(d[8])
.enter()
.append("li")
.classed("Myimageslist",true)
.append("img")
.classed("Myimages",true)
.attr("src", function(d){
return ("http://images.tastespotting.com/thumbnails/" + d.paintingfile);
})
.attr("align", "top");
d3.select(".Myimages")
.on("mouseover", function(){
d3.select("#PaintingDetails")
.selectAll("li")
.classed("selected", true)
});
});
svg.append("g").attr("class","axis x").attr("transform","translate (0 "+y(0)+")").call(xAxis);
// svg.append("g").attr("class","axis y").attr("transform","translate ("+x(margin)+" 0)").call(yAxis);
</script>
<div id="PaintingDetails"></div>
<div id="imageBox"></div>
</body>
</html>
The quick and dirty solution would be to simply use the index of the data element to figure out which title matches which image:
d3.selectAll(".Myimages")
.on("mouseover", function(d, i) {
d3.select("#PaintingDetails")
.selectAll("li")
.classed("selected", function(e, j) { return j == i; })
});

Resources