diff --git a/.babelrc b/.babelrc index af0f0c3..c970805 100644 --- a/.babelrc +++ b/.babelrc @@ -1,3 +1,4 @@ { - "presets": ["es2015"] + "presets": ["es2015"], + "plugins": ["transform-runtime"] } \ No newline at end of file diff --git a/build/app.js b/build/app.js index 10c1165..949adec 100644 --- a/build/app.js +++ b/build/app.js @@ -1,4 +1,125 @@ (function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o 1) { + for (var i = 1; i < arguments.length; i++) { + args[i - 1] = arguments[i]; + } + } + queue.push(new Item(fun, args)); + if (queue.length === 1 && !draining) { + cachedSetTimeout(drainQueue, 0); + } +}; + +// v8 likes predictible objects +function Item(fun, array) { + this.fun = fun; + this.array = array; +} +Item.prototype.run = function () { + this.fun.apply(null, this.array); +}; +process.title = 'browser'; +process.browser = true; +process.env = {}; +process.argv = []; +process.version = ''; // empty string to avoid regexp issues +process.versions = {}; + +function noop() {} + +process.on = noop; +process.addListener = noop; +process.once = noop; +process.off = noop; +process.removeListener = noop; +process.removeAllListeners = noop; +process.emit = noop; + +process.binding = function (name) { + throw new Error('process.binding is not supported'); +}; + +process.cwd = function () { return '/' }; +process.chdir = function (dir) { + throw new Error('process.chdir is not supported'); +}; +process.umask = function() { return 0; }; + +},{}],2:[function(require,module,exports){ 'use strict'; var pheasant = require('./lib/pheasant.js'); @@ -19,53 +140,354 @@ window.onload = function () { return req.send(null); }; -},{"./lib/pheasant.js":2}],2:[function(require,module,exports){ +},{"./lib/pheasant.js":3}],3:[function(require,module,exports){ 'use strict'; +var _keys = require('babel-runtime/core-js/object/keys'); + +var _keys2 = _interopRequireDefault(_keys); + +var _regenerator = require('babel-runtime/regenerator'); + +var _regenerator2 = _interopRequireDefault(_regenerator); + +var _getIterator2 = require('babel-runtime/core-js/get-iterator'); + +var _getIterator3 = _interopRequireDefault(_getIterator2); + +function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } + +var _marked = [eachYear, eachDay, eachHour].map(_regenerator2.default.mark); + // Create heat map data visualization // Creates the visualization function visualize(data, domElementId, opts) { - console.log('visualizing...'); - var options = { - minColor: 0xf0f0f0, - maxColor: 0x050505, - numSteps: 5 + minColor: 0xfbfbfb, + maxColor: 0x606060, + numSteps: 10, + cellWidth: 6, + cellHeight: 6, + cellSpacing: 2 }; var element = document.getElementById(domElementId); - var canvas = createCanvas(500, 200); + var canvas = createCanvas(365 * options.cellWidth, 1000); element.appendChild(canvas); var context = canvas.getContext('2d'); - var steps = calculateColorSteps(options.minColor, options.maxColor, options.numSteps); + var stepColors = calculateColorSteps(options.minColor, options.maxColor, options.numSteps); + var valueSteps = calculateValueSteps(data, options.numSteps); + + console.log(valueSteps); + + // Ensure the data is keyed by Epoch time values - easier to work with + data = ensureEpochKeys(data); + + // Find start and end keys + + var _findStartAndEndTimes = findStartAndEndTimes(data); + + var start = _findStartAndEndTimes.start; + var end = _findStartAndEndTimes.end; + + + var x = 0, + y = 0, + yearOffset = 0; + var w = options.cellWidth, + h = options.cellHeight; // TODO dynamically calculate these from the time dimensions + + var _iteratorNormalCompletion = true; + var _didIteratorError = false; + var _iteratorError = undefined; + + try { + for (var _iterator = (0, _getIterator3.default)(eachYear(parseInt(start), parseInt(end))), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true) { + var year = _step.value; + + // Set x back to 0, and move y down if needed + x = 0; + + var _iteratorNormalCompletion2 = true; + var _didIteratorError2 = false; + var _iteratorError2 = undefined; + + try { + for (var _iterator2 = (0, _getIterator3.default)(eachDay(year)), _step2; !(_iteratorNormalCompletion2 = (_step2 = _iterator2.next()).done); _iteratorNormalCompletion2 = true) { + var day = _step2.value; + + // Set x over one and move y back up to the year offset + y = yearOffset; + + var _iteratorNormalCompletion3 = true; + var _didIteratorError3 = false; + var _iteratorError3 = undefined; + + try { + for (var _iterator3 = (0, _getIterator3.default)(eachHour(day)), _step3; !(_iteratorNormalCompletion3 = (_step3 = _iterator3.next()).done); _iteratorNormalCompletion3 = true) { + var hour = _step3.value; + + // Draw the block onto the canvas + var value = data[hour]; + var color = getColor(value, valueSteps, stepColors); + + context.fillStyle = color; + context.fillRect(x, y, w, h); + y += h + options.cellSpacing; + } + } catch (err) { + _didIteratorError3 = true; + _iteratorError3 = err; + } finally { + try { + if (!_iteratorNormalCompletion3 && _iterator3.return) { + _iterator3.return(); + } + } finally { + if (_didIteratorError3) { + throw _iteratorError3; + } + } + } + + x += w + options.cellSpacing; + } + } catch (err) { + _didIteratorError2 = true; + _iteratorError2 = err; + } finally { + try { + if (!_iteratorNormalCompletion2 && _iterator2.return) { + _iterator2.return(); + } + } finally { + if (_didIteratorError2) { + throw _iteratorError2; + } + } + } + + yearOffset += (h + options.cellSpacing) * 24; + } + } catch (err) { + _didIteratorError = true; + _iteratorError = err; + } finally { + try { + if (!_iteratorNormalCompletion && _iterator.return) { + _iterator.return(); + } + } finally { + if (_didIteratorError) { + throw _iteratorError; + } + } + } +} + +function getColor(value, valueSteps, stepColors) { + var index = 0, + color = '#ffffff'; // White is default + + // TODO Handle negative numebers, or at least 0, a little better + if (value) { + do { + color = stepColors[index]; + } while (index < valueSteps.length && value > valueSteps[index++]); + } + + return color; +} + +// Yields Jan 1 at Midnight in Epoch milliseconds starting at start year, ending with end year +function eachYear(start, end) { + var current, endDate, endYear; + return _regenerator2.default.wrap(function eachYear$(_context) { + while (1) { + switch (_context.prev = _context.next) { + case 0: + current = new Date(new Date(start).getUTCFullYear(), 0), endDate = new Date(new Date(end).getUTCFullYear(), 12), endYear = endDate.getUTCFullYear(); + + case 1: + if (!(current.getUTCFullYear() < endYear)) { + _context.next = 7; + break; + } + + _context.next = 4; + return current.getTime(); + + case 4: + current.setUTCFullYear(current.getUTCFullYear() + 1); + _context.next = 1; + break; + + case 7: + case 'end': + return _context.stop(); + } + } + }, _marked[0], this); +} + +// Yields midnight in milliseconds for each day in the year (handles leap year) +function eachDay(year) { + var date, month, current, end, currentMonth; + return _regenerator2.default.wrap(function eachDay$(_context2) { + while (1) { + switch (_context2.prev = _context2.next) { + case 0: + date = 1, month = 0, current = new Date(new Date(year).getUTCFullYear(), 0), end = new Date(new Date(year).getUTCFullYear(), 12); + + case 1: + if (!(current.getTime() < end.getTime())) { + _context2.next = 9; + break; + } + + _context2.next = 4; + return current.getTime(); + + case 4: + + current.setUTCDate(++date); + + currentMonth = current.getUTCMonth(); + + if (currentMonth > month) { + current.setUTCMonth(++month); + current.setUTCDate(date = 1); + month = currentMonth; + } + _context2.next = 1; + break; + + case 9: + case 'end': + return _context2.stop(); + } + } + }, _marked[1], this); +} + +// Yields start of hour in milliseconds for each hour in the day +function eachHour(day) { + var hours, hourMilliseconds, dayDate, current, end; + return _regenerator2.default.wrap(function eachHour$(_context3) { + while (1) { + switch (_context3.prev = _context3.next) { + case 0: + hours = 0, hourMilliseconds = 60 * 60 * 1000, dayDate = new Date(day), current = new Date(dayDate.getUTCFullYear(), dayDate.getUTCMonth(), dayDate.getUTCDate()).getTime(), end = current + 24 * hourMilliseconds; + + case 1: + if (!(current <= end)) { + _context3.next = 7; + break; + } + + _context3.next = 4; + return current; + + case 4: + current += hourMilliseconds; + _context3.next = 1; + break; - context.fillRect(20, 20, 100, 100); + case 7: + case 'end': + return _context3.stop(); + } + } + }, _marked[2], this); +} + +// function eachHour*(start, end, step = 60 * 60 * 1000) { +// let current = start; +// while (current <= end) { +// yield current; +// current += step; +// } +// } + +function findStartAndEndTimes(data) { + var start = new Date(), + end = new Date(); + + end.setYear(1000); // Maybe there's a better way? + + for (var k in data) { + if (k < start) start = k; + if (k > end) end = k; + } + + return { start: start, end: end }; +} + +function ensureEpochKeys(data) { + var r = {}; + (0, _keys2.default)(data).forEach(function (k) { + var d = new Date(k), + offset = d.getTimezoneOffset() * 60 * 1000; + + r[d.getTime() + offset] = data[k]; + }); + return r; +} + +function calculateValueSteps(data, stepCount) { + var values = getValues(data).sort(function (a, b) { + return a === b ? 0 : a < b ? -1 : 1; + }), + stepSize = Math.round(values.length / stepCount); + + var steps = []; + + for (var i = 0; i < stepCount; i++) { + var index = stepSize * (i + 1); + if (index >= values.length) break; + steps.push(values[index]); + } + + console.log('foo'); + console.log(values[values.length - 50]); + + return steps; +} + +function getValues(obj) { + return (0, _keys2.default)(obj).map(function (k) { + return obj[k]; + }); } function calculateColorSteps(min, max, stepCount) { + // TODO Put this in its own module. var minRed = min & 0xff0000, minGreen = min & 0x00ff00, minBlue = min & 0x0000ff, maxRed = max & 0xff0000, maxGreen = max & 0x00ff00, maxBlue = max & 0x0000ff, - setps = []; + redStepSize = (minRed - maxRed) / stepCount & 0xff0000, + greenStepSize = (minGreen - maxGreen) / stepCount & 0x00ff00, + blueStepSize = (minBlue - maxBlue) / stepCount & 0x0000ff; + + var steps = []; - var redStepSize = Math.floor((maxRed - minRed) / stepCount), - greenStepSize = Math.floor((maxGreen - minGreen) / stepCount), - blueStepSize = Math.floor((maxBlue - minBlue) / stepCount); + for (var i = 0; i < stepCount; i++) { + var r = maxRed + redStepSize * i, + g = maxGreen + greenStepSize * i, + b = maxBlue + blueStepSize * i; - for (; i < stepCount; i++) { - var r = minRed + (redStepSize * i + 1), - g = minGreen + (greenStepSize * i + 1), - b = minBlue + (blueStepSize * i + 1); steps.push(r | g | b); } - return steps; + return steps.map(function (n) { + return '#' + n.toString(16); + }).reverse(); } function createCanvas(width, height) { @@ -80,5 +502,1372 @@ module.exports = { visualize: visualize }; -},{}]},{},[1]) -//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbIi4uLy4uLy5udm0vdmVyc2lvbnMvbm9kZS92Ni4yLjIvbGliL25vZGVfbW9kdWxlcy93YXRjaGlmeS9ub2RlX21vZHVsZXMvYnJvd3Nlci1wYWNrL19wcmVsdWRlLmpzIiwiaW5kZXguanMiLCJsaWIvcGhlYXNhbnQuanMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUE7OztBQ0FBLElBQU0sV0FBVyxRQUFRLG1CQUFSLENBQWpCOztBQUVBLElBQU0sTUFBTSxJQUFJLGNBQUosRUFBWjs7QUFFQSxJQUFJLGdCQUFKLENBQXFCLGtCQUFyQjtBQUNBLElBQUksSUFBSixDQUFTLEtBQVQsRUFBZ0IsaUNBQWhCLEVBQW1ELElBQW5EOztBQUVBLElBQUksa0JBQUosR0FBeUIsWUFBTTtBQUM3QixNQUFJLElBQUksVUFBSixLQUFtQixDQUFuQixJQUF3QixJQUFJLE1BQUosS0FBZSxHQUEzQyxFQUFnRDtBQUM5QyxhQUFTLFNBQVQsQ0FBbUIsS0FBSyxLQUFMLENBQVcsSUFBSSxZQUFmLENBQW5CLEVBQWlELGtCQUFqRDtBQUNEO0FBQ0YsQ0FKRDs7O0FBT0EsT0FBTyxNQUFQLEdBQWdCO0FBQUEsU0FBTSxJQUFJLElBQUosQ0FBUyxJQUFULENBQU47QUFBQSxDQUFoQjs7Ozs7Ozs7QUNYQSxTQUFTLFNBQVQsQ0FBbUIsSUFBbkIsRUFBeUIsWUFBekIsRUFBdUMsSUFBdkMsRUFBNkM7O0FBRTNDLFVBQVEsR0FBUixDQUFZLGdCQUFaOztBQUVBLE1BQUksVUFBVTtBQUNaLGNBQVUsUUFERTtBQUVaLGNBQVUsUUFGRTtBQUdaLGNBQVU7QUFIRSxHQUFkOztBQU1BLE1BQU0sVUFBVSxTQUFTLGNBQVQsQ0FBd0IsWUFBeEIsQ0FBaEI7QUFDQSxNQUFNLFNBQVMsYUFBYSxHQUFiLEVBQWtCLEdBQWxCLENBQWY7QUFDQSxVQUFRLFdBQVIsQ0FBb0IsTUFBcEI7QUFDQSxNQUFNLFVBQVUsT0FBTyxVQUFQLENBQWtCLElBQWxCLENBQWhCOztBQUVBLE1BQU0sUUFBUSxvQkFBb0IsUUFBUSxRQUE1QixFQUFzQyxRQUFRLFFBQTlDLEVBQXdELFFBQVEsUUFBaEUsQ0FBZDs7QUFFQSxVQUFRLFFBQVIsQ0FBaUIsRUFBakIsRUFBcUIsRUFBckIsRUFBeUIsR0FBekIsRUFBOEIsR0FBOUI7QUFDRDs7QUFFRCxTQUFTLG1CQUFULENBQTZCLEdBQTdCLEVBQWtDLEdBQWxDLEVBQXVDLFNBQXZDLEVBQWtEO0FBQ2hELE1BQ0UsU0FBUyxNQUFNLFFBRGpCO0FBQUEsTUFFRSxXQUFXLE1BQU0sUUFGbkI7QUFBQSxNQUdFLFVBQVUsTUFBTSxRQUhsQjtBQUFBLE1BS0UsU0FBUyxNQUFNLFFBTGpCO0FBQUEsTUFNRSxXQUFXLE1BQU0sUUFObkI7QUFBQSxNQU9FLFVBQVUsTUFBTSxRQVBsQjtBQUFBLE1BU0UsUUFBUSxFQVRWOztBQVlBLE1BQ0UsY0FBYyxLQUFLLEtBQUwsQ0FBVyxDQUFDLFNBQVMsTUFBVixJQUFvQixTQUEvQixDQURoQjtBQUFBLE1BRUUsZ0JBQWdCLEtBQUssS0FBTCxDQUFXLENBQUMsV0FBVyxRQUFaLElBQXdCLFNBQW5DLENBRmxCO0FBQUEsTUFHRSxlQUFlLEtBQUssS0FBTCxDQUFXLENBQUMsVUFBVSxPQUFYLElBQXNCLFNBQWpDLENBSGpCOztBQU1BLFNBQU8sSUFBSSxTQUFYLEVBQXNCLEdBQXRCLEVBQTJCO0FBQ3pCLFFBQ0UsSUFBSSxVQUFVLGNBQWMsQ0FBZCxHQUFnQixDQUExQixDQUROO0FBQUEsUUFFRSxJQUFJLFlBQVksZ0JBQWdCLENBQWhCLEdBQWtCLENBQTlCLENBRk47QUFBQSxRQUdFLElBQUksV0FBVyxlQUFlLENBQWYsR0FBaUIsQ0FBNUIsQ0FITjtBQUlBLFVBQU0sSUFBTixDQUFXLElBQUksQ0FBSixHQUFRLENBQW5CO0FBQ0Q7O0FBRUQsU0FBTyxLQUFQO0FBQ0Q7O0FBRUQsU0FBUyxZQUFULENBQXNCLEtBQXRCLEVBQTZCLE1BQTdCLEVBQXFDO0FBQ25DLE1BQU0sU0FBUyxTQUFTLGFBQVQsQ0FBdUIsUUFBdkIsQ0FBZjs7QUFFQSxTQUFPLEtBQVAsR0FBZSxLQUFmO0FBQ0EsU0FBTyxNQUFQLEdBQWdCLE1BQWhCO0FBQ0EsU0FBTyxNQUFQO0FBQ0Q7O0FBR0QsT0FBTyxPQUFQLEdBQWlCO0FBQ2YsYUFBVztBQURJLENBQWpCIiwiZmlsZSI6ImdlbmVyYXRlZC5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzQ29udGVudCI6WyIoZnVuY3Rpb24gZSh0LG4scil7ZnVuY3Rpb24gcyhvLHUpe2lmKCFuW29dKXtpZighdFtvXSl7dmFyIGE9dHlwZW9mIHJlcXVpcmU9PVwiZnVuY3Rpb25cIiYmcmVxdWlyZTtpZighdSYmYSlyZXR1cm4gYShvLCEwKTtpZihpKXJldHVybiBpKG8sITApO3ZhciBmPW5ldyBFcnJvcihcIkNhbm5vdCBmaW5kIG1vZHVsZSAnXCIrbytcIidcIik7dGhyb3cgZi5jb2RlPVwiTU9EVUxFX05PVF9GT1VORFwiLGZ9dmFyIGw9bltvXT17ZXhwb3J0czp7fX07dFtvXVswXS5jYWxsKGwuZXhwb3J0cyxmdW5jdGlvbihlKXt2YXIgbj10W29dWzFdW2VdO3JldHVybiBzKG4/bjplKX0sbCxsLmV4cG9ydHMsZSx0LG4scil9cmV0dXJuIG5bb10uZXhwb3J0c312YXIgaT10eXBlb2YgcmVxdWlyZT09XCJmdW5jdGlvblwiJiZyZXF1aXJlO2Zvcih2YXIgbz0wO288ci5sZW5ndGg7bysrKXMocltvXSk7cmV0dXJuIHN9KSIsImNvbnN0IHBoZWFzYW50ID0gcmVxdWlyZSgnLi9saWIvcGhlYXNhbnQuanMnKTtcblxuY29uc3QgcmVxID0gbmV3IFhNTEh0dHBSZXF1ZXN0KCk7XG5cbnJlcS5vdmVycmlkZU1pbWVUeXBlKCdhcHBsaWNhdGlvbi9qc29uJyk7XG5yZXEub3BlbignR0VUJywgJ2RhdGEvZGFpbHktY3JpbWUtMjAxMC0yMDE1Lmpzb24nLCB0cnVlKTtcblxucmVxLm9ucmVhZHlzdGF0ZWNoYW5nZSA9ICgpID0+IHtcbiAgaWYgKHJlcS5yZWFkeVN0YXRlID09PSA0ICYmIHJlcS5zdGF0dXMgPT09IDIwMCkge1xuICAgIHBoZWFzYW50LnZpc3VhbGl6ZShKU09OLnBhcnNlKHJlcS5yZXNwb25zZVRleHQpLCAnY2FudmFzX2dvZXNfaGVyZScpO1xuICB9XG59XG5cbi8vIEZldGNoIHRoZSBkYXRhXG53aW5kb3cub25sb2FkID0gKCkgPT4gcmVxLnNlbmQobnVsbCk7XG5cbiIsIi8vIENyZWF0ZSBoZWF0IG1hcCBkYXRhIHZpc3VhbGl6YXRpb25cblxuLy8gQ3JlYXRlcyB0aGUgdmlzdWFsaXphdGlvblxuZnVuY3Rpb24gdmlzdWFsaXplKGRhdGEsIGRvbUVsZW1lbnRJZCwgb3B0cykge1xuICBcbiAgY29uc29sZS5sb2coJ3Zpc3VhbGl6aW5nLi4uJylcblxuICBsZXQgb3B0aW9ucyA9IHtcbiAgICBtaW5Db2xvcjogMHhmMGYwZjAsXG4gICAgbWF4Q29sb3I6IDB4MDUwNTA1LFxuICAgIG51bVN0ZXBzOiA1ICAgIFxuICB9XG5cbiAgY29uc3QgZWxlbWVudCA9IGRvY3VtZW50LmdldEVsZW1lbnRCeUlkKGRvbUVsZW1lbnRJZCk7XG4gIGNvbnN0IGNhbnZhcyA9IGNyZWF0ZUNhbnZhcyg1MDAsIDIwMCk7XG4gIGVsZW1lbnQuYXBwZW5kQ2hpbGQoY2FudmFzKTtcbiAgY29uc3QgY29udGV4dCA9IGNhbnZhcy5nZXRDb250ZXh0KCcyZCcpO1xuXG4gIGNvbnN0IHN0ZXBzID0gY2FsY3VsYXRlQ29sb3JTdGVwcyhvcHRpb25zLm1pbkNvbG9yLCBvcHRpb25zLm1heENvbG9yLCBvcHRpb25zLm51bVN0ZXBzKTtcblxuICBjb250ZXh0LmZpbGxSZWN0KDIwLCAyMCwgMTAwLCAxMDApO1xufVxuXG5mdW5jdGlvbiBjYWxjdWxhdGVDb2xvclN0ZXBzKG1pbiwgbWF4LCBzdGVwQ291bnQpIHtcbiAgbGV0XG4gICAgbWluUmVkID0gbWluICYgMHhmZjAwMDAsXG4gICAgbWluR3JlZW4gPSBtaW4gJiAweDAwZmYwMCxcbiAgICBtaW5CbHVlID0gbWluICYgMHgwMDAwZmYsXG5cbiAgICBtYXhSZWQgPSBtYXggJiAweGZmMDAwMCxcbiAgICBtYXhHcmVlbiA9IG1heCAmIDB4MDBmZjAwLFxuICAgIG1heEJsdWUgPSBtYXggJiAweDAwMDBmZixcblxuICAgIHNldHBzID0gW11cbiAgO1xuXG4gIGNvbnN0IFxuICAgIHJlZFN0ZXBTaXplID0gTWF0aC5mbG9vcigobWF4UmVkIC0gbWluUmVkKSAvIHN0ZXBDb3VudCksXG4gICAgZ3JlZW5TdGVwU2l6ZSA9IE1hdGguZmxvb3IoKG1heEdyZWVuIC0gbWluR3JlZW4pIC8gc3RlcENvdW50KSxcbiAgICBibHVlU3RlcFNpemUgPSBNYXRoLmZsb29yKChtYXhCbHVlIC0gbWluQmx1ZSkgLyBzdGVwQ291bnQpXG4gIDtcblxuICBmb3IgKDsgaSA8IHN0ZXBDb3VudDsgaSsrKSB7XG4gICAgbGV0IFxuICAgICAgciA9IG1pblJlZCArIChyZWRTdGVwU2l6ZSAqIGkrMSksXG4gICAgICBnID0gbWluR3JlZW4gKyAoZ3JlZW5TdGVwU2l6ZSAqIGkrMSksXG4gICAgICBiID0gbWluQmx1ZSArIChibHVlU3RlcFNpemUgKiBpKzEpXG4gICAgc3RlcHMucHVzaChyIHwgZyB8IGIpO1xuICB9XG5cbiAgcmV0dXJuIHN0ZXBzO1xufVxuXG5mdW5jdGlvbiBjcmVhdGVDYW52YXMod2lkdGgsIGhlaWdodCkge1xuICBjb25zdCBjYW52YXMgPSBkb2N1bWVudC5jcmVhdGVFbGVtZW50KCdjYW52YXMnKTtcbiAgLy8gVE9ETyBNYXliZSBkbyBzb21lIGNoZWNrcyBmb3IgdmlhYmlsaXR5IGhlcmVcbiAgY2FudmFzLndpZHRoID0gd2lkdGg7XG4gIGNhbnZhcy5oZWlnaHQgPSBoZWlnaHQ7XG4gIHJldHVybiBjYW52YXM7XG59XG5cblxubW9kdWxlLmV4cG9ydHMgPSB7XG4gIHZpc3VhbGl6ZTogdmlzdWFsaXplXG59XG4iXX0= +},{"babel-runtime/core-js/get-iterator":4,"babel-runtime/core-js/object/keys":5,"babel-runtime/regenerator":6}],4:[function(require,module,exports){ +module.exports = { "default": require("core-js/library/fn/get-iterator"), __esModule: true }; +},{"core-js/library/fn/get-iterator":7}],5:[function(require,module,exports){ +module.exports = { "default": require("core-js/library/fn/object/keys"), __esModule: true }; +},{"core-js/library/fn/object/keys":8}],6:[function(require,module,exports){ +module.exports = require("regenerator-runtime"); + +},{"regenerator-runtime":62}],7:[function(require,module,exports){ +require('../modules/web.dom.iterable'); +require('../modules/es6.string.iterator'); +module.exports = require('../modules/core.get-iterator'); +},{"../modules/core.get-iterator":57,"../modules/es6.string.iterator":60,"../modules/web.dom.iterable":61}],8:[function(require,module,exports){ +require('../../modules/es6.object.keys'); +module.exports = require('../../modules/_core').Object.keys; +},{"../../modules/_core":15,"../../modules/es6.object.keys":59}],9:[function(require,module,exports){ +module.exports = function(it){ + if(typeof it != 'function')throw TypeError(it + ' is not a function!'); + return it; +}; +},{}],10:[function(require,module,exports){ +module.exports = function(){ /* empty */ }; +},{}],11:[function(require,module,exports){ +var isObject = require('./_is-object'); +module.exports = function(it){ + if(!isObject(it))throw TypeError(it + ' is not an object!'); + return it; +}; +},{"./_is-object":29}],12:[function(require,module,exports){ +// false -> Array#indexOf +// true -> Array#includes +var toIObject = require('./_to-iobject') + , toLength = require('./_to-length') + , toIndex = require('./_to-index'); +module.exports = function(IS_INCLUDES){ + return function($this, el, fromIndex){ + var O = toIObject($this) + , length = toLength(O.length) + , index = toIndex(fromIndex, length) + , value; + // Array#includes uses SameValueZero equality algorithm + if(IS_INCLUDES && el != el)while(length > index){ + value = O[index++]; + if(value != value)return true; + // Array#toIndex ignores holes, Array#includes - not + } else for(;length > index; index++)if(IS_INCLUDES || index in O){ + if(O[index] === el)return IS_INCLUDES || index || 0; + } return !IS_INCLUDES && -1; + }; +}; +},{"./_to-index":48,"./_to-iobject":50,"./_to-length":51}],13:[function(require,module,exports){ +// getting tag from 19.1.3.6 Object.prototype.toString() +var cof = require('./_cof') + , TAG = require('./_wks')('toStringTag') + // ES3 wrong here + , ARG = cof(function(){ return arguments; }()) == 'Arguments'; + +// fallback for IE11 Script Access Denied error +var tryGet = function(it, key){ + try { + return it[key]; + } catch(e){ /* empty */ } +}; + +module.exports = function(it){ + var O, T, B; + return it === undefined ? 'Undefined' : it === null ? 'Null' + // @@toStringTag case + : typeof (T = tryGet(O = Object(it), TAG)) == 'string' ? T + // builtinTag case + : ARG ? cof(O) + // ES3 arguments fallback + : (B = cof(O)) == 'Object' && typeof O.callee == 'function' ? 'Arguments' : B; +}; +},{"./_cof":14,"./_wks":55}],14:[function(require,module,exports){ +var toString = {}.toString; + +module.exports = function(it){ + return toString.call(it).slice(8, -1); +}; +},{}],15:[function(require,module,exports){ +var core = module.exports = {version: '2.4.0'}; +if(typeof __e == 'number')__e = core; // eslint-disable-line no-undef +},{}],16:[function(require,module,exports){ +// optional / simple context binding +var aFunction = require('./_a-function'); +module.exports = function(fn, that, length){ + aFunction(fn); + if(that === undefined)return fn; + switch(length){ + case 1: return function(a){ + return fn.call(that, a); + }; + case 2: return function(a, b){ + return fn.call(that, a, b); + }; + case 3: return function(a, b, c){ + return fn.call(that, a, b, c); + }; + } + return function(/* ...args */){ + return fn.apply(that, arguments); + }; +}; +},{"./_a-function":9}],17:[function(require,module,exports){ +// 7.2.1 RequireObjectCoercible(argument) +module.exports = function(it){ + if(it == undefined)throw TypeError("Can't call method on " + it); + return it; +}; +},{}],18:[function(require,module,exports){ +// Thank's IE8 for his funny defineProperty +module.exports = !require('./_fails')(function(){ + return Object.defineProperty({}, 'a', {get: function(){ return 7; }}).a != 7; +}); +},{"./_fails":22}],19:[function(require,module,exports){ +var isObject = require('./_is-object') + , document = require('./_global').document + // in old IE typeof document.createElement is 'object' + , is = isObject(document) && isObject(document.createElement); +module.exports = function(it){ + return is ? document.createElement(it) : {}; +}; +},{"./_global":23,"./_is-object":29}],20:[function(require,module,exports){ +// IE 8- don't enum bug keys +module.exports = ( + 'constructor,hasOwnProperty,isPrototypeOf,propertyIsEnumerable,toLocaleString,toString,valueOf' +).split(','); +},{}],21:[function(require,module,exports){ +var global = require('./_global') + , core = require('./_core') + , ctx = require('./_ctx') + , hide = require('./_hide') + , PROTOTYPE = 'prototype'; + +var $export = function(type, name, source){ + var IS_FORCED = type & $export.F + , IS_GLOBAL = type & $export.G + , IS_STATIC = type & $export.S + , IS_PROTO = type & $export.P + , IS_BIND = type & $export.B + , IS_WRAP = type & $export.W + , exports = IS_GLOBAL ? core : core[name] || (core[name] = {}) + , expProto = exports[PROTOTYPE] + , target = IS_GLOBAL ? global : IS_STATIC ? global[name] : (global[name] || {})[PROTOTYPE] + , key, own, out; + if(IS_GLOBAL)source = name; + for(key in source){ + // contains in native + own = !IS_FORCED && target && target[key] !== undefined; + if(own && key in exports)continue; + // export native or passed + out = own ? target[key] : source[key]; + // prevent global pollution for namespaces + exports[key] = IS_GLOBAL && typeof target[key] != 'function' ? source[key] + // bind timers to global for call from export context + : IS_BIND && own ? ctx(out, global) + // wrap global constructors for prevent change them in library + : IS_WRAP && target[key] == out ? (function(C){ + var F = function(a, b, c){ + if(this instanceof C){ + switch(arguments.length){ + case 0: return new C; + case 1: return new C(a); + case 2: return new C(a, b); + } return new C(a, b, c); + } return C.apply(this, arguments); + }; + F[PROTOTYPE] = C[PROTOTYPE]; + return F; + // make static versions for prototype methods + })(out) : IS_PROTO && typeof out == 'function' ? ctx(Function.call, out) : out; + // export proto methods to core.%CONSTRUCTOR%.methods.%NAME% + if(IS_PROTO){ + (exports.virtual || (exports.virtual = {}))[key] = out; + // export proto methods to core.%CONSTRUCTOR%.prototype.%NAME% + if(type & $export.R && expProto && !expProto[key])hide(expProto, key, out); + } + } +}; +// type bitmap +$export.F = 1; // forced +$export.G = 2; // global +$export.S = 4; // static +$export.P = 8; // proto +$export.B = 16; // bind +$export.W = 32; // wrap +$export.U = 64; // safe +$export.R = 128; // real proto method for `library` +module.exports = $export; +},{"./_core":15,"./_ctx":16,"./_global":23,"./_hide":25}],22:[function(require,module,exports){ +module.exports = function(exec){ + try { + return !!exec(); + } catch(e){ + return true; + } +}; +},{}],23:[function(require,module,exports){ +// https://github.com/zloirock/core-js/issues/86#issuecomment-115759028 +var global = module.exports = typeof window != 'undefined' && window.Math == Math + ? window : typeof self != 'undefined' && self.Math == Math ? self : Function('return this')(); +if(typeof __g == 'number')__g = global; // eslint-disable-line no-undef +},{}],24:[function(require,module,exports){ +var hasOwnProperty = {}.hasOwnProperty; +module.exports = function(it, key){ + return hasOwnProperty.call(it, key); +}; +},{}],25:[function(require,module,exports){ +var dP = require('./_object-dp') + , createDesc = require('./_property-desc'); +module.exports = require('./_descriptors') ? function(object, key, value){ + return dP.f(object, key, createDesc(1, value)); +} : function(object, key, value){ + object[key] = value; + return object; +}; +},{"./_descriptors":18,"./_object-dp":36,"./_property-desc":42}],26:[function(require,module,exports){ +module.exports = require('./_global').document && document.documentElement; +},{"./_global":23}],27:[function(require,module,exports){ +module.exports = !require('./_descriptors') && !require('./_fails')(function(){ + return Object.defineProperty(require('./_dom-create')('div'), 'a', {get: function(){ return 7; }}).a != 7; +}); +},{"./_descriptors":18,"./_dom-create":19,"./_fails":22}],28:[function(require,module,exports){ +// fallback for non-array-like ES3 and non-enumerable old V8 strings +var cof = require('./_cof'); +module.exports = Object('z').propertyIsEnumerable(0) ? Object : function(it){ + return cof(it) == 'String' ? it.split('') : Object(it); +}; +},{"./_cof":14}],29:[function(require,module,exports){ +module.exports = function(it){ + return typeof it === 'object' ? it !== null : typeof it === 'function'; +}; +},{}],30:[function(require,module,exports){ +'use strict'; +var create = require('./_object-create') + , descriptor = require('./_property-desc') + , setToStringTag = require('./_set-to-string-tag') + , IteratorPrototype = {}; + +// 25.1.2.1.1 %IteratorPrototype%[@@iterator]() +require('./_hide')(IteratorPrototype, require('./_wks')('iterator'), function(){ return this; }); + +module.exports = function(Constructor, NAME, next){ + Constructor.prototype = create(IteratorPrototype, {next: descriptor(1, next)}); + setToStringTag(Constructor, NAME + ' Iterator'); +}; +},{"./_hide":25,"./_object-create":35,"./_property-desc":42,"./_set-to-string-tag":44,"./_wks":55}],31:[function(require,module,exports){ +'use strict'; +var LIBRARY = require('./_library') + , $export = require('./_export') + , redefine = require('./_redefine') + , hide = require('./_hide') + , has = require('./_has') + , Iterators = require('./_iterators') + , $iterCreate = require('./_iter-create') + , setToStringTag = require('./_set-to-string-tag') + , getPrototypeOf = require('./_object-gpo') + , ITERATOR = require('./_wks')('iterator') + , BUGGY = !([].keys && 'next' in [].keys()) // Safari has buggy iterators w/o `next` + , FF_ITERATOR = '@@iterator' + , KEYS = 'keys' + , VALUES = 'values'; + +var returnThis = function(){ return this; }; + +module.exports = function(Base, NAME, Constructor, next, DEFAULT, IS_SET, FORCED){ + $iterCreate(Constructor, NAME, next); + var getMethod = function(kind){ + if(!BUGGY && kind in proto)return proto[kind]; + switch(kind){ + case KEYS: return function keys(){ return new Constructor(this, kind); }; + case VALUES: return function values(){ return new Constructor(this, kind); }; + } return function entries(){ return new Constructor(this, kind); }; + }; + var TAG = NAME + ' Iterator' + , DEF_VALUES = DEFAULT == VALUES + , VALUES_BUG = false + , proto = Base.prototype + , $native = proto[ITERATOR] || proto[FF_ITERATOR] || DEFAULT && proto[DEFAULT] + , $default = $native || getMethod(DEFAULT) + , $entries = DEFAULT ? !DEF_VALUES ? $default : getMethod('entries') : undefined + , $anyNative = NAME == 'Array' ? proto.entries || $native : $native + , methods, key, IteratorPrototype; + // Fix native + if($anyNative){ + IteratorPrototype = getPrototypeOf($anyNative.call(new Base)); + if(IteratorPrototype !== Object.prototype){ + // Set @@toStringTag to native iterators + setToStringTag(IteratorPrototype, TAG, true); + // fix for some old engines + if(!LIBRARY && !has(IteratorPrototype, ITERATOR))hide(IteratorPrototype, ITERATOR, returnThis); + } + } + // fix Array#{values, @@iterator}.name in V8 / FF + if(DEF_VALUES && $native && $native.name !== VALUES){ + VALUES_BUG = true; + $default = function values(){ return $native.call(this); }; + } + // Define iterator + if((!LIBRARY || FORCED) && (BUGGY || VALUES_BUG || !proto[ITERATOR])){ + hide(proto, ITERATOR, $default); + } + // Plug for library + Iterators[NAME] = $default; + Iterators[TAG] = returnThis; + if(DEFAULT){ + methods = { + values: DEF_VALUES ? $default : getMethod(VALUES), + keys: IS_SET ? $default : getMethod(KEYS), + entries: $entries + }; + if(FORCED)for(key in methods){ + if(!(key in proto))redefine(proto, key, methods[key]); + } else $export($export.P + $export.F * (BUGGY || VALUES_BUG), NAME, methods); + } + return methods; +}; +},{"./_export":21,"./_has":24,"./_hide":25,"./_iter-create":30,"./_iterators":33,"./_library":34,"./_object-gpo":38,"./_redefine":43,"./_set-to-string-tag":44,"./_wks":55}],32:[function(require,module,exports){ +module.exports = function(done, value){ + return {value: value, done: !!done}; +}; +},{}],33:[function(require,module,exports){ +module.exports = {}; +},{}],34:[function(require,module,exports){ +module.exports = true; +},{}],35:[function(require,module,exports){ +// 19.1.2.2 / 15.2.3.5 Object.create(O [, Properties]) +var anObject = require('./_an-object') + , dPs = require('./_object-dps') + , enumBugKeys = require('./_enum-bug-keys') + , IE_PROTO = require('./_shared-key')('IE_PROTO') + , Empty = function(){ /* empty */ } + , PROTOTYPE = 'prototype'; + +// Create object with fake `null` prototype: use iframe Object with cleared prototype +var createDict = function(){ + // Thrash, waste and sodomy: IE GC bug + var iframe = require('./_dom-create')('iframe') + , i = enumBugKeys.length + , gt = '>' + , iframeDocument; + iframe.style.display = 'none'; + require('./_html').appendChild(iframe); + iframe.src = 'javascript:'; // eslint-disable-line no-script-url + // createDict = iframe.contentWindow.Object; + // html.removeChild(iframe); + iframeDocument = iframe.contentWindow.document; + iframeDocument.open(); + iframeDocument.write(' diff --git a/lib/pheasant.js b/lib/pheasant.js index 787a470..8ef5299 100644 --- a/lib/pheasant.js +++ b/lib/pheasant.js @@ -3,26 +3,191 @@ // Creates the visualization function visualize(data, domElementId, opts) { - console.log('visualizing...') - let options = { - minColor: 0xf0f0f0, - maxColor: 0x050505, - numSteps: 5 + minColor: 0xfbfbfb, + maxColor: 0x606060, + numSteps: 10, + cellWidth: 6, + cellHeight: 6, + cellSpacing: 2 } const element = document.getElementById(domElementId); - const canvas = createCanvas(500, 200); + const canvas = createCanvas(365 * options.cellWidth, 1000); element.appendChild(canvas); const context = canvas.getContext('2d'); - const steps = calculateColorSteps(options.minColor, options.maxColor, options.numSteps); + const stepColors = calculateColorSteps(options.minColor, options.maxColor, options.numSteps); + const valueSteps = calculateValueSteps(data, options.numSteps); + + console.log(valueSteps) + + // Ensure the data is keyed by Epoch time values - easier to work with + data = ensureEpochKeys(data); + + // Find start and end keys + let {start, end} = findStartAndEndTimes(data); + + let x = 0, y = 0, yearOffset = 0; + const w = options.cellWidth, h = options.cellHeight; // TODO dynamically calculate these from the time dimensions + + for (let year of eachYear(parseInt(start), parseInt(end))) { + // Set x back to 0, and move y down if needed + x = 0; + + for (let day of eachDay(year)) { + // Set x over one and move y back up to the year offset + y = yearOffset; + + for (let hour of eachHour(day)) { + // Draw the block onto the canvas + let value = data[hour]; + let color = getColor(value, valueSteps, stepColors); + + context.fillStyle = color; + context.fillRect(x, y, w, h); + y += h + options.cellSpacing; + } + + x += w + options.cellSpacing; + } + + yearOffset += (h + options.cellSpacing) * 24; + } - context.fillRect(20, 20, 100, 100); } -function calculateColorSteps(min, max, stepCount) { +function getColor(value, valueSteps, stepColors) { + let + index = 0, + color = '#ffffff'; // White is default + + // TODO Handle negative numebers, or at least 0, a little better + if (value) { + do { + color = stepColors[index]; + } while (index < valueSteps.length && value > valueSteps[index++]); + } + + return color; +} + +// Yields Jan 1 at Midnight in Epoch milliseconds starting at start year, ending with end year +function* eachYear(start, end) { + let + current = new Date((new Date(start)).getUTCFullYear(), 0), + endDate = new Date((new Date(end)).getUTCFullYear(), 12), + endYear = endDate.getUTCFullYear() + ; + + while (current.getUTCFullYear() < endYear) { + yield current.getTime(); + current.setUTCFullYear(current.getUTCFullYear() + 1); + } +} + +// Yields midnight in milliseconds for each day in the year (handles leap year) +function* eachDay(year) { + let + date = 1, + month = 0, + current = new Date((new Date(year)).getUTCFullYear(), 0), + end = new Date((new Date(year)).getUTCFullYear(), 12) + ; + + while (current.getTime() < end.getTime()) { + + yield current.getTime(); + + current.setUTCDate(++date); + + let currentMonth = current.getUTCMonth(); + if (currentMonth > month) { + current.setUTCMonth(++month); + current.setUTCDate(date = 1); + month = currentMonth; + } + } +} + +// Yields start of hour in milliseconds for each hour in the day +function* eachHour(day) { let + hours = 0, + hourMilliseconds = 60 * 60 * 1000, + dayDate = new Date(day), + current = (new Date(dayDate.getUTCFullYear(), dayDate.getUTCMonth(), dayDate.getUTCDate())).getTime(), + end = current + 24 * hourMilliseconds + ; + + while (current <= end) { + yield current; + current += hourMilliseconds; + } +} + +// function eachHour*(start, end, step = 60 * 60 * 1000) { +// let current = start; +// while (current <= end) { +// yield current; +// current += step; +// } +// } + +function findStartAndEndTimes(data) { + let + start = new Date(), + end = new Date() + ; + + end.setYear(1000) // Maybe there's a better way? + + for (let k in data) { + if (k < start) start = k; + if (k > end) end = k; + } + + return { start: start, end: end } +} + +function ensureEpochKeys(data) { + let r = {} + Object.keys(data).forEach(k => { + let d = new Date(k), + offset = d.getTimezoneOffset() * 60 * 1000; + + r[d.getTime() + offset] = data[k] + }); + return r; +} + +function calculateValueSteps(data, stepCount) { + const + values = getValues(data).sort((a, b) => a === b ? 0 : a < b ? -1 : 1), + stepSize = Math.round(values.length / stepCount) + ; + + let steps = []; + + for (let i = 0; i < stepCount; i++) { + let index = stepSize * (i + 1); + if (index >= values.length) break; + steps.push(values[index]); + } + + console.log('foo') + console.log(values[values.length - 50]); + + return steps; +} + +function getValues(obj) { + return Object.keys(obj).map(k => obj[k]); +} + +function calculateColorSteps(min, max, stepCount) { + // TODO Put this in its own module. + const minRed = min & 0xff0000, minGreen = min & 0x00ff00, minBlue = min & 0x0000ff, @@ -30,25 +195,25 @@ function calculateColorSteps(min, max, stepCount) { maxRed = max & 0xff0000, maxGreen = max & 0x00ff00, maxBlue = max & 0x0000ff, - - setps = [] + + redStepSize = ((minRed - maxRed) / stepCount) & 0xff0000, + greenStepSize = ((minGreen - maxGreen) / stepCount) & 0x00ff00, + blueStepSize = ((minBlue - maxBlue) / stepCount) & 0x0000ff ; - const - redStepSize = Math.floor((maxRed - minRed) / stepCount), - greenStepSize = Math.floor((maxGreen - minGreen) / stepCount), - blueStepSize = Math.floor((maxBlue - minBlue) / stepCount) - ; + let steps = []; - for (; i < stepCount; i++) { + for (let i = 0; i < stepCount; i++) { let - r = minRed + (redStepSize * i+1), - g = minGreen + (greenStepSize * i+1), - b = minBlue + (blueStepSize * i+1) + r = maxRed + (redStepSize * i), + g = maxGreen + (greenStepSize * i), + b = maxBlue + (blueStepSize * i) + ; + steps.push(r | g | b); } - return steps; + return steps.map((n) => `#${n.toString(16)}`).reverse(); } function createCanvas(width, height) { diff --git a/package.json b/package.json index 3571093..8135014 100644 --- a/package.json +++ b/package.json @@ -3,8 +3,11 @@ "version": "0.0.1", "description": "", "main": "index.js", - "dependencies": {}, + "dependencies": { + "babel-runtime": "^6.9.2" + }, "devDependencies": { + "babel-plugin-transform-runtime": "^6.9.0", "babel-preset-es2015": "^6.9.0", "babelify": "^7.3.0", "http-server": "^0.9.0"