'use strict'; function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; } function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; } /** *+------------------------------------------------------------------------+ *| 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', '../common/EventTarget', '../../../apiHelpers/SlotAPIHelper', 'underscore', '../VisEventHandler', '../../vipr/VIPRUtils', '../../../lib/@waca/core-client/js/core-client/utils/BrowserUtils', '../../vipr/data/VIPRCatContDataItem', './VIPRDataPointSummarizer'], function ($, EventTarget, SlotAPIHelper, _, VisEventHandler, VIPRUtils, BrowserUtils, VIPRCatContDataItem, VIPRDataPointSummarizer) { var ZOOM_VELOCITY = 0.002; var TOUCHMARGIN = 1; var VIPRTYPE = { CUSTOMDATA: 'customdata', DATAPOINT: 'datapoint', ITEMCLASS: 'itemclass', ITEM: 'item', DATAITEM: 'dataitem', TUPLE: 'tuple', VALUE: 'value', INVALID: null, EMPTY: 'empty' }; var CUSTOMDATATYPE = { KEYDRIVERSHOW: 'keydriver_show', DRIVERINFO: 'driverinfo', TREENODEINFO: 'treenodeinfo' }; var VIPRDATAPOINTTYPE = { VALUE: 'value', TUPLE: 'tuple' }; var VIPREventTarget = function (_EventTarget) { _inherits(VIPREventTarget, _EventTarget); function VIPREventTarget() { var _ret; _classCallCheck(this, VIPREventTarget); for (var _len = arguments.length, args = Array(_len), _key = 0; _key < _len; _key++) { args[_key] = arguments[_key]; } var _this = _possibleConstructorReturn(this, _EventTarget.call.apply(_EventTarget, [this].concat(args))); var options = args[0] || {}; _this.viprWidget = options.viprWidget; _this.viprInteractivity = _this.viprWidget.getInteractivity(); // map from visDecorations to viprDecoration _this.viprDecorations = {}; _this.viprDecorations[VisEventHandler.DECORATIONS.HIGHLIGHT] = 'highlighted'; _this.viprDecorations[VisEventHandler.DECORATIONS.SELECT] = 'selected'; _this.viprDecorations[VisEventHandler.DECORATIONS.TOOLTIP] = 'tooltip'; _this.viprDataPointSummarizer = new VIPRDataPointSummarizer(_this.visualization); return _ret = _this.getAPI(), _possibleConstructorReturn(_this, _ret); } /** * remove viprWidget from event */ VIPREventTarget.prototype.remove = function remove() { this.viprWidget = null; _EventTarget.prototype.remove.call(this); }; /** * @override * Determine whether visualization state (ie. zoom, pan, scroll) can be restored * @return {boolean} true if the visualization state can be restored, otherwise false */ VIPREventTarget.prototype.canRestore = function canRestore() { // state can be restore if // 1. interactivityState property is supportedAND // 2. autoZoom is disabled return VIPRUtils.isPropertyIncluded(this.visualization.getDefinition().getId(), 'interactivityState') && !this.content.getPropertyValue('autoZoom'); }; /** * @override * Restore the visualization state * @param {Object} state object containing the state of the visualization */ VIPREventTarget.prototype.restore = function restore(state) { if (state && this.canRestore()) { this.viprWidget.setState(state); } }; /** * Get the array of event targets based on the event. * Event target represents a data point object that corresponds to the event. * NOTES: * Legend Titles are of type VIPRTYPE.DATAITEM which contain entries of type VIPRTYPE.TUPLE (note: 1 DATAITEM/TUPLE is returned for stacked legends) * Axis Titles are of type VIPRTYPE.ITEMCLASS which contain entries of type VIPRTYPE.ITEM (note: n ITEMCLASSES/ITEMS are returned for stacked axes.) * Chart elements (eg bubbles) are of type VIPRTYPE.DATAPOINT * * @param event Event object */ VIPREventTarget.prototype.getEventTargets = function getEventTargets(event, radius) { var clientX = this._getClientX(event); var clientY = this._getClientY(event); var visCoord = this.viprWidget.getVisCoordinate(clientX, clientY); var items = this.viprWidget.getItemsAtPoint(visCoord, radius); // for mobile events, we make a square around the event coords and return the items inside the square // if nothing was found - consider moving the isIPad check to visEventHandler in the future. if ((!items || items.length === 0) && BrowserUtils.isIPad()) { var coords = this._inflateCoordinates(clientX, clientY); return this.getTargetsByCords(coords, false); } else { var options = { visCoord: visCoord }; return this._processItems(items, options); } }; VIPREventTarget.prototype._inflateCoordinates = function _inflateCoordinates(clientX, clientY) { return [{ x: clientX - TOUCHMARGIN, y: clientY - TOUCHMARGIN }, { x: clientX + TOUCHMARGIN, y: clientY - TOUCHMARGIN }, { x: clientX + TOUCHMARGIN, y: clientY + TOUCHMARGIN }, { x: clientX - TOUCHMARGIN, y: clientY + TOUCHMARGIN }]; }; VIPREventTarget.prototype.getTargetsByCords = function getTargetsByCords(cords, summarize) { var targets; var visCords = cords.map(function (cord) { return this.viprWidget.getVisCoordinate(cord.x, cord.y); }.bind(this)); var items = this.viprWidget.getItemsInPolygon(visCords); var options = { summarize: summarize, visCoord: visCords }; targets = this._processItems(items, options); return targets; }; /** * @override * Get the axis items from event and offset * @param event - the event as generated by visEventHandler * @param offset - an optional offset (in pixels) to ensure the line does not become the target of selections etc. * @return {object} The axis items */ VIPREventTarget.prototype.getValuesAtPoint = function getValuesAtPoint(event, offset) { if (this.viprWidget && this.viprWidget.getVisCoordinate) { var eventOffset = offset || 0; var coord = this.viprWidget.getVisCoordinate(event.clientX + eventOffset, event.clientY - eventOffset); return this.viprWidget.getAxisItemsAtPoint(coord); } }; /** * Visually decorate the targets. * * @param targets Event targets * @param visDecorationName Decoration name (ie. highlight, select) * @return true if target has been decorated, otherwise false */ VIPREventTarget.prototype.decorateTarget = function decorateTarget(targets, visDecorationName, value) { var decorated = false; var viprDecorationName = this.viprDecorations[visDecorationName]; if (viprDecorationName) { _.each(targets, function (target) { if (target.source && typeof target.source.decorate === 'function') { target.source.decorate(viprDecorationName, value); decorated = true; } else if (target.type === 'customdata') { // custom data decoration is handled by applyCustomDataSelection decorated = true; } }); } return decorated; }; /** * Obtain the decoration value from the given target item and decoration name * @param item event target item containing the decoration * @param decorationName common decoration name defined by VisEventHandler.DECORATIONS */ VIPREventTarget.prototype.getDecoration = function getDecoration(item, decorationName) { var viprDecorationName = this.viprDecorations[decorationName]; if (viprDecorationName && item.source && typeof item.source.hasDecoration === 'function' && typeof item.source.getDecoration === 'function' && item.source.hasDecoration(viprDecorationName)) { return item.source.getDecoration(viprDecorationName); } }; /** * Complete the series of decorations. * After clearing and adding multiple decorations, complete the decorations by rendering the visualization. */ VIPREventTarget.prototype.completeDecorations = function completeDecorations() { this.viprWidget.render('client'); }; /** * Apply selection on customdata target. * * @param {ArrayOfObject} targets Event targets */ VIPREventTarget.prototype.applyCustomDataSelection = function applyCustomDataSelection(targets, options) { var target = _.isArray(targets) ? targets[0] : targets; //Sample the targets // target is empty when all decorations needs to be cleared // otherwise the decorating target item type needs to be customdata if (!target || target.type === VIPRTYPE.CUSTOMDATA && !this._isLengendAndEncodingPayload(target)) { //todo: need to make custom data decoration consistent with other data; this._handleCustomDataDecorations(targets, options); } }; //TODO: post Endor R1, we will remove this check and re-enable Comet Chart Legend integration VIPREventTarget.prototype._isLengendAndEncodingPayload = function _isLengendAndEncodingPayload(item) { if (item.area === 'legend' && item.values && item.values.length === 1) { var payload = item.values[0].payload; return payload && payload.type === 'encoding'; } return false; }; /** * Return the list of custom data selections in label/value pair * @param {Object} target event target item of the selected custom data */ VIPREventTarget.prototype.getCustomDataSelections = function getCustomDataSelections(target) { var selections = []; if (target) { _.each(target.values, function (value) { var tooltip = value.payload && value.payload.tooltip; if (tooltip) { // append the name/value pairs if (tooltip.getLines) { var lines = tooltip.getLines(); if (lines && lines.length > 0) { selections.push.apply(selections, tooltip.getLines()); } } // only has generated string caption value without a name/value pair if (tooltip.getCaption) { var caption = tooltip.getCaption(); if (caption) { selections.push({ value: caption }); } } } }); } return selections; }; /** * Determine whether the given interactivity state should be handled * @param {Object} state visualization interactivity state * @return visualization interactivity state if it's supported, otherwise false */ VIPREventTarget.prototype._handleInteractivityState = function _handleInteractivityState(state) { return VIPRUtils.isPropertyIncluded(this.visualization.getDefinition().getId(), 'interactivityState') ? state : undefined; }; /** * Determine whether the vipr visualization supports pan and zoom */ VIPREventTarget.prototype.canZoom = function canZoom() { return this.viprInteractivity ? this.viprInteractivity.canZoom() : false; }; /** * Start the zooming session with the VIPR visualization * * @param event Event object */ VIPREventTarget.prototype.zoomStart = function zoomStart(event) { if (this.viprInteractivity) { try { // in case of a two touch gesture zoom, use the center coordinates of the hammer gesture event var coordinates = event.gesture ? event.gesture.center : event; var visCoord = this.viprWidget.getVisCoordinate(this._getClientX(coordinates), this._getClientY(coordinates)); this.viprInteractivity.startInteractivity(visCoord); // initialize the gesture scale to 1 as we start zoom this.gestureScale = 1; } catch (err) { console.warn(err); } } }; /** * Apply zoom to the VIPR visualization * * @param event Event object */ VIPREventTarget.prototype.zoom = function zoom(event) { if (this.viprInteractivity) { try { // in case of a two touch gesture zoom, use the center coordinates of the hammer gesture event var coordinates = event.gesture ? event.gesture.center : event; var visCoord = this.viprWidget.getVisCoordinate(this._getClientX(coordinates), this._getClientY(coordinates)); var scale = this._getZoomScale(event); this.viprInteractivity.zoom(visCoord, scale); } catch (err) { console.warn(err); } } }; /** * End the zooming session with the VIPR visualization * * @param event Event object * @returns {Object|undefined} interactivity state containing the zoom level OR undefined if state is not supported */ VIPREventTarget.prototype.zoomEnd = function zoomEnd(event) { if (this.viprInteractivity) { try { // in case of a two touch gesture zoom, use the center coordinates of the hammer gesture event var coordinates = event.gesture ? event.gesture.center : event; var visCoord = this.viprWidget.getVisCoordinate(this._getClientX(coordinates), this._getClientY(coordinates)); this.viprInteractivity.endInteractivity(visCoord); // disable the gesture scale as we end zoom this.gestureScale = 0; return this._handleInteractivityState(this.viprWidget.getState()); } catch (err) { console.warn(err); } } }; /** * Start the panning session with VIPR visualization * * @param event Event object */ VIPREventTarget.prototype.panStart = function panStart(event) { if (this.viprInteractivity) { try { var visCoord = this.viprWidget.getVisCoordinate(this._getClientX(event), this._getClientY(event)); this.viprInteractivity.startInteractivity(visCoord); } catch (err) { console.warn(err); } } }; /** * Apply panning to the VIPR visualization * * @param event Event object */ VIPREventTarget.prototype.pan = function pan(event) { if (this.viprInteractivity) { try { var visCoord = this.viprWidget.getVisCoordinate(this._getClientX(event), this._getClientY(event)); this.viprInteractivity.translate(visCoord); } catch (err) { console.warn(err); } } }; /** * End the panning session with VIPR visualization * * @param event Event object * @returns {Object|undefined} interactivity state containing the pan OR undefined if state is not supported */ VIPREventTarget.prototype.panEnd = function panEnd(event) { if (this.viprInteractivity) { try { var visCoord = this.viprWidget.getVisCoordinate(this._getClientX(event), this._getClientY(event)); this.viprInteractivity.endInteractivity(visCoord); return this._handleInteractivityState(this.viprWidget.getState()); } catch (err) { console.warn(err); } } }; VIPREventTarget.prototype.processKeyDown = function processKeyDown() { return false; }; /** * @private * @function _getClientX * Get the clientX property from the given event * @param {Object} event - the event object * @returns clientX value of the event */ VIPREventTarget.prototype._getClientX = function _getClientX(event) { return event.hasOwnProperty('clientX') ? event.clientX : event.originalEvent.clientX; }; /** * @private * @function __getClientY * Get the clientY property from the given event * @param {Object} event - the event object * @returns clientY value of the event */ VIPREventTarget.prototype._getClientY = function _getClientY(event) { return event.hasOwnProperty('clientY') ? event.clientY : event.originalEvent.clientY; }; VIPREventTarget.prototype._processItems = function _processItems(items, options) { var _this2 = this; var targets; var summarize = options.summarize; var visCoord = options.visCoord; switch (this._getTargetType(items)) { case VIPRTYPE.DATAPOINT: { var shouldDataItemsSummarize = VIPRUtils.doesConfigPropertyMatchExpected(this.visualization.getDefinition().getId(), 'shouldMultiPointSelectionSummarize', true); targets = this._processDataPointTargets(items, summarize || shouldDataItemsSummarize); break; } case VIPRTYPE.VALUE: targets = this._processValueTargets(items); break; case VIPRTYPE.ITEM: case VIPRTYPE.TUPLE: targets = this._processTupleItemTargets(items); break; case VIPRTYPE.ITEMCLASS: case VIPRTYPE.DATAITEM: targets = this._processDataItemClassTargets(items); break; case VIPRTYPE.CUSTOMDATA: targets = this._processCustomDataTargets(items); break; case VIPRTYPE.EMPTY: targets = []; break; // invalid or unknown target case VIPRTYPE.INVALID: default: targets = null; } _.each(targets, function (target) { // Attach the clicked area to the target. The area could be of values: visualization, legend, or none if (visCoord && visCoord.area) { target.area = visCoord.area; target.selectionContext = { actions: { actionOptions: { TextAction: { area: visCoord.area } } } }; } // add datasetId; target.datasetId = _this2._datasetId(target); }); return targets; }; VIPREventTarget.prototype._getTargetType = function _getTargetType(items) { if (!items) { return VIPRTYPE.INVALID; } return items.length > 0 ? items[0].infoType : VIPRTYPE.EMPTY; }; VIPREventTarget.prototype._processValueTargets = function _processValueTargets(items) { var targets = []; _.each(items, function (item) { var valueObj = item.source; var slotIndex = valueObj.getIndex(); // build a sparse values array // this allows re-using the existing datapoint mechanism with values var values = []; values[slotIndex] = [item.value]; targets.push({ key: 'value_' + slotIndex, type: VIPRTYPE.DATAPOINT, source: valueObj, values: values }); }); return targets; }; VIPREventTarget.prototype._processDataPointTargets = function _processDataPointTargets(items, summarize) { var _this3 = this; var targets = []; summarize = typeof summarize === 'undefined' ? true : summarize; //The first slot in a selection is its topmost layer in the selection (the selectedLayer). //The datapoint target will be for that layer. var selectedLayer = null; _.each(items, function (item) { var slotAPIs = _this3._getSlotAPIsForItem(item); var slotLayer = slotAPIs && slotAPIs[0].getDefinition().getDatasetIdList()[0]; if (!selectedLayer || slotLayer === selectedLayer) { selectedLayer = slotLayer; targets.push({ key: item.key + ':' + slotLayer, type: VIPRTYPE.DATAPOINT, source: item.source, values: _this3._itemRowToTargetValues(item.row), slotAPIs: slotAPIs, onlyGlobalActions: _this3._isForecastData(item.source) }); } }); // Summarize multiple datapoint targets (area chart) if (targets.length && summarize) { //TODO: Temporarily wire of summarize dataPoint targets. targets = this.viprDataPointSummarizer.summarizeDataPointTargets(targets); } return targets; }; VIPREventTarget.prototype._getSlotAPIsForItem = function _getSlotAPIsForItem(item) { var slotAPIs = item.source && item.source.dataset && item.source.dataset.slotAPIs; if (!slotAPIs && item.source && item.source.dataset) { // the latest implementation of VIPRData does not supply the SlotAPIs // this is rather intentional, and can rather provide the id of the source dataset // @todo this needs to be revised so that the EventTargets simply returns a datasetId, not the list of SlotAPIs slotAPIs = SlotAPIHelper.getMappedSlotListByDataset(this.visualization, item.source.dataset.getId()); } if (slotAPIs && this._isForecastData(item.source)) { return slotAPIs.map( //Position of each slot matters, but we don't want numerical slots, so replace them with null function (slot) { return slot.getDataItemList().find(function (dataItem) { return dataItem.getType() === 'fact'; }) ? null : slot; }); } return slotAPIs; }; VIPREventTarget.prototype._datasetId = function _datasetId(item) { return item && item.source && item.source.dataset && item.source.dataset.getId && item.source.dataset.getId(); }; //This function is called for both VIPRTYPE.ITEM and VIPRTYPE.TUPLE //VIPRTYPE.ITEM corresponds to elements such as axis labels (eg in a bar chart) //When axis labels are stacked, there will be n items of type VIPRTYPE.ITEM //VIPRTYPE.TUPLE corresponds to elements such as legend labels (type VIPRTYPE.TUPLE). VIPREventTarget.prototype._processTupleItemTargets = function _processTupleItemTargets(items) { var _this4 = this; var targets = []; _.each(items, function (item) { var source = item.source; var onlyGlobalActions = false; if (source.datapoints && source.datapoints.length > 0) { onlyGlobalActions = _this4._isForecastData(source.datapoints[0]) || false; } if (source) { targets.push({ key: item.key || item.getUniqueName(), type: VIPRTYPE.ITEM, source: source, values: _this4.viprDataPointSummarizer.summarizeDataPointValues(source.datapoints, source.indexes.dataitem, source), isEdgeSelect: true, onlyGlobalActions: onlyGlobalActions }); } }); if (items.length > 1) { targets = this._mergeTupleItemTargets(targets); } return targets; }; VIPREventTarget.prototype._processDataItemClassTargets = function _processDataItemClassTargets(items) { var _this5 = this; var targets = []; _.each(items, function (item) { var values = []; var slotIds = _.map(_this5.visualization.getSlots().getMappedSlotList(), function (slot) { return slot.getId(); }); var slotIndex = slotIds.indexOf(item.slotDef.name); if (slotIndex > -1) { values[slotIndex] = [{ u: item.source.getUniqueName(), d: item.source.getCaption() }]; targets.push({ key: item.source.getUniqueName(), type: VIPRTYPE.ITEMCLASS, source: item.source, values: values }); } }); return targets; }; VIPREventTarget.prototype._processCustomDataTargets = function _processCustomDataTargets(items) { var targets = []; _.each(items, function (item) { targets.push({ key: item.id, type: item.infoType, source: {}, values: [{ payload: item.payload, customType: item.type }] }); }); return targets; }; VIPREventTarget.prototype._itemRowToTargetValues = function _itemRowToTargetValues(row) { var targetValues = []; if (row) { row.forEach(function (data, indexInRow) { if (data.infoType === VIPRDATAPOINTTYPE.VALUE) { // Don't assume that the indexInRow is the same as the slot index // there could be a slot that contains multiple dataitems // We get the slot index from dataItem.indexes.dataitem var slotIndex = data.dataItem && data.dataItem.source && data.dataItem.source.indexes && data.dataItem.source.indexes.dataitem; if (slotIndex == null) { // not sure if we would ever run into this.. but just in case we have scenarios where the slots index is not found slotIndex = indexInRow; } if (!targetValues[slotIndex]) { targetValues[slotIndex] = []; } var values = targetValues[slotIndex]; var multiMeasureSeries = _.find(row, function (data) { return data.key && data.key.indexOf(SlotAPIHelper.MULTI_MEASURES_SERIES) === 0; }); var index = multiMeasureSeries ? multiMeasureSeries.index : values.length; if (data.dataItem && data.dataItem.source instanceof VIPRCatContDataItem) { values[index] = { u: data.value, d: data.value }; } else { // if possible, use the raw data values[index] = data.source && data.source.getRawValue ? data.source.getRawValue() : data.value; } } else { targetValues[indexInRow] = _.map(data.items, function (catItem) { return { u: catItem.source.item.u, d: catItem.source.item.d, p: catItem.source.item.p }; }); } }); } return targetValues; }; /** * Merge the multiple nested tuple items to one target */ VIPREventTarget.prototype._mergeTupleItemTargets = function _mergeTupleItemTargets(prevTargets) { var params = [true, {}]; return [$.extend.apply($, params.concat(prevTargets))]; }; VIPREventTarget.prototype._handleCustomDataDecorations = function _handleCustomDataDecorations(items, options) { var decorations = []; // Decorate all items found under the pointer _.chain(items).pluck('values').flatten().each(function (value) { if (_.contains(CUSTOMDATATYPE, value.customType)) { decorations.push({ id: value.payload.id, value: true }); } }); this.visAPI.setCustomDataDecoration('selected', decorations, options); }; /** * Get the zoom scale from the event */ VIPREventTarget.prototype._getZoomScale = function _getZoomScale(event) { if (event.gesture) { // default the gesture scale to 1 as we start zoom var previousScale = this.gestureScale || 1; // update the previous gesture scale this.gestureScale = event.gesture.scale; // calculate gesture zoom scale return event.gesture.scale / previousScale; } else { var deltaMode = event.originalEvent.deltaMode; var delta = event.originalEvent.deltaY; // FireFox case: deltaMode is always 1 for zoom in/out. Formula is the same as VIDA demo page if (deltaMode === 1) { delta = delta * 100 / 3; } // Follow VIDA demo to use deltaY. Formula used to calculate the scale is 1 - .002 * i, where i = event.deltaY return 1 - ZOOM_VELOCITY * delta; } }; /** * Checks item source's to see if it contains forecast info * @param {Object} itemSource - target item.source * @return true if the target item is forecast data, otherwise false */ VIPREventTarget.prototype._isForecastData = function _isForecastData(itemSource) { return itemSource && itemSource.deco && itemSource.deco.forecast; }; return VIPREventTarget; }(EventTarget); return VIPREventTarget; }); //# sourceMappingURL=VIPREventTarget.js.map