Skip to content

Instantly share code, notes, and snippets.

@curran
Last active Mar 25, 2017
Embed
What would you like to do?
Stopwatch
license: mit

A stopwatch app constructed using d3-component.

The following components are defined and used to construct the app:

  • app
    The top-level app component. This serves as a "container component" that holds the state of the app, and exposes "actions" to children (similar pattern to Redux. This component manages an instance of d3-timer that keeps track of passing time.

  • buttonPanel
    The panel that contains the two buttons. This shows a simple composition pattern in which props are passed through to child components.

  • resetButton
    The button that says "Reset" and invokes the reset action when clicked.

  • startStopButton
    The button that either starts or stops (pauses) the stopwatch when clicked, depending on whether the stopwatch is running or not.

  • button
    A generic Button component, used by both resetButton and startStopButton.

  • timeDisplay
    A display of the current time.

Built with blockbuilder.org

forked from curran's block: Posts with d3-component


<!DOCTYPE html>
<head>
<meta charset="utf-8">
<script src="https://unpkg.com/d3@4"></script>
<script src="https://unpkg.com/d3-component@3"></script>
<script src="https://unpkg.com/redux@3/dist/redux.min.js"></script>
<style>
body {
text-align: center;
margin-top: 75px;
}
.time-display {
color: #3d3d3d;
font-size: 10em;
font-family: mono;
cursor: default;
}
button {
background-color: #7c7c7c;
border: solid 3px #7c7c7c;
border-radius: 11px;
color: white;
padding: 20px 60px;
margin: 20px;
font-size: 58px;
cursor: pointer;
}
button:hover {
border: solid 3px black;
}
button:focus {
outline: none;
}
</style>
</head>
<body>
<script>
// This function formats the stopwatch time.
var stopwatchFormat = (function (){
var twoDigits = d3.format("02.0f");
return function (milliseconds){
var centiseconds = Math.floor(milliseconds / 10),
centisecond = centiseconds % 100,
seconds = Math.floor(centiseconds /100),
second = seconds % 60,
minutes = Math.floor(seconds / 60),
minute = minutes % 60,
hours = Math.floor(minutes / 60);
return [
hours >= 1 ? hours + ":" : "",
minutes >= 1 ? (
(hours >= 1 ? twoDigits(minute) : minute) + ":"
) : "",
(minutes >= 1 ? twoDigits(second) : second),
hours < 1 ? ":" + twoDigits(centisecond) : "",
].join("").replace(/0/g, "O"); // Don't show the dot in the zeros.
}
}());
// A component that renders the formatted stopwatch time.
var timeDisplay = (function (){
var timerLocal = d3.local();
return d3.component("div", "time-display")
.render(function (selection, d){
var timer = timerLocal.get(selection.node());
timer && timer.stop();
if(d.stopped){
selection.text(stopwatchFormat(d.stopTime - d.startTime));
} else {
timerLocal.set(selection.node(), d3.timer(function (){
selection.text(stopwatchFormat(Date.now() - d.startTime));
}));
}
})
.destroy(function (selection){
var timer = timerLocal.get(selection.node());
timer && timer.stop();
});
}());
// A generic Button component.
var button = d3.component("button")
.render(function (selection, d){
selection
.text(d.text)
.on("mousedown", d.onClick);
});
// The button that either starts or stops (pauses) the stopwatch,
//depending on whether the stopwatch is running or not.
var startStopButton = d3.component("span")
.render(function (selection, d){
selection.call(button, {
text: d.stopped ? "Start" : "Stop",
onClick: d.stopped ? d.actions.start : d.actions.stop
});
});
// The reset button that stops and resets the stopwatch.
var resetButton = d3.component("span")
.render(function (selection, d){
selection.call(button, {
text: "Reset",
onClick: d.actions.reset
});
});
// The panel that contains the two buttons.
var buttonPanel = d3.component("div")
.render(function (selection, d){
selection
.call(startStopButton, d)
.call(resetButton, d);
});
// The top-level app component.
var app = d3.component("div")
.render(function (selection, d){
selection
.call(timeDisplay, d)
.call(buttonPanel, d);
});
function main(){
var store = Redux.createStore(reducer),
actions = actionsFromDispatch(store.dispatch);
store.subscribe(render);
actions.reset();
function reducer (state, action){
var state = state || {
stopped: true
};
switch (action.type) {
case "START":
return Object.assign({}, state, {
stopped: false,
startTime: Date.now() - (state.stopTime - state.startTime)
});
case "STOP":
return Object.assign({}, state, {
stopped: true,
stopTime: Date.now()
});
case "RESET":
var now = Date.now();
return Object.assign({}, state, {
stopped: true,
startTime: now,
stopTime: now
});
default:
return state;
}
}
function actionsFromDispatch(dispatch){
return {
start: function (){
dispatch({ type: "START" });
},
stop: function (){
dispatch({ type: "STOP" });
},
reset: function (){
dispatch({ type: "RESET" });
}
}
}
function render(){
console.log(store.getState());
d3.select("body").call(app, store.getState(), {
actions: actions
});
}
}
main();
</script>
</body>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment