Rxjs throttleTime - update the throttling in realtime? - rxjs

Is there a way to update the throttleTime in Rxjs. You're scrolling near the bottom of the page, so we only need throttle every 400ms, while as you get nearer the top, we increate to 100ms as the target area is in that region
this.scrollSubscription$ = this.scrollSubject$
.asObservable()
.throttleTime(200, undefined, throttleTimeConfig)
.subscribe(event => this.throttledScrollHandler(event));
What I am going to do is have two separate subscripbtions, as I cannot find a way of doing it.
this.slowerSubscriber$ = this.scrollSubject$
.asObservable()
.throttleTime(400,.......
Thought I would ask as I can't think of another way of achieving it

You can use throttle to implement your own silencing duration:
// Make this function extract the position using scrollY, page offsets or whatever you want to use
const getScrollPositionFromEvent = event => 42;
this.scrollSubject$.asObservable()
.throttle(event => Observable.interval(
getScrollPositionFromEvent(event) <= 100 ? 100 : 400
))
.subscribe(console.log);
You can see a working example here. It simply emits by a given input, which in your case would be based on the scroll offset.

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.

THREE.Audio filter not ramping up with linearRampToValueAtTime

I'm having a little trouble working with the linearRampToValueAtTime on a BiQuadFilter applied to a WebAudio.
The audio works ok, and the initial lowpass filter is applied.
Problem is, as soon as I use the linearRamp method to bring up the frequency, it seems to ignore the endTime parameter (or better, it's not time correctly).
Some code to explain it better.
Here's the instancing:
this.audioLoader.load( 'public/media/soundtrack-es_cobwebs_in_the_sky.mp3', buffer => {
this.sounds.soundtrack = new THREE.Audio(this.listener);
const audioContext = this.sounds.soundtrack.context;
this.biquadFilter = audioContext.createBiquadFilter();
this.biquadFilter.type = "lowpass"; // Low pass filter
this.biquadFilter.frequency.setValueAtTime(200, audioContext.currentTime);
this.sounds.soundtrack.setBuffer(buffer);
this.sounds.soundtrack.setFilter(this.biquadFilter);
this.sounds.soundtrack.setVolume(0.5);
this.sounds.soundtrack.play();
})
Until here, everything looks ok. The sound plays muffled as needed.
Then, after a certain event, there's a camera transition, where I want the sound to gradually open up.
As a endTime parameter, I'm passing 2 seconds + the internal context delta.
this.sounds.soundtrack.filters[0].frequency.linearRampToValueAtTime(2400, 2 + this.sounds.soundtrack.context.currentTime);
Expecting to hear the ramp in two seconds, but the sound opens up immediately.
What am I missing?
The linear ramp will be applied using the previous event as the startTime. In your case that will be audioContext.currentTime at the point in time when you created the filter. If that is sufficiently long ago it will sound as if the ramp jumps right to the end value. You can fix that by inserting a new event right before the ramp.
const currentTime = this.sounds.soundtrack.context.currentTime;
const filter = this.sounds.soundtrack.filters[0];
filter.frequency.setValueAtTime(200, currentTime);
filter.frequency.linearRampToValueAtTime(2400, currentTime + 2);

RxJS Buffer, how to group multi click events as stream

In RxJS version 2.2.26
The following code produced a stream that would emit the number of clicks (double click, triple click etc..)
var multiClickStream = clickStream
.buffer(function() { return clickStream.throttle(250); })
.map(function(list) { return list.length; })
.filter(function(x) { return x >= 2; });
In RxJS version 4.0.6
This code no longer produces the desired result.
How can I get that same functionality in RxJS 4.0.6?
Working 2.2.26 Example
Broken 4.0.6 Example
There is actually a much better way to find double clicks via RxJs:
var mouseDowns = Rx.Observable.fromEvent(button, "mousedown");
var doubleClicks = mouseDowns.timeInterval()
.scan<number>((acc, val) => val.interval < 250 ? acc + 1 : 0, 0)
.filter(val => val == 1);
The benefit of this is that you don't need to wait for the full 250ms to recognize the double click - if the user has only 120ms between clicks there is a noticable delay between the double click and the resulting action.
Notice, that through counting up (with .scan()) and filtering to the first count we can limit our stream to just double-clicks and ignore every fast click after that - so click click click will not result in two double clicks.
Just in case someone is wondering on how to do the same thing with RxJS 5 (5.0.0-beta.10), this is how I got it working:
const single$ = Rx.Observable.fromEvent(button, 'click');
single$
.bufferWhen(() => single$.debounceTime(250))
.map(list => list.length)
.filter(length => length >= 2)
.subscribe(totalClicks => {
console.log(`multi clicks total: ${totalClicks}`);
});
I spent a lot of time trying to figure this out as I'm also learning Reactive Programming (with RxJS) so, if you see a problem with this implementation or know of a better way to do the same thing, I'll be very glad to know!
Detect single or double-clicks, but not multi-clicks
Here's a solution I came up with that creates a stream of single or double clicks (not anything more, so no triple clicks). The purpose is to detect multiple double clicks in quick succession, but also to receive notifications of single clicks.
let click$ = Observable.fromEvent(theElement, "click");
let trigger$ = click$.exhaustMap(r =>
click$.merge(Observable.timer(250)).take(1));
let multiclick$ = click$.buffer(trigger$).map(r => r.length);
The reasoning is this, we use the buffer operator to group clicks, and design a buffer trigger event as follows: Every time a click happens, we start a race between two observables:
A 250 msec timer
The original click stream
Once this race concludes (we use take(1) because we are only interested in the first event) we emit the buffer trigger event. What does this do? It makes the buffering stop either when a 2nd click arrives, or when 250 msec have elapsed.
We use the exhaustMap operator to suppress the creation of another race while one is already going on. This would otherwise occur for the 2nd click in a double-click pair.
Here's another take (produces N doubleclicks if the user quickly clicks 2*N times):
const clicks = fromEvent(document, 'click');
const doubleclicks = clicks.pipe(
exhaustMap(() => clicks.pipe(takeUntil(interval(250)))),
);
This answer is inspired by #Aviad P.
I believe the following code is the right answer. It meets all these requirements.
It differentiates single-click, double-click, and triple-click.
No unrecognized clicks will be streamed. i.e. If you click the mouse 6 times quickly, you will get 2 triple-clicks. If you click 5 times, you get one triple-click and one double-click. etc
triple-click will be fired immediately, no need to wait for the whole time-window complete.
No clicks will be lost.
It's easy to make it accept four-click, five-click...
const btn = document.querySelector('#btn');
const click$ = Rx.Observable.fromEvent(btn, "click");
const trigger$ =
click$.exhaustMap(r =>
click$
.take(2)
.last()
.race(click$
.startWith(0)
.debounceTime(500)
.take(1))
);
click$
.buffer(trigger$)
.map(l => l.length)
.map(x => ({
1: 'single',
2: 'double',
3: 'tripple'
}[x]))
.map(c => c + '-click')
.subscribe(log);
Rx.Observable.fromEvent(document.querySelector('#reset'), 'click')
.subscribe(clearLog);
function log(message) {
document.querySelector('.log-content').innerHTML += ('<div>' + message + '</div>');
}
function clearLog() {
document.querySelector('.log-content').innerHTML = null;
}
.log {
padding: 1rem;
background-color: lightgrey;
margin: 0.5rem 0;
}
.log-content {
border-top: 1px solid grey;
margin-top: 0.5rem;
min-height: 2rem;
}
.log-header {
display: flex;
justify-content: space-between;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/rxjs/5.5.7/Rx.js"></script>
<button id='btn'>Click Me</button>
<button type="button" id='reset'> Reset </button>
<div class='log' id='logpanel'>
<div class='log-header'>
Log:
</div>
<div class="log-content"></div>
</div>
A more generic solution: starting from a source observable, detect and propagate when this observable quickly emits the exact same value (at least) twice quickly in a row:
var scheduler = Rx.Scheduler.default; // replace with TestScheduler for unit tests
var events = // some observable
var doubleClickedEvents = events
/* Collect the current and previous value */
.startWith(null) // startWith null is necessary if the double click happens immediately
.pairwise()
.map(pair => ({ previous: pair[0], current: pair[1] })
/* Time the interval between selections. */
.timeInterval(scheduler)
/* Only accept two events when they happen within 500ms */
.filter(timedPair => timedPair.interval < 500)
.map(timedPair => timedPair.value)
/* Only accept two events when they contain the same value */
.filter(pair => pair.previous && pair.previous === pair.current)
.map(pair => pair.current)
/* Throttle output. This way, triple and quadruple clicks are filtered out */
.throttle(500, scheduler)
The difference with the provided solutions is:
Like Wolfgang's solution, this observable will immediately emit the value at the exact moment a 'double click' happens, not after a debounce time of 500ms
Does not needlessly accumulate the amount of clicks. If you ever need to implement 'Triple-click' behavior, then Wolfgang's solution is better. But for double click behavior, this can be replaced with startWith(null).pairwise()
Uses throttle to hold back multiple double clicks.
Actually propagates the values that are emitted from the original events, not just a 'signal'.
throttle was changed to debounce starting in RxJS 3 I believe.
There was a big debate about this way back when because the original naming scheme didn't actually match with other implementations of Rx or the actual definition.
https://github.com/Reactive-Extensions/RxJS/issues/284
And it even managed to confuse people further down the road in the reimplementation of RxJS 5:
https://github.com/ReactiveX/rxjs/issues/480
Found a way fit better to distinguish between single / double click. (So I can get either Single-Click or Double-Click, but not both)
For double click, this handled fast double click 2*N times produce N events:
Observable.prototype.doubleEvent = function(timing = 150, count = 2, evt = this) { /// flexibility to handle multiple events > 2
return this.exhaustMap( () => /// take over tapping event,
evt.takeUntil( Observable.timer(timing) ) /// until time up,
.take(count-1) /// or until matching count.
);
}
Note: argument evt = this make doubleEvent works with startWith(), to take this Observable correctly.
Usage:
this.onTap
.doubleEvent()
.subscribe( (result) => console.log('double click!') );
For distinguish between single / double click:
Observable.prototype.singleEvent = function(timing = 150) {
return this.mergeMap( /// parallel all single click event
() => Observable.timer(timing) /// fire event when time up
);
}
Observable.prototype.singleOrDoubleEvent = function(timing = 150) {
return this.exhaustMap( (e) => /// take over tapping event
Observable.race( /// wait until single or double event raise
this.startWith(e).doubleEvent(timing, 2, this)
.take(1).mapTo(1),
this.startWith(e).singleEvent(timing)
.take(1).mapTo(0),
)
);
}
Usage:
this.onTap
.singleOrDoubleEvent()
.subscribe( (result) => console.log('click result: ', result===1 ? 'double click' : 'single click') );

d3.js - transition interruption event?

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.

How to add a timing event

Is there a way when I click on a ToolStripButton, it shows the WaitCursor and updates the StatusStrip for about 10 seconds, then returns back to normal. I just don't know how to type in the coding.
If someone could guide me through the process. (or even give me the code)
Thank You
J Mahone
Using a timer:
1:Add a timer from your component toolbox to your form.
2:Set the inteval to 10,000 (this is in milliseconds, 1000 = 1 second)
3:In the timers' "Tick" event, write this code:
Timer1.stop 'This assumes your timer it named Timer1
Me.Cursor = Cursors.Default
4: When you want to make it show the cursor, either have a method to do both these lines and call the method or just write these 2 lines all over the place:
Me.Cursor = Cursors.WaitCursor
Timer1.Start
I'd suggest to use async/await to keep the "normal" program flow.
Me.Cursor = Cursors.WaitCursor
Await Task.Delay(10000)
Me.Cursor = Cursors.Default

Resources