How to animate drawing a sequence of line segments - javascript

I would like to draw a point and after 1 sec or so I would like to draw the next point. Is this somehow possible:
I already tried:
function simulate(i) {
setTimeout(function() { drawPoint(vis,i,i); }, 1000);
}
for (var i = 1; i <= 200; ++i)
simulate(i);
function drawPoint(vis,x,y){
var svg = vis.append("circle")
.attr("cx", function(d) {
console.log(x);
return 700/2+x;
})
.attr("cy", function(d) {
return 700/2+y;
})
.attr("r", function(d) {
console.log(x);
return 6;
});
}
Unfortunately, this is not working. It just draws the whole line immediately.

This will not work, because the for loop will immediately run to the end, the setTimeouts will be scheduled simultaneously and all the functions will fire at the same time.
Instead of that, do this:
var i = 1;
(function loop(){
if(i++ > 200) return;
setTimeout(function(){
drawPoint(vis,i,i);
loop()
}, 1000)
})();
Explanation:
This IIFE will run for the first time with i = 1. Then, the if increases i (doing i++) and checks if it is bigger than 200. If it is, the function loop returns. If it's not, a setTimeout is scheduled, which calls drawnPoint and the very function loop again.

Related

moveBall function not working when adding input parameters

I'm trying to create a moveBall function were you put the ID of the ball created in HTML, and it moves from left to right.
This is what I've done first, and it works:
var ball = document.getElementById("ball");
var velocityX = 100;
var positionX = 0;
var reverseX = false;
//write a function that can change the position of the html element "ball"
function moveBall() {
// two fixed x-axis coordinates (you will use these variable in step 3)
var Xmin = 0;
var Xmax = 500;
//reverse condition
if (!reverseX){
positionX = positionX + velocityX;
} else positionX = positionX - velocityX;
ball.style.left = positionX + 'px';
if (positionX === Xmax){
reverseX = true;
} else if (positionX === Xmin){
reverseX = false;
}
}
// This call the moveBall function every 100ms
setInterval(moveBall, 100);
However, when I try to make a function moveBall(ballId) so I can re use it with other balls, it doesn't work. This is my code:
var ballId;
var velocityX = 100;
var positionX = 0;
var reverseX = false;
//write a function that can change the position of the html element "ball"
function moveBall(ballId) {
ball = document.getElementById(ballId)
// two fixed x-axis coordinates (you will use these variable in step 3)
var Xmin = 0;
var Xmax = 500;
//reverse condition
if (!reverseX){
positionX = positionX + velocityX;
} else positionX = positionX - velocityX;
ball.style.left = positionX + 'px';
if (positionX === Xmax){
reverseX = true;
} else if (positionX === Xmin){
reverseX = false;
}
}
// This call the moveBall function every 100ms
setInterval(moveBall("ball"), 100);
Please I would be thrilled to know if you have any clue. The ball doesn't move with the second code, it just remains static in the html file.
Your problem is in the line which says setInterval(moveBall("ball"), 100);
setInterval expects a function as first argument, however, here provided is a function call instead. I'm a bit rusty on theory here, but basically this will execute function call first, which will return undefined and then your line looks same as if you have written setInterval(undefined, 100);, so every 100ms you do nothing...
What you wanna do instead is write function handler to pass into setInterval instead of function call.
You can do either arrow function setInterval(() => moveBall("ball"), 100);
or if you can't use arrow functions you could do setInterval(function () {moveBall("ball")}, 100);
This in other words says execute (nameless) function every 100ms, this nameless function will execute call of your moveBall function with desired paramater.
Your original code without param works since if you look closely, you are passing directly function there, without call.
Thanks to Miroslav Saracevic for the incredibly quick answer,
// This call the moveBall function every 100ms
setInterval(() => moveBall("ball"), 100)
This solved the problem.

How to bind a changing datasource to a set of svg?

I would like to visualize a circle with a random colour at a random x and y coordinate, then add an extra colourful circle at a random location every second.
I am using d3.timer to run a function that appends x and y coordinates to my dataset object, which is bound to all of the circle objects. When I print the dataset object, I can see that my function does in fact append the new x and y coordinates to my dataset object. However, the visualization does not update with the new circles. How can I add a new circle every second?
Relevant functions below:
var reshuffleData = function(){
for (var i=0; i<5; i++){
console.log('Reshuffling')
dataset.push({x: randomXPosition(), y: randomYPosition()})
}
console.log(dataset)
return true
}
d3.timer(reshuffleData, 10);
Full jsfiddle here: http://jsfiddle.net/d74Le5xk/
It wasn't not working because d3.timer is used incorrectly. As d3.timer just takes a function to paint next animation frame. We do not control when the this function will be called but it will be called most likely (1/ frames per second seconds). Where FPS may change every second.
If you want to do something periodically use setInterval also you need to redraw the circles once the dataset size is changed.
Following is the jsfiddle link for the working code.
http://jsfiddle.net/d74Le5xk/3/
Also attaching the code here for reference.
HTML
<svg class='canvas'></svg>
Javascript
(function () {
var width = 420, height = 200;
var randomXPosition = function(d){
return Math.random() * width;
}
var randomYPosition = function(d){
return Math.random() * height;
}
var dataset = [];
var circleBatchSize = 5;
var maxCircleCount = 100;
for (var i=0; i < circleBatchSize; i++){
dataset.push({x: randomXPosition(), y: randomYPosition()})
}
var testInterval = null;
var reshuffleData = function(){
for (var i=0; i<circleBatchSize; i++){
dataset.push({x: randomXPosition(), y: randomYPosition()})
//return true;
}
console.log('Reshuffled ' + dataset.length)
console.log(dataset)
if(dataset.length > maxCircleCount) {
clearInterval(testInterval);
}
}
console.log(dataset);
var colours = ['#FDBB30', '#EE3124', '#EC008C', '#F47521', '#7AC143', '#00B0DD'];
var randomColour = function() {
return colours[Math.floor(Math.random() * colours.length)];
}
//d3.timer(reshuffleData, 0, 5000);
testInterval = window.setInterval(reshuffleData, 2000);
var canvas = d3.select('.canvas')
.attr('width', width)
.attr('height', height)
.style('background-color', 'black');
var datasetOldLength = 0;
function drawCircles() {
if(datasetOldLength === dataset.length ) {
return;
}
datasetOldLength = dataset.length;
var circles = canvas.selectAll('circle')
.data(dataset)
.enter()
.append('circle')
.style('r', 20)
.style('fill', randomColour)
.style('cx', function(d) { return d.x} )
.style('cy', function(d) { return d.y} );
if(dataset.length > maxCircleCount) {
return true;
}
}
d3.timer(drawCircles, 1000);
})();
d3.timer usage explanation
# d3.timer(function[, delay[, time]])
[function] argument is called at every frame rendering by d3. It's called until it returns true.
optional [delay] in milliseconds to delay the first invocation of the [function]. Delay is taken since the [time] passed in third argument. If [time] is not passed delay starts from new Date().getTime().
optional [time] is the epoch time from when the delay is considered.
Reference https://github.com/mbostock/d3/wiki/Transitions#timers

d3 force-directed layout expands on reheat

Consider any d3 force-directed animation, such as:
http://bl.ocks.org/mbostock/1062288
Load that graph and let the nodes settle down so that they are not moving. Hover your mouse over an orange node and let the mouse be still. Click and do nothing else. You will see all the nodes move inwards a little and then expand back to normal.
I do not like that unnecessary movement and I need to remove it.
I believe it has to do with the graph being "reheated" (see https://github.com/mbostock/d3/wiki/Force-Layout
), which sets the alpha parameter to 0.1. Specifically, I see the unnecessary movement occur immediately after any of the following.
a new node is added to the graph
a node is deleted from the graph
force.drag() is called.
What exactly causes this "unnecessary movement", and how can I disable it? (without disabling node dragging)
The behavior comes mainly from the alpha. The alpha function within d3:
force.alpha = function(x) {
if (!arguments.length) return alpha;
x = +x;
if (alpha) {
if (x > 0) alpha = x; else alpha = 0;
} else if (x > 0) {
event.start({
type: "start",
alpha: alpha = x
});
d3.timer(force.tick);
}
return force;
};
So when alpha is called, it starts the tick event if greater than 0
Within the tick event, at the very beginning, you can see the cut off that is causing the 'freeze' you mentioned:
if ((alpha *= .99) < .0005) {
event.end({
type: "end",
alpha: alpha = 0
});
return true;
}
Now, if we look at the dragmove, it calls the resume function:
function dragmove(d) {
d.px = d3.event.x, d.py = d3.event.y;
force.resume();
}
which calls the alpha function with a value of 0.1:
force.resume = function() {
return force.alpha(.1);
};
To stop the jump and also the freeze at 0.0005, I don't see myself any public function you can use.
I have an example fiddle that removed the code from the tick function that causes the freezing and also changed the resume function to start at a smaller alpha.
I believe this gives the result your looking for but of course, I wouldn't really advise customizing the library.
Maybe someone knows how to override the tick function without having to modify the source.
in
// Toggle children on click.
function click(d) {
if (!d3.event.defaultPrevented) {
if (d.children) {
d._children = d.children;
d.children = null;
} else {
d.children = d._children;
d._children = null;
}
update();
}
}
You could move the update to the children open only
// Toggle children on click.
function click(d) {
if (!d3.event.defaultPrevented) {
if (d.children) {
d._children = d.children;
d.children = null;
update();
} else {
d.children = d._children;
d._children = null;
}
}
}
so a node without children (orange) will not be updated on click.
Normally, when force.resume() is fired, the alpha value jumps from 0 to 0.1. By easing alpha up more slowly, we can cover up the "unnecessary movement".
I am still open to other ideas and solutions.
old function:
force.resume = function() {
return force.alpha(.1);
};
replacement function:
force.resume = function() {
var alpha = force.alpha()
if( alpha < 0.005 ){ alpha = 0.0055 }
else if( alpha < 0.11 ){ alpha += 0.0006 }
return force.alpha(alpha);
};

D3 tween - pause and resume controls

I am trying to edit this d3 example.
More specifically, I will try to apply the pause-resume controls of a pause resume guide in addition with a control bar like this we have under videos. At the end I imagine to have something like this:
How can apply the pause resume control in the beginning?
Here's a quick implementation. The pause essentially cancels the current transition and the play resumes it based on time/position it was paused:
var pauseValues = {
lastT: 0,
currentT: 0
};
function transition() {
circle.transition()
.duration(duration - (duration * pauseValues.lastT)) // take into account any pause
.attrTween("transform", translateAlong(path.node()))
.each("end", function(){
pauseValues = {
lastT: 0,
currentT: 0
};
transition()
});
}
function translateAlong(path) {
var l = path.getTotalLength();
return function(d, i, a) {
return function(t) {
t += pauseValues.lastT; // was it previously paused?
var p = path.getPointAtLength(t * l);
pauseValues.currentT = t; // just in case they pause it
return "translate(" + p.x + "," + p.y + ")";
};
};
}
d3.select('button').on('click',function(d,i){
var self = d3.select(this);
if (self.text() == "Pause"){
self.text('Play');
circle.transition()
.duration(0);
setTimeout(function(){
pauseValues.lastT = pauseValues.currentT; // give it a bit to stop the transition
}, 100);
}else{
self.text('Pause');
transition();
}
});
transition();
Example here.

animation glitch in sunburst

I started with the default Sunburst example and tried to add an animation on click - clicking on a wedge makes that wedge the center element. This works great except in a few cases - it looks like if the depth goes above 2 (0 based) then some wedges don't animate correctly; they seem to start the animation in the wrong place, but end up in the correct place.
Here is what I think is the relevant code, and I hope it's useful to someone else:
animate = function (d) {
var oldDs = [], topD, includeds;
if (animating)
return;
animating = true;
topD = (d == selected && d.parent) ? d.parent : d;
if (selected == topD) {
animating = false;
return;
}
selected = topD;
svg.selectAll("path").filter(function (f) {
return (!isDescendant(f, topD))
}).transition().duration(1000).style("opacity", "0").attr("pointer-events","none");
includeds = svg.datum(topD).selectAll("path").filter(function (f) {
return isDescendant(f, topD);
});
// step 1 - save the current arc settings
includeds.data().each(function (d) {
oldDs[d.ac_id] = {x: d.x, dx: d.dx, y: d.y, dy: d.dy};
});
// step 2 - recalculate arc settings and animate
includeds.data(partition.nodes) // recalculates
.transition().duration(1000).attrTween("d", function (d) {
var oldD = oldDs[d.ac_id],
interpolator = d3.interpolateObject(oldD, d);
return function (t) {
return arc(interpolator(t));
}
}).style("opacity", "1").attr("pointer-events","all");
setTimeout(function(){ animating = false; }, 1000);
};
Because my code doesn't have anything to do with the depth, I expect the issue is somewhere else, but I haven't been able to find it. The glitch seems to affect all browsers (at least Chrome, Firefox, and IE).
So, my question: what's the issue and how can I fix it?

Categories

Resources