How to rotate text around its centroid (vertically flip) in SVG / D3? - javascript

I have text objects labeling points that are evenly spaced around a circle. Thanks to this article, I am able to correctly position both the points and text objects but the labels on the left hemisphere of the circle need to be rotated 180 degrees (flipped vertically) to be more legible.
I thought I could rotate the text object about its own origin before rotating it to the appropriate position around the circle but was unable to determine how to locate the center position of each text object.
How can I rotate text objects about their center for those on the left hemisphere of the circle (angle>= PI/2 && angle<=PI*1.5)? Or is there a better technique to use?
<style type="text/css">
* {
font-family: "Helvetica Neue",Helvetica,Arial,sans-serif;
font-size: 13px;
}
circle {
fill: steelblue;
fill-opacity: .8;
}
circle:hover {
fill: orange;
fill-opacity: .8;
}
</style>
<div id="canvas"></div>
<script type="text/javascript" src="https://ajax.googleapis.com/ajax/libs/jquery/1.7.2/jquery.min.js"></script>
<script type="text/javascript" src="http://mbostock.github.com/d3/d3.v2.min.js"></script>
<script type="text/javascript">
(function () {
var paddding = 250;
var createNodes = function () {
var nodeData = [
{ id: 0, label: 'AAA' },
{ id: 1, label: 'BBB' },
{ id: 2, label: 'CCC' },
{ id: 3, label: 'DDD' },
{ id: 4, label: 'EEE' },
{ id: 5, label: 'FFF' },
{ id: 6, label: 'GGG' },
{ id: 7, label: 'HHH' }
];
var radius = 100;
var nodes = [],
width = (radius * 2) + paddding,
height = (radius * 2) + paddding,
angle,
x,
y,
i;
var numNodes = nodeData.length;
for (i = 0; i < numNodes; i++) {
angle = (i / (numNodes / 2)) * Math.PI;
x = (radius * Math.cos(angle)) + (width / 2);
y = (radius * Math.sin(angle)) + (width / 2);
nodes.push({ 'id': i, 'x': x, 'y': y, 'label': nodeData[i].label, 'angle': angle });
}
return nodes;
}
var createSvg = function (radius, callback) {
d3.selectAll('svg').remove();
var svg = d3.select('#canvas').append('svg:svg')
.attr('width', (radius * 2) + paddding)
.attr('height', (radius * 2) + paddding);
callback(svg);
}
var createElements = function (svg, nodes, elementRadius) {
element = svg.selectAll('circle')
.data(nodes)
.enter().append('svg:circle')
.attr('r', elementRadius)
.attr('cx', function (d, i) { return d.x; })
.attr('cy', function (d, i) { return d.y; });
element = svg.selectAll('text')
.data(nodes)
.enter().append('svg:text')
.text(function (d, i) { return d.label + " - " + d.angle.toFixed(2) + ", " + (d.angle*180/Math.PI); })
.attr('x', function (d, i) { return nodes[0].x + 15; }) // add 15 for spacing off point
.attr('y', function (d, i) { return nodes[0].y; })
.attr("dy", ".35em")
.style("alignment-baseline","middle")
.style("text-anchor", "start")
.attr("transform", function(d,i) {
return "rotate(" + (d.angle * 180) / Math.PI + ", 225, 225)";})
;
}
var draw = function () {
var radius = 100;
var nodes = createNodes();
createSvg(radius, function (svg) {
createElements(svg, nodes, 10);
});
}
$(document).ready(function () {
draw();
});
})();
</script>

If you want to reverse the labels for those on the left side of the circle. You can achieve different ways. One way is by modifying three attributes of the text as you append it:
.attr('x', function (d, i) { return nodes[0].x + 15; })
.style("text-anchor", "start")
.attr("transform", function(d,i) {
return "rotate(" + (d.angle * 180) / Math.PI + ", 225, 225)"
})
If you modify only some of these, you might not get the results you are looking for.
Modification of text-end
This is needed as your text will start away from the point you are defining, and as the text may have variable length, defining a start point will be more complex than necessary. For points you need to flip, you'll need to use:
.style("text-anchor", "end")
Modification of the transform and x
The text needs to be rotated 180 degrees so that it is right way up; however, if you modify this function to add 180 degrees to any text, then the text will appear on the wrong side of the display. So, you'll need to set x to a new value too, so that it appears on the correct side of the display:
.attr('x', function (d, i) { return nodes[0].x - 215; }) // radius * 2, add 15 for spacing off point
.attr("transform", function(d,i) {
return "rotate(" + ((d.angle * 180) / Math.PI - 180) + ", 225, 225)"
})
All together, that looks like:
(function () {
var paddding = 250;
var createNodes = function () {
var nodeData = [
{ id: 0, label: 'AAA' },
{ id: 1, label: 'BBB' },
{ id: 2, label: 'CCC' },
{ id: 3, label: 'DDD' },
{ id: 4, label: 'EEE' },
{ id: 5, label: 'FFF' },
{ id: 6, label: 'GGG' },
{ id: 7, label: 'HHH' }
];
var radius = 100;
var nodes = [],
width = (radius * 2) + paddding,
height = (radius * 2) + paddding,
angle,
x,
y,
i;
var numNodes = nodeData.length;
for (i = 0; i < numNodes; i++) {
angle = (i / (numNodes / 2)) * Math.PI;
x = (radius * Math.cos(angle)) + (width / 2);
y = (radius * Math.sin(angle)) + (width / 2);
nodes.push({ 'id': i, 'x': x, 'y': y, 'label': nodeData[i].label, 'angle': angle });
}
return nodes;
}
var createSvg = function (radius, callback) {
d3.selectAll('svg').remove();
var svg = d3.select('#canvas').append('svg:svg')
.attr('width', (radius * 2) + paddding)
.attr('height', (radius * 2) + paddding);
callback(svg);
}
var createElements = function (svg, nodes, elementRadius) {
element = svg.selectAll('circle')
.data(nodes)
.enter().append('svg:circle')
.attr('r', elementRadius)
.attr('cx', function (d, i) { return d.x; })
.attr('cy', function (d, i) { return d.y; });
element = svg.selectAll('text')
.data(nodes)
.enter().append('svg:text')
.text(function (d, i) { return d.label + " - " + d.angle.toFixed(2) + ", " + (d.angle*180/Math.PI); })
.attr('x', function (d, i) { return nodes[0].x - 215; }) // radius * 2, add 15 for spacing off point
.attr('y', function (d, i) { return nodes[0].y; })
.attr("dy", ".35em")
.style("alignment-baseline","middle")
.style("text-anchor", "end")
.attr("transform", function(d,i) {
return "rotate(" + ((d.angle * 180) / Math.PI - 180) + ", 225, 225)";})
;
}
var draw = function () {
var radius = 100;
var nodes = createNodes();
createSvg(radius, function (svg) {
createElements(svg, nodes, 10);
});
}
$(document).ready(function () {
draw();
});
})();
* {
font-family: "Helvetica Neue",Helvetica,Arial,sans-serif;
font-size: 13px;
}
circle {
fill: steelblue;
fill-opacity: .8;
}
circle:hover {
fill: orange;
fill-opacity: .8;
}
<script type="text/javascript" src="https://ajax.googleapis.com/ajax/libs/jquery/1.7.2/jquery.min.js"></script>
<script type="text/javascript" src="http://mbostock.github.com/d3/d3.v2.min.js"></script>
<div id="canvas"></div>
However, now the labels on the right are upside down. All that is left is to determine is whether a label falls on the right half or the left half and assign the appropriate attributes based on this.
Zero degrees points to the right, it is not the top of the diagram. Therefore, you need to ascertain if d.angle is less than 90 degrees (bottom right) or more than 270 degrees (top right), if so, your original code can be applied. If not, then you need to flip the label using the above code:
(function () {
var paddding = 250;
var createNodes = function () {
var nodeData = [
{ id: 0, label: 'AAA' },
{ id: 1, label: 'BBB' },
{ id: 2, label: 'CCC' },
{ id: 3, label: 'DDD' },
{ id: 4, label: 'EEE' },
{ id: 5, label: 'FFF' },
{ id: 6, label: 'GGG' },
{ id: 7, label: 'HHH' }
];
var radius = 100;
var nodes = [],
width = (radius * 2) + paddding,
height = (radius * 2) + paddding,
angle,
x,
y,
i;
var numNodes = nodeData.length;
for (i = 0; i < numNodes; i++) {
angle = (i / (numNodes / 2)) * Math.PI;
x = (radius * Math.cos(angle)) + (width / 2);
y = (radius * Math.sin(angle)) + (width / 2);
nodes.push({ 'id': i, 'x': x, 'y': y, 'label': nodeData[i].label, 'angle': angle });
}
return nodes;
}
var createSvg = function (radius, callback) {
d3.selectAll('svg').remove();
var svg = d3.select('#canvas').append('svg:svg')
.attr('width', (radius * 2) + paddding)
.attr('height', (radius * 2) + paddding);
callback(svg);
}
var createElements = function (svg, nodes, elementRadius) {
element = svg.selectAll('circle')
.data(nodes)
.enter().append('svg:circle')
.attr('r', elementRadius)
.attr('cx', function (d, i) { return d.x; })
.attr('cy', function (d, i) { return d.y; });
element = svg.selectAll('text')
.data(nodes)
.enter().append('svg:text')
.text(function (d, i) { return d.label + " - " + d.angle.toFixed(2) + ", " + (d.angle*180/Math.PI); })
.attr('x', function (d, i) {
if (d.angle > Math.PI/2 && d.angle < 1.5 * Math.PI) {
return nodes[0].x - 215 }
else {
return nodes[0].x + 15;
}
})
.attr('y', function (d, i) { return nodes[0].y; })
.attr("dy", ".35em")
.style("alignment-baseline","middle")
.style("text-anchor", function(d) {
if (d.angle > Math.PI/2 && d.angle < 1.5 * Math.PI) {
return "end"
}
else {
return "start";
}
})
.attr("transform", function(d,i) {
if (d.angle > Math.PI/2 && d.angle < 1.5 * Math.PI) {
return "rotate(" + ((d.angle * 180) / Math.PI - 180) + ", 225, 225)";
}
else {
return "rotate(" + ((d.angle * 180) / Math.PI) + ", 225, 225)"
}
})
;
}
var draw = function () {
var radius = 100;
var nodes = createNodes();
createSvg(radius, function (svg) {
createElements(svg, nodes, 10);
});
}
$(document).ready(function () {
draw();
});
})();
* {
font-family: "Helvetica Neue",Helvetica,Arial,sans-serif;
font-size: 13px;
}
circle {
fill: steelblue;
fill-opacity: .8;
}
circle:hover {
fill: orange;
fill-opacity: .8;
}
<script type="text/javascript" src="https://ajax.googleapis.com/ajax/libs/jquery/1.7.2/jquery.min.js"></script>
<script type="text/javascript" src="http://mbostock.github.com/d3/d3.v2.min.js"></script>
<div id="canvas"></div>

Related

Using d3-3d with pan & zoom while retaining rotation

I am using the d3-3d plugin to graph 3d bar charts, but I'd like to add the pan & zoom functionality while keeping the rotation. Just adding in d3.zoom() seems to conflict with the d3.drag() behavior - it appears to be random which one takes precedence and adds a lot of "jitter".
var origin = [100, 85], scale = 5, j = 10, cubesData = [];
var alpha = 0, beta = 0, startAngle = Math.PI/6;
var svg = d3.select('svg')
.call(d3.drag()
.on('drag', dragged)
.on('start', dragStart)
.on('end', dragEnd))
.append('g');
var color = d3.scaleOrdinal(d3.schemeCategory20);
var cubesGroup = svg.append('g').attr('class', 'cubes');
var mx, my, mouseX, mouseY;
var cubes3D = d3._3d()
.shape('CUBE')
.x(function(d){ return d.x; })
.y(function(d){ return d.y; })
.z(function(d){ return d.z; })
.rotateY( startAngle)
.rotateX(-startAngle)
.origin(origin)
.scale(scale);
var zoom = d3.zoom()
.scaleExtent([1, 40])
.on("zoom", zoomed);
cubesGroup.call(zoom);
function zoomed() {
cubesGroup.attr("transform", d3.event.transform);
}
function processData(data, tt){
/* --------- CUBES ---------*/
var cubes = cubesGroup.selectAll('g.cube')
.data(data, function(d){ return d.id });
var ce = cubes
.enter()
.append('g')
.attr('class', 'cube')
.attr('fill', function(d){ return color(d.id); })
.attr('stroke', function(d){
return d3.color(color(d.id)).darker(2);
})
.merge(cubes)
.sort(cubes3D.sort);
cubes.exit().remove();
/* --------- FACES ---------*/
var faces = cubes.merge(ce)
.selectAll('path.face')
.data(function(d){ return d.faces; },
function(d){ return d.face; }
);
faces.enter()
.append('path')
.attr('class', 'face')
.attr('fill-opacity', 0.95)
.classed('_3d', true)
.merge(faces)
.transition().duration(tt)
.attr('d', cubes3D.draw);
faces.exit().remove();
/* --------- TEXT ---------*/
var texts = cubes.merge(ce)
.selectAll('text.text').data(function(d){
var _t = d.faces.filter(function(d){
return d.face === 'top';
});
return [{height: d.height, centroid: _t[0].centroid}];
});
texts.enter()
.append('text')
.attr('class', 'text')
.attr('dy', '-.7em')
.attr('text-anchor', 'middle')
.attr('font-family', 'sans-serif')
.attr('font-weight', 'bolder')
.attr('x', function(d){
return origin[0] + scale * d.centroid.x
})
.attr('y', function(d){
return origin[1] + scale * d.centroid.y
})
.classed('_3d', true)
.merge(texts)
.transition().duration(tt)
.attr('fill', 'black')
.attr('stroke', 'none')
.attr('x', function(d){
return origin[0] + scale * d.centroid.x
})
.attr('y', function(d){
return origin[1] + scale * d.centroid.y
})
.tween('text', function(d){
var that = d3.select(this);
var i = d3.interpolateNumber(+that.text(), Math.abs(d.height));
return function(t){
that.text(i(t).toFixed(1));
};
});
texts.exit().remove();
/* --------- SORT TEXT & FACES ---------*/
ce.selectAll('._3d').sort(d3._3d().sort);
}
function init(){
cubesData = [];
var cnt = 0;
for(var z = -j/2; z <= j/2; z = z + 5){
for(var x = -j; x <= j; x = x + 5){
var h = d3.randomUniform(-2, -7)();
var _cube = makeCube(h, x, z);
_cube.id = 'cube_' + cnt++;
_cube.height = h;
cubesData.push(_cube);
}
}
processData(cubes3D(cubesData), 1000);
}
function dragStart(){
mx = d3.event.x;
my = d3.event.y;
}
function dragged(){
mouseX = mouseX || 0;
mouseY = mouseY || 0;
beta = (d3.event.x - mx + mouseX) * Math.PI / 230 ;
alpha = (d3.event.y - my + mouseY) * Math.PI / 230 * (-1);
processData(cubes3D.rotateY(beta + startAngle)
.rotateX(alpha - startAngle)(cubesData), 0);
}
function dragEnd(){
mouseX = d3.event.x - mx + mouseX;
mouseY = d3.event.y - my + mouseY;
}
function makeCube(h, x, z){
return [
{x: x - 1, y: h, z: z + 1}, // FRONT TOP LEFT
{x: x - 1, y: 0, z: z + 1}, // FRONT BOTTOM LEFT
{x: x + 1, y: 0, z: z + 1}, // FRONT BOTTOM RIGHT
{x: x + 1, y: h, z: z + 1}, // FRONT TOP RIGHT
{x: x - 1, y: h, z: z - 1}, // BACK TOP LEFT
{x: x - 1, y: 0, z: z - 1}, // BACK BOTTOM LEFT
{x: x + 1, y: 0, z: z - 1}, // BACK BOTTOM RIGHT
{x: x + 1, y: h, z: z - 1}, // BACK TOP RIGHT
];
}
d3.selectAll('button').on('click', init);
init();
button {
position: absolute;
right: 10px;
top: 10px;
}
<!DOCTYPE html>
<meta charset="utf-8">
<script src="https://d3js.org/d3.v4.min.js"></script>
<script src="https://unpkg.com/d3-3d/build/d3-3d.min.js"></script>
<body>
<svg width="200" height="175"></svg>
</body>
I'd like to mimic the behavior from vis.js.
(1) Ctrl+drag would translate the origin (two finger drag on mobile)
(2) drag would rotate (one finger drag on mobile)
(3) zoom would scale (two finger pinch on mobile)
How do I stop the propagation and only handle these events specifically?
Edit: It appears that the bar chart example has a scale() and origin() that can be set - but I would prefer to work with transforms for speed and efficiency of the update (as opposed to re-drawing).
You can get the type of event using d3.event.sourceEvent. In the code you shared, the dragging anywhere in the white space will rotate and dragging on the bars will move.
With d3.event.sourceEvent you can check whether the ctrl key is pressed and move/rotate accordingly. You don't even need the drag function for your svg. It can be handled using the zoom functions alone.
Here's the fiddle:
var origin = [100, 85],
scale = 5,
j = 10,
cubesData = [];
var alpha = 0,
beta = 0,
startAngle = Math.PI / 6;
var zoom = d3.zoom()
.scaleExtent([1, 40])
.on("zoom", zoomed)
.on('start', zoomStart)
.on('end', zoomEnd);
var svg = d3.select('svg').call(zoom)
.append('g');
var color = d3.scaleOrdinal(d3.schemeCategory20);
var cubesGroup = svg.append('g').attr('class', 'cubes').attr('transform', 'translate(0,0) scale(1)');
var mx, my, mouseX, mouseY;
var cubes3D = d3._3d()
.shape('CUBE')
.x(function(d) {
return d.x;
})
.y(function(d) {
return d.y;
})
.z(function(d) {
return d.z;
})
.rotateY(startAngle)
.rotateX(-startAngle)
.origin(origin)
.scale(scale);
function zoomStart() {
mx = d3.event.sourceEvent.x;
my = d3.event.sourceEvent.y;
if (d3.event.sourceEvent !== null && d3.event.sourceEvent.type == 'mousemove' && d3.event.sourceEvent.ctrlKey == true) {
cubesGroup.attr("transform", d3.event.transform);
}
}
function zoomEnd() {
if (d3.event.sourceEvent == null) return;
mouseX = d3.event.sourceEvent.x - mx + mouseX
mouseY = d3.event.sourceEvent.y - my + mouseY
}
function zoomed(d) {
if (d3.event.sourceEvent == null) return;
if (d3.event.sourceEvent !== null && d3.event.sourceEvent.type == 'wheel') {
cubesGroup.attr("transform", "scale(" + d3.event.transform['k'] + ")");
} else if (d3.event.sourceEvent !== null && d3.event.sourceEvent.type == 'mousemove' && d3.event.sourceEvent.ctrlKey == true) {
cubesGroup.attr("transform", "translate(" + d3.event.transform['x'] + "," + d3.event.transform['y'] + ") scale(" + d3.event.transform['k'] + ")");
} else if (d3.event.sourceEvent !== null && d3.event.sourceEvent.type == 'mousemove' && d3.event.sourceEvent.ctrlKey == false) {
mouseX = mouseX || 0;
mouseY = mouseY || 0;
beta = (d3.event.sourceEvent.x - mx + mouseX) * Math.PI / 230;
alpha = (d3.event.sourceEvent.y - my + mouseY) * Math.PI / 230 * (-1);
processData(cubes3D.rotateY(beta + startAngle)
.rotateX(alpha - startAngle)(cubesData), 0);
};
}
function processData(data, tt) {
/* --------- CUBES ---------*/
var cubes = cubesGroup.selectAll('g.cube')
.data(data, function(d) {
return d.id
});
var ce = cubes
.enter()
.append('g')
.attr('class', 'cube')
.attr('fill', function(d) {
return color(d.id);
})
.attr('stroke', function(d) {
return d3.color(color(d.id)).darker(2);
})
.merge(cubes)
.sort(cubes3D.sort);
cubes.exit().remove();
/* --------- FACES ---------*/
var faces = cubes.merge(ce)
.selectAll('path.face')
.data(function(d) {
return d.faces;
},
function(d) {
return d.face;
}
);
faces.enter()
.append('path')
.attr('class', 'face')
.attr('fill-opacity', 0.95)
.classed('_3d', true)
.merge(faces)
.transition().duration(tt)
.attr('d', cubes3D.draw);
faces.exit().remove();
/* --------- TEXT ---------*/
var texts = cubes.merge(ce)
.selectAll('text.text').data(function(d) {
var _t = d.faces.filter(function(d) {
return d.face === 'top';
});
return [{
height: d.height,
centroid: _t[0].centroid
}];
});
texts.enter()
.append('text')
.attr('class', 'text')
.attr('dy', '-.7em')
.attr('text-anchor', 'middle')
.attr('font-family', 'sans-serif')
.attr('font-weight', 'bolder')
.attr('x', function(d) {
return origin[0] + scale * d.centroid.x
})
.attr('y', function(d) {
return origin[1] + scale * d.centroid.y
})
.classed('_3d', true)
.merge(texts)
.transition().duration(tt)
.attr('fill', 'black')
.attr('stroke', 'none')
.attr('x', function(d) {
return origin[0] + scale * d.centroid.x
})
.attr('y', function(d) {
return origin[1] + scale * d.centroid.y
})
.tween('text', function(d) {
var that = d3.select(this);
var i = d3.interpolateNumber(+that.text(), Math.abs(d.height));
return function(t) {
that.text(i(t).toFixed(1));
};
});
texts.exit().remove();
/* --------- SORT TEXT & FACES ---------*/
ce.selectAll('._3d').sort(d3._3d().sort);
}
function init() {
cubesData = [];
var cnt = 0;
for (var z = -j / 2; z <= j / 2; z = z + 5) {
for (var x = -j; x <= j; x = x + 5) {
var h = d3.randomUniform(-2, -7)();
var _cube = makeCube(h, x, z);
_cube.id = 'cube_' + cnt++;
_cube.height = h;
cubesData.push(_cube);
}
}
processData(cubes3D(cubesData), 1000);
}
function dragStart() {
console.log('dragStart')
mx = d3.event.x;
my = d3.event.y;
}
function dragged() {
console.log('dragged')
mouseX = mouseX || 0;
mouseY = mouseY || 0;
beta = (d3.event.x - mx + mouseX) * Math.PI / 230;
alpha = (d3.event.y - my + mouseY) * Math.PI / 230 * (-1);
processData(cubes3D.rotateY(beta + startAngle)
.rotateX(alpha - startAngle)(cubesData), 0);
}
function dragEnd() {
console.log('dragend')
mouseX = d3.event.x - mx + mouseX;
mouseY = d3.event.y - my + mouseY;
}
function makeCube(h, x, z) {
return [{
x: x - 1,
y: h,
z: z + 1
}, // FRONT TOP LEFT
{
x: x - 1,
y: 0,
z: z + 1
}, // FRONT BOTTOM LEFT
{
x: x + 1,
y: 0,
z: z + 1
}, // FRONT BOTTOM RIGHT
{
x: x + 1,
y: h,
z: z + 1
}, // FRONT TOP RIGHT
{
x: x - 1,
y: h,
z: z - 1
}, // BACK TOP LEFT
{
x: x - 1,
y: 0,
z: z - 1
}, // BACK BOTTOM LEFT
{
x: x + 1,
y: 0,
z: z - 1
}, // BACK BOTTOM RIGHT
{
x: x + 1,
y: h,
z: z - 1
}, // BACK TOP RIGHT
];
}
d3.selectAll('button').on('click', init);
init();
button {
position: absolute;
right: 10px;
top: 10px;
}
<!DOCTYPE html>
<meta charset="utf-8">
<script src="https://d3js.org/d3.v4.min.js"></script>
<script src="https://unpkg.com/d3-3d/build/d3-3d.min.js"></script>
<body>
<svg width="500" height="500"></svg>
</body>
On JSFiddle

d3 gauge chart with labels and percentages?

We're searching for a radial chart that looks like the following. What's most interesting about this chart is that it has the percentage. We've been searching for about three days and we haven't found anything that uses the d3.js library.
We found these two solid gauge charts, one from amcharts and the other from anycharts, but neither have the percentage as clear. Highcharts also has something similar, but not labels. Plus, they don't use d3 and not open-source.
Any help is appreciated.
Thanks.
It can be easily coded from scratch
It's possible to get coordinate of segment's end or start, using centroid function, after that you can add text here and rotate it accordingly
var data = [45,33,66,50,90]
var svg = d3.select('#result').append('svg').attr('width',500).attr('height',500)
var arcs = data.map((v,i)=>{
return d3.svg.arc().innerRadius(i*20+60).outerRadius((i+1)*20-5+60)
});
var pieData = data.map((v,i)=>{
return [{value:v*0.75,arc:arcs[i]},{value:(100-v)*0.75,arc:arcs[i]},{value:100*0.25,arc:arcs[i]}]
})
var pie = d3.layout.pie()
.sort(null)
.value(d=>d.value)
var g = svg.selectAll('g').data(pieData).enter().append('g').attr('transform','translate(250,250) rotate(180)').attr('fill-opacity',(d,i)=>2/(i+1))
// progress
g.selectAll('path').data(d=>{return pie(d)}).enter().append('path').attr('d',d=>{return d.data.arc(d)})
.attr('fill',(d,i)=>i==0?'blue':'none')
svg.selectAll('g').each(function(d){
var el = d3.select(this);
el.selectAll('path').each((r,i)=>{
if(i==1){
var centroid = r.data.arc.centroid({startAngle:r.startAngle+0.05,endAngle:r.startAngle+0.001+0.05})
g.append('text').text(100-Math.floor(r.value)+'%').attr('transform',`translate(${centroid[0]},${centroid[1]}) rotate(${180/Math.PI*(r.startAngle)+7})`).attr('alignment-baseline','middle')
}
})
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>
<div id='result'></div>
I have also need to this type chart.
My code is as following this make responsive graph according to parent width.
for it you need to get parent width and assign it to var width variable
Hope this code help you.
var width = 300;
var arcSize = (6 * width / 100);
var innerRadius = arcSize * 3;
var data = [
{value: 45, label: "label_1", color: '#ff0000'},
{value: 33, label: "label_2", color: '#00ff00'},
{value: 66, label: "label_3", color: '#0000ff'},
{value: 50, label: "label_4", color: '#ffff00'},
{value: 90, label: "label_5", color: '#ff0099'}
];
function render() {
var svg = d3.select('#result').append('svg').attr('width', width).attr('height', width);
var arcs = data.map(function (obj, i) {
return d3.svg.arc().innerRadius(i * arcSize + innerRadius).outerRadius((i + 1) * arcSize - (width / 100) + innerRadius);
});
var arcsGrey = data.map(function (obj, i) {
return d3.svg.arc().innerRadius(i * arcSize + (innerRadius + ((arcSize / 2) - 2))).outerRadius((i + 1) * arcSize - ((arcSize / 2)) + (innerRadius));
});
var pieData = data.map(function (obj, i) {
return [
{value: obj.value * 0.75, arc: arcs[i], object: obj},
{value: (100 - obj.value) * 0.75, arc: arcsGrey[i], object: obj},
{value: 100 * 0.25, arc: arcs[i], object: obj}];
});
var pie = d3.layout.pie().sort(null).value(function (d) {
return d.value;
});
var g = svg.selectAll('g').data(pieData).enter()
.append('g')
.attr('transform', 'translate(' + width / 2 + ',' + width / 2 + ') rotate(180)');
var gText = svg.selectAll('g.textClass').data([{}]).enter()
.append('g')
.classed('textClass', true)
.attr('transform', 'translate(' + width / 2 + ',' + width / 2 + ') rotate(180)');
g.selectAll('path').data(function (d) {
return pie(d);
}).enter().append('path')
.attr('id', function (d, i) {
if (i == 1) {
return "Text" + d.data.object.label
}
})
.attr('d', function (d) {
return d.data.arc(d);
}).attr('fill', function (d, i) {
return i == 0 ? d.data.object.color : i == 1 ? '#D3D3D3' : 'none';
});
svg.selectAll('g').each(function (d, index) {
var el = d3.select(this);
var path = el.selectAll('path').each(function (r, i) {
if (i === 1) {
var centroid = r.data.arc.centroid({
startAngle: r.startAngle + 0.05,
endAngle: r.startAngle + 0.001 + 0.05
});
var lableObj = r.data.object;
g.append('text')
.attr('font-size', ((5 * width) / 100))
.attr('dominant-baseline', 'central')
/*.attr('transform', "translate(" + centroid[0] + "," + (centroid[1] + 10) + ") rotate(" + (180 / Math.PI * r.startAngle + 7) + ")")
.attr('alignment-baseline', 'middle')*/
.append("textPath")
.attr("textLength", function (d, i) {
return 0;
})
.attr("xlink:href", "#Text" + r.data.object.label)
.attr("startOffset", '5')
.attr("dy", '-3em')
.text(lableObj.value + '%');
}
if (i === 0) {
var centroidText = r.data.arc.centroid({
startAngle: r.startAngle,
endAngle: r.startAngle
});
var lableObj = r.data.object;
gText.append('text')
.attr('font-size', ((5 * width) / 100))
.text(lableObj.label)
.attr('transform', "translate(" + (centroidText[0] - ((1.5 * width) / 100)) + "," + (centroidText[1] + ") rotate(" + (180) + ")"))
.attr('dominant-baseline', 'central');
}
});
});
}
render()
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>
<div id="result"></div>

Append svg icon always right behind the text

So I work with D3.js and I have a radar chart with data which are represented by text positioned within a circle.
Texts have always different length and I would like to directly after each of these texts append an icon in this order: chart, text label, trash can.
I already tried to calculate length within an attribute where the text is rendered, which gives me length of each label correctly, but then I did not manage to append icon:
.attr("lenght", function() {
var datalong = this.getComputedTextLength();
})
Then I as well tried to append icon separately which works, but I don't know how to then get the length:
axis.append("svg:image")
.attr("xlink:href","http://svgur.com/i/121.svg")
.attr("y",-7)
.attr("opacity",1)
.attr("transform", function(d, i,e) {
var angleI = angleSlice * i * 180 / Math.PI - 90;
var distance = radius * options.trash.trashFactor;
var flip = angleI > 90 ? 180 : 0;
return "rotate(" + angleI + ") translate(" + distance + ")" + "rotate(" + flip + ")"
});
Then I tried to append icon in method where I write data (text), but the icon did not show up. I tried to wrap them into groups and position them next to each other, but also without succeed.
I have a JSFiddle with somewhat closest option I was able to get. I hope it is possible to understand what I am trying to do, if not please refer to fiddle and where you can easily understand what I am trying to achieve.
JSFiddle: https://jsfiddle.net/fsb47ndf/5/
Any help is highly appreciated!
Here is another idea, to use FontAwesome font and icons. This way you avoid the appending of svg:image.
.html(function (d, i) {
var angleI = angleSlice * i * 180 / Math.PI - 90;
if (angleI > 90) {
return ' ' + d;
} else {
return d + ' '
}
});
https://jsfiddle.net/fsb47ndf/25/
&#xf014 is FA unicode for trash icon (http://fontawesome.io/icon/trash-o/). Rotate the icon the same way you rotate text.
You can get the length of the texts with this cumbersome math:
var textLength = d3.select(this.parentNode).select("text").node().getComputedTextLength();
Basicaly, this is what the code does:
d3.select(this.parentNode): It selects the parent of the icon, then...
select("text").node(): It selects the text which is child of that parent, and finally...
getComputedTextLength(): It gets the length of that text.
Then, you use it to set the distance of the icons:
var distance = angleI > 90 ? radius + textLength + 40 : radius + textLength + 30;
Here is your updated fiddle: https://jsfiddle.net/0L4xzmfo/
And here the same code in the snippet:
data = [{
name: 'DATA11111',
value: 22,
}, {
name: 'DATA2',
value: 50,
}, {
name: 'DATA33333333',
value: 0,
}, {
name: 'DATA444444',
value: 24,
}, {
name: 'DATA55',
value: 22,
}, {
name: 'DATA6666',
value: 30,
}, {
name: 'DATA7',
value: 20,
}, {
name: 'DATA8',
value: 41,
}, {
name: 'DATA9',
value: 31,
}, {
name: 'DATA10',
value: 30,
}, {
name: 'DATA1121213213',
value: 30,
}, {
name: 'DATA12',
value: 30,
}, {
name: 'DATA1123123212313',
value: 30,
}, {
name: 'DATA14',
value: 30,
}, ];
var options = {
width: 600,
height: 600,
margins: {
top: 100,
right: 100,
bottom: 100,
left: 100
},
circles: {
levels: 6,
maxValue: 100,
labelFactor: 1.15,
dataFactor: 1.09,
opacity: 0.2,
},
trash: {
trashFactor: 1.32
}
};
var allAxis = (data.map(function(i, j) {
return i.name
})),
total = allAxis.length,
radius = Math.min(options.width / 2, options.height / 2),
angleSlice = Math.PI * 2 / total,
Format = d3.format('');
var rScale = d3.scale.linear()
.domain([0, options.circles.maxValue])
.range([50, radius]);
var svg = d3.select("body").append("svg")
.attr("width", options.width + options.margins.left + options.margins.right)
.attr("height", options.height + options.margins.top + options.margins.bottom);
var g = svg.append("g")
.attr("transform", "translate(" + (options.width / 2 + options.margins.left) + "," + (options.height / 2 + options.margins.top) + ")");
var axisGrid = g.append("g")
.attr("class", "axisWraper");
var axis = axisGrid.selectAll(".axis")
.data(allAxis)
.enter()
.append("g")
.attr("class", "axis")
//append them lines
axis.append("line")
.attr("x1", 0)
.attr("y1", 0)
.attr("x2", function(d, i) {
var tempX2 = radius * Math.cos(angleSlice * i - Math.PI / 2);
return tempX2;
})
.attr("y2", function(d, i) {
var tempY = radius * Math.sin(angleSlice * i - Math.PI / 2);
return tempY;
})
.attr("class", "line")
.attr("stroke", "black")
.attr("fill", "none");
//Draw background circles
axisGrid.selectAll(".levels")
.data([6, 5, 4, 3, 2, 1])
.enter()
.append("circle")
.attr("class", "gridCircle")
.attr("r", function(d, i) {
return parseInt(radius / options.circles.levels * d, 10);
})
.attr("stroke", "black")
.attr("fill-opacity", options.circles.opacity);
//Write data
axis.append("text")
.attr("class", "labels")
.attr("font-size", "12px")
.attr("font-family", "Montserrat")
.attr("text-anchor", function(d, i) {
var angleI = angleSlice * i * 180 / Math.PI - 90;
return angleI > 90 ? "end" : "start"
})
.attr("dy", ".35em")
.attr("fill", "black")
.attr("transform", function(d, i) {
var angleI = angleSlice * i * 180 / Math.PI - 90; // the angle to rotate the label
var distance = radius * options.circles.dataFactor; // the distance from the center
var flip = angleI > 90 ? 180 : 0; // 180 if label needs to be flipped
return "rotate(" + angleI + ") translate(" + distance + ")" + "rotate(" + flip + ")"
})
.text(function(d) {
return d;
});
axis.append("svg:image")
.attr("xlink:href", "http://svgur.com/i/121.svg")
.attr("class", "trash")
.attr("y", -7)
.attr("text-anchor", "end")
.attr("transform", function(d, i) {
var textLength = d3.select(this.parentNode).select("text").node().getComputedTextLength();
var angleI = angleSlice * i * 180 / Math.PI - 90; // the angle to rotate the label
var distance = angleI > 90 ? radius + textLength + 40 : radius + textLength + 30; // the distance from the center
var flip = angleI > 90 ? 180 : 0; // 180 if label needs to be flipped
return "rotate(" + angleI + ") translate(" + distance + ")" + "rotate(" + flip + ")"
});
.trash {
position: absolute;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>
PS: I'm using a magic number because I didn't look at the code to find the exact padding of the text. Please change that magic number accordingly.

D3.js rotate axis labels around the middle point

I am working with D3.js now and even though I found very similar cases I am not really able to put them together and move on.
I have something similar as a radar chart and I would like to append to each ax I create (number of axes is not fixed could be 4, but also 40) text, which I already have, but rotate the text around center point and turn them as soon as they reach 180 degrees, actually 0 degrees.
The result should look like this:
What I have now is this:
I only know how to reach this within arc, which is also nicely shown here, but I did not really figure it out for my case.
This is my code snippet, where I append those text criteria:
//Write criterias
axis.append("text")
.attr("class","labels")
.attr("font-size","12px")
.attr("font-family","Montserrat")
.attr("text-anchor","middle")
.attr("fill","white")
.attr("x",function (d, i) {
return radius * Math.cos(angleSlice * i - Math.PI/2)*options.circles.labelFactor;
})
.attr("y",function (d, i) {
return radius * Math.sin(angleSlice * i - Math.PI/2)*options.circles.labelFactor;
})
.text(function (d) {
return d;
});
EDIT:
Here is my fiddle: https://jsfiddle.net/fsb47ndf/
Thank you for any advise
Some people find it difficult to rotate an SVG element, because the rotate function of the transform attribute rotates the element around the origin (0,0), not around its center:
If optional parameters and are not supplied, the rotate is about the origin of the current user coordinate system (source)
Thus, an easy option is dropping the x and the y attributes of the texts, and positioning them using transform. That way, we can easily calculate the rotation:
.attr("transform", function(d, i) {
var rotate = angleSlice * i > Math.PI / 2 ?
(angleSlice * i * 180 / Math.PI) - 270 :
(angleSlice * i * 180 / Math.PI) - 90;
return "translate(" + radius * Math.cos(angleSlice * i - Math.PI / 2) * options.circles.labelFactor +
"," + radius * Math.sin(angleSlice * i - Math.PI / 2) * options.circles.labelFactor +
") rotate(" + rotate + ")"
})
Here is your code:
data = [{
name: 'DATA1',
value: 22,
}, {
name: 'DATA2',
value: 50,
}, {
name: 'DATA3',
value: 0,
}, {
name: 'DATA4',
value: 24,
}, {
name: 'DATA5',
value: 22,
}, {
name: 'DATA6',
value: 30,
}, {
name: 'DATA7',
value: 20,
}, {
name: 'DATA8',
value: 41,
}, {
name: 'DATA9',
value: 31,
}, {
name: 'DATA10',
value: 30,
}, {
name: 'DATA11',
value: 30,
}, {
name: 'DATA12',
value: 30,
}, {
name: 'DATA13',
value: 30,
}, {
name: 'DATA14',
value: 30,
}, ];
var options = {
width: 600,
height: 600,
margins: {
top: 100,
right: 100,
bottom: 100,
left: 100
},
circles: {
levels: 6,
maxValue: 100,
labelFactor: 1.15,
opacity: 0.2,
},
};
var allAxis = (data.map(function(i, j) {
return i.name
})),
total = allAxis.length,
radius = Math.min(options.width / 2, options.height / 2),
angleSlice = Math.PI * 2 / total,
Format = d3.format('');
var rScale = d3.scale.linear()
.domain([0, options.circles.maxValue])
.range([50, radius]);
var svg = d3.select("body").append("svg")
.attr("width", options.width + options.margins.left + options.margins.right)
.attr("height", options.height + options.margins.top + options.margins.bottom);
var g = svg.append("g")
.attr("transform", "translate(" + (options.width / 2 + options.margins.left) + "," + (options.height / 2 + options.margins.top) + ")");
var axisGrid = g.append("g")
.attr("class", "axisWraper");
var axis = axisGrid.selectAll(".axis")
.data(allAxis)
.enter()
.append("g")
.attr("class", "axis")
//append them lines
axis.append("line")
.attr("x1", 0)
.attr("y1", 0)
.attr("x2", function(d, i) {
var tempX2 = radius * Math.cos(angleSlice * i - Math.PI / 2);
return tempX2;
})
.attr("y2", function(d, i) {
var tempY = radius * Math.sin(angleSlice * i - Math.PI / 2);
return tempY;
})
.attr("class", "line")
.attr("stroke", "black")
.attr("fill", "none");
//Draw background circles
axisGrid.selectAll(".levels")
.data([6, 5, 4, 3, 2, 1])
.enter()
.append("circle")
.attr("class", "gridCircle")
.attr("r", function(d, i) {
return parseInt(radius / options.circles.levels * d, 10);
})
.attr("stroke", "black")
.attr("fill-opacity", options.circles.opacity);
//Write data
axis.append("text")
.attr("class", "labels")
.attr("font-size", "12px")
.attr("font-family", "Montserrat")
.attr("text-anchor", "middle")
.attr("fill", "black")
.attr("transform", function(d, i) {
var rotate = angleSlice * i > Math.PI ? (angleSlice * i * 180 / Math.PI) - 270 : (angleSlice * i * 180 / Math.PI) - 90;
return "translate(" + radius * Math.cos(angleSlice * i - Math.PI / 2) * options.circles.labelFactor + "," + radius * Math.sin(angleSlice * i - Math.PI / 2) * options.circles.labelFactor + ") rotate(" + rotate + ")"
})
.text(function(d) {
return d;
});
<script src="https://d3js.org/d3.v3.min.js"></script>
Like already mentioned by Gerardo Furtado in his answer life can get easier if you ditch your x and y attributes in favor of doing all positioning and rotation via the transform attribute. However, you can take his approach even a step further by letting the browser do all the trigonometry. All you need to do is specify a list of appropriate transform definitions.
.attr("transform", function(d, i) {
var angleI = angleSlice * i * 180 / Math.PI - 90; // the angle to rotate the label
var distance = radius * options.circles.labelFactor; // the distance from the center
var flip = angleI > 90 ? 180 : 0; // 180 if label needs to be flipped
return "rotate(" + angleI + ") translate(" + distance + ")" + "rotate(" + flip + ")");
// ^1.^ ^2.^ ^3.^
})
If you omit the x and y attributes, they will default to 0, which means, that the texts will all start off at the origin. From there it is easy to move and rotate them to their final position applying just three transformations:
rotate the texts to the angle according to their position on the perimeter
translate the rotated texts outwards to their final position
Flip the texts on the left hand side of the circle using another rotate.
Have a look at the following snippet for a working demo:
data = [{
name: 'DATA1',
value: 22,
},
{
name: 'DATA2',
value: 50,
},
{
name: 'DATA3',
value: 0,
},
{
name: 'DATA4',
value: 24,
},
{
name: 'DATA5',
value: 22,
},
{
name: 'DATA6',
value: 30,
},
{
name: 'DATA7',
value: 20,
},
{
name: 'DATA8',
value: 41,
},
{
name: 'DATA9',
value: 31,
},
{
name: 'DATA10',
value: 30,
},
{
name: 'DATA11',
value: 30,
},
{
name: 'DATA12',
value: 30,
},
{
name: 'DATA13',
value: 30,
},
{
name: 'DATA14',
value: 30,
},
];
var options = {
width: 600,
height: 600,
margins: {
top: 100,
right: 100,
bottom: 100,
left: 100
},
circles: {
levels: 6,
maxValue: 100,
labelFactor: 1.15,
opacity: 0.2,
},
};
var allAxis = (data.map(function(i, j) {
return i.name
})),
total = allAxis.length,
radius = Math.min(options.width / 2, options.height / 2),
angleSlice = Math.PI * 2 / total,
Format = d3.format('');
var rScale = d3.scale.linear()
.domain([0, options.circles.maxValue])
.range([50, radius]);
var svg = d3.select("body").append("svg")
.attr("width", options.width + options.margins.left + options.margins.right)
.attr("height", options.height + options.margins.top + options.margins.bottom);
var g = svg.append("g")
.attr("transform", "translate(" + (options.width / 2 + options.margins.left) + "," + (options.height / 2 + options.margins.top) + ")");
var axisGrid = g.append("g")
.attr("class", "axisWraper");
var axis = axisGrid.selectAll(".axis")
.data(allAxis)
.enter()
.append("g")
.attr("class", "axis")
//append them lines
axis.append("line")
.attr("x1", 0)
.attr("y1", 0)
.attr("x2", function(d, i) {
var tempX2 = radius * Math.cos(angleSlice * i - Math.PI / 2);
return tempX2;
})
.attr("y2", function(d, i) {
var tempY = radius * Math.sin(angleSlice * i - Math.PI / 2);
return tempY;
})
.attr("class", "line")
.attr("stroke", "black")
.attr("fill", "none");
//Draw background circles
axisGrid.selectAll(".levels")
.data([6, 5, 4, 3, 2, 1])
.enter()
.append("circle")
.attr("class", "gridCircle")
.attr("r", function(d, i) {
return parseInt(radius / options.circles.levels * d, 10);
})
.attr("stroke", "black")
.attr("fill-opacity", options.circles.opacity);
//Write data
axis.append("text")
.attr("class", "labels")
.attr("font-size", "12px")
.attr("font-family", "Montserrat")
.attr("text-anchor", "middle")
.attr("fill", "black")
.attr("dy", ".35em")
.attr("transform", function(d, i) {
var angleI = angleSlice * i * 180 / Math.PI - 90; // the angle to rotate the label
var distance = radius * options.circles.labelFactor; // the distance from the center
var flip = angleI > 90 ? 180 : 0; // 180 if label needs to be flipped
return "rotate(" + angleI + ") translate(" + distance + ")" + "rotate(" + flip + ")"
})
.text(function(d) {
console.log(d);
return d;
});
<script src="https://d3js.org/d3.v3.js"></script>
You can use something like this to rotate all the labels. You probably have to adjust the positioning and rotation angle based on exactly how you want it.
var angle = 180;
svg.selectAll(".labels")
.attr("transform", "translate(300,0) rotate("+angle+")");

d3 Sunburst reduce parent node inner and outer radius on click

I am trying to reduce the size of the parent level inner and outer radius' when I click on one of its children nodes. You can view my current diagram here: https://jsfiddle.net/2heLd2b1/. As you can see, when a child node is clicked and the distorts to display the selected node and its path, the parent layers take up too much space. I am looking for any suggestions as to how I could reduce or shrink the parent nodes width.
ar width = 960,
height = 750,
radius = (Math.min(width, height) / 2) - 10;
var color = d3.scale.category20c();
var x = d3.scale.linear()
.range([0, 2 * Math.PI]);
var y = d3.scale.linear()
.range([0, radius]);
function percent(d) {
var percentage = (d.value / 956129) * 100;
return percentage.toFixed(2);
}
// var tip = d3.tip()
// .attr('class', 'd3-tip')
// .offset([-10, 0])
// .html(function(d) {
// return "<strong>" + d.name + "</strong> <span style='color:red'>" + percent(d) + "%</span>";
// })
var partition = d3.layout.partition()
// .value(function(d) { return d.size; });
.value(function(d) { return 1; });
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)) })
.cornerRadius(function(d) { return 5;});
var svg = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height)
.append("g")
.attr("transform", "translate(" + width / 2 + "," + height / 2 + ")")
.append("g")
.classed("inner", true);
// svg.call(tip);
d3.json("flare.json", function(error, root) {
if (error) throw error;
var g = svg.selectAll("g")
.data(partition.nodes(root))
.enter().append("g");
path = g.append("path")
.attr("d", arc)
.attr('stroke', 'white')
.attr("fill", function(d) { return color((d.children ? d : d.parent).name); })
.on("click", magnify)
// .on('mouseover', tip.show)
// .on('mouseout', tip.hide)
.each(stash);
var text = g.append("text")
.attr("x", function(d) { return d.x; })
.attr("dx", "6") // margin
.attr("dy", ".35em") // vertical-align
.text(function(d) {
return d.name;
})
.attr('font-size', function(d) {
return '10px';
})
.attr("text-anchor", "middle")
.attr("transform", function(d) {
if (d.depth > 0) {
return "translate(" + arc.centroid(d) + ")" +
"rotate(" + getStartAngle(d) + ")";
} else {
return null;
}
})
.on("click", magnify);
var innerG = d3.selectAll("g.inner");
// Distort the specified node to 80% of its parent.
function magnify(node) {
// get and store parent sequence
var parentSequence = getAncestors(node)
text.transition().attr("opacity", 0);
spin(node);
// check if node has a parent. If so, iterate throught parentSequence and update the size of each node in the sequence
if (node.parent) {
for (var p = 0; p < parentSequence.length; p++) {
if (parent = parentSequence[p].parent) {
var parent,
x = parent.x,
k = 0.95;
parent.children.forEach(function(sibling) {
x += reposition(sibling, x, sibling === parentSequence[p]
? parent.dx * k / parentSequence[p].value
: parent.dx * (1 - k) / (parent.value - parentSequence[p].value));
});
} else {
reposition(parentSequence[p], 0, parentSequence[p].dx / parentSequence[p].value);
}
}
// if node does not have parent (center node) reset all values to original
} else {
if (parent = node.parent) {
var parent,
x = parent.x,
k = 0.95;
parent.children.forEach(function(sibling) {
x += reposition(sibling, x, sibling === node
? parent.dx * k / node.value
: parent.dx * (1 - k) / (parent.value - node.value));
});
} else {
reposition(node, 0, node.dx / node.value);
}
}
path.transition()
.duration(750)
.attrTween("d", arcTween)
.each("end", function(e, i) {
// check if the animated element's data e lies within the visible angle span given in node
if (e.x >= node.x && e.x < (node.x + node.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("x", function(d) {
return d.x;
})
.attr("transform", function(d) {
if (d.depth > 0) {
return "translate(" + arc.centroid(d) + ")" +
"rotate(" + getNewAngle(d) + ")";
} else {
return null;
}
});
}
});
}
function spin(d) {
var spin1 = new Promise (function(resolve, reject) {
var newAngle = - x(d.x + d.dx / 2);
// console.log('newAngle', newAngle)
innerG
.transition()
.duration(1500)
.attr("transform", "rotate(" + ((180 / Math.PI * newAngle)) + ")");
resolve("Success!");
});
spin1.then(function() {
var newerAngle = - x(d.x + d.dx / 2);
// console.log('newerAngle', newerAngle)
innerG
.transition()
.duration(1500)
.attr("transform", "rotate(" + ((180 / Math.PI * newerAngle)) + ")");
})
path
.classed("selected", function (x) { return d.name == x.name; });
}
// Recursively reposition the node at position x with scale k.
function reposition(node, x, k) {
// console.log(k)
node.x = x;
if (node.children && (n = node.children.length)) {
var i = -1, n;
while (++i < n) x += reposition(node.children[i], x, k);
}
return node.dx = node.value * k;
}
// Stash the old values for transition.
function stash(d) {
d.x0 = d.x;
d.dx0 = d.dx;
}
// Interpolate the arcs in data space.
function arcTween(a) {
var i = d3.interpolate({x: a.x0, dx: a.dx0}, a);
return function(t) {
var b = i(t);
a.x0 = b.x;
a.dx0 = b.dx;
return arc(b);
};
};
});
function getStartAngle(d) {
// Offset the angle by 90 deg since the '0' degree axis for arc is Y axis, while
// for text it is the X axis.
var thetaDeg = (180 / Math.PI * (arc.startAngle()(d) + arc.endAngle()(d)) / 2 - 90);
// If we are rotating the text by more than 90 deg, then "flip" it.
// This is why "text-anchor", "middle" is important, otherwise, this "flip" would
// a little harder.
return (thetaDeg > 90) ? thetaDeg - 180 : thetaDeg;
}
function getNewAngle(d) {
var thetaDeg = (180 / Math.PI * (arc.startAngle()(d) + arc.endAngle()(d)) / 2 - 90);
return (thetaDeg < 90) ? thetaDeg - 180 : thetaDeg;
}
function getAncestors(node) {
var path = [];
var current = node;
while (current.parent) {
path.unshift(current);
current = current.parent;
}
return path;
}
I managed to figure it out by combining the tween methods from my original jsFiddle link, https://jsfiddle.net/2heLd2b1/, with the traditional tween used by a zoomable sunburst.You can see the implementation here: https://jsfiddle.net/6e4y0s11/
I altered my innerRadius and outerRadius from:
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)) })
.cornerRadius(function(d) { return 5;});
to:
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(d.depth * 20, y(d.y)) })
.outerRadius(function(d) { return Math.max(100, y(d.y + d.dy)) })
.cornerRadius(function(d) { return 5;});
I also added:
function arcTweenZoom(d) {
var 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) {
y.domain(yd(t)).range(yr(t));
return arc(d);
};
};
}
so that I could interpolate the scales. The final result allows the inner parent level nodes to shrink without actually disappearing. The maintain a minimum radius based on their d.y values.

Categories