diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..622d14a --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +config +server.pyc diff --git a/Readme.md b/Readme.md new file mode 100644 index 0000000..5eacb3c --- /dev/null +++ b/Readme.md @@ -0,0 +1,19 @@ + +# SQL chart +Draw chart any SQL queries + + +## What is this? +A simple web application to visualize data from any arbitrary SQL query. + +## How it is working? +* set `DB_CONNECTION` string either in env variable or in `config` file +* Start the application by running `run.sh`. Visit 'http://127.0.0.1:8000/index.html' +* Enter the SQL query in the text box and submit. +* available columns in the data will be shown in UI. visualize the data by dragging columns into required axis. Also choose requied aggregation function for pivot table +* Entire state of the graph ( SQL, columns in each axis, aggregation function etc ) is stored in the URL ( in URL hash ) so that we can reload the page without loosing the data. + +## Thanks +* Authors of pivottable ( https://github.com/nicolaskruchten/pivottable ). The UI code is shamelessly copied from one of the examples given in this project. +* Simple backed api server ( server.js ) is written in https://falconframework.org/. +* https://plot.ly/javascript/ \ No newline at end of file diff --git a/config.sample b/config.sample new file mode 100644 index 0000000..e3d59a3 --- /dev/null +++ b/config.sample @@ -0,0 +1,2 @@ + +DB_CONNECTION=postgresql://postgres@localhost/postgres diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..ee1f55f --- /dev/null +++ b/requirements.txt @@ -0,0 +1,2 @@ +falcon==1.4.1 +gunicorn==19.9.0 diff --git a/run.sh b/run.sh new file mode 100755 index 0000000..ee83456 --- /dev/null +++ b/run.sh @@ -0,0 +1,4 @@ +#!/usr/bin/env bash + +[ -f ./env.sh ] && . ./env.sh && export DB_CONNECTION +gunicorn server:api diff --git a/server.py b/server.py new file mode 100644 index 0000000..8516162 --- /dev/null +++ b/server.py @@ -0,0 +1,27 @@ +import falcon +import sqlalchemy +import json +import sys +import datetime +import os + +db_url = os.environ[ 'DB_CONNECTION' ] if 'DB_CONNECTION' in os.environ else 'postgresql://postgres@localhost/postgres' +eng = sqlalchemy.create_engine(db_url, pool_size=1, pool_recycle=3600) +conn = eng.connect() +print "Connected to db" + +class QResource: + def on_get(self, req, resp): + """Handles GET requests""" + sql = req.params['q'] + result = conn.execute(sql) + data = [(dict(row.items())) for row in result] + resp.set_header('Access-Control-Allow-Origin', '*') + resp.set_header('Access-Control-Allow-Headers', 'Content-Type,Authorization') + resp.set_header('Access-Control-Allow-Methods', 'GET,PUT,POST,DELETE,OPTIONS') + resp.set_header('content-type', 'application/json; charset=UTF-8') + resp.body = json.dumps( data, default=str ) + +api = falcon.API() +api.add_static_route('/', os.path.dirname(os.path.realpath(__file__)) + '/static' ) +api.add_route('/api/data', QResource()) diff --git a/static/css/pivot.css b/static/css/pivot.css new file mode 100644 index 0000000..4df17e8 --- /dev/null +++ b/static/css/pivot.css @@ -0,0 +1,114 @@ +.pvtUi { color: #333; } + + +table.pvtTable { + font-size: 8pt; + text-align: left; + border-collapse: collapse; +} +table.pvtTable thead tr th, table.pvtTable tbody tr th { + background-color: #e6EEEE; + border: 1px solid #CDCDCD; + font-size: 8pt; + padding: 5px; +} + +table.pvtTable .pvtColLabel {text-align: center;} +table.pvtTable .pvtTotalLabel {text-align: right;} + +table.pvtTable tbody tr td { + color: #3D3D3D; + padding: 5px; + background-color: #FFF; + border: 1px solid #CDCDCD; + vertical-align: top; + text-align: right; +} + +.pvtTotal, .pvtGrandTotal { font-weight: bold; } + +.pvtVals { text-align: center; white-space: nowrap;} +.pvtRowOrder, .pvtColOrder { + cursor:pointer; + width: 15px; + margin-left: 5px; + display: inline-block; } +.pvtAggregator { margin-bottom: 5px ;} + +.pvtAxisContainer, .pvtVals { + border: 1px solid gray; + background: #EEE; + padding: 5px; + min-width: 20px; + min-height: 20px; + + user-select: none; + -webkit-user-select: none; + -moz-user-select: none; + -khtml-user-select: none; + -ms-user-select: none; +} +.pvtAxisContainer li { + padding: 8px 6px; + list-style-type: none; + cursor:move; +} +.pvtAxisContainer li.pvtPlaceholder { + -webkit-border-radius: 5px; + padding: 3px 15px; + -moz-border-radius: 5px; + border-radius: 5px; + border: 1px dashed #aaa; +} + +.pvtAxisContainer li span.pvtAttr { + -webkit-text-size-adjust: 100%; + background: #F3F3F3; + border: 1px solid #DEDEDE; + padding: 2px 5px; + white-space:nowrap; + -webkit-border-radius: 5px; + -moz-border-radius: 5px; + border-radius: 5px; +} + +.pvtTriangle { + cursor:pointer; + color: grey; +} + +.pvtHorizList li { display: inline; } +.pvtVertList { vertical-align: top; } + +.pvtFilteredAttribute { font-style: italic } + +.pvtFilterBox{ + z-index: 100; + width: 300px; + border: 1px solid gray; + background-color: #fff; + position: absolute; + text-align: center; +} + +.pvtFilterBox h4{ margin: 15px; } +.pvtFilterBox p { margin: 10px auto; } +.pvtFilterBox label { font-weight: normal; } +.pvtFilterBox input[type='checkbox'] { margin-right: 10px; margin-left: 10px; } +.pvtFilterBox input[type='text'] { width: 230px; } +.pvtFilterBox .count { color: gray; font-weight: normal; margin-left: 3px;} + +.pvtCheckContainer{ + text-align: left; + font-size: 14px; + white-space: nowrap; + overflow-y: scroll; + width: 100%; + max-height: 250px; + border-top: 1px solid lightgrey; + border-bottom: 1px solid lightgrey; +} + +.pvtCheckContainer p{ margin: 5px; } + +.pvtRendererArea { padding: 5px;} diff --git a/static/index.html b/static/index.html new file mode 100644 index 0000000..7fb0aed --- /dev/null +++ b/static/index.html @@ -0,0 +1,80 @@ + + + + + Pivot Demo + + + + + + + + + + + + + +
+ + + +
+ + +
+ + + diff --git a/static/js/pivot.js b/static/js/pivot.js new file mode 100644 index 0000000..489e412 --- /dev/null +++ b/static/js/pivot.js @@ -0,0 +1,1839 @@ +(function() { + var callWithJQuery, + indexOf = [].indexOf || function(item) { for (var i = 0, l = this.length; i < l; i++) { if (i in this && this[i] === item) return i; } return -1; }, + slice = [].slice, + bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; }, + hasProp = {}.hasOwnProperty; + + callWithJQuery = function(pivotModule) { + if (typeof exports === "object" && typeof module === "object") { + return pivotModule(require("jquery")); + } else if (typeof define === "function" && define.amd) { + return define(["jquery"], pivotModule); + } else { + return pivotModule(jQuery); + } + }; + + callWithJQuery(function($) { + + /* + Utilities + */ + var PivotData, addSeparators, aggregatorTemplates, aggregators, dayNamesEn, derivers, getSort, locales, mthNamesEn, naturalSort, numberFormat, pivotTableRenderer, rd, renderers, rx, rz, sortAs, usFmt, usFmtInt, usFmtPct, zeroPad; + addSeparators = function(nStr, thousandsSep, decimalSep) { + var rgx, x, x1, x2; + nStr += ''; + x = nStr.split('.'); + x1 = x[0]; + x2 = x.length > 1 ? decimalSep + x[1] : ''; + rgx = /(\d+)(\d{3})/; + while (rgx.test(x1)) { + x1 = x1.replace(rgx, '$1' + thousandsSep + '$2'); + } + return x1 + x2; + }; + numberFormat = function(opts) { + var defaults; + defaults = { + digitsAfterDecimal: 2, + scaler: 1, + thousandsSep: ",", + decimalSep: ".", + prefix: "", + suffix: "" + }; + opts = $.extend({}, defaults, opts); + return function(x) { + var result; + if (isNaN(x) || !isFinite(x)) { + return ""; + } + result = addSeparators((opts.scaler * x).toFixed(opts.digitsAfterDecimal), opts.thousandsSep, opts.decimalSep); + return "" + opts.prefix + result + opts.suffix; + }; + }; + usFmt = numberFormat(); + usFmtInt = numberFormat({ + digitsAfterDecimal: 0 + }); + usFmtPct = numberFormat({ + digitsAfterDecimal: 1, + scaler: 100, + suffix: "%" + }); + aggregatorTemplates = { + count: function(formatter) { + if (formatter == null) { + formatter = usFmtInt; + } + return function() { + return function(data, rowKey, colKey) { + return { + count: 0, + push: function() { + return this.count++; + }, + value: function() { + return this.count; + }, + format: formatter + }; + }; + }; + }, + uniques: function(fn, formatter) { + if (formatter == null) { + formatter = usFmtInt; + } + return function(arg) { + var attr; + attr = arg[0]; + return function(data, rowKey, colKey) { + return { + uniq: [], + push: function(record) { + var ref; + if (ref = record[attr], indexOf.call(this.uniq, ref) < 0) { + return this.uniq.push(record[attr]); + } + }, + value: function() { + return fn(this.uniq); + }, + format: formatter, + numInputs: attr != null ? 0 : 1 + }; + }; + }; + }, + sum: function(formatter) { + if (formatter == null) { + formatter = usFmt; + } + return function(arg) { + var attr; + attr = arg[0]; + return function(data, rowKey, colKey) { + return { + sum: 0, + push: function(record) { + if (!isNaN(parseFloat(record[attr]))) { + return this.sum += parseFloat(record[attr]); + } + }, + value: function() { + return this.sum; + }, + format: formatter, + numInputs: attr != null ? 0 : 1 + }; + }; + }; + }, + extremes: function(mode, formatter) { + if (formatter == null) { + formatter = usFmt; + } + return function(arg) { + var attr; + attr = arg[0]; + return function(data, rowKey, colKey) { + return { + val: null, + sorter: getSort(data != null ? data.sorters : void 0, attr), + push: function(record) { + var ref, ref1, ref2, x; + x = record[attr]; + if (mode === "min" || mode === "max") { + x = parseFloat(x); + if (!isNaN(x)) { + this.val = Math[mode](x, (ref = this.val) != null ? ref : x); + } + } + if (mode === "first") { + if (this.sorter(x, (ref1 = this.val) != null ? ref1 : x) <= 0) { + this.val = x; + } + } + if (mode === "last") { + if (this.sorter(x, (ref2 = this.val) != null ? ref2 : x) >= 0) { + return this.val = x; + } + } + }, + value: function() { + return this.val; + }, + format: function(x) { + if (isNaN(x)) { + return x; + } else { + return formatter(x); + } + }, + numInputs: attr != null ? 0 : 1 + }; + }; + }; + }, + quantile: function(q, formatter) { + if (formatter == null) { + formatter = usFmt; + } + return function(arg) { + var attr; + attr = arg[0]; + return function(data, rowKey, colKey) { + return { + vals: [], + push: function(record) { + var x; + x = parseFloat(record[attr]); + if (!isNaN(x)) { + return this.vals.push(x); + } + }, + value: function() { + var i; + if (this.vals.length === 0) { + return null; + } + this.vals.sort(function(a, b) { + return a - b; + }); + i = (this.vals.length - 1) * q; + return (this.vals[Math.floor(i)] + this.vals[Math.ceil(i)]) / 2.0; + }, + format: formatter, + numInputs: attr != null ? 0 : 1 + }; + }; + }; + }, + runningStat: function(mode, ddof, formatter) { + if (mode == null) { + mode = "mean"; + } + if (ddof == null) { + ddof = 1; + } + if (formatter == null) { + formatter = usFmt; + } + return function(arg) { + var attr; + attr = arg[0]; + return function(data, rowKey, colKey) { + return { + n: 0.0, + m: 0.0, + s: 0.0, + push: function(record) { + var m_new, x; + x = parseFloat(record[attr]); + if (isNaN(x)) { + return; + } + this.n += 1.0; + if (this.n === 1.0) { + return this.m = x; + } else { + m_new = this.m + (x - this.m) / this.n; + this.s = this.s + (x - this.m) * (x - m_new); + return this.m = m_new; + } + }, + value: function() { + if (mode === "mean") { + if (this.n === 0) { + return 0 / 0; + } else { + return this.m; + } + } + if (this.n <= ddof) { + return 0; + } + switch (mode) { + case "var": + return this.s / (this.n - ddof); + case "stdev": + return Math.sqrt(this.s / (this.n - ddof)); + } + }, + format: formatter, + numInputs: attr != null ? 0 : 1 + }; + }; + }; + }, + sumOverSum: function(formatter) { + if (formatter == null) { + formatter = usFmt; + } + return function(arg) { + var denom, num; + num = arg[0], denom = arg[1]; + return function(data, rowKey, colKey) { + return { + sumNum: 0, + sumDenom: 0, + push: function(record) { + if (!isNaN(parseFloat(record[num]))) { + this.sumNum += parseFloat(record[num]); + } + if (!isNaN(parseFloat(record[denom]))) { + return this.sumDenom += parseFloat(record[denom]); + } + }, + value: function() { + return this.sumNum / this.sumDenom; + }, + format: formatter, + numInputs: (num != null) && (denom != null) ? 0 : 2 + }; + }; + }; + }, + sumOverSumBound80: function(upper, formatter) { + if (upper == null) { + upper = true; + } + if (formatter == null) { + formatter = usFmt; + } + return function(arg) { + var denom, num; + num = arg[0], denom = arg[1]; + return function(data, rowKey, colKey) { + return { + sumNum: 0, + sumDenom: 0, + push: function(record) { + if (!isNaN(parseFloat(record[num]))) { + this.sumNum += parseFloat(record[num]); + } + if (!isNaN(parseFloat(record[denom]))) { + return this.sumDenom += parseFloat(record[denom]); + } + }, + value: function() { + var sign; + sign = upper ? 1 : -1; + return (0.821187207574908 / this.sumDenom + this.sumNum / this.sumDenom + 1.2815515655446004 * sign * Math.sqrt(0.410593603787454 / (this.sumDenom * this.sumDenom) + (this.sumNum * (1 - this.sumNum / this.sumDenom)) / (this.sumDenom * this.sumDenom))) / (1 + 1.642374415149816 / this.sumDenom); + }, + format: formatter, + numInputs: (num != null) && (denom != null) ? 0 : 2 + }; + }; + }; + }, + fractionOf: function(wrapped, type, formatter) { + if (type == null) { + type = "total"; + } + if (formatter == null) { + formatter = usFmtPct; + } + return function() { + var x; + x = 1 <= arguments.length ? slice.call(arguments, 0) : []; + return function(data, rowKey, colKey) { + return { + selector: { + total: [[], []], + row: [rowKey, []], + col: [[], colKey] + }[type], + inner: wrapped.apply(null, x)(data, rowKey, colKey), + push: function(record) { + return this.inner.push(record); + }, + format: formatter, + value: function() { + return this.inner.value() / data.getAggregator.apply(data, this.selector).inner.value(); + }, + numInputs: wrapped.apply(null, x)().numInputs + }; + }; + }; + } + }; + aggregatorTemplates.countUnique = function(f) { + return aggregatorTemplates.uniques((function(x) { + return x.length; + }), f); + }; + aggregatorTemplates.listUnique = function(s) { + return aggregatorTemplates.uniques((function(x) { + return x.sort(naturalSort).join(s); + }), (function(x) { + return x; + })); + }; + aggregatorTemplates.max = function(f) { + return aggregatorTemplates.extremes('max', f); + }; + aggregatorTemplates.min = function(f) { + return aggregatorTemplates.extremes('min', f); + }; + aggregatorTemplates.first = function(f) { + return aggregatorTemplates.extremes('first', f); + }; + aggregatorTemplates.last = function(f) { + return aggregatorTemplates.extremes('last', f); + }; + aggregatorTemplates.median = function(f) { + return aggregatorTemplates.quantile(0.5, f); + }; + aggregatorTemplates.average = function(f) { + return aggregatorTemplates.runningStat("mean", 1, f); + }; + aggregatorTemplates["var"] = function(ddof, f) { + return aggregatorTemplates.runningStat("var", ddof, f); + }; + aggregatorTemplates.stdev = function(ddof, f) { + return aggregatorTemplates.runningStat("stdev", ddof, f); + }; + aggregators = (function(tpl) { + return { + "Count": tpl.count(usFmtInt), + "Count Unique Values": tpl.countUnique(usFmtInt), + "List Unique Values": tpl.listUnique(", "), + "Sum": tpl.sum(usFmt), + "Integer Sum": tpl.sum(usFmtInt), + "Average": tpl.average(usFmt), + "Median": tpl.median(usFmt), + "Sample Variance": tpl["var"](1, usFmt), + "Sample Standard Deviation": tpl.stdev(1, usFmt), + "Minimum": tpl.min(usFmt), + "Maximum": tpl.max(usFmt), + "First": tpl.first(usFmt), + "Last": tpl.last(usFmt), + "Sum over Sum": tpl.sumOverSum(usFmt), + "80% Upper Bound": tpl.sumOverSumBound80(true, usFmt), + "80% Lower Bound": tpl.sumOverSumBound80(false, usFmt), + "Sum as Fraction of Total": tpl.fractionOf(tpl.sum(), "total", usFmtPct), + "Sum as Fraction of Rows": tpl.fractionOf(tpl.sum(), "row", usFmtPct), + "Sum as Fraction of Columns": tpl.fractionOf(tpl.sum(), "col", usFmtPct), + "Count as Fraction of Total": tpl.fractionOf(tpl.count(), "total", usFmtPct), + "Count as Fraction of Rows": tpl.fractionOf(tpl.count(), "row", usFmtPct), + "Count as Fraction of Columns": tpl.fractionOf(tpl.count(), "col", usFmtPct) + }; + })(aggregatorTemplates); + renderers = { + "Table": function(data, opts) { + return pivotTableRenderer(data, opts); + }, + "Table Barchart": function(data, opts) { + return $(pivotTableRenderer(data, opts)).barchart(); + }, + "Heatmap": function(data, opts) { + return $(pivotTableRenderer(data, opts)).heatmap("heatmap", opts); + }, + "Row Heatmap": function(data, opts) { + return $(pivotTableRenderer(data, opts)).heatmap("rowheatmap", opts); + }, + "Col Heatmap": function(data, opts) { + return $(pivotTableRenderer(data, opts)).heatmap("colheatmap", opts); + } + }; + locales = { + en: { + aggregators: aggregators, + renderers: renderers, + localeStrings: { + renderError: "An error occurred rendering the PivotTable results.", + computeError: "An error occurred computing the PivotTable results.", + uiRenderError: "An error occurred rendering the PivotTable UI.", + selectAll: "Select All", + selectNone: "Select None", + tooMany: "(too many to list)", + filterResults: "Filter values", + apply: "Apply", + cancel: "Cancel", + totals: "Totals", + vs: "vs", + by: "by" + } + } + }; + mthNamesEn = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"]; + dayNamesEn = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"]; + zeroPad = function(number) { + return ("0" + number).substr(-2, 2); + }; + derivers = { + bin: function(col, binWidth) { + return function(record) { + return record[col] - record[col] % binWidth; + }; + }, + dateFormat: function(col, formatString, utcOutput, mthNames, dayNames) { + var utc; + if (utcOutput == null) { + utcOutput = false; + } + if (mthNames == null) { + mthNames = mthNamesEn; + } + if (dayNames == null) { + dayNames = dayNamesEn; + } + utc = utcOutput ? "UTC" : ""; + return function(record) { + var date; + date = new Date(Date.parse(record[col])); + if (isNaN(date)) { + return ""; + } + return formatString.replace(/%(.)/g, function(m, p) { + switch (p) { + case "y": + return date["get" + utc + "FullYear"](); + case "m": + return zeroPad(date["get" + utc + "Month"]() + 1); + case "n": + return mthNames[date["get" + utc + "Month"]()]; + case "d": + return zeroPad(date["get" + utc + "Date"]()); + case "w": + return dayNames[date["get" + utc + "Day"]()]; + case "x": + return date["get" + utc + "Day"](); + case "H": + return zeroPad(date["get" + utc + "Hours"]()); + case "M": + return zeroPad(date["get" + utc + "Minutes"]()); + case "S": + return zeroPad(date["get" + utc + "Seconds"]()); + default: + return "%" + p; + } + }); + }; + } + }; + rx = /(\d+)|(\D+)/g; + rd = /\d/; + rz = /^0/; + naturalSort = (function(_this) { + return function(as, bs) { + var a, a1, b, b1, nas, nbs; + if ((bs != null) && (as == null)) { + return -1; + } + if ((as != null) && (bs == null)) { + return 1; + } + if (typeof as === "number" && isNaN(as)) { + return -1; + } + if (typeof bs === "number" && isNaN(bs)) { + return 1; + } + nas = +as; + nbs = +bs; + if (nas < nbs) { + return -1; + } + if (nas > nbs) { + return 1; + } + if (typeof as === "number" && typeof bs !== "number") { + return -1; + } + if (typeof bs === "number" && typeof as !== "number") { + return 1; + } + if (typeof as === "number" && typeof bs === "number") { + return 0; + } + if (isNaN(nbs) && !isNaN(nas)) { + return -1; + } + if (isNaN(nas) && !isNaN(nbs)) { + return 1; + } + a = String(as); + b = String(bs); + if (a === b) { + return 0; + } + if (!(rd.test(a) && rd.test(b))) { + return (a > b ? 1 : -1); + } + a = a.match(rx); + b = b.match(rx); + while (a.length && b.length) { + a1 = a.shift(); + b1 = b.shift(); + if (a1 !== b1) { + if (rd.test(a1) && rd.test(b1)) { + return a1.replace(rz, ".0") - b1.replace(rz, ".0"); + } else { + return (a1 > b1 ? 1 : -1); + } + } + } + return a.length - b.length; + }; + })(this); + sortAs = function(order) { + var i, l_mapping, mapping, x; + mapping = {}; + l_mapping = {}; + for (i in order) { + x = order[i]; + mapping[x] = i; + if (typeof x === "string") { + l_mapping[x.toLowerCase()] = i; + } + } + return function(a, b) { + if ((mapping[a] != null) && (mapping[b] != null)) { + return mapping[a] - mapping[b]; + } else if (mapping[a] != null) { + return -1; + } else if (mapping[b] != null) { + return 1; + } else if ((l_mapping[a] != null) && (l_mapping[b] != null)) { + return l_mapping[a] - l_mapping[b]; + } else if (l_mapping[a] != null) { + return -1; + } else if (l_mapping[b] != null) { + return 1; + } else { + return naturalSort(a, b); + } + }; + }; + getSort = function(sorters, attr) { + var sort; + if (sorters != null) { + if ($.isFunction(sorters)) { + sort = sorters(attr); + if ($.isFunction(sort)) { + return sort; + } + } else if (sorters[attr] != null) { + return sorters[attr]; + } + } + return naturalSort; + }; + + /* + Data Model class + */ + PivotData = (function() { + function PivotData(input, opts) { + var ref, ref1, ref2, ref3, ref4, ref5, ref6, ref7, ref8, ref9; + if (opts == null) { + opts = {}; + } + this.getAggregator = bind(this.getAggregator, this); + this.getRowKeys = bind(this.getRowKeys, this); + this.getColKeys = bind(this.getColKeys, this); + this.sortKeys = bind(this.sortKeys, this); + this.arrSort = bind(this.arrSort, this); + this.input = input; + this.aggregator = (ref = opts.aggregator) != null ? ref : aggregatorTemplates.count()(); + this.aggregatorName = (ref1 = opts.aggregatorName) != null ? ref1 : "Count"; + this.colAttrs = (ref2 = opts.cols) != null ? ref2 : []; + this.rowAttrs = (ref3 = opts.rows) != null ? ref3 : []; + this.valAttrs = (ref4 = opts.vals) != null ? ref4 : []; + this.sorters = (ref5 = opts.sorters) != null ? ref5 : {}; + this.rowOrder = (ref6 = opts.rowOrder) != null ? ref6 : "key_a_to_z"; + this.colOrder = (ref7 = opts.colOrder) != null ? ref7 : "key_a_to_z"; + this.derivedAttributes = (ref8 = opts.derivedAttributes) != null ? ref8 : {}; + this.filter = (ref9 = opts.filter) != null ? ref9 : (function() { + return true; + }); + this.tree = {}; + this.rowKeys = []; + this.colKeys = []; + this.rowTotals = {}; + this.colTotals = {}; + this.allTotal = this.aggregator(this, [], []); + this.sorted = false; + PivotData.forEachRecord(this.input, this.derivedAttributes, (function(_this) { + return function(record) { + if (_this.filter(record)) { + return _this.processRecord(record); + } + }; + })(this)); + } + + PivotData.forEachRecord = function(input, derivedAttributes, f) { + var addRecord, compactRecord, i, j, k, l, len1, record, ref, results, results1, tblCols; + if ($.isEmptyObject(derivedAttributes)) { + addRecord = f; + } else { + addRecord = function(record) { + var k, ref, v; + for (k in derivedAttributes) { + v = derivedAttributes[k]; + record[k] = (ref = v(record)) != null ? ref : record[k]; + } + return f(record); + }; + } + if ($.isFunction(input)) { + return input(addRecord); + } else if ($.isArray(input)) { + if ($.isArray(input[0])) { + results = []; + for (i in input) { + if (!hasProp.call(input, i)) continue; + compactRecord = input[i]; + if (!(i > 0)) { + continue; + } + record = {}; + ref = input[0]; + for (j in ref) { + if (!hasProp.call(ref, j)) continue; + k = ref[j]; + record[k] = compactRecord[j]; + } + results.push(addRecord(record)); + } + return results; + } else { + results1 = []; + for (l = 0, len1 = input.length; l < len1; l++) { + record = input[l]; + results1.push(addRecord(record)); + } + return results1; + } + } else if (input instanceof $) { + tblCols = []; + $("thead > tr > th", input).each(function(i) { + return tblCols.push($(this).text()); + }); + return $("tbody > tr", input).each(function(i) { + record = {}; + $("td", this).each(function(j) { + return record[tblCols[j]] = $(this).text(); + }); + return addRecord(record); + }); + } else { + throw new Error("unknown input format"); + } + }; + + PivotData.prototype.forEachMatchingRecord = function(criteria, callback) { + return PivotData.forEachRecord(this.input, this.derivedAttributes, (function(_this) { + return function(record) { + var k, ref, v; + if (!_this.filter(record)) { + return; + } + for (k in criteria) { + v = criteria[k]; + if (v !== ((ref = record[k]) != null ? ref : "null")) { + return; + } + } + return callback(record); + }; + })(this)); + }; + + PivotData.prototype.arrSort = function(attrs) { + var a, sortersArr; + sortersArr = (function() { + var l, len1, results; + results = []; + for (l = 0, len1 = attrs.length; l < len1; l++) { + a = attrs[l]; + results.push(getSort(this.sorters, a)); + } + return results; + }).call(this); + return function(a, b) { + var comparison, i, sorter; + for (i in sortersArr) { + if (!hasProp.call(sortersArr, i)) continue; + sorter = sortersArr[i]; + comparison = sorter(a[i], b[i]); + if (comparison !== 0) { + return comparison; + } + } + return 0; + }; + }; + + PivotData.prototype.sortKeys = function() { + var v; + if (!this.sorted) { + this.sorted = true; + v = (function(_this) { + return function(r, c) { + return _this.getAggregator(r, c).value(); + }; + })(this); + switch (this.rowOrder) { + case "value_a_to_z": + this.rowKeys.sort((function(_this) { + return function(a, b) { + return naturalSort(v(a, []), v(b, [])); + }; + })(this)); + break; + case "value_z_to_a": + this.rowKeys.sort((function(_this) { + return function(a, b) { + return -naturalSort(v(a, []), v(b, [])); + }; + })(this)); + break; + default: + this.rowKeys.sort(this.arrSort(this.rowAttrs)); + } + switch (this.colOrder) { + case "value_a_to_z": + return this.colKeys.sort((function(_this) { + return function(a, b) { + return naturalSort(v([], a), v([], b)); + }; + })(this)); + case "value_z_to_a": + return this.colKeys.sort((function(_this) { + return function(a, b) { + return -naturalSort(v([], a), v([], b)); + }; + })(this)); + default: + return this.colKeys.sort(this.arrSort(this.colAttrs)); + } + } + }; + + PivotData.prototype.getColKeys = function() { + this.sortKeys(); + return this.colKeys; + }; + + PivotData.prototype.getRowKeys = function() { + this.sortKeys(); + return this.rowKeys; + }; + + PivotData.prototype.processRecord = function(record) { + var colKey, flatColKey, flatRowKey, l, len1, len2, n, ref, ref1, ref2, ref3, rowKey, x; + colKey = []; + rowKey = []; + ref = this.colAttrs; + for (l = 0, len1 = ref.length; l < len1; l++) { + x = ref[l]; + colKey.push((ref1 = record[x]) != null ? ref1 : "null"); + } + ref2 = this.rowAttrs; + for (n = 0, len2 = ref2.length; n < len2; n++) { + x = ref2[n]; + rowKey.push((ref3 = record[x]) != null ? ref3 : "null"); + } + flatRowKey = rowKey.join(String.fromCharCode(0)); + flatColKey = colKey.join(String.fromCharCode(0)); + this.allTotal.push(record); + if (rowKey.length !== 0) { + if (!this.rowTotals[flatRowKey]) { + this.rowKeys.push(rowKey); + this.rowTotals[flatRowKey] = this.aggregator(this, rowKey, []); + } + this.rowTotals[flatRowKey].push(record); + } + if (colKey.length !== 0) { + if (!this.colTotals[flatColKey]) { + this.colKeys.push(colKey); + this.colTotals[flatColKey] = this.aggregator(this, [], colKey); + } + this.colTotals[flatColKey].push(record); + } + if (colKey.length !== 0 && rowKey.length !== 0) { + if (!this.tree[flatRowKey]) { + this.tree[flatRowKey] = {}; + } + if (!this.tree[flatRowKey][flatColKey]) { + this.tree[flatRowKey][flatColKey] = this.aggregator(this, rowKey, colKey); + } + return this.tree[flatRowKey][flatColKey].push(record); + } + }; + + PivotData.prototype.getAggregator = function(rowKey, colKey) { + var agg, flatColKey, flatRowKey; + flatRowKey = rowKey.join(String.fromCharCode(0)); + flatColKey = colKey.join(String.fromCharCode(0)); + if (rowKey.length === 0 && colKey.length === 0) { + agg = this.allTotal; + } else if (rowKey.length === 0) { + agg = this.colTotals[flatColKey]; + } else if (colKey.length === 0) { + agg = this.rowTotals[flatRowKey]; + } else { + agg = this.tree[flatRowKey][flatColKey]; + } + return agg != null ? agg : { + value: (function() { + return null; + }), + format: function() { + return ""; + } + }; + }; + + return PivotData; + + })(); + $.pivotUtilities = { + aggregatorTemplates: aggregatorTemplates, + aggregators: aggregators, + renderers: renderers, + derivers: derivers, + locales: locales, + naturalSort: naturalSort, + numberFormat: numberFormat, + sortAs: sortAs, + PivotData: PivotData + }; + + /* + Default Renderer for hierarchical table layout + */ + pivotTableRenderer = function(pivotData, opts) { + var aggregator, c, colAttrs, colKey, colKeys, defaults, getClickHandler, i, j, r, result, rowAttrs, rowKey, rowKeys, spanSize, tbody, td, th, thead, totalAggregator, tr, txt, val, x; + defaults = { + table: { + clickCallback: null, + rowTotals: true, + colTotals: true + }, + localeStrings: { + totals: "Totals" + } + }; + opts = $.extend(true, {}, defaults, opts); + colAttrs = pivotData.colAttrs; + rowAttrs = pivotData.rowAttrs; + rowKeys = pivotData.getRowKeys(); + colKeys = pivotData.getColKeys(); + if (opts.table.clickCallback) { + getClickHandler = function(value, rowValues, colValues) { + var attr, filters, i; + filters = {}; + for (i in colAttrs) { + if (!hasProp.call(colAttrs, i)) continue; + attr = colAttrs[i]; + if (colValues[i] != null) { + filters[attr] = colValues[i]; + } + } + for (i in rowAttrs) { + if (!hasProp.call(rowAttrs, i)) continue; + attr = rowAttrs[i]; + if (rowValues[i] != null) { + filters[attr] = rowValues[i]; + } + } + return function(e) { + return opts.table.clickCallback(e, value, filters, pivotData); + }; + }; + } + result = document.createElement("table"); + result.className = "pvtTable"; + spanSize = function(arr, i, j) { + var l, len, n, noDraw, ref, ref1, stop, x; + if (i !== 0) { + noDraw = true; + for (x = l = 0, ref = j; 0 <= ref ? l <= ref : l >= ref; x = 0 <= ref ? ++l : --l) { + if (arr[i - 1][x] !== arr[i][x]) { + noDraw = false; + } + } + if (noDraw) { + return -1; + } + } + len = 0; + while (i + len < arr.length) { + stop = false; + for (x = n = 0, ref1 = j; 0 <= ref1 ? n <= ref1 : n >= ref1; x = 0 <= ref1 ? ++n : --n) { + if (arr[i][x] !== arr[i + len][x]) { + stop = true; + } + } + if (stop) { + break; + } + len++; + } + return len; + }; + thead = document.createElement("thead"); + for (j in colAttrs) { + if (!hasProp.call(colAttrs, j)) continue; + c = colAttrs[j]; + tr = document.createElement("tr"); + if (parseInt(j) === 0 && rowAttrs.length !== 0) { + th = document.createElement("th"); + th.setAttribute("colspan", rowAttrs.length); + th.setAttribute("rowspan", colAttrs.length); + tr.appendChild(th); + } + th = document.createElement("th"); + th.className = "pvtAxisLabel"; + th.textContent = c; + tr.appendChild(th); + for (i in colKeys) { + if (!hasProp.call(colKeys, i)) continue; + colKey = colKeys[i]; + x = spanSize(colKeys, parseInt(i), parseInt(j)); + if (x !== -1) { + th = document.createElement("th"); + th.className = "pvtColLabel"; + th.textContent = colKey[j]; + th.setAttribute("colspan", x); + if (parseInt(j) === colAttrs.length - 1 && rowAttrs.length !== 0) { + th.setAttribute("rowspan", 2); + } + tr.appendChild(th); + } + } + if (parseInt(j) === 0 && opts.table.rowTotals) { + th = document.createElement("th"); + th.className = "pvtTotalLabel pvtRowTotalLabel"; + th.innerHTML = opts.localeStrings.totals; + th.setAttribute("rowspan", colAttrs.length + (rowAttrs.length === 0 ? 0 : 1)); + tr.appendChild(th); + } + thead.appendChild(tr); + } + if (rowAttrs.length !== 0) { + tr = document.createElement("tr"); + for (i in rowAttrs) { + if (!hasProp.call(rowAttrs, i)) continue; + r = rowAttrs[i]; + th = document.createElement("th"); + th.className = "pvtAxisLabel"; + th.textContent = r; + tr.appendChild(th); + } + th = document.createElement("th"); + if (colAttrs.length === 0) { + th.className = "pvtTotalLabel pvtRowTotalLabel"; + th.innerHTML = opts.localeStrings.totals; + } + tr.appendChild(th); + thead.appendChild(tr); + } + result.appendChild(thead); + tbody = document.createElement("tbody"); + for (i in rowKeys) { + if (!hasProp.call(rowKeys, i)) continue; + rowKey = rowKeys[i]; + tr = document.createElement("tr"); + for (j in rowKey) { + if (!hasProp.call(rowKey, j)) continue; + txt = rowKey[j]; + x = spanSize(rowKeys, parseInt(i), parseInt(j)); + if (x !== -1) { + th = document.createElement("th"); + th.className = "pvtRowLabel"; + th.textContent = txt; + th.setAttribute("rowspan", x); + if (parseInt(j) === rowAttrs.length - 1 && colAttrs.length !== 0) { + th.setAttribute("colspan", 2); + } + tr.appendChild(th); + } + } + for (j in colKeys) { + if (!hasProp.call(colKeys, j)) continue; + colKey = colKeys[j]; + aggregator = pivotData.getAggregator(rowKey, colKey); + val = aggregator.value(); + td = document.createElement("td"); + td.className = "pvtVal row" + i + " col" + j; + td.textContent = aggregator.format(val); + td.setAttribute("data-value", val); + if (getClickHandler != null) { + td.onclick = getClickHandler(val, rowKey, colKey); + } + tr.appendChild(td); + } + if (opts.table.rowTotals || colAttrs.length === 0) { + totalAggregator = pivotData.getAggregator(rowKey, []); + val = totalAggregator.value(); + td = document.createElement("td"); + td.className = "pvtTotal rowTotal"; + td.textContent = totalAggregator.format(val); + td.setAttribute("data-value", val); + if (getClickHandler != null) { + td.onclick = getClickHandler(val, rowKey, []); + } + td.setAttribute("data-for", "row" + i); + tr.appendChild(td); + } + tbody.appendChild(tr); + } + if (opts.table.colTotals || rowAttrs.length === 0) { + tr = document.createElement("tr"); + if (opts.table.colTotals || rowAttrs.length === 0) { + th = document.createElement("th"); + th.className = "pvtTotalLabel pvtColTotalLabel"; + th.innerHTML = opts.localeStrings.totals; + th.setAttribute("colspan", rowAttrs.length + (colAttrs.length === 0 ? 0 : 1)); + tr.appendChild(th); + } + for (j in colKeys) { + if (!hasProp.call(colKeys, j)) continue; + colKey = colKeys[j]; + totalAggregator = pivotData.getAggregator([], colKey); + val = totalAggregator.value(); + td = document.createElement("td"); + td.className = "pvtTotal colTotal"; + td.textContent = totalAggregator.format(val); + td.setAttribute("data-value", val); + if (getClickHandler != null) { + td.onclick = getClickHandler(val, [], colKey); + } + td.setAttribute("data-for", "col" + j); + tr.appendChild(td); + } + if (opts.table.rowTotals || colAttrs.length === 0) { + totalAggregator = pivotData.getAggregator([], []); + val = totalAggregator.value(); + td = document.createElement("td"); + td.className = "pvtGrandTotal"; + td.textContent = totalAggregator.format(val); + td.setAttribute("data-value", val); + if (getClickHandler != null) { + td.onclick = getClickHandler(val, [], []); + } + tr.appendChild(td); + } + tbody.appendChild(tr); + } + result.appendChild(tbody); + result.setAttribute("data-numrows", rowKeys.length); + result.setAttribute("data-numcols", colKeys.length); + return result; + }; + + /* + Pivot Table core: create PivotData object and call Renderer on it + */ + $.fn.pivot = function(input, inputOpts, locale) { + var defaults, e, localeDefaults, localeStrings, opts, pivotData, result, x; + if (locale == null) { + locale = "en"; + } + if (locales[locale] == null) { + locale = "en"; + } + defaults = { + cols: [], + rows: [], + vals: [], + rowOrder: "key_a_to_z", + colOrder: "key_a_to_z", + dataClass: PivotData, + filter: function() { + return true; + }, + aggregator: aggregatorTemplates.count()(), + aggregatorName: "Count", + sorters: {}, + derivedAttributes: {}, + renderer: pivotTableRenderer + }; + localeStrings = $.extend(true, {}, locales.en.localeStrings, locales[locale].localeStrings); + localeDefaults = { + rendererOptions: { + localeStrings: localeStrings + }, + localeStrings: localeStrings + }; + opts = $.extend(true, {}, localeDefaults, $.extend({}, defaults, inputOpts)); + result = null; + try { + pivotData = new opts.dataClass(input, opts); + try { + result = opts.renderer(pivotData, opts.rendererOptions); + } catch (error) { + e = error; + if (typeof console !== "undefined" && console !== null) { + console.error(e.stack); + } + result = $("").html(opts.localeStrings.renderError); + } + } catch (error) { + e = error; + if (typeof console !== "undefined" && console !== null) { + console.error(e.stack); + } + result = $("").html(opts.localeStrings.computeError); + } + x = this[0]; + while (x.hasChildNodes()) { + x.removeChild(x.lastChild); + } + return this.append(result); + }; + + /* + Pivot Table UI: calls Pivot Table core above with options set by user + */ + $.fn.pivotUI = function(input, inputOpts, overwrite, locale) { + var a, aggregator, attr, attrLength, attrValues, c, colOrderArrow, defaults, e, existingOpts, fn1, i, initialRender, l, len1, len2, len3, localeDefaults, localeStrings, materializedInput, n, o, opts, ordering, pivotTable, recordsProcessed, ref, ref1, ref2, ref3, refresh, refreshDelayed, renderer, rendererControl, rowOrderArrow, shownAttributes, shownInAggregators, shownInDragDrop, tr1, tr2, uiTable, unused, unusedAttrsVerticalAutoCutoff, unusedAttrsVerticalAutoOverride, x; + if (overwrite == null) { + overwrite = false; + } + if (locale == null) { + locale = "en"; + } + if (locales[locale] == null) { + locale = "en"; + } + defaults = { + derivedAttributes: {}, + aggregators: locales[locale].aggregators, + renderers: locales[locale].renderers, + hiddenAttributes: [], + hiddenFromAggregators: [], + hiddenFromDragDrop: [], + menuLimit: 500, + cols: [], + rows: [], + vals: [], + rowOrder: "key_a_to_z", + colOrder: "key_a_to_z", + dataClass: PivotData, + exclusions: {}, + inclusions: {}, + unusedAttrsVertical: 85, + autoSortUnusedAttrs: false, + onRefresh: null, + showUI: true, + filter: function() { + return true; + }, + sorters: {} + }; + localeStrings = $.extend(true, {}, locales.en.localeStrings, locales[locale].localeStrings); + localeDefaults = { + rendererOptions: { + localeStrings: localeStrings + }, + localeStrings: localeStrings + }; + existingOpts = this.data("pivotUIOptions"); + if ((existingOpts == null) || overwrite) { + opts = $.extend(true, {}, localeDefaults, $.extend({}, defaults, inputOpts)); + } else { + opts = existingOpts; + } + try { + attrValues = {}; + materializedInput = []; + recordsProcessed = 0; + PivotData.forEachRecord(input, opts.derivedAttributes, function(record) { + var attr, base, ref, value; + if (!opts.filter(record)) { + return; + } + materializedInput.push(record); + for (attr in record) { + if (!hasProp.call(record, attr)) continue; + if (attrValues[attr] == null) { + attrValues[attr] = {}; + if (recordsProcessed > 0) { + attrValues[attr]["null"] = recordsProcessed; + } + } + } + for (attr in attrValues) { + value = (ref = record[attr]) != null ? ref : "null"; + if ((base = attrValues[attr])[value] == null) { + base[value] = 0; + } + attrValues[attr][value]++; + } + return recordsProcessed++; + }); + uiTable = $("", { + "class": "pvtUi" + }).attr("cellpadding", 5); + rendererControl = $("").appendTo(uiTable); + aggregator = $("").appendTo(uiTable); + tr2.append($("").append(rendererControl).append(unused)); + } + this.html(uiTable); + ref2 = opts.cols; + for (n = 0, len2 = ref2.length; n < len2; n++) { + x = ref2[n]; + this.find(".pvtCols").append(this.find(".axis_" + ($.inArray(x, shownInDragDrop)))); + } + ref3 = opts.rows; + for (o = 0, len3 = ref3.length; o < len3; o++) { + x = ref3[o]; + this.find(".pvtRows").append(this.find(".axis_" + ($.inArray(x, shownInDragDrop)))); + } + if (opts.aggregatorName != null) { + this.find(".pvtAggregator").val(opts.aggregatorName); + } + if (opts.rendererName != null) { + this.find(".pvtRenderer").val(opts.rendererName); + } + if (!opts.showUI) { + this.find(".pvtUiCell").hide(); + } + initialRender = true; + refreshDelayed = (function(_this) { + return function() { + var exclusions, inclusions, len4, newDropdown, numInputsToProcess, pivotUIOptions, pvtVals, ref4, ref5, subopts, t, u, unusedAttrsContainer, vals; + subopts = { + derivedAttributes: opts.derivedAttributes, + localeStrings: opts.localeStrings, + rendererOptions: opts.rendererOptions, + sorters: opts.sorters, + cols: [], + rows: [], + dataClass: opts.dataClass + }; + numInputsToProcess = (ref4 = opts.aggregators[aggregator.val()]([])().numInputs) != null ? ref4 : 0; + vals = []; + _this.find(".pvtRows li span.pvtAttr").each(function() { + return subopts.rows.push($(this).data("attrName")); + }); + _this.find(".pvtCols li span.pvtAttr").each(function() { + return subopts.cols.push($(this).data("attrName")); + }); + _this.find(".pvtVals select.pvtAttrDropdown").each(function() { + if (numInputsToProcess === 0) { + return $(this).remove(); + } else { + numInputsToProcess--; + if ($(this).val() !== "") { + return vals.push($(this).val()); + } + } + }); + if (numInputsToProcess !== 0) { + pvtVals = _this.find(".pvtVals"); + for (x = t = 0, ref5 = numInputsToProcess; 0 <= ref5 ? t < ref5 : t > ref5; x = 0 <= ref5 ? ++t : --t) { + newDropdown = $("
").addClass("pvtUiCell"); + renderer = $("").addClass('pvtAxisContainer pvtUnused pvtUiCell'); + shownAttributes = (function() { + var results; + results = []; + for (a in attrValues) { + if (indexOf.call(opts.hiddenAttributes, a) < 0) { + results.push(a); + } + } + return results; + })(); + shownInAggregators = (function() { + var l, len1, results; + results = []; + for (l = 0, len1 = shownAttributes.length; l < len1; l++) { + c = shownAttributes[l]; + if (indexOf.call(opts.hiddenFromAggregators, c) < 0) { + results.push(c); + } + } + return results; + })(); + shownInDragDrop = (function() { + var l, len1, results; + results = []; + for (l = 0, len1 = shownAttributes.length; l < len1; l++) { + c = shownAttributes[l]; + if (indexOf.call(opts.hiddenFromDragDrop, c) < 0) { + results.push(c); + } + } + return results; + })(); + unusedAttrsVerticalAutoOverride = false; + if (opts.unusedAttrsVertical === "auto") { + unusedAttrsVerticalAutoCutoff = 120; + } else { + unusedAttrsVerticalAutoCutoff = parseInt(opts.unusedAttrsVertical); + } + if (!isNaN(unusedAttrsVerticalAutoCutoff)) { + attrLength = 0; + for (l = 0, len1 = shownInDragDrop.length; l < len1; l++) { + a = shownInDragDrop[l]; + attrLength += a.length; + } + unusedAttrsVerticalAutoOverride = attrLength > unusedAttrsVerticalAutoCutoff; + } + if (opts.unusedAttrsVertical === true || unusedAttrsVerticalAutoOverride) { + unused.addClass('pvtVertList'); + } else { + unused.addClass('pvtHorizList'); + } + fn1 = function(attr) { + var attrElem, checkContainer, closeFilterBox, controls, filterItem, filterItemExcluded, finalButtons, hasExcludedItem, len2, n, placeholder, ref1, sorter, triangleLink, v, value, valueCount, valueList, values; + values = (function() { + var results; + results = []; + for (v in attrValues[attr]) { + results.push(v); + } + return results; + })(); + hasExcludedItem = false; + valueList = $("
").addClass('pvtFilterBox').hide(); + valueList.append($("

").append($("").text(attr), $("").addClass("count").text("(" + values.length + ")"))); + if (values.length > opts.menuLimit) { + valueList.append($("

").html(opts.localeStrings.tooMany)); + } else { + if (values.length > 5) { + controls = $("

").appendTo(valueList); + sorter = getSort(opts.sorters, attr); + placeholder = opts.localeStrings.filterResults; + $("", { + type: "text" + }).appendTo(controls).attr({ + placeholder: placeholder, + "class": "pvtSearch" + }).bind("keyup", function() { + var accept, accept_gen, filter; + filter = $(this).val().toLowerCase().trim(); + accept_gen = function(prefix, accepted) { + return function(v) { + var real_filter, ref1; + real_filter = filter.substring(prefix.length).trim(); + if (real_filter.length === 0) { + return true; + } + return ref1 = Math.sign(sorter(v.toLowerCase(), real_filter)), indexOf.call(accepted, ref1) >= 0; + }; + }; + accept = filter.indexOf(">=") === 0 ? accept_gen(">=", [1, 0]) : filter.indexOf("<=") === 0 ? accept_gen("<=", [-1, 0]) : filter.indexOf(">") === 0 ? accept_gen(">", [1]) : filter.indexOf("<") === 0 ? accept_gen("<", [-1]) : filter.indexOf("~") === 0 ? function(v) { + if (filter.substring(1).trim().length === 0) { + return true; + } + return v.toLowerCase().match(filter.substring(1)); + } : function(v) { + return v.toLowerCase().indexOf(filter) !== -1; + }; + return valueList.find('.pvtCheckContainer p label span.value').each(function() { + if (accept($(this).text())) { + return $(this).parent().parent().show(); + } else { + return $(this).parent().parent().hide(); + } + }); + }); + controls.append($("
")); + $("

").addClass('pvtVals pvtUiCell').appendTo(tr1).append(aggregator).append(rowOrderArrow).append(colOrderArrow).append($("
")); + $("
").addClass('pvtAxisContainer pvtHorizList pvtCols pvtUiCell').appendTo(tr1); + tr2 = $("
").addClass('pvtAxisContainer pvtRows pvtUiCell').attr("valign", "top")); + pivotTable = $("").attr("valign", "top").addClass('pvtRendererArea').appendTo(tr2); + if (opts.unusedAttrsVertical === true || unusedAttrsVerticalAutoOverride) { + uiTable.find('tr:nth-child(1)').prepend(rendererControl); + uiTable.find('tr:nth-child(2)').prepend(unused); + } else { + uiTable.prepend($("