D3 zoomable sunburst not fully circle - javascript

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.

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.

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>

Capturing/Saving the current state of d3.js Sunburst Chart

I am a newbie to d3.js . I am working in d3.js zoomable sunburst chart http://bl.ocks.org/mbostock/4348373. When the user zoom into a particular arc and I need to capture this state of the sunburst diagram . When the user comes back to the sunburst diagram or load the graph again he should see the state where he left.
Note : I dont want to serialise the svg elements to show the state of the sunburst. If i serialise it then the chart will be static and user cant click on the sunburst and traverse to other arcs.
Proposed Solution :
one solution came to my mind is simulate mouse clicks on the sunburst nodes till the last node user looks into.
I am not able to devise an algorithm for this .
Please guide me whether any other solution is possible ..
The approach you said is easy to implement. I have made a small sample for it.
Click the Start Save button to start saving the state of Sunburst. Click Stop Save button after performing a few zooming actions. You can make any further changes to the chart. Now, clicking Load button will show you the saved state of the chart.
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.sqrt()
.range([0, radius]);
var color = d3.scale.category10();
var svg = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height)
.append("g")
.attr("transform", "translate(" + width / 2 + "," + (height / 2 + 10) + ") rotate(-90 0 0)");
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 = getData();
var savedData = partition.nodes(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("x", function(d) {
return y(d.y);
})
.attr("dx", "6") // margin
.attr("dy", ".35em") // vertical-align
.attr("transform", function(d) {
return "rotate(" + computeTextRotation(d) + ")";
})
.text(function(d) {
return d.name;
})
.style("fill", "white");
function computeTextRotation(d) {
var angle = x(d.x + d.dx / 2) - Math.PI / 2;
return angle / Math.PI * 180;
}
var saveData = false;
var savedData = [];
d3.select("#saveBtn").on("click", function() {
if (d3.select(this).attr("value") == "Start Save") {
savedData = [];
d3.select(this).attr("value", "Stop Save");
saveData = true;
} else {
d3.select(this).attr("value", "Start Save");
saveData = false;
}
});
d3.select("#loadBtn").on("click", function() {
var root = g.filter(function(d) {
return d.name == "Root"
}).data();
click(root[0]);
savedData.forEach(function(val) {
var node = g.filter(function(d, i) {
return i == val
}).data();
click(node[0]);
});
});
function click(d, i) {
if (saveData) {
if(d.name=="Root"){
savedData = [];
} else{
savedData.push(i);
}
}
// fade out all text elements
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) {
// 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);
});
}
});
}
// Word wrap!
var insertLinebreaks = function(t, d, width) {
alert(0)
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);
alert(1)
el.remove();
alert(2)
};
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 getData() {
return {
"name": "Root",
"children": [{
"name": "A1",
"children": [{
"name": "B1",
"size": 30
}, {
"name": "B2",
"size": 40
}, {
"name": "B3",
"children": [{
"name": "C1",
"size": 10
}, {
"name": "C2",
"size": 15
}]
}]
}, {
"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
}
]
}]
}
};
path {
stroke: #fff;
fill-rule: evenodd;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>
<div id="main"></div>
<input type="button" value="Start Save" id="saveBtn"/>
<input type="button" value="Load" id="loadBtn"/>
I don't know where do you plan to save the state of the sunburst. I would suggest that Localstorage would be a nice option.
Hope this helps.

d3js circle turns black - String is not a function

I had an error with my d3 chart that sometimes at loading it give me this error "used JS COnsole":
uncaught TypeError: string is not a function
here is the code
var margin = { top: 290, right: 360, bottom: 290, left: 360 },
radius = Math.min(margin.top, margin.right, margin.bottom, margin.left) - 10;
var x = d3.scale.linear()
.range([0, 2 * Math.PI]);
var y = d3.scale.linear()
.range([0, radius]);
var color = d3.scale.ordinal()
.domain(["foo", "bdo", "baz"])
.range(colorbrewer.Spectral[8]);
var svg = d3.select("#chart2").append("svg")
.attr("width", '100%')
.attr("height", '100%')
.attr('viewBox', '0 0 ' + Math.min(margin.left + margin.right, margin.top + margin.bottom) + ' ' + Math.min(margin.left + margin.right, margin.top + margin.bottom))
.attr('preserveAspectRatio', 'xMinYMin')
.append("g")
.attr("id", "container")
.attr("transform", "translate(" + Math.min(margin.left + margin.right, margin.top + margin.bottom) / 2 + "," + Math.min(margin.left + margin.right, margin.top + margin.bottom) / 2 + ")");
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))); })
// lw 3'erna al y(d.y) we 2smnaha 3la 2 he3'er al radius le al first level
.innerRadius(function (d) { return Math.max(0, y(d.y)); })
.outerRadius(function (d) { return Math.max(0, y(d.y + d.dy)); });
d3.json("#Html.Raw( Url.Action("GetD3_JSON", "Account", new { type=ViewBag.type, year=ViewBag.CurrentYear, month=ViewBag.CurrentMonth, day=ViewBag.CurrentDay, project=ViewBag.ProjectGUID, code = (ViewContext.RouteData.Values["Action"].ToString() == "Income") ? 1 : (ViewContext.RouteData.Values["Action"].ToString() == "Expenses") ? -1 : 0 }) )", function (error, root) {
// Compute the initial layout on the entire tree to sum sizes.
// Also compute the full name and fill color for each node,
// and stash the children so they can be restored as we descend.
partition
.value(function (d) { return d.size; })
.nodes(root)
.forEach(function (d) {
d._children = d.children;
d.sum = d.value;
});
// Now redefine the value function to use the previously-computed sum.
partition
.children(function (d, depth) { return depth < 2 ? d._children : null; })
.value(function (d) { return d.sum; });
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.show([d3.event.clientX, d3.event.clientY], '<div>' + d.name + '</div><div>' + d.value + '</div>')
})
.on('mouseout', function () {
tooltip.cleanup()
})
.each(stash);
// Define the legeneds
var legend = d3.select("#legend").append("svg")
.attr("class", "legend")
.attr("width", radius)
.attr("height", radius)
.selectAll("g")
.data(partition.nodes(root))
.enter().append("g")
.attr("transform", function (d, i) { return "translate(0," + i * 20 + ")"; });
legend.append("rect")
.attr("width", 18)
.attr("height", 18)
.style("fill", function (d) { return color((d.children ? d : d.parent).name); })
.on("click", click);
legend.append("text")
.attr("x", 24)
.attr("y", 9)
.attr("dy", ".35em")
.text(function (d) { return d.name; });
// Define Labels on the arcs
var text = g.append("text")
.attr("dy", ".35em") // vertical-align
.attr("transform", function (d) { return "rotate(" + computeTextRotation(d) + ")"; })
.attr("x", function (d) { return y(d.y); })
.attr("dx", "6") // margin
.attr("display", 'block')
.text(function (d) {
return d.name;
})
.on("click", click);
var center = svg.append("circle")
.attr("r", radius / 3)
.style("fill", "white")
.on("click", click);
center.append("title")
center.datum(root);
function click(d) {
// fade out all text elements
text.transition().attr("opacity", 0);
path.transition()
.duration(500)
.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(250)
.attr("opacity", 1)
.attr("transform", function () { return "rotate(" + computeTextRotation(e) + ")" })
.attr("x", function (d) { return y(d.y); });
}
});
}
});
d3.select(self.frameElement).style("height", margin.top + margin.bottom + "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 * 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;
}
// Stash the old values for transition.
function stash(d) {
d.x0 = d.x;
d.dx0 = d.dx;
}
/*****
* A no frills tooltip implementation.
*****/
(function () {
var tooltip = window.tooltip = {}
tooltip.show = function (pos, content, gravity, dist, parentContainer, classes) {
var container = d3.select('body').selectAll('.tooltip').data([1])
container.enter().append('div').attr('class', 'tooltip ' + (classes ? classes : 'xy-tooltip'))
container.html(content)
gravity = gravity || 'n'
dist = dist || 20
var body = document.getElementsByTagName('body')[0]
var height = parseInt(container[0][0].offsetHeight)
, width = parseInt(container[0][0].offsetWidth)
, windowWidth = window.innerWidth
, windowHeight = window.innerHeight
, scrollTop = body.scrollTop
, scrollLeft = body.scrollLeft
, left = 0
, top = 0
switch (gravity) {
case 'e':
left = pos[0] - width - dist
top = pos[1] - (height / 2)
if (left < scrollLeft) left = pos[0] + dist
if (top < scrollTop) top = scrollTop + 5
if (top + height > scrollTop + windowHeight) top = scrollTop - height - 5
break
case 'w':
left = pos[0] + dist
top = pos[1] - (height / 2)
if (left + width > windowWidth) left = pos[0] - width - dist
if (top < scrollTop) top = scrollTop + 5
if (top + height > scrollTop + windowHeight) top = scrollTop - height - 5
break
case 's':
left = pos[0] - (width / 2)
top = pos[1] + dist
if (left < scrollLeft) left = scrollLeft + 5
if (left + width > windowWidth) left = windowWidth - width - 5
if (top + height > scrollTop + windowHeight) top = pos[1] - height - dist
break
case 'n':
left = pos[0] - (width / 2)
top = pos[1] - height - dist
if (left < scrollLeft) left = scrollLeft + 5
if (left + width > windowWidth) left = windowWidth - width - 5
if (scrollTop > top) top = pos[1] + 20
break
}
container.style('left', left + 'px')
container.style('top', top + 'px')
return container
}
tooltip.cleanup = function () {
// Find the tooltips, mark them for removal by this class (so other tooltip functions won't find it)
var tooltips = d3.selectAll('.tooltip').attr('class', 'tooltip-pending-removal').transition().duration(250).style('opacity', 0).remove()
}
})()
the error with this line
.style("fill", function (d) { return color((d.children ? d : d.parent).name); })
but i fixed it by fixing the structure of the json file "ignore the arabic fonts"
{
"children": [
{
"name": "المصاريف",
"children": [
{
"name": "Expense_1",
"children": [
{
"name": "<غير مصنف>",
"size": 500.0000
},
{
"name": "ExpSubCat1_1",
"size": 5772.0000
},
{
"name": "ExpSubCat1_2",
"size": 5471.0000
}
]
},
{
"name": "ffffff",
"children": [
{
"name": "<غير مصنف>",
"size": 19.0000
}
]
}
]
},
{
"name": "الدخل",
"children": [
{
"name": "ffff",
"children": [
{
"name": "<غير مصنف>",
"size": 10334.0000
}
]
},
{
"name": "Income_1",
"children": [
{
"name": "IncSubCat1_1",
"size": 6371.0000
},
{
"name": "<غير مصنف>",
"size": 11211.0000
},
{
"name": "IncSubCat1_2",
"size": 7160.0000
}
]
},
{
"name": "Income_2",
"children": [
{
"name": "<غير مصنف>",
"size": 39.0000
},
{
"name": "IncSubCat2_2",
"size": 7256.0000
},
{
"name": "IncSubCat2_1",
"size": 583090.0000
}
]
}
]
},
{
"name": "الربح",
"size": 601700.0000
}
]
}

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