// The time duration of our timer var maxDuration = 10000; // d3.timer wrapped in an observable const timer$ = Rx.Observable.create((observer) => { // On subscribe, create a new timer const t = d3.timer(elapsed => { // Pass the elapsed time from the timer observer.next(elapsed); }); // Stop the timer when unsubscribed return t.stop; }); // Create click events for the play, pause, stop, and speed buttons const play$ = Rx.Observable.fromEvent(document.querySelector("#play"), "click"); const pause$ = Rx.Observable.fromEvent(document.querySelector("#pause"), "click"); const stop$ = Rx.Observable.fromEvent(document.querySelector("#stop"), "click"); // Map to speed 1x button to the value 1 on click const speed1x$ = Rx.Observable.fromEvent(document.querySelector("#speed1x"), "click") .mapTo(1); // Map the speed 2x button to the value 2 on click const speed2x$ = Rx.Observable.fromEvent(document.querySelector("#speed2x"), "click") .mapTo(2); // Create the change event for the slider for when a person clicks on the timer. // Map it to the new value of the slider and convert that to a position on our timeline var slider = document.querySelector("input"); var sliderChange$ = Rx.Observable.fromEvent(slider, "change") .map(evt=>evt.target.value / 100 * maxDuration); // Merge the speeds together into 1 stream, and start with 1 by default const speed$ = speed1x$ .merge(speed2x$) .startWith(1); // An observable of new times to jump to based on either stopping or clicking the timer range const scrub$ = stop$.mapTo(0) .merge(sliderChange$); // Whenever someone presses play, create a new timer and calculate the intervals between each elapsed time const interval$ = play$.switchMap(() => timer$ .startWith(0) // start with 0 .pairwise() // emit the last 2 values at a time in an array .map(([a, b]) => b - a) // calculate the difference between the last two values .combineLatest(speed$, (interval, speed) => interval * speed) // multiply the interval by the current speed .takeUntil(pause$.merge(stop$)) // emit these timer intervals until a pause or stop event ); // Use the interval stream with the scrub stream to either add time or jump in time const time$ = interval$ .map(m => (v) => v + m) // When interval stream fires, pass a function that takes previous value and adds latest interval .merge(scrub$.map(m => (v) => m)) // When the scrub stream fires, jump to the new value .startWith(0) // start the time at 0 .scan((val, fn) => fn(val)) // for each value, apply the latest function that fired (either increment or jump) .filter(t=> t <= maxDuration); // only take times within our max duration range // Subscribe: Print the time and update the slider position time$.subscribe(t=>{ document.querySelector("#time").innerHTML = t slider.value = t / maxDuration * 100; });