block by tophtucker 2b5afc7a67cde570cb7d

Timecoder

Full Screen

index.html

<!DOCTYPE html>
<html manifest="timecoder.appcache?v=2">
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" />
<meta name="apple-mobile-web-app-capable" content="yes">

<link rel="shortcut icon" href="favicon.png" type="image/x-icon">
<link rel="apple-touch-icon" href="favicon.png">

<title>Timecoder</title>

<style>
  
html, body {
  margin: 0;
  padding: 0;
  font: 14px sans-serif;
}

* {
  box-sizing: border-box;
}

.record {
  width: 100%;
  font: 28px sans-serif;
  position: fixed;
  top: 0;
  left: 0;
  background: white;
  border: 1px solid black;
  padding: 1em;
  -webkit-touch-callout: none;
  -webkit-tap-highlight-color: rgba(0,0,0,0);
  -webkit-user-select: none;
  -khtml-user-select: none;
  -moz-user-select: none;
  -ms-user-select: none;
  user-select: none;
}

.record:hover {
  background: #ccc;
  cursor: pointer;
}

.record:active {
  color: white;
  background: black;
}

table {
  margin-top: 90px;
  width: 100%;
  border-collapse: collapse;
}

table th, table td {
  padding: 1em;
  width: 33.3%;
  border: 1px solid #ccc;
  text-align: center;
}

hr {
  border: none;
  border-top: 1px solid gray;
}

pre {
  margin-top: 1em;
  width: 100%;
  border-top: 1px solid gray;
  color: gray;
  font-family: monospace;
}

</style>

<body>
  <button class="record">Press and hold</button>
  <div class="table"></div>

  <hr>

  <button class="clear">Clear times</button>
  <button class="epoch">Start timing from now</button> 
  <p>CSV (in seconds after time started): </p>
  <pre></pre>
</body>

<script src="d3.min.js" charset="utf-8"></script>
<script src="d3-jetpack.js" charset="utf-8"></script>

<script>
  
// simple table from
// //bl.ocks.org/gka/17ee676dc59aa752b4e6

var epoch = new Date(0);
var times = [];

// persist times
if(typeof(Storage) !== "undefined") {
  if(!localStorage.getItem("times")) {
    save("times", times);
  } else {
    times = read("times");
  }

  if(!localStorage.getItem("epoch")) {
    localStorage.setItem("epoch", epoch);
  } else {
    epoch = new Date(localStorage.getItem("epoch"));
  }
}

var columns = [
  { head: 'Start', cl: 'start', html: ƒ(0, timeFormat) },
  { head: 'End', cl: 'stop', html: ƒ(1, timeFormat) },
  { head: 'Duration', cl: 'duration', html: diffFormat },
];

// create table
var table = d3.select('.table')
    .append('table');

// create table header
table.append('thead').append('tr')
    .selectAll('th')
    .data(columns).enter()
    .append('th')
    .attr('class', ƒ('cl'))
    .text(ƒ('head'));

// create table body
var tbody = table.append('tbody');

d3.select(".record")
  .on("mousedown", start)
  .on("mouseup", end)
  .on("touchstart", touchStart)
  .on("touchend", touchEnd);

d3.select(".clear").on("click", clear);
d3.select(".epoch").on("click", setEpoch);

render();

function touchStart() {
  d3.event.preventDefault();
  start();
}
function start() {
  times.push([newDate(), null]);
  save("times", times);
  render();
}

function touchEnd() {
  d3.event.preventDefault();
  end();
}
function end() {
  times[times.length-1][1] = newDate();
  save("times", times);
  render();
}

function render() {
  // create empty rows and cells (enter selection)
  var update = tbody
    .selectAll('tr')
    .data(times);

  update.enter()
    .append('tr')
    .selectAll('td')
    .data(columns).enter()
    .append('td');

  // fill cells (update selection)
  update.selectAll('td')
    .data(function(row, i) {
      return columns.map(function(c) {
        // compute cell values for this specific row
        var cell = {};
        d3.keys(c).forEach(function(k) {
          cell[k] = typeof c[k] == 'function' ? c[k](row,i) : c[k];
        });
        return cell;
      });
    })
    .html(ƒ('html'))
    .attr('class', ƒ('cl'));

  update.exit().remove();

  // render csv
  var csv = times.map(function(item) {
    return item.map(function(endpoint) { return +endpoint / 1000; }).join(",")
  }).join("\n");
  d3.select("pre").text("start,end\n" + csv);
}

function clear() {
  if(confirm("Are you sure you want to permanently delete all your recorded times?")) {
    times = [];
    save("times", times);
    render();
  }
}

function setEpoch() {
  if(confirm("New items will show times counting from the moment you hit 'OK'")) {
    epoch = new Date();
    localStorage.setItem("epoch", epoch);
  }
}

function timeFormat(d) {
  if(d === null) return '⋯';

  if(epoch - new Date(0) !== 0) {
    renderTime = new Date(+d + d.getTimezoneOffset()*60*1000);
  } else {
    renderTime = d;
  }

  return d3.time.format('%X')(renderTime);
}

function diffFormat(d) {
  if(d[1] === null) return '⋯';
  return d3.time.format('%M:%S.%L')(new Date(d[1] - d[0]));
}

function save(key, value) {
  return localStorage.setItem(key, JSON.stringify(value));
}

function read(key) {
  var json = JSON.parse(localStorage.getItem(key));
  return json.map(function(item) {
    return item.map(function(endpoint) {
      return new Date(endpoint);
    })
  });
}

function newDate() {
  return new Date(new Date() - epoch);
}

</script>

</html>

d3-jetpack.js

(function() {
        
    function jetpack(d3) {
        d3.selection.prototype.translate = function(xy) {
            return this.attr('transform', function(d,i) {
                return 'translate('+[typeof xy == 'function' ? xy(d,i) : xy]+')';
            });
        };

        d3.transition.prototype.translate = function(xy) {
            return this.attr('transform', function(d,i) {
                return 'translate('+[typeof xy == 'function' ? xy(d,i) : xy]+')';
            });
        };

        d3.selection.prototype.tspans = function(lines, lh) {
            return this.selectAll('tspan')
                .data(lines)
                .enter()
                .append('tspan')
                .text(function(d) { return d; })
                .attr('x', 0)
                .attr('dy', function(d,i) { return i ? lh || 15 : 0; });
        };

        d3.selection.prototype.append = 
        d3.selection.enter.prototype.append = function(name) {
            var n = d3_parse_attributes(name), s;
            //console.log(name, n);
            name = n.attr ? n.tag : name;
            name = d3_selection_creator(name);
            s = this.select(function() {
                return this.appendChild(name.apply(this, arguments));
            });
            return n.attr ? s.attr(n.attr) : s;
        };

        d3.selection.prototype.insert = 
        d3.selection.enter.prototype.insert = function(name, before) {
            var n = d3_parse_attributes(name), s;
            name = n.attr ? n.tag : name;
            name = d3_selection_creator(name);
            before = d3_selection_selector(before);
            s = this.select(function() {
                return this.insertBefore(name.apply(this, arguments), before.apply(this, arguments) || null);
            });
            return n.attr ? s.attr(n.attr) : s;
        };

        var d3_parse_attributes_regex = /([\.#])/g;

        function d3_parse_attributes(name) {
            if (typeof name === "string") {
                var attr = {},
                    parts = name.split(d3_parse_attributes_regex), p;
                    name = parts.shift();
                while ((p = parts.shift())) {
                    if (p == '.') attr['class'] = attr['class'] ? attr['class'] + ' ' + parts.shift() : parts.shift();
                    else if (p == '#') attr.id = parts.shift();
                }
                return attr.id || attr['class'] ? { tag: name, attr: attr } : name;
            }
            return name;
        }

        function d3_selection_creator(name) {
            return typeof name === "function" ? name : (name = d3.ns.qualify(name)).local ? function() {
                return this.ownerDocument.createElementNS(name.space, name.local);
            } : function() {
                return this.ownerDocument.createElementNS(this.namespaceURI, name);
            };
        }

        function d3_selection_selector(selector) {
            return typeof selector === "function" ? selector : function() {
                return this.querySelector(selector);
            };
        }

        d3.wordwrap = function(line, maxCharactersPerLine) {
            var w = line.split(' '),
                lines = [],
                words = [],
                maxChars = maxCharactersPerLine || 40,
                l = 0;
            w.forEach(function(d) {
                if (l+d.length > maxChars) {
                    lines.push(words.join(' '));
                    words.length = 0;
                    l = 0;
                }
                l += d.length;
                words.push(d);
            });
            if (words.length) {
                lines.push(words.join(' '));
            }
            return lines;
        };
        
        d3.ascendingKey = function(key) {
            return typeof key == 'function' ? function (a, b) {
                  return key(a) < key(b) ? -1 : key(a) > key(b) ? 1 : key(a) >= key(b) ? 0 : NaN;
            } : function (a, b) {
                  return a[key] < b[key] ? -1 : a[key] > b[key] ? 1 : a[key] >= b[key] ? 0 : NaN;
            };
        };

        d3.descendingKey = function(key) {
            return typeof key == 'function' ? function (a, b) {
                return key(b) < key(a) ? -1 : key(b) > key(a) ? 1 : key(b) >= key(a) ? 0 : NaN;
            } : function (a, b) {
                return b[key] < a[key] ? -1 : b[key] > a[key] ? 1 : b[key] >= a[key] ? 0 : NaN;
            };
        };
        
        d3.f = function(){
            var functions = arguments;
            //convert all string arguments into field accessors
            var i = 0, l = functions.length;
            while (i < l) {
                if (typeof(functions[i]) === 'string' || typeof(functions[i]) === 'number'){
                    functions[i] = (function(str){ return function(d){ return d[str] }; })(functions[i])
                }
                i++;
            }
             //return composition of functions
            return function(d) {
                var i=0, l = functions.length;
                while (i++ < l) d = functions[i-1].call(this, d);
                return d;
            };
        };
        // store d3.f as convenient unicode character function (alt-f on macs)
        if (!window.hasOwnProperty('ƒ')) window.ƒ = d3.f;
        
        // this tweak allows setting a listener for multiple events, jquery style
        var d3_selection_on = d3.selection.prototype.on;
        d3.selection.prototype.on = function(type, listener, capture) {
            if (typeof type == 'string' && type.indexOf(' ') > -1) {
                type = type.split(' ');
                for (var i = 0; i<type.length; i++) {
                    d3_selection_on.apply(this, [type[i], listener, capture]);
                }
            } else {
                d3_selection_on.apply(this, [type, listener, capture]);
            }
            return this;
        };
        
        // for heaven's sake, let's add prop as alias for property
        d3.selection.prototype.prop = d3.selection.prototype.property;
    }

    if (typeof d3 === 'object' && d3.version) jetpack(d3);
    else if (typeof define === 'function' && define.amd) {
        define(['d3'], jetpack);
    }

})();

timecoder.appcache

CACHE MANIFEST
# v7
d3.min.js
d3-jetpack.js
favicon.png