How to repeat rotation using d3 - d3.js

I'm trying to figure out how to repeat a transition. I' m using world tour with my own tsv file. The tsv file s much smaller which ends the world tour quickly.
How can I repeat the rotation so its starts at beginning?
//Globe rotating
(function transition() {
d3.transition()
.duration(1500)
.each("start", function() {
title.text(countries[i = (i + 1) % n].name);
})
.style("color", "lightgreen")
.style("text-anchor", "middle")
.tween("rotate", function() {
var p = d3.geo.centroid(countries[i]),
r = d3.interpolate(projection.rotate(), [-p[0], -p[1]]);
return function(t) {
projection.rotate(r(t));
c.clearRect(0, 0, width, height); //clear the canvas for redrawing
c.fillStyle = "black", c.beginPath(), path(land), c.fill();
c.fillStyle = "lightgreen", c.beginPath(), path(countries[i]), c.fill();
c.strokeStyle = "green", c.lineWidth = .5, c.beginPath(), path(borders), c.stroke();
c.strokeStyle = "#000", c.lineWidth = 2, c.beginPath(), path(globe), c.stroke();
};
})
.transition()
.each("end", transition);
})();
}

One option would be to reset i to zero when it exceeds the number of countries in your list. Something like this:
.each("start", function() {
i = (i + 1) % n;
if(i >= names.length)
i = 0;
title.text(countries[i].name);
})
Edit: After looking at the World Tour example code, a simpler solution would be redefine n to be the length of your data (instead of the number of countries on the map):
n = names.length; // instead of countries.length
Then you can leave the rest of the code as is. The modulo in this expression - i = (i + 1) % n - will reset to zero once you reach the end of your list.

Related

nested donuts, partial sum of the values in the 2nd dimension array

Is this possible? The reason I am asking the question is first I did the concentric donuts with 2 datasets and the slices size did not match related data it was each proportionate but slightly smaller on the inner ring and I want the slices to match inner and outer. So I read you need nested dataset.
I need the pie slices of the first 2 values of apples to match the first 2 slices of the inner and outer donuts. Then I need the total value of the remaining apples to be one slice and it needs to match the same individual pie slices of the rest of the first array. So the client just wants to compare the summed values or see it as only 3 slices compared to the 5 slices.
I used the working apples and oranges JSfiddle to start with from the internet: https://jsfiddle.net/vgq0z5aL/
I modified it here to use the dataset that will work with my problem but couldn't get it to work. Something wrong with the dataset I think?
My Example: https://jsfiddle.net/aumnxjc8/
How can I fix the dataset so it works?
var dataset = {
apples: [13245, 28479, 1000, 1000, 3000],
apples2: [dataset[0][0], dataset[0][1], sumofapples],
};
var sumofapples = dataset[0][3]+ dataset[0][4]+dataset[0][5];
var width = d3.select('#duration').node().offsetWidth,
height = 300,
cwidth = 33;
var colorO = ['#1352A4', '#2478E5', '#5D9CEC', '#A4C7F4', '#DBE8FB'];
var colorA = ['#58A53B', '#83C969', '#A8D996'];
var pie = d3.layout.pie()
.sort(null);
var arc = d3.svg.arc();
var svg = d3.select("#duration svg")
.append("g")
.attr("transform", "translate(" + width / 2 + "," + height / 2 + ")");
console.log(dataset);
var gs = svg.selectAll("g").data(d3.values(dataset)).enter().append("g");
var path = gs.selectAll("path")
.data(function(d, i) { return pie(d); })
.enter().append("path")
.attr("fill", function(d, i, j) {
if (j == 0) {
return colorO[i];
} else {
return colorA[i];
}
})
.attr("d", function(d, i, j) {
if (j == 0) {
return arc.innerRadius(75 + cwidth * j - 17).outerRadius(cwidth * (j + 2.9))(d);
} else {
return arc.innerRadius(75 + cwidth * j - 5).outerRadius(cwidth * (j + 2.5))(d);
}
});
Try:
const apples = [13245, 28479, 11111, 11000, 3876];
const apples2 = [apples[0], apples[1],
apples.slice(2).reduce((sum,item) => sum + item, 0)];
const dataset = { apples, apples2 };
You can see the result in a fiddle

Rotate every arc of pie chart 180 (like sun) with D3 JS. How to calculate translate parameters

I am working on pie chart with d3 js. I want to rotate every arc of my pie chart 180. I know that I am unable to explain completely show here is my fiddle link.
[fiddle]: https://jsfiddle.net/dsLonquL/
How can i get dynamic parameters for translate() function.
Basically you need to work out the centre point of the edge of each arc. I used this example for help : How to get coordinates of slices along the edge of a pie chart?
This works okay, but I needed to rotate the points to get them in the correct positions. As it is in radians the rotation is the following :
var rotationInRadians = 1.5708 * 1.5;
Now using the example before I used the data for the paths, so the start and end angle and got the center points like so :
var thisAngle = (d.startAngle + rotationInRadians + (d.endAngle + rotationInRadians - d.startAngle + rotationInRadians) / 2);
var x = centreOfPie[0] + radius * 2 * Math.cos(thisAngle)
var y = centreOfPie[1] + radius * 2 * Math.sin(thisAngle)
I created a function to show circles at these points to clarify :
function drawCircle(points, colour) {
svg.append('circle')
.attr('cx', points[0])
.attr('cy', points[1])
.attr('r', 5)
.attr('fill', colour);
}
Called it inside the current function like so :
drawCircle([x, y], color(d.data.label))
And then translated and rotated accordingly :
return 'translate(' + (x) + ',' + y + ') rotate(180)';
I added a transition so you can see it working. Here is the final fiddle :
https://jsfiddle.net/thatOneGuy/dsLonquL/7/
EDIT
In your comments you say you want the biggest segment to be kept in the middle. So we need to run through the segments and get the biggest. I have also taken care of duplicates, i.e if two or more segments are the same size.
Here is the added code :
var biggestSegment = {
angle: 0,
index: []
};
path.each(function(d, i) {
var thisAngle = (d.endAngle - d.startAngle).toFixed(6);//i had to round them as the numbers after around the 7th or 8th decimal point tend to differ tet theyre suppose to be the same value
if (i == 0) {
biggestSegment.angle = thisAngle
} else {
if (biggestSegment.angle < thisAngle) {
biggestSegment.angle = thisAngle;
biggestSegment.index = [i];
} else if (biggestSegment.angle == thisAngle) {
console.log('push')
biggestSegment.index.push(i);
}
}
})
Now this goes through each path checks if its bigger than the current value, if it is overwrite the biggest value and make note of the index. If its the same, add index to index array.
Now when translating the paths, you need to check the current index against the index array above to see if it needs rotating. Like so :
if (biggestSegment.index.indexOf(i) > -1) {
return 'translate(' + (centreOfPie[0]) + ',' + (centreOfPie[1]) + ')' // rotate(180)';
} else {
return 'translate(' + (x) + ',' + y + ') rotate(180)';
}
Updated fiddle : https://jsfiddle.net/thatOneGuy/dsLonquL/8/
I have editted 3 values to be different to the rest. Go ahead and change these, see what you think :)
This is a pure middle school geometry job.
CASE 1: The vertex of each sector rotation is on the outer line of the circle
fiddle
// ... previous code there
.attr('fill', function(d, i) {
return color(d.data.label);
})
.attr("transform", function(d, i) {
var a = (d.endAngle + d.startAngle) / 2, // angle of vertex
dx = 2 * radius * Math.sin(a), // shift/translate is two times of the vertex coordinate
dy = - 2 * radius * Math.cos(a); // the same
return ("translate(" + dx + " " + dy + ") rotate(180)"); // output
});
CASE 2: The vertex on the center of the chord
fiddle
// ... previous code there
.attr('fill', function(d, i) {
return color(d.data.label);
})
.attr("transform", function(d, i) {
var dx = radius * (Math.sin(d.endAngle) + Math.sin(d.startAngle)), // shift/translation as coordinate of vertex
dy = - radius * (Math.cos(d.endAngle) + Math.cos(d.startAngle)); // the same for Y
return ("translate(" + dx + " " + dy + ") rotate(180)"); // output
});

How to set a specific duration to interpolate along a path one point at time?

I'm trying to figure out the best way to interpolate a circle along a path as Mike Bostock does in this example: http://bl.ocks.org/mbostock/1705868. However, instead of setting one transition value as he does, I'd like to be able to set a unique duration for each point-to-point interpolation; e.g., transition the circle from node[0] to node [1] over x milliseconds, transition from node [1] to node [2] over y milliseconds, etc. Is there a way to do this without splitting the path up into a bunch of smaller separate paths and transitioning along them consecutively? The limiting factor seems to be path.getTotalLength() - is there a way to get the length of only the subset of a path?
transition();
function transition() {
circle.transition()
.duration(10000)
.attrTween("transform", translateAlong(path.node()))
.each("end", transition);
}
// Returns an attrTween for translating along the specified path element.
function translateAlong(path) {
var l = path.getTotalLength();
return function(d, i, a) {
return function(t) {
var p = path.getPointAtLength(t * l);
return "translate(" + p.x + "," + p.y + ")";
};
};
}
There's in a fact a way but it's way too ugly (because it needs an initial brute force computation), the solution involves the following:
First of all you need an array with the transition times between nodes, in my example is times, for example the first element 3000 corresponds to the time in ms to get from [480,200] to [580,400]
compute the sum of the transition times (needed for the duration of the overall transition)
compute the linear time in ms to reach each one of the points that made this path, this is actually tricky when the path between two points is not a line e.g. a curve, in my example I compute those times by brute force which makes it ugly, it'd be awesome if there was a method that computed the path length needed to get to some point lying on the path itself, unfortunately such a method doesn't exist as far as I know
Finally once you know the linear times you have to compute the correct time as if it followed the list of the numbers in the times array e.g.
Let's say that the linear time to get to the first point is 50ms and we're currently on the time t < 50ms, we have to map this value which is between [0ms, 50ms] to somewhere in the range [0ms, 3000ms] which is given by the formula 3000 * (t ms - 0ms) / (50ms - 0ms)
var points = [
[480, 200],
[580, 400],
[680, 100],
[780, 300],
[180, 300],
[280, 100],
[380, 400]
];
var times = [3000, 100, 5000, 100, 3000, 100, 1000]
var totalTime = times.reduce(function (a, b) {return a + b}, 0)
var svg = d3.select("body").append("svg")
.attr("width", 960)
.attr("height", 500);
var path = svg.append("path")
.data([points])
.attr("d", d3.svg.line()
.tension(0) // Catmull–Rom
.interpolate("cardinal-closed"));
svg.selectAll(".point")
.data(points)
.enter().append("circle")
.attr("r", 4)
.attr("transform", function(d) { return "translate(" + d + ")"; });
var circle = svg.append("circle")
.attr("r", 13)
.attr("transform", "translate(" + points[0] + ")");
function transition() {
circle.transition()
.duration(totalTime)
.ease('linear')
.attrTween("transform", translateAlong(path.node()))
.each("end", transition);
}
// initial computation, linear time needed to reach a point
var timeToReachPoint = []
var pathLength = path.node().getTotalLength();
var pointIndex = 0
for (var t = 0; pointIndex < points.length && t <= 1; t += 0.0001) {
var data = points[pointIndex]
var point = path.node().getPointAtLength(t * pathLength)
// if the distance to the point[i] is approximately less than 1 unit
// make `t` the linear time needed to get to that point
if (Math.sqrt(Math.pow(data[0] - point.x, 2) + Math.pow(data[1] - point.y, 2)) < 1) {
timeToReachPoint.push(t);
pointIndex += 1
}
}
timeToReachPoint.push(1)
function translateAlong(path) {
return function(d, i, a) {
return function(t) {
// TODO: optimize
var timeElapsed = t * totalTime
var acc = 0
for (var it = 0; acc + times[it] < timeElapsed; it += 1) {
acc += times[it]
}
var previousTime = timeToReachPoint[it]
var diffWithNext = timeToReachPoint[it + 1] - timeToReachPoint[it]
// range mapping
var placeInDiff = diffWithNext * ((timeElapsed - acc) / times[it])
var p = path.getPointAtLength((previousTime + placeInDiff) * pathLength)
return "translate(" + p.x + "," + p.y + ")"
}
}
}
transition();
path {
fill: none;
stroke: #000;
stroke-width: 3px;
}
circle {
fill: steelblue;
stroke: #fff;
stroke-width: 3px;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>

How do I draw horizontal bars with a label using either ChartJS or D3?

What's the best way of drawing multiple horizontal lines and labels for a simple line graph in either ChartJS or D3? I know that I could draw these as individual lines and then do a text overlay but I'm wondering if there is a simpler solution. Ideally I'd be able to create each of the labels below as one unit and move it anywhere.
If this is simpler in another JS graph library then feel free suggest.
Example below
To do it with Chart.js you have to extend the line chart
Chart.types.Line.extend({
name: "LineAlt",
initialize: function (data) {
// it's easier to programmatically update if you store the raw data in the object (vs. storing the geometric data)
this.marks = data.marks;
this.marks.xStart = Number(data.labels[0]);
this.marks.xStep = data.labels[1] - data.labels[0];
// make sure all our x labels are uniformly apart
if (!data.labels.every(function (e, i, arr) { return !i || ((e - arr[i - 1]) === this.marks.xStep); }, this))
throw "labels must be uniformly spaced";
Chart.types.Line.prototype.initialize.apply(this, arguments);
},
draw: function () {
Chart.types.Line.prototype.draw.apply(this, arguments);
// save existing context properties
var self = this;
var ctx = self.chart.ctx;
var scale = self.scale;
ctx.save();
// line properties
ctx.lineWidth = 1;
ctx.fillStyle = "#666";
ctx.strokeStyle = "#666";
ctx.textAlign = "center";
ctx.textBaseline = "bottom";
ctx.font = scale.font;
// draw marks
self.marks.forEach(function (mark) {
// assuming that the marks are always within the data range
var y = scale.calculateY(mark.y);
var x1 = scale.calculateX((mark.x1 - self.marks.xStart) / self.marks.xStep);
var x2 = scale.calculateX((mark.x2 - self.marks.xStart) / self.marks.xStep);
// draw line
ctx.beginPath();
ctx.moveTo(x1, y);
ctx.lineTo(x2, y);
// draw edges
ctx.moveTo(x1, y + 10);
ctx.lineTo(x1, y - 10);
ctx.moveTo(x2, y + 10);
ctx.lineTo(x2, y - 10);
ctx.stroke();
// draw text
ctx.fillText(mark.label, (x1 + x2) / 2, y + scale.fontSize * 1.5);
})
ctx.restore();
},
});
You pass in the data for drawing the lines like so
var data = {
...
marks: [
{
x1: 1.5,
x2: 3.5,
y: 50,
label: 'Label1'
},
{
x1: 5,
x2: 7,
y: 60,
label: 'Label2'
}
]
};
and you create the chart using this extended chart type
var myLineChart = new Chart(ctx).LineAlt(data);
You can update the lines like this
myLineChart.marks[0].y = 80;
myLineChart.marks[0].x1 = 9;
myLineChart.marks[0].x2 = 10;
and then call
myLineChart.update();
to reflect those changes on the canvas
Caveats
The (x axis) labels should be numeric and uniformly spaced.
The lines should be within the scale range of the y axis (alternatively you can do a scaleOverride to set the scale parameters so that the lines are within the y scale range)
Fiddle - http://jsfiddle.net/en92k763/2/

Changing number displayed as svg text gradually, with D3 transition

I am looking for a simple way to gradually change the value of a number displayed as svg text with d3.
var quality = [0.06, 14];
// qSVG is just the main svg element
qSVG.selectAll(".txt")
.data(quality)
.enter()
.append("text")
.attr("class", "txt")
.text(0)
.transition()
.duration(1750)
.text(function(d){
return d;
});
Since text in this case is a number i hope there is an easy way to just increment it to the end of the transition.
Maybe someone of you has an idea.
Cheers
It seems d3JS already provides a suitable function called "tween"
Here is the important part of the code example.
.tween("text", function(d) {
var i = d3.interpolate(this.textContent, d),
prec = (d + "").split("."),
round = (prec.length > 1) ? Math.pow(10, prec[1].length) : 1;
return function(t) {
this.textContent = Math.round(i(t) * round) / round;
};
});​
http://jsfiddle.net/c5YVX/280/
You can increment them over a given time interval from any start to any end value regardless their number precision.
Its implemented for SVG text but of course works the same for standard html text.
If you only need the plain tween function for rounded numbers, it gets a bit more leightweight.
.tween("text", function(d) {
var i = d3.interpolate(this.textContent, d),
return function(t) {
this.textContent = Math.round(i(t));
};
});​

Resources