123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682 |
- 'use strict';
- /**
- * Licensed Materials - Property of IBM
- * IBM Cognos Products: BI Cloud (C) Copyright IBM Corp. 2019, 2020
- * US Government Users Restricted Rights - Use, duplication or disclosure restricted by GSA ADP Schedule Contract with IBM Corp.
- */
- define(['jquery', 'underscore', '../VisView', 'text!./Infographic.html', '../../../util/DashboardFormatter', '../../../widgets/livewidget/nls/StringResources', '../../../lib/@waca/dashboard-common/dist/utils/ScaleUtil', './InfographicScaleView', '../../../lib/@waca/dashboard-common/dist/ui/AuthoringToolbar', '../../../lib/@waca/core-client/js/core-client/utils/Utils', '../../../lib/@waca/core-client/js/core-client/utils/BrowserUtils', '../../../lib/@waca/dashboard-common/dist/utils/EventChainLocal', '../VisEventHandler', './InfographicEventTarget', '../../../DynamicFileLoader'], function ($, _, VisView, InfographicTemplate, Formatter, stringResources, ScaleUtil, InfographicScaleView, Toolbar, Utils, BrowserUtils, EventChainLocal, VisEventHandler, InfographicEventTarget, DynamicFileLoader) {
- 'use strict';
- // Offset used with size of shapes calculations
- var OFFSET = 1;
- /**
- * The InfographicView renders one or more aggregated facts
- * eg: Total Revenue for the currently selected filter values.
- */
- var InfographicView = VisView.extend({
- className: 'dataview infographic-widget',
- templateString: InfographicTemplate,
- events: {
- 'primaryaction .infographic-scale-wrapper': 'infographicToolbar'
- },
- /**
- * Initialize the view and its handlers, then render.
- */
- init: function init() {
- InfographicView.inherited('init', this, arguments);
- this.animationType = this.ANIMATION_TYPES.NONE;
- this.toolbarShown = false;
- this.isPercent = false;
- this.isFirstRender = true;
- this.visModel.on('change:theme', this.onChangeTheme, this);
- this._colorsService = this.dashboardApi.getFeature('Colors');
- this._translationService = this.dashboardApi.getDashboardCoreSvc('TranslationService');
- this._showTranslationIcon = false;
- },
- // @override
- remove: function remove() {
- if (this.eventHandler) {
- this.eventHandler.remove();
- this.eventHandler = null;
- }
- if (this.visModel) {
- this.visModel.off('change:theme', this.onChangeTheme, this);
- }
- this.visControl = null;
- InfographicView.inherited('remove', this, arguments);
- },
- getRenderer: function getRenderer() {
- return 'gemini/dashboard/visualizations/renderer/infographic/InfographicRenderer';
- },
- /**
- * Infographic view overwrites the default animation in order to avoid it fading in/out when
- * applying filters.
- */
- //@Override
- animate: function animate() {
- // Rendering is complete, no animation yet
- this.visModel.renderCompleteBeforeAnimation();
- },
- /**
- * Loads and creates the control. Returns a promise which is resolved when the control
- * is created and ready to render
- */
- //TODO: Remove function
- whenVisControlReady: function whenVisControlReady() {
- var _this = this;
- if (this.visControl) {
- return Promise.resolve(this.visControl);
- } else {
- return DynamicFileLoader.load(['dashboard-analytics/visualizations/renderer/infographic/control/InfographicControl']).then(function (modules) {
- var VisControl = modules[0];
- _this.visControl = new VisControl({
- domNode: _this.$el.find('.infographic-widget-content')[0]
- });
- //TODO: Retain event target
- _this.eventHandler = new VisEventHandler({
- target: new InfographicEventTarget({
- $el: _this.$el,
- visControl: _this.visControl,
- visAPI: _this.visModel,
- view: _this
- }),
- transaction: _this.transactionApi,
- ownerWidget: _this.ownerWidget,
- visAPI: _this.visModel,
- edgeSelection: true
- });
- return _this.visControl;
- });
- }
- },
- _readyToRender: function _readyToRender() {
- //to cover the case where the slot is changed to an infographic and need a shape
- return this.isMappingComplete() && !this.hasMissingFilters() && !this.hasUnavailableMetadataColumns();
- },
- /**
- * render the results.
- * If results are not available, fetch them first.
- * @param {Object} renderInfo - renderInfo passed to all render methods from the render sequence.
- * @returns a promise which is resolved when the render is complete.
- */
- render: function render(renderInfo) {
- this.graphicFillColor = this.content.getPropertyValue('value.graphic.fillColor');
- this.graphicCurrentScaleOption = this.content.getPropertyValue('value.graphic.currentScaleOption');
- this.graphicContent = this.content.getPropertyValue('value.graphic.content');
- var doFillAnimation = !BrowserUtils.isIE();
- if (!this._readyToRender()) {
- this.resizeToWidget(renderInfo);
- this.renderIconView();
- return Promise.resolve(this);
- }
- this.removeIconView();
- this.resizeToWidget(renderInfo);
- var html = this.buildHTML(renderInfo.data.getResult(), doFillAnimation);
- this.addToDom(html);
- this.applyVisModelProperties();
- Utils.embedSVGIcon(this.$el); // patch disappearing SVGs in win10 IE11
- this.updateTranslationIcon();
- return InfographicView.inherited('render', this, arguments);
- },
- /**
- * Shows icon of the chart type and hide the rave output area whose role is application
- * @override VisView._renderIconView
- */
- _renderIconView: function _renderIconView() {
- if (this.$el) {
- this.$el.closest('.infographic-widget').empty().append(this.$iconView);
- }
- this.visModel.renderCompleteBeforeAnimation();
- this.visModel.renderComplete();
- },
- summaryReveal: function summaryReveal() {
- this.visModel.renderCompleteBeforeAnimation();
- if (!this._readyToRender()) {
- return Promise.resolve();
- }
- var animateClass = this._fillVertically() ? 'fillVertical' : 'fillHorizontal';
- var reflowShape = function reflowShape(el) {
- el.classList.remove(animateClass);
- void el.clientWidth; // forces repaint which triggers fill animation
- el.classList.add(animateClass);
- };
- this.$el.find('.animate').each(function (i, el) {
- reflowShape(el);
- });
- return Promise.resolve();
- },
- /**
- * @override VisView.removeIconView
- * */
- removeIconView: function removeIconView() {
- if (this.$iconView && this.$el) {
- this.$el.find('div[class="value"]').addClass('hide');
- this.$el.find('div[class="label"]').addClass('hide');
- this.$iconView.remove();
- this.$iconView = null;
- }
- },
- getDescription: function getDescription() {
- var description;
- if (this.infographicInfo) {
- var label = this.getLabel();
- var infographic = stringResources.get('infographicLabel', {
- label: this.infographicInfo.label,
- value: this.infographicInfo.v
- });
- description = stringResources.get('WidgetLabelWithDescripion', {
- label: label,
- description: infographic
- });
- } else {
- description = InfographicView.inherited('getDescription', this, arguments);
- }
- return description;
- },
- /**
- * Creates and displays toolbar for updating/changing scale options, with inner content
- * @param event - event that caused this function to be triggered. Contains target, which is used to determine where to place toolbar
- * @returns promise resolved when the toolbar is rendered and displayed to user
- */
- infographicToolbar: function infographicToolbar(event) {
- // Prevent widget ODT from displaying/updating
- new EventChainLocal(event).setProperty('preventDefaultContextBar', true);
- if (this.graphicContent && !this.toolbarShown) {
- var toolbar = new Toolbar({
- container: $('body'),
- placement: 'auto bottom',
- calculateBoundingRect: true
- });
- var infoScale = new InfographicScaleView({
- content: this.content,
- visModel: this.visModel,
- widget: this.visModel.ownerWidget,
- widgetValue: this.infographicValue,
- isPercent: this.isPercent,
- currentScaleOption: this.graphicCurrentScaleOption
- });
- toolbar.addItems([{
- responsive: false,
- editable: false,
- changedAction: null,
- subView: infoScale,
- type: 'SubView'
- }]);
- this.visModel.ownerWidget.on('expandedView:visible', this.onShowExpandedContent.bind(this, toolbar));
- this.$el.closest('.page.pageabsolute, .page.pagecontainer').on('scroll.BaseController', this.scaleToolbarOnScroll.bind(this, toolbar));
- this.toolbarShown = true;
- // Checks when the update scale toolbar is closed
- toolbar.on('toolbar:hide', this.onToolbarClosed, this);
- toolbar.on('toolbar:show', function () {
- infoScale.setFocus();
- });
- toolbar.setSelectionContext([event.target]);
- return toolbar.show();
- } else {
- return undefined;
- }
- },
- /**
- * Event handler called when toolbar is closed
- * @returns Sets that no toolbar exists
- */
- onToolbarClosed: function onToolbarClosed() {
- this.toolbarShown = false;
- },
- /**
- * Closes the scale toolbar when expanded view is shown
- * @param toolbar - toolbar object that needs to be closed
- * @param event - Event that triggered the function call
- * @returns {undefined} toolbar is closed and event listener turned off
- */
- onShowExpandedContent: function onShowExpandedContent(toolbar) {
- this.visModel.ownerWidget.off('expandedView:visible', this.onShowExpandedContent, this);
- toolbar.hide();
- },
- /**
- * Closes the scale toolbar on page scroll
- * @param toolbar - toolbar object that needs to be closed
- * @param event - Event that triggered the function call (need to confirm it is a scroll we want to close the toolbar on)
- * @returns {undefined} If valid page scroll the toolbar is closed
- */
- scaleToolbarOnScroll: function scaleToolbarOnScroll(toolbar, event) {
- if (event.target.classList.contains('page') && (event.target.classList.contains('pageabsolute') || event.target.classList.contains('pagecontainer'))) {
- //remove event listener when the toolbar is hidden
- this.$el.closest('.page.pageabsolute, .page.pagecontainer').off('scroll.BaseController');
- toolbar.hide();
- }
- },
- /**
- * Create the HTML output
- * @param queryResults The query results
- * @param doFillAnimation Whether or not the fill animation should be rendered
- */
- buildHTML: function buildHTML(queryResults, doFillAnimation) {
- this.infographicInfo = {};
- var slots = this.content.getFeature('Visualization').getSlots().getMappedSlotList();
- if (!slots || slots.length === 0) {
- return Promise.resolve('');
- }
- this.infographicValue = queryResults && queryResults.getRowCount() > 0 ? queryResults.getValue(0, 0).value : null;
- this.infographicInfo = this._buildHtml(slots[0], this.infographicValue, doFillAnimation);
- return this.dotTemplate(this.infographicInfo);
- },
- /**
- * Listener to rerender when theme changes (default palette changes with
- * theme)
- */
- onChangeTheme: function onChangeTheme() {
- this.visModel.getRenderSequence().reRender();
- },
- _showItemLabel: function _showItemLabel() {
- return this.visModel.getPropertyValue('showItemLabel');
- },
- _getCustomLabel: function _getCustomLabel() {
- return this.visModel.getPropertyValue('baseValueLabel');
- },
- _fillVertically: function _fillVertically() {
- return this.visModel.getPropertyValue('fillDirection') === 'BottomToTop';
- },
- /**
- * Return text formatted value for field to avoid jsHint cyclomatic
- * complexity warnings
- *
- * @param slot, slotAPI
- * @param v The raw fact value.
- * @returns text formatted data value
- */
- _valueFormat: function _valueFormat(dataItem, v) {
- var formatSpec = dataItem.getFormat();
- v = Formatter.format(v, formatSpec);
- if (v.length < 5) {
- // if the formatted value is less than 5 characters long, it
- // looks ugly when stretched so we are adding spaces before and
- // after. The SVG will size it nicely after that.
- var sPrefix = v.length < 3 ? ' ' : ' ';
- v = sPrefix + v + sPrefix;
- }
- return v;
- },
- _getForegroundColor: function _getForegroundColor() {
- var hexColor = this._colorsService.getHexColorFromDashboardColorSet(this.visModel.getPropertyValue('elementColor'));
- if (hexColor === 'transparent') {
- hexColor = '';
- }
- return hexColor;
- },
- getScaleOption: function getScaleOption() {
- var result;
- if (typeof this.graphicCurrentScaleOption !== 'undefined') {
- result = this.graphicCurrentScaleOption;
- } else {
- result = ScaleUtil.SCALE_VALUE_DEFAULT;
- }
- return result;
- },
- getScaleValue: function getScaleValue(scaleProperties, currentOption, value) {
- var result;
- if (scaleProperties && typeof currentOption !== 'undefined') {
- var scales = scaleProperties.availableScales;
- if (currentOption === ScaleUtil.SCALE_VALUE_DEFAULT) {
- // Default value
- result = scaleProperties.optimalScale;
- } else if (currentOption === ScaleUtil.SCALE_VALUE_MANY) {
- // Many
- var scaleManyIndex = scales.indexOf(scaleProperties.optimalScale) - 1;
- if (scaleManyIndex < 0) {
- scaleManyIndex = 0;
- }
- result = scales[scaleManyIndex];
- } else {
- // Few
- var scaleFewIndex = scales.indexOf(scaleProperties.optimalScale) + 1;
- if (scaleFewIndex > scales.length - 1) {
- scaleFewIndex = scales.length - 1;
- }
- result = scales[scaleFewIndex];
- }
- } else if (value) {
- result = ScaleUtil.calcOptimalValue(value);
- }
- return result;
- },
- // livewidget_cleanup there might be a defect here...
- // the correnspoding unittest: `render as infographic` is not passing..
- _calcScale: function _calcScale(v) {
- var scaleObj = {};
- var scaleProperties = ScaleUtil.getScalingProperties(v, this.isPercent);
- scaleObj.currentScaleOption = this.getScaleOption();
- scaleObj.scale = this.getScaleValue(scaleProperties, scaleObj.currentScaleOption, v);
- scaleObj.abbreviatedScale = Formatter.format(scaleObj.scale, {
- decimalFormatLength: 'short'
- });
- // Determine the number of shapes and partial shapes to display
- scaleObj.scaleComponents = ScaleUtil.getScaleComponents(v, scaleObj.scale, this.isPercent);
- return scaleObj;
- },
- _isPercent: function _isPercent(dataItem, v) {
- var dataItemFormat = dataItem.getFormat();
- return dataItemFormat && dataItemFormat.type === 'percent' && Math.abs(v) <= 1;
- },
- _constructShapeStyle: function _constructShapeStyle(hexColor) {
- var shapeStyle = {};
- var property = 'fill:';
- var colorClass = 'colorFill';
- shapeStyle.svgClass = 'Shape';
- if (this.graphicFillColor === 'transparent') {
- colorClass = 'colorStroke';
- property = 'stroke:';
- shapeStyle.svgClass = 'Line';
- }
- if (hexColor !== '') {
- shapeStyle.colorClass = '';
- shapeStyle.coloredStyle = 'style="' + property + hexColor + '"';
- } else {
- shapeStyle.colorClass = colorClass;
- shapeStyle.coloredStyle = '';
- }
- return shapeStyle;
- },
- /**
- * Return information needed to construct the HTML output The values and
- * html fragments returned construct the output.
- *
- * @param slot The slot item which gets its label rendered when showItemLabel is true.
- * @param v The fact value which is rendered into a scaleable svg viewBox.
- * @param doFillAnimation Whether or not the fill animation should be rendered
- * @returns a data structure with values to substitute into the html templateString.
- */
- _buildHtml: function _buildHtml(slot, v, doFillAnimation) {
- var dataItemIndex = 0; //Infographic view only has one data item
- var dataItem = slot.getDataItemList()[dataItemIndex];
- var hexColor = this._getForegroundColor();
- var infographic;
- var fillVertical = this._fillVertically();
- var shapeStyle = this._constructShapeStyle(hexColor);
- this.isPercent = this._isPercent(dataItem, v);
- var scaleObj = this._calcScale(v);
- var scale = scaleObj.scale;
- var scaleComponents = scaleObj.scaleComponents;
- // keep original viewbox
- var matches = this.graphicContent.match(RegExp('viewBox="(.*?)"'));
- var viewBox = matches && matches[1] ? matches[1] : '0 0 100 100';
- var svgContent = this._extractSvgContent(this.graphicContent);
- var clipPathLastElement = this._calculateClipPath({
- x: viewBox.split(' ')[0],
- y: viewBox.split(' ')[1],
- width: viewBox.split(' ')[2],
- height: viewBox.split(' ')[3]
- }, scaleComponents, fillVertical, doFillAnimation);
- infographic = {
- content: svgContent,
- scaleComponents: scaleComponents,
- percentageScaleValue: this._getPercentageScaleValue(v, scale),
- scale: scale,
- abbreviatedScale: scaleObj.abbreviatedScale,
- viewBox: viewBox,
- svgId: _.uniqueId('svgDef'),
- style: shapeStyle,
- fillClass: fillVertical ? 'fillVertical' : 'fillHorizontal',
- doFillAnimation: doFillAnimation,
- // 0.5 -> 500ms animation
- fillDuration: 0.5 / (scaleComponents.numShapes + 1),
- clipPathLastElement: clipPathLastElement
- };
- // Update the graphic model data with things calculated
- this.currentScaleOption = scaleObj.currentScaleOption;
- this.numOfShapes = scaleComponents.numShapes + scaleComponents.numGreyedShapes;
- if (scaleComponents.partialValue > 0) {
- this.numOfShapes++;
- }
- v = this._valueFormat(dataItem, v);
- return {
- v: v,
- showTitleString: this._showItemLabel(),
- label: this._getCustomLabel() || dataItem.getLabel(),
- infographic: infographic
- };
- },
- /**
- * Calculates the string to display for the percentage scale value (i.e. +/- 1%, 10%, or 100%)
- * @param value - The percentage value we are displaying
- * @param scale - The current scale value being used
- * @returns String to be displayed for legend
- */
- _getPercentageScaleValue: function _getPercentageScaleValue(value, scale) {
- if (!this.isPercent) {
- return false;
- }
- var result = scale * 100;
- if (value < 0) {
- result *= -1;
- }
- return Formatter.format(result / 100, {
- type: 'percent',
- maximumFractionDigits: 1,
- minimumFractionalDigits: 1
- });
- },
- /**
- * A helper function that calculates clip path
- * @param {object} viewBoxProp - properties of viewBox {x:xxx, y: xxx, width: xxx, height: xxx}
- * @param {object} scale - scale componenets
- * @param {Boolean} isVertical - true if the shapes are filled from bottom to top
- * @return {object} - a clipPath {x: xxx, y: xxx, width: xxx, height: xxx}
- */
- _calculateClipPath: function _calculateClipPath(viewBoxProp, scale, isVertical) {
- // calculate clipPath width and height based on the viewBox
- var greyedClipPath;
- var partialValue;
- if (isVertical) {
- //viewBoxProp members variable are strings. Convert viewBoxProp.x to do the arithmetic operation
- partialValue = viewBoxProp.height * (1 - scale.partialValue) + Number(viewBoxProp.y);
- greyedClipPath = {
- width: '100%',
- y: partialValue
- };
- } else {
- //viewBoxProp members variable are strings. Convert viewBoxProp.x to do the arithmetic operation
- partialValue = viewBoxProp.width * scale.partialValue + Number(viewBoxProp.x);
- greyedClipPath = {
- width: partialValue,
- y: 0
- };
- }
- return greyedClipPath;
- },
- /**
- * a helper method extract the content inside of a SVG Element
- * @param {string} content - a string that contains svg tag. eg. <svg id='hello'><circle cx="100" cy="100" r="100"/></svg>
- * @return {string} svgContent - a string that does not contain svg tag. eg. <circle cx="100" cy="100" r="100"/>
- */
- _extractSvgContent: function _extractSvgContent(content) {
- var $childrenContent = $(content).children();
- // eslint-disable-next-line no-undef
- var serializer = new XMLSerializer();
- var svgContent = '';
- $childrenContent.each(function () {
- svgContent += serializer.serializeToString(this);
- });
- return svgContent;
- },
- /**
- * Take some HTML and render it to the dom element for this view.
- *
- * @param sHtml A string of HTML to render.
- */
- addToDom: function addToDom(sHtml) {
- this.$el.empty();
- this.$el.prepend(sHtml);
- this.setElement(this.$el);
- },
- onVisible: function onVisible() {
- if (this.renderOnVisible) {
- this._resize();
- }
- },
- /**
- * Apply widget-level properties as specified either in overrides in the
- * visModel or defaults in the definition.
- */
- applyVisModelProperties: function applyVisModelProperties() {
- this._resizeInfographicShapes();
- },
- _resizeInfographicShapes: function _resizeInfographicShapes() {
- // return if nothing to show
- if (this.numOfShapes === 0) {
- return;
- }
- var $node = $(this.el).find('.infographic');
- var width = Math.floor($node.width());
- //set the height to 1 if it is zero to avoid dividing by 0 case
- var height = Math.floor($node.height()) || 1;
- var x = width,
- y = height,
- n = this.numOfShapes;
- var px = Math.ceil(Math.sqrt(n * x / y));
- var sx, sy;
- if (Math.floor(px * y / x) * px < n) {
- sx = y / Math.ceil(px * y / x);
- } else {
- sx = x / px;
- }
- var py = Math.ceil(Math.sqrt(n * y / x));
- if (Math.floor(py * x / y) * py < n) {
- sy = x / Math.ceil(x * py / y);
- } else {
- sy = y / py;
- }
- // UX design of 8 pixels of padding (4 on each side) around each
- var shapePadding = 8;
- // Subtract an extra pixel for small spacing issue
- var size = Math.max(sx, sy) - shapePadding - OFFSET;
- var sizePx = size + 'px';
- this.$el.find('.infographic .infographic-content').each(function () {
- // Going through jQuery here was really expensive on Safari, so go straight to the style attributes.
- this.style.width = this.style.height = sizePx;
- });
- },
- preRenderFillDirectionProperty: function preRenderFillDirectionProperty() {
- return {
- 'removeProperty': false
- };
- },
- setShowTranslationIcon: function setShowTranslationIcon(showTranslationIcon) {
- this._showTranslationIcon = showTranslationIcon;
- },
- updateTranslationIcon: function updateTranslationIcon() {
- var $iconContainer = this.$el.closest('.widgetContentWrapper');
- if (this._showTranslationIcon) {
- this._translationService.appendTranslationIcon($iconContainer);
- } else {
- this._translationService.removeTranslationIcon($iconContainer);
- }
- },
- getDecoratorAPI: function getDecoratorAPI() {
- var _this2 = this;
- return {
- decorateItem: function decorateItem() {
- _this2.setShowTranslationIcon(true);
- },
- decoratePoint: function decoratePoint() {
- return null;
- },
- decorate: function decorate() {
- return null;
- },
- clearItemDecoration: function clearItemDecoration() {
- _this2.setShowTranslationIcon(false);
- },
- updateDecorations: function updateDecorations() {
- _this2.updateTranslationIcon();
- }
- };
- }
- });
- return InfographicView;
- });
- //# sourceMappingURL=InfographicView.js.map
|