cohort-graph
Version:
Cohort Analysis Graph
775 lines (636 loc) • 25.3 kB
JavaScript
(function webpackUniversalModuleDefinition(root, factory) {
if(typeof exports === 'object' && typeof module === 'object')
module.exports = factory();
else if(typeof define === 'function' && define.amd)
define([], factory);
else if(typeof exports === 'object')
exports["CohortGraph"] = factory();
else
root["CohortGraph"] = factory();
})(this, function() {
return /******/ (function(modules) { // webpackBootstrap
/******/ // The module cache
/******/ var installedModules = {};
/******/
/******/ // The require function
/******/ function __webpack_require__(moduleId) {
/******/
/******/ // Check if module is in cache
/******/ if(installedModules[moduleId]) {
/******/ return installedModules[moduleId].exports;
/******/ }
/******/ // Create a new module (and put it into the cache)
/******/ var module = installedModules[moduleId] = {
/******/ i: moduleId,
/******/ l: false,
/******/ exports: {}
/******/ };
/******/
/******/ // Execute the module function
/******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);
/******/
/******/ // Flag the module as loaded
/******/ module.l = true;
/******/
/******/ // Return the exports of the module
/******/ return module.exports;
/******/ }
/******/
/******/
/******/ // expose the modules object (__webpack_modules__)
/******/ __webpack_require__.m = modules;
/******/
/******/ // expose the module cache
/******/ __webpack_require__.c = installedModules;
/******/
/******/ // identity function for calling harmony imports with the correct context
/******/ __webpack_require__.i = function(value) { return value; };
/******/
/******/ // define getter function for harmony exports
/******/ __webpack_require__.d = function(exports, name, getter) {
/******/ if(!__webpack_require__.o(exports, name)) {
/******/ Object.defineProperty(exports, name, {
/******/ configurable: false,
/******/ enumerable: true,
/******/ get: getter
/******/ });
/******/ }
/******/ };
/******/
/******/ // getDefaultExport function for compatibility with non-harmony modules
/******/ __webpack_require__.n = function(module) {
/******/ var getter = module && module.__esModule ?
/******/ function getDefault() { return module['default']; } :
/******/ function getModuleExports() { return module; };
/******/ __webpack_require__.d(getter, 'a', getter);
/******/ return getter;
/******/ };
/******/
/******/ // Object.prototype.hasOwnProperty.call
/******/ __webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); };
/******/
/******/ // __webpack_public_path__
/******/ __webpack_require__.p = "";
/******/
/******/ // Load entry module and return exports
/******/ return __webpack_require__(__webpack_require__.s = 5);
/******/ })
/************************************************************************/
/******/ ([
/* 0 */
/***/ (function(module, exports, __webpack_require__) {
;
Object.defineProperty(exports, "__esModule", {
value: true
});
/**
* Created by jyothi on 16/6/17.
*/
var VALUE_KEYS = exports.VALUE_KEYS = {
VALUE: "value",
PERCENT: "percent"
};
/***/ }),
/* 1 */
/***/ (function(module, exports, __webpack_require__) {
;
Object.defineProperty(exports, "__esModule", {
value: true
});
/**
* Created by jyothi on 16/6/17.
*/
var DEFAULT_CELL_COLOR = exports.DEFAULT_CELL_COLOR = "#F5F5F5";
var DEFAULT_KEY_CELL_COLOR = exports.DEFAULT_KEY_CELL_COLOR = "#EEE";
var DEFAULT_HEADER_CELL_COLOR = exports.DEFAULT_HEADER_CELL_COLOR = "#DDD";
var DEFAULT_BORDER = exports.DEFAULT_BORDER = "1px solid #CCC";
var TableStyles = exports.TableStyles = {
display: 'table',
width: "100%",
borderCollapse: 'collapse',
textAlign: 'center',
borderLeft: DEFAULT_BORDER
};
var TableRowStyles = exports.TableRowStyles = {
display: 'table-row'
};
var TableHeaderStyles = exports.TableHeaderStyles = {
display: 'table-header-group',
backgroundColor: DEFAULT_HEADER_CELL_COLOR,
fontWeight: 'bold',
padding: '5px 10px',
borderBottom: DEFAULT_BORDER,
borderTop: DEFAULT_BORDER
};
var TableBodyStyles = exports.TableBodyStyles = {
display: 'table-row-group'
};
var TableCellStyles = exports.TableCellStyles = {
display: 'table-cell',
backgroundColor: DEFAULT_HEADER_CELL_COLOR,
padding: '5px 10px',
borderBottom: '1px solid #DDD',
borderRight: DEFAULT_BORDER
};
var HeaderValueStyles = exports.HeaderValueStyles = {
fontSize: '12px'
};
var HeaderLabelStyles = exports.HeaderLabelStyles = {
fontSize: '16px',
padding: '0',
margin: '0'
};
/*Styles*/
/***/ }),
/* 2 */
/***/ (function(module, exports, __webpack_require__) {
;
Object.defineProperty(exports, "__esModule", {
value: true
});
var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }(); /**
* Created by jyothi on 16/6/17.
*/
var _styles = __webpack_require__(1);
var _DataStore = __webpack_require__(3);
var _DataStore2 = _interopRequireDefault(_DataStore);
var _Elements = __webpack_require__(4);
var _constants = __webpack_require__(0);
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
var CohortGraph = function () {
function CohortGraph(element, data, options) {
_classCallCheck(this, CohortGraph);
this.root = element;
this.options = options; //TODO: handle default options
this.dataStore = new _DataStore2.default({});
this.currentType = "";
this.valueType = _constants.VALUE_KEYS.VALUE;
this.init(data);
}
_createClass(CohortGraph, [{
key: 'init',
value: function init(data) {
var keys = Object.keys(data);
if (keys.length > 0) {
this.currentType = Object.keys(data)[0]; //taking first key as type by default TODO: give it as option
this.dataStore = new _DataStore2.default(data || {});
this.render();
}
}
}, {
key: 'render',
value: function render() {
var root = this.root,
options = this.options,
dataStore = this.dataStore,
currentType = this.currentType,
valueType = this.valueType;
var headerData = dataStore.getHeader(currentType);
var rowsData = dataStore.getRows(currentType);
root.appendChild((0, _Elements.Table)({ header: headerData, body: rowsData }, valueType));
}
}]);
return CohortGraph;
}();
exports.default = CohortGraph;
/***/ }),
/* 3 */
/***/ (function(module, exports, __webpack_require__) {
;
Object.defineProperty(exports, "__esModule", {
value: true
});
var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) { return typeof obj; } : function (obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; };
var _styles = __webpack_require__(1);
var _constants = __webpack_require__(0);
function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; }
function _toConsumableArray(arr) { if (Array.isArray(arr)) { for (var i = 0, arr2 = Array(arr.length); i < arr.length; i++) { arr2[i] = arr[i]; } return arr2; } else { return Array.from(arr); } }
function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } /**
* Created by jyothi on 16/6/17.
*/
var VALUE = _constants.VALUE_KEYS.VALUE,
PERCENT = _constants.VALUE_KEYS.PERCENT;
var DataStore =
/**
*
* @param data {Object}
* @param options {Object}
* options = {
* shadeColor: {string} HEX
* headerColor
* }
*/
function DataStore(data, options) {
_classCallCheck(this, DataStore);
_initialiseProps.call(this);
this.isValid = true;
this._checkValidity(data);
this.rawStore = data;
this.store = {};
this.headers = {};
if (this.isValid) {
this._buildStore(data);
this._buildHeaders();
} else {
throw new Error("Invalid Data for cohort graph..!");
}
}
/**
*
* @param data
* @private
*/
/**
*
* @param data
* @private
*/
/**
* builds header for table
* @private
*/
/**
* Sum of Array Elements
* @param arr
* @private
*/
/**
*
* @param arr
* @param index
* @returns {number}
* @private
*/
/**
*
* @param arr
* @param index
* @returns {number}
* @private
*/
/**
*
* @param type
* @returns {*}
*/
/**
*
* @param type
*/
/**
*
* @param type
* @param row
* @param col
* @returns {*}
*/
/**
*
* @param type
* @param col
* @returns {*}
*/
/**
*
* @param type
* @returns {*}
*/
/**
*
* @param type
* @returns {*}
*/
/**
* Cell Shade color based on percentage
* @param percent
* @param color
* @returns {string}
*/
/**
*
* @param total
* @param value
* @returns {number}
*/
/**
*
* @param color
* @returns {boolean}
*/
;
var _initialiseProps = function _initialiseProps() {
var _this = this;
this._checkValidity = function (data) {
if ((typeof data === 'undefined' ? 'undefined' : _typeof(data)) === 'object' && !Array.isArray(data)) {
for (var key in data) {
if (data.hasOwnProperty(key) && _typeof(data[key]) === 'object' && !Array.isArray(data[key])) {
for (var anotherKey in data[key]) {
if (data[key].hasOwnProperty(anotherKey) && !Array.isArray(data[key][anotherKey])) {
_this.isValid = false;
return;
}
}
} else {
_this.isValid = false;
return;
}
}
} else {
_this.isValid = false;
}
};
this._buildStore = function (data) {
var _loop = function _loop(key) {
if (data.hasOwnProperty(key)) {
_this.store[key] = [];
var _loop2 = function _loop2(anotherKey) {
if (data[key].hasOwnProperty(anotherKey)) {
var cellData = {};
cellData.type = key;
cellData[VALUE] = anotherKey;
cellData.valueFor = anotherKey;
cellData.total = data[key][anotherKey][0];
cellData[PERCENT] = 100;
cellData.color = _styles.DEFAULT_KEY_CELL_COLOR;
cellData.isLabel = true;
_this.store[key].push([cellData].concat(_toConsumableArray(data[key][anotherKey].map(function (value, index) {
var _ref;
var percent = _this._getPercentage(cellData.total, value);
return _ref = {
type: key
}, _defineProperty(_ref, VALUE, value), _defineProperty(_ref, 'valueFor', anotherKey), _defineProperty(_ref, 'total', cellData.total), _defineProperty(_ref, 'isTotal', index === 0), _defineProperty(_ref, PERCENT, percent), _defineProperty(_ref, 'color', index === 0 ? _styles.DEFAULT_CELL_COLOR : _this._shadeCellWithColor(percent)), _ref;
}))));
}
};
for (var anotherKey in data[key]) {
_loop2(anotherKey);
}
}
};
for (var key in data) {
_loop(key);
}
};
this._buildHeaders = function () {
var _loop3 = function _loop3(key) {
if (_this.store.hasOwnProperty(key)) {
var labelPrefix = _this._turnCamelCase(key.slice(0, -1));
_this.headers[key] = [];
_this.headers[key].push({ //first cell
value: "", //TODO:
color: _styles.DEFAULT_HEADER_CELL_COLOR,
isLabel: true,
label: _this._turnCamelCase(key)
});
var cellData = {};
cellData.isHeader = true;
cellData.index = 0;
cellData.type = key;
cellData[VALUE] = _this._sumOfColumnWithIndex(_this.store[key], 1);
cellData.valueFor = key;
cellData.total = cellData.value;
cellData[PERCENT] = 100;
cellData.isTotal = true;
cellData.color = _styles.DEFAULT_HEADER_CELL_COLOR;
cellData.label = labelPrefix + ' ' + 0;
_this.headers[key].push(cellData); //second cell
var largeRow = _this.store[key][0];
var totalRows = _this.store[key].length;
largeRow.forEach(function (el, index) {
var _this$headers$key$pus;
if (index < 2) return;
var value = _this._sumOfColumnWithIndex(_this.store[key], index);
var percent = _this._getPercentage(_this._sumOfFirstColumnUpToIndex(_this.store[key], totalRows), value);
_this.headers[key].push((_this$headers$key$pus = {
isHeader: true,
index: index,
type: key
}, _defineProperty(_this$headers$key$pus, VALUE, value), _defineProperty(_this$headers$key$pus, 'valueFor', largeRow[0]), _defineProperty(_this$headers$key$pus, 'total', cellData.total), _defineProperty(_this$headers$key$pus, PERCENT, percent), _defineProperty(_this$headers$key$pus, 'color', _this._shadeCellWithColor(percent)), _defineProperty(_this$headers$key$pus, 'label', labelPrefix + ' ' + (index - 1)), _this$headers$key$pus));
});
}
};
//TODO: can also take custom headers
for (var key in _this.store) {
_loop3(key);
}
};
this._sumOfArrayElements = function (arr) {
return arr.reduce(function (a, b) {
return a + b;
});
};
this._sumOfColumnWithIndex = function (arr, index) {
var sum = 0;
arr.forEach(function (el) {
try {
sum += el[index].value;
} catch (e) {
sum += 0;
}
});
return sum;
};
this._sumOfFirstColumnUpToIndex = function (arr, index) {
var sum = 0;
for (var i = 0; i <= index; i++) {
try {
sum += arr[i][1].value;
} catch (e) {
break;
}
}
return sum;
};
this.getTypeData = function (type) {
if (_this.store.hasOwnProperty(type)) {
return _this.store[type]; //returns [][]
} else {
throw new Error('No Data Found for type => ' + type);
}
};
this.getHighestRowSize = function (type) {
if (_this.store.hasOwnProperty(type)) {
return _this.store[type][0].length; //returns [][]
} else {
throw new Error('No Columns Found for type => ' + type);
}
};
this.getCellData = function (type, row, col) {
if (_this.store.hasOwnProperty(type)) {
try {
return _this.store[type][row][col];
} catch (e) {
throw new Error('No Data Found for cell with type => ' + type + ', row => ' + row + ', col => ' + col);
}
} else {
throw new Error('No Data Found for cell with type => ' + type + ', row => ' + row + ', col => ' + col);
}
};
this.getHeaderCellData = function (type, col) {
if (_this.headers.hasOwnProperty(type)) {
try {
return _this.headers[type][col];
} catch (e) {
throw new Error('No Data Found for cell with type => ' + type + ', col => ' + col);
}
} else {
throw new Error('No Data Found for cell with type => ' + type + ', col => ' + col);
}
};
this.getHeader = function (type) {
if (_this.headers.hasOwnProperty(type)) {
return _this.headers[type]; //returns [][]
} else {
throw new Error('No Headers Found for type => ' + type);
}
};
this.getRows = function (type) {
if (_this.store.hasOwnProperty(type)) {
return _this.store[type]; //returns [][]
} else {
throw new Error('No Headers Found for type => ' + type);
}
};
this._shadeCellWithColor = function (percent) {
var color = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : "#3f83a3";
var rate = 1.0 - Math.ceil(percent / 10) / 10;
var f = parseInt(color.slice(1), 16),
t = rate < 0 ? 0 : 255,
p = rate < 0 ? rate * -1 : rate,
R = f >> 16,
G = f >> 8 & 0x00FF,
B = f & 0x0000FF;
return '#' + (0x1000000 + (Math.round((t - R) * p) + R) * 0x10000 + (Math.round((t - G) * p) + G) * 0x100 + (Math.round((t - B) * p) + B)).toString(16).slice(1);
};
this._getPercentage = function (total, value) {
return total ? Math.round(value / total * 100 * 100) / 100 : total;
};
this._isValidHex = function (color) {
return (/(^#[0-9A-F]{6}$)|(^#[0-9A-F]{3}$)/i.test(color)
);
};
this._turnCamelCase = function (text) {
var defaultReturnValue = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : "";
if (typeof text === 'string') {
return text.toLowerCase().replace(/\b\w/g, function (replaced) {
return replaced.toUpperCase();
});
}
};
};
exports.default = DataStore;
/***/ }),
/* 4 */
/***/ (function(module, exports, __webpack_require__) {
;
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.Container = exports.Table = exports.Body = exports.Header = exports.Tr = exports.Td = exports.Th = undefined;
var _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; /**
* Created by jyothi on 16/6/17.
*/
var _styles = __webpack_require__(1);
var _constants = __webpack_require__(0);
function _objectDestructuringEmpty(obj) { if (obj == null) throw new TypeError("Cannot destructure undefined"); }
var VALUE = _constants.VALUE_KEYS.VALUE,
PERCENT = _constants.VALUE_KEYS.PERCENT;
var renderValue = function renderValue(props, valueType) {
var isTotal = props.isTotal,
isLabel = props.isLabel;
return isTotal || isLabel ? props[VALUE] : valueType === PERCENT ? props[PERCENT] + ' %' : props[VALUE];
};
var appendStyles = function appendStyles(element, newStyles) {
for (var style in newStyles) {
if (newStyles.hasOwnProperty(style)) {
element.style[style] = newStyles[style];
}
}
};
var Th = exports.Th = function Th(props, valueType) {
var div = document.createElement("div");
var p = document.createElement("p");
var span = document.createElement("span");
p.style = _extends({}, p.style, _styles.HeaderLabelStyles);
p.innerHTML = props.label;
appendStyles(span, _styles.HeaderValueStyles);
span.innerHTML = renderValue(props, valueType);
appendStyles(div, _extends({}, _styles.TableCellStyles, { backgroundColor: props.color }));
div.appendChild(p);
div.appendChild(span);
return div;
};
var Td = exports.Td = function Td(props, valueType) {
var div = document.createElement("div");
appendStyles(div, _extends({}, _styles.TableCellStyles, { backgroundColor: props.color }));
div.setAttribute("title", 'Out of ' + props.total + ' on ' + props.valueFor);
div.innerHTML = renderValue(props, valueType);
return div;
};
var Tr = exports.Tr = function Tr(props, valueType) {
var div = document.createElement("div");
appendStyles(div, _styles.TableRowStyles);
props.forEach(function (cellData) {
div.appendChild(Td(cellData, valueType));
});
return div;
};
var Header = exports.Header = function Header(props, valueType) {
var header = document.createElement("div");
appendStyles(header, _styles.TableHeaderStyles);
props.forEach(function (headerCellData) {
header.appendChild(Th(headerCellData, valueType));
});
return header;
};
var Body = exports.Body = function Body(props, valueType) {
var body = document.createElement("div");
appendStyles(body, _styles.TableBodyStyles);
props.forEach(function (rowData) {
body.appendChild(Tr(rowData, valueType));
});
return body;
};
var Table = exports.Table = function Table(props, valueType) {
var table = document.createElement("div");
appendStyles(table, _styles.TableStyles);
table.appendChild(Header(props.header, valueType));
table.appendChild(Body(props.body, valueType));
return table;
};
var Container = exports.Container = function Container(props, valueType) {
//TODO: wrapper container with controls
_objectDestructuringEmpty(props);
var container = document.createElement("div");
container.setAttribute('style', null);
var header = document.createElement("div");
header.setAttribute('style', null);
header.appendChild(title);
var title = document.createElement("p");
title.setAttribute('style', null);
title.setAttribute('title', props.title);
var controls = document.createElement("div");
header.setAttribute('style', null);
header.appendChild(title);
if (props.enableDateRange) {
var dateRange = document.createElement("a");
dateRange.setAttribute('type', "text");
dateRange.setAttribute('id', "retention-date-range");
dateRange.setAttribute('style', null);
var anchorWrapper = document.createElement("a");
anchorWrapper.setAttribute('style', null);
anchorWrapper.appendChild(dateRange);
}
};
/***/ }),
/* 5 */
/***/ (function(module, exports, __webpack_require__) {
;
/**
* Created by jyothi on 20/6/17.
*/
module.exports = __webpack_require__(2).default;
/***/ })
/******/ ]);
});