123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861 |
- /**
- * jqGrid pivot functions
- * Copyright (c) 2008-2014, Tony Tomov, tony@trirand.com, http://trirand.com/blog/
- * Copyright (c) 2014-2018, Oleg Kiriljuk, oleg.kiriljuk@ok-soft-gmbh.com
- * The modul is created initially by Tony Tomov and it's full rewritten
- * for free jqGrid: https://github.com/free-jqgrid/jqGrid by Oleg Kiriljuk
- * Dual licensed under the MIT and GPL licenses:
- * http://www.opensource.org/licenses/mit-license.php
- * http://www.gnu.org/licenses/gpl-2.0.html
- */
- /*jshint eqeqeq:false */
- /*global jQuery, define, exports, module, require */
- /*jslint eqeq: true, plusplus: true, continue: true, white: true */
- (function (factory) {
- "use strict";
- if (typeof define === "function" && define.amd) {
- // AMD. Register as an anonymous module.
- define([
- "jquery",
- "./jquery.fmatter",
- "./grid.grouping"
- ], factory);
- } else if (typeof module === "object" && module.exports) {
- // Node/CommonJS
- module.exports = function (root, $) {
- if (!root) {
- root = window;
- }
- if ($ === undefined) {
- // require("jquery") returns a factory that requires window to
- // build a jQuery instance, we normalize how we use modules
- // that require this pattern but the window provided is a noop
- // if it's defined (how jquery works)
- $ = typeof window !== "undefined" ?
- require("jquery") :
- require("jquery")(root);
- }
- require("./jquery.fmatter");
- require("./grid.grouping");
- factory($);
- return $;
- };
- } else {
- // Browser globals
- factory(jQuery);
- }
- }(function ($) {
- "use strict";
- var jgrid = $.jgrid;
- // begin module grid.pivot
- function Aggregation(aggregator, context, pivotOptions) {
- if (!(this instanceof Aggregation)) {
- return new Aggregation(aggregator);
- }
- //this.result = undefined;
- //this.count = undefined;
- this.aggregator = aggregator;
- this.finilized = false;
- this.context = context;
- this.pivotOptions = pivotOptions;
- }
- Aggregation.prototype.calc = function (v, fieldName, row, iRow, rows) {
- var self = this;
- if (v !== undefined) {
- self.result = self.result || 0; // change undefined to 0
- v = parseFloat(v);
- switch (self.aggregator) {
- case "sum":
- self.result += v;
- break;
- case "count":
- self.result++;
- break;
- case "avg":
- if (self.finilized) {
- self.count = self.count || 0; // change undefined to 0
- self.result = (self.result * self.count + v) / (self.count + 1);
- self.count++;
- } else {
- self.result += v;
- self.count = self.count || 0; // change undefined to 0
- self.count++;
- }
- break;
- case "min":
- self.result = Math.min(self.result, v);
- break;
- case "max":
- self.result = Math.max(self.result, v);
- break;
- default:
- if ($.isFunction(self.aggregator)) {
- self.result = self.aggregator.call(self.context, {
- previousResult: self.result,
- value: v,
- fieldName: fieldName,
- item: row,
- iItem: iRow,
- items: rows
- });
- }
- break;
- }
- }
- };
- Aggregation.prototype.getResult = function (obj, propName, forceSaving) {
- var self = this;
- if (self.result !== undefined || forceSaving) {
- if (forceSaving) {
- if (self.result !== undefined) {
- self.result = 0;
- self.count = 0;
- }
- }
- if (self.result !== undefined && !self.finilized && self.aggregator === "avg") {
- self.result = self.result / self.count;
- self.finilized = true;
- }
- obj[propName] = self.result;
- }
- };
- function ArrayOfFieldsets(trimByCollect, caseSensitive, skipSort, dimension, fieldName) {
- var iField, dimensionLength = dimension.length, dimensionItem, self = this,
- stringCompare = function (a, b) {
- var a1 = a, b1 = b;
- if (a1 == null) { a1 = ""; } // we will place undefined and null values as the lowest TOGETHER with ""
- if (b1 == null) { b1 = ""; }
- // be sure that we have no other input data (Number, Date and so on)
- a1 = String(a1);
- b1 = String(b1);
- if (!this.caseSensitive) {
- a1 = a1.toUpperCase();
- b1 = b1.toUpperCase();
- }
- if (a1 === b1) {
- if (a === b) {//typeof a === typeof b) {
- return 0;
- }
- // either a or b is undefined or null
- if (a === undefined) { return -1; } // make undefined less as all other
- if (b === undefined) { return 1; }
- if (a === null) { return -1; } // make null less as all other with the exception undefined
- if (b === null) { return 1; }
- }
- if (a1 < b1) {
- return -1;
- }
- return 1;
- },
- numberCompare = function (a, b) {
- a = Number(a);
- b = Number(b);
- if (a === b) {
- return 0;
- }
- if (a < b) {
- return -1;
- }
- return 1;
- },
- integerCompare = function (a, b) {
- a = Math.floor(Number(a));
- b = Math.floor(Number(b));
- if (a === b) {
- return 0;
- }
- if (a < b) {
- return -1;
- }
- return 1;
- };
- self.items = [];
- self.indexesOfSourceData = [];
- self.trimByCollect = trimByCollect;
- self.caseSensitive = caseSensitive;
- self.skipSort = skipSort;
- self.fieldLength = dimensionLength;
- self.fieldNames = new Array(dimensionLength);
- self.fieldSortDirection = new Array(dimensionLength);
- self.fieldCompare = new Array(dimensionLength); // 0 - number, 1 - integer, 2 - string, one can extend for Date and other
- for (iField = 0; iField < dimensionLength; iField++) {
- dimensionItem = dimension[iField];
- self.fieldNames[iField] = dimensionItem[fieldName || "dataName"];
- switch (dimensionItem.sorttype) {
- case "integer":
- case "int":
- self.fieldCompare[iField] = integerCompare;
- break;
- case "number":
- case "currency":
- case "float":
- self.fieldCompare[iField] = numberCompare;
- break;
- default:
- self.fieldCompare[iField] = $.isFunction(dimensionItem.compare) ? dimensionItem.compare : stringCompare;
- break;
- }
- self.fieldSortDirection[iField] = dimensionItem.sortorder === "desc" ? -1 : 1;
- }
- }
- ArrayOfFieldsets.prototype.compareVectorsEx = function (vector1, vector2) {
- var self = this, fieldLength = self.fieldLength, iField, compareResult;
- for (iField = 0; iField < fieldLength; iField++) {
- compareResult = self.fieldCompare[iField](vector1[iField], vector2[iField]);
- if (compareResult !== 0) {
- return {
- index: iField,
- result: compareResult
- };
- }
- }
- return {
- index: -1,
- result: 0
- };
- };
- ArrayOfFieldsets.prototype.getIndexOfDifferences = function (vector1, vector2) {
- if (vector2 === null || vector1 === null) {
- return 0;
- }
- return this.compareVectorsEx(vector1, vector2).index;
- };
- ArrayOfFieldsets.prototype.compareVectors = function (vector1, vector2) {
- var compareRestlts = this.compareVectorsEx(vector1, vector2),
- sortDirection = compareRestlts.index >= 0 ? this.fieldSortDirection[compareRestlts.index] : 1;
- return sortDirection > 0 ? compareRestlts.result : -compareRestlts.result;
- };
- ArrayOfFieldsets.prototype.getItem = function (index) {
- return this.items[index];
- };
- ArrayOfFieldsets.prototype.getIndexLength = function () {
- return this.items.length;
- };
- ArrayOfFieldsets.prototype.getIndexesOfSourceData = function (index) {
- return this.indexesOfSourceData[index];
- };
- ArrayOfFieldsets.prototype.createDataIndex = function (data) {
- var self = this, iRow, nRows = data.length, fieldLength = self.fieldLength, values, v,
- fieldNames = self.fieldNames, indexesOfSourceData = self.indexesOfSourceData, iField, compareResult, i, item,
- items = self.items, iMin, iMax;
- for (iRow = 0; iRow < nRows; iRow++) {
- item = data[iRow];
- // build the set of fields with data of the current item
- values = new Array(fieldLength);
- for (iField = 0; iField < fieldLength; iField++) {
- v = item[fieldNames[iField]];
- if (v !== undefined) {
- if (typeof v === "string" && self.trimByCollect) {
- v = $.trim(v);
- }
- values[iField] = v;
- }
- }
- // compare values with items having index iMax and iMin
- // If we use skipSort:true option then we compare always
- // with iMax item only.
- iMin = 0;
- iMax = items.length - 1;
- if (iMax < 0) {
- items.push(values);
- indexesOfSourceData.push([iRow]);
- continue;
- }
- compareResult = self.compareVectors(values, items[iMax]);
- if (compareResult === 0) {
- indexesOfSourceData[iMax].push(iRow);
- continue;
- }
- if (compareResult === 1 || self.skipSort) {
- // in case of the empty array this.items or if the values is larger as the
- // the max (last) element of this.items: append values to the array this.items
- items.push(values);
- indexesOfSourceData.push([iRow]);
- continue;
- }
- compareResult = self.compareVectors(items[0], values);
- if (compareResult === 1) {
- // if the min (first) element values is larger as the values:
- // insert the values as the first element of the array this.items
- items.unshift(values);
- indexesOfSourceData.unshift([iRow]);
- continue;
- }
- if (compareResult === 0) {
- indexesOfSourceData[0].push(iRow);
- continue;
- }
- // we are sure that items[iMin] < values < items[iMax]
- while (true) {
- if (iMax - iMin < 2) {
- // no identical items are found we need to insert the item at i index
- items.splice(iMax, 0, values); // insert after iMin
- indexesOfSourceData.splice(iMax, 0, [iRow]);
- break;
- }
- i = Math.floor((iMin + iMax) / 2); // | 0 means Math.floor, but it's faster sometimes.
- compareResult = self.compareVectors(items[i], values);
- if (compareResult === 0) {
- indexesOfSourceData[i].push(iRow);
- break;
- }
- if (compareResult === 1) {
- iMax = i;
- } else {
- iMin = i;
- }
- }
- }
- };
- jgrid.extend({
- pivotSetup: function (data, options) {
- // data should come in json format
- // The function return the new colModel and the transformed data
- // again with group setup options which then will be passed to the grid
- var self = this[0], isArray = $.isArray, summaries = {},
- groupingView = {
- groupField: [],
- groupSummary: [],
- groupSummaryPos: []
- },
- groupOptions = {
- grouping: true,
- groupingView: groupingView
- },
- o = $.extend({
- totals: false, // replacement for rowTotals. totalText and totalHeader can be used additionally
- useColSpanStyle: false,
- trimByCollect: true,
- skipSortByX: false,
- skipSortByY: false,
- caseSensitive: false,
- footerTotals: false, // replacement colTotals. footerAggregator option and totalText properties of xDimension[i] can be used additionally
- groupSummary: true,
- groupSummaryPos: "header",
- frozenStaticCols: false,
- defaultFormatting: true,
- data: data
- }, options || {}),
- row, i, k, nRows = data.length, x, y, cm, iRow, cmName, iXData, itemXData, pivotInfos, rows,
- xDimension = o.xDimension, yDimension = o.yDimension, aggregates = o.aggregates, aggrContext,
- isRowTotal = o.totalText || o.totals || o.rowTotals || o.totalHeader, aggrTotal, gi,
- xlen = isArray(xDimension) ? xDimension.length : 0,
- ylen = isArray(yDimension) ? yDimension.length : 0,
- aggrlen = isArray(aggregates) ? aggregates.length : 0,
- headerLevels = ylen - (aggrlen === 1 ? 1 : 0),
- colHeaders = [], hasGroupTotal = [], colModel = [], outputItems = [],
- additionalProperties = ["pivotInfos"],
- aggrContextTotalRows = new Array(aggrlen), aggrContextGroupTotalRows = new Array(ylen),
- xIndexLength, indexesOfDataWithTheSameXValues, iYData, itemYData, indexesOfDataWithTheSameYValues,
- iRows, agr, outputItem, previousY, groupHeaders, iRowsY, xIndex, yIndex, yIndexLength,
- indexDataBy = function (dimension, skipSort, compareVectors) {
- var index = new ArrayOfFieldsets(o.trimByCollect, o.caseSensitive, skipSort, dimension);
- if ($.isFunction(compareVectors)) {
- index.compareVectorsEx = compareVectors;
- }
- index.createDataIndex(data);
- return index;
- },
- buildColModelItem = function (colType, agr1, iAggr, level, iyData) {
- var label, name, cmItem;
- switch (colType) {
- case 1: // total group
- label = yDimension[level].totalText || "{0} {1} {2}";
- name = "y" + iyData + "t" + level;
- break;
- case 2: // grand total
- label = o.totalText || "{0}";
- name = "t";
- break;
- //case 0: // standard column
- default:
- label = aggrlen > 1 ?
- agr1.label || "{0}" :
- ($.isFunction(yDimension[level].label) ?
- yDimension[level].label :
- yIndex.getItem(iyData)[level]);
- name = "y" + iyData;
- break;
- }
- cmItem = $.extend({}, agr1, {
- name: name + (aggrlen > 1 ? "a" + iAggr : ""),
- label: $.isFunction(label) ?
- (label.call(self, colType === 2 ?
- { aggregate: agr1, iAggregate: iAggr, pivotOptions: o } :
- (colType === 1 ?
- { yIndex: yIndex.getItem(iyData), aggregate: agr1, iAggregate: iAggr, yLevel: level, pivotOptions: o } :
- { yData: yIndex.getItem(iyData)[level], yIndex: yIndex.getItem(iyData), yLevel: level, pivotOptions: o }))) :
- (jgrid.template.apply(self, colType === 2 ?
- [String(label), agr1.aggregator, agr1.member, iAggr] :
- [String(label), agr1.aggregator, agr1.member, yIndex.getItem(iyData)[level], level]))
- });
- delete cmItem.member;
- delete cmItem.aggregator;
- return cmItem;
- },
- addColumnToColModel = function (colType, level, iyData) {
- var iAggr, aggregate;
- for (iAggr = 0; iAggr < aggrlen; iAggr++) {
- aggregate = aggregates[iAggr];
- if (aggregate.template === undefined && aggregate.formatter === undefined && o.defaultFormatting) {
- aggregate.template = aggregate.aggregator === "count" ? "integer" : "number";
- }
- colModel.push(buildColModelItem(colType, aggregate, iAggr, level, iyData));
- }
- },
- addGroupTotalHeaders = function (iyData, level, previousY1) {
- var iLevel, j, totalHeader, headerOnTop;
- for (iLevel = headerLevels - 1; iLevel >= level; iLevel--) {
- if (hasGroupTotal[iLevel]) {
- for (j = 0; j <= iLevel; j++) {
- groupHeaders = colHeaders[j].groupHeaders;
- groupHeaders[groupHeaders.length - 1].numberOfColumns += aggrlen;
- }
- y = yDimension[iLevel];
- totalHeader = y.totalHeader;
- headerOnTop = y.headerOnTop;
- for (j = iLevel + 1; j <= headerLevels - 1; j++) {
- colHeaders[j].groupHeaders.push({
- titleText: ((headerOnTop && j === iLevel + 1) || (!headerOnTop && j === headerLevels - 1)) ?
- ($.isFunction(totalHeader) ?
- totalHeader.call(self, previousY1, iLevel) :
- jgrid.template.call(self, String(totalHeader || ""), previousY1[iLevel], iLevel)) :
- "",
- startColumnName: "y" + (iyData - 1) + "t" + iLevel + (aggrlen === 1 ? "" : "a0"),
- numberOfColumns: aggrlen
- });
- }
- }
- }
- },
- createTotalAggregation = function (iAggr) {
- var aggrGroup = new Aggregation(aggregates[iAggr].aggregator === "count" ? "sum" : aggregates[iAggr].aggregator, self, options);
- aggrGroup.groupInfo = { iRows: [], rows: [], ys: [], iYs: [] };
- return aggrGroup;
- },
- initializeGroupTotals = function () {
- var iLevel, iAggr;
- for (iLevel = headerLevels - 1; iLevel >= 0; iLevel--) {
- if (hasGroupTotal[iLevel]) {
- if (aggrContextGroupTotalRows[iLevel] == null) {// first call
- aggrContextGroupTotalRows[iLevel] = new Array(aggrlen);
- }
- for (iAggr = 0; iAggr < aggrlen; iAggr++) {
- aggrContextGroupTotalRows[iLevel][iAggr] = createTotalAggregation(iAggr);
- }
- }
- }
- },
- finalizeGroupTotals = function (iyData, itemYData1, previousY1, iAggr) {
- var iLevel, level = yIndex.getIndexOfDifferences(itemYData1, previousY1), fieldName, aggrGroup;
- if (previousY1 !== null) {
- // test whether the group is finished and one need to get results
- level = Math.max(level, 0); // change -1 to 0 for the last call (itemYData === previousY)
- for (iLevel = headerLevels - 1; iLevel >= level; iLevel--) {
- fieldName = "y" + iyData + "t" + iLevel + (aggrlen > 1 ? "a" + iAggr : "");
- if (hasGroupTotal[iLevel] && outputItem[fieldName] === undefined) {
- aggrGroup = aggrContextGroupTotalRows[iLevel][iAggr];
- aggrGroup.getResult(outputItem, fieldName);
- outputItem.pivotInfos[fieldName] = {
- colType: 1,
- iA: iAggr,
- a: aggregates[iAggr],
- level: iLevel,
- iRows: aggrGroup.groupInfo.iRows,
- rows: aggrGroup.groupInfo.rows,
- ys: aggrGroup.groupInfo.ys,
- iYs: aggrGroup.groupInfo.iYs
- };
- if (itemYData1 !== previousY1) {
- aggrContextGroupTotalRows[iLevel][iAggr] = createTotalAggregation(iAggr);
- }
- }
- }
- }
- },
- calculateGroupTotals = function (itemYData1, previousY1, aggregate, iAggr, row1, iRow1, iyData) {
- // the method will be called at the first time with previousY === null in every output row
- // and finally with itemYData === previousY for getting results of all aggregation contexts
- var iLevel, aggrGroup, groupInfo;
- if (itemYData1 !== previousY1) { // not the last call in the row
- for (iLevel = headerLevels - 1; iLevel >= 0; iLevel--) {
- if (hasGroupTotal[iLevel]) {
- aggrGroup = aggrContextGroupTotalRows[iLevel][iAggr];
- aggrGroup.calc(row1[aggregate.member], aggregate.member, row1, iRow1, data);
- groupInfo = aggrGroup.groupInfo;
- if ($.inArray(iyData, groupInfo.iYs) < 0) {
- groupInfo.iYs.push(iyData);
- groupInfo.ys.push(itemYData1);
- }
- if ($.inArray(iRow1, groupInfo.iRows) < 0) {
- groupInfo.iRows.push(iRow1);
- groupInfo.rows.push(row1);
- }
- }
- }
- }
- };
- if (xlen === 0 || aggrlen === 0) {
- throw ("xDimension or aggregates options are not set!");
- }
- // ****************************************************************
- // The step 1: scan input data and build the list of unique indexes
- // ****************************************************************
- xIndex = indexDataBy(xDimension, o.skipSortByX, o.compareVectorsByX);
- yIndex = indexDataBy(yDimension, o.skipSortByY, o.compareVectorsByY);
- // save to be used probably later
- options.xIndex = xIndex;
- options.yIndex = yIndex;
- // *******************************************
- // The step 2: build colModel and groupOptions
- // *******************************************
- // fill the first xlen columns of colModel and fill the groupOptions
- // the names of the first columns will be "x"+i. The first column have the name "x0".
- for (i = 0; i < xlen; i++) {
- x = xDimension[i];
- cm = {
- name: "x" + i,
- label: x.label != null ?
- ($.isFunction(x.label) ? x.label.call(self, x, i, o) : x.label) :
- x.dataName,
- frozen: o.frozenStaticCols
- };
- if (i < xlen - 1 && !x.skipGrouping && !x.additionalProperty) {
- // based on xDimension levels build grouping
- groupingView.groupField.push(cm.name);
- groupingView.groupSummary.push(o.groupSummary);
- groupingView.groupSummaryPos.push(o.groupSummaryPos);
- }
- cm = $.extend(cm, x);
- delete cm.dataName;
- delete cm.footerText;
- if (!x.additionalProperty) {
- colModel.push(cm);
- groupOptions.sortname = cm.name;
- } else {
- additionalProperties.push(cm.name);
- }
- }
- if (xlen < 2) {
- groupOptions.grouping = false; // no grouping is needed
- }
- groupingView.hideFirstGroupCol = true;
- // Fill hasGroupTotal and groupColumnsPerLevel arrays
- // The hasGroupTotal just shows whether one need create additional totals for every group.
- for (i = 0; i < ylen; i++) {
- y = yDimension[i];
- hasGroupTotal.push(y.totals || y.rowTotals || y.totalText || y.totalHeader ? true : false);
- }
- // fill other columns of colModel based on collected uniqueYData and aggregates options
- // the names of the first columns will be "y"+i in case of one aggregate and
- // "y"+i+"a"+k in case of multiple aggregates. The name of the first "y"-column is "y0" or "y0a0"
- // The next function build and insert item in colModel
- // colType: 0 - means standard column, 1 - total group, 2 - grand total
- previousY = yIndex.getItem(0);
- addColumnToColModel(0, ylen - 1, 0); // add standard column
- yIndexLength = yIndex.getIndexLength();
- for (iYData = 1; iYData < yIndexLength; iYData++) {
- itemYData = yIndex.getItem(iYData);
- /*
- * find where (on which level) the itemYData have the differences to
- * the previous y (previousY). If the level has (totals:true/rowTotals:true) in yDimension
- * then one should insert new total columns for all levels starting with the highest one
- * (yDimension[yDimension.length-1]) and till the current one.
- */
- i = yIndex.getIndexOfDifferences(itemYData, previousY);
- for (k = headerLevels - 1; k >= i; k--) {
- if (hasGroupTotal[k]) {
- addColumnToColModel(1, k, iYData - 1); // add group total columns
- }
- }
- previousY = itemYData;
- addColumnToColModel(0, ylen - 1, iYData); // add standard column
- }
- // finalize of all totals
- for (i = headerLevels - 1; i >= 0; i--) {
- if (hasGroupTotal[i]) {
- addColumnToColModel(1, i, yIndexLength - 1); // add the last group total columns
- }
- }
- // add total columns calculated over all data of the row
- if (isRowTotal) {
- addColumnToColModel(2);
- }
- // ********************************
- // The step 3: build column headers
- // ********************************
- // initialize colHeaders
- previousY = yIndex.getItem(0);
- for (k = 0; k < headerLevels; k++) {
- colHeaders.push({
- useColSpanStyle: o.useColSpanStyle,
- groupHeaders: [{
- titleText: ($.isFunction(yDimension[k].label) ?
- yDimension[k].label.call(self, { yData: previousY[k], yIndex: previousY, yLevel: k, pivotOptions: o }) :
- previousY[k]),
- startColumnName: aggrlen === 1 ? "y0" : "y0a0",
- numberOfColumns: aggrlen
- }]
- });
- }
- for (iYData = 1; iYData < yIndexLength; iYData++) {
- itemYData = yIndex.getItem(iYData);
- i = yIndex.getIndexOfDifferences(itemYData, previousY);
- // We placed QNIQUE data in uniqueYData array.
- // So we always find a difference on one level
- addGroupTotalHeaders(iYData, i, previousY);
- // add column headers which corresponds the main data
- for (k = headerLevels - 1; k >= i; k--) {
- colHeaders[k].groupHeaders.push({
- titleText: ($.isFunction(yDimension[k].label) ?
- yDimension[k].label.call(self, { yData: itemYData[k], yIndex: itemYData, yLevel: k, pivotOptions: o }) :
- itemYData[k]),
- startColumnName: "y" + iYData + (aggrlen === 1 ? "" : "a0"),
- numberOfColumns: aggrlen
- });
- }
- for (k = 0; k < i; k++) {
- groupHeaders = colHeaders[k].groupHeaders;
- groupHeaders[groupHeaders.length - 1].numberOfColumns += aggrlen;
- }
- previousY = itemYData;
- }
- addGroupTotalHeaders(yIndexLength, 0, previousY);
- // fill groupHeaders without taking in consideration group total columns
- if (isRowTotal) {
- for (i = 0; i < headerLevels; i++) {
- colHeaders[i].groupHeaders.push({
- titleText: (i < headerLevels - 1 ? "" : o.totalHeader || ""),
- startColumnName: "t" + (aggrlen === 1 ? "" : "a0"),
- numberOfColumns: aggrlen
- });
- }
- }
- // *****************************
- // The step 4: fill data of grid
- // *****************************
- xIndexLength = xIndex.getIndexLength();
- for (iXData = 0; iXData < xIndexLength; iXData++) {
- itemXData = xIndex.getItem(iXData);
- pivotInfos = { iX: iXData, x: itemXData };
- outputItem = { pivotInfos: pivotInfos }; // item of output data
- // itemXData corresponds to the row of output data
- for (i = 0; i < xlen; i++) {
- // fill first columns of data
- outputItem["x" + i] = itemXData[i];
- }
- indexesOfDataWithTheSameXValues = xIndex.getIndexesOfSourceData(iXData);
- // The rows of input data with indexes from indexesOfDataWithTheSameXValues contains itemXData
- // Now we build columns of itemXData row
- if (isRowTotal) {
- for (k = 0; k < aggrlen; k++) {
- aggrContextTotalRows[k] = createTotalAggregation(k);
- }
- }
- previousY = null;
- initializeGroupTotals();
- for (iYData = 0; iYData < yIndexLength; iYData++) {
- itemYData = yIndex.getItem(iYData);
- indexesOfDataWithTheSameYValues = yIndex.getIndexesOfSourceData(iYData);
- // we calculate aggregate in every itemYData
- for (k = 0; k < aggrlen; k++) {
- if (previousY !== null) { // empty input data
- finalizeGroupTotals(iYData - 1, itemYData, previousY, k);
- }
- iRows = [];
- for (i = 0; i < indexesOfDataWithTheSameYValues.length; i++) {
- iRowsY = indexesOfDataWithTheSameYValues[i];
- if ($.inArray(iRowsY, indexesOfDataWithTheSameXValues) >= 0) {
- iRows.push(iRowsY);
- }
- }
- if (iRows.length > 0) {
- // iRows array have all indexes of input data which have both itemXData and itemYData
- // We need calculate aggregate agr over all the items
- rows = new Array(iRows.length);
- agr = aggregates[k];
- aggrContext = new Aggregation(agr.aggregator, self, options);
- for (iRow = 0; iRow < iRows.length; iRow++) {
- i = iRows[iRow];
- row = data[i];
- rows[iRow] = row;
- aggrContext.calc(row[agr.member], agr.member, row, i, data);
- if (isRowTotal) {
- aggrTotal = aggrContextTotalRows[k];
- aggrTotal.calc(row[agr.member], agr.member, row, i, data);
- gi = aggrTotal.groupInfo;
- if ($.inArray(i, gi.iYs) < 0) {
- gi.iYs.push(iYData);
- gi.ys.push(itemYData);
- }
- if ($.inArray(i, gi.iRows) < 0) {
- gi.iRows.push(i);
- gi.rows.push(row);
- }
- }
- calculateGroupTotals(itemYData, previousY, agr, k, row, i, iYData);
- }
- cmName = "y" + iYData + (aggrlen === 1 ? "" : "a" + k);
- aggrContext.getResult(outputItem, cmName);
- pivotInfos[cmName] = {
- colType: 0, // standard row
- iY: iYData,
- y: itemYData,
- iA: k,
- a: agr,
- iRows: iRows,
- rows: rows
- };
- }
- }
- previousY = itemYData;
- }
- if (previousY !== null) { // if non-empty input data
- for (k = 0; k < aggrlen; k++) {
- finalizeGroupTotals(yIndexLength - 1, previousY, previousY, k);
- }
- }
- if (isRowTotal) {
- for (k = 0; k < aggrlen; k++) {
- cmName = "t" + (aggrlen === 1 ? "" : "a" + k);
- aggrTotal = aggrContextTotalRows[k];
- aggrTotal.getResult(outputItem, cmName);
- gi = aggrTotal.groupInfo;
- pivotInfos[cmName] = {
- colType: 2, // row total
- iA: k,
- a: aggregates[k],
- iRows: gi.iRows,
- rows: gi.rows,
- iYs: gi.iYs,
- ys: gi.ys
- };
- }
- }
- outputItems.push(outputItem);
- }
- // *****************************
- // The step 5: fill total footer
- // *****************************
- if (o.footerTotals || o.colTotals) {
- nRows = outputItems.length;
- for (i = 0; i < xlen; i++) {
- summaries["x" + i] = xDimension[i].footerText || "";
- }
- for (i = xlen; i < colModel.length; i++) {
- cmName = colModel[i].name;
- aggrContext = new Aggregation(o.footerAggregator || "sum", self, options);
- for (iRow = 0; iRow < nRows; iRow++) {
- outputItem = outputItems[iRow];
- aggrContext.calc(outputItem[cmName], cmName, outputItem, iRow, outputItems);
- }
- aggrContext.getResult(summaries, cmName);
- }
- }
- // return the final result.
- options.colHeaders = colHeaders;
- return {
- colModel: colModel,
- additionalProperties: additionalProperties,
- options: options,
- rows: outputItems,
- groupOptions: groupOptions,
- groupHeaders: colHeaders,
- summary: summaries
- };
- },
- jqPivot: function (data, pivotOpt, gridOpt, ajaxOpt) {
- return this.each(function () {
- var $t = this, $self = $($t), $j = $.fn.jqGrid;
- function pivot() {
- var pivotGrid = $j.pivotSetup.call($self, data, pivotOpt),
- gHead = pivotGrid.groupHeaders,
- assocArraySize = function (obj) {
- // http://stackoverflow.com/a/6700/11236
- var size = 0, key;
- for (key in obj) {
- if (obj.hasOwnProperty(key)) {
- size++;
- }
- }
- return size;
- },
- footerrow = assocArraySize(pivotGrid.summary) > 0 ? true : false,
- groupingView = pivotGrid.groupOptions.groupingView,
- query = jgrid.from.call($t, pivotGrid.rows), i;
- if (!pivotOpt.skipSortByX) {
- for (i = 0; i < groupingView.groupField.length; i++) {
- query.orderBy(groupingView.groupField[i],
- gridOpt != null && gridOpt.groupingView && gridOpt.groupingView.groupOrder != null && gridOpt.groupingView.groupOrder[i] === "desc" ? "d" : "a",
- "text",
- "");
- }
- }
- pivotOpt.data = data;
- $j.call($self, $.extend(true, {
- datastr: $.extend(query.select(), footerrow ? { userdata: pivotGrid.summary } : {}),
- datatype: "jsonstring",
- footerrow: footerrow,
- userDataOnFooter: footerrow,
- colModel: pivotGrid.colModel,
- additionalProperties: pivotGrid.additionalProperties,
- pivotOptions: pivotGrid.options,
- viewrecords: true,
- sortname: pivotOpt.xDimension[0].dataName // ?????
- }, pivotGrid.groupOptions, gridOpt || {}));
- if (gHead.length) {
- for (i = 0; i < gHead.length; i++) {
- // Multiple calls of setGroupHeaders for one grid are wrong,
- // but there are produces good results in case of usage
- // useColSpanStyle: false option. The rowspan values
- // needed be increased in case of usage useColSpanStyle: true
- if (gHead[i] && gHead[i].groupHeaders.length) {
- $j.setGroupHeaders.call($self, gHead[i]);
- }
- }
- }
- if (pivotOpt.frozenStaticCols) {
- $j.setFrozenColumns.call($self);
- }
- }
- if (typeof data === "string") {
- $.ajax($.extend({
- url: data,
- dataType: "json",
- success: function (data1) {
- data = jgrid.getAccessor(data1, ajaxOpt && ajaxOpt.reader ? ajaxOpt.reader : "rows");
- pivot();
- }
- }, ajaxOpt || {}));
- } else {
- pivot();
- }
- });
- }
- });
- // end module grid.pivot
- }));
|