How to fake a computation elapsed time and message within transition? - d3.js

I have a D3 JS code that does a transition between two states of a barchart. I now need some extra elapsed time in the middle of the transition i.e. after the old state is removed and before the new state appears that adds an extra delay and shows something like "Computing something, please wait ...".
This example could be used as reference. What I need is an extra pause in between the transition that shows a message to the user.

For a fixed delay, you can use use delay. See https://github.com/d3/d3-transition#transition_delay
For a dynamic delay, you can use the 'end' event, coupled with another transition (from https://stackoverflow.com/a/10692220/6184972), like:
d3.select('#myid').transition().style('opacity', '0').on('end', function () {
console.log('computing...');
setTimeout(function () {
console.log('Done!');
d3.select('#myid').transition().style('opacity', 1);
}, Math.random() * 2000 + 1000);
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>
<div id="myid">Hi World</div>

Related

How do I avoid CLS (Cumulative Layout Shift) if I'm loading external content and the height is dynamically assigned?

I have a button to pull some data from backend. But the length of the data is uncertain and the time to load is also uncertain.
This will generate high CLS penalty. Is there anyway to avoid this?
var stHandler = 0;
$('#render').click(function(){
clearTimeout(stHandler);
$('#result').html('loading...');
// [Simulate the server response time]
// CLS will not be penalized if the everything happens in 100ms,
// but most of the case, the server will return the data longer than that
stHandler = setTimeout(function(){
$('#result').html(fakeResultBuilder);
}, 500 + Math.random()*1000);
})
// [Simulate the real-world condition]
// Every time you load this content, the height will be different.
function fakeResultBuilder(){
var html = '';
for (var i=0; i<Math.random()*100; i++) {
html += '<div class="block"></div>';
}
return html;
}
button {cursor:pointer}
.block {height:10px; background:#f00; width:20px; margin:5px}
#result {border:1px solid #999}
footer {margin-top:1em}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<div>
<button id="render">Render</button>
</div>
<h2>Result:</h2>
<div id="result"></div>
<footer>Page Footer - will have huge CLS sometimes</footer>
The key is to adjust as much as possible within the 500ms grace period (note not 100ms as given in your code comment). The 500ms grace period is set because clicking on things (like your Render button) is an expected shift. However, if it happens considerably later (and 500ms is set as the limit) then it's back to being an unexpected shift. The user may be in the middle of reading some of the footer while they wait.
While the full response may not be able to be returned within that 500ms, you can still reserve some space (perhaps using min-height?) to, at the very least, reduce the CLS. The default height of an empty div is 0px so ANYTHING you do here will be better than than that as surely it will be larger than 0ms?
Additionally if it's enough to always knock the footer off-screen then adding a minimum height to push that footer off screen will mean any further moves for it are not considered CLS.
This may leave a large white space, which may not be ideal, so you may wish to consider some kind of "Loading..." message or a loading spinner. This will give an indication that something is happening to the user rather than them being unsure and perhaps rage-clicking the render button again and again.

d3 transition wait for previous?

d3.select('#' + linkId[0]).transition().duration(2500).attr('stroke', 'green');
d3.select('#' + nodeId[0]).transition().duration(5000).attr('fill', 'blue');
I have the above code that is animating a graph traversal. I want the second transition to only activate (and preferably remove the duration) once the link has been transitioned. How would i accomplish this? I have tried putting the whole second line of code within a timeout like so:
setTimeout(() => { d3 transition here }, 2500);
However this completely messed up the timing of events. Im basically looking for something similar to python where you can call .sleep(milliseconds) to specify code execution wait a certain amount of time.
There are two quick options available:
transition.delay()
You could use transition.delay(time) which allows you to specify a delay before a transition starts. This would look like:
d3.select('#' + linkId[0]).transition().duration(2500).attr('stroke', 'green');
d3.select('#' + nodeId[0]).transition().delay(2500).duration(5000).attr('fill', 'blue');
While simple, I'd suggest using the next approach instead.
transition.on("end", ... )
Another option is to use transition.on("end", function() { /* set up next transition */ }). Now .on("end",callbackFunction) will trigger on the end of each transition (if transitioning many elements, it'll trigger when each element finishes its transition), but you are transitioning single elements (as IDs are unique), so you could use something like this:
d3.select('#' + linkId[0]).transition()
.duration(2500)
.attr('stroke', 'green')
.on("end", function() {
d3.select('#' + nodeId[0]).transition().duration(5000).attr('fill', 'blue');
})
If you had many elements transitioning simultaneously you'd need to modify this slightly to check to see if any transitions were still in progress before starting the next transition.

What is the `e` parameter passed to the d3.js force tick callback function?

below is a normal example of tick function:
function tick(e) {
nodes
.each(cluster(10 * e.alpha * e.alpha));
}
who can tell me the definition of "e"? What properties does it have?
I can't find any description of "e", and what's the meaning of e.alpha. Yes, I used google but with no results.
Thanks for the help you gave below.
I'm copying some code, which use
var force = d3.layout.force()
.nodes(nodes)
.size([width, height])
.charge(-70)
.gravity(0.1)
.on("tick", tick)
.start();
so it's just the case you guess. I'm new to d3, a skim of force.layout API didn't give me any clue. Thanks for your precious time!
Without the full context of your "normal" function, this is a bit of a guess, but here goes:
tick is used in many contexts within d3. The inlcusion of alpha suggests that this is a force layout tick function, which is called by the force layout object on a tick event, in which case e would be the tick event object.
There is not a lot of documentation about the tick event, as most examples don't use it. If you inspect the source code, you will see
// A rudimentary force layout using Gauss-Seidel.
d3.layout.force = function() { //line 11
var force = {},
event = d3.dispatch("start", "tick", "end");
/* ... */
force.tick = function() { //line 58
// simulated annealing, basically
if ((alpha *= .99) < .005) {
event.end({type: "end", alpha: alpha = 0});
return true;
}
/* code to implement default force layout adjustments */
event.tick({type: "tick", alpha: alpha}); //line 128
};
/* ... */
return d3.rebind(force, event, "on"); //line 305
};
In other words, the tick event is one of three types of custom event created within the d3 source code using the d3.dispatch process. The tick event in particular is dispatched at the end of the internal tick function, and only contains one custom property: the current alpha parameter within the force layout. In order that these events actually go anywhere, the on method of the event dispatcher object is rebound on to the force layout object, so that the user can register listener functions for the custom events.
If all that is way too much d3 internals for you, just focus on these details:
e is a custom event object passed to your tick function every time it is called
e.alpha is the force layout's current alpha value, which by default starts at 0.1 and gets reduced (according to the friction parameter) at each tick until it drops below 0.005 and the layout freezes:
Internally, the layout uses a cooling parameter alpha which controls the layout temperature: as the physical simulation converges on a stable layout, the temperature drops, causing nodes to move more slowly. Eventually, alpha drops below a threshold and the simulation stops completely, freeing the CPU and avoiding battery drain. (From the API wiki for force.start)

d3.js tween factory return function applied to non-interpolable property values

This question builds on the (correct) answer provided to this. I simply haven't been able to get any further..
With the help of an interpolator function, d3.js's tween allows smooth graphical transition between existing and new (ie to be set) DOM element values. At the simplest level, for a given animation we have a target element, an start state, an end state, a transition, a tween function and an interpolator.
Now, say I want every so often to programmatically update the contents of an input (text field) element. The value to be entered is non-interpolable (either the text is submitted, or it is not. There is no in-between state). In providing a closure (allowing for text retrieval at the scheduled transition time), tween would seem to be a good vehicle for the updates. Either I replace the interpolator with a fixed value, ensure the start and end values are identical, or find some other way of forcing it to fire at t=1. That's the theory..
To this end, in my case each property (not value) is modified in it's own update call, into which are passed transition, element index and parent element selection.
First cut:
an outer, 'governing' transition with delay values staggered using a multiple of the current element's index
playback_transition = d3.transition()
.delay(function(d, i, j) {
return (time_interval * i);
})
.duration(function() {
return 1; // the minimum
});
within a call to playback_transition.each() pass the transition as a parameter to a dependent animation by means of an update() interface
within this dependent animation, apply the transition and tween to the current element(s):
input // a UI dialog element
.transition()
.tween(i.toString(), setChordname( waveplot.chordname ));
Where:
function setChordname(newValue) {
return function() {
var i = newValue; // a string
return function(t) {
this.value = i;
inputChanged.call(this);
};
};
};
and
function inputChanged() {
if (!this.value) return;
try {
var chord = chordify.chordObjFromChordName(this.value);
purge(); // rid display of superceded elements
plotChord(chord, options); // calculate & draw chord using new input property
} catch (e) {
console.log(e.toString());
}
}
PROBLEM
While setChordname always fires (each chord is in turn correctly found and it's value stored), of the scheduled returned functions, only the first fires and results in display of the associated waveform. For all subsequent return function occurrences, it is as if they had never been scheduled.
From the display:
direct user update to the input field still works fine
only the first of setChordname's return functions fire, but, for this initial chord, carries right through, correctly displaying the cluster of associated chord and note waves.
From this, we can say that the problem has nothing to do with the integrity of the waveplotting functions.
From the console
transitions are accumulating correctly.
chord supply is all good
associated (ie initial) tween fires at t=1. (specifically, tween appears to accept omission of an interpolator function).
looking at the output of transition.toSource(), though the associated outer index increases by single figure leaps, tween itself is always paired with an empty pair of curly brackets.
transition = [[{__transition__:{8:{tween:{}, time:1407355314749, eas..
For the moment, apart from this and the initial execution, the tween factory return function behaviour is a mystery.
From Experiment
Neither of the following have any impact:
Extending the period before the initial transition takes effect
Extending (by a multiple) each staggered transition delay
Furthermore
the same transition configuration used in a different scenario works fine.
These seem to eliminate timing issues as a possible cause, leaving the focus more on the integrity of the tween setup, or conditions surrounding waveplot append/remove.
Afraid it might be interfering with input property text submission via the tween, I also tried disabling a parallel event listener (listening for 'change' events, triggering a call to inputChanged()). Apart from no longer being able to enter own chordnames by hand, no impact.
In addition to 'change', I tried out a variety of event.types ('submit', 'input', 'click' etc). No improvement.
The single most important clue is (to my mind) that only the first setChordname() return function is executed. This suggests that some fundamental rule of tween usage is being breached. The most likely candidate seems to be that the return value of tween **must* be an interpolator.
3 related questions, glad of answers to any:
Anything blatently wrong in this approach?
For a shared transition scenario such as this, do you see a better approach to transitioning a non-interpolable (and normally user-supplied) input property than using tween ?
Provided they are staggered in time, multiple transitions may be scheduled on the same element - but what about multiple tweens? Here, as the staggered transition/tween combos are operating on only one element, they seem likely to be passed identical data (d) and index(i) in every call. Impact?
I'm now able to answer my own question. Don't be put off by the initial couple of paragraphs: there are a couple of valuable lessons further down..
Ok, there were one or two trivial DOM-to-d3 reworking issues in my adoption of the original code. Moreover, an extra returned function construct managed to find it's way into this:
Was:
function setChordname(newValue) {
return function() { <--- Nasty..
var i = newValue;
return function(t) {
this.value = i;
inputChanged.call(this);
};
};
};
Should have been:
function setChordname(newValue) {
var i = newValue;
return function(t) {
this.value = i;
inputChanged.call(this);
};
};
The fundamental problem, however, was that the transition -passed in as a parameter to an update() function- seems in this case to have been blocked or ignored.
Originally (as documented in the question) defined as:
input // a UI dialog element
.transition()
.tween(i.toString(), setChordname( waveplot.chordname ));
..but should have been defined as:
transition
.select("#chordinput") // id attribute of the input element
.tween(i.toString(), setChordname( waveplot.chordname ));
My guess is that the first version tries to create a new transition (with no delay or duration defined), whereas the second uses the transition passed in through the update() interface.
Strange is that:
what worked for another dependent animation did not for this.
the staggered delays and their associated durations were nevertheless accepted by the original version, allowing me to be misled by console logs..
Just to round this topic off, I can point out the the following (event-based) approach seems to work just as well as the tween variant with non-interpolable values documented above. I can switch freely between the two with no apparent difference in the resulting animations:
transition
.select("#chordinput") // id attribute of the input element
.each("start", setChordname( waveplot.chordname ));
Thug

Example on how to control tweens using Ticker in CreateJS

I'm working with CreateJS and wondered if anyone here has examples of controlling tweens using the Ticker object. I'm trying to get a sprite to follow a path defined by waypoints but i don't want to control each tween (in between waypoints) by time. I want to have smooth movement between each waypoint controlled by the Ticker object. I tried this code which doesn't seem to work at all.
var index = 0;
function move(){
index++;
if (index < path.length) {
createjs.Tween.get(person)
.to({x:gridSize * path[index][0] - pathOffset,y:gridSize * path[index][1] - pathOffset})
.call(move);
}
}
move();
createjs.Ticker.setFPS(30);
createjs.Ticker.addEventListener("tick", function(event){
createjs.Tween.tick(1);
stage.update();
});
This code seems to only jump between waypoints and not tween at all. Any ideas what i may be doing wrong or any code/tutorials which might help?
You need to add a duration(in milliseconds) to your tween, otherwise it would default to 0, this will cause the "jump", e.g.: 500 for half a second
instead of: .to({x:..., y:...})
use: .to({x:..., y:...},500)
And a second thing: You don't NEED to call createjs.Tween.tick(1); this is usually called automatically by the Tween-class.
Here is some help and some small examples: http://www.createjs.com/Docs/TweenJS/classes/Tween.html
Advanced Examples:
https://github.com/CreateJS/TweenJS/tree/master/examples

Resources