I want to create rotate animation in a d3.js chart. With help of Gilsha I was able to create a chart like this :
Now on click of any of the outer circles i want to rotate all circles and align the one which was clicked in the place of "hllooo" circle.
I have copied one angletween which is rotating the circles but the problem is that text inside that circle also gets rotated and creates something like this:
As you can see the text is also rotated which i dont want. The code for transform function is:
function angleTween(d, i) {
var angle = 360 - ((i + 1) * 70);
var i = d3.interpolate(0, 90);
return function (t) {
return "rotate(" + i(t) + ")";
};
}
So how to keep text unrotated and just rotate the shape ?
Calulcate the x and y attributes of text elements by using SVGPoint matrixTransform.
earth.on('click', function() {
texts.style("opacity", 0);
earthLayer
.transition()
.duration(2000)
.attrTween("transform", angleTween)
.each("end", function() {
var svgEl = this.ownerSVGElement;
var angle = d3.select(this).attr("transform").match(/\d+/g)[0];
var matrix = svgEl.createSVGMatrix().rotate(angle);
texts.each(function(d, i) {
var point = this.ownerSVGElement.createSVGPoint();
point.x = +d.cx;
point.y = +d.cy;
point = point.matrixTransform(matrix);
d3.select(this).attr("x", point.x).attr("y", point.y);
});
texts.style("opacity", 1);
});
});
var now = d3.time.year.floor(new Date());
var spacetime = d3.select('body');
var width = 960,
height = 700,
radius = Math.min(width, height);
var radii = {
"sun": radius / 6,
"earthOrbit": radius / 2.5,
"earth": radius / 15,
"moonOrbit": radius / 16,
"moon": radius / 96
};
// Space
var svg = spacetime.append("svg")
.attr("width", width)
.attr("height", height)
.append("g")
.attr("transform", "translate(" + width / 2 + "," + height / 2 + ")");
// Sun
var sun = svg.append("circle")
.attr("class", "sun")
.attr("r", radii.sun)
//.style("fill", "rgba(255, 204, 0, 1.0)");
.style("stroke", "#f58c2e")
.style("stroke-width", "10")
.style("fill", "none");
// Earth's orbit
var orbit = svg.append("circle")
.attr("class", "earthOrbit")
.attr("r", radii.earthOrbit)
.style("fill", "none")
.style("stroke", "#bababa")
.style("stroke-width", "30");
// Current position of Earth in its orbit
var earthOrbitPosition = d3.svg.arc()
.outerRadius(radii.earthOrbit + 1)
.innerRadius(radii.earthOrbit - 1)
.startAngle(0)
.endAngle(0);
svg.append("path")
.attr("class", "earthOrbitPosition")
.attr("d", earthOrbitPosition)
.style("fill", "rgba(255, 204, 0, 0.75)");
// Time of day
var day = d3.svg.arc()
.outerRadius(radii.earth)
.innerRadius(0)
.startAngle(0)
.endAngle(0);
svg.append("path")
.attr("class", "day")
.attr("d", day)
.attr("transform", "translate(0," + -radii.earthOrbit + ")")
.style("fill", "rgba(53, 110, 195, 1.0)");
// Current position of the Moon in its orbit
var moonOrbitPosition = d3.svg.arc()
.outerRadius(radii.moonOrbit + 1)
.innerRadius(radii.moonOrbit - 1)
.startAngle(0)
.endAngle(0);
svg.append("path")
.attr("class", "moonOrbitPosition")
.attr("d", moonOrbitPosition)
.attr("transform", "translate(0," + -radii.earthOrbit + ")")
.style("fill", "rgba(113, 170, 255, 0.75)");
function getCirclePoints(points, radius, center) {
var circlePositions = [];
var slice = 2 * Math.PI / points;
for (var i = 0; i < points; i++) {
var angle = slice * i;
var newX = (center.X + radius * Math.cos(angle));
var newY = (center.Y + radius * Math.sin(angle));
circlePositions.push({
cx: newX,
cy: newY
});
}
return circlePositions;
}
var circlePositions = getCirclePoints(10, radii.earthOrbit, {
X: 0,
Y: 0
});
var earthLayer = svg.append("g").classed("earthLayer", true);
var textLayer = svg.append("g").classed("textLayer", true);
var earth = earthLayer.selectAll(".earth").data(circlePositions)
.enter()
.append("circle")
.attr("cx", function(d) {
return d.cx;
})
.attr("cy", function(d) {
return d.cy;
})
.attr("class", "earth")
.style("fill", "white")
.attr("r", radii.earth)
.style("stroke", "#bababa")
.style("stroke-width", "10");
texts = textLayer.selectAll("text").data(circlePositions).enter().append("text").attr("x", function(d) {
return d.cx
}).attr("dx", -radii.earth / 2).attr("y", function(d) {
return d.cy
}).text(function(d, i) {
if (i == 0) return "hllooo";
else return "hllooo" + i;
});
earth.on('click', function() {
texts.style("opacity", 0);
earthLayer
.transition()
.duration(2000)
.attrTween("transform", angleTween)
.each("end", function() {
var svgEl = this.ownerSVGElement;
var angle = d3.select(this).attr("transform").match(/\d+/g)[0];
var matrix = svgEl.createSVGMatrix().rotate(angle);
texts.each(function(d, i) {
var point = this.ownerSVGElement.createSVGPoint();
point.x = +d.cx;
point.y = +d.cy;
point = point.matrixTransform(matrix);
d3.select(this).attr("x", point.x).attr("y", point.y);
});
texts.style("opacity", 1);
});
});
function angleTween(d, i) {
var angle = 360 - ((i + 1) * 70);
var i = d3.interpolate(0, angle);
return function(t) {
return "rotate(" + i(t) + ")";
};
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>
Hope this helps.
Related
I am trying to figure out how to arrange the labels so that they do not overlap. Here is a picture of the chart
As you can see, with really small values, the text labels overlap. I tried to iterate over each text element and modify it's position, but that doesn't seem to be working. You can see at the bottom of this function that I tried to get the position of each text element and then modify it. What am I doing wrong? I've been at it for hours.
_renderDonutChart() {
let self = this;
// console.log("Donut Chart is beginning render")
let textOffset = 14;
self.graph.data[0].forEach(function (d) {
d.value = +d.value;
})
console.log(self.graph.data[0])
let boxSize = (self.options.radius + self.options.padding) * 2;
let parent = d3.select(self.ui.parent);
//let color = d3.scaleOrdinal(['#dc8710', '#9e3400', '#f19b12']);
let color = d3.scaleOrdinal(d3.schemeCategory20c);
let svg = parent.append('svg')
.attr('width', boxSize * 2)
.attr('height', boxSize)
.attr('transform', 'translate(-111,0)')
.append('g')
.attr('transform', 'translate(' + boxSize + ',' + boxSize / 2 + ')');
svg.append('g')
.attr('class', 'slices')
svg.append("g")
.attr("class", "labelName")
svg.append("g")
.attr("class", "labelValue")
svg.append("g")
.attr("class", "lines")
svg.append("div")
.attr("class", "progress-circle__box progress-circle__box--victorytype")
let arc = d3.arc()
.innerRadius(self.options.radius - self.options.border)
.outerRadius(self.options.radius);
let outerArc = d3.arc()
.innerRadius((self.options.radius - self.options.border) * 1.2)
.outerRadius((self.options.radius) * 1.2);
let legendRectSize = self.options.radius * 0.05;
let legendSpacing = self.options.radius * 0.02;
let pie = d3.pie()
.value(function(d) { return d.value; })
.sort(null);
let slice = svg.select('.slices')
.selectAll('path.slice')
.data(pie(self.graph.data[0]))
.enter()
.append('path')
.attr("class", "slice")
.attr('d', arc)
.attr('fill', function(d, i) {
return color(d.data.label);
})
.transition().duration(1000)
.attrTween("d", function(d) {
this._current = this._current || 0;
var interpolate = d3.interpolate(this._current, d);
this._current = interpolate(0);
return function(t) {
return arc(interpolate(t));
};
})
function midAngle(d){
return d.startAngle + (d.endAngle - d.startAngle)/2;
}
let text = svg.select(".labelName").selectAll("text")
.data(pie(self.graph.data[0]))
.enter()
.append("text")
.attr('class', 'label')
.attr("dy", ".35em")
.attr('transform', function(d) {
// effectively computes the centre of the slice.
// see https://github.com/d3/d3-shape/blob/master/README.md#arc_centroid
var pos = outerArc.centroid(d);
// changes the point to be on left or right depending on where label is.
pos[0] = self.options.radius * 0.97 * (midAngle(d) < Math.PI ? 1 : -1);
return 'translate(' + pos + ')';
})
.style('text-anchor', function(d) {
// if slice centre is on the left, anchor text to start, otherwise anchor to end
return (midAngle(d)) < Math.PI ? 'start' : 'end';
})
.style("fill", "white")
.text(function(d) {
return (" " + d.data.label+": " +d.value+"");
})
.transition().duration(1000)
.attrTween("transform", function(d) {
this._current = this._current || d;
var interpolate = d3.interpolate(this._current, d);
this._current = interpolate(0);
return function(t) {
var d2 = interpolate(t);
var pos = outerArc.centroid(d2);
pos[0] = self.options.radius * (midAngle(d2) < Math.PI ? 1 : -1);
return "translate("+ pos +")";
};
})
.styleTween("text-anchor", function(d){
this._current = this._current || d;
var interpolate = d3.interpolate(this._current, d);
this._current = interpolate(0);
return function(t) {
var d2 = interpolate(t);
return midAngle(d2) < Math.PI ? "start":"end";
};
})
.text(function(d) {
return (d.data.label+": "+d.value+"%");
})
let polyline = svg.select(".lines").selectAll("polyline")
.data(pie(self.graph.data[0]))
.enter()
.append("polyline")
.attr('points', function(d) {
var pos = outerArc.centroid(d);
pos[0] = self.options.radius * 0.95 * (midAngle(d) < Math.PI ? 1 : -1);
return [arc.centroid(d), outerArc.centroid(d), pos]
})
.style("fill", "none")
.style("stroke", "white")
.style("stroke-width", "1px");
let prev;
text.each(function(d, i) {
if(i > 0) {
let thisbb = this.getBoundingClientRect(),
prevbb = prev.getBoundingClientRect();
// move if they overlap
console.log(thisbb.left);
console.log(prevbb.right);
if(!(thisbb.right < prevbb.left ||
thisbb.left > prevbb.right ||
thisbb.bottom < prevbb.top ||
thisbb.top > prevbb.bottom)) {
var ctx = thisbb.left + (thisbb.right - thisbb.left)/2,
cty = thisbb.top + (thisbb.bottom - thisbb.top)/2,
cpx = prevbb.left + (prevbb.right - prevbb.left)/2,
cpy = prevbb.top + (prevbb.bottom - prevbb.top)/2,
off = Math.sqrt(Math.pow(ctx - cpx, 2) + Math.pow(cty - cpy, 2))/2;
d3.select(this).attr("transform",
"translate(" + Math.cos(((d.startAngle + d.endAngle - Math.PI) / 2)) *
(self.options.radius + textOffset + off) + "," +
Math.sin((d.startAngle + d.endAngle - Math.PI) / 2) *
(self.options.radius + textOffset + off) + ")");
}
}
prev = this;
});
// console.log("Donut Chart is ending render")
}
I had the same issue. The best solution for me was to increase the size of the Svg area (based on current window) and add some padding to the legends based on the radius of the donut.
ie:
var margin = {top: 20, right: 100, bottom: 30, left: 40};
var svgWidth = window.innerWidth - (window.innerWidth/4);
var width = svgWidth,
height = (Math.min(width) / 2) + 100,
radius = Math.min(width, height) / 3;
var legendRectSize = (radius * 0.08);
var legendSpacing = (radius * 0.05);
This worked for me until the item count became too high. I amended from labels around the edge to display in the center, on slice/arc hover events. (There is an animation on the data value, which is why they are slanted in this snapshot)
I thought this made the unreadable data labels less confusing.
I thing you had same problem with this, you need to add more margin to your pie chart segment, assume your pie return 3% value, and you draw a text with line, it will stack on another, you need to devine margin if that value < 3 draw text with y+margin
if(percent<3){
o[1]
pos[1] += i*15
}
//return [label.centroid(d),[o[0],0[1]] , pos];
return [label.centroid(d),[o[0],pos[1]] , pos];
})
try to append this code structure to your chart
Can someone help me implementing a spiral chart similar to the one below using d3.js?
I've just got the basic spiral plot (a simple one) as of now but not been able to append bars to the plot based on the timeline as shown in the image. I'm trying out a few things (if you see the commented code).
Here's my fiddle, and my code:
var width = 400,
height = 430,
axes = 12,
tick_axis = 9,
start = 0,
end = 2.25;
var theta = function(r) {
return 2 * Math.PI * r;
};
var angle = d3.scale.linear()
.domain([0, axes]).range([0, 360])
var r = d3.min([width, height]) / 2 - 40;
var r2 = r;
var radius = d3.scale.linear()
.domain([start, end])
.range([0, r]);
var svg = d3.select("#chart").append("svg")
.attr("width", width)
.attr("height", height)
.append("g")
.attr("transform", "translate(" + width / 2 + "," + (height / 2 + 8) + ")");
var points = d3.range(start, end + 0.001, (end - start) / 1000);
var spiral = d3.svg.line.radial()
.interpolate("cardinal")
.angle(theta)
.radius(radius);
var path = svg.selectAll(".spiral")
.data([points])
.enter().append("path")
.attr("class", "spiral")
.attr("d", spiral)
var z = d3.scale.category20();
var circles = svg.selectAll('.circle')
.data(points);
/* circles.enter().append('circle')
.attr('r', 5)
.attr('transform', function(d) { return 'translate(' + d + ')'})
.style('fill', function(d) { return z(d); });
*/
var circle = svg.append("circle")
.attr("r", 13)
.attr("transform", "translate(" + points[0] + ")");
var movingCircle = circle.transition().duration(4000)
.attrTween('transform', translateAlongPath(path.node()))
// .attr('cx', function(d) { return radius(d) * Math.cos(theta(d))})
// .attr('cy', function(d) { return radius(d) * Math.sin(theta(d))})
function translateAlongPath(path) {
var l = path.getTotalLength();
return function(d, i, a) {
return function(t) {
var p = path.getPointAtLength(t * l);
//console.log(p)
return "translate(" + p.x + "," + p.y + ")";
};
};
}
function pathXY(path) {
var l = path.getTotalLength();
var start = 0;
/* for(i=start; i<l; i++) {
var point = path.getPointAtLength(i);
svg.append('rect').transition().duration(400).attr('transform', 'translate(' + point.x +','+point.y+')')
.attr('width', 10).attr('height', 30).style('fill', z);
}*/
}
pathXY(path.node());
/*var test = translateAlongPath(path.node())()();
//console.log(test)
var bars = svg.selectAll('.bar')
.data(points).enter().append('rect').transition().duration(2000)
// .attrTween('transform', translateAlongPath(path.node()))
.attr('class', 'bar')
.attr('width', 10)
.attr('height', 20)
.style('fill', function(d) { return z(d)});
*/
var rect = svg.append('rect').attr('width', 10).attr('height', 10);
rect.transition().duration(3400)
.attrTween('transform', translateAlongPath(path.node()));
It'd be great to have a few similar examples (i.e. spiral timeline plot).
Thanks.
Glad you came back and updated your question, because this is an interesting one. Here's a running minimal implementation. I've commented it ok, so let me know if you have any questions...
<!DOCTYPE html>
<html>
<head>
<script data-require="d3#4.0.0" data-semver="4.0.0" src="https://d3js.org/d3.v4.js"></script>
</head>
<body>
<div id="chart"></div>
<script>
var width = 500,
height = 500,
start = 0,
end = 2.25,
numSpirals = 4;
var theta = function(r) {
return numSpirals * Math.PI * r;
};
var r = d3.min([width, height]) / 2 - 40;
var radius = d3.scaleLinear()
.domain([start, end])
.range([40, r]);
var svg = d3.select("#chart").append("svg")
.attr("width", width)
.attr("height", height)
.append("g")
.attr("transform", "translate(" + width / 2 + "," + height / 2 + ")");
// create the spiral, borrowed from http://bl.ocks.org/syntagmatic/3543186
var points = d3.range(start, end + 0.001, (end - start) / 1000);
var spiral = d3.radialLine()
.curve(d3.curveCardinal)
.angle(theta)
.radius(radius);
var path = svg.append("path")
.datum(points)
.attr("id", "spiral")
.attr("d", spiral)
.style("fill", "none")
.style("stroke", "steelblue");
// fudge some data, 2 years of data starting today
var spiralLength = path.node().getTotalLength(),
N = 730,
barWidth = (spiralLength / N) - 1;
var someData = [];
for (var i = 0; i < N; i++) {
var currentDate = new Date();
currentDate.setDate(currentDate.getDate() + i);
someData.push({
date: currentDate,
value: Math.random()
});
}
// here's our time scale that'll run along the spiral
var timeScale = d3.scaleTime()
.domain(d3.extent(someData, function(d){
return d.date;
}))
.range([0, spiralLength]);
// yScale for the bar height
var yScale = d3.scaleLinear()
.domain([0, d3.max(someData, function(d){
return d.value;
})])
.range([0, (r / numSpirals) - 30]);
// append our rects
svg.selectAll("rect")
.data(someData)
.enter()
.append("rect")
.attr("x", function(d,i){
// placement calculations
var linePer = timeScale(d.date),
posOnLine = path.node().getPointAtLength(linePer),
angleOnLine = path.node().getPointAtLength(linePer - barWidth);
d.linePer = linePer; // % distance are on the spiral
d.x = posOnLine.x; // x postion on the spiral
d.y = posOnLine.y; // y position on the spiral
d.a = (Math.atan2(angleOnLine.y, angleOnLine.x) * 180 / Math.PI) - 90; //angle at the spiral position
return d.x;
})
.attr("y", function(d){
return d.y;
})
.attr("width", function(d){
return barWidth;
})
.attr("height", function(d){
return yScale(d.value);
})
.style("fill", "steelblue")
.style("stroke", "none")
.attr("transform", function(d){
return "rotate(" + d.a + "," + d.x + "," + d.y + ")"; // rotate the bar
});
// add date labels
var tF = d3.timeFormat("%b %Y"),
firstInMonth = {};
svg.selectAll("text")
.data(someData)
.enter()
.append("text")
.attr("dy", 10)
.style("text-anchor", "start")
.style("font", "10px arial")
.append("textPath")
// only add for the first of each month
.filter(function(d){
var sd = tF(d.date);
if (!firstInMonth[sd]){
firstInMonth[sd] = 1;
return true;
}
return false;
})
.text(function(d){
return tF(d.date);
})
// place text along spiral
.attr("xlink:href", "#spiral")
.style("fill", "grey")
.attr("startOffset", function(d){
return ((d.linePer / spiralLength) * 100) + "%";
})
</script>
</body>
</html>
I want to create something similar to this. I am going to use d3.js. I have created outer orbit and inner circle. But how to calculate position of outer rings on the outer orbit ?
The number of outer circles is dynamic.
A point at angle theta on the circle whose centre is (x0,y0) and whose radius is r is (x0 + r cos theta, y0 + r sin theta). Now choose theta values evenly spaced between 0 and 2pi.
Reference: Calculating the position of points in a circle
var orbit = svg.append("circle")
.attr("class", "earthOrbit")
.attr("r", radii.earthOrbit)
.style("fill", "none")
.style("stroke", "#bababa")
.style("stroke-width", "30");
var circlePositions = getCirclePoints(15, radii.earthOrbit, {
X: 0,
Y: 0
});
svg.selectAll(".earth").data(circlePositions)
.enter()
.append("circle")
.attr("class", "earth")
.style("fill", "white")
.attr("r", radii.earth)
.attr("cx", function(d) {
return d.cx
})
.attr("cy", function(d) {
return d.cy
})
.style("stroke", "#bababa")
.style("stroke-width", "10");
var now = d3.time.year.floor(new Date());
var spacetime = d3.select('body');
var width = 960,
height = 700,
radius = Math.min(width, height);
var radii = {
"sun": radius / 6,
"earthOrbit": radius / 2.5,
"earth": radius / 32,
"moonOrbit": radius / 16,
"moon": radius / 96
};
// Space
var svg = spacetime.append("svg")
.attr("width", width)
.attr("height", height)
.append("g")
.attr("transform", "translate(" + width / 2 + "," + height / 2 + ")");
// Sun
var sun = svg.append("circle")
.attr("class", "sun")
.attr("r", radii.sun)
//.style("fill", "rgba(255, 204, 0, 1.0)");
.style("stroke", "#f58c2e")
.style("stroke-width", "10")
.style("fill", "none");
// Earth's orbit
var orbit = svg.append("circle")
.attr("class", "earthOrbit")
.attr("r", radii.earthOrbit)
.style("fill", "none")
.style("stroke", "#bababa")
.style("stroke-width", "30");
// Current position of Earth in its orbit
var earthOrbitPosition = d3.svg.arc()
.outerRadius(radii.earthOrbit + 1)
.innerRadius(radii.earthOrbit - 1)
.startAngle(0)
.endAngle(0);
svg.append("path")
.attr("class", "earthOrbitPosition")
.attr("d", earthOrbitPosition)
.style("fill", "rgba(255, 204, 0, 0.75)");
// Time of day
var day = d3.svg.arc()
.outerRadius(radii.earth)
.innerRadius(0)
.startAngle(0)
.endAngle(0);
svg.append("path")
.attr("class", "day")
.attr("d", day)
.attr("transform", "translate(0," + -radii.earthOrbit + ")")
.style("fill", "rgba(53, 110, 195, 1.0)");
// Current position of the Moon in its orbit
var moonOrbitPosition = d3.svg.arc()
.outerRadius(radii.moonOrbit + 1)
.innerRadius(radii.moonOrbit - 1)
.startAngle(0)
.endAngle(0);
svg.append("path")
.attr("class", "moonOrbitPosition")
.attr("d", moonOrbitPosition)
.attr("transform", "translate(0," + -radii.earthOrbit + ")")
.style("fill", "rgba(113, 170, 255, 0.75)");
function getCirclePoints(points, radius, center) {
var circlePositions = [];
var slice = 2 * Math.PI / points;
for (var i = 0; i < points; i++) {
var angle = slice * i;
var newX = (center.X + radius * Math.cos(angle));
var newY = (center.Y + radius * Math.sin(angle));
circlePositions.push({
cx: newX,
cy: newY
});
}
return circlePositions;
}
var circlePositions = getCirclePoints(15, radii.earthOrbit, {
X: 0,
Y: 0
});
svg.selectAll(".earth").data(circlePositions)
.enter()
.append("circle")
.attr("class", "earth")
.style("fill", "white")
.attr("r", radii.earth)
.attr("cx", function(d) {
return d.cx
})
.attr("cy", function(d) {
return d.cy
})
.style("stroke", "#bababa")
.style("stroke-width", "10");
.earth {}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>
Just wondering is it possible to do something like this with d3?
http://jsfiddle.net/8T7Ew/
Where when you click on a certain pie slice the slice moves on click?
Have the pie created up to this point just wondering if I can add this feature
<!DOCTYPE html>
<meta charset="utf-8">
<style>
body {
font: 10px sans-serif;
}
.arc path {
stroke: #fff;
}
</style>
<body>
<script src="http://d3js.org/d3.v3.min.js"></script>
<script>
var width = 960,
height = 500,
radius = Math.min(width, height) / 2;
var color = d3.scale.ordinal()
.range(["#98abc5", "#8a89a6", "#7b6888", "#6b486b", "#a05d56", "#d0743c", "#ff8c00"]);
var arc = d3.svg.arc()
.outerRadius(radius - 10)
.innerRadius(0);
var pie = d3.layout.pie()
.sort(null)
.value(function(d) { return d.population; });
var svg = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height)
.append("g")
.attr("transform", "translate(" + width / 2 + "," + height / 2 + ")");
d3.csv("data.csv", function(error, data) {
data.forEach(function(d) {
d.population = +d.population;
});
var g = svg.selectAll(".arc")
.data(pie(data))
.enter().append("g")
.attr("class", "arc");
g.append("path")
.attr("d", arc)
.style("fill", function(d) { return color(d.data.age); });
g.append("text")
.attr("transform", function(d) { return "translate(" + arc.centroid(d) + ")"; })
.attr("dy", ".35em")
.style("text-anchor", "middle")
.text(function(d) { return d.data.age; });
});
</script>
Data is coming from a csv file.
Thanks
You can increase the arc radius of pie for highlighting. JSFiddle
var arcOver = d3.svg.arc()
.outerRadius(r + 10);
g.append("path")
.attr("d", arc)
.style("fill", function(d) { return color(d.data.age); })
.on("mouseenter", function(d) {
d3.select(this)
.attr("stroke","white")
.transition()
.duration(1000)
.attr("d", arcOver)
.attr("stroke-width",6);
})
.on("mouseleave", function(d) {
d3.select(this).transition()
.attr("d", arc)
.attr("stroke","none");
});
I prefer changing Radius rather than stroke as it gives you smooth and nicer animation...
Using a function like this:
function pathEnter() {
t = d3.select(this);
t.transition()
.attr('d', pathIn);
}
Run this code below to see the interaction:
<!DOCTYPE html>
<meta charset="utf-8">
<style>
.arc text {
font: 10px sans-serif;
text-anchor: middle;
}
.arc path {
stroke: #fff;
}
</style>
<svg width="520" height="280"></svg>
<script src="https://d3js.org/d3.v4.min.js"></script>
<script>
var svg = d3.select("svg"),
width = +svg.attr("width"),
height = +svg.attr("height"),
radius = Math.min(width, height) / 2 - 20,
g = svg.append("g").attr("transform", "translate(" + width / 2 + "," + height / 2 + ")");
var color = d3.scaleOrdinal(d3.schemeCategory10);
var pie = d3.pie()
.sort(null)
.value(function(d) {
return d.population;
});
var path = d3.arc()
.outerRadius(radius)
.innerRadius(0);
var pathIn = d3.arc()
.outerRadius(radius + 6)
.innerRadius(0);
function pathEnter() {
t = d3.select(this);
t.transition()
.attr('d', pathIn);
}
function pathOut() {
t = d3.select(this);
t.transition()
.attr('d', path);
}
var label = d3.arc()
.outerRadius(radius - 40)
.innerRadius(radius - 40);
data = [{
age: '<5',
population: '2704659'
},
{
age: '5-13',
population: '4499890'
},
{
age: '14-17',
population: '2159981'
},
{
age: '18-24',
population: '3853788'
},
{
age: '25-44',
population: '14106543'
},
{
age: '45-64',
population: '8819342'
},
{
age: '≥65',
population: '612463'
},
];
data.population = +data.population;
var arc = g.selectAll(".arc")
.data(pie(data))
.enter().append("g")
.attr("class", "arc");
arc.append("path")
.attr("d", path)
.on('mouseenter', pathEnter)
.on('mouseout', pathOut)
.attr("fill", function(d) {
return color(d.data.age);
});
arc.append("text")
.attr("transform", function(d) {
return "translate(" + label.centroid(d) + ")";
})
.attr("dy", "0.35em")
.text(function(d) {
return d.data.age;
});
</script>
Using the attribute transform="translate(x, y)" would do the actual moving of each slice of the pie.
http://jsfiddle.net/qkHK6/3306/
Building on #Gilsha's answer (I know this question is old, but I figured I'd put this answer up for archival purposes)...
g.append("path")
.attr("d", arc)
.attr("opacity", "1.0")
.on("mouseenter", function (d) {
var arcOver = d3.arc()
.outerRadius(radius).innerRadius(0).startAngle(d.startAngle + 0.01).endAngle(d.endAngle - 0.01);
var transformText = getTranslate(d.startAngle + 0.01, d.endAngle - 0.01, 20);
d3.select(this)
.attr("d", arcOver)
.transition()
.duration(200).ease(d3.easeBack)
.attr("transform", transformText)
.attr("style", "fill: rgb(102, 102, 102)");
})
.on("mouseleave", function (d) {
d3.select(this)
.attr("d", arc)
.transition().ease(d3.easeBack)
.duration(600)
.attr("transform", "translate(0,0)")
.attr("style", "fill: " + color(d.data));
})
.style("fill", function (d) { return color(d.data); });
Also d3.arc() is from version 4.
Helper methods below:
getTranslate = function (startAngle, endAngle, distance) {
var xTranslate, yTranslate;
var startQ = getQuadrant(startAngle);
var endQ = getQuadrant(endAngle);
//Assume there are 7 possibilities since last slice always ends at Tau or 12 o'clock when doing a d.endAngle
switch (true) {
case (startQ == 1 && endQ == 1):
xTranslate = distance * 0.5;
yTranslate = distance * -1.5;
break;
case (startQ == 1 && endQ == 4):
xTranslate = distance * 1.5;
yTranslate = distance * 0.5;
break;
case (startQ == 4 && endQ == 4):
xTranslate = distance * 0.5;
yTranslate = distance * 1.5;
break;
case (startQ == 4 && endQ == 3):
xTranslate = distance * -0.5;
yTranslate = distance * 1.5;
break;
case (startQ == 3 && endQ == 3):
xTranslate = distance * -1.5;
yTranslate = distance * 0.5;
break;
case (startQ == 3 && endQ == 2):
xTranslate = distance * -1.5;
yTranslate = distance * -0.5;
break;
case (startQ == 2 && endQ == 2):
xTranslate = distance * -0.5;
yTranslate = distance * -1.5;
break;
}
return "translate(" + xTranslate + "," + yTranslate + ")";
}
getQuadrant = function (angle) {
switch (true) {
case angle < (Math.PI * 0.5):
return 1;
break;
case angle >= (Math.PI * 1.5):
return 2;
break;
case ((Math.PI < angle) && angle <= (Math.PI * 1.5)):
return 3;
break;
default:
return 4;
}
}
I have two simple objects in d3.js, they should be circling around the center of the viewport (like planets around the sun).
I am new to d3.js and I know that I have to use transitions but as the planets have to circle all the time and not just on enter or exit I don't know where and how to set the transition.
Here is my current code:
var planets = [
{d:100,r:2},
{d:150,r:4}
];
var w = 500, h = 400, svg, circle;
function init(){
svg = d3.select("#drawArea").append("svg").attr({width: w, height: h});
var center = {
x: Math.floor(w/2),
y: Math.floor(h/2)
};
svg.append('circle').attr({
'cx': center.x,
'cy': center.y,
'r': 10,
'class': 'sun'
});
circle = svg.selectAll(".planet")
.data(planets)
.enter()
.append("circle")
.attr("class", "planet")
.attr("r", function(s){return s.r});
circle.attr({
// distance from the center
'cx': function(s){ return center.x - s.d; },
// center of the screen
'cy': function(s){ return center.y; }
});
}
And here is a jsfiddle to play around.
You need to:
Place your planets in g groups in a g that is centered on your sun
Create an d3.timer in which you rotate your group.
For, example of the use of d3.timer see Mike Bostocks Epicyclic Gearing example. Using that example, I put together something similar to what you asked: http://bl.ocks.org/4953593
Core of the example:
var w = 800, h = 800;
var t0 = Date.now();
var planets = [
{ R: 300, r: 5, speed: 5, phi0: 90},
{ R: 150, r: 10, speed: 2, phi0: 190}
];
var svg = d3.select("#planetarium").insert("svg")
.attr("width", w).attr("height", h);
svg.append("circle").attr("r", 20).attr("cx", w/2)
.attr("cy", h/2).attr("class", "sun")
var container = svg.append("g")
.attr("transform", "translate(" + w/2 + "," + h/2 + ")")
container.selectAll("g.planet").data(planets).enter().append("g")
.attr("class", "planet").each(function(d, i) {
d3.select(this).append("circle").attr("class", "orbit")
.attr("r", d.R);
d3.select(this).append("circle").attr("r", d.r).attr("cx",d.R)
.attr("cy", 0).attr("class", "planet");
});
d3.timer(function() {
var delta = (Date.now() - t0);
svg.selectAll(".planet").attr("transform", function(d) {
return "rotate(" + d.phi0 + delta * d.speed/200 + ")";
});
});
I know this might come too late. But I do hope this can help future readers if they also are facing the same problem.
I created fiddles and add some explanation in here, hope it can help:
http://www.zestyrock.com/data-visualization/d3-rotate-object-around-the-center-object-part-2/
Here is the code:
var start = Date.now();
var mode = 0;
var svg = d3.select("#canvas")
.append("svg")
.attr("width", 500)
.attr("height", 500);
svg.append("circle")
.attr("r", 50)
.attr("cx", 250)
.attr("cy", 250)
.attr("fill", "#e05038");
var rotateElements = svg.append("g");
rotateElements.append("circle")
.attr("r", 25)
.attr("cx", 250)
.attr("cy", 50)
.attr("fill", "#f2b632")
.attr("class", "orangeNode");
rotateElements.append("line")
.attr("x1", 250)
.attr("y1", 75)
.attr("x2", 250)
.attr("y2", 200)
.attr("class", "edge")
.attr("stroke", "grey");
d3.select("#start")
.on("click", startAnimation);
function startAnimation() {
if (mode === 0) {
d3.timer(function() {
var angle = (Date.now() - start);
var transform = function() {
return "rotate(" + angle + ", 250, 250)";
};
d3.selectAll(".orangeNode, .edge")
.attr("transform", transform);
if (mode === 0) {
return true;
} else {
return false;
}
});
mode = 1;
} else {
mode = 0;
}
}