UNPKG

cohort-graph

Version:
550 lines (472 loc) 20.5 kB
/** * Created by Araja Jyothi Babu on 22-May-16. */ (function(factory) { 'use strict'; if (typeof define === 'function' && define.amd) { define(['old/js/jquery'], factory); } else if (typeof exports !== 'undefined') { module.exports = factory(require('old/js/jquery')); } else { factory(jQuery); } }(function($) { 'use strict'; var Retention = window.Retention || {}; Retention = (function() { function Retention(element, settings) { var _this = this; _this.element = element; _this.originalOptions = settings; _this.defaults = { data : {}, title : "Retention Analysis", startDate : null, endDate : null, inputDateFormat : null, dateDisplayFormat: null, cellClickEvent : function (d, i) { //do something }, showEmptyDataMessage : true, customEmptyDataMessage : null, enableInactive : false, dayClickEvent : function(day, startDate, endDate){ //do something }, retentionDays : 7, retentionWeeks : 4, retentionMonths : 3, enableTooltip : true, enableDateRange : false, showAbsolute : false, toggleValues : true, showHeaderValues : false }; _this.options = _this.generateOptions(); _this.proceedFlag = Object.keys(_this.options.data).length; _this.totalCounts = {}; _this.options.data = _this.sortData(); _this.currentData = _this.options.data['days']; _this.setDateRanges(); _this.currentSelected = 'retentionDays'; _this.isActive = true; _this.showValue = _this.options.showAbsolute; _this.isDateModified = false; _this.headerValues = []; _this.initialValues = []; //some dom Events $(document).ready(function() { $(document).on('click', 'td.clickable', function () { _this.options.cellClickEvent($(this).attr('date'), $(this).attr('day')); }); $(document).on('click', 'td.head-clickable', function () { _this.options.dayClickEvent($(this).attr('day'), _this.options.startDate, _this.options.endDate); }); $(document).on('mouseover mouseout', 'td.head-clickable', function () { $('.col' + $(this).attr('day')).toggleClass('hover'); }); $(document).on('click', '#retention-active-switch', function () { var body = $('.retention-body'); if ($(this).hasClass('retention-inactive')) { _this.isActive = false; _this.start(body); $(this).addClass('retention-active btn-success').removeClass('retention-inactive btn-warning').text("Active"); $('.retention-title').text("Inactive User Analysis"); } else { _this.isActive = true; _this.start(body); $(this).addClass('retention-inactive btn-warning').removeClass('retention-active btn-success').text("Inactive"); $('.retention-title').text("Active User Analysis"); } }); $(document).on('click', '#retention-toggle-values', function () { var body = $('.retention-body'); if (_this.showValue) { _this.showValue = false; _this.start(body); $(this).addClass('retention-active btn-info').removeClass('retention-inactive btn-warning').text("#"); } else { _this.showValue = true; _this.start(body); $(this).addClass('retention-inactive btn-primary').removeClass('retention-active btn-info').text("%"); } }); $('input[name="retention-switch"]').click(function () { var body = $('.retention-body'); var selectedKey = $(this).val(); var key = selectedKey == "Day" ? 'days' : (selectedKey == "Week" ? 'weeks' : 'months'); if(_this.options.enableDateRange) { var drp = $('#retention-date-range'); if(selectedKey != "Day") { $(drp).attr('readonly', 'true'); $('.calendar').hide(); }else{ $(drp).removeAttr('readonly'); $('.calendar').show(); } } _this.currentSelected = 'retention' + selectedKey + 's'; _this.currentData = _this.options.data[key]; _this.start(body, false); }); if(_this.options.enableDateRange) { $('#retention-date-range').daterangepicker({ "autoApply" : true, "startDate": _this.options.startDate, "endDate": _this.options.endDate, "minDate" : _this.options.startDate, "maxDate" : _this.options.endDate, 'opens' : 'left', "locale": { "format": _this.options.inputDateFormat || "DD-MM-YYYY" } },function(start, end, label) { _this.isDateModified = true; _this.options.startDate = start; _this.options.endDate = end; var body = $('.retention-body'); _this.start(body); }); } if (_this.options.enableTooltip) { $('body').tooltip({ selector: '[data-toggle="tooltip"]' }); } }); _this.init(); } return Retention; }()); Retention.prototype.init = function () { var container = this.getContainer(); $("input[value='Day']").attr("checked", ""); //default days checked var body = this.getBody(); body.appendTo(container); if(this.proceedFlag){ this.start(body); }else{ $('#retention-active-switch').hide(); this.emptyMessage(body); } }; Retention.prototype.emptyMessage = function (body) { if(this.options.showEmptyDataMessage){ $('<h3 />', { align : 'center', style : 'color:red', text : this.options.customEmptyDataMessage ? this.options.customEmptyDataMessage : "No data available to show..!" }).appendTo(body); } }; Retention.prototype.start = function (body) { var _this = this; $(body).html(''); var table = this.getTable(); table.appendTo(body); var tbody = $('<tbody />').appendTo(table); var rowsData = this.getRows(); this.headerValues = new Array(this.options[this.currentSelected] + 2).join('0').split('').map(parseFloat); this.initialValues = new Array(rowsData.length).join('0').split('').map(parseFloat); //initialising to 0 rowsData.forEach(function(data, index){ _this.generateRow(data).appendTo(tbody); _this.initialValues[index] = data[1]; //pushes initial values before calculating retention. In fact calculating retention for these values }); var tableHeader = this.generateTableHeader(table); tableHeader.appendTo(table); }; Retention.prototype.toggleValueButton = function () { var _this = this; var switchInput = $('<a />', { type : 'button', id : 'retention-toggle-values', class : 'retention-toggle-values btn btn-primary', text : '#' }); return switchInput; }; Retention.prototype.getContainer = function () { var container = $('<div />', { class : 'retention-box' }).appendTo(this.element); var header = $('<div />', { class : "retention-box-header with-border" }).appendTo(container); var title = $('<p />', { class : "retention-title retention-box-title", text : this.options.title }).appendTo(header); var controls = $('<div />', { class : "retention-box-tools" }).appendTo(header); if(this.options.enableDateRange) { var anchorWrapper = $('<a />', { style : 'display: inline-block;margin:0 5px;' }).appendTo(controls); var dateRange = $('<input />', { id: "retention-date-range", type: "text", class: 'form-control' }).appendTo(anchorWrapper); } var switchData = this.getSwitchData(); var switchContainer = $('<a />', { class : "switch-field" + (switchData.length == 1 ? " hide" : "") }).appendTo(controls); for(var key in switchData){ $('<input />', { type : "radio", name : "retention-switch", id : switchData[key], value : switchData[key], class : 'form-control' }).appendTo(switchContainer); $('<label />', { for : switchData[key], title : "Feature yet to implement", text : switchData[key] + "s" //appending s for "days" }).appendTo(switchContainer); } if(this.options.enableInactive) { var retentionSwitch = this.getInactiveSwitch(); retentionSwitch.appendTo(controls); } if(this.options.toggleValues){ var toggleValueButton = this.toggleValueButton(); toggleValueButton.appendTo(controls); } return container; }; Retention.prototype.getSwitchData = function () { var switchData = []; var _this = this; Object.keys(_this.options.data).forEach(function (key) { if(Object.keys(_this.options.data[key]).length > 0) switchData.push(key.capitalize().slice(0,-1)); }); return switchData; }; Retention.prototype.getInactiveSwitch = function () { var _this = this; var switchInput = $('<a />', { type : 'button', id : 'retention-active-switch', class : 'retention-inactive btn btn-warning', text : 'Inactive', style : 'width: 85px;' }); return switchInput; }; Retention.prototype.getBody = function () { var body = $('<div />', { class : "retention-body retention-box-body" }); return body; }; Retention.prototype.getTable = function () { var table = $('<table />', { class : "table table-bordered text-center" }); return table; }; /** * Gives overall percentage of column * @param value * @param index, index of retention */ Retention.prototype.getTotalPercentage = function (value, index) { var total = 0; var threshold = this.initialValues.length - index; this.initialValues.forEach(function (data, i) { if(i < threshold) total += data; }); return this.getPercentage(total, value); }; Retention.prototype.generateTableHeader = function (table) { var tHead = $('<thead />').appendTo(table); var tHeadRow = $('<tr />', { class : "retention-thead" }).appendTo(tHead); var headerData = this.getHeaderData(); var length = headerData.length; var _this = this; var td = "", span = ""; headerData.forEach(function(data, key){ //FIXME: no need to use headerData here td = $('<td />', { class : function(){ return key > 0 ? "retention-cell head-clickable" : "retention-cell key-cell"; }, day : key-1, text : data + " " }).appendTo(tHeadRow); if(key > 0) { span = $('<span />', { class: 'retention-badge badge-info', text: function () { return _this.showValue? _this.headerValues[key-1] : _this.getTotalPercentage(_this.headerValues[key-1], key-1) + "%"; } }).appendTo(td); } }); $('.head-clickable .retention-cell').css('width', (85 / (length+1)) + '%'); $('.retention-cell.key-cell').css('width', '15%'); return tHead; }; Retention.prototype.generateOptions = function () { var options = {}; var keys = Object.keys(this.defaults); var key; for(var i in keys){ key = keys[i]; options[key] = typeof this.originalOptions[key] == 'undefined' ? this.defaults[key] : this.originalOptions[key]; } return options; }; Retention.prototype.sortData = function () { var ordered = {}; var _this = this; var count = 0; var totalCounts = {}; var intermediate = {}; Object.keys(_this.options.data).forEach(function(key) { Object.keys(_this.options.data[key]).sort().forEach(function(subKey){ intermediate[subKey] = _this.options.data[key][subKey]; count += intermediate[subKey][0]; }); ordered[key] = intermediate; totalCounts[key] = count; intermediate = {}; count = 0; }); _this.totalCounts = totalCounts; return ordered; }; Retention.prototype.getModifiedRows = function () { var rows = []; var keys = Object.keys(this.currentData); var date; for(var key in keys){ date = keys[key]; if(this.currentData.hasOwnProperty(date) && this.isDateBetween(date)) { rows.push([keys[key]].concat(this.currentData[keys[key]])); } } return rows; } Retention.prototype.getRows = function(){ var rows = []; var keys = Object.keys(this.currentData); for(var key in keys){ if(this.currentData.hasOwnProperty(keys[key]) && this.isDateBetween(keys[key])) { rows.push([keys[key]].concat(this.currentData[keys[key]])); } } return rows; }; Retention.prototype.setDateRanges = function(){ var rows = []; var keys = Object.keys(this.currentData); this.options.startDate = keys[0]; this.options.endDate = keys[keys.length-1]; }; Retention.prototype.formatDate = function(date){ if(! Date.parse(date)) return date; return (this.options.dateDisplayFormat != null) ? (this.options.inputDateFormat ? moment(date, this.options.inputDateFormat).format(this.options.dateDisplayFormat) : moment(date).format(this.options.dateDisplayFormat)) : date; }; Retention.prototype.getPercentage = function (total, value) { if(total == 0 || value == 0) return 0; //handling Infinity return Math.round((value/total * 100) * 100) / 100; }; Retention.prototype.isValidHex = function(color){ return /(^#[0-9A-F]{6}$)|(^#[0-9A-F]{3}$)/i.test(color); }; String.prototype.capitalize = function(){ return this.toLowerCase().replace( /\b\w/g, function (m) { return m.toUpperCase(); }); }; Retention.prototype.shadeColor = function(color, percent) { //# color = this.isValidHex(color) ? color : "#3f83a3"; //handling null color; percent = 1.0 - Math.ceil(percent / 10) / 10; var f=parseInt(color.slice(1),16),t=percent<0?0:255,p=percent<0?percent*-1:percent,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); }; Retention.prototype.tooltipData = function(date, total, count, dayIndex){ var fromDate = this.formatDate(date);//.format("MMM DD"); var toDate = moment(this.formatDate(date), this.options.dateDisplayFormat).add(dayIndex-1, "days").format(this.options.dateDisplayFormat); var active = this.isActive? " active " : " inactive "; return "Of " + total + " users came on " + fromDate + ", " + (count + " were " + active + " on " + toDate); }; Retention.prototype.isDateBetween = function (date) { if(this.currentSelected != 'retentionDays') return true; var start = this.options.startDate; var end = this.options.endDate; date = this.options.inputDateFormat != null ? moment(date, this.options.inputDateFormat) : date; return this.isDateModified? start.diff(date) <= 0 && date.diff(end) <= 0 : true; //moment ref:http://momentjs.com/docs/#/displaying/difference/ }; Retention.prototype.getHeaderData = function(){ var selector = $("input[name='retention-switch']:checked"); //changes by selection of switch var headerDataAppender = $(selector).val(); var headerData = []; var length = this.options['retention' + headerDataAppender + 's'] + 2; for(var i = 0; i < length; i++){ headerData.push(i > 0? (headerDataAppender + " " + (i-1)) : (this.totalCounts[headerDataAppender.toLowerCase() + 's'] + " Users")); } return headerData; }; Retention.prototype.generateRow = function(data){ var _this = this; var row = $('<tr />'); var date = data[0]; var dayCount = 0; var count = data[1]; //|| 1; //to handle divisionBy0 var td, div; var keysLength = _this.options[_this.currentSelected] + 2; var actualLength = data.length; for(var key = 0; key < keysLength; key++){ if(key < actualLength) { dayCount = _this.isActive ? data[key] : (count - data[key]); _this.headerValues[key-1] += (key == 1? count : dayCount); var className = (key > 0 ? "retention-cell" + (key > 1 ? " clickable" : "") : "retention-date") + (" col" + (key - 1)); td = $('<td />', { class: className, style: function () { if (key > 1) { return "background-color :" + _this.shadeColor("", _this.getPercentage(count, dayCount)); } }, date: date, day: key - 1 }).appendTo(row); div = $('<div />', { 'data-toggle': _this.options.enableTooltip ? "tooltip" : "", 'data-original-title': function () { if (key > 1) { return _this.tooltipData(date, count, dayCount, key); } }, text: _this.displayValue(key, data[key], count) }).appendTo(td); } } return row; }; Retention.prototype.displayValue = function(index, value, count){ if(index == 0) return this.formatDate(value); if(index == 1) return value; value = this.isActive? value : count - value; return this.showValue? value : (this.getPercentage(count, value) + " %"); }; $.fn.retention = function(options) { if(typeof options == 'object'){ new Retention(this, options); }else{ throw new Error("Need options to show retention graph..!"); } }; }));