Skip to content

Instantly share code, notes, and snippets.

@skokenes
Last active May 4, 2018 13:12
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save skokenes/80d4dd564656cc939dab521fd1bee7c5 to your computer and use it in GitHub Desktop.
Save skokenes/80d4dd564656cc939dab521fd1bee7c5 to your computer and use it in GitHub Desktop.
A simple progress bar with RxJS and D3
<html>
<head>
<script src = "https://cdnjs.cloudflare.com/ajax/libs/d3/4.9.1/d3.min.js"></script>
<script src = "https://cdnjs.cloudflare.com/ajax/libs/rxjs/5.4.1/Rx.min.js"></script>
</head>
<body>
<div id="time"></div>
<input type="range"></input>
<button id = "play">Play</button>
<button id = "pause">Pause</button>
<button id = "stop">Stop</button>
<button id = "speed1x">1x</button>
<button id = "speed2x">2x</button>
<script src = "main.js"></script>
</body>
</html>
// 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;
});
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment