grid.pivot.js 30 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861
  1. /**
  2. * jqGrid pivot functions
  3. * Copyright (c) 2008-2014, Tony Tomov, tony@trirand.com, http://trirand.com/blog/
  4. * Copyright (c) 2014-2018, Oleg Kiriljuk, oleg.kiriljuk@ok-soft-gmbh.com
  5. * The modul is created initially by Tony Tomov and it's full rewritten
  6. * for free jqGrid: https://github.com/free-jqgrid/jqGrid by Oleg Kiriljuk
  7. * Dual licensed under the MIT and GPL licenses:
  8. * http://www.opensource.org/licenses/mit-license.php
  9. * http://www.gnu.org/licenses/gpl-2.0.html
  10. */
  11. /*jshint eqeqeq:false */
  12. /*global jQuery, define, exports, module, require */
  13. /*jslint eqeq: true, plusplus: true, continue: true, white: true */
  14. (function (factory) {
  15. "use strict";
  16. if (typeof define === "function" && define.amd) {
  17. // AMD. Register as an anonymous module.
  18. define([
  19. "jquery",
  20. "./jquery.fmatter",
  21. "./grid.grouping"
  22. ], factory);
  23. } else if (typeof module === "object" && module.exports) {
  24. // Node/CommonJS
  25. module.exports = function (root, $) {
  26. if (!root) {
  27. root = window;
  28. }
  29. if ($ === undefined) {
  30. // require("jquery") returns a factory that requires window to
  31. // build a jQuery instance, we normalize how we use modules
  32. // that require this pattern but the window provided is a noop
  33. // if it's defined (how jquery works)
  34. $ = typeof window !== "undefined" ?
  35. require("jquery") :
  36. require("jquery")(root);
  37. }
  38. require("./jquery.fmatter");
  39. require("./grid.grouping");
  40. factory($);
  41. return $;
  42. };
  43. } else {
  44. // Browser globals
  45. factory(jQuery);
  46. }
  47. }(function ($) {
  48. "use strict";
  49. var jgrid = $.jgrid;
  50. // begin module grid.pivot
  51. function Aggregation(aggregator, context, pivotOptions) {
  52. if (!(this instanceof Aggregation)) {
  53. return new Aggregation(aggregator);
  54. }
  55. //this.result = undefined;
  56. //this.count = undefined;
  57. this.aggregator = aggregator;
  58. this.finilized = false;
  59. this.context = context;
  60. this.pivotOptions = pivotOptions;
  61. }
  62. Aggregation.prototype.calc = function (v, fieldName, row, iRow, rows) {
  63. var self = this;
  64. if (v !== undefined) {
  65. self.result = self.result || 0; // change undefined to 0
  66. v = parseFloat(v);
  67. switch (self.aggregator) {
  68. case "sum":
  69. self.result += v;
  70. break;
  71. case "count":
  72. self.result++;
  73. break;
  74. case "avg":
  75. if (self.finilized) {
  76. self.count = self.count || 0; // change undefined to 0
  77. self.result = (self.result * self.count + v) / (self.count + 1);
  78. self.count++;
  79. } else {
  80. self.result += v;
  81. self.count = self.count || 0; // change undefined to 0
  82. self.count++;
  83. }
  84. break;
  85. case "min":
  86. self.result = Math.min(self.result, v);
  87. break;
  88. case "max":
  89. self.result = Math.max(self.result, v);
  90. break;
  91. default:
  92. if ($.isFunction(self.aggregator)) {
  93. self.result = self.aggregator.call(self.context, {
  94. previousResult: self.result,
  95. value: v,
  96. fieldName: fieldName,
  97. item: row,
  98. iItem: iRow,
  99. items: rows
  100. });
  101. }
  102. break;
  103. }
  104. }
  105. };
  106. Aggregation.prototype.getResult = function (obj, propName, forceSaving) {
  107. var self = this;
  108. if (self.result !== undefined || forceSaving) {
  109. if (forceSaving) {
  110. if (self.result !== undefined) {
  111. self.result = 0;
  112. self.count = 0;
  113. }
  114. }
  115. if (self.result !== undefined && !self.finilized && self.aggregator === "avg") {
  116. self.result = self.result / self.count;
  117. self.finilized = true;
  118. }
  119. obj[propName] = self.result;
  120. }
  121. };
  122. function ArrayOfFieldsets(trimByCollect, caseSensitive, skipSort, dimension, fieldName) {
  123. var iField, dimensionLength = dimension.length, dimensionItem, self = this,
  124. stringCompare = function (a, b) {
  125. var a1 = a, b1 = b;
  126. if (a1 == null) { a1 = ""; } // we will place undefined and null values as the lowest TOGETHER with ""
  127. if (b1 == null) { b1 = ""; }
  128. // be sure that we have no other input data (Number, Date and so on)
  129. a1 = String(a1);
  130. b1 = String(b1);
  131. if (!this.caseSensitive) {
  132. a1 = a1.toUpperCase();
  133. b1 = b1.toUpperCase();
  134. }
  135. if (a1 === b1) {
  136. if (a === b) {//typeof a === typeof b) {
  137. return 0;
  138. }
  139. // either a or b is undefined or null
  140. if (a === undefined) { return -1; } // make undefined less as all other
  141. if (b === undefined) { return 1; }
  142. if (a === null) { return -1; } // make null less as all other with the exception undefined
  143. if (b === null) { return 1; }
  144. }
  145. if (a1 < b1) {
  146. return -1;
  147. }
  148. return 1;
  149. },
  150. numberCompare = function (a, b) {
  151. a = Number(a);
  152. b = Number(b);
  153. if (a === b) {
  154. return 0;
  155. }
  156. if (a < b) {
  157. return -1;
  158. }
  159. return 1;
  160. },
  161. integerCompare = function (a, b) {
  162. a = Math.floor(Number(a));
  163. b = Math.floor(Number(b));
  164. if (a === b) {
  165. return 0;
  166. }
  167. if (a < b) {
  168. return -1;
  169. }
  170. return 1;
  171. };
  172. self.items = [];
  173. self.indexesOfSourceData = [];
  174. self.trimByCollect = trimByCollect;
  175. self.caseSensitive = caseSensitive;
  176. self.skipSort = skipSort;
  177. self.fieldLength = dimensionLength;
  178. self.fieldNames = new Array(dimensionLength);
  179. self.fieldSortDirection = new Array(dimensionLength);
  180. self.fieldCompare = new Array(dimensionLength); // 0 - number, 1 - integer, 2 - string, one can extend for Date and other
  181. for (iField = 0; iField < dimensionLength; iField++) {
  182. dimensionItem = dimension[iField];
  183. self.fieldNames[iField] = dimensionItem[fieldName || "dataName"];
  184. switch (dimensionItem.sorttype) {
  185. case "integer":
  186. case "int":
  187. self.fieldCompare[iField] = integerCompare;
  188. break;
  189. case "number":
  190. case "currency":
  191. case "float":
  192. self.fieldCompare[iField] = numberCompare;
  193. break;
  194. default:
  195. self.fieldCompare[iField] = $.isFunction(dimensionItem.compare) ? dimensionItem.compare : stringCompare;
  196. break;
  197. }
  198. self.fieldSortDirection[iField] = dimensionItem.sortorder === "desc" ? -1 : 1;
  199. }
  200. }
  201. ArrayOfFieldsets.prototype.compareVectorsEx = function (vector1, vector2) {
  202. var self = this, fieldLength = self.fieldLength, iField, compareResult;
  203. for (iField = 0; iField < fieldLength; iField++) {
  204. compareResult = self.fieldCompare[iField](vector1[iField], vector2[iField]);
  205. if (compareResult !== 0) {
  206. return {
  207. index: iField,
  208. result: compareResult
  209. };
  210. }
  211. }
  212. return {
  213. index: -1,
  214. result: 0
  215. };
  216. };
  217. ArrayOfFieldsets.prototype.getIndexOfDifferences = function (vector1, vector2) {
  218. if (vector2 === null || vector1 === null) {
  219. return 0;
  220. }
  221. return this.compareVectorsEx(vector1, vector2).index;
  222. };
  223. ArrayOfFieldsets.prototype.compareVectors = function (vector1, vector2) {
  224. var compareRestlts = this.compareVectorsEx(vector1, vector2),
  225. sortDirection = compareRestlts.index >= 0 ? this.fieldSortDirection[compareRestlts.index] : 1;
  226. return sortDirection > 0 ? compareRestlts.result : -compareRestlts.result;
  227. };
  228. ArrayOfFieldsets.prototype.getItem = function (index) {
  229. return this.items[index];
  230. };
  231. ArrayOfFieldsets.prototype.getIndexLength = function () {
  232. return this.items.length;
  233. };
  234. ArrayOfFieldsets.prototype.getIndexesOfSourceData = function (index) {
  235. return this.indexesOfSourceData[index];
  236. };
  237. ArrayOfFieldsets.prototype.createDataIndex = function (data) {
  238. var self = this, iRow, nRows = data.length, fieldLength = self.fieldLength, values, v,
  239. fieldNames = self.fieldNames, indexesOfSourceData = self.indexesOfSourceData, iField, compareResult, i, item,
  240. items = self.items, iMin, iMax;
  241. for (iRow = 0; iRow < nRows; iRow++) {
  242. item = data[iRow];
  243. // build the set of fields with data of the current item
  244. values = new Array(fieldLength);
  245. for (iField = 0; iField < fieldLength; iField++) {
  246. v = item[fieldNames[iField]];
  247. if (v !== undefined) {
  248. if (typeof v === "string" && self.trimByCollect) {
  249. v = $.trim(v);
  250. }
  251. values[iField] = v;
  252. }
  253. }
  254. // compare values with items having index iMax and iMin
  255. // If we use skipSort:true option then we compare always
  256. // with iMax item only.
  257. iMin = 0;
  258. iMax = items.length - 1;
  259. if (iMax < 0) {
  260. items.push(values);
  261. indexesOfSourceData.push([iRow]);
  262. continue;
  263. }
  264. compareResult = self.compareVectors(values, items[iMax]);
  265. if (compareResult === 0) {
  266. indexesOfSourceData[iMax].push(iRow);
  267. continue;
  268. }
  269. if (compareResult === 1 || self.skipSort) {
  270. // in case of the empty array this.items or if the values is larger as the
  271. // the max (last) element of this.items: append values to the array this.items
  272. items.push(values);
  273. indexesOfSourceData.push([iRow]);
  274. continue;
  275. }
  276. compareResult = self.compareVectors(items[0], values);
  277. if (compareResult === 1) {
  278. // if the min (first) element values is larger as the values:
  279. // insert the values as the first element of the array this.items
  280. items.unshift(values);
  281. indexesOfSourceData.unshift([iRow]);
  282. continue;
  283. }
  284. if (compareResult === 0) {
  285. indexesOfSourceData[0].push(iRow);
  286. continue;
  287. }
  288. // we are sure that items[iMin] < values < items[iMax]
  289. while (true) {
  290. if (iMax - iMin < 2) {
  291. // no identical items are found we need to insert the item at i index
  292. items.splice(iMax, 0, values); // insert after iMin
  293. indexesOfSourceData.splice(iMax, 0, [iRow]);
  294. break;
  295. }
  296. i = Math.floor((iMin + iMax) / 2); // | 0 means Math.floor, but it's faster sometimes.
  297. compareResult = self.compareVectors(items[i], values);
  298. if (compareResult === 0) {
  299. indexesOfSourceData[i].push(iRow);
  300. break;
  301. }
  302. if (compareResult === 1) {
  303. iMax = i;
  304. } else {
  305. iMin = i;
  306. }
  307. }
  308. }
  309. };
  310. jgrid.extend({
  311. pivotSetup: function (data, options) {
  312. // data should come in json format
  313. // The function return the new colModel and the transformed data
  314. // again with group setup options which then will be passed to the grid
  315. var self = this[0], isArray = $.isArray, summaries = {},
  316. groupingView = {
  317. groupField: [],
  318. groupSummary: [],
  319. groupSummaryPos: []
  320. },
  321. groupOptions = {
  322. grouping: true,
  323. groupingView: groupingView
  324. },
  325. o = $.extend({
  326. totals: false, // replacement for rowTotals. totalText and totalHeader can be used additionally
  327. useColSpanStyle: false,
  328. trimByCollect: true,
  329. skipSortByX: false,
  330. skipSortByY: false,
  331. caseSensitive: false,
  332. footerTotals: false, // replacement colTotals. footerAggregator option and totalText properties of xDimension[i] can be used additionally
  333. groupSummary: true,
  334. groupSummaryPos: "header",
  335. frozenStaticCols: false,
  336. defaultFormatting: true,
  337. data: data
  338. }, options || {}),
  339. row, i, k, nRows = data.length, x, y, cm, iRow, cmName, iXData, itemXData, pivotInfos, rows,
  340. xDimension = o.xDimension, yDimension = o.yDimension, aggregates = o.aggregates, aggrContext,
  341. isRowTotal = o.totalText || o.totals || o.rowTotals || o.totalHeader, aggrTotal, gi,
  342. xlen = isArray(xDimension) ? xDimension.length : 0,
  343. ylen = isArray(yDimension) ? yDimension.length : 0,
  344. aggrlen = isArray(aggregates) ? aggregates.length : 0,
  345. headerLevels = ylen - (aggrlen === 1 ? 1 : 0),
  346. colHeaders = [], hasGroupTotal = [], colModel = [], outputItems = [],
  347. additionalProperties = ["pivotInfos"],
  348. aggrContextTotalRows = new Array(aggrlen), aggrContextGroupTotalRows = new Array(ylen),
  349. xIndexLength, indexesOfDataWithTheSameXValues, iYData, itemYData, indexesOfDataWithTheSameYValues,
  350. iRows, agr, outputItem, previousY, groupHeaders, iRowsY, xIndex, yIndex, yIndexLength,
  351. indexDataBy = function (dimension, skipSort, compareVectors) {
  352. var index = new ArrayOfFieldsets(o.trimByCollect, o.caseSensitive, skipSort, dimension);
  353. if ($.isFunction(compareVectors)) {
  354. index.compareVectorsEx = compareVectors;
  355. }
  356. index.createDataIndex(data);
  357. return index;
  358. },
  359. buildColModelItem = function (colType, agr1, iAggr, level, iyData) {
  360. var label, name, cmItem;
  361. switch (colType) {
  362. case 1: // total group
  363. label = yDimension[level].totalText || "{0} {1} {2}";
  364. name = "y" + iyData + "t" + level;
  365. break;
  366. case 2: // grand total
  367. label = o.totalText || "{0}";
  368. name = "t";
  369. break;
  370. //case 0: // standard column
  371. default:
  372. label = aggrlen > 1 ?
  373. agr1.label || "{0}" :
  374. ($.isFunction(yDimension[level].label) ?
  375. yDimension[level].label :
  376. yIndex.getItem(iyData)[level]);
  377. name = "y" + iyData;
  378. break;
  379. }
  380. cmItem = $.extend({}, agr1, {
  381. name: name + (aggrlen > 1 ? "a" + iAggr : ""),
  382. label: $.isFunction(label) ?
  383. (label.call(self, colType === 2 ?
  384. { aggregate: agr1, iAggregate: iAggr, pivotOptions: o } :
  385. (colType === 1 ?
  386. { yIndex: yIndex.getItem(iyData), aggregate: agr1, iAggregate: iAggr, yLevel: level, pivotOptions: o } :
  387. { yData: yIndex.getItem(iyData)[level], yIndex: yIndex.getItem(iyData), yLevel: level, pivotOptions: o }))) :
  388. (jgrid.template.apply(self, colType === 2 ?
  389. [String(label), agr1.aggregator, agr1.member, iAggr] :
  390. [String(label), agr1.aggregator, agr1.member, yIndex.getItem(iyData)[level], level]))
  391. });
  392. delete cmItem.member;
  393. delete cmItem.aggregator;
  394. return cmItem;
  395. },
  396. addColumnToColModel = function (colType, level, iyData) {
  397. var iAggr, aggregate;
  398. for (iAggr = 0; iAggr < aggrlen; iAggr++) {
  399. aggregate = aggregates[iAggr];
  400. if (aggregate.template === undefined && aggregate.formatter === undefined && o.defaultFormatting) {
  401. aggregate.template = aggregate.aggregator === "count" ? "integer" : "number";
  402. }
  403. colModel.push(buildColModelItem(colType, aggregate, iAggr, level, iyData));
  404. }
  405. },
  406. addGroupTotalHeaders = function (iyData, level, previousY1) {
  407. var iLevel, j, totalHeader, headerOnTop;
  408. for (iLevel = headerLevels - 1; iLevel >= level; iLevel--) {
  409. if (hasGroupTotal[iLevel]) {
  410. for (j = 0; j <= iLevel; j++) {
  411. groupHeaders = colHeaders[j].groupHeaders;
  412. groupHeaders[groupHeaders.length - 1].numberOfColumns += aggrlen;
  413. }
  414. y = yDimension[iLevel];
  415. totalHeader = y.totalHeader;
  416. headerOnTop = y.headerOnTop;
  417. for (j = iLevel + 1; j <= headerLevels - 1; j++) {
  418. colHeaders[j].groupHeaders.push({
  419. titleText: ((headerOnTop && j === iLevel + 1) || (!headerOnTop && j === headerLevels - 1)) ?
  420. ($.isFunction(totalHeader) ?
  421. totalHeader.call(self, previousY1, iLevel) :
  422. jgrid.template.call(self, String(totalHeader || ""), previousY1[iLevel], iLevel)) :
  423. "",
  424. startColumnName: "y" + (iyData - 1) + "t" + iLevel + (aggrlen === 1 ? "" : "a0"),
  425. numberOfColumns: aggrlen
  426. });
  427. }
  428. }
  429. }
  430. },
  431. createTotalAggregation = function (iAggr) {
  432. var aggrGroup = new Aggregation(aggregates[iAggr].aggregator === "count" ? "sum" : aggregates[iAggr].aggregator, self, options);
  433. aggrGroup.groupInfo = { iRows: [], rows: [], ys: [], iYs: [] };
  434. return aggrGroup;
  435. },
  436. initializeGroupTotals = function () {
  437. var iLevel, iAggr;
  438. for (iLevel = headerLevels - 1; iLevel >= 0; iLevel--) {
  439. if (hasGroupTotal[iLevel]) {
  440. if (aggrContextGroupTotalRows[iLevel] == null) {// first call
  441. aggrContextGroupTotalRows[iLevel] = new Array(aggrlen);
  442. }
  443. for (iAggr = 0; iAggr < aggrlen; iAggr++) {
  444. aggrContextGroupTotalRows[iLevel][iAggr] = createTotalAggregation(iAggr);
  445. }
  446. }
  447. }
  448. },
  449. finalizeGroupTotals = function (iyData, itemYData1, previousY1, iAggr) {
  450. var iLevel, level = yIndex.getIndexOfDifferences(itemYData1, previousY1), fieldName, aggrGroup;
  451. if (previousY1 !== null) {
  452. // test whether the group is finished and one need to get results
  453. level = Math.max(level, 0); // change -1 to 0 for the last call (itemYData === previousY)
  454. for (iLevel = headerLevels - 1; iLevel >= level; iLevel--) {
  455. fieldName = "y" + iyData + "t" + iLevel + (aggrlen > 1 ? "a" + iAggr : "");
  456. if (hasGroupTotal[iLevel] && outputItem[fieldName] === undefined) {
  457. aggrGroup = aggrContextGroupTotalRows[iLevel][iAggr];
  458. aggrGroup.getResult(outputItem, fieldName);
  459. outputItem.pivotInfos[fieldName] = {
  460. colType: 1,
  461. iA: iAggr,
  462. a: aggregates[iAggr],
  463. level: iLevel,
  464. iRows: aggrGroup.groupInfo.iRows,
  465. rows: aggrGroup.groupInfo.rows,
  466. ys: aggrGroup.groupInfo.ys,
  467. iYs: aggrGroup.groupInfo.iYs
  468. };
  469. if (itemYData1 !== previousY1) {
  470. aggrContextGroupTotalRows[iLevel][iAggr] = createTotalAggregation(iAggr);
  471. }
  472. }
  473. }
  474. }
  475. },
  476. calculateGroupTotals = function (itemYData1, previousY1, aggregate, iAggr, row1, iRow1, iyData) {
  477. // the method will be called at the first time with previousY === null in every output row
  478. // and finally with itemYData === previousY for getting results of all aggregation contexts
  479. var iLevel, aggrGroup, groupInfo;
  480. if (itemYData1 !== previousY1) { // not the last call in the row
  481. for (iLevel = headerLevels - 1; iLevel >= 0; iLevel--) {
  482. if (hasGroupTotal[iLevel]) {
  483. aggrGroup = aggrContextGroupTotalRows[iLevel][iAggr];
  484. aggrGroup.calc(row1[aggregate.member], aggregate.member, row1, iRow1, data);
  485. groupInfo = aggrGroup.groupInfo;
  486. if ($.inArray(iyData, groupInfo.iYs) < 0) {
  487. groupInfo.iYs.push(iyData);
  488. groupInfo.ys.push(itemYData1);
  489. }
  490. if ($.inArray(iRow1, groupInfo.iRows) < 0) {
  491. groupInfo.iRows.push(iRow1);
  492. groupInfo.rows.push(row1);
  493. }
  494. }
  495. }
  496. }
  497. };
  498. if (xlen === 0 || aggrlen === 0) {
  499. throw ("xDimension or aggregates options are not set!");
  500. }
  501. // ****************************************************************
  502. // The step 1: scan input data and build the list of unique indexes
  503. // ****************************************************************
  504. xIndex = indexDataBy(xDimension, o.skipSortByX, o.compareVectorsByX);
  505. yIndex = indexDataBy(yDimension, o.skipSortByY, o.compareVectorsByY);
  506. // save to be used probably later
  507. options.xIndex = xIndex;
  508. options.yIndex = yIndex;
  509. // *******************************************
  510. // The step 2: build colModel and groupOptions
  511. // *******************************************
  512. // fill the first xlen columns of colModel and fill the groupOptions
  513. // the names of the first columns will be "x"+i. The first column have the name "x0".
  514. for (i = 0; i < xlen; i++) {
  515. x = xDimension[i];
  516. cm = {
  517. name: "x" + i,
  518. label: x.label != null ?
  519. ($.isFunction(x.label) ? x.label.call(self, x, i, o) : x.label) :
  520. x.dataName,
  521. frozen: o.frozenStaticCols
  522. };
  523. if (i < xlen - 1 && !x.skipGrouping && !x.additionalProperty) {
  524. // based on xDimension levels build grouping
  525. groupingView.groupField.push(cm.name);
  526. groupingView.groupSummary.push(o.groupSummary);
  527. groupingView.groupSummaryPos.push(o.groupSummaryPos);
  528. }
  529. cm = $.extend(cm, x);
  530. delete cm.dataName;
  531. delete cm.footerText;
  532. if (!x.additionalProperty) {
  533. colModel.push(cm);
  534. groupOptions.sortname = cm.name;
  535. } else {
  536. additionalProperties.push(cm.name);
  537. }
  538. }
  539. if (xlen < 2) {
  540. groupOptions.grouping = false; // no grouping is needed
  541. }
  542. groupingView.hideFirstGroupCol = true;
  543. // Fill hasGroupTotal and groupColumnsPerLevel arrays
  544. // The hasGroupTotal just shows whether one need create additional totals for every group.
  545. for (i = 0; i < ylen; i++) {
  546. y = yDimension[i];
  547. hasGroupTotal.push(y.totals || y.rowTotals || y.totalText || y.totalHeader ? true : false);
  548. }
  549. // fill other columns of colModel based on collected uniqueYData and aggregates options
  550. // the names of the first columns will be "y"+i in case of one aggregate and
  551. // "y"+i+"a"+k in case of multiple aggregates. The name of the first "y"-column is "y0" or "y0a0"
  552. // The next function build and insert item in colModel
  553. // colType: 0 - means standard column, 1 - total group, 2 - grand total
  554. previousY = yIndex.getItem(0);
  555. addColumnToColModel(0, ylen - 1, 0); // add standard column
  556. yIndexLength = yIndex.getIndexLength();
  557. for (iYData = 1; iYData < yIndexLength; iYData++) {
  558. itemYData = yIndex.getItem(iYData);
  559. /*
  560. * find where (on which level) the itemYData have the differences to
  561. * the previous y (previousY). If the level has (totals:true/rowTotals:true) in yDimension
  562. * then one should insert new total columns for all levels starting with the highest one
  563. * (yDimension[yDimension.length-1]) and till the current one.
  564. */
  565. i = yIndex.getIndexOfDifferences(itemYData, previousY);
  566. for (k = headerLevels - 1; k >= i; k--) {
  567. if (hasGroupTotal[k]) {
  568. addColumnToColModel(1, k, iYData - 1); // add group total columns
  569. }
  570. }
  571. previousY = itemYData;
  572. addColumnToColModel(0, ylen - 1, iYData); // add standard column
  573. }
  574. // finalize of all totals
  575. for (i = headerLevels - 1; i >= 0; i--) {
  576. if (hasGroupTotal[i]) {
  577. addColumnToColModel(1, i, yIndexLength - 1); // add the last group total columns
  578. }
  579. }
  580. // add total columns calculated over all data of the row
  581. if (isRowTotal) {
  582. addColumnToColModel(2);
  583. }
  584. // ********************************
  585. // The step 3: build column headers
  586. // ********************************
  587. // initialize colHeaders
  588. previousY = yIndex.getItem(0);
  589. for (k = 0; k < headerLevels; k++) {
  590. colHeaders.push({
  591. useColSpanStyle: o.useColSpanStyle,
  592. groupHeaders: [{
  593. titleText: ($.isFunction(yDimension[k].label) ?
  594. yDimension[k].label.call(self, { yData: previousY[k], yIndex: previousY, yLevel: k, pivotOptions: o }) :
  595. previousY[k]),
  596. startColumnName: aggrlen === 1 ? "y0" : "y0a0",
  597. numberOfColumns: aggrlen
  598. }]
  599. });
  600. }
  601. for (iYData = 1; iYData < yIndexLength; iYData++) {
  602. itemYData = yIndex.getItem(iYData);
  603. i = yIndex.getIndexOfDifferences(itemYData, previousY);
  604. // We placed QNIQUE data in uniqueYData array.
  605. // So we always find a difference on one level
  606. addGroupTotalHeaders(iYData, i, previousY);
  607. // add column headers which corresponds the main data
  608. for (k = headerLevels - 1; k >= i; k--) {
  609. colHeaders[k].groupHeaders.push({
  610. titleText: ($.isFunction(yDimension[k].label) ?
  611. yDimension[k].label.call(self, { yData: itemYData[k], yIndex: itemYData, yLevel: k, pivotOptions: o }) :
  612. itemYData[k]),
  613. startColumnName: "y" + iYData + (aggrlen === 1 ? "" : "a0"),
  614. numberOfColumns: aggrlen
  615. });
  616. }
  617. for (k = 0; k < i; k++) {
  618. groupHeaders = colHeaders[k].groupHeaders;
  619. groupHeaders[groupHeaders.length - 1].numberOfColumns += aggrlen;
  620. }
  621. previousY = itemYData;
  622. }
  623. addGroupTotalHeaders(yIndexLength, 0, previousY);
  624. // fill groupHeaders without taking in consideration group total columns
  625. if (isRowTotal) {
  626. for (i = 0; i < headerLevels; i++) {
  627. colHeaders[i].groupHeaders.push({
  628. titleText: (i < headerLevels - 1 ? "" : o.totalHeader || ""),
  629. startColumnName: "t" + (aggrlen === 1 ? "" : "a0"),
  630. numberOfColumns: aggrlen
  631. });
  632. }
  633. }
  634. // *****************************
  635. // The step 4: fill data of grid
  636. // *****************************
  637. xIndexLength = xIndex.getIndexLength();
  638. for (iXData = 0; iXData < xIndexLength; iXData++) {
  639. itemXData = xIndex.getItem(iXData);
  640. pivotInfos = { iX: iXData, x: itemXData };
  641. outputItem = { pivotInfos: pivotInfos }; // item of output data
  642. // itemXData corresponds to the row of output data
  643. for (i = 0; i < xlen; i++) {
  644. // fill first columns of data
  645. outputItem["x" + i] = itemXData[i];
  646. }
  647. indexesOfDataWithTheSameXValues = xIndex.getIndexesOfSourceData(iXData);
  648. // The rows of input data with indexes from indexesOfDataWithTheSameXValues contains itemXData
  649. // Now we build columns of itemXData row
  650. if (isRowTotal) {
  651. for (k = 0; k < aggrlen; k++) {
  652. aggrContextTotalRows[k] = createTotalAggregation(k);
  653. }
  654. }
  655. previousY = null;
  656. initializeGroupTotals();
  657. for (iYData = 0; iYData < yIndexLength; iYData++) {
  658. itemYData = yIndex.getItem(iYData);
  659. indexesOfDataWithTheSameYValues = yIndex.getIndexesOfSourceData(iYData);
  660. // we calculate aggregate in every itemYData
  661. for (k = 0; k < aggrlen; k++) {
  662. if (previousY !== null) { // empty input data
  663. finalizeGroupTotals(iYData - 1, itemYData, previousY, k);
  664. }
  665. iRows = [];
  666. for (i = 0; i < indexesOfDataWithTheSameYValues.length; i++) {
  667. iRowsY = indexesOfDataWithTheSameYValues[i];
  668. if ($.inArray(iRowsY, indexesOfDataWithTheSameXValues) >= 0) {
  669. iRows.push(iRowsY);
  670. }
  671. }
  672. if (iRows.length > 0) {
  673. // iRows array have all indexes of input data which have both itemXData and itemYData
  674. // We need calculate aggregate agr over all the items
  675. rows = new Array(iRows.length);
  676. agr = aggregates[k];
  677. aggrContext = new Aggregation(agr.aggregator, self, options);
  678. for (iRow = 0; iRow < iRows.length; iRow++) {
  679. i = iRows[iRow];
  680. row = data[i];
  681. rows[iRow] = row;
  682. aggrContext.calc(row[agr.member], agr.member, row, i, data);
  683. if (isRowTotal) {
  684. aggrTotal = aggrContextTotalRows[k];
  685. aggrTotal.calc(row[agr.member], agr.member, row, i, data);
  686. gi = aggrTotal.groupInfo;
  687. if ($.inArray(i, gi.iYs) < 0) {
  688. gi.iYs.push(iYData);
  689. gi.ys.push(itemYData);
  690. }
  691. if ($.inArray(i, gi.iRows) < 0) {
  692. gi.iRows.push(i);
  693. gi.rows.push(row);
  694. }
  695. }
  696. calculateGroupTotals(itemYData, previousY, agr, k, row, i, iYData);
  697. }
  698. cmName = "y" + iYData + (aggrlen === 1 ? "" : "a" + k);
  699. aggrContext.getResult(outputItem, cmName);
  700. pivotInfos[cmName] = {
  701. colType: 0, // standard row
  702. iY: iYData,
  703. y: itemYData,
  704. iA: k,
  705. a: agr,
  706. iRows: iRows,
  707. rows: rows
  708. };
  709. }
  710. }
  711. previousY = itemYData;
  712. }
  713. if (previousY !== null) { // if non-empty input data
  714. for (k = 0; k < aggrlen; k++) {
  715. finalizeGroupTotals(yIndexLength - 1, previousY, previousY, k);
  716. }
  717. }
  718. if (isRowTotal) {
  719. for (k = 0; k < aggrlen; k++) {
  720. cmName = "t" + (aggrlen === 1 ? "" : "a" + k);
  721. aggrTotal = aggrContextTotalRows[k];
  722. aggrTotal.getResult(outputItem, cmName);
  723. gi = aggrTotal.groupInfo;
  724. pivotInfos[cmName] = {
  725. colType: 2, // row total
  726. iA: k,
  727. a: aggregates[k],
  728. iRows: gi.iRows,
  729. rows: gi.rows,
  730. iYs: gi.iYs,
  731. ys: gi.ys
  732. };
  733. }
  734. }
  735. outputItems.push(outputItem);
  736. }
  737. // *****************************
  738. // The step 5: fill total footer
  739. // *****************************
  740. if (o.footerTotals || o.colTotals) {
  741. nRows = outputItems.length;
  742. for (i = 0; i < xlen; i++) {
  743. summaries["x" + i] = xDimension[i].footerText || "";
  744. }
  745. for (i = xlen; i < colModel.length; i++) {
  746. cmName = colModel[i].name;
  747. aggrContext = new Aggregation(o.footerAggregator || "sum", self, options);
  748. for (iRow = 0; iRow < nRows; iRow++) {
  749. outputItem = outputItems[iRow];
  750. aggrContext.calc(outputItem[cmName], cmName, outputItem, iRow, outputItems);
  751. }
  752. aggrContext.getResult(summaries, cmName);
  753. }
  754. }
  755. // return the final result.
  756. options.colHeaders = colHeaders;
  757. return {
  758. colModel: colModel,
  759. additionalProperties: additionalProperties,
  760. options: options,
  761. rows: outputItems,
  762. groupOptions: groupOptions,
  763. groupHeaders: colHeaders,
  764. summary: summaries
  765. };
  766. },
  767. jqPivot: function (data, pivotOpt, gridOpt, ajaxOpt) {
  768. return this.each(function () {
  769. var $t = this, $self = $($t), $j = $.fn.jqGrid;
  770. function pivot() {
  771. var pivotGrid = $j.pivotSetup.call($self, data, pivotOpt),
  772. gHead = pivotGrid.groupHeaders,
  773. assocArraySize = function (obj) {
  774. // http://stackoverflow.com/a/6700/11236
  775. var size = 0, key;
  776. for (key in obj) {
  777. if (obj.hasOwnProperty(key)) {
  778. size++;
  779. }
  780. }
  781. return size;
  782. },
  783. footerrow = assocArraySize(pivotGrid.summary) > 0 ? true : false,
  784. groupingView = pivotGrid.groupOptions.groupingView,
  785. query = jgrid.from.call($t, pivotGrid.rows), i;
  786. if (!pivotOpt.skipSortByX) {
  787. for (i = 0; i < groupingView.groupField.length; i++) {
  788. query.orderBy(groupingView.groupField[i],
  789. gridOpt != null && gridOpt.groupingView && gridOpt.groupingView.groupOrder != null && gridOpt.groupingView.groupOrder[i] === "desc" ? "d" : "a",
  790. "text",
  791. "");
  792. }
  793. }
  794. pivotOpt.data = data;
  795. $j.call($self, $.extend(true, {
  796. datastr: $.extend(query.select(), footerrow ? { userdata: pivotGrid.summary } : {}),
  797. datatype: "jsonstring",
  798. footerrow: footerrow,
  799. userDataOnFooter: footerrow,
  800. colModel: pivotGrid.colModel,
  801. additionalProperties: pivotGrid.additionalProperties,
  802. pivotOptions: pivotGrid.options,
  803. viewrecords: true,
  804. sortname: pivotOpt.xDimension[0].dataName // ?????
  805. }, pivotGrid.groupOptions, gridOpt || {}));
  806. if (gHead.length) {
  807. for (i = 0; i < gHead.length; i++) {
  808. // Multiple calls of setGroupHeaders for one grid are wrong,
  809. // but there are produces good results in case of usage
  810. // useColSpanStyle: false option. The rowspan values
  811. // needed be increased in case of usage useColSpanStyle: true
  812. if (gHead[i] && gHead[i].groupHeaders.length) {
  813. $j.setGroupHeaders.call($self, gHead[i]);
  814. }
  815. }
  816. }
  817. if (pivotOpt.frozenStaticCols) {
  818. $j.setFrozenColumns.call($self);
  819. }
  820. }
  821. if (typeof data === "string") {
  822. $.ajax($.extend({
  823. url: data,
  824. dataType: "json",
  825. success: function (data1) {
  826. data = jgrid.getAccessor(data1, ajaxOpt && ajaxOpt.reader ? ajaxOpt.reader : "rows");
  827. pivot();
  828. }
  829. }, ajaxOpt || {}));
  830. } else {
  831. pivot();
  832. }
  833. });
  834. }
  835. });
  836. // end module grid.pivot
  837. }));