I've started trying to learn D3 as merely a hobby and I've wanted to create a type of animation that displays the 'method of exhaustion' to approximate the area of a circle. The behavior I wish to obtain is shown in this animation.
I have been able to draw the circle, the points around the circle as well as the triangles. Now I am working on a function called 'update' which upon listening to a slider event on the HTML dynamically changes the value for 'n' and use that to recalculate the coordinates and display them on screen.
I have managed to be able to change the fill of the circles but aside from that the update function doesn't update the coordinates of the circles as well as add new circles. I would really appreciate if someone could help me get this working as well as provide some insight as to why my approach is failing.
var w = 500;
var h = 500;
var n = 17;
var r = h / 2 - 20;
var coords = [];
//var id = 0;
function points() {
coords = []; //Clear Coords Array
for (var i = 0; i < n; i++) {
var p_i = {};
p_i.x = w / 2 + r * math.cos((2 * math.pi / n) * i);
p_i.y = h / 2 - r * math.sin((2 * math.pi / n) * i);
p_i.id = i;
coords.push(p_i);
}
//id++;
};
points(); //Generate Points
var svg = d3.select('body') //SVG Canvas
.append('svg')
.attr('width', w)
.attr('height', h);
var circle = svg.append('circle') //Draw Big Circle
.attr('cx', w / 2)
.attr('cy', h / 2)
.attr('r', r)
.attr('fill', 'teal')
.attr('stroke', 'black')
.attr('stroke-width', w / 100);
var center = svg.append('circle') //Construct Center
.attr('cx', w / 2)
.attr('cy', h / 2)
.attr('r', r / 50)
.attr('fill', 'red')
.attr('stroke', 'red')
.attr('stroke-width', w / 100);
var approx_pts = svg.selectAll('circle_points') //Construct Approx Points
.data(coords, function(d) {
return d.id; //Sets Identifier to uniquely identify data.
})
.enter()
.append('circle')
.attr('cx', function(d) {
return d.x;
})
.attr('cy', function(d) {
return d.y;
})
.attr('r', w / 100)
.attr('fill', 'red');
var approx_tri = svg.selectAll('polygon') //Draw Triangles
.data(coords, function(d) {
return d.id;
})
.enter()
.append('polygon')
.attr('points', function(d, i) {
if (i != n - 1) {
return w / 2 + ',' + h / 2 + ' ' + d.x + ',' + d.y + ' ' + coords[i + 1].x + ',' + coords[i + 1].y;
} else {
return w / 2 + ',' + h / 2 + ' ' + d.x + ',' + d.y + ' ' + coords[0].x + ',' + coords[0].y;
}
})
.attr('fill', 'Transparent')
.attr('stroke', 'orange')
.attr('stroke-width', w / 250);
d3.select('#slider') //Listen to Slider Event Change
.on('input', function() {
n = +this.value;
update(n);
});
function update() {
console.log('Hey man!');
points(); // Re-generate points
console.log('coords[1].x = ' + coords[1].x);
console.log('coords[1].y = ' + coords[1].y);
//Make Selection
approx_pts
.selectAll('circle')
.data(coords, function(d) {
return d.id;
});
approx_pts
.attr('fill', 'blue')
.attr('r', r / 25);
approx_pts
.enter()
.append('circle')
.attr('cx', function(d) {
return d.x;
})
.attr('cy', function(d) {
return d.y;
})
.attr('r', r / 50)
.attr('fill', 'green');
};
<script src="https://d3js.org/d3.v4.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/mathjs/3.16.2/math.min.js"></script>
<input id="slider" type="range" min="3" max="100" step="1" value="3">
You are missing a proper "enter", "update" and "exit" selections:
var updateSelection = svg.selectAll('.circle_points')
.data(coords, function(d) {
return d.id;
});
var exitSelection = updateSelection.exit().remove();
updateSelection.attr('cx', function(d) {
return d.x;
})
.attr('cy', function(d) {
return d.y;
})
.attr('r', r / 50)
.attr('fill', 'blue')
.attr('r', r / 25);
var enterSelection = updateSelection.enter()
.append('circle')
.attr('cx', function(d) {
return d.x;
})
.attr('cy', function(d) {
return d.y;
})
.attr('r', r / 50)
.attr('fill', 'green')
.attr("class", "circle_points");
Here is your code with those changes:
var w = 500;
var h = 500;
var n = 17;
var r = h / 2 - 20;
var coords = [];
//var id = 0;
function points() {
coords = []; //Clear Coords Array
for (var i = 0; i < n; i++) {
var p_i = {};
p_i.x = w / 2 + r * math.cos((2 * math.pi / n) * i);
p_i.y = h / 2 - r * math.sin((2 * math.pi / n) * i);
p_i.id = i;
coords.push(p_i);
}
//id++;
};
points(); //Generate Points
var svg = d3.select('body') //SVG Canvas
.append('svg')
.attr('width', w)
.attr('height', h);
var circle = svg.append('circle') //Draw Big Circle
.attr('cx', w / 2)
.attr('cy', h / 2)
.attr('r', r)
.attr('fill', 'teal')
.attr('stroke', 'black')
.attr('stroke-width', w / 100);
var center = svg.append('circle') //Construct Center
.attr('cx', w / 2)
.attr('cy', h / 2)
.attr('r', r / 50)
.attr('fill', 'red')
.attr('stroke', 'red')
.attr('stroke-width', w / 100);
var approx_pts = svg.selectAll('circle_points') //Construct Approx Points
.data(coords, function(d) {
return d.id; //Sets Identifier to uniquely identify data.
})
.enter()
.append('circle')
.attr('cx', function(d) {
return d.x;
})
.attr('cy', function(d) {
return d.y;
})
.attr('r', w / 100)
.attr('fill', 'red')
.attr("class", "circle_points");
var approx_tri = svg.selectAll('polygon') //Draw Triangles
.data(coords, function(d) {
return d.id;
})
.enter()
.append('polygon')
.attr('points', function(d, i) {
if (i != n - 1) {
return w / 2 + ',' + h / 2 + ' ' + d.x + ',' + d.y + ' ' + coords[i + 1].x + ',' + coords[i + 1].y;
} else {
return w / 2 + ',' + h / 2 + ' ' + d.x + ',' + d.y + ' ' + coords[0].x + ',' + coords[0].y;
}
})
.attr('fill', 'Transparent')
.attr('stroke', 'orange')
.attr('stroke-width', w / 250);
d3.select('#slider') //Listen to Slider Event Change
.on('input', function() {
n = +this.value;
update(n);
});
function update() {
points(); // Re-generate points
//Make Selection
var updateSelection = svg.selectAll('.circle_points')
.data(coords, function(d) {
return d.id;
});
var exitSelection = updateSelection.exit().remove();
updateSelection.attr('cx', function(d) {
return d.x;
})
.attr('cy', function(d) {
return d.y;
})
.attr('r', r / 50)
.attr('fill', 'blue')
.attr('r', r / 25);
var enterSelection = updateSelection.enter()
.append('circle')
.attr('cx', function(d) {
return d.x;
})
.attr('cy', function(d) {
return d.y;
})
.attr('r', r / 50)
.attr('fill', 'green')
.attr("class", "circle_points");
};
<script src="https://d3js.org/d3.v4.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/mathjs/3.16.2/math.min.js"></script>
<input id="slider" type="range" min="3" max="100" step="1" value="3">
You need to remove the 'circle_points' and then .exit().remove() and then .enter()
The Update Pattern is needed here.
var w = 500;
var h = 250;
var n = 4;
var r = h / 2 - 201
var coords = [];
//var id = 0;
function points() {
coords = []; //Clear Coords Array
for (var i = 0; i < n; i++) {
var p_i = {};
p_i.x = w / 2 + r * math.cos((2 * math.pi / n) * i);
p_i.y = h / 2 - r * math.sin((2 * math.pi / n) * i);
p_i.id = i;
coords.push(p_i);
}
};
points(); //Generate Points
var svg = d3.select('body') //SVG Canvas
.append('svg')
.attr('width', w)
.attr('height', h);
var circle = svg.append('circle') //Draw Big Circle
.attr('cx', w / 2)
.attr('cy', h / 2)
.attr('r', r)
.attr('fill', 'teal')
.attr('stroke', 'black')
.attr('stroke-width', w / 100);
var center = svg.append('circle') //Construct Center
.attr('cx', w / 2)
.attr('cy', h / 2)
.attr('r', r / 50)
.attr('fill', 'red')
.attr('stroke', 'red')
.attr('stroke-width', w / 100);
var approx_pts = svg.selectAll('circle_points') //Construct Approx Points
.data(coords, function(d) {
return d.id; //Sets Identifier to uniquely identify data.
})
.enter()
.append('circle')
.attr('cx', function(d) {
return d.x;
})
.attr('cy', function(d) {
return d.y;
})
.attr('r', w / 100)
.attr('fill', 'red');
var approx_tri = svg.selectAll('polygon') //Draw Triangles
.data(coords, function(d) {
return d.id;
})
.enter()
.append('polygon')
.attr('points', function(d, i) {
if (i != n - 1) {
return w / 2 + ',' + h / 2 + ' ' + d.x + ',' + d.y + ' ' + coords[i + 1].x + ',' + coords[i + 1].y;
} else {
return w / 2 + ',' + h / 2 + ' ' + d.x + ',' + d.y + ' ' + coords[0].x + ',' + coords[0].y;
}
})
.attr('fill', 'Transparent')
.attr('stroke', 'orange')
.attr('stroke-width', w / 250);
d3.select('#slider') //Listen to Slider Event Change
.on('input', function() {
n = +this.value;
update(n);
});
function update(n) {
points(); // Re-generate points
d3.selectAll("circle").remove();
var approx_pts = svg.selectAll('circle_points') //Construct Approx Points
.data(coords);
approx_pts.exit().remove();
approx_pts.enter()
.append('circle')
.attr('cx', function(d) {
return d.x;
})
.attr('cy', function(d) {
return d.y;
})
.attr('r', 6)
.attr('fill', 'red');
d3.selectAll("polygon").remove();
var approx_tri = svg.selectAll('polygon') //Draw Triangles
.data(coords)
approx_tri.exit().remove();
approx_tri.enter()
.append('polygon')
.attr('points', function(d, i) {
if (i != n - 1) {
return w / 2 + ',' + h / 2 + ' ' + d.x + ',' + d.y + ' ' + coords[i + 1].x + ',' + coords[i + 1].y;
} else {
return w / 2 + ',' + h / 2 + ' ' + d.x + ',' + d.y + ' ' + coords[0].x + ',' + coords[0].y;
}
})
.attr('fill', 'Transparent')
.attr('stroke', 'orange')
.attr('stroke-width', w / 250);
};
<script src="https://cdnjs.cloudflare.com/ajax/libs/mathjs/3.16.2/math.min.js"></script>
<script src="https://d3js.org/d3.v4.min.js"></script>
<!DOCTYPE html>
<meta charset="utf-8">
<body>
<input id="slider" type="range" min="3" max="100" step="4" value="17">
</body>
var w = 500;
var h = 500;
var n = 17;
var r = h / 2 - 20;
var coords = [];
//var id = 0;
function points() {
coords = []; //Clear Coords Array
for (var i = 0; i < n; i++) {
var p_i = {};
p_i.x = w / 2 + r * math.cos((2 * math.pi / n) * i);
p_i.y = h / 2 - r * math.sin((2 * math.pi / n) * i);
p_i.id = i;
coords.push(p_i);
}
//id++;
};
points(); //Generate Points
var svg = d3.select('body') //SVG Canvas
.append('svg')
.attr('width', w)
.attr('height', h);
var circle = svg.append('circle') //Draw Big Circle
.attr('cx', w / 2)
.attr('cy', h / 2)
.attr('r', r)
.attr('fill', 'teal')
.attr('stroke', 'black')
.attr('stroke-width', w / 100);
var center = svg.append('circle') //Construct Center
.attr('cx', w / 2)
.attr('cy', h / 2)
.attr('r', r / 50)
.attr('fill', 'red')
.attr('stroke', 'red')
.attr('stroke-width', w / 100);
var approx_pts = svg.selectAll('circle_points') //Construct Approx Points
.data(coords, function(d) {
return d.id; //Sets Identifier to uniquely identify data.
})
.enter()
.append('circle')
.attr('cx', function(d) {
return d.x;
})
.attr('cy', function(d) {
return d.y;
})
.attr('r', w / 100)
.attr('fill', 'red');
var approx_tri = svg.selectAll('polygon') //Draw Triangles
.data(coords, function(d) {
return d.id;
})
.enter()
.append('polygon')
.attr('points', function(d, i) {
if (i != n - 1) {
return w / 2 + ',' + h / 2 + ' ' + d.x + ',' + d.y + ' ' + coords[i + 1].x + ',' + coords[i + 1].y;
} else {
return w / 2 + ',' + h / 2 + ' ' + d.x + ',' + d.y + ' ' + coords[0].x + ',' + coords[0].y;
}
})
.attr('fill', 'Transparent')
.attr('stroke', 'orange')
.attr('stroke-width', w / 250);
d3.select('#slider') //Listen to Slider Event Change
.on('input', function() {
n = +this.value;
update(n);
});
function update(n) {
//console.log('Hey man!');
points(); // Re-generate points
//console.log('coords[1].x = ' + coords[1].x);
//console.log('coords[1].y = ' + coords[1].y);
//Make Selection
approx_pts
.selectAll('circle')
.data(coords, function(d) {
return d.id;
});
approx_pts
.attr('fill', 'blue')
.attr('r', r / 25);
approx_pts
.attr('cx', function(d) {
return (d.x)+n;
})
.attr('cy', function(d) {
return d.y+n;
})
.attr('r', r / 50)
.attr('fill', 'green');
};
<script src="https://d3js.org/d3.v4.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/mathjs/3.16.2/math.min.js"></script>
<input id="slider" type="range" min="3" max="100" step="1" value="3">
remember to update the value when you drag it
Related
I am trying to tween the heights of the various arcs in this chart
jsfiddle
http://jsfiddle.net/0ht35rpb/193/
I've seen this example but not sure how to start implementing it.
d3 how to tween inner radius for pie chart
I've seen this sample on various tweening maths. http://andyshora.com/tweening-shapes-paths-d3-js.html
var $this = $("#chart");
var data = [{
"label": "Overall Stress",
"value": 89
},{
"label": "Emotional Stress",
"value": 1
},{
"label": "Behavioural difficulties",
"value": 29
},{
"label": "hyperactivity and concetration",
"value": 89
},{
"label": "Getting along with others",
"value": 19
},{
"label": "Keen and helpful behaviour",
"value": 45
}];
var w = 350;
var h = 350;
function colores_google(n) {
var colores_g = ["#f7b363", "#448875", "#c12f39", "#2b2d39", "#f8dd2f", "#c12fff"];
return colores_g[n % colores_g.length];
}
var arcGenerator = {
radius: 70,
oldData: "",
init: function(el, data, w, h){
var stardata = [
{
"segments": data
}
];
this.el = el;
var clone = $.extend(true, {}, stardata);
this.oldData = this.setData(clone, false);
this.setup(el, this.setData(stardata, true), w, h);
},
update: function(data){
var clone = $.extend(true, {}, data);
this.animate(this.setData(data, true));
this.oldData = this.setData(clone, false);
},
animate: function(data){
var that = this;
var chart = d3.select(this.el);
that.generateArcs(chart, data);
},
setData: function(data, isSorted){
var diameter = 2 * Math.PI * this.radius;
var localData = new Array();
var displacement = 0;
var oldBatchLength = 0;
$.each(data, function(index, value) {
var riseLevels = value.segments;
var riseLevelCount = riseLevels.length;
if(oldBatchLength !=undefined){
displacement+=oldBatchLength;
}
var arcBatchLength = 2*Math.PI;
var arcPartition = arcBatchLength/riseLevelCount;
$.each(riseLevels, function( ri, value ) {
var startAngle = (ri*arcPartition);
var endAngle = ((ri+1)*arcPartition);
if(index!=0){
startAngle+=displacement;
endAngle+=displacement;
}
riseLevels[ri]["startAngle"] = startAngle;
riseLevels[ri]["endAngle"] = endAngle;
});
oldBatchLength = arcBatchLength;
localData.push(riseLevels);
});
var finalArray = new Array();
$.each(localData, function(index, value) {
$.each(localData[index], function(i, v) {
finalArray.push(v);
});
});
return finalArray;
},
generateArcs: function(chart, data){
var that = this;
//_arc paths
//append previous value to it.
$.each(data, function(index, value) {
if(that.oldData[index] != undefined){
data[index]["previousEndAngle"] = that.oldData[index].endAngle;
}
else{
data[index]["previousEndAngle"] = 0;
}
});
var arcpaths = that.arcpaths.selectAll("path")
.data(data);
arcpaths.enter().append("svg:path")
.style("fill", function(d, i){
return colores_google(i);
})
.transition()
.ease(d3.easeElastic)
.duration(750)
.attrTween("d", arcTween);
arcpaths.transition()
.ease(d3.easeElastic)
.style("fill", function(d, i){
return colores_google(i);
})
.duration(750)
.attrTween("d",arcTween);
arcpaths.exit().transition()
.ease(d3.easeBounce)
.duration(750)
.attrTween("d", arcTween)
.remove();
function arcTween(b) {
var prev = JSON.parse(JSON.stringify(b));
prev.endAngle = b.previousEndAngle;
var i = d3.interpolate(prev, b);
return function(t) {
return that.getArc()(i(t));
};
}
//_arc paths
var r = that.radius + 40;
var ir = that.radius - 30;
var legendHeight = this.legendPaddingTop;
var ySpace = 18;
var labelPadding = 3;
//draw labels legends
var labels = that.label_group.selectAll("text.labels")
.data(data);
labels.enter().append("svg:text")
.attr("class", "labels")
.attr("dy", function(d, i) {
legendHeight+=ySpace;
return (ySpace * i) + labelPadding;
})
.attr("text-anchor", function(d) {
return "start";
})
.text(function(d) {
return d.label;
});
labels.exit().remove();
var legend = that.legend_group.selectAll("circle").data(data);
legend.enter().append("svg:circle")
.attr("cx", 100)
.attr("cy", function(d, i) {
return ySpace * i;
})
.attr("r", 7)
.attr("width", 18)
.attr("height", 18)
.style("fill", function(d, i) {
return colores_google(i);
});
legend.exit().remove();
//reset legend height
//console.log("optimum height for legend", legendHeight);
$this.find('.legend').attr("height", legendHeight);
/*
//__labels
var starlabels = that.starlabels.selectAll("text")
.data(data);
starlabels.enter()
.append("text")
.attr("text-anchor", "middle")
starlabels.text(function(d) {
return d.label;
})
.each(function(d) {
var a = d.startAngle + (d.endAngle - d.startAngle)/2 - Math.PI/2;
d.cx = Math.cos(a) * (ir+((r-ir)/2));
d.cy = Math.sin(a) * (ir+((r-ir)/2));
d.x = d.x || Math.cos(a) * (r + 20);
d.y = d.y || Math.sin(a) * (r + 20);
var bbox = this.getBBox();
d.sx = d.x - bbox.width/2 - 2;
d.ox = d.x + bbox.width/2 + 2;
d.sy = d.oy = d.y + 5;
})
.transition()
.duration(300)
.attr("x", function(d) {
var a = d.startAngle + (d.endAngle - d.startAngle)/2 - Math.PI/2;
return d.x = Math.cos(a) * (r + 20);
})
.attr("y", function(d) {
var a = d.startAngle + (d.endAngle - d.startAngle)/2 - Math.PI/2;
return d.y = Math.sin(a) * (r + 20);
});
starlabels.exit().remove();
//__labels
//__pointers
that.pointers.append("defs").append("marker")
.attr("id", "circ")
.attr("markerWidth", 6)
.attr("markerHeight", 6)
.attr("refX", 3)
.attr("refY", 3)
.append("circle")
.attr("cx", 3)
.attr("cy", 3)
.attr("r", 3)
.style("fill", "#005a70");
var pointers = that.pointers.selectAll("path.pointer")
.data(data);
pointers.enter()
.append("path")
.attr("class", "pointer")
.style("fill", "none")
.style("stroke", "#005a70")
.attr("marker-end", "url(#circ)");
pointers
.transition()
.duration(300)
.attr("d", function(d) {
if(d.cx > d.ox) {
return "M" + d.sx + "," + d.sy + "L" + d.ox + "," + d.oy + " " + d.cx + "," + d.cy;
} else {
return "M" + d.ox + "," + d.oy + "L" + d.sx + "," + d.sy + " " + d.cx + "," + d.cy;
}
});
pointers.exit().remove();
//__pointers
*/
},
setup: function(el, data, w, h){
var chart = d3.select(el).append("svg")
.attr("class", "chart")
.attr("width", w)
.attr("height", h)
var arcchart = chart.append("g")
.attr("class", "starchart")
.attr("transform", "translate("+w/4+","+h/2+")");
this.arcpaths = arcchart.append("g")
.attr("class", "arcpaths");
this.starlabels = arcchart.append("g")
.attr("class", "labels");
this.pointers = arcchart.append("g")
.attr("class", "pointer");
var margin = 25;
var padding = 15;
this.legendPaddingTop = 30;
var legend = chart.append("g")
.attr("class", "legend")
.attr("width", w/3)
.attr("height", h - 50)
.attr("transform", "translate(" + (w-20) + "," + (h/4) + ")");
this.label_group = legend.append("g")
.attr("class", "label_group")
.attr("transform", "translate(" + (-(w / 3) + 20) + "," + 0 + ")");
this.legend_group = legend.append("g")
.attr("class", "legend_group")
.attr("transform", "translate(" + (-(w / 3) - 100) + "," + 0 + ")");
var radiusControl = 16;
this.dataset = "big";//more than 2 results
if(data.length <=2){
radiusControl = 65;//make the radius smaller to compromise with there being less results
this.dataset = "small";
}
this.radius = w/4 - radiusControl;
this.generateArcs(chart, data);
},
getArc: function(){
var that = this;
var arc = d3.arc()
.innerRadius(function(d, i){
var threshold = 50;
if(that.dataset == "small"){
threshold = 20;
}
return that.radius-threshold;//negative makes it deeper
})
.outerRadius(function(d){
var maxHeight = 120;
var ratio = (d.value/maxHeight * 100)+that.radius;
return ratio;
})
.startAngle(function(d, i){
return d.startAngle;
})
.endAngle(function(d, i){
return d.endAngle;
});
return arc;
}
}
arcGenerator.init($this[0], data, w, h);
In your attrTween function instead of interpolating angle interpolate the value:
function arcTween(b) {
var prev = JSON.parse(JSON.stringify(b));
prev.endAngle = b.previousEndAngle; <-- incorrect
var i = d3.interpolate(prev, b);
Interpolate outer radius like below
function arcTween(b) {
var prev = JSON.parse(JSON.stringify(b));
prev.value = 0;
var i = d3.interpolate(prev, b);
working code here
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.
I want to add draggable dots to a stack of rectangles drawn using a data array. The rectangles, the texts and the dots were drawn fine but when I try to call the drag function for each dot, only the first dot is affected.
The following represents the code and the result.
JSfiddle
var dragme = d3.drag()
.on("start", function (d) {
xx = 0;
yy = 0;
coordinates = [0, 0];
dragdot2 = canvas.append("svg:circle")
.attr("cx", function (d) {
return inputstartx + elementwidth;
})
.attr("cy", function (d, i) {
return inputstarty + (elementheight / 2) + ((elementheight + verticalmargin) * i);
})
.attr("r", function () {
return elementheight / 4;
})
.attr("fill", "black");
dragline = canvas.append("svg:line")
.attr("x1", function (d) {
return inputstartx + elementwidth;
})
.attr("x2", function (d) {
return inputstartx + elementwidth;
})
.attr("y1", function (d, i) {
return inputstarty + (elementheight / 2) + ((elementheight + verticalmargin) * i);
})
.attr("y2", function (d, i) {
return inputstarty + (elementheight / 2) + ((elementheight + verticalmargin) * i);
})
.style("stroke", "rgb(0,150,150)")
.style("stroke-width", "2");
})
.on("drag", function (d) {
coordinates = d3.mouse(this);
xx = coordinates[0];
yy = coordinates[1];
dragline.attr("x2", xx).attr("y2", yy);
dragdot2.attr("cx", xx).attr("cy", yy);
})
.on("end", function (d) {
d3.select(".coors").text(xx + "-" + yy);
});
var inputdragdot = inputcontainer.selectAll("circle")
.data(inputs)
.enter().append("circle")
.attr("class", "dragme")
.attr("cx", function (d) {
return inputstartx + elementwidth;
})
.attr("cy", function (d, i) {
return inputstarty + (elementheight / 2) + ((elementheight + verticalmargin) * i);
})
.attr("r", function () {
return elementheight / 4;
})
.attr("fill", "black")
.call(dragme);
It's not exactly clear what your problem is, but if you want to see the line coming from the respective circle, just get the values of cx and cy:
var thisdragY = d3.select(this).attr("cy");
var thisdragX = d3.select(this).attr("cx");
Here is your fiddle: https://jsfiddle.net/mzt0qf31/
I have a sunburst for which I would like to initiate zoom ability via dropdown. That is when a country name is selected from the drop down menu its part in the sunburst zooms exactly like when its clicked.
js fiddle: http://jsfiddle.net/8wd2xt9n/7/
var width = 960,
height = 700,
radius = Math.min(width, height) / 2;
var b = {
w: 130, h: 30, s: 3, t: 10
};
var x = d3.scale.linear()
.range([0, 2 * Math.PI]);
var y = d3.scale.sqrt()
.range([0, radius]);
var changeArray = [100,80,60,0, -60, -80, -100];
var colorArray = ["#67000d", "#b73e43", "#d5464a", "#f26256", "#fb876e", "#fca78e", "#fcbba1", "#fee0d2", "#fff5f0"];
var svg = d3.select("#diagram").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.Total; });
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)); });
console.log(arc)
function checkIt(error, root){
initializeBreadcrumbTrail();
//intilaize dropdown
if (error) throw error;
var g = svg.selectAll("g")
.data(partition.nodes(root))
.enter().append("g");
var path = g.append("path")
.attr("d", arc)
.style("fill", function(d) { var color;
if(d.Total_Change > 100)
{color = colorArray[0]
}
else if(d.Total_Change > 0 && d.Total_Change < 100)
{color = colorArray[1]
}
else
{
color = colorArray[2]
}
d.color = color;
return color})
.on("click", click)
.on("mouseover", mouseover);
var tooltip = d3.select("body")
.append("div")
.attr("id", "tooltips")
.style("position", "absolute")
.style("background-color", "#fff")
.style("z-index", "10")
.style("visibility", "hidden");
g.on("mouseover", function(d){return tooltip.style("visibility", "visible")
.html("<div class=" + "tooltip_container>"+"<h4 class=" + "tooltip_heading>" +d.name.replace(/[_-]/g, " ") +"</h4>" +"<br>" + "<p> Emissions 2013:" + " " + "<br>" + d.Total + " " +"<span>in Million Tons</span></p>"+ "<br>"+ "<p> Change in Emissions: <span>" + (d.Total_Change/d.Total*100).toPrecision(3) + "%" + "</span></p>"+"</div>" );})
.on("mousemove", function(){return tooltip.style("top",(d3.event.pageY-10)+"px").style("left",(d3.event.pageX+10)+"px");})
.on("mouseout", function(){return tooltip.style("visibility", "hidden");});
//creating a dropdown
var dropDown = d3.select("#dropdown_container")
.append("select")
.attr("class", "selection")
.attr("name", "country-list");
var nodeArr = partition.nodes(root);
var options = dropDown.selectAll("option")
.data(nodeArr)
.enter()
.append("option");
options.text(function (d) {
var prefix = new Array(d.depth + 1);
var dropdownValues = d.name.replace(/[_-]/g, " ");
return dropdownValues;
}).attr("value", function (d) {
var dropdownValues = d.name;
return dropdownValues;
});
// transition on click
function click(d) {
// fade out all text elements
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(".selection").on("change", function changePie() {
var value = this.value;
var index = this.selectedIndex;
var dataObj = nodeArr[index];
path[0].forEach(function(p){
var data = d3.select(p).data();//get the data from the path
if (data[0].name === value){
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); });
}
});
}
});
console.log(this.value + " :c " + dataObj["Iron and steel"] + " in " + (dataObj.parent && dataObj.parent.name));
});
};
d3.json("https://gist.githubusercontent.com/heenaI/cbbc5c5f49994f174376/raw/55c672bbca7991442f1209cfbbb6ded45d5e8c8e/data.json", checkIt);
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 initializeBreadcrumbTrail() {
// Add the svg area.
var trail = d3.select("#sequence").append("svg:svg")
.attr("width", width)
.attr("height", 50)
.attr("id", "trail");
// Add the label at the end, for the percentage.
trail.append("svg:text")
.attr("id", "endlabel")
.style("fill", "#000");
}
function breadcrumbPoints(d, i) {
var points = [];
points.push("0,0");
points.push(b.w + ",0");
points.push(b.w + b.t + "," + (b.h / 2));
points.push(b.w + "," + b.h);
points.push("0," + b.h);
if (i > 0) { // Leftmost breadcrumb; don't include 6th vertex.
points.push(b.t + "," + (b.h / 2));
}
return points.join(" ");
}
// Update the breadcrumb trail to show the current sequence and percentage.
function updateBreadcrumbs(nodeArray) {
// Data join; key function combines name and depth (= position in sequence).
var g = d3.select("#trail")
.selectAll("g")
.data(nodeArray, function(d) { return d.name.replace(/[_-]/g, " ") + d.Total; });
// Add breadcrumb and label for entering nodes.
var entering = g.enter().append("svg:g");
entering.append("svg:polygon")
.attr("points", breadcrumbPoints)
.style("fill", "#d3d3d3");
entering.append("svg:text")
.attr("x", (b.w + b.t) / 2)
.attr("y", b.h / 2)
.attr("dy", "0.35em")
.attr("text-anchor", "middle")
.text(function(d) { return d.name.replace(/[_-]/g, " "); });
// Set position for entering and updating nodes.
g.attr("transform", function(d, i) {
return "translate(" + i * (b.w + b.s) + ", 0)";
});
// Remove exiting nodes.
g.exit().remove();
// Now move and update the percentage at the end.
d3.select("#trail").select("#endlabel")
.attr("x", (nodeArray.length + 0.5) * (b.w + b.s))
.attr("y", b.h / 2)
.attr("dy", "0.35em")
.attr("text-anchor", "middle");
// Make the breadcrumb trail visible, if it's hidden.
d3.select("#trail")
.style("visibility", "");
}
function getAncestors(node) {
var path = [];
var current = node;
while (current.parent) {
path.unshift(current);
current = current.parent;
}
return path;
}
function mouseover(d) {
var sequenceArray = getAncestors(d);
updateBreadcrumbs(sequenceArray);}
Add this one line and it will work :)
Inside your selection change add this var d = data[0]; as shown below:
d3.select(".selection").on("change", function changePie() {
var value = this.value;
var index = this.selectedIndex;
var dataObj = nodeArr[index];
path[0].forEach(function(p){
var data = d3.select(p).data();//get the data from the path
if (data[0].name === value){
var d = data[0];//this line is missing
path.transition()
Working code here
Other option is to call the click function on selection change like below
d3.select(".selection").on("change", function changePie() {
var value = this.value;
var index = this.selectedIndex;
var dataObj = nodeArr[index];
path[0].forEach(function (p) {
var data = d3.select(p).data(); //get the data from the path
if (data[0].name === value) {
console.log(data)
click(data[0]);//call the click function
}
Working code here
Hope this helps!
I'm trying to add text to the partition-sunburst example. I have followed a hint given on Google Group and I was able to add the text. Now I want to rotate it. The new angles seem to be correct, but all the text get gathered in one point.
This is the piece of code where I add and rotate the text:
var text = vis.data([json]).selectAll("text")
.data(partition.nodes)
.enter().append("svg:text")
.attr("x", function(d) { return radius * Math.cos(d.x + d.dx/2); } )
.attr("y", function(d) { return radius * Math.sin(d.x + d.dx/2); } )
.attr("dy", ".31em")
.style("font-size", "5px")
.attr("transform", function(d) {
var angle = (2 * Math.PI - d.x) * (180/Math.PI);
var rotation = "rotate(" + angle + ")";
console.log(rotation);
console.log("d.x=" + d.x + ", d.y=" + d.y);
return rotation;
})
.text(function(d) { return d.name; });
This is an image of what I get:
and this is the full script:
var width = 1000,
height = 1000,
radius = 350,
x = d3.scale.linear().range([0, 2 * Math.PI]),
y = d3.scale.pow().exponent(1.3).domain([0, 1]).range([0, radius]),
padding = 5,
color = d3.scale.category20c();
var vis = d3.select("#chart").append("svg")
.attr("width", width)
.attr("height", height)
.append("g")
.attr("transform", "translate(" + width / 2 + "," + height / 2 + ")");
var partition = d3.layout.partition()
.sort(null)
.size([2 * Math.PI, radius * radius])
.value(function(d) { return 1; });
var arc = d3.svg.arc()
.startAngle(function(d) { return d.x; })
.endAngle(function(d) { return d.x + d.dx; })
.innerRadius(function(d) { return Math.sqrt(d.y); })
.outerRadius(function(d) { return Math.sqrt(d.y + d.dy); });
d3.json("fracking.json", function(json) {
var path = vis.data([json]).selectAll("path")
.data(partition.nodes)
.enter().append("svg:path")
.attr("display", function(d) { return d.depth ? null : "none"; })
// hide inner ring
.attr("d", arc)
.attr("fill-rule", "evenodd")
.style("stroke", "#fff")
.style("fill", function(d) { return color((d.children ? d : d.parent).name); })
.each(stash);
var text = vis.data([json]).selectAll("text")
.data(partition.nodes)
.enter().append("svg:text")
.attr("x", function(d) { return radius * Math.cos(d.x + d.dx/2); } )
.attr("y", function(d) { return radius * Math.sin(d.x + d.dx/2); } )
.attr("dy", ".31em")
.style("font-size", "5px")
.attr("transform", function(d) {
var angle = (2 * Math.PI - d.x) * (180/Math.PI);
var rotation = "rotate(" + angle + ")";
console.log(rotation);
console.log("d.x=" + d.x + ", d.y=" + d.y);
return rotation;
})
.text(function(d) { return d.name; });
d3.select("#size").on("click", function() {
path
.data(partition.value(function(d) { return d.size; }))
.transition()
.duration(1500)
.attrTween("d", arcTween);
d3.select("#size").classed("active", true);
d3.select("#count").classed("active", false);
});
d3.select("#count").on("click", function() {
path
.data(partition.value(function(d) { return 1; }))
.transition()
.duration(1500)
.attrTween("d", arcTween);
d3.select("#size").classed("active", false);
d3.select("#count").classed("active", true);
});
});
// 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);
};
}
I think you need to specify the center of rotation. Try this:
var text = vis.data([json]).selectAll("text")
.data(partition.nodes)
.enter().append("svg:text")
.attr("x", function(d) { return radius * Math.cos(d.x + d.dx/2); } )
.attr("y", function(d) { return radius * Math.sin(d.x + d.dx/2); } )
.attr("dy", ".31em")
.style("font-size", "5px")
.attr("transform", function(d) {
var angle = (2 * Math.PI - d.x) * (180/Math.PI);
var rotation = "rotate(" + angle + "," + d3.select(this).attr("x") + "," d3.select(this).attr("y") +")";
console.log(rotation);
console.log("d.x=" + d.x + ", d.y=" + d.y);
return rotation;
})
.text(function(d) { return d.name; });