d3.js - transition interruption event? - events

I was wondering how to handle the fact that an interrupted transition within d3.js does not trigger an end event. As the API doc says
Note that if the transition is superseded by a later-scheduled
transition on a given element, no end event will be dispatched for
that element; interrupted transitions do not trigger end events.
from: https://github.com/mbostock/d3/wiki/Transitions#control
In my case transitions are triggered by user interaction. These transitions might be interrupted when the user triggers a new transition through mouse click. Let's say in the first transition an element was meant to fade out and be removed at the end of the transition. If this transition is interrupted the element will never be removed. I could disallow further user interaction during the time a transition happens yet that is not really what I want (particular as i have back and forward buttons which allow the user to click through previous states of my svg graph quickly ... ) Basically I would need an "Interruption Event"
Thanks
martin

I think there is no really satisfactory way to do this. A little bit painful workaround would be counting the number of transitions currently taking place and reasoning from that.
So, initialize:
var transitionCount = 0;
And whenever you schedule new transitions:
if ( transitionCount != 0 ) {
// handle interrupted transitions here somehow
transitionCount = 0;
}
var myTransition = selection.transition().... ;
transitionCount += myTransition.size();
myTransition.each('end', function() { transitionCount --; });
If you can handle manually cleaning up interrupted transitions like this, this would be fine. Notice, that you can't use 'start' events to increment the counter as there is a delay between scheduling a transition and it being started so you'd get a race condition there.

Related

Compose event streams with a resettable delay on one of them

I want to change the size of an element while it is being scrolled (touch pan, etc). In order to do that I intend to use touch events as observable sources. The size should grow when user starts to pan the element and should shrink 5s after user stopped.
In other words:
Given the streams of 'start' and 'end' events, immediately do the action on 'start' and delay the action on 'end'. But if during that delay new event 'start' come, the delay should be cancelled.
Here is marble, the best I could do.
PS
Feel free to suggest a better title. I struggle to come up with something descriptive enough.
Assuming that start$ and end$ are the Observable streams representing the start and end events, than I would probably proceed like this
start$.pipe(
// use tap to implement the side effect of growing the size of an element
// the event is passed assuming that it contains somehow the reference to the element
// whose size has to grow
tap(event => growSize(event)),
// any time a start event is notified, we pass the control to the end$ stream
// if a new start event is notified, the subscription to end$ will be unsubscribed
// a new subscription will be establishes, which means that a new delay will start
switchMap(() =>
end$.pipe(
// delay 5 seconds
delay(5000),
// decrease the size of the element as a side effect
tap(event => decreaseSize(event)),
)
)
)
.subscribe()
You could merge your start and end triggers and use debounceTime with filter:
merge(start$, end$).pipe(
debounceTime(5000),
filter(event => event === 'end')
);
Here's a little StackBlitz to play with.

How do you make multiple animations start from clicking one object?

I've started learning a-frame recently and I'm trying to create a domino effect type thing. I want all of my animations to start after I click on the first object. I've tried using delay but the delay starts immediately instead of after I start the animation. How do I make it so after someone clicks object 1, object 2's animation would start shortly after?
Let's try the delay approach - with a custom component for some managment :)
Lets say you have a setup like this (html pseudocode):
<a-box class='domino' foo animation='startEvents: go; ...
<a-box class='domino' animation='startEvents: go; delay: 200 ...
<a-box class='domino' animation='startEvents: go; delay: 400 ...
All items have some attributes / components:
The class attribute to make them easy to both grab and distinguish from any other entities.
The animation component which will make them animate - whats important: startEvents - here we'll throw the event which will be emitted simultaneously on all boxes, delay - so every next box will wait before moving.
This foo component - we'll make it by ourselves. It will detect a mouseclick, and emit the go event on all boxes.
So the concept is as follows:
We click one box with the foo component
the foo component detects the click and emits go on all .domino elements
all .domino elements should start their animations one after another - but each one 200ms later than the previous one.
Now lets make the custom component. Keep in mind it has to be defined before the <a-scene>:
<script src='component.js'></script>
<script>
// component
</script>
<a-scene>
</a-scene>
We will implement all logic within the init function - which is called upon initialisation.
// component definition
AFRAME.registerComponent('foo', {
// this is called upon initialisation
init: function() {
// grab all the domino pieces
var pieces = document.getElementsByClassName('domino')
// if this one gets pressed...
this.el.addEventListener('click', e => {
// ...emit the event on all of them
for (let piece of pieces) {
piece.emit('go')
}
})
}
})
and actually - thats it. To see it in action - here are two examples - in both clicking the blue box, should start the animation.
Simple delayed animation cascade
Domino-like animation

Controlling animations in an animator via parameters, in sequences

So I am animating an avatar, and this avatar has its own animator with states and such.
When interacting with props, the props itself has an animator with states in it. In both case, I transition to some animations through parameters in the animator (bool type).
For example, for a door, the character will have "isOpeningDoor", while the door will have "isOpen".
Now the question: when I change the value on an animator on GO1, and then change the bool on GO2; do the first animation finish and then the second start? Because in my case, it does not happen; they start almost at the same time.
void OnTriggerEnter (collider door)
{
if (door.gameObject.tag=="door")
{
GOAnimator1.SetBool("isOpeningDoor", true);
GOAnimator2.SetBool("isOpen", true);
}
}
I believe that I am doing it wrong, since I change the parameter on the animator, but I do not check for the animation to end; is this even possible or am I doing something not kosher?
I really think it might be doable!
As you have it in your code now, the animations on GO1 and GO2 start at almost the same time because that's how it's written. The OnTriggerEnter() function will complete the execution in the frame it is called, and return the control to Unity.
What I think that might help you are coroutines and sendMessage between gameobjects:
http://docs.unity3d.com/Manual/Coroutines.html
http://docs.unity3d.com/ScriptReference/GameObject.SendMessage.html
The idea is to:
Create a coroutine in GO2 that waits an amount of time until it sets the GOAnimator2 variable to activate the door animation.
Create a function in GO2 that calls the aforementioned coroutine
From the OnTriggerEnter() send a message to GO2 to execute the newly created function
It reads complicated, but it's fairly simple. The execution would be like this:
1.Code for the coroutine:
function GO2coroutine(){
float timeToWait = 0.5f; //Tweak this
for ( float t = 0f; t < timeToWait; t+=time.deltaTime)
yield;
GetComponent<Animator>().SetBool("isOpen",true);
}
Code for the function calling it:
function callCoroutine() {
StartCoroutine("Fade");
}
And the code modification for your OnTriggerEnter():
void OnTriggerEnter (collider door)
{
if (door.gameObject.tag=="door")
{
GOAnimator1.SetBool("isOpeningDoor", true);
GO2.SendMessage("callCoroutine");
}
}
I didn't have a chance to test the code, so please don't copy paste it, there might be slight changes to do.
There is another way, but I don't like it much. That is making the animation longer with an idle status to wait for the first game object animation to end... but it will be a hassle in case you shorten the animation because you have to, or have any other models or events.
Anyway, I think the way to go is with the coroutine! Good Luck!

Double tap recognition takes too long? (Hammer.js 2.0)

I am programming a highly responsive web application and came across the issue that most time is used to recognize double taps.
I am using this code from the website:
var singleTap = new Hammer.Tap({ event: 'singletap' });
var doubleTap = new Hammer.Tap({event: 'doubletap', taps: 2 });
hammer.add([doubleTap, singleTap]);
doubleTap.recognizeWith(singleTap);
singleTap.requireFailure(doubleTap);
This basically works quite fine. However, due to the timeouts/intervals the recognition of a double tap takes quite "long". I guess its about 2 times the interval - one for each tap.
The waiting for the last interval (waiting for a third tap) is senseless in my scenario.
Is there any "ok tapCount == 2, we fire now and don't wait any longer"-TapRecognizer option?
Update, I have done some logging:
First column: passed ms since first event
0 input: mousedown
74ms input: mouseup
145ms input: mousedown
218ms input: mouseup
520ms double tap
-
0 input: mousedown
64ms input: mouseup
366ms single tap
This confirms my theory that double tap is waiting for a third click but I don't think there's an option to disable this.
I share my solution to the problem:
I copied the TapRecognizer and named it DblTapRecognizer. The interesting source code lines are:
if (tapCount === 0) {
// no failing requirements, immediately trigger the tap event
// or wait as long as the multitap interval to trigger
if (!this.hasRequireFailures()) {
return STATE_RECOGNIZED;
} else {
this._timer = setTimeoutContext(function() {
this.state = STATE_RECOGNIZED;
this.tryEmit();
}, options.interval, this);
return STATE_BEGAN;
}
}
"if (!this.hasRequireFailures())" seems to misbehave in my situation, since the comment hints at immediate firing... So just "return STATE_RECOGNIZED;" and delete the rest for the DblTapRecognizer.
We ran into similar slowness issues. Apparently there is an inherent lag on tap action on touch devices.
We ended up using FastClick
All you need to do is FastClick.attach(document.body);
This improved the "tap performance" for us.

SlickGrid 'mouseleave' event not fired when row invalidated after 'mouseenter' fired

I have a slickgrid that, in the 'onMouseEnter' event, I do the following:
change the underlying css for the row in question
call grid.invalidateRow()
call grid.render()
These latter two calls are necessary for the new css classes to be reflected. However, I then need to capture the onMouseLeave event, and it is not fired when I move my mouse away from the cell (or row), presumably because the call to invalidate/render has placed a new DOM element under my mouse, and it's no longer the one I initially "entered."
So I have two questions:
Is there another way to have the new css classes for a given cell be rendered without calling invalidateRow/render?
If not, is there another way to do this and still have the onMouseLeave event fired?
One option is to use the setCellCssStyles function.
grid.onMouseEnter.subscribe(function(e, args){
var cell = grid.getCellFromEvent(e),
param = {},
columnCss = {};
for(index in columns){
var id = columns[index].id;
columnCss[id] = 'my_highlighter_style'
}
param[cell.row] = columnCss
args.grid.setCellCssStyles("row_highlighter", param);
})
So the above changes the background-color of every cell of the row that has been moused into. In my fiddle, the mouseLeave subscription performs a simple console.log to ensure it is still firing.
Edit: fixed the external resource usages in the fiddle for cross-browser support

Resources