Doughnut pie hybrid d3 version 4 - javascript

I have an old doughnut hybrid pie chart/bubble chart combo working on d3v3.
//doughnut bubble chart v3
https://jsfiddle.net/ajevh5wf/
var $this = $('body');
var w = 300,
h = 300;
var radius = Math.min(w, h) / 2;
var svg = d3.select($this[0])
.append("svg")
.attr("width", w)
.attr("height", h)
.append("g")
svg.append("g")
.attr("class", "slices");
svg.append("g")
.attr("class", "placeholders");
var pie = d3.layout.pie()
.sort(null)
.value(function(d) {
return d.value;
});
var arc = d3.svg.arc()
.outerRadius(radius * 0.85)
.innerRadius(radius * 0.83);
var arc2 = d3.svg.arc()
.outerRadius(radius - 10)
.innerRadius(0);
var outerArc = d3.svg.arc()
.innerRadius(radius * 0.9)
.outerRadius(radius * 0.9);
svg.attr("transform", "translate(" + w / 2 + "," + h / 2 + ")");
function colores_google(n) {
var colores_g = ["#f7b363", "#448875", "#c12f39", "#2b2d39", "#f8dd2f"];
return colores_g[n % colores_g.length];
}
var data = [{
"group": "<5",
"value": 1000,
"children": [{
"group": "<5",
"label": "Mel",
"value": 1000,
"totalGroupValue": 1000
}]
}, {
"group": "5-13",
"value": 1000,
"children": [{
"group": "5-13",
"label": "Erica",
"value": 1000,
"totalGroupValue": 1000
}]
}, {
"group": "14-17",
"value": 2000,
"children": [{
"group": "14-17",
"label": "Jessica",
"value": 1500,
"totalGroupValue": 2000
}, {
"group": "14-17",
"label": "Jill",
"value": 500,
"totalGroupValue": 2000
}]
}, {
"group": "18-24",
"value": 1300,
"children": [{
"group": "18-24",
"label": "Jerry",
"value": 500,
"totalGroupValue": 1300
}, {
"group": "18-24",
"label": "Ben",
"value": 500,
"totalGroupValue": 1300
}, {
"group": "18-24",
"label": "Billy",
"value": 150,
"totalGroupValue": 1300
}, {
"group": "18-24",
"label": "Billy2",
"value": 150,
"totalGroupValue": 1300
}]
}, {
"group": "25-44",
"value": 1000,
"children": [{
"group": "25-44",
"label": "Kelly",
"value": 1000,
"totalGroupValue": 1000
}]
}];
$.each(data, function(index, value) {
value["groupid"] = index;
$.each(value.children, function(i, v) {
v["groupid"] = index;
});
});
//slices
var slice = svg.select(".slices").selectAll("path.slice")
.data(pie(data));
slice.enter()
.insert("path")
.style("fill", function(d) {
return colores_google(d.data.groupid);
})
.attr("class", "slice");
slice
.transition().duration(1000)
.attrTween("d", function(d) {
this._current = this._current || d;
var interpolate = d3.interpolate(this._current, d);
this._current = interpolate(0);
return function(t) {
return arc(interpolate(t));
};
})
slice.exit()
.remove();
//slices
//placeholder bubbles
var placeholders = svg.select(".placeholders").selectAll("g.placeholder")
.data(pie(data));
placeholders.enter()
.insert("g")
.attr("class", function(d, i) {
return "placeholder place" + i;
});
placeholders
.transition().duration(1000)
.attr("transform", function(d) {
return "translate(" + arc2.centroid(d) + ")";
})
placeholders.exit()
.remove();
//placeholder bubbles
//bubbles
function bubbledata(data) {
//loop through data -- and MERGE children
var childs = [];
$.each(data, function(index, value) {
childs.push(value.children);
});
return $.extend(true, {}, {
"children": data
}); // return deep clone
}
function setBubbleChart(width, index, data) {
//_create bubble
var diameter = width / 2; //take half/width
var bubs = svg.select(".place" + index).append("g")
.attr("class", "bubs");
bubs.attr("transform", "translate(" + -diameter / 2 + "," + -diameter / 2 + ")");
var bubble = d3.layout.pack()
.size([diameter, diameter])
.value(function(d) {
return d.value;
})
.padding(3);
//_create bubble
var data = bubbledata(data);
var nodes = bubble.nodes(data)
.filter(function(d) {
return !d.children;
}); // filter out the outer bubble
var bubbles = bubs.selectAll('circle')
.data(nodes);
bubbles.enter()
.insert("circle")
.attr('transform', function(d) {
return 'translate(' + d.x + ',' + d.y + ')';
})
.attr('r', function(d) {
return d.r;
})
.style("fill", function(d) {
return colores_google(d.groupid);
});
bubbles = bubbles.transition()
.transition()
.duration(250)
.attr('transform', function(d) {
return 'translate(' + d.x + ',' + d.y + ')';
})
.attr('r', function(d) {
return d.r;
})
.ease('sine');
}
//loop through data and for EACH children array paint dots.
$.each(data, function(index, value) {
setBubbleChart(100, index, value.children);
});
//custom bubble chart
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.5.17/d3.min.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.2.1/jquery.min.js"></script>
//doughnut bubble chart v4 - not working
https://jsfiddle.net/4rdj0fze/1/
var $this = $('body');
var data = [{
"group": "<5",
"value": 1000,
"children": [{
"group": "<5",
"label": "Mel",
"value": 1000,
"totalGroupValue": 1000
}]
}, {
"group": "5-13",
"value": 1000,
"children": [{
"group": "5-13",
"label": "Erica",
"value": 1000,
"totalGroupValue": 1000
}]
}, {
"group": "14-17",
"value": 2000,
"children": [{
"group": "14-17",
"label": "Jessica",
"value": 1500,
"totalGroupValue": 2000
}, {
"group": "14-17",
"label": "Jill",
"value": 500,
"totalGroupValue": 2000
}]
}, {
"group": "18-24",
"value": 1300,
"children": [{
"group": "18-24",
"label": "Jerry",
"value": 500,
"totalGroupValue": 1300
}, {
"group": "18-24",
"label": "Ben",
"value": 500,
"totalGroupValue": 1300
}, {
"group": "18-24",
"label": "Billy",
"value": 150,
"totalGroupValue": 1300
}, {
"group": "18-24",
"label": "Billy2",
"value": 150,
"totalGroupValue": 1300
}]
}, {
"group": "25-44",
"value": 1000,
"children": [{
"group": "25-44",
"label": "Kelly",
"value": 1000,
"totalGroupValue": 1000
}]
}];
var width = 300,
height = 300;
var color = d3.scaleOrdinal()
.range(["#eb6383", "#fa9191", "#ffe9c5", "#b4f2e1"]);
var margin = {
top: 20,
right: 5,
bottom: 30,
left: 20
},
width = width - margin.left - margin.right,
height = height - margin.top - margin.bottom;
var svg = d3.select($this[0])
.append("svg")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.append("g")
.attr('class', 'doughnutbubblechart')
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
var doughnutbubble = svg.append('g').attr('class', 'doughnutbubble');
var radius = Math.min(width, height) / 2;
doughnutbubble.append("g")
.attr("class", "slices");
doughnutbubble.append("g")
.attr("class", "placeholders");
var pie = d3.pie()
.sort(null)
.value(function(d) {
return d.value;
});
var arc = d3.arc()
.outerRadius(radius * 0.85)
.innerRadius(radius * 0.83);
var arc2 = d3.arc()
.outerRadius(radius - 10)
.innerRadius(0);
var outerArc = d3.arc()
.innerRadius(radius * 0.9)
.outerRadius(radius * 0.9);
doughnutbubble.attr("transform", "translate(" + width / 2 + "," + height / 2 + ")");
function colores_google(n) {
var colores_g = ["#f7b363", "#448875", "#c12f39", "#2b2d39", "#f8dd2f"];
return colores_g[n % colores_g.length];
}
$.each(data, function(index, value) {
value["groupid"] = index;
$.each(value.children, function(i, v) {
v["groupid"] = index;
});
});
//slices
var slice = doughnutbubble.select(".slices").selectAll("path.slice")
.data(pie(data));
slice.enter()
.insert("path")
.style("fill", function(d) {
return colores_google(d.data.groupid);
})
.attr("class", "slice")
.transition().duration(1000)
.attrTween("d", function(d) {
this._current = this._current || d;
var interpolate = d3.interpolate(this._current, d);
this._current = interpolate(0);
return function(t) {
return arc(interpolate(t));
};
})
slice.exit()
.remove();
//slices
//placeholder bubbles
var placeholders = doughnutbubble.select(".placeholders").selectAll("g.placeholder")
.data(pie(data));
placeholders.enter()
.insert("g")
.attr("class", function(d, i) {
return "placeholder place" + i;
})
.transition().duration(1000)
.attr("transform", function(d) {
return "translate(" + arc2.centroid(d) + ")";
})
placeholders.exit()
.remove();
//placeholder bubbles
//bubbles
function bubbledata(data) {
//loop through data -- and MERGE children
var childs = [];
$.each(data, function(index, value) {
childs.push(value.children);
});
return $.extend(true, {}, {
"children": data
}); // return deep clone
}
function setBubbleChart(width, index, data) {
//_create bubble
var diameter = width / 2; //take half/width
var bubs = doughnutbubble.select(".place" + index).append("g")
.attr("class", "bubs");
bubs.attr("transform", "translate(" + -diameter / 2 + "," + -diameter / 2 + ")");
//var data = bubbledata(data);
//console.log("data", data);
///
var force = d3.forceSimulation();
var foci = {
x: 150,
y: 150
}
var data = funnelData(data, width, height)
.map(function(d) {
d.foci = foci;
return d;
})
force.nodes(data);
var bubbles = bubs.append("g")
.attr("class", "bubbles")
var nodes = bubbles.selectAll("circle")
.data(data)
.enter()
.append('circle')
.attr('r', 1)
.attr('fill', function(d, i) {
return color(i);
});
// Update
nodes.transition()
.delay(300)
.duration(1000)
.attr("r", function(d) {
return d.value * (width / 6) / 100;
});
force.on("tick", function() {
data.forEach(cluster);
data.forEach(collide(0.1));
nodes.attr("cx", function(d) {
return d.x;
})
.attr("cy", function(d) {
return d.y;
})
});
function funnelData(data, width, height) {
function getRandom(min, max) {
return Math.floor(Math.random() * (max - min + 1)) + min;
}
var max_amount = d3.max(data, function(d) {
return parseInt(d.value)
})
var radius_scale = d3.scalePow().exponent(0.5).domain([0, max_amount]).range([2, 85])
data.forEach(function(elem, index) {
elem.radius = radius_scale(elem.value) * .8;
elem.all = 'all';
elem.x = getRandom(0, width);
elem.y = getRandom(0, height);
});
return data;
}
function cluster(d, i) {
var f = d.foci;
var k = 0.55;
d.y += (f.y - d.y) * k * force.alpha()
d.x += (f.x - d.x) * k * force.alpha()
}
var maxRadius = d3.max(data, function(d) {
return d.radius;
})
var padding = 4;
function collide(alpha) {
var quadtree = d3.quadtree().x(function(d) {
return d.x;
}).y(function(d) {
return d.y;
}).extent([
[-1, -1],
[width + 1, height + 1]
]).addAll(data);
return function(d) {
var r = d.radius + maxRadius + padding,
nx1 = d.x - r,
nx2 = d.x + r,
ny1 = d.y - r,
ny2 = d.y + r;
quadtree.visit(function(quad, x1, y1, x2, y2) {
if (!quad.length && (quad.data !== d)) {
var x = d.x - quad.data.x,
y = d.y - quad.data.y,
l = Math.sqrt(x * x + y * y),
r = d.radius + quad.data.radius + padding;
if (l < r) {
l = (l - r) / l * alpha;
d.x -= x *= l;
d.y -= y *= l;
quad.data.x += x;
quad.data.y += y;
}
}
return x1 > nx2 || x2 < nx1 || y1 > ny2 || y2 < ny1;
});
};
}
}
//loop through data and for EACH children array paint dots.
$.each(data, function(index, value) {
setBubbleChart(100, index, value.children);
});
//custom bubble chart
<script src="https://d3js.org/d3.v4.min.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.2.1/jquery.min.js"></script>
---bubblechart v4 working -
https://jsfiddle.net/of48xjar/
but when I try and add this bubblechartv4 code to the base v4 -- I can't seem to get the bubbles to render properly in the placeholders or the sizes -- and I think in the old version3 had to exclude the parent level to ensure it just renders children bubbles.

Many things have changed from v3 to v4, like d3.pack() instead of d3.layout.pack(), and having to import the "ease" module and reference d3.easeSin instead of 'sine'. Once you take care of all these little differences, your code works:
https://jsfiddle.net/dc6eugtn/1
Here's the relevant section of changes:
function setBubbleChart(width, index, data) {
var diameter = width / 2; //take half/width
var bubs = doughnutbubble.select(".place" + index).append("g")
.attr("class", "bubs");
bubs.attr("transform", "translate(" + -diameter / 2 + "," + -diameter / 2 + ")");
var data = bubbledata(data);
var bubble = d3.pack(data)
.size([diameter, diameter])
.padding(3);
var nodes = d3.hierarchy(data).sum(d => d.value);
var bubbles = bubs.selectAll('circle')
.data(bubble(nodes).descendants());
bubbles.enter()
.filter(d => !d.children)
.insert("circle")
.attr('transform', function(d) {
return 'translate(' + d.x + ',' + d.y + ')';
})
.attr('r', d => d.r)
.style("fill", function(d) {
return colores_google(d.data.groupid);
});
bubbles = bubbles.transition()
.transition()
.duration(250)
.attr('transform', function(d) {
return 'translate(' + d.x + ',' + d.y + ')';
})
.attr('r', d => d.r)
.ease(d3.easeSin);
}

Related

I wanted to make last child or root node in d3 zoomable sunburst as a hyperlink, trying to find the last child node and make the transition to url

I'm new to d3js so I watched many tutorial video and developed the chart, but now I got stuck to make the last child node as a hyperlink, I also refered this How can I make last node in d3 zoomable sunburst as a hyperlink? and tried to solve this but I couldn't. Since I'm new could anyone please help me in solving this.
Here I'm attaching the js file please help me in which location do I need to check for the child node and while transition I wanted to make it as hyperlink.
`
function getData() {
return {
"name": "Main",
"children": [
{
"name": "Section1",
"url": "",
"children": [{
"name": "Test11 ",
"size": 1,
"url": "https://www.google.com"
}, {
"name": "Section12 ",
"size": 1
}, {
"name": "Section13 ",
"size": 1
}, {
"name": "Section14 ",
"size": 1
}]
},
{
"name": "Section2",
"url": "",
"children": [{
"name": "Section21 ",
"size": 1,
"url": "https://www.google.com"
}, {
"name": "Section22 ",
"size": 1
}, {
"name": "Section23 ",
"size": 1
}, {
"name": "Section24 ",
"size": 1
}]
}, {
"name": "Section3",
"url": "",
"children": [{
"name": "Section31 ",
"size": 1,
"url": "https://www.google.com"
}, {
"name": "Section32 ",
"size": 1
}, {
"name": "Section33 ",
"size": 1
}, {
"name": "Section34 ",
"size": 1
}]
}, {
"name": "Section4",
"url": "",
"children": [{
"name": "Section41 ",
"size": 1,
"url": "https://www.google.com"
}, {
"name": "Section42 ",
"size": 1
}, {
"name": "Section43 ",
"size": 1
}, {
"name": "Section44 ",
"size": 1
}]
},]
}
};
var colorScheme = ["#1c1c1e", "#FFCC00", "#007AFF", "#FF2C55", "#FF9501", "#AF52DE", "#5AC8FB", "#5855D6", "#35C75A", "#00a86b", "#ca3433", "#00E676"];
dataset = getData();
var width = 500,
height = 500,
radius = Math.min(width / 2, height / 2) * .80;
var x = d3.scale.linear()
.range([0, 2 * Math.PI]);
var y = d3.scale.linear()
.range([-90, radius]);
var color = d3.scale.ordinal().range(colorScheme);
var divHeight = height;
var divWidth = width;
var svg = d3.select("#bodyChart").append("svg")
.attr("width", width)
.attr("height", height)
.append("g")
.attr("transform", "translate(" + divWidth / 2 + "," + height / 2 + ") rotate(-90 0 0)");
var partition = d3.layout.partition()
.value(function (d) {
return d.size;
});
var tooltip = d3.select("body")
.append("div")
.attr("class", "tooltip")
.style("position", "absolute")
.style("z-index", "10")
.style("opacity", 0);
var arc = d3.svg.arc()
.startAngle(function (d) {
if (d.size != 0)
return Math.max(0, Math.min(2 * Math.PI, x(d.x)));
})
.endAngle(function (d) {
if (d.depth == 0)
return Math.max(0, Math.min(2 * Math.PI, x(d.x + d.dx)));
if (d.depth == 2) {
return Math.max(0, Math.min(2 * Math.PI, x(d.x + d.dx)));
}
if (d.size != 0)
return Math.max(0, Math.min(2 * Math.PI, x(d.x + d.dx)));
})
.innerRadius(function (d) {
if (d.size != 0)
return Math.max(0, y(d.y));
})
.outerRadius(function (d) {
if (d.size != 0)
return Math.max(0, y(d.y + d.dy) * 1.25);
});
var root = dataset;
var g = svg.selectAll("g")
.data(partition.nodes(root))
.enter().append("g");
var path = g.append("path")
.attr("d", arc)
.style("fill", function (d) { return color((d.children ? d : d.parent).name); })
.on("click", click)
.on("mouseover", function (d) {
tooltip.html('<a href= "' + d.url + '"' +
"</a>" + d.name);
//function () {return d.name + "<br>" + d.url; });
return tooltip.transition()
.duration(100)
.style("opacity", 0.9);
})
.on("mousemove", function (d) {
return tooltip
.style("top", (d3.event.pageY - 2) + "px")
.style("left", (d3.event.pageX + 2) + "px");
})
.on("mouseout", function () { return tooltip.style("opacity", 0); });
var text = g.append("text")
.attr("x", function (d) {
return y(d.y);
})
.attr("dx", function (d) {
return d.depth == 0 ? "9" : "9"
}) // margin
.attr("dy", ".35em")
.attr("transform", function (d) {
return "rotate(" + computeTextRotation(d) + ")";
})
.text(function (d) {
return d.size == null ? d.name : d.name;
})
.style("fill", "black");
function computeTextRotation(d) {
var angle = x(d.x + d.dx / 2) - Math.PI / 2;
return angle / Math.PI * 180;
}
function click(d) {
if (d.size !== undefined) {
d.size += 100;
};
text.transition().attr("opacity", 0);
path.transition()
.duration(750)
.attrTween("d", arcTween(d))
.each("end", function (e, i) {
if (e.x >= d.x && e.x < (d.x + d.dx)) {
var arcText = d3.select(this.parentNode).select("text");
arcText.transition().duration(750)
.attr("opacity", 1)
.attr("transform", function () {
return "rotate(" + computeTextRotation(e) + ")"
})
.attr("x", function (d) {
return y(d.y);
});
}
});
}
var insertLinebreaks = function (t, d, width) {
var el = d3.select(t);
var p = d3.select(t.parentNode);
p.append("g")
.attr("x", function (d) {
return y(d.y);
})
.attr("transform", function (d) {
return "rotate(" + computeTextRotation(d) + ")";
})
.append("foreignObject")
.attr('x', -width / 2)
.attr("width", width)
.attr("height", 200)
.append("xhtml:p")
.attr('style', 'word-wrap: break-word; text-align:center;')
.html('d.name');
el.remove();
};
d3.select(self.frameElement).style("height", height + "px");
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);
};
};
}
Thank you in advance.

D3 zoomable sunburst not fully circle

I'm just getting started with d3js and I'm trying to make a zoomable sunburst. I've copied a tutorial but because most of the data given there were very complex I tried to change the data into much simpler ones. Unfortunately after that , the outer svg becomes an incomplete circle. There's a line at the bottom of it. Not sure whether I have to change the data or the outerradius. Thanks in advance.
var flaredata = {
"name": "Root",
"children": [{
"name": "A1",
"children": [{
"name": "B1",
"size": 30
},
{
"name": "B2",
"size": 40
},
{
"name": "B3",
"size": 40
}
]
},
{
"name": "A2",
"children": [{
"name": "B4",
"size": 40
},
{
"name": "B5",
"size": 30
},
{
"name": "B6",
"size": 10
}
]
},
{
"name": "A3",
"children": [{
"name": "B7",
"size": 50
},
{
"name": "B8",
"size": 15
}
]
}
]
}
var width = 500,
height = 500,
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("section").append("svg")
.attr("width", width)
.attr("height", height)
.datum(flaredata) // this line is modified according to provided solution here: https://stackoverflow.com/questions/17019572/d3-sunburst-doesnt-draw-with-inline-json
// data() was replaced with datum() as suggested by Lars Kotthoff
.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("flare.json", function(error, root) {
var g = svg.selectAll("g")
.data(partition.nodes) // removed "root" argument here
.enter().append("g");
var path = g.append("path")
.attr("d", arc)
.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;
}
The circle is drawn completely. The problem might be that your SVG is not big enough to display all of it with the given y-translation of 10.
One of the most easy way might be to change this line of code:
radius = (Math.min(width, height) / 2) - 5; // <-- -5 is just a suggestion.
If you want to display it in the biggest size possible without any borders you can change the following line of code instead which is "the main source" of your problem:
.attr("transform", "translate(" + width / 2 + "," + (height / 2 + 10) + ")"); // + 10 as y translation makes the element appear with a top distance of 260 in this case.
If you remove the +10 it should perfectly fit inside the SVG.

how to update foci dynamically in multi-foci force-layout in d3.js

I have a multi-foci layout and couldn't find a way to dynamically set the foci.
In the code below using a subset of data, I wish to be able to toggle between id-group and familiarity, which would change the chart from 3 clusters of bubbles to 5 clusters of bubbles. Current foci are hard-coded, which prevent toggling from working.
var data = [
{"id": 0, "name": "AngularJS", "familiarity":0,"r": 50 },
{"id": 0, "name": "HTML5", "familiarity":1,"r": 40 },
{"id": 0, "name": "Javascript", "familiarity":2,"r": 30 },
{"id": 1, "name": "Actionscript","familiarity":0, "r": 50 },
{"id": 1, "name": "Flash", "familiarity":4, "r": 32 },
{"id": 2, "name": "Node Webkit", "familiarity":3,"r": 40 },
{"id": 2, "name": "Chrome App", "familiarity":3,"r": 30 },
{"id": 2, "name": "Cordova", "familiarity":0,"r": 45 },
];
var width = window.innerWidth,
height = 450;
var fill = d3.scale.category10();
var nodes = [], labels = [],
foci = [{x: 0, y: 150}, {x: 400, y: 150}, {x: 200, y: 150}];
var svg = d3.select("body").append("svg")
.attr("width", "100%")
.attr("height", height)
//.attr("domflag", '');
var force = d3.layout.force()
.nodes(nodes)
.links([])
.charge(-200)
.gravity(0.1)
.friction(0.8)
.size([width, height])
.on("tick", tick);
var node = svg.selectAll("g");
var counter = 0;
function tick(e) {
var k = .3 * e.alpha;
// Push nodes toward their designated focus.
nodes.forEach(function(o, i) {
o.y += (foci[o.id].y - o.y) * k;
o.x += (foci[o.id].x - o.x) * k;
});
node.attr("transform", function(d) { return "translate(" + d.x + "," + d.y + ")"; });
}
var timer = setInterval(function(){
if (nodes.length > data.length-1) { clearInterval(timer); return;}
var item = data[counter];
nodes.push({id: item.id, r: item.r, name: item.name});
force.start();
node = node.data(nodes);
var n = node.enter().append("g")
.attr("class", "node")
.attr("transform", function(d) { return "translate(" + d.x + "," + d.y + ")"; })
.style('cursor', 'pointer')
.on('mousedown', function() {
var sel = d3.select(this);
sel.moveToFront();
})
.call(force.drag);
n.append("circle")
.attr("r", function(d) { return d.r/2; })
.style("fill", function(d) { return fill(d.id); })
n.append("text")
.text(function(d){
return d.name;
})
.style("font-size", function(d) {
return Math.min(2 * d.r, (2 * d.r - 8) / this.getComputedTextLength() * 16) + "px";
})
.attr("dy", ".35em")
counter++;
}, 100);
d3.selection.prototype.moveToFront = function() {
return this.each(function(){
this.parentNode.appendChild(this);
});
};
function resize() {
width = window.innerWidth;
force.size([width, height]);
force.start();
}
d3.select(window).on('resize', resize);
circle {
stroke: #fff;
}
<script src="//d3js.org/d3.v3.min.js"></script>
How can I dynamically set the coordinates of the foci such that if it's just 3-4 cluster, align it in one row, but if it's 10 cluster, make it a 3-row small multiples?
Thanks.
You most important change here is modifying the tick function to give the option of selecting one set of foci or the other.
First, however, we need to keep track of which foci points are currently being used. All this needs to do is toggle between "family" and "familiarity" or something less intuitive such as true or false if you want. I've used the variable current in the code below.
Now we can add to your existing tick function by adding some sort of check to see what set of foci should be used:
function tick(e) {
var k = .3 * e.alpha;
// nudge nodes to proper foci:
if(current == "family" ) {
nodes.forEach(function(o, i) {
o.y += (familyFoci[o.id].y - o.y) * k;
o.x += (familyFoci[o.id].x - o.x) * k;
});
}
else {
nodes.forEach(function(o, i) {
o.y += (familiarityFoci[o.familiarity].y - o.y) * k;
o.x += (familiarityFoci[o.familiarity].x - o.x) * k;
});
}
node.attr("transform", function(d) { return "translate(" + d.x + "," + d.y + ")"; });
}
I renamed the array foci to familyFoci as both foci could describe either array, I also made sure that your nodes have a familiarity property in the snippets below
This modification allows us to easily specify the property used to set a specific focal point in a set of focal points, and specify which set of focal points we want.
Now we can create a second set of foci:
var familyFoci = [{x: 0, y: 150}, {x: 400, y: 150}, {x: 200, y: 150}];
var familiarityFoci = [{x:0,y:200},{x:100,y:100},{x:200,y:200},{x:300,y:100},{x:400,y:200}];
For the sake of completeness I've added a basic set of buttons that use an onclick function to check to see what the desired set of focal points is.
Here's all that in a quick snippet:
var data = [
{"id": 0, "name": "AngularJS", "familiarity":0,"r": 50 },
{"id": 0, "name": "HTML5", "familiarity":1,"r": 40 },
{"id": 0, "name": "Javascript", "familiarity":2,"r": 30 },
{"id": 1, "name": "Actionscript","familiarity":0, "r": 50 },
{"id": 1, "name": "Flash", "familiarity":4, "r": 32 },
{"id": 2, "name": "Node Webkit", "familiarity":3,"r": 40 },
{"id": 2, "name": "Chrome App", "familiarity":3,"r": 30 },
{"id": 2, "name": "Cordova", "familiarity":0,"r": 45 },
];
var width = window.innerWidth,
height = 450;
var fill = d3.scale.category10();
var nodes = [], labels = [];
// two sets of foci:
var familyFoci = [{x: 0, y: 150}, {x: 400, y: 150}, {x: 200, y: 150}];
var familiarityFoci = [{x:0,y:200},{x:100,y:100},{x:200,y:200},{x:300,y:100},{x:400,y:200}];
var svg = d3.select("body").append("svg")
.attr("width", "100%")
.attr("height", height)
var force = d3.layout.force()
.nodes(nodes)
.links([])
.charge(-200)
.gravity(0.1)
.friction(0.8)
.size([width, height])
.on("tick", tick);
//var node = svg.selectAll("circle");
var node = svg.selectAll("g");
var counter = 0;
//
// Create a basic interface:
//
var current = "family";
var buttons = svg.selectAll(null)
.data(["family","familiarity"])
.enter()
.append("g")
.attr("transform",function(d,i) { return "translate("+(i*120+50)+","+50+")"; })
.on("click", function(d) {
if(d != current) {
current = d;
}
})
.style("cursor","pointer")
buttons.append("rect")
.attr("width",100)
.attr("height",50)
.attr("fill","lightgrey")
buttons.append("text")
.text(function(d) { return d; })
.attr("dy", 30)
.attr("dx", 50)
.style("text-anchor","middle");
function tick(e) {
var k = .3 * e.alpha;
//
// Check to see what foci set we should gravitate to:
//
if(current == "family") {
// Push nodes toward their designated focus.
nodes.forEach(function(o, i) {
o.y += (familyFoci[o.id].y - o.y) * k;
o.x += (familyFoci[o.id].x - o.x) * k;
});
}
else {
nodes.forEach(function(o, i) {
o.y += (familiarityFoci[o.familiarity].y - o.y) * k;
o.x += (familiarityFoci[o.familiarity].x - o.x) * k;
});
}
node.attr("transform", function(d) { return "translate(" + d.x + "," + d.y + ")"; });
}
var timer = setInterval(function(){
if (nodes.length > data.length-1) { clearInterval(timer); return;}
var item = data[counter];
nodes.push({id: item.id, r: item.r, name: item.name, familiarity: item.familiarity});
force.start();
node = node.data(nodes);
var n = node.enter().append("g")
.attr("class", "node")
.attr("transform", function(d) { return "translate(" + d.x + "," + d.y + ")"; })
.style('cursor', 'pointer')
.on('mousedown', function() {
var sel = d3.select(this);
sel.moveToFront();
})
.call(force.drag);
n.append("circle")
.attr("r", function(d) { return d.r/2; })
.style("fill", function(d) { return fill(d.id); })
n.append("text")
.text(function(d){
return d.name;
})
.style("font-size", function(d) {
return Math.min(2 * d.r, (2 * d.r - 8) / this.getComputedTextLength() * 16) + "px";
})
.attr("dy", ".35em")
counter++;
}, 100);
d3.selection.prototype.moveToFront = function() {
return this.each(function(){
this.parentNode.appendChild(this);
});
};
function resize() {
width = window.innerWidth;
force.size([width, height]);
force.start();
}
d3.select(window).on('resize', resize);
circle {
stroke: #fff;
}
<script src="https://d3js.org/d3.v3.min.js"></script>
Click on one option, and if it isn't the currently selected foci, the force changes which foci it is using.
But, there is a problem here, the graph continues to cool down as you shift the foci until it ultimately stops. We can grease the wheels a bit and reset the temperature (alpha) with one more line of code when we click on one of our buttons:
.on("click", function(d) {
if(d != current) {
current = d;
force.alpha(0.228); // reset the alpha
}
})
And here's a demo:
var data = [
{"id": 0, "name": "AngularJS", "familiarity":0,"r": 50 },
{"id": 0, "name": "HTML5", "familiarity":1,"r": 40 },
{"id": 0, "name": "Javascript", "familiarity":2,"r": 30 },
{"id": 1, "name": "Actionscript","familiarity":0, "r": 50 },
{"id": 1, "name": "Flash", "familiarity":4, "r": 32 },
{"id": 2, "name": "Node Webkit", "familiarity":3,"r": 40 },
{"id": 2, "name": "Chrome App", "familiarity":3,"r": 30 },
{"id": 2, "name": "Cordova", "familiarity":0,"r": 45 },
];
var width = window.innerWidth,
height = 450;
var fill = d3.scale.category10();
var nodes = [], labels = [];
// two sets of foci:
var familyFoci = [{x: 0, y: 150}, {x: 400, y: 150}, {x: 200, y: 150}];
var familiarityFoci = [{x:0,y:200},{x:100,y:100},{x:200,y:200},{x:300,y:100},{x:400,y:200}];
var svg = d3.select("body").append("svg")
.attr("width", "100%")
.attr("height", height)
var force = d3.layout.force()
.nodes(nodes)
.links([])
.charge(-200)
.gravity(0.1)
.friction(0.8)
.size([width, height])
.on("tick", tick);
var node = svg.selectAll("g");
var counter = 0;
//
// Create a basic interface:
//
var current = "family";
var buttons = svg.selectAll(null)
.data(["family","familiarity"])
.enter()
.append("g")
.attr("transform",function(d,i) { return "translate("+(i*120+50)+","+50+")"; })
.on("click", function(d) {
if(d != current) {
current = d;
force.alpha(0.228);
}
})
.style("cursor","pointer")
buttons.append("rect")
.attr("width",100)
.attr("height",50)
.attr("fill","lightgrey")
buttons.append("text")
.text(function(d) { return d; })
.attr("dy", 30)
.attr("dx", 50)
.style("text-anchor","middle");
function tick(e) {
var k = .3 * e.alpha;
//
// Check to see what foci set we should gravitate to:
//
if(current == "family") {
// Push nodes toward their designated focus.
nodes.forEach(function(o, i) {
o.y += (familyFoci[o.id].y - o.y) * k;
o.x += (familyFoci[o.id].x - o.x) * k;
});
}
else {
nodes.forEach(function(o, i) {
o.y += (familiarityFoci[o.familiarity].y - o.y) * k;
o.x += (familiarityFoci[o.familiarity].x - o.x) * k;
});
}
node.attr("transform", function(d) { return "translate(" + d.x + "," + d.y + ")"; });
}
var timer = setInterval(function(){
if (nodes.length > data.length-1) { clearInterval(timer); return;}
var item = data[counter];
nodes.push({id: item.id, r: item.r, name: item.name, familiarity: item.familiarity});
force.start();
node = node.data(nodes);
var n = node.enter().append("g")
.attr("class", "node")
.attr("transform", function(d) { return "translate(" + d.x + "," + d.y + ")"; })
.style('cursor', 'pointer')
.on('mousedown', function() {
var sel = d3.select(this);
sel.moveToFront();
})
.call(force.drag);
n.append("circle")
.attr("r", function(d) { return d.r/2; })
.style("fill", function(d) { return fill(d.id); })
n.append("text")
.text(function(d){
return d.name;
})
.style("font-size", function(d) {
return Math.min(2 * d.r, (2 * d.r - 8) / this.getComputedTextLength() * 16) + "px";
})
.attr("dy", ".35em")
counter++;
}, 100);
d3.selection.prototype.moveToFront = function() {
return this.each(function(){
this.parentNode.appendChild(this);
});
};
function resize() {
width = window.innerWidth;
force.size([width, height]);
force.start();
}
d3.select(window).on('resize', resize);
circle {
stroke: #fff;
}
<script src="https://d3js.org/d3.v3.min.js"></script>

d3.js animation of a dougnut pie chart

I am working on an application that will be a hybrid pie chart and bubble chart -
here is a working animation for a simple doughnut pie chart.
http://jsfiddle.net/Qh9X5/8817/
var svg = d3.select("body")
.append("svg")
.append("g")
svg.append("g")
.attr("class", "slices");
svg.append("g")
.attr("class", "labels");
svg.append("g")
.attr("class", "lines");
var width = 560,
height = 450,
radius = Math.min(width, height) / 2;
var pie = d3.layout.pie()
.sort(null)
.value(function(d) {
return d.value;
});
var arc = d3.svg.arc()
.outerRadius(radius * 0.85)
.innerRadius(radius * 0.83);
var outerArc = d3.svg.arc()
.innerRadius(radius * 0.9)
.outerRadius(radius * 0.9);
svg.attr("transform", "translate(" + width / 2 + "," + height / 2 + ")");
var key = function(d){ return d.data.label; };
var color = d3.scale.ordinal()
.domain(["Lorem ipsum", "dolor sit", "amet", "consectetur", "adipisicing"])
.range(["#98abc5", "#8a89a6", "#7b6888", "#6b486b", "#a05d56"]);
function randomData (){
var labels = color.domain();
return labels.map(function(label){
return { label: label, value: Math.random() }
});
}
console.log("randomData()", randomData());
change(randomData());
d3.select(".randomize")
.on("click", function(){
change(randomData());
});
function change(data) {
/* ------- PIE SLICES -------*/
var slice = svg.select(".slices").selectAll("path.slice")
.data(pie(data), key);
slice.enter()
.insert("path")
.style("fill", function(d) { return color(d.data.label); })
.attr("class", "slice");
slice
.transition().duration(1000)
.attrTween("d", function(d) {
this._current = this._current || d;
var interpolate = d3.interpolate(this._current, d);
this._current = interpolate(0);
return function(t) {
return arc(interpolate(t));
};
})
slice.exit()
.remove();
};
http://jsfiddle.net/NYEaX/1487/
This is a static doughnut chart -- where I have calculated the mid-arcs -- but have lost the animation. So with the above sample of code -- where is it possible to obtain the arc.centroid(d)?
var width = 960,
height = 500,
radius = Math.min(width, height) / 2;
var color = d3.scale.ordinal()
.range(["#98abc5", "#8a89a6", "#7b6888", "#6b486b", "#a05d56", "#d0743c", "#ff8c00"]);
var arc = d3.svg.arc()
.outerRadius(radius - 60)
.innerRadius(radius - 70);
var pie = d3.layout.pie()
.sort(null)
.value(function(d) { return d.value; });
var svg = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height)
.append("g")
.attr("transform", "translate(" + width / 2 + "," + height / 2 + ")");
var data = [
{
"group": "<5",
"value": 1000,
"children": [
{
"group": "<5",
"label": "Mel",
"value": 1000,
"totalGroupValue": 1000
}
]
},
{
"group": "5-13",
"value": 1000,
"children": [
{
"group": "5-13",
"label": "Erica",
"value": 1000,
"totalGroupValue": 1000
}
]
},
{
"group": "14-17",
"value": 2000,
"children": [
{
"group": "14-17",
"label": "Jessica",
"value": 1500,
"totalGroupValue": 2000
},
{
"group": "14-17",
"label": "Jill",
"value": 500,
"totalGroupValue": 2000
}
]
},
{
"group": "18-24",
"value": 1300,
"children": [
{
"group": "18-24",
"label": "Jerry",
"value": 500,
"totalGroupValue": 1300
},
{
"group": "18-24",
"label": "Ben",
"value": 500,
"totalGroupValue": 1300
},
{
"group": "18-24",
"label": "Billy",
"value": 300,
"totalGroupValue": 1300
}
]
},
{
"group": "25-44",
"value": 1000,
"children": [
{
"group": "25-44",
"label": "Kelly",
"value": 1000,
"totalGroupValue": 1000
}
]
}
];
var g = svg.selectAll(".arc")
.data(pie(data))
.enter().append("g")
.attr("class", "arc");
g.append("path")
.attr("d", arc)
.style("fill", function(d) {
return color(d.data.group);
});
arc
.outerRadius(radius - 10)
.innerRadius(0);
//create zone regions
var zones = [];
g.append("circle")
.attr("transform", function(d) {
zones[d.data.group] = arc.centroid(d);
return "translate(" + arc.centroid(d) + ")";
})
.attr("r", "1px")
.style("fill", function(d) {
return "black"//color(d.data.group);
});
g.append("g")
.attr("class", function(d,i) {
console.log("d", d)
return "bubble"+i;//color(d.data.group);
})
.attr("transform", function(d) {
zones[d.data.group] = arc.centroid(d);
return "translate(" + arc.centroid(d) + ")";
})
.attr("r", "1px")
.style("fill", function(d) {
return "black"//color(d.data.group);
});
//create zone regions
//custom bubble chart
function makeBubbles(transform, group, radius){
g.append("circle")
.attr("transform", function(d) {
return "translate("+transform+")";
})
.attr("r", radius)
.style("stroke", function(d) {
return "black";//color(group);
})
.style("fill", function(d) {
return color(group);
});
}
function bubbledata(data){
console.log("data", data)
//loop through data -- and MERGE children
var childs = [];
$.each(data, function( index, value ) {
childs.push(value.children);
});
var merged = data;//[].concat.apply([], childs);//flatterns multidimensional array
return $.extend(true, {}, {"children": merged});// return deep clone
}
function setBubbleChart(width, index, data){
//_create bubble
var diameter = width/2;//take half/width
var bubs = svg.select(".bubble"+index).append("g")
.attr("class", "bubs");
bubs.attr("transform", "translate("+-diameter/2+","+-diameter/2+")");
var bubble = d3.layout.pack()
.size([diameter, diameter])
.value(function(d) {
return d.value;
})
.padding(3);
//_create bubble
var data = bubbledata(data);
var nodes = bubble.nodes(data)
.filter(function(d) {
return !d.children;
}); // filter out the outer bubble
var bubbles = bubs.selectAll('circle')
.data(nodes);
bubbles.enter()
.insert("circle")
.attr('transform', function (d) {
return 'translate(' + d.x + ',' + d.y + ')';
})
.attr('r', function (d) {
return d.r;
})
.style("fill", function (d) {
return color(d.group);
});
bubbles = bubbles.transition()
.transition()
.duration(250)
.attr('transform', function(d) {
return 'translate(' + d.x + ',' + d.y + ')';
})
.attr('r', function (d) {
return d.r;
})
.ease('sine');
}
//loop through data and for EACH children array paint dots.
$.each(data, function( index, value ) {
setBubbleChart(100, index, value.children);
});
//custom bubble chart
function type(d) {
d.value = +d.value;
return d;
}
I've plotted the mid-arcs - but they are not being updated on a change of data?
http://jsfiddle.net/Qh9X5/10066/
function change(data) {
var slice = svg.select(".slices").selectAll("path.slice")
.data(pie(data), key);
slice.enter()
.insert("path")
.style("fill", function(d) {
return color(d.data.label);
})
.attr("class", "slice");
slice
.transition().duration(1000)
.attrTween("d", function(d) {
this._current = this._current || d;
var interpolate = d3.interpolate(this._current, d);
this._current = interpolate(0);
return function(t) {
return arc(interpolate(t));
};
})
slice.exit()
.remove();
var placeholders = svg.select(".placeholders").selectAll("circle.placeholder")
.data(pie(data), key);
placeholders.enter()
.insert("circle")
.style("fill", function(d) {
return "white";
//return color(d.data.label);
})
.attr("transform", function(d) {
return "translate(" + arc.centroid(d) + ")";
})
.attr("r", "5")
.attr("class", "placeholder");
placeholders
.transition().duration(1000)
placeholders.exit()
.remove();
};
I've managed to create an arc2 -- and animate the placeholders.
http://jsfiddle.net/Qh9X5/10068/
var svg = d3.select("#pies")
.append("svg")
.append("g")
svg.append("g")
.attr("class", "slices");
svg.append("g")
.attr("class", "placeholders");
svg.append("g")
.attr("class", "labels");
svg.append("g")
.attr("class", "lines");
var width = 560,
height = 450,
radius = Math.min(width, height) / 2;
var pie = d3.layout.pie()
.sort(null)
.value(function(d) {
return d.value;
});
var arc = d3.svg.arc()
.outerRadius(radius * 0.85)
.innerRadius(radius * 0.83);
var arc2 = d3.svg.arc()
.outerRadius(radius - 10)
.innerRadius(0);
var outerArc = d3.svg.arc()
.innerRadius(radius * 0.9)
.outerRadius(radius * 0.9);
svg.attr("transform", "translate(" + width / 2 + "," + height / 2 + ")");
var key = function(d) {
return d.data.label;
};
var color = d3.scale.ordinal()
.domain(["Lorem ipsum", "dolor sit", "amet", "consectetur", "adipisicing"])
.range(["#98abc5", "#8a89a6", "#7b6888", "#6b486b", "#a05d56"]);
function randomData() {
var labels = color.domain();
return labels.map(function(label) {
return {
label: label,
value: Math.random()
}
});
}
console.log("randomData()", randomData());
change(randomData());
d3.select(".randomize")
.on("click", function() {
change(randomData());
});
function change(data) {
var slice = svg.select(".slices").selectAll("path.slice")
.data(pie(data), key);
slice.enter()
.insert("path")
.style("fill", function(d) {
return color(d.data.label);
})
.attr("class", "slice");
slice
.transition().duration(1000)
.attrTween("d", function(d) {
this._current = this._current || d;
var interpolate = d3.interpolate(this._current, d);
this._current = interpolate(0);
return function(t) {
return arc(interpolate(t));
};
})
slice.exit()
.remove();
var placeholders = svg.select(".placeholders").selectAll("circle.placeholder")
.data(pie(data), key);
placeholders.enter()
.insert("circle")
.style("fill", function(d) {
return "white";
})
.attr("r", "5")
.attr("class", "placeholder");
placeholders
.transition().duration(1000)
.attr("transform", function(d) {
console.log("arc.centroid(d)", arc2.centroid(d))
return "translate(" + arc2.centroid(d) + ")";
})
placeholders.exit()
.remove();
};
http://jsfiddle.net/Qh9X5/10075/
I have managed to merge the pie chart with the bubble chart -- if the data sets change - the animations should be stable.
var svg = d3.select("#pies")
.append("svg")
.append("g")
svg.append("g")
.attr("class", "slices");
svg.append("g")
.attr("class", "placeholders");
var width = 560,
height = 450,
radius = Math.min(width, height) / 2;
var pie = d3.layout.pie()
.sort(null)
.value(function(d) {
return d.value;
});
var arc = d3.svg.arc()
.outerRadius(radius * 0.85)
.innerRadius(radius * 0.83);
var arc2 = d3.svg.arc()
.outerRadius(radius - 10)
.innerRadius(0);
var outerArc = d3.svg.arc()
.innerRadius(radius * 0.9)
.outerRadius(radius * 0.9);
svg.attr("transform", "translate(" + width / 2 + "," + height / 2 + ")");
function colores_google(n) {
var colores_g = ["#f7b363", "#448875", "#c12f39", "#2b2d39", "#f8dd2f"];
return colores_g[n % colores_g.length];
}
var data = [{
"group": "<5",
"value": 1000,
"children": [{
"group": "<5",
"label": "Mel",
"value": 1000,
"totalGroupValue": 1000
}]
}, {
"group": "5-13",
"value": 1000,
"children": [{
"group": "5-13",
"label": "Erica",
"value": 1000,
"totalGroupValue": 1000
}]
}, {
"group": "14-17",
"value": 2000,
"children": [{
"group": "14-17",
"label": "Jessica",
"value": 1500,
"totalGroupValue": 2000
}, {
"group": "14-17",
"label": "Jill",
"value": 500,
"totalGroupValue": 2000
}]
}, {
"group": "18-24",
"value": 1300,
"children": [{
"group": "18-24",
"label": "Jerry",
"value": 500,
"totalGroupValue": 1300
}, {
"group": "18-24",
"label": "Ben",
"value": 500,
"totalGroupValue": 1300
}, {
"group": "18-24",
"label": "Billy",
"value": 300,
"totalGroupValue": 1300
}]
}, {
"group": "25-44",
"value": 1000,
"children": [{
"group": "25-44",
"label": "Kelly",
"value": 1000,
"totalGroupValue": 1000
}]
}];
$.each(data, function(index, value) {
value["groupid"] = index;
$.each(value.children, function(i, v) {
v["groupid"] = index;
});
});
//slices
var slice = svg.select(".slices").selectAll("path.slice")
.data(pie(data));
slice.enter()
.insert("path")
.style("fill", function(d) {
return colores_google(d.data.groupid);
})
.attr("class", "slice");
slice
.transition().duration(1000)
.attrTween("d", function(d) {
this._current = this._current || d;
var interpolate = d3.interpolate(this._current, d);
this._current = interpolate(0);
return function(t) {
return arc(interpolate(t));
};
})
slice.exit()
.remove();
//slices
//placeholder bubbles
var placeholders = svg.select(".placeholders").selectAll("g.placeholder")
.data(pie(data));
placeholders.enter()
.insert("g")
.attr("class", function(d, i) {
return "placeholder place" + i;
});
placeholders
.transition().duration(1000)
.attr("transform", function(d) {
return "translate(" + arc2.centroid(d) + ")";
})
placeholders.exit()
.remove();
//placeholder bubbles
//bubbles
function bubbledata(data) {
//loop through data -- and MERGE children
var childs = [];
$.each(data, function(index, value) {
childs.push(value.children);
});
return $.extend(true, {}, {
"children": data
}); // return deep clone
}
function setBubbleChart(width, index, data) {
//_create bubble
var diameter = width / 2; //take half/width
var bubs = svg.select(".place" + index).append("g")
.attr("class", "bubs");
bubs.attr("transform", "translate(" + -diameter / 2 + "," + -diameter / 2 + ")");
var bubble = d3.layout.pack()
.size([diameter, diameter])
.value(function(d) {
return d.value;
})
.padding(3);
//_create bubble
var data = bubbledata(data);
var nodes = bubble.nodes(data)
.filter(function(d) {
return !d.children;
}); // filter out the outer bubble
var bubbles = bubs.selectAll('circle')
.data(nodes);
bubbles.enter()
.insert("circle")
.attr('transform', function(d) {
return 'translate(' + d.x + ',' + d.y + ')';
})
.attr('r', function(d) {
return d.r;
})
.style("fill", function(d) {
return colores_google(d.groupid);
});
bubbles = bubbles.transition()
.transition()
.duration(250)
.attr('transform', function(d) {
return 'translate(' + d.x + ',' + d.y + ')';
})
.attr('r', function(d) {
return d.r;
})
.ease('sine');
}
//loop through data and for EACH children array paint dots.
$.each(data, function(index, value) {
setBubbleChart(100, index, value.children);
});
//custom bubble chart

D3: Rotating labels in the third and fourth quad in sunburst example

I am using D3 and javascript for the first time to draw sunburst, following examples at http://bl.ocks.org/mbostock/ and http://bl.ocks.org/mbostock/4063423. The labels in the thirsd and fourth quads are upside down. I found coffee wheel example here http://www.jasondavies.com/coffee-wheel/ and tried to incorporate in my example but was unsuccessful in rotating the labels. I did found a similar post here How to properly rotate text labels in a D3 sunburst diagram, but was unable to solve the issue as one of the link is not working.
<!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 = 1600,
height = 1300,
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("ex.json", function(error, root) {
var g = svg.selectAll("g")
.data(partition.nodes(root))
.enter().append("g");
var path = g.append("path")
.attr("d", arc)
.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>
ex.json
[{
"name": "aaa",
"size": 5000,
"children":
[
{
"name": "aaab",
"size": 2952,
"children": [
{"name": "xxx", "size": 45},
{"name": "xxy", "size": 29},
{"name": "xxz", "size": 28},
{"name": "xxa", "size": 4}
]
},
{
"name": "aaac",
"size": 251,
"children": [
{
"name": "ddd",
"size": 7,
"children": [
{"name": "ppp", "size": 4},
{"name": "qqq", "size": 2}
]
},
{"name": "xxt", "size": 4},
{"name": "xxu", "size": 1},
{"name": "xxv", "size": 1}
]
},
{"name": "aaad","size": 222}
]
}][1]
Change your computeTextRotation function to the following:
function computeTextRotation(d) {
var ang = (x(d.x + d.dx / 2) - Math.PI / 2) / Math.PI * 180;
return (ang > 90) ? 180 + ang : ang;
}
And set the point of rotation to be the position of centroid of the respective arc:
var text = g.append("text")
.attr("transform", function(d) { return "translate(" + arc.centroid(d) + ")rotate(" + computeTextRotation(d) + ")"; })
.attr('text-anchor', function (d) { return computeTextRotation(d) > 180 ? "end" : "start"; })
.attr("dx", "6") // margin
.attr("dy", ".35em") // vertical-align
.text(function(d) { return d.name; });
Example: http://jsfiddle.net/Hm49x/
With your data: http://plnkr.co/edit/tct95toQ2IjyUkQJQPB9?p=preview

Categories

Resources