block by curran 593ffae30c42789a9af36f08c983867e

Stopwatch

Full Screen

A stopwatch app constructed using d3-component.

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

Built with blockbuilder.org

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


index.html

<!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>