pivot.js 47 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363
  1. (function() {
  2. var callWithJQuery,
  3. __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; },
  4. __slice = [].slice,
  5. __bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; },
  6. __hasProp = {}.hasOwnProperty;
  7. callWithJQuery = function(pivotModule) {
  8. if (typeof exports === "object" && typeof module === "object") {
  9. return pivotModule(require("jquery"));
  10. } else if (typeof define === "function" && define.amd) {
  11. return define(["jquery"], pivotModule);
  12. } else {
  13. return pivotModule(jQuery);
  14. }
  15. };
  16. callWithJQuery(function($) {
  17. /*
  18. Utilities
  19. */
  20. var PivotData, addSeparators, aggregatorTemplates, aggregators, dayNamesEn, derivers, locales, mthNamesEn, naturalSort, numberFormat, pivotTableRenderer, renderers, usFmt, usFmtInt, usFmtPct, zeroPad;
  21. addSeparators = function(nStr, thousandsSep, decimalSep) {
  22. var rgx, x, x1, x2;
  23. nStr += '';
  24. x = nStr.split('.');
  25. x1 = x[0];
  26. x2 = x.length > 1 ? decimalSep + x[1] : '';
  27. rgx = /(\d+)(\d{3})/;
  28. while (rgx.test(x1)) {
  29. x1 = x1.replace(rgx, '$1' + thousandsSep + '$2');
  30. }
  31. return x1 + x2;
  32. };
  33. numberFormat = function(opts) {
  34. var defaults;
  35. defaults = {
  36. digitsAfterDecimal: 2,
  37. scaler: 1,
  38. thousandsSep: ",",
  39. decimalSep: ".",
  40. prefix: "",
  41. suffix: "",
  42. showZero: false
  43. };
  44. opts = $.extend(defaults, opts);
  45. return function(x) {
  46. var result;
  47. if (isNaN(x) || !isFinite(x)) {
  48. return "";
  49. }
  50. if (x === 0 && !opts.showZero) {
  51. return "";
  52. }
  53. result = addSeparators((opts.scaler * x).toFixed(opts.digitsAfterDecimal), opts.thousandsSep, opts.decimalSep);
  54. return "" + opts.prefix + result + opts.suffix;
  55. };
  56. };
  57. usFmt = numberFormat();
  58. usFmtInt = numberFormat({
  59. digitsAfterDecimal: 0
  60. });
  61. usFmtPct = numberFormat({
  62. digitsAfterDecimal: 1,
  63. scaler: 100,
  64. suffix: "%"
  65. });
  66. aggregatorTemplates = {
  67. count: function(formatter) {
  68. if (formatter == null) {
  69. formatter = usFmtInt;
  70. }
  71. return function() {
  72. return function(data, rowKey, colKey) {
  73. return {
  74. count: 0,
  75. push: function() {
  76. return this.count++;
  77. },
  78. value: function() {
  79. return this.count;
  80. },
  81. format: formatter
  82. };
  83. };
  84. };
  85. },
  86. countUnique: function(formatter) {
  87. if (formatter == null) {
  88. formatter = usFmtInt;
  89. }
  90. return function(_arg) {
  91. var attr;
  92. attr = _arg[0];
  93. return function(data, rowKey, colKey) {
  94. return {
  95. uniq: [],
  96. push: function(record) {
  97. var _ref;
  98. if (_ref = record[attr], __indexOf.call(this.uniq, _ref) < 0) {
  99. return this.uniq.push(record[attr]);
  100. }
  101. },
  102. value: function() {
  103. return this.uniq.length;
  104. },
  105. format: formatter,
  106. numInputs: attr != null ? 0 : 1
  107. };
  108. };
  109. };
  110. },
  111. listUnique: function(sep) {
  112. return function(_arg) {
  113. var attr;
  114. attr = _arg[0];
  115. return function(data, rowKey, colKey) {
  116. return {
  117. uniq: [],
  118. push: function(record) {
  119. var _ref;
  120. if (_ref = record[attr], __indexOf.call(this.uniq, _ref) < 0) {
  121. return this.uniq.push(record[attr]);
  122. }
  123. },
  124. value: function() {
  125. return this.uniq.join(sep);
  126. },
  127. format: function(x) {
  128. return x;
  129. },
  130. numInputs: attr != null ? 0 : 1
  131. };
  132. };
  133. };
  134. },
  135. sum: function(formatter) {
  136. if (formatter == null) {
  137. formatter = usFmt;
  138. }
  139. return function(_arg) {
  140. var attr;
  141. attr = _arg[0];
  142. return function(data, rowKey, colKey) {
  143. return {
  144. sum: 0,
  145. push: function(record) {
  146. if (!isNaN(parseFloat(record[attr]))) {
  147. return this.sum += parseFloat(record[attr]);
  148. }
  149. },
  150. value: function() {
  151. return this.sum;
  152. },
  153. format: formatter,
  154. numInputs: attr != null ? 0 : 1
  155. };
  156. };
  157. };
  158. },
  159. average: function(formatter) {
  160. if (formatter == null) {
  161. formatter = usFmt;
  162. }
  163. return function(_arg) {
  164. var attr;
  165. attr = _arg[0];
  166. return function(data, rowKey, colKey) {
  167. return {
  168. sum: 0,
  169. len: 0,
  170. push: function(record) {
  171. if (!isNaN(parseFloat(record[attr]))) {
  172. this.sum += parseFloat(record[attr]);
  173. return this.len++;
  174. }
  175. },
  176. value: function() {
  177. return this.sum / this.len;
  178. },
  179. format: formatter,
  180. numInputs: attr != null ? 0 : 1
  181. };
  182. };
  183. };
  184. },
  185. sumOverSum: function(formatter) {
  186. if (formatter == null) {
  187. formatter = usFmt;
  188. }
  189. return function(_arg) {
  190. var denom, num;
  191. num = _arg[0], denom = _arg[1];
  192. return function(data, rowKey, colKey) {
  193. return {
  194. sumNum: 0,
  195. sumDenom: 0,
  196. push: function(record) {
  197. if (!isNaN(parseFloat(record[num]))) {
  198. this.sumNum += parseFloat(record[num]);
  199. }
  200. if (!isNaN(parseFloat(record[denom]))) {
  201. return this.sumDenom += parseFloat(record[denom]);
  202. }
  203. },
  204. value: function() {
  205. return this.sumNum / this.sumDenom;
  206. },
  207. format: formatter,
  208. numInputs: (num != null) && (denom != null) ? 0 : 2
  209. };
  210. };
  211. };
  212. },
  213. sumOverSumBound80: function(upper, formatter) {
  214. if (upper == null) {
  215. upper = true;
  216. }
  217. if (formatter == null) {
  218. formatter = usFmt;
  219. }
  220. return function(_arg) {
  221. var denom, num;
  222. num = _arg[0], denom = _arg[1];
  223. return function(data, rowKey, colKey) {
  224. return {
  225. sumNum: 0,
  226. sumDenom: 0,
  227. push: function(record) {
  228. if (!isNaN(parseFloat(record[num]))) {
  229. this.sumNum += parseFloat(record[num]);
  230. }
  231. if (!isNaN(parseFloat(record[denom]))) {
  232. return this.sumDenom += parseFloat(record[denom]);
  233. }
  234. },
  235. value: function() {
  236. var sign;
  237. sign = upper ? 1 : -1;
  238. 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);
  239. },
  240. format: formatter,
  241. numInputs: (num != null) && (denom != null) ? 0 : 2
  242. };
  243. };
  244. };
  245. },
  246. fractionOf: function(wrapped, type, formatter) {
  247. if (type == null) {
  248. type = "total";
  249. }
  250. if (formatter == null) {
  251. formatter = usFmtPct;
  252. }
  253. return function() {
  254. var x;
  255. x = 1 <= arguments.length ? __slice.call(arguments, 0) : [];
  256. return function(data, rowKey, colKey) {
  257. return {
  258. selector: {
  259. total: [[], []],
  260. row: [rowKey, []],
  261. col: [[], colKey]
  262. }[type],
  263. inner: wrapped.apply(null, x)(data, rowKey, colKey),
  264. push: function(record) {
  265. return this.inner.push(record);
  266. },
  267. format: formatter,
  268. value: function() {
  269. return this.inner.value() / data.getAggregator.apply(data, this.selector).inner.value();
  270. },
  271. numInputs: wrapped.apply(null, x)().numInputs
  272. };
  273. };
  274. };
  275. }
  276. };
  277. aggregators = (function(tpl) {
  278. return {
  279. "Count": tpl.count(usFmtInt),
  280. "Count Unique Values": tpl.countUnique(usFmtInt),
  281. "List Unique Values": tpl.listUnique(", "),
  282. "Sum": tpl.sum(usFmt),
  283. "Integer Sum": tpl.sum(usFmtInt),
  284. "Average": tpl.average(usFmt),
  285. "Sum over Sum": tpl.sumOverSum(usFmt),
  286. "80% Upper Bound": tpl.sumOverSumBound80(true, usFmt),
  287. "80% Lower Bound": tpl.sumOverSumBound80(false, usFmt),
  288. "Sum as Fraction of Total": tpl.fractionOf(tpl.sum(), "total", usFmtPct),
  289. "Sum as Fraction of Rows": tpl.fractionOf(tpl.sum(), "row", usFmtPct),
  290. "Sum as Fraction of Columns": tpl.fractionOf(tpl.sum(), "col", usFmtPct),
  291. "Count as Fraction of Total": tpl.fractionOf(tpl.count(), "total", usFmtPct),
  292. "Count as Fraction of Rows": tpl.fractionOf(tpl.count(), "row", usFmtPct),
  293. "Count as Fraction of Columns": tpl.fractionOf(tpl.count(), "col", usFmtPct)
  294. };
  295. })(aggregatorTemplates);
  296. renderers = {
  297. "Table": function(pvtData, opts) {
  298. return pivotTableRenderer(pvtData, opts);
  299. },
  300. "Table Barchart": function(pvtData, opts) {
  301. return $(pivotTableRenderer(pvtData, opts)).barchart();
  302. },
  303. "Heatmap": function(pvtData, opts) {
  304. return $(pivotTableRenderer(pvtData, opts)).heatmap();
  305. },
  306. "Row Heatmap": function(pvtData, opts) {
  307. return $(pivotTableRenderer(pvtData, opts)).heatmap("rowheatmap");
  308. },
  309. "Col Heatmap": function(pvtData, opts) {
  310. return $(pivotTableRenderer(pvtData, opts)).heatmap("colheatmap");
  311. }
  312. };
  313. locales = {
  314. en: {
  315. aggregators: aggregators,
  316. renderers: renderers,
  317. localeStrings: {
  318. renderError: "An error occurred rendering the PivotTable results.",
  319. computeError: "An error occurred computing the PivotTable results.",
  320. uiRenderError: "An error occurred rendering the PivotTable UI.",
  321. selectAll: "Select All",
  322. selectNone: "Select None",
  323. tooMany: "(too many to list)",
  324. filterResults: "Filter results",
  325. totals: "Totals",
  326. vs: "vs",
  327. by: "by"
  328. }
  329. }
  330. };
  331. mthNamesEn = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"];
  332. dayNamesEn = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"];
  333. zeroPad = function(number) {
  334. return ("0" + number).substr(-2, 2);
  335. };
  336. derivers = {
  337. bin: function(col, binWidth) {
  338. return function(record) {
  339. return record[col] - record[col] % binWidth;
  340. };
  341. },
  342. dateFormat: function(col, formatString, mthNames, dayNames) {
  343. if (mthNames == null) {
  344. mthNames = mthNamesEn;
  345. }
  346. if (dayNames == null) {
  347. dayNames = dayNamesEn;
  348. }
  349. return function(record) {
  350. var date;
  351. date = new Date(Date.parse(record[col]));
  352. if (isNaN(date)) {
  353. return "";
  354. }
  355. return formatString.replace(/%(.)/g, function(m, p) {
  356. switch (p) {
  357. case "y":
  358. return date.getFullYear();
  359. case "m":
  360. return zeroPad(date.getMonth() + 1);
  361. case "n":
  362. return mthNames[date.getMonth()];
  363. case "d":
  364. return zeroPad(date.getDate());
  365. case "w":
  366. return dayNames[date.getDay()];
  367. case "x":
  368. return date.getDay();
  369. case "H":
  370. return zeroPad(date.getHours());
  371. case "M":
  372. return zeroPad(date.getMinutes());
  373. case "S":
  374. return zeroPad(date.getSeconds());
  375. default:
  376. return "%" + p;
  377. }
  378. });
  379. };
  380. }
  381. };
  382. naturalSort = (function(_this) {
  383. return function(as, bs) {
  384. var a, a1, b, b1, rd, rx, rz;
  385. rx = /(\d+)|(\D+)/g;
  386. rd = /\d/;
  387. rz = /^0/;
  388. if (typeof as === "number" || typeof bs === "number") {
  389. if (isNaN(as)) {
  390. return 1;
  391. }
  392. if (isNaN(bs)) {
  393. return -1;
  394. }
  395. return as - bs;
  396. }
  397. a = String(as).toLowerCase();
  398. b = String(bs).toLowerCase();
  399. if (a === b) {
  400. return 0;
  401. }
  402. if (!(rd.test(a) && rd.test(b))) {
  403. return (a > b ? 1 : -1);
  404. }
  405. a = a.match(rx);
  406. b = b.match(rx);
  407. while (a.length && b.length) {
  408. a1 = a.shift();
  409. b1 = b.shift();
  410. if (a1 !== b1) {
  411. if (rd.test(a1) && rd.test(b1)) {
  412. return a1.replace(rz, ".0") - b1.replace(rz, ".0");
  413. } else {
  414. return (a1 > b1 ? 1 : -1);
  415. }
  416. }
  417. }
  418. return a.length - b.length;
  419. };
  420. })(this);
  421. $.pivotUtilities = {
  422. aggregatorTemplates: aggregatorTemplates,
  423. aggregators: aggregators,
  424. renderers: renderers,
  425. derivers: derivers,
  426. locales: locales,
  427. naturalSort: naturalSort,
  428. numberFormat: numberFormat
  429. };
  430. /*
  431. Data Model class
  432. */
  433. PivotData = (function() {
  434. function PivotData(input, opts) {
  435. this.getAggregator = __bind(this.getAggregator, this);
  436. this.getRowKeys = __bind(this.getRowKeys, this);
  437. this.getColKeys = __bind(this.getColKeys, this);
  438. this.sortKeys = __bind(this.sortKeys, this);
  439. this.arrSort = __bind(this.arrSort, this);
  440. this.natSort = __bind(this.natSort, this);
  441. this.aggregator = opts.aggregator;
  442. this.aggregatorName = opts.aggregatorName;
  443. this.colAttrs = opts.cols;
  444. this.rowAttrs = opts.rows;
  445. this.valAttrs = opts.vals;
  446. this.tree = {};
  447. this.rowKeys = [];
  448. this.colKeys = [];
  449. this.rowTotals = {};
  450. this.colTotals = {};
  451. this.allTotal = this.aggregator(this, [], []);
  452. this.sorted = false;
  453. PivotData.forEachRecord(input, opts.derivedAttributes, (function(_this) {
  454. return function(record) {
  455. if (opts.filter(record)) {
  456. return _this.processRecord(record);
  457. }
  458. };
  459. })(this));
  460. }
  461. PivotData.forEachRecord = function(input, derivedAttributes, f) {
  462. var addRecord, compactRecord, i, j, k, record, tblCols, _i, _len, _ref, _results, _results1;
  463. if ($.isEmptyObject(derivedAttributes)) {
  464. addRecord = f;
  465. } else {
  466. addRecord = function(record) {
  467. var k, v, _ref;
  468. for (k in derivedAttributes) {
  469. v = derivedAttributes[k];
  470. record[k] = (_ref = v(record)) != null ? _ref : record[k];
  471. }
  472. return f(record);
  473. };
  474. }
  475. if ($.isFunction(input)) {
  476. return input(addRecord);
  477. } else if ($.isArray(input)) {
  478. if ($.isArray(input[0])) {
  479. _results = [];
  480. for (i in input) {
  481. if (!__hasProp.call(input, i)) continue;
  482. compactRecord = input[i];
  483. if (!(i > 0)) {
  484. continue;
  485. }
  486. record = {};
  487. _ref = input[0];
  488. for (j in _ref) {
  489. if (!__hasProp.call(_ref, j)) continue;
  490. k = _ref[j];
  491. record[k] = compactRecord[j];
  492. }
  493. _results.push(addRecord(record));
  494. }
  495. return _results;
  496. } else {
  497. _results1 = [];
  498. for (_i = 0, _len = input.length; _i < _len; _i++) {
  499. record = input[_i];
  500. _results1.push(addRecord(record));
  501. }
  502. return _results1;
  503. }
  504. } else if (input instanceof jQuery) {
  505. tblCols = [];
  506. $("thead > tr > th", input).each(function(i) {
  507. return tblCols.push($(this).text());
  508. });
  509. return $("tbody > tr", input).each(function(i) {
  510. record = {};
  511. $("td", this).each(function(j) {
  512. return record[tblCols[j]] = $(this).text();
  513. });
  514. return addRecord(record);
  515. });
  516. } else {
  517. throw new Error("unknown input format");
  518. }
  519. };
  520. PivotData.convertToArray = function(input) {
  521. var result;
  522. result = [];
  523. PivotData.forEachRecord(input, {}, function(record) {
  524. return result.push(record);
  525. });
  526. return result;
  527. };
  528. PivotData.prototype.natSort = function(as, bs) {
  529. return naturalSort(as, bs);
  530. };
  531. PivotData.prototype.arrSort = function(a, b) {
  532. return this.natSort(a.join(), b.join());
  533. };
  534. PivotData.prototype.sortKeys = function() {
  535. if (!this.sorted) {
  536. this.rowKeys.sort(this.arrSort);
  537. this.colKeys.sort(this.arrSort);
  538. }
  539. return this.sorted = true;
  540. };
  541. PivotData.prototype.getColKeys = function() {
  542. this.sortKeys();
  543. return this.colKeys;
  544. };
  545. PivotData.prototype.getRowKeys = function() {
  546. this.sortKeys();
  547. return this.rowKeys;
  548. };
  549. PivotData.prototype.processRecord = function(record) {
  550. var colKey, flatColKey, flatRowKey, rowKey, x, _i, _j, _len, _len1, _ref, _ref1, _ref2, _ref3;
  551. colKey = [];
  552. rowKey = [];
  553. _ref = this.colAttrs;
  554. for (_i = 0, _len = _ref.length; _i < _len; _i++) {
  555. x = _ref[_i];
  556. colKey.push((_ref1 = record[x]) != null ? _ref1 : "null");
  557. }
  558. _ref2 = this.rowAttrs;
  559. for (_j = 0, _len1 = _ref2.length; _j < _len1; _j++) {
  560. x = _ref2[_j];
  561. rowKey.push((_ref3 = record[x]) != null ? _ref3 : "null");
  562. }
  563. flatRowKey = rowKey.join(String.fromCharCode(0));
  564. flatColKey = colKey.join(String.fromCharCode(0));
  565. this.allTotal.push(record);
  566. if (rowKey.length !== 0) {
  567. if (!this.rowTotals[flatRowKey]) {
  568. this.rowKeys.push(rowKey);
  569. this.rowTotals[flatRowKey] = this.aggregator(this, rowKey, []);
  570. }
  571. this.rowTotals[flatRowKey].push(record);
  572. }
  573. if (colKey.length !== 0) {
  574. if (!this.colTotals[flatColKey]) {
  575. this.colKeys.push(colKey);
  576. this.colTotals[flatColKey] = this.aggregator(this, [], colKey);
  577. }
  578. this.colTotals[flatColKey].push(record);
  579. }
  580. if (colKey.length !== 0 && rowKey.length !== 0) {
  581. if (!this.tree[flatRowKey]) {
  582. this.tree[flatRowKey] = {};
  583. }
  584. if (!this.tree[flatRowKey][flatColKey]) {
  585. this.tree[flatRowKey][flatColKey] = this.aggregator(this, rowKey, colKey);
  586. }
  587. return this.tree[flatRowKey][flatColKey].push(record);
  588. }
  589. };
  590. PivotData.prototype.getAggregator = function(rowKey, colKey) {
  591. var agg, flatColKey, flatRowKey;
  592. flatRowKey = rowKey.join(String.fromCharCode(0));
  593. flatColKey = colKey.join(String.fromCharCode(0));
  594. if (rowKey.length === 0 && colKey.length === 0) {
  595. agg = this.allTotal;
  596. } else if (rowKey.length === 0) {
  597. agg = this.colTotals[flatColKey];
  598. } else if (colKey.length === 0) {
  599. agg = this.rowTotals[flatRowKey];
  600. } else {
  601. agg = this.tree[flatRowKey][flatColKey];
  602. }
  603. return agg != null ? agg : {
  604. value: (function() {
  605. return null;
  606. }),
  607. format: function() {
  608. return "";
  609. }
  610. };
  611. };
  612. return PivotData;
  613. })();
  614. /*
  615. Default Renderer for hierarchical table layout
  616. */
  617. pivotTableRenderer = function(pivotData, opts) {
  618. var aggregator, c, colAttrs, colKey, colKeys, defaults, i, j, r, result, rowAttrs, rowKey, rowKeys, spanSize, td, th, totalAggregator, tr, txt, val, x;
  619. defaults = {
  620. localeStrings: {
  621. totals: "Totals"
  622. }
  623. };
  624. opts = $.extend(defaults, opts);
  625. colAttrs = pivotData.colAttrs;
  626. rowAttrs = pivotData.rowAttrs;
  627. rowKeys = pivotData.getRowKeys();
  628. colKeys = pivotData.getColKeys();
  629. result = document.createElement("table");
  630. result.className = "pvtTable";
  631. spanSize = function(arr, i, j) {
  632. var len, noDraw, stop, x, _i, _j;
  633. if (i !== 0) {
  634. noDraw = true;
  635. for (x = _i = 0; 0 <= j ? _i <= j : _i >= j; x = 0 <= j ? ++_i : --_i) {
  636. if (arr[i - 1][x] !== arr[i][x]) {
  637. noDraw = false;
  638. }
  639. }
  640. if (noDraw) {
  641. return -1;
  642. }
  643. }
  644. len = 0;
  645. while (i + len < arr.length) {
  646. stop = false;
  647. for (x = _j = 0; 0 <= j ? _j <= j : _j >= j; x = 0 <= j ? ++_j : --_j) {
  648. if (arr[i][x] !== arr[i + len][x]) {
  649. stop = true;
  650. }
  651. }
  652. if (stop) {
  653. break;
  654. }
  655. len++;
  656. }
  657. return len;
  658. };
  659. for (j in colAttrs) {
  660. if (!__hasProp.call(colAttrs, j)) continue;
  661. c = colAttrs[j];
  662. tr = document.createElement("tr");
  663. if (parseInt(j) === 0 && rowAttrs.length !== 0) {
  664. th = document.createElement("th");
  665. th.setAttribute("colspan", rowAttrs.length);
  666. th.setAttribute("rowspan", colAttrs.length);
  667. tr.appendChild(th);
  668. }
  669. th = document.createElement("th");
  670. th.className = "pvtAxisLabel";
  671. th.textContent = c;
  672. tr.appendChild(th);
  673. for (i in colKeys) {
  674. if (!__hasProp.call(colKeys, i)) continue;
  675. colKey = colKeys[i];
  676. x = spanSize(colKeys, parseInt(i), parseInt(j));
  677. if (x !== -1) {
  678. th = document.createElement("th");
  679. th.className = "pvtColLabel";
  680. th.textContent = colKey[j];
  681. th.setAttribute("colspan", x);
  682. if (parseInt(j) === colAttrs.length - 1 && rowAttrs.length !== 0) {
  683. th.setAttribute("rowspan", 2);
  684. }
  685. tr.appendChild(th);
  686. }
  687. }
  688. if (parseInt(j) === 0) {
  689. th = document.createElement("th");
  690. th.className = "pvtTotalLabel";
  691. th.innerHTML = opts.localeStrings.totals;
  692. th.setAttribute("rowspan", colAttrs.length + (rowAttrs.length === 0 ? 0 : 1));
  693. tr.appendChild(th);
  694. }
  695. result.appendChild(tr);
  696. }
  697. if (rowAttrs.length !== 0) {
  698. tr = document.createElement("tr");
  699. for (i in rowAttrs) {
  700. if (!__hasProp.call(rowAttrs, i)) continue;
  701. r = rowAttrs[i];
  702. th = document.createElement("th");
  703. th.className = "pvtAxisLabel";
  704. th.textContent = r;
  705. tr.appendChild(th);
  706. }
  707. th = document.createElement("th");
  708. if (colAttrs.length === 0) {
  709. th.className = "pvtTotalLabel";
  710. th.innerHTML = opts.localeStrings.totals;
  711. }
  712. tr.appendChild(th);
  713. result.appendChild(tr);
  714. }
  715. for (i in rowKeys) {
  716. if (!__hasProp.call(rowKeys, i)) continue;
  717. rowKey = rowKeys[i];
  718. tr = document.createElement("tr");
  719. for (j in rowKey) {
  720. if (!__hasProp.call(rowKey, j)) continue;
  721. txt = rowKey[j];
  722. x = spanSize(rowKeys, parseInt(i), parseInt(j));
  723. if (x !== -1) {
  724. th = document.createElement("th");
  725. th.className = "pvtRowLabel";
  726. th.textContent = txt;
  727. th.setAttribute("rowspan", x);
  728. if (parseInt(j) === rowAttrs.length - 1 && colAttrs.length !== 0) {
  729. th.setAttribute("colspan", 2);
  730. }
  731. tr.appendChild(th);
  732. }
  733. }
  734. for (j in colKeys) {
  735. if (!__hasProp.call(colKeys, j)) continue;
  736. colKey = colKeys[j];
  737. aggregator = pivotData.getAggregator(rowKey, colKey);
  738. val = aggregator.value();
  739. td = document.createElement("td");
  740. td.className = "pvtVal row" + i + " col" + j;
  741. td.innerHTML = aggregator.format(val);
  742. td.setAttribute("data-value", val);
  743. tr.appendChild(td);
  744. }
  745. totalAggregator = pivotData.getAggregator(rowKey, []);
  746. val = totalAggregator.value();
  747. td = document.createElement("td");
  748. td.className = "pvtTotal rowTotal";
  749. td.innerHTML = totalAggregator.format(val);
  750. td.setAttribute("data-value", val);
  751. td.setAttribute("data-for", "row" + i);
  752. tr.appendChild(td);
  753. result.appendChild(tr);
  754. }
  755. tr = document.createElement("tr");
  756. th = document.createElement("th");
  757. th.className = "pvtTotalLabel";
  758. th.innerHTML = opts.localeStrings.totals;
  759. th.setAttribute("colspan", rowAttrs.length + (colAttrs.length === 0 ? 0 : 1));
  760. tr.appendChild(th);
  761. for (j in colKeys) {
  762. if (!__hasProp.call(colKeys, j)) continue;
  763. colKey = colKeys[j];
  764. totalAggregator = pivotData.getAggregator([], colKey);
  765. val = totalAggregator.value();
  766. td = document.createElement("td");
  767. td.className = "pvtTotal colTotal";
  768. td.innerHTML = totalAggregator.format(val);
  769. td.setAttribute("data-value", val);
  770. td.setAttribute("data-for", "col" + j);
  771. tr.appendChild(td);
  772. }
  773. totalAggregator = pivotData.getAggregator([], []);
  774. val = totalAggregator.value();
  775. td = document.createElement("td");
  776. td.className = "pvtGrandTotal";
  777. td.innerHTML = totalAggregator.format(val);
  778. td.setAttribute("data-value", val);
  779. tr.appendChild(td);
  780. result.appendChild(tr);
  781. result.setAttribute("data-numrows", rowKeys.length);
  782. result.setAttribute("data-numcols", colKeys.length);
  783. return result;
  784. };
  785. /*
  786. Pivot Table core: create PivotData object and call Renderer on it
  787. */
  788. $.fn.pivot = function(input, opts) {
  789. var defaults, e, pivotData, result, x;
  790. defaults = {
  791. cols: [],
  792. rows: [],
  793. filter: function() {
  794. return true;
  795. },
  796. aggregator: aggregatorTemplates.count()(),
  797. aggregatorName: "Count",
  798. derivedAttributes: {},
  799. renderer: pivotTableRenderer,
  800. rendererOptions: null,
  801. localeStrings: locales.en.localeStrings
  802. };
  803. opts = $.extend(defaults, opts);
  804. result = null;
  805. try {
  806. pivotData = new PivotData(input, opts);
  807. try {
  808. result = opts.renderer(pivotData, opts.rendererOptions);
  809. } catch (_error) {
  810. e = _error;
  811. if (typeof console !== "undefined" && console !== null) {
  812. console.error(e.stack);
  813. }
  814. result = $("<span>").html(opts.localeStrings.renderError);
  815. }
  816. } catch (_error) {
  817. e = _error;
  818. if (typeof console !== "undefined" && console !== null) {
  819. console.error(e.stack);
  820. }
  821. result = $("<span>").html(opts.localeStrings.computeError);
  822. }
  823. x = this[0];
  824. while (x.hasChildNodes()) {
  825. x.removeChild(x.lastChild);
  826. }
  827. return this.append(result);
  828. };
  829. /*
  830. Pivot Table UI: calls Pivot Table core above with options set by user
  831. */
  832. $.fn.pivotUI = function(input, inputOpts, overwrite, locale) {
  833. var a, aggregator, attrLength, axisValues, c, colList, defaults, e, existingOpts, i, initialRender, k, opts, pivotTable, refresh, refreshDelayed, renderer, rendererControl, shownAttributes, tblCols, tr1, tr2, uiTable, unusedAttrsVerticalAutoOverride, x, _fn, _i, _j, _k, _l, _len, _len1, _len2, _len3, _ref, _ref1, _ref2, _ref3, _ref4;
  834. if (overwrite == null) {
  835. overwrite = false;
  836. }
  837. if (locale == null) {
  838. locale = "en";
  839. }
  840. defaults = {
  841. derivedAttributes: {},
  842. aggregators: locales[locale].aggregators,
  843. renderers: locales[locale].renderers,
  844. hiddenAttributes: [],
  845. menuLimit: 200,
  846. cols: [],
  847. rows: [],
  848. vals: [],
  849. exclusions: {},
  850. unusedAttrsVertical: "auto",
  851. autoSortUnusedAttrs: false,
  852. rendererOptions: {
  853. localeStrings: locales[locale].localeStrings
  854. },
  855. onRefresh: null,
  856. filter: function() {
  857. return true;
  858. },
  859. localeStrings: locales[locale].localeStrings
  860. };
  861. existingOpts = this.data("pivotUIOptions");
  862. if ((existingOpts == null) || overwrite) {
  863. opts = $.extend(defaults, inputOpts);
  864. } else {
  865. opts = existingOpts;
  866. }
  867. try {
  868. input = PivotData.convertToArray(input);
  869. tblCols = (function() {
  870. var _ref, _results;
  871. _ref = input[0];
  872. _results = [];
  873. for (k in _ref) {
  874. if (!__hasProp.call(_ref, k)) continue;
  875. _results.push(k);
  876. }
  877. return _results;
  878. })();
  879. _ref = opts.derivedAttributes;
  880. for (c in _ref) {
  881. if (!__hasProp.call(_ref, c)) continue;
  882. if ((__indexOf.call(tblCols, c) < 0)) {
  883. tblCols.push(c);
  884. }
  885. }
  886. axisValues = {};
  887. for (_i = 0, _len = tblCols.length; _i < _len; _i++) {
  888. x = tblCols[_i];
  889. axisValues[x] = {};
  890. }
  891. PivotData.forEachRecord(input, opts.derivedAttributes, function(record) {
  892. var v, _base, _results;
  893. _results = [];
  894. for (k in record) {
  895. if (!__hasProp.call(record, k)) continue;
  896. v = record[k];
  897. if (!(opts.filter(record))) {
  898. continue;
  899. }
  900. if (v == null) {
  901. v = "null";
  902. }
  903. if ((_base = axisValues[k])[v] == null) {
  904. _base[v] = 0;
  905. }
  906. _results.push(axisValues[k][v]++);
  907. }
  908. return _results;
  909. });
  910. uiTable = $("<table cellpadding='5'>");
  911. rendererControl = $("<td>");
  912. renderer = $("<select class='pvtRenderer'>").appendTo(rendererControl).bind("change", function() {
  913. return refresh();
  914. });
  915. _ref1 = opts.renderers;
  916. for (x in _ref1) {
  917. if (!__hasProp.call(_ref1, x)) continue;
  918. $("<option>").val(x).html(x).appendTo(renderer);
  919. }
  920. colList = $("<td class='pvtAxisContainer pvtUnused'>");
  921. shownAttributes = (function() {
  922. var _j, _len1, _results;
  923. _results = [];
  924. for (_j = 0, _len1 = tblCols.length; _j < _len1; _j++) {
  925. c = tblCols[_j];
  926. if (__indexOf.call(opts.hiddenAttributes, c) < 0) {
  927. _results.push(c);
  928. }
  929. }
  930. return _results;
  931. })();
  932. unusedAttrsVerticalAutoOverride = false;
  933. if (opts.unusedAttrsVertical === "auto") {
  934. attrLength = 0;
  935. for (_j = 0, _len1 = shownAttributes.length; _j < _len1; _j++) {
  936. a = shownAttributes[_j];
  937. attrLength += a.length;
  938. }
  939. unusedAttrsVerticalAutoOverride = attrLength > 120;
  940. }
  941. if (opts.unusedAttrsVertical === true || unusedAttrsVerticalAutoOverride) {
  942. colList.addClass('pvtVertList');
  943. } else {
  944. colList.addClass('pvtHorizList');
  945. }
  946. _fn = function(c) {
  947. var attrElem, btns, checkContainer, filterItem, filterItemExcluded, hasExcludedItem, keys, showFilterList, triangleLink, updateFilter, v, valueList, _k, _len2, _ref2;
  948. keys = (function() {
  949. var _results;
  950. _results = [];
  951. for (k in axisValues[c]) {
  952. _results.push(k);
  953. }
  954. return _results;
  955. })();
  956. hasExcludedItem = false;
  957. valueList = $("<div>").addClass('pvtFilterBox').hide();
  958. valueList.append($("<h4>").text("" + c + " (" + keys.length + ")"));
  959. if (keys.length > opts.menuLimit) {
  960. valueList.append($("<p>").html(opts.localeStrings.tooMany));
  961. } else {
  962. btns = $("<p>").appendTo(valueList);
  963. btns.append($("<button>", {
  964. type: "button"
  965. }).html(opts.localeStrings.selectAll).bind("click", function() {
  966. return valueList.find("input:visible").prop("checked", true);
  967. }));
  968. btns.append($("<button>", {
  969. type: "button"
  970. }).html(opts.localeStrings.selectNone).bind("click", function() {
  971. return valueList.find("input:visible").prop("checked", false);
  972. }));
  973. btns.append($("<input>").addClass("pvtSearch").attr("placeholder", opts.localeStrings.filterResults).bind("keyup", function() {
  974. var filter;
  975. filter = $(this).val().toLowerCase();
  976. return $(this).parents(".pvtFilterBox").find('label span').each(function() {
  977. var testString;
  978. testString = $(this).text().toLowerCase().indexOf(filter);
  979. if (testString !== -1) {
  980. return $(this).parent().show();
  981. } else {
  982. return $(this).parent().hide();
  983. }
  984. });
  985. }));
  986. checkContainer = $("<div>").addClass("pvtCheckContainer").appendTo(valueList);
  987. _ref2 = keys.sort(naturalSort);
  988. for (_k = 0, _len2 = _ref2.length; _k < _len2; _k++) {
  989. k = _ref2[_k];
  990. v = axisValues[c][k];
  991. filterItem = $("<label>");
  992. filterItemExcluded = opts.exclusions[c] ? (__indexOf.call(opts.exclusions[c], k) >= 0) : false;
  993. hasExcludedItem || (hasExcludedItem = filterItemExcluded);
  994. $("<input type='checkbox' class='pvtFilter'>").attr("checked", !filterItemExcluded).data("filter", [c, k]).appendTo(filterItem);
  995. filterItem.append($("<span>").text("" + k + " (" + v + ")"));
  996. checkContainer.append($("<p>").append(filterItem));
  997. }
  998. }
  999. updateFilter = function() {
  1000. var unselectedCount;
  1001. unselectedCount = $(valueList).find("[type='checkbox']").length - $(valueList).find("[type='checkbox']:checked").length;
  1002. if (unselectedCount > 0) {
  1003. attrElem.addClass("pvtFilteredAttribute");
  1004. } else {
  1005. attrElem.removeClass("pvtFilteredAttribute");
  1006. }
  1007. if (keys.length > opts.menuLimit) {
  1008. return valueList.toggle();
  1009. } else {
  1010. return valueList.toggle(0, refresh);
  1011. }
  1012. };
  1013. $("<p>").appendTo(valueList).append($("<button>", {
  1014. type: "button"
  1015. }).text("OK").bind("click", updateFilter));
  1016. showFilterList = function(e) {
  1017. valueList.css({
  1018. left: e.pageX,
  1019. top: e.pageY
  1020. }).toggle();
  1021. $('.pvtSearch').val('');
  1022. return $('label').show();
  1023. };
  1024. triangleLink = $("<span class='pvtTriangle'>").html(" &#x25BE;").bind("click", showFilterList);
  1025. attrElem = $("<li class='axis_" + i + "'>").append($("<span class='pvtAttr'>").text(c).data("attrName", c).append(triangleLink));
  1026. if (hasExcludedItem) {
  1027. attrElem.addClass('pvtFilteredAttribute');
  1028. }
  1029. colList.append(attrElem).append(valueList);
  1030. return attrElem.bind("dblclick", showFilterList);
  1031. };
  1032. for (i in shownAttributes) {
  1033. c = shownAttributes[i];
  1034. _fn(c);
  1035. }
  1036. tr1 = $("<tr>").appendTo(uiTable);
  1037. aggregator = $("<select class='pvtAggregator'>").bind("change", function() {
  1038. return refresh();
  1039. });
  1040. _ref2 = opts.aggregators;
  1041. for (x in _ref2) {
  1042. if (!__hasProp.call(_ref2, x)) continue;
  1043. aggregator.append($("<option>").val(x).html(x));
  1044. }
  1045. $("<td class='pvtVals'>").appendTo(tr1).append(aggregator).append($("<br>"));
  1046. $("<td class='pvtAxisContainer pvtHorizList pvtCols'>").appendTo(tr1);
  1047. tr2 = $("<tr>").appendTo(uiTable);
  1048. tr2.append($("<td valign='top' class='pvtAxisContainer pvtRows'>"));
  1049. pivotTable = $("<td valign='top' class='pvtRendererArea'>").appendTo(tr2);
  1050. if (opts.unusedAttrsVertical === true || unusedAttrsVerticalAutoOverride) {
  1051. uiTable.find('tr:nth-child(1)').prepend(rendererControl);
  1052. uiTable.find('tr:nth-child(2)').prepend(colList);
  1053. } else {
  1054. uiTable.prepend($("<tr>").append(rendererControl).append(colList));
  1055. }
  1056. this.html(uiTable);
  1057. _ref3 = opts.cols;
  1058. for (_k = 0, _len2 = _ref3.length; _k < _len2; _k++) {
  1059. x = _ref3[_k];
  1060. this.find(".pvtCols").append(this.find(".axis_" + (shownAttributes.indexOf(x))));
  1061. }
  1062. _ref4 = opts.rows;
  1063. for (_l = 0, _len3 = _ref4.length; _l < _len3; _l++) {
  1064. x = _ref4[_l];
  1065. this.find(".pvtRows").append(this.find(".axis_" + (shownAttributes.indexOf(x))));
  1066. }
  1067. if (opts.aggregatorName != null) {
  1068. this.find(".pvtAggregator").val(opts.aggregatorName);
  1069. }
  1070. if (opts.rendererName != null) {
  1071. this.find(".pvtRenderer").val(opts.rendererName);
  1072. }
  1073. initialRender = true;
  1074. refreshDelayed = (function(_this) {
  1075. return function() {
  1076. var attr, exclusions, natSort, newDropdown, numInputsToProcess, pivotUIOptions, pvtVals, subopts, unusedAttrsContainer, vals, _len4, _m, _n, _ref5;
  1077. subopts = {
  1078. derivedAttributes: opts.derivedAttributes,
  1079. localeStrings: opts.localeStrings,
  1080. rendererOptions: opts.rendererOptions,
  1081. cols: [],
  1082. rows: []
  1083. };
  1084. numInputsToProcess = (_ref5 = opts.aggregators[aggregator.val()]([])().numInputs) != null ? _ref5 : 0;
  1085. vals = [];
  1086. _this.find(".pvtRows li span.pvtAttr").each(function() {
  1087. return subopts.rows.push($(this).data("attrName"));
  1088. });
  1089. _this.find(".pvtCols li span.pvtAttr").each(function() {
  1090. return subopts.cols.push($(this).data("attrName"));
  1091. });
  1092. _this.find(".pvtVals select.pvtAttrDropdown").each(function() {
  1093. if (numInputsToProcess === 0) {
  1094. return $(this).remove();
  1095. } else {
  1096. numInputsToProcess--;
  1097. if ($(this).val() !== "") {
  1098. return vals.push($(this).val());
  1099. }
  1100. }
  1101. });
  1102. if (numInputsToProcess !== 0) {
  1103. pvtVals = _this.find(".pvtVals");
  1104. for (x = _m = 0; 0 <= numInputsToProcess ? _m < numInputsToProcess : _m > numInputsToProcess; x = 0 <= numInputsToProcess ? ++_m : --_m) {
  1105. newDropdown = $("<select class='pvtAttrDropdown'>").append($("<option>")).bind("change", function() {
  1106. return refresh();
  1107. });
  1108. for (_n = 0, _len4 = shownAttributes.length; _n < _len4; _n++) {
  1109. attr = shownAttributes[_n];
  1110. newDropdown.append($("<option>").val(attr).text(attr));
  1111. }
  1112. pvtVals.append(newDropdown);
  1113. }
  1114. }
  1115. if (initialRender) {
  1116. vals = opts.vals;
  1117. i = 0;
  1118. _this.find(".pvtVals select.pvtAttrDropdown").each(function() {
  1119. $(this).val(vals[i]);
  1120. return i++;
  1121. });
  1122. initialRender = false;
  1123. }
  1124. subopts.aggregatorName = aggregator.val();
  1125. subopts.vals = vals;
  1126. subopts.aggregator = opts.aggregators[aggregator.val()](vals);
  1127. subopts.renderer = opts.renderers[renderer.val()];
  1128. exclusions = {};
  1129. _this.find('input.pvtFilter').not(':checked').each(function() {
  1130. var filter;
  1131. filter = $(this).data("filter");
  1132. if (exclusions[filter[0]] != null) {
  1133. return exclusions[filter[0]].push(filter[1]);
  1134. } else {
  1135. return exclusions[filter[0]] = [filter[1]];
  1136. }
  1137. });
  1138. subopts.filter = function(record) {
  1139. var excludedItems, _ref6;
  1140. if (!opts.filter(record)) {
  1141. return false;
  1142. }
  1143. for (k in exclusions) {
  1144. excludedItems = exclusions[k];
  1145. if (_ref6 = "" + record[k], __indexOf.call(excludedItems, _ref6) >= 0) {
  1146. return false;
  1147. }
  1148. }
  1149. return true;
  1150. };
  1151. pivotTable.pivot(input, subopts);
  1152. pivotUIOptions = $.extend(opts, {
  1153. cols: subopts.cols,
  1154. rows: subopts.rows,
  1155. vals: vals,
  1156. exclusions: exclusions,
  1157. aggregatorName: aggregator.val(),
  1158. rendererName: renderer.val()
  1159. });
  1160. _this.data("pivotUIOptions", pivotUIOptions);
  1161. if (opts.autoSortUnusedAttrs) {
  1162. natSort = $.pivotUtilities.naturalSort;
  1163. unusedAttrsContainer = _this.find("td.pvtUnused.pvtAxisContainer");
  1164. $(unusedAttrsContainer).children("li").sort(function(a, b) {
  1165. return natSort($(a).text(), $(b).text());
  1166. }).appendTo(unusedAttrsContainer);
  1167. }
  1168. pivotTable.css("opacity", 1);
  1169. if (opts.onRefresh != null) {
  1170. return opts.onRefresh(pivotUIOptions);
  1171. }
  1172. };
  1173. })(this);
  1174. refresh = (function(_this) {
  1175. return function() {
  1176. pivotTable.css("opacity", 0.5);
  1177. return setTimeout(refreshDelayed, 10);
  1178. };
  1179. })(this);
  1180. refresh();
  1181. this.find(".pvtAxisContainer").sortable({
  1182. update: function(e, ui) {
  1183. if (ui.sender == null) {
  1184. return refresh();
  1185. }
  1186. },
  1187. connectWith: this.find(".pvtAxisContainer"),
  1188. items: 'li',
  1189. placeholder: 'pvtPlaceholder'
  1190. });
  1191. } catch (_error) {
  1192. e = _error;
  1193. if (typeof console !== "undefined" && console !== null) {
  1194. console.error(e.stack);
  1195. }
  1196. this.html(opts.localeStrings.uiRenderError);
  1197. }
  1198. return this;
  1199. };
  1200. /*
  1201. Heatmap post-processing
  1202. */
  1203. $.fn.heatmap = function(scope) {
  1204. var colorGen, heatmapper, i, j, numCols, numRows, _i, _j;
  1205. if (scope == null) {
  1206. scope = "heatmap";
  1207. }
  1208. numRows = this.data("numrows");
  1209. numCols = this.data("numcols");
  1210. colorGen = function(color, min, max) {
  1211. var hexGen;
  1212. hexGen = (function() {
  1213. switch (color) {
  1214. case "red":
  1215. return function(hex) {
  1216. return "ff" + hex + hex;
  1217. };
  1218. case "green":
  1219. return function(hex) {
  1220. return "" + hex + "ff" + hex;
  1221. };
  1222. case "blue":
  1223. return function(hex) {
  1224. return "" + hex + hex + "ff";
  1225. };
  1226. }
  1227. })();
  1228. return function(x) {
  1229. var hex, intensity;
  1230. intensity = 255 - Math.round(255 * (x - min) / (max - min));
  1231. hex = intensity.toString(16).split(".")[0];
  1232. if (hex.length === 1) {
  1233. hex = 0 + hex;
  1234. }
  1235. return hexGen(hex);
  1236. };
  1237. };
  1238. heatmapper = (function(_this) {
  1239. return function(scope, color) {
  1240. var colorFor, forEachCell, values;
  1241. forEachCell = function(f) {
  1242. return _this.find(scope).each(function() {
  1243. var x;
  1244. x = $(this).data("value");
  1245. if ((x != null) && isFinite(x)) {
  1246. return f(x, $(this));
  1247. }
  1248. });
  1249. };
  1250. values = [];
  1251. forEachCell(function(x) {
  1252. return values.push(x);
  1253. });
  1254. colorFor = colorGen(color, Math.min.apply(Math, values), Math.max.apply(Math, values));
  1255. return forEachCell(function(x, elem) {
  1256. return elem.css("background-color", "#" + colorFor(x));
  1257. });
  1258. };
  1259. })(this);
  1260. switch (scope) {
  1261. case "heatmap":
  1262. heatmapper(".pvtVal", "red");
  1263. break;
  1264. case "rowheatmap":
  1265. for (i = _i = 0; 0 <= numRows ? _i < numRows : _i > numRows; i = 0 <= numRows ? ++_i : --_i) {
  1266. heatmapper(".pvtVal.row" + i, "red");
  1267. }
  1268. break;
  1269. case "colheatmap":
  1270. for (j = _j = 0; 0 <= numCols ? _j < numCols : _j > numCols; j = 0 <= numCols ? ++_j : --_j) {
  1271. heatmapper(".pvtVal.col" + j, "red");
  1272. }
  1273. }
  1274. heatmapper(".pvtTotal.rowTotal", "red");
  1275. heatmapper(".pvtTotal.colTotal", "red");
  1276. return this;
  1277. };
  1278. /*
  1279. Barchart post-processing
  1280. */
  1281. return $.fn.barchart = function() {
  1282. var barcharter, i, numCols, numRows, _i;
  1283. numRows = this.data("numrows");
  1284. numCols = this.data("numcols");
  1285. barcharter = (function(_this) {
  1286. return function(scope) {
  1287. var forEachCell, max, scaler, values;
  1288. forEachCell = function(f) {
  1289. return _this.find(scope).each(function() {
  1290. var x;
  1291. x = $(this).data("value");
  1292. if ((x != null) && isFinite(x)) {
  1293. return f(x, $(this));
  1294. }
  1295. });
  1296. };
  1297. values = [];
  1298. forEachCell(function(x) {
  1299. return values.push(x);
  1300. });
  1301. max = Math.max.apply(Math, values);
  1302. scaler = function(x) {
  1303. return 100 * x / (1.4 * max);
  1304. };
  1305. return forEachCell(function(x, elem) {
  1306. var text, wrapper;
  1307. text = elem.text();
  1308. wrapper = $("<div>").css({
  1309. "position": "relative",
  1310. "height": "55px"
  1311. });
  1312. wrapper.append($("<div>").css({
  1313. "position": "absolute",
  1314. "bottom": 0,
  1315. "left": 0,
  1316. "right": 0,
  1317. "height": scaler(x) + "%",
  1318. "background-color": "gray"
  1319. }));
  1320. wrapper.append($("<div>").text(text).css({
  1321. "position": "relative",
  1322. "padding-left": "5px",
  1323. "padding-right": "5px"
  1324. }));
  1325. return elem.css({
  1326. "padding": 0,
  1327. "padding-top": "5px",
  1328. "text-align": "center"
  1329. }).html(wrapper);
  1330. });
  1331. };
  1332. })(this);
  1333. for (i = _i = 0; 0 <= numRows ? _i < numRows : _i > numRows; i = 0 <= numRows ? ++_i : --_i) {
  1334. barcharter(".pvtVal.row" + i);
  1335. }
  1336. barcharter(".pvtTotal.colTotal");
  1337. return this;
  1338. };
  1339. });
  1340. }).call(this);
  1341. //# sourceMappingURL=pivot.js.map