/**
* Copyright (c) 2015, Oracle and/or its affiliates.
* All rights reserved.
*/
/*jslint browser: true*/
/**
* @class oj.Cube
* @classdesc Functions implemented by oj.DataColumnCube and oj.DataValueAttributeCube
* @see oj.DataColumnCube
* @see oj.DataValueAttributeCube
* @since 1.1.0
*/
/**
* @constructor
* @export
* @private
*/
oj.Cube = function(rowset, layout)
{
this.Init();
// Wrap the incoming data in the appropriate interface
this._rows = rowset;
this._layout = /** @type {Array.<{axis,levels}>} */ (layout);
this.BuildCube();
};
// Subclass from oj.Object
oj.Object.createSubclass(oj.Cube, oj.Object, "oj.Cube");
/**
* Initializes instance with the set options
* @private
*/
oj.Cube.prototype.Init = function()
{
oj.Cube.superclass.Init.call(this);
};
/**
* Get the oj.CubeAxis objects in this cube
* @return {Array} an array of oj.CubeAxis objects
* @export
*/
oj.Cube.prototype.getAxes = function() {
// Generate axes list as union of subcube axes for particular page + page axes
var cube = this._getPinnedCube();
var axes = [];
Array.prototype.push.apply(axes, cube ? cube.getAxes() : this._axes);
// Add on the page axes
for (var i = 2; i < this._axes.length; i++) {
axes.push(this._axes[i]);
}
return axes;
};
oj.Cube.prototype._getNonPageLayout = function() {
return this._axes;
};
/**
* Get oj.CubeDataValues from this cube. These represent the values of the data in the "body" of the cube
*
* @param {Array} indices an axis-ordered array of Objects or numbers. If Objects, each should contain a 'start' property
* (the zero based start index for the axis) and a 'count' representing the number of data values beginning at 'start' to return on this axis.
* This format allows the retrieval of a block of data. Passing an array of numbers alone is equivalent to passing {start:<index>, count:1} and getting a single
* oj.CubeDataValue
* @return {Array|Object} either an array of arrays of oj.CubeDataValue, depending on the number of values requested in indices, or a single oj.CubeDataValue
* The first subscript represents the 0th axis' values, and so on.
* @export
*/
oj.Cube.prototype.getValues = function(indices) {
// Get pinned cube
var cube = this._getPinnedCube();
// Normalize the argument to be objects with start/count properties
var ind = cube._normalizeIndices(indices);
var origRet = cube._walkIndex(ind, 0, [], []);
var val = origRet;
// Unpack single values tucked in arrays
while (Array.isArray(val) && val.length === 1) {
val = val[0];
if (!Array.isArray(val)) {
// Found an object in the middle--return it
return val;
}
}
return origRet;
};
/**
* Set a pinned index for all axes above axis 1 ("pages")
* @param {Array} pin an array of objects containing an integer 'axis' attribute and its corresponding 'index' value (to which to pin the cube)
*/
oj.Cube.prototype.setPage = function(pin) {
// Make sure it's an array
if (pin instanceof Array) {
this._pin = pin;
}
else {
this._pin = [pin];
}
};
/**
*
* @param {number} axisFrom the axis from which to move a level
* @param {number} levelFrom the level within axisFrom to move to axisTo/levelTo (zero is slowest/outermost)
* @param {number} axisTo the axis to which to move levelFrom
* @param {number} levelTo the level within axisTo to move the levelFrom level (zero is slowest/outermost)
* @param {oj.Cube.PivotType} type the type of pivot to perform
* @returns {boolean} true if successful
* @export
*/
oj.Cube.prototype.pivot = function(axisFrom, levelFrom, axisTo, levelTo, type) {
var layout = this._layout;
var axisFromObj = this._findAxisInLayout(axisFrom);
// No from axis found: can't pivot
if (!axisFromObj) {
return false;
}
var axisToObj = null;
if (axisTo < layout.length) {
axisToObj = this._findAxisInLayout(axisTo);
}
else {
// Add a new axis
axisToObj = {'axis':axisTo, 'levels':[]};
layout.push(axisToObj);
}
var levelsFrom = axisFromObj['levels'];
var levelsTo = axisToObj['levels'];
var levelFromObj = levelFrom < levelsFrom.length ? levelsFrom[levelFrom] : null;
// No from level here, can't pivot
if (!levelFromObj) {
return false;
}
// Find where to move
if (levelTo >= levelsTo.length) {
// beyond end--just add it. after, before, swap doesn't matter
levelsTo.push(levelFromObj);
// Remove it from the old location
levelsFrom.splice(levelFrom, 1);
}
else {
if (type === oj.Cube.PivotType['SWAP']) {
// Swap the elements
levelsFrom[levelFrom] = levelsTo[levelTo];
levelsTo[levelTo] = levelFromObj;
}
else {
if (type === oj.Cube.PivotType['AFTER']) {
// Splice operates as a "before" loc
levelTo++;
}
// Insert the from obj into the to
levelsTo.splice(levelTo, 0, levelFromObj);
if (levelsTo === levelsFrom && levelTo < levelFrom) {
// Relocate the from location, because the to splice shifted it (the from was in the same axis as the to, and was after the to)
levelFrom++;
}
// On a move, remove the from object from the old axis
levelsFrom.splice(levelFrom, 1);
}
}
// Rebuild using the altered layout
this.BuildCube();
return true;
};
// Return the entry in the layout arg that represents axis #
oj.Cube.prototype._findAxisInLayout = function(axis) {
for (var i = 0; i < this._layout.length; i++) {
if (this._layout[i]['axis'] === axis) {
return this._layout[i];
}
}
return null;
};
/**
* Return the current layout used to build the cube
*
* @returns {Array} current layout
* @see oj.Cube
* @export
*/
oj.Cube.prototype.getLayout = function() {
return this._layout;
};
/**
* Valid pivot types
* @enum {string}
* @export
*/
oj.Cube.PivotType = {
/**
* Move the from location before the to location
*/
'BEFORE' : "before",
/**
* Move the from location after the to location
*/
'AFTER' : "after",
/**
* Exchange the from location with the to location
*/
'SWAP' : "swap"
};
oj.Cube.prototype._walkIndex = function(indices, depth, location, returnValue) {
if (indices.length === 0) {
// Get the current value at location
var loc = location.slice(0);
//loc.reverse();
return this._getValue(loc);
}
else {
var remaining = indices.slice(1);
var start = indices[0].start;
var count = indices[0].count;
for (location[depth] = start; location[depth] < start+count; location[depth]++) {
returnValue.push(this._walkIndex(remaining, depth+1, location, []));
}
}
return returnValue;
};
// Make sure all start/count data is filled in and within range
oj.Cube.prototype._normalizeIndices = function(indices) {
var ind = [];
if (!indices) {
return ind;
}
var numAxes = Math.min(indices.length, this._axes.length);
for (var a = 0; a < numAxes; a++) {
var index = indices[a];
if (index instanceof Object && (index.hasOwnProperty('start') || index.hasOwnProperty('count'))) {
if (index.hasOwnProperty('start')) {
if (index.hasOwnProperty('count')) {
ind.push(this._generateIndex(index.start, index.count, a));
}
else {
ind.push(this._generateIndex(index.start, 1, a));
}
}
else {
// Must have count
ind.push(this._generateIndex(0, index.count, a));
}
}
else {
// Convert the number to an object
ind.push(this._generateIndex(index, 1, a));
}
}
return ind;
};
oj.Cube.prototype._generateIndex = function(start, count, axis) {
// Get true count
var trueCount = this.getAxes()[axis].getExtent();
if (start >= trueCount || start < 0) {
start = 0;
}
count = Math.min(count,trueCount-start);
return {start: start, index: start, count: count};
};
oj.Cube.prototype._getValue = function(indices) {
// indices is just an axis-ordered array of locations for which to get the value
// Should only be one--only one data value here
var key = this._createCubeKeys(indices);
if (key) {
var hash = key.GetHashCodes();
if (hash.length > 0) {
var obj = this._data[hash[0].key];
if (obj) {
return new oj.CubeDataValue(obj.value, indices, obj.aggType, obj.rows, obj.square);
}
}
}
return new oj.CubeDataValue(null, indices, undefined, []);
};
// Generate the axes from the layout
oj.Cube.prototype.GenerateAxes = function() {
var pageLayout = this._getPageLayout();
// If we have pages--only generate those for this cube, and set up the default 0th pin
this._pin = [];
for (var i = 0; i < pageLayout.length; i++) {
this._getAxis(pageLayout[i]['axis'], pageLayout[i]['levels']);
this._pin.push({'axis':pageLayout[i]['axis'], 'index':0});
}
if (pageLayout.length === 0) {
// Do remaining axes if any (row and column)
var nonPageLayout = this._getNonPageLayout();
for (var i = 0; i < nonPageLayout.length; i++) {
this._getAxis(nonPageLayout[i]['axis'], nonPageLayout[i]['levels']);
}
}
};
oj.Cube.prototype._getPageLayout = function() {
var pageOnlyLayout = [];
for (var i = 0; i < this._layout.length; i++) {
var axis = this._layout[i]['axis'];
if (axis > 1) {
pageOnlyLayout.push(this._layout[i]);
}
}
return pageOnlyLayout;
};
oj.Cube.prototype._getNonPageLayout = function() {
var nonPageLayout = [];
for (var i = 0; i < this._layout.length; i++) {
var axis = this._layout[i]['axis'];
if (axis < 2) {
nonPageLayout.push(this._layout[i]);
}
}
return nonPageLayout;
};
oj.Cube.prototype.BuildCube = function() {
this._axes = [];
// Associative array for data
this._data = [];
// Associative array for cubes
this._cubes = [];
this.GenerateAxes();
// Can start a cube with no data--add on from "parent"
if (this._rows === null) {
return;
}
// Walk each line of the rowset, and distribute its attributes to the axes and the data store
for (var row = 0; row < this._rows.length; row++) {
// Generate a "page key"
var pageKey = new oj.CubeKeys();
for (var axis = 2; axis < this._axes.length; axis++) {
pageKey = this._axes[axis].ProcessRow(this._rows[row], pageKey);
}
// Grab the data value from the page if it's there
var pageHashObj = pageKey.GetHashCodes();
// See if we have a cubes for these pageKeys
for (var ph = 0; ph < pageHashObj.length; ph++) {
var pageHash = pageHashObj[ph].key;
var cube = this._cubes[pageHash];
if (!cube) {
// Generate a new one
cube = this._cubes[pageHash] = this.GenerateCube(this._getNonPageLayout());
}
// Send the row through the lower axes
var keys = new oj.CubeKeys();
var maxAxes = cube._axes.length;
for (var axis = 0; axis < maxAxes; axis++) {
keys = cube._axes[axis].ProcessRow(this._rows[row], keys);
}
// Convert the retrieved keys->data into data storage
var hash = keys.GetHashCodes();
// Must account for data value coming from page, possibly...pass in the hash that has the data
var dataHash = hash;
if (pageHashObj[ph].dataValue !== undefined) {
dataHash = [];
// Must be in an array
for (var h = 0; h < hash.length; h++) {
dataHash.push(pageHashObj[ph]);
}
}
cube._storeData(hash, dataHash, this._rows[row]);
}
}
};
oj.Cube.prototype._storeData = function(hash, dataHash, row) {
for (var i = 0; i < hash.length; i++) {
this._data[hash[i].key] = this._aggregate(dataHash[i], this._data[hash[i].key], row);
}
};
oj.Cube.prototype._getPinnedCube = function() {
return this._cubes[this._getHashFromPin(this._pin)];
};
oj.Cube.prototype._getHashFromPin = function(pin) {
var keys = new oj.CubeKeys();
if (pin && pin.length > 0) {
// Sort by axis attribute
pin.sort(function(a,b) {
return a['axis'] - b['axis'];
});
// Translate the axis/index pin to hash key
var axes = this._axes;
for (var i = 0; i < pin.length; i++) {
keys = axes[pin[i]['axis']].GetCubeKeys(pin[i]['index'], keys);
}
}
return keys.GetHashCodes()[0].key;
};
oj.Cube._isValid = function(value) {
if (value) {
return value.value !== undefined && value.value !== null;
}
return false;
};
oj.Cube.prototype._createAggValue = function(value, aggType, rows, row, props) {
rows.push(row);
var retObj = {};
for (var p in props) {
if (props.hasOwnProperty(p)) {
retObj[p] = props[p];
}
}
retObj.value = value;
retObj.aggType = aggType;
retObj.rows = rows;
return retObj;
};
// Don't treat strings as numbers, ever
oj.Cube._isNumber = function(value) {
if (oj.StringUtils.isString(value.value)) {
return false;
}
// String versions of numbers return false for isNaN--we want to treat them as non numbers always
return !isNaN(value.value);
};
oj.Cube.prototype._aggregate = function(hash, currValue, row) {
var aggObj = this.GetAggType(hash.dataValue);
var aggType = /** @type {Object} */(aggObj.aggregation);
var validCurr = oj.Cube._isValid(currValue);
var validHash = oj.Cube._isValid(hash);
var isNumCurr = validCurr && oj.Cube._isNumber(currValue);
var isNumHash = validHash && oj.Cube._isNumber(hash);
switch (aggType) {
case oj.CubeAggType['SUM']:
if (validCurr && validHash) {
if (isNumCurr && isNumHash) {
return this._createAggValue(currValue.value + hash.value, aggType, currValue.rows, row, {});
}
return this._createAggValue(NaN, aggType, currValue.rows, row, {});
}
if (validHash && !validCurr) {
if (isNumHash) {
return this._createAggValue(hash.value, aggType, [], row, {});
}
return this._createAggValue(NaN, aggType, [], row, {});
}
return currValue;
case oj.CubeAggType['AVERAGE']:
if (validCurr && validHash) {
if (isNumCurr && isNumHash) {
return this._createAggValue((currValue.sum + hash.value) / (currValue.rows.length+1), aggType, currValue.rows, row, {sum:(currValue.sum + hash.value)});
}
return this._createAggValue(NaN, aggType, currValue.rows, row, {sum:currValue.sum});
}
if (validHash && !validCurr) {
if (isNumHash) {
return this._createAggValue(hash.value, aggType, [], row, {sum:hash.value});
}
return this._createAggValue(NaN, aggType, [], row, {sum:NaN});
}
return currValue;
case oj.CubeAggType['VARIANCE']:
case oj.CubeAggType['STDDEV']:
if (validCurr && validHash) {
if (isNumCurr && isNumHash) {
var newCount = currValue.rows.length+1;
var avg = currValue.value + (hash.value - currValue.value) / newCount;
return this._createAggValue(avg, aggType, currValue.rows, row, {square:(currValue.square + (hash.value - currValue.value) * (hash.value - avg))});
}
return this._createAggValue(NaN, aggType, currValue.rows, row, {square:NaN});
}
if (validHash && !validCurr) {
if (isNumHash) {
return this._createAggValue(hash.value, aggType, [], row, {square:0});
}
return this._createAggValue(NaN, aggType, [], row, {square:NaN});
}
return currValue;
case oj.CubeAggType['NONE']:
return this._createAggValue(null, aggType, validCurr ? currValue.rows : [], row, {});
case oj.CubeAggType['FIRST']:
if (validCurr) {
return this._createAggValue(currValue.value, aggType, currValue.rows, row, {});
}
if (validHash) {
return this._createAggValue(hash.value, aggType, [], row, {});
}
return currValue;
case oj.CubeAggType['MIN']:
if (validCurr && validHash) {
if (isNumCurr && isNumHash) {
return this._createAggValue(Math.min(currValue.value, hash.value), aggType, currValue.rows, row, {});
}
return this._createAggValue(NaN, aggType, currValue.rows, row, {});
}
if (validHash && !validCurr) {
if (isNumHash) {
return this._createAggValue(hash.value, aggType, [], row, {});
}
return this._createAggValue(NaN, aggType, [], row, {});
}
return currValue;
case oj.CubeAggType['MAX']:
if (validCurr && validHash) {
if (isNumCurr && isNumHash) {
return this._createAggValue(Math.max(currValue.value, hash.value), aggType, currValue.rows, row, {});
}
return this._createAggValue(NaN, aggType, currValue.rows, row, {});
}
if (validHash && !validCurr) {
if (isNumHash) {
return this._createAggValue(hash.value, aggType, [], row, {});
}
return this._createAggValue(NaN, aggType, [], row, {});
}
return currValue;
case oj.CubeAggType['COUNT']:
if (validCurr && validHash) {
return this._createAggValue(currValue.value+1, aggType, currValue.rows, row, {});
}
if (validHash && !validCurr) {
return this._createAggValue(1, aggType, [], row, {});
}
return currValue;
case oj.CubeAggType['CUSTOM']:
var callback = aggObj.callback;
var val = callback.call(this, validCurr ? currValue.value : undefined, validHash ? hash.value : undefined);
return this._createAggValue(val, aggType, validCurr ? currValue.rows : [], row, {});
}
};
// Get or create the given axis
oj.Cube.prototype._getAxis = function(axis, levels) {
if (axis >= this._axes.length) {
// Must add on enough to cover
var newElems = new Array(axis-this._axes.length+1);
Array.prototype.push.apply(this._axes, newElems);
}
// Initialize it if blank
if (!this._axes[axis]) {
this._axes[axis] = new oj.CubeAxis(levels, axis, this);
}
return this._axes[axis];
};
// Create a key object from an axis-ordered array of slowest-to-fastest arrays of CubeAxisValues
oj.Cube.prototype._createCubeKeys = function(indices) {
var axes = this.getAxes();
var keys = new oj.CubeKeys();
for (var a = 0; a < indices.length; a++) {
keys = axes[a].GetCubeKeys(indices[a], keys);
}
return keys;
};
oj.Cube.prototype.ProcessLevel = function(levels, levelNum, currNode, row, keys, addKeys) {
oj.Assert.failedInAbstractFunction();
};
oj.Cube.prototype.GenerateCube = function(layout) {
oj.Assert.failedInAbstractFunction();
};
oj.Cube.prototype.GenerateLevel = function(level, axis) {
oj.Assert.failedInAbstractFunction();
};
/**
* @param {Object} dataValue
* @returns {Object}
* @private
*/
oj.Cube.prototype.GetAggType = function(dataValue) {
oj.Assert.failedInAbstractFunction();
return {};
};
Source: src/main/javascript/oracle/oj/ojcube/Cube.js
Oracle® JavaScript Extension Toolkit (JET)
1.1.2
E65298-01