'use strict'; /* *+------------------------------------------------------------------------+ *| Licensed Materials - Property of IBM *| IBM Cognos Products: Dashboard *| (C) Copyright IBM Corp. 2017, 2020 *| *| US Government Users Restricted Rights - Use, duplication or disclosure *| restricted by GSA ADP Schedule Contract with IBM Corp. *+------------------------------------------------------------------------+ */ define(['jquery', 'underscore', '../../lib/@waca/core-client/js/core-client/ui/core/Class', '../../apiHelpers/SlotAPIHelper', '../../widgets/livewidget/nls/StringResources', '../../util/DashboardFormatter', 'com/ibm/vida/vizbundles/extensions/decorations'], function ($, _, Class, SlotAPIHelper, StringResources, Formatter, decorations) { 'use strict'; // implements IDecorated var VIPRDecoratable = Class.extend({ init: function init(object, context) { this.context = context; //Save indexes that help describes the results data location of the decoratable object. //(eg. A VIPRItemClass will have the dataitem index defined but not the tuple (since it corresponds to an axis title) // A VIPRTuple will have dataitem and tuple defined (since it corresponds to the nth intersected value) // A VIPRItem will have dataitem, tuple and tupleitem (since it corresponds to an item in a slot) this.indexes = context && context.index && { dataitem: context.index.dataitem, //The resultsDataItem index (corresponds to the slot index) tuple: context.index.tuple, //The tuple (corresponds to the nth value eg: 2005-Camping) tupleitem: context.index.tupleitem //The tupleitem is the index (n) for the nth item assigned to the slot } || {}; this.decorations = object ? object.deco || {} : {}; }, hasDecoration: function hasDecoration(name) { return typeof this.decorations[name] !== 'undefined'; }, getDecoration: function getDecoration(name) { return this.decorations[name]; }, /** * decorate vipr data - Deprecated! do not update * @param {String} name - name of decoration * @param {Object} [value] - value of decoration * @return the decoration value being saved in decorations. */ decorate: function decorate(name, value) { /** * TODO need to find a generic way to use decoration APIs post Endor R4 * Endor R4 solution - compare line stays as a special case to use the new VIDA decoration */ var decorationValue = value; if (name === 'lines') { decorationValue = this._createLinesDecoration(name, value); } // If this decoration isn't already in the map add it if (!(name in this.decorations) || this.decorations[name] !== decorationValue) { this.decorations[name] = decorationValue; this.context.version++; } return decorationValue; }, clearDecorations: function clearDecorations() { var _this = this; var deleted = false; _.each(_.keys(this.decorations), function (name) { delete _this.decorations[name]; deleted = true; }); if (deleted) { this.context.version++; } }, _createLinesDecoration: function _createLinesDecoration(name, value) { var existingLinesDecorations = this.getDecoration(name); var linesDecorations = []; var baseLine = void 0; var grabber = void 0; var vane = void 0; _.each(existingLinesDecorations, function (lineDecoration) { if (lineDecoration instanceof decorations.BaseLine) { baseLine = lineDecoration; } else if (lineDecoration instanceof decorations.Grabber) { grabber = lineDecoration; } else if (lineDecoration instanceof decorations.Vane) { vane = lineDecoration; } }); // value can be a spec object (when first creating the decoration) or an array of lines decoration (when re-apply the SAME decoration) var isReDecorate = Array.isArray(value); if (isReDecorate) { linesDecorations = value; } else if (value && !isReDecorate) { if (value.baseLine) { baseLine = baseLine ? baseLine : this._createBaseline('baseLine'); if (typeof value.baseLine.position !== 'undefined') { baseLine.setPosition(value.baseLine.position); } if (value.baseLine.color) { baseLine.setColor(value.baseLine.color); } baseLine.setShowInLegend(false || value.baseLine.setShowInLegend); linesDecorations.push(baseLine); } if (value.grabber && baseLine) { grabber = grabber ? grabber : this._createGrabber('grabber'); grabber.setPositionType(value.grabber.positionType); baseLine.setGrabber(grabber); linesDecorations.push(grabber); } // the label on the side of the compare line if (value.vane && baseLine) { vane = vane ? vane : this._createVane('vane'); vane.setCaption(value.vane.caption); baseLine.setVane(vane); linesDecorations.push(vane); } } return linesDecorations; }, _createBaseline: function _createBaseline() { var baseLineId = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : _.uniqueId('baseLine'); return new decorations.BaseLine(baseLineId); }, _createGrabber: function _createGrabber() { var grabberId = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : _.uniqueId('grabber'); return new decorations.Grabber(grabberId); }, _createVane: function _createVane() { var vaneId = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : _.uniqueId('vane'); return new decorations.Vane(vaneId); } }); // implements IFormatter var VIPRFormatter = Class.extend({ init: function init(slotIndex, dataItemIndex, context) { this.slot = context.dataset.slotAPIs[slotIndex]; this.dataItemIndex = dataItemIndex; this._format = this.slot.getDataItemList()[dataItemIndex].getFormat(); }, format: function format(value) { return Formatter.format(value, this._format); } }); // implements IItemClass var VIPRItemClass = VIPRDecoratable.extend({ init: function init(itemClass, context) { VIPRItemClass.inherited('init', this, [itemClass, context]); this.itemClass = itemClass; this.context = context; }, getUniqueName: function getUniqueName() { return this.itemClass.u; }, getCaption: function getCaption() { var summaryStr = void 0; if (!this.context.hideAggTypeInAxisLabels) { summaryStr = this.itemClass.aggregate && this.itemClass.aggregate !== 'none' ? ' (' + StringResources.get(this.itemClass.aggregate) + ')' : null; } var caption = this.itemClass.d ? this.itemClass.d : this.itemClass.u; return summaryStr ? caption + summaryStr : caption; } }); // implements IItem var VIPRItem = VIPRDecoratable.extend({ init: function init(item, tuple, context, content) { VIPRItem.inherited('init', this, [item, context, content]); this.item = item; this.context = context; this.index = context.index.tupleitem; this.content = content; this.datapoints = tuple.datapoints; this.recalculateFormattedValue(); }, /** * TODO: This function is a duplicate of the one in VIPRItem * these classes will be removed once forecasting and smart annotations move to the new query format. */ recalculateFormattedValue: function recalculateFormattedValue() { var formatter = new VIPRFormatter(this.context.index.dataitem, this.context.index.tupleitem, this.context); if (this.item.d === undefined || this.item.d === null) { this.formattedValue = formatter.format(null); this.unformattedValue = this.formattedValue; } else if (this.item.aggregate) { var aggregateTypeVisible = !!(!this.content || this.content.getPropertyValue('widget.legend.aggregateTypeVisible')); if (aggregateTypeVisible) { this.formattedValue = formatter.format(StringResources.get('multiMeasureLabel', { 'measureName': this.item.d.toString(), 'aggregate': StringResources.get(this.item.aggregate.toString()) })); this.unformattedValue = this.item.d + this.item.aggregate; } else { this.formattedValue = formatter.format(StringResources.get('measureLabel', { 'measureName': this.item.d.toString() })); this.unformattedValue = this.item.d + this.item.aggregate; } } else { this.formattedValue = formatter.format(this.item.d.toString()); this.unformattedValue = this.item.d; } }, getUniqueName: function getUniqueName() { return this.item.u === undefined || this.item.u === null ? this.item.u : this.item.u.toString(); }, getCaption: function getCaption() { if (this.item.aggregate) { var currentAggregationPropertyValue = !this.content || this.content.getPropertyValue('widget.legend.aggregateTypeVisible'); if (currentAggregationPropertyValue != this.aggregationPropertyValue) { this.recalculateFormattedValue(); this.aggregationPropertyValue = currentAggregationPropertyValue; } } if (!this.formattedValue) { this.recalculateFormattedValue(); } return this.formattedValue; }, getParent: function getParent() { return this.item.p; }, decorate: function decorate(action, value) { if (this.datapoints.length > 0) { this.context.version++; // decorate the datapoints _.each(this.datapoints, function (dp) { dp.decorate(action, value); }); } } }); var VIPRItem_CaptionAsUnid = VIPRItem.extend({ getUniqueName: function getUniqueName() { return this.getCaption(); } }); // implements IItemClassSet var VIPRItemClassSet = VIPRDecoratable.extend({ init: function init(header, context) { VIPRItemClassSet.inherited('init', this, [header, context]); this.header = header; this.context = context; this.itemClasses = []; }, getItemClassCount: function getItemClassCount() { return this.header.length; }, getItemClass: function getItemClass(index) { if (this.itemClasses[index] === undefined) { this.itemClasses[index] = new VIPRItemClass(this.header[index], this.context); } return this.itemClasses[index]; } }); // implements ITuple var VIPRTuple = VIPRDecoratable.extend({ init: function init(tuple, context, content) { VIPRTuple.inherited('init', this, [tuple, context, content]); this.tuple = tuple; this.context = context; this.datapoints = []; this.items = []; this.dataset = context.dataset; this.dataItemIndex = this.context.index.dataitem; this.itemClassSetIndex = this.context.index.itemClassSet; this.content = content; }, getUniqueName: function getUniqueName() { return this.getItem(0).getUniqueName(); }, //This function should be only used for rave2bundlesnetwork bundle //this bundle has certain slots that have subtype: reference //this function now works as a workaround and has no impact on livewidget vis getReference: function getReference() { return this.getItem(0).getUniqueName(); }, getCaption: function getCaption() { return this.getItem(0).getCaption(); }, getParent: function getParent() { return this.getItem(0).getParent(); }, getItemClassSetIndex: function getItemClassSetIndex() { //Since Enodr release, a tuple could have a itemClassSetIndex property //that indicates which itemClass the current tuple is using return this.itemClassSetIndex; }, getItem: function getItem(index) { if (this.items[index] === undefined) { // keep track of the tuple item index as part of the context index this.context.index.tupleitem = index; this.context.dataset = this.dataset; var isCaptionAsUniqueName = false; var dataItemIndex = this.context.index.dataitem; if (dataItemIndex >= 0) { var oSlotAPI = this.context.dataset.slotAPIs[dataItemIndex]; isCaptionAsUniqueName = oSlotAPI.isCaptionAsUniqueName && oSlotAPI.isCaptionAsUniqueName(); } var classOfVIPRItem = isCaptionAsUniqueName ? VIPRItem_CaptionAsUnid : VIPRItem; this.items[index] = new classOfVIPRItem(this.tuple[index], this, this.context, this.content); } return this.items[index]; }, getItemCount: function getItemCount() { return this.tuple ? this.tuple.length : 0; }, addDataPoint: function addDataPoint(datapoint) { if (this.datapoints.indexOf(datapoint) === -1) { this.datapoints.push(datapoint); } }, decorate: function decorate(action, value) { VIPRTuple.inherited('decorate', this, [action, value]); if (this.datapoints.length > 0) { this.context.version++; // decorate the datapoints _.each(this.datapoints, function (dp) { dp.decorate(action, value); }); } } }); // implements ICatDataItem var VIPRCatDataItem = VIPRDecoratable.extend({ init: function init(dataItem, context, content) { VIPRCatDataItem.inherited('init', this, [dataItem._dataItem, context]); this.dataItem = dataItem; this.context = context; this.index = this.context.index.dataitem; this.dataset = this.context.dataset; this.tuples = []; this.itemClassSets = []; this.content = content; _.each(this.dataItem.getTupleHeaders(), function (entry) { this.itemClassSets.push(new VIPRItemClassSet(entry, this.context)); }.bind(this)); }, getUniqueName: function getUniqueName() {}, postProcessing: function postProcessing() {}, getCaption: function getCaption() {}, getType: function getType() { return 'cat'; }, getTupleCount: function getTupleCount() { return this.dataItem ? this.dataItem.getTupleCount() : 0; }, getTuple: function getTuple(index) { if (this.tuples[index] === undefined) { this.context.dataset = this.dataset; this.context.index = { dataitem: this.index, tuple: index, itemClassSet: this.dataItem.getTupleItemClassSetIndex(index) }; this.tuples[index] = new VIPRTuple(this.dataItem.getTuple(index), this.context, this.content); } return this.tuples[index]; }, getDataWindowStart: function getDataWindowStart() { return 0; }, getDataWindowEnd: function getDataWindowEnd() { return this.getTupleCount(); }, getItemClassSetCount: function getItemClassSetCount() { return this.itemClassSets.length; }, getItemClassSet: function getItemClassSet(index) { return this.itemClassSets[index || 0]; } }); var VIPRCatContDataItem = VIPRCatDataItem.extend({ init: function init() { VIPRCatContDataItem.inherited('init', this, arguments); this.formatter = new VIPRFormatter(this.context.index.dataitem, 0, this.context); this.postProcessing(); }, getType: function getType() { return 'cont'; }, getFormatter: function getFormatter() { return this.formatter; }, postProcessing: function postProcessing() { var index = this.context.index.dataitem; var subType = this.dataset.slotAPIs[index].getSubType(); if (subType && subType === 'latitude' || subType === 'longitude') { _.each(this.dataItem._dataItem.items, function (item) { if (item.t[0].hasOwnProperty('d') && item.t[0].hasOwnProperty('u') && item.t[0].d !== item.t[0].u) { item.t[0].u = item.t[0].d; } }); } }, getItemClass: function getItemClass() { return null; }, getDomain: function getDomain() { return null; } }); // implements IContDataItem var VIPRContDataItem = VIPRDecoratable.extend({ init: function init(dataItem, context) { VIPRContDataItem.inherited('init', this, [dataItem._dataItem, context]); this.dataItem = dataItem; this.context = context; this.queryResult = context && context.queryResult; // Ordinal slots normally does not take multiple dataitems (ie. stacked) // even for multi-measures case (ie. bar, column) the multiple ordinal dataitems are merged in to a single ordinal measure // and each measures are separated as series. // So at the end, from the VIPRData perspective a numeric dataitem (aka slot), has only one measure. this.formatter = new VIPRFormatter(this.context.index.dataitem, 0, this.context); //getTupleHeaders() return the array of itemClass, each Item Class contains an array of Tuple headers this.itemClass = new VIPRItemClass(this.dataItem.getTupleHeaders()[0][0], this.context); this.domain = this._createDomain(); // Min Max Data Item domain }, /** * @returns the domain when the data item is a multi measure. */ _createMultiMeasureDomain: function _createMultiMeasureDomain() { var domain = null; var topBottomMappings = this.queryResult && this.queryResult.getTopBottomMappings(); var keys = _.keys(topBottomMappings); var overAllMin; var overAllMax; /* * This may have to be tweaked depending on how Ruben asks for the domain * as we may have to deal with aggregated or not. For now keep it simple. * Make the min the min of all the min values. Make the max the max of all * max values. */ if (keys.length) { keys.forEach(function (key) { var minValue = topBottomMappings[key].bottomResult.useValue; var maxValue = topBottomMappings[key].topResult.useValue; overAllMin = overAllMin ? Math.min(overAllMin, minValue) : minValue; overAllMax = overAllMax ? Math.max(overAllMax, maxValue) : maxValue; }); domain = { min: overAllMin, max: overAllMax }; } return domain; }, /** * @returns the domain when the data item is not a multi measure. */ _createNonMultiMeasureDomain: function _createNonMultiMeasureDomain() { var domain = null; var topBottomMappings = this.queryResult && this.queryResult.getTopBottomMappings(); // Use the mappings object to determine the min and max of the domain var topBottomMapItemName = this.itemClass.itemClass.u + this.itemClass.itemClass.aggregate; if (topBottomMapItemName && topBottomMappings) { var topBottom = topBottomMappings[topBottomMapItemName]; if (topBottom) { domain = { min: topBottom.bottomResult.useValue, max: topBottom.topResult.useValue }; } } return domain; }, /** * @returns the domain (min/max) for the data item */ _createDomain: function _createDomain() { var domain = null; if (this.itemClass && this.itemClass.itemClass) { if (this.itemClass.itemClass.u === '_multiMeasuresValue') { domain = this._createMultiMeasureDomain(); } else { domain = this._createNonMultiMeasureDomain(); } } return domain; }, getUniqueName: function getUniqueName() { return this.itemClass.getUniqueName(); }, getCaption: function getCaption() { return this.itemClass.getCaption(); }, getType: function getType() { return 'cont'; }, getDomain: function getDomain() /*aggregation TODO for stacked charts*/{ return this._createDomain(); }, getFormatter: function getFormatter() { return this.formatter; }, getItemClass: function getItemClass() { return this.itemClass; } }); // IValue-ization function asIValue(valueObj, formatter, index) { //FIXME The situation happens Only when using Fact value with multiple itemsetClassset, //Will fix this when VIDA provides a solution for this. if (_.isNumber(valueObj)) { valueObj = { v: valueObj }; } if (typeof valueObj.getValue === 'undefined') { valueObj.getIndex = function () { return index; }; valueObj.getValue = function () { return this.v; }; valueObj.getValueType = function () { if (this.v === null) { return 'missing'; //NULL Value support, to be parsed by VIDA } return 'numeric'; //Default Value Type }; var caption = formatter.format(valueObj.v); valueObj.getCaption = function () { return caption; }; } return valueObj; } // IDataPoint-ization function asIDataPoint(datapoint, dataset, context) { if (typeof datapoint.context === 'undefined') { datapoint.dataset = dataset; datapoint.getTupleIndex = function (index) { var tupleIndex = this.pt[index]; // Keep a reverse-reference from tuple to the datapoint this.dataset.getDataItem(index).getTuple(tupleIndex).addDataPoint(this); return tupleIndex; }.bind(datapoint); //return all of the categorical indexes in the pt array as a string 'key' for this datapoint. datapoint.getDataPointKey = function () { return this.pt ? _.filter(this.pt, function (pt) { return typeof pt === 'number'; }).toString() : ''; }.bind(datapoint); var getValue = typeof datapoint.getValue === 'function' ? datapoint.getValue : undefined; datapoint.getValue = function (index) { var valueObj = getValue ? getValue(index) : this.pt[index]; var bIsUseCategoryAsValue = this.dataset.slotAPIs[index].getDefinition().useCategoryAsValue; if (bIsUseCategoryAsValue) { var tuple = this.dataset.resultData.getResultDataItem(index).getTuple(this.pt[index]); valueObj = { 'v': tuple[0].u }; } var formatter = this.dataset.getDataItem(index).getFormatter(); return asIValue(valueObj, formatter, index); }.bind(datapoint); // IDecorated-ize the datapoint if (!datapoint.hasDecoration || !datapoint.getDecoration || !datapoint.decorate) { datapoint._decorations = new VIPRDecoratable(datapoint, context); datapoint.hasDecoration = datapoint._decorations.hasDecoration.bind(datapoint._decorations); datapoint.getDecoration = datapoint._decorations.getDecoration.bind(datapoint._decorations); datapoint.clearDecorations = datapoint._decorations.clearDecorations.bind(datapoint._decorations); datapoint.decorate = function (action, value) { // default value: true value = typeof value === 'undefined' ? true : value; this._decorations.decorate(action, value); }.bind(datapoint); } } return datapoint; } // implements IDataPointIterator var VIPRDataPointIterator = Class.extend({ init: function init(dataset, context) { this.dataset = dataset; this.resultData = dataset.resultData; this.slotAPIs = dataset.slotAPIs; this.datapoints = this.resultData.getDatapoints(); this.context = context; this.index = -1; }, nextValue: function nextValue() { if (this.index < this.datapoints.length - 1) { return asIDataPoint(this.datapoints[++this.index], this.dataset, this.context); } return null; }, getTupleItems: function getTupleItems() { var items = []; for (var i = 0; i < this.datapoints[this.index].pt.length; i++) { var value = this.resultData.getCellValue(this.index, i); if ($.isArray(value)) { items = items.concat(value); } } return items; } }); var VIPRDataSet = VIPRDecoratable.extend({ init: function init(resultData, slotAPIs, queryResult, context, content, datasetId) { VIPRDataSet.inherited('init', this, []); this.resultData = resultData; this.slotAPIs = slotAPIs; // Need to preload dataItems to handle empty result. this.dataItems = []; var aResultDataItems = this.resultData.getResultDataItems(); this.context = context; this.context.queryResult = queryResult; this.context.dataset = this; this.content = content; this.id = datasetId; var numberOfDataItems = aResultDataItems ? aResultDataItems.length : 0; for (var index = 0; index < numberOfDataItems; index++) { var dataItem = aResultDataItems[index]; if (this.slotAPIs[index]) { // TODO livewidget_cleanup .. old API.. but where is it used ? var slotType = this.slotAPIs[index].getDataItemList()[0].getType(); var VIPRDataItem = slotType === 'attribute' ? VIPRCatDataItem : VIPRContDataItem; //fixme hack if (this.slotAPIs[index].getId().indexOf('latlongLocations.lat') !== -1 || this.slotAPIs[index].getId().indexOf('latlongLocations.long') !== -1) { VIPRDataItem = VIPRCatContDataItem; } this.context.index = { dataitem: index }; this.dataItems[index] = new VIPRDataItem(dataItem, this.context, content); } } }, // implement IDataSet getDataItem: function getDataItem(index) { return index < this.dataItems.length ? this.dataItems[index] : null; }, getDataItemCount: function getDataItemCount() { return this.resultData.getResultDataItemCount(); }, getDataPointIterator: function getDataPointIterator() { return new VIPRDataPointIterator(this, this.context); } }); var VIPRData = VIPRDecoratable.extend({ /** * @param {Object[]} queryResults */ init: function init(queryResults, visualization, hideAggTypeInAxisLabels, content) { VIPRData.inherited('init', this, []); this.content = content; this.context = { version: 0, VIPRData: this, hideAggTypeInAxisLabels: hideAggTypeInAxisLabels }; this._aVIPRDataSets = []; var aDataSets = visualization.getDefinition().getDatasetList(); _.each(aDataSets, function (dataset) { var aQueryResult = _.find(queryResults, function (entry) { return entry.getDataViewId() === dataset.id; }); if (aQueryResult) { this._aVIPRDataSets.push(new VIPRDataSet(aQueryResult.getFacetData(), SlotAPIHelper.getMappedSlotListByDataset(visualization, aQueryResult.getDataViewId()), aQueryResult, this.context, this.content, dataset.id)); } else { this._aVIPRDataSets.push(null); } }.bind(this)); }, getDataSetAt: function getDataSetAt(index) { return this._aVIPRDataSets[index]; }, getDataSetCount: function getDataSetCount() { return this._aVIPRDataSets.length; }, getDecorationsVersion: function getDecorationsVersion() { return this.context.version; } }); return VIPRData; }); //# sourceMappingURL=VIPRData.js.map