//new comment var highchartsJs = [ '//code.highcharts.com/highcharts-3d.js', '//code.highcharts.com/modules/exporting.js', //'//code.highcharts.com/modules/series-label.js', '//code.highcharts.com/modules/offline-exporting.js', '//cdnjs.cloudflare.com/ajax/libs/jspdf/1.3.5/jspdf.min.js' ]; if (typeof define == "function") { define(['https://cdnjs.cloudflare.com/ajax/libs/handlebars.js/2.0.0/handlebars.js', 'jquery', edroot + '/js/jquery.dataTables.min.js', 'datepicker', edroot + '/js/chart.js', '//code.highcharts.com/highcharts.js'], easy); } else { jQuery.getScript('//code.highcharts.com/highcharts.js').then(function () { easy(Handlebars, jQuery, null, null, Chart); }); } if (typeof edroot == "undefined") { var edroot = ""; } //https://www.sitepoint.com/trimming-strings-in-javascript/ //if (!String.prototype.trimLeft) String.prototype.trimLeft = function (charlist) { if (charlist === undefined) charlist = "\\s"; return this.replace(new RegExp("^[" + charlist + "]+"), ""); }; //https://www.sitepoint.com/trimming-strings-in-javascript/ //if (!String.prototype.trimRight) String.prototype.trimRight = function (charlist) { if (charlist === undefined) charlist = "\\s"; return this.replace(new RegExp("[" + charlist + "]+$"), ""); }; //https://www.sitepoint.com/trimming-strings-in-javascript/ //if (!String.prototype.trim) String.prototype.trim = function (charlist) { return this.trimRight(charlist).trimLeft(charlist); }; // if (!Array.prototype.aggregate) Array.prototype.aggregate = function (initial, aggregator, selector) { if (selector && typeof selector != "function") throw "selector must be a function.."; var values = selector ? this.map(selector) : this; return values.reduce(aggregator, initial); }; // if (!Array.prototype.sum) Array.prototype.sum = function (selector) { return this.aggregate(0.0, (a, b) => a + b, selector); }; // if (!Array.prototype.avg) Array.prototype.avg = function (selector) { return this.sum(selector) / this.length; }; function easy(Handlebars, $, DataTables, DatePicker, Chart) { highchartsJs.forEach(function (js) { return jQuery.getScript(js); }); setTimeout(init, 10); var inProd = window.location.href.indexOf("research") >= 0; var chartsPerView = 3; var config = { "3dchart": !inProd}; var curPivot; var monthNames = ["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"]; var chart; var curChartType = ''; var chartTypes = ['Line', 'Bar', 'Horizontal Bar', 'Pie', 'Donut'].filter(function(t, i){ return i <= 1 || !inProd}); //var chartColors = [ // "rgba(241,90,96,1)", "rgba(122,195,106,1)", "rgba(115,115,115,.6)", "rgba(90,155,212,1)", // "rgba(215,127,180,1)", "rgba(250,167,91,1)", "rgba(158,103,171,1)", "rgba(206,112,88,1)" //]; //var chartColors = [ // "#3366CC", "#DC3912", "#FF9900", "#109618", "#990099", "#3B3EAC", "#0099C6", "#DD4477", "#66AA00", "#B82E2E", // "#316395", "#994499", "#22AA99", "#AAAA11", "#6633CC", "#E67300", "#8B0707", "#329262", "#5574A6", "#3B3EAC" //]; var chartColors = [ "rgba(241,90,96,.99)", "rgba(122,195,106,.99)", "rgba(115,115,115,.99)", "rgba(90,155,212,.99)", "rgba(215,127,180,.99)", "rgba(250,167,91,.99)", "rgba(158,103,171,.99)", "rgba(206,112,88,.99)", "rgba(250, 190, 190,.99)", "rgba(0, 128, 128,.99)", "rgba(230, 190, 255,.99)", "rgba(170, 110, 40,.99)", "rgba(255, 250, 200,.99)", "rgba(128, 0, 0,.99)", "rgba(170, 255, 195,.99)", "rgba(128, 128, 0,.99)", "rgba(255, 215, 180,.99)", "rgba(0, 0, 128,.99)", "rgba(128, 128, 128,.99)", "rgba(0, 130, 200,.99)", "rgba(60, 180, 75,.99)", "rgba(245, 130, 48,.99)", "rgba(145, 30, 180,.99)", "rgba(230, 25, 75,.99)", "rgba(0, 0, 0,.99)", "rgba(70, 240, 240,.99)", "rgba(240, 50, 230,.99)", "rgba(210, 245, 60,.99)", ]; function rgbaToHex(rgba) { if (/^\#[0-9]{4, 6}/.test(rgba)) return rgba; var parts = rgba.substring(rgba.indexOf("(")).split(","), r = parseInt(parts[0].substring(1).trim(), 10), g = parseInt(parts[1].trim(), 10), b = parseInt(parts[2].trim(), 10), a = parseFloat(parts[3].substring(0, parts[3].length - 1).trim()).toFixed(2); return ('#' + r.toString(16) + g.toString(16) + b.toString(16) + (a * 255).toString(16).substring(0, 2)); } function init() { loadConfig(function (err, cfg) { if (err) { $("#easy").html(err); return; } config = Object.assign(cfg, config); config.edroot = edroot; loadTemplate(function (err, text) { if (err) { $("#easy").html(err); return; } loadNavData(config, function (err, data) { if (err) { $("#easy").html(err); return; } data.config = config; var template = Handlebars.compile(text); var html = template(convertCountsToArray(data)); $("#easy").append(html); renderFilters(data); loadData(); }); }); if (window.Highcharts) Highcharts.setOptions({ credits: { text: "GeoPoll.com", href: "https://www.geopoll.com/", enabled: true }, // exporting:{ // url: 'http://localhost:7800' // }, colors: chartColors.map(function (color) { var hex = rgbaToHex(color); return { radialGradient: { cx: 0.5, cy: 0.3, r: 0.7 }, stops: [ [0, color], [1, Highcharts.Color(color).brighten(-0.3).get('rgb')] // darken ] }; }) }); }); } function renderFilters(data) { if (data.counts && data.counts.CreatedDate && data.counts.CreatedDate.length > 0) { var minDate = new Date(data.counts.CreatedDate[0].key); } else { var minDate = new Date($("#fromDate").data("min")); } var maxDate = new Date($("#toDate").data("max")); var dpoptions = { format: "yyyy-mm-dd", autoclose: true, onRender: function (date) { if (date.valueOf() < minDate || date.valueOf() > maxDate) { return 'disabled'; } return ''; } }; if ($("#fromDate").datepicker) { $("#fromDate").datepicker(dpoptions); $("#toDate").datepicker(dpoptions); } $(".customFilterActive a, .customFilter a").click(function (ev) { var $el = $(ev.currentTarget); if ($el.parent().hasClass("customFilterActive")) { $el.parent() .removeClass("customFilterActive") .addClass("customFilter"); } else { if ($el.parent().hasClass("metric")) { $(".customFilterActive.metric, .customFilter.metric").removeClass("customFilterActive").addClass("customFilter"); } $el.parent() .removeClass("customFilter") .addClass("customFilterActive"); } }); //make sure we get an active measure: if ($("input[name='measure']:checked").length == 0) { $("input[name='measure']").first().prop('checked', true); } //handle All $("#filters .all").change(function (ev) { console.log(ev.target.name); if (ev.target.checked) { $("input[name='" + ev.target.name + "']").prop('checked', true); } else { $("input[name='" + ev.target.name + "']").prop('checked', false); } }); $("#filters input[type='checkbox']").not(".all").change(function (ev) { //if there all checked, check all, if not, uncheck all var allChecked = true; var $inp = $("#filters input[name='" + ev.target.name + "']").not(".all"); for (var i = 0; i < $inp.length; i++) { if (!$inp[i].checked) { allChecked = false; break; } } if (allChecked) { $("#filters input[name='" + ev.target.name + "']").prop('checked', true); } else { $("#filters input[name='" + ev.target.name + "'].all").prop('checked', false); } }); //bind filter events $("#easy input, #easy select").change(loadData); $("#fromDate, #toDate").on('changeDate', function (ev) { $(this).data('datepicker').hide(); loadData(); }); $("#fromDate + span, #toDate + span").on('click', function (ev) { var dp = $(this).prev().data('datepicker').show(); }); } function loadConfig(cb) { var url = edroot + "config.json"; $.ajax({ url: url, dataType: "json", success: function (data) { cb(null, data); }, error: function (xhr, status, error) { console.log(error); cb("Connection Error loading config: "); } }); } function loadTemplate(cb) { var url = edroot + "template/nav.html"; $.ajax({ url: url, success: function (data) { cb(null, data); }, error: function (xhr, status, error) { cb("Connection Error downloading /template/nav.html"); } }); } function loadNavData(options, cb) { $.ajax({ url: edroot + 'search?limit=1&q=*:*&counts=' + JSON.stringify(options.filters || options.counts), dataType: 'json', success: function (data) { cb(null, data); }, error: function (xhr, status, error) { cb("Connection Error downloading Navigation Data"); } }); } function loadData(cb) { //get query options var opt = getQueryOptions(); //send request getDataFromServer(opt, function (err, data) { var pivot = pivotData(data, opt); if (data.rows[0] && data.rows[0].doc) $("#questionText").html(data.rows[0].doc.QuestionText.replace("Invalid response.", "").trim()); curPivot = pivot; if (opt.metric == "Net Promoter Score") { pivot = addNPS(pivot); } if (opt.display == "Percentage") { pivot = convertToPercentage(pivot, opt); } if (opt.measure.indexOf("MonthCount") >= 0) { pivot.rows.forEach(function (row, i) { var d = new Date(row[0]); row[0] = monthNames[d.getUTCMonth()].substr(0, 3) + ' ' + d.getUTCFullYear(); }); } if (opt.measure.indexOf("WeekCount") >= 0) { pivot.rows.forEach(function (row, i) { row[0] = "Week of " + row[0]; }); } var dtData = convertPivotToDataTables(pivot, opt); loadChart(pivot, opt); //redraw chart on window resize $(window).off('resize').on("resize", function () { loadChart(pivot, opt); }); loadDatatable(dtData, opt); $(document).on("click", "#charttypes .chartopt", function () { var $this = $(this) var tag = (($this.data("tag") || "") + ":" + $this.text()).trim(':'); curChartType = tag.split(':')[0]; var curChartSubType = tag.split(':')[1]; $("#charttypes span").removeClass('activeChartType'); $("#charttypes span:contains(" + curChartSubType + ")").addClass('activeChartType'); //get query options opt.chartType = curChartType; opt.chartSubType = curChartSubType; loadChart(pivot, opt); }) }); } function addNPS(pivot) { //NPS= (Number of Promoters — Number of Detractors) / (Number of Respondents) X 100 pivot.columns.splice(1, 0, "Net Promoter Score"); pivot.rows.forEach(function (row, i) { //var nps = Math.round((row[3]-row[1])/row[4] * 100); var nps = row[3] - row[1]; row.splice(1, 0, nps); }); return pivot; } function convertCountsToArray(data) { //convert data to work better with handlebart templates. Enables @last in template var counts, count, newcount, newcounts = {}; counts = data.counts; for (var key in counts) { count = counts[key]; newcount = []; for (var id in count) { newcount.push({ key: id, value: count[id] }); } newcounts[key] = newcount; } data.counts = newcounts return data; } function getQueryOptions() { //Get the measures var ret = { metric: $("input[name='metric']:checked").val(), measure: $("input[name='measure']:checked").data("measure"), display: $("input[name='measure']:checked").data("display"), fromAge: $("input[name='fromAge']").val(), toAge: $("input[name='toAge']").val(), gender: $("input[name='gender']:checked").val(), LSMGroup: $("input[name='LSMGroup']:checked").val(), county: $("input[name='county']:checked").val(), region: $("input[name='region']:checked").val(), topography: $("input[name='topography']:checked").val(), province: $("input[name='province']:checked").val(), fromDate: new Date($("input[name='fromDate']").val()).toISOString().substring(0, 10).replace(/\-/g, ''), toDate: new Date($("input[name='toDate']").val()).toISOString().substring(0, 10).replace(/\-/g, ''), chartType: curChartType } if (ret.measure == "DayCount") { ret.measure = $("select[name='measure-rollingaveragedays']").val(); } if (ret.measure.indexOf("MonthCount") >= 0) { ret.fromDate = ret.fromDate.substring(0, 6) + '01'; } ret.query = { counts: JSON.stringify([ret.measure, ret.measure.replace('Count', 'Sample').replace('By','')]), limit: 1, include_docs: true, q: 'QuestionName:"' + ret.metric + '"' //+ ' AND CreatedDateRange:[' + ret.fromDate + ' TO ' + ret.toDate + ']' + ' AND ' + ret.measure + ':[' + ret.fromDate + ' TO ' + ret.toDate + '*]' + ' AND AgeRange:[' + ret.fromAge + ' TO ' + ret.toAge + ']' + (ret.gender == 'all' || ret.gender == undefined ? '' : ' AND Gender:"' + ret.gender + '"') + (ret.education == 'all' || ret.education == undefined ? '' : ' AND Education:"' + ret.education + '"') + (ret.country == 'all' || ret.country == undefined ? '' : ' AND Country:"' + ret.country + '"') + ((!ret.LSMGroup || ret.LSMGroup == 'all') ? '' : ' AND LSMGroup:"' + ret.LSMGroup + '"') + ((!ret.province || ret.province == 'all') ? '' : ' AND Province:"' + ret.province + '"') + ((!ret.region || ret.region == 'all') ? '' : ' AND Region:"' + ret.region + '"') + ((!ret.topography || ret.topography == 'all') ? '' : ' AND Topography:"' + ret.topography + '"') //if(ret.p) }; console.log(ret.query.q); return ret; } function convertToPercentage(pivot, options) { pivot.rows.forEach(function (row, rowidx) { for (var i = 1; i < pivot.columns.length - 1; i++) { row[i] = Math.floor((row[i] / row[row.length - 1]) * 1000) / 10; } }); return pivot; } //Parse and pivot the result from the search counts function pivotData(data, options) { var count = data.counts[options.measure]; var sample = data.counts[options.measure.replace("Count", "Sample").replace('By','')]; var ret = { columns: ["Date"], rows: [] }; var lastDate = ""; var date, answer, row; //find all the columns and sort them for (var dateAnswer in count) { date = dateAnswer.split('|')[0]; answer = dateAnswer.split('|')[1]; if (date >= options.fromDate && date <= options.toDate || (options.measure.indexOf('DayCount') == -1 && options.measure.indexOf('MonthCount') == -1 && options.measure.indexOf('WeekCount') == -1)) { if (ret.columns.indexOf(answer) == -1) { ret.columns.push(answer); } } } ret.columns = ret.columns.sort(function (a, b) { if (a == "Date") { return -1; } else if (b == "Date") { return 1; } return a < b ? -1 : 1; }); ret.columns.push("Sample"); //build the rows array for (var dateAnswer in count) { date = dateAnswer.split('|')[0]; if (date >= options.fromDate && date <= options.toDate || (options.measure.indexOf('DayCount') == -1 && options.measure.indexOf('MonthCount') == -1 && options.measure.indexOf('WeekCount') == -1)) { date = date.substring(0, 4) + '-' + date.substring(4, 6) + '-' + date.substring(6, 8); answer = dateAnswer.split('|')[1]; if (date != lastDate) { lastDate = date; var row = [date]; ret.rows.push(row); } row[ret.columns.indexOf(answer)] = count[dateAnswer]; } } //fill in the blanks with 0 for missing rows //add a total to it as well ret.rows.forEach(function (row, rowidx) { for (var i = 0; i < ret.columns.length; i++) { if (typeof row[i] == "undefined") { row[i] = 0; } if (ret.columns[i] == "Sample") { var key = row[0].replace(/\-/g, ''); key = Object.keys(sample).find(function(k){ return k.startsWith(key); }) || key; row[i] = sample[key] || row[i]; } } }); return ret; } //convert pivot to DataTables.net function convertPivotToDataTables(pivot, options) { var data = { //bSearchable:false, bFilter: false, bPaginate: false, bAutoWidth: true, aoColumns: pivot.columns.map(function (col) { return { "sTitle": col }; }), aaData: pivot.rows.map(function (row) { return row.map(function (cell, i) { if (i == 0) { // return cell || ""; } if (options.metric == "Net Promoter Score" && i == 1) { return Math.round(cell || "0"); } if (options.display == "Percentage" && pivot.columns[i] != "Sample") { return (cell || "0") + '%'; } return cell || ""; }); }) }; return data; } function posibleChartTypes(data) { var checks = { 'Line': data.labels.length > 1, 'Bar': data.datasets.length > 1, 'Horizontal Bar': data.datasets.length > 1, 'Pie': data.datasets.length > 1, 'Donut': data.datasets.length > 1 } return chartTypes.filter(function (t) { return checks[t]; }); } function loadChart(pivot, options) { if (chart && chart.destroy) chart.destroy(); var data = convertPivotToChart(pivot, options); options.chartType = options.chartType || posibleChartTypes(data)[0] || 'Bar'; var $tc = $("#trackingchart"); $tc.html(''); var $canvas = $tc.find("#chartCanvas"); if (config["3dchart"]) $tc.width("100%"); else $canvas.attr({width: $tc.width(), height: $tc.height()}); $canvas.height($tc.height()).width($tc.width()); var ctx = $canvas[0].getContext('2d'); ctx.width = $tc.width(); ctx.height = $tc.height(); var html = posibleChartTypes(data).map(function (h) { if (["Pie", "Donut"].indexOf(h) >= 0) { return "