UNPKG

3.31 MBSource Map (JSON)View Raw
1{"version":3,"file":"Visuals.js","sources":["webpack:///webpack/bootstrap c35d750342349c7c595f?6b86**","webpack:///./src/Clients/Visuals/module.ts","webpack:///./src/Clients/Externals/ThirdPartyIP/jqueryui/1.11.4/jquery-ui.min.css","webpack:///./src/Clients/Visuals/styles/visuals.less","webpack:///./src/Clients/Visuals/images/locationButton.svg","webpack:///./src/Clients/Visuals/_references.ts","webpack:///./src/Clients/Visuals/typedefs/typedefs.ts","webpack:///./src/Clients/Visuals/common/point.ts","webpack:///./src/Clients/Visuals/common/rect.ts","webpack:///./src/Clients/Visuals/common/fontUtils.ts","webpack:///./src/Clients/Visuals/legend.ts","webpack:///./src/Clients/Visuals/types/axisScale.ts","webpack:///./src/Clients/Visuals/types/axisStyle.ts","webpack:///./src/Clients/Visuals/types/axisType.ts","webpack:///./src/Clients/Visuals/types/basicShapeType.ts","webpack:///./src/Clients/Visuals/types/imageScalingType.ts","webpack:///./src/Clients/Visuals/types/labelPosition.ts","webpack:///./src/Clients/Visuals/types/labelStyle.ts","webpack:///./src/Clients/Visuals/types/legendPosition.ts","webpack:///./src/Clients/Visuals/types/kpi_direction_type.ts","webpack:///./src/Clients/Visuals/types/lineStyle.ts","webpack:///./src/Clients/Visuals/types/outline.ts","webpack:///./src/Clients/Visuals/types/referenceLinePosition.ts","webpack:///./src/Clients/Visuals/types/slicerOrientation.ts","webpack:///./src/Clients/Visuals/types/yAxisPosition.ts","webpack:///./src/Clients/Visuals/types/sliderMode.ts","webpack:///./src/Clients/Visuals/animators/animatorCommon.ts","webpack:///./src/Clients/Visuals/animators/columnChartAnimator.ts","webpack:///./src/Clients/Visuals/animators/donutChartAnimator.ts","webpack:///./src/Clients/Visuals/animators/funnelChartAnimator.ts","webpack:///./src/Clients/Visuals/animators/treemapAnimator.ts","webpack:///./src/Clients/Visuals/capabilities/dataViewObjectProperties.ts","webpack:///./src/Clients/Visuals/capabilities/animatedNumber.capabilities.ts","webpack:///./src/Clients/Visuals/capabilities/basicShape.capabilities.ts","webpack:///./src/Clients/Visuals/capabilities/columnChart.capabilities.ts","webpack:///./src/Clients/Visuals/capabilities/comboChart.capabilities.ts","webpack:///./src/Clients/Visuals/capabilities/donutChart.capabilities.ts","webpack:///./src/Clients/Visuals/capabilities/dataDotChart.capabilities.ts","webpack:///./src/Clients/Visuals/capabilities/filledMap.capabilities.ts","webpack:///./src/Clients/Visuals/capabilities/funnelChart.capabilities.ts","webpack:///./src/Clients/Visuals/capabilities/gauge.capabilities.ts","webpack:///./src/Clients/Visuals/capabilities/imageVisual.capabilities.ts","webpack:///./src/Clients/Visuals/capabilities/scriptVisual.capabilities.ts","webpack:///./src/Clients/Visuals/capabilities/samples/consoleWriter.capabilities.ts","webpack:///./src/Clients/Visuals/visuals/samples/consoleWriter.ts","webpack:///./src/Clients/Visuals/capabilities/lineChart.capabilities.ts","webpack:///./src/Clients/Visuals/capabilities/map.capabilities.ts","webpack:///./src/Clients/Visuals/capabilities/multiRowCard.capabilities.ts","webpack:///./src/Clients/Visuals/capabilities/textbox.capabilities.ts","webpack:///./src/Clients/Visuals/capabilities/sampleVisual.capabilities.ts","webpack:///./src/Clients/Visuals/capabilities/scatterChart.capabilities.ts","webpack:///./src/Clients/Visuals/capabilities/slicer.capabilities.ts","webpack:///./src/Clients/Visuals/capabilities/table.capabilities.ts","webpack:///./src/Clients/Visuals/capabilities/matrix.capabilities.ts","webpack:///./src/Clients/Visuals/capabilities/treemap.capabilities.ts","webpack:///./src/Clients/Visuals/capabilities/card.capabilities.ts","webpack:///./src/Clients/Visuals/capabilities/waterfallChart.capabilities.ts","webpack:///./src/Clients/Visuals/capabilities/kpiStatusWithHistory.capabilities.ts","webpack:///./src/Clients/Visuals/pluginsCapabilities.ts","webpack:///./src/Clients/Visuals/behaviours/columnChartBehaviors.ts","webpack:///./src/Clients/Visuals/behaviours/dataDotChartBehavior.ts","webpack:///./src/Clients/Visuals/behaviours/donutChartBehaviors.ts","webpack:///./src/Clients/Visuals/behaviours/funnelBehaviors.ts","webpack:///./src/Clients/Visuals/behaviours/playChartBehaviors.ts","webpack:///./src/Clients/Visuals/behaviours/lineChartBehaviors.ts","webpack:///./src/Clients/Visuals/behaviours/mapBehaviors.ts","webpack:///./src/Clients/Visuals/behaviours/scatterChartBehaviors.ts","webpack:///./src/Clients/Visuals/behaviours/horizontalSlicerBehaviors.ts","webpack:///./src/Clients/Visuals/behaviours/verticalSlicerBehaviors.ts","webpack:///./src/Clients/Visuals/behaviours/slicerBehaviors.ts","webpack:///./src/Clients/Visuals/behaviours/legendBehaviors.ts","webpack:///./src/Clients/Visuals/behaviours/treemapBehaviors.ts","webpack:///./src/Clients/Visuals/behaviours/waterfallChartBehavior.ts","webpack:///./src/Clients/Visuals/behaviours/LabelsBehavior.ts","webpack:///./src/Clients/Visuals/behaviours/cartesianChartBehaviors.ts","webpack:///./src/Clients/Visuals/contracts/contracts.ts","webpack:///./src/Clients/Visuals/common/axisHelper.ts","webpack:///./src/Clients/Visuals/common/basicShapeUtils.ts","webpack:///./src/Clients/Visuals/common/cartesianHelper.ts","webpack:///./src/Clients/Visuals/common/colorHelper.ts","webpack:///./src/Clients/Visuals/common/columnChartUtil.ts","webpack:///./src/Clients/Visuals/common/converterHelper.ts","webpack:///./src/Clients/Visuals/common/dataLabelUtils.ts","webpack:///./src/Clients/Visuals/common/donutLabelUtils.ts","webpack:///./src/Clients/Visuals/common/newDataLabelUtils.ts","webpack:///./src/Clients/Visuals/common/kpiUtil.ts","webpack:///./src/Clients/Visuals/common/dateUtil.ts","webpack:///./src/Clients/Visuals/common/labelDataPointSorter.ts","webpack:///./src/Clients/Visuals/common/referenceLineHelper.ts","webpack:///./src/Clients/Visuals/common/interactivityUtils.ts","webpack:///./src/Clients/Visuals/common/invalidDataValuesChecker.ts","webpack:///./src/Clients/Visuals/common/listView.ts","webpack:///./src/Clients/Visuals/common/mapUtil.ts","webpack:///./src/Clients/Visuals/common/selectionManager.ts","webpack:///./src/Clients/Visuals/common/shapes.ts","webpack:///./src/Clients/Visuals/common/slicerUtil.ts","webpack:///./src/Clients/Visuals/common/tooltipUtils.ts","webpack:///./src/Clients/Visuals/common/svgUtil.ts","webpack:///./src/Clients/Visuals/common/textUtil.ts","webpack:///./src/Clients/Visuals/common/gradientHelper.ts","webpack:///./src/Clients/Visuals/common/visualBackgroundHelper.ts","webpack:///./src/Clients/Visuals/common/objectEnumerationBuilder.ts","webpack:///./src/Clients/Visuals/common/visualBorderUtil.ts","webpack:///./src/Clients/Visuals/common/transform.ts","webpack:///./src/Clients/Visuals/common/trendLineHelper.ts","webpack:///./src/Clients/Visuals/common/visibilityUtil.ts","webpack:///./src/Clients/Visuals/common/visualObjectRepetition.ts","webpack:///./src/Clients/Visuals/converter/slicer.converter.ts","webpack:///./src/Clients/Visuals/formatting/dataLabelManager.ts","webpack:///./src/Clients/Visuals/formatting/labelLayout.ts","webpack:///./src/Clients/Visuals/formatting/donutLabelLayout.ts","webpack:///./src/Clients/Visuals/formatting/filledMapLabelLayout.ts","webpack:///./src/Clients/Visuals/services/colorAllocatorFactory.ts","webpack:///./src/Clients/Visuals/services/defaultVisualHostService.ts","webpack:///./src/Clients/Visuals/services/interactivityService.ts","webpack:///./src/Clients/Visuals/services/geocoder.ts","webpack:///./src/Clients/Visuals/services/geocodingCache.ts","webpack:///./src/Clients/Visuals/services/geolocationService.ts","webpack:///./src/Clients/Visuals/controls/scrollbar/scrollbar.ts","webpack:///./src/Clients/Visuals/controls/tablix/internal/tablixGridPresenter.ts","webpack:///./src/Clients/Visuals/controls/tablix/internal/tablixRealizationManager.ts","webpack:///./src/Clients/Visuals/controls/tablix/internal/tablixGrid.ts","webpack:///./src/Clients/Visuals/controls/tablix/internal/tablixLayoutManager.ts","webpack:///./src/Clients/Visuals/controls/tablix/internal/tablixUtils.ts","webpack:///./src/Clients/Visuals/controls/tablix/iTablixHierarchyNavigator.ts","webpack:///./src/Clients/Visuals/controls/tablix/iTablixBinder.ts","webpack:///./src/Clients/Visuals/controls/tablix/iTablixLayoutManager.ts","webpack:///./src/Clients/Visuals/controls/tablix/tablixControl.ts","webpack:///./src/Clients/Visuals/controls/tablix/tablixDimension.ts","webpack:///./src/Clients/Visuals/controls/tablix/tablixTouchDelegate.ts","webpack:///./src/Clients/Visuals/controls/tablix/touchRegionAbstraction.ts","webpack:///./src/Clients/Visuals/controls/tablix/ITablixFormatting.ts","webpack:///./src/Clients/Visuals/controls/tablix/tablixColumnWidthManager.ts","webpack:///./src/Clients/Visuals/visuals/animatedText.ts","webpack:///./src/Clients/Visuals/visuals/animatedNumber.ts","webpack:///./src/Clients/Visuals/visuals/basicShape.ts","webpack:///./src/Clients/Visuals/cartesian/cartesianChart.ts","webpack:///./src/Clients/Visuals/cartesian/columnChart.ts","webpack:///./src/Clients/Visuals/cartesian/columnChartClustered.ts","webpack:///./src/Clients/Visuals/cartesian/columnChartStacked.ts","webpack:///./src/Clients/Visuals/visuals/samples/helloIVisual.ts","webpack:///./src/Clients/Visuals/cartesian/comboChart.ts","webpack:///./src/Clients/Visuals/dataColorPalette.ts","webpack:///./src/Clients/Visuals/cartesian/dataDotChart.ts","webpack:///./src/Clients/Visuals/visuals/funnelChart.ts","webpack:///./src/Clients/Visuals/visuals/gauge.ts","webpack:///./src/Clients/Visuals/visuals/imageVisual.ts","webpack:///./src/Clients/Visuals/visuals/kpiStatusWithHistory.ts","webpack:///./src/Clients/Visuals/cartesian/lineChart.ts","webpack:///./src/Clients/Visuals/visuals/map.ts","webpack:///./src/Clients/Visuals/visuals/multiRowCard.ts","webpack:///./src/Clients/Visuals/visuals/textbox.ts","webpack:///./src/Clients/Visuals/visuals/sampleVisual.ts","webpack:///./src/Clients/Visuals/cartesian/scatterChart.ts","webpack:///./src/Clients/Visuals/cartesian/playChart.ts","webpack:///./src/Clients/Visuals/visuals/verticalSlicer.ts","webpack:///./src/Clients/Visuals/visuals/horizontalSlicer.ts","webpack:///./src/Clients/Visuals/visuals/slicer.ts","webpack:///./src/Clients/Visuals/visuals/table.ts","webpack:///./src/Clients/Visuals/visuals/matrix.ts","webpack:///./src/Clients/Visuals/visuals/treemap.ts","webpack:///./src/Clients/Visuals/visuals/card.ts","webpack:///./src/Clients/Visuals/visuals/owlGauge.ts","webpack:///./src/Clients/Visuals/warnings/visualWarnings.ts","webpack:///./src/Clients/Visuals/cartesian/waterfallChart.ts","webpack:///./src/Clients/Visuals/tooltip.ts","webpack:///./src/Clients/Visuals/styles/visualStyles.ts","webpack:///./src/Clients/Visuals/visuals/donutChart.ts","webpack:///./src/Clients/Visuals/visuals/scriptVisual.ts","webpack:///./src/Clients/Visuals/visuals/system/debugVisual.ts","webpack:///./src/Clients/Visuals/plugins.ts","webpack:///./src/Clients/Visuals/common/canvasBackgroundHelper.ts","webpack:///./src/Clients/Visuals/common/scaleRange.ts","webpack:///./src/Clients/Visuals/stylePresets/table.stylePresets.ts"],"sourcesContent":[" \t// The module cache\n \tvar installedModules = {};\n\n \t// The require function\n \tfunction __webpack_require__(moduleId) {\n\n \t\t// Check if module is in cache\n \t\tif(installedModules[moduleId])\n \t\t\treturn installedModules[moduleId].exports;\n\n \t\t// Create a new module (and put it into the cache)\n \t\tvar module = installedModules[moduleId] = {\n \t\t\texports: {},\n \t\t\tid: moduleId,\n \t\t\tloaded: false\n \t\t};\n\n \t\t// Execute the module function\n \t\tmodules[moduleId].call(module.exports, module, module.exports, __webpack_require__);\n\n \t\t// Flag the module as loaded\n \t\tmodule.loaded = true;\n\n \t\t// Return the exports of the module\n \t\treturn module.exports;\n \t}\n\n\n \t// expose the modules object (__webpack_modules__)\n \t__webpack_require__.m = modules;\n\n \t// expose the module cache\n \t__webpack_require__.c = installedModules;\n\n \t// __webpack_public_path__\n \t__webpack_require__.p = \"\";\n\n \t// Load entry module and return exports\n \treturn __webpack_require__(0);\n\n\n\n/** WEBPACK FOOTER **\n ** webpack/bootstrap c35d750342349c7c595f\n **/","/// <reference path=\"./_references.ts\"/>\r\n\r\nwindow.jsCommon = window.jsCommon || {};\r\nwindow.powerbi = window.powerbi || {};\r\nwindow.debug = window.debug || {};\r\nwindow.InJs = window.InJs || {};\r\nwindow.Microsoft = window.Microsoft || {};\r\n\r\nrequire(\"../Externals/ThirdPartyIP/jqueryui/1.11.4/jquery-ui.min.css\");\r\nrequire(\"./styles/visuals.less\");\r\n\r\nrequire(\"file?name=images/[name].[ext]!./images/locationButton.svg\");\r\n\r\n// Require all files from the `_references.ts`\r\nrequire(\"ReferencesLoader!./_references.ts\");\r\n\n\n\n/** WEBPACK FOOTER **\n ** ./src/Clients/Visuals/module.ts\n **/","// removed by extract-text-webpack-plugin\n\n\n/*****************\n ** WEBPACK FOOTER\n ** ./src/Clients/Externals/ThirdPartyIP/jqueryui/1.11.4/jquery-ui.min.css\n ** module id = 91\n ** module chunks = 2 7\n **/","// removed by extract-text-webpack-plugin\n\n\n/*****************\n ** WEBPACK FOOTER\n ** ./src/Clients/Visuals/styles/visuals.less\n ** module id = 92\n ** module chunks = 2 7\n **/","module.exports = __webpack_public_path__ + \"images/locationButton.svg\";\n\n\n/*****************\n ** WEBPACK FOOTER\n ** ./~/file-loader?name=images/[name].[ext]!./src/Clients/Visuals/images/locationButton.svg\n ** module id = 93\n ** module chunks = 2 7\n **/","/*** IMPORTS FROM imports-loader ***/\nvar jsCommon = window.jsCommon;\nvar powerbi = window.powerbi;\nvar powerbitests = window.powerbitests;\nvar InJs = window.InJs;\nvar debug = window.debug;\nvar jasmine = window.jasmine;\nvar Microsoft = window.Microsoft;\n\nrequire(\"./typedefs/typedefs.ts\");\r\n/// <reference path=\"../VisualsContracts/_references.ts\"/>\r\n/// <reference path=\"../VisualsCommon/_references.ts\"/>\r\n/// <reference path=\"../VisualsData/_references.ts\"/>\r\n/// <reference path=\"../VisualsExtensibility/_references.ts\"/>\r\n/// <reference path=\"../../../src/Clients/Typedefs/webpack/webpack-env.d.ts\"/>\r\n/// <reference path=\"../../../src/Clients/Typedefs/common/window.d.ts\"/>\r\nrequire(\"./common/point.ts\");\r\nrequire(\"./common/rect.ts\");\r\nrequire(\"./common/fontUtils.ts\");\r\nrequire(\"./legend.ts\");\r\nrequire(\"./types/axisScale.ts\");\r\nrequire(\"./types/axisStyle.ts\");\r\nrequire(\"./types/axisType.ts\");\r\nrequire(\"./types/basicShapeType.ts\");\r\nrequire(\"./types/imageScalingType.ts\");\r\nrequire(\"./types/labelPosition.ts\");\r\nrequire(\"./types/labelStyle.ts\");\r\nrequire(\"./types/legendPosition.ts\");\r\nrequire(\"./types/kpi_direction_type.ts\");\r\nrequire(\"./types/lineStyle.ts\");\r\nrequire(\"./types/outline.ts\");\r\nrequire(\"./types/referenceLinePosition.ts\");\r\nrequire(\"./types/slicerOrientation.ts\");\r\nrequire(\"./types/yAxisPosition.ts\");\r\nrequire(\"./types/sliderMode.ts\");\r\nrequire(\"./animators/animatorCommon.ts\");\r\nrequire(\"./animators/columnChartAnimator.ts\");\r\nrequire(\"./animators/donutChartAnimator.ts\");\r\nrequire(\"./animators/funnelChartAnimator.ts\");\r\nrequire(\"./animators/treemapAnimator.ts\");\r\nrequire(\"./capabilities/dataViewObjectProperties.ts\");\r\nrequire(\"./capabilities/animatedNumber.capabilities.ts\");\r\nrequire(\"./capabilities/basicShape.capabilities.ts\");\r\nrequire(\"./capabilities/columnChart.capabilities.ts\");\r\nrequire(\"./capabilities/comboChart.capabilities.ts\");\r\nrequire(\"./capabilities/donutChart.capabilities.ts\");\r\nrequire(\"./capabilities/dataDotChart.capabilities.ts\");\r\nrequire(\"./capabilities/filledMap.capabilities.ts\");\r\nrequire(\"./capabilities/funnelChart.capabilities.ts\");\r\nrequire(\"./capabilities/gauge.capabilities.ts\");\r\nrequire(\"./capabilities/imageVisual.capabilities.ts\");\r\nrequire(\"./capabilities/scriptVisual.capabilities.ts\");\r\nrequire(\"./capabilities/samples/consoleWriter.capabilities.ts\");\r\nrequire(\"./visuals/samples/consoleWriter.ts\");\r\nrequire(\"./capabilities/lineChart.capabilities.ts\");\r\nrequire(\"./capabilities/map.capabilities.ts\");\r\nrequire(\"./capabilities/multiRowCard.capabilities.ts\");\r\nrequire(\"./capabilities/textbox.capabilities.ts\");\r\nrequire(\"./capabilities/sampleVisual.capabilities.ts\");\r\nrequire(\"./capabilities/scatterChart.capabilities.ts\");\r\nrequire(\"./capabilities/slicer.capabilities.ts\");\r\nrequire(\"./capabilities/table.capabilities.ts\");\r\nrequire(\"./capabilities/matrix.capabilities.ts\");\r\nrequire(\"./capabilities/treemap.capabilities.ts\");\r\nrequire(\"./capabilities/card.capabilities.ts\");\r\nrequire(\"./capabilities/waterfallChart.capabilities.ts\");\r\nrequire(\"./capabilities/kpiStatusWithHistory.capabilities.ts\");\r\nrequire(\"./pluginsCapabilities.ts\");\r\nrequire(\"./behaviours/columnChartBehaviors.ts\");\r\nrequire(\"./behaviours/dataDotChartBehavior.ts\");\r\nrequire(\"./behaviours/donutChartBehaviors.ts\");\r\nrequire(\"./behaviours/funnelBehaviors.ts\");\r\nrequire(\"./behaviours/playChartBehaviors.ts\");\r\nrequire(\"./behaviours/lineChartBehaviors.ts\");\r\nrequire(\"./behaviours/mapBehaviors.ts\");\r\nrequire(\"./behaviours/scatterChartBehaviors.ts\");\r\nrequire(\"./behaviours/horizontalSlicerBehaviors.ts\");\r\nrequire(\"./behaviours/verticalSlicerBehaviors.ts\");\r\nrequire(\"./behaviours/slicerBehaviors.ts\");\r\nrequire(\"./behaviours/legendBehaviors.ts\");\r\nrequire(\"./behaviours/treemapBehaviors.ts\");\r\nrequire(\"./behaviours/waterfallChartBehavior.ts\");\r\nrequire(\"./behaviours/LabelsBehavior.ts\");\r\nrequire(\"./behaviours/cartesianChartBehaviors.ts\");\r\nrequire(\"./contracts/contracts.ts\");\r\nrequire(\"./common/axisHelper.ts\");\r\nrequire(\"./common/basicShapeUtils.ts\");\r\nrequire(\"./common/cartesianHelper.ts\");\r\nrequire(\"./common/colorHelper.ts\");\r\nrequire(\"./common/columnChartUtil.ts\");\r\nrequire(\"./common/converterHelper.ts\");\r\nrequire(\"./common/dataLabelUtils.ts\");\r\nrequire(\"./common/donutLabelUtils.ts\");\r\nrequire(\"./common/newDataLabelUtils.ts\");\r\nrequire(\"./common/kpiUtil.ts\");\r\nrequire(\"./common/dateUtil.ts\");\r\nrequire(\"./common/labelDataPointSorter.ts\");\r\nrequire(\"./common/referenceLineHelper.ts\");\r\nrequire(\"./common/interactivityUtils.ts\");\r\nrequire(\"./common/invalidDataValuesChecker.ts\");\r\nrequire(\"./common/listView.ts\");\r\nrequire(\"./common/mapUtil.ts\");\r\nrequire(\"./common/selectionManager.ts\");\r\nrequire(\"./common/shapes.ts\");\r\nrequire(\"./common/slicerUtil.ts\");\r\nrequire(\"./common/tooltipUtils.ts\");\r\nrequire(\"./common/svgUtil.ts\");\r\nrequire(\"./common/textUtil.ts\");\r\nrequire(\"./common/gradientHelper.ts\");\r\nrequire(\"./common/visualBackgroundHelper.ts\");\r\nrequire(\"./common/objectEnumerationBuilder.ts\");\r\nrequire(\"./common/visualBorderUtil.ts\");\r\nrequire(\"./common/transform.ts\");\r\nrequire(\"./common/trendLineHelper.ts\");\r\nrequire(\"./common/visibilityUtil.ts\");\r\nrequire(\"./common/visualObjectRepetition.ts\");\r\nrequire(\"./converter/slicer.converter.ts\");\r\nrequire(\"./formatting/dataLabelManager.ts\");\r\nrequire(\"./formatting/labelLayout.ts\");\r\nrequire(\"./formatting/donutLabelLayout.ts\");\r\nrequire(\"./formatting/filledMapLabelLayout.ts\");\r\nrequire(\"./services/colorAllocatorFactory.ts\");\r\nrequire(\"./services/defaultVisualHostService.ts\");\r\nrequire(\"./services/interactivityService.ts\");\r\nrequire(\"./services/geocoder.ts\");\r\nrequire(\"./services/geocodingCache.ts\");\r\nrequire(\"./services/geolocationService.ts\");\r\nrequire(\"./controls/scrollbar/scrollbar.ts\");\r\nrequire(\"./controls/tablix/internal/tablixGridPresenter.ts\");\r\nrequire(\"./controls/tablix/internal/tablixRealizationManager.ts\");\r\nrequire(\"./controls/tablix/internal/tablixGrid.ts\");\r\nrequire(\"./controls/tablix/internal/tablixLayoutManager.ts\");\r\nrequire(\"./controls/tablix/internal/tablixUtils.ts\");\r\nrequire(\"./controls/tablix/iTablixHierarchyNavigator.ts\");\r\nrequire(\"./controls/tablix/iTablixBinder.ts\");\r\nrequire(\"./controls/tablix/iTablixLayoutManager.ts\");\r\nrequire(\"./controls/tablix/tablixControl.ts\");\r\nrequire(\"./controls/tablix/tablixDimension.ts\");\r\nrequire(\"./controls/tablix/tablixTouchDelegate.ts\");\r\nrequire(\"./controls/tablix/touchRegionAbstraction.ts\");\r\nrequire(\"./controls/tablix/ITablixFormatting.ts\");\r\nrequire(\"./controls/tablix/tablixColumnWidthManager.ts\");\r\nrequire(\"./visuals/animatedText.ts\");\r\nrequire(\"./visuals/animatedNumber.ts\");\r\nrequire(\"./visuals/basicShape.ts\");\r\nrequire(\"./cartesian/cartesianChart.ts\");\r\nrequire(\"./cartesian/columnChart.ts\");\r\nrequire(\"./cartesian/columnChartClustered.ts\");\r\nrequire(\"./cartesian/columnChartStacked.ts\");\r\nrequire(\"./visuals/samples/helloIVisual.ts\");\r\nrequire(\"./cartesian/comboChart.ts\");\r\nrequire(\"./dataColorPalette.ts\");\r\nrequire(\"./cartesian/dataDotChart.ts\");\r\nrequire(\"./visuals/funnelChart.ts\");\r\nrequire(\"./visuals/gauge.ts\");\r\nrequire(\"./visuals/imageVisual.ts\");\r\nrequire(\"./visuals/kpiStatusWithHistory.ts\");\r\nrequire(\"./cartesian/lineChart.ts\");\r\nrequire(\"./visuals/map.ts\");\r\nrequire(\"./visuals/multiRowCard.ts\");\r\nrequire(\"./visuals/textbox.ts\");\r\nrequire(\"./visuals/sampleVisual.ts\");\r\nrequire(\"./cartesian/scatterChart.ts\");\r\nrequire(\"./cartesian/playChart.ts\");\r\nrequire(\"./visuals/verticalSlicer.ts\");\r\nrequire(\"./visuals/horizontalSlicer.ts\");\r\nrequire(\"./visuals/slicer.ts\");\r\nrequire(\"./visuals/table.ts\");\r\nrequire(\"./visuals/matrix.ts\");\r\nrequire(\"./visuals/treemap.ts\");\r\nrequire(\"./visuals/card.ts\");\r\nrequire(\"./visuals/owlGauge.ts\");\r\nrequire(\"./warnings/visualWarnings.ts\");\r\nrequire(\"./cartesian/waterfallChart.ts\");\r\nrequire(\"./tooltip.ts\");\r\nrequire(\"./styles/visualStyles.ts\");\r\nrequire(\"./visuals/donutChart.ts\");\r\nrequire(\"./visuals/scriptVisual.ts\");\r\nrequire(\"./visuals/system/debugVisual.ts\");\r\nrequire(\"./plugins.ts\");\r\nrequire(\"./common/canvasBackgroundHelper.ts\");\r\nrequire(\"./common/scaleRange.ts\");\r\nrequire(\"./stylePresets/table.stylePresets.ts\"); \r\n\n\n\n\n/*****************\n ** WEBPACK FOOTER\n ** ./build/webpack/ReferencesLoader.js!./src/Clients/Visuals/_references.ts\n ** module id = 94\n ** module chunks = 2 7\n **/","/*\r\n * Power BI Visualizations\r\n *\r\n * Copyright (c) Microsoft Corporation\r\n * All rights reserved. \r\n * MIT License\r\n *\r\n * Permission is hereby granted, free of charge, to any person obtaining a copy\r\n * of this software and associated documentation files (the \"\"Software\"\"), to deal\r\n * in the Software without restriction, including without limitation the rights\r\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\r\n * copies of the Software, and to permit persons to whom the Software is\r\n * furnished to do so, subject to the following conditions:\r\n * \r\n * The above copyright notice and this permission notice shall be included in \r\n * all copies or substantial portions of the Software.\r\n * \r\n * THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR \r\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, \r\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE \r\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER \r\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\r\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\r\n * THE SOFTWARE.\r\n */\r\n\r\n///<reference path=\"../../Typedefs/d3/d3.d.ts\"/>\r\n///<reference path=\"../../Typedefs/jquery-visible/jquery-visible.d.ts\"/>\r\n///<reference path=\"../../Typedefs/jquery/jquery.d.ts\"/>\r\n///<reference path=\"../../Typedefs/microsoftMaps/Microsoft.Maps.d.ts\" />\r\n///<reference path=\"../../Typedefs/moment/moment.d.ts\"/>\r\n///<reference path=\"../../Typedefs/velocity/velocity-animate.d.ts\"/>\r\n///<reference path=\"../../Typedefs/lodash/lodash.d.ts\"/>\r\n///<reference path=\"../../Typedefs/quill/quill.d.ts\"/>\r\n///<reference path=\"../../Typedefs/ie/ie.d.ts\"/>\r\n///<reference path=\"../../Typedefs/noUiSlider/noUiSlider.d.ts\"/>\r\n///<reference path=\"../../Typedefs/jquery.scrollbar/jquery.scrollbar.d.ts\"/>\r\n\n\n\n/** WEBPACK FOOTER **\n ** ./src/Clients/Visuals/typedefs/typedefs.ts\n **/","/*\r\n * Power BI Visualizations\r\n *\r\n * Copyright (c) Microsoft Corporation\r\n * All rights reserved. \r\n * MIT License\r\n *\r\n * Permission is hereby granted, free of charge, to any person obtaining a copy\r\n * of this software and associated documentation files (the \"\"Software\"\"), to deal\r\n * in the Software without restriction, including without limitation the rights\r\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\r\n * copies of the Software, and to permit persons to whom the Software is\r\n * furnished to do so, subject to the following conditions:\r\n * \r\n * The above copyright notice and this permission notice shall be included in \r\n * all copies or substantial portions of the Software.\r\n * \r\n * THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR \r\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, \r\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE \r\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER \r\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\r\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\r\n * THE SOFTWARE.\r\n */\r\n\r\n/// <reference path=\"../_references.ts\"/>\r\n\r\nmodule powerbi.visuals {\r\n export class Point implements IPoint {\r\n public x: number;\r\n public y: number;\r\n\r\n constructor(x?: number, y?: number) {\r\n this.x = x || 0;\r\n this.y = y || 0;\r\n }\r\n }\r\n}\n\n\n/** WEBPACK FOOTER **\n ** ./src/Clients/Visuals/common/point.ts\n **/","/*\r\n * Power BI Visualizations\r\n *\r\n * Copyright (c) Microsoft Corporation\r\n * All rights reserved. \r\n * MIT License\r\n *\r\n * Permission is hereby granted, free of charge, to any person obtaining a copy\r\n * of this software and associated documentation files (the \"\"Software\"\"), to deal\r\n * in the Software without restriction, including without limitation the rights\r\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\r\n * copies of the Software, and to permit persons to whom the Software is\r\n * furnished to do so, subject to the following conditions:\r\n * \r\n * The above copyright notice and this permission notice shall be included in \r\n * all copies or substantial portions of the Software.\r\n * \r\n * THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR \r\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, \r\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE \r\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER \r\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\r\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\r\n * THE SOFTWARE.\r\n */\r\n\r\n/// <reference path=\"../_references.ts\"/>\r\n\r\nmodule powerbi.visuals {\r\n\r\n export class Rect implements IRect {\r\n // Fields\r\n public left: number;\r\n public top: number;\r\n public width: number;\r\n public height: number;\r\n\r\n // Constructor\r\n constructor(left?: number, top?: number, width?: number, height?: number) {\r\n this.left = left || 0;\r\n this.top = top || 0;\r\n this.width = width || 0;\r\n this.height = height || 0;\r\n }\r\n }\r\n}\n\n\n/** WEBPACK FOOTER **\n ** ./src/Clients/Visuals/common/rect.ts\n **/","/*\r\n * Power BI Visualizations\r\n *\r\n * Copyright (c) Microsoft Corporation\r\n * All rights reserved. \r\n * MIT License\r\n *\r\n * Permission is hereby granted, free of charge, to any person obtaining a copy\r\n * of this software and associated documentation files (the \"\"Software\"\"), to deal\r\n * in the Software without restriction, including without limitation the rights\r\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\r\n * copies of the Software, and to permit persons to whom the Software is\r\n * furnished to do so, subject to the following conditions:\r\n * \r\n * The above copyright notice and this permission notice shall be included in \r\n * all copies or substantial portions of the Software.\r\n * \r\n * THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR \r\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, \r\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE \r\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER \r\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\r\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\r\n * THE SOFTWARE.\r\n */\r\n\r\n/// <reference path=\"../_references.ts\"/>\r\n\r\nmodule powerbi.visuals {\r\n export module Font {\r\n\r\n export class FamilyInfo {\r\n constructor(public families: string[]) { };\r\n\r\n /**\r\n * Gets the first font \"wf_\" font family since it will always be loaded.\r\n */\r\n get family(): string {\r\n return this.getFamily();\r\n }\r\n\r\n /**\r\n * Gets the first font family that matches regex (if provided).\r\n * Default regex looks for \"wf_\" fonts which are always loaded.\r\n */\r\n getFamily(regex: RegExp = /^wf_/): string {\r\n if (!this.families) {\r\n return null;\r\n }\r\n\r\n return regex ? _.find(this.families, (fontFamily) => regex.test(fontFamily)) : this.families[0];\r\n }\r\n\r\n /**\r\n * Gets the CSS string for the \"font-family\" CSS attribute.\r\n */\r\n get css(): string {\r\n return this.getCSS();\r\n }\r\n\r\n /**\r\n * Gets the CSS string for the \"font-family\" CSS attribute.\r\n */\r\n getCSS(): string {\r\n return this.families ? this.families.map((font => font.indexOf(' ') > 0 ? \"'\" + font + \"'\" : font)).join(', ') : null;\r\n }\r\n }\r\n\r\n // These should map to the fonts in src\\clients\\StyleLibrary\\less\\fonts.less\r\n export var Family = {\r\n light: new FamilyInfo(['Segoe UI Light', 'wf_segoe-ui_light', 'helvetica', 'arial', 'sans-serif']),\r\n semilight: new FamilyInfo(['Segoe UI Semilight', 'wf_segoe-ui_semilight', 'helvetica', 'arial', 'sans-serif']),\r\n regular: new FamilyInfo(['Segoe UI', 'wf_segoe-ui_normal', 'helvetica', 'arial', 'sans-serif']),\r\n semibold: new FamilyInfo(['Segoe UI Semibold', 'wf_segoe-ui_semibold', 'helvetica', 'arial', 'sans-serif']),\r\n bold: new FamilyInfo(['Segoe UI Bold', 'wf_segoe-ui_bold', 'helvetica', 'arial', 'sans-serif']),\r\n lightSecondary: new FamilyInfo(['wf_standard-font_light', 'helvetica', 'arial', 'sans-serif']),\r\n regularSecondary: new FamilyInfo(['wf_standard-font', 'helvetica', 'arial', 'sans-serif']),\r\n boldSecondary: new FamilyInfo(['wf_standard-font_bold', 'helvetica', 'arial', 'sans-serif'])\r\n }; \r\n }\r\n}\n\n\n/** WEBPACK FOOTER **\n ** ./src/Clients/Visuals/common/fontUtils.ts\n **/","/*\r\n * Power BI Visualizations\r\n *\r\n * Copyright (c) Microsoft Corporation\r\n * All rights reserved. \r\n * MIT License\r\n *\r\n * Permission is hereby granted, free of charge, to any person obtaining a copy\r\n * of this software and associated documentation files (the \"\"Software\"\"), to deal\r\n * in the Software without restriction, including without limitation the rights\r\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\r\n * copies of the Software, and to permit persons to whom the Software is\r\n * furnished to do so, subject to the following conditions:\r\n * \r\n * The above copyright notice and this permission notice shall be included in \r\n * all copies or substantial portions of the Software.\r\n * \r\n * THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR \r\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, \r\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE \r\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER \r\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\r\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\r\n * THE SOFTWARE.\r\n */\r\n\r\n/// <reference path=\"./_references.ts\"/>\r\n\r\nmodule powerbi.visuals {\r\n import ClassAndSelector = jsCommon.CssConstants.ClassAndSelector;\r\n import createClassAndSelector = jsCommon.CssConstants.createClassAndSelector;\r\n import PixelConverter = jsCommon.PixelConverter;\r\n\r\n export enum LegendIcon {\r\n Box,\r\n Circle,\r\n Line\r\n }\r\n\r\n export enum LegendPosition {\r\n Top,\r\n Bottom,\r\n Right,\r\n Left,\r\n None,\r\n TopCenter,\r\n BottomCenter,\r\n RightCenter,\r\n LeftCenter,\r\n }\r\n\r\n export interface LegendPosition2D {\r\n textPosition?: Point;\r\n glyphPosition?: Point;\r\n }\r\n\r\n export interface LegendDataPoint extends SelectableDataPoint, LegendPosition2D {\r\n label: string;\r\n color: string;\r\n icon: LegendIcon;\r\n category?: string;\r\n measure?: any;\r\n iconOnlyOnLabel?: boolean;\r\n tooltip?: string;\r\n layerNumber?: number;\r\n }\r\n\r\n export interface LegendData {\r\n title?: string;\r\n dataPoints: LegendDataPoint[];\r\n grouped?: boolean;\r\n labelColor?: string;\r\n fontSize?: number;\r\n }\r\n\r\n export const legendProps = {\r\n show: 'show',\r\n position: 'position',\r\n titleText: 'titleText',\r\n showTitle: 'showTitle',\r\n labelColor: 'labelColor',\r\n fontSize: 'fontSize',\r\n };\r\n\r\n export function createLegend(legendParentElement: JQuery,\r\n interactive: boolean,\r\n interactivityService: IInteractivityService,\r\n isScrollable: boolean = false,\r\n legendPosition: LegendPosition = LegendPosition.Top): ILegend {\r\n if (interactive) return new CartesianChartInteractiveLegend(legendParentElement);\r\n else return new SVGLegend(legendParentElement, legendPosition, interactivityService, isScrollable);\r\n }\r\n\r\n export interface ILegend {\r\n getMargins(): IViewport;\r\n\r\n isVisible(): boolean;\r\n changeOrientation(orientation: LegendPosition): void;\r\n getOrientation(): LegendPosition;\r\n drawLegend(data: LegendData, viewport: IViewport);\r\n /**\r\n * Reset the legend by clearing it\r\n */\r\n reset(): void;\r\n }\r\n\r\n export module Legend {\r\n export function isLeft(orientation: LegendPosition): boolean {\r\n switch (orientation) {\r\n case LegendPosition.Left:\r\n case LegendPosition.LeftCenter:\r\n return true;\r\n default:\r\n return false;\r\n }\r\n }\r\n\r\n export function isTop(orientation: LegendPosition): boolean {\r\n switch (orientation) {\r\n case LegendPosition.Top:\r\n case LegendPosition.TopCenter:\r\n return true;\r\n default:\r\n return false;\r\n }\r\n }\r\n\r\n export function positionChartArea(chartArea: D3.Selection, legend: ILegend): void {\r\n let legendMargins = legend.getMargins();\r\n let legendOrientation = legend.getOrientation();\r\n chartArea.style({\r\n 'margin-left': Legend.isLeft(legendOrientation) ? legendMargins.width + 'px' : null,\r\n 'margin-top': Legend.isTop(legendOrientation) ? legendMargins.height + 'px' : null,\r\n });\r\n }\r\n }\r\n\r\n interface TitleLayout {\r\n x: number;\r\n y: number;\r\n text: string;\r\n width: number;\r\n height: number;\r\n }\r\n\r\n const enum NavigationArrowType {\r\n Increase,\r\n Decrease\r\n }\r\n\r\n interface NavigationArrow {\r\n x: number;\r\n y: number;\r\n path: string;\r\n rotateTransform: string;\r\n type: NavigationArrowType;\r\n }\r\n\r\n interface LegendLayout {\r\n numberOfItems: number;\r\n title: TitleLayout;\r\n navigationArrows: NavigationArrow[];\r\n }\r\n \r\n interface LegendItem {\r\n dataPoint: LegendDataPoint;\r\n textProperties: TextProperties;\r\n width: number;\r\n desiredWidth: number;\r\n desiredOverMaxWidth: boolean;\r\n }\r\n\r\n export class SVGLegend implements ILegend {\r\n private orientation: LegendPosition;\r\n private viewport: IViewport;\r\n private parentViewport: IViewport;\r\n private svg: D3.Selection;\r\n private group: D3.Selection;\r\n private clearCatcher: D3.Selection;\r\n private element: JQuery;\r\n private interactivityService: IInteractivityService;\r\n private legendDataStartIndex = 0;\r\n private arrowPosWindow = 1;\r\n private data: LegendData;\r\n private isScrollable: boolean;\r\n\r\n private lastCalculatedWidth = 0;\r\n private visibleLegendWidth = 0;\r\n private visibleLegendHeight = 0;\r\n private legendFontSizeMarginDifference = 0;\r\n private legendFontSizeMarginValue = 0;\r\n\r\n public static DefaultFontSizeInPt = 8;\r\n private static LegendIconRadius = 5;\r\n private static LegendIconRadiusFactor = 5;\r\n private static MaxTextLength = 60;\r\n private static MaxTitleLength = 80;\r\n private static TextAndIconPadding = 5;\r\n private static TitlePadding = 15;\r\n private static LegendEdgeMariginWidth = 10;\r\n private static LegendMaxWidthFactor = 0.3;\r\n private static TopLegendHeight = 24;\r\n private static DefaultTextMargin = PixelConverter.fromPointToPixel(SVGLegend.DefaultFontSizeInPt);\r\n private static DefaultMaxLegendFactor = SVGLegend.MaxTitleLength / SVGLegend.DefaultTextMargin;\r\n private static LegendIconYRatio = 0.52;\r\n \r\n // Navigation Arrow constants\r\n private static LegendArrowOffset = 10;\r\n private static LegendArrowHeight = 15;\r\n private static LegendArrowWidth = 7.5;\r\n\r\n private static DefaultFontFamily: string = Font.Family.regular.css;\r\n private static DefaultTitleFontFamily: string = Font.Family.semibold.css;\r\n\r\n private static LegendItem: ClassAndSelector = createClassAndSelector('legendItem');\r\n private static LegendText: ClassAndSelector = createClassAndSelector('legendText');\r\n private static LegendIcon: ClassAndSelector = createClassAndSelector('legendIcon');\r\n private static LegendTitle: ClassAndSelector = createClassAndSelector('legendTitle');\r\n private static NavigationArrow: ClassAndSelector = createClassAndSelector('navArrow');\r\n\r\n constructor(\r\n element: JQuery,\r\n legendPosition: LegendPosition,\r\n interactivityService: IInteractivityService,\r\n isScrollable: boolean) {\r\n\r\n this.svg = d3.select(element.get(0)).append('svg').style('position', 'absolute');\r\n this.svg.style('display', 'inherit');\r\n this.svg.classed('legend', true);\r\n if (interactivityService)\r\n this.clearCatcher = appendClearCatcher(this.svg);\r\n this.group = this.svg.append('g').attr('id', 'legendGroup');\r\n this.interactivityService = interactivityService;\r\n this.isScrollable = isScrollable;\r\n this.element = element;\r\n this.changeOrientation(legendPosition);\r\n this.parentViewport = { height: 0, width: 0 };\r\n this.calculateViewport();\r\n this.updateLayout();\r\n }\r\n\r\n private updateLayout() {\r\n let legendViewport = this.viewport;\r\n let orientation = this.orientation;\r\n this.svg.attr({\r\n 'height': legendViewport.height || (orientation === LegendPosition.None ? 0 : this.parentViewport.height),\r\n 'width': legendViewport.width || (orientation === LegendPosition.None ? 0 : this.parentViewport.width)\r\n });\r\n\r\n let isRight = orientation === LegendPosition.Right || orientation === LegendPosition.RightCenter;\r\n let isBottom = orientation === LegendPosition.Bottom || orientation === LegendPosition.BottomCenter;\r\n this.svg.style({\r\n 'margin-left': isRight ? (this.parentViewport.width - legendViewport.width) + 'px' : null,\r\n 'margin-top': isBottom ? (this.parentViewport.height - legendViewport.height) + 'px' : null,\r\n });\r\n }\r\n\r\n private calculateViewport(): void {\r\n switch (this.orientation) {\r\n case LegendPosition.Top:\r\n case LegendPosition.Bottom:\r\n case LegendPosition.TopCenter:\r\n case LegendPosition.BottomCenter:\r\n let pixelHeight = PixelConverter.fromPointToPixel(this.data && this.data.fontSize ? this.data.fontSize : SVGLegend.DefaultFontSizeInPt);\r\n let fontHeightSize = SVGLegend.TopLegendHeight + (pixelHeight - SVGLegend.DefaultFontSizeInPt);\r\n this.viewport = { height: fontHeightSize, width: 0 };\r\n return;\r\n case LegendPosition.Right:\r\n case LegendPosition.Left:\r\n case LegendPosition.RightCenter:\r\n case LegendPosition.LeftCenter:\r\n let width = this.lastCalculatedWidth ? this.lastCalculatedWidth : this.parentViewport.width * SVGLegend.LegendMaxWidthFactor;\r\n this.viewport = { height: 0, width: width };\r\n return;\r\n\r\n case LegendPosition.None:\r\n this.viewport = { height: 0, width: 0 };\r\n }\r\n }\r\n\r\n public getMargins(): IViewport {\r\n return this.viewport;\r\n }\r\n\r\n public isVisible(): boolean {\r\n return this.orientation !== LegendPosition.None;\r\n }\r\n\r\n public changeOrientation(orientation: LegendPosition): void {\r\n if (orientation) {\r\n this.orientation = orientation;\r\n } else {\r\n this.orientation = LegendPosition.Top;\r\n }\r\n this.svg.attr('orientation', orientation);\r\n }\r\n\r\n public getOrientation(): LegendPosition {\r\n return this.orientation;\r\n }\r\n\r\n public drawLegend(data: LegendData, viewport: IViewport): void {\r\n // clone because we modify legend item label with ellipsis if it is truncated\r\n let clonedData = Prototype.inherit(data);\r\n let newDataPoints: LegendDataPoint[] = [];\r\n for (let dp of data.dataPoints) {\r\n newDataPoints.push(Prototype.inherit(dp));\r\n }\r\n clonedData.dataPoints = newDataPoints;\r\n\r\n this.setTooltipToLegendItems(clonedData);\r\n this.drawLegendInternal(clonedData, viewport, true /* perform auto width */);\r\n }\r\n\r\n public drawLegendInternal(data: LegendData, viewport: IViewport, autoWidth: boolean): void {\r\n this.parentViewport = viewport;\r\n this.data = data;\r\n\r\n if (this.interactivityService)\r\n this.interactivityService.applySelectionStateToData(data.dataPoints);\r\n\r\n if (data.dataPoints.length === 0) {\r\n this.changeOrientation(LegendPosition.None);\r\n }\r\n\r\n if (this.getOrientation() === LegendPosition.None) {\r\n data.dataPoints = [];\r\n }\r\n\r\n // Adding back the workaround for Legend Left/Right position for Map\r\n let mapControl = this.element.children(\".mapControl\");\r\n if (mapControl.length > 0 && !this.isTopOrBottom(this.orientation)) {\r\n mapControl.css(\"display\", \"inline-block\");\r\n }\r\n\r\n this.calculateViewport();\r\n\r\n let layout = this.calculateLayout(data, autoWidth);\r\n let titleLayout = layout.title;\r\n let titleData = titleLayout ? [titleLayout] : [];\r\n let hasSelection = this.interactivityService && powerbi.visuals.dataHasSelection(data.dataPoints);\r\n\r\n let group = this.group;\r\n\r\n //transform the wrapping group if position is centered\r\n if (this.isCentered(this.orientation)) {\r\n let centerOffset = 0;\r\n if (this.isTopOrBottom(this.orientation)) {\r\n centerOffset = Math.max(0, (this.parentViewport.width - this.visibleLegendWidth) / 2);\r\n group.attr('transform', SVGUtil.translate(centerOffset, 0));\r\n }\r\n else {\r\n centerOffset = Math.max((this.parentViewport.height - this.visibleLegendHeight) / 2);\r\n group.attr('transform', SVGUtil.translate(0, centerOffset));\r\n }\r\n }\r\n else {\r\n group.attr('transform', null);\r\n }\r\n\r\n let legendTitle = group\r\n .selectAll(SVGLegend.LegendTitle.selector)\r\n .data(titleData);\r\n\r\n legendTitle.enter()\r\n .append('text')\r\n .classed(SVGLegend.LegendTitle.class, true);\r\n\r\n legendTitle\r\n .style({\r\n 'fill': data.labelColor,\r\n 'font-size': PixelConverter.fromPoint(data.fontSize),\r\n 'font-family': SVGLegend.DefaultTitleFontFamily\r\n })\r\n .text((d: TitleLayout) => d.text)\r\n .attr({\r\n 'x': (d: TitleLayout) => d.x,\r\n 'y': (d: TitleLayout) => d.y\r\n })\r\n .append('title').text(data.title);\r\n\r\n legendTitle.exit().remove();\r\n\r\n let virtualizedDataPoints = data.dataPoints.slice(this.legendDataStartIndex, this.legendDataStartIndex + layout.numberOfItems);\r\n\r\n let iconRadius = TextMeasurementService.estimateSvgTextHeight(SVGLegend.getTextProperties(false, '', this.data.fontSize)) / SVGLegend.LegendIconRadiusFactor;\r\n iconRadius = (this.legendFontSizeMarginValue > SVGLegend.DefaultTextMargin) && iconRadius > SVGLegend.LegendIconRadius\r\n ? iconRadius :\r\n SVGLegend.LegendIconRadius;\r\n\r\n let legendItems = group\r\n .selectAll(SVGLegend.LegendItem.selector)\r\n .data(virtualizedDataPoints, (d: LegendDataPoint) => d.identity.getKey() + (d.layerNumber != null ? d.layerNumber : ''));\r\n\r\n let itemsEnter = legendItems.enter()\r\n .append('g')\r\n .classed(SVGLegend.LegendItem.class, true);\r\n\r\n itemsEnter\r\n .append('circle')\r\n .classed(SVGLegend.LegendIcon.class, true);\r\n\r\n itemsEnter\r\n .append('text')\r\n .classed(SVGLegend.LegendText.class, true);\r\n\r\n itemsEnter\r\n .append('title')\r\n .text((d: LegendDataPoint) => d.tooltip);\r\n\r\n legendItems\r\n .select(SVGLegend.LegendIcon.selector)\r\n .attr({\r\n 'cx': (d: LegendDataPoint, i) => d.glyphPosition.x,\r\n 'cy': (d: LegendDataPoint) => d.glyphPosition.y,\r\n 'r': iconRadius,\r\n })\r\n .style({\r\n 'fill': (d: LegendDataPoint) => {\r\n if (hasSelection && !d.selected)\r\n return LegendBehavior.dimmedLegendColor;\r\n else\r\n return d.color;\r\n }\r\n });\r\n\r\n legendItems\r\n .select('title')\r\n .text((d: LegendDataPoint) => d.tooltip);\r\n\r\n legendItems\r\n .select(SVGLegend.LegendText.selector)\r\n .attr({\r\n 'x': (d: LegendDataPoint) => d.textPosition.x,\r\n 'y': (d: LegendDataPoint) => d.textPosition.y,\r\n })\r\n .text((d: LegendDataPoint) => d.label)\r\n .style({\r\n 'fill': data.labelColor,\r\n 'font-size': PixelConverter.fromPoint(data.fontSize)\r\n });\r\n\r\n if (this.interactivityService) {\r\n let iconsSelection = legendItems.select(SVGLegend.LegendIcon.selector);\r\n let behaviorOptions: LegendBehaviorOptions = {\r\n legendItems: legendItems,\r\n legendIcons: iconsSelection,\r\n clearCatcher: this.clearCatcher,\r\n };\r\n\r\n this.interactivityService.bind(data.dataPoints, new LegendBehavior(), behaviorOptions, { isLegend: true });\r\n }\r\n\r\n legendItems.exit().remove();\r\n\r\n this.drawNavigationArrows(layout.navigationArrows);\r\n\r\n this.updateLayout();\r\n }\r\n\r\n private normalizePosition(points: any[]): void {\r\n if (this.legendDataStartIndex >= points.length) {\r\n this.legendDataStartIndex = points.length - 1;\r\n }\r\n\r\n if (this.legendDataStartIndex < 0) {\r\n this.legendDataStartIndex = 0;\r\n }\r\n }\r\n\r\n private calculateTitleLayout(title: string): TitleLayout {\r\n let width = 0;\r\n let hasTitle = !_.isEmpty(title);\r\n\r\n if (hasTitle) {\r\n let isHorizontal = this.isTopOrBottom(this.orientation);\r\n let maxMeasureLength: number;\r\n\r\n if (isHorizontal) {\r\n let fontSizeMargin = this.legendFontSizeMarginValue > SVGLegend.DefaultTextMargin ? SVGLegend.TextAndIconPadding + this.legendFontSizeMarginDifference : SVGLegend.TextAndIconPadding;\r\n let fixedHorizontalIconShift = SVGLegend.TextAndIconPadding + SVGLegend.LegendIconRadius;\r\n let fixedHorizontalTextShift = SVGLegend.LegendIconRadius + fontSizeMargin + fixedHorizontalIconShift;\r\n // TODO This can be negative for narrow viewports. May need to rework this logic.\r\n maxMeasureLength = this.parentViewport.width * SVGLegend.LegendMaxWidthFactor - fixedHorizontalTextShift - SVGLegend.LegendEdgeMariginWidth;\r\n }\r\n else {\r\n maxMeasureLength = this.legendFontSizeMarginValue < SVGLegend.DefaultTextMargin ? SVGLegend.MaxTitleLength :\r\n SVGLegend.MaxTitleLength + (SVGLegend.DefaultMaxLegendFactor * this.legendFontSizeMarginDifference);\r\n }\r\n\r\n let textProperties = SVGLegend.getTextProperties(true, title, this.data.fontSize);\r\n let text = title;\r\n width = TextMeasurementService.measureSvgTextWidth(textProperties);\r\n\r\n if (width > maxMeasureLength) {\r\n text = TextMeasurementService.getTailoredTextOrDefault(textProperties, maxMeasureLength);\r\n textProperties.text = text;\r\n \r\n // Remeasure the text since its measurement may be different than the max (ex. when the max is negative, the text will be ellipsis, and not have a negative width)\r\n width = TextMeasurementService.measureSvgTextWidth(textProperties);\r\n };\r\n\r\n if (isHorizontal)\r\n width += SVGLegend.TitlePadding;\r\n else\r\n text = TextMeasurementService.getTailoredTextOrDefault(textProperties, this.viewport.width);\r\n\r\n return {\r\n x: 0,\r\n y: 0,\r\n text: text,\r\n width: width,\r\n height: TextMeasurementService.estimateSvgTextHeight(textProperties)\r\n };\r\n }\r\n return null;\r\n\r\n }\r\n /** Performs layout offline for optimal perfomance */\r\n private calculateLayout(data: LegendData, autoWidth: boolean): LegendLayout {\r\n let dataPoints = data.dataPoints;\r\n if (data.dataPoints.length === 0) {\r\n return {\r\n numberOfItems: 0,\r\n title: null,\r\n navigationArrows: []\r\n };\r\n }\r\n\r\n this.legendFontSizeMarginValue = PixelConverter.fromPointToPixel(this.data && this.data.fontSize !== undefined ? this.data.fontSize : SVGLegend.DefaultFontSizeInPt);\r\n this.legendFontSizeMarginDifference = (this.legendFontSizeMarginValue - SVGLegend.DefaultTextMargin);\r\n\r\n this.normalizePosition(dataPoints);\r\n if (this.legendDataStartIndex < dataPoints.length) {\r\n dataPoints = dataPoints.slice(this.legendDataStartIndex);\r\n }\r\n\r\n let title = this.calculateTitleLayout(data.title);\r\n\r\n let navArrows: NavigationArrow[];\r\n let numberOfItems: number;\r\n if (this.isTopOrBottom(this.orientation)) {\r\n navArrows = this.isScrollable ? this.calculateHorizontalNavigationArrowsLayout(title) : [];\r\n numberOfItems = this.calculateHorizontalLayout(dataPoints, title, navArrows);\r\n }\r\n else {\r\n navArrows = this.isScrollable ? this.calculateVerticalNavigationArrowsLayout(title) : [];\r\n numberOfItems = this.calculateVerticalLayout(dataPoints, title, navArrows, autoWidth);\r\n }\r\n return {\r\n numberOfItems: numberOfItems,\r\n title: title,\r\n navigationArrows: navArrows\r\n };\r\n }\r\n\r\n private updateNavigationArrowLayout(navigationArrows: NavigationArrow[], remainingDataLength: number, visibleDataLength: number): void {\r\n if (this.legendDataStartIndex === 0) {\r\n navigationArrows.shift();\r\n }\r\n\r\n let lastWindow = this.arrowPosWindow;\r\n this.arrowPosWindow = visibleDataLength;\r\n\r\n if (navigationArrows && navigationArrows.length > 0 && this.arrowPosWindow === remainingDataLength) {\r\n this.arrowPosWindow = lastWindow;\r\n navigationArrows.length = navigationArrows.length - 1;\r\n }\r\n }\r\n\r\n private calculateHorizontalNavigationArrowsLayout(title: TitleLayout): NavigationArrow[] {\r\n let height = SVGLegend.LegendArrowHeight;\r\n let width = SVGLegend.LegendArrowWidth;\r\n let translateY = (this.viewport.height / 2) - (height / 2);\r\n\r\n let data: NavigationArrow[] = [];\r\n let rightShift = title ? title.x + title.width : 0;\r\n let arrowLeft = SVGUtil.createArrow(width, height, 180 /*angle*/);\r\n let arrowRight = SVGUtil.createArrow(width, height, 0 /*angle*/);\r\n\r\n data.push({\r\n x: rightShift,\r\n y: translateY,\r\n path: arrowLeft.path,\r\n rotateTransform: arrowLeft.transform,\r\n type: NavigationArrowType.Decrease\r\n });\r\n\r\n data.push({\r\n x: this.parentViewport.width - width,\r\n y: translateY,\r\n path: arrowRight.path,\r\n rotateTransform: arrowRight.transform,\r\n type: NavigationArrowType.Increase\r\n });\r\n\r\n return data;\r\n }\r\n\r\n private calculateVerticalNavigationArrowsLayout(title: TitleLayout): NavigationArrow[] {\r\n let height = SVGLegend.LegendArrowHeight;\r\n let width = SVGLegend.LegendArrowWidth;\r\n\r\n let verticalCenter = this.viewport.height / 2;\r\n let data: NavigationArrow[] = [];\r\n let rightShift = verticalCenter + height / 2;\r\n let arrowTop = SVGUtil.createArrow(width, height, 270 /*angle*/);\r\n let arrowBottom = SVGUtil.createArrow(width, height, 90 /*angle*/);\r\n let titleHeight = title ? title.height : 0;\r\n\r\n data.push({\r\n x: rightShift,\r\n y: width + titleHeight,\r\n path: arrowTop.path,\r\n rotateTransform: arrowTop.transform,\r\n type: NavigationArrowType.Decrease\r\n });\r\n\r\n data.push({\r\n x: rightShift,\r\n y: this.parentViewport.height - height,\r\n path: arrowBottom.path,\r\n rotateTransform: arrowBottom.transform,\r\n type: NavigationArrowType.Increase\r\n });\r\n\r\n return data;\r\n }\r\n \r\n /**\r\n * Calculates the widths for each horizontal legend item.\r\n */\r\n private static calculateHorizontalLegendItemsWidths(dataPoints: LegendDataPoint[], availableWidth: number, iconPadding: number, fontSize: number): LegendItem[] {\r\n\r\n let dataPointsLength = dataPoints.length;\r\n\r\n // Set the maximum amount of space available to each item. They can use less, but can't go over this number.\r\n let maxItemWidth = dataPointsLength > 0 ? availableWidth / dataPointsLength | 0 : 0;\r\n let maxItemTextWidth = maxItemWidth - iconPadding;\r\n\r\n // Makes sure the amount of space available to each item is at least SVGLegend.MaxTextLength wide.\r\n // If you had many items and/or a narrow amount of available width, the availableTextWidthPerItem would be small, essentially making everything ellipsis.\r\n // This prevents that from happening by giving each item at least SVGLegend.MaxTextLength of space.\r\n if (maxItemTextWidth < SVGLegend.MaxTextLength) {\r\n maxItemTextWidth = SVGLegend.MaxTextLength;\r\n maxItemWidth = maxItemTextWidth + iconPadding;\r\n }\r\n\r\n // Make sure the availableWidthPerItem is less than the availableWidth. This lets the long text properly add ellipsis when we're displaying one item at a time.\r\n if (maxItemWidth > availableWidth) {\r\n maxItemWidth = availableWidth;\r\n maxItemTextWidth = maxItemWidth - iconPadding;\r\n }\r\n\r\n let occupiedWidth = 0;\r\n let legendItems: LegendItem[] = [];\r\n\r\n // Add legend items until we can't fit any more (the last one doesn't fit) or we've added all of them\r\n for (let dataPoint of dataPoints) {\r\n\r\n let textProperties = SVGLegend.getTextProperties(false, dataPoint.label, fontSize);\r\n let itemTextWidth = TextMeasurementService.measureSvgTextWidth(textProperties);\r\n let desiredWidth = itemTextWidth + iconPadding;\r\n let overMaxWidth = desiredWidth > maxItemWidth;\r\n let actualWidth = overMaxWidth ? maxItemWidth : desiredWidth;\r\n occupiedWidth += actualWidth;\r\n\r\n if (occupiedWidth >= availableWidth) {\r\n \r\n // Always add at least 1 element\r\n if (legendItems.length === 0) {\r\n\r\n legendItems.push({\r\n dataPoint: dataPoint,\r\n textProperties: textProperties,\r\n desiredWidth: desiredWidth,\r\n desiredOverMaxWidth: true,\r\n width: desiredWidth\r\n });\r\n \r\n // Set the width to the amount of space we actually have\r\n occupiedWidth = availableWidth;\r\n } else {\r\n // Subtract the width from what was just added since it won't fit\r\n occupiedWidth -= actualWidth;\r\n }\r\n break;\r\n }\r\n\r\n legendItems.push({\r\n dataPoint: dataPoint,\r\n textProperties: textProperties,\r\n desiredWidth: desiredWidth,\r\n desiredOverMaxWidth: overMaxWidth,\r\n width: desiredWidth\r\n });\r\n }\r\n\r\n // If there are items at max width, evenly redistribute the extra space to them\r\n let itemsOverMax = _.filter(legendItems, (li) => li.desiredOverMaxWidth);\r\n let numItemsOverMax = itemsOverMax.length;\r\n\r\n if (numItemsOverMax > 0) {\r\n let extraWidth = availableWidth - occupiedWidth;\r\n\r\n for (let item of itemsOverMax) {\r\n // Divvy up the extra space and add it to the max\r\n // We need to do this calculation in every loop since the remainingWidth may not be changed by the same amount every time\r\n let extraWidthPerItem = extraWidth / numItemsOverMax;\r\n let newMaxItemWidth = maxItemWidth + extraWidthPerItem;\r\n\r\n let usedExtraWidth: number;\r\n if (item.desiredWidth <= newMaxItemWidth) {\r\n // If the item doesn't need all the extra space, it's not at max anymore\r\n item.desiredOverMaxWidth = false;\r\n usedExtraWidth = item.desiredWidth - maxItemWidth;\r\n } else {\r\n // Otherwise the item is taking up all the extra space so update the actual width to indicate that\r\n item.width = newMaxItemWidth;\r\n usedExtraWidth = newMaxItemWidth - maxItemWidth;\r\n }\r\n \r\n extraWidth -= usedExtraWidth;\r\n numItemsOverMax--;\r\n }\r\n }\r\n\r\n return legendItems;\r\n }\r\n\r\n private calculateHorizontalLayout(dataPoints: LegendDataPoint[], title: TitleLayout, navigationArrows: NavigationArrow[]): number {\r\n debug.assertValue(navigationArrows, 'navigationArrows');\r\n // calculate the text shift\r\n let HorizontalTextShift = 4 + SVGLegend.LegendIconRadius;\r\n // check if we need more space for the margin, or use the default text padding\r\n let fontSizeBiggerThanDefault = this.legendFontSizeMarginDifference > 0;\r\n let fontSizeMargin = fontSizeBiggerThanDefault ? SVGLegend.TextAndIconPadding + this.legendFontSizeMarginDifference : SVGLegend.TextAndIconPadding;\r\n let fixedTextShift = (fontSizeMargin / (SVGLegend.LegendIconRadiusFactor / 2)) + HorizontalTextShift;\r\n let occupiedWidth = 0;\r\n // calculate the size of the space for both sides of the radius\r\n let iconTotalItemPadding = SVGLegend.LegendIconRadius * 2 + fontSizeMargin * 1.5;\r\n let numberOfItems: number = dataPoints.length;\r\n // get the Y coordinate which is the middle of the container + the middle of the text height - the delta of the text \r\n let defaultTextProperties = SVGLegend.getTextProperties(false, '', this.data.fontSize);\r\n let verticalCenter = this.viewport.height / 2;\r\n let textYCoordinate = verticalCenter + TextMeasurementService.estimateSvgTextHeight(defaultTextProperties) / 2\r\n - TextMeasurementService.estimateSvgTextBaselineDelta(defaultTextProperties);\r\n\r\n if (title) {\r\n occupiedWidth += title.width;\r\n // get the Y coordinate which is the middle of the container + the middle of the text height - the delta of the text \r\n title.y = verticalCenter + title.height / 2 - TextMeasurementService.estimateSvgTextBaselineDelta(SVGLegend.getTextProperties(true, title.text, this.data.fontSize));\r\n }\r\n\r\n // if an arrow should be added, we add space for it\r\n if (this.legendDataStartIndex > 0) {\r\n occupiedWidth += SVGLegend.LegendArrowOffset;\r\n }\r\n\r\n // Calculate the width for each of the legend items\r\n let dataPointsLength = dataPoints.length;\r\n let availableWidth = this.parentViewport.width - occupiedWidth;\r\n let legendItems = SVGLegend.calculateHorizontalLegendItemsWidths(dataPoints, availableWidth, iconTotalItemPadding, this.data.fontSize);\r\n numberOfItems = legendItems.length;\r\n\r\n // If we can't show all the legend items, subtract the \"next\" arrow space from the available space and re-run the width calculations \r\n if (numberOfItems !== dataPointsLength) {\r\n availableWidth -= SVGLegend.LegendArrowOffset;\r\n legendItems = SVGLegend.calculateHorizontalLegendItemsWidths(dataPoints, availableWidth, iconTotalItemPadding, this.data.fontSize);\r\n numberOfItems = legendItems.length;\r\n }\r\n\r\n for (let legendItem of legendItems) {\r\n\r\n let dataPoint = legendItem.dataPoint;\r\n\r\n dataPoint.glyphPosition = {\r\n // the space taken so far + the radius + the margin / radiusFactor to prevent huge spaces\r\n x: occupiedWidth + SVGLegend.LegendIconRadius + (this.legendFontSizeMarginDifference / SVGLegend.LegendIconRadiusFactor),\r\n // The middle of the container but a bit lower due to text not being in the middle (qP for example making middle between q and P)\r\n y: (this.viewport.height * SVGLegend.LegendIconYRatio),\r\n };\r\n\r\n dataPoint.textPosition = {\r\n x: occupiedWidth + fixedTextShift,\r\n y: textYCoordinate,\r\n };\r\n\r\n // If we're over the max width, process it so it fits\r\n if (legendItem.desiredOverMaxWidth) {\r\n let textWidth = legendItem.width - iconTotalItemPadding;\r\n let text = TextMeasurementService.getTailoredTextOrDefault(legendItem.textProperties, textWidth);\r\n dataPoint.label = text;\r\n }\r\n\r\n occupiedWidth += legendItem.width;\r\n }\r\n\r\n this.visibleLegendWidth = occupiedWidth;\r\n this.updateNavigationArrowLayout(navigationArrows, dataPointsLength, numberOfItems);\r\n\r\n return numberOfItems;\r\n }\r\n\r\n private calculateVerticalLayout(\r\n dataPoints: LegendDataPoint[],\r\n title: TitleLayout,\r\n navigationArrows: NavigationArrow[],\r\n autoWidth: boolean): number {\r\n // check if we need more space for the margin, or use the default text padding\r\n let fontSizeBiggerThenDefault = this.legendFontSizeMarginDifference > 0;\r\n let fontFactor = fontSizeBiggerThenDefault ? this.legendFontSizeMarginDifference : 0;\r\n // calculate the size needed after font size change\r\n let verticalLegendHeight = 20 + fontFactor;\r\n let spaceNeededByTitle = 15 + fontFactor;\r\n let extraShiftForTextAlignmentToIcon = 4 + fontFactor;\r\n let totalSpaceOccupiedThusFar = verticalLegendHeight;\r\n // the default space for text and icon radius + the margin after the font size change\r\n let fixedHorizontalIconShift = SVGLegend.TextAndIconPadding + SVGLegend.LegendIconRadius + (this.legendFontSizeMarginDifference / SVGLegend.LegendIconRadiusFactor);\r\n let fixedHorizontalTextShift = fixedHorizontalIconShift * 2;\r\n // check how much space is needed\r\n let maxHorizontalSpaceAvaliable = autoWidth\r\n ? this.parentViewport.width * SVGLegend.LegendMaxWidthFactor\r\n - fixedHorizontalTextShift - SVGLegend.LegendEdgeMariginWidth\r\n : this.lastCalculatedWidth\r\n - fixedHorizontalTextShift - SVGLegend.LegendEdgeMariginWidth;\r\n let numberOfItems: number = dataPoints.length;\r\n\r\n let maxHorizontalSpaceUsed = 0;\r\n let parentHeight = this.parentViewport.height;\r\n\r\n if (title) {\r\n totalSpaceOccupiedThusFar += spaceNeededByTitle;\r\n title.x = SVGLegend.TextAndIconPadding;\r\n title.y = spaceNeededByTitle;\r\n maxHorizontalSpaceUsed = title.width || 0;\r\n }\r\n // if an arrow should be added, we add space for it\r\n if (this.legendDataStartIndex > 0)\r\n totalSpaceOccupiedThusFar += SVGLegend.LegendArrowOffset;\r\n\r\n let dataPointsLength = dataPoints.length;\r\n for (let i = 0; i < dataPointsLength; i++) {\r\n let dp = dataPoints[i];\r\n let textProperties = SVGLegend.getTextProperties(false, dp.label, this.data.fontSize);\r\n\r\n dp.glyphPosition = {\r\n x: fixedHorizontalIconShift,\r\n y: (totalSpaceOccupiedThusFar + extraShiftForTextAlignmentToIcon) - TextMeasurementService.estimateSvgTextBaselineDelta(textProperties)\r\n };\r\n\r\n dp.textPosition = {\r\n x: fixedHorizontalTextShift,\r\n y: totalSpaceOccupiedThusFar + extraShiftForTextAlignmentToIcon\r\n };\r\n\r\n // TODO: [PERF] Get rid of this extra measurement, and modify\r\n // getTailoredTextToReturnWidth + Text\r\n let width = TextMeasurementService.measureSvgTextWidth(textProperties);\r\n if (width > maxHorizontalSpaceUsed) {\r\n maxHorizontalSpaceUsed = width;\r\n }\r\n\r\n if (width > maxHorizontalSpaceAvaliable) {\r\n let text = TextMeasurementService.getTailoredTextOrDefault(\r\n textProperties,\r\n maxHorizontalSpaceAvaliable);\r\n dp.label = text;\r\n }\r\n\r\n totalSpaceOccupiedThusFar += verticalLegendHeight;\r\n\r\n if (totalSpaceOccupiedThusFar > parentHeight) {\r\n numberOfItems = i;\r\n break;\r\n }\r\n }\r\n\r\n if (autoWidth) {\r\n if (maxHorizontalSpaceUsed < maxHorizontalSpaceAvaliable) {\r\n this.lastCalculatedWidth = this.viewport.width = Math.ceil(maxHorizontalSpaceUsed + fixedHorizontalTextShift + SVGLegend.LegendEdgeMariginWidth);\r\n } else {\r\n this.lastCalculatedWidth = this.viewport.width = Math.ceil(this.parentViewport.width * SVGLegend.LegendMaxWidthFactor);\r\n }\r\n }\r\n else {\r\n this.viewport.width = this.lastCalculatedWidth;\r\n }\r\n\r\n this.visibleLegendHeight = totalSpaceOccupiedThusFar;\r\n\r\n navigationArrows.forEach(d => d.x = this.lastCalculatedWidth / 2);\r\n this.updateNavigationArrowLayout(navigationArrows, dataPointsLength, numberOfItems);\r\n\r\n return numberOfItems;\r\n }\r\n\r\n private drawNavigationArrows(layout: NavigationArrow[]) {\r\n let arrows = this.group.selectAll(SVGLegend.NavigationArrow.selector)\r\n .data(layout);\r\n\r\n arrows\r\n .enter()\r\n .append('g')\r\n .on('click', (d: NavigationArrow) => {\r\n let pos = this.legendDataStartIndex;\r\n this.legendDataStartIndex = d.type === NavigationArrowType.Increase\r\n ? pos + this.arrowPosWindow : pos - this.arrowPosWindow;\r\n this.drawLegendInternal(this.data, this.parentViewport, false);\r\n })\r\n .classed(SVGLegend.NavigationArrow.class, true)\r\n .append('path');\r\n\r\n arrows\r\n .attr('transform', (d: NavigationArrow) => SVGUtil.translate(d.x, d.y))\r\n .select('path')\r\n .attr({\r\n 'd': (d: NavigationArrow) => d.path,\r\n 'transform': (d: NavigationArrow) => d.rotateTransform\r\n });\r\n\r\n arrows.exit().remove();\r\n }\r\n\r\n private isTopOrBottom(orientation: LegendPosition) {\r\n switch (orientation) {\r\n case LegendPosition.Top:\r\n case LegendPosition.Bottom:\r\n case LegendPosition.BottomCenter:\r\n case LegendPosition.TopCenter:\r\n return true;\r\n default:\r\n return false;\r\n }\r\n }\r\n\r\n private isCentered(orientation: LegendPosition): boolean {\r\n switch (orientation) {\r\n case LegendPosition.BottomCenter:\r\n case LegendPosition.LeftCenter:\r\n case LegendPosition.RightCenter:\r\n case LegendPosition.TopCenter:\r\n return true;\r\n default:\r\n return false;\r\n }\r\n }\r\n\r\n public reset(): void {\r\n // Intentionally left blank. \r\n }\r\n\r\n private static getTextProperties(isTitle: boolean, text?: string, fontSize?: number): TextProperties {\r\n return {\r\n text: text,\r\n fontFamily: isTitle ? SVGLegend.DefaultTitleFontFamily : SVGLegend.DefaultFontFamily,\r\n fontSize: PixelConverter.fromPoint(fontSize || SVGLegend.DefaultFontSizeInPt)\r\n };\r\n }\r\n\r\n private setTooltipToLegendItems(data: LegendData) {\r\n //we save the values to tooltip before cut\r\n for (let dataPoint of data.dataPoints) {\r\n dataPoint.tooltip = dataPoint.label;\r\n }\r\n }\r\n }\r\n\r\n class CartesianChartInteractiveLegend implements ILegend {\r\n private static LegendHeight = 70;\r\n private static LegendContainerClass = 'interactive-legend';\r\n private static LegendContainerSelector = '.interactive-legend';\r\n private static LegendTitleClass = 'title';\r\n private static LegendItem = 'item';\r\n private static legendPlaceSelector = '\\u25CF';\r\n private static legendIconClass = 'icon';\r\n private static legendColorCss = 'color';\r\n private static legendItemNameClass = 'itemName';\r\n private static legendItemMeasureClass = 'itemMeasure';\r\n private legendContainerParent: D3.Selection;\r\n private legendContainerDiv: D3.Selection;\r\n\r\n constructor(element: JQuery) {\r\n this.legendContainerParent = d3.select(element.get(0));\r\n }\r\n\r\n public getMargins(): IViewport {\r\n return {\r\n height: CartesianChartInteractiveLegend.LegendHeight,\r\n width: 0\r\n };\r\n }\r\n\r\n public drawLegend(legendData: LegendData) {\r\n debug.assertValue(legendData, 'legendData');\r\n let data = legendData.dataPoints;\r\n debug.assertValue(data, 'dataPoints');\r\n if (data.length < 1) return;\r\n\r\n let legendContainerDiv = this.legendContainerParent.select(CartesianChartInteractiveLegend.LegendContainerSelector);\r\n if (legendContainerDiv.empty()) {\r\n if (!data.length) return;\r\n let divToPrepend = $('<div></div>')\r\n .height(this.getMargins().height)\r\n .addClass(CartesianChartInteractiveLegend.LegendContainerClass);\r\n // Prepending, as legend should always be on topmost visual.\r\n $(this.legendContainerParent[0]).prepend(divToPrepend);\r\n legendContainerDiv = d3.select(divToPrepend.get(0));\r\n }\r\n this.legendContainerDiv = legendContainerDiv;\r\n\r\n // Construct the legend title and items.\r\n this.drawTitle(data);\r\n this.drawLegendItems(data);\r\n }\r\n\r\n public reset(): void {\r\n if (this.legendContainerDiv) {\r\n this.legendContainerDiv.remove();\r\n this.legendContainerDiv = null;\r\n }\r\n }\r\n\r\n public isVisible(): boolean {\r\n return true;\r\n }\r\n\r\n public changeOrientation(orientation: LegendPosition) {\r\n // Not supported\r\n }\r\n\r\n public getOrientation(): LegendPosition {\r\n return LegendPosition.Top;\r\n }\r\n\r\n /**\r\n * Draw the legend title\r\n */\r\n private drawTitle(data: LegendDataPoint[]): void {\r\n debug.assert(data && data.length > 0, 'data is null or empty');\r\n let titleDiv: D3.Selection = this.legendContainerDiv.selectAll('div.' + CartesianChartInteractiveLegend.LegendTitleClass);\r\n let item: D3.UpdateSelection = titleDiv.data([data[0]]);\r\n\r\n // Enter\r\n let itemEnter: D3.EnterSelection = item.enter();\r\n let titleDivEnter: D3.Selection = itemEnter.append('div').attr('class', CartesianChartInteractiveLegend.LegendTitleClass);\r\n titleDivEnter\r\n .filter((d: LegendDataPoint) => d.iconOnlyOnLabel)\r\n .append('span')\r\n .attr('class', CartesianChartInteractiveLegend.legendIconClass)\r\n .html(CartesianChartInteractiveLegend.legendPlaceSelector);\r\n titleDivEnter.append('span');\r\n\r\n // Update\r\n item.filter((d: LegendDataPoint) => d.iconOnlyOnLabel)\r\n .select('span.' + CartesianChartInteractiveLegend.legendIconClass)\r\n .style(CartesianChartInteractiveLegend.legendColorCss, (d: LegendDataPoint) => d.color);\r\n item.select('span:last-child').text((d: LegendDataPoint) => d.category);\r\n }\r\n\r\n /**\r\n * Draw the legend items\r\n */\r\n private drawLegendItems(data: LegendDataPoint[]): void {\r\n // Add Mesaures - the items of the category in the legend\r\n this.ensureLegendTableCreated();\r\n \r\n let dataPointsMatrix: LegendDataPoint[][] = [data];\r\n let legendItemsContainer: D3.UpdateSelection = this.legendContainerDiv.select('tbody').selectAll('tr').data(dataPointsMatrix);\r\n\r\n // Enter\r\n let legendItemsEnter: D3.EnterSelection = legendItemsContainer.enter();\r\n let rowEnter: D3.Selection = legendItemsEnter.append('tr');\r\n let cellEnter: D3.Selection = rowEnter.selectAll('td')\r\n .data((d: LegendDataPoint[]) => d, (d: LegendDataPoint) => d.label)\r\n .enter()\r\n .append('td').attr('class', CartesianChartInteractiveLegend.LegendItem);\r\n let cellSpanEnter: D3.Selection = cellEnter.append('span');\r\n cellSpanEnter.filter((d: LegendDataPoint) => !d.iconOnlyOnLabel)\r\n .append('span')\r\n .html(CartesianChartInteractiveLegend.legendPlaceSelector)\r\n .attr('class', CartesianChartInteractiveLegend.legendIconClass)\r\n .attr('white-space', 'nowrap')\r\n .style({\r\n 'font-size': '20px', // this creates a circle of 10px\r\n 'margin-bottom': '7px'\r\n });\r\n cellSpanEnter.append('span').attr('class', CartesianChartInteractiveLegend.legendItemNameClass);\r\n cellSpanEnter.append('span').attr('class', CartesianChartInteractiveLegend.legendItemMeasureClass);\r\n\r\n // Update\r\n let legendCells: D3.UpdateSelection = legendItemsContainer.selectAll('td').data((d: LegendDataPoint[]) => d, (d: LegendDataPoint) => d.label);\r\n legendCells.select('span.' + CartesianChartInteractiveLegend.legendItemNameClass).html((d: LegendDataPoint) => powerbi.visuals.TextUtil.removeBreakingSpaces(d.label));\r\n legendCells.select('span.' + CartesianChartInteractiveLegend.legendItemMeasureClass).html((d: LegendDataPoint) => '&nbsp;' + d.measure);\r\n legendCells.select('span.' + CartesianChartInteractiveLegend.legendIconClass).style('color', (d: LegendDataPoint) => d.color);\r\n\r\n // Exit\r\n legendCells.exit().remove();\r\n }\r\n\r\n /**\r\n * Ensure legend table is created and set horizontal pan gestures on it\r\n */\r\n private ensureLegendTableCreated(): void {\r\n if (this.legendContainerDiv.select('div table').empty()) {\r\n let legendTable: D3.Selection = this.legendContainerDiv.append('div').append('table');\r\n legendTable.style('table-layout', 'fixed').append('tbody');\r\n // Setup Pan Gestures of the legend\r\n this.setPanGestureOnLegend(legendTable);\r\n }\r\n }\r\n\r\n /**\r\n * Set Horizontal Pan gesture for the legend\r\n */\r\n private setPanGestureOnLegend(legendTable: D3.Selection): void {\r\n let viewportWidth: number = $(this.legendContainerParent[0]).width();\r\n let xscale: D3.Scale.LinearScale = d3.scale.linear().domain([0, viewportWidth]).range([0, viewportWidth]);\r\n let zoom: D3.Behavior.Zoom = d3.behavior.zoom()\r\n .scaleExtent([1, 1]) // disable scaling\r\n .x(xscale)\r\n .on(\"zoom\", () => {\r\n // horizontal pan is valid only in case the legend items width are bigger than the viewport width\r\n if ($(legendTable[0]).width() > viewportWidth) {\r\n let t: number[] = zoom.translate();\r\n let tx: number = t[0];\r\n let ty: number = t[1];\r\n tx = Math.min(tx, 0);\r\n tx = Math.max(tx, viewportWidth - $(legendTable[0]).width());\r\n zoom.translate([tx, ty]);\r\n legendTable.style(\"-ms-transform\", () => { /* IE 9 */\r\n return SVGUtil.translateXWithPixels(tx);\r\n });\r\n legendTable.style(\"-webkit-transform\", () => { /* Safari */\r\n return SVGUtil.translateXWithPixels(tx);\r\n });\r\n legendTable.style(\"transform\", () => {\r\n return SVGUtil.translateXWithPixels(tx);\r\n });\r\n }\r\n });\r\n if (this.legendContainerDiv) {\r\n this.legendContainerDiv.call(zoom);\r\n } else {\r\n legendTable.call(zoom);\r\n }\r\n }\r\n }\r\n\r\n export module LegendData {\r\n\r\n export var DefaultLegendLabelFillColor: string = '#666666';\r\n\r\n export function update(legendData: LegendData, legendObject: DataViewObject): void {\r\n debug.assertValue(legendData, 'legendData');\r\n debug.assertValue(legendObject, 'legendObject');\r\n\r\n if (legendObject[legendProps.show] == null) {\r\n legendObject[legendProps.show] = true;\r\n }\r\n\r\n if (legendObject[legendProps.show] === false)\r\n legendData.dataPoints = [];\r\n\r\n if (legendObject[legendProps.show] === true && legendObject[legendProps.position] == null)\r\n legendObject[legendProps.position] = legendPosition.top;\r\n\r\n if (legendObject[legendProps.fontSize] !== undefined)\r\n legendData.fontSize = <number>legendObject[legendProps.fontSize];\r\n\r\n if (legendObject[legendProps.labelColor] !== undefined) {\r\n\r\n let fillColor = <Fill>legendObject[legendProps.labelColor];\r\n\r\n if (fillColor != null) {\r\n legendData.labelColor = fillColor.solid.color;\r\n }\r\n }\r\n\r\n if (legendObject[legendProps.showTitle] === false)\r\n legendData.title = \"\";\r\n else if (legendObject[legendProps.titleText] !== undefined) {\r\n legendData.title = <string>legendObject[legendProps.titleText];\r\n }\r\n }\r\n }\r\n}\n\n\n/** WEBPACK FOOTER **\n ** ./src/Clients/Visuals/legend.ts\n **/","/*\r\n* Power BI Visualizations\r\n*\r\n* Copyright (c) Microsoft Corporation\r\n* All rights reserved. \r\n* MIT License\r\n*\r\n* Permission is hereby granted, free of charge, to any person obtaining a copy\r\n* of this software and associated documentation files (the \"\"Software\"\"), to deal\r\n* in the Software without restriction, including without limitation the rights\r\n* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\r\n* copies of the Software, and to permit persons to whom the Software is\r\n* furnished to do so, subject to the following conditions:\r\n* \r\n* The above copyright notice and this permission notice shall be included in \r\n* all copies or substantial portions of the Software.\r\n* \r\n* THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR \r\n* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, \r\n* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE \r\n* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER \r\n* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\r\n* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\r\n* THE SOFTWARE.\r\n*/\r\n\r\n/// <reference path=\"../_references.ts\"/>\r\n\r\nmodule powerbi.visuals {\r\n export module axisScale {\r\n export const linear: string = 'linear';\r\n export const log: string = 'log'; \r\n\r\n export const type: IEnumType = createEnumType([\r\n { value: linear, displayName: resources => resources.get('Visual_Axis_Linear') },\r\n { value: log, displayName: resources => resources.get('Visual_Axis_Log') } \r\n ]);\r\n }\r\n}\n\n\n/** WEBPACK FOOTER **\n ** ./src/Clients/Visuals/types/axisScale.ts\n **/","/*\r\n * Power BI Visualizations\r\n *\r\n * Copyright (c) Microsoft Corporation\r\n * All rights reserved. \r\n * MIT License\r\n *\r\n * Permission is hereby granted, free of charge, to any person obtaining a copy\r\n * of this software and associated documentation files (the \"\"Software\"\"), to deal\r\n * in the Software without restriction, including without limitation the rights\r\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\r\n * copies of the Software, and to permit persons to whom the Software is\r\n * furnished to do so, subject to the following conditions:\r\n * \r\n * The above copyright notice and this permission notice shall be included in \r\n * all copies or substantial portions of the Software.\r\n * \r\n * THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR \r\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, \r\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE \r\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER \r\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\r\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\r\n * THE SOFTWARE.\r\n */\r\n\r\n/// <reference path=\"../_references.ts\"/>\r\n\r\nmodule powerbi.visuals {\r\n export module axisStyle {\r\n export const showBoth: string = 'showBoth';\r\n export const showTitleOnly: string = 'showTitleOnly';\r\n export const showUnitOnly: string = 'showUnitOnly';\r\n\r\n export const type: IEnumType = createEnumType([\r\n { value: showTitleOnly, displayName: resources => resources.get('Visual_Axis_ShowTitleOnly') },\r\n { value: showUnitOnly, displayName: resources => resources.get('Visual_Axis_ShowUnitOnly') },\r\n { value: showBoth, displayName: resources => resources.get('Visual_Axis_ShowBoth') }\r\n ]);\r\n }\r\n}\n\n\n/** WEBPACK FOOTER **\n ** ./src/Clients/Visuals/types/axisStyle.ts\n **/","/*\r\n * Power BI Visualizations\r\n *\r\n * Copyright (c) Microsoft Corporation\r\n * All rights reserved. \r\n * MIT License\r\n *\r\n * Permission is hereby granted, free of charge, to any person obtaining a copy\r\n * of this software and associated documentation files (the \"\"Software\"\"), to deal\r\n * in the Software without restriction, including without limitation the rights\r\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\r\n * copies of the Software, and to permit persons to whom the Software is\r\n * furnished to do so, subject to the following conditions:\r\n * \r\n * The above copyright notice and this permission notice shall be included in \r\n * all copies or substantial portions of the Software.\r\n * \r\n * THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR \r\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, \r\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE \r\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER \r\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\r\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\r\n * THE SOFTWARE.\r\n */\r\n\r\n/// <reference path=\"../_references.ts\"/>\r\n\r\nmodule powerbi.visuals {\r\n export module axisType {\r\n export const scalar: string = 'Scalar';\r\n export const categorical: string = 'Categorical'; \r\n export const both: string = 'Both';\r\n\r\n export const type: IEnumType = createEnumType([\r\n { value: scalar, displayName: resources => resources.get('Visual_Axis_Scalar') },\r\n { value: categorical, displayName: resources => resources.get('Visual_Axis_Categorical') },\r\n ]);\r\n }\r\n}\n\n\n/** WEBPACK FOOTER **\n ** ./src/Clients/Visuals/types/axisType.ts\n **/","/*\r\n * Power BI Visualizations\r\n *\r\n * Copyright (c) Microsoft Corporation\r\n * All rights reserved. \r\n * MIT License\r\n *\r\n * Permission is hereby granted, free of charge, to any person obtaining a copy\r\n * of this software and associated documentation files (the \"\"Software\"\"), to deal\r\n * in the Software without restriction, including without limitation the rights\r\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\r\n * copies of the Software, and to permit persons to whom the Software is\r\n * furnished to do so, subject to the following conditions:\r\n * \r\n * The above copyright notice and this permission notice shall be included in \r\n * all copies or substantial portions of the Software.\r\n * \r\n * THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR \r\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, \r\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE \r\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER \r\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\r\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\r\n * THE SOFTWARE.\r\n */\r\n\r\n/// <reference path=\"../_references.ts\"/>\r\n\r\nmodule powerbi.visuals {\r\n export module basicShapeType {\r\n export const rectangle: string = 'rectangle';\r\n export const oval: string = 'oval';\r\n export const line: string = 'line';\r\n export const arrow: string = 'arrow';\r\n export const triangle: string = 'triangle';\r\n\r\n export const type: IEnumType = createEnumType([\r\n { value: rectangle, displayName: 'rectangle' },\r\n { value: oval, displayName: 'oval' },\r\n { value: line, displayName: 'line' },\r\n { value: arrow, displayName: 'arrow' },\r\n { value: triangle, displayName: 'triangle' }\r\n ]);\r\n }\r\n}\n\n\n/** WEBPACK FOOTER **\n ** ./src/Clients/Visuals/types/basicShapeType.ts\n **/","/*\r\n * Power BI Visualizations\r\n *\r\n * Copyright (c) Microsoft Corporation\r\n * All rights reserved. \r\n * MIT License\r\n *\r\n * Permission is hereby granted, free of charge, to any person obtaining a copy\r\n * of this software and associated documentation files (the \"\"Software\"\"), to deal\r\n * in the Software without restriction, including without limitation the rights\r\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\r\n * copies of the Software, and to permit persons to whom the Software is\r\n * furnished to do so, subject to the following conditions:\r\n * \r\n * The above copyright notice and this permission notice shall be included in \r\n * all copies or substantial portions of the Software.\r\n * \r\n * THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR \r\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, \r\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE \r\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER \r\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\r\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\r\n * THE SOFTWARE.\r\n */\r\n\r\n/// <reference path=\"../_references.ts\"/>\r\n\r\nmodule powerbi.visuals {\r\n export module imageScalingType {\r\n export const normal: string = 'Normal';\r\n export const fit: string = 'Fit';\r\n export const fill: string = 'Fill';\r\n\r\n export const type: IEnumType = createEnumType([\r\n { value: normal, displayName: resources => resources.get('Visual_ImageScalingType_Normal') },\r\n { value: fit, displayName: resources => resources.get('Visual_ImageScalingType_Fit') },\r\n { value: fill, displayName: resources => resources.get('Visual_ImageScalingType_Fill') },\r\n ]);\r\n }\r\n}\n\n\n/** WEBPACK FOOTER **\n ** ./src/Clients/Visuals/types/imageScalingType.ts\n **/","/*\r\n * Power BI Visualizations\r\n *\r\n * Copyright (c) Microsoft Corporation\r\n * All rights reserved. \r\n * MIT License\r\n *\r\n * Permission is hereby granted, free of charge, to any person obtaining a copy\r\n * of this software and associated documentation files (the \"\"Software\"\"), to deal\r\n * in the Software without restriction, including without limitation the rights\r\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\r\n * copies of the Software, and to permit persons to whom the Software is\r\n * furnished to do so, subject to the following conditions:\r\n * \r\n * The above copyright notice and this permission notice shall be included in \r\n * all copies or substantial portions of the Software.\r\n * \r\n * THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR \r\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, \r\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE \r\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER \r\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\r\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\r\n * THE SOFTWARE.\r\n */\r\n\r\n/// <reference path=\"../_references.ts\"/>\r\n\r\nmodule powerbi.visuals {\r\n\r\n export module labelPosition {\r\n\r\n export const insideEnd: string = 'InsideEnd';\r\n export const insideCenter: string = 'InsideCenter';\r\n export const outsideEnd: string = 'OutsideEnd';\r\n export const insideBase: string = 'InsideBase';\r\n\r\n export const type: IEnumType = createEnumType([\r\n { value: insideEnd, displayName: resources => resources.get('Visual_LabelPosition_InsideEnd') },\r\n { value: outsideEnd, displayName: resources => resources.get('Visual_LabelPosition_OutsideEnd') },\r\n { value: insideCenter, displayName: resources => resources.get('Visual_LabelPosition_InsideCenter') },\r\n { value: insideBase, displayName: resources => resources.get('Visual_LabelPosition_InsideBase') },\r\n ]);\r\n }\r\n}\n\n\n/** WEBPACK FOOTER **\n ** ./src/Clients/Visuals/types/labelPosition.ts\n **/","/*\r\n * Power BI Visualizations\r\n *\r\n * Copyright (c) Microsoft Corporation\r\n * All rights reserved. \r\n * MIT License\r\n *\r\n * Permission is hereby granted, free of charge, to any person obtaining a copy\r\n * of this software and associated documentation files (the \"\"Software\"\"), to deal\r\n * in the Software without restriction, including without limitation the rights\r\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\r\n * copies of the Software, and to permit persons to whom the Software is\r\n * furnished to do so, subject to the following conditions:\r\n * \r\n * The above copyright notice and this permission notice shall be included in \r\n * all copies or substantial portions of the Software.\r\n * \r\n * THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR \r\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, \r\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE \r\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER \r\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\r\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\r\n * THE SOFTWARE.\r\n */\r\n\r\n/// <reference path=\"../_references.ts\"/>\r\n\r\nmodule powerbi.visuals {\r\n export module labelStyle {\r\n export const category: string = 'Category';\r\n export const data: string = 'Data';\r\n export const both: string = 'Both';\r\n\r\n export const type: IEnumType = createEnumType([\r\n { value: category, displayName: resources => resources.get('Visual_LabelStyle_Category') },\r\n { value: data, displayName: resources => resources.get('Visual_LabelStyle_DataValue') },\r\n { value: both, displayName: resources => resources.get('Visual_LabelStyle_Both') },\r\n ]);\r\n }\r\n}\n\n\n/** WEBPACK FOOTER **\n ** ./src/Clients/Visuals/types/labelStyle.ts\n **/","/*\r\n * Power BI Visualizations\r\n *\r\n * Copyright (c) Microsoft Corporation\r\n * All rights reserved. \r\n * MIT License\r\n *\r\n * Permission is hereby granted, free of charge, to any person obtaining a copy\r\n * of this software and associated documentation files (the \"\"Software\"\"), to deal\r\n * in the Software without restriction, including without limitation the rights\r\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\r\n * copies of the Software, and to permit persons to whom the Software is\r\n * furnished to do so, subject to the following conditions:\r\n * \r\n * The above copyright notice and this permission notice shall be included in \r\n * all copies or substantial portions of the Software.\r\n * \r\n * THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR \r\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, \r\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE \r\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER \r\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\r\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\r\n * THE SOFTWARE.\r\n */\r\n\r\n/// <reference path=\"../_references.ts\"/>\r\n\r\nmodule powerbi.visuals {\r\n export module legendPosition {\r\n export const top: string = 'Top';\r\n export const bottom: string = 'Bottom';\r\n export const left: string = 'Left';\r\n export const right: string = 'Right';\r\n export const topCenter: string = 'TopCenter';\r\n export const bottomCenter: string = 'BottomCenter';\r\n export const leftCenter: string = 'LeftCenter';\r\n export const rightCenter: string = 'RightCenter';\r\n\r\n export const type: IEnumType = createEnumType([\r\n { value: top, displayName: resources => resources.get('Visual_LegendPosition_Top') },\r\n { value: bottom, displayName: resources => resources.get('Visual_LegendPosition_Bottom') },\r\n { value: left, displayName: resources => resources.get('Visual_LegendPosition_Left') },\r\n { value: right, displayName: resources => resources.get('Visual_LegendPosition_Right') },\r\n { value: topCenter, displayName: resources => resources.get('Visual_LegendPosition_TopCenter') },\r\n { value: bottomCenter, displayName: resources => resources.get('Visual_LegendPosition_BottomCenter') },\r\n { value: leftCenter, displayName: resources => resources.get('Visual_LegendPosition_LeftCenter') },\r\n { value: rightCenter, displayName: resources => resources.get('Visual_LegendPosition_RightCenter') },\r\n ]);\r\n }\r\n}\n\n\n/** WEBPACK FOOTER **\n ** ./src/Clients/Visuals/types/legendPosition.ts\n **/","/*\r\n * Power BI Visualizations\r\n *\r\n * Copyright (c) Microsoft Corporation\r\n * All rights reserved. \r\n * MIT License\r\n *\r\n * Permission is hereby granted, free of charge, to any person obtaining a copy\r\n * of this software and associated documentation files (the \"\"Software\"\"), to deal\r\n * in the Software without restriction, including without limitation the rights\r\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\r\n * copies of the Software, and to permit persons to whom the Software is\r\n * furnished to do so, subject to the following conditions:\r\n * \r\n * The above copyright notice and this permission notice shall be included in \r\n * all copies or substantial portions of the Software.\r\n * \r\n * THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR \r\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, \r\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE \r\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER \r\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\r\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\r\n * THE SOFTWARE.\r\n */\r\n\r\n/// <reference path=\"../_references.ts\"/>\r\n\r\nmodule powerbi.visuals {\r\n export module kpiDirection {\r\n export const positive: string = 'Positive';\r\n export const negative: string = 'Negative';\r\n\r\n export const type: IEnumType = createEnumType([\r\n { value: positive, displayName: resources => resources.get('Visual_KPI_Direction_Positive') },\r\n { value: negative, displayName: resources => resources.get('Visual_KPI_Direction_Negative') }\r\n ]);\r\n }\r\n}\n\n\n/** WEBPACK FOOTER **\n ** ./src/Clients/Visuals/types/kpi_direction_type.ts\n **/","/*\r\n * Power BI Visualizations\r\n *\r\n * Copyright (c) Microsoft Corporation\r\n * All rights reserved. \r\n * MIT License\r\n *\r\n * Permission is hereby granted, free of charge, to any person obtaining a copy\r\n * of this software and associated documentation files (the \"\"Software\"\"), to deal\r\n * in the Software without restriction, including without limitation the rights\r\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\r\n * copies of the Software, and to permit persons to whom the Software is\r\n * furnished to do so, subject to the following conditions:\r\n * \r\n * The above copyright notice and this permission notice shall be included in \r\n * all copies or substantial portions of the Software.\r\n * \r\n * THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR \r\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, \r\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE \r\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER \r\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\r\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\r\n * THE SOFTWARE.\r\n */\r\n\r\n/// <reference path=\"../_references.ts\"/>\r\n\r\nmodule powerbi.visuals {\r\n export module lineStyle {\r\n export const dashed: string = 'dashed';\r\n export const solid: string = 'solid';\r\n export const dotted: string = 'dotted';\r\n\r\n export const type: IEnumType = createEnumType([\r\n { value: dashed, displayName: resources => resources.get('Visual_LineStyle_Dashed') },\r\n { value: solid, displayName: resources => resources.get('Visual_LineStyle_Solid') },\r\n { value: dotted, displayName: resources => resources.get('Visual_LineStyle_Dotted') }\r\n ]);\r\n }\r\n}\n\n\n/** WEBPACK FOOTER **\n ** ./src/Clients/Visuals/types/lineStyle.ts\n **/","/*\r\n* Power BI Visualizations\r\n*\r\n* Copyright (c) Microsoft Corporation\r\n* All rights reserved. \r\n* MIT License\r\n*\r\n* Permission is hereby granted, free of charge, to any person obtaining a copy\r\n* of this software and associated documentation files (the \"\"Software\"\"), to deal\r\n* in the Software without restriction, including without limitation the rights\r\n* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\r\n* copies of the Software, and to permit persons to whom the Software is\r\n* furnished to do so, subject to the following conditions:\r\n* \r\n* The above copyright notice and this permission notice shall be included in \r\n* all copies or substantial portions of the Software.\r\n* \r\n* THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR \r\n* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, \r\n* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE \r\n* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER \r\n* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\r\n* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\r\n* THE SOFTWARE.\r\n*/\r\n\r\n/// <reference path=\"../_references.ts\"/>\r\n\r\nmodule powerbi.visuals {\r\n export module outline {\r\n export const none: string = 'None';\r\n export const bottomOnly: string = 'BottomOnly';\r\n export const topOnly: string = 'TopOnly';\r\n export const leftOnly: string = 'LeftOnly';\r\n export const rightOnly: string = 'RightOnly';\r\n export const topBottom: string = 'TopBottom';\r\n export const leftRight: string = 'LeftRight';\r\n export const frame: string = 'Frame';\r\n\r\n export function showTop(outline: string): boolean {\r\n return [topOnly, topBottom, frame].some((o) => o === outline);\r\n }\r\n\r\n export function showRight(outline: string): boolean {\r\n return [rightOnly, leftRight, frame].some((o) => o === outline);\r\n }\r\n\r\n export function showBottom(outline: string): boolean {\r\n return [bottomOnly, topBottom, frame].some((o) => o === outline);\r\n }\r\n\r\n export function showLeft(outline: string): boolean {\r\n return [leftOnly, leftRight, frame].some((o) => o === outline);\r\n }\r\n\r\n export const type: IEnumType = createEnumType([\r\n { value: none, displayName: resources => resources.get('Visual_Outline_none') },\r\n { value: bottomOnly, displayName: resources => resources.get('Visual_Outline_bottom_only') },\r\n { value: topOnly, displayName: resources => resources.get('Visual_Outline_top_only') },\r\n { value: leftOnly, displayName: resources => resources.get('Visual_Outline_LeftOnly') },\r\n { value: rightOnly, displayName: resources => resources.get('Visual_Outline_RightOnly') },\r\n { value: topBottom, displayName: resources => resources.get('Visual_Outline_top_Bottom') },\r\n { value: leftRight, displayName: resources => resources.get('Visual_Outline_leftRight') },\r\n { value: frame, displayName: resources => resources.get('Visual_Outline_frame') }\r\n ]);\r\n }\r\n}\n\n\n/** WEBPACK FOOTER **\n ** ./src/Clients/Visuals/types/outline.ts\n **/","/*\r\n * Power BI Visualizations\r\n *\r\n * Copyright (c) Microsoft Corporation\r\n * All rights reserved. \r\n * MIT License\r\n *\r\n * Permission is hereby granted, free of charge, to any person obtaining a copy\r\n * of this software and associated documentation files (the \"\"Software\"\"), to deal\r\n * in the Software without restriction, including without limitation the rights\r\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\r\n * copies of the Software, and to permit persons to whom the Software is\r\n * furnished to do so, subject to the following conditions:\r\n * \r\n * The above copyright notice and this permission notice shall be included in \r\n * all copies or substantial portions of the Software.\r\n * \r\n * THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR \r\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, \r\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE \r\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER \r\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\r\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\r\n * THE SOFTWARE.\r\n */\r\n\r\n/// <reference path=\"../_references.ts\"/>\r\n\r\nmodule powerbi.visuals {\r\n export module referenceLinePosition {\r\n export const back: string = 'back';\r\n export const front: string = 'front';\r\n\r\n export const type: IEnumType = createEnumType([\r\n { value: back, displayName: resources => resources.get('Visual_Reference_Line_Behind') },\r\n { value: front, displayName: resources => resources.get('Visual_Reference_Line_InFront') },\r\n ]);\r\n }\r\n\r\n export module referenceLineDataLabelHorizontalPosition {\r\n export const left: string = 'left';\r\n export const right: string = 'right';\r\n\r\n export const type: IEnumType = createEnumType([\r\n { value: left, displayName: resources => resources.get('Visual_Reference_Line_Data_Label_Left') },\r\n { value: right, displayName: resources => resources.get('Visual_Reference_Line_Data_Label_Right') },\r\n ]);\r\n }\r\n\r\n export module referenceLineDataLabelVerticalPosition {\r\n export const above: string = 'above';\r\n export const under: string = 'under';\r\n\r\n export const type: IEnumType = createEnumType([\r\n { value: above, displayName: resources => resources.get('Visual_Reference_Line_Data_Label_Above') },\r\n { value: under, displayName: resources => resources.get('Visual_Reference_Line_Data_Label_Under') },\r\n ]);\r\n }\r\n}\n\n\n/** WEBPACK FOOTER **\n ** ./src/Clients/Visuals/types/referenceLinePosition.ts\n **/","/*\r\n* Power BI Visualizations\r\n*\r\n* Copyright (c) Microsoft Corporation\r\n* All rights reserved. \r\n* MIT License\r\n*\r\n* Permission is hereby granted, free of charge, to any person obtaining a copy\r\n* of this software and associated documentation files (the \"\"Software\"\"), to deal\r\n* in the Software without restriction, including without limitation the rights\r\n* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\r\n* copies of the Software, and to permit persons to whom the Software is\r\n* furnished to do so, subject to the following conditions:\r\n* \r\n* The above copyright notice and this permission notice shall be included in \r\n* all copies or substantial portions of the Software.\r\n* \r\n* THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR \r\n* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, \r\n* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE \r\n* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER \r\n* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\r\n* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\r\n* THE SOFTWARE.\r\n*/\r\n\r\n/// <reference path=\"../_references.ts\"/>\r\n\r\nmodule powerbi.visuals {\r\n export module slicerOrientation {\r\n export const enum Orientation {\r\n Vertical,\r\n Horizontal\r\n }\r\n\r\n export const type: IEnumType = createEnumType([\r\n { value: Orientation.Vertical, displayName: resources => resources.get('Slicer_Orientation_Vertical') },\r\n { value: Orientation.Horizontal, displayName: resources => resources.get('Slicer_Orientation_Horizontal') },\r\n ]);\r\n }\r\n}\n\n\n/** WEBPACK FOOTER **\n ** ./src/Clients/Visuals/types/slicerOrientation.ts\n **/","/*\r\n * Power BI Visualizations\r\n *\r\n * Copyright (c) Microsoft Corporation\r\n * All rights reserved. \r\n * MIT License\r\n *\r\n * Permission is hereby granted, free of charge, to any person obtaining a copy\r\n * of this software and associated documentation files (the \"\"Software\"\"), to deal\r\n * in the Software without restriction, including without limitation the rights\r\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\r\n * copies of the Software, and to permit persons to whom the Software is\r\n * furnished to do so, subject to the following conditions:\r\n * \r\n * The above copyright notice and this permission notice shall be included in \r\n * all copies or substantial portions of the Software.\r\n * \r\n * THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR \r\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, \r\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE \r\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER \r\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\r\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\r\n * THE SOFTWARE.\r\n */\r\n\r\n/// <reference path=\"../_references.ts\"/>\r\n\r\nmodule powerbi.visuals {\r\n export module yAxisPosition {\r\n export const left: string = 'Left';\r\n export const right: string = 'Right'; \r\n \r\n export const type: IEnumType = createEnumType([\r\n { value: left, displayName: resources => resources.get('Visual_yAxis_Left') },\r\n { value: right, displayName: resources => resources.get('Visual_yAxis_Right') },\r\n ]);\r\n }\r\n}\n\n\n/** WEBPACK FOOTER **\n ** ./src/Clients/Visuals/types/yAxisPosition.ts\n **/","/*\r\n * Power BI Visualizations\r\n *\r\n * Copyright (c) Microsoft Corporation\r\n * All rights reserved. \r\n * MIT License\r\n *\r\n * Permission is hereby granted, free of charge, to any person obtaining a copy\r\n * of this software and associated documentation files (the \"\"Software\"\"), to deal\r\n * in the Software without restriction, including without limitation the rights\r\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\r\n * copies of the Software, and to permit persons to whom the Software is\r\n * furnished to do so, subject to the following conditions:\r\n * \r\n * The above copyright notice and this permission notice shall be included in \r\n * all copies or substantial portions of the Software.\r\n * \r\n * THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR \r\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, \r\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE \r\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER \r\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\r\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\r\n * THE SOFTWARE.\r\n */\r\n\r\n/// <reference path=\"../_references.ts\"/>\r\n\r\nmodule powerbi.visuals {\r\n export module sliderMode {\r\n export const before: string = 'Before';\r\n export const after: string = 'After';\r\n export const between: string = 'Between';\r\n\r\n export const type: IEnumType = createEnumType([\r\n { value: between, displayName: resources => resources.get('Visual_SliderMode_Between') },\r\n { value: before, displayName: resources => resources.get('Visual_SliderMode_Before') },\r\n { value: after, displayName: resources => resources.get('Visual_SliderMode_After') }\r\n ]);\r\n }\r\n}\n\n\n/** WEBPACK FOOTER **\n ** ./src/Clients/Visuals/types/sliderMode.ts\n **/","/*\r\n * Power BI Visualizations\r\n *\r\n * Copyright (c) Microsoft Corporation\r\n * All rights reserved. \r\n * MIT License\r\n *\r\n * Permission is hereby granted, free of charge, to any person obtaining a copy\r\n * of this software and associated documentation files (the \"\"Software\"\"), to deal\r\n * in the Software without restriction, including without limitation the rights\r\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\r\n * copies of the Software, and to permit persons to whom the Software is\r\n * furnished to do so, subject to the following conditions:\r\n * \r\n * The above copyright notice and this permission notice shall be included in \r\n * all copies or substantial portions of the Software.\r\n * \r\n * THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR \r\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, \r\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE \r\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER \r\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\r\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\r\n * THE SOFTWARE.\r\n */\r\n\r\n/// <reference path=\"../_references.ts\"/>\r\n\r\nmodule powerbi.visuals {\r\n export module AnimatorCommon {\r\n export const MinervaAnimationDuration = 250;\r\n \r\n // The maximum number of data points we can performantly animate with SVG. If we have more, turn off animations.\r\n export const MaxDataPointsToAnimate = 1000;\r\n\r\n export function GetAnimationDuration(animator: IGenericAnimator, suppressAnimations: boolean) {\r\n return (suppressAnimations || !animator) ? 0 : animator.getDuration();\r\n }\r\n }\r\n\r\n export interface IAnimatorOptions {\r\n duration?: number;\r\n }\r\n\r\n export interface IAnimationOptions {\r\n interactivityService: IInteractivityService;\r\n }\r\n\r\n export interface IAnimationResult {\r\n failed: boolean;\r\n }\r\n\r\n export interface IAnimator<T extends IAnimatorOptions, U extends IAnimationOptions, V extends IAnimationResult> {\r\n getDuration(): number;\r\n getEasing(): string;\r\n\r\n animate(options: U): V;\r\n }\r\n\r\n export type IGenericAnimator = IAnimator<IAnimatorOptions, IAnimationOptions, IAnimationResult>;\r\n\r\n /** \r\n * We just need to have a non-null animator to allow axis animations in cartesianChart.\r\n * Note: Use this temporarily for Line/Scatter until we add more animations (MinervaPlugins only).\r\n */\r\n export class BaseAnimator<T extends IAnimatorOptions, U extends IAnimationOptions, V extends IAnimationResult> implements IAnimator<T, U, V> {\r\n protected animationDuration: number;\r\n\r\n constructor(options?: T) {\r\n if (options && options.duration) {\r\n this.animationDuration = options.duration;\r\n }\r\n\r\n this.animationDuration = this.animationDuration >= 0 ? this.animationDuration : AnimatorCommon.MinervaAnimationDuration;\r\n }\r\n\r\n public getDuration(): number {\r\n return this.animationDuration;\r\n }\r\n\r\n public animate(options: U): V {\r\n return null;\r\n }\r\n\r\n public getEasing(): string {\r\n return 'cubic-in-out';\r\n }\r\n }\r\n}\n\n\n/** WEBPACK FOOTER **\n ** ./src/Clients/Visuals/animators/animatorCommon.ts\n **/","/*\r\n * Power BI Visualizations\r\n *\r\n * Copyright (c) Microsoft Corporation\r\n * All rights reserved. \r\n * MIT License\r\n *\r\n * Permission is hereby granted, free of charge, to any person obtaining a copy\r\n * of this software and associated documentation files (the \"\"Software\"\"), to deal\r\n * in the Software without restriction, including without limitation the rights\r\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\r\n * copies of the Software, and to permit persons to whom the Software is\r\n * furnished to do so, subject to the following conditions:\r\n * \r\n * The above copyright notice and this permission notice shall be included in \r\n * all copies or substantial portions of the Software.\r\n * \r\n * THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR \r\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, \r\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE \r\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER \r\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\r\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\r\n * THE SOFTWARE.\r\n */\r\n\r\n/// <reference path=\"../_references.ts\"/>\r\n\r\nmodule powerbi.visuals {\r\n import ClassAndSelector = jsCommon.CssConstants.ClassAndSelector;\r\n\r\n export interface ColumnChartAnimationOptions extends IAnimationOptions {\r\n viewModel: ColumnChartData;\r\n series: D3.UpdateSelection;\r\n layout: IColumnLayout;\r\n itemCS: ClassAndSelector;\r\n mainGraphicsContext: D3.Selection;\r\n viewPort: IViewport;\r\n }\r\n\r\n export interface ColumnChartAnimationResult extends IAnimationResult {\r\n shapes: D3.UpdateSelection;\r\n }\r\n\r\n export type IColumnChartAnimator = IAnimator<IAnimatorOptions, ColumnChartAnimationOptions, ColumnChartAnimationResult>;\r\n\r\n export class WebColumnChartAnimator extends BaseAnimator<IAnimatorOptions, ColumnChartAnimationOptions, ColumnChartAnimationResult> implements IColumnChartAnimator {\r\n private previousViewModel: ColumnChartData;\r\n\r\n constructor(options?: IAnimatorOptions) {\r\n super(options);\r\n }\r\n\r\n public animate(options: ColumnChartAnimationOptions): ColumnChartAnimationResult {\r\n let result: ColumnChartAnimationResult = {\r\n failed: true,\r\n shapes: null,\r\n };\r\n\r\n let viewModel = options.viewModel;\r\n let previousViewModel = this.previousViewModel;\r\n \r\n let dataPointCount = viewModel.categories.length * viewModel.series.length;\r\n if (dataPointCount > AnimatorCommon.MaxDataPointsToAnimate) {\r\n // Too many data points to animate.\r\n return result;\r\n }\r\n\r\n if (!previousViewModel) {\r\n // This is the initial drawing of the chart, which has no special animation for now.\r\n }\r\n else if (viewModel.hasHighlights && !previousViewModel.hasHighlights) {\r\n result = this.animateNormalToHighlighted(options);\r\n }\r\n else if (viewModel.hasHighlights && previousViewModel.hasHighlights) {\r\n result = this.animateHighlightedToHighlighted(options);\r\n }\r\n else if (!viewModel.hasHighlights && previousViewModel.hasHighlights) {\r\n result = this.animateHighlightedToNormal(options);\r\n }\r\n\r\n this.previousViewModel = viewModel;\r\n return result;\r\n }\r\n\r\n private animateNormalToHighlighted(options: ColumnChartAnimationOptions): ColumnChartAnimationResult {\r\n let data = options.viewModel;\r\n let itemCS = options.itemCS;\r\n let shapeSelection = options.series.selectAll(itemCS.selector);\r\n let shapes = shapeSelection.data((d: ColumnChartSeries) => d.data, (d: ColumnChartDataPoint) => d.key);\r\n let hasHighlights = data.hasHighlights;\r\n\r\n shapes\r\n .enter()\r\n .append('rect')\r\n .attr(\"class\", (d: ColumnChartDataPoint) => itemCS.class.concat(d.highlight ? \" highlight\" : \"\"))\r\n .attr(options.layout.shapeLayoutWithoutHighlights); // Start out at the non-highlight layout\r\n\r\n shapes\r\n .style(\"fill\", (d: ColumnChartDataPoint) => d.color)\r\n .style(\"fill-opacity\", (d: ColumnChartDataPoint) => ColumnUtil.getFillOpacity(d.selected, d.highlight, false, hasHighlights))\r\n .transition()\r\n .duration(this.animationDuration)\r\n .attr(options.layout.shapeLayout);\r\n\r\n shapes\r\n .exit()\r\n .remove();\r\n\r\n return {\r\n failed: false,\r\n shapes: shapes,\r\n };\r\n }\r\n\r\n private animateHighlightedToHighlighted(options: ColumnChartAnimationOptions): ColumnChartAnimationResult {\r\n let shapes = this.animateDefaultShapes(options.viewModel, options.series, options.layout, options.itemCS);\r\n\r\n return {\r\n failed: false,\r\n shapes: shapes,\r\n };\r\n }\r\n\r\n private animateHighlightedToNormal(options: ColumnChartAnimationOptions): ColumnChartAnimationResult {\r\n let itemCS = options.itemCS;\r\n let shapeSelection = options.series.selectAll(itemCS.selector);\r\n let shapes = shapeSelection.data((d: ColumnChartSeries) => d.data, (d: ColumnChartDataPoint) => d.key);\r\n let hasSelection = options.interactivityService && options.interactivityService.hasSelection();\r\n\r\n shapes\r\n .enter()\r\n .append('rect')\r\n .attr(\"class\", (d: ColumnChartDataPoint) => itemCS.class.concat(d.highlight ? \" highlight\" : \"\"));\r\n\r\n shapes\r\n .style(\"fill\", (d: ColumnChartDataPoint) => d.color)\r\n .style(\"fill-opacity\", (d: ColumnChartDataPoint) => ColumnUtil.getFillOpacity(d.selected, d.highlight, d.selected, !d.selected))\r\n .transition()\r\n .duration(this.animationDuration)\r\n .attr(options.layout.shapeLayout)\r\n .transition()\r\n .duration(0)\r\n .delay(this.animationDuration)\r\n .style(\"fill-opacity\", (d: ColumnChartDataPoint) => ColumnUtil.getFillOpacity(d.selected, d.highlight, hasSelection, false));\r\n\r\n shapes\r\n .exit()\r\n .transition()\r\n .duration(this.animationDuration)\r\n .attr(hasSelection ? options.layout.zeroShapeLayout : options.layout.shapeLayoutWithoutHighlights)\r\n .remove();\r\n\r\n return {\r\n failed: false,\r\n shapes: shapes,\r\n };\r\n }\r\n\r\n private animateDefaultShapes(data: ColumnChartData, series: D3.UpdateSelection, layout: IColumnLayout, itemCS: ClassAndSelector): D3.UpdateSelection {\r\n let shapeSelection = series.selectAll(itemCS.selector);\r\n let shapes = shapeSelection.data((d: ColumnChartSeries) => d.data, (d: ColumnChartDataPoint) => d.key);\r\n\r\n shapes\r\n .enter()\r\n .append('rect')\r\n .attr(\"class\", (d: ColumnChartDataPoint) => itemCS.class.concat(d.highlight ? \" highlight\" : \"\"));\r\n\r\n shapes\r\n .style(\"fill\", (d: ColumnChartDataPoint) => d.color)\r\n .style(\"fill-opacity\", (d: ColumnChartDataPoint) => ColumnUtil.getFillOpacity(d.selected, d.highlight, false, data.hasHighlights))\r\n .transition()\r\n .duration(this.animationDuration)\r\n .attr(layout.shapeLayout);\r\n\r\n shapes\r\n .exit()\r\n .remove();\r\n\r\n return shapes;\r\n }\r\n }\r\n}\n\n\n/** WEBPACK FOOTER **\n ** ./src/Clients/Visuals/animators/columnChartAnimator.ts\n **/","/*\r\n * Power BI Visualizations\r\n *\r\n * Copyright (c) Microsoft Corporation\r\n * All rights reserved. \r\n * MIT License\r\n *\r\n * Permission is hereby granted, free of charge, to any person obtaining a copy\r\n * of this software and associated documentation files (the \"\"Software\"\"), to deal\r\n * in the Software without restriction, including without limitation the rights\r\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\r\n * copies of the Software, and to permit persons to whom the Software is\r\n * furnished to do so, subject to the following conditions:\r\n * \r\n * The above copyright notice and this permission notice shall be included in \r\n * all copies or substantial portions of the Software.\r\n * \r\n * THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR \r\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, \r\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE \r\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER \r\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\r\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\r\n * THE SOFTWARE.\r\n */\r\n\r\n/// <reference path=\"../_references.ts\"/>\r\n\r\nmodule powerbi.visuals {\r\n export interface DonutChartAnimationOptions extends IAnimationOptions {\r\n viewModel: DonutData;\r\n graphicsContext: D3.Selection;\r\n labelGraphicsContext: D3.Selection;\r\n colors: IDataColorPalette;\r\n layout: DonutLayout;\r\n sliceWidthRatio: number;\r\n radius: number;\r\n viewport: IViewport;\r\n innerArcRadiusRatio: number;\r\n labels: Label[];\r\n }\r\n\r\n export interface DonutChartAnimationResult extends IAnimationResult {\r\n shapes: D3.UpdateSelection;\r\n highlightShapes: D3.UpdateSelection;\r\n }\r\n\r\n export type IDonutChartAnimator = IAnimator<IAnimatorOptions, DonutChartAnimationOptions, DonutChartAnimationResult>;\r\n\r\n export class WebDonutChartAnimator extends BaseAnimator<IAnimatorOptions, DonutChartAnimationOptions, DonutChartAnimationResult> implements IDonutChartAnimator {\r\n private previousViewModel: DonutData;\r\n\r\n constructor(options?: IAnimatorOptions) {\r\n super(options);\r\n }\r\n\r\n public animate(options: DonutChartAnimationOptions): DonutChartAnimationResult {\r\n let result: DonutChartAnimationResult = {\r\n failed: true,\r\n shapes: null,\r\n highlightShapes: null,\r\n };\r\n\r\n let viewModel = options.viewModel;\r\n let previousViewModel = this.previousViewModel;\r\n if (viewModel.highlightsOverflow || (previousViewModel && previousViewModel.highlightsOverflow)) {\r\n // Do not animate when we have highlights but they are overflowing\r\n this.previousViewModel = viewModel;\r\n return result;\r\n }\r\n let previousHasHighlights = previousViewModel && previousViewModel.hasHighlights;\r\n let currentHasHighlights = viewModel.hasHighlights;\r\n\r\n if (!previousViewModel) {\r\n // This is the initial drawing of the chart, which has no special animation for now.\r\n }\r\n else if (currentHasHighlights && !previousHasHighlights) {\r\n result = this.animateNormalToHighlighted(options);\r\n }\r\n else if (currentHasHighlights && previousHasHighlights) {\r\n result = this.animateHighlightedToHighlighted(options);\r\n }\r\n else if (!currentHasHighlights && previousHasHighlights) {\r\n result = this.animateHighlightedToNormal(options);\r\n }\r\n this.previousViewModel = viewModel;\r\n return result;\r\n }\r\n\r\n private animateNormalToHighlighted(options: DonutChartAnimationOptions): DonutChartAnimationResult {\r\n let shapes = this.animateDefaultShapes(options);\r\n\r\n let highlightShapes = options.graphicsContext.select('.slices')\r\n .selectAll('path.slice-highlight')\r\n .data(options.viewModel.dataPoints.filter((value: DonutArcDescriptor) => value.data.highlightRatio != null), (d: DonutArcDescriptor) => d.data.identity.getKey());\r\n\r\n highlightShapes.enter()\r\n .insert('path')\r\n .classed('slice-highlight', true)\r\n .each(function (d) { this._current = d; });\r\n\r\n DonutChart.isSingleColor(options.viewModel.dataPoints.filter((value: DonutArcDescriptor) => value.data.highlightRatio != null));\r\n\r\n highlightShapes\r\n .style('fill', (d: DonutArcDescriptor) => d.data.color ? d.data.color : options.colors.getNewColorScale().getColor(d.data.identity.getKey()).value)\r\n .style('fill-opacity', (d: DonutArcDescriptor) => ColumnUtil.getFillOpacity(d.data.selected, true, false, options.viewModel.hasHighlights))\r\n .style(\"stroke-dasharray\", (d: DonutArcDescriptor) => DonutChart.drawStrokeForDonutChart(options.radius, options.innerArcRadiusRatio, d, options.sliceWidthRatio, d.data.highlightRatio))\r\n .style(\"stroke-width\", (d: DonutArcDescriptor) => d.data.strokeWidth)\r\n .attr(options.layout.shapeLayout) // Start at the non-highlight layout, then transition to the highlight layout.\r\n .transition()\r\n .duration(this.animationDuration)\r\n .attr(options.layout.highlightShapeLayout);\r\n\r\n highlightShapes.exit()\r\n .remove();\r\n\r\n NewDataLabelUtils.drawDefaultLabels(options.labelGraphicsContext, options.labels, false, true, true /*has tooltip */);\r\n NewDataLabelUtils.drawLabelLeaderLines(options.labelGraphicsContext, options.labels);\r\n\r\n return {\r\n failed: false,\r\n shapes: shapes,\r\n highlightShapes: highlightShapes,\r\n };\r\n }\r\n\r\n private animateHighlightedToHighlighted(options: DonutChartAnimationOptions): DonutChartAnimationResult {\r\n let shapes = this.animateDefaultShapes(options);\r\n\r\n let highlightShapes = this.animateDefaultHighlightShapes(options);\r\n NewDataLabelUtils.drawDefaultLabels(options.labelGraphicsContext, options.labels, false, true, true /*has tooltip */);\r\n NewDataLabelUtils.drawLabelLeaderLines(options.labelGraphicsContext, options.labels);\r\n\r\n return {\r\n failed: false,\r\n shapes: shapes,\r\n highlightShapes: highlightShapes,\r\n };\r\n }\r\n\r\n private animateHighlightedToNormal(options: DonutChartAnimationOptions): DonutChartAnimationResult {\r\n let hasSelection = options.interactivityService && options.interactivityService.hasSelection();\r\n let duration = this.animationDuration;\r\n\r\n let shapes = options.graphicsContext.select('.slices')\r\n .selectAll('path.slice')\r\n .data(options.viewModel.dataPoints, (d: DonutArcDescriptor) => d.data.identity.getKey());\r\n\r\n shapes.enter()\r\n .insert('path')\r\n .classed('slice', true)\r\n .each(function (d) { this._current = d; });\r\n\r\n DonutChart.isSingleColor(options.viewModel.dataPoints);\r\n\r\n // For any slice that is selected we want to keep showing it as dimmed (partially highlighted). After the highlight animation\r\n // finishes we will set the opacity based on the selection state.\r\n shapes\r\n .style('fill', (d: DonutArcDescriptor) => d.data.color ? d.data.color : options.colors.getNewColorScale().getColor(d.data.identity.getKey()).value)\r\n .style('fill-opacity', (d: DonutArcDescriptor) => ColumnUtil.getFillOpacity(d.data.selected, false, d.data.selected, !d.data.selected))\r\n .style(\"stroke-dasharray\", (d: DonutArcDescriptor) => DonutChart.drawStrokeForDonutChart(options.radius, options.innerArcRadiusRatio, d, options.sliceWidthRatio))\r\n .style(\"stroke-width\", (d: DonutArcDescriptor) => d.data.strokeWidth)\r\n .transition()\r\n .duration(duration)\r\n .attr(options.layout.shapeLayout)\r\n .transition()\r\n .duration(0)\r\n .delay(duration)\r\n .style(\"fill-opacity\", (d: DonutArcDescriptor) => ColumnUtil.getFillOpacity(d.data.selected, false, hasSelection, false));;\r\n\r\n shapes.exit()\r\n .remove();\r\n\r\n let highlightShapes = options.graphicsContext.select('.slices')\r\n .selectAll('path.slice-highlight')\r\n .data(options.viewModel.dataPoints.filter((value: DonutArcDescriptor) => value.data.highlightRatio != null), (d: DonutArcDescriptor) => d.data.identity.getKey());\r\n\r\n highlightShapes.enter()\r\n .insert('path')\r\n .classed('slice-highlight', true)\r\n .each(function (d) { this._current = d; });\r\n\r\n DonutChart.isSingleColor(options.viewModel.dataPoints.filter((value: DonutArcDescriptor) => value.data.highlightRatio != null));\r\n\r\n highlightShapes\r\n .style('fill', (d: DonutArcDescriptor) => d.data.color ? d.data.color : options.colors.getNewColorScale().getColor(d.data.identity.getKey()).value)\r\n .style('fill-opacity', (d: DonutArcDescriptor) => ColumnUtil.getFillOpacity(false, true, false, true))\r\n .style(\"stroke-dasharray\", (d: DonutArcDescriptor) => DonutChart.drawStrokeForDonutChart(options.radius, options.innerArcRadiusRatio, d, options.sliceWidthRatio, d.data.highlightRatio))\r\n .style(\"stroke-width\", (d: DonutArcDescriptor) => d.data.strokeWidth)\r\n .transition()\r\n .duration(duration)\r\n .attr(hasSelection ? options.layout.zeroShapeLayout : options.layout.shapeLayout) // Transition to the non-highlight layout\r\n .remove();\r\n\r\n highlightShapes.exit()\r\n .remove();\r\n\r\n NewDataLabelUtils.drawDefaultLabels(options.labelGraphicsContext, options.labels, false, true, true /*has tooltip */);\r\n NewDataLabelUtils.drawLabelLeaderLines(options.labelGraphicsContext, options.labels);\r\n\r\n return {\r\n failed: false,\r\n shapes: shapes,\r\n highlightShapes: highlightShapes,\r\n };\r\n }\r\n\r\n private animateDefaultShapes(options: DonutChartAnimationOptions): D3.UpdateSelection {\r\n let shapes = options.graphicsContext.select('.slices')\r\n .selectAll('path.slice')\r\n .data(options.viewModel.dataPoints, (d: DonutArcDescriptor) => d.data.identity.getKey());\r\n\r\n shapes.enter()\r\n .insert('path')\r\n .classed('slice', true)\r\n .each(function (d) { this._current = d; });\r\n\r\n DonutChart.isSingleColor(options.viewModel.dataPoints);\r\n\r\n shapes\r\n .style('fill', (d: DonutArcDescriptor) => d.data.color ? d.data.color : options.colors.getNewColorScale().getColor(d.data.identity.getKey()).value)\r\n .style('fill-opacity', (d: DonutArcDescriptor) => ColumnUtil.getFillOpacity(d.data.selected, false, false, options.viewModel.hasHighlights))\r\n .style(\"stroke-dasharray\", (d: DonutArcDescriptor) => DonutChart.drawStrokeForDonutChart(options.radius, options.innerArcRadiusRatio, d, options.sliceWidthRatio))\r\n .style(\"stroke-width\", (d: DonutArcDescriptor) => d.data.strokeWidth)\r\n .transition()\r\n .duration(this.animationDuration)\r\n .attr(options.layout.shapeLayout);\r\n\r\n shapes.exit()\r\n .remove();\r\n\r\n return shapes;\r\n }\r\n\r\n private animateDefaultHighlightShapes(options: DonutChartAnimationOptions): D3.UpdateSelection {\r\n let highlightShapes = options.graphicsContext.select('.slices')\r\n .selectAll('path.slice-highlight')\r\n .data(options.viewModel.dataPoints.filter((value: DonutArcDescriptor) => value.data.highlightRatio != null), (d: DonutArcDescriptor) => d.data.identity.getKey());\r\n\r\n highlightShapes.enter()\r\n .insert('path')\r\n .classed('slice-highlight', true)\r\n .each(function (d) { this._current = d; });\r\n\r\n DonutChart.isSingleColor(options.viewModel.dataPoints.filter((value: DonutArcDescriptor) => value.data.highlightRatio != null));\r\n\r\n highlightShapes\r\n .style('fill', (d: DonutArcDescriptor) => d.data.color ? d.data.color : options.colors.getNewColorScale().getColor(d.data.identity.getKey()).value)\r\n .style('fill-opacity', (d: DonutArcDescriptor) => ColumnUtil.getFillOpacity(d.data.selected, true, false, options.viewModel.hasHighlights))\r\n .style(\"stroke-dasharray\", (d: DonutArcDescriptor) => DonutChart.drawStrokeForDonutChart(options.radius, options.innerArcRadiusRatio, d, options.sliceWidthRatio, d.data.highlightRatio))\r\n .style(\"stroke-width\", (d: DonutArcDescriptor) => d.data.strokeWidth)\r\n .transition()\r\n .duration(this.animationDuration)\r\n .attr(options.layout.highlightShapeLayout);\r\n\r\n highlightShapes.exit()\r\n .remove();\r\n\r\n return highlightShapes;\r\n }\r\n }\r\n} \n\n\n/** WEBPACK FOOTER **\n ** ./src/Clients/Visuals/animators/donutChartAnimator.ts\n **/","/*\r\n * Power BI Visualizations\r\n *\r\n * Copyright (c) Microsoft Corporation\r\n * All rights reserved. \r\n * MIT License\r\n *\r\n * Permission is hereby granted, free of charge, to any person obtaining a copy\r\n * of this software and associated documentation files (the \"\"Software\"\"), to deal\r\n * in the Software without restriction, including without limitation the rights\r\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\r\n * copies of the Software, and to permit persons to whom the Software is\r\n * furnished to do so, subject to the following conditions:\r\n * \r\n * The above copyright notice and this permission notice shall be included in \r\n * all copies or substantial portions of the Software.\r\n * \r\n * THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR \r\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, \r\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE \r\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER \r\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\r\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\r\n * THE SOFTWARE.\r\n */\r\n\r\n/// <reference path=\"../_references.ts\"/>\r\n\r\nmodule powerbi.visuals {\r\n export interface FunnelAnimationOptions extends IAnimationOptions{\r\n viewModel: FunnelData;\r\n layout: IFunnelLayout;\r\n axisGraphicsContext: D3.Selection;\r\n shapeGraphicsContext: D3.Selection;\r\n percentGraphicsContext: D3.Selection;\r\n labelGraphicsContext: D3.Selection;\r\n axisOptions: FunnelAxisOptions;\r\n dataPointsWithoutHighlights: FunnelDataPoint[];\r\n labelLayout: Label[];\r\n isHidingPercentBars: boolean;\r\n visualInitOptions: VisualInitOptions;\r\n }\r\n\r\n export interface FunnelAnimationResult extends IAnimationResult {\r\n shapes: D3.UpdateSelection;\r\n dataLabels: D3.UpdateSelection;\r\n }\r\n\r\n export type IFunnelAnimator = IAnimator<IAnimatorOptions, FunnelAnimationOptions, FunnelAnimationResult>;\r\n\r\n export class WebFunnelAnimator extends BaseAnimator<IAnimatorOptions, FunnelAnimationOptions, FunnelAnimationResult> implements IFunnelAnimator {\r\n private previousViewModel: FunnelData;\r\n\r\n constructor(options?: IAnimatorOptions) {\r\n super(options);\r\n }\r\n\r\n public animate(options: FunnelAnimationOptions): FunnelAnimationResult {\r\n let result: FunnelAnimationResult = {\r\n failed: true,\r\n shapes: null,\r\n dataLabels: null,\r\n };\r\n\r\n let viewModel = options.viewModel;\r\n let previousViewModel = this.previousViewModel;\r\n\r\n if (!previousViewModel) {\r\n // This is the initial drawing of the chart, which has no special animation for now.\r\n }\r\n else if (viewModel.hasHighlights && !previousViewModel.hasHighlights) {\r\n result = this.animateNormalToHighlighted(options);\r\n }\r\n else if (viewModel.hasHighlights && previousViewModel.hasHighlights) {\r\n result = this.animateHighlightedToHighlighted(options);\r\n }\r\n else if (!viewModel.hasHighlights && previousViewModel.hasHighlights) {\r\n result = this.animateHighlightedToNormal(options);\r\n }\r\n\r\n this.previousViewModel = viewModel;\r\n return result;\r\n }\r\n\r\n private animateNormalToHighlighted(options: FunnelAnimationOptions): FunnelAnimationResult {\r\n let data = options.viewModel;\r\n let layout = options.layout;\r\n let hasHighlights = true;\r\n let hasSelection = false;\r\n\r\n this.animateDefaultAxis(options.axisGraphicsContext, options.axisOptions, options.isHidingPercentBars);\r\n\r\n let shapes = options.shapeGraphicsContext.selectAll(FunnelChart.Selectors.funnel.bars.selector).data(data.dataPoints, (d: FunnelDataPoint) => d.key);\r\n\r\n shapes.enter()\r\n .append('rect')\r\n .attr(\"class\", (d: FunnelDataPoint) => d.highlight ? FunnelChart.FunnelBarHighlightClass : FunnelChart.Selectors.funnel.bars.class)\r\n .attr(layout.shapeLayoutWithoutHighlights); // Start by laying out all rectangles ignoring highlights\r\n\r\n shapes\r\n .style(\"fill\", (d: FunnelDataPoint) => d.color)\r\n .style(\"fill-opacity\", (d: FunnelDataPoint) => ColumnUtil.getFillOpacity(d.selected, d.highlight, hasSelection, hasHighlights))\r\n .transition()\r\n .duration(this.animationDuration)\r\n .attr(layout.shapeLayout); // Then transition to the layout that uses highlights\r\n\r\n shapes.exit().remove();\r\n\r\n this.animatePercentBars(options);\r\n\r\n let dataLabels: D3.UpdateSelection = NewDataLabelUtils.animateDefaultLabels(options.labelGraphicsContext, options.labelLayout, this.animationDuration);\r\n \r\n return {\r\n failed: false,\r\n shapes: shapes,\r\n dataLabels: dataLabels,\r\n };\r\n }\r\n\r\n private animateHighlightedToHighlighted(options: FunnelAnimationOptions): FunnelAnimationResult {\r\n let data = options.viewModel;\r\n let layout = options.layout;\r\n\r\n this.animateDefaultAxis(options.axisGraphicsContext, options.axisOptions, options.isHidingPercentBars);\r\n\r\n // Simply animate to the new shapes.\r\n let shapes = this.animateDefaultShapes(data, data.dataPoints, options.shapeGraphicsContext, layout);\r\n this.animatePercentBars(options);\r\n let dataLabels: D3.UpdateSelection = NewDataLabelUtils.animateDefaultLabels(options.labelGraphicsContext, options.labelLayout, this.animationDuration);\r\n\r\n return {\r\n failed: false,\r\n shapes: shapes,\r\n dataLabels: dataLabels,\r\n };\r\n }\r\n\r\n private animateHighlightedToNormal(options: FunnelAnimationOptions): FunnelAnimationResult {\r\n let data = options.viewModel;\r\n let layout = options.layout;\r\n let hasSelection = options.interactivityService ? options.interactivityService.hasSelection() : false;\r\n\r\n this.animateDefaultAxis(options.axisGraphicsContext, options.axisOptions, options.isHidingPercentBars);\r\n\r\n let shapes = options.shapeGraphicsContext.selectAll(FunnelChart.Selectors.funnel.bars.selector).data(data.dataPoints, (d: FunnelDataPoint) => d.key);\r\n\r\n shapes.enter()\r\n .append('rect')\r\n .attr(\"class\", (d: FunnelDataPoint) => d.highlight ? FunnelChart.FunnelBarHighlightClass : FunnelChart.Selectors.funnel.bars.class);\r\n\r\n shapes\r\n .style(\"fill\",(d: FunnelDataPoint) => d.color)\r\n .style(\"fill-opacity\", (d: FunnelDataPoint) => ColumnUtil.getFillOpacity(d.selected, d.highlight, hasSelection, !d.selected))\r\n .transition()\r\n .duration(this.animationDuration)\r\n .attr(layout.shapeLayoutWithoutHighlights) // Transition to layout without highlights\r\n .transition()\r\n .duration(0)\r\n .delay(this.animationDuration)\r\n .style(\"fill-opacity\", (d: FunnelDataPoint) => ColumnUtil.getFillOpacity(d.selected, d.highlight, hasSelection, false));\r\n\r\n let exitShapes = shapes.exit();\r\n\r\n exitShapes\r\n .transition()\r\n .duration(this.animationDuration)\r\n .attr(hasSelection ? layout.zeroShapeLayout : layout.shapeLayoutWithoutHighlights) // Transition to layout without highlights\r\n .remove();\r\n\r\n this.animatePercentBars(options);\r\n\r\n let dataLabels: D3.UpdateSelection = NewDataLabelUtils.animateDefaultLabels(options.labelGraphicsContext, options.labelLayout, this.animationDuration);\r\n\r\n return {\r\n failed: false,\r\n shapes: shapes,\r\n dataLabels: dataLabels,\r\n };\r\n }\r\n\r\n private animateDefaultAxis(graphicsContext: D3.Selection, axisOptions: FunnelAxisOptions, isHidingPercentBars: boolean): void {\r\n let xScaleForAxis = d3.scale.ordinal()\r\n .domain(axisOptions.categoryLabels)\r\n .rangeBands([axisOptions.rangeStart, axisOptions.rangeEnd], axisOptions.barToSpaceRatio, isHidingPercentBars ? axisOptions.barToSpaceRatio : FunnelChart.PercentBarToBarRatio);\r\n let xAxis = d3.svg.axis()\r\n .scale(xScaleForAxis)\r\n .orient(\"right\")\r\n .tickPadding(FunnelChart.TickPadding)\r\n .innerTickSize(FunnelChart.InnerTickSize);\r\n graphicsContext.classed('axis', true)\r\n .transition()\r\n .duration(this.animationDuration)\r\n .attr('transform', SVGUtil.translate(0, axisOptions.margin.top))\r\n .call(xAxis);\r\n }\r\n\r\n private animateDefaultShapes(data: FunnelData, dataPoints: FunnelDataPoint[], graphicsContext: D3.Selection, layout: IFunnelLayout): D3.UpdateSelection {\r\n let hasHighlights = data.hasHighlights;\r\n let shapes = graphicsContext.selectAll(FunnelChart.Selectors.funnel.bars.selector).data(dataPoints, (d: FunnelDataPoint) => d.key);\r\n\r\n shapes.enter()\r\n .append('rect')\r\n .attr(\"class\", (d: FunnelDataPoint) => d.highlight ? FunnelChart.FunnelBarHighlightClass : FunnelChart.Selectors.funnel.bars.class);\r\n\r\n shapes\r\n .style(\"fill\", (d: FunnelDataPoint) => d.color)\r\n .style(\"fill-opacity\", d => (d: FunnelDataPoint) => ColumnUtil.getFillOpacity(d.selected, d.highlight, false, hasHighlights))\r\n .transition()\r\n .duration(this.animationDuration)\r\n .attr(layout.shapeLayout);\r\n\r\n shapes.exit().remove();\r\n\r\n return shapes;\r\n }\r\n\r\n private animatePercentBars(options: FunnelAnimationOptions): void {\r\n let data: FunnelData = options.viewModel;\r\n let isHidingPercentBars: boolean = options.isHidingPercentBars;\r\n\r\n if (isHidingPercentBars || !data.dataPoints || (data.hasHighlights ? data.dataPoints.length / 2 : data.dataPoints.length) < 2) {\r\n // TODO: call percentBarComponents with flag with empty data to clear drawing smoothly\r\n this.animatePercentBarComponents([], options);\r\n return;\r\n }\r\n\r\n let dataPoints = [data.dataPoints[data.hasHighlights ? 1 : 0], data.dataPoints[data.dataPoints.length - 1]];\r\n let baseline = FunnelChart.getValueFromDataPoint(dataPoints[0]);\r\n\r\n if (baseline <= 0) {\r\n // TODO: call percentBarComponents with flag with empty data to clear drawing smoothly\r\n this.animatePercentBarComponents([], options);\r\n return;\r\n }\r\n\r\n let percentData = dataPoints.map((dataPoint, i) => <FunnelPercent>{\r\n value: FunnelChart.getValueFromDataPoint(dataPoint),\r\n percent: i === 0 ? 1 : FunnelChart.getValueFromDataPoint(dataPoint) / baseline,\r\n isTop: i === 0,\r\n });\r\n\r\n this.animatePercentBarComponents(percentData, options);\r\n }\r\n\r\n private animateToFunnelPercent(context: D3.Selection, targetData: FunnelPercent[], layout: any): D3.Transition.Transition {\r\n return context\r\n .data(targetData)\r\n .transition()\r\n .duration(this.animationDuration)\r\n .attr(layout);\r\n }\r\n\r\n private animatePercentBarComponents(data: FunnelPercent[], options: FunnelAnimationOptions) {\r\n let graphicsContext: D3.Selection = options.percentGraphicsContext;\r\n let layout: IFunnelLayout = options.layout;\r\n let zeroData: FunnelPercent[] = [\r\n { percent: 0, value: 0, isTop: true },\r\n { percent: 0, value: 0, isTop: false },\r\n ];\r\n\r\n // Main line\r\n let mainLine: D3.UpdateSelection = graphicsContext.selectAll(FunnelChart.Selectors.percentBar.mainLine.selector).data(data);\r\n\r\n this.animateToFunnelPercent(mainLine.exit(), zeroData, layout.percentBarLayout.mainLine)\r\n .remove();\r\n\r\n mainLine.enter()\r\n .append('line')\r\n .classed(FunnelChart.Selectors.percentBar.mainLine.class, true)\r\n .data(zeroData)\r\n .attr(layout.percentBarLayout.mainLine);\r\n\r\n this.animateToFunnelPercent(mainLine, data, layout.percentBarLayout.mainLine);\r\n\r\n // Left tick\r\n let leftTick: D3.UpdateSelection = graphicsContext.selectAll(FunnelChart.Selectors.percentBar.leftTick.selector).data(data);\r\n\r\n this.animateToFunnelPercent(leftTick.exit(), zeroData, layout.percentBarLayout.leftTick)\r\n .remove();\r\n \r\n leftTick.enter()\r\n .append('line')\r\n .classed(FunnelChart.Selectors.percentBar.leftTick.class, true)\r\n .data(zeroData)\r\n .attr(layout.percentBarLayout.leftTick);\r\n\r\n this.animateToFunnelPercent(leftTick, data, layout.percentBarLayout.leftTick);\r\n\r\n // Right tick\r\n let rightTick: D3.UpdateSelection = graphicsContext.selectAll(FunnelChart.Selectors.percentBar.rightTick.selector).data(data);\r\n \r\n this.animateToFunnelPercent(rightTick.exit(), zeroData, layout.percentBarLayout.rightTick)\r\n .remove();\r\n\r\n rightTick.enter()\r\n .append('line')\r\n .classed(FunnelChart.Selectors.percentBar.rightTick.class, true)\r\n .data(zeroData)\r\n .attr(layout.percentBarLayout.rightTick);\r\n\r\n this.animateToFunnelPercent(rightTick, data, layout.percentBarLayout.rightTick);\r\n\r\n // Text\r\n let text: D3.UpdateSelection = graphicsContext.selectAll(FunnelChart.Selectors.percentBar.text.selector).data(data);\r\n \r\n this.animateToFunnelPercent(text.exit(), zeroData, layout.percentBarLayout.text)\r\n .remove();\r\n\r\n text.enter()\r\n .append('text')\r\n .classed(FunnelChart.Selectors.percentBar.text.class, true)\r\n .data(zeroData)\r\n .attr(layout.percentBarLayout.text);\r\n\r\n this.animateToFunnelPercent(text, data, layout.percentBarLayout.text)\r\n .text((fp: FunnelPercent) => {\r\n return formattingService.formatValue(fp.percent, valueFormatter.getLocalizedString(\"Percentage1\"));\r\n });\r\n\r\n SVGUtil.flushAllD3TransitionsIfNeeded(options.visualInitOptions);\r\n }\r\n }\r\n}\n\n\n/** WEBPACK FOOTER **\n ** ./src/Clients/Visuals/animators/funnelChartAnimator.ts\n **/","/*\r\n * Power BI Visualizations\r\n *\r\n * Copyright (c) Microsoft Corporation\r\n * All rights reserved. \r\n * MIT License\r\n *\r\n * Permission is hereby granted, free of charge, to any person obtaining a copy\r\n * of this software and associated documentation files (the \"\"Software\"\"), to deal\r\n * in the Software without restriction, including without limitation the rights\r\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\r\n * copies of the Software, and to permit persons to whom the Software is\r\n * furnished to do so, subject to the following conditions:\r\n * \r\n * The above copyright notice and this permission notice shall be included in \r\n * all copies or substantial portions of the Software.\r\n * \r\n * THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR \r\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, \r\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE \r\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER \r\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\r\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\r\n * THE SOFTWARE.\r\n */\r\n\r\n/// <reference path=\"../_references.ts\"/>\r\n\r\nmodule powerbi.visuals {\r\n export interface TreemapAnimationOptions extends IAnimationOptions {\r\n viewModel: TreemapData;\r\n nodes: D3.Layout.GraphNode[];\r\n highlightNodes: D3.Layout.GraphNode[];\r\n majorLabeledNodes: D3.Layout.GraphNode[];\r\n minorLabeledNodes: D3.Layout.GraphNode[];\r\n shapeGraphicsContext: D3.Selection;\r\n labelGraphicsContext: D3.Selection;\r\n layout: ITreemapLayout;\r\n labelSettings: VisualDataLabelsSettings;\r\n }\r\n\r\n export interface TreemapAnimationResult extends IAnimationResult {\r\n shapes: D3.UpdateSelection;\r\n highlightShapes: D3.UpdateSelection;\r\n majorLabels: D3.UpdateSelection;\r\n minorLabels: D3.UpdateSelection;\r\n }\r\n\r\n export type ITreemapAnimator = IAnimator<IAnimatorOptions, TreemapAnimationOptions, TreemapAnimationResult>;\r\n\r\n export class WebTreemapAnimator extends BaseAnimator<IAnimatorOptions, TreemapAnimationOptions, TreemapAnimationResult> implements ITreemapAnimator {\r\n previousViewModel: TreemapData;\r\n\r\n constructor(options?: IAnimatorOptions) {\r\n super(options);\r\n }\r\n\r\n public animate(options: TreemapAnimationOptions): TreemapAnimationResult {\r\n let result: TreemapAnimationResult = {\r\n failed: true,\r\n shapes: null,\r\n highlightShapes: null,\r\n majorLabels: null,\r\n minorLabels: null,\r\n };\r\n\r\n let viewModel = options.viewModel;\r\n let previousViewModel = this.previousViewModel;\r\n\r\n if (!previousViewModel) {\r\n // This is the initial drawing of the chart, which has no special animation for now.\r\n }\r\n else if (viewModel.hasHighlights && !previousViewModel.hasHighlights) {\r\n result = this.animateNormalToHighlighted(options);\r\n }\r\n else if (viewModel.hasHighlights && previousViewModel.hasHighlights) {\r\n result = this.animateHighlightedToHighlighted(options);\r\n }\r\n else if (!viewModel.hasHighlights && previousViewModel.hasHighlights) {\r\n result = this.animateHighlightedToNormal(options);\r\n }\r\n\r\n this.previousViewModel = viewModel;\r\n return result;\r\n }\r\n\r\n private animateNormalToHighlighted(options: TreemapAnimationOptions): TreemapAnimationResult {\r\n let hasSelection = false;\r\n let hasHighlights = true;\r\n\r\n let shapes = this.animateDefaultShapes(options.shapeGraphicsContext, options.nodes, hasSelection, hasHighlights, options.layout);\r\n\r\n let highlightShapes = options.shapeGraphicsContext.selectAll('.' + Treemap.HighlightNodeClassName)\r\n .data(options.highlightNodes, (d: TreemapNode) => d.key + \"highlight\");\r\n\r\n highlightShapes.enter().append('rect')\r\n .attr('class', options.layout.highlightShapeClass)\r\n .attr(options.layout.shapeLayout); // Start using the normal shape layout\r\n\r\n highlightShapes\r\n .style(\"fill\", (d: TreemapNode) => Treemap.getFill(d, true))\r\n .style(\"fill-opacity\", (d: TreemapNode) => Treemap.getFillOpacity(d, hasSelection, hasHighlights, true))\r\n .transition()\r\n .duration(this.animationDuration)\r\n .attr(options.layout.highlightShapeLayout); // Animate to the highlighted positions\r\n\r\n highlightShapes.exit().remove();\r\n\r\n let majorLabels = this.animateDefaultMajorLabels(options.labelGraphicsContext, options.majorLabeledNodes, options.labelSettings, options.layout);\r\n let minorLabels = this.animateDefaultMinorLabels(options.labelGraphicsContext, options.minorLabeledNodes, options.labelSettings, options.layout);\r\n\r\n return {\r\n failed: false,\r\n shapes: shapes,\r\n highlightShapes: highlightShapes,\r\n majorLabels: majorLabels,\r\n minorLabels: minorLabels,\r\n };\r\n }\r\n\r\n private animateHighlightedToHighlighted(options: TreemapAnimationOptions): TreemapAnimationResult {\r\n let hasSelection = false;\r\n let hasHighlights = true;\r\n\r\n let shapes = this.animateDefaultShapes(options.shapeGraphicsContext, options.nodes, hasSelection, hasHighlights, options.layout);\r\n\r\n options.shapeGraphicsContext.selectAll('.' + Treemap.HighlightNodeClassName)\r\n .data(options.highlightNodes, (d: TreemapNode) => d.key + \"highlight\");\r\n\r\n let highlightShapes = this.animateDefaultHighlightShapes(options.shapeGraphicsContext, options.highlightNodes, hasSelection, hasHighlights, options.layout);\r\n \r\n let majorLabels = this.animateDefaultMajorLabels(options.labelGraphicsContext, options.majorLabeledNodes, options.labelSettings, options.layout);\r\n let minorLabels = this.animateDefaultMinorLabels(options.labelGraphicsContext, options.minorLabeledNodes, options.labelSettings, options.layout);\r\n\r\n return {\r\n failed: false,\r\n shapes: shapes,\r\n highlightShapes: highlightShapes,\r\n majorLabels: majorLabels,\r\n minorLabels: minorLabels,\r\n };\r\n }\r\n\r\n private animateHighlightedToNormal(options: TreemapAnimationOptions): TreemapAnimationResult {\r\n let hasSelection = options.interactivityService ? options.interactivityService.hasSelection() : false;\r\n\r\n let shapes = options.shapeGraphicsContext.selectAll('.' + Treemap.TreemapNodeClassName)\r\n .data(options.nodes, (d: TreemapNode) => d.key);\r\n\r\n shapes.enter().append('rect')\r\n .attr('class', options.layout.shapeClass);\r\n\r\n shapes\r\n .transition()\r\n .duration(this.animationDuration)\r\n .style(\"fill\", (d: TreemapNode) => Treemap.getFill(d, false))\r\n .style(\"fill-opacity\", (d: TreemapNode) => ColumnUtil.getFillOpacity(d.selected, false, d.selected, !d.selected))\r\n .attr(options.layout.shapeLayout)\r\n .transition()\r\n .duration(0)\r\n .delay(this.animationDuration)\r\n .style(\"fill-opacity\", (d: TreemapNode) => ColumnUtil.getFillOpacity(d.selected, false, hasSelection, false));\r\n\r\n shapes.exit().remove();\r\n\r\n let highlightShapes = options.shapeGraphicsContext.selectAll('.' + Treemap.HighlightNodeClassName)\r\n .data(options.nodes, (d: TreemapNode) => d.key + \"highlight\");\r\n\r\n highlightShapes.enter().append('rect')\r\n .attr('class', options.layout.highlightShapeClass);\r\n\r\n highlightShapes\r\n .style(\"fill\", (d: TreemapNode) => Treemap.getFill(d, true))\r\n .style(\"fill-opacity\", (d: TreemapNode) => ColumnUtil.getFillOpacity(d.selected, true, d.selected, !d.selected))\r\n .transition()\r\n .duration(this.animationDuration)\r\n .attr(hasSelection ? options.layout.zeroShapeLayout : options.layout.shapeLayout) // Animate to the normal shape layout or zero shape layout depending on whether we have a selection or not\r\n .remove();\r\n\r\n highlightShapes.exit().remove();\r\n\r\n let majorLabels = this.animateDefaultMajorLabels(options.labelGraphicsContext, options.majorLabeledNodes, options.labelSettings, options.layout);\r\n let minorLabels = this.animateDefaultMinorLabels(options.labelGraphicsContext, options.minorLabeledNodes, options.labelSettings, options.layout);\r\n\r\n return {\r\n failed: false,\r\n shapes: shapes,\r\n highlightShapes: highlightShapes,\r\n majorLabels: majorLabels,\r\n minorLabels: minorLabels,\r\n };\r\n }\r\n\r\n private animateDefaultShapes(context: D3.Selection, nodes: D3.Layout.GraphNode[], hasSelection: boolean, hasHighlights: boolean, layout: ITreemapLayout): D3.UpdateSelection {\r\n let isHighlightShape = false;\r\n let shapes = context.selectAll('.' + Treemap.TreemapNodeClassName)\r\n .data(nodes, (d: TreemapNode) => d.key);\r\n\r\n shapes.enter().append('rect')\r\n .attr('class', layout.shapeClass);\r\n\r\n shapes\r\n .transition()\r\n .duration(this.animationDuration)\r\n .style(\"fill\", (d: TreemapNode) => Treemap.getFill(d, isHighlightShape))\r\n .style(\"fill-opacity\", (d: TreemapNode) => Treemap.getFillOpacity(d, hasSelection, hasHighlights, isHighlightShape))\r\n .attr(layout.shapeLayout);\r\n\r\n shapes.exit().remove();\r\n\r\n return shapes;\r\n }\r\n\r\n private animateDefaultHighlightShapes(context: D3.Selection, nodes: D3.Layout.GraphNode[], hasSelection: boolean, hasHighlights: boolean, layout: ITreemapLayout): D3.UpdateSelection {\r\n let isHighlightShape = true;\r\n let highlightShapes = context.selectAll('.' + Treemap.HighlightNodeClassName)\r\n .data(nodes, (d) => d.key + \"highlight\");\r\n\r\n highlightShapes.enter().append('rect')\r\n .attr('class', layout.highlightShapeClass);\r\n\r\n highlightShapes\r\n .transition()\r\n .duration(this.animationDuration)\r\n .style(\"fill\", (d: TreemapNode) => Treemap.getFill(d, isHighlightShape))\r\n .style(\"fill-opacity\", (d: TreemapNode) => Treemap.getFillOpacity(d, hasSelection, hasHighlights, isHighlightShape))\r\n .attr(layout.highlightShapeLayout);\r\n\r\n highlightShapes.exit().remove();\r\n return highlightShapes;\r\n }\r\n\r\n private animateDefaultMajorLabels(context: D3.Selection, nodes: D3.Layout.GraphNode[], labelSettings: VisualDataLabelsSettings, layout: ITreemapLayout): D3.UpdateSelection {\r\n let labels = context\r\n .selectAll('.' + Treemap.MajorLabelClassName)\r\n .data(nodes, (d: TreemapNode) => d.key);\r\n\r\n labels.enter().append('text')\r\n .attr('class', layout.majorLabelClass);\r\n\r\n labels\r\n .text(layout.majorLabelText)\r\n .style('fill', () => labelSettings.labelColor)\r\n .transition()\r\n .duration(this.animationDuration)\r\n .attr(layout.majorLabelLayout);\r\n\r\n labels.exit().remove();\r\n\r\n return labels;\r\n }\r\n\r\n private animateDefaultMinorLabels(context: D3.Selection, nodes: D3.Layout.GraphNode[], labelSettings: VisualDataLabelsSettings, layout: ITreemapLayout): D3.UpdateSelection {\r\n let labels = context\r\n .selectAll('.' + Treemap.MinorLabelClassName)\r\n .data(nodes, (d: TreemapNode) => d.key);\r\n\r\n labels.enter().append('text')\r\n .attr('class', layout.minorLabelClass);\r\n\r\n labels\r\n .text(layout.minorLabelText)\r\n .style('fill', () => labelSettings.labelColor)\r\n .transition()\r\n .duration(this.animationDuration)\r\n .attr(layout.minorLabelLayout);\r\n\r\n labels.exit().remove();\r\n\r\n return labels;\r\n }\r\n }\r\n}\n\n\n/** WEBPACK FOOTER **\n ** ./src/Clients/Visuals/animators/treemapAnimator.ts\n **/","/*\r\n * Power BI Visualizations\r\n *\r\n * Copyright (c) Microsoft Corporation\r\n * All rights reserved. \r\n * MIT License\r\n *\r\n * Permission is hereby granted, free of charge, to any person obtaining a copy\r\n * of this software and associated documentation files (the \"\"Software\"\"), to deal\r\n * in the Software without restriction, including without limitation the rights\r\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\r\n * copies of the Software, and to permit persons to whom the Software is\r\n * furnished to do so, subject to the following conditions:\r\n * \r\n * The above copyright notice and this permission notice shall be included in \r\n * all copies or substantial portions of the Software.\r\n * \r\n * THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR \r\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, \r\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE \r\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER \r\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\r\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\r\n * THE SOFTWARE.\r\n */\r\n\r\n/// <reference path=\"../_references.ts\"/>\r\n\r\nmodule powerbi.visuals {\r\n /**\r\n * This is the baseline for some most common used object properties across visuals.\r\n * When adding new properties, please try to reuse the existing ones.\r\n */\r\n export const StandardObjectProperties = {\r\n axisEnd: {\r\n displayName: data.createDisplayNameGetter('Visual_Axis_End'),\r\n description: data.createDisplayNameGetter('Visual_Axis_EndDescription'),\r\n placeHolderText: data.createDisplayNameGetter('Visual_Precision_Auto'),\r\n type: { numeric: true },\r\n suppressFormatPainterCopy: true,\r\n },\r\n axisScale: {\r\n displayName: data.createDisplayNameGetter('Visual_Axis_Scale'),\r\n type: { enumeration: axisScale.type }\r\n },\r\n axisStart: {\r\n displayName: data.createDisplayNameGetter('Visual_Axis_Start'),\r\n description: data.createDisplayNameGetter('Visual_Axis_StartDescription'),\r\n placeHolderText: data.createDisplayNameGetter('Visual_Precision_Auto'),\r\n type: { numeric: true },\r\n suppressFormatPainterCopy: true,\r\n },\r\n axisStyle: {\r\n displayName: data.createDisplayNameGetter('Visual_Axis_Style'),\r\n type: { enumeration: axisStyle.type }\r\n },\r\n axisType: {\r\n displayName: data.createDisplayNameGetter('Visual_Axis_Type'),\r\n type: { enumeration: axisType.type },\r\n },\r\n backColor: {\r\n displayName: data.createDisplayNameGetter('Visual_Tablix_BackColor'),\r\n description: data.createDisplayNameGetter('Visual_Tablix_BackColor_Desc'),\r\n type: { fill: { solid: { color: true } } }\r\n },\r\n dataColor: {\r\n displayName: data.createDisplayNameGetter('Visual_LabelsFill'),\r\n description: data.createDisplayNameGetter('Visual_LabelsFillDescription'),\r\n type: { fill: { solid: { color: true } } }\r\n },\r\n dataLabelColor: {\r\n displayName: data.createDisplayNameGetter(\"Visual_Reference_Line_Data_Label_Color\"),\r\n description: data.createDisplayNameGetter('Visual_Reference_Line_Data_Label_Color_Description'),\r\n type: { fill: { solid: { color: true } } }\r\n },\r\n dataLabelDecimalPoints: {\r\n displayName: data.createDisplayNameGetter(\"Visual_Reference_Line_Data_Decimal_Points\"),\r\n placeHolderText: data.createDisplayNameGetter('Visual_Precision_Auto'),\r\n type: { numeric: true }\r\n },\r\n dataLabelDisplayUnits: {\r\n displayName: data.createDisplayNameGetter('Visual_DisplayUnits'),\r\n description: data.createDisplayNameGetter('Visual_DisplayUnitsDescription'),\r\n type: { formatting: { labelDisplayUnits: true } },\r\n suppressFormatPainterCopy: true,\r\n },\r\n dataLabelHorizontalPosition: {\r\n displayName: data.createDisplayNameGetter(\"Visual_Reference_Line_Data_Horizontal_Position\"),\r\n description: data.createDisplayNameGetter('Visual_Reference_Line_Data_Label_Horizontal_Position_Description'),\r\n type: { enumeration: referenceLineDataLabelHorizontalPosition.type }\r\n },\r\n dataLabelShow: {\r\n displayName: data.createDisplayNameGetter(\"Visual_Reference_Line_Data_Label\"),\r\n description: data.createDisplayNameGetter('Visual_Reference_Line_Data_Label_Show_Description'),\r\n type: { bool: true }\r\n },\r\n dataLabelVerticalPosition: {\r\n displayName: data.createDisplayNameGetter(\"Visual_Reference_Line_Data_Vertical_Position\"),\r\n description: data.createDisplayNameGetter('Visual_Reference_Line_Data_Label_Vertical_Position_Description'),\r\n type: { enumeration: referenceLineDataLabelVerticalPosition.type }\r\n },\r\n defaultColor: {\r\n displayName: data.createDisplayNameGetter('Visual_DefaultColor'),\r\n type: { fill: { solid: { color: true } } }\r\n },\r\n fill: {\r\n displayName: data.createDisplayNameGetter('Visual_Fill'),\r\n type: { fill: { solid: { color: true } } }\r\n },\r\n fontColor: {\r\n displayName: data.createDisplayNameGetter('Visual_FontColor'),\r\n description: data.createDisplayNameGetter('Visual_Tablix_FontColor_Desc'),\r\n type: { fill: { solid: { color: true } } }\r\n },\r\n fontSize: {\r\n displayName: data.createDisplayNameGetter('Visual_TextSize'),\r\n type: { formatting: { fontSize: true } }\r\n },\r\n formatString: {\r\n type: { formatting: { formatString: true } },\r\n },\r\n image: {\r\n type: { image: {} },\r\n },\r\n labelColor: {\r\n displayName: data.createDisplayNameGetter('Visual_LegendTitleColor'),\r\n type: { fill: { solid: { color: true } } }\r\n },\r\n labelDisplayUnits: {\r\n displayName: data.createDisplayNameGetter('Visual_DisplayUnits'),\r\n description: data.createDisplayNameGetter('Visual_DisplayUnitsDescription'),\r\n type: { formatting: { labelDisplayUnits: true } }\r\n },\r\n labelPrecision: {\r\n displayName: data.createDisplayNameGetter('Visual_Precision'),\r\n description: data.createDisplayNameGetter('Visual_PrecisionDescription'),\r\n placeHolderText: data.createDisplayNameGetter('Visual_Precision_Auto'),\r\n type: { numeric: true }\r\n },\r\n legendPosition: {\r\n displayName: data.createDisplayNameGetter('Visual_LegendPosition'),\r\n description: data.createDisplayNameGetter('Visual_LegendPositionDescription'),\r\n type: { enumeration: legendPosition.type },\r\n },\r\n legendTitle: {\r\n displayName: data.createDisplayNameGetter('Visual_LegendName'),\r\n description: data.createDisplayNameGetter('Visual_LegendNameDescription'),\r\n type: { text: true },\r\n },\r\n lineColor: {\r\n displayName: data.createDisplayNameGetter('Visual_Reference_Line_Color'),\r\n description: data.createDisplayNameGetter('Visual_Reference_Line_Color_Description'),\r\n type: { fill: { solid: { color: true } } }\r\n },\r\n outline: {\r\n displayName: data.createDisplayNameGetter('Visual_Outline'),\r\n type: { enumeration: outline.type }\r\n },\r\n outlineColor: {\r\n displayName: data.createDisplayNameGetter('Visual_OutlineColor'),\r\n description: data.createDisplayNameGetter('Visual_OutlineColor_Desc'),\r\n type: { fill: { solid: { color: true } } }\r\n },\r\n outlineWeight: {\r\n displayName: data.createDisplayNameGetter('Visual_OutlineWeight'),\r\n description: data.createDisplayNameGetter('Visual_OutlineWeight_Desc'),\r\n type: { numeric: true }\r\n },\r\n show: {\r\n displayName: data.createDisplayNameGetter('Visual_Show'),\r\n type: { bool: true }\r\n },\r\n showAllDataPoints: {\r\n displayName: data.createDisplayNameGetter('Visual_DataPoint_Show_All'),\r\n type: { bool: true }\r\n },\r\n showLegendTitle: {\r\n displayName: data.createDisplayNameGetter('Visual_LegendShowTitle'),\r\n description: data.createDisplayNameGetter('Visual_LegendShowTitleDescription'),\r\n type: { bool: true }\r\n },\r\n referenceLinePosition: {\r\n displayName: data.createDisplayNameGetter('Visual_Reference_Line_Arrange'),\r\n description: data.createDisplayNameGetter('Visual_Reference_Line_Arrange_Description'),\r\n type: { enumeration: referenceLinePosition.type }\r\n },\r\n referenceLineStyle: {\r\n displayName: data.createDisplayNameGetter('Visual_Reference_Line_Style'),\r\n description: data.createDisplayNameGetter('Visual_Reference_Line_Style_Description'),\r\n type: { enumeration: lineStyle.type }\r\n },\r\n transparency: {\r\n displayName: data.createDisplayNameGetter('Visual_Background_Transparency'),\r\n description: data.createDisplayNameGetter('Visual_Background_TransparencyDescription'),\r\n type: { numeric: true }\r\n },\r\n yAxisPosition: {\r\n displayName: data.createDisplayNameGetter('Visual_YAxis_Position'),\r\n description: data.createDisplayNameGetter('Visual_YAxis_PositionDescription'),\r\n type: { enumeration: yAxisPosition.type },\r\n },\r\n };\r\n}\n\n\n/** WEBPACK FOOTER **\n ** ./src/Clients/Visuals/capabilities/dataViewObjectProperties.ts\n **/","/*\r\n * Power BI Visualizations\r\n *\r\n * Copyright (c) Microsoft Corporation\r\n * All rights reserved. \r\n * MIT License\r\n *\r\n * Permission is hereby granted, free of charge, to any person obtaining a copy\r\n * of this software and associated documentation files (the \"\"Software\"\"), to deal\r\n * in the Software without restriction, including without limitation the rights\r\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\r\n * copies of the Software, and to permit persons to whom the Software is\r\n * furnished to do so, subject to the following conditions:\r\n * \r\n * The above copyright notice and this permission notice shall be included in \r\n * all copies or substantial portions of the Software.\r\n * \r\n * THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR \r\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, \r\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE \r\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER \r\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\r\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\r\n * THE SOFTWARE.\r\n */\r\n\r\n/// <reference path=\"../_references.ts\"/>\r\n\r\nmodule powerbi.visuals {\r\n export const animatedTextObjectDescs: data.DataViewObjectDescriptors = {\r\n general: {\r\n properties: {\r\n formatString: StandardObjectProperties.formatString,\r\n },\r\n }\r\n };\r\n\r\n export const animatedNumberCapabilities: VisualCapabilities = {\r\n objects: animatedTextObjectDescs,\r\n dataViewMappings: [{\r\n single: { role: \"Values\" }\r\n }],\r\n supportsSelection: false,\r\n };\r\n}\n\n\n/** WEBPACK FOOTER **\n ** ./src/Clients/Visuals/capabilities/animatedNumber.capabilities.ts\n **/","/*\r\n * Power BI Visualizations\r\n *\r\n * Copyright (c) Microsoft Corporation\r\n * All rights reserved. \r\n * MIT License\r\n *\r\n * Permission is hereby granted, free of charge, to any person obtaining a copy\r\n * of this software and associated documentation files (the \"\"Software\"\"), to deal\r\n * in the Software without restriction, including without limitation the rights\r\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\r\n * copies of the Software, and to permit persons to whom the Software is\r\n * furnished to do so, subject to the following conditions:\r\n * \r\n * The above copyright notice and this permission notice shall be included in \r\n * all copies or substantial portions of the Software.\r\n * \r\n * THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR \r\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, \r\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE \r\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER \r\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\r\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\r\n * THE SOFTWARE.\r\n */\r\n\r\n/// <reference path=\"../_references.ts\"/>\r\n\r\nmodule powerbi.visuals {\r\n export const basicShapeCapabilities: VisualCapabilities = {\r\n objects: {\r\n line: {\r\n displayName: data.createDisplayNameGetter('Visual_BasicShape_Line'),\r\n properties: {\r\n lineColor: {\r\n displayName: data.createDisplayNameGetter('Visual_BasicShape_LineColor'),\r\n type: { fill: { solid: { color: true } } }\r\n },\r\n transparency: StandardObjectProperties.transparency,\r\n weight: {\r\n displayName: data.createDisplayNameGetter('Visual_BasicShape_Weight'),\r\n type: { numeric: true }\r\n },\r\n roundEdge: {\r\n displayName: data.createDisplayNameGetter('Visual_BasicShape_RoundEdges'),\r\n type: { numeric: true }\r\n }\r\n }\r\n },\r\n fill: {\r\n displayName: data.createDisplayNameGetter('Visual_Fill'),\r\n properties: {\r\n show: StandardObjectProperties.show,\r\n fillColor: {\r\n displayName: data.createDisplayNameGetter('Visual_BasicShape_FillColor'),\r\n type: { fill: { solid: { color: true } } }\r\n },\r\n transparency: StandardObjectProperties.transparency,\r\n }\r\n },\r\n rotation: {\r\n displayName: data.createDisplayNameGetter('Visual_BasicShape_Rotate'),\r\n properties: {\r\n angle: {\r\n displayName: data.createDisplayNameGetter('Visual_BasicShape_Rotate'),\r\n type: { numeric: true }\r\n }\r\n }\r\n },\r\n general: {\r\n properties: {\r\n shapeType: {\r\n type: { text: true },\r\n suppressFormatPainterCopy: true,\r\n }\r\n }\r\n }\r\n },\r\n suppressDefaultTitle: true,\r\n suppressDefaultPadding: true,\r\n canRotate: false,\r\n supportsSelection: false,\r\n };\r\n\r\n export const basicShapeProps = {\r\n general: {\r\n shapeType: <DataViewObjectPropertyIdentifier>{ objectName: 'general', propertyName: 'shapeType' },\r\n },\r\n line: {\r\n transparency: <DataViewObjectPropertyIdentifier>{ objectName: 'line', propertyName: 'transparency' },\r\n weight: <DataViewObjectPropertyIdentifier>{ objectName: 'line', propertyName: 'weight' },\r\n roundEdge: <DataViewObjectPropertyIdentifier>{ objectName: 'line', propertyName: 'roundEdge' },\r\n lineColor: <DataViewObjectPropertyIdentifier>{ objectName: 'line', propertyName: 'lineColor' }\r\n },\r\n fill: {\r\n transparency: <DataViewObjectPropertyIdentifier>{ objectName: 'fill', propertyName: 'transparency' },\r\n fillColor: <DataViewObjectPropertyIdentifier>{ objectName: 'fill', propertyName: 'fillColor' },\r\n show: <DataViewObjectPropertyIdentifier>{ objectName: 'fill', propertyName: 'show' }\r\n },\r\n rotation: {\r\n angle: <DataViewObjectPropertyIdentifier>{ objectName: 'rotation', propertyName: 'angle' }\r\n }\r\n };\r\n}\n\n\n/** WEBPACK FOOTER **\n ** ./src/Clients/Visuals/capabilities/basicShape.capabilities.ts\n **/","/*\r\n * Power BI Visualizations\r\n *\r\n * Copyright (c) Microsoft Corporation\r\n * All rights reserved. \r\n * MIT License\r\n *\r\n * Permission is hereby granted, free of charge, to any person obtaining a copy\r\n * of this software and associated documentation files (the \"\"Software\"\"), to deal\r\n * in the Software without restriction, including without limitation the rights\r\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\r\n * copies of the Software, and to permit persons to whom the Software is\r\n * furnished to do so, subject to the following conditions:\r\n * \r\n * The above copyright notice and this permission notice shall be included in \r\n * all copies or substantial portions of the Software.\r\n * \r\n * THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR \r\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, \r\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE \r\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER \r\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\r\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\r\n * THE SOFTWARE.\r\n */\r\n\r\n/// <reference path=\"../_references.ts\"/>\r\n\r\nmodule powerbi.visuals {\r\n export function getColumnChartCapabilities(transposeAxes: boolean = false): VisualCapabilities {\r\n return {\r\n dataRoles: [\r\n {\r\n name: 'Category',\r\n kind: VisualDataRoleKind.Grouping,\r\n displayName: data.createDisplayNameGetter('Role_DisplayName_Axis'),\r\n description: data.createDisplayNameGetter('Role_DisplayName_AxisDescription'),\r\n cartesianKind: CartesianRoleKind.X,\r\n }, {\r\n name: 'Series',\r\n kind: VisualDataRoleKind.Grouping,\r\n displayName: data.createDisplayNameGetter('Role_DisplayName_Legend'),\r\n description: data.createDisplayNameGetter('Role_DisplayName_LegendDescription')\r\n }, {\r\n name: 'Y',\r\n kind: VisualDataRoleKind.Measure,\r\n displayName: data.createDisplayNameGetter('Role_DisplayName_Value'),\r\n requiredTypes: [{ numeric: true }, { integer: true }],\r\n cartesianKind: CartesianRoleKind.Y,\r\n }, {\r\n name: 'Gradient',\r\n kind: VisualDataRoleKind.Measure,\r\n displayName: data.createDisplayNameGetter('Role_DisplayName_Gradient'),\r\n description: data.createDisplayNameGetter('Role_DisplayName_GradientDescription'),\r\n requiredTypes: [{ numeric: true }, { integer: true }],\r\n joinPredicate: JoinPredicateBehavior.None,\r\n }\r\n ],\r\n objects: {\r\n general: {\r\n displayName: data.createDisplayNameGetter('Visual_General'),\r\n properties: {\r\n formatString: StandardObjectProperties.formatString,\r\n },\r\n },\r\n legend: {\r\n displayName: data.createDisplayNameGetter('Visual_Legend'),\r\n description: data.createDisplayNameGetter('Visual_LegendDescription'),\r\n properties: {\r\n show: StandardObjectProperties.show,\r\n position: StandardObjectProperties.legendPosition,\r\n showTitle: StandardObjectProperties.showLegendTitle,\r\n titleText: $.extend({}, StandardObjectProperties.legendTitle, {\r\n suppressFormatPainterCopy: true\r\n }),\r\n labelColor: StandardObjectProperties.labelColor,\r\n fontSize: StandardObjectProperties.fontSize,\r\n }\r\n },\r\n categoryAxis: {\r\n displayName: transposeAxes ? data.createDisplayNameGetter('Visual_YAxis') : data.createDisplayNameGetter('Visual_XAxis'),\r\n properties: {\r\n show: StandardObjectProperties.show,\r\n position: StandardObjectProperties.yAxisPosition,\r\n axisScale: StandardObjectProperties.axisScale,\r\n start: StandardObjectProperties.axisStart,\r\n end: StandardObjectProperties.axisEnd,\r\n axisType: StandardObjectProperties.axisType,\r\n showAxisTitle: {\r\n displayName: data.createDisplayNameGetter('Visual_Axis_Title'),\r\n description: transposeAxes ? data.createDisplayNameGetter('Visual_Axis_YTitleDescription') : data.createDisplayNameGetter('Visual_Axis_XTitleDescription'),\r\n type: { bool: true }\r\n },\r\n axisStyle: StandardObjectProperties.axisStyle,\r\n labelColor: StandardObjectProperties.labelColor,\r\n labelDisplayUnits: StandardObjectProperties.labelDisplayUnits,\r\n labelPrecision: StandardObjectProperties.labelPrecision,\r\n }\r\n },\r\n valueAxis: {\r\n displayName: transposeAxes ? data.createDisplayNameGetter('Visual_XAxis') : data.createDisplayNameGetter('Visual_YAxis'),\r\n properties: {\r\n show: StandardObjectProperties.show,\r\n position: StandardObjectProperties.yAxisPosition,\r\n axisScale: StandardObjectProperties.axisScale,\r\n start: StandardObjectProperties.axisStart,\r\n end: StandardObjectProperties.axisEnd,\r\n intersection: {\r\n displayName: data.createDisplayNameGetter('Visual_Axis_Intersection'),\r\n type: { numeric: true },\r\n placeHolderText: data.createDisplayNameGetter('Visual_Precision_Auto'),\r\n },\r\n showAxisTitle: {\r\n displayName: data.createDisplayNameGetter('Visual_Axis_Title'),\r\n description: transposeAxes ? data.createDisplayNameGetter('Visual_Axis_YTitleDescription') : data.createDisplayNameGetter('Visual_Axis_XTitleDescription'),\r\n type: { bool: true }\r\n },\r\n axisStyle: StandardObjectProperties.axisStyle,\r\n labelColor: StandardObjectProperties.labelColor,\r\n labelDisplayUnits: StandardObjectProperties.labelDisplayUnits,\r\n labelPrecision: StandardObjectProperties.labelPrecision,\r\n }\r\n },\r\n y1AxisReferenceLine: {\r\n displayName: data.createDisplayNameGetter('Visual_Reference_Line'),\r\n description: data.createDisplayNameGetter('Visual_Reference_Line_Description'),\r\n properties: {\r\n show: StandardObjectProperties.show,\r\n value: {\r\n displayName: data.createDisplayNameGetter('Visual_Reference_Line_Value'),\r\n description: data.createDisplayNameGetter('Visual_Reference_Line_Value_Description'),\r\n type: { numeric: true }\r\n },\r\n lineColor: StandardObjectProperties.lineColor,\r\n transparency: {\r\n displayName: data.createDisplayNameGetter('Visual_Reference_Line_Transparency'),\r\n description: data.createDisplayNameGetter('Visual_Reference_Line_Transparency_Description'),\r\n type: { numeric: true }\r\n },\r\n style: StandardObjectProperties.referenceLineStyle,\r\n position: StandardObjectProperties.referenceLinePosition,\r\n dataLabelShow: StandardObjectProperties.dataLabelShow,\r\n dataLabelColor: StandardObjectProperties.dataLabelColor,\r\n dataLabelDecimalPoints: StandardObjectProperties.dataLabelDecimalPoints,\r\n dataLabelHorizontalPosition: StandardObjectProperties.dataLabelHorizontalPosition,\r\n dataLabelVerticalPosition: StandardObjectProperties.dataLabelVerticalPosition,\r\n dataLabelDisplayUnits: StandardObjectProperties.dataLabelDisplayUnits,\r\n },\r\n },\r\n dataPoint: {\r\n displayName: data.createDisplayNameGetter('Visual_DataPoint'),\r\n description: data.createDisplayNameGetter('Visual_DataPointDescription'),\r\n properties: {\r\n defaultColor: StandardObjectProperties.defaultColor,\r\n showAllDataPoints: StandardObjectProperties.showAllDataPoints,\r\n fill: StandardObjectProperties.fill,\r\n fillRule: {\r\n displayName: data.createDisplayNameGetter('Visual_Gradient'),\r\n type: { fillRule: {} },\r\n rule: {\r\n inputRole: 'Gradient',\r\n output: {\r\n property: 'fill',\r\n selector: ['Category'],\r\n },\r\n },\r\n }\r\n }\r\n },\r\n trend: {\r\n displayName: data.createDisplayNameGetter('Visual_Trend_Line'),\r\n properties: {\r\n show: {\r\n type: { bool: true }\r\n },\r\n lineColor: {\r\n displayName: data.createDisplayNameGetter('Visual_Trend_Line_Color'),\r\n description: data.createDisplayNameGetter('Visual_Trend_Line_Color_Description'),\r\n type: { fill: { solid: { color: true } } }\r\n },\r\n transparency: {\r\n displayName: data.createDisplayNameGetter('Visual_Trend_Line_Transparency'),\r\n description: data.createDisplayNameGetter('Visual_Trend_Line_Transparency_Description'),\r\n type: { numeric: true }\r\n },\r\n style: {\r\n displayName: data.createDisplayNameGetter('Visual_Trend_Line_Style'),\r\n description: data.createDisplayNameGetter('Visual_Trend_Line_Style_Description'),\r\n type: { enumeration: lineStyle.type }\r\n },\r\n combineSeries: {\r\n displayName: data.createDisplayNameGetter('Visual_Trend_Line_Combine_Series'),\r\n description: data.createDisplayNameGetter('Visual_Trend_Line_Combine_Series_Description'),\r\n type: { bool: true }\r\n },\r\n useHighlightValues: {\r\n displayName: data.createDisplayNameGetter('Visual_Trend_Line_UseHighlightValues'),\r\n description: data.createDisplayNameGetter('Visual_Trend_Line_UseHighlightValues_Description'),\r\n type: { bool: true }\r\n },\r\n }\r\n },\r\n labels: {\r\n displayName: data.createDisplayNameGetter('Visual_DataPointsLabels'),\r\n description: data.createDisplayNameGetter('Visual_DataPointsLabelsDescription'),\r\n properties: {\r\n show: StandardObjectProperties.show,\r\n showSeries: {\r\n displayName: data.createDisplayNameGetter('Visual_Show'),\r\n type: { bool: true }\r\n },\r\n color: StandardObjectProperties.dataColor,\r\n labelDisplayUnits: StandardObjectProperties.dataLabelDisplayUnits,\r\n labelPrecision: $.extend({}, StandardObjectProperties.labelPrecision, {\r\n suppressFormatPainterCopy: true\r\n }),\r\n showAll: {\r\n displayName: data.createDisplayNameGetter('Visual_LabelSeriesShowAll'), \r\n type: { bool: true } \r\n },\r\n fontSize: StandardObjectProperties.fontSize,\r\n },\r\n },\r\n plotArea: {\r\n displayName: data.createDisplayNameGetter('Visual_Plot'),\r\n //description: data.createDisplayNameGetter('Visual_PlotDescription'),\r\n properties: {\r\n transparency: StandardObjectProperties.transparency,\r\n image: StandardObjectProperties.image,\r\n },\r\n },\r\n },\r\n dataViewMappings: [{\r\n conditions: [\r\n { 'Category': { max: 1 }, 'Series': { max: 0 }, 'Gradient': { max: 0 } },\r\n { 'Category': { max: 1 }, 'Series': { min: 1, max: 1 }, 'Y': { max: 1 }, 'Gradient': { max: 0 } },\r\n { 'Category': { max: 1 }, 'Series': { max: 0 }, 'Y': { min: 0, max: 1 }, 'Gradient': { max: 1 } },\r\n ],\r\n categorical: {\r\n categories: {\r\n for: { in: 'Category' },\r\n dataReductionAlgorithm: { top: {} }\r\n },\r\n values: {\r\n group: {\r\n by: 'Series',\r\n select: [{ for: { in: 'Y' } }, { bind: { to: 'Gradient' } }],\r\n dataReductionAlgorithm: { top: {} }\r\n }\r\n },\r\n rowCount: { preferred: { min: 2 }, supported: { min: 0 } }\r\n },\r\n }, {\r\n conditions: [\r\n { 'Category': { max: 1 }, 'Series': { max: 0 }, 'Gradient': { max: 0 } },\r\n { 'Category': { max: 1 }, 'Series': { min: 1, max: 1 }, 'Y': { max: 1 }, 'Gradient': { max: 0 } },\r\n { 'Category': { max: 1 }, 'Series': { max: 0 }, 'Y': { min: 0, max: 1 }, 'Gradient': { max: 1 } },\r\n ],\r\n requiredProperties: [{ objectName: 'trend', propertyName: 'show' }],\r\n usage: {\r\n regression: {\r\n combineSeries: { objectName: 'trend', propertyName: 'combineSeries' }\r\n },\r\n },\r\n categorical: {\r\n categories: {\r\n for: { in: 'regression.X' },\r\n },\r\n values: {\r\n group: {\r\n by: 'regression.Series',\r\n select: [{ for: { in: 'regression.Y' } }],\r\n },\r\n }\r\n }\r\n }],\r\n supportsHighlight: true,\r\n sorting: {\r\n default: {},\r\n },\r\n drilldown: {\r\n roles: ['Category']\r\n },\r\n };\r\n }\r\n\r\n export const columnChartProps = {\r\n dataPoint: {\r\n defaultColor: <DataViewObjectPropertyIdentifier>{ objectName: 'dataPoint', propertyName: 'defaultColor' },\r\n fill: <DataViewObjectPropertyIdentifier>{ objectName: 'dataPoint', propertyName: 'fill' },\r\n showAllDataPoints: <DataViewObjectPropertyIdentifier>{ objectName: 'dataPoint', propertyName: 'showAllDataPoints' },\r\n },\r\n general: {\r\n formatString: <DataViewObjectPropertyIdentifier>{ objectName: 'general', propertyName: 'formatString' },\r\n },\r\n categoryAxis: {\r\n axisType: <DataViewObjectPropertyIdentifier>{ objectName: 'categoryAxis', propertyName: 'axisType' },\r\n },\r\n legend: {\r\n labelColor: <DataViewObjectPropertyIdentifier>{ objectName: 'legend', propertyName: 'labelColor' },\r\n },\r\n plotArea: {\r\n image: <DataViewObjectPropertyIdentifier>{ objectName: 'plotArea', propertyName: 'image' },\r\n transparency: <DataViewObjectPropertyIdentifier>{ objectName: 'plotArea', propertyName: 'transparency' },\r\n },\r\n };\r\n}\n\n\n/** WEBPACK FOOTER **\n ** ./src/Clients/Visuals/capabilities/columnChart.capabilities.ts\n **/","/*\r\n * Power BI Visualizations\r\n *\r\n * Copyright (c) Microsoft Corporation\r\n * All rights reserved. \r\n * MIT License\r\n *\r\n * Permission is hereby granted, free of charge, to any person obtaining a copy\r\n * of this software and associated documentation files (the \"\"Software\"\"), to deal\r\n * in the Software without restriction, including without limitation the rights\r\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\r\n * copies of the Software, and to permit persons to whom the Software is\r\n * furnished to do so, subject to the following conditions:\r\n * \r\n * The above copyright notice and this permission notice shall be included in \r\n * all copies or substantial portions of the Software.\r\n * \r\n * THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR \r\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, \r\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE \r\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER \r\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\r\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\r\n * THE SOFTWARE.\r\n */\r\n\r\n/// <reference path=\"../_references.ts\"/>\r\n\r\nmodule powerbi.visuals {\r\n export const comboChartCapabilities: VisualCapabilities = {\r\n dataRoles: [\r\n {\r\n name: 'Category',\r\n kind: VisualDataRoleKind.Grouping,\r\n displayName: data.createDisplayNameGetter('Role_ComboChart_Category'),\r\n description: data.createDisplayNameGetter('Role_ComboChart_CategoryDescription'),\r\n cartesianKind: CartesianRoleKind.X,\r\n }, {\r\n name: 'Series',\r\n kind: VisualDataRoleKind.Grouping,\r\n displayName: data.createDisplayNameGetter('Role_ComboChart_Series'),\r\n }, {\r\n name: 'Y',\r\n kind: VisualDataRoleKind.Measure,\r\n displayName: data.createDisplayNameGetter('Role_ComboChart_Y'),\r\n description: data.createDisplayNameGetter('Role_ComboChart_YDescription'),\r\n requiredTypes: [{ numeric: true }, { integer: true }],\r\n cartesianKind: CartesianRoleKind.Y,\r\n }, {\r\n name: 'Y2',\r\n kind: VisualDataRoleKind.Measure,\r\n displayName: data.createDisplayNameGetter('Role_ComboChart_Y2'),\r\n description: data.createDisplayNameGetter('Role_ComboChart_Y2Description'),\r\n requiredTypes: [{ numeric: true }, { integer: true }],\r\n cartesianKind: CartesianRoleKind.Y,\r\n },\r\n ],\r\n objects: {\r\n general: {\r\n properties: {\r\n formatString: StandardObjectProperties.formatString,\r\n visualType1: {\r\n type: { text: true }\r\n },\r\n visualType2: {\r\n type: { text: true }\r\n },\r\n },\r\n },\r\n legend: {\r\n displayName: data.createDisplayNameGetter('Visual_Legend'),\r\n description: data.createDisplayNameGetter('Visual_LegendDescription'),\r\n properties: {\r\n show: StandardObjectProperties.show,\r\n position: StandardObjectProperties.legendPosition,\r\n showTitle: StandardObjectProperties.showLegendTitle,\r\n titleText: $.extend({}, StandardObjectProperties.legendTitle, {\r\n suppressFormatPainterCopy: true\r\n }),\r\n labelColor: StandardObjectProperties.labelColor,\r\n fontSize: StandardObjectProperties.fontSize,\r\n }\r\n },\r\n categoryAxis: {\r\n displayName: data.createDisplayNameGetter('Visual_XAxis'),\r\n properties: {\r\n show: StandardObjectProperties.show,\r\n axisScale: StandardObjectProperties.axisScale,\r\n start: StandardObjectProperties.axisStart,\r\n end: StandardObjectProperties.axisEnd,\r\n axisType: StandardObjectProperties.axisType,\r\n showAxisTitle: {\r\n displayName: data.createDisplayNameGetter('Visual_Axis_Title'),\r\n description: data.createDisplayNameGetter('Visual_Axis_XTitleDescription'),\r\n type: { bool: true }\r\n },\r\n axisStyle: StandardObjectProperties.axisStyle,\r\n labelDisplayUnits: StandardObjectProperties.labelDisplayUnits,\r\n labelPrecision: StandardObjectProperties.labelPrecision,\r\n }\r\n },\r\n valueAxis: {\r\n displayName: data.createDisplayNameGetter('Visual_YAxis'),\r\n properties: {\r\n show: StandardObjectProperties.show,\r\n axisLabel: {\r\n displayName: data.createDisplayNameGetter('Visual_YAxis_ColumnTitle'),\r\n type: { none: true },\r\n },\r\n position: StandardObjectProperties.yAxisPosition,\r\n axisScale: StandardObjectProperties.axisScale,\r\n start: StandardObjectProperties.axisStart,\r\n end: StandardObjectProperties.axisEnd,\r\n showAxisTitle: {\r\n displayName: data.createDisplayNameGetter('Visual_Axis_Title'),\r\n description: data.createDisplayNameGetter('Visual_Axis_YTitleDescription'),\r\n type: { bool: true }\r\n },\r\n axisStyle: StandardObjectProperties.axisStyle,\r\n labelDisplayUnits: StandardObjectProperties.labelDisplayUnits,\r\n labelPrecision: StandardObjectProperties.labelPrecision,\r\n secShow: {\r\n displayName: data.createDisplayNameGetter('Visual_YAxis_ShowSecondary'),\r\n type: { bool: true },\r\n },\r\n secAxisLabel: {\r\n displayName: data.createDisplayNameGetter('Visual_YAxis_LineTitle'),\r\n type: { none: true },\r\n },\r\n secPosition: {\r\n displayName: data.createDisplayNameGetter('Visual_YAxis_Position'),\r\n type: { enumeration: yAxisPosition.type },\r\n },\r\n secAxisScale: StandardObjectProperties.axisScale,\r\n secStart: {\r\n displayName: data.createDisplayNameGetter('Visual_Axis_Start'),\r\n description: data.createDisplayNameGetter('Visual_Axis_StartDescription'),\r\n type: { numeric: true },\r\n },\r\n secEnd: {\r\n displayName: data.createDisplayNameGetter('Visual_Axis_End'),\r\n description: data.createDisplayNameGetter('Visual_Axis_EndDescription'),\r\n type: { numeric: true },\r\n },\r\n secShowAxisTitle: {\r\n displayName: data.createDisplayNameGetter('Visual_Axis_Title'),\r\n description: data.createDisplayNameGetter('Visual_Axis_YTitleDescription'),\r\n type: { bool: true },\r\n },\r\n secAxisStyle: StandardObjectProperties.axisStyle,\r\n secLabelDisplayUnits: StandardObjectProperties.labelDisplayUnits,\r\n secLabelPrecision: StandardObjectProperties.labelPrecision,\r\n }\r\n },\r\n dataPoint: {\r\n displayName: data.createDisplayNameGetter('Visual_DataPoint'),\r\n description: data.createDisplayNameGetter('Visual_DataPointDescription'),\r\n properties: {\r\n defaultColor: $.extend({}, StandardObjectProperties.defaultColor, {\r\n displayName: data.createDisplayNameGetter('Visual_DefaultColumnColor'),\r\n }),\r\n showAllDataPoints: StandardObjectProperties.showAllDataPoints,\r\n fill: StandardObjectProperties.fill,\r\n fillRule: {\r\n displayName: data.createDisplayNameGetter('Visual_Gradient'),\r\n type: { fillRule: {} },\r\n rule: {\r\n inputRole: 'Gradient',\r\n output: {\r\n property: 'fill',\r\n selector: ['Category'],\r\n },\r\n },\r\n }\r\n }\r\n },\r\n labels: {\r\n displayName: data.createDisplayNameGetter('Visual_DataPointsLabels'),\r\n description: data.createDisplayNameGetter('Visual_DataPointsLabelsDescription'),\r\n properties: {\r\n show: StandardObjectProperties.show,\r\n color: StandardObjectProperties.dataColor,\r\n labelDisplayUnits: StandardObjectProperties.dataLabelDisplayUnits,\r\n labelPrecision: $.extend({}, StandardObjectProperties.labelPrecision, {\r\n suppressFormatPainterCopy: true,\r\n }),\r\n fontSize: StandardObjectProperties.fontSize,\r\n labelDensity: {\r\n displayName: data.createDisplayNameGetter('Visual_LabelDensity'),\r\n type: { formatting: { labelDensity: true } },\r\n },\r\n },\r\n },\r\n plotArea: {\r\n displayName: data.createDisplayNameGetter('Visual_Plot'),\r\n properties: {\r\n transparency: StandardObjectProperties.transparency,\r\n image: StandardObjectProperties.image,\r\n },\r\n },\r\n trend: {\r\n displayName: data.createDisplayNameGetter('Visual_Trend_Line'),\r\n properties: {\r\n show: {\r\n type: { bool: true }\r\n },\r\n lineColor: {\r\n displayName: data.createDisplayNameGetter('Visual_Trend_Line_Color'),\r\n description: data.createDisplayNameGetter('Visual_Trend_Line_Color_Description'),\r\n type: { fill: { solid: { color: true } } }\r\n },\r\n transparency: {\r\n displayName: data.createDisplayNameGetter('Visual_Trend_Line_Transparency'),\r\n description: data.createDisplayNameGetter('Visual_Trend_Line_Transparency_Description'),\r\n type: { numeric: true }\r\n },\r\n style: {\r\n displayName: data.createDisplayNameGetter('Visual_Trend_Line_Style'),\r\n description: data.createDisplayNameGetter('Visual_Trend_Line_Style_Description'),\r\n type: { enumeration: lineStyle.type }\r\n },\r\n combineSeries: {\r\n displayName: data.createDisplayNameGetter('Visual_Trend_Line_Combine_Series'),\r\n description: data.createDisplayNameGetter('Visual_Trend_Line_Combine_Series_Description'),\r\n type: { bool: true }\r\n },\r\n useHighlightValues: {\r\n displayName: data.createDisplayNameGetter('Visual_Trend_Line_UseHighlightValues'),\r\n description: data.createDisplayNameGetter('Visual_Trend_Line_UseHighlightValues_Description'),\r\n type: { bool: true }\r\n },\r\n }\r\n },\r\n },\r\n dataViewMappings: [\r\n {\r\n conditions: [\r\n { 'Category': { max: 1 }, 'Series': { max: 0 } },\r\n { 'Category': { max: 1 }, 'Series': { min: 1, max: 1 }, 'Y': { max: 1 } },\r\n ],\r\n categorical: {\r\n categories: {\r\n for: { in: 'Category' },\r\n dataReductionAlgorithm: { top: {} }\r\n },\r\n values: {\r\n group: {\r\n by: 'Series',\r\n select: [\r\n { for: { in: 'Y' } }\r\n ],\r\n dataReductionAlgorithm: { top: {} }\r\n }\r\n },\r\n rowCount: { preferred: { min: 2 }, supported: { min: 0 } }\r\n }\r\n }, {\r\n conditions: [\r\n { 'Category': { max: 1 }, 'Series': { max: 0 }, 'Y2': { min: 1 } },\r\n { 'Category': { max: 1 }, 'Series': { min: 1, max: 1 }, 'Y': { max: 1 }, 'Y2': { min: 1 } },\r\n ],\r\n categorical: {\r\n categories: {\r\n for: { in: 'Category' },\r\n dataReductionAlgorithm: { top: {} }\r\n },\r\n values: {\r\n select: [\r\n { for: { in: 'Y2' } }\r\n ],\r\n },\r\n rowCount: { preferred: { min: 2 }, supported: { min: 0 } }\r\n },\r\n }, {\r\n conditions: [\r\n { 'Category': { max: 1 }, 'Series': { max: 0 } },\r\n { 'Category': { max: 1 }, 'Series': { min: 1, max: 1 }, 'Y': { max: 1 } },\r\n ],\r\n requiredProperties: [{ objectName: 'trend', propertyName: 'show' }],\r\n usage: {\r\n regression: {\r\n combineSeries: { objectName: 'trend', propertyName: 'combineSeries' }\r\n },\r\n },\r\n categorical: {\r\n categories: {\r\n for: { in: 'regression.X' }\r\n },\r\n values: {\r\n group: {\r\n by: 'regression.Series',\r\n select: [{ for: { in: 'regression.Y' } }],\r\n },\r\n },\r\n }\r\n },\r\n ],\r\n supportsHighlight: true,\r\n sorting: {\r\n default: {},\r\n },\r\n drilldown: {\r\n roles: ['Category']\r\n },\r\n };\r\n\r\n export const comboChartProps = {\r\n general: {\r\n formatString: <DataViewObjectPropertyIdentifier>{ objectName: 'general', propertyName: 'formatString' },\r\n },\r\n valueAxis: {\r\n secShow: <DataViewObjectPropertyIdentifier>{ objectName: 'valueAxis', propertyName: 'secShow' },\r\n },\r\n legend: {\r\n labelColor: <DataViewObjectPropertyIdentifier>{ objectName: 'legend', propertyName: 'labelColor' },\r\n },\r\n dataPoint: {\r\n showAllDataPoints: <DataViewObjectPropertyIdentifier>{ objectName: 'dataPoint', propertyName: 'showAllDataPoints' },\r\n },\r\n };\r\n}\n\n\n/** WEBPACK FOOTER **\n ** ./src/Clients/Visuals/capabilities/comboChart.capabilities.ts\n **/","/*\r\n * Power BI Visualizations\r\n *\r\n * Copyright (c) Microsoft Corporation\r\n * All rights reserved. \r\n * MIT License\r\n *\r\n * Permission is hereby granted, free of charge, to any person obtaining a copy\r\n * of this software and associated documentation files (the \"\"Software\"\"), to deal\r\n * in the Software without restriction, including without limitation the rights\r\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\r\n * copies of the Software, and to permit persons to whom the Software is\r\n * furnished to do so, subject to the following conditions:\r\n * \r\n * The above copyright notice and this permission notice shall be included in \r\n * all copies or substantial portions of the Software.\r\n * \r\n * THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR \r\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, \r\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE \r\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER \r\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\r\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\r\n * THE SOFTWARE.\r\n */\r\n\r\n/// <reference path=\"../_references.ts\"/>\r\n\r\nmodule powerbi.visuals {\r\n export const donutChartCapabilities: VisualCapabilities = {\r\n dataRoles: [\r\n {\r\n name: 'Category',\r\n kind: VisualDataRoleKind.Grouping,\r\n displayName: data.createDisplayNameGetter('Role_DisplayName_Legend'),\r\n description: data.createDisplayNameGetter('Role_DisplayName_LegendDescription')\r\n }, {\r\n name: 'Series',\r\n kind: VisualDataRoleKind.Grouping,\r\n displayName: data.createDisplayNameGetter('Role_DisplayName_Details'),\r\n description: data.createDisplayNameGetter('Role_DisplayName_DetailsDonutChartDescription'),\r\n\r\n }, {\r\n name: 'Y',\r\n kind: VisualDataRoleKind.Measure,\r\n displayName: data.createDisplayNameGetter('Role_DisplayName_Values'),\r\n description: data.createDisplayNameGetter('Role_DisplayName_ValuesDescription'),\r\n requiredTypes: [{ numeric: true }, { integer: true }],\r\n }\r\n ],\r\n objects:{ \r\n general: {\r\n displayName: data.createDisplayNameGetter('Visual_General'),\r\n properties: {\r\n formatString: StandardObjectProperties.formatString,\r\n },\r\n },\r\n legend: {\r\n displayName: data.createDisplayNameGetter('Visual_Legend'),\r\n description: data.createDisplayNameGetter('Visual_LegendDescription'),\r\n properties: {\r\n show: StandardObjectProperties.show,\r\n position: StandardObjectProperties.legendPosition,\r\n showTitle: StandardObjectProperties.showLegendTitle,\r\n titleText: $.extend({}, StandardObjectProperties.legendTitle, {\r\n suppressFormatPainterCopy: true\r\n }),\r\n labelColor: StandardObjectProperties.labelColor,\r\n fontSize: StandardObjectProperties.fontSize,\r\n }\r\n },\r\n dataPoint: {\r\n displayName: data.createDisplayNameGetter('Visual_DataPoint'),\r\n description: data.createDisplayNameGetter('Visual_DataPointDescription'),\r\n properties: {\r\n defaultColor: StandardObjectProperties.defaultColor,\r\n fill: StandardObjectProperties.fill,\r\n }\r\n },\r\n labels: {\r\n displayName: data.createDisplayNameGetter('Visual_DetailLabels'),\r\n properties: {\r\n show: StandardObjectProperties.show,\r\n color: StandardObjectProperties.dataColor,\r\n labelDisplayUnits: StandardObjectProperties.dataLabelDisplayUnits,\r\n labelPrecision: $.extend({}, StandardObjectProperties.labelPrecision, {\r\n suppressFormatPainterCopy: true,\r\n }),\r\n fontSize: $.extend({}, StandardObjectProperties.fontSize, {\r\n suppressFormatPainterCopy: true,\r\n }),\r\n labelStyle: {\r\n displayName: data.createDisplayNameGetter('Visual_LabelStyle'),\r\n type: { enumeration: labelStyle.type }\r\n },\r\n },\r\n },\r\n },\r\n dataViewMappings: [{\r\n conditions: [\r\n { 'Category': { max: 1 }, 'Series': { max: 0 } },\r\n { 'Category': { max: 1 }, 'Series': { min: 1, max: 1 }, 'Y': { max: 1 } }\r\n ],\r\n categorical: {\r\n categories: {\r\n for: { in: 'Category' },\r\n dataReductionAlgorithm: { top: {} }\r\n },\r\n values: {\r\n group: {\r\n by: 'Series',\r\n select: [{ bind: { to: 'Y' } }],\r\n dataReductionAlgorithm: { top: {} }\r\n }\r\n },\r\n rowCount: { preferred: { min: 2 }, supported: { min: 1 } }\r\n },\r\n }],\r\n sorting: {\r\n default: {},\r\n },\r\n supportsHighlight: true,\r\n drilldown: {\r\n roles: ['Category']\r\n },\r\n };\r\n\r\n export const donutChartProps = {\r\n general: {\r\n formatString: <DataViewObjectPropertyIdentifier>{ objectName: 'general', propertyName: 'formatString' },\r\n },\r\n dataPoint: {\r\n defaultColor: <DataViewObjectPropertyIdentifier>{ objectName: 'dataPoint', propertyName: 'defaultColor' },\r\n fill: <DataViewObjectPropertyIdentifier>{ objectName: 'dataPoint', propertyName: 'fill' },\r\n },\r\n legend: {\r\n show: <DataViewObjectPropertyIdentifier>{ objectName: 'legend', propertyName: 'show' },\r\n position: <DataViewObjectPropertyIdentifier>{ objectName: 'legend', propertyName: 'position' },\r\n showTitle: <DataViewObjectPropertyIdentifier>{ objectName: 'legend', propertyName: 'showTitle' },\r\n titleText: <DataViewObjectPropertyIdentifier>{ objectName: 'legend', propertyName: 'titleText' },\r\n labelColor: <DataViewObjectPropertyIdentifier>{ objectName: 'legend', propertyName: 'labelColor' },\r\n }, \r\n };\r\n}\n\n\n/** WEBPACK FOOTER **\n ** ./src/Clients/Visuals/capabilities/donutChart.capabilities.ts\n **/","/*\r\n * Power BI Visualizations\r\n *\r\n * Copyright (c) Microsoft Corporation\r\n * All rights reserved. \r\n * MIT License\r\n *\r\n * Permission is hereby granted, free of charge, to any person obtaining a copy\r\n * of this software and associated documentation files (the \"\"Software\"\"), to deal\r\n * in the Software without restriction, including without limitation the rights\r\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\r\n * copies of the Software, and to permit persons to whom the Software is\r\n * furnished to do so, subject to the following conditions:\r\n * \r\n * The above copyright notice and this permission notice shall be included in \r\n * all copies or substantial portions of the Software.\r\n * \r\n * THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR \r\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, \r\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE \r\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER \r\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\r\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\r\n * THE SOFTWARE.\r\n */\r\n\r\n/// <reference path=\"../_references.ts\"/>\r\n\r\nmodule powerbi.visuals {\r\n // I support a categorical (ordinal) X with measure Y for a single series\r\n export const dataDotChartCapabilities: VisualCapabilities = {\r\n dataRoles: [\r\n {\r\n name: 'Category',\r\n kind: VisualDataRoleKind.Grouping,\r\n displayName: data.createDisplayNameGetter('Role_DisplayName_Axis'),\r\n description: data.createDisplayNameGetter('Role_DisplayName_AxisDescription')\r\n }, {\r\n name: 'Y',\r\n kind: VisualDataRoleKind.Measure,\r\n displayName: data.createDisplayNameGetter('Role_DisplayName_Value'),\r\n requiredTypes: [{ numeric: true }, { integer: true }]\r\n },\r\n ],\r\n objects: {\r\n general: {\r\n displayName: data.createDisplayNameGetter('Visual_General'),\r\n properties: {\r\n formatString: StandardObjectProperties.formatString,\r\n },\r\n },\r\n },\r\n dataViewMappings: [{\r\n conditions: [\r\n { 'Category': { max: 1 }, 'Y': { max: 1 } }\r\n ],\r\n categorical: {\r\n categories: {\r\n for: { in: 'Category' },\r\n dataReductionAlgorithm: { top: {} }\r\n },\r\n values: {\r\n select: [{\r\n for: { in: 'Y' },\r\n }]\r\n },\r\n },\r\n }]\r\n };\r\n} \n\n\n/** WEBPACK FOOTER **\n ** ./src/Clients/Visuals/capabilities/dataDotChart.capabilities.ts\n **/","/*\r\n * Power BI Visualizations\r\n *\r\n * Copyright (c) Microsoft Corporation\r\n * All rights reserved. \r\n * MIT License\r\n *\r\n * Permission is hereby granted, free of charge, to any person obtaining a copy\r\n * of this software and associated documentation files (the \"\"Software\"\"), to deal\r\n * in the Software without restriction, including without limitation the rights\r\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\r\n * copies of the Software, and to permit persons to whom the Software is\r\n * furnished to do so, subject to the following conditions:\r\n * \r\n * The above copyright notice and this permission notice shall be included in \r\n * all copies or substantial portions of the Software.\r\n * \r\n * THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR \r\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, \r\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE \r\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER \r\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\r\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\r\n * THE SOFTWARE.\r\n */\r\n\r\n/// <reference path=\"../_references.ts\"/>\r\n\r\nmodule powerbi.visuals {\r\n export const filledMapCapabilities: VisualCapabilities = {\r\n dataRoles: [\r\n {\r\n name: 'Category',\r\n kind: VisualDataRoleKind.Grouping,\r\n displayName: data.createDisplayNameGetter('Role_DisplayName_Location'),\r\n description: data.createDisplayNameGetter('Role_DisplayName_LocationFilledMapDescription'),\r\n preferredTypes: [\r\n { geography: { address: true } },\r\n { geography: { city: true } },\r\n { geography: { continent: true } },\r\n { geography: { country: true } },\r\n { geography: { county: true } },\r\n { geography: { place: true } },\r\n { geography: { postalCode: true } },\r\n { geography: { region: true } },\r\n { geography: { stateOrProvince: true } },\r\n ],\r\n }, {\r\n name: 'Series',\r\n kind: VisualDataRoleKind.Grouping,\r\n displayName: data.createDisplayNameGetter('Role_DisplayName_Legend'),\r\n description: data.createDisplayNameGetter('Role_DisplayName_LegendDescription')\r\n }, {\r\n name: 'Y',\r\n kind: VisualDataRoleKind.Measure,\r\n displayName: data.createDisplayNameGetter('Role_DisplayName_Latitude'),\r\n description: data.createDisplayNameGetter('Role_DisplayName_LatitudeFilledMapDescription'),\r\n preferredTypes: [\r\n { geography: { latitude: true } }\r\n ],\r\n }, {\r\n name: 'X',\r\n kind: VisualDataRoleKind.Measure,\r\n displayName: data.createDisplayNameGetter('Role_DisplayName_Longitude'),\r\n description: data.createDisplayNameGetter('Role_DisplayName_LongitudeFilledMapDescription'),\r\n preferredTypes: [\r\n { geography: { longitude: true } }\r\n ],\r\n }, {\r\n name: 'Size',\r\n kind: VisualDataRoleKind.Measure,\r\n displayName: data.createDisplayNameGetter('Role_DisplayName_Gradient'),\r\n description: data.createDisplayNameGetter('Role_DisplayName_GradientDescription'),\r\n requiredTypes: [{ numeric: true }, { integer: true }],\r\n }\r\n ],\r\n objects: {\r\n general: {\r\n displayName: data.createDisplayNameGetter('Visual_General'),\r\n properties: {\r\n formatString: StandardObjectProperties.formatString,\r\n },\r\n },\r\n legend: {\r\n displayName: data.createDisplayNameGetter('Visual_Legend'),\r\n description: data.createDisplayNameGetter('Visual_LegendDescription'),\r\n properties: {\r\n show: StandardObjectProperties.show,\r\n position: StandardObjectProperties.legendPosition,\r\n showTitle: StandardObjectProperties.showLegendTitle,\r\n titleText: StandardObjectProperties.legendTitle,\r\n fontSize: StandardObjectProperties.fontSize,\r\n }\r\n },\r\n dataPoint: {\r\n displayName: data.createDisplayNameGetter('Visual_DataPoint'),\r\n description: data.createDisplayNameGetter('Visual_DataPointDescription'),\r\n properties: {\r\n defaultColor: StandardObjectProperties.defaultColor,\r\n showAllDataPoints: StandardObjectProperties.showAllDataPoints,\r\n fill: StandardObjectProperties.fill,\r\n fillRule: {\r\n displayName: data.createDisplayNameGetter('Role_DisplayName_Values'),\r\n description: data.createDisplayNameGetter('Role_DisplayName_ValuesDescription'),\r\n type: { fillRule: {} },\r\n rule: {\r\n inputRole: 'Size',\r\n output: {\r\n property: 'fill',\r\n selector: ['Category'],\r\n },\r\n },\r\n }\r\n }\r\n },\r\n labels: {\r\n displayName: data.createDisplayNameGetter('Visual_DataPointsLabels'),\r\n properties: {\r\n show: StandardObjectProperties.show,\r\n color: StandardObjectProperties.dataColor,\r\n labelDisplayUnits: StandardObjectProperties.labelDisplayUnits,\r\n labelPrecision: StandardObjectProperties.labelPrecision,\r\n },\r\n },\r\n categoryLabels: {\r\n displayName: data.createDisplayNameGetter('Visual_CategoryLabels'),\r\n properties: {\r\n show: StandardObjectProperties.show,\r\n },\r\n }\r\n },\r\n dataViewMappings: [{\r\n conditions: [\r\n { 'Category': { max: 1 }, 'Series': { max: 1 }, 'X': { max: 1 }, 'Y': { max: 1 }, 'Size': { max: 1 } },\r\n { 'Category': { max: 1 }, 'Series': { max: 0 }, 'X': { max: 1 }, 'Y': { max: 1 }, 'Size': { max: 1 } }\r\n ],\r\n categorical: {\r\n categories: {\r\n for: { in: 'Category' },\r\n dataReductionAlgorithm: { top: {} }\r\n },\r\n values: {\r\n group: {\r\n by: 'Series',\r\n select: [\r\n { bind: { to: 'X' } },\r\n { bind: { to: 'Y' } },\r\n { bind: { to: 'Size' } },\r\n ],\r\n dataReductionAlgorithm: { top: {} }\r\n }\r\n },\r\n rowCount: { preferred: { min: 2 } },\r\n dataVolume: 4,\r\n },\r\n }],\r\n sorting: {\r\n custom: {},\r\n implicit: {\r\n clauses: [{ role: 'Size', direction: SortDirection.Descending }]\r\n },\r\n },\r\n drilldown: {\r\n roles: ['Category']\r\n },\r\n };\r\n\r\n export const filledMapProps = {\r\n general: {\r\n formatString: <DataViewObjectPropertyIdentifier>{ objectName: 'general', propertyName: 'formatString' },\r\n },\r\n dataPoint: {\r\n defaultColor: <DataViewObjectPropertyIdentifier>{ objectName: 'dataPoint', propertyName: 'defaultColor' },\r\n fill: <DataViewObjectPropertyIdentifier>{ objectName: 'dataPoint', propertyName: 'fill' },\r\n showAllDataPoints: <DataViewObjectPropertyIdentifier>{ objectName: 'dataPoint', propertyName: 'showAllDataPoints' },\r\n },\r\n legend: {\r\n show: <DataViewObjectPropertyIdentifier>{ objectName: 'legend', propertyName: 'show' },\r\n position: <DataViewObjectPropertyIdentifier>{ objectName: 'legend', propertyName: 'position' },\r\n showTitle: <DataViewObjectPropertyIdentifier>{ objectName: 'legend', propertyName: 'showTitle' },\r\n titleText: <DataViewObjectPropertyIdentifier>{ objectName: 'legend', propertyName: 'titleText' },\r\n },\r\n labels: {\r\n show: <DataViewObjectPropertyIdentifier>{ objectName: 'labels', propertyName: 'show' },\r\n color: <DataViewObjectPropertyIdentifier>{ objectName: 'labels', propertyName: 'color' },\r\n labelDisplayUnits: <DataViewObjectPropertyIdentifier>{ objectName: 'labels', propertyName: 'labelDisplayUnits' },\r\n labelPrecision: <DataViewObjectPropertyIdentifier>{ objectName: 'labels', propertyName: 'labelPrecision' },\r\n },\r\n categoryLabels: {\r\n show: <DataViewObjectPropertyIdentifier>{ objectName: 'categoryLabels', propertyName: 'show' },\r\n }\r\n };\r\n}\n\n\n/** WEBPACK FOOTER **\n ** ./src/Clients/Visuals/capabilities/filledMap.capabilities.ts\n **/","/*\r\n * Power BI Visualizations\r\n *\r\n * Copyright (c) Microsoft Corporation\r\n * All rights reserved. \r\n * MIT License\r\n *\r\n * Permission is hereby granted, free of charge, to any person obtaining a copy\r\n * of this software and associated documentation files (the \"\"Software\"\"), to deal\r\n * in the Software without restriction, including without limitation the rights\r\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\r\n * copies of the Software, and to permit persons to whom the Software is\r\n * furnished to do so, subject to the following conditions:\r\n * \r\n * The above copyright notice and this permission notice shall be included in \r\n * all copies or substantial portions of the Software.\r\n * \r\n * THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR \r\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, \r\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE \r\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER \r\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\r\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\r\n * THE SOFTWARE.\r\n */\r\n\r\n/// <reference path=\"../_references.ts\"/>\r\n\r\nmodule powerbi.visuals {\r\n export const funnelChartCapabilities: VisualCapabilities = {\r\n dataRoles: [\r\n {\r\n name: 'Category',\r\n kind: VisualDataRoleKind.Grouping,\r\n displayName: data.createDisplayNameGetter('Role_DisplayName_Group'),\r\n description: data.createDisplayNameGetter('Role_DisplayName_GroupFunnelDescription')\r\n }, {\r\n name: 'Y',\r\n kind: VisualDataRoleKind.Measure,\r\n displayName: data.createDisplayNameGetter('Role_DisplayName_Values'),\r\n description: data.createDisplayNameGetter('Role_DisplayName_ValuesDescription'),\r\n requiredTypes: [{ numeric: true }, { integer: true }],\r\n }, {\r\n name: 'Gradient',\r\n kind: VisualDataRoleKind.Measure,\r\n displayName: data.createDisplayNameGetter('Role_DisplayName_Gradient'),\r\n description: data.createDisplayNameGetter('Role_DisplayName_GradientDescription'),\r\n requiredTypes: [{ numeric: true }, { integer: true }],\r\n joinPredicate: JoinPredicateBehavior.None,\r\n }\r\n ],\r\n dataViewMappings: [{\r\n conditions: [\r\n // NOTE: Ordering of the roles prefers to add measures to Y before Gradient.\r\n { 'Category': { max: 0 }, 'Gradient': { max: 0 } },\r\n { 'Category': { max: 1 }, 'Y': { max: 1 }, 'Gradient': { max: 1 } },\r\n ],\r\n categorical: {\r\n categories: {\r\n for: { in: 'Category' },\r\n dataReductionAlgorithm: { top: {} }\r\n },\r\n values: {\r\n select: [{ for: { in: 'Y' } }, { bind: { to: 'Gradient' } }],\r\n },\r\n rowCount: { preferred: { min: 1 } }\r\n },\r\n }],\r\n objects: {\r\n general: {\r\n displayName: data.createDisplayNameGetter('Visual_General'),\r\n properties: {\r\n formatString: StandardObjectProperties.formatString,\r\n },\r\n },\r\n dataPoint: {\r\n displayName: data.createDisplayNameGetter('Visual_DataPoint'),\r\n description: data.createDisplayNameGetter('Visual_DataPointDescription'),\r\n properties: {\r\n defaultColor: StandardObjectProperties.defaultColor,\r\n fill: StandardObjectProperties.fill,\r\n fillRule: {\r\n displayName: data.createDisplayNameGetter('Visual_Gradient'),\r\n type: { fillRule: {} },\r\n rule: {\r\n inputRole: 'Gradient',\r\n output: {\r\n property: 'fill',\r\n selector: ['Category'],\r\n },\r\n },\r\n }\r\n }\r\n },\r\n labels: {\r\n displayName: data.createDisplayNameGetter('Visual_DataPointsLabels'),\r\n description: data.createDisplayNameGetter('Visual_DataPointsLabelsDescription'),\r\n properties: {\r\n show: StandardObjectProperties.show,\r\n color: StandardObjectProperties.dataColor,\r\n labelPosition: {\r\n displayName: data.createDisplayNameGetter('Visual_Position'),\r\n type: { enumeration: labelPosition.type },\r\n suppressFormatPainterCopy: true,\r\n },\r\n labelDisplayUnits: StandardObjectProperties.dataLabelDisplayUnits,\r\n labelPrecision: $.extend({}, StandardObjectProperties.labelPrecision, {\r\n suppressFormatPainterCopy: true,\r\n }),\r\n fontSize: StandardObjectProperties.fontSize,\r\n }\r\n },\r\n percentBarLabel: {\r\n displayName: data.createDisplayNameGetter('Visual_PercentBarLabel'),\r\n description: data.createDisplayNameGetter('Visual_PercentBarLabelDescription'),\r\n properties: {\r\n show: StandardObjectProperties.show,\r\n color: StandardObjectProperties.dataColor,\r\n fontSize: StandardObjectProperties.fontSize,\r\n }\r\n },\r\n },\r\n supportsHighlight: true,\r\n sorting: {\r\n default: {},\r\n },\r\n drilldown: {\r\n roles: ['Category']\r\n },\r\n };\r\n\r\n export const funnelChartProps = {\r\n general: {\r\n formatString: <DataViewObjectPropertyIdentifier>{ objectName: 'general', propertyName: 'formatString' },\r\n },\r\n dataPoint: {\r\n defaultColor: <DataViewObjectPropertyIdentifier>{ objectName: 'dataPoint', propertyName: 'defaultColor' },\r\n fill: <DataViewObjectPropertyIdentifier>{ objectName: 'dataPoint', propertyName: 'fill' },\r\n },\r\n };\r\n}\n\n\n/** WEBPACK FOOTER **\n ** ./src/Clients/Visuals/capabilities/funnelChart.capabilities.ts\n **/","/*\r\n * Power BI Visualizations\r\n *\r\n * Copyright (c) Microsoft Corporation\r\n * All rights reserved. \r\n * MIT License\r\n *\r\n * Permission is hereby granted, free of charge, to any person obtaining a copy\r\n * of this software and associated documentation files (the \"\"Software\"\"), to deal\r\n * in the Software without restriction, including without limitation the rights\r\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\r\n * copies of the Software, and to permit persons to whom the Software is\r\n * furnished to do so, subject to the following conditions:\r\n * \r\n * The above copyright notice and this permission notice shall be included in \r\n * all copies or substantial portions of the Software.\r\n * \r\n * THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR \r\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, \r\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE \r\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER \r\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\r\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\r\n * THE SOFTWARE.\r\n */\r\n\r\n/// <reference path=\"../_references.ts\"/>\r\n\r\nmodule powerbi.visuals {\r\n export const gaugeRoleNames = {\r\n y: 'Y',\r\n minValue: 'MinValue',\r\n maxValue: 'MaxValue',\r\n targetValue: 'TargetValue'\r\n };\r\n\r\n export const gaugeCapabilities: VisualCapabilities = {\r\n dataRoles: [\r\n {\r\n name: gaugeRoleNames.y,\r\n kind: VisualDataRoleKind.Measure,\r\n displayName: data.createDisplayNameGetter('Role_DisplayName_Value'),\r\n description: data.createDisplayNameGetter('Role_DisplayName_ValueDescription'),\r\n requiredTypes: [{ numeric: true }, { integer: true }],\r\n }, {\r\n name: gaugeRoleNames.minValue,\r\n kind: VisualDataRoleKind.Measure,\r\n displayName: data.createDisplayNameGetter('Role_DisplayName_MinValue'),\r\n description: data.createDisplayNameGetter('Role_DisplayName_MinValueDescription'),\r\n requiredTypes: [{ numeric: true }, { integer: true }],\r\n }, {\r\n name: gaugeRoleNames.maxValue,\r\n kind: VisualDataRoleKind.Measure,\r\n displayName: data.createDisplayNameGetter('Role_DisplayName_MaxValue'),\r\n description: data.createDisplayNameGetter('Role_DisplayName_MaxValueDescription'),\r\n requiredTypes: [{ numeric: true }, { integer: true }],\r\n }, {\r\n name: gaugeRoleNames.targetValue,\r\n kind: VisualDataRoleKind.Measure,\r\n displayName: data.createDisplayNameGetter('Role_DisplayName_TargetValue'),\r\n description: data.createDisplayNameGetter('Role_DisplayName_TargetValueDescription'),\r\n requiredTypes: [{ numeric: true }, { integer: true }],\r\n }\r\n ],\r\n objects: {\r\n general: {\r\n properties: {\r\n formatString: StandardObjectProperties.formatString,\r\n },\r\n },\r\n axis: {\r\n displayName: data.createDisplayNameGetter('Visual_Gauge_Axis'),\r\n properties: {\r\n min: {\r\n displayName: data.createDisplayNameGetter('Visual_Gauge_Axis_Min'),\r\n type: { numeric: true }\r\n },\r\n max: {\r\n displayName: data.createDisplayNameGetter('Visual_Gauge_Axis_Max'),\r\n type: { numeric: true }\r\n },\r\n target: {\r\n displayName: data.createDisplayNameGetter('Visual_Gauge_Axis_Target'),\r\n type: { numeric: true }\r\n },\r\n },\r\n },\r\n labels: {\r\n displayName: data.createDisplayNameGetter('Visual_DataPointsLabels'),\r\n properties: {\r\n show: StandardObjectProperties.show,\r\n color: StandardObjectProperties.dataColor,\r\n labelDisplayUnits: StandardObjectProperties.labelDisplayUnits,\r\n labelPrecision: StandardObjectProperties.labelPrecision,\r\n fontSize: StandardObjectProperties.fontSize,\r\n },\r\n },\r\n calloutValue: {\r\n displayName: data.createDisplayNameGetter('Visual_Gauge_CalloutValue'),\r\n properties: {\r\n show: StandardObjectProperties.show,\r\n color: StandardObjectProperties.dataColor,\r\n labelDisplayUnits: StandardObjectProperties.labelDisplayUnits,\r\n labelPrecision: StandardObjectProperties.labelPrecision,\r\n },\r\n },\r\n dataPoint: {\r\n displayName: data.createDisplayNameGetter('Visual_DataPoint'),\r\n properties: {\r\n fill: StandardObjectProperties.fill,\r\n target: {\r\n // TODO find a better string\r\n displayName: data.createDisplayNameGetter('Visual_Gauge_Axis_Target'),\r\n type: { fill: { solid: { color: true } } }\r\n }\r\n }\r\n }\r\n },\r\n dataViewMappings: [{\r\n conditions: [\r\n { 'Y': { max: 1 }, 'MinValue': { max: 1 }, 'MaxValue': { max: 1 }, 'TargetValue': { max: 1 } },\r\n ],\r\n categorical: {\r\n values: {\r\n select: [\r\n { bind: { to: 'Y' } },\r\n { bind: { to: 'MinValue' } },\r\n { bind: { to: 'MaxValue' } },\r\n { bind: { to: 'TargetValue' } },\r\n ]\r\n },\r\n },\r\n }],\r\n supportsSelection: false,\r\n };\r\n\r\n export const gaugeProps = {\r\n dataPoint: {\r\n fill: <DataViewObjectPropertyIdentifier>{ objectName: 'dataPoint', propertyName: 'fill' },\r\n target: <DataViewObjectPropertyIdentifier>{ objectName: 'dataPoint', propertyName: 'target' }\r\n }\r\n };\r\n}\n\n\n/** WEBPACK FOOTER **\n ** ./src/Clients/Visuals/capabilities/gauge.capabilities.ts\n **/","/*\r\n * Power BI Visualizations\r\n *\r\n * Copyright (c) Microsoft Corporation\r\n * All rights reserved. \r\n * MIT License\r\n *\r\n * Permission is hereby granted, free of charge, to any person obtaining a copy\r\n * of this software and associated documentation files (the \"\"Software\"\"), to deal\r\n * in the Software without restriction, including without limitation the rights\r\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\r\n * copies of the Software, and to permit persons to whom the Software is\r\n * furnished to do so, subject to the following conditions:\r\n * \r\n * The above copyright notice and this permission notice shall be included in \r\n * all copies or substantial portions of the Software.\r\n * \r\n * THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR \r\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, \r\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE \r\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER \r\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\r\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\r\n * THE SOFTWARE.\r\n */\r\n\r\n/// <reference path=\"../_references.ts\"/>\r\n\r\nmodule powerbi.visuals {\r\n export const imageVisualCapabilities: VisualCapabilities = {\r\n objects: {\r\n general: {\r\n properties: {\r\n imageUrl: {\r\n type: { misc: { imageUrl: true } },\r\n suppressFormatPainterCopy: true,\r\n }\r\n }\r\n },\r\n imageScaling: {\r\n displayName: data.createDisplayNameGetter('Visual_Image_Scaling_Type'),\r\n properties: {\r\n imageScalingType: {\r\n displayName: data.createDisplayNameGetter('Visual_Image_Scaling_Type'),\r\n type: { enumeration: imageScalingType.type }\r\n },\r\n }\r\n },\r\n },\r\n suppressDefaultTitle: true,\r\n supportsSelection: false,\r\n };\r\n} \n\n\n/** WEBPACK FOOTER **\n ** ./src/Clients/Visuals/capabilities/imageVisual.capabilities.ts\n **/","/*\r\n * Power BI Visualizations\r\n *\r\n * Copyright (c) Microsoft Corporation\r\n * All rights reserved. \r\n * MIT License\r\n *\r\n * Permission is hereby granted, free of charge, to any person obtaining a copy\r\n * of this software and associated documentation files (the \"\"Software\"\"), to deal\r\n * in the Software without restriction, including without limitation the rights\r\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\r\n * copies of the Software, and to permit persons to whom the Software is\r\n * furnished to do so, subject to the following conditions:\r\n * \r\n * The above copyright notice and this permission notice shall be included in \r\n * all copies or substantial portions of the Software.\r\n * \r\n * THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR \r\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, \r\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE \r\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER \r\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\r\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\r\n * THE SOFTWARE.\r\n */\r\n\r\n/// <reference path=\"../_references.ts\"/>\r\n\r\nmodule powerbi.visuals {\r\n export var scriptVisualCapabilities: VisualCapabilities = {\r\n dataRoles: [{\r\n name: 'Values',\r\n kind: VisualDataRoleKind.GroupingOrMeasure,\r\n displayName: data.createDisplayNameGetter('Role_DisplayName_Values'),\r\n }],\r\n dataViewMappings: [{\r\n scriptResult: {\r\n dataInput: {\r\n table: {\r\n rows: {\r\n for: {\r\n in: 'Values'\r\n },\r\n dataReductionAlgorithm: {\r\n top: {}\r\n }\r\n },\r\n },\r\n },\r\n script: {\r\n source: {\r\n objectName: 'script',\r\n propertyName: 'source'\r\n },\r\n provider: {\r\n objectName: 'script',\r\n propertyName: 'provider'\r\n },\r\n }\r\n }\r\n }],\r\n objects: {\r\n script: {\r\n properties: {\r\n provider: {\r\n type: { text: true }\r\n },\r\n source: {\r\n type: { scripting: { source: true }\r\n }\r\n },\r\n }\r\n },\r\n },\r\n };\r\n} \n\n\n/** WEBPACK FOOTER **\n ** ./src/Clients/Visuals/capabilities/scriptVisual.capabilities.ts\n **/","/*\r\n* Power BI Visualizations\r\n*\r\n* Copyright (c) Microsoft Corporation\r\n* All rights reserved. \r\n* MIT License\r\n*\r\n* Permission is hereby granted, free of charge, to any person obtaining a copy\r\n* of this software and associated documentation files (the \"\"Software\"\"), to deal\r\n* in the Software without restriction, including without limitation the rights\r\n* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\r\n* copies of the Software, and to permit persons to whom the Software is\r\n* furnished to do so, subject to the following conditions:\r\n* \r\n* The above copyright notice and this permission notice shall be included in \r\n* all copies or substantial portions of the Software.\r\n* \r\n* THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR \r\n* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, \r\n* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE \r\n* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER \r\n* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\r\n* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\r\n* THE SOFTWARE.\r\n*/\r\n\r\n/// <reference path=\"../../_references.ts\"/>\r\n\r\nmodule powerbi.visuals.samples {\r\n export var consoleWriterCapabilities: VisualCapabilities = {\r\n dataRoles: [\r\n {\r\n name: 'Category',\r\n kind: VisualDataRoleKind.Grouping,\r\n displayName: data.createDisplayNameGetter('Role_DisplayName_Axis'),\r\n description: data.createDisplayNameGetter('Role_DisplayName_AxisDescription')\r\n },\r\n {\r\n name: 'Y',\r\n kind: VisualDataRoleKind.Measure,\r\n displayName: data.createDisplayNameGetter('Role_DisplayName_Y'),\r\n description: data.createDisplayNameGetter('Role_DisplayName_YDescription')\r\n },\r\n ],\r\n dataViewMappings: [{\r\n categorical: {\r\n categories: {\r\n for: { in: 'Category' },\r\n },\r\n },\r\n }],\r\n };\r\n}\n\n\n/** WEBPACK FOOTER **\n ** ./src/Clients/Visuals/capabilities/samples/consoleWriter.capabilities.ts\n **/","/*\r\n * Power BI Visualizations\r\n *\r\n * Copyright (c) Microsoft Corporation\r\n * All rights reserved. \r\n * MIT License\r\n *\r\n * Permission is hereby granted, free of charge, to any person obtaining a copy\r\n * of this software and associated documentation files (the \"\"Software\"\"), to deal\r\n * in the Software without restriction, including without limitation the rights\r\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\r\n * copies of the Software, and to permit persons to whom the Software is\r\n * furnished to do so, subject to the following conditions:\r\n * \r\n * The above copyright notice and this permission notice shall be included in \r\n * all copies or substantial portions of the Software.\r\n * \r\n * THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR \r\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, \r\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE \r\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER \r\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\r\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\r\n * THE SOFTWARE.\r\n */\r\n\r\n/// <reference path=\"../../_references.ts\"/>\r\n\r\nmodule powerbi.visuals.samples {\r\n\r\n export class ConsoleWriter implements IVisual {\r\n\r\n public static converter(dataView: DataView): any {\r\n window.console.log('converter');\r\n window.console.log(dataView);\r\n\r\n return {};\r\n }\r\n\r\n public init(options: VisualInitOptions): void {\r\n let div = d3.select(options.element.get(0)).append(\"div\");\r\n \r\n div.append(\"h1\").text(\"ConsoleWriter\");\r\n div.append(\"p\").text(\"This IVisual writes messages passed to it to the javscript console output. Check your console for the actual messages passed. For more information, click below\");\r\n let anchor = div.append('a');\r\n anchor.attr('href', \"http://microsoft.github.io/PowerBI-visuals/modules/powerbi.html\")\r\n .text(\"Online help\");\r\n\r\n window.console.log('init');\r\n window.console.log(options);\r\n }\r\n\r\n public onResizing(viewport: IViewport) { /* This API will be depricated */ }\r\n\r\n public update(options: VisualUpdateOptions) {\r\n window.console.log('update');\r\n window.console.log(options);\r\n }\r\n }\r\n}\n\n\n/** WEBPACK FOOTER **\n ** ./src/Clients/Visuals/visuals/samples/consoleWriter.ts\n **/","/*\r\n * Power BI Visualizations\r\n *\r\n * Copyright (c) Microsoft Corporation\r\n * All rights reserved.\r\n * MIT License\r\n *\r\n * Permission is hereby granted, free of charge, to any person obtaining a copy\r\n * of this software and associated documentation files (the \"\"Software\"\"), to deal\r\n * in the Software without restriction, including without limitation the rights\r\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\r\n * copies of the Software, and to permit persons to whom the Software is\r\n * furnished to do so, subject to the following conditions:\r\n *\r\n * The above copyright notice and this permission notice shall be included in\r\n * all copies or substantial portions of the Software.\r\n *\r\n * THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\r\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\r\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\r\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\r\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\r\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\r\n * THE SOFTWARE.\r\n */\r\n\r\n/// <reference path=\"../_references.ts\"/>\r\n\r\nmodule powerbi.visuals {\r\n export const lineChartCapabilities: VisualCapabilities = {\r\n dataRoles: [\r\n {\r\n name: 'Category',\r\n kind: VisualDataRoleKind.Grouping,\r\n displayName: data.createDisplayNameGetter('Role_DisplayName_Axis'),\r\n description: data.createDisplayNameGetter('Role_DisplayName_AxisDescription'),\r\n cartesianKind: CartesianRoleKind.X,\r\n }, {\r\n name: 'Series',\r\n kind: VisualDataRoleKind.Grouping,\r\n displayName: data.createDisplayNameGetter('Role_DisplayName_Legend'),\r\n description: data.createDisplayNameGetter('Role_DisplayName_LegendDescription')\r\n }, {\r\n name: 'Y',\r\n kind: VisualDataRoleKind.Measure,\r\n displayName: data.createDisplayNameGetter('Role_DisplayName_Values'),\r\n description: data.createDisplayNameGetter('Role_DisplayName_ValuesDescription'),\r\n requiredTypes: [{ numeric: true }, { integer: true }],\r\n cartesianKind: CartesianRoleKind.Y,\r\n },\r\n ],\r\n objects: {\r\n general: {\r\n displayName: data.createDisplayNameGetter('Visual_General'),\r\n properties: {\r\n formatString: StandardObjectProperties.formatString,\r\n },\r\n },\r\n legend: {\r\n displayName: data.createDisplayNameGetter('Visual_Legend'),\r\n description: data.createDisplayNameGetter('Visual_LegendDescription'),\r\n properties: {\r\n show: StandardObjectProperties.show,\r\n position: StandardObjectProperties.legendPosition,\r\n showTitle: StandardObjectProperties.showLegendTitle,\r\n titleText: $.extend({}, StandardObjectProperties.legendTitle, {\r\n suppressFormatPainterCopy: true\r\n }),\r\n labelColor: StandardObjectProperties.labelColor,\r\n fontSize: StandardObjectProperties.fontSize,\r\n }\r\n },\r\n dataPoint: {\r\n displayName: data.createDisplayNameGetter('Visual_DataPoint'),\r\n description: data.createDisplayNameGetter('Visual_DataPointDescription'),\r\n properties: {\r\n defaultColor: StandardObjectProperties.defaultColor,\r\n fill: StandardObjectProperties.fill,\r\n }\r\n },\r\n trend: {\r\n displayName: data.createDisplayNameGetter('Visual_Trend_Line'),\r\n properties: {\r\n show: StandardObjectProperties.show,\r\n lineColor: {\r\n displayName: data.createDisplayNameGetter('Visual_Trend_Line_Color'),\r\n description: data.createDisplayNameGetter('Visual_Trend_Line_Color_Description'),\r\n type: { fill: { solid: { color: true } } }\r\n },\r\n transparency: {\r\n displayName: data.createDisplayNameGetter('Visual_Trend_Line_Transparency'),\r\n description: data.createDisplayNameGetter('Visual_Trend_Line_Transparency_Description'),\r\n type: { numeric: true }\r\n },\r\n style: {\r\n displayName: data.createDisplayNameGetter('Visual_Trend_Line_Style'),\r\n description: data.createDisplayNameGetter('Visual_Trend_Line_Style_Description'),\r\n type: { enumeration: lineStyle.type }\r\n },\r\n combineSeries: {\r\n displayName: data.createDisplayNameGetter('Visual_Trend_Line_Combine_Series'),\r\n description: data.createDisplayNameGetter('Visual_Trend_Line_Combine_Series_Description'),\r\n type: { bool: true }\r\n },\r\n useHighlightValues: {\r\n displayName: data.createDisplayNameGetter('Visual_Trend_Line_UseHighlightValues'),\r\n description: data.createDisplayNameGetter('Visual_Trend_Line_UseHighlightValues_Description'),\r\n type: { bool: true }\r\n },\r\n }\r\n },\r\n scalarKey: {\r\n properties: {\r\n min: {\r\n type: { dateTime: true }\r\n },\r\n }\r\n },\r\n categoryAxis: {\r\n displayName: data.createDisplayNameGetter('Visual_XAxis'),\r\n properties: {\r\n show: StandardObjectProperties.show,\r\n axisScale: StandardObjectProperties.axisScale,\r\n start: StandardObjectProperties.axisStart,\r\n end: StandardObjectProperties.axisEnd,\r\n axisType: StandardObjectProperties.axisType,\r\n showAxisTitle: {\r\n displayName: data.createDisplayNameGetter('Visual_Axis_Title'),\r\n description: data.createDisplayNameGetter('Visual_Axis_XTitleDescription'),\r\n type: { bool: true }\r\n },\r\n axisStyle: StandardObjectProperties.axisStyle,\r\n labelColor: StandardObjectProperties.labelColor,\r\n labelDisplayUnits: StandardObjectProperties.labelDisplayUnits,\r\n labelPrecision: StandardObjectProperties.labelPrecision,\r\n }\r\n },\r\n valueAxis: {\r\n displayName: data.createDisplayNameGetter('Visual_YAxis'),\r\n properties: {\r\n show: StandardObjectProperties.show,\r\n position: StandardObjectProperties.yAxisPosition,\r\n axisScale: StandardObjectProperties.axisScale,\r\n start: StandardObjectProperties.axisStart,\r\n end: StandardObjectProperties.axisEnd,\r\n showAxisTitle: {\r\n displayName: data.createDisplayNameGetter('Visual_Axis_Title'),\r\n description: data.createDisplayNameGetter('Visual_Axis_YTitleDescription'),\r\n type: { bool: true }\r\n },\r\n axisStyle: StandardObjectProperties.axisStyle,\r\n labelColor: StandardObjectProperties.labelColor,\r\n labelDisplayUnits: StandardObjectProperties.labelDisplayUnits,\r\n labelPrecision: StandardObjectProperties.labelPrecision,\r\n }\r\n },\r\n y1AxisReferenceLine: {\r\n displayName: data.createDisplayNameGetter('Visual_Reference_Line'),\r\n description: data.createDisplayNameGetter('Visual_Reference_Line_Description'),\r\n properties: {\r\n show: StandardObjectProperties.show,\r\n value: {\r\n displayName: data.createDisplayNameGetter('Visual_Reference_Line_Value'),\r\n description: data.createDisplayNameGetter('Visual_Reference_Line_Value_Description'),\r\n type: { numeric: true }\r\n },\r\n lineColor: StandardObjectProperties.lineColor,\r\n transparency: {\r\n displayName: data.createDisplayNameGetter('Visual_Reference_Line_Transparency'),\r\n description: data.createDisplayNameGetter('Visual_Reference_Line_Transparency_Description'),\r\n type: { numeric: true }\r\n },\r\n style: StandardObjectProperties.referenceLineStyle,\r\n position: StandardObjectProperties.referenceLinePosition,\r\n dataLabelShow: StandardObjectProperties.dataLabelShow,\r\n dataLabelColor: StandardObjectProperties.dataLabelColor,\r\n dataLabelDecimalPoints: StandardObjectProperties.dataLabelDecimalPoints,\r\n dataLabelHorizontalPosition: StandardObjectProperties.dataLabelHorizontalPosition,\r\n dataLabelVerticalPosition: StandardObjectProperties.dataLabelVerticalPosition,\r\n dataLabelDisplayUnits: StandardObjectProperties.dataLabelDisplayUnits,\r\n },\r\n },\r\n labels: {\r\n displayName: data.createDisplayNameGetter('Visual_DataPointsLabels'),\r\n description: data.createDisplayNameGetter('Visual_DataPointsLabelsDescription'),\r\n properties: {\r\n show: StandardObjectProperties.show,\r\n showSeries: {\r\n displayName: data.createDisplayNameGetter('Visual_Show'),\r\n type: { bool: true }\r\n },\r\n color: StandardObjectProperties.dataColor,\r\n labelDisplayUnits: StandardObjectProperties.dataLabelDisplayUnits,\r\n labelPrecision: $.extend({}, StandardObjectProperties.labelPrecision, {\r\n suppressFormatPainterCopy: true,\r\n }),\r\n showAll: {\r\n displayName: data.createDisplayNameGetter('Visual_LabelSeriesShowAll'),\r\n type: { bool: true }\r\n },\r\n fontSize: StandardObjectProperties.fontSize,\r\n labelDensity: {\r\n displayName: data.createDisplayNameGetter('Visual_LabelDensity'),\r\n type: { formatting: { labelDensity: true } },\r\n },\r\n },\r\n },\r\n plotArea: {\r\n displayName: data.createDisplayNameGetter('Visual_Plot'),\r\n //description: data.createDisplayNameGetter('Visual_PlotDescription'),\r\n properties: {\r\n transparency: StandardObjectProperties.transparency,\r\n image: StandardObjectProperties.image,\r\n },\r\n },\r\n },\r\n dataViewMappings: [{\r\n conditions: [\r\n { 'Category': { max: 1 }, 'Series': { max: 0 } },\r\n { 'Category': { max: 1 }, 'Series': { min: 1, max: 1 }, 'Y': { max: 1 } }\r\n ],\r\n categorical: {\r\n categories: {\r\n for: { in: 'Category' },\r\n dataReductionAlgorithm: { top: {} }\r\n },\r\n values: {\r\n group: {\r\n by: 'Series',\r\n select: [{ for: { in: 'Y' } }],\r\n dataReductionAlgorithm: { top: {} }\r\n }\r\n },\r\n },\r\n }, {\r\n conditions: [\r\n { 'Category': { max: 1 }, 'Series': { max: 0 } },\r\n { 'Category': { max: 1 }, 'Series': { min: 1, max: 1 }, 'Y': { max: 1 } }\r\n ],\r\n requiredProperties: [{ objectName: 'trend', propertyName: 'show' }],\r\n usage: {\r\n regression: {\r\n combineSeries: { objectName: 'trend', propertyName: 'combineSeries' }\r\n },\r\n },\r\n categorical: {\r\n categories: {\r\n for: { in: 'regression.X' },\r\n },\r\n values: {\r\n group: {\r\n by: 'regression.Series',\r\n select: [{ for: {in: 'regression.Y' } }],\r\n },\r\n }\r\n }\r\n }],\r\n sorting: {\r\n default: {},\r\n },\r\n };\r\n\r\n export const lineChartProps = {\r\n general: {\r\n formatString: <DataViewObjectPropertyIdentifier>{ objectName: 'general', propertyName: 'formatString' },\r\n },\r\n dataPoint: {\r\n defaultColor: <DataViewObjectPropertyIdentifier>{ objectName: 'dataPoint', propertyName: 'defaultColor' },\r\n fill: <DataViewObjectPropertyIdentifier>{ objectName: 'dataPoint', propertyName: 'fill' },\r\n },\r\n trend: {\r\n show: <DataViewObjectPropertyIdentifier>{ objectName: 'trend', propertyName: 'show' },\r\n },\r\n scalarKey: {\r\n scalarKeyMin: <DataViewObjectPropertyIdentifier>{ objectName: 'scalarKey', propertyName: 'min' },\r\n },\r\n categoryAxis: {\r\n axisType: <DataViewObjectPropertyIdentifier>{ objectName: 'categoryAxis', propertyName: 'axisType' },\r\n },\r\n legend: {\r\n labelColor: <DataViewObjectPropertyIdentifier>{ objectName: 'legend', propertyName: 'labelColor' },\r\n },\r\n labels: {\r\n labelDensity: <DataViewObjectPropertyIdentifier>{ objectName: 'labels', propertyName: 'labelDensity' },\r\n },\r\n plotArea: {\r\n image: <DataViewObjectPropertyIdentifier>{ objectName: 'plotArea', propertyName: 'image' },\r\n transparency: <DataViewObjectPropertyIdentifier>{ objectName: 'plotArea', propertyName: 'transparency' },\r\n },\r\n };\r\n}\n\n\n/** WEBPACK FOOTER **\n ** ./src/Clients/Visuals/capabilities/lineChart.capabilities.ts\n **/","/*\r\n * Power BI Visualizations\r\n *\r\n * Copyright (c) Microsoft Corporation\r\n * All rights reserved. \r\n * MIT License\r\n *\r\n * Permission is hereby granted, free of charge, to any person obtaining a copy\r\n * of this software and associated documentation files (the \"\"Software\"\"), to deal\r\n * in the Software without restriction, including without limitation the rights\r\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\r\n * copies of the Software, and to permit persons to whom the Software is\r\n * furnished to do so, subject to the following conditions:\r\n * \r\n * The above copyright notice and this permission notice shall be included in \r\n * all copies or substantial portions of the Software.\r\n * \r\n * THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR \r\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, \r\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE \r\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER \r\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\r\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\r\n * THE SOFTWARE.\r\n */\r\n\r\n/// <reference path=\"../_references.ts\"/>\r\n\r\nmodule powerbi.visuals {\r\n export const mapCapabilities: VisualCapabilities = {\r\n dataRoles: [\r\n {\r\n name: 'Category',\r\n kind: VisualDataRoleKind.Grouping,\r\n displayName: data.createDisplayNameGetter('Role_DisplayName_Location'),\r\n description: data.createDisplayNameGetter('Role_DisplayName_LocationMapDescription'),\r\n preferredTypes: [\r\n { geography: { address: true } },\r\n { geography: { city: true } },\r\n { geography: { continent: true } },\r\n { geography: { country: true } },\r\n { geography: { county: true } },\r\n { geography: { place: true } },\r\n { geography: { postalCode: true } },\r\n { geography: { region: true } },\r\n { geography: { stateOrProvince: true } },\r\n ],\r\n }, {\r\n name: 'Series',\r\n kind: VisualDataRoleKind.Grouping,\r\n displayName: data.createDisplayNameGetter('Role_DisplayName_Legend'),\r\n description: data.createDisplayNameGetter('Role_DisplayName_LegendDescription')\r\n }, {\r\n name: 'Y',\r\n kind: VisualDataRoleKind.GroupingOrMeasure,\r\n displayName: data.createDisplayNameGetter('Role_DisplayName_Latitude'),\r\n description: data.createDisplayNameGetter('Role_DisplayName_LatitudeMapDescription'),\r\n preferredTypes: [\r\n { geography: { latitude: true } }\r\n ],\r\n }, {\r\n name: 'X',\r\n kind: VisualDataRoleKind.GroupingOrMeasure,\r\n displayName: data.createDisplayNameGetter('Role_DisplayName_Longitude'),\r\n description: data.createDisplayNameGetter('Role_DisplayName_LongitudeMapDescription'),\r\n preferredTypes: [\r\n { geography: { longitude: true } }\r\n ],\r\n }, {\r\n name: 'Size',\r\n kind: VisualDataRoleKind.Measure,\r\n displayName: data.createDisplayNameGetter('Role_DisplayName_Size'),\r\n description: data.createDisplayNameGetter('Role_DisplayName_SizeDescription'),\r\n requiredTypes: [{ numeric: true }, { integer: true }],\r\n }, {\r\n name: 'Gradient',\r\n kind: VisualDataRoleKind.Measure,\r\n displayName: data.createDisplayNameGetter('Role_DisplayName_Gradient'),\r\n description: data.createDisplayNameGetter('Role_DisplayName_GradientDescription'),\r\n requiredTypes: [{ numeric: true }, { integer: true }],\r\n joinPredicate: JoinPredicateBehavior.None,\r\n }\r\n ],\r\n objects: {\r\n general: {\r\n displayName: data.createDisplayNameGetter('Visual_General'),\r\n properties: {\r\n formatString: StandardObjectProperties.formatString,\r\n },\r\n },\r\n legend: {\r\n displayName: data.createDisplayNameGetter('Visual_Legend'),\r\n description: data.createDisplayNameGetter('Visual_LegendDescription'),\r\n properties: {\r\n show: StandardObjectProperties.show,\r\n position: StandardObjectProperties.legendPosition,\r\n showTitle: StandardObjectProperties.showLegendTitle,\r\n titleText: StandardObjectProperties.legendTitle,\r\n fontSize: StandardObjectProperties.fontSize,\r\n }\r\n },\r\n dataPoint: {\r\n displayName: data.createDisplayNameGetter('Visual_DataPoint'),\r\n description: data.createDisplayNameGetter('Visual_DataPointDescription'),\r\n properties: {\r\n defaultColor: StandardObjectProperties.defaultColor,\r\n showAllDataPoints: StandardObjectProperties.showAllDataPoints,\r\n fill: StandardObjectProperties.fill,\r\n fillRule: {\r\n displayName: data.createDisplayNameGetter('Visual_Gradient'),\r\n type: { fillRule: {} },\r\n rule: {\r\n inputRole: 'Gradient',\r\n output: {\r\n property: 'fill',\r\n selector: ['Category', 'X', 'Y'],\r\n },\r\n },\r\n }\r\n }\r\n },\r\n categoryLabels: {\r\n displayName: data.createDisplayNameGetter('Visual_CategoryLabels'),\r\n description: data.createDisplayNameGetter('Visual_CategoryLabelsDescription'),\r\n properties: {\r\n show: StandardObjectProperties.show,\r\n color: StandardObjectProperties.dataColor,\r\n fontSize: StandardObjectProperties.fontSize,\r\n },\r\n },\r\n },\r\n dataViewMappings: [{\r\n conditions: [\r\n { 'Category': { min: 1, max: 1 }, 'Series': { max: 1 }, 'X': { max: 1, kind: VisualDataRoleKind.Measure }, 'Y': { max: 1, kind: VisualDataRoleKind.Measure }, 'Size': { max: 1 }, 'Gradient': { max: 0 } },\r\n { 'Category': { min: 1, max: 1 }, 'Series': { max: 0 }, 'X': { max: 1, kind: VisualDataRoleKind.Measure }, 'Y': { max: 1, kind: VisualDataRoleKind.Measure }, 'Size': { max: 1 }, 'Gradient': { max: 1 } },\r\n ],\r\n categorical: {\r\n categories: {\r\n for: { in: 'Category' },\r\n dataReductionAlgorithm: { top: {} }\r\n },\r\n values: {\r\n group: {\r\n by: 'Series',\r\n select: [\r\n { bind: { to: 'X' } },\r\n { bind: { to: 'Y' } },\r\n { bind: { to: 'Size' } },\r\n { bind: { to: 'Gradient' } },\r\n ],\r\n dataReductionAlgorithm: { top: {} }\r\n }\r\n },\r\n rowCount: { preferred: { min: 2 } },\r\n dataVolume: 4,\r\n }\r\n }, {\r\n conditions: [\r\n { 'Category': { max: 0 }, 'Series': { max: 1 }, 'X': { max: 1, kind: VisualDataRoleKind.Grouping }, 'Y': { max: 1, kind: VisualDataRoleKind.Grouping }, 'Size': { max: 1 }, 'Gradient': { max: 0 } },\r\n { 'Category': { max: 0 }, 'Series': { max: 0 }, 'X': { max: 1, kind: VisualDataRoleKind.Grouping }, 'Y': { max: 1, kind: VisualDataRoleKind.Grouping }, 'Size': { max: 1 }, 'Gradient': { max: 1 } }\r\n ],\r\n categorical: {\r\n categories: {\r\n select: [\r\n { bind: { to: 'X' } },\r\n { bind: { to: 'Y' } },\r\n ],\r\n dataReductionAlgorithm: { top: {} }\r\n },\r\n values: {\r\n group: {\r\n by: 'Series',\r\n select: [\r\n { bind: { to: 'Size' } },\r\n { bind: { to: 'Gradient' } },\r\n ],\r\n dataReductionAlgorithm: { top: {} }\r\n }\r\n },\r\n rowCount: { preferred: { min: 2 } },\r\n dataVolume: 4,\r\n },\r\n }],\r\n sorting: {\r\n custom: {},\r\n implicit: {\r\n clauses: [{ role: 'Size', direction: SortDirection.Descending }]\r\n },\r\n },\r\n drilldown: {\r\n roles: ['Category']\r\n },\r\n };\r\n\r\n export const mapProps = {\r\n general: {\r\n formatString: <DataViewObjectPropertyIdentifier>{ objectName: 'general', propertyName: 'formatString' },\r\n },\r\n dataPoint: {\r\n defaultColor: <DataViewObjectPropertyIdentifier>{ objectName: 'dataPoint', propertyName: 'defaultColor' },\r\n fill: <DataViewObjectPropertyIdentifier>{ objectName: 'dataPoint', propertyName: 'fill' },\r\n showAllDataPoints: <DataViewObjectPropertyIdentifier>{ objectName: 'dataPoint', propertyName: 'showAllDataPoints' },\r\n },\r\n legend: {\r\n show: <DataViewObjectPropertyIdentifier>{ objectName: 'legend', propertyName: 'show' },\r\n position: <DataViewObjectPropertyIdentifier>{ objectName: 'legend', propertyName: 'position' },\r\n showTitle: <DataViewObjectPropertyIdentifier>{ objectName: 'legend', propertyName: 'showTitle' },\r\n titleText: <DataViewObjectPropertyIdentifier>{ objectName: 'legend', propertyName: 'titleText' },\r\n },\r\n };\r\n}\n\n\n/** WEBPACK FOOTER **\n ** ./src/Clients/Visuals/capabilities/map.capabilities.ts\n **/","/*\r\n * Power BI Visualizations\r\n *\r\n * Copyright (c) Microsoft Corporation\r\n * All rights reserved. \r\n * MIT License\r\n *\r\n * Permission is hereby granted, free of charge, to any person obtaining a copy\r\n * of this software and associated documentation files (the \"\"Software\"\"), to deal\r\n * in the Software without restriction, including without limitation the rights\r\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\r\n * copies of the Software, and to permit persons to whom the Software is\r\n * furnished to do so, subject to the following conditions:\r\n * \r\n * The above copyright notice and this permission notice shall be included in \r\n * all copies or substantial portions of the Software.\r\n * \r\n * THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR \r\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, \r\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE \r\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER \r\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\r\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\r\n * THE SOFTWARE.\r\n */\r\n\r\n/// <reference path=\"../_references.ts\"/>\r\n\r\nmodule powerbi.visuals {\r\n export const multiRowCardCapabilities: VisualCapabilities = {\r\n dataRoles: [\r\n {\r\n name: 'Values',\r\n kind: VisualDataRoleKind.GroupingOrMeasure,\r\n displayName: data.createDisplayNameGetter('Role_DisplayName_Fields'),\r\n description: data.createDisplayNameGetter('Role_DisplayName_FieldsDescription')\r\n }\r\n ],\r\n objects: {\r\n general: {\r\n properties: {\r\n formatString: StandardObjectProperties.formatString,\r\n },\r\n },\r\n cardTitle: {\r\n displayName: data.createDisplayNameGetter('Visual_CardTitle'),\r\n description: data.createDisplayNameGetter('Visual_CardTitleDescription'),\r\n properties: {\r\n color: StandardObjectProperties.dataColor,\r\n fontSize: StandardObjectProperties.fontSize,\r\n }\r\n },\r\n dataLabels: {\r\n displayName: data.createDisplayNameGetter('Visual_DataPointsLabels'),\r\n description: data.createDisplayNameGetter('Visual_DataPointsLabelsDescription'),\r\n properties: {\r\n color: StandardObjectProperties.dataColor,\r\n fontSize: StandardObjectProperties.fontSize,\r\n }\r\n },\r\n categoryLabels: {\r\n displayName: data.createDisplayNameGetter('Visual_CategoryLabels'),\r\n description: data.createDisplayNameGetter('Visual_CategoryLabelsDescription'),\r\n properties: {\r\n show: StandardObjectProperties.show,\r\n color: StandardObjectProperties.dataColor,\r\n fontSize: StandardObjectProperties.fontSize,\r\n }\r\n },\r\n card: {\r\n displayName: data.createDisplayNameGetter('Card_ToolTip'),\r\n properties: {\r\n outline: {\r\n displayName: data.createDisplayNameGetter('Visual_Outline'),\r\n type: { enumeration: outline.type }\r\n },\r\n outlineColor: {\r\n displayName: data.createDisplayNameGetter('Visual_OutlineColor'),\r\n description: data.createDisplayNameGetter('Visual_OutlineColor_Desc'),\r\n type: { fill: { solid: { color: true } } }\r\n },\r\n outlineWeight: {\r\n displayName: data.createDisplayNameGetter('Visual_OutlineWeight'),\r\n description: data.createDisplayNameGetter('Visual_OutlineWeight_Desc'),\r\n type: { numeric: true }\r\n },\r\n barShow: {\r\n displayName: data.createDisplayNameGetter('Visual_MultiRowCard_BarShow'),\r\n description: data.createDisplayNameGetter('Visual_MultiRowCard_BarShow_Desc'),\r\n type: { bool: true }\r\n },\r\n barColor: {\r\n displayName: data.createDisplayNameGetter('Visual_MultiRowCard_BarColor'),\r\n type: { fill: { solid: { color: true } } }\r\n },\r\n barWeight: {\r\n displayName: data.createDisplayNameGetter('Visual_MultiRowCard_BarWeight'),\r\n description: data.createDisplayNameGetter('Visual_MultiRowCard_BarWeight_Desc'),\r\n type: { numeric: true }\r\n },\r\n cardPadding: {\r\n displayName: data.createDisplayNameGetter('Visual_MultiRowCard_CardPadding'),\r\n description: data.createDisplayNameGetter('Visual_MultiRowCard_CardBackground'),\r\n type: { numeric: true }\r\n },\r\n cardBackground: {\r\n displayName: data.createDisplayNameGetter('Visual_Background'),\r\n type: { fill: { solid: { color: true } } }\r\n }\r\n }\r\n }\r\n },\r\n dataViewMappings: [{\r\n table: {\r\n rows: {\r\n for: { in: 'Values' },\r\n dataReductionAlgorithm: { window: {} }\r\n },\r\n rowCount: { preferred: { min: 1 } }\r\n },\r\n }],\r\n sorting: {\r\n default: {},\r\n },\r\n suppressDefaultTitle: true,\r\n supportsSelection: false,\r\n disableVisualDetails: true,\r\n };\r\n \r\n export const multiRowCardProps = {\r\n card: {\r\n outline: <DataViewObjectPropertyIdentifier>{ objectName: 'card', propertyName: 'outline' },\r\n outlineColor: <DataViewObjectPropertyIdentifier>{ objectName: 'card', propertyName: 'outlineColor' },\r\n outlineWeight: <DataViewObjectPropertyIdentifier>{ objectName: 'card', propertyName: 'outlineWeight' },\r\n barShow: <DataViewObjectPropertyIdentifier>{ objectName: 'card', propertyName: 'barShow' },\r\n barColor: <DataViewObjectPropertyIdentifier>{ objectName: 'card', propertyName: 'barColor' },\r\n barWeight: <DataViewObjectPropertyIdentifier>{ objectName: 'card', propertyName: 'barWeight' },\r\n cardPadding: <DataViewObjectPropertyIdentifier>{ objectName: 'card', propertyName: 'cardPadding' },\r\n cardBackground: <DataViewObjectPropertyIdentifier>{ objectName: 'card', propertyName: 'cardBackground' },\r\n\r\n }\r\n };\r\n}\n\n\n/** WEBPACK FOOTER **\n ** ./src/Clients/Visuals/capabilities/multiRowCard.capabilities.ts\n **/","/*\r\n * Power BI Visualizations\r\n *\r\n * Copyright (c) Microsoft Corporation\r\n * All rights reserved. \r\n * MIT License\r\n *\r\n * Permission is hereby granted, free of charge, to any person obtaining a copy\r\n * of this software and associated documentation files (the \"\"Software\"\"), to deal\r\n * in the Software without restriction, including without limitation the rights\r\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\r\n * copies of the Software, and to permit persons to whom the Software is\r\n * furnished to do so, subject to the following conditions:\r\n * \r\n * The above copyright notice and this permission notice shall be included in \r\n * all copies or substantial portions of the Software.\r\n * \r\n * THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR \r\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, \r\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE \r\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER \r\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\r\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\r\n * THE SOFTWARE.\r\n */\r\n\r\n/// <reference path=\"../_references.ts\"/>\r\n\r\nmodule powerbi.visuals {\r\n export const textboxCapabilities: VisualCapabilities = {\r\n objects: {\r\n general: {\r\n properties: {\r\n paragraphs: {\r\n type: { paragraphs: {} },\r\n suppressFormatPainterCopy: true,\r\n }\r\n }\r\n }\r\n },\r\n suppressDefaultTitle: true,\r\n supportsSelection: false,\r\n };\r\n}\n\n\n/** WEBPACK FOOTER **\n ** ./src/Clients/Visuals/capabilities/textbox.capabilities.ts\n **/","/*\r\n* Power BI Visualizations\r\n*\r\n* Copyright (c) Microsoft Corporation\r\n* All rights reserved. \r\n* MIT License\r\n*\r\n* Permission is hereby granted, free of charge, to any person obtaining a copy\r\n* of this software and associated documentation files (the \"\"Software\"\"), to deal\r\n* in the Software without restriction, including without limitation the rights\r\n* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\r\n* copies of the Software, and to permit persons to whom the Software is\r\n* furnished to do so, subject to the following conditions:\r\n* \r\n* The above copyright notice and this permission notice shall be included in \r\n* all copies or substantial portions of the Software.\r\n* \r\n* THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR \r\n* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, \r\n* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE \r\n* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER \r\n* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\r\n* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\r\n* THE SOFTWARE.\r\n*/\r\n\r\n/// <reference path=\"../_references.ts\"/>\r\n\r\nmodule powerbi.visuals {\r\n export const cheerMeterCapabilities: VisualCapabilities = {\r\n dataRoles: [\r\n {\r\n name: 'Category',\r\n kind: VisualDataRoleKind.Grouping,\r\n displayName: data.createDisplayNameGetter('Role_DisplayName_Axis'),\r\n description: data.createDisplayNameGetter('Role_DisplayName_AxisDescription')\r\n },\r\n {\r\n name: 'Y',\r\n kind: VisualDataRoleKind.Measure,\r\n requiredTypes: [{ numeric: true }, { integer: true }],\r\n displayName: data.createDisplayNameGetter('Role_DisplayName_Y'),\r\n description: data.createDisplayNameGetter('Role_DisplayName_YDescription')\r\n },\r\n ],\r\n dataViewMappings: [{\r\n categorical: {\r\n categories: {\r\n for: { in: 'Category' },\r\n },\r\n },\r\n }],\r\n objects: {\r\n dataPoint: {\r\n displayName: data.createDisplayNameGetter('Visual_DataPoint'),\r\n description: data.createDisplayNameGetter('Visual_DataPointDescription'),\r\n properties: {\r\n fill: StandardObjectProperties.fill,\r\n }\r\n }\r\n }\r\n };\r\n}\n\n\n/** WEBPACK FOOTER **\n ** ./src/Clients/Visuals/capabilities/sampleVisual.capabilities.ts\n **/","/*\r\n * Power BI Visualizations\r\n *\r\n * Copyright (c) Microsoft Corporation\r\n * All rights reserved.\r\n * MIT License\r\n *\r\n * Permission is hereby granted, free of charge, to any person obtaining a copy\r\n * of this software and associated documentation files (the \"\"Software\"\"), to deal\r\n * in the Software without restriction, including without limitation the rights\r\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\r\n * copies of the Software, and to permit persons to whom the Software is\r\n * furnished to do so, subject to the following conditions:\r\n *\r\n * The above copyright notice and this permission notice shall be included in\r\n * all copies or substantial portions of the Software.\r\n *\r\n * THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\r\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\r\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\r\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\r\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\r\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\r\n * THE SOFTWARE.\r\n */\r\n\r\n/// <reference path=\"../_references.ts\"/>\r\n\r\nmodule powerbi.visuals {\r\n export const scatterChartCapabilities: VisualCapabilities = {\r\n dataRoles: [\r\n {\r\n name: 'Category',\r\n kind: VisualDataRoleKind.Grouping,\r\n displayName: data.createDisplayNameGetter('Role_DisplayName_Details'),\r\n description: data.createDisplayNameGetter('Role_DisplayName_DetailsScatterChartDescription'),\r\n }, {\r\n name: 'Series',\r\n kind: VisualDataRoleKind.Grouping,\r\n displayName: data.createDisplayNameGetter('Role_DisplayName_Legend'),\r\n description: data.createDisplayNameGetter('Role_DisplayName_LegendDescription')\r\n }, {\r\n name: 'X',\r\n kind: VisualDataRoleKind.Measure,\r\n displayName: data.createDisplayNameGetter('Role_DisplayName_X'),\r\n description: data.createDisplayNameGetter('Role_DisplayName_XScatterChartDescription'),\r\n requiredTypes: [{ numeric: true }, { integer: true }],\r\n cartesianKind: CartesianRoleKind.X,\r\n }, {\r\n name: 'Y',\r\n kind: VisualDataRoleKind.Measure,\r\n displayName: data.createDisplayNameGetter('Role_DisplayName_Y'),\r\n description: data.createDisplayNameGetter('Role_DisplayName_YScatterChartDescription'),\r\n requiredTypes: [{ numeric: true }, { integer: true }],\r\n cartesianKind: CartesianRoleKind.Y,\r\n }, {\r\n name: 'Size',\r\n kind: VisualDataRoleKind.Measure,\r\n displayName: data.createDisplayNameGetter('Role_DisplayName_Size'),\r\n description: data.createDisplayNameGetter('Role_DisplayName_SizeDescription'),\r\n requiredTypes: [{ numeric: true }, { integer: true }],\r\n }, {\r\n name: 'Gradient',\r\n kind: VisualDataRoleKind.Measure,\r\n displayName: data.createDisplayNameGetter('Role_DisplayName_Gradient'),\r\n description: data.createDisplayNameGetter('Role_DisplayName_GradientDescription'),\r\n requiredTypes: [{ numeric: true }, { integer: true }],\r\n joinPredicate: JoinPredicateBehavior.None,\r\n }, {\r\n name: 'Play',\r\n kind: VisualDataRoleKind.Grouping,\r\n displayName: data.createDisplayNameGetter('Role_DisplayName_Play'),\r\n }\r\n ],\r\n objects: {\r\n dataPoint: {\r\n displayName: data.createDisplayNameGetter('Visual_DataPoint'),\r\n description: data.createDisplayNameGetter('Visual_DataPointDescription'),\r\n properties: {\r\n defaultColor: StandardObjectProperties.defaultColor,\r\n showAllDataPoints: StandardObjectProperties.showAllDataPoints,\r\n fill: StandardObjectProperties.fill,\r\n fillRule: {\r\n displayName: data.createDisplayNameGetter('Visual_Gradient'),\r\n type: { fillRule: {} },\r\n rule: {\r\n inputRole: 'Gradient',\r\n output: {\r\n property: 'fill',\r\n selector: ['Category'],\r\n },\r\n },\r\n }\r\n }\r\n },\r\n general: {\r\n displayName: data.createDisplayNameGetter('Visual_General'),\r\n properties: {\r\n formatString: StandardObjectProperties.formatString,\r\n },\r\n },\r\n trend: {\r\n displayName: data.createDisplayNameGetter('Visual_Trend_Line'),\r\n properties: {\r\n show: StandardObjectProperties.show,\r\n lineColor: {\r\n displayName: data.createDisplayNameGetter('Visual_Trend_Line_Color'),\r\n description: data.createDisplayNameGetter('Visual_Trend_Line_Color_Description'),\r\n type: { fill: { solid: { color: true } } }\r\n },\r\n transparency: {\r\n displayName: data.createDisplayNameGetter('Visual_Trend_Line_Transparency'),\r\n description: data.createDisplayNameGetter('Visual_Trend_Line_Transparency_Description'),\r\n type: { numeric: true }\r\n },\r\n style: {\r\n displayName: data.createDisplayNameGetter('Visual_Trend_Line_Style'),\r\n description: data.createDisplayNameGetter('Visual_Trend_Line_Style_Description'),\r\n type: { enumeration: lineStyle.type }\r\n },\r\n combineSeries: {\r\n displayName: data.createDisplayNameGetter('Visual_Trend_Line_Combine_Series'),\r\n description: data.createDisplayNameGetter('Visual_Trend_Line_Combine_Series_Description'),\r\n type: { bool: true }\r\n },\r\n }\r\n },\r\n categoryAxis: {\r\n displayName: data.createDisplayNameGetter('Visual_XAxis'),\r\n properties: {\r\n show: StandardObjectProperties.show,\r\n axisScale: StandardObjectProperties.axisScale,\r\n start: StandardObjectProperties.axisStart,\r\n end: StandardObjectProperties.axisEnd,\r\n showAxisTitle: {\r\n displayName: data.createDisplayNameGetter('Visual_Axis_Title'),\r\n description: data.createDisplayNameGetter('Visual_Axis_XTitleDescription'),\r\n type: { bool: true }\r\n },\r\n axisStyle: StandardObjectProperties.axisStyle,\r\n labelColor: StandardObjectProperties.labelColor,\r\n labelDisplayUnits: StandardObjectProperties.labelDisplayUnits,\r\n labelPrecision: StandardObjectProperties.labelPrecision,\r\n }\r\n },\r\n valueAxis: {\r\n displayName: data.createDisplayNameGetter('Visual_YAxis'),\r\n properties: {\r\n show: StandardObjectProperties.show,\r\n position: StandardObjectProperties.yAxisPosition,\r\n axisScale: StandardObjectProperties.axisScale,\r\n start: StandardObjectProperties.axisStart,\r\n end: StandardObjectProperties.axisEnd,\r\n showAxisTitle: {\r\n displayName: data.createDisplayNameGetter('Visual_Axis_Title'),\r\n description: data.createDisplayNameGetter('Visual_Axis_YTitleDescription'),\r\n type: { bool: true }\r\n },\r\n axisStyle: StandardObjectProperties.axisStyle,\r\n labelColor: StandardObjectProperties.labelColor,\r\n labelDisplayUnits: StandardObjectProperties.labelDisplayUnits,\r\n labelPrecision: StandardObjectProperties.labelPrecision,\r\n }\r\n },\r\n xAxisReferenceLine: {\r\n displayName: data.createDisplayNameGetter('Visual_Reference_Line_X'),\r\n description: data.createDisplayNameGetter('Visual_Reference_Line_Description'),\r\n properties: {\r\n show: StandardObjectProperties.show,\r\n value: {\r\n displayName: data.createDisplayNameGetter('Visual_Reference_Line_Value'),\r\n description: data.createDisplayNameGetter('Visual_Reference_Line_Value_Description'),\r\n type: { numeric: true }\r\n },\r\n lineColor: StandardObjectProperties.lineColor,\r\n transparency: {\r\n displayName: data.createDisplayNameGetter('Visual_Reference_Line_Transparency'),\r\n description: data.createDisplayNameGetter('Visual_Reference_Line_Transparency_Description'),\r\n type: { numeric: true }\r\n },\r\n style: StandardObjectProperties.referenceLineStyle,\r\n position: StandardObjectProperties.referenceLinePosition,\r\n dataLabelShow: StandardObjectProperties.dataLabelShow,\r\n dataLabelColor: StandardObjectProperties.dataLabelColor,\r\n dataLabelDecimalPoints: StandardObjectProperties.dataLabelDecimalPoints,\r\n dataLabelHorizontalPosition: StandardObjectProperties.dataLabelHorizontalPosition,\r\n dataLabelVerticalPosition: StandardObjectProperties.dataLabelVerticalPosition,\r\n dataLabelDisplayUnits: StandardObjectProperties.dataLabelDisplayUnits,\r\n },\r\n },\r\n y1AxisReferenceLine: {\r\n displayName: data.createDisplayNameGetter('Visual_Reference_Line_Y'),\r\n description: data.createDisplayNameGetter('Visual_Reference_Line_Description'),\r\n properties: {\r\n show: StandardObjectProperties.show,\r\n value: {\r\n displayName: data.createDisplayNameGetter('Visual_Reference_Line_Value'),\r\n description: data.createDisplayNameGetter('Visual_Reference_Line_Value_Description'),\r\n type: { numeric: true }\r\n },\r\n lineColor: StandardObjectProperties.lineColor,\r\n transparency: {\r\n displayName: data.createDisplayNameGetter('Visual_Reference_Line_Transparency'),\r\n description: data.createDisplayNameGetter('Visual_Reference_Line_Transparency_Description'),\r\n type: { numeric: true }\r\n },\r\n style: StandardObjectProperties.referenceLineStyle,\r\n position: StandardObjectProperties.referenceLinePosition,\r\n dataLabelShow: StandardObjectProperties.dataLabelShow,\r\n dataLabelColor: StandardObjectProperties.dataLabelColor,\r\n dataLabelDecimalPoints: StandardObjectProperties.dataLabelDecimalPoints,\r\n dataLabelHorizontalPosition: StandardObjectProperties.dataLabelHorizontalPosition,\r\n dataLabelVerticalPosition: StandardObjectProperties.dataLabelVerticalPosition,\r\n dataLabelDisplayUnits: StandardObjectProperties.dataLabelDisplayUnits,\r\n },\r\n },\r\n legend: {\r\n displayName: data.createDisplayNameGetter('Visual_Legend'),\r\n description: data.createDisplayNameGetter('Visual_LegendDescription'),\r\n properties: {\r\n show: StandardObjectProperties.show,\r\n position: StandardObjectProperties.legendPosition,\r\n showTitle: StandardObjectProperties.showLegendTitle,\r\n titleText: $.extend({}, StandardObjectProperties.legendTitle, {\r\n suppressFormatPainterCopy: true\r\n }),\r\n labelColor: StandardObjectProperties.labelColor,\r\n fontSize: StandardObjectProperties.fontSize,\r\n }\r\n },\r\n categoryLabels: {\r\n displayName: data.createDisplayNameGetter('Visual_CategoryLabels'),\r\n description: data.createDisplayNameGetter('Visual_CategoryLabelsDescription'),\r\n properties: {\r\n show: StandardObjectProperties.show,\r\n color: StandardObjectProperties.dataColor,\r\n fontSize: StandardObjectProperties.fontSize,\r\n },\r\n },\r\n colorBorder: {\r\n displayName: data.createDisplayNameGetter('Visual_ColorBorder'),\r\n properties: {\r\n show: StandardObjectProperties.show,\r\n },\r\n },\r\n fillPoint: {\r\n displayName: data.createDisplayNameGetter('Visual_FillPoint'),\r\n properties: {\r\n show: StandardObjectProperties.show,\r\n },\r\n },\r\n colorByCategory: {\r\n displayName: data.createDisplayNameGetter('Visual_ColorByCategory'),\r\n properties: {\r\n show: StandardObjectProperties.show,\r\n }\r\n },\r\n currentFrameIndex: {\r\n properties: {\r\n index: {\r\n type: { numeric: true },\r\n }\r\n }\r\n },\r\n plotArea: {\r\n displayName: data.createDisplayNameGetter('Visual_Plot'),\r\n //description: data.createDisplayNameGetter('Visual_PlotDescription'),\r\n properties: {\r\n transparency: StandardObjectProperties.transparency,\r\n image: StandardObjectProperties.image,\r\n },\r\n },\r\n },\r\n dataViewMappings: [{\r\n conditions: [\r\n { 'Category': { max: 1 }, 'Series': { max: 1 }, 'X': { max: 1 }, 'Y': { max: 1 }, 'Size': { max: 1 }, 'Gradient': { max: 0 }, 'Play': { max: 0 } },\r\n { 'Category': { max: 1 }, 'Series': { max: 0 }, 'X': { max: 1 }, 'Y': { max: 1 }, 'Size': { max: 1 }, 'Gradient': { max: 1 }, 'Play': { max: 0 } },\r\n ],\r\n categorical: {\r\n categories: {\r\n for: { in: 'Category' },\r\n },\r\n values: {\r\n group: {\r\n by: 'Series',\r\n select: [\r\n { bind: { to: 'X' } },\r\n { bind: { to: 'Y' } },\r\n { bind: { to: 'Size' } },\r\n { bind: { to: 'Gradient' } },\r\n ],\r\n }\r\n },\r\n rowCount: { preferred: { min: 2 } },\r\n dataReductionAlgorithm: { sample: {} },\r\n dataVolume: 4,\r\n }\r\n }, {\r\n conditions: [\r\n { 'Category': { max: 1 }, 'Series': { max: 1 }, 'X': { max: 1 }, 'Y': { max: 1 }, 'Size': { max: 1 }, 'Gradient': { max: 0 }, 'Play': { min: 1, max: 1 } },\r\n { 'Category': { max: 1 }, 'Series': { max: 0 }, 'X': { max: 1 }, 'Y': { max: 1 }, 'Size': { max: 1 }, 'Gradient': { max: 1 }, 'Play': { min: 1, max: 1 } },\r\n ],\r\n // Long term: consider adding the 'name' concept and have this be a reference to the other dataViewMapping above.\r\n // Then we'd also move the splitting logic of Matrix->Categorical[] into DataViewTransform, and other visuals would benefit.\r\n matrix: {\r\n rows: {\r\n select: [\r\n { bind: { to: 'Play' } },\r\n { bind: { to: 'Category' } },\r\n ],\r\n /* Explicitly override the server data reduction to make it appropriate for matrix/play. */\r\n dataReductionAlgorithm: { bottom: { count: 5000 } }\r\n },\r\n columns: {\r\n for: { in: 'Series' },\r\n /* Explicitly override the server data reduction to make it appropriate for matrix/play. */\r\n dataReductionAlgorithm: { top: { count: 60 } }\r\n },\r\n values: {\r\n select: [\r\n { bind: { to: 'X' } },\r\n { bind: { to: 'Y' } },\r\n { bind: { to: 'Size' } },\r\n ]\r\n }\r\n }\r\n }, {\r\n conditions: [\r\n { 'Category': { max: 1 }, 'Series': { max: 1 }, 'X': { max: 1 }, 'Y': { max: 1 }, 'Size': { max: 0 }, 'Gradient': { max: 0 }, 'Play': { max: 0 } },\r\n { 'Category': { max: 1 }, 'Series': { max: 0 }, 'X': { max: 1 }, 'Y': { max: 1 }, 'Size': { max: 0 }, 'Gradient': { max: 1 }, 'Play': { max: 0 } },\r\n ],\r\n requiredProperties: [{ objectName: 'trend', propertyName: 'show' }],\r\n usage: {\r\n regression: {\r\n combineSeries: { objectName: 'trend', propertyName: 'combineSeries' }\r\n },\r\n },\r\n categorical: {\r\n categories: {\r\n for: { in: 'regression.X' }\r\n },\r\n values: {\r\n group: {\r\n by: 'regression.Series',\r\n select: [{ for: { in: 'regression.Y' } }],\r\n },\r\n },\r\n dataReductionAlgorithm: { sample: {} },\r\n dataVolume: 4,\r\n }\r\n }],\r\n sorting: {\r\n custom: {},\r\n implicit: {\r\n clauses: [{ role: 'Play', direction: SortDirection.Ascending }] //typically a datetime field, sort asc\r\n },\r\n },\r\n drilldown: {\r\n roles: ['Category']\r\n },\r\n };\r\n\r\n export const scatterChartProps = {\r\n general: {\r\n formatString: <DataViewObjectPropertyIdentifier>{ objectName: 'general', propertyName: 'formatString' },\r\n },\r\n dataPoint: {\r\n defaultColor: <DataViewObjectPropertyIdentifier>{ objectName: 'dataPoint', propertyName: 'defaultColor' },\r\n fill: <DataViewObjectPropertyIdentifier>{ objectName: 'dataPoint', propertyName: 'fill' },\r\n },\r\n trend: {\r\n show: <DataViewObjectPropertyIdentifier>{ objectName: 'trend', propertyName: 'show' },\r\n },\r\n colorBorder: {\r\n show: <DataViewObjectPropertyIdentifier>{ objectName: 'colorBorder', propertyName: 'show' },\r\n },\r\n fillPoint: {\r\n show: <DataViewObjectPropertyIdentifier>{ objectName: 'fillPoint', propertyName: 'show' },\r\n },\r\n colorByCategory: {\r\n show: <DataViewObjectPropertyIdentifier>{ objectName: 'colorByCategory', propertyName: 'show' },\r\n },\r\n currentFrameIndex: {\r\n index: <DataViewObjectPropertyIdentifier>{ objectName: 'currentFrameIndex', propertyName: 'index' },\r\n },\r\n legend: {\r\n labelColor: <DataViewObjectPropertyIdentifier>{ objectName: 'legend', propertyName: 'labelColor' },\r\n },\r\n plotArea: {\r\n image: <DataViewObjectPropertyIdentifier>{ objectName: 'plotArea', propertyName: 'image' },\r\n transparency: <DataViewObjectPropertyIdentifier>{ objectName: 'plotArea', propertyName: 'transparency' },\r\n },\r\n };\r\n}\n\n\n/** WEBPACK FOOTER **\n ** ./src/Clients/Visuals/capabilities/scatterChart.capabilities.ts\n **/","/*\r\n * Power BI Visualizations\r\n *\r\n * Copyright (c) Microsoft Corporation\r\n * All rights reserved. \r\n * MIT License\r\n *\r\n * Permission is hereby granted, free of charge, to any person obtaining a copy\r\n * of this software and associated documentation files (the \"\"Software\"\"), to deal\r\n * in the Software without restriction, including without limitation the rights\r\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\r\n * copies of the Software, and to permit persons to whom the Software is\r\n * furnished to do so, subject to the following conditions:\r\n * \r\n * The above copyright notice and this permission notice shall be included in \r\n * all copies or substantial portions of the Software.\r\n * \r\n * THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR \r\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, \r\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE \r\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER \r\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\r\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\r\n * THE SOFTWARE.\r\n */\r\n\r\n/// <reference path=\"../_references.ts\"/>\r\n\r\nmodule powerbi.visuals {\r\n export const slicerCapabilities: VisualCapabilities = {\r\n dataRoles: [\r\n {\r\n name: 'Values',\r\n kind: VisualDataRoleKind.Grouping,\r\n displayName: powerbi.data.createDisplayNameGetter('Role_DisplayName_Field'),\r\n description: data.createDisplayNameGetter('Role_DisplayName_FieldDescription')\r\n }\r\n ],\r\n objects: {\r\n general: {\r\n displayName: data.createDisplayNameGetter('Visual_General'),\r\n properties: {\r\n filter: {\r\n type: { filter: {} },\r\n },\r\n selfFilter: {\r\n type: { filter: { selfFilter: true } },\r\n },\r\n defaultValue: {\r\n type: { expression: { defaultValue: true } },\r\n },\r\n formatString: StandardObjectProperties.formatString,\r\n outlineColor: StandardObjectProperties.outlineColor,\r\n outlineWeight: StandardObjectProperties.outlineWeight,\r\n orientation: {\r\n displayName: data.createDisplayNameGetter('Slicer_Orientation'),\r\n type: { enumeration: slicerOrientation.type }\r\n },\r\n count: {\r\n type: { integer: true }\r\n },\r\n selfFilterEnabled: {\r\n type: { operations: { searchEnabled: true } }\r\n },\r\n },\r\n },\r\n selection: {\r\n displayName: data.createDisplayNameGetter('Visual_SelectionControls'),\r\n properties: {\r\n selectAllCheckboxEnabled: {\r\n displayName: data.createDisplayNameGetter('Visual_SelectAll'),\r\n type: { bool: true }\r\n },\r\n singleSelect: {\r\n displayName: data.createDisplayNameGetter('Visual_SingleSelect'),\r\n type: { bool: true }\r\n }\r\n },\r\n },\r\n header: {\r\n displayName: data.createDisplayNameGetter('Visual_Header'),\r\n properties: {\r\n show: StandardObjectProperties.show,\r\n fontColor: StandardObjectProperties.fontColor,\r\n background: {\r\n displayName: data.createDisplayNameGetter('Visual_Background'),\r\n type: { fill: { solid: { color: true } } }\r\n },\r\n outline: StandardObjectProperties.outline,\r\n textSize: {\r\n displayName: data.createDisplayNameGetter('Visual_TextSize'),\r\n type: { numeric: true }\r\n },\r\n }\r\n },\r\n items: {\r\n displayName: data.createDisplayNameGetter('Role_DisplayName_Items'),\r\n properties: {\r\n fontColor: StandardObjectProperties.fontColor,\r\n background: {\r\n displayName: data.createDisplayNameGetter('Visual_Background'),\r\n type: { fill: { solid: { color: true } } }\r\n },\r\n outline: StandardObjectProperties.outline,\r\n textSize: {\r\n displayName: data.createDisplayNameGetter('Visual_TextSize'),\r\n type: { numeric: true }\r\n },\r\n }\r\n }\r\n },\r\n dataViewMappings: [{\r\n conditions: [{ 'Values': { max: 1 } }],\r\n categorical: {\r\n categories: {\r\n for: { in: 'Values' },\r\n dataReductionAlgorithm: { window: {} }\r\n },\r\n includeEmptyGroups: true,\r\n }\r\n }],\r\n\r\n sorting: {\r\n default: {},\r\n },\r\n suppressDefaultTitle: true,\r\n disableVisualDetails: true,\r\n };\r\n\r\n // TODO: Generate these from above, defining twice just introduces potential for error\r\n export const slicerProps = {\r\n general: {\r\n outlineColor: <DataViewObjectPropertyIdentifier>{ objectName: 'general', propertyName: 'outlineColor' },\r\n outlineWeight: <DataViewObjectPropertyIdentifier>{ objectName: 'general', propertyName: 'outlineWeight' },\r\n orientation: <DataViewObjectPropertyIdentifier>{ objectName: 'general', propertyName: 'orientation' },\r\n count: <DataViewObjectPropertyIdentifier>{ objectName: 'general', propertyName: 'count' },\r\n selfFilterEnabled: <DataViewObjectPropertyIdentifier>{ objectName: 'general', propertyName: 'selfFilterEnabled' },\r\n },\r\n selection: {\r\n selectAllCheckboxEnabled: <DataViewObjectPropertyIdentifier>{ objectName: 'selection', propertyName: 'selectAllCheckboxEnabled' },\r\n singleSelect: <DataViewObjectPropertyIdentifier>{ objectName: 'selection', propertyName: 'singleSelect' }\r\n },\r\n header: {\r\n show: <DataViewObjectPropertyIdentifier>{ objectName: 'header', propertyName: 'show' },\r\n fontColor: <DataViewObjectPropertyIdentifier>{ objectName: 'header', propertyName: 'fontColor' },\r\n background: <DataViewObjectPropertyIdentifier>{ objectName: 'header', propertyName: 'background' },\r\n outline: <DataViewObjectPropertyIdentifier>{ objectName: 'header', propertyName: 'outline' },\r\n textSize: <DataViewObjectPropertyIdentifier>{ objectName: 'header', propertyName: 'textSize' },\r\n },\r\n items: {\r\n fontColor: <DataViewObjectPropertyIdentifier>{ objectName: 'items', propertyName: 'fontColor' },\r\n background: <DataViewObjectPropertyIdentifier>{ objectName: 'items', propertyName: 'background' },\r\n outline: <DataViewObjectPropertyIdentifier>{ objectName: 'items', propertyName: 'outline' },\r\n textSize: <DataViewObjectPropertyIdentifier>{ objectName: 'items', propertyName: 'textSize' },\r\n },\r\n filterPropertyIdentifier: <DataViewObjectPropertyIdentifier> { objectName: 'general', propertyName: 'filter' },\r\n selfFilterPropertyIdentifier: <DataViewObjectPropertyIdentifier>{ objectName: 'general', propertyName: 'selfFilter' },\r\n formatString: <DataViewObjectPropertyIdentifier> { objectName: 'general', propertyName: 'formatString' },\r\n defaultValue: <DataViewObjectPropertyIdentifier>{ objectName: 'general', propertyName: 'defaultValue' },\r\n };\r\n}\n\n\n/** WEBPACK FOOTER **\n ** ./src/Clients/Visuals/capabilities/slicer.capabilities.ts\n **/","/*\r\n * Power BI Visualizations\r\n *\r\n * Copyright (c) Microsoft Corporation\r\n * All rights reserved.\r\n * MIT License\r\n *\r\n * Permission is hereby granted, free of charge, to any person obtaining a copy\r\n * of this software and associated documentation files (the \"\"Software\"\"), to deal\r\n * in the Software without restriction, including without limitation the rights\r\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\r\n * copies of the Software, and to permit persons to whom the Software is\r\n * furnished to do so, subject to the following conditions:\r\n *\r\n * The above copyright notice and this permission notice shall be included in\r\n * all copies or substantial portions of the Software.\r\n *\r\n * THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\r\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\r\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\r\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\r\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\r\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\r\n * THE SOFTWARE.\r\n */\r\n\r\n/// <reference path=\"../_references.ts\"/>\r\n\r\nmodule powerbi.visuals {\r\n export const tableCapabilities: VisualCapabilities = {\r\n dataRoles: [{\r\n name: 'Values',\r\n kind: VisualDataRoleKind.GroupingOrMeasure,\r\n displayName: data.createDisplayNameGetter('Role_DisplayName_Values'),\r\n }],\r\n objects: {\r\n general: {\r\n displayName: data.createDisplayNameGetter('Visual_General'),\r\n properties: {\r\n formatString: StandardObjectProperties.formatString,\r\n columnWidth: {\r\n type: { numeric: true }\r\n },\r\n totals: {\r\n type: { bool: true },\r\n displayName: data.createDisplayNameGetter('Visual_Totals'),\r\n suppressFormatPainterCopy: true,\r\n },\r\n autoSizeColumnWidth: {\r\n type: { bool: true },\r\n displayName: data.createDisplayNameGetter('Visual_Adjust_Column_Width'),\r\n suppressFormatPainterCopy: true,\r\n },\r\n textSize: {\r\n displayName: data.createDisplayNameGetter('Visual_TextSize'),\r\n type: { numeric: true }\r\n },\r\n },\r\n },\r\n\r\n grid: {\r\n displayName: data.createDisplayNameGetter('Visual_Grid'),\r\n properties: {\r\n outlineColor: StandardObjectProperties.outlineColor,\r\n outlineWeight: StandardObjectProperties.outlineWeight,\r\n gridVertical: {\r\n displayName: data.createDisplayNameGetter('Visual_GridVertical'),\r\n description: data.createDisplayNameGetter('Visual_GridVertical_Desc'),\r\n type: { bool: true }\r\n },\r\n gridVerticalColor: {\r\n displayName: data.createDisplayNameGetter('Visual_GridVertical_Color'),\r\n description: data.createDisplayNameGetter('Visual_GridVertical_Color_Desc'),\r\n type: { fill: { solid: { color: true } } }\r\n },\r\n gridVerticalWeight: {\r\n displayName: data.createDisplayNameGetter('Visual_GridVertical_Weight'),\r\n description: data.createDisplayNameGetter('Visual_GridVertical_Weight_Desc'),\r\n type: { numeric: true }\r\n },\r\n gridHorizontal: {\r\n displayName: data.createDisplayNameGetter('Visual_GridHorizontal'),\r\n description: data.createDisplayNameGetter('Visual_GridHorizontal_Desc'),\r\n type: { bool: true }\r\n },\r\n gridHorizontalColor: {\r\n displayName: data.createDisplayNameGetter('Visual_GridHorizontal_Color'),\r\n description: data.createDisplayNameGetter('Visual_GridHorizontal_Color_Desc'),\r\n type: { fill: { solid: { color: true } } }\r\n },\r\n gridHorizontalWeight: {\r\n displayName: data.createDisplayNameGetter('Visual_GridHorizontal_Weight'),\r\n description: data.createDisplayNameGetter('Visual_GridHorizontal_Weight_Desc'),\r\n type: { numeric: true }\r\n },\r\n rowPadding: {\r\n displayName: data.createDisplayNameGetter('Visual_RowPadding'),\r\n description: data.createDisplayNameGetter('Visual_RowPadding_Desc'),\r\n type: { numeric: true }\r\n },\r\n imageHeight: {\r\n displayName: data.createDisplayNameGetter('Visual_Tablix_ImageHeight'),\r\n description: data.createDisplayNameGetter('Visual_Tablix_ImageHeight_Desc'),\r\n type: { numeric: true }\r\n },\r\n }\r\n },\r\n\r\n columnHeaders: {\r\n displayName: data.createDisplayNameGetter('Visual_Tablix_ColumnHeaders'),\r\n properties: {\r\n outline: StandardObjectProperties.outline,\r\n fontColor: StandardObjectProperties.fontColor,\r\n backColor: StandardObjectProperties.backColor,\r\n }\r\n },\r\n\r\n values: {\r\n displayName: data.createDisplayNameGetter('Visual_Tablix_Values'),\r\n properties: {\r\n outline: StandardObjectProperties.outline,\r\n // backColor overrides backColorPrimary and Secondary (e.g., in the case of conditional formatting)\r\n backColor: {\r\n type: { fill: { solid: { color: true } } }\r\n },\r\n fontColorPrimary: {\r\n displayName: data.createDisplayNameGetter('Visual_Tablix_FontColorPrimary'),\r\n description: data.createDisplayNameGetter('Visual_Tablix_FontColorPrimary_Desc'),\r\n type: { fill: { solid: { color: true } } }\r\n },\r\n backColorPrimary: {\r\n displayName: data.createDisplayNameGetter('Visual_Tablix_BackColorPrimary'),\r\n description: data.createDisplayNameGetter('Visual_Tablix_BackColorPrimary_Desc'),\r\n type: { fill: { solid: { color: true } } }\r\n },\r\n fontColorSecondary: {\r\n displayName: data.createDisplayNameGetter('Visual_Tablix_FontColorSecondary'),\r\n description: data.createDisplayNameGetter('Visual_Tablix_FontColorSecondary_Desc'),\r\n type: { fill: { solid: { color: true } } }\r\n },\r\n backColorSecondary: {\r\n displayName: data.createDisplayNameGetter('Visual_Tablix_BackColorSecondary'),\r\n description: data.createDisplayNameGetter('Visual_Tablix_BackColorSecondary_Desc'),\r\n type: { fill: { solid: { color: true } } }\r\n },\r\n urlIcon: {\r\n displayName: data.createDisplayNameGetter('Visual_Tablix_UrlIcon'),\r\n description: data.createDisplayNameGetter('Visual_Tablix_UrlIcon_Desc'),\r\n type: { bool: true }\r\n },\r\n }\r\n },\r\n\r\n total: {\r\n displayName: data.createDisplayNameGetter('Visual_Tablix_TotalGrand'),\r\n properties: {\r\n outline: StandardObjectProperties.outline,\r\n fontColor: StandardObjectProperties.fontColor,\r\n backColor: StandardObjectProperties.backColor,\r\n }\r\n },\r\n },\r\n dataViewMappings: [{\r\n table: {\r\n rows: {\r\n for: { in: 'Values' },\r\n dataReductionAlgorithm: { window: { count: 500 } }\r\n },\r\n rowCount: { preferred: { min: 1 } }\r\n },\r\n }],\r\n sorting: {\r\n custom: {},\r\n },\r\n suppressDefaultTitle: true,\r\n supportsSelection: false,\r\n disableVisualDetails: true,\r\n };\r\n}\n\n\n/** WEBPACK FOOTER **\n ** ./src/Clients/Visuals/capabilities/table.capabilities.ts\n **/","/*\r\n * Power BI Visualizations\r\n *\r\n * Copyright (c) Microsoft Corporation\r\n * All rights reserved. \r\n * MIT License\r\n *\r\n * Permission is hereby granted, free of charge, to any person obtaining a copy\r\n * of this software and associated documentation files (the \"\"Software\"\"), to deal\r\n * in the Software without restriction, including without limitation the rights\r\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\r\n * copies of the Software, and to permit persons to whom the Software is\r\n * furnished to do so, subject to the following conditions:\r\n * \r\n * The above copyright notice and this permission notice shall be included in \r\n * all copies or substantial portions of the Software.\r\n * \r\n * THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR \r\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, \r\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE \r\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER \r\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\r\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\r\n * THE SOFTWARE.\r\n */\r\n\r\n/// <reference path=\"../_references.ts\"/>\r\n\r\nmodule powerbi.visuals {\r\n export const matrixRoleNames = {\r\n rows: 'Rows',\r\n columns: 'Columns',\r\n values: 'Values',\r\n };\r\n\r\n export const matrixCapabilities: VisualCapabilities = {\r\n dataRoles: [\r\n {\r\n name: matrixRoleNames.rows,\r\n kind: VisualDataRoleKind.Grouping,\r\n displayName: data.createDisplayNameGetter('Role_DisplayName_Rows'),\r\n description: data.createDisplayNameGetter('Role_DisplayName_RowsDescription')\r\n }, {\r\n name: matrixRoleNames.columns,\r\n kind: VisualDataRoleKind.Grouping,\r\n displayName: data.createDisplayNameGetter('Role_DisplayName_Columns'),\r\n description: data.createDisplayNameGetter('Role_DisplayName_ColumnsDescription')\r\n }, {\r\n name: matrixRoleNames.values,\r\n kind: VisualDataRoleKind.Measure,\r\n displayName: data.createDisplayNameGetter('Role_DisplayName_Values')\r\n }\r\n ],\r\n objects: {\r\n general: {\r\n displayName: data.createDisplayNameGetter('Visual_General'),\r\n properties: {\r\n formatString: StandardObjectProperties.formatString,\r\n columnWidth: {\r\n type: { numeric: true }\r\n },\r\n rowSubtotals: {\r\n type: { bool: true },\r\n displayName: data.createDisplayNameGetter('Visual_TotalRow'),\r\n suppressFormatPainterCopy: true,\r\n },\r\n columnSubtotals: {\r\n type: { bool: true },\r\n displayName: data.createDisplayNameGetter('Visual_TotalColumn'),\r\n suppressFormatPainterCopy: true,\r\n },\r\n autoSizeColumnWidth: {\r\n type: { bool: true },\r\n displayName: data.createDisplayNameGetter('Visual_Adjust_Column_Width'),\r\n suppressFormatPainterCopy: true,\r\n },\r\n textSize: {\r\n displayName: data.createDisplayNameGetter('Visual_TextSize'),\r\n type: { numeric: true }\r\n },\r\n },\r\n },\r\n\r\n grid: {\r\n displayName: data.createDisplayNameGetter('Visual_Grid'),\r\n properties: {\r\n outlineColor: StandardObjectProperties.outlineColor,\r\n outlineWeight: StandardObjectProperties.outlineWeight,\r\n gridVertical: {\r\n displayName: data.createDisplayNameGetter('Visual_GridVertical'),\r\n description: data.createDisplayNameGetter('Visual_GridVertical_Desc'),\r\n type: { bool: true }\r\n },\r\n gridVerticalColor: {\r\n displayName: data.createDisplayNameGetter('Visual_GridVertical_Color'),\r\n description: data.createDisplayNameGetter('Visual_GridVertical_Color_Desc'),\r\n type: { fill: { solid: { color: true } } }\r\n },\r\n gridVerticalWeight: {\r\n displayName: data.createDisplayNameGetter('Visual_GridVertical_Weight'),\r\n description: data.createDisplayNameGetter('Visual_GridVertical_Weight_Desc'),\r\n type: { numeric: true }\r\n },\r\n gridHorizontal: {\r\n displayName: data.createDisplayNameGetter('Visual_GridHorizontal'),\r\n description: data.createDisplayNameGetter('Visual_GridHorizontal_Desc'),\r\n type: { bool: true }\r\n },\r\n gridHorizontalColor: {\r\n displayName: data.createDisplayNameGetter('Visual_GridHorizontal_Color'),\r\n description: data.createDisplayNameGetter('Visual_GridHorizontal_Color_Desc'),\r\n type: { fill: { solid: { color: true } } }\r\n },\r\n gridHorizontalWeight: {\r\n displayName: data.createDisplayNameGetter('Visual_GridHorizontal_Weight'),\r\n description: data.createDisplayNameGetter('Visual_GridHorizontal_Weight_Desc'),\r\n type: { numeric: true }\r\n },\r\n rowPadding: {\r\n displayName: data.createDisplayNameGetter('Visual_RowPadding'),\r\n description: data.createDisplayNameGetter('Visual_RowPadding_Desc'),\r\n type: { numeric: true }\r\n },\r\n imageHeight: {\r\n displayName: data.createDisplayNameGetter('Visual_Tablix_ImageHeight'),\r\n description: data.createDisplayNameGetter('Visual_Tablix_ImageHeight_Desc'),\r\n type: { numeric: true }\r\n },\r\n },\r\n },\r\n\r\n columnHeaders: {\r\n displayName: data.createDisplayNameGetter('Visual_Tablix_ColumnHeaders'),\r\n properties: {\r\n outline: StandardObjectProperties.outline,\r\n fontColor: StandardObjectProperties.fontColor,\r\n backColor: StandardObjectProperties.backColor,\r\n }\r\n },\r\n\r\n rowHeaders:\r\n {\r\n displayName: data.createDisplayNameGetter('Visual_Tablix_RowHeaders'),\r\n properties: {\r\n outline: StandardObjectProperties.outline,\r\n fontColor: StandardObjectProperties.fontColor,\r\n backColor: StandardObjectProperties.backColor,\r\n }\r\n },\r\n values:\r\n {\r\n displayName: data.createDisplayNameGetter('Visual_Tablix_Values'),\r\n properties: {\r\n outline: StandardObjectProperties.outline,\r\n fontColorPrimary: {\r\n displayName: data.createDisplayNameGetter('Visual_Tablix_FontColorPrimary'),\r\n description: data.createDisplayNameGetter('Visual_Tablix_FontColorPrimary_Desc'),\r\n type: { fill: { solid: { color: true } } }\r\n },\r\n backColorPrimary: {\r\n displayName: data.createDisplayNameGetter('Visual_Tablix_BackColorPrimary'),\r\n description: data.createDisplayNameGetter('Visual_Tablix_BackColorPrimary_Desc'),\r\n type: { fill: { solid: { color: true } } }\r\n },\r\n fontColorSecondary: {\r\n displayName: data.createDisplayNameGetter('Visual_Tablix_FontColorSecondary'),\r\n description: data.createDisplayNameGetter('Visual_Tablix_FontColorSecondary_Desc'),\r\n type: { fill: { solid: { color: true } } }\r\n },\r\n backColorSecondary: {\r\n displayName: data.createDisplayNameGetter('Visual_Tablix_BackColorSecondary'),\r\n description: data.createDisplayNameGetter('Visual_Tablix_BackColorSecondary_Desc'),\r\n type: { fill: { solid: { color: true } } }\r\n },\r\n }\r\n },\r\n subTotals:\r\n {\r\n displayName: data.createDisplayNameGetter('Visual_Tablix_TotalSub'),\r\n properties: {\r\n outline: StandardObjectProperties.outline,\r\n fontColor: StandardObjectProperties.fontColor,\r\n backColor: StandardObjectProperties.backColor,\r\n }\r\n },\r\n },\r\n dataViewMappings: [{\r\n conditions: [\r\n { 'Rows': { max: 0 }, 'Columns': { max: 0 }, 'Values': { min: 1 } },\r\n { 'Rows': { min: 1 }, 'Columns': { min: 0 }, 'Values': { min: 0 } },\r\n { 'Rows': { min: 0 }, 'Columns': { min: 1 }, 'Values': { min: 0 } }\r\n ],\r\n matrix: {\r\n rows: {\r\n for: { in: 'Rows' },\r\n /* Explicitly override the server data reduction to make it appropriate for matrix. */\r\n dataReductionAlgorithm: { window: { count: 500 } }\r\n },\r\n columns: {\r\n for: { in: 'Columns' },\r\n /* Explicitly override the server data reduction to make it appropriate for matrix. */\r\n dataReductionAlgorithm: { top: { count: 100 } }\r\n },\r\n values: {\r\n for: { in: 'Values' }\r\n }\r\n }\r\n }],\r\n filterMappings: {\r\n measureFilter: {\r\n targetRoles: [matrixRoleNames.rows]\r\n }\r\n },\r\n sorting: {\r\n custom: {},\r\n },\r\n suppressDefaultTitle: true,\r\n supportsSelection: false,\r\n disableVisualDetails: true,\r\n };\r\n}\n\n\n/** WEBPACK FOOTER **\n ** ./src/Clients/Visuals/capabilities/matrix.capabilities.ts\n **/","/*\r\n * Power BI Visualizations\r\n *\r\n * Copyright (c) Microsoft Corporation\r\n * All rights reserved. \r\n * MIT License\r\n *\r\n * Permission is hereby granted, free of charge, to any person obtaining a copy\r\n * of this software and associated documentation files (the \"\"Software\"\"), to deal\r\n * in the Software without restriction, including without limitation the rights\r\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\r\n * copies of the Software, and to permit persons to whom the Software is\r\n * furnished to do so, subject to the following conditions:\r\n * \r\n * The above copyright notice and this permission notice shall be included in \r\n * all copies or substantial portions of the Software.\r\n * \r\n * THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR \r\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, \r\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE \r\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER \r\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\r\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\r\n * THE SOFTWARE.\r\n */\r\n\r\n/// <reference path=\"../_references.ts\"/>\r\n\r\nmodule powerbi.visuals {\r\n \r\n export const treemapRoles = {\r\n group: 'Group',\r\n details: 'Details',\r\n values: 'Values',\r\n gradient: 'Gradient'\r\n };\r\n \r\n export const treemapCapabilities: VisualCapabilities = {\r\n dataRoles: [\r\n {\r\n name: 'Group',\r\n kind: VisualDataRoleKind.Grouping,\r\n displayName: data.createDisplayNameGetter('Role_DisplayName_Group'),\r\n description: data.createDisplayNameGetter('Role_DisplayName_GroupTreemapDescription')\r\n }, {\r\n name: 'Details',\r\n kind: VisualDataRoleKind.Grouping,\r\n displayName: data.createDisplayNameGetter('Role_DisplayName_Details'),\r\n }, {\r\n name: 'Values',\r\n kind: VisualDataRoleKind.Measure,\r\n displayName: data.createDisplayNameGetter('Role_DisplayName_Values'),\r\n description: data.createDisplayNameGetter('Role_DisplayName_ValuesDescription'),\r\n requiredTypes: [{ numeric: true }, { integer: true }],\r\n }, {\r\n name: 'Gradient',\r\n kind: VisualDataRoleKind.Measure,\r\n displayName: data.createDisplayNameGetter('Role_DisplayName_Gradient'),\r\n description: data.createDisplayNameGetter('Role_DisplayName_GradientDescription'),\r\n requiredTypes: [{ numeric: true }, { integer: true }],\r\n joinPredicate: JoinPredicateBehavior.None,\r\n }\r\n ],\r\n objects: {\r\n general: {\r\n displayName: data.createDisplayNameGetter('Visual_General'),\r\n properties: {\r\n formatString: StandardObjectProperties.formatString,\r\n },\r\n },\r\n legend: {\r\n displayName: data.createDisplayNameGetter('Visual_Legend'),\r\n description: data.createDisplayNameGetter('Visual_LegendDescription'),\r\n properties: {\r\n show: StandardObjectProperties.show,\r\n position: StandardObjectProperties.legendPosition,\r\n showTitle: StandardObjectProperties.showLegendTitle,\r\n titleText: StandardObjectProperties.legendTitle,\r\n labelColor: StandardObjectProperties.labelColor,\r\n fontSize: StandardObjectProperties.fontSize,\r\n }\r\n },\r\n dataPoint: {\r\n displayName: data.createDisplayNameGetter('Visual_DataPoint'),\r\n description: data.createDisplayNameGetter('Visual_DataPointDescription'),\r\n properties: { \r\n fill: {\r\n type: { fill: { solid: { color: true } } }\r\n },\r\n fillRule: {\r\n displayName: data.createDisplayNameGetter('Visual_Gradient'),\r\n type: { fillRule: {} },\r\n rule: {\r\n inputRole: 'Gradient',\r\n output: {\r\n property: 'fill',\r\n selector: ['Group'],\r\n }\r\n }\r\n }\r\n }\r\n },\r\n labels: {\r\n displayName: data.createDisplayNameGetter('Visual_DataPointsLabels'),\r\n description: data.createDisplayNameGetter('Visual_DataPointsLabelsDescription'),\r\n properties: {\r\n show: StandardObjectProperties.show,\r\n color: StandardObjectProperties.dataColor,\r\n labelDisplayUnits: StandardObjectProperties.labelDisplayUnits,\r\n labelPrecision: StandardObjectProperties.labelPrecision,\r\n }\r\n },\r\n categoryLabels: {\r\n displayName: data.createDisplayNameGetter('Visual_CategoryLabels'),\r\n description: data.createDisplayNameGetter('Visual_CategoryLabelsDescription'),\r\n properties: {\r\n show: StandardObjectProperties.show,\r\n },\r\n },\r\n },\r\n dataViewMappings: [{\r\n conditions: [\r\n { 'Group': { max: 1 }, 'Details': { max: 0 }, 'Gradient': { max: 1 } },\r\n { 'Group': { max: 1 }, 'Details': { min: 1, max: 1 }, 'Values': { max: 1 }, 'Gradient': { max: 0 } }\r\n ],\r\n categorical: {\r\n categories: {\r\n for: { in: 'Group' },\r\n dataReductionAlgorithm: { top: {} }\r\n },\r\n values: {\r\n group: {\r\n by: 'Details',\r\n select: [{ bind: { to: 'Values' } }, { bind: { to: 'Gradient' } }],\r\n dataReductionAlgorithm: { top: {} }\r\n }\r\n },\r\n rowCount: { preferred: { min: 2 } }\r\n }\r\n }],\r\n supportsHighlight: true,\r\n sorting: {\r\n custom: {},\r\n implicit: {\r\n clauses: [{ role: 'Values', direction: SortDirection.Descending }]\r\n },\r\n },\r\n drilldown: {\r\n roles: ['Group']\r\n },\r\n };\r\n \r\n export const treemapProps = {\r\n general: {\r\n formatString: <DataViewObjectPropertyIdentifier>{ objectName: 'general', propertyName: 'formatString' },\r\n },\r\n dataPoint: {\r\n fill: <DataViewObjectPropertyIdentifier>{ objectName: 'dataPoint', propertyName: 'fill' },\r\n },\r\n legend: {\r\n show: <DataViewObjectPropertyIdentifier>{ objectName: 'legend', propertyName: 'show' },\r\n position: <DataViewObjectPropertyIdentifier>{ objectName: 'legend', propertyName: 'position' },\r\n showTitle: <DataViewObjectPropertyIdentifier>{ objectName: 'legend', propertyName: 'showTitle' },\r\n titleText: <DataViewObjectPropertyIdentifier>{ objectName: 'legend', propertyName: 'titleText' },\r\n labelColor: <DataViewObjectPropertyIdentifier>{ objectName: 'legend', propertyName: 'labelColor' },\r\n },\r\n labels: {\r\n show: <DataViewObjectPropertyIdentifier>{ objectName: 'labels', propertyName: 'show' },\r\n color: <DataViewObjectPropertyIdentifier>{ objectName: 'labels', propertyName: 'color' },\r\n labelDisplayUnits: <DataViewObjectPropertyIdentifier>{ objectName: 'labels', propertyName: 'labelDisplayUnits' },\r\n labelPrecision: <DataViewObjectPropertyIdentifier>{ objectName: 'labels', propertyName: 'labelPrecision' },\r\n },\r\n categoryLabels: {\r\n show: <DataViewObjectPropertyIdentifier>{ objectName: 'categoryLabels', propertyName: 'show' },\r\n },\r\n };\r\n}\n\n\n/** WEBPACK FOOTER **\n ** ./src/Clients/Visuals/capabilities/treemap.capabilities.ts\n **/","/*\r\n * Power BI Visualizations\r\n *\r\n * Copyright (c) Microsoft Corporation\r\n * All rights reserved. \r\n * MIT License\r\n *\r\n * Permission is hereby granted, free of charge, to any person obtaining a copy\r\n * of this software and associated documentation files (the \"\"Software\"\"), to deal\r\n * in the Software without restriction, including without limitation the rights\r\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\r\n * copies of the Software, and to permit persons to whom the Software is\r\n * furnished to do so, subject to the following conditions:\r\n * \r\n * The above copyright notice and this permission notice shall be included in \r\n * all copies or substantial portions of the Software.\r\n * \r\n * THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR \r\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, \r\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE \r\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER \r\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\r\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\r\n * THE SOFTWARE.\r\n */\r\n\r\n/// <reference path=\"../_references.ts\"/>\r\n\r\nmodule powerbi.visuals {\r\n export const cardCapabilities: VisualCapabilities = {\r\n dataRoles: [\r\n {\r\n name: 'Values',\r\n kind: VisualDataRoleKind.Measure,\r\n displayName: data.createDisplayNameGetter('Role_DisplayName_Fields'),\r\n description: data.createDisplayNameGetter('Role_DisplayName_FieldsDescription'),\r\n }\r\n ],\r\n objects: {\r\n general: {\r\n properties: {\r\n formatString: StandardObjectProperties.formatString,\r\n },\r\n },\r\n labels: {\r\n displayName: data.createDisplayNameGetter('Visual_DataPointLabel'),\r\n properties: {\r\n color: StandardObjectProperties.dataColor,\r\n labelDisplayUnits: StandardObjectProperties.labelDisplayUnits,\r\n labelPrecision: StandardObjectProperties.labelPrecision,\r\n // NOTE: Consider adding a ValueType for fontSize.\r\n fontSize: StandardObjectProperties.fontSize,\r\n },\r\n },\r\n categoryLabels: {\r\n displayName: data.createDisplayNameGetter('Visual_CategoryLabel'),\r\n properties: {\r\n show: StandardObjectProperties.show,\r\n color: StandardObjectProperties.dataColor,\r\n // NOTE: Consider adding a ValueType for fontSize.\r\n fontSize: StandardObjectProperties.fontSize,\r\n },\r\n },\r\n wordWrap: {\r\n displayName: data.createDisplayNameGetter('Visual_WordWrap'),\r\n properties: {\r\n show: StandardObjectProperties.show,\r\n },\r\n },\r\n },\r\n dataViewMappings: [{\r\n conditions: [\r\n { 'Values': { max: 1 } }\r\n ],\r\n single: { role: \"Values\" }\r\n }],\r\n suppressDefaultTitle: true,\r\n supportsSelection: false,\r\n };\r\n\r\n export var cardProps = {\r\n categoryLabels: {\r\n show: <DataViewObjectPropertyIdentifier>{ objectName: 'categoryLabels', propertyName: 'show' },\r\n color: <DataViewObjectPropertyIdentifier>{ objectName: 'categoryLabels', propertyName: 'color' },\r\n fontSize: <DataViewObjectPropertyIdentifier>{ objectName: 'categoryLabels', propertyName: 'fontSize' },\r\n },\r\n labels: {\r\n color: <DataViewObjectPropertyIdentifier>{ objectName: 'labels', propertyName: 'color' },\r\n labelPrecision: <DataViewObjectPropertyIdentifier>{ objectName: 'labels', propertyName: 'labelPrecision' },\r\n labelDisplayUnits: <DataViewObjectPropertyIdentifier>{ objectName: 'labels', propertyName: 'labelDisplayUnits' },\r\n fontSize: <DataViewObjectPropertyIdentifier>{ objectName: 'labels', propertyName: 'fontSize' },\r\n },\r\n wordWrap: {\r\n show: <DataViewObjectPropertyIdentifier>{ objectName: 'wordWrap', propertyName: 'show' },\r\n },\r\n };\r\n\r\n} \n\n\n/** WEBPACK FOOTER **\n ** ./src/Clients/Visuals/capabilities/card.capabilities.ts\n **/","/*\r\n * Power BI Visualizations\r\n *\r\n * Copyright (c) Microsoft Corporation\r\n * All rights reserved. \r\n * MIT License\r\n *\r\n * Permission is hereby granted, free of charge, to any person obtaining a copy\r\n * of this software and associated documentation files (the \"\"Software\"\"), to deal\r\n * in the Software without restriction, including without limitation the rights\r\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\r\n * copies of the Software, and to permit persons to whom the Software is\r\n * furnished to do so, subject to the following conditions:\r\n * \r\n * The above copyright notice and this permission notice shall be included in \r\n * all copies or substantial portions of the Software.\r\n * \r\n * THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR \r\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, \r\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE \r\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER \r\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\r\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\r\n * THE SOFTWARE.\r\n */\r\n\r\n/// <reference path=\"../_references.ts\"/>\r\n\r\nmodule powerbi.visuals {\r\n export const waterfallChartCapabilities: VisualCapabilities = {\r\n dataRoles: [\r\n {\r\n name: 'Category',\r\n kind: VisualDataRoleKind.Grouping,\r\n displayName: data.createDisplayNameGetter('Role_DisplayName_Category'),\r\n description: data.createDisplayNameGetter('Role_DisplayName_CategoryWaterfallDescription')\r\n }, {\r\n name: 'Y',\r\n kind: VisualDataRoleKind.Measure,\r\n requiredTypes: [{ numeric: true }, { integer: true }],\r\n displayName: data.createDisplayNameGetter('Role_DisplayName_Y'),\r\n }\r\n ],\r\n objects: {\r\n general: {\r\n displayName: data.createDisplayNameGetter('Visual_General'),\r\n properties: {\r\n formatString: StandardObjectProperties.formatString,\r\n },\r\n },\r\n legend: {\r\n displayName: data.createDisplayNameGetter('Visual_Legend'),\r\n description: data.createDisplayNameGetter('Visual_LegendDescription'),\r\n properties: {\r\n show: StandardObjectProperties.show,\r\n position: StandardObjectProperties.legendPosition,\r\n showTitle: StandardObjectProperties.showLegendTitle,\r\n titleText: $.extend({}, StandardObjectProperties.legendTitle, {\r\n suppressFormatPainterCopy: true,\r\n }),\r\n labelColor: StandardObjectProperties.labelColor,\r\n fontSize: StandardObjectProperties.fontSize,\r\n }\r\n },\r\n labels: {\r\n displayName: data.createDisplayNameGetter('Visual_DataPointsLabels'),\r\n description: data.createDisplayNameGetter('Visual_DataPointsLabelsDescription'),\r\n properties: {\r\n show: StandardObjectProperties.show,\r\n color: StandardObjectProperties.dataColor,\r\n labelDisplayUnits: StandardObjectProperties.dataLabelDisplayUnits,\r\n labelPrecision: $.extend({}, StandardObjectProperties.labelPrecision, {\r\n suppressFormatPainterCopy: true,\r\n }),\r\n fontSize: StandardObjectProperties.fontSize,\r\n }\r\n },\r\n sentimentColors: {\r\n displayName: data.createDisplayNameGetter('Waterfall_SentimentColors'),\r\n properties: {\r\n increaseFill: {\r\n displayName: data.createDisplayNameGetter('Waterfall_IncreaseLabel'),\r\n type: { fill: { solid: { color: true } } }\r\n },\r\n decreaseFill: {\r\n displayName: data.createDisplayNameGetter('Waterfall_DecreaseLabel'),\r\n type: { fill: { solid: { color: true } } }\r\n },\r\n totalFill: {\r\n displayName: data.createDisplayNameGetter('Waterfall_TotalLabel'),\r\n type: { fill: { solid: { color: true } } }\r\n }\r\n },\r\n },\r\n categoryAxis: {\r\n displayName: data.createDisplayNameGetter('Visual_XAxis'),\r\n properties: {\r\n show: StandardObjectProperties.show,\r\n showAxisTitle: {\r\n displayName: data.createDisplayNameGetter('Visual_Axis_Title'),\r\n description: data.createDisplayNameGetter('Visual_Axis_XTitleDescription'),\r\n type: { bool: true }\r\n },\r\n axisStyle: StandardObjectProperties.axisStyle,\r\n labelColor: StandardObjectProperties.labelColor,\r\n }\r\n },\r\n valueAxis: {\r\n displayName: data.createDisplayNameGetter('Visual_YAxis'),\r\n properties: {\r\n show: StandardObjectProperties.show,\r\n position: StandardObjectProperties.yAxisPosition,\r\n start: StandardObjectProperties.axisStart,\r\n end: StandardObjectProperties.axisEnd,\r\n showAxisTitle: {\r\n displayName: data.createDisplayNameGetter('Visual_Axis_Title'),\r\n description: data.createDisplayNameGetter('Visual_Axis_YTitleDescription'),\r\n type: { bool: true }\r\n },\r\n axisStyle: StandardObjectProperties.axisStyle,\r\n labelColor: StandardObjectProperties.labelColor,\r\n labelDisplayUnits: StandardObjectProperties.labelDisplayUnits,\r\n labelPrecision: StandardObjectProperties.labelPrecision,\r\n }\r\n },\r\n y1AxisReferenceLine: {\r\n displayName: data.createDisplayNameGetter('Visual_Reference_Line'),\r\n description: data.createDisplayNameGetter('Visual_Reference_Line_Description'),\r\n properties: {\r\n show: StandardObjectProperties.show,\r\n value: {\r\n displayName: data.createDisplayNameGetter('Visual_Reference_Line_Value'),\r\n description: data.createDisplayNameGetter('Visual_Reference_Line_Value_Description'),\r\n type: { numeric: true }\r\n },\r\n lineColor: StandardObjectProperties.lineColor,\r\n transparency: {\r\n displayName: data.createDisplayNameGetter('Visual_Reference_Line_Transparency'),\r\n description: data.createDisplayNameGetter('Visual_Reference_Line_Transparency_Description'),\r\n type: { numeric: true }\r\n },\r\n style: StandardObjectProperties.referenceLineStyle,\r\n position: StandardObjectProperties.referenceLinePosition,\r\n dataLabelShow: StandardObjectProperties.dataLabelShow,\r\n dataLabelColor: StandardObjectProperties.dataLabelColor,\r\n dataLabelDecimalPoints: StandardObjectProperties.dataLabelDecimalPoints,\r\n dataLabelHorizontalPosition: StandardObjectProperties.dataLabelHorizontalPosition,\r\n dataLabelVerticalPosition: StandardObjectProperties.dataLabelVerticalPosition,\r\n dataLabelDisplayUnits: StandardObjectProperties.dataLabelDisplayUnits,\r\n },\r\n },\r\n plotArea: {\r\n displayName: data.createDisplayNameGetter('Visual_Plot'),\r\n properties: {\r\n transparency: StandardObjectProperties.transparency,\r\n image: StandardObjectProperties.image,\r\n },\r\n },\r\n },\r\n dataViewMappings: [{\r\n conditions: [\r\n { 'Category': { max: 1 }, 'Y': { max: 1 } },\r\n ],\r\n categorical: {\r\n categories: {\r\n for: { in: 'Category' },\r\n dataReductionAlgorithm: { top: {} }\r\n },\r\n values: {\r\n select: [{ bind: { to: 'Y' } }]\r\n },\r\n },\r\n }],\r\n sorting: {\r\n default: {},\r\n },\r\n drilldown: {\r\n roles: ['Category']\r\n },\r\n };\r\n\r\n export const waterfallChartProps = {\r\n general: {\r\n formatString: <DataViewObjectPropertyIdentifier>{ objectName: 'general', propertyName: 'formatString' },\r\n },\r\n sentimentColors: {\r\n increaseFill: <DataViewObjectPropertyIdentifier>{ objectName: 'sentimentColors', propertyName: 'increaseFill' },\r\n decreaseFill: <DataViewObjectPropertyIdentifier>{ objectName: 'sentimentColors', propertyName: 'decreaseFill' },\r\n totalFill: <DataViewObjectPropertyIdentifier>{ objectName: 'sentimentColors', propertyName: 'totalFill' },\r\n },\r\n legend: {\r\n labelColor: <DataViewObjectPropertyIdentifier>{ objectName: 'legend', propertyName: 'labelColor' },\r\n },\r\n };\r\n}\n\n\n/** WEBPACK FOOTER **\n ** ./src/Clients/Visuals/capabilities/waterfallChart.capabilities.ts\n **/","/*\r\n * Power BI Visualizations\r\n *\r\n * Copyright (c) Microsoft Corporation\r\n * All rights reserved. \r\n * MIT License\r\n *\r\n * Permission is hereby granted, free of charge, to any person obtaining a copy\r\n * of this software and associated documentation files (the \"\"Software\"\"), to deal\r\n * in the Software without restriction, including without limitation the rights\r\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\r\n * copies of the Software, and to permit persons to whom the Software is\r\n * furnished to do so, subject to the following conditions:\r\n * \r\n * The above copyright notice and this permission notice shall be included in \r\n * all copies or substantial portions of the Software.\r\n * \r\n * THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR \r\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, \r\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE \r\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER \r\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\r\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\r\n * THE SOFTWARE.\r\n */\r\n\r\n/// <reference path=\"../_references.ts\"/>\r\n\r\nmodule powerbi.visuals {\r\n export const KPIStatusWithHistoryCapabilities: VisualCapabilities = {\r\n dataRoles: [\r\n {\r\n name: 'Indicator',\r\n kind: VisualDataRoleKind.Measure,\r\n displayName: data.createDisplayNameGetter('Role_DisplayName_KPI_Indicator'),\r\n description: data.createDisplayNameGetter('Role_DisplayName_KPI_IndicatorDescription')\r\n }, {\r\n name: 'TrendLine',\r\n kind: VisualDataRoleKind.Grouping,\r\n displayName: data.createDisplayNameGetter('Role_DisplayName_KPI_TrendLine'),\r\n description: data.createDisplayNameGetter('Role_DisplayName_KPI_Trendline_Description')\r\n }, {\r\n name: 'Goal',\r\n kind: VisualDataRoleKind.Measure,\r\n displayName: data.createDisplayNameGetter('Role_DisplayName_KPI_Goal'),\r\n description: data.createDisplayNameGetter('Role_DisplayName_KPI_GoalDescription')\r\n }],\r\n dataViewMappings: [{\r\n conditions: [\r\n { 'Indicator': { max: 1 }, 'TrendLine': { max: 1 }, 'Goal': { max: 2 } },\r\n ],\r\n categorical: {\r\n categories: {\r\n for: { in: 'TrendLine' },\r\n dataReductionAlgorithm: { top: {} }\r\n },\r\n values: {\r\n select: [\r\n { bind: { to: 'Indicator' } },\r\n { bind: { to: 'Goal' } }\r\n ]\r\n }\r\n },\r\n }],\r\n objects: {\r\n general: {\r\n properties: {\r\n formatString: StandardObjectProperties.formatString,\r\n },\r\n },\r\n indicator: {\r\n displayName: data.createDisplayNameGetter('Role_DisplayName_KPI_Indicator'),\r\n description: data.createDisplayNameGetter('Role_DisplayName_KPI_IndicatorDescription'),\r\n properties: {\r\n indicatorDisplayUnits: {\r\n displayName: data.createDisplayNameGetter('Visual_DisplayUnits'),\r\n description: data.createDisplayNameGetter('Visual_DisplayUnitsDescription'),\r\n type: { formatting: { labelDisplayUnits: true } }\r\n },\r\n indicatorPrecision: {\r\n displayName: data.createDisplayNameGetter('Visual_Precision'),\r\n description: data.createDisplayNameGetter('Visual_PrecisionDescription'),\r\n placeHolderText: data.createDisplayNameGetter('Visual_Precision_Auto'),\r\n type: { numeric: true }\r\n },\r\n kpiFormat: {\r\n displayName: data.createDisplayNameGetter('TaskPane_Format'),\r\n type: { text: true },\r\n }\r\n }\r\n },\r\n trendline: {\r\n displayName: data.createDisplayNameGetter('Role_DisplayName_KPI_TrendLine'),\r\n description: data.createDisplayNameGetter('Role_DisplayName_KPI_Trendline_Description'),\r\n properties: {\r\n show: StandardObjectProperties.show,\r\n }\r\n },\r\n goals: {\r\n displayName: data.createDisplayNameGetter('Role_DisplayName_KPI_Goals'),\r\n description: data.createDisplayNameGetter('Role_DisplayName_KPI_Goals'),\r\n properties: {\r\n showGoal: {\r\n displayName: data.createDisplayNameGetter('Role_DisplayName_KPI_Show_Goal'),\r\n type: { bool: true }\r\n },\r\n showDistance: {\r\n displayName: data.createDisplayNameGetter('Role_DisplayName_KPI_Show_Distance'),\r\n type: { bool: true }\r\n },\r\n }\r\n },\r\n status: {\r\n displayName: data.createDisplayNameGetter('Role_DisplayName_KPI_Status'),\r\n description: data.createDisplayNameGetter('Role_DisplayName_KPI_Status'),\r\n properties: {\r\n direction: {\r\n displayName: data.createDisplayNameGetter('Visual_KPI_Direction'),\r\n type: { enumeration: kpiDirection.type }\r\n }\r\n }\r\n }\r\n },\r\n };\r\n}\n\n\n/** WEBPACK FOOTER **\n ** ./src/Clients/Visuals/capabilities/kpiStatusWithHistory.capabilities.ts\n **/","/*\r\n * Power BI Visualizations\r\n *\r\n * Copyright (c) Microsoft Corporation\r\n * All rights reserved. \r\n * MIT License\r\n *\r\n * Permission is hereby granted, free of charge, to any person obtaining a copy\r\n * of this software and associated documentation files (the \"\"Software\"\"), to deal\r\n * in the Software without restriction, including without limitation the rights\r\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\r\n * copies of the Software, and to permit persons to whom the Software is\r\n * furnished to do so, subject to the following conditions:\r\n * \r\n * The above copyright notice and this permission notice shall be included in \r\n * all copies or substantial portions of the Software.\r\n * \r\n * THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR \r\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, \r\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE \r\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER \r\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\r\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\r\n * THE SOFTWARE.\r\n */\r\n\r\n/// <reference path=\"./_references.ts\"/>\r\n\r\nmodule powerbi.visuals.capabilities {\r\n // This file registers the built-in capabilities\r\n // Please use this file to register the capabilities in the plugins.ts/pluginsNotForOSS.ts\r\n\r\n export let animatedNumber = powerbi.visuals.animatedNumberCapabilities;\r\n\r\n export let areaChart = powerbi.visuals.lineChartCapabilities;\r\n\r\n export let barChart = powerbi.visuals.getColumnChartCapabilities(true);\r\n\r\n export let card = powerbi.visuals.cardCapabilities;\r\n\r\n export let multiRowCard = powerbi.visuals.multiRowCardCapabilities;\r\n\r\n export let clusteredBarChart = powerbi.visuals.getColumnChartCapabilities(true);\r\n\r\n export let clusteredColumnChart = powerbi.visuals.getColumnChartCapabilities();\r\n\r\n export let columnChart = powerbi.visuals.getColumnChartCapabilities();\r\n\r\n export let comboChart = powerbi.visuals.comboChartCapabilities;\r\n\r\n export let dataDotChart = powerbi.visuals.dataDotChartCapabilities;\r\n\r\n export let dataDotClusteredColumnComboChart = powerbi.visuals.comboChartCapabilities;\r\n\r\n export let dataDotStackedColumnComboChart = powerbi.visuals.comboChartCapabilities;\r\n\r\n export let donutChart = powerbi.visuals.donutChartCapabilities;\r\n\r\n export let funnel = powerbi.visuals.funnelChartCapabilities;\r\n\r\n export let gauge = powerbi.visuals.gaugeCapabilities;\r\n\r\n export let hundredPercentStackedBarChart = powerbi.visuals.getColumnChartCapabilities(true);\r\n\r\n export let hundredPercentStackedColumnChart = powerbi.visuals.getColumnChartCapabilities();\r\n\r\n export let image = powerbi.visuals.imageVisualCapabilities;\r\n\r\n export let lineChart = powerbi.visuals.lineChartCapabilities;\r\n\r\n export let lineStackedColumnComboChart = powerbi.visuals.comboChartCapabilities;\r\n\r\n export let lineClusteredColumnComboChart = powerbi.visuals.comboChartCapabilities;\r\n\r\n export let map = powerbi.visuals.mapCapabilities;\r\n\r\n export let filledMap = powerbi.visuals.filledMapCapabilities;\r\n\r\n export let treemap = powerbi.visuals.treemapCapabilities;\r\n\r\n export let pieChart = powerbi.visuals.donutChartCapabilities;\r\n\r\n export let scatterChart = powerbi.visuals.scatterChartCapabilities;\r\n\r\n export let table = powerbi.visuals.tableCapabilities;\r\n\r\n export let matrix = powerbi.visuals.matrixCapabilities;\r\n\r\n export let slicer = powerbi.visuals.slicerCapabilities;\r\n\r\n export let textbox = powerbi.visuals.textboxCapabilities;\r\n\r\n export let waterfallChart = powerbi.visuals.waterfallChartCapabilities;\r\n\r\n export let cheerMeter = powerbi.visuals.cheerMeterCapabilities;\r\n\r\n export let scriptVisual = powerbi.visuals.scriptVisualCapabilities;\r\n \r\n export let kpi = powerbi.visuals.KPIStatusWithHistoryCapabilities;\r\n}\n\n\n/** WEBPACK FOOTER **\n ** ./src/Clients/Visuals/pluginsCapabilities.ts\n **/","/*\r\n * Power BI Visualizations\r\n *\r\n * Copyright (c) Microsoft Corporation\r\n * All rights reserved. \r\n * MIT License\r\n *\r\n * Permission is hereby granted, free of charge, to any person obtaining a copy\r\n * of this software and associated documentation files (the \"\"Software\"\"), to deal\r\n * in the Software without restriction, including without limitation the rights\r\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\r\n * copies of the Software, and to permit persons to whom the Software is\r\n * furnished to do so, subject to the following conditions:\r\n * \r\n * The above copyright notice and this permission notice shall be included in \r\n * all copies or substantial portions of the Software.\r\n * \r\n * THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR \r\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, \r\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE \r\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER \r\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\r\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\r\n * THE SOFTWARE.\r\n */\r\n\r\n/// <reference path=\"../_references.ts\"/>\r\n\r\nmodule powerbi.visuals {\r\n export interface ColumnBehaviorOptions {\r\n datapoints: SelectableDataPoint[];\r\n bars: D3.Selection;\r\n eventGroup: D3.Selection;\r\n mainGraphicsContext: D3.Selection;\r\n hasHighlights: boolean;\r\n viewport: IViewport;\r\n axisOptions: ColumnAxisOptions;\r\n showLabel: boolean;\r\n }\r\n\r\n export class ColumnChartWebBehavior implements IInteractiveBehavior {\r\n private options: ColumnBehaviorOptions;\r\n\r\n public bindEvents(options: ColumnBehaviorOptions, selectionHandler: ISelectionHandler) {\r\n this.options = options;\r\n let eventGroup = options.eventGroup;\r\n\r\n eventGroup.on('click', () => {\r\n let d = ColumnChartWebBehavior.getDatumForLastInputEvent();\r\n \r\n selectionHandler.handleSelection(d, d3.event.ctrlKey);\r\n });\r\n\r\n eventGroup.on('contextmenu', () => {\r\n if (d3.event.ctrlKey)\r\n return;\r\n\r\n d3.event.preventDefault();\r\n\r\n let d = ColumnChartWebBehavior.getDatumForLastInputEvent();\r\n let position = InteractivityUtils.getPositionOfLastInputEvent();\r\n \r\n selectionHandler.handleContextMenu(d, position);\r\n });\r\n }\r\n\r\n public renderSelection(hasSelection: boolean) {\r\n let options = this.options;\r\n options.bars.style(\"fill-opacity\", (d: ColumnChartDataPoint) => ColumnUtil.getFillOpacity(d.selected, d.highlight, !d.highlight && hasSelection, !d.selected && options.hasHighlights));\r\n }\r\n\r\n private static getDatumForLastInputEvent(): any {\r\n let target = d3.event.target;\r\n return d3.select(target).datum();\r\n }\r\n }\r\n} \n\n\n/** WEBPACK FOOTER **\n ** ./src/Clients/Visuals/behaviours/columnChartBehaviors.ts\n **/","/*\r\n * Power BI Visualizations\r\n *\r\n * Copyright (c) Microsoft Corporation\r\n * All rights reserved. \r\n * MIT License\r\n *\r\n * Permission is hereby granted, free of charge, to any person obtaining a copy\r\n * of this software and associated documentation files (the \"\"Software\"\"), to deal\r\n * in the Software without restriction, including without limitation the rights\r\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\r\n * copies of the Software, and to permit persons to whom the Software is\r\n * furnished to do so, subject to the following conditions:\r\n * \r\n * The above copyright notice and this permission notice shall be included in \r\n * all copies or substantial portions of the Software.\r\n * \r\n * THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR \r\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, \r\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE \r\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER \r\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\r\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\r\n * THE SOFTWARE.\r\n */\r\n\r\n/// <reference path=\"../_references.ts\"/>\r\n\r\nmodule powerbi.visuals {\r\n export interface DataDotChartBehaviorOptions {\r\n dots: D3.Selection;\r\n dotLabels: D3.Selection;\r\n isPartOfCombo?: boolean;\r\n datapoints?: DataDotChartDataPoint[];\r\n }\r\n\r\n export class DataDotChartWebBehavior implements IInteractiveBehavior {\r\n private dots: D3.Selection;\r\n\r\n public bindEvents(options: DataDotChartBehaviorOptions, selectionHandler: ISelectionHandler): void {\r\n let dots = this.dots = options.dots;\r\n let dotLabels = options.dotLabels;\r\n\r\n InteractivityUtils.registerStandardInteractivityHandlers(dots, selectionHandler);\r\n\r\n if (dotLabels) {\r\n InteractivityUtils.registerStandardInteractivityHandlers(dotLabels, selectionHandler);\r\n }\r\n }\r\n\r\n public renderSelection(hasSelection: boolean): void {\r\n this.dots.style(\"fill-opacity\",(d: DataDotChartDataPoint) => ColumnUtil.getFillOpacity(d.selected, d.highlight, hasSelection, false));\r\n }\r\n }\r\n} \n\n\n/** WEBPACK FOOTER **\n ** ./src/Clients/Visuals/behaviours/dataDotChartBehavior.ts\n **/","/*\r\n * Power BI Visualizations\r\n *\r\n * Copyright (c) Microsoft Corporation\r\n * All rights reserved. \r\n * MIT License\r\n *\r\n * Permission is hereby granted, free of charge, to any person obtaining a copy\r\n * of this software and associated documentation files (the \"\"Software\"\"), to deal\r\n * in the Software without restriction, including without limitation the rights\r\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\r\n * copies of the Software, and to permit persons to whom the Software is\r\n * furnished to do so, subject to the following conditions:\r\n * \r\n * The above copyright notice and this permission notice shall be included in \r\n * all copies or substantial portions of the Software.\r\n * \r\n * THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR \r\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, \r\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE \r\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER \r\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\r\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\r\n * THE SOFTWARE.\r\n */\r\n\r\n/// <reference path=\"../_references.ts\"/>\r\n\r\nmodule powerbi.visuals {\r\n export interface DonutBehaviorOptions {\r\n slices: D3.Selection;\r\n highlightSlices: D3.Selection;\r\n clearCatcher: D3.Selection;\r\n hasHighlights: boolean;\r\n allowDrilldown: boolean;\r\n visual: IVisual;\r\n }\r\n\r\n export class DonutChartWebBehavior implements IInteractiveBehavior {\r\n private slices: D3.Selection;\r\n private highlightSlices: D3.Selection;\r\n private hasHighlights: boolean;\r\n\r\n public bindEvents(options: DonutBehaviorOptions, selectionHandler: ISelectionHandler): void {\r\n let slices = this.slices = options.slices;\r\n let highlightSlices = this.highlightSlices = options.highlightSlices;\r\n let clearCatcher = options.clearCatcher;\r\n this.hasHighlights = options.hasHighlights;\r\n\r\n let clickHandler = (d: DonutArcDescriptor) => {\r\n selectionHandler.handleSelection(d.data, d3.event.ctrlKey);\r\n };\r\n\r\n let contextMenuHandler = (d: DonutArcDescriptor) => {\r\n if (d3.event.ctrlKey)\r\n return;\r\n\r\n let position = InteractivityUtils.getPositionOfLastInputEvent();\r\n selectionHandler.handleContextMenu(d.data, position);\r\n d3.event.preventDefault();\r\n };\r\n\r\n slices.on('click', clickHandler);\r\n slices.on('contextmenu', contextMenuHandler);\r\n\r\n highlightSlices.on('click', clickHandler);\r\n highlightSlices.on('contextmenu', contextMenuHandler);\r\n\r\n clearCatcher.on('click', () => {\r\n selectionHandler.handleClearSelection();\r\n });\r\n }\r\n\r\n public renderSelection(hasSelection: boolean): void {\r\n let hasHighlights = this.hasHighlights;\r\n this.slices.style(\"fill-opacity\", (d: DonutArcDescriptor) => ColumnUtil.getFillOpacity(d.data.selected, false, hasSelection, hasHighlights && !d.data.selected));\r\n this.highlightSlices.style(\"fill-opacity\", (d: DonutArcDescriptor) => ColumnUtil.getFillOpacity(d.data.selected, true, false, hasHighlights));\r\n }\r\n }\r\n} \n\n\n/** WEBPACK FOOTER **\n ** ./src/Clients/Visuals/behaviours/donutChartBehaviors.ts\n **/","/*\r\n * Power BI Visualizations\r\n *\r\n * Copyright (c) Microsoft Corporation\r\n * All rights reserved. \r\n * MIT License\r\n *\r\n * Permission is hereby granted, free of charge, to any person obtaining a copy\r\n * of this software and associated documentation files (the \"\"Software\"\"), to deal\r\n * in the Software without restriction, including without limitation the rights\r\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\r\n * copies of the Software, and to permit persons to whom the Software is\r\n * furnished to do so, subject to the following conditions:\r\n * \r\n * The above copyright notice and this permission notice shall be included in \r\n * all copies or substantial portions of the Software.\r\n * \r\n * THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR \r\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, \r\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE \r\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER \r\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\r\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\r\n * THE SOFTWARE.\r\n */\r\n\r\n/// <reference path=\"../_references.ts\"/>\r\n\r\nmodule powerbi.visuals {\r\n export interface FunnelBehaviorOptions {\r\n bars: D3.Selection;\r\n interactors: D3.Selection;\r\n clearCatcher: D3.Selection;\r\n hasHighlights: boolean;\r\n }\r\n\r\n export class FunnelWebBehavior implements IInteractiveBehavior {\r\n private bars: D3.Selection;\r\n private interactors: D3.Selection;\r\n private hasHighlights: boolean;\r\n\r\n public bindEvents(options: FunnelBehaviorOptions, selectionHandler: ISelectionHandler): void {\r\n\r\n let bars = this.bars = options.bars;\r\n let interactors = this.interactors = options.interactors;\r\n let clearCatcher = options.clearCatcher;\r\n\r\n this.hasHighlights = options.hasHighlights;\r\n\r\n InteractivityUtils.registerStandardInteractivityHandlers(bars, selectionHandler);\r\n InteractivityUtils.registerStandardInteractivityHandlers(interactors, selectionHandler);\r\n\r\n clearCatcher.on('click', () => {\r\n selectionHandler.handleClearSelection();\r\n });\r\n }\r\n\r\n public renderSelection(hasSelection: boolean): void {\r\n let hasHighlights = this.hasHighlights;\r\n this.bars.style(\"fill-opacity\", (d: FunnelDataPoint) => ColumnUtil.getFillOpacity(d.selected, d.highlight, !d.highlight && hasSelection, !d.selected && hasHighlights));\r\n }\r\n }\r\n}\r\n\n\n\n/** WEBPACK FOOTER **\n ** ./src/Clients/Visuals/behaviours/funnelBehaviors.ts\n **/","/*\r\n * Power BI Visualizations\r\n *\r\n * Copyright (c) Microsoft Corporation\r\n * All rights reserved. \r\n * MIT License\r\n *\r\n * Permission is hereby granted, free of charge, to any person obtaining a copy\r\n * of this software and associated documentation files (the \"\"Software\"\"), to deal\r\n * in the Software without restriction, including without limitation the rights\r\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\r\n * copies of the Software, and to permit persons to whom the Software is\r\n * furnished to do so, subject to the following conditions:\r\n * \r\n * The above copyright notice and this permission notice shall be included in \r\n * all copies or substantial portions of the Software.\r\n * \r\n * THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR \r\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, \r\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE \r\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER \r\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\r\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\r\n * THE SOFTWARE.\r\n */\r\n\r\n/// <reference path=\"../_references.ts\"/>\r\n\r\nmodule powerbi.visuals {\r\n export interface PlayBehaviorOptions {\r\n traceLineRenderer?: ITraceLineRenderer;\r\n }\r\n} \n\n\n/** WEBPACK FOOTER **\n ** ./src/Clients/Visuals/behaviours/playChartBehaviors.ts\n **/","/*\r\n * Power BI Visualizations\r\n *\r\n * Copyright (c) Microsoft Corporation\r\n * All rights reserved. \r\n * MIT License\r\n *\r\n * Permission is hereby granted, free of charge, to any person obtaining a copy\r\n * of this software and associated documentation files (the \"\"Software\"\"), to deal\r\n * in the Software without restriction, including without limitation the rights\r\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\r\n * copies of the Software, and to permit persons to whom the Software is\r\n * furnished to do so, subject to the following conditions:\r\n * \r\n * The above copyright notice and this permission notice shall be included in \r\n * all copies or substantial portions of the Software.\r\n * \r\n * THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR \r\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, \r\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE \r\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER \r\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\r\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\r\n * THE SOFTWARE.\r\n */\r\n\r\n/// <reference path=\"../_references.ts\"/>\r\n\r\nmodule powerbi.visuals {\r\n export interface LineChartBehaviorOptions {\r\n lines: D3.Selection;\r\n interactivityLines: D3.Selection;\r\n dots: D3.Selection;\r\n areas: D3.Selection;\r\n isPartOfCombo?: boolean;\r\n tooltipOverlay: D3.Selection;\r\n }\r\n\r\n export class LineChartWebBehavior implements IInteractiveBehavior {\r\n private lines: D3.Selection;\r\n private dots: D3.Selection;\r\n private areas: D3.Selection;\r\n private tooltipOverlay: D3.Selection;\r\n\r\n public bindEvents(options: LineChartBehaviorOptions, selectionHandler: ISelectionHandler): void {\r\n this.lines = options.lines;\r\n let interactivityLines = options.interactivityLines;\r\n let dots = this.dots = options.dots;\r\n let areas = this.areas = options.areas;\r\n let tooltipOverlay = this.tooltipOverlay = options.tooltipOverlay;\r\n\r\n InteractivityUtils.registerStandardInteractivityHandlers(interactivityLines, selectionHandler);\r\n InteractivityUtils.registerStandardInteractivityHandlers(dots, selectionHandler);\r\n\r\n if (areas) {\r\n InteractivityUtils.registerStandardInteractivityHandlers(areas, selectionHandler);\r\n }\r\n\r\n if (tooltipOverlay)\r\n tooltipOverlay.on('click', () => selectionHandler.handleClearSelection());\r\n }\r\n\r\n public renderSelection(hasSelection: boolean) {\r\n this.lines.style(\"stroke-opacity\", (d: SelectableDataPoint) => ColumnUtil.getFillOpacity(d.selected, false, hasSelection, false));\r\n this.dots.style(\"fill-opacity\", (d: SelectableDataPoint) => ColumnUtil.getFillOpacity(d.selected, false, hasSelection, false));\r\n if (this.areas)\r\n this.areas.style(\"fill-opacity\", (d: SelectableDataPoint) => (hasSelection && !d.selected) ? LineChart.DimmedAreaFillOpacity : LineChart.AreaFillOpacity);\r\n }\r\n }\r\n} \n\n\n/** WEBPACK FOOTER **\n ** ./src/Clients/Visuals/behaviours/lineChartBehaviors.ts\n **/","/*\r\n * Power BI Visualizations\r\n *\r\n * Copyright (c) Microsoft Corporation\r\n * All rights reserved. \r\n * MIT License\r\n *\r\n * Permission is hereby granted, free of charge, to any person obtaining a copy\r\n * of this software and associated documentation files (the \"\"Software\"\"), to deal\r\n * in the Software without restriction, including without limitation the rights\r\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\r\n * copies of the Software, and to permit persons to whom the Software is\r\n * furnished to do so, subject to the following conditions:\r\n * \r\n * The above copyright notice and this permission notice shall be included in \r\n * all copies or substantial portions of the Software.\r\n * \r\n * THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR \r\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, \r\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE \r\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER \r\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\r\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\r\n * THE SOFTWARE.\r\n */\r\n\r\n/// <reference path=\"../_references.ts\"/>\r\n\r\nmodule powerbi.visuals {\r\n import MapSliceContainer = powerbi.visuals.MapSliceContainer;\r\n\r\n export interface MapBehaviorOptions {\r\n dataPoints: SelectableDataPoint[];\r\n bubbles?: D3.Selection;\r\n slices?: D3.Selection;\r\n shapes?: D3.Selection;\r\n clearCatcher: D3.Selection;\r\n bubbleEventGroup?: D3.Selection;\r\n sliceEventGroup?: D3.Selection;\r\n shapeEventGroup?: D3.Selection;\r\n }\r\n\r\n export class MapBehavior implements IInteractiveBehavior {\r\n private bubbles: D3.Selection;\r\n private slices: D3.Selection;\r\n private shapes: D3.Selection;\r\n private mapPointerEventsDisabled = false;\r\n private mapPointerTimeoutSet = false;\r\n private viewChangedSinceLastClearMouseDown = false;\r\n private receivedZoomOrPanEvent = false;\r\n\r\n public bindEvents(options: MapBehaviorOptions, selectionHandler: ISelectionHandler): void {\r\n let bubbles = this.bubbles = options.bubbles;\r\n let slices = this.slices = options.slices;\r\n let shapes = this.shapes = options.shapes;\r\n let clearCatcher = options.clearCatcher;\r\n\r\n let clickHandler = () => {\r\n let target = d3.event.target;\r\n let d = <SelectableDataPoint>d3.select(target).datum();\r\n\r\n if (bubbles)\r\n bubbles.style(\"pointer-events\", \"all\");\r\n if (shapes)\r\n shapes.style(\"pointer-events\", \"all\");\r\n selectionHandler.handleSelection(d, d3.event.ctrlKey);\r\n };\r\n\r\n if (!this.mapPointerEventsDisabled) {\r\n if (bubbles)\r\n bubbles.style(\"pointer-events\", \"all\");\r\n if (slices)\r\n slices.style(\"pointer-events\", \"all\");\r\n if (shapes)\r\n shapes.style(\"pointer-events\", \"all\");\r\n }\r\n\r\n if (bubbles) {\r\n options.bubbleEventGroup.on('click', clickHandler);\r\n options.bubbleEventGroup.on('mousewheel', () => {\r\n if (!this.mapPointerEventsDisabled)\r\n bubbles.style(\"pointer-events\", \"none\");\r\n this.mapPointerEventsDisabled = true;\r\n if (!this.mapPointerTimeoutSet) {\r\n this.mapPointerTimeoutSet = true;\r\n setTimeout(() => {\r\n if (bubbles)\r\n bubbles.style(\"pointer-events\", \"all\");\r\n this.mapPointerEventsDisabled = false;\r\n this.mapPointerTimeoutSet = false;\r\n }, 200);\r\n }\r\n });\r\n\r\n InteractivityUtils.registerGroupContextMenuHandler(options.bubbleEventGroup, selectionHandler);\r\n }\r\n\r\n if (slices) {\r\n options.sliceEventGroup.on('click', () => {\r\n slices.style(\"pointer-events\", \"all\");\r\n this.mapPointerEventsDisabled = false;\r\n\r\n let target = d3.event.target;\r\n let d = <MapSliceContainer>d3.select(target).datum();\r\n selectionHandler.handleSelection(d.data, d3.event.ctrlKey);\r\n });\r\n options.sliceEventGroup.on('mousewheel', () => {\r\n if (!this.mapPointerEventsDisabled)\r\n slices.style(\"pointer-events\", \"none\");\r\n this.mapPointerEventsDisabled = true;\r\n if (!this.mapPointerTimeoutSet) {\r\n this.mapPointerTimeoutSet = true;\r\n setTimeout(() => {\r\n if (slices)\r\n slices.style(\"pointer-events\", \"all\");\r\n this.mapPointerEventsDisabled = false;\r\n this.mapPointerTimeoutSet = false;\r\n }, 200);\r\n }\r\n });\r\n\r\n options.sliceEventGroup.on('contextmenu', () => {\r\n if (d3.event.ctrlKey)\r\n return;\r\n\r\n d3.event.preventDefault();\r\n let position = InteractivityUtils.getPositionOfLastInputEvent();\r\n\r\n let target = d3.event.target;\r\n let d = <MapSliceContainer>d3.select(target).datum();\r\n selectionHandler.handleContextMenu(d.data, position);\r\n });\r\n }\r\n\r\n if (shapes) {\r\n options.shapeEventGroup.on('click', clickHandler);\r\n options.shapeEventGroup.on('mousewheel', () => {\r\n if (!this.mapPointerEventsDisabled) {\r\n shapes.style(\"pointer-events\", \"none\");\r\n }\r\n this.mapPointerEventsDisabled = true;\r\n if (!this.mapPointerTimeoutSet) {\r\n this.mapPointerTimeoutSet = true;\r\n setTimeout(() => {\r\n if (shapes)\r\n shapes.style(\"pointer-events\", \"all\");\r\n this.mapPointerEventsDisabled = false;\r\n this.mapPointerTimeoutSet = false;\r\n }, 200);\r\n }\r\n });\r\n\r\n InteractivityUtils.registerGroupContextMenuHandler(options.shapeEventGroup, selectionHandler);\r\n }\r\n\r\n clearCatcher.on('mouseup', () => {\r\n if (!this.viewChangedSinceLastClearMouseDown) {\r\n selectionHandler.handleClearSelection();\r\n this.receivedZoomOrPanEvent = true;\r\n }\r\n });\r\n\r\n clearCatcher.on('mousedown', () => {\r\n this.viewChangedSinceLastClearMouseDown = false;\r\n });\r\n\r\n clearCatcher.on('mousewheel', () => {\r\n this.receivedZoomOrPanEvent = true;\r\n });\r\n }\r\n\r\n public renderSelection(hasSelection: boolean) {\r\n if (this.bubbles) {\r\n this.bubbles\r\n .style({\r\n 'fill-opacity': (d: MapBubble) => ColumnUtil.getFillOpacity(d.selected, false, hasSelection, false),\r\n 'stroke-opacity': (d: MapBubble) => ColumnUtil.getFillOpacity(d.selected, false, hasSelection, false),\r\n });\r\n }\r\n if (this.slices) {\r\n this.slices\r\n .style({\r\n \"fill-opacity\": (d) => ColumnUtil.getFillOpacity(d.data.selected, false, hasSelection, false),\r\n \"stroke-opacity\": (d) => ColumnUtil.getFillOpacity(d.data.selected, false, hasSelection, false),\r\n });\r\n }\r\n if (this.shapes) {\r\n this.shapes\r\n .style({\r\n \"fill-opacity\": (d) => ColumnUtil.getFillOpacity(d.selected, false, hasSelection, false),\r\n \"stroke-opacity\": (d) => ColumnUtil.getFillOpacity(d.selected, false, hasSelection, false),\r\n });\r\n }\r\n }\r\n\r\n public viewChanged() {\r\n this.viewChangedSinceLastClearMouseDown = true;\r\n }\r\n\r\n public resetZoomPan() {\r\n this.receivedZoomOrPanEvent = false;\r\n }\r\n\r\n public hasReceivedZoomOrPanEvent(): boolean {\r\n return this.receivedZoomOrPanEvent;\r\n }\r\n }\r\n}\n\n\n/** WEBPACK FOOTER **\n ** ./src/Clients/Visuals/behaviours/mapBehaviors.ts\n **/","/*\r\n * Power BI Visualizations\r\n *\r\n * Copyright (c) Microsoft Corporation\r\n * All rights reserved. \r\n * MIT License\r\n *\r\n * Permission is hereby granted, free of charge, to any person obtaining a copy\r\n * of this software and associated documentation files (the \"\"Software\"\"), to deal\r\n * in the Software without restriction, including without limitation the rights\r\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\r\n * copies of the Software, and to permit persons to whom the Software is\r\n * furnished to do so, subject to the following conditions:\r\n * \r\n * The above copyright notice and this permission notice shall be included in \r\n * all copies or substantial portions of the Software.\r\n * \r\n * THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR \r\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, \r\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE \r\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER \r\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\r\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\r\n * THE SOFTWARE.\r\n */\r\n\r\n/// <reference path=\"../_references.ts\"/>\r\n\r\nmodule powerbi.visuals {\r\n import ClassAndSelector = jsCommon.CssConstants.ClassAndSelector;\r\n import createClassAndSelector = jsCommon.CssConstants.createClassAndSelector;\r\n\r\n export interface ScatterBehaviorChartData {\r\n xCol: DataViewMetadataColumn;\r\n yCol: DataViewMetadataColumn;\r\n dataPoints: ScatterChartDataPoint[];\r\n legendData: LegendData;\r\n axesLabels: ChartAxesLabels;\r\n size?: DataViewMetadataColumn;\r\n sizeRange: NumberRange;\r\n fillPoint?: boolean;\r\n colorBorder?: boolean;\r\n }\r\n\r\n export interface ScatterBehaviorOptions {\r\n dataPointsSelection: D3.Selection;\r\n eventGroup?: D3.Selection;\r\n data: ScatterBehaviorChartData;\r\n plotContext: D3.Selection;\r\n playOptions?: PlayBehaviorOptions;\r\n }\r\n\r\n export interface ScatterMobileBehaviorOptions extends ScatterBehaviorOptions {\r\n host: ICartesianVisualHost;\r\n root: D3.Selection;\r\n background: D3.Selection;\r\n visualInitOptions: VisualInitOptions;\r\n xAxisProperties: IAxisProperties;\r\n yAxisProperties: IAxisProperties;\r\n }\r\n\r\n export class ScatterChartWebBehavior implements IInteractiveBehavior {\r\n private bubbles: D3.Selection;\r\n private shouldEnableFill: boolean;\r\n private colorBorder: boolean;\r\n private playOptions: PlayBehaviorOptions;\r\n\r\n public bindEvents(options: ScatterBehaviorOptions, selectionHandler: ISelectionHandler): void {\r\n let bubbles = this.bubbles = options.dataPointsSelection;\r\n let data = options.data;\r\n let eventGroup = options.eventGroup;\r\n\r\n // If we are removing play-axis, remove the trace lines as well\r\n // TODO: revisit this design, I think ideally this is done when rendering scatter.\r\n if (this.playOptions\r\n && this.playOptions.traceLineRenderer\r\n && (!options.playOptions || !options.playOptions.traceLineRenderer)) {\r\n this.playOptions.traceLineRenderer.remove();\r\n }\r\n\r\n this.playOptions = options.playOptions;\r\n this.shouldEnableFill = (!data.sizeRange || !data.sizeRange.min) && data.fillPoint;\r\n this.colorBorder = data.colorBorder;\r\n\r\n if (eventGroup) {\r\n InteractivityUtils.registerGroupInteractivityHandlers(eventGroup, selectionHandler);\r\n } else {\r\n InteractivityUtils.registerStandardInteractivityHandlers(bubbles, selectionHandler);\r\n }\r\n }\r\n\r\n public renderSelection(hasSelection: boolean) {\r\n let shouldEnableFill = this.shouldEnableFill;\r\n let colorBorder = this.colorBorder;\r\n this.bubbles.style(\"fill-opacity\", (d: ScatterChartDataPoint) => ScatterChart.getMarkerFillOpacity(d.size != null, shouldEnableFill, hasSelection, d.selected));\r\n this.bubbles.style(\"stroke-opacity\", (d: ScatterChartDataPoint) => ScatterChart.getMarkerStrokeOpacity(d.size != null, colorBorder, hasSelection, d.selected));\r\n\r\n if (this.playOptions && this.bubbles) {\r\n let selectedPoints = this.bubbles.filter((d: SelectableDataPoint) => d.selected).data();\r\n let traceLineRenderer = this.playOptions.traceLineRenderer;\r\n if (selectedPoints && selectedPoints.length > 0 && traceLineRenderer != null) {\r\n traceLineRenderer.render(selectedPoints, true);\r\n }\r\n else {\r\n traceLineRenderer.remove();\r\n }\r\n }\r\n }\r\n }\r\n\r\n export const enum DragType {\r\n Drag,\r\n DragEnd\r\n }\r\n\r\n interface MouseCoordinates {\r\n x: number;\r\n y: number;\r\n }\r\n\r\n export class ScatterChartMobileBehavior implements IInteractiveBehavior {\r\n private static CrosshairClassName = 'crosshair';\r\n private static ScatterChartCircleTagName = 'circle';\r\n private static DotClassName = 'dot';\r\n private static DotClassSelector = '.' + ScatterChartMobileBehavior.DotClassName;\r\n\r\n private static Horizontal: ClassAndSelector = createClassAndSelector('horizontal');\r\n private static Vertical: ClassAndSelector = createClassAndSelector('vertical');\r\n\r\n private host: ICartesianVisualHost;\r\n private mainGraphicsContext: D3.Selection;\r\n private data: ScatterBehaviorChartData;\r\n private crosshair: D3.Selection;\r\n private crosshairHorizontal: D3.Selection;\r\n private crosshairVertical: D3.Selection;\r\n private lastDotIndex: number;\r\n private xAxisProperties: IAxisProperties;\r\n private yAxisProperties: IAxisProperties;\r\n\r\n public bindEvents(options: ScatterMobileBehaviorOptions, selectionHandler: ISelectionHandler): void {\r\n this.setOptions(options);\r\n\r\n if (!options.visualInitOptions || !options.visualInitOptions.interactivity.isInteractiveLegend) {\r\n // Don't bind events if we are not in interactiveLegend mode\r\n // This case happend when on mobile we show the whole dashboard in still not on focus\r\n return;\r\n }\r\n\r\n this.makeDataPointsSelectable(options.dataPointsSelection);\r\n this.makeRootSelectable(options.root);\r\n this.makeDragable(options.root);\r\n this.disableDefaultTouchInteractions(options.root);\r\n this.selectRoot();\r\n }\r\n\r\n public renderSelection(HasSelection: boolean): void { }\r\n\r\n public setSelectionHandler(selectionHandler: ISelectionHandler): void { }\r\n\r\n private makeDataPointsSelectable(...selection: D3.Selection[]): void {\r\n for (let i = 0, len = selection.length; i < len; i++) {\r\n var sel = selection[i];\r\n\r\n sel.on('click', (d: SelectableDataPoint, i: number) => {\r\n this.select(i);\r\n });\r\n }\r\n }\r\n\r\n private makeRootSelectable(selection: D3.Selection): void {\r\n selection.on('click', (d: SelectableDataPoint, i: number) => {\r\n this.selectRoot();\r\n });\r\n }\r\n\r\n private makeDragable(...selection: D3.Selection[]): void {\r\n for (let i = 0, len = selection.length; i < len; i++) {\r\n let sel = selection[i];\r\n\r\n let drag = d3.behavior.drag()\r\n .on('drag', (d) => { this.drag(DragType.Drag); })\r\n .on('dragend', (d) => { this.drag(DragType.DragEnd); });\r\n\r\n sel.call(drag);\r\n }\r\n }\r\n\r\n private disableDefaultTouchInteractions(selection: D3.Selection): void {\r\n selection.style('touch-action', 'none');\r\n }\r\n\r\n public setOptions(options: ScatterMobileBehaviorOptions) {\r\n this.data = options.data;\r\n this.mainGraphicsContext = options.plotContext;\r\n this.xAxisProperties = options.xAxisProperties;\r\n this.yAxisProperties = options.yAxisProperties;\r\n this.host = options.host;\r\n }\r\n\r\n private select(index: number) {\r\n this.selectDotByIndex(index);\r\n }\r\n\r\n public selectRoot() {\r\n let marker = jsCommon.PerformanceUtil.create('selectRoot');\r\n this.onClick();\r\n marker.end();\r\n }\r\n\r\n public drag(t: DragType) {\r\n switch (t) {\r\n case DragType.Drag:\r\n this.onDrag();\r\n break;\r\n case DragType.DragEnd:\r\n this.onClick();\r\n break;\r\n default:\r\n debug.assertFail('Unknown Drag Type');\r\n }\r\n }\r\n\r\n private onDrag(): void {\r\n //find the current x and y position\r\n let xy = this.getMouseCoordinates();\r\n //move the crosshair to the current position\r\n this.moveCrosshairToXY(xy.x, xy.y);\r\n //update the style and the legend of the dots\r\n let selectedIndex = this.findClosestDotIndex(xy.x, xy.y);\r\n this.selectDot(selectedIndex);\r\n this.updateLegend(selectedIndex);\r\n }\r\n\r\n private onClick(): void {\r\n //find the current x and y position\r\n let xy = this.getMouseCoordinates();\r\n let selectedIndex = this.findClosestDotIndex(xy.x, xy.y);\r\n if (selectedIndex !== -1)\r\n this.selectDotByIndex(selectedIndex);\r\n }\r\n\r\n private getMouseCoordinates(): MouseCoordinates {\r\n let mainGfxContext = this.mainGraphicsContext;\r\n // select (0,0) in cartesian coordinates\r\n let x = 0;\r\n let y = parseInt(mainGfxContext.attr('height'), 10);\r\n y = y || 0;\r\n\r\n try {\r\n let mouse = d3.mouse(mainGfxContext.node());\r\n x = mouse[0];\r\n y = mouse[1];\r\n } catch(e){ \r\n }\r\n\r\n return { x: x, y: y, };\r\n }\r\n\r\n private selectDotByIndex(index: number): void {\r\n this.selectDot(index);\r\n this.moveCrosshairToIndexDot(index);\r\n this.updateLegend(index);\r\n }\r\n\r\n private selectDot(dotIndex: number): void {\r\n let root = this.mainGraphicsContext;\r\n\r\n root.selectAll(ScatterChartMobileBehavior.ScatterChartCircleTagName + ScatterChartMobileBehavior.DotClassSelector).classed({ selected: false, notSelected: true });\r\n root.selectAll(ScatterChartMobileBehavior.ScatterChartCircleTagName + ScatterChartMobileBehavior.DotClassSelector).filter((d, i) => {\r\n let dataPoints = this.data.dataPoints;\r\n debug.assert(dataPoints.length > dotIndex, \"dataPoints length:\" + dataPoints.length + \"is smaller than index:\" + dotIndex);\r\n let currentPoint: ScatterChartDataPoint = dataPoints[dotIndex];\r\n return (d.x === currentPoint.x) && (d.y === currentPoint.y);\r\n }).classed({ selected: true, notSelected: false });\r\n }\r\n\r\n private moveCrosshairToIndexDot(index: number): void {\r\n let dataPoints = this.data.dataPoints;\r\n let root = this.mainGraphicsContext;\r\n\r\n debug.assert(dataPoints.length > index, \"dataPoints length:\" + dataPoints.length + \"is smaller than index:\" + index);\r\n let x = this.xAxisProperties.scale(dataPoints[index].x);\r\n let y = this.yAxisProperties.scale(dataPoints[index].y);\r\n if (this.crosshair == null) {\r\n let width = +root.attr('width');\r\n let height = +root.attr('height');\r\n this.crosshair = this.drawCrosshair(root, x, y, width, height);\r\n this.crosshairHorizontal = this.crosshair.select(ScatterChartMobileBehavior.Horizontal.selector);\r\n this.crosshairVertical = this.crosshair.select(ScatterChartMobileBehavior.Vertical.selector);\r\n } else {\r\n this.moveCrosshairToXY(x, y);\r\n }\r\n }\r\n\r\n private moveCrosshairToXY(x: number, y: number): void {\r\n this.crosshairHorizontal.attr({ y1: y, y2: y });\r\n this.crosshairVertical.attr({ x1: x, x2: x });\r\n }\r\n\r\n private drawCrosshair(addTo: D3.Selection, x: number, y: number, width: number, height: number): D3.Selection {\r\n let crosshair = addTo.append(\"g\");\r\n crosshair.classed(ScatterChartMobileBehavior.CrosshairClassName, true);\r\n crosshair.append('line').classed(ScatterChartMobileBehavior.Horizontal.class, true).attr({ x1: 0, x2: width, y1: y, y2: y });\r\n crosshair.append('line').classed(ScatterChartMobileBehavior.Vertical.class, true).attr({ x1: x, x2: x, y1: height, y2: 0 });\r\n return crosshair;\r\n }\r\n\r\n private findClosestDotIndex(x: number, y: number): number {\r\n let selectedIndex = -1;\r\n let minDistance = Number.MAX_VALUE;\r\n let dataPoints = this.data.dataPoints;\r\n let xAxisPropertiesScale = this.xAxisProperties.scale;\r\n let yAxisPropertiesScale = this.yAxisProperties.scale;\r\n for (let i in dataPoints) {\r\n let currentPoint: ScatterChartDataPoint = dataPoints[i];\r\n let circleX = xAxisPropertiesScale(currentPoint.x);\r\n let circleY = yAxisPropertiesScale(currentPoint.y);\r\n let horizontalDistance = circleX - x;\r\n let verticalDistance = circleY - y;\r\n let distanceSqrd = (horizontalDistance * horizontalDistance) + (verticalDistance * verticalDistance);\r\n if (minDistance === Number.MAX_VALUE) {\r\n selectedIndex = <any>i;\r\n minDistance = distanceSqrd;\r\n }\r\n else if (minDistance && minDistance > distanceSqrd) {\r\n selectedIndex = <any>i;\r\n minDistance = distanceSqrd;\r\n }\r\n }\r\n return selectedIndex;\r\n }\r\n\r\n private updateLegend(dotIndex: number): void {\r\n if (this.lastDotIndex == null || this.lastDotIndex !== dotIndex) {//update the legend only if the data change.\r\n let legendItems = this.createLegendDataPoints(dotIndex);\r\n this.host.updateLegend(legendItems);\r\n this.lastDotIndex = dotIndex;\r\n }\r\n }\r\n\r\n private createLegendDataPoints(dotIndex: number): LegendData {\r\n let formatStringProp = scatterChartProps.general.formatString;\r\n let legendItems: LegendDataPoint[] = [];\r\n let data = this.data;\r\n debug.assert(data.dataPoints.length > dotIndex, \"dataPoints length:\" + data.dataPoints.length + \"is smaller than index:\" + dotIndex);\r\n let point = data.dataPoints[dotIndex];\r\n //set the title of the legend to be the category or radius or group or blank\r\n let blank = valueFormatter.format(null);\r\n let title = blank;\r\n let legendData = data.legendData;\r\n debug.assertValue(legendData, \"legendData\");\r\n debug.assertValue(legendData.dataPoints, \"legendData\");\r\n let legendDataPoints = legendData.dataPoints;\r\n let category = point.formattedCategory.getValue();\r\n if (category !== blank) {\r\n title = category;\r\n if (point != null && point.radius != null && point.radius.sizeMeasure != null) {\r\n title += \"; \" + valueFormatter.format(point.radius.sizeMeasure.source.groupName);\r\n }\r\n } else if (point.radius.sizeMeasure != null) {\r\n title = valueFormatter.format(point.radius.sizeMeasure.source.groupName);\r\n } else if (legendDataPoints.length >= dotIndex && legendDataPoints[dotIndex].label !== blank) {\r\n title = legendDataPoints[dotIndex].label;\r\n }\r\n\r\n if (data.xCol != null) {\r\n legendItems.push({\r\n category: title,\r\n color: point.fill,\r\n identity: SelectionIdBuilder.builder().withMeasure(data.xCol.queryName).createSelectionId(),\r\n selected: point.selected,\r\n icon: LegendIcon.Box,\r\n label: valueFormatter.format(this.data.axesLabels.x),\r\n measure: valueFormatter.format(point.x, valueFormatter.getFormatString(data.xCol, formatStringProp)),\r\n iconOnlyOnLabel: true,\r\n });\r\n }\r\n if (data.yCol != null) {\r\n legendItems.push({\r\n category: title,\r\n color: point.fill,\r\n identity: SelectionIdBuilder.builder().withMeasure(data.yCol.queryName).createSelectionId(),\r\n selected: point.selected,\r\n icon: LegendIcon.Box,\r\n label: valueFormatter.format(data.axesLabels.y),\r\n measure: valueFormatter.format(point.y, valueFormatter.getFormatString(data.yCol, formatStringProp)),\r\n iconOnlyOnLabel: true,\r\n });\r\n }\r\n if (data.size != null) {\r\n legendItems.push({\r\n category: title,\r\n color: point.fill,\r\n identity: SelectionIdBuilder.builder().withMeasure(data.size.queryName).createSelectionId(),\r\n selected: point.selected,\r\n icon: LegendIcon.Box,\r\n label: valueFormatter.format(data.size.displayName),\r\n measure: valueFormatter.format(point.radius.sizeMeasure.values[point.radius.index], valueFormatter.getFormatString(data.size, formatStringProp)),\r\n iconOnlyOnLabel: true\r\n });\r\n }\r\n\r\n return {dataPoints: legendItems };\r\n }\r\n }\r\n}\r\n\n\n\n/** WEBPACK FOOTER **\n ** ./src/Clients/Visuals/behaviours/scatterChartBehaviors.ts\n **/","/*\r\n * Power BI Visualizations\r\n *\r\n * Copyright (c) Microsoft Corporation\r\n * All rights reserved. \r\n * MIT License\r\n *\r\n * Permission is hereby granted, free of charge, to any person obtaining a copy\r\n * of this software and associated documentation files (the \"\"Software\"\"), to deal\r\n * in the Software without restriction, including without limitation the rights\r\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\r\n * copies of the Software, and to permit persons to whom the Software is\r\n * furnished to do so, subject to the following conditions:\r\n * \r\n * The above copyright notice and this permission notice shall be included in \r\n * all copies or substantial portions of the Software.\r\n * \r\n * THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR \r\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, \r\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE \r\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER \r\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\r\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\r\n * THE SOFTWARE.\r\n */\r\n\r\n/// <reference path=\"../_references.ts\"/>\r\n\r\nmodule powerbi.visuals {\r\n export interface HorizontalSlicerBehaviorOptions extends SlicerBehaviorOptions {\r\n itemsContainer: D3.Selection;\r\n }\r\n\r\n export class HorizontalSlicerWebBehavior implements IInteractiveBehavior {\r\n private itemLabels: D3.Selection;\r\n private dataPoints: SlicerDataPoint[];\r\n private interactivityService: IInteractivityService;\r\n private slicerSettings: SlicerSettings;\r\n\r\n public bindEvents(options: HorizontalSlicerBehaviorOptions, selectionHandler: ISelectionHandler): void {\r\n this.itemLabels = options.itemLabels;\r\n this.dataPoints = options.dataPoints;\r\n this.interactivityService = options.interactivityService;\r\n this.slicerSettings = options.settings;\r\n\r\n SlicerWebBehavior.bindSlicerEvents(options.slicerContainer, this.itemLabels, options.clear, selectionHandler, this.slicerSettings, this.interactivityService, options.slicerValueHandler);\r\n }\r\n\r\n public renderSelection(hasSelection: boolean): void {\r\n SlicerWebBehavior.setSelectionOnSlicerItems(this.itemLabels, this.itemLabels, hasSelection, this.interactivityService, this.slicerSettings);\r\n }\r\n }\r\n} \n\n\n/** WEBPACK FOOTER **\n ** ./src/Clients/Visuals/behaviours/horizontalSlicerBehaviors.ts\n **/","/*\r\n * Power BI Visualizations\r\n *\r\n * Copyright (c) Microsoft Corporation\r\n * All rights reserved. \r\n * MIT License\r\n *\r\n * Permission is hereby granted, free of charge, to any person obtaining a copy\r\n * of this software and associated documentation files (the \"\"Software\"\"), to deal\r\n * in the Software without restriction, including without limitation the rights\r\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\r\n * copies of the Software, and to permit persons to whom the Software is\r\n * furnished to do so, subject to the following conditions:\r\n * \r\n * The above copyright notice and this permission notice shall be included in \r\n * all copies or substantial portions of the Software.\r\n * \r\n * THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR \r\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, \r\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE \r\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER \r\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\r\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\r\n * THE SOFTWARE.\r\n */\r\n\r\n/// <reference path=\"../_references.ts\"/>\r\n\r\nmodule powerbi.visuals {\r\n export interface VerticalSlicerBehaviorOptions extends SlicerBehaviorOptions {\r\n itemContainers: D3.Selection;\r\n itemInputs: D3.Selection;\r\n searchInput: D3.Selection;\r\n }\r\n\r\n export class VerticalSlicerWebBehavior implements IInteractiveBehavior {\r\n private itemLabels: D3.Selection;\r\n private itemInputs: D3.Selection;\r\n private dataPoints: SlicerDataPoint[];\r\n private interactivityService: IInteractivityService;\r\n private settings: SlicerSettings;\r\n\r\n public bindEvents(options: VerticalSlicerBehaviorOptions, selectionHandler: ISelectionHandler): void {\r\n let slicers = options.itemContainers;\r\n\r\n this.itemLabels = options.itemLabels;\r\n this.itemInputs = options.itemInputs;\r\n this.dataPoints = options.dataPoints;\r\n this.interactivityService = options.interactivityService;\r\n this.settings = options.settings;\r\n\r\n SlicerWebBehavior.bindSlicerEvents(options.slicerContainer, slicers, options.clear, selectionHandler, this.settings, this.interactivityService, options.slicerValueHandler, options.searchInput);\r\n }\r\n\r\n public renderSelection(hasSelection: boolean): void {\r\n SlicerWebBehavior.setSelectionOnSlicerItems(this.itemInputs, this.itemLabels, hasSelection, this.interactivityService, this.settings);\r\n }\r\n }\r\n} \n\n\n/** WEBPACK FOOTER **\n ** ./src/Clients/Visuals/behaviours/verticalSlicerBehaviors.ts\n **/","/*\r\n * Power BI Visualizations\r\n *\r\n * Copyright (c) Microsoft Corporation\r\n * All rights reserved. \r\n * MIT License\r\n *\r\n * Permission is hereby granted, free of charge, to any person obtaining a copy\r\n * of this software and associated documentation files (the \"\"Software\"\"), to deal\r\n * in the Software without restriction, including without limitation the rights\r\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\r\n * copies of the Software, and to permit persons to whom the Software is\r\n * furnished to do so, subject to the following conditions:\r\n * \r\n * The above copyright notice and this permission notice shall be included in \r\n * all copies or substantial portions of the Software.\r\n * \r\n * THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR \r\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, \r\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE \r\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER \r\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\r\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\r\n * THE SOFTWARE.\r\n */\r\n\r\n/// <reference path=\"../_references.ts\"/>\r\n\r\nmodule powerbi.visuals {\r\n import DOMConstants = jsCommon.DOMConstants;\r\n import KeyUtils = jsCommon.KeyUtils;\r\n import SlicerOrientation = slicerOrientation.Orientation;\r\n\r\n export interface SlicerOrientationBehaviorOptions {\r\n behaviorOptions: SlicerBehaviorOptions;\r\n orientation: slicerOrientation.Orientation;\r\n }\r\n\r\n export interface SlicerBehaviorOptions {\r\n slicerContainer: D3.Selection;\r\n itemLabels: D3.Selection;\r\n clear: D3.Selection;\r\n dataPoints: SlicerDataPoint[];\r\n interactivityService: IInteractivityService;\r\n settings: SlicerSettings;\r\n slicerValueHandler: SlicerValueHandler;\r\n }\r\n\r\n export class SlicerWebBehavior implements IInteractiveBehavior {\r\n private behavior: IInteractiveBehavior;\r\n private static searchInputTimeoutDuration = 500;\r\n\r\n public bindEvents(options: SlicerOrientationBehaviorOptions, selectionHandler: ISelectionHandler): void {\r\n this.behavior = this.createWebBehavior(options);\r\n this.behavior.bindEvents(options.behaviorOptions, selectionHandler);\r\n }\r\n\r\n public renderSelection(hasSelection: boolean): void {\r\n this.behavior.renderSelection(hasSelection);\r\n }\r\n\r\n public static bindSlicerEvents(\r\n slicerContainer: D3.Selection,\r\n slicers: D3.Selection,\r\n slicerClear: D3.Selection,\r\n selectionHandler: ISelectionHandler,\r\n slicerSettings: SlicerSettings,\r\n interactivityService: IInteractivityService,\r\n slicerValueHandler: SlicerValueHandler,\r\n slicerSearch?: D3.Selection): void {\r\n SlicerWebBehavior.bindSlicerItemSelectionEvent(slicers, selectionHandler, slicerSettings, interactivityService);\r\n SlicerWebBehavior.bindSlicerClearEvent(slicerClear, selectionHandler);\r\n if (slicerSearch)\r\n SlicerWebBehavior.bindSlicerSearchEvent(slicerSearch, selectionHandler, slicerValueHandler);\r\n\r\n SlicerWebBehavior.styleSlicerContainer(slicerContainer, interactivityService);\r\n }\r\n\r\n public static setSelectionOnSlicerItems(selectableItems: D3.Selection, itemLabel: D3.Selection, hasSelection: boolean, interactivityService: IInteractivityService, slicerSettings: SlicerSettings): void {\r\n if (!hasSelection && !interactivityService.isSelectionModeInverted()) {\r\n selectableItems.filter('.selected').classed('selected', false);\r\n selectableItems.filter('.partiallySelected').classed('partiallySelected', false);\r\n let input = selectableItems.selectAll('input');\r\n if (input) {\r\n input.property('checked', false);\r\n }\r\n itemLabel.style('color', slicerSettings.slicerText.color);\r\n }\r\n else {\r\n SlicerWebBehavior.styleSlicerItems(selectableItems, hasSelection, interactivityService.isSelectionModeInverted());\r\n }\r\n }\r\n\r\n public static styleSlicerItems(slicerItems: D3.Selection, hasSelection: boolean, isSelectionInverted: boolean): void {\r\n slicerItems.each(function (d: SlicerDataPoint) {\r\n let slicerItem: HTMLElement = this;\r\n let shouldCheck: boolean = false;\r\n if (d.isSelectAllDataPoint) {\r\n if (hasSelection) {\r\n slicerItem.classList.add('partiallySelected');\r\n shouldCheck = false;\r\n }\r\n else {\r\n slicerItem.classList.remove('partiallySelected');\r\n shouldCheck = isSelectionInverted;\r\n }\r\n }\r\n else {\r\n shouldCheck = jsCommon.LogicExtensions.XOR(d.selected, isSelectionInverted);\r\n }\r\n \r\n if (shouldCheck)\r\n slicerItem.classList.add('selected');\r\n else\r\n slicerItem.classList.remove('selected');\r\n\r\n // Set input selected state to match selection\r\n let input = slicerItem.getElementsByTagName('input')[0];\r\n if (input)\r\n input.checked = shouldCheck;\r\n });\r\n }\r\n\r\n private static bindSlicerItemSelectionEvent(slicers: D3.Selection, selectionHandler: ISelectionHandler, slicerSettings: SlicerSettings, interactivityService: IInteractivityService): void {\r\n slicers.on(\"click\", (d: SlicerDataPoint) => {\r\n d3.event.preventDefault();\r\n if (d.isSelectAllDataPoint) {\r\n selectionHandler.toggleSelectionModeInversion();\r\n }\r\n else {\r\n selectionHandler.handleSelection(d, SlicerWebBehavior.isMultiSelect(d3.event, slicerSettings, interactivityService));\r\n }\r\n selectionHandler.persistSelectionFilter(slicerProps.filterPropertyIdentifier);\r\n });\r\n }\r\n\r\n private static bindSlicerClearEvent(slicerClear: D3.Selection, selectionHandler: ISelectionHandler): void {\r\n if (slicerClear) {\r\n slicerClear.on(\"click\", () => {\r\n selectionHandler.handleClearSelection();\r\n selectionHandler.persistSelectionFilter(slicerProps.filterPropertyIdentifier);\r\n });\r\n }\r\n }\r\n \r\n private static bindSlicerSearchEvent(slicerSearch: D3.Selection, selectionHandler: ISelectionHandler, slicerValueHandler: SlicerValueHandler): void {\r\n if (slicerSearch.empty())\r\n return;\r\n\r\n slicerSearch.on(DOMConstants.keyDownEventName, () => {\r\n if (d3.event.ctrlKey && KeyUtils.isCtrlDefaultKey(d3.event.keyCode))\r\n d3.event.stopPropagation();\r\n else if (KeyUtils.isArrowKey(d3.event.keyCode) || d3.event.keyCode === DOMConstants.deleteKeyCode)\r\n d3.event.stopPropagation();\r\n else if (d3.event.keyCode === DOMConstants.escKeyCode) {\r\n\r\n // Clear search when ESC key is pressed\r\n selectionHandler.persistSelfFilter(slicerProps.selfFilterPropertyIdentifier, null);\r\n d3.event.stopPropagation();\r\n }\r\n else if (d3.event.keyCode === DOMConstants.enterKeyCode) {\r\n SlicerWebBehavior.startSearch(slicerSearch, selectionHandler, slicerValueHandler);\r\n d3.event.stopPropagation();\r\n }\r\n }).on(DOMConstants.keyUpEventName, _.debounce(() => {\r\n SlicerWebBehavior.startSearch(slicerSearch, selectionHandler, slicerValueHandler);\r\n }, SlicerWebBehavior.searchInputTimeoutDuration));\r\n }\r\n\r\n private static startSearch(slicerSearch: D3.Selection, selectionHandler: ISelectionHandler, slicerValueHandler: SlicerValueHandler): void {\r\n let element: HTMLInputElement = <HTMLInputElement>slicerSearch.node();\r\n let searchKey: string = element && element.value;\r\n searchKey = _.trim(searchKey);\r\n // When searchKey is cleared.\r\n if (_.isEmpty(searchKey)) {\r\n selectionHandler.persistSelfFilter(slicerProps.selfFilterPropertyIdentifier, null);\r\n return;\r\n }\r\n\r\n let updatedFilter = slicerValueHandler.getUpdatedSelfFilter(searchKey);\r\n if (updatedFilter)\r\n selectionHandler.persistSelfFilter(slicerProps.selfFilterPropertyIdentifier, updatedFilter);\r\n }\r\n\r\n private static styleSlicerContainer(slicerContainer: D3.Selection, interactivityService: IInteractivityService) {\r\n let hasSelection = (interactivityService.hasSelection() && interactivityService.isDefaultValueEnabled() === undefined)\r\n || interactivityService.isDefaultValueEnabled() === false;\r\n slicerContainer.classed('hasSelection', hasSelection);\r\n }\r\n\r\n private static isMultiSelect(event: D3.D3Event, settings: SlicerSettings, interactivityService: IInteractivityService): boolean {\r\n // If selection is inverted, assume we're always in multi-select mode;\r\n // Also, Ctrl can be used to multi-select even in single-select mode.\r\n return interactivityService.isSelectionModeInverted()\r\n || !settings.selection.singleSelect\r\n || event.ctrlKey;\r\n }\r\n\r\n private createWebBehavior(options: SlicerOrientationBehaviorOptions): IInteractiveBehavior {\r\n let behavior: IInteractiveBehavior;\r\n let orientation = options.orientation;\r\n switch (orientation) {\r\n case SlicerOrientation.Horizontal:\r\n behavior = new HorizontalSlicerWebBehavior();\r\n break;\r\n\r\n case SlicerOrientation.Vertical:\r\n default:\r\n behavior = new VerticalSlicerWebBehavior();\r\n break;\r\n }\r\n return behavior;\r\n }\r\n }\r\n} \n\n\n/** WEBPACK FOOTER **\n ** ./src/Clients/Visuals/behaviours/slicerBehaviors.ts\n **/","/*\r\n * Power BI Visualizations\r\n *\r\n * Copyright (c) Microsoft Corporation\r\n * All rights reserved. \r\n * MIT License\r\n *\r\n * Permission is hereby granted, free of charge, to any person obtaining a copy\r\n * of this software and associated documentation files (the \"\"Software\"\"), to deal\r\n * in the Software without restriction, including without limitation the rights\r\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\r\n * copies of the Software, and to permit persons to whom the Software is\r\n * furnished to do so, subject to the following conditions:\r\n * \r\n * The above copyright notice and this permission notice shall be included in \r\n * all copies or substantial portions of the Software.\r\n * \r\n * THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR \r\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, \r\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE \r\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER \r\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\r\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\r\n * THE SOFTWARE.\r\n */\r\n\r\n/// <reference path=\"../_references.ts\"/>\r\n\r\nmodule powerbi.visuals {\r\n export interface LegendBehaviorOptions {\r\n legendItems: D3.Selection;\r\n legendIcons: D3.Selection;\r\n clearCatcher: D3.Selection;\r\n }\r\n\r\n export class LegendBehavior implements IInteractiveBehavior {\r\n public static dimmedLegendColor = '#A6A6A6';\r\n private legendIcons;\r\n\r\n public bindEvents(options: LegendBehaviorOptions, selectionHandler: ISelectionHandler): void {\r\n let legendItems = options.legendItems;\r\n this.legendIcons = options.legendIcons;\r\n let clearCatcher = options.clearCatcher;\r\n\r\n InteractivityUtils.registerStandardSelectionHandler(legendItems, selectionHandler);\r\n\r\n clearCatcher.on('click', () => {\r\n selectionHandler.handleClearSelection();\r\n });\r\n }\r\n\r\n public renderSelection(hasSelection: boolean): void {\r\n if (hasSelection) {\r\n this.legendIcons.style({\r\n 'fill': (d: LegendDataPoint) => {\r\n if (!d.selected)\r\n return LegendBehavior.dimmedLegendColor; \r\n else\r\n return d.color;\r\n }\r\n });\r\n }\r\n else {\r\n this.legendIcons.style({\r\n 'fill': (d: LegendDataPoint) => {\r\n return d.color;\r\n }\r\n });\r\n }\r\n }\r\n }\r\n}\n\n\n/** WEBPACK FOOTER **\n ** ./src/Clients/Visuals/behaviours/legendBehaviors.ts\n **/","/*\r\n * Power BI Visualizations\r\n *\r\n * Copyright (c) Microsoft Corporation\r\n * All rights reserved. \r\n * MIT License\r\n *\r\n * Permission is hereby granted, free of charge, to any person obtaining a copy\r\n * of this software and associated documentation files (the \"\"Software\"\"), to deal\r\n * in the Software without restriction, including without limitation the rights\r\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\r\n * copies of the Software, and to permit persons to whom the Software is\r\n * furnished to do so, subject to the following conditions:\r\n * \r\n * The above copyright notice and this permission notice shall be included in \r\n * all copies or substantial portions of the Software.\r\n * \r\n * THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR \r\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, \r\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE \r\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER \r\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\r\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\r\n * THE SOFTWARE.\r\n */\r\n\r\n/// <reference path=\"../_references.ts\"/>\r\n\r\nmodule powerbi.visuals {\r\n export interface TreemapBehaviorOptions {\r\n shapes: D3.Selection;\r\n highlightShapes: D3.Selection;\r\n majorLabels: D3.Selection;\r\n minorLabels: D3.Selection;\r\n nodes: TreemapNode[];\r\n hasHighlights: boolean;\r\n }\r\n\r\n export class TreemapWebBehavior implements IInteractiveBehavior {\r\n private shapes: D3.Selection;\r\n private highlightShapes: D3.Selection;\r\n private hasHighlights: boolean;\r\n\r\n public bindEvents(options: TreemapBehaviorOptions, selectionHandler: ISelectionHandler): void {\r\n let shapes = this.shapes = options.shapes;\r\n let highlightShapes = this.highlightShapes = options.highlightShapes;\r\n let majorLabels = options.majorLabels;\r\n let minorLabels = options.minorLabels;\r\n this.hasHighlights = options.hasHighlights;\r\n\r\n InteractivityUtils.registerStandardInteractivityHandlers(shapes, selectionHandler);\r\n InteractivityUtils.registerStandardInteractivityHandlers(highlightShapes, selectionHandler);\r\n\r\n if (majorLabels) {\r\n InteractivityUtils.registerStandardInteractivityHandlers(majorLabels, selectionHandler);\r\n }\r\n if (minorLabels) {\r\n InteractivityUtils.registerStandardInteractivityHandlers(minorLabels, selectionHandler);\r\n }\r\n }\r\n\r\n public renderSelection(hasSelection: boolean): void {\r\n let hasHighlights = this.hasHighlights;\r\n this.shapes\r\n .style(\"fill\", (d: TreemapNode) => Treemap.getFill(d, /* isHighlightRect */ false))\r\n .style(\"fill-opacity\", (d: TreemapNode) => Treemap.getFillOpacity(d, hasSelection, !d.selected && hasHighlights, /* isHighlightRect */ false));\r\n this.highlightShapes\r\n .style(\"fill\", (d: TreemapNode) => Treemap.getFill(d, /* isHighlightRect */ true))\r\n .style(\"fill-opacity\", (d: TreemapNode) => Treemap.getFillOpacity(d, hasSelection, !d.selected && hasHighlights, /* isHighlightRect */ true));\r\n }\r\n }\r\n}\n\n\n/** WEBPACK FOOTER **\n ** ./src/Clients/Visuals/behaviours/treemapBehaviors.ts\n **/","/*\r\n * Power BI Visualizations\r\n *\r\n * Copyright (c) Microsoft Corporation\r\n * All rights reserved. \r\n * MIT License\r\n *\r\n * Permission is hereby granted, free of charge, to any person obtaining a copy\r\n * of this software and associated documentation files (the \"\"Software\"\"), to deal\r\n * in the Software without restriction, including without limitation the rights\r\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\r\n * copies of the Software, and to permit persons to whom the Software is\r\n * furnished to do so, subject to the following conditions:\r\n * \r\n * The above copyright notice and this permission notice shall be included in \r\n * all copies or substantial portions of the Software.\r\n * \r\n * THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR \r\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, \r\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE \r\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER \r\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\r\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\r\n * THE SOFTWARE.\r\n */\r\n\r\n/// <reference path=\"../_references.ts\"/>\r\n\r\nmodule powerbi.visuals {\r\n export interface WaterfallChartBehaviorOptions {\r\n bars: D3.Selection;\r\n }\r\n\r\n export class WaterfallChartWebBehavior {\r\n private bars: D3.Selection;\r\n\r\n public bindEvents(options: WaterfallChartBehaviorOptions, selectionHandler: ISelectionHandler): void {\r\n let bars = this.bars = options.bars;\r\n\r\n bars.on('click', (d: WaterfallChartDataPoint) => {\r\n if (!d.isTotal) {\r\n selectionHandler.handleSelection(d, d3.event.ctrlKey);\r\n }\r\n });\r\n\r\n bars.on('contextmenu', (d: WaterfallChartDataPoint) => {\r\n if (d3.event.ctrlKey)\r\n return;\r\n\r\n d3.event.preventDefault();\r\n\r\n if (!d.isTotal) {\r\n let position = InteractivityUtils.getPositionOfLastInputEvent();\r\n selectionHandler.handleContextMenu(d, position);\r\n }\r\n });\r\n }\r\n\r\n public renderSelection(hasSelection: boolean): void {\r\n this.bars.style(\"fill-opacity\", (d: WaterfallChartDataPoint) => d.isTotal ? ColumnUtil.DefaultOpacity : ColumnUtil.getFillOpacity(d.selected, d.highlight, hasSelection, false));\r\n }\r\n }\r\n} \n\n\n/** WEBPACK FOOTER **\n ** ./src/Clients/Visuals/behaviours/waterfallChartBehavior.ts\n **/","/*\r\n * Power BI Visualizations\r\n *\r\n * Copyright (c) Microsoft Corporation\r\n * All rights reserved. \r\n * MIT License\r\n *\r\n * Permission is hereby granted, free of charge, to any person obtaining a copy\r\n * of this software and associated documentation files (the \"\"Software\"\"), to deal\r\n * in the Software without restriction, including without limitation the rights\r\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\r\n * copies of the Software, and to permit persons to whom the Software is\r\n * furnished to do so, subject to the following conditions:\r\n * \r\n * The above copyright notice and this permission notice shall be included in \r\n * all copies or substantial portions of the Software.\r\n * \r\n * THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR \r\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, \r\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE \r\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER \r\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\r\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\r\n * THE SOFTWARE.\r\n */\r\n\r\n/// <reference path=\"../_references.ts\"/>\r\n\r\nmodule powerbi.visuals {\r\n export interface LabelsBehaviorOptions {\r\n labelItems: D3.Selection;\r\n }\r\n\r\n export class LabelsBehavior implements IInteractiveBehavior {\r\n public static DefaultLabelOpacity = 1;\r\n public static DimmedLabelOpacity = 0.6;\r\n private labelItems: D3.Selection;\r\n\r\n public bindEvents(options: LabelsBehaviorOptions, selectionHandler: ISelectionHandler): void {\r\n this.labelItems = options.labelItems;\r\n\r\n InteractivityUtils.registerStandardSelectionHandler(this.labelItems, selectionHandler);\r\n }\r\n\r\n public renderSelection(hasSelection: boolean): void {\r\n if (hasSelection) {\r\n this.labelItems.style({\r\n 'opacity': (d: Label) => {\r\n if (!d.selected)\r\n return LabelsBehavior.DimmedLabelOpacity;\r\n else\r\n return LabelsBehavior.DefaultLabelOpacity;\r\n }\r\n });\r\n }\r\n else {\r\n this.labelItems.style({\r\n 'opacity': LabelsBehavior.DefaultLabelOpacity,\r\n });\r\n }\r\n }\r\n }\r\n}\r\n\n\n\n/** WEBPACK FOOTER **\n ** ./src/Clients/Visuals/behaviours/LabelsBehavior.ts\n **/","/*\r\n * Power BI Visualizations\r\n *\r\n * Copyright (c) Microsoft Corporation\r\n * All rights reserved. \r\n * MIT License\r\n *\r\n * Permission is hereby granted, free of charge, to any person obtaining a copy\r\n * of this software and associated documentation files (the \"\"Software\"\"), to deal\r\n * in the Software without restriction, including without limitation the rights\r\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\r\n * copies of the Software, and to permit persons to whom the Software is\r\n * furnished to do so, subject to the following conditions:\r\n * \r\n * The above copyright notice and this permission notice shall be included in \r\n * all copies or substantial portions of the Software.\r\n * \r\n * THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR \r\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, \r\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE \r\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER \r\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\r\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\r\n * THE SOFTWARE.\r\n */\r\n\r\n/// <reference path=\"../_references.ts\"/>\r\n\r\nmodule powerbi.visuals {\r\n export interface CartesianBehaviorOptions {\r\n layerOptions: any[];\r\n clearCatcher: D3.Selection;\r\n }\r\n\r\n export class CartesianChartBehavior implements IInteractiveBehavior {\r\n private behaviors: IInteractiveBehavior[];\r\n\r\n constructor(behaviors: IInteractiveBehavior[]) {\r\n this.behaviors = behaviors;\r\n }\r\n\r\n public bindEvents(options: CartesianBehaviorOptions, selectionHandler: ISelectionHandler): void {\r\n let behaviors = this.behaviors;\r\n for (let i = 0, ilen = behaviors.length; i < ilen; i++) {\r\n behaviors[i].bindEvents(options.layerOptions[i], selectionHandler);\r\n }\r\n\r\n options.clearCatcher.on('click', () => {\r\n selectionHandler.handleClearSelection();\r\n });\r\n }\r\n\r\n public renderSelection(hasSelection: boolean) {\r\n for (let behavior of this.behaviors) {\r\n behavior.renderSelection(hasSelection);\r\n }\r\n }\r\n }\r\n} \n\n\n/** WEBPACK FOOTER **\n ** ./src/Clients/Visuals/behaviours/cartesianChartBehaviors.ts\n **/","/*\r\n * Power BI Visualizations\r\n *\r\n * Copyright (c) Microsoft Corporation\r\n * All rights reserved. \r\n * MIT License\r\n *\r\n * Permission is hereby granted, free of charge, to any person obtaining a copy\r\n * of this software and associated documentation files (the \"\"Software\"\"), to deal\r\n * in the Software without restriction, including without limitation the rights\r\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\r\n * copies of the Software, and to permit persons to whom the Software is\r\n * furnished to do so, subject to the following conditions:\r\n * \r\n * The above copyright notice and this permission notice shall be included in \r\n * all copies or substantial portions of the Software.\r\n * \r\n * THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR \r\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, \r\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE \r\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER \r\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\r\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\r\n * THE SOFTWARE.\r\n */\r\n\r\n/// <reference path=\"../_references.ts\"/>\r\n\r\nmodule powerbi.visuals {\r\n\r\n export interface VisualConfig {\r\n\r\n visualType: string;\r\n\r\n projections: data.QueryProjectionsByRole[];\r\n \r\n /**\r\n * This is the one that has info like Total, Combochart viz types, legend settings, etc...\r\n * Each IVisual implementation, should simply cast this to whatever object they expect.\r\n */\r\n config?: any;\r\n }\r\n}\n\n\n/** WEBPACK FOOTER **\n ** ./src/Clients/Visuals/contracts/contracts.ts\n **/","/*\r\n * Power BI Visualizations\r\n *\r\n * Copyright (c) Microsoft Corporation\r\n * All rights reserved. \r\n * MIT License\r\n *\r\n * Permission is hereby granted, free of charge, to any person obtaining a copy\r\n * of this software and associated documentation files (the \"\"Software\"\"), to deal\r\n * in the Software without restriction, including without limitation the rights\r\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\r\n * copies of the Software, and to permit persons to whom the Software is\r\n * furnished to do so, subject to the following conditions:\r\n * \r\n * The above copyright notice and this permission notice shall be included in \r\n * all copies or substantial portions of the Software.\r\n * \r\n * THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR \r\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, \r\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE \r\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER \r\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\r\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\r\n * THE SOFTWARE.\r\n */\r\n\r\n/// <reference path=\"../_references.ts\"/>\r\n\r\nmodule powerbi.visuals {\r\n import ITextAsSVGMeasurer = powerbi.ITextAsSVGMeasurer;\r\n\r\n /**\r\n * Default ranges are for when we have a field chosen for the axis,\r\n * but no values are returned by the query.\r\n */\r\n export const emptyDomain = [0, 0];\r\n\r\n export interface IAxisProperties {\r\n /** \r\n * The D3 Scale object.\r\n */\r\n scale: D3.Scale.GenericScale<any>;\r\n /** \r\n * The D3 Axis object.\r\n */\r\n axis: D3.Svg.Axis;\r\n /**\r\n * An array of the tick values to display for this axis.\r\n */\r\n values: any[];\r\n /** \r\n * The ValueType of the column used for this axis.\r\n */\r\n axisType: ValueType;\r\n /**\r\n * A formatter with appropriate properties configured for this field.\r\n */\r\n formatter: IValueFormatter;\r\n /**\r\n * The axis title label.\r\n */\r\n axisLabel: string;\r\n /**\r\n * Cartesian axes are either a category or value axis.\r\n */\r\n isCategoryAxis: boolean; \r\n /** \r\n * (optional) The max width for category tick label values. used for ellipsis truncation / label rotation.\r\n */\r\n xLabelMaxWidth?: number;\r\n /** \r\n * (optional) The thickness of each category on the axis.\r\n */\r\n categoryThickness?: number;\r\n /** \r\n * (optional) The outer padding in pixels applied to the D3 scale.\r\n */\r\n outerPadding?: number;\r\n /** \r\n * (optional) Whether we are using a default domain.\r\n */\r\n usingDefaultDomain?: boolean;\r\n /** \r\n * (optional) do default d3 axis labels fit?\r\n */\r\n willLabelsFit?: boolean;\r\n /**\r\n * (optional) word break axis labels\r\n */\r\n willLabelsWordBreak?: boolean;\r\n /** \r\n * (optional) Whether log scale is possible on the current domain.\r\n */\r\n isLogScaleAllowed?: boolean;\r\n /** \r\n * (optional) Whether domain contains zero value and log scale is enabled.\r\n */\r\n hasDisallowedZeroInDomain?: boolean;\r\n /** \r\n *(optional) The original data domain. Linear scales use .nice() to round to cleaner edge values. Keep the original data domain for later.\r\n */\r\n dataDomain?: number[];\r\n /** \r\n * (optional) The D3 graphics context for this axis\r\n */\r\n graphicsContext?: D3.Selection;\r\n }\r\n\r\n export interface IMargin {\r\n top: number;\r\n bottom: number;\r\n left: number;\r\n right: number;\r\n }\r\n\r\n export interface CreateAxisOptions {\r\n /**\r\n * The dimension length for the axis, in pixels.\r\n */\r\n pixelSpan: number;\r\n /** \r\n * The data domain. [min, max] for a scalar axis, or [1...n] index array for ordinal.\r\n */\r\n dataDomain: number[];\r\n /** \r\n * The DataViewMetadataColumn will be used for dataType and tick value formatting.\r\n */\r\n metaDataColumn: DataViewMetadataColumn; //TODO: remove this, we should just be passing in the formatString and the ValueType, not this DataView-specific object\r\n /**\r\n * The format string.\r\n */\r\n formatString: string;\r\n /** \r\n * outerPadding to be applied to the axis.\r\n */\r\n outerPadding: number; \r\n /** \r\n * Indicates if this is the category axis.\r\n */\r\n isCategoryAxis?: boolean;\r\n /**\r\n * If true and the dataType is numeric or dateTime,\r\n * create a linear axis, else create an ordinal axis.\r\n */\r\n isScalar?: boolean;\r\n /**\r\n * (optional) The scale is inverted for a vertical axis,\r\n * and different optimizations are made for tick labels.\r\n */\r\n isVertical?: boolean;\r\n /** \r\n * (optional) For visuals that do not need zero (e.g. column/bar) use tickInterval.\r\n */\r\n useTickIntervalForDisplayUnits?: boolean;\r\n /**\r\n * (optional) Combo charts can override the tick count to\r\n * align y1 and y2 grid lines.\r\n */\r\n forcedTickCount?: number;\r\n /**\r\n * (optional) For scalar axis with scalar keys, the number of ticks should never exceed the number of scalar keys, \r\n * or labeling will look wierd (i.e. level of detail is Year, but month labels are shown between years)\r\n */\r\n maxTickCount?: number;\r\n /** \r\n * (optional) Callback for looking up actual values from indices, \r\n * used when formatting tick labels. \r\n */\r\n getValueFn?: (index: number, type: ValueType) => any;\r\n /**\r\n * (optional) The width/height of each category on the axis.\r\n */\r\n categoryThickness?: number;\r\n /** (optional) the scale type of the axis. e.g. log, linear */\r\n scaleType?: string;\r\n /** (optional) user selected display units */\r\n axisDisplayUnits?: number;\r\n /** (optional) user selected precision */\r\n axisPrecision?: number;\r\n /** (optional) for 100 percent stacked charts, causes formatString override and minTickInterval adjustments */\r\n is100Pct?: boolean;\r\n /** (optional) sets clamping on the D3 scale, useful for drawing column chart rectangles as it simplifies the math during layout */\r\n shouldClamp?: boolean;\r\n }\r\n\r\n export interface CreateScaleResult {\r\n scale: D3.Scale.GenericScale<any>;\r\n bestTickCount: number;\r\n usingDefaultDomain?: boolean;\r\n }\r\n\r\n export interface TickLabelMargins {\r\n xMax: number;\r\n yLeft: number;\r\n yRight: number;\r\n }\r\n\r\n export module AxisHelper {\r\n let XLabelMaxAllowedOverflow = 35;\r\n let TextHeightConstant = 10;\r\n let MinTickCount = 2;\r\n let DefaultBestTickCount = 3;\r\n let LeftPadding = 10;\r\n let ScalarTickLabelPadding = 3;\r\n\r\n export function getRecommendedNumberOfTicksForXAxis(availableWidth: number) {\r\n if (availableWidth < 300)\r\n return 3;\r\n if (availableWidth < 500)\r\n return 5;\r\n\r\n return 8;\r\n }\r\n\r\n export function getRecommendedNumberOfTicksForYAxis(availableWidth: number) {\r\n if (availableWidth < 150)\r\n return 3;\r\n if (availableWidth < 300)\r\n return 5;\r\n\r\n return 8;\r\n }\r\n\r\n /**\r\n * Get the best number of ticks based on minimum value, maximum value,\r\n * measure metadata and max tick count.\r\n * \r\n * @param min The minimum of the data domain.\r\n * @param max The maximum of the data domain.\r\n * @param valuesMetadata The measure metadata array.\r\n * @param maxTickCount The max count of intervals.\r\n * @param isDateTime - flag to show single tick when min is equal to max.\r\n */\r\n export function getBestNumberOfTicks(min: number, max: number, valuesMetadata: DataViewMetadataColumn[], maxTickCount: number, isDateTime?: boolean): number {\r\n debug.assert(maxTickCount >= 0, \"maxTickCount must be greater or equal to zero\");\r\n\r\n if (isNaN(min) || isNaN(max))\r\n return DefaultBestTickCount;\r\n\r\n debug.assert(min <= max, \"min value needs to be less or equal to max value\");\r\n\r\n if (maxTickCount <= 1 || (max <= 1 && min >= -1))\r\n return maxTickCount;\r\n\r\n if (min === max) {\r\n // datetime needs to only show one tick value in this case so formatting works correctly\r\n if (!!isDateTime)\r\n return 1;\r\n return DefaultBestTickCount;\r\n }\r\n\r\n if (hasNonIntegerData(valuesMetadata))\r\n return maxTickCount;\r\n\r\n // e.g. 5 - 2 + 1 = 4, => [2,3,4,5]\r\n return Math.min(max - min + 1, maxTickCount);\r\n }\r\n\r\n export function hasNonIntegerData(valuesMetadata: DataViewMetadataColumn[]): boolean {\r\n for (let i = 0, len = valuesMetadata.length; i < len; i++) {\r\n let currentMetadata = valuesMetadata[i];\r\n if (currentMetadata && currentMetadata.type && !currentMetadata.type.integer) {\r\n return true;\r\n }\r\n }\r\n\r\n return false;\r\n }\r\n\r\n export function getRecommendedTickValues(maxTicks: number,\r\n scale: D3.Scale.GenericScale<any>,\r\n axisType: ValueType,\r\n isScalar: boolean,\r\n minTickInterval?: number): any[] {\r\n\r\n if (!isScalar || isOrdinalScale(scale)) {\r\n return getRecommendedTickValuesForAnOrdinalRange(maxTicks, scale.domain());\r\n }\r\n else if (isDateTime(axisType)) {\r\n return getRecommendedTickValuesForADateTimeRange(maxTicks, scale.domain());\r\n }\r\n return getRecommendedTickValuesForAQuantitativeRange(maxTicks, scale, minTickInterval);\r\n }\r\n\r\n export function getRecommendedTickValuesForAnOrdinalRange(maxTicks: number, labels: string[]): string[] {\r\n let tickLabels: string[] = [];\r\n\r\n // return no ticks in this case\r\n if (maxTicks <= 0)\r\n return tickLabels;\r\n\r\n let len = labels.length;\r\n if (maxTicks > len)\r\n return labels;\r\n\r\n for (let i = 0, step = Math.ceil(len / maxTicks); i < len; i += step) {\r\n tickLabels.push(labels[i]);\r\n }\r\n return tickLabels;\r\n }\r\n\r\n export function getRecommendedTickValuesForAQuantitativeRange(maxTicks: number, scale: D3.Scale.GenericScale<any>, minInterval?: number): number[] {\r\n let tickLabels: number[] = [];\r\n\r\n //if maxticks is zero return none\r\n if (maxTicks === 0)\r\n return tickLabels;\r\n\r\n let quantitiveScale = <D3.Scale.QuantitativeScale>scale;\r\n if (quantitiveScale.ticks) {\r\n tickLabels = quantitiveScale.ticks(maxTicks);\r\n if (tickLabels.length > maxTicks && maxTicks > 1)\r\n tickLabels = quantitiveScale.ticks(maxTicks - 1);\r\n if (tickLabels.length < MinTickCount) {\r\n tickLabels = quantitiveScale.ticks(maxTicks + 1);\r\n }\r\n tickLabels = createTrueZeroTickLabel(tickLabels);\r\n\r\n if (minInterval && tickLabels.length > 1) {\r\n let tickInterval = tickLabels[1] - tickLabels[0];\r\n while (tickInterval > 0 && tickInterval < minInterval) {\r\n for (let i = 1; i < tickLabels.length; i++) {\r\n tickLabels.splice(i, 1);\r\n }\r\n\r\n tickInterval = tickInterval * 2;\r\n }\r\n // keep at least two labels - the loop above may trim all but one if we have odd # of tick labels and dynamic range < minInterval\r\n if (tickLabels.length === 1) {\r\n tickLabels.push(tickLabels[0] + minInterval);\r\n }\r\n }\r\n return tickLabels;\r\n }\r\n\r\n debug.assertFail('must pass a quantitative scale to this method');\r\n\r\n return tickLabels;\r\n }\r\n\r\n /** \r\n * Round out very small zero tick values (e.g. -1e-33 becomes 0).\r\n * \r\n * @param ticks Array of numbers (from d3.scale.ticks([maxTicks])).\r\n * @param epsilon Max ratio of calculated tick interval which we will recognize as zero.\r\n * \r\n * e.g.\r\n * ticks = [-2, -1, 1e-10, 3, 4]; epsilon = 1e-5;\r\n * closeZero = 1e-5 * | 2 - 1 | = 1e-5\r\n * // Tick values <= 1e-5 replaced with 0\r\n * return [-2, -1, 0, 3, 4];\r\n */\r\n function createTrueZeroTickLabel(ticks: number[], epsilon: number = 1e-5): number[] {\r\n if (!ticks || ticks.length < 2)\r\n return ticks;\r\n\r\n let closeZero = epsilon * Math.abs(ticks[1] - ticks[0]);\r\n\r\n return ticks.map((tick) => Math.abs(tick) <= closeZero ? 0 : tick);\r\n }\r\n\r\n function getRecommendedTickValuesForADateTimeRange(maxTicks: number, dataDomain: number[]): number[] {\r\n let tickLabels: number[] = [];\r\n\r\n if (dataDomain[0] === 0 && dataDomain[1] === 0)\r\n return [];\r\n\r\n let dateTimeTickLabels = DateTimeSequence.calculate(new Date(dataDomain[0]), new Date(dataDomain[1]), maxTicks).sequence;\r\n tickLabels = dateTimeTickLabels.map(d => d.getTime());\r\n tickLabels = ensureValuesInRange(tickLabels, dataDomain[0], dataDomain[1]);\r\n return tickLabels;\r\n }\r\n\r\n function normalizeLinearDomain(domain: NumberRange): NumberRange {\r\n if (isNaN(domain.min) || isNaN(domain.max)) {\r\n domain.min = emptyDomain[0];\r\n domain.max = emptyDomain[1];\r\n }\r\n else if (domain.min === domain.max) {\r\n // d3 linear scale will give zero tickValues if max === min, so extend a little\r\n domain.min = domain.min < 0 ? domain.min * 1.2 : domain.min * 0.8;\r\n domain.max = domain.max < 0 ? domain.max * 0.8 : domain.max * 1.2;\r\n }\r\n else {\r\n // Check that min is very small and is a negligable portion of the whole domain.\r\n // (fix floating pt precision bugs)\r\n // sometimes highlight value math causes small negative numbers which makes the axis add\r\n // a large tick interval instead of just rendering at zero.\r\n if (Math.abs(domain.min) < 0.0001 && domain.min / (domain.max - domain.min) < 0.0001) {\r\n domain.min = 0;\r\n }\r\n }\r\n\r\n return domain;\r\n }\r\n\r\n export function getMargin(availableWidth: number, availableHeight: number, xMargin: number, yMargin: number): IMargin {\r\n if (getRecommendedNumberOfTicksForXAxis(availableWidth - xMargin) === 0\r\n || getRecommendedNumberOfTicksForYAxis(availableHeight - yMargin) === 0) {\r\n return {\r\n top: 0,\r\n right: xMargin,\r\n bottom: yMargin,\r\n left: 0\r\n };\r\n }\r\n\r\n return {\r\n top: 20,\r\n right: 30,\r\n bottom: 40,\r\n left: 30\r\n };\r\n } \r\n\r\n // TODO: Put the parameters into one object\r\n export function getTickLabelMargins(\r\n viewport: IViewport,\r\n yMarginLimit: number,\r\n textWidthMeasurer: ITextAsSVGMeasurer,\r\n textHeightMeasurer: ITextAsSVGMeasurer,\r\n axes: CartesianAxisProperties,\r\n bottomMarginLimit: number,\r\n properties: TextProperties,\r\n scrollbarVisible?: boolean,\r\n showOnRight?: boolean,\r\n renderXAxis?: boolean,\r\n renderY1Axis?: boolean,\r\n renderY2Axis?: boolean): TickLabelMargins /*IMargin - can't update this because custom visuals use it*/ {\r\n\r\n debug.assertValue(axes, 'axes');\r\n let xAxisProperties: IAxisProperties = axes.x;\r\n let y1AxisProperties: IAxisProperties = axes.y1;\r\n let y2AxisProperties: IAxisProperties = axes.y2;\r\n\r\n debug.assertValue(viewport, 'viewport');\r\n debug.assertValue(textWidthMeasurer, 'textWidthMeasurer');\r\n debug.assertValue(textHeightMeasurer, 'textHeightMeasurer');\r\n debug.assertValue(xAxisProperties, 'xAxis');\r\n debug.assertValue(y1AxisProperties, 'yAxis');\r\n\r\n let xLabels = xAxisProperties.values;\r\n let y1Labels = y1AxisProperties.values;\r\n\r\n let leftOverflow = 0;\r\n let rightOverflow = 0;\r\n let maxWidthY1 = 0;\r\n let maxWidthY2 = 0;\r\n let xMax = 0; // bottom margin\r\n let ordinalLabelOffset = xAxisProperties.categoryThickness ? xAxisProperties.categoryThickness / 2 : 0;\r\n let scaleIsOrdinal = isOrdinalScale(xAxisProperties.scale);\r\n\r\n let xLabelOuterPadding = 0;\r\n if (xAxisProperties.outerPadding !== undefined) {\r\n xLabelOuterPadding = xAxisProperties.outerPadding;\r\n }\r\n else if (xAxisProperties.xLabelMaxWidth !== undefined) {\r\n xLabelOuterPadding = Math.max(0, (viewport.width - xAxisProperties.xLabelMaxWidth * xLabels.length) / 2);\r\n }\r\n\r\n if (getRecommendedNumberOfTicksForXAxis(viewport.width) !== 0\r\n || getRecommendedNumberOfTicksForYAxis(viewport.height) !== 0) {\r\n let rotation;\r\n if (scrollbarVisible)\r\n rotation = LabelLayoutStrategy.DefaultRotationWithScrollbar;\r\n else\r\n rotation = LabelLayoutStrategy.DefaultRotation;\r\n\r\n if (renderY1Axis) {\r\n for (let i = 0, len = y1Labels.length; i < len; i++) {\r\n properties.text = y1Labels[i];\r\n maxWidthY1 = Math.max(maxWidthY1, textWidthMeasurer(properties));\r\n }\r\n }\r\n\r\n if (y2AxisProperties && renderY2Axis) {\r\n let y2Labels = y2AxisProperties.values;\r\n for (let i = 0, len = y2Labels.length; i < len; i++) {\r\n properties.text = y2Labels[i];\r\n maxWidthY2 = Math.max(maxWidthY2, textWidthMeasurer(properties));\r\n }\r\n }\r\n\r\n let textHeight = textHeightMeasurer(properties);\r\n let maxNumLines = Math.floor(bottomMarginLimit / textHeight);\r\n let xScale = xAxisProperties.scale;\r\n let xDomain = xScale.domain();\r\n if (renderXAxis && xLabels.length > 0) {\r\n for (let i = 0, len = xLabels.length; i < len; i++) {\r\n // find the max height of the x-labels, perhaps rotated or wrapped\r\n let height: number;\r\n properties.text = xLabels[i];\r\n let width = textWidthMeasurer(properties);\r\n if (xAxisProperties.willLabelsWordBreak) {\r\n // Split label and count rows\r\n let wordBreaks = jsCommon.WordBreaker.splitByWidth(properties.text, properties, textWidthMeasurer, xAxisProperties.xLabelMaxWidth, maxNumLines);\r\n height = wordBreaks.length * textHeight;\r\n // word wrapping will truncate at xLabelMaxWidth\r\n width = xAxisProperties.xLabelMaxWidth;\r\n }\r\n else if (!xAxisProperties.willLabelsFit && scaleIsOrdinal) {\r\n height = width * rotation.sine;\r\n width = width * rotation.cosine;\r\n }\r\n else {\r\n height = TextHeightConstant;\r\n }\r\n\r\n // calculate left and right overflow due to wide X labels\r\n // (Note: no right overflow when rotated)\r\n if (i === 0) {\r\n if (scaleIsOrdinal) {\r\n if (!xAxisProperties.willLabelsFit /*rotated text*/)\r\n leftOverflow = width - ordinalLabelOffset - xLabelOuterPadding;\r\n else\r\n leftOverflow = (width / 2) - ordinalLabelOffset - xLabelOuterPadding;\r\n leftOverflow = Math.max(leftOverflow, 0);\r\n }\r\n else if (xDomain.length > 1) {\r\n // Scalar - do some math\r\n let xPos = xScale(xDomain[0]);\r\n // xPos already incorporates xLabelOuterPadding, don't subtract it twice\r\n leftOverflow = (width / 2) - xPos;\r\n leftOverflow = Math.max(leftOverflow, 0);\r\n }\r\n } else if (i === len - 1) {\r\n if (scaleIsOrdinal) {\r\n // if we are rotating text (!willLabelsFit) there won't be any right overflow\r\n if (xAxisProperties.willLabelsFit || xAxisProperties.willLabelsWordBreak) {\r\n // assume this label is placed near the edge\r\n rightOverflow = (width / 2) - ordinalLabelOffset - xLabelOuterPadding;\r\n rightOverflow = Math.max(rightOverflow, 0);\r\n }\r\n }\r\n else if (xDomain.length > 1) {\r\n // Scalar - do some math\r\n let xPos = xScale(xDomain[1]);\r\n // xPos already incorporates xLabelOuterPadding, don't subtract it twice\r\n rightOverflow = (width / 2) - (viewport.width - xPos);\r\n rightOverflow = Math.max(rightOverflow, 0);\r\n }\r\n }\r\n\r\n xMax = Math.max(xMax, height);\r\n }\r\n // trim any actual overflow to the limit\r\n leftOverflow = Math.min(leftOverflow, XLabelMaxAllowedOverflow);\r\n rightOverflow = Math.min(rightOverflow, XLabelMaxAllowedOverflow);\r\n }\r\n }\r\n\r\n let rightMargin = 0,\r\n leftMargin = 0,\r\n bottomMargin = Math.min(Math.ceil(xMax), bottomMarginLimit);\r\n\r\n if (showOnRight) {\r\n leftMargin = Math.min(Math.max(leftOverflow, maxWidthY2), yMarginLimit);\r\n rightMargin = Math.min(Math.max(rightOverflow, maxWidthY1), yMarginLimit);\r\n }\r\n else {\r\n leftMargin = Math.min(Math.max(leftOverflow, maxWidthY1), yMarginLimit);\r\n rightMargin = Math.min(Math.max(rightOverflow, maxWidthY2), yMarginLimit);\r\n }\r\n \r\n return {\r\n xMax: Math.ceil(bottomMargin),\r\n yLeft: Math.ceil(leftMargin),\r\n yRight: Math.ceil(rightMargin),\r\n };\r\n }\r\n\r\n export function columnDataTypeHasValue(dataType: ValueTypeDescriptor) {\r\n return dataType && (dataType.bool || dataType.numeric || dataType.text || dataType.dateTime);\r\n }\r\n\r\n export function createOrdinalType(): ValueType {\r\n return ValueType.fromDescriptor({ text: true });\r\n }\r\n\r\n export function isOrdinal(type: ValueTypeDescriptor): boolean {\r\n return !!(type && (type.text || type.bool));\r\n }\r\n\r\n export function isOrdinalScale(scale: any): boolean {\r\n return typeof scale.invert === 'undefined';\r\n }\r\n\r\n export function isDateTime(type: ValueTypeDescriptor): boolean {\r\n return !!(type && type.dateTime);\r\n }\r\n\r\n export function invertScale(scale: any, x) {\r\n if (isOrdinalScale(scale)) {\r\n return invertOrdinalScale(scale, x);\r\n }\r\n return scale.invert(x);\r\n }\r\n\r\n export function extent(scale: any): number[] {\r\n if (isOrdinalScale(scale)) {\r\n return scale.rangeExtent();\r\n }\r\n return scale.range();\r\n }\r\n\r\n export function invertOrdinalScale(scale: D3.Scale.OrdinalScale, x: number) {\r\n let leftEdges = scale.range();\r\n if (leftEdges.length < 2)\r\n return 0;\r\n\r\n let width = scale.rangeBand();\r\n let halfInnerPadding = (leftEdges[1] - leftEdges[0] - width) / 2;\r\n\r\n let j;\r\n for (j = 0; x > (leftEdges[j] + width + halfInnerPadding) && j < (leftEdges.length - 1); j++)\r\n ;\r\n return scale.domain()[j];\r\n }\r\n\r\n export function findClosestXAxisIndex(categoryValue: number, categoryAxisValues: CartesianDataPoint[]): number {\r\n let closestValueIndex: number = -1;\r\n let minDistance = Number.MAX_VALUE;\r\n for (let i in categoryAxisValues) {\r\n let distance = Math.abs(categoryValue - categoryAxisValues[i].categoryValue);\r\n if (distance < minDistance) {\r\n minDistance = distance;\r\n closestValueIndex = parseInt(i, 10);\r\n }\r\n }\r\n return closestValueIndex;\r\n }\r\n\r\n export function lookupOrdinalIndex(scale: D3.Scale.OrdinalScale, pixelValue: number): number {\r\n let closestValueIndex: number = -1;\r\n let minDistance = Number.MAX_VALUE;\r\n let domain = scale.domain();\r\n if (domain.length < 2)\r\n return 0;\r\n var halfWidth = (scale(1) - scale(0)) / 2;\r\n for (let idx in domain) {\r\n let leftEdgeInPixels = scale(idx);\r\n var midPoint = leftEdgeInPixels + halfWidth;\r\n let distance = Math.abs(pixelValue - midPoint);\r\n if (distance < minDistance) {\r\n minDistance = distance;\r\n closestValueIndex = parseInt(idx, 10);\r\n }\r\n }\r\n return closestValueIndex;\r\n }\r\n\r\n /** scale(value1) - scale(value2) with zero checking and min(+/-1, result) */\r\n export function diffScaled(\r\n scale: D3.Scale.GenericScale<any>,\r\n value1: any,\r\n value2: any): number {\r\n debug.assertValue(scale, 'scale');\r\n\r\n let value: number = scale(value1) - scale(value2);\r\n if (value === 0)\r\n return 0;\r\n\r\n if (value < 0)\r\n return Math.min(value, -1);\r\n return Math.max(value, 1);\r\n }\r\n\r\n export function createDomain(data: CartesianSeries[], axisType: ValueTypeDescriptor, isScalar: boolean, forcedScalarDomain: any[], ensureDomain?: NumberRange): number[] {\r\n if (isScalar && !isOrdinal(axisType)) {\r\n let userMin, userMax;\r\n if (forcedScalarDomain && forcedScalarDomain.length === 2) {\r\n userMin = forcedScalarDomain[0];\r\n userMax = forcedScalarDomain[1];\r\n }\r\n return createScalarDomain(data, userMin, userMax, axisType, ensureDomain);\r\n }\r\n\r\n return createOrdinalDomain(data);\r\n }\r\n\r\n export function ensureValuesInRange(values: number[], min: number, max: number): number[] {\r\n debug.assert(min <= max, \"min must be less or equal to max\");\r\n let filteredValues = values.filter(v => v >= min && v <= max);\r\n if (filteredValues.length < 2)\r\n filteredValues = [min, max];\r\n return filteredValues;\r\n }\r\n\r\n /**\r\n * Gets the ValueType of a category column, defaults to Text if the type is not present.\r\n */\r\n export function getCategoryValueType(metadataColumn: DataViewMetadataColumn, isScalar?: boolean): ValueType {\r\n if (metadataColumn && columnDataTypeHasValue(metadataColumn.type))\r\n return <ValueType>metadataColumn.type;\r\n\r\n if (isScalar) {\r\n return ValueType.fromDescriptor({ numeric: true });\r\n }\r\n\r\n return ValueType.fromDescriptor({ text: true });\r\n }\r\n\r\n /**\r\n * Create a D3 axis including scale. Can be vertical or horizontal, and either datetime, numeric, or text.\r\n * @param options The properties used to create the axis.\r\n */\r\n export function createAxis(options: CreateAxisOptions): IAxisProperties {\r\n let pixelSpan = options.pixelSpan,\r\n dataDomain = options.dataDomain,\r\n metaDataColumn = options.metaDataColumn,\r\n formatString = options.formatString,\r\n outerPadding = options.outerPadding || 0,\r\n isCategoryAxis = !!options.isCategoryAxis,\r\n isScalar = !!options.isScalar,\r\n isVertical = !!options.isVertical,\r\n useTickIntervalForDisplayUnits = !!options.useTickIntervalForDisplayUnits, // DEPRECATE: same meaning as isScalar?\r\n getValueFn = options.getValueFn,\r\n categoryThickness = options.categoryThickness,\r\n axisDisplayUnits = options.axisDisplayUnits,\r\n axisPrecision = options.axisPrecision,\r\n is100Pct = !!options.is100Pct;\r\n\r\n let dataType: ValueType = AxisHelper.getCategoryValueType(metaDataColumn, isScalar);\r\n\r\n // Create the Scale\r\n let scaleResult: CreateScaleResult = AxisHelper.createScale(options);\r\n let scale = scaleResult.scale;\r\n let bestTickCount = scaleResult.bestTickCount;\r\n let scaleDomain = scale.domain();\r\n let isLogScaleAllowed = AxisHelper.isLogScalePossible(dataDomain, dataType);\r\n\r\n // fix categoryThickness if scalar and the domain was adjusted when making the scale \"nice\"\r\n if (categoryThickness && isScalar && dataDomain && dataDomain.length === 2) {\r\n let oldSpan = dataDomain[1] - dataDomain[0];\r\n let newSpan = scaleDomain[1] - scaleDomain[0];\r\n if (oldSpan > 0 && newSpan > 0) {\r\n categoryThickness = categoryThickness * oldSpan / newSpan;\r\n }\r\n }\r\n\r\n // Prepare Tick Values for formatting\r\n let tickValues: any[];\r\n if (isScalar && bestTickCount === 1) {\r\n tickValues = [dataDomain[0]];\r\n }\r\n else {\r\n let minTickInterval = isScalar ? getMinTickValueInterval(formatString, dataType, is100Pct) : undefined;\r\n tickValues = getRecommendedTickValues(bestTickCount, scale, dataType, isScalar, minTickInterval);\r\n }\r\n\r\n if (options.scaleType && options.scaleType === axisScale.log && isLogScaleAllowed) {\r\n tickValues = tickValues.filter((d) => { return AxisHelper.powerOfTen(d); });\r\n }\r\n\r\n let formatter = createFormatter(\r\n scaleDomain,\r\n dataDomain,\r\n dataType,\r\n isScalar,\r\n formatString,\r\n bestTickCount,\r\n tickValues,\r\n getValueFn,\r\n useTickIntervalForDisplayUnits,\r\n axisDisplayUnits,\r\n axisPrecision);\r\n\r\n // sets default orientation only, cartesianChart will fix y2 for comboChart\r\n // tickSize(pixelSpan) is used to create gridLines\r\n let axis = d3.svg.axis()\r\n .scale(scale)\r\n .tickSize(6, 0)\r\n .orient(isVertical ? 'left' : 'bottom')\r\n .ticks(bestTickCount)\r\n .tickValues(tickValues);\r\n\r\n let formattedTickValues = [];\r\n if (metaDataColumn)\r\n formattedTickValues = formatAxisTickValues(axis, tickValues, formatter, dataType, getValueFn);\r\n\r\n let xLabelMaxWidth;\r\n // Use category layout of labels if specified, otherwise use scalar layout of labels\r\n if (!isScalar && categoryThickness) {\r\n xLabelMaxWidth = Math.max(1, categoryThickness - CartesianChart.TickLabelPadding * 2);\r\n }\r\n else {\r\n // When there are 0 or 1 ticks, then xLabelMaxWidth = pixelSpan \r\n xLabelMaxWidth = tickValues.length > 1 ? getScalarLabelMaxWidth(scale, tickValues) : pixelSpan;\r\n xLabelMaxWidth = xLabelMaxWidth - ScalarTickLabelPadding * 2;\r\n }\r\n\r\n return {\r\n scale: scale,\r\n axis: axis,\r\n formatter: formatter,\r\n values: formattedTickValues,\r\n axisType: dataType,\r\n axisLabel: null,\r\n isCategoryAxis: isCategoryAxis,\r\n xLabelMaxWidth: xLabelMaxWidth,\r\n categoryThickness: categoryThickness,\r\n outerPadding: outerPadding,\r\n usingDefaultDomain: scaleResult.usingDefaultDomain,\r\n isLogScaleAllowed: isLogScaleAllowed,\r\n dataDomain: dataDomain,\r\n };\r\n }\r\n\r\n function getScalarLabelMaxWidth(scale: D3.Scale.GenericScale<any>, tickValues: number[]): number {\r\n debug.assertValue(scale, \"scale\");\r\n debug.assertNonEmpty(tickValues, \"tickValues\");\r\n // find the distance between two ticks. scalar ticks can be anywhere, such as:\r\n // |---50----------100--------|\r\n if (scale && !_.isEmpty(tickValues)) {\r\n return Math.abs(scale(tickValues[1]) - scale(tickValues[0]));\r\n }\r\n\r\n return 1;\r\n }\r\n\r\n export function createScale(options: CreateAxisOptions): CreateScaleResult {\r\n let pixelSpan = options.pixelSpan,\r\n dataDomain = options.dataDomain,\r\n metaDataColumn = options.metaDataColumn,\r\n outerPadding = options.outerPadding || 0,\r\n isScalar = !!options.isScalar,\r\n isVertical = !!options.isVertical,\r\n forcedTickCount = options.forcedTickCount,\r\n categoryThickness = options.categoryThickness,\r\n shouldClamp = !!options.shouldClamp,\r\n maxTickCount = options.maxTickCount;\r\n\r\n let dataType: ValueType = AxisHelper.getCategoryValueType(metaDataColumn, isScalar);\r\n\r\n let maxTicks = isVertical ? getRecommendedNumberOfTicksForYAxis(pixelSpan) : getRecommendedNumberOfTicksForXAxis(pixelSpan);\r\n if (maxTickCount &&\r\n maxTicks > maxTickCount)\r\n maxTicks = maxTickCount;\r\n\r\n let scalarDomain = dataDomain ? dataDomain.slice() : null;\r\n let bestTickCount = maxTicks;\r\n let scale: D3.Scale.GenericScale<any>;\r\n let usingDefaultDomain = false;\r\n\r\n if (dataDomain == null || (dataDomain.length === 2 && dataDomain[0] == null && dataDomain[1] == null) || (dataDomain.length !== 2 && isScalar)) {\r\n usingDefaultDomain = true;\r\n\r\n if (dataType.dateTime || !isOrdinal(dataType))\r\n dataDomain = emptyDomain;\r\n else //ordinal\r\n dataDomain = [];\r\n\r\n if (isOrdinal(dataType)) {\r\n scale = createOrdinalScale(pixelSpan, dataDomain, categoryThickness ? outerPadding / categoryThickness : 0);\r\n }\r\n else {\r\n scale = createNumericalScale(options.scaleType, pixelSpan, dataDomain, dataType, outerPadding, bestTickCount);\r\n }\r\n }\r\n else {\r\n if (isScalar && dataDomain.length > 0) {\r\n bestTickCount = forcedTickCount !== undefined\r\n ? (maxTicks !== 0 ? forcedTickCount : 0)\r\n : AxisHelper.getBestNumberOfTicks(dataDomain[0], dataDomain[dataDomain.length - 1], [metaDataColumn], maxTicks, dataType.dateTime);\r\n\r\n let normalizedRange = normalizeLinearDomain({ min: dataDomain[0], max: dataDomain[dataDomain.length - 1] });\r\n scalarDomain = [normalizedRange.min, normalizedRange.max];\r\n }\r\n\r\n if (isScalar && dataType.numeric && !dataType.dateTime) {\r\n scale = createNumericalScale(options.scaleType, pixelSpan, scalarDomain, dataType, outerPadding, bestTickCount, shouldClamp);\r\n }\r\n else if (isScalar && dataType.dateTime) {\r\n // Use of a linear scale, instead of a D3.time.scale, is intentional since we want\r\n // to control the formatting of the time values, since d3's implementation isn't\r\n // in accordance to our design.\r\n // scalarDomain: should already be in long-int time (via category.values[0].getTime())\r\n scale = createLinearScale(pixelSpan, scalarDomain, outerPadding, null, shouldClamp); // DO NOT PASS TICKCOUNT\r\n }\r\n else if (dataType.text || dataType.dateTime || dataType.numeric || dataType.bool) {\r\n scale = createOrdinalScale(pixelSpan, scalarDomain, categoryThickness ? outerPadding / categoryThickness : 0);\r\n bestTickCount = maxTicks === 0 ? 0\r\n : Math.min(\r\n scalarDomain.length,\r\n (pixelSpan - outerPadding * 2) / CartesianChart.MinOrdinalRectThickness);\r\n }\r\n else {\r\n debug.assertFail('unsupported dataType, something other than text or numeric');\r\n }\r\n }\r\n\r\n // vertical ordinal axis (e.g. categorical bar chart) does not need to reverse\r\n if (isVertical && isScalar) {\r\n scale.range(scale.range().reverse());\r\n }\r\n\r\n ColumnUtil.normalizeInfinityInScale(scale);\r\n\r\n return {\r\n scale: scale,\r\n bestTickCount: bestTickCount,\r\n usingDefaultDomain: usingDefaultDomain,\r\n };\r\n }\r\n\r\n export function createFormatter(\r\n scaleDomain: any[],\r\n dataDomain: any[],\r\n dataType,\r\n isScalar: boolean,\r\n formatString: string,\r\n bestTickCount: number,\r\n tickValues: any[],\r\n getValueFn: any,\r\n useTickIntervalForDisplayUnits: boolean = false,\r\n axisDisplayUnits?: number,\r\n axisPrecision?: number): IValueFormatter {\r\n\r\n let formatter: IValueFormatter;\r\n if (dataType.dateTime) {\r\n if (isScalar) {\r\n let value = new Date(scaleDomain[0]);\r\n let value2 = new Date(scaleDomain[1]);\r\n // datetime with only one value needs to pass the same value\r\n // (from the original dataDomain value, not the adjusted scaleDomain)\r\n // so formatting works correctly.\r\n if (bestTickCount === 1)\r\n value = value2 = new Date(dataDomain[0]);\r\n // this will ignore the formatString and create one based on the smallest non-zero portion of the values supplied.\r\n formatter = valueFormatter.create({\r\n format: formatString,\r\n value: value,\r\n value2: value2,\r\n tickCount: bestTickCount,\r\n });\r\n }\r\n else {\r\n // Use the model formatString for ordinal datetime\r\n formatter = valueFormatter.createDefaultFormatter(formatString, true);\r\n }\r\n }\r\n else {\r\n if (getValueFn == null && !isScalar) {\r\n debug.assertFail('getValueFn must be supplied for ordinal tickValues');\r\n }\r\n if (useTickIntervalForDisplayUnits && isScalar && tickValues.length > 1) {\r\n let value1 = axisDisplayUnits ? axisDisplayUnits : tickValues[1] - tickValues[0];\r\n\r\n let options: ValueFormatterOptions = {\r\n format: formatString,\r\n value: value1,\r\n value2: 0, //force tickInterval or display unit to be used\r\n allowFormatBeautification: true,\r\n };\r\n\r\n if (axisPrecision)\r\n options.precision = axisPrecision;\r\n else\r\n options.detectAxisPrecision = true;\r\n\r\n formatter = valueFormatter.create(options);\r\n }\r\n else {\r\n // do not use display units, just the basic value formatter\r\n // datetime is handled above, so we are ordinal and either boolean, numeric, or text.\r\n formatter = valueFormatter.createDefaultFormatter(formatString, true);\r\n }\r\n }\r\n\r\n return formatter;\r\n }\r\n \r\n /**\r\n * Format the linear tick labels or the category labels.\r\n */\r\n function formatAxisTickValues(\r\n axis: D3.Svg.Axis,\r\n tickValues: any[],\r\n formatter: IValueFormatter,\r\n dataType: ValueType,\r\n getValueFn?: (index: number, type: ValueType) => any) {\r\n\r\n let formattedTickValues = [];\r\n\r\n if (!getValueFn)\r\n getValueFn = data => data;\r\n\r\n if (formatter) {\r\n axis.tickFormat(d => formatter.format(getValueFn(d, dataType)));\r\n formattedTickValues = tickValues.map(d => formatter.format(getValueFn(d, dataType)));\r\n }\r\n else {\r\n formattedTickValues = tickValues.map((d) => getValueFn(d, dataType));\r\n }\r\n\r\n return formattedTickValues;\r\n }\r\n\r\n export function getMinTickValueInterval(formatString: string, columnType: ValueType, is100Pct?: boolean): number {\r\n let isCustomFormat = formatString && !powerbi.NumberFormat.isStandardFormat(formatString);\r\n if (isCustomFormat) {\r\n let precision = powerbi.NumberFormat.getCustomFormatMetadata(formatString, true /*calculatePrecision*/).precision;\r\n if (formatString.indexOf('%') > -1)\r\n precision += 2; //percent values are multiplied by 100 during formatting\r\n return Math.pow(10, -precision);\r\n }\r\n else if (is100Pct)\r\n return 0.01;\r\n else if (columnType.integer)\r\n return 1;\r\n\r\n return 0;\r\n }\r\n\r\n function createScalarDomain(data: CartesianSeries[], userMin: DataViewPropertyValue, userMax: DataViewPropertyValue, axisType: ValueTypeDescriptor, ensureDomain?: NumberRange): number[] {\r\n debug.assertValue(data, 'data');\r\n if (data.length === 0) {\r\n return null;\r\n }\r\n\r\n let defaultMinX = <number>d3.min(data, (kv) => { return d3.min(kv.data, d => { return d.categoryValue; }); });\r\n let defaultMaxX = <number>d3.max(data, (kv) => { return d3.max(kv.data, d => { return d.categoryValue; }); });\r\n\r\n return combineDomain([userMin, userMax], [defaultMinX, defaultMaxX], ensureDomain);\r\n }\r\n\r\n /**\r\n * Creates a [min,max] from your Cartiesian data values.\r\n * \r\n * @param data The series array of CartesianDataPoints.\r\n * @param includeZero Columns and bars includeZero, line and scatter do not.\r\n */\r\n export function createValueDomain(data: CartesianSeries[], includeZero: boolean): number[] {\r\n debug.assertValue(data, 'data');\r\n if (data.length === 0)\r\n return null;\r\n\r\n let minY = <number>d3.min(data, (kv) => { return d3.min(kv.data, d => { return d.value; }); });\r\n let maxY = <number>d3.max(data, (kv) => { return d3.max(kv.data, d => { return d.value; }); });\r\n\r\n if (includeZero)\r\n return [Math.min(minY, 0), Math.max(maxY, 0)];\r\n return [minY, maxY];\r\n }\r\n\r\n function createOrdinalDomain(data: CartesianSeries[]): number[] {\r\n if (_.isEmpty(data))\r\n return [];\r\n\r\n // each series shares the same categories for oridinal axes (even if a series has some nulls)\r\n let domain = [];\r\n let firstSeries = data[0];\r\n for (let dp of firstSeries.data) {\r\n if (!dp.highlight)\r\n domain.push(dp.categoryIndex);\r\n }\r\n return domain;\r\n }\r\n\r\n export module LabelLayoutStrategy {\r\n export function willLabelsFit(\r\n axisProperties: IAxisProperties,\r\n availableWidth: number,\r\n textMeasurer: ITextAsSVGMeasurer,\r\n properties: TextProperties): boolean {\r\n\r\n let labels = axisProperties.values;\r\n if (labels.length === 0)\r\n return false;\r\n\r\n let labelMaxWidth = axisProperties.xLabelMaxWidth !== undefined\r\n ? axisProperties.xLabelMaxWidth\r\n : availableWidth / labels.length;\r\n\r\n return !labels.some(d => {\r\n properties.text = d;\r\n return textMeasurer(properties) > labelMaxWidth;\r\n });\r\n }\r\n\r\n export function willLabelsWordBreak(\r\n axisProperties: IAxisProperties,\r\n margin: IMargin,\r\n availableWidth: number,\r\n textWidthMeasurer: ITextAsSVGMeasurer,\r\n textHeightMeasurer: ITextAsSVGMeasurer,\r\n textTruncator: (properties: TextProperties, maxWidth: number) => string,\r\n properties: TextProperties) {\r\n let labels = axisProperties.values;\r\n let labelMaxWidth = axisProperties.xLabelMaxWidth !== undefined\r\n ? axisProperties.xLabelMaxWidth\r\n : availableWidth / labels.length;\r\n let maxRotatedLength = margin.bottom / DefaultRotation.sine;\r\n let height = textHeightMeasurer(properties);\r\n let maxNumLines = Math.max(1, Math.floor(margin.bottom / height)); // TODO: not taking axis label into account\r\n\r\n if (labels.length === 0)\r\n return false;\r\n\r\n // If no break character and exceeds max width, word breaking will not work, return false\r\n let mustRotate = labels.some(label => {\r\n // Detect must rotate and return immediately\r\n properties.text = label;\r\n return !jsCommon.WordBreaker.hasBreakers(label) && textWidthMeasurer(properties) > labelMaxWidth;\r\n });\r\n if (mustRotate)\r\n return false;\r\n\r\n let moreWordBreakChars = labels.filter((label, index: number) => {\r\n // ...otherwise compare rotation versus word breaking\r\n let allowedLengthProjectedOnXAxis =\r\n // Left margin is the width of Y axis.\r\n margin.left\r\n // There could be a padding before the first category.\r\n + axisProperties.outerPadding\r\n // Align the rotated text's top right corner to the middle of the corresponding category first.\r\n + axisProperties.categoryThickness * (index + 0.5)\r\n // Subtracting the left padding space from the allowed length\r\n - LeftPadding;\r\n let allowedLength = allowedLengthProjectedOnXAxis / DefaultRotation.cosine;\r\n let rotatedLength = Math.min(allowedLength, maxRotatedLength);\r\n \r\n // Which shows more characters? Rotated or maxNumLines truncated to labelMaxWidth?\r\n let wordBreakChars = jsCommon.WordBreaker.splitByWidth(label, properties, textWidthMeasurer, labelMaxWidth, maxNumLines, textTruncator).join(' ');\r\n properties.text = label;\r\n let rotateChars = textTruncator(properties, rotatedLength);\r\n\r\n // prefer word break (>=) as it takes up less plot area\r\n return TextUtil.removeEllipses(wordBreakChars).length >= TextUtil.removeEllipses(rotateChars).length;\r\n });\r\n\r\n // prefer word break (>=) as it takes up less plot area\r\n return moreWordBreakChars.length >= Math.floor(labels.length / 2);\r\n }\r\n\r\n export const DefaultRotation = {\r\n sine: Math.sin(Math.PI * (35 / 180)),\r\n cosine: Math.cos(Math.PI * (35 / 180)),\r\n tangent: Math.tan(Math.PI * (35 / 180)),\r\n transform: 'rotate(-35)',\r\n dy: '-0.5em',\r\n };\r\n\r\n export const DefaultRotationWithScrollbar = {\r\n sine: Math.sin(Math.PI * (90 / 180)),\r\n cosine: Math.cos(Math.PI * (90 / 180)),\r\n tangent: Math.tan(Math.PI * (90 / 180)),\r\n transform: 'rotate(-90)',\r\n dy: '-0.8em',\r\n };\r\n\r\n export function rotate(\r\n labelSelection: D3.Selection,\r\n maxBottomMargin: number,\r\n textTruncator: (properties: TextProperties, maxWidth: number) => string,\r\n textProperties: TextProperties,\r\n needRotate: boolean,\r\n needEllipsis: boolean,\r\n axisProperties: IAxisProperties,\r\n margin: IMargin,\r\n scrollbarVisible: boolean) {\r\n\r\n let rotatedLength;\r\n let defaultRotation: any;\r\n\r\n if (scrollbarVisible)\r\n defaultRotation = DefaultRotationWithScrollbar;\r\n else\r\n defaultRotation = DefaultRotation;\r\n\r\n if (needRotate) {\r\n rotatedLength = maxBottomMargin / defaultRotation.sine;\r\n }\r\n\r\n labelSelection.each(function () {\r\n let axisLabel = d3.select(this);\r\n let labelText = axisLabel.text();\r\n textProperties.text = labelText;\r\n if (needRotate) {\r\n let textContentIndex = axisProperties.values.indexOf(this.textContent);\r\n let allowedLengthProjectedOnXAxis =\r\n // Left margin is the width of Y axis.\r\n margin.left\r\n // There could be a padding before the first category.\r\n + axisProperties.outerPadding\r\n // Align the rotated text's top right corner to the middle of the corresponding category first.\r\n + axisProperties.categoryThickness * (textContentIndex + 0.5);\r\n\r\n // Subtracting the left padding space from the allowed length.\r\n if (!scrollbarVisible)\r\n allowedLengthProjectedOnXAxis -= LeftPadding;\r\n\r\n // Truncate if scrollbar is visible or rotatedLength exceeds allowedLength\r\n let allowedLength = allowedLengthProjectedOnXAxis / defaultRotation.cosine;\r\n if (scrollbarVisible || needEllipsis || (allowedLength < rotatedLength)) {\r\n labelText = textTruncator(textProperties, Math.min(allowedLength, rotatedLength));\r\n axisLabel.text(labelText);\r\n }\r\n\r\n axisLabel.style('text-anchor', 'end')\r\n .attr({\r\n 'dx': '-0.5em',\r\n 'dy': defaultRotation.dy,\r\n 'transform': defaultRotation.transform\r\n });\r\n } else {\r\n let newLabelText = textTruncator(textProperties, axisProperties.xLabelMaxWidth);\r\n if (newLabelText !== labelText)\r\n axisLabel.text(newLabelText);\r\n axisLabel.style('text-anchor', 'middle')\r\n .attr(\r\n {\r\n 'dx': '0em',\r\n 'dy': '1em',\r\n 'transform': 'rotate(0)'\r\n });\r\n }\r\n });\r\n }\r\n\r\n export function wordBreak(\r\n text: D3.Selection,\r\n axisProperties: IAxisProperties,\r\n maxHeight: number\r\n ) {\r\n let allowedLength = axisProperties.xLabelMaxWidth;\r\n\r\n text.each(function () {\r\n let node = d3.select(this);\r\n\r\n // Reset style of text node\r\n node\r\n .style('text-anchor', 'middle')\r\n .attr({\r\n 'dx': '0em',\r\n 'dy': '1em',\r\n 'transform': 'rotate(0)'\r\n });\r\n\r\n TextMeasurementService.wordBreak(this, allowedLength, maxHeight);\r\n });\r\n }\r\n\r\n export function clip(text: D3.Selection, availableWidth: number, svgEllipsis: (textElement: SVGTextElement, maxWidth: number) => void) {\r\n if (text.size() === 0)\r\n return;\r\n\r\n text.each(function () {\r\n let text = d3.select(this);\r\n svgEllipsis(text[0][0], availableWidth);\r\n });\r\n }\r\n }\r\n\r\n export function createOrdinalScale(pixelSpan: number, dataDomain: any[], outerPaddingRatio: number = 0): D3.Scale.OrdinalScale {\r\n debug.assert(outerPaddingRatio >= 0 && outerPaddingRatio < 4, 'outerPaddingRatio should be a value between zero and four');\r\n let scale = d3.scale.ordinal()\r\n /* Avoid using rangeRoundBands here as it is adding some extra padding to the axis*/\r\n .rangeBands([0, pixelSpan], CartesianChart.InnerPaddingRatio, outerPaddingRatio)\r\n .domain(dataDomain);\r\n return scale;\r\n }\r\n\r\n export function isLogScalePossible(domain: any[], axisType?: ValueType): boolean {\r\n if (domain == null)\r\n return false;\r\n if (isDateTime(axisType))\r\n return false;\r\n\r\n return (domain[0] > 0 && domain[1] > 0) || (domain[0] < 0 && domain[1] < 0);//doman must exclude 0\r\n }\r\n\r\n //this function can return different scales e.g. log, linear\r\n // NOTE: export only for testing, do not access directly\r\n export function createNumericalScale(\r\n axisScaleType: string,\r\n pixelSpan: number,\r\n dataDomain: any[],\r\n dataType: ValueType,\r\n outerPadding: number = 0,\r\n niceCount?: number,\r\n shouldClamp?: boolean): D3.Scale.GenericScale<any> {\r\n\r\n if (axisScaleType === axisScale.log && isLogScalePossible(dataDomain, dataType)) {\r\n return createLogScale(pixelSpan, dataDomain, outerPadding, niceCount);\r\n }\r\n else {\r\n return createLinearScale(pixelSpan, dataDomain, outerPadding, niceCount, shouldClamp);\r\n }\r\n }\r\n\r\n function createLogScale(pixelSpan: number, dataDomain: any[], outerPadding: number = 0, niceCount?: number): D3.Scale.LinearScale {\r\n debug.assert(isLogScalePossible(dataDomain), \"dataDomain cannot include 0\");\r\n let scale = d3.scale.log()\r\n .range([outerPadding, pixelSpan - outerPadding])\r\n .domain([dataDomain[0], dataDomain[1]])\r\n .clamp(true);\r\n\r\n if (niceCount) {\r\n scale.nice(niceCount);\r\n }\r\n\r\n return scale;\r\n }\r\n\r\n // NOTE: export only for testing, do not access directly\r\n export function createLinearScale(pixelSpan: number, dataDomain: any[], outerPadding: number = 0, niceCount?: number, shouldClamp?: boolean): D3.Scale.LinearScale {\r\n let scale = d3.scale.linear()\r\n .range([outerPadding, pixelSpan - outerPadding])\r\n .domain([dataDomain[0], dataDomain[1]])\r\n .clamp(shouldClamp);\r\n // .nice(undefined) still modifies the scale boundaries, and for datetime this messes things up.\r\n // we use millisecond ticks since epoch for datetime, so we don't want any \"nice\" with numbers like 17398203392.\r\n if (niceCount) {\r\n scale.nice(niceCount);\r\n }\r\n return scale;\r\n }\r\n\r\n export function getRangeForColumn(sizeColumn: DataViewValueColumn): NumberRange {\r\n let result: NumberRange = {};\r\n if (sizeColumn) {\r\n result.min = sizeColumn.min == null\r\n ? sizeColumn.minLocal == null ? d3.min(sizeColumn.values) : sizeColumn.minLocal\r\n : sizeColumn.min;\r\n result.max = sizeColumn.max == null\r\n ? sizeColumn.maxLocal == null ? d3.max(sizeColumn.values) : sizeColumn.maxLocal\r\n : sizeColumn.max;\r\n }\r\n return result;\r\n }\r\n \r\n /**\r\n * Set customized domain, but don't change when nothing is set\r\n */\r\n export function applyCustomizedDomain(customizedDomain, forcedDomain: any[]): any[] {\r\n let domain: any[] = [undefined, undefined];\r\n\r\n if (forcedDomain && forcedDomain.length === 2) {\r\n domain = [forcedDomain[0], forcedDomain[1]];\r\n }\r\n\r\n if (customizedDomain && customizedDomain.length === 2) {\r\n if (customizedDomain[0] != null) {\r\n domain[0] = customizedDomain[0];\r\n }\r\n if (customizedDomain[1] != null) {\r\n domain[1] = customizedDomain[1];\r\n }\r\n }\r\n\r\n if (domain[0] == null && domain[1] == null) {\r\n return forcedDomain;//return untouched object\r\n }\r\n\r\n //do extra check to see if the user input was valid with the merged axis values.\r\n if (domain[0] != null && domain[1] != null) {\r\n if (domain[0] > domain[1]) {\r\n return forcedDomain;\r\n }\r\n }\r\n\r\n return domain;\r\n }\r\n \r\n /**\r\n * Combine the forced domain with the actual domain if one of the values was set.\r\n * The forcedDomain is in 1st priority. Extends the domain if the any reference point requires it.\r\n */\r\n export function combineDomain(forcedDomain: any[], domain: any[], ensureDomain?: NumberRange): any[] {\r\n let combinedDomain: any[] = domain ? [domain[0], domain[1]] : [];\r\n\r\n if (ensureDomain) {\r\n if (combinedDomain[0] == null || ensureDomain.min < combinedDomain[0])\r\n combinedDomain[0] = ensureDomain.min;\r\n\r\n if (combinedDomain[1] == null || ensureDomain.max > combinedDomain[1])\r\n combinedDomain[1] = ensureDomain.max;\r\n }\r\n\r\n let domainBeforeForced: any[] = [combinedDomain[0], combinedDomain[1]];\r\n\r\n if (forcedDomain && forcedDomain.length === 2) {\r\n if (forcedDomain[0] != null) {\r\n combinedDomain[0] = forcedDomain[0];\r\n }\r\n if (forcedDomain[1] != null) {\r\n combinedDomain[1] = forcedDomain[1];\r\n }\r\n if (combinedDomain[0] > combinedDomain[1]) {\r\n combinedDomain = domainBeforeForced;//this is invalid, so take the original domain considering the values and the reference line\r\n }\r\n }\r\n return combinedDomain;\r\n }\r\n\r\n export function createAxisLabel(properties: DataViewObject, label: string, unitType: string, y2: boolean = false): string {\r\n let propertyName = y2 ? 'secAxisStyle' : 'axisStyle';\r\n if (!properties || !properties[propertyName]) {\r\n return label;\r\n }\r\n\r\n let modifiedLabel;\r\n if (properties[propertyName] === axisStyle.showBoth) {\r\n modifiedLabel = label + ' (' + unitType + ')';//todo: localize\r\n }\r\n else if (properties[propertyName] === axisStyle.showUnitOnly) {\r\n modifiedLabel = unitType;\r\n }\r\n else {\r\n modifiedLabel = label;\r\n }\r\n return modifiedLabel;\r\n }\r\n\r\n export function scaleShouldClamp(combinedDomain: any[], domain: any[]): boolean {\r\n if (!combinedDomain || !domain || combinedDomain.length < 2 || domain.length < 2)\r\n return false;\r\n //when the start or end is different, clamp it\r\n return combinedDomain[0] !== domain[0] || combinedDomain[1] !== domain[1];\r\n }\r\n\r\n export function normalizeNonFiniteNumber(value: number): number {\r\n if (isNaN(value))\r\n return null;\r\n else if (value === Number.POSITIVE_INFINITY)\r\n return Number.MAX_VALUE;\r\n else if (value === Number.NEGATIVE_INFINITY)\r\n return -Number.MAX_VALUE;\r\n\r\n return value;\r\n }\r\n\r\n /**\r\n * Indicates whether the number is power of 10.\r\n */\r\n export function powerOfTen(d: any): boolean {\r\n let value = Math.abs(d);\r\n // formula log2(Y)/log2(10) = log10(Y)\r\n // because double issues this won't return exact value\r\n // we need to ceil it to nearest number.\r\n let log10: number = Math.log(value) / Math.LN10;\r\n log10 = Math.ceil(log10 - 1e-12);\r\n return value / Math.pow(10, log10) === 1;\r\n }\r\n }\r\n}\n\n\n/** WEBPACK FOOTER **\n ** ./src/Clients/Visuals/common/axisHelper.ts\n **/","/*\r\n* Power BI Visualizations\r\n*\r\n* Copyright (c) Microsoft Corporation\r\n* All rights reserved. \r\n* MIT License\r\n*\r\n* Permission is hereby granted, free of charge, to any person obtaining a copy\r\n* of this software and associated documentation files (the \"\"Software\"\"), to deal\r\n* in the Software without restriction, including without limitation the rights\r\n* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\r\n* copies of the Software, and to permit persons to whom the Software is\r\n* furnished to do so, subject to the following conditions:\r\n* \r\n* The above copyright notice and this permission notice shall be included in \r\n* all copies or substantial portions of the Software.\r\n* \r\n* THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR \r\n* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, \r\n* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE \r\n* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER \r\n* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\r\n* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\r\n* THE SOFTWARE.\r\n*/\r\n\r\n/// <reference path=\"../_references.ts\"/>\r\n\r\nmodule powerbi.visuals {\r\n export module ShapeFactory {\r\n\r\n export module ShapeFactoryConsts {\r\n export const PaddingConstRatio: number = 0.01;\r\n export const TrianglePaddingConstRatio: number = 0.15;\r\n export const TriangleEndPaddingConstRatio: number = 0.85;\r\n export const ShapeConstRatio: number = 1.0 - (ShapeFactoryConsts.PaddingConstRatio * 2);\r\n export const SmallPaddingConstValue: number = 10;\r\n export const OvalRadiusConst: number = 2;\r\n export const OvalRadiusConstPadding: number = 0.2;\r\n export const ArrowLeftHeadPoint: Point = { x : 0.05, y : 0.42 };\r\n export const ArrowMiddleHeadPoint: Point = { x: 0.5, y: 0.016 };\r\n export const ArrowRightHeadPoint: Point = { x: 0.95, y: 0.42 };\r\n export const ArrowRightMiddleHeadPoint: Point = { x: 0.764, y: 0.42 };\r\n export const ArrowBottomRightPoint: Point = { x: 0.764, y: 0.993 }; \r\n export const ArrowBottomLeftPoint: Point = { x: 0.246, y: 0.993 };\r\n export const ArrowLeftMiddleHeadPoint: Point = { x: 0.246, y: 0.42 };\r\n }\r\n\r\n /** this function creates a rectangle svg */\r\n export function createRectangle(data: BasicShapeData, viewportHeight: number, viewportWidth: number, selectedElement: D3.Selection, degrees: number): void {\r\n let x = (viewportWidth * ShapeFactoryConsts.PaddingConstRatio) + (data.lineWeight / 2);\r\n let y = (viewportHeight * ShapeFactoryConsts.PaddingConstRatio) + (data.lineWeight / 2);\r\n let width = (viewportWidth * ShapeFactoryConsts.ShapeConstRatio) - (data.lineWeight);\r\n let height = (viewportHeight * ShapeFactoryConsts.ShapeConstRatio) - (data.lineWeight);\r\n let attrs = { x: x, y: y, width: width, height: height, rx: data.roundEdge, ry: data.roundEdge };\r\n let scale = getScale(width, height, degrees);\r\n\r\n createShape(data, viewportHeight, viewportWidth, selectedElement, degrees, scale, 'rect', attrs);\r\n }\r\n\r\n /** this function creates a oval svg */\r\n export function createOval(data: BasicShapeData, viewportHeight: number, viewportWidth: number, selectedElement: D3.Selection, degrees: number): void {\r\n let widthForCircle = (viewportWidth / ShapeFactoryConsts.OvalRadiusConst).toString();\r\n let heightForCircle = (viewportHeight / ShapeFactoryConsts.OvalRadiusConst).toString();\r\n let radiusXForCircle = ((viewportWidth / (ShapeFactoryConsts.OvalRadiusConst + ShapeFactoryConsts.OvalRadiusConstPadding)) - data.lineWeight);\r\n let radiusYForCircle = ((viewportHeight / (ShapeFactoryConsts.OvalRadiusConst + ShapeFactoryConsts.OvalRadiusConstPadding))- data.lineWeight);\r\n let attrs = { cx: widthForCircle, cy: heightForCircle, rx: radiusXForCircle, ry: radiusYForCircle };\r\n\r\n let scale = getScale(viewportWidth, viewportHeight, degrees);\r\n\r\n createShape(data, viewportHeight, viewportWidth, selectedElement, degrees, scale, 'ellipse', attrs);\r\n }\r\n\r\n /** this function creates a line svg */\r\n export function createLine(data: BasicShapeData, viewportHeight: number, viewportWidth: number, selectedElement: D3.Selection, degrees: number): void {\r\n let x1, y1, x2, y2;\r\n let width = (viewportWidth - ShapeFactoryConsts.SmallPaddingConstValue) - ShapeFactoryConsts.SmallPaddingConstValue;\r\n let height = (viewportHeight - ShapeFactoryConsts.SmallPaddingConstValue) - ShapeFactoryConsts.SmallPaddingConstValue;\r\n\r\n let ratio;\r\n if (degrees <= 45) {\r\n ratio = degrees / 90;\r\n\r\n x1 = viewportWidth / 2 + width * ratio;\r\n y1 = ShapeFactoryConsts.SmallPaddingConstValue;\r\n x2 = viewportWidth / 2 - width * ratio;\r\n y2 = (viewportHeight - ShapeFactoryConsts.SmallPaddingConstValue);\r\n } else if (degrees <= 135) {\r\n ratio = (degrees - 45) / 90;\r\n\r\n x1 = (viewportWidth - ShapeFactoryConsts.SmallPaddingConstValue);\r\n y1 = ShapeFactoryConsts.SmallPaddingConstValue + height * ratio;\r\n x2 = ShapeFactoryConsts.SmallPaddingConstValue;\r\n y2 = (viewportHeight - ShapeFactoryConsts.SmallPaddingConstValue) - height * ratio;\r\n } else if (degrees <= 225) {\r\n ratio = (degrees - 135) / 90;\r\n\r\n x1 = (viewportWidth - ShapeFactoryConsts.SmallPaddingConstValue) - width * ratio;\r\n y1 = (viewportHeight - ShapeFactoryConsts.SmallPaddingConstValue);\r\n x2 = ShapeFactoryConsts.SmallPaddingConstValue + width * ratio;\r\n y2 = ShapeFactoryConsts.SmallPaddingConstValue;\r\n } else if (degrees <= 315) {\r\n ratio = (degrees - 225) / 90;\r\n\r\n x1 = ShapeFactoryConsts.SmallPaddingConstValue;\r\n y1 = (viewportHeight - ShapeFactoryConsts.SmallPaddingConstValue) - height * ratio;\r\n x2 = (viewportWidth - ShapeFactoryConsts.SmallPaddingConstValue);\r\n y2 = ShapeFactoryConsts.SmallPaddingConstValue + height * ratio;\r\n } else if (degrees <= 360) {\r\n ratio = (degrees - 315) / 90;\r\n\r\n x1 = ShapeFactoryConsts.SmallPaddingConstValue + width * ratio;\r\n y1 = ShapeFactoryConsts.SmallPaddingConstValue;\r\n x2 = (viewportWidth - ShapeFactoryConsts.SmallPaddingConstValue) - width * ratio;\r\n y2 = (viewportHeight - ShapeFactoryConsts.SmallPaddingConstValue);\r\n }\r\n\r\n // create the inner path with the wanted shape\r\n selectedElement\r\n .append('svg')\r\n .attr({\r\n width: viewportWidth,\r\n height: viewportHeight\r\n })\r\n .append('line')\r\n .attr({\r\n x1: x1,\r\n y1: y1,\r\n x2: x2,\r\n y2: y2,\r\n })\r\n .style({\r\n 'vector-effect': 'non-scaling-stroke',\r\n 'stroke-width': data.lineWeight + 'px',\r\n 'stroke-opacity': (100 - data.lineTransparency) / 100,\r\n 'stroke': data.lineColor\r\n });\r\n }\r\n\r\n /** this function creates a arrow svg */\r\n export function createUpArrow(data: BasicShapeData, viewportHeight: number, viewportWidth: number, selectedElement: D3.Selection, degrees: number): void {\r\n let lineWeight = data.lineWeight;\r\n let viewportHeightWeight = viewportHeight - lineWeight;\r\n let viewportWidthWeight = viewportWidth - lineWeight;\r\n\r\n let arrowPoints = [\r\n { 'x': (viewportWidthWeight * ShapeFactoryConsts.ArrowLeftHeadPoint.x).toString(), 'y': (viewportHeightWeight * ShapeFactoryConsts.ArrowLeftHeadPoint.y).toString() },\r\n { 'x': (viewportWidthWeight * ShapeFactoryConsts.ArrowMiddleHeadPoint.x).toString(), 'y': (viewportHeightWeight * ShapeFactoryConsts.ArrowMiddleHeadPoint.y).toString() },\r\n { 'x': (viewportWidthWeight * ShapeFactoryConsts.ArrowRightHeadPoint.x).toString(), 'y': (viewportHeightWeight * ShapeFactoryConsts.ArrowRightHeadPoint.y).toString() },\r\n { 'x': (viewportWidthWeight * ShapeFactoryConsts.ArrowRightMiddleHeadPoint.x).toString(), 'y': (viewportHeightWeight * ShapeFactoryConsts.ArrowRightMiddleHeadPoint.y).toString() },\r\n { 'x': (viewportWidthWeight * ShapeFactoryConsts.ArrowBottomRightPoint.x).toString(), 'y': (viewportHeightWeight * ShapeFactoryConsts.ArrowBottomRightPoint.y).toString() },\r\n { 'x': (viewportWidthWeight * ShapeFactoryConsts.ArrowBottomLeftPoint.x).toString(), 'y': (viewportHeightWeight * ShapeFactoryConsts.ArrowBottomLeftPoint.y).toString() },\r\n { 'x': (viewportWidthWeight * ShapeFactoryConsts.ArrowLeftMiddleHeadPoint.x).toString(), 'y': (viewportHeightWeight * ShapeFactoryConsts.ArrowLeftMiddleHeadPoint.y).toString() },\r\n ];\r\n\r\n // create the inner path with the wanted shape\r\n createPathFromArray(data, arrowPoints, selectedElement, viewportHeight, viewportWidth, degrees);\r\n }\r\n\r\n /** this function creates a triangle svg */\r\n export function createTriangle(data: BasicShapeData, viewportHeight: number, viewportWidth: number, selectedElement: D3.Selection, degrees: number): void {\r\n let lineWeight = data.lineWeight;\r\n // remove the basic line weight\r\n if (lineWeight > 3) {\r\n lineWeight -= 3;\r\n }\r\n\r\n let firstPointX = ((viewportWidth + lineWeight) * ShapeFactoryConsts.TrianglePaddingConstRatio);\r\n let firstPointY = (viewportHeight - ShapeFactoryConsts.SmallPaddingConstValue - lineWeight) < 0 ?\r\n (viewportHeight - ShapeFactoryConsts.SmallPaddingConstValue) : (viewportHeight - ShapeFactoryConsts.SmallPaddingConstValue - lineWeight);\r\n let secondPointY = ((viewportHeight + lineWeight) * ShapeFactoryConsts.TrianglePaddingConstRatio);\r\n let thirdPointX = ((viewportWidth - lineWeight) * ShapeFactoryConsts.TriangleEndPaddingConstRatio) < 0 ?\r\n (viewportWidth * ShapeFactoryConsts.TriangleEndPaddingConstRatio) : ((viewportWidth - lineWeight) * ShapeFactoryConsts.TriangleEndPaddingConstRatio);\r\n let thirdPointY = (viewportHeight - ShapeFactoryConsts.SmallPaddingConstValue - lineWeight) < 0 ?\r\n (viewportHeight - ShapeFactoryConsts.SmallPaddingConstValue) : (viewportHeight - lineWeight - ShapeFactoryConsts.SmallPaddingConstValue);\r\n let secondPointX = ((firstPointX + thirdPointX) / 2);\r\n\r\n if (firstPointX < 10) {\r\n firstPointX = ShapeFactoryConsts.SmallPaddingConstValue;\r\n }\r\n\r\n if (secondPointY < 10) {\r\n secondPointY = ShapeFactoryConsts.SmallPaddingConstValue;\r\n }\r\n\r\n let trianglePoints = [\r\n { 'x': firstPointX, 'y': firstPointY },\r\n { 'x': secondPointX, 'y': secondPointY },\r\n { 'x': thirdPointX, 'y': thirdPointY },\r\n ];\r\n\r\n createPathFromArray(data, trianglePoints, selectedElement, viewportHeight, viewportWidth, degrees);\r\n }\r\n\r\n /** this funcion adds a path to an svg element from an array of points (x,y) */\r\n function createPathFromArray(data: BasicShapeData, points: Object[], selectedElement: D3.Selection, viewportHeight: number, viewportWidth: number, degrees: number): void {\r\n let lineFunction = d3.svg.line()\r\n .x(function (d) { return d.x; })\r\n .y(function (d) { return d.y; })\r\n .interpolate('linear');\r\n let attrs = { d: lineFunction(points) + ' Z' };\r\n\r\n let scale = getScale(viewportWidth, viewportHeight, degrees);\r\n\r\n createShape(data, viewportHeight, viewportWidth, selectedElement, degrees, scale, 'path', attrs);\r\n }\r\n\r\n function createShape(data: BasicShapeData, viewportHeight: number, viewportWidth: number, selectedElement: D3.Selection, degrees: number, scale: number, shapeType: string, shapeAttrs: Object): void {\r\n selectedElement\r\n .append('div')\r\n .style({\r\n 'transform': 'rotate(' + degrees + 'deg) scale(' + scale + ')',\r\n 'transform-origin': 'center',\r\n // for testing with phantomjs we need the webkit prefix\r\n '-webkit-transform': 'rotate(' + degrees + 'deg) scale(' + scale + ')',\r\n '-webkit-transform-origin': 'center',\r\n 'width': viewportWidth + 'px',\r\n 'height': viewportHeight + 'px'\r\n })\r\n .append('svg')\r\n .attr({\r\n width: viewportWidth,\r\n height: viewportHeight\r\n })\r\n .append(shapeType)\r\n .attr(shapeAttrs)\r\n .style({\r\n 'vector-effect': 'non-scaling-stroke',\r\n 'stroke-width': data.lineWeight + 'px',\r\n 'stroke': data.lineColor,\r\n 'stroke-opacity': (100 - data.lineTransparency) / 100,\r\n 'fill': data.fillColor,\r\n 'fill-opacity': data.showFill === true ? ((100 - data.shapeTransparency) / 100) : 0\r\n });\r\n }\r\n\r\n // this function return the scale to add to the shape. \r\n // it calculate it by the ratio of the original shape's diagonal and the shape's diagonal after rotate (the maximum diagonal that still fit to the container).\r\n // it calculate the shape's diagonal by the rotate angle.\r\n function getScale(width: number, height: number, degrees: number): number {\r\n let originalWidth = width;\r\n let originalHeight = height;\r\n let offsetAngle = Math.atan2(height, width);\r\n let originalFactor = Math.sqrt(Math.pow(height, 2) + Math.pow(width, 2));\r\n let radians = (degrees / 180) * Math.PI;\r\n\r\n if (width >= height) {\r\n if (degrees < 90) {\r\n radians += offsetAngle;\r\n } else if (degrees < 180) {\r\n radians -= offsetAngle;\r\n } else if (degrees < 270) {\r\n radians += offsetAngle;\r\n } else {\r\n radians -= offsetAngle;\r\n }\r\n\r\n return (originalHeight / Math.abs(Math.sin(radians))) / originalFactor;\r\n }\r\n else {\r\n if (degrees < 90) {\r\n radians -= offsetAngle;\r\n } else if (degrees < 180) {\r\n radians += offsetAngle;\r\n } else if (degrees < 270) {\r\n radians -= offsetAngle;\r\n } else {\r\n radians += offsetAngle;\r\n }\r\n\r\n return (originalWidth / Math.abs(Math.cos(radians))) / originalFactor;\r\n }\r\n }\r\n }\r\n}\n\n\n/** WEBPACK FOOTER **\n ** ./src/Clients/Visuals/common/basicShapeUtils.ts\n **/","/*\r\n * Power BI Visualizations\r\n *\r\n * Copyright (c) Microsoft Corporation\r\n * All rights reserved. \r\n * MIT License\r\n *\r\n * Permission is hereby granted, free of charge, to any person obtaining a copy\r\n * of this software and associated documentation files (the \"\"Software\"\"), to deal\r\n * in the Software without restriction, including without limitation the rights\r\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\r\n * copies of the Software, and to permit persons to whom the Software is\r\n * furnished to do so, subject to the following conditions:\r\n * \r\n * The above copyright notice and this permission notice shall be included in \r\n * all copies or substantial portions of the Software.\r\n * \r\n * THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR \r\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, \r\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE \r\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER \r\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\r\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\r\n * THE SOFTWARE.\r\n */\r\n\r\n/// <reference path=\"../_references.ts\"/>\r\n\r\nmodule powerbi.visuals {\r\n export module CartesianHelper {\r\n export function getCategoryAxisProperties(dataViewMetadata: DataViewMetadata, axisTitleOnByDefault?: boolean): DataViewObject {\r\n let toReturn: DataViewObject = {};\r\n if (!dataViewMetadata)\r\n return toReturn;\r\n\r\n let objects = dataViewMetadata.objects;\r\n\r\n if (objects) {\r\n let categoryAxisObject = objects['categoryAxis'];\r\n\r\n if (categoryAxisObject) {\r\n toReturn = {\r\n show: categoryAxisObject['show'],\r\n axisType: categoryAxisObject['axisType'],\r\n axisScale: categoryAxisObject['axisScale'],\r\n start: categoryAxisObject['start'],\r\n end: categoryAxisObject['end'],\r\n showAxisTitle: categoryAxisObject['showAxisTitle'] == null ? axisTitleOnByDefault : categoryAxisObject['showAxisTitle'],\r\n axisStyle: categoryAxisObject['axisStyle'],\r\n labelColor: categoryAxisObject['labelColor'],\r\n labelDisplayUnits: categoryAxisObject['labelDisplayUnits'],\r\n labelPrecision: categoryAxisObject['labelPrecision'],\r\n duration: categoryAxisObject['duration'],\r\n };\r\n }\r\n }\r\n return toReturn;\r\n }\r\n\r\n export function getValueAxisProperties(dataViewMetadata: DataViewMetadata, axisTitleOnByDefault?: boolean): DataViewObject {\r\n let toReturn: DataViewObject = {};\r\n if (!dataViewMetadata)\r\n return toReturn;\r\n\r\n let objects = dataViewMetadata.objects;\r\n\r\n if (objects) {\r\n let valueAxisObject = objects['valueAxis'];\r\n if (valueAxisObject) {\r\n toReturn = {\r\n show: valueAxisObject['show'],\r\n position: valueAxisObject['position'],\r\n axisScale: valueAxisObject['axisScale'],\r\n start: valueAxisObject['start'],\r\n end: valueAxisObject['end'],\r\n showAxisTitle: valueAxisObject['showAxisTitle'] == null ? axisTitleOnByDefault : valueAxisObject['showAxisTitle'],\r\n axisStyle: valueAxisObject['axisStyle'],\r\n labelColor: valueAxisObject['labelColor'],\r\n labelDisplayUnits: valueAxisObject['labelDisplayUnits'],\r\n labelPrecision: valueAxisObject['labelPrecision'],\r\n secShow: valueAxisObject['secShow'],\r\n secPosition: valueAxisObject['secPosition'],\r\n secAxisScale: valueAxisObject['secAxisScale'],\r\n secStart: valueAxisObject['secStart'],\r\n secEnd: valueAxisObject['secEnd'],\r\n secShowAxisTitle: valueAxisObject['secShowAxisTitle'],\r\n secAxisStyle: valueAxisObject['secAxisStyle'],\r\n secLabelColor: valueAxisObject['secLabelColor'],\r\n secLabelDisplayUnits: valueAxisObject['secLabelDisplayUnits'],\r\n secLabelPrecision: valueAxisObject['secLabelPrecision'],\r\n };\r\n }\r\n }\r\n return toReturn;\r\n } \r\n \r\n export function isScalar(isScalar: boolean, xAxisCardProperties: DataViewObject): boolean {\r\n if (isScalar) {\r\n //now check what the user wants\r\n isScalar = xAxisCardProperties && xAxisCardProperties['axisType'] ? xAxisCardProperties['axisType'] === axisType.scalar : true;\r\n }\r\n return isScalar;\r\n }\r\n\r\n export function getPrecision(precision: DataViewPropertyValue): number {\r\n if (precision != null) {\r\n if (precision < 0) {\r\n return 0;\r\n }\r\n return <number>precision;\r\n }\r\n return null;\r\n }\r\n\r\n export function lookupXValue(data: CartesianData, index: number, type: ValueType, isScalar: boolean): any {\r\n debug.assertValue(data, 'data');\r\n debug.assertValue(type, 'type');\r\n\r\n let isDateTime = AxisHelper.isDateTime(type);\r\n\r\n if (isScalar) {\r\n if (isDateTime)\r\n return new Date(index);\r\n\r\n // index is the numeric value\r\n return index;\r\n }\r\n\r\n if (type.text) {\r\n debug.assert(index < data.categories.length, 'category index out of range');\r\n return data.categories[index];\r\n }\r\n\r\n if (data && data.series && data.series.length > 0) {\r\n let firstSeries = data.series[0];\r\n if (firstSeries) {\r\n let seriesValues = firstSeries.data;\r\n if (seriesValues) {\r\n if (data.hasHighlights)\r\n index = index * 2;\r\n let dataAtIndex = seriesValues[index];\r\n if (dataAtIndex) {\r\n if (isDateTime && dataAtIndex.categoryValue != null)\r\n return new Date(dataAtIndex.categoryValue);\r\n return dataAtIndex.categoryValue;\r\n }\r\n }\r\n }\r\n }\r\n\r\n return index;\r\n }\r\n\r\n export function findMaxCategoryIndex(series: CartesianSeries[]): number {\r\n if (_.isEmpty(series)) {\r\n return 0;\r\n }\r\n let maxCategoryIndex: number = 0;\r\n for (let singleSeries of series) {\r\n if (!_.isEmpty(singleSeries.data)) {\r\n let lastIndex = singleSeries.data[singleSeries.data.length - 1].categoryIndex;\r\n maxCategoryIndex = Math.max(lastIndex, maxCategoryIndex);\r\n }\r\n }\r\n return maxCategoryIndex;\r\n }\r\n }\r\n}\n\n\n/** WEBPACK FOOTER **\n ** ./src/Clients/Visuals/common/cartesianHelper.ts\n **/","/*\r\n * Power BI Visualizations\r\n *\r\n * Copyright (c) Microsoft Corporation\r\n * All rights reserved. \r\n * MIT License\r\n *\r\n * Permission is hereby granted, free of charge, to any person obtaining a copy\r\n * of this software and associated documentation files (the \"\"Software\"\"), to deal\r\n * in the Software without restriction, including without limitation the rights\r\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\r\n * copies of the Software, and to permit persons to whom the Software is\r\n * furnished to do so, subject to the following conditions:\r\n * \r\n * The above copyright notice and this permission notice shall be included in \r\n * all copies or substantial portions of the Software.\r\n * \r\n * THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR \r\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, \r\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE \r\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER \r\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\r\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\r\n * THE SOFTWARE.\r\n */\r\n\r\n/// <reference path=\"../_references.ts\"/>\r\n\r\nmodule powerbi.visuals {\r\n import SQExprShortSerializer = data.SQExprShortSerializer;\r\n\r\n export class ColorHelper {\r\n private fillProp: DataViewObjectPropertyIdentifier;\r\n private defaultDataPointColor: string;\r\n private colors: IDataColorPalette;\r\n private defaultColorScale: IColorScale;\r\n\r\n constructor(colors: IDataColorPalette, fillProp?: DataViewObjectPropertyIdentifier, defaultDataPointColor?: string) {\r\n this.colors = colors;\r\n this.fillProp = fillProp;\r\n this.defaultDataPointColor = defaultDataPointColor;\r\n this.defaultColorScale = colors.getNewColorScale();\r\n }\r\n\r\n /**\r\n * Gets the color for the given series value.\r\n * If no explicit color or default color has been set then the color is\r\n * allocated from the color scale for this series.\r\n */\r\n public getColorForSeriesValue(objects: DataViewObjects, fieldIds: powerbi.data.ISQExpr[], value: string): string {\r\n return (this.fillProp && DataViewObjects.getFillColor(objects, this.fillProp))\r\n || this.defaultDataPointColor\r\n || this.getColorScaleForSeries(fieldIds).getColor(value).value;\r\n }\r\n\r\n /**\r\n * Gets the color scale for the given series.\r\n */\r\n public getColorScaleForSeries(fieldIds: powerbi.data.ISQExpr[]): IColorScale {\r\n return this.colors.getColorScaleByKey(SQExprShortSerializer.serializeArray(<data.SQExpr[]>fieldIds || []));\r\n }\r\n\r\n /** \r\n * Gets the color for the given measure.\r\n */\r\n public getColorForMeasure(objects: DataViewObjects, measureKey: any): string {\r\n // Note, this allocates the color from the scale regardless of if we use it or not which helps keep colors stable.\r\n let scaleColor = this.defaultColorScale.getColor(measureKey).value;\r\n\r\n return (this.fillProp && DataViewObjects.getFillColor(objects, this.fillProp))\r\n || this.defaultDataPointColor\r\n || scaleColor;\r\n }\r\n\r\n public static normalizeSelector(selector: data.Selector, isSingleSeries?: boolean): data.Selector {\r\n debug.assertAnyValue(selector, 'selector');\r\n\r\n // For dynamic series charts, colors are set per category. So, exclude any measure (metadata repetition) from the selector.\r\n if (selector && (isSingleSeries || selector.data))\r\n return { data: selector.data };\r\n\r\n return selector;\r\n }\r\n }\r\n}\n\n\n/** WEBPACK FOOTER **\n ** ./src/Clients/Visuals/common/colorHelper.ts\n **/","/*\r\n * Power BI Visualizations\r\n *\r\n * Copyright (c) Microsoft Corporation\r\n * All rights reserved. \r\n * MIT License\r\n *\r\n * Permission is hereby granted, free of charge, to any person obtaining a copy\r\n * of this software and associated documentation files (the \"\"Software\"\"), to deal\r\n * in the Software without restriction, including without limitation the rights\r\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\r\n * copies of the Software, and to permit persons to whom the Software is\r\n * furnished to do so, subject to the following conditions:\r\n * \r\n * The above copyright notice and this permission notice shall be included in \r\n * all copies or substantial portions of the Software.\r\n * \r\n * THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR \r\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, \r\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE \r\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER \r\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\r\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\r\n * THE SOFTWARE.\r\n */\r\n\r\n/// <reference path=\"../_references.ts\"/>\r\n\r\nmodule powerbi.visuals {\r\n import ClassAndSelector = jsCommon.CssConstants.ClassAndSelector;\r\n\r\n const rectName = 'rect';\r\n\r\n export module ColumnUtil {\r\n export const DimmedOpacity = 0.4;\r\n export const DefaultOpacity = 1.0;\r\n\r\n export function applyUserMinMax(isScalar: boolean, dataView: DataViewCategorical, xAxisCardProperties: DataViewObject): DataViewCategorical {\r\n if (isScalar) {\r\n let min = xAxisCardProperties['start'];\r\n let max = xAxisCardProperties['end'];\r\n\r\n return ColumnUtil.transformDomain(dataView, min, max);\r\n }\r\n\r\n return dataView;\r\n }\r\n\r\n export function transformDomain(dataView: DataViewCategorical, min: DataViewPropertyValue, max: DataViewPropertyValue): DataViewCategorical {\r\n if (!dataView.categories || !dataView.values || dataView.categories.length === 0 || dataView.values.length === 0)\r\n return dataView;// no need to do something when there are no categories\r\n \r\n if (typeof min !== \"number\" && typeof max !== \"number\")\r\n return dataView;//user did not set min max, nothing to do here\r\n\r\n let category = dataView.categories[0];//at the moment we only support one category\r\n let categoryType = category ? category.source.type : null;\r\n\r\n // Min/Max comparison won't work if category source is Ordinal\r\n if (AxisHelper.isOrdinal(categoryType))\r\n return;\r\n\r\n let categoryValues = category.values;\r\n let categoryObjects = category.objects;\r\n\r\n if (!categoryValues || !categoryObjects)\r\n return dataView;\r\n let newcategoryValues = [];\r\n let newValues = [];\r\n let newObjects = [];\r\n\r\n //get new min max\r\n if (typeof min !== \"number\") {\r\n min = categoryValues[0];\r\n }\r\n if (typeof max !== \"number\") {\r\n max = categoryValues[categoryValues.length - 1];\r\n }\r\n\r\n //don't allow this\r\n if (min > max)\r\n return dataView;\r\n\r\n //build measure array\r\n for (let j = 0, len = dataView.values.length; j < len; j++) {\r\n newValues.push([]);\r\n }\r\n\r\n for (let t = 0, len = categoryValues.length; t < len; t++) {\r\n if (categoryValues[t] >= min && categoryValues[t] <= max) {\r\n newcategoryValues.push(categoryValues[t]);\r\n if (categoryObjects) {\r\n newObjects.push(categoryObjects[t]);\r\n }\r\n \r\n //on each measure set the new range\r\n if (dataView.values) {\r\n for (let k = 0; k < dataView.values.length; k++) {\r\n newValues[k].push(dataView.values[k].values[t]);\r\n }\r\n }\r\n }\r\n }\r\n\r\n //don't write directly to dataview\r\n let resultDataView = Prototype.inherit(dataView);\r\n let resultDataViewValues = resultDataView.values = Prototype.inherit(resultDataView.values);\r\n let resultDataViewCategories = resultDataView.categories = Prototype.inherit(dataView.categories);\r\n let resultDataViewCategories0 = resultDataView.categories[0] = Prototype.inherit(resultDataViewCategories[0]);\r\n\r\n resultDataViewCategories0.values = newcategoryValues;\r\n //only if we had objects, then you set the new objects\r\n if (resultDataViewCategories0.objects) {\r\n resultDataViewCategories0.objects = newObjects;\r\n }\r\n\r\n //update measure array\r\n for (let t = 0, len = dataView.values.length; t < len; t++) {\r\n let measureArray = resultDataViewValues[t] = Prototype.inherit(resultDataViewValues[t]);\r\n measureArray.values = newValues[t];\r\n }\r\n\r\n return resultDataView;\r\n }\r\n\r\n export function getCategoryAxis(\r\n data: ColumnChartData,\r\n size: number,\r\n layout: CategoryLayout,\r\n isVertical: boolean,\r\n forcedXMin?: DataViewPropertyValue,\r\n forcedXMax?: DataViewPropertyValue,\r\n axisScaleType?: string,\r\n axisDisplayUnits?: number,\r\n axisPrecision?: number,\r\n ensureXDomain?: NumberRange): IAxisProperties {\r\n\r\n let categoryThickness = layout.categoryThickness;\r\n let isScalar = layout.isScalar;\r\n let outerPaddingRatio = layout.outerPaddingRatio;\r\n let domain = AxisHelper.createDomain(data.series, data.categoryMetadata ? data.categoryMetadata.type : ValueType.fromDescriptor({ text: true }), isScalar, [forcedXMin, forcedXMax], ensureXDomain);\r\n\r\n let axisProperties = AxisHelper.createAxis({\r\n pixelSpan: size,\r\n dataDomain: domain,\r\n metaDataColumn: data.categoryMetadata,\r\n formatString: valueFormatter.getFormatString(data.categoryMetadata, columnChartProps.general.formatString),\r\n outerPadding: categoryThickness * outerPaddingRatio,\r\n isCategoryAxis: true,\r\n isScalar: isScalar,\r\n isVertical: isVertical,\r\n categoryThickness: categoryThickness,\r\n useTickIntervalForDisplayUnits: true,\r\n getValueFn: (index, type) => CartesianHelper.lookupXValue(data, index, type, isScalar),\r\n scaleType: axisScaleType,\r\n axisDisplayUnits: axisDisplayUnits,\r\n axisPrecision: axisPrecision\r\n });\r\n\r\n // intentionally updating the input layout by ref\r\n layout.categoryThickness = axisProperties.categoryThickness;\r\n\r\n return axisProperties;\r\n }\r\n\r\n export function applyInteractivity(columns: D3.Selection, onDragStart): void {\r\n debug.assertValue(columns, 'columns');\r\n\r\n if (onDragStart) {\r\n columns\r\n .attr('draggable', 'true')\r\n .on('dragstart', onDragStart);\r\n }\r\n }\r\n\r\n export function getFillOpacity(selected: boolean, highlight: boolean, hasSelection: boolean, hasPartialHighlights: boolean): number {\r\n if ((hasPartialHighlights && !highlight) || (hasSelection && !selected))\r\n return DimmedOpacity;\r\n return DefaultOpacity;\r\n }\r\n\r\n export function getClosestColumnIndex(coordinate: number, columnsCenters: number[]): number {\r\n let currentIndex = 0;\r\n let distance: number = Number.MAX_VALUE;\r\n for (let i = 0, ilen = columnsCenters.length; i < ilen; i++) {\r\n let currentDistance = Math.abs(coordinate - columnsCenters[i]);\r\n if (currentDistance < distance) {\r\n distance = currentDistance;\r\n currentIndex = i;\r\n }\r\n }\r\n\r\n return currentIndex;\r\n }\r\n\r\n export function setChosenColumnOpacity(mainGraphicsContext: D3.Selection, columnGroupSelector: string, selectedColumnIndex: number, lastColumnIndex: number): void {\r\n let series = mainGraphicsContext.selectAll(ColumnChart.SeriesClasses.selector);\r\n let lastColumnUndefined = typeof lastColumnIndex === 'undefined';\r\n // find all columns that do not belong to the selected column and set a dimmed opacity with a smooth animation to those columns\r\n series.selectAll(rectName + columnGroupSelector).filter((d: ColumnChartDataPoint) => {\r\n return (d.categoryIndex !== selectedColumnIndex) && (lastColumnUndefined || d.categoryIndex === lastColumnIndex);\r\n }).transition().style('fill-opacity', DimmedOpacity);\r\n\r\n // set the default opacity for the selected column\r\n series.selectAll(rectName + columnGroupSelector).filter((d: ColumnChartDataPoint) => {\r\n return d.categoryIndex === selectedColumnIndex;\r\n }).style('fill-opacity', DefaultOpacity);\r\n }\r\n\r\n export function drawSeries(data: ColumnChartData, graphicsContext: D3.Selection, axisOptions: ColumnAxisOptions): D3.UpdateSelection {\r\n let colGroupSelection = graphicsContext.selectAll(ColumnChart.SeriesClasses.selector);\r\n let series = colGroupSelection.data(data.series,(d: ColumnChartSeries) => d.key);\r\n\r\n series\r\n .enter()\r\n .append('g')\r\n .classed(ColumnChart.SeriesClasses.class, true);\r\n \r\n series\r\n .style({\r\n fill: (d: ColumnChartSeries) => d.color, \r\n });\r\n \r\n series\r\n .exit()\r\n .remove();\r\n\r\n return series;\r\n }\r\n\r\n export function drawDefaultShapes(data: ColumnChartData, series: D3.UpdateSelection, layout: IColumnLayout, itemCS: ClassAndSelector, filterZeros: boolean, hasSelection: boolean): D3.UpdateSelection {\r\n // We filter out invisible (0, null, etc.) values from the dataset\r\n // based on whether animations are enabled or not, Dashboard and\r\n // Exploration mode, respectively.\r\n let dataSelector: (d: ColumnChartSeries) => any[];\r\n if (filterZeros) {\r\n dataSelector = (d: ColumnChartSeries) => {\r\n let filteredData = _.filter(d.data,(datapoint: ColumnChartDataPoint) => !!datapoint.value);\r\n return filteredData;\r\n };\r\n }\r\n else {\r\n dataSelector = (d: ColumnChartSeries) => d.data;\r\n }\r\n\r\n let shapeSelection = series.selectAll(itemCS.selector);\r\n let shapes = shapeSelection.data(dataSelector, (d: ColumnChartDataPoint) => d.key);\r\n\r\n shapes.enter()\r\n .append(rectName)\r\n .attr(\"class\",(d: ColumnChartDataPoint) => itemCS.class.concat(d.highlight ? \" highlight\" : \"\"));\r\n\r\n shapes\r\n .style(\"fill-opacity\", (d: ColumnChartDataPoint) => ColumnUtil.getFillOpacity(d.selected, d.highlight, hasSelection, data.hasHighlights))\r\n .style(\"fill\", (d: ColumnChartDataPoint) => d.color !== data.series[d.seriesIndex].color ? d.color : null) // PERF: Only set the fill color if it is different than series.\r\n .attr(layout.shapeLayout);\r\n\r\n shapes\r\n .exit()\r\n .remove();\r\n\r\n return shapes;\r\n }\r\n\r\n export function drawDefaultLabels(series: D3.UpdateSelection, context: D3.Selection, layout: ILabelLayout, viewPort: IViewport, isAnimator: boolean = false, animationDuration?: number): D3.UpdateSelection {\r\n if (series) {\r\n let seriesData = series.data();\r\n let dataPoints: ColumnChartDataPoint[] = [];\r\n\r\n for (let i = 0, len = seriesData.length; i < len; i++) {\r\n Array.prototype.push.apply(dataPoints, seriesData[i].data);\r\n }\r\n\r\n return dataLabelUtils.drawDefaultLabelsForDataPointChart(dataPoints, context, layout, viewPort, isAnimator, animationDuration);\r\n }\r\n else {\r\n dataLabelUtils.cleanDataLabels(context);\r\n }\r\n }\r\n\r\n export function normalizeInfinityInScale(scale: D3.Scale.GenericScale<any>): void {\r\n // When large values (eg Number.MAX_VALUE) are involved, a call to scale.nice occasionally\r\n // results in infinite values being included in the domain. To correct for that, we need to\r\n // re-normalize the domain now to not include infinities.\r\n let scaledDomain = scale.domain();\r\n for (let i = 0, len = scaledDomain.length; i < len; ++i) {\r\n if (scaledDomain[i] === Number.POSITIVE_INFINITY)\r\n scaledDomain[i] = Number.MAX_VALUE;\r\n else if (scaledDomain[i] === Number.NEGATIVE_INFINITY)\r\n scaledDomain[i] = -Number.MAX_VALUE;\r\n }\r\n\r\n scale.domain(scaledDomain);\r\n }\r\n\r\n export function calculatePosition(d: ColumnChartDataPoint, axisOptions: ColumnAxisOptions): number {\r\n let xScale = axisOptions.xScale;\r\n let yScale = axisOptions.yScale;\r\n let scaledY0 = yScale(0);\r\n let scaledX0 = xScale(0);\r\n switch (d.chartType) {\r\n case ColumnChartType.stackedBar:\r\n case ColumnChartType.hundredPercentStackedBar:\r\n return scaledX0 + Math.abs(AxisHelper.diffScaled(xScale, 0, d.valueAbsolute)) +\r\n AxisHelper.diffScaled(xScale, d.position - d.valueAbsolute, 0) + dataLabelUtils.defaultColumnLabelMargin;\r\n case ColumnChartType.clusteredBar:\r\n return scaledX0 + AxisHelper.diffScaled(xScale, Math.max(0, d.value), 0) + dataLabelUtils.defaultColumnLabelMargin;\r\n case ColumnChartType.stackedColumn:\r\n case ColumnChartType.hundredPercentStackedColumn:\r\n return scaledY0 + AxisHelper.diffScaled(yScale, d.position, 0) - dataLabelUtils.defaultColumnLabelMargin;\r\n case ColumnChartType.clusteredColumn:\r\n return scaledY0 + AxisHelper.diffScaled(yScale, Math.max(0, d.value), 0) - dataLabelUtils.defaultColumnLabelMargin;\r\n }\r\n\r\n }\r\n }\r\n\r\n export module ClusteredUtil {\r\n\r\n export function clearColumns(\r\n mainGraphicsContext: D3.Selection,\r\n itemCS: ClassAndSelector): void {\r\n\r\n debug.assertValue(mainGraphicsContext, 'mainGraphicsContext');\r\n debug.assertValue(itemCS, 'itemCS');\r\n\r\n let cols = mainGraphicsContext.selectAll(itemCS.selector)\r\n .data([]);\r\n\r\n cols.exit().remove();\r\n }\r\n }\r\n\r\n export interface ValueMultiplers {\r\n pos: number;\r\n neg: number;\r\n }\r\n\r\n export module StackedUtil {\r\n const PctRoundingError = 0.0001;\r\n\r\n export function getSize(scale: D3.Scale.GenericScale<any>, size: number, zeroVal: number = 0): number {\r\n return AxisHelper.diffScaled(scale, zeroVal, size);\r\n }\r\n\r\n export function calcValueDomain(data: ColumnChartSeries[], is100pct: boolean): NumberRange {\r\n let defaultNumberRange = {\r\n min: 0,\r\n max: 10\r\n };\r\n\r\n if (data.length === 0)\r\n return defaultNumberRange;\r\n\r\n // Can't use AxisHelper because Stacked layout has a slightly different calc, (position - valueAbs)\r\n let min = d3.min<ColumnChartSeries, number>(data, d => d3.min<ColumnChartDataPoint, number>(d.data, e => e.position - e.valueAbsolute));\r\n let max = d3.max<ColumnChartSeries, number>(data, d => d3.max<ColumnChartDataPoint, number>(d.data, e => e.position));\r\n\r\n if (is100pct) {\r\n min = Double.roundToPrecision(min, PctRoundingError);\r\n max = Double.roundToPrecision(max, PctRoundingError);\r\n }\r\n\r\n return {\r\n min: min,\r\n max: max,\r\n };\r\n }\r\n\r\n export function getStackedMultiplier(\r\n dataView: DataViewCategorical,\r\n rowIdx: number,\r\n seriesCount: number,\r\n categoryCount: number,\r\n converterStrategy: IColumnChartConverterStrategy): ValueMultiplers {\r\n debug.assertValue(dataView, 'dataView');\r\n debug.assertValue(rowIdx, 'rowIdx');\r\n\r\n let pos: number = 0,\r\n neg: number = 0;\r\n \r\n for (let i = 0; i < seriesCount; i++) {\r\n let value: number = converterStrategy.getValueBySeriesAndCategory(i, rowIdx);\r\n value = AxisHelper.normalizeNonFiniteNumber(value);\r\n\r\n if (value > 0)\r\n pos += value;\r\n else if (value < 0)\r\n neg -= value;\r\n }\r\n\r\n let absTotal = pos + neg;\r\n return {\r\n pos: pos ? (pos / absTotal) / pos : 1,\r\n neg: neg ? (neg / absTotal) / neg : 1,\r\n };\r\n }\r\n\r\n export function clearColumns(\r\n mainGraphicsContext: D3.Selection,\r\n itemCS: ClassAndSelector): void {\r\n\r\n debug.assertValue(mainGraphicsContext, 'mainGraphicsContext');\r\n debug.assertValue(itemCS, 'itemCS');\r\n\r\n let bars = mainGraphicsContext.selectAll(itemCS.selector)\r\n .data([]);\r\n\r\n bars.exit().remove();\r\n }\r\n }\r\n}\n\n\n/** WEBPACK FOOTER **\n ** ./src/Clients/Visuals/common/columnChartUtil.ts\n **/","/*\r\n * Power BI Visualizations\r\n *\r\n * Copyright (c) Microsoft Corporation\r\n * All rights reserved. \r\n * MIT License\r\n *\r\n * Permission is hereby granted, free of charge, to any person obtaining a copy\r\n * of this software and associated documentation files (the \"\"Software\"\"), to deal\r\n * in the Software without restriction, including without limitation the rights\r\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\r\n * copies of the Software, and to permit persons to whom the Software is\r\n * furnished to do so, subject to the following conditions:\r\n * \r\n * The above copyright notice and this permission notice shall be included in \r\n * all copies or substantial portions of the Software.\r\n * \r\n * THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR \r\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, \r\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE \r\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER \r\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\r\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\r\n * THE SOFTWARE.\r\n */\r\n\r\n/// <reference path=\"../_references.ts\"/>\r\n\r\nmodule powerbi.visuals {\r\n import DataRoleHelper = powerbi.data.DataRoleHelper;\r\n\r\n export interface PivotedCategoryInfo {\r\n categories?: any[];\r\n categoryFormatter?: IValueFormatter;\r\n categoryIdentities?: DataViewScopeIdentity[];\r\n categoryObjects?: DataViewObjects[];\r\n }\r\n\r\n export module converterHelper {\r\n export function categoryIsAlsoSeriesRole(dataView: DataViewCategorical, seriesRoleName: string, categoryRoleName: string): boolean {\r\n if (dataView.categories && dataView.categories.length > 0) {\r\n // Need to pivot data if our category soure is a series role\r\n let category = dataView.categories[0];\r\n return category.source &&\r\n DataRoleHelper.hasRole(category.source, seriesRoleName) &&\r\n DataRoleHelper.hasRole(category.source, categoryRoleName);\r\n }\r\n\r\n return false;\r\n }\r\n\r\n export function getPivotedCategories(dataView: DataViewCategorical, formatStringProp: DataViewObjectPropertyIdentifier): PivotedCategoryInfo {\r\n if (dataView.categories && dataView.categories.length > 0) {\r\n let category = dataView.categories[0];\r\n let categoryValues = category.values;\r\n\r\n return category.values.length > 0\r\n ? {\r\n categories: categoryValues,\r\n categoryFormatter: valueFormatter.create({\r\n format: valueFormatter.getFormatString(category.source, formatStringProp),\r\n value: categoryValues[0],\r\n value2: categoryValues[categoryValues.length - 1],\r\n // Do not use display units such as K/M/bn etc. on the x-axis.\r\n // PowerView does not use units either as large ranges will make the x-axis indecipherable.\r\n displayUnitSystemType: DisplayUnitSystemType.Verbose,\r\n }),\r\n categoryIdentities: category.identity,\r\n categoryObjects: category.objects,\r\n }\r\n : {\r\n categories: [],\r\n categoryFormatter: { format: valueFormatter.format },\r\n };\r\n }\r\n\r\n // For cases where the category source is just a series role, we are pivoting the data on the role which means we\r\n // will have no categories.\r\n return defaultCategories();\r\n }\r\n\r\n export function getSeriesName(source: DataViewMetadataColumn): string {\r\n debug.assertValue(source, 'source');\r\n\r\n return (source.groupName !== undefined)\r\n ? source.groupName\r\n : source.queryName;\r\n }\r\n\r\n export function getFormattedLegendLabel(source: DataViewMetadataColumn, values: DataViewValueColumns, formatStringProp: DataViewObjectPropertyIdentifier): string {\r\n debug.assertValue(source, 'source');\r\n debug.assertValue(values, 'values');\r\n\r\n let sourceForFormat = source;\r\n let nameForFormat = source.displayName;\r\n if (source.groupName !== undefined) {\r\n sourceForFormat = values.source;\r\n nameForFormat = source.groupName;\r\n }\r\n\r\n return valueFormatter.format(nameForFormat, valueFormatter.getFormatString(sourceForFormat, formatStringProp));\r\n }\r\n\r\n function defaultCategories(): PivotedCategoryInfo {\r\n return {\r\n categories: [null],\r\n categoryFormatter: { format: valueFormatter.format },\r\n };\r\n }\r\n\r\n export function createAxesLabels(categoryAxisProperties: DataViewObject,\r\n valueAxisProperties: DataViewObject,\r\n category: DataViewMetadataColumn,\r\n values: DataViewMetadataColumn[]) {\r\n let xAxisLabel = null;\r\n let yAxisLabel = null;\r\n\r\n if (categoryAxisProperties) {\r\n\r\n // Take the value only if it's there\r\n if (category && category.displayName) {\r\n xAxisLabel = category.displayName;\r\n }\r\n }\r\n\r\n if (valueAxisProperties) {\r\n let valuesNames: string[] = [];\r\n \r\n if (values) {\r\n // Take the name from the values, and make it unique because there are sometimes duplications\r\n valuesNames = values.map(v => v ? v.displayName : '').filter((value, index, self) => value !== '' && self.indexOf(value) === index);\r\n yAxisLabel = valueFormatter.formatListAnd(valuesNames);\r\n }\r\n }\r\n return { xAxisLabel: xAxisLabel, yAxisLabel: yAxisLabel };\r\n }\r\n\r\n export function isImageUrlColumn(column: DataViewMetadataColumn): boolean {\r\n let misc = getMiscellaneousTypeDescriptor(column);\r\n return misc != null && misc.imageUrl === true;\r\n }\r\n\r\n export function isWebUrlColumn(column: DataViewMetadataColumn): boolean {\r\n let misc = getMiscellaneousTypeDescriptor(column);\r\n return misc != null && misc.webUrl === true;\r\n }\r\n\r\n function getMiscellaneousTypeDescriptor(column: DataViewMetadataColumn): MiscellaneousTypeDescriptor {\r\n return column\r\n && column.type\r\n && column.type.misc;\r\n }\r\n\r\n export function hasImageUrlColumn(dataView: DataView): boolean {\r\n if (!dataView || !dataView.metadata || _.isEmpty(dataView.metadata.columns))\r\n return false;\r\n\r\n return _.any(dataView.metadata.columns, column => isImageUrlColumn(column) === true);\r\n }\r\n\r\n export function formatFromMetadataColumn(value: any, column: DataViewMetadataColumn, formatStringProp: DataViewObjectPropertyIdentifier): string {\r\n debug.assertValue(column, 'column should exist');\r\n let formatString: string = valueFormatter.getFormatString(column, formatStringProp, true);\r\n if (!formatString && column) {\r\n formatString = column.format;\r\n }\r\n return valueFormatter.format(value, formatString);\r\n }\r\n }\r\n}\n\n\n/** WEBPACK FOOTER **\n ** ./src/Clients/Visuals/common/converterHelper.ts\n **/","/*\r\n * Power BI Visualizations\r\n *\r\n * Copyright (c) Microsoft Corporation\r\n * All rights reserved. \r\n * MIT License\r\n *\r\n * Permission is hereby granted, free of charge, to any person obtaining a copy\r\n * of this software and associated documentation files (the \"\"Software\"\"), to deal\r\n * in the Software without restriction, including without limitation the rights\r\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\r\n * copies of the Software, and to permit persons to whom the Software is\r\n * furnished to do so, subject to the following conditions:\r\n * \r\n * The above copyright notice and this permission notice shall be included in \r\n * all copies or substantial portions of the Software.\r\n * \r\n * THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR \r\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, \r\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE \r\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER \r\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\r\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\r\n * THE SOFTWARE.\r\n */\r\n\r\n/// <reference path=\"../_references.ts\"/>\r\n\r\nmodule powerbi.visuals {\r\n import ClassAndSelector = jsCommon.CssConstants.ClassAndSelector;\r\n import createClassAndSelector = jsCommon.CssConstants.createClassAndSelector;\r\n import PixelConverter = jsCommon.PixelConverter;\r\n import LabelStyle = labelStyle;\r\n\r\n export const enum PointLabelPosition {\r\n Above,\r\n Below,\r\n }\r\n\r\n export interface PointDataLabelsSettings extends VisualDataLabelsSettings {\r\n position: PointLabelPosition;\r\n }\r\n\r\n export interface LabelFormattedTextOptions {\r\n label: any;\r\n maxWidth?: number;\r\n format?: string;\r\n formatter?: IValueFormatter;\r\n fontSize?: number;\r\n }\r\n\r\n export interface VisualDataLabelsSettings {\r\n show: boolean;\r\n showLabelPerSeries?: boolean;\r\n isSeriesExpanded?: boolean;\r\n displayUnits?: number;\r\n showCategory?: boolean;\r\n position?: any;\r\n precision?: number;\r\n labelColor: string;\r\n categoryLabelColor?: string;\r\n fontSize?: number;\r\n labelStyle?: any;\r\n }\r\n\r\n /*\r\n Options for setting the labels card on the property pane\r\n */\r\n export interface VisualDataLabelsSettingsOptions {\r\n show: boolean;\r\n enumeration: ObjectEnumerationBuilder;\r\n dataLabelsSettings: VisualDataLabelsSettings;\r\n displayUnits?: boolean;\r\n precision?: boolean;\r\n position?: boolean;\r\n positionObject?: string[];\r\n selector?: powerbi.data.Selector;\r\n fontSize?: boolean;\r\n showAll?: boolean;\r\n labelDensity?: boolean;\r\n labelStyle?: boolean;\r\n }\r\n\r\n export interface LabelEnabledDataPoint {\r\n //for collistion detection use\r\n labelX?: number;\r\n labelY?: number;\r\n //for overriding color from label settings\r\n labelFill?: string;\r\n //for display units and precision\r\n labeltext?: string;\r\n //taken from column metadata\r\n labelFormatString?: string;\r\n isLabelInside?: boolean;\r\n labelFontSize?: number;\r\n }\r\n\r\n export interface IColumnFormatterCache {\r\n [column: string]: IValueFormatter;\r\n defaultFormatter?: IValueFormatter;\r\n }\r\n\r\n export interface IColumnFormatterCacheManager {\r\n cache: IColumnFormatterCache;\r\n getOrCreate: (formatString: string, labelSetting: VisualDataLabelsSettings, value2?: number) => IValueFormatter;\r\n }\r\n\r\n export interface LabelPosition {\r\n y: (d: any, i: number) => number;\r\n x: (d: any, i: number) => number;\r\n }\r\n\r\n export interface ILabelLayout {\r\n labelText: (d: any) => string;\r\n labelLayout: LabelPosition;\r\n filter: (d: any) => boolean;\r\n style: {};\r\n }\r\n\r\n export interface DataLabelObject extends DataViewObject {\r\n show: boolean;\r\n color: Fill;\r\n labelDisplayUnits: number;\r\n labelPrecision?: number;\r\n labelPosition: any;\r\n fontSize?: number;\r\n showAll?: boolean;\r\n showSeries?: boolean;\r\n labelDensity?: string;\r\n labelStyle?: any;\r\n }\r\n\r\n export module dataLabelUtils {\r\n export const minLabelFontSize: number = 8;\r\n export const labelMargin: number = 8;\r\n export const maxLabelWidth: number = 50;\r\n export const defaultColumnLabelMargin: number = 5;\r\n export const defaultColumnHalfLabelHeight: number = 4;\r\n export const defaultLabelDensity: string = \"50\";\r\n export const DefaultDy: string = '-0.15em';\r\n export const DefaultFontSizeInPt = 9;\r\n export const StandardFontFamily = Font.Family.regular.css;\r\n export const LabelTextProperties: TextProperties = {\r\n fontFamily: Font.Family.regularSecondary.css,\r\n fontSize: PixelConverter.fromPoint(DefaultFontSizeInPt),\r\n fontWeight: 'normal',\r\n };\r\n export const defaultLabelColor = \"#777777\";\r\n export const defaultInsideLabelColor = \"#ffffff\";\r\n export const hundredPercentFormat = \"0.00 %;-0.00 %;0.00 %\";\r\n \r\n export const defaultLabelPrecision: number = undefined;\r\n const defaultCountLabelPrecision: number = 0;\r\n\r\n const labelGraphicsContextClass: ClassAndSelector = createClassAndSelector('labels');\r\n const linesGraphicsContextClass: ClassAndSelector = createClassAndSelector('lines');\r\n const labelsClass: ClassAndSelector = createClassAndSelector('data-labels');\r\n const lineClass: ClassAndSelector = createClassAndSelector('line-label');\r\n\r\n export function updateLabelSettingsFromLabelsObject(labelsObj: DataLabelObject, labelSettings: VisualDataLabelsSettings): void {\r\n if (labelsObj) {\r\n if (labelsObj.show !== undefined)\r\n labelSettings.show = labelsObj.show;\r\n if (labelsObj.showSeries !== undefined)\r\n labelSettings.show = labelsObj.showSeries;\r\n if (labelsObj.color !== undefined)\r\n labelSettings.labelColor = labelsObj.color.solid.color;\r\n if (labelsObj.labelDisplayUnits !== undefined)\r\n labelSettings.displayUnits = labelsObj.labelDisplayUnits;\r\n if (labelsObj.labelPrecision !== undefined)\r\n labelSettings.precision = (labelsObj.labelPrecision >= 0) ? labelsObj.labelPrecision : defaultLabelPrecision;\r\n if (labelsObj.fontSize !== undefined)\r\n labelSettings.fontSize = labelsObj.fontSize;\r\n if (labelsObj.showAll !== undefined)\r\n labelSettings.showLabelPerSeries = labelsObj.showAll;\r\n if (labelsObj.labelStyle !== undefined)\r\n labelSettings.labelStyle = labelsObj.labelStyle;\r\n if(labelsObj.labelPosition) {\r\n labelSettings.position = labelsObj.labelPosition;\r\n }\r\n }\r\n }\r\n\r\n export function updateLineChartLabelSettingsFromLabelsObject(labelsObj: DataLabelObject, labelSettings: LineChartDataLabelsSettings): void {\r\n\r\n updateLabelSettingsFromLabelsObject(labelsObj, labelSettings);\r\n\r\n if (labelsObj && labelsObj.labelDensity !== undefined)\r\n labelSettings.labelDensity = labelsObj.labelDensity;\r\n }\r\n\r\n export function getDefaultLabelSettings(show: boolean = false, labelColor?: string, fontSize?: number): VisualDataLabelsSettings {\r\n return {\r\n show: show,\r\n position: PointLabelPosition.Above,\r\n displayUnits: 0,\r\n precision: defaultLabelPrecision,\r\n labelColor: labelColor || defaultLabelColor,\r\n fontSize: fontSize || DefaultFontSizeInPt,\r\n };\r\n }\r\n\r\n export function getDefaultCardLabelSettings(labelColor: string, categoryLabelColor: string, fontSize?: number): VisualDataLabelsSettings {\r\n let labelSettings = getDefaultLabelSettings(true, labelColor, fontSize);\r\n labelSettings.showCategory = true;\r\n labelSettings.categoryLabelColor = categoryLabelColor;\r\n return labelSettings;\r\n }\r\n\r\n export function getDefaultTreemapLabelSettings(): VisualDataLabelsSettings {\r\n return {\r\n show: false,\r\n displayUnits: 0,\r\n precision: defaultLabelPrecision,\r\n labelColor: defaultInsideLabelColor,\r\n showCategory: true,\r\n };\r\n }\r\n\r\n export function getDefaultSunburstLabelSettings(): VisualDataLabelsSettings {\r\n return {\r\n show: false, \r\n labelColor: defaultInsideLabelColor,\r\n fontSize: DefaultFontSizeInPt,\r\n displayUnits: 0,\r\n precision: defaultLabelPrecision,\r\n showCategory: true,\r\n };\r\n }\r\n\r\n export function getDefaultColumnLabelSettings(isLabelPositionInside: boolean): VisualDataLabelsSettings {\r\n let labelSettings = getDefaultLabelSettings(false, undefined);\r\n labelSettings.position = null;\r\n labelSettings.labelColor = undefined;\r\n return labelSettings;\r\n }\r\n\r\n export function getDefaultPointLabelSettings(): PointDataLabelsSettings {\r\n return {\r\n show: false,\r\n position: PointLabelPosition.Above,\r\n displayUnits: 0,\r\n precision: defaultLabelPrecision,\r\n labelColor: defaultLabelColor,\r\n fontSize: DefaultFontSizeInPt,\r\n };\r\n }\r\n\r\n export function getDefaultLineChartLabelSettings(isComboChart?: boolean): LineChartDataLabelsSettings {\r\n return {\r\n show: false,\r\n position: PointLabelPosition.Above,\r\n displayUnits: 0,\r\n precision: defaultLabelPrecision,\r\n labelColor: defaultLabelColor,\r\n fontSize: DefaultFontSizeInPt,\r\n labelDensity: defaultLabelDensity,\r\n };\r\n }\r\n\r\n export function getDefaultMapLabelSettings(): PointDataLabelsSettings {\r\n return {\r\n show: false,\r\n showCategory: false,\r\n position: PointLabelPosition.Above,\r\n displayUnits: 0,\r\n precision: defaultLabelPrecision,\r\n labelColor: defaultInsideLabelColor,\r\n fontSize: DefaultFontSizeInPt,\r\n };\r\n }\r\n\r\n export function getDefaultDonutLabelSettings(): VisualDataLabelsSettings {\r\n let labelSettings = dataLabelUtils.getDefaultLabelSettings(true, defaultLabelColor, DefaultFontSizeInPt);\r\n labelSettings.labelStyle = LabelStyle.category;\r\n return labelSettings;\r\n }\r\n\r\n export function getDefaultGaugeLabelSettings(): VisualDataLabelsSettings {\r\n return {\r\n show: true,\r\n displayUnits: 0,\r\n precision: defaultLabelPrecision,\r\n labelColor: null,\r\n position: null,\r\n fontSize: dataLabelUtils.minLabelFontSize,\r\n };\r\n }\r\n\r\n export function getDefaultKpiLabelSettings(): VisualDataLabelsSettings {\r\n return {\r\n show: false,\r\n displayUnits: 0,\r\n precision: defaultLabelPrecision,\r\n labelColor: defaultLabelColor,\r\n position: null,\r\n showCategory: true,\r\n };\r\n }\r\n\r\n export function getLabelPrecision(precision: number, format: string): number {\r\n debug.assertAnyValue(format, 'format');\r\n\r\n if (precision !== defaultLabelPrecision)\r\n return precision;\r\n\r\n if (format === 'g' || format === 'G')\r\n return;\r\n\r\n if (format) {\r\n // Calculate precision from positive format by default\r\n let positiveFormat = NumberFormat.getComponents(format).positive;\r\n let formatMetadata = NumberFormat.getCustomFormatMetadata(positiveFormat, true /*calculatePrecision*/);\r\n if (formatMetadata.hasDots) {\r\n return formatMetadata.precision;\r\n }\r\n }\r\n\r\n // For count fields we do not want a precision by default\r\n return defaultCountLabelPrecision;\r\n }\r\n\r\n export function drawDefaultLabelsForDataPointChart(data: any[], context: D3.Selection, layout: ILabelLayout,\r\n viewport: IViewport, isAnimator: boolean = false, animationDuration?: number, hasSelection?: boolean): D3.UpdateSelection {\r\n debug.assertValue(data, 'data cannot be null or undefined');\r\n\r\n // Hide and reposition labels that overlap\r\n let dataLabelManager = new DataLabelManager();\r\n let filteredData = dataLabelManager.hideCollidedLabels(viewport, data, layout);\r\n\r\n let hasAnimation = isAnimator && !!animationDuration;\r\n let labels = selectLabels(filteredData, context, false, hasAnimation);\r\n\r\n if (!labels)\r\n return;\r\n\r\n if (hasAnimation) {\r\n labels\r\n .text((d: LabelEnabledDataPoint) => d.labeltext)\r\n .transition()\r\n .duration(animationDuration)\r\n .style(layout.style)\r\n .style('opacity', hasSelection ? (d: SelectableDataPoint) => ColumnUtil.getFillOpacity(d.selected, false, hasSelection, false) : 1)\r\n .attr({ x: (d: LabelEnabledDataPoint) => d.labelX, y: (d: LabelEnabledDataPoint) => d.labelY });\r\n\r\n labels\r\n .exit()\r\n .transition()\r\n .duration(animationDuration)\r\n .style('opacity', 0) //fade out labels that are removed\r\n .remove();\r\n }\r\n else {\r\n labels\r\n .attr({ x: (d: LabelEnabledDataPoint) => d.labelX, y: (d: LabelEnabledDataPoint) => d.labelY })\r\n .text((d: LabelEnabledDataPoint) => d.labeltext)\r\n .style(layout.style);\r\n\r\n labels\r\n .exit()\r\n .remove();\r\n }\r\n\r\n return labels;\r\n }\r\n \r\n function selectLabels(filteredData: LabelEnabledDataPoint[], context: D3.Selection, isDonut: boolean = false, forAnimation: boolean = false): D3.UpdateSelection {\r\n\r\n // Check for a case where resizing leaves no labels - then we need to remove the labels 'g'\r\n if (filteredData.length === 0) {\r\n cleanDataLabels(context, true);\r\n return null;\r\n }\r\n\r\n if (context.select(labelGraphicsContextClass.selector).empty())\r\n context.append('g').classed(labelGraphicsContextClass.class, true);\r\n\r\n // line chart ViewModel has a special 'key' property for point identification since the 'identity' field is set to the series identity\r\n let hasKey: boolean = (<any>filteredData)[0].key != null;\r\n let hasDataPointIdentity: boolean = (<any>filteredData)[0].identity != null;\r\n let getIdentifier = hasKey ?\r\n (d: any) => d.key\r\n : hasDataPointIdentity ?\r\n (d: SelectableDataPoint) => d.identity.getKey()\r\n : undefined;\r\n\r\n let labels = isDonut ?\r\n context.select(labelGraphicsContextClass.selector).selectAll(labelsClass.selector).data(filteredData, (d: DonutArcDescriptor) => d.data.identity.getKey())\r\n : getIdentifier != null ?\r\n context.select(labelGraphicsContextClass.selector).selectAll(labelsClass.selector).data(filteredData, getIdentifier)\r\n : context.select(labelGraphicsContextClass.selector).selectAll(labelsClass.selector).data(filteredData);\r\n\r\n let newLabels = labels.enter()\r\n .append('text')\r\n .classed(labelsClass.class, true);\r\n if (forAnimation)\r\n newLabels.style('opacity', 0);\r\n\r\n return labels;\r\n }\r\n\r\n export function cleanDataLabels(context: D3.Selection, removeLines: boolean = false) {\r\n let empty = [];\r\n let labels = context.selectAll(labelsClass.selector).data(empty);\r\n labels.exit().remove();\r\n context.selectAll(labelGraphicsContextClass.selector).remove();\r\n if (removeLines) {\r\n let lines = context.selectAll(lineClass.selector).data(empty);\r\n lines.exit().remove();\r\n context.selectAll(linesGraphicsContextClass.selector).remove();\r\n }\r\n }\r\n\r\n export function setHighlightedLabelsOpacity(context: D3.Selection, hasSelection: boolean, hasHighlights: boolean) {\r\n context.selectAll(labelsClass.selector).style(\"fill-opacity\", (d: ColumnChartDataPoint) => {\r\n let labelOpacity = ColumnUtil.getFillOpacity(d.selected, d.highlight, !d.highlight && hasSelection, !d.selected && hasHighlights) < 1 ? 0 : 1;\r\n return labelOpacity;\r\n });\r\n }\r\n\r\n export function getLabelFormattedText(options: LabelFormattedTextOptions): string {\r\n let properties: TextProperties = {\r\n text: options.formatter\r\n ? options.formatter.format(options.label)\r\n : formattingService.formatValue(options.label, options.format),\r\n fontFamily: LabelTextProperties.fontFamily,\r\n fontSize: PixelConverter.fromPoint(options.fontSize),\r\n fontWeight: LabelTextProperties.fontWeight,\r\n };\r\n return TextMeasurementService.getTailoredTextOrDefault(properties, options.maxWidth ? options.maxWidth : maxLabelWidth);\r\n }\r\n\r\n export function getLabelLayoutXYForWaterfall(xAxisProperties: IAxisProperties, categoryWidth: number, yAxisProperties: IAxisProperties, dataDomain: number[]): LabelPosition {\r\n return {\r\n x: (d: WaterfallChartDataPoint) => xAxisProperties.scale(d.categoryIndex) + (categoryWidth / 2),\r\n y: (d: WaterfallChartDataPoint) => getWaterfallLabelYPosition(yAxisProperties.scale, d, dataDomain)\r\n };\r\n }\r\n\r\n function getWaterfallLabelYPosition(scale: D3.Scale.GenericScale<any>, d: WaterfallChartDataPoint, dataDomain: number[]): number {\r\n\r\n let yValue = scale(0) - scale(Math.abs(d.value));\r\n let yPos = scale(d.position);\r\n let scaleMinDomain = scale(dataDomain[0]);\r\n let endPosition = scale(d.position + d.value);\r\n\r\n if (d.value < 0) {\r\n let properties: TextProperties = {\r\n text: d.labeltext,\r\n fontFamily: dataLabelUtils.LabelTextProperties.fontFamily,\r\n fontSize: dataLabelUtils.LabelTextProperties.fontSize,\r\n fontWeight: dataLabelUtils.LabelTextProperties.fontWeight,\r\n };\r\n let outsideBelowPosition = yPos + yValue + TextMeasurementService.estimateSvgTextHeight(properties);\r\n // Try to honor the position, but if the label doesn't fit where specified, then swap the position.\r\n if (scaleMinDomain > outsideBelowPosition) {\r\n return outsideBelowPosition;\r\n }\r\n }\r\n else {\r\n let outsideAbovePosition = yPos - yValue - dataLabelUtils.labelMargin;\r\n // Try to honor the position, but if the label doesn't fit where specified, then swap the position.\r\n if (outsideAbovePosition > 0) {\r\n return outsideAbovePosition;\r\n }\r\n }\r\n d.isLabelInside = true;\r\n return getWaterfallInsideLabelYPosition(yPos, endPosition, scaleMinDomain);\r\n }\r\n\r\n function getWaterfallInsideLabelYPosition(startPosition: number, endPosition: number, scaleMinDomain: number): number {\r\n // Get the start and end position of the column\r\n // If the start or end is outside of the visual because of clipping - adjust the position\r\n startPosition = startPosition < 0 ? 0 : startPosition;\r\n startPosition = startPosition > scaleMinDomain ? scaleMinDomain : startPosition;\r\n\r\n endPosition = endPosition < 0 ? 0 : endPosition;\r\n endPosition = endPosition > scaleMinDomain ? scaleMinDomain : endPosition;\r\n\r\n return (Math.abs(endPosition - startPosition) / 2) + Math.min(startPosition, endPosition);\r\n }\r\n\r\n export function doesDataLabelFitInShape(d: WaterfallChartDataPoint, yAxisProperties: IAxisProperties, layout: WaterfallLayout): boolean {\r\n\r\n if (d == null || d.value === null)\r\n return false;\r\n\r\n let properties: TextProperties = {\r\n text: layout.labelText(d),\r\n fontFamily: dataLabelUtils.LabelTextProperties.fontFamily,\r\n fontSize: dataLabelUtils.LabelTextProperties.fontSize,\r\n fontWeight: dataLabelUtils.LabelTextProperties.fontWeight,\r\n };\r\n\r\n let outsidePosition = WaterfallChart.getRectTop(yAxisProperties.scale, d.position, d.value) - dataLabelUtils.labelMargin;\r\n\r\n // The shape is fit to be outside\r\n if (outsidePosition > 0)\r\n return true;\r\n\r\n let textWidth = TextMeasurementService.measureSvgTextWidth(properties);\r\n let textHeight = TextMeasurementService.estimateSvgTextHeight(properties);\r\n\r\n let shapeWidth = layout.categoryWidth;\r\n let shapeHeight = Math.abs(AxisHelper.diffScaled(yAxisProperties.scale, Math.max(0, Math.abs(d.value)), 0));\r\n\r\n //checking that labels aren't greater than shape\r\n if ((textWidth > shapeWidth) || (textHeight > shapeHeight))\r\n return false;\r\n return true;\r\n }\r\n\r\n export function getMapLabelLayout(labelSettings: PointDataLabelsSettings): ILabelLayout {\r\n\r\n return {\r\n labelText: (d: MapVisualDataPoint) => {\r\n return getLabelFormattedText({\r\n label: d.labeltext,\r\n fontSize: labelSettings.fontSize\r\n });\r\n },\r\n labelLayout: {\r\n x: (d: MapVisualDataPoint) => d.x,\r\n y: (d: MapVisualDataPoint) => {\r\n let margin = d.radius + labelMargin;\r\n return labelSettings.position === PointLabelPosition.Above ? d.y - margin : d.y + margin;\r\n },\r\n },\r\n filter: (d: MapVisualDataPoint) => {\r\n return (d != null && d.labeltext != null);\r\n },\r\n style: {\r\n 'fill': (d: MapVisualDataPoint) => d.labelFill,\r\n 'font-size': PixelConverter.fromPoint(labelSettings.fontSize),\r\n },\r\n };\r\n }\r\n\r\n export function getColumnChartLabelLayout(\r\n data: ColumnChartData,\r\n labelLayoutXY: any,\r\n isColumn: boolean,\r\n isHundredPercent: boolean,\r\n axisFormatter: IValueFormatter,\r\n axisOptions: ColumnAxisOptions,\r\n interactivityService: IInteractivityService,\r\n visualWidth?: number): ILabelLayout {\r\n\r\n let formatOverride: string = (isHundredPercent) ? hundredPercentFormat : null;\r\n let formattersCache = createColumnFormatterCacheManager();\r\n let hasSelection = interactivityService ? interactivityService.hasSelection() : false;\r\n\r\n return {\r\n labelText: (d: ColumnChartDataPoint) => {\r\n let formatString = (formatOverride != null) ? formatOverride : d.labelFormatString;\r\n let value2: number = getDisplayUnitValueFromAxisFormatter(axisFormatter, d.labelSettings);\r\n let formatter = formattersCache.getOrCreate(formatString, d.labelSettings, value2);\r\n return getLabelFormattedText({\r\n label: formatter.format(d.value),\r\n maxWidth: maxLabelWidth\r\n });\r\n },\r\n labelLayout: labelLayoutXY,\r\n filter: (d: ColumnChartDataPoint) => dataLabelUtils.getColumnChartLabelFilter(d, hasSelection, data.hasHighlights, axisOptions, visualWidth),\r\n style: {\r\n 'fill': (d: ColumnChartDataPoint) => d.labelFill,\r\n 'text-anchor': isColumn ? 'middle' : 'start',\r\n },\r\n };\r\n }\r\n \r\n /**\r\n * Valide for stacked column/bar chart and 100% stacked column/bar chart,\r\n * that labels that should to be inside the shape aren't bigger then shapes.\r\n */\r\n function validateLabelsSize(d: ColumnChartDataPoint, axisOptions: ColumnAxisOptions, visualWidth?: number): boolean {\r\n let xScale = axisOptions.xScale;\r\n let yScale = axisOptions.yScale;\r\n let columnWidth = axisOptions.columnWidth;\r\n let properties: TextProperties = {\r\n text: d.labeltext,\r\n fontFamily: dataLabelUtils.LabelTextProperties.fontFamily,\r\n fontSize: dataLabelUtils.LabelTextProperties.fontSize,\r\n fontWeight: dataLabelUtils.LabelTextProperties.fontWeight,\r\n };\r\n let textWidth = TextMeasurementService.measureSvgTextWidth(properties);\r\n let textHeight = TextMeasurementService.estimateSvgTextHeight(properties);\r\n let shapeWidth, shapeHeight;\r\n let inside = false;\r\n let outsidePosition: number = ColumnUtil.calculatePosition(d, axisOptions);\r\n switch (d.chartType) {\r\n case ColumnChartType.stackedBar:\r\n case ColumnChartType.hundredPercentStackedBar:\r\n // if the series isn't last or the label doesn't fit where specified, then it should be inside \r\n if (!d.lastSeries || (outsidePosition + textWidth > visualWidth) || d.chartType === ColumnChartType.hundredPercentStackedBar) {\r\n shapeWidth = -StackedUtil.getSize(xScale, d.valueAbsolute);\r\n shapeHeight = columnWidth;\r\n inside = true;\r\n }\r\n break;\r\n case ColumnChartType.clusteredBar:\r\n \r\n // if the label doesn't fit where specified, then it should be inside \r\n if ((outsidePosition + textWidth) > visualWidth) {\r\n shapeWidth = Math.abs(AxisHelper.diffScaled(xScale, 0, d.value));\r\n shapeHeight = columnWidth;\r\n inside = true;\r\n }\r\n break;\r\n case ColumnChartType.stackedColumn:\r\n case ColumnChartType.hundredPercentStackedColumn:\r\n // if the series isn't last or the label doesn't fit where specified, then it should be inside \r\n if (!d.lastSeries || outsidePosition <= 0 || d.chartType === ColumnChartType.hundredPercentStackedColumn) {\r\n shapeWidth = columnWidth;\r\n shapeHeight = StackedUtil.getSize(yScale, d.valueAbsolute);\r\n inside = true;\r\n }\r\n break;\r\n case ColumnChartType.clusteredColumn:\r\n // if the label doesn't fit where specified, then it should be inside \r\n if (outsidePosition <= 0) {\r\n shapeWidth = columnWidth;\r\n shapeHeight = Math.abs(AxisHelper.diffScaled(yScale, 0, d.value));\r\n inside = true;\r\n }\r\n break;\r\n default:\r\n return true;\r\n }\r\n\r\n //checking that labels aren't greater than shape\r\n if (inside && ((textWidth > shapeWidth) || textHeight > shapeHeight)) return false;\r\n return true;\r\n }\r\n\r\n export function getColumnChartLabelFilter(d: ColumnChartDataPoint, hasSelection: boolean, hasHighlights: boolean, axisOptions: ColumnAxisOptions, visualWidth?: number): any {\r\n //labels of dimmed are hidden\r\n let shapesOpacity = hasSelection ? ColumnUtil.getFillOpacity(d.selected, d.highlight, !d.highlight && hasSelection, !d.selected && hasHighlights) :\r\n ColumnUtil.getFillOpacity(d.selected, d.highlight, hasSelection, hasHighlights);\r\n return (d != null && d.value != null && validateLabelsSize(d, axisOptions, visualWidth) && shapesOpacity === 1);\r\n }\r\n\r\n export function getScatterChartLabelLayout(xScale: D3.Scale.GenericScale<any>, yScale: D3.Scale.GenericScale<any>, labelSettings: PointDataLabelsSettings, viewport: IViewport, sizeRange: NumberRange): ILabelLayout {\r\n\r\n return {\r\n labelText: (d: ScatterChartDataPoint) => {\r\n return getLabelFormattedText({\r\n label: d.formattedCategory.getValue(),\r\n maxWidth: maxLabelWidth * 2.0\r\n });\r\n },\r\n labelLayout: {\r\n x: (d: ScatterChartDataPoint) => xScale(d.x),\r\n y: (d: ScatterChartDataPoint) => {\r\n let margin = ScatterChart.getBubbleRadius(d.radius, sizeRange, viewport) + labelMargin;\r\n return labelSettings.position === PointLabelPosition.Above ? yScale(d.y) - margin : yScale(d.y) + margin;\r\n },\r\n },\r\n filter: (d: ScatterChartDataPoint) => {\r\n return (d != null && d.formattedCategory.getValue() != null);\r\n },\r\n style: {\r\n 'fill': (d: ScatterChartDataPoint) => d.labelFill,\r\n },\r\n };\r\n }\r\n\r\n export function getLineChartLabelLayout(xScale: D3.Scale.GenericScale<any>, yScale: D3.Scale.GenericScale<any>, labelSettings: PointDataLabelsSettings, isScalar: boolean, axisFormatter: IValueFormatter): ILabelLayout {\r\n let formattersCache = createColumnFormatterCacheManager();\r\n\r\n return {\r\n labelText: (d: LineChartDataPoint) => {\r\n let value2: number = getDisplayUnitValueFromAxisFormatter(axisFormatter, d.labelSettings);\r\n let formatter = formattersCache.getOrCreate(d.labelFormatString, d.labelSettings, value2);\r\n return getLabelFormattedText({ label: formatter.format(d.value) });\r\n },\r\n labelLayout: {\r\n x: (d: LineChartDataPoint) => xScale(isScalar ? d.categoryValue : d.categoryIndex),\r\n y: (d: LineChartDataPoint) => { return labelSettings.position === PointLabelPosition.Above ? yScale(d.value) - labelMargin : yScale(d.value) + labelMargin; },\r\n },\r\n filter: (d: LineChartDataPoint) => {\r\n return (d != null && d.value != null);\r\n },\r\n style: {\r\n 'fill': (d: LineChartDataPoint) => d.labelFill,\r\n 'font-size': (d: LineChartDataPoint) => PixelConverter.fromPoint(d.labelSettings.fontSize),\r\n },\r\n };\r\n }\r\n\r\n export function enumerateDataLabels(\r\n options: VisualDataLabelsSettingsOptions): ObjectEnumerationBuilder {\r\n\r\n debug.assertValue(options, 'options');\r\n debug.assertValue(options.enumeration, 'enumeration');\r\n\r\n if (!options.dataLabelsSettings)\r\n return;\r\n\r\n let instance: VisualObjectInstance = {\r\n objectName: 'labels',\r\n selector: options.selector,\r\n properties: {},\r\n };\r\n\r\n if (options.show && options.selector) {\r\n instance.properties['showSeries'] = options.dataLabelsSettings.show;\r\n }\r\n else if (options.show) {\r\n instance.properties['show'] = options.dataLabelsSettings.show;\r\n }\r\n\r\n instance.properties['color'] = options.dataLabelsSettings.labelColor || defaultLabelColor;\r\n\r\n if (options.displayUnits) {\r\n instance.properties['labelDisplayUnits'] = options.dataLabelsSettings.displayUnits;\r\n }\r\n if (options.precision) {\r\n let precision = options.dataLabelsSettings.precision;\r\n instance.properties['labelPrecision'] = precision === defaultLabelPrecision ? null : precision;\r\n }\r\n if (options.position) {\r\n instance.properties['labelPosition'] = options.dataLabelsSettings.position;\r\n if (options.positionObject) {\r\n debug.assert(!instance.validValues, '!instance.validValues');\r\n\r\n instance.validValues = { 'labelPosition': options.positionObject };\r\n }\r\n }\r\n if (options.labelStyle)\r\n instance.properties['labelStyle'] = options.dataLabelsSettings.labelStyle;\r\n if (options.fontSize)\r\n instance.properties['fontSize'] = options.dataLabelsSettings.fontSize;\r\n if (options.labelDensity) {\r\n var lineChartSettings = <LineChartDataLabelsSettings> options.dataLabelsSettings;\r\n if (lineChartSettings)\r\n instance.properties['labelDensity'] = lineChartSettings.labelDensity;\r\n }\r\n //Keep show all as the last property of the instance.\r\n if (options.showAll)\r\n instance.properties['showAll'] = options.dataLabelsSettings.showLabelPerSeries;\r\n\r\n return options.enumeration.pushInstance(instance);\r\n }\r\n\r\n export function enumerateCategoryLabels(enumeration: ObjectEnumerationBuilder, dataLabelsSettings: VisualDataLabelsSettings, withFill: boolean, isShowCategory: boolean = false, fontSize?: number): void {\r\n let labelSettings = (dataLabelsSettings)\r\n ? dataLabelsSettings\r\n : getDefaultPointLabelSettings();\r\n\r\n let instance: VisualObjectInstance = {\r\n objectName: 'categoryLabels',\r\n selector: null,\r\n properties: {\r\n show: isShowCategory\r\n ? labelSettings.showCategory\r\n : labelSettings.show,\r\n fontSize: dataLabelsSettings ? dataLabelsSettings.fontSize : DefaultFontSizeInPt,\r\n },\r\n };\r\n\r\n if (withFill) {\r\n instance.properties['color'] = labelSettings.categoryLabelColor\r\n ? labelSettings.categoryLabelColor\r\n : labelSettings.labelColor;\r\n }\r\n\r\n if (fontSize) {\r\n instance.properties['fontSize'] = fontSize;\r\n }\r\n\r\n enumeration.pushInstance(instance);\r\n }\r\n\r\n function getDisplayUnitValueFromAxisFormatter(axisFormatter: IValueFormatter, labelSettings: VisualDataLabelsSettings): number {\r\n if (axisFormatter && axisFormatter.displayUnit && labelSettings.displayUnits === 0)\r\n return axisFormatter.displayUnit.value;\r\n return null;\r\n }\r\n\r\n export function createColumnFormatterCacheManager(): IColumnFormatterCacheManager {\r\n return <IColumnFormatterCacheManager> {\r\n\r\n cache: { defaultFormatter: null, },\r\n getOrCreate(formatString: string, labelSetting: VisualDataLabelsSettings, value2?: number) {\r\n if (formatString) {\r\n let cacheKeyObject = {\r\n formatString: formatString,\r\n displayUnits: labelSetting.displayUnits,\r\n precision: getLabelPrecision(labelSetting.precision, formatString),\r\n value2: value2\r\n };\r\n let cacheKey = JSON.stringify(cacheKeyObject);\r\n if (!this.cache[cacheKey])\r\n this.cache[cacheKey] = valueFormatter.create(getOptionsForLabelFormatter(labelSetting, formatString, value2, cacheKeyObject.precision));\r\n return this.cache[cacheKey];\r\n }\r\n if (!this.cache.defaultFormatter) {\r\n this.cache.defaultFormatter = valueFormatter.create(getOptionsForLabelFormatter(labelSetting, formatString, value2, labelSetting.precision));\r\n }\r\n return this.cache.defaultFormatter;\r\n }\r\n };\r\n }\r\n\r\n export function getOptionsForLabelFormatter(labelSetting: VisualDataLabelsSettings, formatString: string, value2?: number, precision?: number): ValueFormatterOptions {\r\n return {\r\n displayUnitSystemType: DisplayUnitSystemType.DataLabels,\r\n format: formatString,\r\n precision: precision,\r\n value: labelSetting.displayUnits,\r\n value2: value2,\r\n allowFormatBeautification: true,\r\n };\r\n }\r\n\r\n export function isTextWidthOverflows(textWidth, maxTextWidth): boolean {\r\n return textWidth > maxTextWidth;\r\n };\r\n\r\n export function isTextHeightOverflows(textHeight, innerChordLength): boolean {\r\n return textHeight > innerChordLength;\r\n };\r\n }\r\n}\r\n\n\n\n/** WEBPACK FOOTER **\n ** ./src/Clients/Visuals/common/dataLabelUtils.ts\n **/","/*\r\n * Power BI Visualizations\r\n *\r\n * Copyright (c) Microsoft Corporation\r\n * All rights reserved. \r\n * MIT License\r\n *\r\n * Permission is hereby granted, free of charge, to any person obtaining a copy\r\n * of this software and associated documentation files (the \"\"Software\"\"), to deal\r\n * in the Software without restriction, including without limitation the rights\r\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\r\n * copies of the Software, and to permit persons to whom the Software is\r\n * furnished to do so, subject to the following conditions:\r\n * \r\n * The above copyright notice and this permission notice shall be included in \r\n * all copies or substantial portions of the Software.\r\n * \r\n * THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR \r\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, \r\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE \r\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER \r\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\r\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\r\n * THE SOFTWARE.\r\n */\r\n\r\n/// <reference path=\"../_references.ts\"/>\r\n\r\nmodule powerbi.visuals {\r\n import ISize = shapes.ISize;\r\n\r\n export module DonutLabelUtils {\r\n export const LineStrokeWidth: number = 1;\r\n export const DiagonalLineIndex: number = 0;\r\n export const HorizontalLineIndex: number = 1;\r\n\r\n export function getLabelLeaderLineForDonutChart(donutArcDescriptor: DonutArcDescriptor, donutProperties: DonutChartProperties,\r\n parentPoint: IPoint, sliceArc: number = 0): number[][] {\r\n let innerLinePointMultiplier = 2.05;\r\n let textPoint: number[];\r\n let midPoint: number[];\r\n let chartPoint: number[];\r\n // Label position has changed\r\n if (sliceArc) {\r\n let arc = sliceArc;\r\n let outerRadius = donutProperties.radius * donutProperties.outerArcRadiusRatio;\r\n let innerRadius = (donutProperties.radius / 2) * donutProperties.innerArcRadiusRatio;\r\n midPoint = [Math.cos(arc) * outerRadius, Math.sin(arc) * outerRadius];\r\n chartPoint = [Math.cos(arc) * innerRadius, Math.sin(arc) * innerRadius];\r\n }\r\n // Original label position\r\n else {\r\n midPoint = donutProperties.outerArc.centroid(donutArcDescriptor);\r\n chartPoint = donutProperties.arc.centroid(donutArcDescriptor);\r\n }\r\n let textPointX = parentPoint.x;\r\n let lineMargin = NewDataLabelUtils.maxLabelOffset / 2;\r\n textPointX += textPointX < 0 ? -lineMargin : lineMargin;\r\n textPoint = [textPointX, parentPoint.y];\r\n chartPoint[0] *= innerLinePointMultiplier;\r\n chartPoint[1] *= innerLinePointMultiplier;\r\n return [chartPoint, midPoint, textPoint];\r\n }\r\n\r\n /** We calculate the rectangles of the leader lines for collision detection \r\n *width: x2 - x1; height: y2 - y1 */\r\n export function getLabelLeaderLinesSizeForDonutChart(leaderLinePoints: number[][]): ISize[] {\r\n if (leaderLinePoints && leaderLinePoints.length > 2) {\r\n let diagonalLineSize: ISize = {\r\n width: Math.abs(leaderLinePoints[1][0] - leaderLinePoints[0][0]),\r\n height: Math.abs(leaderLinePoints[1][1] - leaderLinePoints[0][1]),\r\n };\r\n // For horizontal line we set 1 in the height\r\n let horizontalLineSize: ISize = {\r\n width: Math.abs(leaderLinePoints[2][0] - leaderLinePoints[1][0]),\r\n height: DonutLabelUtils.LineStrokeWidth,\r\n };\r\n return [diagonalLineSize, horizontalLineSize];\r\n }\r\n return null;\r\n }\r\n\r\n export function getXPositionForDonutLabel(textPointX: number): number {\r\n let margin = textPointX < 0 ? -NewDataLabelUtils.maxLabelOffset : NewDataLabelUtils.maxLabelOffset;\r\n return textPointX += margin;\r\n }\r\n\r\n export function getSpaceAvailableForDonutLabels(labelXPos: number, viewport: IViewport): number {\r\n return viewport.width / 2 - Math.abs(labelXPos) - NewDataLabelUtils.maxLabelOffset;\r\n }\r\n }\r\n}\n\n\n/** WEBPACK FOOTER **\n ** ./src/Clients/Visuals/common/donutLabelUtils.ts\n **/","/*\r\n * Power BI Visualizations\r\n *\r\n * Copyright (c) Microsoft Corporation\r\n * All rights reserved. \r\n * MIT License\r\n *\r\n * Permission is hereby granted, free of charge, to any person obtaining a copy\r\n * of this software and associated documentation files (the \"\"Software\"\"), to deal\r\n * in the Software without restriction, including without limitation the rights\r\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\r\n * copies of the Software, and to permit persons to whom the Software is\r\n * furnished to do so, subject to the following conditions:\r\n * \r\n * The above copyright notice and this permission notice shall be included in \r\n * all copies or substantial portions of the Software.\r\n * \r\n * THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR \r\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, \r\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE \r\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER \r\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\r\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\r\n * THE SOFTWARE.\r\n */\r\n\r\n/// <reference path=\"../_references.ts\"/>\r\n\r\nmodule powerbi.visuals {\r\n import ClassAndSelector = jsCommon.CssConstants.ClassAndSelector;\r\n import createClassAndSelector = jsCommon.CssConstants.createClassAndSelector;\r\n import PixelConverter = jsCommon.PixelConverter;\r\n import ISize = shapes.ISize;\r\n\r\n export module NewDataLabelUtils {\r\n export const DefaultLabelFontSizeInPt = 9;\r\n export const MapPolylineOpacity = 0.5;\r\n export const LabelDensityBufferFactor = 3;\r\n export const LabelDensityPadding = 6;\r\n export let startingLabelOffset = 8;\r\n export let maxLabelOffset = 8;\r\n export let maxLabelWidth: number = 50;\r\n export let hundredPercentFormat = '0.00 %;-0.00 %;0.00 %';\r\n export let LabelTextProperties: TextProperties = {\r\n fontFamily: Font.Family.regularSecondary.css,\r\n fontSize: PixelConverter.fromPoint(DefaultLabelFontSizeInPt),\r\n fontWeight: 'normal',\r\n };\r\n export let defaultLabelColor = \"#777777\";\r\n export let defaultInsideLabelColor = \"#ffffff\"; //white\r\n export const horizontalLabelBackgroundPadding = 4;\r\n export const verticalLabelBackgroundPadding = 2;\r\n \r\n let labelBackgroundRounding = 4;\r\n let defaultLabelPrecision: number = undefined;\r\n let defaultCountLabelPrecision: number = 0;\r\n\r\n export let labelGraphicsContextClass: ClassAndSelector = createClassAndSelector('labelGraphicsContext');\r\n export let labelBackgroundGraphicsContextClass: ClassAndSelector = createClassAndSelector('labelBackgroundGraphicsContext');\r\n let labelsClass: ClassAndSelector = createClassAndSelector('label');\r\n let secondLineLabelClass: ClassAndSelector = createClassAndSelector('label-second-line');\r\n\r\n const linesGraphicsContextClass: ClassAndSelector = createClassAndSelector('leader-lines');\r\n const lineClass: ClassAndSelector = createClassAndSelector('line-label');\r\n\r\n export function drawDefaultLabels(context: D3.Selection, dataLabels: Label[], numeric: boolean = false, twoRows: boolean = false, hasTooltip: boolean = false): D3.UpdateSelection {\r\n let filteredDataLabels = _.filter(dataLabels, (d: Label) => d.isVisible);\r\n let labels = context.selectAll(labelsClass.selector)\r\n .data(filteredDataLabels, labelKeyFunction);\r\n labels.enter()\r\n .append(\"text\")\r\n .classed(labelsClass.class, true);\r\n let labelAttr = {\r\n x: (d: Label) => {\r\n return (d.boundingBox.left + (d.boundingBox.width / 2));\r\n },\r\n y: (d: Label) => {\r\n if (d.hasBackground)\r\n return d.boundingBox.top + d.boundingBox.height - verticalLabelBackgroundPadding;\r\n else\r\n return d.boundingBox.top + d.boundingBox.height;\r\n },\r\n dy: \"-0.15em\",\r\n };\r\n if (numeric) { // For numeric labels, we use a tighter bounding box, so remove the dy because it doesn't need to be centered\r\n labelAttr.dy = undefined;\r\n }\r\n\r\n labels\r\n .interrupt()\r\n .text((d: Label) => d.text)\r\n .attr(labelAttr)\r\n .style({\r\n 'fill': (d: Label) => d.fill,\r\n 'font-size': (d: Label) => PixelConverter.fromPoint(d.fontSize || DefaultLabelFontSizeInPt),\r\n 'text-anchor': (d: Label) => d.textAnchor,\r\n });\r\n labels.exit()\r\n .remove();\r\n\r\n let filteredCategoryLabels = _.filter(twoRows ? dataLabels : [], (d: Label) => d.isVisible && !_.isEmpty(d.secondRowText));\r\n let secondLineLabels = context.selectAll(secondLineLabelClass.selector)\r\n .data(filteredCategoryLabels, (d: Label, index: number) => { return d.identity ? d.identity.getKeyWithoutHighlight() : index; });\r\n secondLineLabels.enter()\r\n .append(\"text\")\r\n .classed(secondLineLabelClass.class, true);\r\n\r\n labelAttr = {\r\n x: (d: Label) => {\r\n return (d.boundingBox.left + (d.boundingBox.width / 2));\r\n },\r\n y: (d: Label) => {\r\n let boundingBoxHeight = (d.text !== undefined) ? d.boundingBox.height / 2 : d.boundingBox.height;\r\n if (d.hasBackground)\r\n return d.boundingBox.top + boundingBoxHeight - verticalLabelBackgroundPadding;\r\n else\r\n return d.boundingBox.top + boundingBoxHeight;\r\n },\r\n dy: \"-0.15em\",\r\n };\r\n if (numeric) { // For numeric labels, we use a tighter bounding box, so remove the dy because it doesn't need to be centered\r\n labelAttr.dy = undefined;\r\n }\r\n secondLineLabels\r\n .interrupt()\r\n .text((d: Label) => d.secondRowText)\r\n .attr(labelAttr)\r\n .style({\r\n 'fill': (d: Label) => d.fill,\r\n 'font-size': (d: Label) => PixelConverter.fromPoint(d.fontSize || DefaultLabelFontSizeInPt),\r\n 'text-anchor': (d: Label) => d.textAnchor,\r\n });\r\n\r\n secondLineLabels.exit()\r\n .remove();\r\n\r\n if (hasTooltip) {\r\n labels.append('title').text((d: Label) => d.tooltip);\r\n secondLineLabels.append('title').text((d: Label) => d.tooltip);\r\n labels.style(\"pointer-events\", \"all\");\r\n secondLineLabels.style(\"pointer-events\", \"all\");\r\n }\r\n\r\n return labels;\r\n }\r\n\r\n export function animateDefaultLabels(context: D3.Selection, dataLabels: Label[], duration: number, numeric: boolean = false, easeType: string = 'cubic-in-out'): D3.UpdateSelection {\r\n let labels = context.selectAll(labelsClass.selector)\r\n .data(_.filter(dataLabels, (d: Label) => d.isVisible), labelKeyFunction);\r\n\r\n labels.enter()\r\n .append(\"text\")\r\n .classed(labelsClass.class, true)\r\n .style('opacity', 0);\r\n\r\n let labelAttr = {\r\n x: (d: Label) => {\r\n return (d.boundingBox.left + (d.boundingBox.width / 2));\r\n },\r\n y: (d: Label) => {\r\n return d.boundingBox.top + d.boundingBox.height;\r\n },\r\n dy: \"-0.15em\",\r\n };\r\n if (numeric) { // For numeric labels, we use a tighter bounding box, so remove the dy because it doesn't need to be centered\r\n labelAttr.dy = undefined;\r\n }\r\n\r\n labels.text((d: Label) => d.text)\r\n .style({\r\n 'fill': (d: Label) => d.fill,\r\n 'font-size': (d: Label) => PixelConverter.fromPoint(d.fontSize || DefaultLabelFontSizeInPt),\r\n })\r\n .transition()\r\n .ease(easeType)\r\n .duration(duration)\r\n .attr(labelAttr)\r\n .style('opacity', 1);\r\n\r\n labels.exit()\r\n .transition()\r\n .duration(duration)\r\n .style('opacity', 0)\r\n .remove();\r\n\r\n return labels;\r\n }\r\n\r\n /** Draws black rectangles based on the bounding bx of labels, to be used in debugging */\r\n export function drawLabelBackground(context: D3.Selection, dataLabels: Label[], fill?: string, fillOpacity?: number): D3.UpdateSelection {\r\n let labelRects = context.selectAll(\"rect\")\r\n .data(_.filter(dataLabels, (d: Label) => d.isVisible), labelKeyFunction);\r\n\r\n labelRects.enter()\r\n .append(\"rect\");\r\n\r\n labelRects\r\n .attr({\r\n x: (d: Label) => {\r\n return d.boundingBox.left - horizontalLabelBackgroundPadding;\r\n },\r\n y: (d: Label) => {\r\n return d.boundingBox.top - verticalLabelBackgroundPadding;\r\n },\r\n rx: labelBackgroundRounding,\r\n ry: labelBackgroundRounding,\r\n width: (d: Label) => {\r\n return d.boundingBox.width + 2 * horizontalLabelBackgroundPadding;\r\n },\r\n height: (d: Label) => {\r\n if (d.text === undefined && d.secondRowText === undefined) {\r\n return 0;\r\n }\r\n return d.boundingBox.height + 2 * verticalLabelBackgroundPadding;\r\n },\r\n })\r\n .style(\"fill\", fill ? fill : \"#000000\")\r\n .style(\"fill-opacity\", fillOpacity != null ? fillOpacity : 1);\r\n\r\n labelRects.exit()\r\n .remove();\r\n\r\n return labelRects;\r\n }\r\n\r\n export function drawLabelLeaderLines(context: D3.Selection, filteredDataLabels: Label[], key?: (data: any, index?: number) => any, leaderLineColor?: string) {\r\n if (context.select(linesGraphicsContextClass.selector).empty())\r\n context.append('g').classed(linesGraphicsContextClass.class, true);\r\n\r\n let lines = context.select(linesGraphicsContextClass.selector).selectAll('polyline')\r\n .data(filteredDataLabels, key);\r\n\r\n lines.enter()\r\n .append('polyline')\r\n .classed(lineClass.class, true);\r\n\r\n lines\r\n .attr('points', (d: Label) => {\r\n return d.leaderLinePoints;\r\n }).\r\n style({\r\n 'stroke': (d: Label) => leaderLineColor ? leaderLineColor : d.fill,\r\n 'stroke-width': DonutLabelUtils.LineStrokeWidth,\r\n });\r\n\r\n lines\r\n .exit()\r\n .remove();\r\n }\r\n\r\n export function getLabelFormattedText(label: string | number, format?: string, formatter?: IValueFormatter): string {\r\n return formatter ? formatter.format(label) : formattingService.formatValue(label, format);\r\n }\r\n\r\n export function getDisplayUnitValueFromAxisFormatter(axisFormatter: IValueFormatter, labelSettings: VisualDataLabelsSettings): number {\r\n if (axisFormatter && axisFormatter.displayUnit && labelSettings.displayUnits === 0)\r\n return axisFormatter.displayUnit.value;\r\n return null;\r\n }\r\n\r\n function getLabelPrecision(precision: number, format: string): number {\r\n debug.assertAnyValue(format, 'format');\r\n\r\n if (precision !== defaultLabelPrecision)\r\n return precision;\r\n\r\n if (format) {\r\n // Calculate precision from positive format by default\r\n let positiveFormat = format.split(\";\")[0];\r\n let formatMetadata = NumberFormat.getCustomFormatMetadata(positiveFormat, true /*calculatePrecision*/);\r\n if (formatMetadata.hasDots) {\r\n return formatMetadata.precision;\r\n }\r\n }\r\n // For count fields we do not want a precision by default\r\n return defaultCountLabelPrecision;\r\n }\r\n\r\n export function createColumnFormatterCacheManager(): IColumnFormatterCacheManager {\r\n return <IColumnFormatterCacheManager> {\r\n\r\n cache: { defaultFormatter: null, },\r\n getOrCreate(formatString: string, labelSetting: VisualDataLabelsSettings, value2?: number) {\r\n if (formatString) {\r\n let cacheKeyObject = {\r\n formatString: formatString,\r\n displayUnits: labelSetting.displayUnits,\r\n precision: getLabelPrecision(labelSetting.precision, formatString),\r\n value2: value2\r\n };\r\n let cacheKey = JSON.stringify(cacheKeyObject);\r\n if (!this.cache[cacheKey])\r\n this.cache[cacheKey] = valueFormatter.create(getOptionsForLabelFormatter(labelSetting, formatString, value2, cacheKeyObject.precision));\r\n return this.cache[cacheKey];\r\n }\r\n if (!this.cache.defaultFormatter) {\r\n this.cache.defaultFormatter = valueFormatter.create(getOptionsForLabelFormatter(labelSetting, formatString, value2, labelSetting.precision));\r\n }\r\n return this.cache.defaultFormatter;\r\n }\r\n };\r\n }\r\n\r\n function getOptionsForLabelFormatter(labelSetting: VisualDataLabelsSettings, formatString: string, value2?: number, precision?: number): ValueFormatterOptions {\r\n return {\r\n displayUnitSystemType: DisplayUnitSystemType.DataLabels,\r\n format: formatString,\r\n precision: precision,\r\n value: labelSetting.displayUnits,\r\n value2: value2,\r\n allowFormatBeautification: true,\r\n };\r\n }\r\n\r\n export function removeDuplicates(labelDataPoints: LabelDataPoint[]): LabelDataPoint[] {\r\n let uniqueLabelDataPoints: LabelDataPoint[] = [];\r\n let labelDataPointMap = {};\r\n let sameParentIsInArray = (newValue: any, array: any[], parentIsRect: boolean) => {\r\n return array.some((arrayValue) => {\r\n if (parentIsRect) {\r\n return shapes.Rect.equals(newValue.parentShape.rect, arrayValue.rect);\r\n }\r\n else {\r\n return shapes.Point.equals(newValue.parentShape.point, arrayValue.point);\r\n }\r\n });\r\n };\r\n for (let dataPoint of labelDataPoints) {\r\n let parentIsRect = dataPoint.parentType === LabelDataPointParentType.Rectangle;\r\n let resultsFromMap = labelDataPointMap[dataPoint.text];\r\n if (!resultsFromMap) {\r\n uniqueLabelDataPoints.push(dataPoint);\r\n labelDataPointMap[dataPoint.text] = [dataPoint.parentShape];\r\n }\r\n else {\r\n if (!sameParentIsInArray(dataPoint, resultsFromMap, parentIsRect)) {\r\n uniqueLabelDataPoints.push(dataPoint);\r\n resultsFromMap.push(dataPoint.parentShape);\r\n }\r\n }\r\n }\r\n return uniqueLabelDataPoints;\r\n }\r\n \r\n export function getDataLabelLayoutOptions(type: CartesianChartType): DataLabelLayoutOptions {\r\n switch (type) {\r\n case CartesianChartType.Scatter:\r\n return {\r\n maximumOffset: ScatterChart.dataLabelLayoutMaximumOffset,\r\n startingOffset: ScatterChart.dataLabelLayoutStartingOffset,\r\n offsetIterationDelta: ScatterChart.dataLabelLayoutOffsetIterationDelta,\r\n allowLeaderLines: true,\r\n attemptToMoveLabelsIntoViewport: true,\r\n };\r\n default:\r\n return {\r\n maximumOffset: NewDataLabelUtils.maxLabelOffset,\r\n startingOffset: NewDataLabelUtils.startingLabelOffset,\r\n attemptToMoveLabelsIntoViewport: true,\r\n };\r\n }\r\n }\r\n\r\n export function getTextSize(text: string, fontSize: number): ISize {\r\n let labelTextProperties = NewDataLabelUtils.LabelTextProperties;\r\n let properties = {\r\n text: text,\r\n fontFamily: labelTextProperties.fontFamily,\r\n fontSize: jsCommon.PixelConverter.fromPoint(fontSize),\r\n fontWeight: labelTextProperties.fontWeight,\r\n };\r\n return {\r\n width: TextMeasurementService.measureSvgTextWidth(properties),\r\n height: TextMeasurementService.estimateSvgTextHeight(properties),\r\n };\r\n }\r\n\r\n /**\r\n * Obtains the key from the label. Index is required to use as a backup in cases\r\n * where labels have no key or identity.\r\n */\r\n function labelKeyFunction(label: Label, index: number): any {\r\n if (label.key) {\r\n return label.key;\r\n }\r\n if (label.identity) {\r\n return label.identity.getKeyWithoutHighlight();\r\n }\r\n return index;\r\n }\r\n }\r\n}\r\n\n\n\n/** WEBPACK FOOTER **\n ** ./src/Clients/Visuals/common/newDataLabelUtils.ts\n **/","/*\r\n * Power BI Visualizations\r\n *\r\n * Copyright (c) Microsoft Corporation\r\n * All rights reserved. \r\n * MIT License\r\n *\r\n * Permission is hereby granted, free of charge, to any person obtaining a copy\r\n * of this software and associated documentation files (the \"\"Software\"\"), to deal\r\n * in the Software without restriction, including without limitation the rights\r\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\r\n * copies of the Software, and to permit persons to whom the Software is\r\n * furnished to do so, subject to the following conditions:\r\n * \r\n * The above copyright notice and this permission notice shall be included in \r\n * all copies or substantial portions of the Software.\r\n * \r\n * THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR \r\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, \r\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE \r\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER \r\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\r\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\r\n * THE SOFTWARE.\r\n */\r\n\r\n/// <reference path=\"../_references.ts\"/>\r\n\r\nmodule powerbi.visuals {\r\n\r\n export module KpiUtil {\r\n\r\n export const enum KpiImageSize {\r\n Small,\r\n Big,\r\n }\r\n\r\n export interface KpiImageMetadata {\r\n statusGraphic: string;\r\n caption: string;\r\n class: string;\r\n }\r\n\r\n export interface KPIGraphicClass {\r\n kpiIconClass: string;\r\n statusValues: string[];\r\n }\r\n\r\n const KPIImageClassName = 'powervisuals-glyph';\r\n const BigImageClassName = 'big-kpi';\r\n const RYGStatusIconClassNames = ['kpi-red', 'kpi-yellow', 'kpi-green'];\r\n\r\n const threeLights = {\r\n kpiIconClass: 'circle',\r\n statusValues: RYGStatusIconClassNames,\r\n };\r\n\r\n const roadSigns = {\r\n kpiIconClass: '',\r\n statusValues: ['circle-x kpi-red', 'circle-exclamation kpi-yellow', 'circle-checkmark kpi-green'],\r\n };\r\n\r\n const trafficLight = {\r\n kpiIconClass: 'traffic-light',\r\n statusValues: RYGStatusIconClassNames,\r\n };\r\n\r\n const shapes = {\r\n kpiIconClass: '',\r\n statusValues: ['rhombus kpi-red', 'triangle kpi-yellow', 'circle kpi-green'],\r\n };\r\n\r\n const gauge = {\r\n kpiIconClass: '',\r\n statusValues: ['circle-empty', 'circle-one-quarter', 'circle-half', 'circle-three-quarters', 'circle-full'],\r\n };\r\n\r\n const statusGraphicFormatStrings = {\r\n 'THREE CIRCLES COLORED': threeLights,\r\n 'TRAFFIC LIGHT - SINGLE': threeLights,\r\n\r\n 'THREE FLAGS COLORED': {\r\n kpiIconClass: 'flag',\r\n statusValues: RYGStatusIconClassNames,\r\n },\r\n\r\n 'ROAD SIGNS': roadSigns,\r\n 'THREE SYMBOLS CIRCLED COLORED': roadSigns,\r\n\r\n 'TRAFFIC LIGHT': trafficLight,\r\n 'THREE TRAFFIC LIGHTS RIMMED COLORED': trafficLight,\r\n\r\n 'THREE SYMBOLS UNCIRCLED COLORED': {\r\n kpiIconClass: '',\r\n statusValues: ['x kpi-red', 'exclamation kpi-yellow', 'checkmark kpi-green'],\r\n },\r\n\r\n 'SHAPES': shapes,\r\n 'SMILEY FACE': shapes,\r\n 'THERMOMETER': shapes,\r\n 'CYLINDER': shapes,\r\n 'THREE SIGNS COLORED': shapes,\r\n\r\n 'THREE STARS COLORED': {\r\n kpiIconClass: 'star-stacked',\r\n statusValues: ['star-empty', 'star-half-full', 'star-full'],\r\n },\r\n 'FIVE BARS COLORED': {\r\n kpiIconClass: 'bars-stacked',\r\n statusValues: ['bars-zero', 'bars-one', 'bars-two', 'bars-three', 'bars-four'],\r\n },\r\n 'FIVE BOXES COLORED': {\r\n kpiIconClass: 'boxes-stacked',\r\n statusValues: ['boxes-zero', 'boxes-one', 'boxes-two', 'boxes-three', 'boxes-four'],\r\n },\r\n\r\n 'FIVE QUARTERS COLORED': gauge,\r\n 'GAUGE - ASCENDING': gauge,\r\n 'GAUGE - DESCENDING': {\r\n kpiIconClass: '',\r\n statusValues: ['circle-full', 'circle-three-quarters', 'circle-half', 'circle-one-quarter', 'circle-empty'],\r\n },\r\n\r\n 'STANDARD ARROW': {\r\n kpiIconClass: '',\r\n statusValues: ['arrow-down', 'arrow-right-down', 'arrow-right', 'arrow-right-up', 'arrow-up'],\r\n },\r\n\r\n 'VARIANCE ARROW': {\r\n kpiIconClass: '',\r\n statusValues: ['arrow-down kpi-red', 'arrow-right kpi-yellow', 'arrow-up kpi-green'],\r\n },\r\n\r\n 'STATUS ARROW - ASCENDING': {\r\n kpiIconClass: '',\r\n statusValues: ['arrow-down kpi-red', 'arrow-right-down kpi-yellow', 'arrow-right kpi-yellow', 'arrow-right-up kpi-yellow', 'arrow-up kpi-green'],\r\n },\r\n 'STATUS ARROW - DESCENDING': {\r\n kpiIconClass: '',\r\n statusValues: ['arrow-up kpi-green', 'arrow-right-up kpi-yellow', 'arrow-right kpi-yellow', 'arrow-right-down kpi-yellow', 'arrow-down kpi-red'],\r\n },\r\n };\r\n\r\n function getKpiIcon(kpi: DataViewKpiColumnMetadata, value: string): string {\r\n let numValue = parseFloat(value);\r\n\r\n if (!kpi)\r\n return;\r\n\r\n let statusGraphicFormat = statusGraphicFormatStrings[kpi.graphic.toUpperCase()];\r\n\r\n if (!statusGraphicFormat || isNaN(numValue))\r\n return undefined;\r\n\r\n let statusValues = statusGraphicFormat.statusValues;\r\n\r\n // Normalize range of (-1, -0.5, 0, 0.5, 1) to (-2, -1, 0, 1, 2)\r\n if (kpi.normalizedFiveStateKpiRange && statusValues.length === 5)\r\n numValue = numValue * 2;\r\n\r\n // Convert values from the range of (-n/2, ..., 0, ..., n/2) to (0, 1, ..., n-1)\r\n let num = numValue + Math.floor(statusValues.length / 2);\r\n\r\n return [statusGraphicFormat.kpiIconClass, statusValues[num]].join(' ').trim();\r\n }\r\n\r\n function getKpiIconClassName(kpiIcon: string, kpiImageSize?: KpiImageSize): string {\r\n if (!kpiIcon)\r\n return undefined;\r\n\r\n if (kpiImageSize === KpiImageSize.Big)\r\n return [KPIImageClassName, BigImageClassName, kpiIcon].join(' ');\r\n else\r\n return [KPIImageClassName, kpiIcon].join(' ');\r\n }\r\n\r\n export function getClassForKpi(kpi: DataViewKpiColumnMetadata, value: string, kpiImageSize?: KpiImageSize): string {\r\n debug.assertValue(kpi, 'kpi');\r\n debug.assertValue(value, 'value');\r\n\r\n let kpiIcon: string = getKpiIcon(kpi, value);\r\n return getKpiIconClassName(kpiIcon, kpiImageSize);\r\n }\r\n\r\n export function getKpiImageMetadata(metaDataColumn: DataViewMetadataColumn, value: string, kpiImageSize?: KpiImageSize): KpiImageMetadata {\r\n let kpi: DataViewKpiColumnMetadata = metaDataColumn && metaDataColumn.kpi;\r\n\r\n if (kpi) {\r\n let kpiIcon = getKpiIcon(kpi, value);\r\n if (kpiIcon) {\r\n return {\r\n caption: kpiIcon,\r\n statusGraphic: kpi.graphic,\r\n class: getKpiIconClassName(kpiIcon, kpiImageSize),\r\n };\r\n }\r\n }\r\n }\r\n }\r\n}\n\n\n/** WEBPACK FOOTER **\n ** ./src/Clients/Visuals/common/kpiUtil.ts\n **/","/*\r\n * Power BI Visualizations\r\n *\r\n * Copyright (c) Microsoft Corporation\r\n * All rights reserved. \r\n * MIT License\r\n *\r\n * Permission is hereby granted, free of charge, to any person obtaining a copy\r\n * of this software and associated documentation files (the \"\"Software\"\"), to deal\r\n * in the Software without restriction, including without limitation the rights\r\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\r\n * copies of the Software, and to permit persons to whom the Software is\r\n * furnished to do so, subject to the following conditions:\r\n * \r\n * The above copyright notice and this permission notice shall be included in \r\n * all copies or substantial portions of the Software.\r\n * \r\n * THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR \r\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, \r\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE \r\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER \r\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\r\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\r\n * THE SOFTWARE.\r\n */\r\n\r\n/// <reference path=\"../_references.ts\"/>\r\n\r\nmodule powerbi.visuals {\r\n export module DateUtil {\r\n export function isEqual(date1: Date, date2: Date): boolean {\r\n if (date1 == null && date2 == null) {\r\n return true;\r\n }\r\n else if (date1 == null || date2 == null) {\r\n return false;\r\n }\r\n\r\n return date1.getTime() === date2.getTime();\r\n }\r\n }\r\n}\n\n\n/** WEBPACK FOOTER **\n ** ./src/Clients/Visuals/common/dateUtil.ts\n **/","/*\r\n * Power BI Visualizations\r\n *\r\n * Copyright (c) Microsoft Corporation\r\n * All rights reserved.\r\n * MIT License\r\n *\r\n * Permission is hereby granted, free of charge, to any person obtaining a copy\r\n * of this software and associated documentation files (the \"\"Software\"\"), to deal\r\n * in the Software without restriction, including without limitation the rights\r\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\r\n * copies of the Software, and to permit persons to whom the Software is\r\n * furnished to do so, subject to the following conditions:\r\n *\r\n * The above copyright notice and this permission notice shall be included in\r\n * all copies or substantial portions of the Software.\r\n *\r\n * THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\r\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\r\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\r\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\r\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\r\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\r\n * THE SOFTWARE.\r\n */\r\n\r\n/// <reference path=\"../_references.ts\"/>\r\n\r\nmodule powerbi.visuals {\r\n export interface MinMaxLabelDataPointSorterOptions {\r\n unsortedLabelDataPointGroups: LabelDataPointGroup[];\r\n series: CartesianSeries[];\r\n yAxisProperties: IAxisProperties;\r\n viewport: IViewport;\r\n }\r\n\r\n interface WeightCalculationResult {\r\n weightedLabelDataPoints: WeightedLabelDataPoint[];\r\n minIndex: number;\r\n maxIndex: number;\r\n }\r\n\r\n interface WeightedLabelDataPoint extends LabelDataPoint {\r\n weight?: number; // A weighting used for sorting mins and maxes (details in the weight calculation function)\r\n }\r\n\r\n /**\r\n * Interface used to track non-min/max groups of labels so that they can be\r\n * sliced up without allocating many extra arrays\r\n */\r\n interface NonMinMaxSet {\r\n startingIndex: number;\r\n count: number;\r\n }\r\n\r\n enum MinMaxType {\r\n Minimum,\r\n Maximum,\r\n Neither,\r\n }\r\n\r\n interface MinMaxInRange {\r\n minIndex: number;\r\n minValue: number;\r\n maxIndex: number;\r\n maxValue: number;\r\n }\r\n\r\n interface MinMaxPoint {\r\n index: number;\r\n type: MinMaxType;\r\n value: number;\r\n weight?: number;\r\n }\r\n\r\n export class MinMaxLabelDataPointSorter {\r\n private unsortedLabelDataPointGroups: LabelDataPointGroup[];\r\n private series: CartesianSeries[];\r\n private yScale: D3.Scale.GenericScale<any>;\r\n private viewport: IViewport;\r\n \r\n /** A rough estimate for how wide labels are for purposes of calculating density, window size, etc. */\r\n public static estimatedLabelWidth = 40;\r\n\r\n private static minimumWeightToConsiderMinMax = 0.015;\r\n private static maxNumberToSortFactor = 2; // Once we've sorted first/last/min/max and local max/mins, we limit ourselves to twice the maximum number of labels to render\r\n\r\n constructor(options: MinMaxLabelDataPointSorterOptions) {\r\n this.unsortedLabelDataPointGroups = options.unsortedLabelDataPointGroups;\r\n this.series = options.series;\r\n this.yScale = options.yAxisProperties.scale;\r\n this.viewport = options.viewport;\r\n }\r\n\r\n public getSortedDataLabels(): LabelDataPointGroup[] {\r\n let unsortedLabelDataPointGroups = this.unsortedLabelDataPointGroups;\r\n let sortedLabelDataPointGroups: LabelDataPointGroup[] = [];\r\n\r\n for (let seriesIndex = 0, seriesCount = unsortedLabelDataPointGroups.length; seriesIndex < seriesCount; seriesIndex++) {\r\n let unsortedLabelDataPointGroup = unsortedLabelDataPointGroups[seriesIndex];\r\n let numberOfLabelsToSort = MinMaxLabelDataPointSorter.maxNumberToSortFactor * unsortedLabelDataPointGroup.maxNumberOfLabels;\r\n if (_.isEmpty(unsortedLabelDataPointGroup.labelDataPoints))\r\n continue;\r\n let unsortedLabelDataPoints: LabelDataPoint[] = unsortedLabelDataPointGroup.labelDataPoints;\r\n let sortedLabelDataPoints: WeightedLabelDataPoint[] = [];\r\n let data = _.filter(this.series[seriesIndex].data, (dataPoint: CartesianDataPoint) => dataPoint.value != null);\r\n \r\n // Iterate over the data points to find the min and max index and values\r\n let globalMinMaxInRange = MinMaxLabelDataPointSorter.getMinMaxInRange(0, data.length - 1, data);\r\n\r\n // Add first, last, max, and min\r\n let numberOfLabelsAdded = this.addFirstLastMaxMin(unsortedLabelDataPoints, sortedLabelDataPoints, globalMinMaxInRange.maxIndex, globalMinMaxInRange.minIndex);\r\n\r\n let unsortedWeightedLabelDataPoints: WeightedLabelDataPoint[];\r\n // If we have enough labels added, don't bother adding local min/maxes\r\n if (!(numberOfLabelsAdded >= numberOfLabelsToSort)) {\r\n // Prepare the data to calculate weights and find global min/max\r\n unsortedWeightedLabelDataPoints = this.calculateWeights(unsortedLabelDataPoints, data, numberOfLabelsToSort, globalMinMaxInRange);\r\n // Add all mins and maxes, sorted by weight (skipping first/last/min/max)\r\n let maximumnMinMaxesToAdd = Math.max(numberOfLabelsToSort - numberOfLabelsAdded, 0);\r\n numberOfLabelsAdded += this.addLocalMinMaxes(unsortedWeightedLabelDataPoints, sortedLabelDataPoints, globalMinMaxInRange.maxIndex, globalMinMaxInRange.minIndex, maximumnMinMaxesToAdd);\r\n }\r\n\r\n // If we have enough labels added, don't bother adding non-min/maxes\r\n if (!(numberOfLabelsAdded >= numberOfLabelsToSort)) {\r\n // Split the remaining array and add labels at the mid point of the largest sections until all labels\r\n // are added or we've added maxNumber\r\n let maximumNonMinMaxesToAdd = Math.max(numberOfLabelsToSort - numberOfLabelsAdded, 0);\r\n this.addNonMinMaxes(unsortedWeightedLabelDataPoints, sortedLabelDataPoints, maximumNonMinMaxesToAdd);\r\n }\r\n\r\n sortedLabelDataPointGroups.push({ labelDataPoints: sortedLabelDataPoints, maxNumberOfLabels: unsortedLabelDataPointGroup.maxNumberOfLabels });\r\n }\r\n\r\n return sortedLabelDataPointGroups;\r\n }\r\n\r\n /**\r\n * The weight for each min/max is made up of four values, which are averaged into \r\n * a single weight. You have a weight based on the value difference for both the\r\n * left and right side and a weight for the index difference for both left and\r\n * right. These values are normalized as such:\r\n *\r\n * valueWeight = abs(scaledValueDifference / totalScaledValueDifference)\r\n * indexWeight = abs(indexDifference / categoryCount)\r\n *\r\n * Since we don't care about the direction of these change, we take the absolute\r\n * value for both. We use scaled coordinates for the valueWeight because this\r\n * will more accurately represent what the user sees (consider a log scale; small\r\n * visual changes at the top would otherwise trump large visual changes at the\r\n * bottom of the axis)\r\n *\r\n * In code, the averaging is done by averaging together the \"current\" value and\r\n * index weights and then assigning it to the current dataPoint. Then, when the\r\n * \"next\" data point's weight is calculated, that weight (with respect to \"current\")\r\n * is then averaged with the weight originally assigned. Data points next to nulls\r\n * or on the edge of the visual only have a weight associated with the one side that\r\n * is non-null.\r\n *\r\n * Also note that weights are only calculated for minimums and maximums.\r\n *\r\n * @param labelDataPoints The labelDataPoints to apply the weighting to\r\n */\r\n private calculateWeights(labelDataPoints: LabelDataPoint[], data: CartesianDataPoint[], numberOfLabelsToSort: number, globalMinMax: MinMaxInRange): WeightedLabelDataPoint[] {\r\n let previousMinMaxPoint: MinMaxPoint;\r\n let currentMinMaxPoint: MinMaxPoint;\r\n let categoryCount = data.length;\r\n let yScale = this.yScale;\r\n\r\n // Obtain all maximums and minimums\r\n let minMaxPoints = this.findMinMaxesBasedOnSmoothedValues(labelDataPoints, data);\r\n\r\n // Iterate over the mins/maxes, calcuating the weight as you go. \"Current\" weight is calcuated with\r\n // regard to previous, which is used for the \"current\" data point's left weight and the \"previous\"\r\n // data point's right weight.\r\n let totalValueDifference = Math.abs(yScale(globalMinMax.maxValue) - yScale(globalMinMax.minValue));\r\n for (let minMaxIndex = 0, minMaxCount = minMaxPoints.length; minMaxIndex < minMaxCount; minMaxIndex++) {\r\n currentMinMaxPoint = minMaxPoints[minMaxIndex];\r\n let weight: number;\r\n if (previousMinMaxPoint) {\r\n let valueWeight = Math.abs((yScale(previousMinMaxPoint.value) - yScale(currentMinMaxPoint.value)) / totalValueDifference);\r\n let indexWeight = Math.abs((previousMinMaxPoint.index - currentMinMaxPoint.index)) / (categoryCount - 1);\r\n weight = (valueWeight + indexWeight) / 2;\r\n } // Note: if there is no previous data point, do not calculate a weight because there is no left weight\r\n if (weight != null && previousMinMaxPoint) {\r\n let previousLabelDataPoint = labelDataPoints[previousMinMaxPoint.index];\r\n if (previousLabelDataPoint.weight != null) {\r\n // Previous has a left weight; average that with this weight which provides the right weight\r\n previousLabelDataPoint.weight = (previousLabelDataPoint.weight + weight) / 2;\r\n }\r\n else {\r\n // Previous has no left weight because it's the first of a line segment; just use the right weight\r\n previousLabelDataPoint.weight = weight;\r\n }\r\n // Current's left weight is set\r\n labelDataPoints[currentMinMaxPoint.index].weight = weight;\r\n } // Current's right weight will be applied by the next iteration unless it has none\r\n previousMinMaxPoint = currentMinMaxPoint;\r\n }\r\n\r\n // Cull min/maxes that are extremely low weight.\r\n for (let labelDataPoint of labelDataPoints) {\r\n if (labelDataPoint.weight < MinMaxLabelDataPointSorter.minimumWeightToConsiderMinMax) {\r\n labelDataPoint.weight = undefined;\r\n }\r\n }\r\n\r\n return labelDataPoints;\r\n }\r\n\r\n private findMinMaxesBasedOnSmoothedValues(labelDataPoints: LabelDataPoint[], data: CartesianDataPoint[]): MinMaxPoint[] {\r\n let minMaxPoints: MinMaxPoint[] = [];\r\n \r\n let windowSize = this.getWindowSize(data);\r\n let halfWindowSize = Math.floor(windowSize / 2);\r\n let scaledSmoothedValues = this.calculateSmoothedValues(data, windowSize);\r\n\r\n // Find mins and maxes based on the scaled and smooth values\r\n for (let categoryIndex = 0, categoryCount = labelDataPoints.length; categoryIndex < categoryCount; categoryIndex++) {\r\n let minMaxType = this.getMinMaxType(categoryIndex, scaledSmoothedValues);\r\n if (minMaxType === MinMaxType.Neither)\r\n continue;\r\n let currentMinMaxPoint: MinMaxPoint = {\r\n index: categoryIndex,\r\n type: minMaxType,\r\n value: data[categoryIndex].value,\r\n };\r\n minMaxPoints.push(currentMinMaxPoint);\r\n }\r\n\r\n // Adjust mins and maxes to be the actual mins/maxes based on the data, because the min/max of the smoothed values\r\n // may not be the actual min max in the data within the window, and we want to apply the weight to the actual min/max\r\n let previousMinMax: MinMaxPoint;\r\n let currentMinMax: MinMaxPoint;\r\n let nextMinMax: MinMaxPoint;\r\n for (let minMaxIndex = 0, minMaxCount = minMaxPoints.length; minMaxIndex < minMaxCount; minMaxIndex++) {\r\n previousMinMax = minMaxPoints[minMaxIndex - 1];\r\n currentMinMax = minMaxPoints[minMaxIndex];\r\n nextMinMax = minMaxPoints[minMaxIndex + 1];\r\n if (!previousMinMax || !nextMinMax)\r\n continue;\r\n let actualMinMaxInRange = MinMaxLabelDataPointSorter.getMinMaxInRange(Math.max(previousMinMax.index, currentMinMax.index - halfWindowSize), Math.min(nextMinMax.index, currentMinMax.index + halfWindowSize), data);\r\n if (currentMinMax.type === MinMaxType.Maximum) {\r\n let actualIndex = actualMinMaxInRange.maxIndex;\r\n currentMinMax.index = actualIndex;\r\n currentMinMax.value = data[actualIndex].value;\r\n }\r\n else {\r\n let actualIndex = actualMinMaxInRange.minIndex;\r\n currentMinMax.index = actualIndex;\r\n currentMinMax.value = data[actualIndex].value;\r\n }\r\n }\r\n \r\n return minMaxPoints;\r\n }\r\n\r\n private static getMinMaxInRange(startIndex: number, endIndex: number, data: CartesianDataPoint[]): MinMaxInRange {\r\n let minValue: number;\r\n let maxValue: number;\r\n let minIndex: number;\r\n let maxIndex: number;\r\n\r\n // Iterate over the data points to find the min and max index and values\r\n for (let categoryIndex = startIndex, dataLength = data.length; categoryIndex <= endIndex && categoryIndex < dataLength; categoryIndex++) {\r\n let value = data[categoryIndex].value;\r\n if (value == null)\r\n continue;\r\n if (minValue === undefined || value < minValue) {\r\n minValue = value;\r\n minIndex = categoryIndex;\r\n }\r\n if (maxValue === undefined || value > maxValue) {\r\n maxValue = value;\r\n maxIndex = categoryIndex;\r\n }\r\n }\r\n return {\r\n minIndex: minIndex,\r\n minValue: minValue,\r\n maxIndex: maxIndex,\r\n maxValue: maxValue,\r\n };\r\n }\r\n\r\n private getWindowSize(data: CartesianDataPoint[]): number {\r\n let idealSize = (data.length / this.viewport.width) * MinMaxLabelDataPointSorter.estimatedLabelWidth;\r\n let actualsize = Math.floor(idealSize / 2) * 2 + 1; // Force the window size to be a nearby odd number\r\n return actualsize;\r\n }\r\n\r\n private calculateSmoothedValues(data: CartesianDataPoint[], windowSize: number): number[] {\r\n let gaussianValues: number[] = MinMaxLabelDataPointSorter.getGaussianDistribution(windowSize);\r\n let scaledAndSmoothedValues: number[] = [];\r\n\r\n for (let categoryIndex = 0, categoryCount = data.length; categoryIndex < categoryCount; categoryIndex++) {\r\n if (windowSize === 1) {\r\n scaledAndSmoothedValues.push(data[categoryIndex].value);\r\n }\r\n else {\r\n let scaledValue = this.getSmoothedValue(data, categoryIndex, windowSize, gaussianValues);\r\n scaledAndSmoothedValues.push(scaledValue);\r\n }\r\n }\r\n\r\n return scaledAndSmoothedValues;\r\n }\r\n\r\n private static getGaussianDistribution(windowSize: number): number[] {\r\n debug.assert(windowSize / 2 !== Math.floor(windowSize / 2), \"window size should be a whole odd number\");\r\n let gaussianDistribution: number[] = [];\r\n let halfWayIndex = Math.floor(windowSize / 2); // Value at which, value should be 1.\r\n // Standard gaussian equation:\r\n // y = a * e ^ -((x-b)^2 / (2c^2))\r\n // height: 1 (height at the maximum)\r\n // maxPosition: 1 (position of maximum)\r\n // standardDeviation: 0.5 (standard deviation; this ratio places the edge of the window around 0.1)\r\n let height = 1;\r\n let maxPosition = halfWayIndex;\r\n let standardDeviation = halfWayIndex / 2;\r\n for (let i = 0; i < halfWayIndex; i++) {\r\n let gaussianValue = height * Math.pow(Math.E, (-1 * ((i - maxPosition) * (i - maxPosition)) / (2 * standardDeviation * standardDeviation)));\r\n gaussianDistribution.push(gaussianValue);\r\n }\r\n gaussianDistribution.push(1); // Add the maximum, which should always be 1\r\n for (let i = halfWayIndex - 1; i >= 0; i--) { // Invert the left side of the curve to create the right side.\r\n gaussianDistribution.push(gaussianDistribution[i]);\r\n }\r\n return gaussianDistribution;\r\n }\r\n\r\n private getSmoothedValue(data: CartesianDataPoint[], categoryIndex: number, windowSize: number, gaussianValues: number[]): number {\r\n if (data[categoryIndex].value == null)\r\n return data[categoryIndex].value;\r\n let halfWindowSize = Math.floor(windowSize / 2);\r\n let startingIndex = categoryIndex - halfWindowSize;\r\n let endingIndex = categoryIndex + halfWindowSize;\r\n let totalValue = 0;\r\n let totalValueCount = 0;\r\n let lastDataIndex = data.length - 1;\r\n\r\n for (let currentIndex = startingIndex, gaussianIndex = 0; currentIndex <= endingIndex; currentIndex++ , gaussianIndex++) {\r\n let valueIndex = Math.max(0, Math.min(currentIndex, lastDataIndex));\r\n let value = data[valueIndex].value;\r\n if (value != null) {\r\n totalValue += value * gaussianValues[gaussianIndex];\r\n totalValueCount++;\r\n }\r\n }\r\n\r\n return totalValue / totalValueCount;\r\n }\r\n\r\n private addFirstLastMaxMin(unsorted: WeightedLabelDataPoint[], sorted: WeightedLabelDataPoint[], maxIndex: number, minIndex: number): number {\r\n let labelsAdded = 0;\r\n // Don't add anything if unsorted is empty\r\n if (_.isEmpty(unsorted))\r\n return labelsAdded;\r\n // Push first\r\n sorted.push(unsorted[0]);\r\n labelsAdded++;\r\n // Push last only last != first\r\n let lastIndex = unsorted.length - 1;\r\n if (lastIndex !== 0) {\r\n sorted.push(unsorted[lastIndex]);\r\n labelsAdded++;\r\n }\r\n // Push max if it is neither first nor last\r\n if (maxIndex !== 0 && maxIndex !== lastIndex) {\r\n sorted.push(unsorted[maxIndex]);\r\n labelsAdded++;\r\n }\r\n // Push min if it is neither first nor last\r\n if (minIndex !== 0 && minIndex !== lastIndex) {\r\n sorted.push(unsorted[minIndex]);\r\n labelsAdded++;\r\n }\r\n return labelsAdded;\r\n }\r\n\r\n private addLocalMinMaxes(unsorted: WeightedLabelDataPoint[], sorted: WeightedLabelDataPoint[], maxIndex: number, minIndex: number, maxNumberOfLabels: number): number {\r\n let lastIndex = unsorted.length - 1;\r\n // Obtain all local min/maxes; all min/maxes should have weights now and we filter out first/last/max/min\r\n let localMinMaxes = _.filter(unsorted, (labelDataPoint, index) => {\r\n if (index === 0 || index === lastIndex || index === maxIndex || index === minIndex) {\r\n return false;\r\n }\r\n return labelDataPoint.weight != null;\r\n });\r\n let sortedMinMaxes = _.sortBy(localMinMaxes, (weighedLabelDataPoint) => {\r\n return -weighedLabelDataPoint.weight; // Return weight as a negative since _.sortBy sorts ascending, and we want descending\r\n });\r\n let labelsAdded = 0;\r\n // Add labels until you run out of max/mins or you've sorted enough labels\r\n for (let i = 0, ilen = Math.min(sortedMinMaxes.length, maxNumberOfLabels); i < ilen; i++) {\r\n sorted.push(sortedMinMaxes[i]);\r\n labelsAdded++;\r\n }\r\n return labelsAdded;\r\n }\r\n\r\n private addNonMinMaxes(unsorted: WeightedLabelDataPoint[], sorted: WeightedLabelDataPoint[], maxNumberOfLabels: number): void {\r\n // First construct sets of non-min/maxes by iterating over the unsorted data points.\r\n let nonMinMaxSets: NonMinMaxSet[] = [];\r\n let currentNonMinMaxSet: NonMinMaxSet;\r\n for (let categoryIndex = 0, categoryCount = unsorted.length; categoryIndex < categoryCount; categoryIndex++) {\r\n if (unsorted[categoryIndex].weight != null) {\r\n // If the current data point is a min/max, we add the old NonMinMaxSet to the array, reset it so that\r\n // a new one will be constructed, and then continue\r\n if (currentNonMinMaxSet && currentNonMinMaxSet.count > 0) {\r\n nonMinMaxSets.push(currentNonMinMaxSet);\r\n currentNonMinMaxSet = null;\r\n }\r\n continue;\r\n }\r\n if (!currentNonMinMaxSet) {\r\n // If the previous data point was a min/max and this one isn't, set up a new NonMinMaxSet\r\n currentNonMinMaxSet = {\r\n startingIndex: categoryIndex,\r\n count: 1,\r\n };\r\n }\r\n else {\r\n // Otherwise, we're just \"adding\" another non-min/max point to the set, so increase count.\r\n currentNonMinMaxSet.count++;\r\n }\r\n }\r\n\r\n let numberOfLabelsAdded = 0;\r\n while (nonMinMaxSets.length > 0 && numberOfLabelsAdded < maxNumberOfLabels) {\r\n // Find the index of the largest set\r\n let currentMaxCount = 0;\r\n let maxIndex = 0;\r\n for (let i = 0, ilen = nonMinMaxSets.length; i < ilen; i++) {\r\n let currentCount = nonMinMaxSets[i].count;\r\n if (currentCount > currentMaxCount) {\r\n currentMaxCount = currentCount;\r\n maxIndex = i;\r\n }\r\n }\r\n let setToSplit = nonMinMaxSets.splice(maxIndex, 1)[0];\r\n if (setToSplit.count === 1) {\r\n sorted.push(unsorted[setToSplit.startingIndex]);\r\n }\r\n else {\r\n let splitIndex = Math.floor(setToSplit.count / 2) + setToSplit.startingIndex;\r\n // Split the array in two, putting the split point into sorted, and creating two new sets by splitting the old set\r\n sorted.push(unsorted[splitIndex]);\r\n let leftCount = splitIndex - setToSplit.startingIndex;\r\n if (leftCount > 0) {\r\n nonMinMaxSets.push({\r\n startingIndex: setToSplit.startingIndex,\r\n count: leftCount,\r\n });\r\n }\r\n let rightCount = setToSplit.startingIndex + setToSplit.count - splitIndex - 1;\r\n if (rightCount > 0) {\r\n nonMinMaxSets.push({\r\n startingIndex: splitIndex + 1,\r\n count: rightCount,\r\n });\r\n }\r\n }\r\n numberOfLabelsAdded++;\r\n }\r\n }\r\n\r\n private getMinMaxType(index: number, scaledDataPoints: number[]): MinMaxType {\r\n let currentValue = scaledDataPoints[index];\r\n // Check to see if the point's value is null; these are not considered min/maxes\r\n if (scaledDataPoints[index] == null)\r\n return MinMaxType.Neither;\r\n // If the array is of length one, return neither to exit early\r\n if (scaledDataPoints.length < 2)\r\n return MinMaxType.Neither;\r\n\r\n // Check for cases at the very edge of the array\r\n if (scaledDataPoints[index - 1] == null) {\r\n return scaledDataPoints[index + 1] > currentValue ? MinMaxType.Minimum : MinMaxType.Maximum;\r\n }\r\n if (scaledDataPoints[index + 1] == null) {\r\n return scaledDataPoints[index - 1] > currentValue ? MinMaxType.Minimum : MinMaxType.Maximum;\r\n }\r\n\r\n let prevValue = scaledDataPoints[index - 1];\r\n let nextValue = scaledDataPoints[index + 1];\r\n // Check for cases next to nulls\r\n if (prevValue == null && nextValue == null) {\r\n return MinMaxType.Neither;\r\n }\r\n if (prevValue == null) {\r\n return nextValue > currentValue ? MinMaxType.Minimum : MinMaxType.Maximum;\r\n }\r\n if (nextValue == null) {\r\n return prevValue > currentValue ? MinMaxType.Minimum : MinMaxType.Maximum;\r\n }\r\n\r\n // Check for typical min/maxes\r\n if (prevValue > currentValue && currentValue < nextValue) {\r\n return MinMaxType.Minimum;\r\n }\r\n if (prevValue < currentValue && currentValue > nextValue) {\r\n return MinMaxType.Maximum;\r\n }\r\n\r\n return MinMaxType.Neither;\r\n }\r\n }\r\n}\n\n\n/** WEBPACK FOOTER **\n ** ./src/Clients/Visuals/common/labelDataPointSorter.ts\n **/","/*\r\n * Power BI Visualizations\r\n *\r\n * Copyright (c) Microsoft Corporation\r\n * All rights reserved. \r\n * MIT License\r\n *\r\n * Permission is hereby granted, free of charge, to any person obtaining a copy\r\n * of this software and associated documentation files (the \"\"Software\"\"), to deal\r\n * in the Software without restriction, including without limitation the rights\r\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\r\n * copies of the Software, and to permit persons to whom the Software is\r\n * furnished to do so, subject to the following conditions:\r\n * \r\n * The above copyright notice and this permission notice shall be included in \r\n * all copies or substantial portions of the Software.\r\n * \r\n * THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR \r\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, \r\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE \r\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER \r\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\r\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\r\n * THE SOFTWARE.\r\n */\r\n\r\n/// <reference path=\"../_references.ts\"/>\r\n\r\nmodule powerbi.visuals {\r\n export module ReferenceLineHelper {\r\n export const referenceLineProps = {\r\n show: 'show',\r\n lineColor: 'lineColor',\r\n transparency: 'transparency',\r\n value: 'value',\r\n style: 'style',\r\n position: 'position',\r\n dataLabelShow: 'dataLabelShow',\r\n dataLabelColor: 'dataLabelColor',\r\n dataLabelDecimalPoints: 'dataLabelDecimalPoints',\r\n dataLabelHorizontalPosition: 'dataLabelHorizontalPosition',\r\n dataLabelVerticalPosition: 'dataLabelVerticalPosition',\r\n dataLabelDisplayUnits: 'dataLabelDisplayUnits',\r\n };\r\n\r\n export function enumerateObjectInstances(enumeration: ObjectEnumerationBuilder, referenceLines: DataViewObjectMap, defaultColor: string, objectName: string): void {\r\n debug.assertValue(enumeration, 'enumeration');\r\n\r\n if (_.isEmpty(referenceLines)) {\r\n // NOTE: We do not currently have support for object maps in the property pane. For now we will generate a single reference line \r\n // object that the format pane can handle.In the future we will need property pane support for multiple reference lines. Also, we're\r\n // assuming that the user-defined IDs will be numeric strings, this may change in the future and will likley be controlled by the property pane.\r\n let instance: VisualObjectInstance = {\r\n selector: {\r\n id: '0'\r\n },\r\n properties: {\r\n show: false,\r\n value: '',\r\n lineColor: { solid: { color: defaultColor } },\r\n transparency: 50,\r\n style: lineStyle.dashed,\r\n position: referenceLinePosition.back,\r\n dataLabelShow: false,\r\n },\r\n objectName: objectName\r\n };\r\n\r\n enumeration.pushInstance(instance);\r\n\r\n return;\r\n }\r\n\r\n for (let referenceLine of referenceLines) {\r\n let referenceLineProperties = referenceLine.object;\r\n let show = DataViewObject.getValue(referenceLineProperties, referenceLineProps.show, false);\r\n let value = DataViewObject.getValue(referenceLineProperties, referenceLineProps.value);\r\n let lineColor = DataViewObject.getValue(referenceLineProperties, referenceLineProps.lineColor, { solid: { color: defaultColor } });\r\n let transparency = DataViewObject.getValue(referenceLineProperties, referenceLineProps.transparency, 50);\r\n let style = DataViewObject.getValue(referenceLineProperties, referenceLineProps.style, lineStyle.dashed);\r\n let position = DataViewObject.getValue(referenceLineProperties, referenceLineProps.position, referenceLinePosition.back);\r\n let dataLabelShow = DataViewObject.getValue(referenceLineProperties, referenceLineProps.dataLabelShow, false);\r\n\r\n let instance: VisualObjectInstance = {\r\n selector: {\r\n id: referenceLine.id\r\n },\r\n properties: {\r\n show: show,\r\n value: value,\r\n lineColor: lineColor,\r\n transparency: transparency,\r\n style: style,\r\n position: position,\r\n dataLabelShow: dataLabelShow,\r\n },\r\n objectName: objectName\r\n };\r\n\r\n // Show the data label properties only if the user chose to show the data label\r\n if (dataLabelShow) {\r\n let dataLabelColor = DataViewObject.getValue(referenceLineProperties, referenceLineProps.dataLabelColor, { solid: { color: defaultColor } });\r\n let dataLabelHorizontalPosition = DataViewObject.getValue(referenceLineProperties, referenceLineProps.dataLabelHorizontalPosition, referenceLineDataLabelHorizontalPosition.left);\r\n let dataLabelVerticalPosition = DataViewObject.getValue(referenceLineProperties, referenceLineProps.dataLabelVerticalPosition, referenceLineDataLabelVerticalPosition.above);\r\n let dataLabelDecimalPoints = DataViewObject.getValue(referenceLineProperties, referenceLineProps.dataLabelDecimalPoints, undefined) < 0\r\n ? undefined\r\n : DataViewObject.getValue(referenceLineProperties, referenceLineProps.dataLabelDecimalPoints, undefined);\r\n let dataLabelDisplayUnits = DataViewObject.getValue(referenceLineProperties, referenceLineProps.dataLabelDisplayUnits, 0);\r\n\r\n instance.properties[referenceLineProps.dataLabelColor] = dataLabelColor;\r\n instance.properties[referenceLineProps.dataLabelHorizontalPosition] = dataLabelHorizontalPosition;\r\n instance.properties[referenceLineProps.dataLabelVerticalPosition] = dataLabelVerticalPosition;\r\n instance.properties[referenceLineProps.dataLabelDisplayUnits] = dataLabelDisplayUnits;\r\n instance.properties[referenceLineProps.dataLabelDecimalPoints] = dataLabelDecimalPoints;\r\n }\r\n\r\n enumeration.pushInstance(instance);\r\n }\r\n }\r\n\r\n export function render(options: ReferenceLineOptions): void {\r\n let graphicContext = options.graphicContext;\r\n let axes = options.axes;\r\n let referenceLineProperties = options.referenceLineProperties;\r\n let isHorizontal = options.isHorizontal;\r\n let viewport = options.viewport;\r\n let classAndSelector = options.classAndSelector;\r\n\r\n let xScale = axes.x.scale;\r\n let yScale = axes.y1.scale;\r\n\r\n let refValue = DataViewObject.getValue(referenceLineProperties, referenceLineProps.value, 0);\r\n let lineColor = DataViewObject.getValue(referenceLineProperties, referenceLineProps.lineColor, { solid: { color: options.defaultColor } });\r\n let transparency = DataViewObject.getValue<number>(referenceLineProperties, referenceLineProps.transparency);\r\n let style = DataViewObject.getValue(referenceLineProperties, referenceLineProps.style, lineStyle.dashed);\r\n let position = DataViewObject.getValue(referenceLineProperties, referenceLineProps.position, referenceLinePosition.back);\r\n\r\n let refLine = graphicContext.select(classAndSelector.selector);\r\n \r\n let index = $(refLine[0]).index();\r\n let currentPosition = index > 1 ? referenceLinePosition.front : referenceLinePosition.back;\r\n let isRefLineExists = index !== -1; \r\n let isPositionChanged = currentPosition !== position;\r\n\r\n if (isRefLineExists && isPositionChanged) \r\n refLine.remove();\r\n \r\n if (!isRefLineExists || isPositionChanged) \r\n refLine = (position === referenceLinePosition.back) ? graphicContext.insert('line', \":first-child\") : graphicContext.append('line'); \r\n\r\n let refLineX1 = isHorizontal ? 0 : xScale(refValue);\r\n let refLineY1 = isHorizontal ? yScale(refValue) : 0;\r\n let refLineX2 = isHorizontal ? viewport.width : xScale(refValue);\r\n let refLineY2 = isHorizontal ? yScale(refValue) : viewport.height;\r\n\r\n refLine.attr({\r\n 'class': classAndSelector.class,\r\n x1: refLineX1,\r\n y1: refLineY1,\r\n x2: refLineX2,\r\n y2: refLineY2,\r\n })\r\n .style({\r\n 'stroke': lineColor.solid.color,\r\n });\r\n\r\n if (transparency != null) \r\n refLine.style('stroke-opacity', ((100 - transparency) / 100)); \r\n\r\n if (style === lineStyle.dashed) {\r\n refLine.style('stroke-dasharray', (\"5, 5\"));\r\n }\r\n else if (style === lineStyle.dotted) {\r\n refLine.style({\r\n 'stroke-dasharray': (\"1, 5\"),\r\n 'stroke-linecap': \"round\"\r\n });\r\n }\r\n else if (style === lineStyle.solid) {\r\n refLine.style({\r\n 'stroke-dasharray': null,\r\n 'stroke-linecap': null\r\n });\r\n }\r\n }\r\n\r\n export function createLabelDataPoint(options: ReferenceLineDataLabelOptions): LabelDataPoint {\r\n let offsetRefLine = 5;\r\n\r\n let axes = options.axes;\r\n let referenceLineProperties = options.referenceLineProperties;\r\n let isHorizontal = options.isHorizontal;\r\n let viewport = options.viewport;\r\n\r\n let xScale = axes.x.scale;\r\n let yScale = axes.y1.scale;\r\n\r\n // Get the data label properties \r\n let refValue = DataViewObject.getValue(referenceLineProperties, referenceLineProps.value, 0);\r\n let color = DataViewObject.getValue<Fill>(referenceLineProperties, referenceLineProps.dataLabelColor, { solid: { color: options.defaultColor } });\r\n let decimalPoints: number = <number>(referenceLineProperties[referenceLineProps.dataLabelDecimalPoints] < 0 ? undefined : referenceLineProperties[referenceLineProps.dataLabelDecimalPoints]);\r\n let horizontalPosition = referenceLineProperties[referenceLineProps.dataLabelHorizontalPosition] || referenceLineDataLabelHorizontalPosition.left;\r\n let verticalPosition = referenceLineProperties[referenceLineProps.dataLabelVerticalPosition] || referenceLineDataLabelVerticalPosition.above;\r\n let displayUnits = DataViewObject.getValue(referenceLineProperties, referenceLineProps.dataLabelDisplayUnits, 0);\r\n\r\n // Format the reference line data label text according to the matching axis formatter\r\n // When options is null default formatter is used either boolean, numeric, or text\r\n let axisFormatter = isHorizontal ? axes.y1.formatter : axes.x.formatter;\r\n let formatterForReferenceLineDataLabel = axisFormatter;\r\n\r\n if (axisFormatter.options != null) {\r\n let formatterOptions = Prototype.inherit(axisFormatter.options);\r\n formatterOptions.precision = decimalPoints;\r\n formatterOptions.value = displayUnits;\r\n formatterOptions.detectAxisPrecision = false;\r\n formatterForReferenceLineDataLabel = valueFormatter.create(formatterOptions);\r\n }\r\n\r\n let text: string = NewDataLabelUtils.getLabelFormattedText(formatterForReferenceLineDataLabel.format(<number>refValue));\r\n\r\n let properties: TextProperties = {\r\n text: text,\r\n fontFamily: dataLabelUtils.LabelTextProperties.fontFamily,\r\n fontSize: dataLabelUtils.LabelTextProperties.fontSize,\r\n fontWeight: dataLabelUtils.LabelTextProperties.fontWeight,\r\n };\r\n\r\n // Get the height and with of the text element that will be created in order to place it correctly\r\n let rectWidth: number = TextMeasurementService.measureSvgTextWidth(properties);\r\n let rectHeight: number = TextMeasurementService.estimateSvgTextHeight(properties);\r\n\r\n let dataLabelX: number;\r\n let dataLabelY: number;\r\n\r\n let x1 = isHorizontal ? 0 : xScale(refValue);\r\n let y1 = isHorizontal ? yScale(refValue) : 0;\r\n let x2 = isHorizontal ? viewport.width : xScale(refValue);\r\n let y2 = isHorizontal ? yScale(refValue) : viewport.height;\r\n let validPositions = [NewPointLabelPosition.Above];\r\n\r\n if (isHorizontal) {\r\n // Horizontal line. y1 = y2\r\n dataLabelX = (horizontalPosition === referenceLineDataLabelHorizontalPosition.left) ? x1 + offsetRefLine : x2 - (rectWidth / 2) - offsetRefLine;\r\n dataLabelY = y1;\r\n validPositions = (verticalPosition === referenceLineDataLabelVerticalPosition.above) ? [NewPointLabelPosition.Above] : [NewPointLabelPosition.Below];\r\n }\r\n else {\r\n // Vertical line. x1 = x2 \r\n dataLabelX = x1;\r\n dataLabelY = (verticalPosition === referenceLineDataLabelVerticalPosition.above) ? y1 + (rectHeight / 2) + offsetRefLine : y2 - (rectHeight / 2) - offsetRefLine;\r\n validPositions = (horizontalPosition === referenceLineDataLabelHorizontalPosition.left) ? [NewPointLabelPosition.Left] : [NewPointLabelPosition.Right];\r\n }\r\n\r\n let textWidth = TextMeasurementService.measureSvgTextWidth(properties);\r\n let textHeight = TextMeasurementService.estimateSvgTextHeight(properties, true /* tightFitForNumeric */);\r\n let parentShape: LabelParentPoint;\r\n\r\n parentShape = {\r\n point: {\r\n x: dataLabelX,\r\n y: dataLabelY,\r\n },\r\n radius: 0,\r\n validPositions: validPositions,\r\n };\r\n \r\n return {\r\n isPreferred: true,\r\n text: text,\r\n textSize: {\r\n width: textWidth,\r\n height: textHeight,\r\n },\r\n outsideFill: color.solid.color,\r\n insideFill: null,\r\n parentShape: parentShape,\r\n parentType: LabelDataPointParentType.Point,\r\n fontSize: 9,\r\n identity: null,\r\n secondRowText: null,\r\n key: options.key,\r\n };\r\n }\r\n\r\n export function extractReferenceLineValue(referenceLineProperties: DataViewObject): number {\r\n let referenceLineValue: number = null;\r\n\r\n if (referenceLineProperties && DataViewObject.getValue(referenceLineProperties, ReferenceLineHelper.referenceLineProps.show, false))\r\n referenceLineValue = DataViewObject.getValue(referenceLineProperties, ReferenceLineHelper.referenceLineProps.value, null);\r\n\r\n return referenceLineValue;\r\n }\r\n }\r\n}\n\n\n/** WEBPACK FOOTER **\n ** ./src/Clients/Visuals/common/referenceLineHelper.ts\n **/","/*\r\n * Power BI Visualizations\r\n *\r\n * Copyright (c) Microsoft Corporation\r\n * All rights reserved. \r\n * MIT License\r\n *\r\n * Permission is hereby granted, free of charge, to any person obtaining a copy\r\n * of this software and associated documentation files (the \"\"Software\"\"), to deal\r\n * in the Software without restriction, including without limitation the rights\r\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\r\n * copies of the Software, and to permit persons to whom the Software is\r\n * furnished to do so, subject to the following conditions:\r\n * \r\n * The above copyright notice and this permission notice shall be included in \r\n * all copies or substantial portions of the Software.\r\n * \r\n * THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR \r\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, \r\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE \r\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER \r\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\r\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\r\n * THE SOFTWARE.\r\n */\r\n\r\n/// <reference path=\"../_references.ts\"/>\r\n\r\nmodule powerbi.visuals {\r\n export module InteractivityUtils {\r\n export function getPositionOfLastInputEvent(): IPoint {\r\n return {\r\n x: d3.event.clientX,\r\n y: d3.event.clientY\r\n };\r\n }\r\n\r\n export function registerStandardInteractivityHandlers(selection: D3.Selection, selectionHandler: ISelectionHandler): void {\r\n registerStandardSelectionHandler(selection, selectionHandler);\r\n registerStandardContextMenuHandler(selection, selectionHandler);\r\n }\r\n\r\n export function registerStandardSelectionHandler(selection: D3.Selection, selectionHandler: ISelectionHandler): void {\r\n selection.on('click', (d: SelectableDataPoint) => handleSelection(d, selectionHandler));\r\n }\r\n\r\n export function registerStandardContextMenuHandler(selection: D3.Selection, selectionHandler: ISelectionHandler): void {\r\n selection.on('contextmenu', (d: SelectableDataPoint) => handleContextMenu(d, selectionHandler));\r\n }\r\n\r\n export function registerGroupInteractivityHandlers(group: D3.Selection, selectionHandler: ISelectionHandler): void {\r\n registerGroupSelectionHandler(group, selectionHandler);\r\n registerGroupContextMenuHandler(group, selectionHandler);\r\n }\r\n\r\n export function registerGroupSelectionHandler(group: D3.Selection, selectionHandler: ISelectionHandler): void {\r\n group.on('click', () => {\r\n let target = d3.event.target;\r\n let d = <SelectableDataPoint>d3.select(target).datum();\r\n handleSelection(d, selectionHandler);\r\n });\r\n }\r\n\r\n export function registerGroupContextMenuHandler(group: D3.Selection, selectionHandler: ISelectionHandler): void {\r\n group.on('contextmenu', () => {\r\n let target = d3.event.target;\r\n let d = <SelectableDataPoint>d3.select(target).datum();\r\n handleContextMenu(d, selectionHandler);\r\n });\r\n }\r\n\r\n function handleContextMenu(d: SelectableDataPoint, selectionHandler: ISelectionHandler): void {\r\n if (d3.event.ctrlKey)\r\n return;\r\n\r\n d3.event.preventDefault();\r\n let position = InteractivityUtils.getPositionOfLastInputEvent();\r\n selectionHandler.handleContextMenu(d, position);\r\n }\r\n\r\n function handleSelection(d: SelectableDataPoint, selectionHandler: ISelectionHandler): void {\r\n selectionHandler.handleSelection(d, d3.event.ctrlKey);\r\n }\r\n }\r\n} \n\n\n/** WEBPACK FOOTER **\n ** ./src/Clients/Visuals/common/interactivityUtils.ts\n **/","/*\r\n * Power BI Visualizations\r\n *\r\n * Copyright (c) Microsoft Corporation\r\n * All rights reserved. \r\n * MIT License\r\n *\r\n * Permission is hereby granted, free of charge, to any person obtaining a copy\r\n * of this software and associated documentation files (the \"\"Software\"\"), to deal\r\n * in the Software without restriction, including without limitation the rights\r\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\r\n * copies of the Software, and to permit persons to whom the Software is\r\n * furnished to do so, subject to the following conditions:\r\n * \r\n * The above copyright notice and this permission notice shall be included in \r\n * all copies or substantial portions of the Software.\r\n * \r\n * THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR \r\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, \r\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE \r\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER \r\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\r\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\r\n * THE SOFTWARE.\r\n */\r\n\r\n/// <reference path=\"../_references.ts\"/>\r\n\r\nmodule powerbi.visuals {\r\n import DataView = powerbi.DataView;\r\n\r\n export function getInvalidValueWarnings(\r\n dataViews: DataView[],\r\n supportsNaN: boolean,\r\n supportsNegativeInfinity: boolean,\r\n supportsPositiveInfinity: boolean): IVisualWarning[] {\r\n\r\n let checker: InvalidDataValuesChecker = new InvalidDataValuesChecker(\r\n supportsNaN /*supportsNaN*/,\r\n supportsNegativeInfinity /*supportsNegativeInfinity*/,\r\n supportsPositiveInfinity /*supportsPositiveInfinity*/);\r\n\r\n // Show a warning if necessary.\r\n return checker.getWarningMessages(dataViews);\r\n }\r\n\r\n class InvalidDataValuesChecker {\r\n private supportsNaN: boolean;\r\n private supportsNegativeInfinity: boolean;\r\n private supportsPositiveInfinity: boolean;\r\n\r\n private hasNaN: boolean;\r\n private hasNegativeInfinity: boolean;\r\n private hasPositiveInfinity: boolean;\r\n private hasOutOfRange: boolean;\r\n\r\n constructor(supportsNaN: boolean, supportsNegativeInfinity: boolean, supportsPositiveInfinity: boolean) {\r\n this.supportsNaN = supportsNaN;\r\n this.supportsNegativeInfinity = supportsNegativeInfinity;\r\n this.supportsPositiveInfinity = supportsPositiveInfinity;\r\n }\r\n\r\n public getWarningMessages(dataViews: DataView[]): IVisualWarning[] {\r\n this.loadWarningStatus(dataViews);\r\n\r\n let warnings: IVisualWarning[] = [];\r\n if (this.hasNaN && !this.supportsNaN) {\r\n warnings.push(new NaNNotSupportedWarning());\r\n }\r\n\r\n if ((this.hasNegativeInfinity && !this.supportsNegativeInfinity)\r\n || (this.hasPositiveInfinity && !this.supportsPositiveInfinity)) {\r\n warnings.push(new InfinityValuesNotSupportedWarning());\r\n }\r\n\r\n if (this.hasOutOfRange) {\r\n warnings.push(new ValuesOutOfRangeWarning());\r\n }\r\n\r\n return warnings;\r\n }\r\n\r\n private loadWarningStatus(dataViews: DataView[]) {\r\n this.hasNaN = false;\r\n this.hasNegativeInfinity = false;\r\n this.hasOutOfRange = false;\r\n this.hasPositiveInfinity = false;\r\n\r\n for (let k: number = 0; k < dataViews.length; k++) {\r\n let dataView = dataViews[k];\r\n let values = dataView && dataView.categorical && dataView.categorical.values\r\n ? dataView.categorical.values\r\n : null;\r\n\r\n if (!values)\r\n return;\r\n\r\n let valueLength = values.length;\r\n for (let i: number = 0; i < valueLength; i++) {\r\n let value = values[i];\r\n\r\n if (value.values) {\r\n let valueValueLength = value.values.length;\r\n for (let j: number = 0; j < valueValueLength; j++) {\r\n let v = value.values[j];\r\n\r\n if (isNaN(v))\r\n this.hasNaN = true;\r\n else if (v === Number.POSITIVE_INFINITY)\r\n this.hasPositiveInfinity = true;\r\n else if (v === Number.NEGATIVE_INFINITY)\r\n this.hasNegativeInfinity = true;\r\n else if (v < -1e300 || v > 1e300)\r\n this.hasOutOfRange = true;\r\n }\r\n }\r\n }\r\n }\r\n }\r\n }\r\n} \n\n\n/** WEBPACK FOOTER **\n ** ./src/Clients/Visuals/common/invalidDataValuesChecker.ts\n **/","/*\r\n * Power BI Visualizations\r\n *\r\n * Copyright (c) Microsoft Corporation\r\n * All rights reserved. \r\n * MIT License\r\n *\r\n * Permission is hereby granted, free of charge, to any person obtaining a copy\r\n * of this software and associated documentation files (the \"\"Software\"\"), to deal\r\n * in the Software without restriction, including without limitation the rights\r\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\r\n * copies of the Software, and to permit persons to whom the Software is\r\n * furnished to do so, subject to the following conditions:\r\n * \r\n * The above copyright notice and this permission notice shall be included in \r\n * all copies or substantial portions of the Software.\r\n * \r\n * THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR \r\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, \r\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE \r\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER \r\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\r\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\r\n * THE SOFTWARE.\r\n */\r\n\r\n/// <reference path=\"../_references.ts\"/>\r\n\r\nmodule powerbi.visuals {\r\n\r\n export interface IListView {\r\n data(data: any[], dataIdFunction: (d) => {}, dataAppended: boolean): IListView;\r\n rowHeight(rowHeight: number): IListView;\r\n viewport(viewport: IViewport): IListView;\r\n render(): void;\r\n empty(): void;\r\n }\r\n\r\n export module ListViewFactory {\r\n export function createListView(options): IListView {\r\n return new ListView(options);\r\n }\r\n }\r\n\r\n export interface ListViewOptions {\r\n enter: (selection: D3.Selection) => void;\r\n exit: (selection: D3.Selection) => void;\r\n update: (selection: D3.Selection) => void;\r\n loadMoreData: () => void;\r\n baseContainer: D3.Selection;\r\n rowHeight: number;\r\n viewport: IViewport;\r\n scrollEnabled: boolean;\r\n isReadMode: () => boolean;\r\n }\r\n\r\n /**\r\n * A UI Virtualized List, that uses the D3 Enter, Update & Exit pattern to update rows.\r\n * It can create lists containing either HTML or SVG elements.\r\n */\r\n class ListView implements IListView {\r\n private getDatumIndex: (d: any) => {};\r\n private _data: any[];\r\n private _totalRows: number;\r\n\r\n private options: ListViewOptions;\r\n private visibleGroupContainer: D3.Selection;\r\n private scrollContainer: D3.Selection;\r\n private scrollbarInner: D3.Selection;\r\n private cancelMeasurePass: () => void;\r\n private renderTimeoutId: number;\r\n \r\n /**\r\n * The value indicates the percentage of data already shown\r\n * in the list view that triggers a loadMoreData call.\r\n */\r\n private static loadMoreDataThreshold = 0.8;\r\n private static defaultRowHeight = 1;\r\n\r\n public constructor(options: ListViewOptions) {\r\n // make a copy of options so that it is not modified later by caller\r\n this.options = $.extend(true, {}, options);\r\n\r\n this.scrollbarInner = options.baseContainer\r\n .append('div')\r\n .classed('scrollbar-inner', true)\r\n .on('scroll', () => this.renderImpl(this.options.rowHeight));\r\n\r\n this.scrollContainer = this.scrollbarInner\r\n .append('div')\r\n .classed('scrollRegion', true)\r\n .on('touchstart', () => this.stopTouchPropagation())\r\n .on('touchmove', () => this.stopTouchPropagation());\r\n\r\n this.visibleGroupContainer = this.scrollContainer\r\n .append('div')\r\n .classed('visibleGroup', true);\r\n\r\n $(options.baseContainer.node()).find('.scroll-element').attr('drag-resize-disabled', 'true');\r\n\r\n ListView.SetDefaultOptions(options);\r\n }\r\n\r\n private static SetDefaultOptions(options: ListViewOptions) {\r\n options.rowHeight = options.rowHeight || ListView.defaultRowHeight;\r\n }\r\n\r\n public rowHeight(rowHeight: number): ListView {\r\n this.options.rowHeight = Math.ceil(rowHeight);\r\n return this;\r\n }\r\n\r\n public data(data: any[], getDatumIndex: (d) => {}, dataReset: boolean = false): IListView {\r\n this._data = data;\r\n this.getDatumIndex = getDatumIndex;\r\n this.setTotalRows();\r\n if (dataReset)\r\n $(this.scrollbarInner.node()).scrollTop(0);\r\n\r\n this.render();\r\n return this;\r\n }\r\n\r\n public viewport(viewport: IViewport): IListView {\r\n this.options.viewport = viewport;\r\n this.render();\r\n return this;\r\n }\r\n\r\n public empty(): void {\r\n this._data = [];\r\n this.render();\r\n }\r\n\r\n public render(): void {\r\n if (this.renderTimeoutId)\r\n window.clearTimeout(this.renderTimeoutId);\r\n\r\n this.renderTimeoutId = window.setTimeout(() => {\r\n this.getRowHeight().then((rowHeight: number) => {\r\n this.renderImpl(rowHeight);\r\n });\r\n this.renderTimeoutId = undefined;\r\n },0);\r\n }\r\n\r\n private renderImpl(rowHeight: number): void {\r\n let totalHeight = this.options.scrollEnabled ? Math.max(0, (this._totalRows * rowHeight)) : this.options.viewport.height;\r\n this.scrollContainer\r\n .style('height', totalHeight + \"px\")\r\n .attr('height', totalHeight);\r\n\r\n this.scrollToFrame(true /*loadMoreData*/);\r\n }\r\n\r\n /*\r\n * This method is called in order to prevent a bug found in the Interact.js.\r\n * The bug is caused when finishing a scroll outside the scroll area.\r\n * In that case the Interact doesn't process a touchcancel event and thinks a touch point still exists.\r\n * since the Interact listens on the visualContainer, by stoping the propagation we prevent the bug from taking place.\r\n */\r\n private stopTouchPropagation(): void {\r\n //Stop the propagation only in read mode so the drag won't be affected.\r\n if (this.options.isReadMode()) {\r\n if (d3.event.type === \"touchstart\") {\r\n let event: TouchEvent = <any>d3.event;\r\n //If there is another touch point outside this visual than the event should be propagated.\r\n //This way the pinch to zoom will not be affected.\r\n if (event.touches && event.touches.length === 1) {\r\n d3.event.stopPropagation();\r\n }\r\n }\r\n if (d3.event.type === \"touchmove\") {\r\n d3.event.stopPropagation();\r\n }\r\n }\r\n }\r\n\r\n private scrollToFrame(loadMoreData: boolean): void {\r\n let options = this.options;\r\n let visibleGroupContainer = this.visibleGroupContainer;\r\n let totalRows = this._totalRows;\r\n let rowHeight = options.rowHeight || ListView.defaultRowHeight;\r\n let visibleRows = this.getVisibleRows();\r\n let scrollTop: number = this.scrollbarInner.node().scrollTop;\r\n let scrollPosition = (scrollTop === 0) ? 0 : Math.floor(scrollTop / rowHeight);\r\n let transformAttr = SVGUtil.translateWithPixels(0, scrollPosition * rowHeight);\r\n\r\n visibleGroupContainer.style({\r\n //order matters for proper overriding\r\n 'transform': d => transformAttr,\r\n '-webkit-transform': transformAttr\r\n });\r\n\r\n let position0 = Math.max(0, Math.min(scrollPosition, totalRows - visibleRows + 1)),\r\n position1 = position0 + visibleRows;\r\n \r\n let rowSelection = visibleGroupContainer.selectAll(\".row\")\r\n .data(this._data.slice(position0, Math.min(position1, totalRows)), this.getDatumIndex);\r\n\r\n rowSelection\r\n .enter()\r\n .append('div')\r\n .classed('row', true)\r\n .call(d => options.enter(d));\r\n rowSelection.order();\r\n\r\n let rowUpdateSelection = visibleGroupContainer.selectAll('.row:not(.transitioning)');\r\n\r\n rowUpdateSelection.call(d => options.update(d));\r\n\r\n rowSelection\r\n .exit()\r\n .call(d => options.exit(d))\r\n .remove();\r\n\r\n if (loadMoreData && visibleRows !== totalRows && position1 >= totalRows * ListView.loadMoreDataThreshold)\r\n options.loadMoreData();\r\n }\r\n\r\n private setTotalRows(): void {\r\n let data = this._data;\r\n this._totalRows = data ? data.length : 0;\r\n }\r\n\r\n private getVisibleRows(): number {\r\n const minimumVisibleRows = 1;\r\n let options = this.options;\r\n let rowHeight = options.rowHeight;\r\n let viewportHeight = options.viewport.height;\r\n\r\n if (!rowHeight || rowHeight < 1)\r\n return minimumVisibleRows;\r\n\r\n // How many rows of space the viewport can hold (not the number of rows it can display).\r\n let viewportRowCount = viewportHeight / rowHeight;\r\n \r\n if (this.options.scrollEnabled) {\r\n // Ceiling the count since we can have items be partially displayed when scrolling.\r\n // Add 1 to make sure we always render enough rows to cover the entire viewport (handles when rows are partially visible when scrolling).\r\n // Ex. If you have a viewport that can show 280 (viewport height) / 100 (row height) = 2.8 rows, you need to have up to Math.ceil(2.8) + 1 = 4 rows of data to cover the viewport.\r\n // If you only had Math.ceil(2.8) = 3 rows of data, and the top rows was 50% visible (scrolled up), you'd only be able to cover .5 + 1 + 1 = 2.5 rows of the viewport.\r\n // This makes a gap at the bottom of the listview.\r\n // Add an extra row of data and we can cover .5 + 1 + 1 + 1 = 3.5 rows of the viewport. 3.5 is enough to cover the entire viewport as only 2.8 is needed.\r\n // 1 is always added, even if not needed, to keep logic simple. Advanced logic would figure out what % of the top row is visible and use that to add 1 if needed.\r\n return Math.min(Math.ceil(viewportRowCount) + 1, this._totalRows) || minimumVisibleRows;\r\n }\r\n\r\n // Floor the count since that's the maximum number of entire rows we can display without scrolling.\r\n return Math.min(Math.floor(viewportRowCount), this._totalRows) || minimumVisibleRows;\r\n }\r\n\r\n private getRowHeight(): JQueryPromise<number> {\r\n let deferred = $.Deferred<number>();\r\n let listView = this;\r\n let options = listView.options;\r\n if (this.cancelMeasurePass)\r\n this.cancelMeasurePass();\r\n\r\n // if there is no data, resolve and return\r\n if (!(this._data && this._data.length && options)) {\r\n listView.rowHeight(ListView.defaultRowHeight);\r\n return deferred.resolve(options.rowHeight).promise();\r\n }\r\n\r\n //render the first item to calculate the row height\r\n this.scrollToFrame(false /*loadMoreData*/);\r\n let requestAnimationFrameId = window.requestAnimationFrame(() => {\r\n //measure row height\r\n let rows = listView.visibleGroupContainer.select(\".row\");\r\n if (!rows.empty()) {\r\n let firstRow = rows.node();\r\n // If the container (child) has margins amd the row (parent) doesn't, the child's margins will collapse into the parent.\r\n // outerHeight doesn't report the correct height for the parent in this case, but it does measure the child properly.\r\n // Fix for #7497261 Measures both and take the max to work around this issue.\r\n let rowHeight: number = Math.max($(firstRow).outerHeight(true), $(firstRow).children().first().outerHeight(true));\r\n listView.rowHeight(rowHeight);\r\n deferred.resolve(rowHeight);\r\n }\r\n\r\n listView.cancelMeasurePass = undefined;\r\n window.cancelAnimationFrame(requestAnimationFrameId);\r\n });\r\n\r\n this.cancelMeasurePass = () => {\r\n window.cancelAnimationFrame(requestAnimationFrameId);\r\n deferred.reject();\r\n };\r\n\r\n return deferred.promise();\r\n }\r\n }\r\n}\n\n\n/** WEBPACK FOOTER **\n ** ./src/Clients/Visuals/common/listView.ts\n **/","/*\r\n * Power BI Visualizations\r\n *\r\n * Copyright (c) Microsoft Corporation\r\n * All rights reserved. \r\n * MIT License\r\n *\r\n * Permission is hereby granted, free of charge, to any person obtaining a copy\r\n * of this software and associated documentation files (the \"\"Software\"\"), to deal\r\n * in the Software without restriction, including without limitation the rights\r\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\r\n * copies of the Software, and to permit persons to whom the Software is\r\n * furnished to do so, subject to the following conditions:\r\n * \r\n * The above copyright notice and this permission notice shall be included in \r\n * all copies or substantial portions of the Software.\r\n * \r\n * THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR \r\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, \r\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE \r\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER \r\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\r\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\r\n * THE SOFTWARE.\r\n */\r\n\r\n/// <reference path=\"../_references.ts\"/>\r\n\r\nmodule powerbi.visuals {\r\n const defaultLevelOfDetail = 11;\r\n\r\n export module MapUtil {\r\n export interface IPixelArrayResult {\r\n array: Float64Array;\r\n arrayString: string;\r\n }\r\n\r\n export const Settings = {\r\n /** Maximum Bing requests at once. The Bing have limit how many request at once you can do per socket. */\r\n MaxBingRequest: 6,\r\n\r\n /** Maximum cache size of cached geocode data. */\r\n MaxCacheSize: 3000,\r\n\r\n /** Maximum cache overflow of cached geocode data to kick the cache reducing. */\r\n MaxCacheSizeOverflow: 100,\r\n\r\n // Bing Keys and URL\r\n BingKey: \"insert your key\",\r\n BingUrl: \"https://dev.virtualearth.net/REST/v1/Locations\",\r\n BingUrlGeodata: \"https://platform.bing.com/geo/spatial/v1/public/Geodata?\",\r\n\r\n /** Switch the data result for geodata polygons to by double array instead locations array */\r\n UseDoubleArrayGeodataResult: true,\r\n UseDoubleArrayDequeueTimeout: 0,\r\n };\r\n\r\n // Bing map min/max boundaries\r\n export const MinAllowedLatitude = -85.05112878;\r\n export const MaxAllowedLatitude = 85.05112878;\r\n export const MinAllowedLongitude = -180;\r\n export const MaxAllowedLongitude = 180;\r\n export const TileSize = 256;\r\n export const MaxLevelOfDetail = 23;\r\n export const MinLevelOfDetail = 1;\r\n export const MaxAutoZoomLevel = 5;\r\n export const DefaultLevelOfDetail = 11;\r\n export const WorkerErrorName = \"___error___\";\r\n\r\n export const CategoryTypes = {\r\n Address: \"Address\",\r\n City: \"City\",\r\n Continent: \"Continent\",\r\n CountryRegion: \"Country\", // The text has to stay \"Country\" because it is used as a key in the geocoding caching dictionary\r\n County: \"County\",\r\n Longitude: \"Longitude\",\r\n Latitude: \"Latitude\",\r\n Place: \"Place\",\r\n PostalCode: \"PostalCode\",\r\n StateOrProvince: \"StateOrProvince\"\r\n };\r\n\r\n const safeCharacters: string = \"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789_-\";\r\n\r\n export function clip(n: number, minValue: number, maxValue: number): number {\r\n return Math.min(Math.max(n, minValue), maxValue);\r\n }\r\n\r\n export function getMapSize(levelOfDetail: number): number {\r\n if (levelOfDetail === 23)\r\n return 2147483648; //256 << 23 overflow the integer and return a negative value\r\n\r\n if (Math.floor(levelOfDetail) === levelOfDetail)\r\n return 256 << levelOfDetail;\r\n\r\n return 256 * Math.pow(2, levelOfDetail);\r\n }\r\n\r\n /**\r\n * pointArrayChunkLength Motivation: \r\n * When the number is too small (e.g. less than 1000) the tile is rendering but VERY SLOW, \r\n * when it's too high there is a risk to get \"stack overflow\" error on mobile (while joining).\r\n * this is the lowest number I managed to get without any noticeable slowness.\r\n */\r\n const pointArrayChunkLength = 15000;\r\n\r\n /**\r\n * @param latLongArray - is a Float64Array as [lt0, lon0, lat1, long1, lat2, long2,....]\r\n * @param buildString - optional, if true returns also a string as \"x0 y0 x1 y1 x2 y2 ....\"\r\n * @returns IPixelArrayResult with Float64Array as [x0, y0, x1, y1, x2, y2,....]\r\n */\r\n export function latLongToPixelXYArray(latLongArray: Float64Array, levelOfDetail: number, buildString: boolean = false): IPixelArrayResult {\r\n let helperArray: number[] = [];\r\n let result: IPixelArrayResult = {\r\n array: new Float64Array(latLongArray.length),\r\n arrayString: \"\"\r\n };\r\n\r\n for (let i = 0; i < latLongArray.length; i += 2) {\r\n let latitude = clip(latLongArray[i], MinAllowedLatitude, MaxAllowedLatitude);\r\n let longitude = clip(latLongArray[i + 1], MinAllowedLongitude, MaxAllowedLongitude);\r\n\r\n let x: number = (longitude + 180) / 360;\r\n let sinLatitude: number = Math.sin(latitude * Math.PI / 180);\r\n let y: number = 0.5 - Math.log((1 + sinLatitude) / (1 - sinLatitude)) / (4 * Math.PI);\r\n\r\n let mapSize: number = getMapSize(levelOfDetail);\r\n result.array[i] = clip(x * mapSize + 0.5, 0.0, mapSize - 1);\r\n result.array[i + 1] = clip(y * mapSize + 0.5, 0.0, mapSize - 1);\r\n\r\n if (buildString) {\r\n helperArray.push(result.array[i], result.array[i + 1]);\r\n if (helperArray.length >= pointArrayChunkLength) {\r\n result.arrayString += helperArray.join(\" \") + \" \";\r\n helperArray = [];\r\n }\r\n }\r\n }\r\n\r\n if (buildString) {\r\n result.arrayString += helperArray.join(\" \") + \" \";\r\n }\r\n return result;\r\n }\r\n\r\n export function getLocationBoundaries(latLongArray: Float64Array): Microsoft.Maps.LocationRect {\r\n const northWest = {\r\n latitude: -90, longitude: 180\r\n };\r\n const southEast = {\r\n latitude: 90, longitude: -180\r\n };\r\n\r\n for (let i = 0; i < latLongArray.length; i += 2) {\r\n northWest.latitude = Math.max(latLongArray[i], northWest.latitude);\r\n northWest.longitude = Math.min(latLongArray[i + 1], northWest.longitude);\r\n southEast.latitude = Math.min(latLongArray[i], southEast.latitude);\r\n southEast.longitude = Math.max(latLongArray[i + 1], southEast.longitude);\r\n }\r\n\r\n northWest.longitude = clip(northWest.longitude, -180, 180);\r\n southEast.longitude = clip(southEast.longitude, -180, 180);\r\n\r\n return Microsoft.Maps.LocationRect.fromCorners(\r\n new Microsoft.Maps.Location(northWest.latitude, northWest.longitude),\r\n new Microsoft.Maps.Location(southEast.latitude, southEast.longitude));\r\n }\r\n\r\n /**\r\n * Note: this code is taken from Bing.\r\n * see Point Compression Algorithm http://msdn.microsoft.com/en-us/library/jj158958.aspx\r\n * see Decompression Algorithm in http://msdn.microsoft.com/en-us/library/dn306801.aspx\r\n */\r\n export function parseEncodedSpatialValueArray(value): Float64Array {\r\n let list: number[] = [];\r\n let index = 0;\r\n let xsum = 0;\r\n let ysum = 0;\r\n let max = 4294967296;\r\n\r\n while (index < value.length) {\r\n let n = 0;\r\n let k = 0;\r\n\r\n while (1) {\r\n\r\n if (index >= value.length) {\r\n return null;\r\n }\r\n\r\n let b = safeCharacters.indexOf(value.charAt(index++));\r\n if (b === -1) {\r\n return null;\r\n }\r\n\r\n let tmp = ((b & 31) * (Math.pow(2, k)));\r\n\r\n let ht = tmp / max;\r\n let lt = tmp % max;\r\n\r\n let hn = n / max;\r\n let ln = n % max;\r\n\r\n let nl = (lt | ln) >>> 0;\r\n n = (ht | hn) * max + nl;\r\n k += 5;\r\n if (b < 32) break;\r\n }\r\n\r\n let diagonal = Math.floor((Math.sqrt(8 * n + 5) - 1) / 2);\r\n n -= diagonal * (diagonal + 1) / 2;\r\n let ny = Math.floor(n);\r\n let nx = diagonal - ny;\r\n nx = (nx >> 1) ^ -(nx & 1);\r\n ny = (ny >> 1) ^ -(ny & 1);\r\n xsum += nx;\r\n ysum += ny;\r\n let lat = ysum * 0.00001;\r\n let lon = xsum * 0.00001;\r\n\r\n list.push(lat);\r\n list.push(lon);\r\n }\r\n return new Float64Array(list);\r\n }\r\n\r\n export function calcGeoData(data: IGeocodeBoundaryCoordinate) {\r\n let locations = data.locations;\r\n\r\n for (let i = 0; i < locations.length; i++) {\r\n let location = locations[i];\r\n if (!location.geographic) {\r\n location.geographic = MapUtil.parseEncodedSpatialValueArray(location.nativeBing);\r\n }\r\n let polygon = location.geographic;\r\n if (polygon) {\r\n if (!location.absolute) {\r\n let result = MapUtil.latLongToPixelXYArray(polygon, MapUtil.DefaultLevelOfDetail, true);\r\n location.absolute = result.array;\r\n location.absoluteString = result.arrayString;\r\n\r\n let geographicBounds = MapUtil.getLocationBoundaries(polygon);\r\n location.absoluteBounds = MapUtil.locationRectToRectXY(geographicBounds, MapUtil.DefaultLevelOfDetail);\r\n }\r\n }\r\n }\r\n }\r\n\r\n export function locationToPixelXY(location: Microsoft.Maps.Location, levelOfDetail: number): powerbi.visuals.Point {\r\n return latLongToPixelXY(location.latitude, location.longitude, levelOfDetail);\r\n }\r\n\r\n export function locationRectToRectXY(locationRect: Microsoft.Maps.LocationRect, levelOfDetail: number): powerbi.visuals.Rect {\r\n let topleft = locationToPixelXY(locationRect.getNorthwest(), levelOfDetail);\r\n let bottomRight = locationToPixelXY(locationRect.getSoutheast(), levelOfDetail);\r\n return new powerbi.visuals.Rect(topleft.x, topleft.y, bottomRight.x - topleft.x, bottomRight.y - topleft.y);\r\n }\r\n\r\n export function latLongToPixelXY(latitude: number, longitude: number, levelOfDetail: number): powerbi.visuals.Point {\r\n let array: Float64Array = latLongToPixelXYArray(new Float64Array([latitude, longitude]), levelOfDetail).array;\r\n return new powerbi.visuals.Point(array[0], array[1]);\r\n }\r\n\r\n export function pixelXYToLocation(pixelX: number, pixelY: number, levelOfDetail: number): Microsoft.Maps.Location {\r\n let mapSize = getMapSize(levelOfDetail);\r\n let x = (clip(pixelX, 0, mapSize - 1) / mapSize) - 0.5;\r\n let y = 0.5 - (clip(pixelY, 0, mapSize - 1) / mapSize);\r\n let latitude = 90 - 360 * Math.atan(Math.exp(-y * 2 * Math.PI)) / Math.PI;\r\n let longitude = 360 * x;\r\n return new Microsoft.Maps.Location(latitude, longitude);\r\n }\r\n\r\n export module CurrentLocation {\r\n\r\n export function createPushpin(location: Microsoft.Maps.Location): Microsoft.Maps.Pushpin {\r\n let template = '<svg width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" xmlns=\"http://www.w3.org/2000/svg\">'\r\n + '<circle fill=\"#FF5F00\" cx=\"12\" cy=\"12\" r=\"6\"/>'\r\n + '<circle fill=\"none\" stroke=\"#FF5F00\" stroke-width=\"2\" cx=\"12\" cy=\"12\" r=\"10\"/>'\r\n + '</svg>';\r\n\r\n let options: Microsoft.Maps.PushpinOptions = {\r\n draggable: false,\r\n htmlContent: template,\r\n height: 24,\r\n width: 24\r\n };\r\n\r\n return new Microsoft.Maps.Pushpin(location, options);\r\n }\r\n }\r\n }\r\n\r\n export class MapPolygonInfo {\r\n private _locationRect: Microsoft.Maps.LocationRect;\r\n private _baseRect: Rect;\r\n private _currentRect: Rect;\r\n\r\n constructor() {\r\n this._locationRect = new Microsoft.Maps.LocationRect(new Microsoft.Maps.Location(30, -30), 60, 60);\r\n }\r\n\r\n public reCalc(mapControl: Microsoft.Maps.Map, width: number, height: number) {\r\n let baseLocations = [this._locationRect.getNorthwest(), this._locationRect.getSoutheast()];\r\n width = width / 2.00;\r\n height = height / 2.00;\r\n\r\n if (!this._baseRect) {\r\n let l0 = MapUtil.locationToPixelXY(this._locationRect.getNorthwest(), defaultLevelOfDetail);\r\n let l1 = MapUtil.locationToPixelXY(this._locationRect.getSoutheast(), defaultLevelOfDetail);\r\n this._baseRect = new Rect(l0.x, l0.y, l1.x - l0.x, l1.y - l0.y);\r\n }\r\n\r\n let l = mapControl.tryLocationToPixel(baseLocations);\r\n this._currentRect = new Rect(l[0].x + width, l[0].y + height, l[1].x - l[0].x, l[1].y - l[0].y);\r\n }\r\n\r\n public get scale(): number {\r\n if (this._baseRect) {\r\n return this._currentRect.width / this._baseRect.width;\r\n }\r\n return 1.0;\r\n }\r\n\r\n public get transform(): Transform {\r\n let base = this._baseRect;\r\n let current = this._currentRect;\r\n let transform = new Transform();\r\n transform.translate(current.left, current.top);\r\n transform.scale((current.width / base.width), (current.height / base.height));\r\n transform.translate(-base.left, -base.top);\r\n return transform;\r\n }\r\n\r\n public get outherTransform() {\r\n let base = this._baseRect;\r\n let current = this._currentRect;\r\n let transform = new Transform();\r\n transform.translate(current.left, current.top);\r\n let scale = Math.sqrt(current.width / base.width);\r\n transform.scale(scale, scale);\r\n return transform;\r\n }\r\n\r\n public setViewBox(svg: SVGSVGElement) {\r\n let rect = svg.getBoundingClientRect();\r\n let current = this._currentRect;\r\n svg.setAttribute(\"viewBox\", [-current.left, -current.top, rect.width, rect.height].join(\" \"));\r\n }\r\n\r\n public get innerTransform() {\r\n let base = this._baseRect;\r\n let current = this._currentRect;\r\n let transform = new Transform();\r\n let scale = current.width / base.width;\r\n transform.scale(scale, scale);\r\n transform.translate(-base.left, -base.top);\r\n return transform;\r\n }\r\n\r\n public transformToString(transform: Transform) {\r\n let m = transform.matrix;\r\n return \"matrix(\" + m.m00 + \" \" + m.m10 + \" \" + m.m01 + \" \" + m.m11 + \" \" + m.m02 + \" \" + m.m12 + \")\";\r\n }\r\n }\r\n}\n\n\n/** WEBPACK FOOTER **\n ** ./src/Clients/Visuals/common/mapUtil.ts\n **/","/*\r\n* Power BI Visualizations\r\n*\r\n* Copyright (c) Microsoft Corporation\r\n* All rights reserved. \r\n* MIT License\r\n*\r\n* Permission is hereby granted, free of charge, to any person obtaining a copy\r\n* of this software and associated documentation files (the \"\"Software\"\"), to deal\r\n* in the Software without restriction, including without limitation the rights\r\n* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\r\n* copies of the Software, and to permit persons to whom the Software is\r\n* furnished to do so, subject to the following conditions:\r\n* \r\n* The above copyright notice and this permission notice shall be included in \r\n* all copies or substantial portions of the Software.\r\n* \r\n* THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR \r\n* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, \r\n* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE \r\n* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER \r\n* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\r\n* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\r\n* THE SOFTWARE.\r\n*/\r\n\r\n/// <reference path=\"../_references.ts\"/>\r\n\r\nmodule powerbi.visuals.utility {\r\n export interface SelectionManagerOptions{\r\n hostServices: IVisualHostServices;\r\n };\r\n\r\n export class SelectionManager {\r\n private selectedIds: SelectionId[];\r\n private hostServices: IVisualHostServices;\r\n\r\n public constructor(options: SelectionManagerOptions) {\r\n this.hostServices = options.hostServices;\r\n this.selectedIds = [];\r\n }\r\n\r\n public select(selectionId: SelectionId, multiSelect: boolean = false): JQueryDeferred<SelectionId[]> {\r\n let deferred: JQueryDeferred<data.Selector[]> = $.Deferred();\r\n\r\n if (this.hostServices.shouldRetainSelection()) {\r\n this.sendSelectionToHost([selectionId]);\r\n }\r\n else {\r\n this.selectInternal(selectionId, multiSelect);\r\n this.sendSelectionToHost(this.selectedIds);\r\n }\r\n\r\n deferred.resolve(this.selectedIds);\r\n return deferred;\r\n }\r\n\r\n public showContextMenu(selectionId: SelectionId, position?: Point): JQueryDeferred<{}> {\r\n let deferred: JQueryDeferred<{}> = $.Deferred();\r\n\r\n position = position || InteractivityUtils.getPositionOfLastInputEvent();\r\n this.sendContextMenuToHost(selectionId, position);\r\n\r\n deferred.resolve();\r\n return deferred;\r\n }\r\n\r\n public hasSelection(): boolean {\r\n return this.selectedIds.length > 0;\r\n }\r\n\r\n public clear(): JQueryDeferred<{}> {\r\n let deferred = $.Deferred();\r\n this.selectedIds = [];\r\n this.sendSelectionToHost([]);\r\n deferred.resolve();\r\n return deferred;\r\n }\r\n\r\n public getSelectionIds(): SelectionId[] {\r\n return this.selectedIds;\r\n }\r\n\r\n private sendSelectionToHost(ids: SelectionId[]) {\r\n let selectArgs: SelectEventArgs = {\r\n data: ids\r\n .filter((value: SelectionId) => value.hasIdentity())\r\n .map((value: SelectionId) => value.getSelector())\r\n };\r\n\r\n let data2 = this.getSelectorsByColumn(ids);\r\n\r\n if (!_.isEmpty(data2))\r\n selectArgs.data2 = data2;\r\n\r\n this.hostServices.onSelect(selectArgs);\r\n }\r\n\r\n private sendContextMenuToHost(selectionId: SelectionId, position: IPoint): void {\r\n let selectors = this.getSelectorsByColumn([selectionId]);\r\n if (_.isEmpty(selectors))\r\n return;\r\n\r\n let args: ContextMenuArgs = {\r\n data: selectors,\r\n position: position\r\n };\r\n\r\n this.hostServices.onContextMenu(args);\r\n }\r\n\r\n private getSelectorsByColumn(selectionIds: SelectionId[]): SelectorsByColumn[] {\r\n return _(selectionIds)\r\n .filter(value => value.hasIdentity)\r\n .map(value => value.getSelectorsByColumn())\r\n .compact()\r\n .value();\r\n }\r\n\r\n private selectInternal(selectionId: SelectionId, multiSelect: boolean) {\r\n if (SelectionManager.containsSelection(this.selectedIds, selectionId)) {\r\n this.selectedIds = multiSelect\r\n ? this.selectedIds.filter(d => !data.Selector.equals(d, selectionId))\r\n : this.selectedIds.length > 1\r\n ? [selectionId] : [];\r\n } else {\r\n if (multiSelect)\r\n this.selectedIds.push(selectionId);\r\n else\r\n this.selectedIds = [selectionId];\r\n }\r\n }\r\n\r\n public static containsSelection(list: SelectionId[], id: SelectionId) {\r\n return list.some(d => data.Selector.equals(d.getSelector(), id.getSelector()));\r\n }\r\n }\r\n} \n\n\n/** WEBPACK FOOTER **\n ** ./src/Clients/Visuals/common/selectionManager.ts\n **/","/*\r\n * Power BI Visualizations\r\n *\r\n * Copyright (c) Microsoft Corporation\r\n * All rights reserved. \r\n * MIT License\r\n *\r\n * Permission is hereby granted, free of charge, to any person obtaining a copy\r\n * of this software and associated documentation files (the \"\"Software\"\"), to deal\r\n * in the Software without restriction, including without limitation the rights\r\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\r\n * copies of the Software, and to permit persons to whom the Software is\r\n * furnished to do so, subject to the following conditions:\r\n * \r\n * The above copyright notice and this permission notice shall be included in \r\n * all copies or substantial portions of the Software.\r\n * \r\n * THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR \r\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, \r\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE \r\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER \r\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\r\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\r\n * THE SOFTWARE.\r\n */\r\n\r\n/// <reference path=\"../_references.ts\"/>\r\n\r\nmodule powerbi.visuals {\r\n \"use strict\";\r\n\r\n export module shapes {\r\n import Utility = jsCommon.Utility;\r\n\r\n export class Polygon {\r\n\r\n private _absoluteCentroid: IPoint;\r\n private _absoluteBoundingRect: Rect;\r\n public polygonPoints: IPoint[];\r\n public pixelBoundingRect: Rect;\r\n\r\n constructor(absolutePoints: Float64Array) {\r\n this.polygonPoints = this.convertArrayPathToPoints(absolutePoints);\r\n }\r\n\r\n public absoluteCentroid(): IPoint {\r\n if (this._absoluteCentroid == null) {\r\n this._absoluteCentroid = this.calculatePolygonCentroid();\r\n }\r\n return this._absoluteCentroid;\r\n }\r\n\r\n public absoluteBoundingRect(): Rect {\r\n if (this._absoluteBoundingRect == null) {\r\n this._absoluteBoundingRect = this.calculateBoundingRect();\r\n }\r\n return this._absoluteBoundingRect;\r\n } \r\n\r\n /**\r\n * Check if label text contain in polygon shape.\r\n * \r\n * @return true/false is the label fit in polygon.\r\n * measure if rects points are inside the polygon shape\r\n * return true if there is at least 3 point inside the polygon\r\n */\r\n public contains(rect: IRect): boolean {\r\n let topLeft: IPoint = { x: rect.left, y: rect.top };\r\n let topRight: IPoint = { x: rect.left + rect.width, y: rect.top };\r\n let bottomLeft: IPoint = { x: rect.left, y: rect.top + rect.height };\r\n let bottomRight: IPoint = { x: rect.left + rect.width, y: rect.top + rect.height };\r\n\r\n return (this.inside(topLeft)\r\n && this.inside(topRight)\r\n && this.inside(bottomLeft)\r\n && this.inside(bottomRight));\r\n }\r\n\r\n /**\r\n * Check if label text is outside of polygon shape.\r\n * It checks 8 points in the label. TopLeft, TopCenter, TopRight, MiddleLeft, MiddleRight, BottomLeft, BottomMiddle, BottomRight\r\n * @return true/false is there is any conflict (at least one point inside the shape).\r\n */\r\n public conflicts(rect: IRect): boolean {\r\n if (!this.isConflictWithBoundingBox(rect)) {\r\n return false;\r\n }\r\n let topLeft: IPoint = { x: rect.left, y: rect.top };\r\n let topCenter: IPoint = { x: rect.left + rect.width/2, y: rect.top };\r\n let topRight: IPoint = { x: rect.left + rect.width, y: rect.top };\r\n let bottomLeft: IPoint = { x: rect.left, y: rect.top + rect.height };\r\n let bottomCenter: IPoint = { x: rect.left + rect.width/2, y: rect.top + rect.height };\r\n let bottomRight: IPoint = { x: rect.left + rect.width, y: rect.top + rect.height };\r\n let middleLeft: IPoint = { x: rect.left, y: rect.top + rect.height / 2};\r\n let middleRight: IPoint = { x: rect.left + rect.width, y: rect.top + rect.height/2 };\r\n\r\n return (this.inside(topLeft)\r\n || this.inside(topCenter)\r\n || this.inside(topRight)\r\n || this.inside(bottomLeft)\r\n || this.inside(bottomCenter)\r\n || this.inside(bottomRight)\r\n || this.inside(middleLeft)\r\n || this.inside(middleRight));\r\n }\r\n\r\n /**\r\n * returns intersection point of a line (depicted by two points) and a polygon.\r\n * \r\n * @return the point of intersection or null if there is no intersection.\r\n */\r\n public lineIntersectionPoint(p0: IPoint, p1: IPoint): IPoint {\r\n for (var i = 0; i < this.polygonPoints.length; i++) {\r\n let p2: IPoint = this.polygonPoints[i];\r\n let p3: IPoint = (i === this.polygonPoints.length - 1 ? this.polygonPoints[0] : this.polygonPoints[i + 1]);\r\n\r\n let intersection = this.getLineIntersection(p0, p1, p2, p3);\r\n\r\n if (intersection !== null) {\r\n return intersection;\r\n }\r\n }\r\n\r\n return null;\r\n }\r\n\r\n /**\r\n * calculate Polygon Area.\r\n *\r\n * @return the area of the polygon (as number).\r\n */\r\n public static calculateAbsolutePolygonArea(polygonPoints: IPoint[]): number {\r\n let i, j = 1;\r\n let area = 0.0;\r\n\r\n for (i = 0; i < polygonPoints.length; i++) {\r\n area += polygonPoints[i].x * polygonPoints[j].y - polygonPoints[j].x * polygonPoints[i].y;\r\n j = (j + 1) % polygonPoints.length;\r\n }\r\n area *= 0.5;\r\n return area;\r\n }\r\n\r\n /**\r\n * Check if label text is outside of polygon bounding box.\r\n * \r\n * @return true/false is there is any conflict (at least one point inside the shape).\r\n */\r\n private isConflictWithBoundingBox(rect: IRect): boolean {\r\n return Rect.isIntersecting(this.absoluteBoundingRect(), rect);\r\n }\r\n\r\n /**\r\n * Calculate Polygon Centroid.\r\n *\r\n * @return 'center' point of the polygon.\r\n * calculate the polygon area\r\n * calculate the average points of the polygon by x & y axis. \r\n * divided the average point by the area\r\n */\r\n private calculatePolygonCentroid(): IPoint {\r\n let area, tempPoint, cx, cy, i, j: number;\r\n \r\n /* First calculate the polygon's signed area A */\r\n area = Polygon.calculateAbsolutePolygonArea(this.polygonPoints);\r\n\r\n /* Now calculate the centroid coordinates Cx and Cy */\r\n cx = cy = 0.0;\r\n j = 1;\r\n for (i = 0; i < this.polygonPoints.length; i++) {\r\n tempPoint = this.polygonPoints[i].x * this.polygonPoints[j].y - this.polygonPoints[j].x * this.polygonPoints[i].y;\r\n cx += (this.polygonPoints[i].x + this.polygonPoints[j].x) * tempPoint;\r\n cy += (this.polygonPoints[i].y + this.polygonPoints[j].y) * tempPoint;\r\n j = (j + 1) % this.polygonPoints.length;\r\n }\r\n cx = cx / (6.0 * area);\r\n cy = cy / (6.0 * area);\r\n\r\n return {\r\n x: cx,\r\n y: cy,\r\n };\r\n }\r\n\r\n private calculateBoundingRect(): Rect {\r\n let minX: number = Number.POSITIVE_INFINITY;\r\n let minY: number = Number.POSITIVE_INFINITY;\r\n let maxX: number = Number.NEGATIVE_INFINITY;\r\n let maxY: number = Number.NEGATIVE_INFINITY;\r\n\r\n for (let i = 0; i < this.polygonPoints.length; i++) {\r\n if (this.polygonPoints[i].x < minX) {\r\n minX = this.polygonPoints[i].x;\r\n } else if (this.polygonPoints[i].x > maxX) {\r\n maxX = this.polygonPoints[i].x;\r\n }\r\n\r\n if (this.polygonPoints[i].y < minY) {\r\n minY = this.polygonPoints[i].y;\r\n } else if (this.polygonPoints[i].y > maxY) {\r\n maxY = this.polygonPoints[i].y;\r\n }\r\n }\r\n\r\n return {\r\n left: minX,\r\n top: minY,\r\n width: maxX - minX,\r\n height: maxY - minY\r\n };\r\n }\r\n\r\n /**\r\n * Check if point exist inside polygon shape.\r\n * \r\n * @return true/false if point exist inside shape.\r\n * ray-casting algorithm based on:\r\n * http://www.ecse.rpi.edu/Homepages/wrf/Research/Short_Notes/pnpoly.html\r\n */\r\n private inside(point: IPoint): boolean {\r\n var x = point.x, y = point.y;\r\n\r\n var insideVar: boolean = false;\r\n for (var i = 0, j = this.polygonPoints.length - 1; i < this.polygonPoints.length; j = i++) {\r\n var xi = this.polygonPoints[i].x, yi = this.polygonPoints[i].y;\r\n var xj = this.polygonPoints[j].x, yj = this.polygonPoints[j].y;\r\n\r\n var intersect: boolean = ((yi > y) !== (yj > y))\r\n && (x < (xj - xi) * (y - yi) / (yj - yi) + xi);\r\n if (intersect) {\r\n insideVar = !insideVar;\r\n }\r\n }\r\n return insideVar;\r\n };\r\n\r\n /**\r\n * Checks if a line (presented as two points) intersects with a another line\r\n */\r\n private getLineIntersection(line0p1: IPoint, line0p2: IPoint, line1p1: IPoint, line1p2: IPoint): IPoint {\r\n let p0_x: number = line0p1.x;\r\n let p0_y: number = line0p1.y;\r\n let p1_x: number = line0p2.x;\r\n let p1_y: number = line0p2.y;\r\n let p2_x: number = line1p1.x;\r\n let p2_y: number = line1p1.y;\r\n let p3_x: number = line1p2.x;\r\n let p3_y: number = line1p2.y;\r\n\r\n let s1_x: number = p1_x - p0_x;\r\n let s1_y: number = p1_y - p0_y;\r\n let s2_x: number = p3_x - p2_x;\r\n let s2_y: number = p3_y - p2_y;\r\n\r\n //Calculating collisions using cross products\r\n let s: number = (-s1_y * (p0_x - p2_x) + s1_x * (p0_y - p2_y)) / (-s2_x * s1_y + s1_x * s2_y);\r\n let t: number = (s2_x * (p0_y - p2_y) - s2_y * (p0_x - p2_x)) / (-s2_x * s1_y + s1_x * s2_y);\r\n\r\n // 0<=s<=1 and 0<=t<=1 ensures that the collision is part of the original line\r\n if (s >= 0 && s <= 1 && t >= 0 && t <= 1) {\r\n // Collision detected\r\n return { x: (p0_x + (t * s1_x)), y: (p0_y + (t * s1_y)) };\r\n }\r\n\r\n return null; // No collision\r\n }\r\n\r\n private convertArrayPathToPoints(path: Float64Array): IPoint[] {\r\n let pointsRes: IPoint[] = [];\r\n\r\n for (var i = 0; i < path.length; i += 2) {\r\n let x = path[i];\r\n let y = path[i + 1];\r\n let newPoint = {\r\n x: x,\r\n y: y,\r\n };\r\n pointsRes.push(newPoint);\r\n }\r\n return pointsRes;\r\n }\r\n }\r\n\r\n export module Point {\r\n\r\n export function offset(point: IPoint, offsetX: number, offsetY: number): IPoint {\r\n let newPointX = ((point.x + offsetX) >= 0) ? (point.x + offsetX) : 0;\r\n let newPointY = ((point.y + offsetY) >= 0) ? (point.y + offsetY) : 0;\r\n return { x: newPointX, y: newPointY };\r\n }\r\n\r\n export function equals(point: IPoint, other: IPoint): boolean {\r\n return point !== undefined && point !== null && other !== undefined && other !== null && point.x === other.x && point.y === other.y;\r\n }\r\n\r\n export function clone(point: IPoint): IPoint {\r\n return (point !== null) ? { x: point.x, y: point.y } : null;\r\n }\r\n\r\n export function toString(point: IPoint): string {\r\n return \"{x:\" + point.x + \", y:\" + point.y + \"}\";\r\n }\r\n\r\n export function serialize(point: IPoint): string {\r\n return point.x + \",\" + point.y;\r\n }\r\n\r\n export function getDistance(point: IPoint, other: IPoint): number {\r\n if ((point === null) || (other) === null) {\r\n return null;\r\n }\r\n let diffX = other.x - point.x;\r\n let diffY = other.y - point.y;\r\n return Math.sqrt(diffX * diffX + diffY * diffY);\r\n }\r\n\r\n export function equalWithPrecision(point1: IPoint, point2: IPoint): boolean {\r\n return point1 === point2 ||\r\n (point1 !== undefined && point2 !== undefined && Double.equalWithPrecision(point1.x, point2.x) && Double.equalWithPrecision(point1.y, point2.y));\r\n }\r\n\r\n export function parsePoint(value: any, defaultValue?: IPoint): IPoint {\r\n if (value === null) {\r\n return (defaultValue === undefined) ? null : defaultValue;\r\n } else if (value === undefined) {\r\n return (defaultValue === undefined) ? null : defaultValue;\r\n } else {\r\n if (value.length === 2) {\r\n return { x: Utility.parseNumber(value[0]), y: Utility.parseNumber(value[1]) };\r\n } else if (typeof value === \"string\") {\r\n let parts = (<string>value).split(\",\");\r\n if (parts.length !== 2) {\r\n return (defaultValue === undefined) ? null : defaultValue;\r\n }\r\n return { x: Utility.parseNumber(parts[0]), y: Utility.parseNumber(parts[1]) };\r\n } else if ((value.length !== 2) && (typeof value !== \"string\")) {\r\n return (defaultValue === undefined) ? null : defaultValue;\r\n }\r\n else {\r\n return { x: Utility.parseNumber(value.x), y: Utility.parseNumber(value.y) };\r\n }\r\n }\r\n }\r\n }\r\n\r\n export module Size {\r\n\r\n export function isEmpty(size: ISize): boolean {\r\n return size.width === 0 && size.height === 0;\r\n }\r\n\r\n export function equals(size: ISize, other: ISize): boolean {\r\n return size !== undefined && size !== null && other !== undefined && other !== null && size.width === other.width && size.height === other.height;\r\n }\r\n\r\n export function clone(size: ISize): ISize {\r\n return (size !== null) ? { width: size.width, height: size.height } : null;\r\n }\r\n\r\n export function inflate(size: ISize, padding: IThickness): ISize {\r\n let result = clone(size);\r\n if (padding) {\r\n result.width += padding.left + padding.right;\r\n result.height += padding.top + padding.bottom;\r\n }\r\n return result;\r\n }\r\n\r\n export function deflate(size: ISize, padding: IThickness): ISize {\r\n let result = clone(size);\r\n if (padding) {\r\n result.width = result.width - padding.left - padding.right;\r\n if (result.width < 0) {\r\n result.width = 0;\r\n }\r\n result.height = result.height - padding.top - padding.bottom;\r\n if (result.height < 0) {\r\n result.height = 0;\r\n }\r\n }\r\n return result;\r\n }\r\n\r\n export function combine(size: ISize, other: ISize): ISize {\r\n if (other) {\r\n size.width = Math.max(size.width, other.width);\r\n size.height = Math.max(size.height, other.height);\r\n }\r\n return size;\r\n }\r\n\r\n export function toRect(size: ISize): IRect {\r\n return { left: 0, top: 0, width: size.width, height: size.height };\r\n }\r\n\r\n export function toString(size: ISize): string {\r\n return \"{width:\" + size.width + \", height:\" + size.height + \"}\";\r\n }\r\n\r\n export function equal(size1: ISize, size2: ISize): boolean {\r\n return size1 === size2 ||\r\n (size1 !== undefined && size2 !== undefined && size1.width === size2.width && size1.height === size2.height);\r\n }\r\n\r\n export function equalWithPrecision(size1: ISize, size2: ISize): boolean {\r\n return size1 === size2 ||\r\n (size1 !== undefined && size2 !== undefined && Double.equalWithPrecision(size1.width, size2.width) && Double.equalWithPrecision(size1.height, size2.height));\r\n }\r\n\r\n export function parseSize(value: any, defaultValue?: ISize): ISize {\r\n if (value === null) {\r\n return (defaultValue === undefined) ? null : defaultValue;\r\n } else if (value === undefined) {\r\n return (defaultValue === undefined) ? null : defaultValue;\r\n } else {\r\n if (value.length === 2) {\r\n return { width: Utility.parseNumber(value[0]), height: Utility.parseNumber(value[1]) };\r\n } else if (typeof value === \"string\") {\r\n let parts = (<string>value).split(\",\");\r\n if (parts.length !== 2) {\r\n return (defaultValue === undefined) ? null : defaultValue;\r\n }\r\n return { width: Utility.parseNumber(parts[0]), height: Utility.parseNumber(parts[1]) };\r\n }\r\n else if ((value.length !== 2) && (typeof value !== \"string\")) {\r\n return (defaultValue === undefined) ? null : defaultValue;\r\n } else {\r\n return { width: Utility.parseNumber(value.width), height: Utility.parseNumber(value.height) };\r\n }\r\n }\r\n }\r\n }\r\n\r\n export module Rect {\r\n\r\n export function getOffset(rect: IRect): IPoint {\r\n return { x: rect.left, y: rect.top };\r\n }\r\n\r\n export function getSize(rect: IRect): ISize {\r\n return { width: rect.width, height: rect.height };\r\n }\r\n\r\n export function setSize(rect: IRect, value: ISize): void {\r\n rect.width = value.width;\r\n rect.height = value.height;\r\n }\r\n\r\n export function right(rect: IRect): number {\r\n return rect.left + rect.width;\r\n }\r\n\r\n export function bottom(rect: IRect): number {\r\n return rect.top + rect.height;\r\n }\r\n\r\n export function topLeft(rect: IRect): IPoint {\r\n return { x: rect.left, y: rect.top };\r\n }\r\n\r\n export function topRight(rect: IRect): IPoint {\r\n return { x: rect.left + rect.width, y: rect.top };\r\n }\r\n\r\n export function bottomLeft(rect: IRect): IPoint {\r\n return { x: rect.left, y: rect.top + rect.height };\r\n }\r\n\r\n export function bottomRight(rect: IRect): IPoint {\r\n return { x: rect.left + rect.width, y: rect.top + rect.height };\r\n }\r\n\r\n export function equals(rect: IRect, other: IRect): boolean {\r\n return other !== undefined && other !== null &&\r\n rect.left === other.left && rect.top === other.top && rect.width === other.width && rect.height === other.height;\r\n }\r\n\r\n export function clone(rect: IRect): IRect {\r\n return (rect !== null) ? { left: rect.left, top: rect.top, width: rect.width, height: rect.height } : null;\r\n }\r\n\r\n export function toString(rect: IRect): string {\r\n return \"{left:\" + rect.left + \", top:\" + rect.top + \", width:\" + rect.width + \", height:\" + rect.height + \"}\";\r\n }\r\n\r\n export function offset(rect: IRect, offsetX: number, offsetY: number): IRect {\r\n let newLeft = ((rect.left + offsetX) >= 0) ? rect.left + offsetX : 0;\r\n let newTop = ((rect.top + offsetY) >= 0) ? rect.top + offsetY : 0;\r\n\r\n return { left: newLeft, top: newTop, width: rect.width, height: rect.height };\r\n }\r\n\r\n export function inflate(rect: IRect, padding: IThickness): IRect {\r\n let result = clone(rect);\r\n if (padding) {\r\n result.left -= padding.left;\r\n result.top -= padding.top;\r\n result.width += padding.left + padding.right;\r\n result.height += padding.top + padding.bottom;\r\n }\r\n return result;\r\n }\r\n\r\n export function deflate(rect: IRect, padding: IThickness): IRect {\r\n let result = clone(rect);\r\n if (padding) {\r\n result.left += padding.left;\r\n result.top += padding.top;\r\n result.width -= padding.left + padding.right;\r\n result.height -= padding.top + padding.bottom;\r\n }\r\n return result;\r\n }\r\n\r\n export function inflateBy(rect: IRect, padding: number): IRect {\r\n return { left: rect.left - padding, top: rect.top - padding, width: rect.width + padding + padding, height: rect.height + padding + padding };\r\n }\r\n\r\n export function deflateBy(rect: IRect, padding: number): IRect {\r\n return { left: rect.left + padding, top: rect.top + padding, width: rect.width - padding - padding, height: rect.height - padding - padding };\r\n }\r\n \r\n /**\r\n * Get closest point.\r\n * \r\n * @return the closest point on the rect to the (x,y) point given.\r\n * In case the (x,y) given is inside the rect, (x,y) will be returned.\r\n * Otherwise, a point on a border will be returned.\r\n */\r\n export function getClosestPoint(rect: IRect, x: number, y: number): IPoint {\r\n return {\r\n x: Math.min(Math.max(rect.left, x), rect.left + rect.width),\r\n y: Math.min(Math.max(rect.top, y), rect.top + rect.height)\r\n };\r\n }\r\n\r\n export function equal(rect1: IRect, rect2: IRect): boolean {\r\n return rect1 === rect2 ||\r\n (rect1 !== undefined && rect2 !== undefined && rect1.left === rect2.left && rect1.top === rect2.top && rect1.width === rect2.width && rect1.height === rect2.height);\r\n }\r\n\r\n export function equalWithPrecision(rect1: IRect, rect2: IRect): boolean {\r\n return rect1 === rect2 ||\r\n (rect1 !== undefined && rect2 !== undefined &&\r\n Double.equalWithPrecision(rect1.left, rect2.left) && Double.equalWithPrecision(rect1.top, rect2.top) &&\r\n Double.equalWithPrecision(rect1.width, rect2.width) && Double.equalWithPrecision(rect1.height, rect2.height));\r\n }\r\n\r\n export function isEmpty(rect: IRect): boolean {\r\n return rect === undefined || rect === null || (rect.width === 0 && rect.height === 0);\r\n }\r\n\r\n export function containsPoint(rect: IRect, point: IPoint): boolean {\r\n if ((rect === null) || (point === null)) {\r\n return false;\r\n }\r\n return Double.lessOrEqualWithPrecision(rect.left, point.x) &&\r\n Double.lessOrEqualWithPrecision(point.x, rect.left + rect.width) &&\r\n Double.lessOrEqualWithPrecision(rect.top, point.y) &&\r\n Double.lessOrEqualWithPrecision(point.y, rect.top + rect.height);\r\n }\r\n\r\n export function isIntersecting(rect1: IRect, rect2: IRect): boolean {\r\n if (!rect1 || !rect2) {\r\n return false;\r\n }\r\n let left = Math.max(rect1.left, rect2.left);\r\n let right = Math.min(rect1.left + rect1.width, rect2.left + rect2.width);\r\n if (left > right) {\r\n return false;\r\n }\r\n let top = Math.max(rect1.top, rect2.top);\r\n let bottom = Math.min(rect1.top + rect1.height, rect2.top + rect2.height);\r\n return top <= bottom;\r\n }\r\n\r\n export function intersect(rect1: IRect, rect2: IRect): IRect {\r\n if (!rect1) {\r\n return rect2;\r\n }\r\n if (!rect2) {\r\n return rect1;\r\n }\r\n let left = Math.max(rect1.left, rect2.left);\r\n let top = Math.max(rect1.top, rect2.top);\r\n let right = Math.min(rect1.left + rect1.width, rect2.left + rect2.width);\r\n let bottom = Math.min(rect1.top + rect1.height, rect2.top + rect2.height);\r\n if (left <= right && top <= bottom) {\r\n return { left: left, top: top, width: right - left, height: bottom - top };\r\n } else {\r\n return { left: 0, top: 0, width: 0, height: 0 };\r\n }\r\n }\r\n\r\n export function combine(rect1: IRect, rect2: IRect): IRect {\r\n if (!rect1) {\r\n return rect2;\r\n }\r\n if (!rect2) {\r\n return rect1;\r\n }\r\n let left = Math.min(rect1.left, rect2.left);\r\n let top = Math.min(rect1.top, rect2.top);\r\n let right = Math.max(rect1.left + rect1.width, rect2.left + rect2.width);\r\n let bottom = Math.max(rect1.top + rect1.height, rect2.top + rect2.height);\r\n\r\n return { left: left, top: top, width: right - left, height: bottom - top };\r\n }\r\n\r\n export function parseRect(value: any, defaultValue?: IRect): IRect {\r\n if (value === null) {\r\n return (defaultValue === undefined) ? null : defaultValue;\r\n } else if (value === undefined) {\r\n return (defaultValue === undefined) ? null : defaultValue;\r\n } else {\r\n if (value.length === 4) {\r\n return { left: Utility.parseNumber(value[0]), top: Utility.parseNumber(value[1]), width: Utility.parseNumber(value[2]), height: Utility.parseNumber(value[3]) };\r\n } else if (typeof value === \"string\") {\r\n let parts = (<string>value).split(\",\");\r\n if (parts.length !== 4) {\r\n return (defaultValue === undefined) ? null : defaultValue;\r\n }\r\n return {\r\n left: Utility.parseNumber(parts[0]), top: Utility.parseNumber(parts[1]), width: Utility.parseNumber(parts[2]), height: Utility.parseNumber(parts[3])\r\n };\r\n }\r\n else if ((value.length !== 4) && (typeof value !== \"string\")) {\r\n return (defaultValue === undefined) ? null : defaultValue;\r\n }\r\n else {\r\n return { left: Utility.parseNumber(value.left), top: Utility.parseNumber(value.top), width: Utility.parseNumber(value.width), height: Utility.parseNumber(value.height) };\r\n }\r\n }\r\n }\r\n }\r\n\r\n export module Thickness {\r\n\r\n export function inflate(thickness: IThickness, other: IThickness): IThickness {\r\n let result = clone(thickness);\r\n if (other) {\r\n result.left = thickness.left + other.left;\r\n result.right = thickness.right + other.right;\r\n result.bottom = thickness.bottom + other.bottom;\r\n result.top = thickness.top + other.top;\r\n }\r\n return result;\r\n }\r\n\r\n export function getWidth(thickness: IThickness): number {\r\n return thickness.left + thickness.right;\r\n }\r\n\r\n export function getHeight(thickness: IThickness): number {\r\n return thickness.top + thickness.bottom;\r\n }\r\n\r\n export function clone(thickness: IThickness): IThickness {\r\n return (thickness !== null) ? { left: thickness.left, top: thickness.top, right: thickness.right, bottom: thickness.bottom } : null;\r\n }\r\n\r\n export function equals(thickness: IThickness, other: IThickness): boolean {\r\n return thickness !== undefined && thickness !== null && other !== undefined && other !== null && thickness.left === other.left && thickness.bottom === other.bottom && thickness.right === other.right && thickness.top === other.top;\r\n }\r\n\r\n export function flipHorizontal(thickness: IThickness): void {\r\n let temp = thickness.right;\r\n thickness.right = thickness.left;\r\n thickness.left = temp;\r\n }\r\n\r\n export function flipVertical(thickness: IThickness): void {\r\n let top = thickness.top;\r\n thickness.top = thickness.bottom;\r\n thickness.bottom = top;\r\n }\r\n\r\n export function toString(thickness: IThickness): string {\r\n return \"{top:\" + thickness.top + \", left:\" + thickness.left + \", right:\" + thickness.right + \", bottom:\" + thickness.bottom + \"}\";\r\n }\r\n\r\n export function toCssString(thickness: IThickness): string {\r\n return thickness.top + \"px \" + thickness.right + \"px \" + thickness.bottom + \"px \" + thickness.left + \"px\";\r\n }\r\n\r\n export function isEmpty(thickness: IThickness): boolean {\r\n return thickness.left === 0 && thickness.top === 0 && thickness.right === 0 && thickness.bottom === 0;\r\n }\r\n\r\n export function equal(thickness1: IThickness, thickness2: IThickness): boolean {\r\n return thickness1 === thickness2 ||\r\n (thickness1 !== undefined && thickness2 !== undefined && thickness1.left === thickness2.left && thickness1.top === thickness2.top && thickness1.right === thickness2.right && thickness1.bottom === thickness2.bottom);\r\n }\r\n\r\n export function equalWithPrecision(thickness1: IThickness, thickness2: IThickness): boolean {\r\n return thickness1 === thickness2 ||\r\n (thickness1 !== undefined && thickness2 !== undefined &&\r\n Double.equalWithPrecision(thickness1.left, thickness2.left) && Double.equalWithPrecision(thickness1.top, thickness2.top) &&\r\n Double.equalWithPrecision(thickness1.right, thickness2.right) && Double.equalWithPrecision(thickness1.bottom, thickness2.bottom));\r\n }\r\n\r\n export function parseThickness(value: any, defaultValue?: IThickness, resetValue?: any): IThickness {\r\n if (value === null) {\r\n return (defaultValue === undefined) ? null : defaultValue;\r\n } else if (value === undefined) {\r\n return (defaultValue === undefined) ? null : defaultValue;\r\n } else {\r\n if (value.length === 4) {\r\n return { left: Utility.parseNumber(value[0]), top: Utility.parseNumber(value[1]), right: Utility.parseNumber(value[2]), bottom: Utility.parseNumber(value[3]) };\r\n } else if (typeof value === \"string\") {\r\n let parts = (<string>value).split(\",\");\r\n if (parts.length !== 4) {\r\n return (defaultValue === undefined) ? null : defaultValue;\r\n }\r\n return { left: Utility.parseNumber(parts[0]), top: Utility.parseNumber(parts[1]), right: Utility.parseNumber(parts[2]), bottom: Utility.parseNumber(parts[3]) };\r\n } else if ((value.length !== 4) && (typeof value !== \"string\")) {\r\n return (defaultValue === undefined) ? null : defaultValue;\r\n }\r\n else {\r\n return { left: Utility.parseNumber(value.left), top: Utility.parseNumber(value.top), right: Utility.parseNumber(value.right), bottom: Utility.parseNumber(value.bottom) };\r\n }\r\n }\r\n }\r\n }\r\n\r\n export module Vector {\r\n\r\n export function isEmpty(vector: IVector): boolean {\r\n return vector.x === 0 && vector.y === 0;\r\n }\r\n\r\n export function equals(vector: IVector, other: IPoint): boolean {\r\n return vector !== undefined && vector !== null && other !== undefined && other !== null && vector.x === other.x && vector.y === other.y;\r\n }\r\n\r\n export function clone(vector: IVector): IVector {\r\n return (vector !== null) ? { x: vector.x, y: vector.y } : null;\r\n }\r\n\r\n export function toString(vector: IVector): string {\r\n return \"{x:\" + vector.x + \", y:\" + vector.y + \"}\";\r\n }\r\n\r\n export function getLength(vector: IVector): number {\r\n return Math.sqrt(vector.x * vector.x + vector.y * vector.y);\r\n }\r\n\r\n export function getLengthSqr(vector: IVector): number {\r\n return vector.x * vector.x + vector.y * vector.y;\r\n }\r\n\r\n export function scale(vector: IVector, scalar: number): IVector {\r\n return { x: vector.x * scalar, y: vector.y * scalar };\r\n }\r\n\r\n export function normalize(vector: IVector): IVector {\r\n return !isEmpty(vector) ? scale(vector, 1 / getLength(vector)) : vector;\r\n }\r\n\r\n export function rotate90DegCW(vector: IVector): IVector {\r\n return { x: vector.y, y: -vector.x };\r\n }\r\n\r\n export function rotate90DegCCW(vector: IVector): IVector {\r\n return { x: -vector.y, y: vector.x };\r\n }\r\n\r\n export function rotate(vector: IVector, angle: number): IVector {\r\n let newX = vector.x * Math.cos(angle) - vector.y * Math.sin(angle);\r\n let newY = vector.x * Math.sin(angle) + vector.y * Math.cos(angle);\r\n return { x: newX, y: newY };\r\n }\r\n\r\n export function equal(vector1: IVector, vector2: IVector): boolean {\r\n return vector1 === vector2 ||\r\n (vector1 !== undefined && vector2 !== undefined && vector1.x === vector2.x && vector1.y === vector2.y);\r\n }\r\n\r\n export function equalWithPrecision(vector1: IVector, vector2: IVector): boolean {\r\n return vector1 === vector2 ||\r\n (vector1 !== undefined && vector2 !== undefined && Double.equalWithPrecision(vector1.x, vector2.x) && Double.equalWithPrecision(vector1.y, vector2.y));\r\n }\r\n\r\n export function add(vect1: IVector, vect2: IVector): IVector {\r\n if (!vect1 || !vect2) {\r\n return undefined;\r\n }\r\n return { x: vect1.x + vect2.x, y: vect1.y + vect2.y };\r\n }\r\n\r\n export function subtract(vect1: IVector, vect2: IVector): IVector {\r\n if (!vect1 || !vect2) {\r\n return undefined;\r\n }\r\n return { x: vect1.x - vect2.x, y: vect1.y - vect2.y };\r\n }\r\n\r\n export function dotProduct(vect1: IVector, vect2: IVector): number {\r\n if (!vect1 || !vect2) {\r\n return undefined;\r\n }\r\n return vect1.x * vect2.x + vect1.y * vect2.y;\r\n }\r\n\r\n export function getDeltaVector(p0: IPoint, p1: IPoint): IVector {\r\n if (!p0 || !p1) {\r\n return undefined;\r\n }\r\n return { x: p1.x - p0.x, y: p1.y - p0.y };\r\n }\r\n }\r\n\r\n }\r\n}\n\n\n/** WEBPACK FOOTER **\n ** ./src/Clients/Visuals/common/shapes.ts\n **/","/*\r\n * Power BI Visualizations\r\n *\r\n * Copyright (c) Microsoft Corporation\r\n * All rights reserved. \r\n * MIT License\r\n *\r\n * Permission is hereby granted, free of charge, to any person obtaining a copy\r\n * of this software and associated documentation files (the \"\"Software\"\"), to deal\r\n * in the Software without restriction, including without limitation the rights\r\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\r\n * copies of the Software, and to permit persons to whom the Software is\r\n * furnished to do so, subject to the following conditions:\r\n * \r\n * The above copyright notice and this permission notice shall be included in \r\n * all copies or substantial portions of the Software.\r\n * \r\n * THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR \r\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, \r\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE \r\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER \r\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\r\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\r\n * THE SOFTWARE.\r\n */\r\n\r\n/// <reference path=\"../_references.ts\"/>\r\n\r\nmodule powerbi.visuals {\r\n import PixelConverter = jsCommon.PixelConverter;\r\n import SQExpr = powerbi.data.SQExpr;\r\n import SQExprBuilder = powerbi.data.SQExprBuilder;\r\n import SemanticFilter = powerbi.data.SemanticFilter;\r\n\r\n /** Utility class for slicer*/\r\n export module SlicerUtil {\r\n /** CSS selectors for slicer elements. */\r\n export module Selectors {\r\n import createClassAndSelector = jsCommon.CssConstants.createClassAndSelector;\r\n\r\n export const HeaderContainer = createClassAndSelector('headerContainer');\r\n export const Header = createClassAndSelector('slicerHeader');\r\n export const TitleHeader = createClassAndSelector('titleHeader');\r\n export const HeaderText = createClassAndSelector('headerText');\r\n export const Body = createClassAndSelector('slicerBody');\r\n export const Label = createClassAndSelector('slicerLabel');\r\n export const LabelText = createClassAndSelector('slicerText');\r\n export const LabelImage = createClassAndSelector('slicerImage');\r\n export const CountText = createClassAndSelector('slicerCountText');\r\n export const Clear = createClassAndSelector('clear');\r\n export const SearchHeader = createClassAndSelector('searchHeader');\r\n export const SearchInput = createClassAndSelector('searchInput');\r\n export const SearchHeaderCollapsed = createClassAndSelector('collapsed');\r\n export const SearchHeaderShow = createClassAndSelector('show');\r\n export const MultiSelectEnabled = createClassAndSelector('isMultiSelectEnabled');\r\n }\r\n\r\n /** Const declarations*/\r\n export module DisplayNameKeys {\r\n export const Clear = 'Slicer_Clear';\r\n export const SelectAll = 'Slicer_SelectAll';\r\n export const Search = 'SearchBox_Text';\r\n }\r\n\r\n /** Helper class for slicer settings */\r\n export module SettingsHelper {\r\n export function areSettingsDefined(data: SlicerData): boolean {\r\n return data != null && data.slicerSettings != null;\r\n }\r\n }\r\n\r\n /** Helper class for handling slicer default value */\r\n export module DefaultValueHandler {\r\n export function getIdentityFields(dataView: DataView): SQExpr[] {\r\n if (!dataView)\r\n return;\r\n\r\n let dataViewCategorical = dataView.categorical;\r\n if (!dataViewCategorical || _.isEmpty(dataViewCategorical.categories))\r\n return;\r\n\r\n return <SQExpr[]>dataViewCategorical.categories[0].identityFields;\r\n }\r\n }\r\n\r\n export function getContainsFilter(expr: SQExpr, containsText: string): SemanticFilter {\r\n let containsTextExpr = SQExprBuilder.text(containsText);\r\n let filterExpr = SQExprBuilder.contains(expr, containsTextExpr);\r\n return SemanticFilter.fromSQExpr(filterExpr);\r\n }\r\n\r\n // Compare the sqExpr of the scopeId with sqExprs of the retained values. \r\n // If match found, remove the item from the retainedValues list, and return true, \r\n // otherwise return false.\r\n export function tryRemoveValueFromRetainedList(value: DataViewScopeIdentity, selectedScopeIds: DataViewScopeIdentity[], caseInsensitive?: boolean): boolean {\r\n if (!value || _.isEmpty(selectedScopeIds))\r\n return false;\r\n\r\n for (let i = 0, len = selectedScopeIds.length; i < len; i++) {\r\n let retainedValueScopeId = selectedScopeIds[i];\r\n if (DataViewScopeIdentity.equals(value, retainedValueScopeId, caseInsensitive)) {\r\n selectedScopeIds.splice(i, 1);\r\n return true;\r\n }\r\n }\r\n\r\n return false;\r\n }\r\n\r\n /** Helper class for creating and measuring slicer DOM elements */\r\n export class DOMHelper {\r\n private static SearchInputHeight = 20;\r\n\r\n public createSlicerHeader(hostServices: IVisualHostServices): HTMLElement {\r\n let slicerHeaderDiv = document.createElement('div');\r\n slicerHeaderDiv.className = Selectors.Header.class;\r\n\r\n let slicerHeader: D3.Selection = d3.select(slicerHeaderDiv);\r\n let slicerTitle = slicerHeader.append('h2')\r\n .classed(Selectors.TitleHeader.class, true);\r\n slicerTitle.append('span')\r\n .classed(Selectors.Clear.class, true)\r\n .attr('title', hostServices.getLocalizedString(DisplayNameKeys.Clear));\r\n slicerTitle.append('div').classed(Selectors.HeaderText.class, true);\r\n let slicerSearch = slicerHeader.append('div')\r\n .classed(Selectors.SearchHeader.class, true)\r\n .classed(Selectors.SearchHeaderCollapsed.class, true);\r\n slicerSearch.append('span')\r\n .classed('powervisuals-glyph search', true)\r\n .attr('title', hostServices.getLocalizedString(DisplayNameKeys.Search));\r\n\r\n slicerSearch.append('input')\r\n .attr('type', 'text')\r\n .classed(Selectors.SearchInput.class, true)\r\n .attr('drag-resize-disabled', 'true');\r\n \r\n return slicerHeaderDiv;\r\n }\r\n\r\n public getHeaderTextProperties(settings: SlicerSettings): TextProperties {\r\n let headerTextProperties: TextProperties = {\r\n fontFamily: Font.Family.regular.css,\r\n fontSize: '10px'\r\n };\r\n if (settings.header.show) {\r\n headerTextProperties.fontSize = PixelConverter.fromPoint(settings.header.textSize);\r\n }\r\n return headerTextProperties;\r\n }\r\n\r\n public getSlicerBodyViewport(currentViewport: IViewport, settings: SlicerSettings, headerTextProperties: TextProperties): IViewport {\r\n let headerHeight = (settings.header.show) ? this.getHeaderHeight(settings, headerTextProperties) : 0;\r\n let searchHeaderHight = settings.search.enabled ? DOMHelper.SearchInputHeight : 0;\r\n let slicerBodyHeight = currentViewport.height - (headerHeight + settings.header.borderBottomWidth + searchHeaderHight);\r\n return {\r\n height: slicerBodyHeight,\r\n width: currentViewport.width\r\n };\r\n }\r\n\r\n // TODO: Slicer body height and width update should be done through less file\r\n public updateSlicerBodyDimensions(currentViewport: IViewport, slicerBody: D3.Selection, settings: SlicerSettings): void {\r\n let slicerViewport = this.getSlicerBodyViewport(currentViewport, settings, this.getHeaderTextProperties(settings));\r\n slicerBody.style({\r\n 'height': PixelConverter.toString(slicerViewport.height),\r\n 'width': PixelConverter.toString(slicerViewport.width),\r\n });\r\n }\r\n\r\n public getHeaderHeight(settings: SlicerSettings, textProperties: TextProperties): number {\r\n return TextMeasurementService.estimateSvgTextHeight(this.getTextProperties(settings.header.textSize, textProperties)) + settings.general.outlineWeight;\r\n }\r\n\r\n public getRowHeight(settings: SlicerSettings, textProperties: TextProperties): number {\r\n return TextMeasurementService.estimateSvgTextHeight(this.getTextProperties(settings.slicerText.textSize, textProperties)) + this.getRowsOutlineWidth(settings.slicerText.outline, settings.general.outlineWeight);\r\n }\r\n\r\n public styleSlicerHeader(slicerHeader: D3.Selection, settings: SlicerSettings, headerText: string): void {\r\n let titleHeader = slicerHeader.select(SlicerUtil.Selectors.TitleHeader.selector);\r\n let searchHeader = slicerHeader.select(SlicerUtil.Selectors.SearchHeader.selector);\r\n if (settings.header.show) {\r\n titleHeader.style('display', 'block');\r\n let headerTextElement = slicerHeader.select(Selectors.HeaderText.selector)\r\n .text(headerText);\r\n this.setSlicerHeaderTextStyle(titleHeader, headerTextElement, settings, settings.search.enabled);\r\n } else {\r\n titleHeader.style('display', 'none');\r\n }\r\n\r\n if (settings.search.enabled) {\r\n searchHeader.classed(Selectors.SearchHeaderShow.class, true);\r\n searchHeader.classed(Selectors.SearchHeaderCollapsed.class, false);\r\n } else {\r\n searchHeader.classed(Selectors.SearchHeaderShow.class, false);\r\n searchHeader.classed(Selectors.SearchHeaderCollapsed.class, true);\r\n }\r\n }\r\n\r\n public setSlicerTextStyle(slicerText: D3.Selection, settings: SlicerSettings): void {\r\n slicerText\r\n .style({\r\n 'color': settings.slicerText.color,\r\n 'background-color': settings.slicerText.background,\r\n 'border-style': 'solid',\r\n 'border-color': settings.general.outlineColor,\r\n 'border-width': VisualBorderUtil.getBorderWidth(settings.slicerText.outline, settings.general.outlineWeight),\r\n 'font-size': PixelConverter.fromPoint(settings.slicerText.textSize),\r\n });\r\n }\r\n\r\n public getRowsOutlineWidth(outlineElement: string, outlineWeight: number): number {\r\n switch (outlineElement) {\r\n case outline.none:\r\n case outline.leftRight:\r\n return 0;\r\n case outline.bottomOnly:\r\n case outline.topOnly:\r\n return outlineWeight;\r\n case outline.topBottom:\r\n case outline.frame:\r\n return outlineWeight * 2;\r\n default:\r\n return 0;\r\n }\r\n }\r\n\r\n private setSlicerHeaderTextStyle(slicerHeader: D3.Selection, headerTextElement: D3.Selection, settings: SlicerSettings, searchEnabled: boolean): void {\r\n let hideOutline = false;\r\n\r\n // When search is enabled, we will hide the default outline if the outline properties haven't been customized by user.\r\n if (searchEnabled) {\r\n let defaultSetting = Slicer.DefaultStyleProperties();\r\n hideOutline = (settings.header.outline === defaultSetting.header.outline\r\n && settings.general.outlineWeight === defaultSetting.general.outlineWeight\r\n && settings.general.outlineColor === defaultSetting.general.outlineColor);\r\n }\r\n\r\n slicerHeader\r\n .style({\r\n 'border-style': hideOutline ? 'none': 'solid',\r\n 'border-color': settings.general.outlineColor,\r\n 'border-width': VisualBorderUtil.getBorderWidth(settings.header.outline, settings.general.outlineWeight),\r\n });\r\n\r\n headerTextElement\r\n .style({\r\n 'color': settings.header.fontColor,\r\n 'background-color': settings.header.background,\r\n 'font-size': PixelConverter.fromPoint(settings.header.textSize),\r\n });\r\n }\r\n\r\n private getTextProperties(textSize: number, textProperties: TextProperties): TextProperties {\r\n textProperties.fontSize = PixelConverter.fromPoint(textSize);\r\n return textProperties;\r\n }\r\n }\r\n }\r\n}\n\n\n/** WEBPACK FOOTER **\n ** ./src/Clients/Visuals/common/slicerUtil.ts\n **/","/*\r\n * Power BI Visualizations\r\n *\r\n * Copyright (c) Microsoft Corporation\r\n * All rights reserved. \r\n * MIT License\r\n *\r\n * Permission is hereby granted, free of charge, to any person obtaining a copy\r\n * of this software and associated documentation files (the \"\"Software\"\"), to deal\r\n * in the Software without restriction, including without limitation the rights\r\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\r\n * copies of the Software, and to permit persons to whom the Software is\r\n * furnished to do so, subject to the following conditions:\r\n * \r\n * The above copyright notice and this permission notice shall be included in \r\n * all copies or substantial portions of the Software.\r\n * \r\n * THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR \r\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, \r\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE \r\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER \r\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\r\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\r\n * THE SOFTWARE.\r\n */\r\n\r\n/// <reference path=\"../_references.ts\"/>\r\n\r\nmodule powerbi.visuals {\r\n /**\r\n * Contains functions/constants to aid in adding tooltips. \r\n */\r\n export module tooltipUtils {\r\n\r\n export function tooltipUpdate(selection: D3.Selection, tooltips: string[]): void {\r\n if (tooltips.length === 0)\r\n return;\r\n\r\n debug.assert(selection.length === tooltips.length || selection[0].length === tooltips.length, 'data length should match dom element count');\r\n let titles = selection.selectAll('title');\r\n let titlesUpdate = titles.data((d, i) => [tooltips[i]]);\r\n\r\n titlesUpdate.enter().append('title');\r\n titlesUpdate.exit().remove();\r\n titlesUpdate.text((d) => d);\r\n }\r\n }\r\n}\n\n\n/** WEBPACK FOOTER **\n ** ./src/Clients/Visuals/common/tooltipUtils.ts\n **/","/*\r\n * Power BI Visualizations\r\n *\r\n * Copyright (c) Microsoft Corporation\r\n * All rights reserved. \r\n * MIT License\r\n *\r\n * Permission is hereby granted, free of charge, to any person obtaining a copy\r\n * of this software and associated documentation files (the \"\"Software\"\"), to deal\r\n * in the Software without restriction, including without limitation the rights\r\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\r\n * copies of the Software, and to permit persons to whom the Software is\r\n * furnished to do so, subject to the following conditions:\r\n * \r\n * The above copyright notice and this permission notice shall be included in \r\n * all copies or substantial portions of the Software.\r\n * \r\n * THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR \r\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, \r\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE \r\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER \r\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\r\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\r\n * THE SOFTWARE.\r\n */\r\n\r\n/// <reference path=\"../_references.ts\"/>\r\n\r\nmodule powerbi.visuals {\r\n /**\r\n * Contains functions/constants to aid in SVG manupilation. \r\n */\r\n export module SVGUtil {\r\n /** \r\n * Very small values, when stringified, may be converted to scientific notation and cause a temporarily \r\n * invalid attribute or style property value.\r\n * For example, the number 0.0000001 is converted to the string \"1e-7\". \r\n * This is particularly noticeable when interpolating opacity values.\r\n * To avoid scientific notation, start or end the transition at 1e-6,\r\n * which is the smallest value that is not stringified in exponential notation.\r\n */\r\n export const AlmostZero = 1e-6;\r\n\r\n /**\r\n * Creates a translate string for use with the SVG transform call.\r\n */\r\n export function translate(x: number, y: number): string {\r\n debug.assertValue(x, 'x');\r\n debug.assertValue(y, 'y');\r\n return 'translate(' + x + ',' + y + ')';\r\n }\r\n\r\n /**\r\n * Creates a translateX string for use with the SVG transform call.\r\n */\r\n export function translateXWithPixels(x: number): string {\r\n debug.assertValue(x, 'x');\r\n return 'translateX(' + x + 'px)';\r\n }\r\n\r\n export function translateWithPixels(x: number, y: number): string {\r\n debug.assertValue(x, 'x');\r\n debug.assertValue(y, 'y');\r\n return 'translate(' + x + 'px,' + y + 'px)';\r\n }\r\n\r\n /**\r\n * Creates a translate + rotate string for use with the SVG transform call.\r\n */\r\n export function translateAndRotate(x: number, y: number, px: number, py: number, angle: number): string {\r\n debug.assertValue(x, 'x');\r\n debug.assertValue(y, 'y');\r\n debug.assertValue(px, 'px');\r\n debug.assertValue(py, 'py');\r\n debug.assertValue(angle, 'angle');\r\n\r\n return 'transform', \"translate(\"\r\n + x + \",\" + y + \")\"\r\n + \" rotate(\" + angle + \",\" + px + \",\" + py + \")\";\r\n }\r\n\r\n /**\r\n * Creates a scale string for use in a CSS transform property.\r\n */\r\n export function scale(scale: number): string {\r\n debug.assertValue(scale, 'scale');\r\n\r\n return `scale(${scale})`;\r\n }\r\n\r\n /**\r\n * Creates a translate + scale string for use with the SVG transform call.\r\n */\r\n export function translateAndScale(x: number, y: number, ratio: number): string {\r\n debug.assertValue(x, 'x');\r\n debug.assertValue(y, 'y');\r\n debug.assertValue(ratio, 'ratio');\r\n\r\n return 'transform', \"translate(\"\r\n + x + \",\" + y + \")\"\r\n + \" scale(\" + ratio + \")\";\r\n }\r\n\r\n /**\r\n * Creates a transform origin string for use in a CSS transform-origin property.\r\n */\r\n export function transformOrigin(xOffset: string, yOffset: string): string {\r\n debug.assertValue(xOffset, 'xOffset');\r\n debug.assertValue(yOffset, 'yOffset');\r\n\r\n return `${xOffset} ${yOffset}`;\r\n }\r\n\r\n /**\r\n * Forces all D3 transitions to complete.\r\n * Normally, zero-delay transitions are executed after an instantaneous delay (<10ms). \r\n * This can cause a brief flicker if the browser renders the page twice: once at the end of the first event loop, \r\n * then again immediately on the first timer callback. By flushing the timer queue at the end of the first event loop,\r\n * you can run any zero-delay transitions immediately and avoid the flicker.\r\n * \r\n * These flickers are noticable on IE, and with a large number of webviews(not recommend you ever do this) on iOS.\r\n */\r\n export function flushAllD3Transitions() {\r\n let now = Date.now;\r\n Date.now = function () { return Infinity; };\r\n d3.timer.flush();\r\n Date.now = now;\r\n }\r\n\r\n /**\r\n * Wrapper for flushAllD3Transitions.\r\n */\r\n export function flushAllD3TransitionsIfNeeded(options: VisualInitOptions | AnimationOptions) {\r\n if (!options)\r\n return;\r\n\r\n let animationOptions: AnimationOptions = <AnimationOptions>options;\r\n\r\n let asVisualInitOptions = <VisualInitOptions>options;\r\n if (asVisualInitOptions.animation)\r\n animationOptions = asVisualInitOptions.animation;\r\n\r\n if (animationOptions && animationOptions.transitionImmediate) {\r\n flushAllD3Transitions();\r\n }\r\n }\r\n\r\n /**\r\n * There is a known bug in IE10 that causes cryptic crashes for SVG elements with a null 'd' attribute:\r\n * https://github.com/mbostock/d3/issues/1737\r\n */\r\n export function ensureDAttribute(pathElement: D3.D3Element) {\r\n if (!pathElement.getAttribute('d')) {\r\n pathElement.setAttribute('d', '');\r\n }\r\n }\r\n\r\n /**\r\n * In IE10, it is possible to return SVGPoints with NaN members.\r\n */\r\n export function ensureValidSVGPoint(point: SVGPoint) {\r\n if (isNaN(point.x)) {\r\n point.x = 0;\r\n }\r\n if (isNaN(point.y)) {\r\n point.y = 0;\r\n }\r\n }\r\n\r\n /**\r\n * Parse the Transform string with value 'translate(x,y)'.\r\n * In Chrome for the translate(position) string the delimiter \r\n * is a comma and in IE it is a spaceso checking for both.\r\n */\r\n export function parseTranslateTransform(input: string): { x: string; y: string } {\r\n if (!input || input.length === 0) { // Interpet falsy and empty string as a no-op translate\r\n return {\r\n x: \"0\",\r\n y: \"0\",\r\n };\r\n }\r\n let translateCoordinates = input.split(/[\\s,]+/);\r\n\r\n debug.assertValue(translateCoordinates, 'translateCoordinates');\r\n debug.assert(translateCoordinates.length > 0, 'translate array must atleast have one value');\r\n\r\n let yValue = '0';\r\n let xValue: string;\r\n let xCoord = translateCoordinates[0];\r\n\r\n // Y coordinate is ommited in I.E if it is 0, so need to check against that\r\n if (translateCoordinates.length === 1) {\r\n // 10 refers to the length of 'translate('\r\n xValue = xCoord.substring(10, xCoord.length - 1);\r\n } else {\r\n let yCoord = translateCoordinates[1];\r\n yValue = yCoord.substring(0, yCoord.length - 1);\r\n // 10 refers to the length of 'translate('\r\n xValue = xCoord.substring(10, xCoord.length);\r\n }\r\n\r\n return {\r\n x: xValue,\r\n y: yValue\r\n };\r\n }\r\n\r\n /**\r\n * Create an arrow.\r\n */\r\n export function createArrow(width: number, height: number, rotate: number): { path: string; transform: string } {\r\n let transform = \"rotate(\" + rotate + \" \" + width / 2 + \" \" + height / 2 + \")\";\r\n let path = \"M0 0\";\r\n path += \"L0 \" + height;\r\n path += \"L\" + width + \" \" + height / 2 + \" Z\";\r\n return {\r\n path: path,\r\n transform: transform\r\n };\r\n }\r\n\r\n /**\r\n * Use the ratio of the scaled bounding rect and the SVG DOM bounding box to get the x and y transform scale values\r\n * @deprecated This function is unreliable across browser implementations, prefer to use SVGScaleDetector if needed.\r\n */\r\n export function getTransformScaleRatios(svgElement: SVGSVGElement): Point {\r\n debug.assertFail('deprecated');\r\n\r\n if (svgElement != null) {\r\n let scaledRect = svgElement.getBoundingClientRect();\r\n let domRect = svgElement.getBBox();\r\n if (domRect.height > 0 && domRect.width > 0) {\r\n return {\r\n x: scaledRect.width / domRect.width,\r\n y: scaledRect.height / domRect.height\r\n };\r\n }\r\n }\r\n return { x: 1, y: 1 };\r\n }\r\n }\r\n\r\n export class SVGScaleDetector {\r\n private scaleDetectorElement: SVGRectElement;\r\n\r\n constructor(svgElement: D3.Selection) {\r\n this.scaleDetectorElement = <SVGRectElement>svgElement\r\n .append('rect') // Using a <rect> which should have a reliable bounding box across browser implementations.\r\n .classed('scale-detector', true)\r\n .attr({\r\n width: 1,\r\n height: 1,\r\n 'stroke-width': '0px',\r\n fill: 'none',\r\n })\r\n .node();\r\n }\r\n\r\n public getScale(): Point {\r\n let scaledRect = this.scaleDetectorElement.getBoundingClientRect();\r\n let domRect = this.scaleDetectorElement.getBBox();\r\n if (domRect.height > 0 && domRect.width > 0) {\r\n return {\r\n x: scaledRect.width / domRect.width,\r\n y: scaledRect.height / domRect.height\r\n };\r\n }\r\n\r\n return {\r\n x: 1,\r\n y: 1\r\n };\r\n }\r\n }\r\n}\n\n\n/** WEBPACK FOOTER **\n ** ./src/Clients/Visuals/common/svgUtil.ts\n **/","/*\r\n * Power BI Visualizations\r\n *\r\n * Copyright (c) Microsoft Corporation\r\n * All rights reserved. \r\n * MIT License\r\n *\r\n * Permission is hereby granted, free of charge, to any person obtaining a copy\r\n * of this software and associated documentation files (the \"\"Software\"\"), to deal\r\n * in the Software without restriction, including without limitation the rights\r\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\r\n * copies of the Software, and to permit persons to whom the Software is\r\n * furnished to do so, subject to the following conditions:\r\n * \r\n * The above copyright notice and this permission notice shall be included in \r\n * all copies or substantial portions of the Software.\r\n * \r\n * THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR \r\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, \r\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE \r\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER \r\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\r\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\r\n * THE SOFTWARE.\r\n */\r\n\r\n/// <reference path=\"../_references.ts\"/>\r\n\r\nmodule powerbi.visuals {\r\n /**\r\n * Contains functions/constants to aid in text manupilation. \r\n */\r\n export module TextUtil {\r\n \r\n /**\r\n * Remove breaking spaces from given string and replace by none breaking space (&nbsp).\r\n */\r\n export function removeBreakingSpaces(str: string): string {\r\n return str.toString().replace(new RegExp(' ', 'g'), '&nbsp');\r\n }\r\n\r\n /**\r\n * Remove ellipses from a given string\r\n */\r\n export function removeEllipses(str: string): string {\r\n return str.replace(/…/g, '');\r\n }\r\n\r\n /**\r\n * Replace every whitespace (0x20) with Non-Breaking Space (0xA0)\r\n * @param {string} txt String to replace White spaces\r\n * @returns Text after replcing white spaces\r\n */\r\n export function replaceSpaceWithNBSP(txt: string): string {\r\n if (txt != null)\r\n return txt.replace(/ /g, \"\\xA0\");\r\n }\r\n }\r\n}\n\n\n/** WEBPACK FOOTER **\n ** ./src/Clients/Visuals/common/textUtil.ts\n **/","/*\r\n * Power BI Visualizations\r\n *\r\n * Copyright (c) Microsoft Corporation\r\n * All rights reserved.\r\n * MIT License\r\n *\r\n * Permission is hereby granted, free of charge, to any person obtaining a copy\r\n * of this software and associated documentation files (the \"\"Software\"\"), to deal\r\n * in the Software without restriction, including without limitation the rights\r\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\r\n * copies of the Software, and to permit persons to whom the Software is\r\n * furnished to do so, subject to the following conditions:\r\n *\r\n * The above copyright notice and this permission notice shall be included in\r\n * all copies or substantial portions of the Software.\r\n *\r\n * THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\r\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\r\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\r\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\r\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\r\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\r\n * THE SOFTWARE.\r\n */\r\n\r\n/// <reference path=\"../_references.ts\"/>\r\n\r\nmodule powerbi.visuals {\r\n import DataRoleHelper = powerbi.data.DataRoleHelper;\r\n\r\n export interface GradientSettings {\r\n diverging: boolean;\r\n minColor: any;\r\n midColor?: any;\r\n maxColor: any;\r\n minValue?: number;\r\n midValue?: number;\r\n maxValue?: number;\r\n };\r\n\r\n interface GradientColors {\r\n minColor: string;\r\n midColor?: string;\r\n maxColor: string;\r\n }\r\n\r\n export module GradientUtils {\r\n\r\n import SQExprBuilder = powerbi.data.SQExprBuilder;\r\n import DataViewObjectPropertyDefinition = powerbi.data.DataViewObjectPropertyDefinition;\r\n const DefaultMidColor: string = \"#ffffff\";\r\n const DefaultColor: string = DefaultMidColor;\r\n const DataPointPropertyIdentifier: string = \"dataPoint\";\r\n const FillRulePropertyIdentifier: string = \"fillRule\";\r\n\r\n export function getFillRuleRole(objectDescs: powerbi.data.DataViewObjectDescriptors): string {\r\n if (!objectDescs)\r\n return;\r\n\r\n for (let objectName in objectDescs) {\r\n let objectDesc = objectDescs[objectName];\r\n for (let propertyName in objectDesc.properties) {\r\n let propertyDesc = objectDesc.properties[propertyName];\r\n if (propertyDesc.type && propertyDesc.type[FillRulePropertyIdentifier]) {\r\n return propertyDesc.rule.inputRole;\r\n }\r\n }\r\n }\r\n }\r\n\r\n export function shouldShowGradient(visualConfig): boolean {\r\n let isShowGradienCard: boolean = visualConfig && visualConfig.query && visualConfig.query.projections && visualConfig.query.projections['Gradient'] ? true : false;\r\n return isShowGradienCard;\r\n }\r\n\r\n export function getUpdatedGradientSettings(gradientObject: data.DataViewObjectDefinitions): GradientSettings {\r\n let gradientSettings: GradientSettings;\r\n\r\n if (gradientObject && !$.isEmptyObject(gradientObject)) {\r\n\r\n gradientSettings = getDefaultGradientSettings();\r\n\r\n for (let propertyName in gradientSettings) {\r\n let hasProperty: boolean = (<Object>gradientObject).hasOwnProperty(propertyName);\r\n if (hasProperty) {\r\n let value: any = gradientObject[propertyName];\r\n\r\n if (value && value.solid && value.solid.color) {\r\n value = value.solid.color;\r\n }\r\n\r\n gradientSettings[propertyName] = value;\r\n }\r\n }\r\n }\r\n\r\n return gradientSettings;\r\n }\r\n\r\n export function getGradientMeasureIndex(dataViewCategorical: DataViewCategorical): number {\r\n if (dataViewCategorical && dataViewCategorical.values && dataViewCategorical.values.grouped) {\r\n let grouped = dataViewCategorical.values.grouped();\r\n return DataRoleHelper.getMeasureIndexOfRole(grouped, 'Gradient');\r\n }\r\n return -1;\r\n }\r\n\r\n export function getGradientValueColumn(dataViewCategorical: DataViewCategorical): DataViewValueColumn {\r\n if (dataViewCategorical == null) return null;\r\n // check for gradient measure index\r\n let gradientMeasureIndex: number = GradientUtils.getGradientMeasureIndex(dataViewCategorical);\r\n let gradientValueColumn: DataViewValueColumn = gradientMeasureIndex === - 1 ? null : dataViewCategorical.values[gradientMeasureIndex];\r\n return gradientValueColumn;\r\n }\r\n\r\n export function hasGradientRole(dataViewCategorical: DataViewCategorical): boolean {\r\n let gradientMeasureIndex = getGradientMeasureIndex(dataViewCategorical);\r\n return gradientMeasureIndex >= 0;\r\n }\r\n\r\n export function getDefaultGradientSettings(): GradientSettings {\r\n\r\n let colors: GradientColors = getDefaultColors();\r\n let gradientSettings: GradientSettings = {\r\n diverging: false,\r\n minColor: colors.minColor,\r\n midColor: DefaultMidColor,\r\n maxColor: colors.maxColor,\r\n minValue: undefined,\r\n midValue: undefined,\r\n maxValue: undefined,\r\n };\r\n\r\n return gradientSettings;\r\n }\r\n\r\n export function getDefaultFillRuleDefinition(): DataViewObjectPropertyDefinition {\r\n return getLinearGradien2FillRuleDefinition();\r\n }\r\n\r\n export function updateFillRule(propertyName: string, propertyValue: any, definitions: powerbi.data.DataViewObjectDefinitions): void {\r\n\r\n let dataPointObjectDefinition: data.DataViewObjectDefinition = data.DataViewObjectDefinitions.ensure(definitions, DataPointPropertyIdentifier, null);\r\n let fillRule: FillRuleDefinition = getFillRule(definitions);\r\n let numericValueExpr: data.SQConstantExpr;\r\n let colorValueExpr: data.SQExpr;\r\n\r\n if (!fillRule) {\r\n return;\r\n }\r\n\r\n if ($.isNumeric(propertyValue)) {\r\n numericValueExpr = propertyValue !== undefined ? SQExprBuilder.double(+propertyValue) : undefined;;\r\n }\r\n\r\n if (propertyName === \"minColor\" || propertyName === \"midColor\" || propertyName === \"maxColor\") {\r\n colorValueExpr = getColorExpressionValue(fillRule, propertyName, propertyValue);\r\n }\r\n\r\n if (propertyName === \"minColor\") {\r\n updateMinColor(fillRule, colorValueExpr);\r\n }\r\n else if (propertyName === \"midColor\") {\r\n updateMidColor(fillRule, colorValueExpr);\r\n }\r\n else if (propertyName === \"maxColor\") {\r\n updateMaxColor(fillRule, colorValueExpr);\r\n }\r\n else if (propertyName === \"minValue\") {\r\n updateMinValue(fillRule, numericValueExpr);\r\n }\r\n else if (propertyName === \"midValue\") {\r\n updateMidValue(fillRule, numericValueExpr);\r\n }\r\n else if (propertyName === \"maxValue\") {\r\n updateMaxValue(fillRule, numericValueExpr);\r\n }\r\n else if (propertyName === \"diverging\") {\r\n if (propertyValue) {\r\n fillRule = getLinearGradien3FillRuleDefinition(fillRule);\r\n }\r\n else {\r\n fillRule = getLinearGradien2FillRuleDefinition(fillRule);\r\n }\r\n dataPointObjectDefinition.properties[FillRulePropertyIdentifier] = fillRule;\r\n }\r\n else if (propertyName === \"revertToDefault\") {\r\n fillRule = this.getDefaultFillRuleDefinition();\r\n dataPointObjectDefinition.properties[FillRulePropertyIdentifier] = fillRule;\r\n }\r\n }\r\n\r\n export function getGradientSettings(baseFillRule: FillRuleDefinition): GradientSettings {\r\n if (baseFillRule) {\r\n return getGradientSettingsFromRule(baseFillRule);\r\n }\r\n else {\r\n return getDefaultGradientSettings();\r\n }\r\n }\r\n\r\n export function getFillRule(objectDefinitions: data.DataViewObjectDefinitions): FillRuleDefinition {\r\n let fillRuleDefinition: FillRuleDefinition = data.DataViewObjectDefinitions.getValue(objectDefinitions, { objectName: DataPointPropertyIdentifier, propertyName: FillRulePropertyIdentifier }, null);\r\n return fillRuleDefinition;\r\n }\r\n\r\n function getDefaultColors(): GradientColors {\r\n\r\n let dataColors: IDataColorPalette = new powerbi.visuals.DataColorPalette();\r\n let maxColorInfo: IColorInfo = dataColors.getColorByIndex(0);\r\n let colors = d3.scale.linear()\r\n .domain([0, 100])\r\n .range([\"#ffffff\", maxColorInfo.value]);\r\n let maxColor: string = maxColorInfo.value;\r\n let minColor: string = <any>colors(20);\r\n let midColor: string = DefaultMidColor;\r\n\r\n return {\r\n minColor: minColor,\r\n midColor: midColor,\r\n maxColor: maxColor,\r\n };\r\n }\r\n\r\n export function getGradientSettingsFromRule(fillRule: FillRuleDefinition): GradientSettings {\r\n let maxColor: string;\r\n let minColor: string;\r\n let midColor: string = DefaultMidColor;\r\n let maxValue: number;\r\n let midValue: number;\r\n let minValue: number;\r\n let diverging: boolean = fillRule.linearGradient3 !== undefined;\r\n\r\n if (fillRule.linearGradient2) {\r\n let maxColorExpr: any = fillRule.linearGradient2.max.color;\r\n let minColorExpr: any = fillRule.linearGradient2.min.color;\r\n let maxValueExpr: any = fillRule.linearGradient2.max.value;\r\n let minValueExpr: any = fillRule.linearGradient2.min.value;\r\n maxColor = maxColorExpr.value;\r\n minColor = minColorExpr.value;\r\n if (maxValueExpr) {\r\n maxValue = <number>maxValueExpr.value;\r\n }\r\n if (minValueExpr) {\r\n minValue = <number>minValueExpr.value;\r\n }\r\n }\r\n else if (fillRule.linearGradient3) {\r\n let maxColorExpr: any = fillRule.linearGradient3.max.color;\r\n let midColorExpr: any = fillRule.linearGradient3.mid.color;\r\n let minColorExpr: any = fillRule.linearGradient3.min.color;\r\n let maxValueExpr: any = fillRule.linearGradient3.max.value;\r\n let midValueExpr: any = fillRule.linearGradient3.mid.value;\r\n let minValueExpr: any = fillRule.linearGradient3.min.value;\r\n maxColor = maxColorExpr.value;\r\n midColor = midColorExpr.value;\r\n minColor = minColorExpr.value;\r\n if (maxValueExpr) {\r\n maxValue = <number>maxValueExpr.value;\r\n }\r\n if (midValueExpr) {\r\n midValue = <number>midValueExpr.value;\r\n }\r\n if (minValueExpr) {\r\n minValue = <number>minValueExpr.value;\r\n }\r\n }\r\n\r\n return {\r\n diverging: diverging,\r\n minColor: minColor,\r\n midColor: midColor,\r\n maxColor: maxColor,\r\n minValue: minValue,\r\n midValue: midValue,\r\n maxValue: maxValue,\r\n };\r\n }\r\n\r\n /** Returns a string representing the gradient to be used for the GradientBar directive. */\r\n export function getGradientBarColors(gradientSettings: GradientSettings): string {\r\n let colors: string[] = [];\r\n gradientSettings.minColor = gradientSettings.minColor || DefaultColor;\r\n colors.push(gradientSettings.minColor);\r\n\r\n if (gradientSettings.diverging) {\r\n gradientSettings.midColor = gradientSettings.midColor || DefaultColor;\r\n colors.push(gradientSettings.midColor || DefaultColor);\r\n }\r\n\r\n gradientSettings.maxColor = gradientSettings.maxColor || DefaultColor;\r\n colors.push(gradientSettings.maxColor || DefaultColor);\r\n return colors.join(\",\");\r\n }\r\n\r\n function getLinearGradien2FillRuleDefinition(baseFillRule?: FillRuleDefinition): DataViewObjectPropertyDefinition {\r\n let gradientSettings: GradientSettings = getGradientSettings(baseFillRule);\r\n let fillRuleDefinition: FillRuleDefinition = {\r\n linearGradient2: {\r\n max: { color: SQExprBuilder.text(gradientSettings.maxColor) },\r\n min: { color: SQExprBuilder.text(gradientSettings.minColor) },\r\n }\r\n };\r\n\r\n return fillRuleDefinition;\r\n }\r\n\r\n function getLinearGradien3FillRuleDefinition(baseFillRule?: FillRuleDefinition): DataViewObjectPropertyDefinition {\r\n let gradientSettings: GradientSettings = getGradientSettings(baseFillRule);\r\n let fillRuleDefinition: FillRuleDefinition = {\r\n linearGradient3: {\r\n max: { color: SQExprBuilder.text(gradientSettings.maxColor) },\r\n mid: { color: SQExprBuilder.text(gradientSettings.midColor) },\r\n min: { color: SQExprBuilder.text(gradientSettings.minColor) },\r\n }\r\n };\r\n\r\n return fillRuleDefinition;\r\n }\r\n\r\n function getDefaultColorExpression(fillRule: FillRuleDefinition, propertyName: string): data.SQExpr {\r\n let defaultColor: data.SQExpr;\r\n let defaultFillRule: FillRuleDefinition;\r\n\r\n if (fillRule.linearGradient3) {\r\n defaultFillRule = getLinearGradien3FillRuleDefinition();\r\n if (propertyName === \"minColor\") {\r\n defaultColor = defaultFillRule.linearGradient3.min.color;\r\n }\r\n else if (propertyName === \"midColor\") {\r\n defaultColor = defaultFillRule.linearGradient3.mid.color;\r\n }\r\n else if (propertyName === \"maxColor\") {\r\n defaultColor = defaultFillRule.linearGradient3.max.color;\r\n }\r\n }\r\n else if (fillRule.linearGradient2) {\r\n defaultFillRule = getLinearGradien2FillRuleDefinition();\r\n if (propertyName === \"minColor\") {\r\n defaultColor = defaultFillRule.linearGradient2.min.color;\r\n }\r\n else if (propertyName === \"maxColor\") {\r\n defaultColor = defaultFillRule.linearGradient2.max.color;\r\n }\r\n }\r\n\r\n return defaultColor;\r\n }\r\n\r\n function getColorExpressionValue(fillRule: FillRuleDefinition, propertyName: string, propertyValue: string): data.SQExpr {\r\n let colorExpressionValue: data.SQExpr;\r\n if (propertyValue) {\r\n colorExpressionValue = SQExprBuilder.text(propertyValue);\r\n }\r\n else {\r\n colorExpressionValue = getDefaultColorExpression(fillRule, propertyName);\r\n }\r\n return colorExpressionValue;\r\n }\r\n\r\n function updateMinColor(fillRule: FillRuleDefinition, colorExpressionValue: data.SQExpr) {\r\n if (fillRule.linearGradient2) {\r\n fillRule.linearGradient2.min.color = colorExpressionValue;\r\n }\r\n else if (fillRule.linearGradient3) {\r\n fillRule.linearGradient3.min.color = colorExpressionValue;\r\n }\r\n }\r\n\r\n function updateMidColor(fillRule: FillRuleDefinition, colorExpressionValue: data.SQExpr) {\r\n if (fillRule.linearGradient3) {\r\n fillRule.linearGradient3.mid.color = colorExpressionValue;\r\n }\r\n }\r\n\r\n function updateMaxColor(fillRule: FillRuleDefinition, colorExpressionValue: data.SQExpr) {\r\n if (fillRule.linearGradient2) {\r\n fillRule.linearGradient2.max.color = colorExpressionValue;\r\n }\r\n else if (fillRule.linearGradient3) {\r\n fillRule.linearGradient3.max.color = colorExpressionValue;\r\n }\r\n }\r\n\r\n function updateMinValue(fillRule: FillRuleDefinition, value: data.SQConstantExpr) {\r\n if (fillRule.linearGradient2) {\r\n fillRule.linearGradient2.min.value = value;\r\n }\r\n else if (fillRule.linearGradient3) {\r\n fillRule.linearGradient3.min.value = value;\r\n }\r\n }\r\n\r\n function updateMidValue(fillRule: FillRuleDefinition, value: data.SQConstantExpr) {\r\n if (fillRule.linearGradient3) {\r\n fillRule.linearGradient3.mid.value = value;\r\n }\r\n }\r\n\r\n function updateMaxValue(fillRule: FillRuleDefinition, value: data.SQConstantExpr) {\r\n if (fillRule.linearGradient2) {\r\n fillRule.linearGradient2.max.value = value;\r\n }\r\n else if (fillRule.linearGradient3) {\r\n fillRule.linearGradient3.max.value = value;\r\n }\r\n }\r\n };\r\n}\n\n\n/** WEBPACK FOOTER **\n ** ./src/Clients/Visuals/common/gradientHelper.ts\n **/","/*\r\n * Power BI Visualizations\r\n *\r\n * Copyright (c) Microsoft Corporation\r\n * All rights reserved. \r\n * MIT License\r\n *\r\n * Permission is hereby granted, free of charge, to any person obtaining a copy\r\n * of this software and associated documentation files (the \"\"Software\"\"), to deal\r\n * in the Software without restriction, including without limitation the rights\r\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\r\n * copies of the Software, and to permit persons to whom the Software is\r\n * furnished to do so, subject to the following conditions:\r\n * \r\n * The above copyright notice and this permission notice shall be included in \r\n * all copies or substantial portions of the Software.\r\n * \r\n * THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR \r\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, \r\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE \r\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER \r\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\r\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\r\n * THE SOFTWARE.\r\n */\r\n\r\n/// <reference path=\"../_references.ts\"/>\r\n\r\nmodule powerbi.visuals {\r\n export interface VisualBackground {\r\n image?: ImageValue;\r\n transparency?: number;\r\n }\r\n\r\n export module visualBackgroundHelper {\r\n export function getDefaultColor(): string {\r\n return '#FFF';\r\n }\r\n\r\n export function getDefaultTransparency(): number {\r\n return 50;\r\n }\r\n\r\n export function getDefaultShow(): boolean {\r\n return false;\r\n }\r\n\r\n export function getDefaultValues() {\r\n return {\r\n color: getDefaultColor(),\r\n transparency: getDefaultTransparency(),\r\n show: getDefaultShow()\r\n };\r\n }\r\n\r\n export function enumeratePlot(enumeration: ObjectEnumerationBuilder, background: VisualBackground): void {\r\n let transparency = (background && background.transparency);\r\n if (transparency == null)\r\n transparency = getDefaultTransparency();\r\n\r\n let backgroundObject: VisualObjectInstance = {\r\n selector: null,\r\n properties: {\r\n transparency: transparency,\r\n image: (background && background.image)\r\n },\r\n objectName: 'plotArea',\r\n };\r\n\r\n enumeration.pushInstance(backgroundObject);\r\n }\r\n\r\n export function renderBackgroundImage(\r\n background: VisualBackground,\r\n visualElement: JQuery,\r\n layout: Rect): void {\r\n let image = background && background.image;\r\n let imageUrl = image && image.url;\r\n let imageFit = image && image.scaling;\r\n let imageTransparency = background && background.transparency;\r\n let backgroundImage = visualElement.children('.background-image');\r\n\r\n // If there were image and it was removed\r\n if (!imageUrl) {\r\n if (backgroundImage.length !== 0)\r\n backgroundImage.remove();\r\n return;\r\n }\r\n\r\n // If this is the first edit of the image\r\n if (backgroundImage.length === 0) {\r\n // Place the div only if the image exists in order to keep the html as clean as possible\r\n visualElement.prepend('<div class=\"background-image\"></div>');\r\n backgroundImage = visualElement.children('.background-image');\r\n\r\n // the div should be positioned absolute in order to get on top of the sibling svg\r\n backgroundImage.css('position', 'absolute');\r\n }\r\n\r\n // Get the size and margins from the visual for the div will placed inside the plot area\r\n backgroundImage.css({\r\n 'width': layout.width,\r\n 'height': layout.height,\r\n 'margin-left': layout.left,\r\n 'margin-top': layout.top,\r\n });\r\n\r\n // Background properties\r\n backgroundImage.css({\r\n 'background-image': 'url(' + imageUrl + ')',\r\n 'background-repeat': 'no-repeat',\r\n 'opacity': (100 - imageTransparency) / 100,\r\n });\r\n\r\n switch (imageFit) {\r\n // The image will be centered in its initial size\r\n case visuals.imageScalingType.normal: {\r\n backgroundImage.css({\r\n 'background-size': '',\r\n 'background-position': '50% 50%',\r\n });\r\n break;\r\n }\r\n // The image will be streched all over the background\r\n case visuals.imageScalingType.fit: {\r\n backgroundImage.css({\r\n 'background-size': '100% 100%',\r\n 'background-position': '',\r\n });\r\n break;\r\n }\r\n // The image will stretch on the width and the height will scale accordingly\r\n case visuals.imageScalingType.fill: {\r\n backgroundImage.css({\r\n 'background-size': '100%',\r\n 'background-position': '50% 50%',\r\n });\r\n break;\r\n }\r\n default: {\r\n backgroundImage.css({\r\n 'background-size': '',\r\n 'background-position': '50% 50%',\r\n });\r\n break;\r\n }\r\n }\r\n }\r\n }\r\n} \n\n\n/** WEBPACK FOOTER **\n ** ./src/Clients/Visuals/common/visualBackgroundHelper.ts\n **/","/*\r\n * Power BI Visualizations\r\n *\r\n * Copyright (c) Microsoft Corporation\r\n * All rights reserved. \r\n * MIT License\r\n *\r\n * Permission is hereby granted, free of charge, to any person obtaining a copy\r\n * of this software and associated documentation files (the \"\"Software\"\"), to deal\r\n * in the Software without restriction, including without limitation the rights\r\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\r\n * copies of the Software, and to permit persons to whom the Software is\r\n * furnished to do so, subject to the following conditions:\r\n * \r\n * The above copyright notice and this permission notice shall be included in \r\n * all copies or substantial portions of the Software.\r\n * \r\n * THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR \r\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, \r\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE \r\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER \r\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\r\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\r\n * THE SOFTWARE.\r\n */\r\n\r\n/// <reference path=\"../_references.ts\"/>\r\n\r\nmodule powerbi.visuals {\r\n import Selector = powerbi.data.Selector;\r\n\r\n /**\r\n * A helper class for building a VisualObjectInstanceEnumerationObject:\r\n * - Allows call chaining (e.g., builder.pushInstance({...}).pushInstance({...})\r\n * - Allows creating of containers (via pushContainer/popContainer)\r\n */\r\n export class ObjectEnumerationBuilder {\r\n private instances: VisualObjectInstance[];\r\n private containers: VisualObjectInstanceContainer[];\r\n private containerIdx: number;\r\n\r\n public pushInstance(instance: VisualObjectInstance): ObjectEnumerationBuilder {\r\n debug.assertValue(instance, 'instance');\r\n\r\n let instances = this.instances;\r\n if (!instances) {\r\n instances = this.instances = [];\r\n }\r\n\r\n let containerIdx = this.containerIdx;\r\n if (containerIdx != null) {\r\n instance.containerIdx = containerIdx;\r\n }\r\n\r\n // Attempt to merge with an existing item if possible.\r\n for (let existingInstance of instances) {\r\n if (this.canMerge(existingInstance, instance)) {\r\n this.extend(existingInstance, instance, 'properties');\r\n this.extend(existingInstance, instance, 'validValues');\r\n\r\n return this;\r\n }\r\n }\r\n\r\n instances.push(instance);\r\n\r\n return this;\r\n }\r\n\r\n public pushContainer(container: VisualObjectInstanceContainer): ObjectEnumerationBuilder {\r\n debug.assertValue(container, 'container');\r\n\r\n let containers = this.containers;\r\n if (!containers) {\r\n containers = this.containers = [];\r\n }\r\n\r\n let updatedLen = containers.push(container);\r\n this.containerIdx = updatedLen - 1;\r\n\r\n return this;\r\n }\r\n\r\n public popContainer(): ObjectEnumerationBuilder {\r\n this.containerIdx = undefined;\r\n\r\n return this;\r\n }\r\n\r\n public complete(): VisualObjectInstanceEnumerationObject {\r\n if (!this.instances)\r\n return;\r\n\r\n let result: VisualObjectInstanceEnumerationObject = {\r\n instances: this.instances,\r\n };\r\n\r\n let containers = this.containers;\r\n if (containers) {\r\n result.containers = containers;\r\n }\r\n\r\n return result;\r\n }\r\n\r\n private canMerge(x: VisualObjectInstance, y: VisualObjectInstance): boolean {\r\n debug.assertValue(x, 'x');\r\n debug.assertValue(y, 'y');\r\n\r\n return x.objectName === y.objectName &&\r\n x.containerIdx === y.containerIdx &&\r\n Selector.equals(x.selector, y.selector);\r\n }\r\n\r\n private extend(target: VisualObjectInstance, source: VisualObjectInstance, propertyName: string): void {\r\n debug.assertValue(target, 'target');\r\n debug.assertValue(source, 'source');\r\n debug.assertValue(propertyName, 'propertyName');\r\n\r\n let sourceValues = source[propertyName];\r\n if (!sourceValues)\r\n return;\r\n\r\n let targetValues = target[propertyName];\r\n if (!targetValues)\r\n targetValues = target[propertyName] = {};\r\n\r\n for (let valuePropertyName in sourceValues) {\r\n if (targetValues[valuePropertyName]) {\r\n // Properties have first-writer-wins semantics.\r\n continue;\r\n }\r\n\r\n targetValues[valuePropertyName] = sourceValues[valuePropertyName];\r\n }\r\n }\r\n\r\n public static merge(x: VisualObjectInstanceEnumeration, y: VisualObjectInstanceEnumeration): VisualObjectInstanceEnumerationObject {\r\n let xNormalized = ObjectEnumerationBuilder.normalize(x);\r\n let yNormalized = ObjectEnumerationBuilder.normalize(y);\r\n\r\n if (!xNormalized || !yNormalized)\r\n return xNormalized || yNormalized;\r\n\r\n debug.assertValue(xNormalized, 'xNormalized');\r\n debug.assertValue(yNormalized, 'yNormalized');\r\n\r\n let xCategoryCount = xNormalized.containers ? xNormalized.containers.length : 0;\r\n\r\n for (let yInstance of yNormalized.instances) {\r\n xNormalized.instances.push(yInstance);\r\n\r\n if (yInstance.containerIdx != null)\r\n yInstance.containerIdx += xCategoryCount;\r\n }\r\n\r\n let yContainers = yNormalized.containers;\r\n if (!_.isEmpty(yContainers)) {\r\n if (xNormalized.containers)\r\n Array.prototype.push.apply(xNormalized.containers, yContainers);\r\n else\r\n xNormalized.containers = yContainers;\r\n }\r\n\r\n return xNormalized;\r\n }\r\n\r\n public static normalize(x: VisualObjectInstanceEnumeration): VisualObjectInstanceEnumerationObject {\r\n debug.assertAnyValue(x, 'x');\r\n\r\n if (_.isArray(x)) {\r\n return { instances: <VisualObjectInstance[]>x };\r\n }\r\n\r\n return <VisualObjectInstanceEnumerationObject>x;\r\n }\r\n\r\n public static getContainerForInstance(enumeration: VisualObjectInstanceEnumerationObject, instance: VisualObjectInstance): VisualObjectInstanceContainer {\r\n debug.assertValue(enumeration, \"enumeration\");\r\n debug.assertValue(instance, \"instance\");\r\n debug.assertValue(enumeration.containers, \"containers\");\r\n debug.assert(enumeration.containers.length > instance.containerIdx, \"no container found in containers collection\");\r\n\r\n return enumeration.containers[instance.containerIdx];\r\n }\r\n \r\n }\r\n}\n\n\n/** WEBPACK FOOTER **\n ** ./src/Clients/Visuals/common/objectEnumerationBuilder.ts\n **/","/*\r\n * Power BI Visualizations\r\n *\r\n * Copyright (c) Microsoft Corporation\r\n * All rights reserved. \r\n * MIT License\r\n *\r\n * Permission is hereby granted, free of charge, to any person obtaining a copy\r\n * of this software and associated documentation files (the \"\"Software\"\"), to deal\r\n * in the Software without restriction, including without limitation the rights\r\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\r\n * copies of the Software, and to permit persons to whom the Software is\r\n * furnished to do so, subject to the following conditions:\r\n * \r\n * The above copyright notice and this permission notice shall be included in \r\n * all copies or substantial portions of the Software.\r\n * \r\n * THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR \r\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, \r\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE \r\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER \r\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\r\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\r\n * THE SOFTWARE.\r\n */\r\n\r\n/// <reference path=\"../_references.ts\"/>\r\n\r\nmodule powerbi.visuals {\r\n /** Helper class for Visual border styles */\r\n export module VisualBorderUtil {\r\n /**\r\n * Gets The Boder Width string (e.g. 0px 1px 2px 3px)\r\n * @param {OutlineType} string Type of the Outline, one of Visuals.outline.<XX> const strings\r\n * @param {number} outlineWeight Weight of the outline in pixels\r\n * @returns String representing the Border Width\r\n */\r\n export function getBorderWidth(outlineType: string, outlineWeight: number): string {\r\n switch (outlineType) {\r\n case outline.none:\r\n return '0px';\r\n case outline.bottomOnly:\r\n return '0px 0px ' + outlineWeight + 'px 0px';\r\n case outline.topOnly:\r\n return outlineWeight + 'px 0px 0px 0px';\r\n case outline.leftOnly:\r\n return '0px 0px 0px ' + outlineWeight + 'px';\r\n case outline.rightOnly:\r\n return '0px ' + outlineWeight + 'px 0px 0px';\r\n case outline.topBottom:\r\n return outlineWeight + 'px 0px';\r\n case outline.leftRight:\r\n return '0px ' + outlineWeight + 'px';\r\n case outline.frame:\r\n return outlineWeight + 'px';\r\n default:\r\n debug.assertFail('Unexpected OutlineType value: ' + outlineType);\r\n return '0px';\r\n }\r\n }\r\n }\r\n} \n\n\n/** WEBPACK FOOTER **\n ** ./src/Clients/Visuals/common/visualBorderUtil.ts\n **/","/*\r\n * Power BI Visualizations\r\n *\r\n * Copyright (c) Microsoft Corporation\r\n * All rights reserved. \r\n * MIT License\r\n *\r\n * Permission is hereby granted, free of charge, to any person obtaining a copy\r\n * of this software and associated documentation files (the \"\"Software\"\"), to deal\r\n * in the Software without restriction, including without limitation the rights\r\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\r\n * copies of the Software, and to permit persons to whom the Software is\r\n * furnished to do so, subject to the following conditions:\r\n * \r\n * The above copyright notice and this permission notice shall be included in \r\n * all copies or substantial portions of the Software.\r\n * \r\n * THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR \r\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, \r\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE \r\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER \r\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\r\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\r\n * THE SOFTWARE.\r\n */\r\n\r\n/// <reference path=\"../_references.ts\"/>\r\n\r\nmodule powerbi.visuals {\r\n export interface I2DTransformMatrix {\r\n m00: number;\r\n m01: number;\r\n m02: number;\r\n\r\n m10: number;\r\n m11: number;\r\n m12: number;\r\n // 3rd row not used so we don't declare it\r\n }\r\n\r\n /** Transformation matrix math wrapper */\r\n export class Transform {\r\n\r\n // Fields\r\n private _inverse: Transform;\r\n public matrix: I2DTransformMatrix;\r\n\r\n // Constructor\r\n constructor(m?: I2DTransformMatrix) {\r\n this.matrix = m || {\r\n m00: 1, m01: 0, m02: 0,\r\n m10: 0, m11: 1, m12: 0,\r\n };\r\n }\r\n\r\n // Methods\r\n public applyToPoint(point: IPoint): IPoint {\r\n if (!point) {\r\n return point;\r\n }\r\n let m = this.matrix;\r\n return {\r\n x: m.m00 * point.x + m.m01 * point.y + m.m02,\r\n y: m.m10 * point.x + m.m11 * point.y + m.m12,\r\n };\r\n }\r\n\r\n public applyToRect(rect: Rect): IRect {\r\n if (!rect) {\r\n return rect;\r\n }\r\n\r\n let x0 = rect.left;\r\n let y0 = rect.top;\r\n\r\n let m = this.matrix;\r\n let isScaled = m.m00 !== 1 || m.m11 !== 1;\r\n let isRotated = m.m01 !== 0 || m.m10 !== 0;\r\n if (!isRotated && !isScaled) {\r\n // Optimize for the translation only case\r\n return { left: x0 + m.m02, top: y0 + m.m12, width: rect.width, height: rect.height };\r\n }\r\n\r\n let x1 = rect.left + rect.width;\r\n let y1 = rect.top + rect.height;\r\n\r\n let minX: number;\r\n let maxX: number;\r\n let minY: number;\r\n let maxY: number;\r\n\r\n if (isRotated) {\r\n let p0x = m.m00 * x0 + m.m01 * y0 + m.m02;\r\n let p0y = m.m10 * x0 + m.m11 * y0 + m.m12;\r\n let p1x = m.m00 * x0 + m.m01 * y1 + m.m02;\r\n let p1y = m.m10 * x0 + m.m11 * y1 + m.m12;\r\n let p2x = m.m00 * x1 + m.m01 * y0 + m.m02;\r\n let p2y = m.m10 * x1 + m.m11 * y0 + m.m12;\r\n let p3x = m.m00 * x1 + m.m01 * y1 + m.m02;\r\n let p3y = m.m10 * x1 + m.m11 * y1 + m.m12;\r\n minX = Math.min(p0x, p1x, p2x, p3x);\r\n maxX = Math.max(p0x, p1x, p2x, p3x);\r\n minY = Math.min(p0y, p1y, p2y, p3y);\r\n maxY = Math.max(p0y, p1y, p2y, p3y);\r\n } else {\r\n let p0x = m.m00 * x0 + m.m02;\r\n let p0y = m.m11 * y0 + m.m12;\r\n let p3x = m.m00 * x1 + m.m02;\r\n let p3y = m.m11 * y1 + m.m12;\r\n minX = Math.min(p0x, p3x);\r\n maxX = Math.max(p0x, p3x);\r\n minY = Math.min(p0y, p3y);\r\n maxY = Math.max(p0y, p3y);\r\n }\r\n\r\n return { left: minX, top: minY, width: maxX - minX, height: maxY - minY };\r\n }\r\n\r\n public translate(xOffset: number, yOffset: number): void {\r\n if (xOffset !== 0 || yOffset !== 0) {\r\n let m = createTranslateMatrix(xOffset, yOffset);\r\n this.matrix = multiplyMatrices(this.matrix, m);\r\n this._inverse = null;\r\n }\r\n }\r\n\r\n public scale(xScale: number, yScale: number): void {\r\n if (xScale !== 1 || yScale !== 1) {\r\n let m = createScaleMatrix(xScale, yScale);\r\n this.matrix = multiplyMatrices(this.matrix, m);\r\n this._inverse = null;\r\n }\r\n }\r\n\r\n public rotate(angleInRadians: number): void {\r\n if (angleInRadians !== 0) {\r\n let m = createRotationMatrix(angleInRadians);\r\n this.matrix = multiplyMatrices(this.matrix, m);\r\n this._inverse = null;\r\n }\r\n }\r\n\r\n public add(other: Transform): void {\r\n if (other) {\r\n this.matrix = multiplyMatrices(this.matrix, other.matrix);\r\n this._inverse = null;\r\n }\r\n }\r\n\r\n public getInverse(): Transform {\r\n if (!this._inverse) {\r\n this._inverse = new Transform(createInverseMatrix(this.matrix));\r\n }\r\n return this._inverse;\r\n }\r\n }\r\n\r\n export function createTranslateMatrix(xOffset: number, yOffset: number): I2DTransformMatrix {\r\n return {\r\n m00: 1, m01: 0, m02: xOffset,\r\n m10: 0, m11: 1, m12: yOffset,\r\n };\r\n }\r\n\r\n export function createScaleMatrix(xScale: number, yScale: number): I2DTransformMatrix {\r\n return {\r\n m00: xScale, m01: 0, m02: 0,\r\n m10: 0, m11: yScale, m12: 0\r\n };\r\n }\r\n\r\n export function createRotationMatrix(angleInRads: number): I2DTransformMatrix {\r\n let a = angleInRads;\r\n let sinA = Math.sin(a);\r\n let cosA = Math.cos(a);\r\n return {\r\n m00: cosA, m01: -sinA, m02: 0,\r\n m10: sinA, m11: cosA, m12: 0,\r\n };\r\n }\r\n\r\n export function createInverseMatrix(m: I2DTransformMatrix): I2DTransformMatrix {\r\n let determinant = m.m00 * m.m11 - m.m01 * m.m10;\r\n let invdet = 1 / determinant;\r\n return {\r\n m00: m.m11 * invdet,\r\n m01: - m.m01 * invdet,\r\n m02: (m.m01 * m.m12 - m.m02 * m.m11) * invdet,\r\n\r\n m10: -m.m10 * invdet,\r\n m11: m.m00 * invdet,\r\n m12: - (m.m00 * m.m12 - m.m10 * m.m02) * invdet\r\n };\r\n }\r\n\r\n function multiplyMatrices(a: I2DTransformMatrix, b: I2DTransformMatrix): I2DTransformMatrix {\r\n return {\r\n m00: a.m00 * b.m00 + a.m01 * b.m10,\r\n m01: a.m00 * b.m01 + a.m01 * b.m11,\r\n m02: a.m00 * b.m02 + a.m01 * b.m12 + a.m02,\r\n m10: a.m10 * b.m00 + a.m11 * b.m10,\r\n m11: a.m10 * b.m01 + a.m11 * b.m11,\r\n m12: a.m10 * b.m02 + a.m11 * b.m12 + a.m12,\r\n };\r\n }\r\n}\n\n\n/** WEBPACK FOOTER **\n ** ./src/Clients/Visuals/common/transform.ts\n **/","/*\r\n * Power BI Visualizations\r\n *\r\n * Copyright (c) Microsoft Corporation\r\n * All rights reserved.\r\n * MIT License\r\n *\r\n * Permission is hereby granted, free of charge, to any person obtaining a copy\r\n * of this software and associated documentation files (the \"\"Software\"\"), to deal\r\n * in the Software without restriction, including without limitation the rights\r\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\r\n * copies of the Software, and to permit persons to whom the Software is\r\n * furnished to do so, subject to the following conditions:\r\n *\r\n * The above copyright notice and this permission notice shall be included in\r\n * all copies or substantial portions of the Software.\r\n *\r\n * THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\r\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\r\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\r\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\r\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\r\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\r\n * THE SOFTWARE.\r\n */\r\n\r\n/// <reference path=\"../_references.ts\"/>\r\n\r\nmodule powerbi.visuals {\r\n import Color = jsCommon.Color;\r\n import DataRoleHelper = powerbi.data.DataRoleHelper;\r\n\r\n export interface TrendLine {\r\n points: IPoint[];\r\n show: boolean;\r\n lineColor: Fill;\r\n transparency: number;\r\n style: string;\r\n combineSeries: boolean;\r\n useHighlightValues: boolean;\r\n y2Axis: boolean;\r\n }\r\n\r\n export module TrendLineHelper {\r\n const trendLinePropertyNames = {\r\n show: 'show',\r\n lineColor: 'lineColor',\r\n transparency: 'transparency',\r\n style: 'style',\r\n combineSeries: 'combineSeries',\r\n useHighlightValues: 'useHighlightValues',\r\n };\r\n const trendObjectName = 'trend';\r\n\r\n export const defaults = {\r\n lineColor: <Fill>{ solid: { color: '#000' } },\r\n lineStyle: lineStyle.dashed,\r\n transparency: 0,\r\n combineSeries: true,\r\n useHighlightValues: true,\r\n };\r\n const TrendLineClassSelector: jsCommon.CssConstants.ClassAndSelector = jsCommon.CssConstants.createClassAndSelector('trend-line');\r\n const TrendLineLayerClassSelector: jsCommon.CssConstants.ClassAndSelector = jsCommon.CssConstants.createClassAndSelector('trend-line-layer');\r\n\r\n export function enumerateObjectInstances(enumeration: ObjectEnumerationBuilder, trendLines: TrendLine[]): void {\r\n debug.assertValue(enumeration, 'enumeration');\r\n\r\n if (_.isEmpty(trendLines)) {\r\n enumeration.pushInstance({\r\n selector: null,\r\n properties: {\r\n show: false,\r\n lineColor: defaults.lineColor,\r\n transparency: defaults.transparency,\r\n style: defaults.lineStyle,\r\n combineSeries: defaults.combineSeries,\r\n },\r\n objectName: trendObjectName,\r\n });\r\n\r\n return;\r\n }\r\n\r\n let trendLine = trendLines[0];\r\n let properties: { [propertyName: string]: DataViewPropertyValue } = {};\r\n properties['show'] = trendLine.show;\r\n\r\n if (trendLine.combineSeries)\r\n properties['lineColor'] = trendLine.lineColor;\r\n\r\n properties['transparency'] = trendLine.transparency;\r\n properties['style'] = trendLine.style;\r\n properties['combineSeries'] = trendLine.combineSeries;\r\n properties['useHighlightValues'] = trendLine.useHighlightValues;\r\n\r\n enumeration.pushInstance({\r\n selector: null,\r\n properties: properties,\r\n objectName: trendObjectName,\r\n });\r\n }\r\n\r\n export function isDataViewForRegression(dataView: DataView): boolean {\r\n return DataRoleHelper.hasRoleInDataView(dataView, 'regression.X');\r\n }\r\n\r\n export function readDataView(dataView: DataView, sourceDataView: DataView, y2: boolean, colors: IDataColorPalette): TrendLine[] {\r\n if (!dataView || !dataView.categorical)\r\n return;\r\n\r\n let categorical = dataView.categorical;\r\n if (_.isEmpty(categorical.categories) || _.isEmpty(categorical.values))\r\n return;\r\n\r\n let categories = categorical.categories[0].values;\r\n let groups = categorical.values.grouped();\r\n if (!categories || !groups)\r\n return;\r\n\r\n let trendProperties = DataViewObjects.getObject(dataView.metadata.objects, trendObjectName, {});\r\n let show = DataViewObject.getValue<boolean>(trendProperties, trendLinePropertyNames.show, false);\r\n let lineColor = DataViewObject.getValue<Fill>(trendProperties, trendLinePropertyNames.lineColor);\r\n let transparency = DataViewObject.getValue<number>(trendProperties, trendLinePropertyNames.transparency, defaults.transparency);\r\n let style = DataViewObject.getValue<string>(trendProperties, trendLinePropertyNames.style, defaults.lineStyle);\r\n let combineSeries = DataViewObject.getValue<boolean>(trendProperties, trendLinePropertyNames.combineSeries, defaults.combineSeries);\r\n let useHighlightValues = DataViewObject.getValue<boolean>(trendProperties, trendLinePropertyNames.useHighlightValues, defaults.useHighlightValues);\r\n\r\n // Trend lines generated by Insights will be putting line color here, we should convert the Insights code to create\r\n // \"trend\" objects like above and write the upgrade code to handle pinned tiles with trend lines before removing any feature switch.\r\n let legacyColor = DataViewObjects.getValue<Fill>(categorical.values[0].source.objects, lineChartProps.dataPoint.fill);\r\n if (legacyColor)\r\n lineColor = legacyColor;\r\n\r\n let objects = sourceDataView.metadata.objects;\r\n let defaultColor = DataViewObjects.getFillColor(objects, { objectName: 'dataPoint', propertyName: 'defaultColor' });\r\n let colorHelper = new ColorHelper(colors, { objectName: 'dataPoint', propertyName: 'fill' }, defaultColor);\r\n\r\n let trendLines: TrendLine[] = [];\r\n for (let groupIndex = 0; groupIndex < groups.length; groupIndex++) {\r\n let group = groups[groupIndex];\r\n\r\n let points: IPoint[] = [];\r\n for (let i = 0; i < categories.length; i++) {\r\n let x = AxisHelper.normalizeNonFiniteNumber(categories[i]);\r\n\r\n // There is a assumption here that the group only has 1 set of values in it. Once we add more things like confidence bands,\r\n // this assumption will not be true. This assumption comes from the way dataViewRegresion generates the dataView\r\n let valueColumn = group.values[0];\r\n\r\n let values: any[];\r\n if (useHighlightValues && valueColumn.highlights) {\r\n values = valueColumn.highlights;\r\n }\r\n else {\r\n values = valueColumn.values;\r\n }\r\n let y = AxisHelper.normalizeNonFiniteNumber(values[i]);\r\n\r\n if (x != null && y != null) {\r\n points.push({\r\n x: x,\r\n y: y,\r\n });\r\n }\r\n }\r\n\r\n let seriesLineColor: Fill;\r\n if (combineSeries) {\r\n seriesLineColor = lineColor || defaults.lineColor;\r\n }\r\n else {\r\n // TODO: This should likely be delegated to the layer which knows how to choose the correct color for any given situation.\r\n if (sourceDataView.categorical.values.source) {\r\n // Dynamic series\r\n let sourceGroups = sourceDataView.categorical.values.grouped();\r\n let color = colorHelper.getColorForSeriesValue(sourceGroups[groupIndex].objects, sourceDataView.categorical.values.identityFields, group.name);\r\n color = darkenTrendLineColor(color);\r\n seriesLineColor = { solid: { color: color } };\r\n }\r\n else {\r\n // Static series\r\n let matchingMeasure = sourceDataView.categorical.values[groupIndex];\r\n let color = colorHelper.getColorForMeasure(matchingMeasure.source.objects, group.name);\r\n color = darkenTrendLineColor(color);\r\n seriesLineColor = { solid: { color: color } };\r\n }\r\n }\r\n\r\n trendLines.push({\r\n points: points,\r\n show: show,\r\n lineColor: seriesLineColor,\r\n transparency: transparency,\r\n style: style,\r\n combineSeries: combineSeries,\r\n useHighlightValues: useHighlightValues,\r\n y2Axis: y2,\r\n });\r\n }\r\n return trendLines;\r\n }\r\n\r\n export function darkenTrendLineColor(color: string): string {\r\n let rgb = Color.parseColorString(color);\r\n rgb = Color.darken(rgb, 20);\r\n return Color.rgbString(rgb);\r\n }\r\n\r\n export function render(trendLines: TrendLine[], graphicsContext: D3.Selection, axes: CartesianAxisProperties, viewport: IViewport): void {\r\n let layer = graphicsContext.select(TrendLineLayerClassSelector.selector);\r\n if (layer.empty()) {\r\n layer = graphicsContext.append('svg').classed(TrendLineLayerClassSelector.class, true);\r\n }\r\n\r\n layer.attr({\r\n height: viewport.height,\r\n width: viewport.width\r\n });\r\n\r\n let lines = layer.selectAll(TrendLineClassSelector.selector).data(trendLines || []);\r\n lines.enter().append('path').classed(TrendLineClassSelector.class, true);\r\n\r\n lines\r\n .attr('d', (d: TrendLine) => {\r\n let xScale = axes.x.scale;\r\n let yScale = (d.y2Axis && axes.y2) ? axes.y2.scale : axes.y1.scale;\r\n\r\n let pathGen = d3.svg.line()\r\n .x((point: IPoint) => xScale(point.x))\r\n .y((point: IPoint) => yScale(point.y));\r\n\r\n return pathGen(_.filter(d.points, (point) => point.x != null && point.y != null));\r\n });\r\n\r\n lines.each(function (d: TrendLine) {\r\n let line = d3.select(this);\r\n let style: any = {};\r\n\r\n style.stroke = d.lineColor.solid.color;\r\n\r\n if (d.transparency != null) {\r\n style['stroke-opacity'] = (100 - d.transparency) / 100;\r\n }\r\n\r\n if (d.style === lineStyle.dashed) {\r\n style['stroke-dasharray'] = \"5, 5\";\r\n }\r\n else if (d.style === lineStyle.dotted) {\r\n style['stroke-dasharray'] = \"1, 5\";\r\n style['stroke-linecap'] = \"round\";\r\n }\r\n else if (d.style === lineStyle.solid) {\r\n style['stroke-dasharray'] = null;\r\n style['stroke-linecap'] = null;\r\n }\r\n\r\n line.style(style);\r\n });\r\n\r\n lines.exit().remove();\r\n }\r\n }\r\n}\r\n\n\n\n/** WEBPACK FOOTER **\n ** ./src/Clients/Visuals/common/trendLineHelper.ts\n **/","/*\r\n * Power BI Visualizations\r\n *\r\n * Copyright (c) Microsoft Corporation\r\n * All rights reserved. \r\n * MIT License\r\n *\r\n * Permission is hereby granted, free of charge, to any person obtaining a copy\r\n * of this software and associated documentation files (the \"\"Software\"\"), to deal\r\n * in the Software without restriction, including without limitation the rights\r\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\r\n * copies of the Software, and to permit persons to whom the Software is\r\n * furnished to do so, subject to the following conditions:\r\n * \r\n * The above copyright notice and this permission notice shall be included in \r\n * all copies or substantial portions of the Software.\r\n * \r\n * THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR \r\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, \r\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE \r\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER \r\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\r\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\r\n * THE SOFTWARE.\r\n */\r\n\r\n/// <reference path=\"../_references.ts\"/>\r\n\r\nmodule powerbi.visuals {\r\n export module visibilityHelper {\r\n /** Helper method that uses jQuery :visible selector to determine if visual is visible.\r\n Elements are considered visible if they consume space in the document. Visible elements have a width or height that is greater than zero.\r\n Elements with visibility: hidden or opacity: 0 are considered visible, since they still consume space in the layout.\r\n */\r\n export function partiallyVisible(element: JQuery): boolean {\r\n return element.is(\":visible\");\r\n }\r\n }\r\n}\n\n\n/** WEBPACK FOOTER **\n ** ./src/Clients/Visuals/common/visibilityUtil.ts\n **/","/*\r\n * Power BI Visualizations\r\n *\r\n * Copyright (c) Microsoft Corporation\r\n * All rights reserved.\r\n * MIT License\r\n *\r\n * Permission is hereby granted, free of charge, to any person obtaining a copy\r\n * of this software and associated documentation files (the \"\"Software\"\"), to deal\r\n * in the Software without restriction, including without limitation the rights\r\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\r\n * copies of the Software, and to permit persons to whom the Software is\r\n * furnished to do so, subject to the following conditions:\r\n *\r\n * The above copyright notice and this permission notice shall be included in\r\n * all copies or substantial portions of the Software.\r\n *\r\n * THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\r\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\r\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\r\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\r\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\r\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\r\n * THE SOFTWARE.\r\n */\r\n\r\n/// <reference path=\"../_references.ts\"/>\r\n\r\nmodule powerbi {\r\n import Selector = data.Selector;\r\n\r\n export module VisualObjectRepetition {\r\n /** Determines whether two repetitions are equal. */\r\n export function equals(x: VisualObjectRepetition, y: VisualObjectRepetition): boolean {\r\n // Normalize falsy to null\r\n x = x || null;\r\n y = y || null;\r\n\r\n if (x === y)\r\n return true;\r\n\r\n if (!x !== !y)\r\n return false;\r\n\r\n debug.assertValue(x, 'x');\r\n debug.assertValue(y, 'y');\r\n\r\n if (!Selector.equals(x.selector, y.selector))\r\n return false;\r\n\r\n return _.isEqual(x.objects, y.objects);\r\n }\r\n }\r\n}\n\n\n/** WEBPACK FOOTER **\n ** ./src/Clients/Visuals/common/visualObjectRepetition.ts\n **/","/*\r\n * Power BI Visualizations\r\n *\r\n * Copyright (c) Microsoft Corporation\r\n * All rights reserved. \r\n * MIT License\r\n *\r\n * Permission is hereby granted, free of charge, to any person obtaining a copy\r\n * of this software and associated documentation files (the \"\"Software\"\"), to deal\r\n * in the Software without restriction, including without limitation the rights\r\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\r\n * copies of the Software, and to permit persons to whom the Software is\r\n * furnished to do so, subject to the following conditions:\r\n * \r\n * The above copyright notice and this permission notice shall be included in \r\n * all copies or substantial portions of the Software.\r\n * \r\n * THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR \r\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, \r\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE \r\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER \r\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\r\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\r\n * THE SOFTWARE.\r\n */\r\n\r\n/// <reference path=\"../_references.ts\"/>\r\n\r\nmodule powerbi.visuals {\r\n import DefaultSQExprVisitor = powerbi.data.DefaultSQExprVisitor;\r\n import SemanticFilter = powerbi.data.SemanticFilter;\r\n import SQColumnRefExpr = powerbi.data.SQColumnRefExpr;\r\n import SQConstantExpr = powerbi.data.SQConstantExpr;\r\n import SQContainsExpr = powerbi.data.SQContainsExpr;\r\n import SQExpr = powerbi.data.SQExpr;\r\n import UrlUtils = jsCommon.UrlUtils;\r\n\r\n /** Helper module for converting a DataView into SlicerData. */\r\n export module DataConversion {\r\n export function convert(dataView: DataView, localizedSelectAllText: string, interactivityService: IInteractivityService | ISelectionHandler, hostServices: IVisualHostServices): SlicerData {\r\n debug.assertValue(hostServices, 'hostServices');\r\n if (!dataView || !dataView.categorical || _.isEmpty(dataView.categorical.categories))\r\n return;\r\n\r\n let identityFields = dataView.categorical.categories[0].identityFields;\r\n\r\n if (!identityFields)\r\n return;\r\n\r\n let filter: SemanticFilter = <SemanticFilter>(\r\n dataView.metadata &&\r\n dataView.metadata.objects &&\r\n DataViewObjects.getValue(dataView.metadata.objects, visuals.slicerProps.filterPropertyIdentifier));\r\n\r\n let analyzer = hostServices.analyzeFilter({\r\n dataView: dataView,\r\n defaultValuePropertyId: slicerProps.defaultValue,\r\n filter: filter,\r\n fieldSQExprs: identityFields\r\n });\r\n if (!analyzer)\r\n return;\r\n \r\n let analyzedSemanticFilter = <SemanticFilter>analyzer.filter;\r\n if (analyzedSemanticFilter && !SemanticFilter.isSameFilter(analyzedSemanticFilter, filter)) {\r\n (<ISelectionHandler>interactivityService).handleClearSelection();\r\n let filterPropertyIdentifier = slicerProps.filterPropertyIdentifier;\r\n let properties: { [propertyName: string]: DataViewPropertyValue } = {};\r\n properties[filterPropertyIdentifier.propertyName] = analyzer.filter;\r\n let instance = {\r\n objectName: filterPropertyIdentifier.objectName,\r\n selector: undefined,\r\n properties: properties\r\n };\r\n\r\n let changes: VisualObjectInstancesToPersist = {\r\n merge: [instance]\r\n };\r\n hostServices.persistProperties(changes);\r\n }\r\n\r\n let slicerData = getSlicerData(analyzer, dataView.metadata, dataView.categorical, localizedSelectAllText, <IInteractivityService>interactivityService, hostServices);\r\n return slicerData;\r\n }\r\n\r\n function getSlicerData(\r\n analyzer: AnalyzedFilter,\r\n dataViewMetadata: DataViewMetadata,\r\n categorical: DataViewCategorical,\r\n localizedSelectAllText: string, interactivityService: IInteractivityService, hostServices: IVisualHostServices): SlicerData {\r\n let isInvertedSelectionMode: boolean = interactivityService && interactivityService.isSelectionModeInverted();\r\n let selectedScopeIds = analyzer.selectedIdentities;\r\n\r\n let hasSelectionOverride = !_.isEmpty(selectedScopeIds) || isInvertedSelectionMode === true;\r\n if (!isInvertedSelectionMode && analyzer.filter)\r\n isInvertedSelectionMode = analyzer.isNotFilter;\r\n\r\n if (interactivityService) {\r\n // To indicate whether the selection is Not selected items\r\n interactivityService.setSelectionModeInverted(isInvertedSelectionMode);\r\n\r\n // defaultValueMode will be used when determine show/hide clear button.\r\n interactivityService.setDefaultValueMode(SemanticFilter.isDefaultFilter(<SemanticFilter>analyzer.filter));\r\n }\r\n\r\n let category = categorical.categories[0];\r\n let categoryValuesLen: number = category && category.values ? category.values.length : 0;\r\n let slicerDataPoints: SlicerDataPoint[] = [];\r\n let formatString = valueFormatter.getFormatString(category.source, slicerProps.formatString);\r\n let numOfSelected: number = 0;\r\n let valueCounts = categorical.values && categorical.values[0] && categorical.values[0].values;\r\n if (valueCounts && _.isEmpty(valueCounts))\r\n valueCounts = undefined;\r\n\r\n debug.assert(!valueCounts || valueCounts.length === categoryValuesLen, \"valueCounts doesn't match values\");\r\n let isImageData = dataViewMetadata &&\r\n !_.isEmpty(dataViewMetadata.columns) && converterHelper.isImageUrlColumn(dataViewMetadata.columns[0]);\r\n let displayNameIdentityPairs: DisplayNameIdentityPair[] = [];\r\n for (let i = 0; i < categoryValuesLen; i++) {\r\n let scopeId = category.identity && category.identity[i];\r\n let value = category.values && category.values[i];\r\n let count = valueCounts && valueCounts[i];\r\n\r\n let isRetained = hasSelectionOverride ? SlicerUtil.tryRemoveValueFromRetainedList(scopeId, selectedScopeIds) : false;\r\n let label: string = valueFormatter.format(value, formatString);\r\n let isImage = isImageData === true && UrlUtils.isValidImageUrl(label);\r\n let slicerData: SlicerDataPoint = {\r\n value: label,\r\n tooltip: label,\r\n identity: SelectionIdBuilder.builder().withCategory(category, i).createSelectionId(),\r\n selected: isRetained,\r\n count: <number>count,\r\n isImage: isImage,\r\n };\r\n\r\n if (isRetained) {\r\n let displayNameIdentityPair: DisplayNameIdentityPair = {\r\n displayName: label,\r\n identity: scopeId\r\n };\r\n displayNameIdentityPairs.push(displayNameIdentityPair);\r\n }\r\n\r\n slicerDataPoints.push(slicerData);\r\n if (slicerData.selected)\r\n numOfSelected++;\r\n }\r\n\r\n if (!_.isEmpty(displayNameIdentityPairs))\r\n hostServices.setIdentityDisplayNames(displayNameIdentityPairs);\r\n\r\n // Add retained values that are not in the returned dataview to the value list.\r\n if (hasSelectionOverride && !_.isEmpty(selectedScopeIds)) {\r\n \r\n let displayNamesIdentityPairs = hostServices.getIdentityDisplayNames(selectedScopeIds);\r\n if (!_.isEmpty(displayNamesIdentityPairs)) {\r\n for (let pair of displayNamesIdentityPairs) {\r\n // When there is no valueCounts, set count to be undefined, otherwise use 0 as the count for retained values\r\n let slicerData: SlicerDataPoint = {\r\n value: pair.displayName,\r\n tooltip: pair.displayName,\r\n identity: SelectionIdBuilder.builder().withCategoryIdentity(category, pair.identity).createSelectionId(),\r\n selected: true,\r\n count: valueCounts != null ? 0 : undefined,\r\n };\r\n\r\n slicerDataPoints.push(slicerData);\r\n numOfSelected++;\r\n }\r\n }\r\n }\r\n\r\n let searchKey = getSearchKey(dataViewMetadata);\r\n let defaultSettings = createDefaultSettings(dataViewMetadata);\r\n // When search is on, we hide the SelectAll option.\r\n if (defaultSettings.selection.selectAllCheckboxEnabled && _.isEmpty(searchKey)) {\r\n slicerDataPoints.unshift({\r\n value: localizedSelectAllText,\r\n tooltip: localizedSelectAllText,\r\n identity: SelectionId.createWithMeasure(localizedSelectAllText),\r\n selected: !!isInvertedSelectionMode && numOfSelected === 0,\r\n isSelectAllDataPoint: true,\r\n count: undefined,\r\n });\r\n }\r\n\r\n let slicerData: SlicerData = {\r\n categorySourceName: category.source.displayName,\r\n slicerSettings: defaultSettings,\r\n slicerDataPoints: slicerDataPoints,\r\n hasSelectionOverride: hasSelectionOverride,\r\n defaultValue: analyzer.defaultValue,\r\n searchKey: searchKey,\r\n };\r\n\r\n return slicerData;\r\n }\r\n\r\n function getSearchKey(dataViewMetadata: DataViewMetadata): string {\r\n let selfFilter = DataViewObjects.getValue<SemanticFilter>(dataViewMetadata.objects, slicerProps.selfFilterPropertyIdentifier, undefined);\r\n // The searchKey need to be empty string so that the inputbox dom content gets updated after search is removed.\r\n // When the search key is undefined, the previous content will not updated while binding data.\r\n if (!selfFilter)\r\n return '';\r\n \r\n let filterItems = selfFilter.conditions();\r\n debug.assert(filterItems.length === 1, 'There should be exactly 1 filter expression.');\r\n let containsFilter = <SQContainsExpr>filterItems[0];\r\n if (containsFilter) {\r\n let containsValueVisitor = new ConditionsFilterValueVisitor();\r\n containsFilter.accept(containsValueVisitor);\r\n return containsValueVisitor.getValueForField();\r\n }\r\n }\r\n\r\n function createDefaultSettings(dataViewMetadata: DataViewMetadata): SlicerSettings {\r\n let defaultSettings = Slicer.DefaultStyleProperties();\r\n let objects = dataViewMetadata.objects;\r\n let forceSingleSelect = dataViewMetadata.columns && _.some(dataViewMetadata.columns, (column) => column.discourageAggregationAcrossGroups);\r\n\r\n if (objects) {\r\n defaultSettings.general.outlineColor = DataViewObjects.getFillColor(objects, slicerProps.general.outlineColor, defaultSettings.general.outlineColor);\r\n defaultSettings.general.outlineWeight = DataViewObjects.getValue<number>(objects, slicerProps.general.outlineWeight, defaultSettings.general.outlineWeight);\r\n defaultSettings.general.orientation = DataViewObjects.getValue<slicerOrientation.Orientation>(objects, slicerProps.general.orientation, defaultSettings.general.orientation);\r\n\r\n defaultSettings.header.show = DataViewObjects.getValue<boolean>(objects, slicerProps.header.show, defaultSettings.header.show);\r\n defaultSettings.header.fontColor = DataViewObjects.getFillColor(objects, slicerProps.header.fontColor, defaultSettings.header.fontColor);\r\n let headerBackground = DataViewObjects.getFillColor(objects, slicerProps.header.background);\r\n if (headerBackground)\r\n defaultSettings.header.background = headerBackground;\r\n defaultSettings.header.outline = DataViewObjects.getValue<string>(objects, slicerProps.header.outline, defaultSettings.header.outline);\r\n defaultSettings.header.textSize = DataViewObjects.getValue<number>(objects, slicerProps.header.textSize, defaultSettings.header.textSize);\r\n\r\n defaultSettings.slicerText.color = DataViewObjects.getFillColor(objects, slicerProps.items.fontColor, defaultSettings.slicerText.color);\r\n let textBackground = DataViewObjects.getFillColor(objects, slicerProps.items.background);\r\n if (textBackground)\r\n defaultSettings.slicerText.background = textBackground;\r\n\r\n defaultSettings.slicerText.outline = DataViewObjects.getValue<string>(objects, slicerProps.items.outline, defaultSettings.slicerText.outline);\r\n defaultSettings.slicerText.textSize = DataViewObjects.getValue<number>(objects, slicerProps.items.textSize, defaultSettings.slicerText.textSize);\r\n\r\n defaultSettings.selection.selectAllCheckboxEnabled = !forceSingleSelect && DataViewObjects.getValue<boolean>(objects, slicerProps.selection.selectAllCheckboxEnabled, defaultSettings.selection.selectAllCheckboxEnabled);\r\n defaultSettings.selection.singleSelect = forceSingleSelect || DataViewObjects.getValue<boolean>(objects, slicerProps.selection.singleSelect, defaultSettings.selection.singleSelect);\r\n defaultSettings.search.enabled = DataViewObjects.getValue<boolean>(objects, slicerProps.general.selfFilterEnabled, defaultSettings.search.enabled);\r\n }\r\n\r\n return defaultSettings;\r\n }\r\n\r\n class ConditionsFilterValueVisitor extends DefaultSQExprVisitor<void> {\r\n private value: string;\r\n private fieldExpr: SQExpr;\r\n\r\n public visitConstant(expr: SQConstantExpr): void {\r\n if (expr.type && expr.type.text)\r\n this.value = expr.value;\r\n }\r\n\r\n public visitContains(expr: SQContainsExpr): void {\r\n expr.left.accept(this);\r\n expr.right.accept(this);\r\n }\r\n\r\n public visitColumnRef(expr: SQColumnRefExpr): void {\r\n this.fieldExpr = expr;\r\n }\r\n\r\n public visitDefault(expr: SQExpr): void {\r\n this.value = undefined;\r\n this.fieldExpr = undefined;\r\n }\r\n\r\n public getValueForField(): string {\r\n return this.fieldExpr && this.value;\r\n }\r\n }\r\n }\r\n}\n\n\n/** WEBPACK FOOTER **\n ** ./src/Clients/Visuals/converter/slicer.converter.ts\n **/","/*\r\n * Power BI Visualizations\r\n *\r\n * Copyright (c) Microsoft Corporation\r\n * All rights reserved. \r\n * MIT License\r\n *\r\n * Permission is hereby granted, free of charge, to any person obtaining a copy\r\n * of this software and associated documentation files (the \"\"Software\"\"), to deal\r\n * in the Software without restriction, including without limitation the rights\r\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\r\n * copies of the Software, and to permit persons to whom the Software is\r\n * furnished to do so, subject to the following conditions:\r\n * \r\n * The above copyright notice and this permission notice shall be included in \r\n * all copies or substantial portions of the Software.\r\n * \r\n * THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR \r\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, \r\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE \r\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER \r\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\r\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\r\n * THE SOFTWARE.\r\n */\r\n\r\n/// <reference path=\"../_references.ts\"/>\r\n\r\nmodule powerbi {\r\n\r\n import shapes = powerbi.visuals.shapes;\r\n import IRect = powerbi.visuals.IRect;\r\n import IThickness = visuals.shapes.IThickness;\r\n\r\n /** Defines possible content positions. */\r\n export const enum ContentPositions {\r\n\r\n /** Content position is not defined. */\r\n None = 0,\r\n\r\n /** Content aligned top left. */\r\n TopLeft = 1,\r\n\r\n /** Content aligned top center. */\r\n TopCenter = 2,\r\n\r\n /** Content aligned top right. */\r\n TopRight = 4,\r\n\r\n /** Content aligned middle left. */\r\n MiddleLeft = 8,\r\n\r\n /** Content aligned middle center. */\r\n MiddleCenter = 16,\r\n\r\n /** Content aligned middle right. */\r\n MiddleRight = 32,\r\n\r\n /** Content aligned bottom left. */\r\n BottomLeft = 64,\r\n\r\n /** Content aligned bottom center. */\r\n BottomCenter = 128,\r\n\r\n /** Content aligned bottom right. */\r\n BottomRight = 256,\r\n\r\n /** Content is placed inside the bounding rectangle in the center. */\r\n InsideCenter = 512,\r\n\r\n /** Content is placed inside the bounding rectangle at the base. */\r\n InsideBase = 1024,\r\n\r\n /** Content is placed inside the bounding rectangle at the end. */\r\n InsideEnd = 2048,\r\n\r\n /** Content is placed outside the bounding rectangle at the base. */\r\n OutsideBase = 4096,\r\n\r\n /** Content is placed outside the bounding rectangle at the end. */\r\n OutsideEnd = 8192,\r\n\r\n /** Content supports all possible positions. */\r\n All =\r\n TopLeft |\r\n TopCenter |\r\n TopRight |\r\n MiddleLeft |\r\n MiddleCenter |\r\n MiddleRight |\r\n BottomLeft |\r\n BottomCenter |\r\n BottomRight |\r\n InsideCenter |\r\n InsideBase |\r\n InsideEnd |\r\n OutsideBase |\r\n OutsideEnd,\r\n }\r\n\r\n /**\r\n * Rectangle orientation. Rectangle orientation is used to define vertical or horizontal orientation \r\n * and starting/ending side of the rectangle.\r\n */\r\n export enum RectOrientation {\r\n /** Rectangle with no specific orientation. */\r\n None,\r\n\r\n /** Vertical rectangle with base at the bottom. */\r\n VerticalBottomTop,\r\n\r\n /** Vertical rectangle with base at the top. */\r\n VerticalTopBottom,\r\n\r\n /** Horizontal rectangle with base at the left. */\r\n HorizontalLeftRight,\r\n\r\n /** Horizontal rectangle with base at the right. */\r\n HorizontalRightLeft,\r\n }\r\n\r\n /**\r\n * Defines if panel elements are allowed to be positioned \r\n * outside of the panel boundaries.\r\n */\r\n export enum OutsidePlacement {\r\n /** Elements can be positioned outside of the panel. */\r\n Allowed,\r\n\r\n /** Elements can not be positioned outside of the panel. */\r\n Disallowed,\r\n\r\n /** Elements can be partially outside of the panel. */\r\n Partial\r\n }\r\n\r\n /**\r\n * Defines an interface for information needed for default label positioning. Used in DataLabelsPanel.\r\n * Note the question marks: none of the elements are mandatory.\r\n */\r\n export interface IDataLabelSettings {\r\n /** Distance from the anchor point. */\r\n anchorMargin?: number;\r\n\r\n /** Orientation of the anchor rectangle. */\r\n anchorRectOrientation?: RectOrientation;\r\n\r\n /** Preferable position for the label. */\r\n contentPosition?: ContentPositions;\r\n\r\n /** Defines the rules if the elements can be positioned outside panel bounds. */\r\n outsidePlacement?: OutsidePlacement;\r\n\r\n /** Defines the valid positions if repositionOverlapped is true. */\r\n validContentPositions?: ContentPositions;\r\n\r\n /** Defines maximum moving distance to reposition an element. */\r\n minimumMovingDistance?: number;\r\n\r\n /** Defines minimum moving distance to reposition an element. */\r\n maximumMovingDistance?: number;\r\n\r\n /** Opacity effect of the label. Use it for dimming. */\r\n opacity?: number;\r\n }\r\n\r\n /**\r\n * Defines an interface for information needed for label positioning. \r\n * None of the elements are mandatory, but at least anchorPoint OR anchorRect is needed.\r\n */\r\n export interface IDataLabelInfo extends IDataLabelSettings {\r\n\r\n /** The point to which label is anchored. */\r\n anchorPoint?: shapes.IPoint;\r\n\r\n /** The rectangle to which label is anchored. */\r\n anchorRect?: IRect;\r\n\r\n /** Disable label rendering and processing. */\r\n hideLabel?: boolean;\r\n\r\n /**\r\n * Defines the visibility rank. This will not be processed by arrange phase, \r\n * but can be used for preprocessing the hideLabel value.\r\n */\r\n visibilityRank?: number;\r\n\r\n /** Defines the starting offset from AnchorRect. */\r\n offset?: number;\r\n\r\n /** Defines the callout line data. It is calculated and used during processing. */\r\n callout?: { start: shapes.IPoint; end: shapes.IPoint; };\r\n\r\n /** Source of the label. */\r\n source?: any;\r\n\r\n size?: shapes.ISize;\r\n }\r\n\r\n /** Interface for label rendering. */\r\n export interface IDataLabelRenderer {\r\n renderLabelArray(labels: IArrangeGridElementInfo[]): void;\r\n }\r\n\r\n /** Interface used in internal arrange structures. */\r\n export interface IArrangeGridElementInfo {\r\n element: IDataLabelInfo;\r\n rect: IRect;\r\n }\r\n\r\n /**\r\n * Arranges label elements using the anchor point or rectangle. Collisions\r\n * between elements can be automatically detected and as a result elements \r\n * can be repositioned or get hidden.\r\n */\r\n export class DataLabelManager {\r\n\r\n public movingStep: number = 3;\r\n public hideOverlapped: boolean = true;\r\n public static DefaultAnchorMargin: number = 0; // For future use\r\n public static DefaultMaximumMovingDistance: number = 12;\r\n public static DefaultMinimumMovingDistance: number = 3;\r\n public static InflateAmount: number = 5;\r\n\r\n // The global settings for all labels. \r\n // They can be oweridden by each label we add into the panel, because contains same properties.\r\n private defaultDataLabelSettings: IDataLabelSettings = {\r\n anchorMargin: DataLabelManager.DefaultAnchorMargin,\r\n anchorRectOrientation: RectOrientation.None,\r\n contentPosition: ContentPositions.BottomCenter,\r\n outsidePlacement: OutsidePlacement.Disallowed,\r\n maximumMovingDistance: DataLabelManager.DefaultMaximumMovingDistance,\r\n minimumMovingDistance: DataLabelManager.DefaultMinimumMovingDistance,\r\n validContentPositions: ContentPositions.BottomCenter,\r\n opacity: 1\r\n };\r\n\r\n public get defaultSettings(): IDataLabelSettings {\r\n return this.defaultDataLabelSettings;\r\n }\r\n\r\n /** Arranges the lables position and visibility*/\r\n public hideCollidedLabels(viewport: IViewport, data: any[], layout: any, addTransform: boolean = false): powerbi.visuals.LabelEnabledDataPoint[] {\r\n\r\n // Split size into a grid\r\n let arrangeGrid = new DataLabelArrangeGrid(viewport, data, layout);\r\n let filteredData = [];\r\n let transform: shapes.IVector = { x: 0, y: 0 };\r\n\r\n if (addTransform) {\r\n transform.x = viewport.width / 2;\r\n transform.y = viewport.height / 2;\r\n }\r\n\r\n for (let i = 0, len = data.length; i < len; i++) {\r\n\r\n // Filter unwanted data points\r\n if (!layout.filter(data[i]))\r\n continue;\r\n\r\n // Set default values where properties values are undefined\r\n let info = this.getLabelInfo(data[i]);\r\n\r\n info.anchorPoint = {\r\n x: layout.labelLayout.x(data[i]) + transform.x,\r\n y: layout.labelLayout.y(data[i]) + transform.y,\r\n };\r\n\r\n let position: IRect = this.calculateContentPosition(info, info.contentPosition, data[i].size, info.anchorMargin);\r\n\r\n if (DataLabelManager.isValid(position) && !this.hasCollisions(arrangeGrid, info, position, viewport)) {\r\n data[i].labelX = position.left - transform.x;\r\n data[i].labelY = position.top - transform.y;\r\n\r\n // Keep track of all panel elements positions.\r\n arrangeGrid.add(info, position);\r\n\r\n // Save all data points to display\r\n filteredData.push(data[i]);\r\n }\r\n }\r\n\r\n return filteredData;\r\n }\r\n\r\n /**\r\n * Merges the label element info with the panel element info and returns correct label info.\r\n * @param source The label info.\r\n */\r\n public getLabelInfo(source: IDataLabelInfo): IDataLabelInfo {\r\n\r\n let settings = this.defaultDataLabelSettings;\r\n source.anchorMargin = source.anchorMargin !== undefined ? source.anchorMargin : settings.anchorMargin;\r\n source.anchorRectOrientation = source.anchorRectOrientation !== undefined ? source.anchorRectOrientation : settings.anchorRectOrientation;\r\n source.contentPosition = source.contentPosition !== undefined ? source.contentPosition : settings.contentPosition;\r\n source.maximumMovingDistance = source.maximumMovingDistance !== undefined ? source.maximumMovingDistance : settings.maximumMovingDistance;\r\n source.minimumMovingDistance = source.minimumMovingDistance !== undefined ? source.minimumMovingDistance : settings.minimumMovingDistance;\r\n source.outsidePlacement = source.outsidePlacement !== undefined ? source.outsidePlacement : settings.outsidePlacement;\r\n source.validContentPositions = source.validContentPositions !== undefined ? source.validContentPositions : settings.validContentPositions;\r\n source.opacity = source.opacity !== undefined ? source.opacity : settings.opacity;\r\n source.maximumMovingDistance += source.anchorMargin;\r\n return source;\r\n }\r\n\r\n /**\r\n * (Private) Calculates element position using anchor point..\r\n */\r\n private calculateContentPositionFromPoint(anchorPoint: shapes.IPoint, contentPosition: ContentPositions, contentSize: shapes.ISize, offset: number): IRect {\r\n let position: shapes.IPoint = { x: 0, y: 0 };\r\n if (anchorPoint) {\r\n\r\n if (anchorPoint.x !== undefined && isFinite(anchorPoint.x)) {\r\n position.x = anchorPoint.x;\r\n switch (contentPosition) {\r\n // D3 positions the label in the middle by default.\r\n // The algorithem asumed the label was positioned in right so this is why we add/substract half width\r\n case ContentPositions.TopLeft:\r\n case ContentPositions.MiddleLeft:\r\n case ContentPositions.BottomLeft:\r\n position.x -= contentSize.width / 2.0;\r\n break;\r\n\r\n case ContentPositions.TopRight:\r\n case ContentPositions.MiddleRight:\r\n case ContentPositions.BottomRight:\r\n position.x += contentSize.width / 2.0;\r\n break;\r\n }\r\n }\r\n\r\n if (anchorPoint.y !== undefined && isFinite(anchorPoint.y)) {\r\n position.y = anchorPoint.y;\r\n switch (contentPosition) {\r\n case ContentPositions.MiddleLeft:\r\n case ContentPositions.MiddleCenter:\r\n case ContentPositions.MiddleRight:\r\n position.y -= contentSize.height / 2.0;\r\n break;\r\n case ContentPositions.TopRight:\r\n case ContentPositions.TopLeft:\r\n case ContentPositions.TopCenter:\r\n position.y -= contentSize.height;\r\n break;\r\n }\r\n }\r\n\r\n if (offset !== undefined && isFinite(offset)) {\r\n switch (contentPosition) {\r\n case ContentPositions.TopLeft:\r\n position.x -= offset;\r\n position.y -= offset;\r\n break;\r\n case ContentPositions.MiddleLeft:\r\n position.x -= offset;\r\n break;\r\n case ContentPositions.BottomLeft:\r\n position.x -= offset;\r\n position.y += offset;\r\n break;\r\n case ContentPositions.TopCenter:\r\n position.y -= offset;\r\n break;\r\n case ContentPositions.MiddleCenter:\r\n // Offset is not applied\r\n break;\r\n case ContentPositions.BottomCenter:\r\n position.y += offset;\r\n break;\r\n case ContentPositions.TopRight:\r\n position.x += offset;\r\n position.y -= offset;\r\n break;\r\n case ContentPositions.MiddleRight:\r\n position.x += offset;\r\n break;\r\n case ContentPositions.BottomRight:\r\n position.x += offset;\r\n position.y += offset;\r\n break;\r\n default:\r\n debug.assertFail(\"Unsupported content position.\");\r\n break;\r\n }\r\n }\r\n }\r\n return { left: position.x, top: position.y, width: contentSize.width, height: contentSize.height };\r\n }\r\n\r\n /** (Private) Calculates element position using anchor rect. */\r\n private calculateContentPositionFromRect(anchorRect: IRect, anchorRectOrientation: RectOrientation, contentPosition: ContentPositions, contentSize: shapes.ISize, offset: number): IRect {\r\n\r\n switch (contentPosition) {\r\n case ContentPositions.InsideCenter:\r\n return this.handleInsideCenterPosition(anchorRectOrientation, contentSize, anchorRect, offset);\r\n case ContentPositions.InsideEnd:\r\n return this.handleInsideEndPosition(anchorRectOrientation, contentSize, anchorRect, offset);\r\n case ContentPositions.InsideBase:\r\n return this.handleInsideBasePosition(anchorRectOrientation, contentSize, anchorRect, offset);\r\n case ContentPositions.OutsideEnd:\r\n return this.handleOutsideEndPosition(anchorRectOrientation, contentSize, anchorRect, offset);\r\n case ContentPositions.OutsideBase:\r\n return this.handleOutsideBasePosition(anchorRectOrientation, contentSize, anchorRect, offset);\r\n default:\r\n debug.assertFail(\"Unsupported ContentPosition.\");\r\n }\r\n\r\n return { left: 0, top: 0, width: -1, height: -1 };\r\n }\r\n\r\n /** (Private) Calculates element inside center position using anchor rect. */\r\n private handleInsideCenterPosition(anchorRectOrientation: RectOrientation, contentSize: shapes.ISize, anchorRect: IRect, offset: number): IRect {\r\n switch (anchorRectOrientation) {\r\n case RectOrientation.VerticalBottomTop:\r\n case RectOrientation.VerticalTopBottom:\r\n return LocationConverter.middleVertical(contentSize, anchorRect, offset);\r\n case RectOrientation.HorizontalLeftRight:\r\n case RectOrientation.HorizontalRightLeft:\r\n default:\r\n return LocationConverter.middleHorizontal(contentSize, anchorRect, offset);\r\n }\r\n }\r\n\r\n /** (Private) Calculates element inside end position using anchor rect. */\r\n private handleInsideEndPosition(anchorRectOrientation: RectOrientation, contentSize: shapes.ISize, anchorRect: IRect, offset: number): IRect {\r\n switch (anchorRectOrientation) {\r\n case RectOrientation.VerticalBottomTop:\r\n return LocationConverter.topInside(contentSize, anchorRect, offset);\r\n case RectOrientation.VerticalTopBottom:\r\n return LocationConverter.bottomInside(contentSize, anchorRect, offset);\r\n case RectOrientation.HorizontalRightLeft:\r\n return LocationConverter.leftInside(contentSize, anchorRect, offset);\r\n case RectOrientation.HorizontalLeftRight:\r\n default:\r\n return LocationConverter.rightInside(contentSize, anchorRect, offset);\r\n }\r\n }\r\n\r\n /** (Private) Calculates element inside base position using anchor rect. */\r\n private handleInsideBasePosition(anchorRectOrientation: RectOrientation, contentSize: shapes.ISize, anchorRect: IRect, offset: number): IRect {\r\n switch (anchorRectOrientation) {\r\n case RectOrientation.VerticalBottomTop:\r\n return LocationConverter.bottomInside(contentSize, anchorRect, offset);\r\n case RectOrientation.VerticalTopBottom:\r\n return LocationConverter.topInside(contentSize, anchorRect, offset);\r\n case RectOrientation.HorizontalRightLeft:\r\n return LocationConverter.rightInside(contentSize, anchorRect, offset);\r\n case RectOrientation.HorizontalLeftRight:\r\n default:\r\n return LocationConverter.leftInside(contentSize, anchorRect, offset);\r\n }\r\n }\r\n\r\n /** (Private) Calculates element outside end position using anchor rect. */\r\n private handleOutsideEndPosition(anchorRectOrientation: RectOrientation, contentSize: shapes.ISize, anchorRect: IRect, offset: number): IRect {\r\n switch (anchorRectOrientation) {\r\n case RectOrientation.VerticalBottomTop:\r\n return LocationConverter.topOutside(contentSize, anchorRect, offset);\r\n case RectOrientation.VerticalTopBottom:\r\n return LocationConverter.bottomOutside(contentSize, anchorRect, offset);\r\n case RectOrientation.HorizontalRightLeft:\r\n return LocationConverter.leftOutside(contentSize, anchorRect, offset);\r\n case RectOrientation.HorizontalLeftRight:\r\n default:\r\n return LocationConverter.rightOutside(contentSize, anchorRect, offset);\r\n }\r\n }\r\n\r\n /** (Private) Calculates element outside base position using anchor rect. */\r\n private handleOutsideBasePosition(anchorRectOrientation: RectOrientation, contentSize: shapes.ISize, anchorRect: IRect, offset: number): IRect {\r\n switch (anchorRectOrientation) {\r\n case RectOrientation.VerticalBottomTop:\r\n return LocationConverter.bottomOutside(contentSize, anchorRect, offset);\r\n case RectOrientation.VerticalTopBottom:\r\n return LocationConverter.topOutside(contentSize, anchorRect, offset);\r\n case RectOrientation.HorizontalRightLeft:\r\n return LocationConverter.rightOutside(contentSize, anchorRect, offset);\r\n case RectOrientation.HorizontalLeftRight:\r\n default:\r\n return LocationConverter.leftOutside(contentSize, anchorRect, offset);\r\n }\r\n }\r\n\r\n /** (Private) Calculates element position. */\r\n private calculateContentPosition(anchoredElementInfo: IDataLabelInfo, contentPosition: ContentPositions, contentSize: shapes.ISize, offset: number): IRect {\r\n\r\n if (contentPosition !== ContentPositions.InsideEnd &&\r\n contentPosition !== ContentPositions.InsideCenter &&\r\n contentPosition !== ContentPositions.InsideBase &&\r\n contentPosition !== ContentPositions.OutsideBase &&\r\n contentPosition !== ContentPositions.OutsideEnd) {\r\n // Determine position using anchor point.\r\n return this.calculateContentPositionFromPoint(\r\n anchoredElementInfo.anchorPoint,\r\n contentPosition,\r\n contentSize,\r\n offset);\r\n }\r\n\r\n // Determine position using anchor rectangle.\r\n return this.calculateContentPositionFromRect(\r\n anchoredElementInfo.anchorRect,\r\n anchoredElementInfo.anchorRectOrientation,\r\n contentPosition,\r\n contentSize,\r\n offset);\r\n }\r\n\r\n /** (Private) Check for collisions. */\r\n private hasCollisions(arrangeGrid: DataLabelArrangeGrid, info: IDataLabelInfo, position: IRect, size: shapes.ISize): boolean {\r\n let rect = shapes.Rect;\r\n\r\n if (arrangeGrid.hasConflict(position)) {\r\n return true;\r\n }\r\n // Since we divide the height by 2 we add it back to the top of the view port so labels won't be cut off\r\n let intersection = { left: 0, top: position.height / 2, width: size.width, height: size.height };\r\n intersection = rect.inflate(intersection, { left: DataLabelManager.InflateAmount, top: 0, right: DataLabelManager.InflateAmount, bottom: 0 });\r\n\r\n intersection = rect.intersect(intersection, position);\r\n\r\n if (rect.isEmpty(intersection))\r\n // Empty rectangle means there is a collision\r\n return true;\r\n\r\n let lessWithPrecision = powerbi.Double.lessWithPrecision;\r\n\r\n switch (info.outsidePlacement) {\r\n // D3 positions the label in the middle by default.\r\n // The algorithem asumed the label was positioned in right so this is why we devide by 2 or 4\r\n case OutsidePlacement.Disallowed:\r\n return lessWithPrecision(intersection.width, position.width) ||\r\n lessWithPrecision(intersection.height, position.height / 2);\r\n\r\n case OutsidePlacement.Partial:\r\n return lessWithPrecision(intersection.width, position.width / 2) ||\r\n lessWithPrecision(intersection.height, position.height / 4);\r\n }\r\n return false;\r\n }\r\n\r\n public static isValid(rect: IRect): boolean {\r\n return !shapes.Rect.isEmpty(rect) && (rect.width > 0 && rect.height > 0);\r\n }\r\n }\r\n\r\n /**\r\n * Utility class to speed up the conflict detection by collecting the arranged items in the DataLabelsPanel. \r\n */\r\n export class DataLabelArrangeGrid {\r\n\r\n private grid: IArrangeGridElementInfo[][][] = [];\r\n //size of a grid cell \r\n private cellSize: shapes.ISize;\r\n private rowCount: number;\r\n private colCount: number;\r\n\r\n private static ARRANGEGRID_MIN_COUNT = 1;\r\n private static ARRANGEGRID_MAX_COUNT = 100;\r\n\r\n /**\r\n * Creates new ArrangeGrid.\r\n * @param size The available size\r\n */\r\n constructor(size: shapes.ISize, elements: any[], layout: powerbi.visuals.ILabelLayout) {\r\n if (size.width === 0 || size.height === 0) {\r\n this.cellSize = size;\r\n this.rowCount = this.colCount = 0;\r\n }\r\n\r\n let baseProperties: TextProperties = {\r\n fontFamily: powerbi.visuals.dataLabelUtils.LabelTextProperties.fontFamily,\r\n fontSize: powerbi.visuals.dataLabelUtils.LabelTextProperties.fontSize,\r\n fontWeight: powerbi.visuals.dataLabelUtils.LabelTextProperties.fontWeight,\r\n };\r\n\r\n //sets the cell size to be twice of the Max with and Max height of the elements \r\n this.cellSize = { width: 0, height: 0 };\r\n for (let i = 0, len = elements.length; i < len; i++) {\r\n let child = elements[i];\r\n\r\n // Fill label field\r\n child.labeltext = layout.labelText(child);\r\n\r\n let properties: TextProperties = Prototype.inherit(baseProperties);\r\n properties.text = child.labeltext;\r\n properties.fontSize = (child.data) ? child.data.labelFontSize :\r\n child.labelFontSize ? child.labelFontSize : powerbi.visuals.dataLabelUtils.LabelTextProperties.fontSize;\r\n\r\n child.size = {\r\n width: TextMeasurementService.measureSvgTextWidth(properties),\r\n height: TextMeasurementService.estimateSvgTextHeight(properties),\r\n };\r\n\r\n let w = child.size.width * 2;\r\n let h = child.size.height * 2;\r\n if (w > this.cellSize.width)\r\n this.cellSize.width = w;\r\n if (h > this.cellSize.height)\r\n this.cellSize.height = h;\r\n }\r\n\r\n if (this.cellSize.width === 0)\r\n this.cellSize.width = size.width;\r\n if (this.cellSize.height === 0)\r\n this.cellSize.height = size.height;\r\n\r\n this.colCount = this.getGridRowColCount(this.cellSize.width, size.width, DataLabelArrangeGrid.ARRANGEGRID_MIN_COUNT, DataLabelArrangeGrid.ARRANGEGRID_MAX_COUNT);\r\n this.rowCount = this.getGridRowColCount(this.cellSize.height, size.height, DataLabelArrangeGrid.ARRANGEGRID_MIN_COUNT, DataLabelArrangeGrid.ARRANGEGRID_MAX_COUNT);\r\n this.cellSize.width = size.width / this.colCount;\r\n this.cellSize.height = size.height / this.rowCount;\r\n\r\n let grid = this.grid;\r\n for (let x = 0; x < this.colCount; x++) {\r\n grid[x] = [];\r\n for (let y = 0; y < this.rowCount; y++) {\r\n grid[x][y] = [];\r\n }\r\n }\r\n }\r\n\r\n /**\r\n * Register a new label element.\r\n * @param element The label element to register.\r\n * @param rect The label element position rectangle.\r\n */\r\n public add(element: IDataLabelInfo, rect: IRect) {\r\n let indexRect = this.getGridIndexRect(rect);\r\n let grid = this.grid;\r\n for (let x = indexRect.left; x < indexRect.right; x++) {\r\n for (let y = indexRect.top; y < indexRect.bottom; y++) {\r\n grid[x][y].push({ element: element, rect: rect });\r\n }\r\n }\r\n }\r\n\r\n /**\r\n * Checks for conflict of given rectangle in registered elements.\r\n * @param rect The rectengle to check.\r\n * @return True if conflict is detected.\r\n */\r\n public hasConflict(rect: IRect): boolean {\r\n let indexRect = this.getGridIndexRect(rect);\r\n let grid = this.grid;\r\n let isIntersecting = shapes.Rect.isIntersecting;\r\n\r\n for (let x = indexRect.left; x < indexRect.right; x++) {\r\n for (let y = indexRect.top; y < indexRect.bottom; y++) {\r\n for (let z = 0; z < grid[x][y].length; z++) {\r\n let item = grid[x][y][z];\r\n if (isIntersecting(item.rect, rect)) {\r\n return true;\r\n }\r\n }\r\n }\r\n }\r\n return false;\r\n }\r\n\r\n /**\r\n * Calculates the number of rows or columns in a grid\r\n * @param step is the largest label size (width or height)\r\n * @param length is the grid size (width or height)\r\n * @param minCount is the minimum allowed size\r\n * @param maxCount is the maximum allowed size\r\n * @return the number of grid rows or columns\r\n */\r\n private getGridRowColCount(step: number, length: number, minCount: number, maxCount: number): number {\r\n return Math.min(Math.max(Math.ceil(length / step), minCount), maxCount);\r\n }\r\n\r\n /**\r\n * Returns the grid index of a given recangle\r\n * @param rect The rectengle to check.\r\n * @return grid index as a thickness object.\r\n */\r\n private getGridIndexRect(rect: IRect): IThickness {\r\n let restrict = (n, min, max) => Math.min(Math.max(n, min), max);\r\n return {\r\n left: restrict(Math.floor(rect.left / this.cellSize.width), 0, this.colCount),\r\n top: restrict(Math.floor(rect.top / this.cellSize.height), 0, this.rowCount),\r\n right: restrict(Math.ceil((rect.left + rect.width) / this.cellSize.width), 0, this.colCount),\r\n bottom: restrict(Math.ceil((rect.top + rect.height) / this.cellSize.height), 0, this.rowCount)\r\n };\r\n }\r\n }\r\n\r\n /**\r\n * (Private) Contains methods for calculating the top-left coordinate of rectangle based on content size and anchor rect. \r\n */\r\n module LocationConverter {\r\n\r\n export function topInside(size: shapes.ISize, rect: IRect, offset: number): IRect {\r\n return {\r\n left: rect.left + rect.width / 2.0 - size.width / 2.0,\r\n top: rect.top + offset,\r\n width: size.width,\r\n height: size.height\r\n };\r\n }\r\n\r\n export function bottomInside(size: shapes.ISize, rect: IRect, offset: number): IRect {\r\n return {\r\n left: rect.left + rect.width / 2.0 - size.width / 2.0,\r\n top: (rect.top + rect.height) - size.height - offset,\r\n width: size.width,\r\n height: size.height\r\n };\r\n }\r\n\r\n export function rightInside(size: shapes.ISize, rect: IRect, offset: number): IRect {\r\n return {\r\n left: (rect.left + rect.width) - size.width - offset,\r\n top: rect.top + rect.height / 2.0 - size.height / 2.0,\r\n width: size.width,\r\n height: size.height\r\n };\r\n }\r\n\r\n export function leftInside(size: shapes.ISize, rect: IRect, offset: number): IRect {\r\n return {\r\n left: rect.left + offset,\r\n top: rect.top + rect.height / 2.0 - size.height / 2.0,\r\n width: size.width,\r\n height: size.height\r\n };\r\n }\r\n\r\n export function topOutside(size: shapes.ISize, rect: IRect, offset: number): IRect {\r\n return {\r\n left: rect.left + rect.width / 2.0 - size.width / 2.0,\r\n top: rect.top - size.height - offset,\r\n width: size.width,\r\n height: size.height\r\n };\r\n }\r\n\r\n export function bottomOutside(size: shapes.ISize, rect: IRect, offset: number): IRect {\r\n return {\r\n left: rect.left + rect.width / 2.0 - size.width / 2.0,\r\n top: (rect.top + rect.height) + offset,\r\n width: size.width,\r\n height: size.height\r\n };\r\n }\r\n\r\n export function rightOutside(size: shapes.ISize, rect: IRect, offset: number): IRect {\r\n return {\r\n left: (rect.left + rect.width) + offset,\r\n top: rect.top + rect.height / 2.0 - size.height / 2.0,\r\n width: size.width,\r\n height: size.height\r\n };\r\n }\r\n\r\n export function leftOutside(size: shapes.ISize, rect: IRect, offset: number): IRect {\r\n return {\r\n left: rect.left - size.width - offset,\r\n top: rect.top + rect.height / 2.0 - size.height / 2.0,\r\n width: size.width,\r\n height: size.height\r\n };\r\n }\r\n\r\n export function middleHorizontal(size: shapes.ISize, rect: IRect, offset: number): IRect {\r\n return {\r\n left: rect.left + rect.width / 2.0 - size.width / 2.0 + offset,\r\n top: rect.top + rect.height / 2.0 - size.height / 2.0,\r\n width: size.width,\r\n height: size.height\r\n };\r\n }\r\n\r\n export function middleVertical(size: shapes.ISize, rect: IRect, offset: number): IRect {\r\n return {\r\n left: rect.left + rect.width / 2.0 - size.width / 2.0,\r\n top: rect.top + rect.height / 2.0 - size.height / 2.0 + offset,\r\n width: size.width,\r\n height: size.height\r\n };\r\n }\r\n }\r\n}\n\n\n/** WEBPACK FOOTER **\n ** ./src/Clients/Visuals/formatting/dataLabelManager.ts\n **/","/*\r\n * Power BI Visualizations\r\n *\r\n * Copyright (c) Microsoft Corporation\r\n * All rights reserved. \r\n * MIT License\r\n *\r\n * Permission is hereby granted, free of charge, to any person obtaining a copy\r\n * of this software and associated documentation files (the \"\"Software\"\"), to deal\r\n * in the Software without restriction, including without limitation the rights\r\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\r\n * copies of the Software, and to permit persons to whom the Software is\r\n * furnished to do so, subject to the following conditions:\r\n * \r\n * The above copyright notice and this permission notice shall be included in \r\n * all copies or substantial portions of the Software.\r\n * \r\n * THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR \r\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, \r\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE \r\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER \r\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\r\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\r\n * THE SOFTWARE.\r\n */\r\n\r\n/// <reference path=\"../_references.ts\"/>\r\n\r\nmodule powerbi {\r\n\r\n import shapes = powerbi.visuals.shapes;\r\n import ISize = shapes.ISize;\r\n import IRect = powerbi.visuals.IRect;\r\n import IPoint = shapes.IPoint;\r\n import SelectableDataPoint = powerbi.visuals.SelectableDataPoint;\r\n import Rect = powerbi.visuals.shapes.Rect;\r\n import NewDataLabelUtils = powerbi.visuals.NewDataLabelUtils;\r\n\r\n /**\r\n * Defines possible data label positions relative to rectangles\r\n */\r\n export const enum RectLabelPosition {\r\n\r\n /** Position is not defined. */\r\n None = 0,\r\n \r\n /** Content is placed inside the parent rectangle in the center. */\r\n InsideCenter = 1,\r\n\r\n /** Content is placed inside the parent rectangle at the base. */\r\n InsideBase = 2,\r\n\r\n /** Content is placed inside the parent rectangle at the end. */\r\n InsideEnd = 4,\r\n\r\n /** Content is placed outside the parent rectangle at the base. */\r\n OutsideBase = 8,\r\n\r\n /** Content is placed outside the parent rectangle at the end. */\r\n OutsideEnd = 16,\r\n\r\n /** Content supports all possible positions. */\r\n All =\r\n InsideCenter |\r\n InsideBase |\r\n InsideEnd |\r\n OutsideBase |\r\n OutsideEnd,\r\n\r\n /** Content supports positions inside the rectangle */\r\n InsideAll =\r\n InsideCenter |\r\n InsideBase |\r\n InsideEnd,\r\n }\r\n \r\n /**\r\n * Defines possible data label positions relative to points or circles\r\n */\r\n export const enum NewPointLabelPosition {\r\n /** Position is not defined. */\r\n None = 0,\r\n\r\n Above = 1 << 0,\r\n\r\n Below = 1 << 1,\r\n\r\n Left = 1 << 2,\r\n\r\n Right = 1 << 3,\r\n\r\n BelowRight = 1 << 4,\r\n\r\n BelowLeft = 1 << 5,\r\n\r\n AboveRight = 1 << 6,\r\n\r\n AboveLeft = 1 << 7,\r\n\r\n Center = 1 << 8,\r\n\r\n All =\r\n Above |\r\n Below |\r\n Left |\r\n Right |\r\n BelowRight |\r\n BelowLeft |\r\n AboveRight |\r\n AboveLeft |\r\n Center,\r\n }\r\n \r\n /**\r\n * Rectangle orientation, defined by vertical vs horizontal and which direction\r\n * the \"base\" is at.\r\n */\r\n export const enum NewRectOrientation {\r\n /** Rectangle with no specific orientation. */\r\n None,\r\n\r\n /** Vertical rectangle with base at the bottom. */\r\n VerticalBottomBased,\r\n\r\n /** Vertical rectangle with base at the top. */\r\n VerticalTopBased,\r\n\r\n /** Horizontal rectangle with base at the left. */\r\n HorizontalLeftBased,\r\n\r\n /** Horizontal rectangle with base at the right. */\r\n HorizontalRightBased,\r\n }\r\n\r\n export const enum LabelDataPointParentType {\r\n /* parent shape of data label is a point*/\r\n Point,\r\n \r\n /* parent shape of data label is a rectangle*/\r\n Rectangle,\r\n \r\n /* parent shape of data label is a polygon*/\r\n Polygon\r\n }\r\n\r\n export interface LabelParentRect {\r\n /** The rectangle this data label belongs to */\r\n rect: IRect;\r\n\r\n /** The orientation of the parent rectangle */\r\n orientation: NewRectOrientation;\r\n\r\n /** Valid positions to place the label ordered by preference */\r\n validPositions: RectLabelPosition[];\r\n }\r\n\r\n export interface LabelParentPoint {\r\n /** The point this data label belongs to */\r\n point: IPoint;\r\n\r\n /** The radius of the point to be added to the offset (for circular geometry) */\r\n radius: number;\r\n \r\n /** Valid positions to place the label ordered by preference */\r\n validPositions: NewPointLabelPosition[];\r\n }\r\n\r\n export interface LabelDataPoint {\r\n // Layout members; used by the layout system to position labels\r\n\r\n /** The measured size of the text */\r\n textSize: ISize;\r\n\r\n /** Is data label preferred? Preferred labels will be rendered first */\r\n isPreferred: boolean;\r\n\r\n /** Whether the parent type is a rectangle, point or polygon */\r\n parentType: LabelDataPointParentType;\r\n\r\n /** The parent geometry for the data label */\r\n parentShape: LabelParentRect | LabelParentPoint | LabelParentPolygon;\r\n\r\n /** Whether or not the label has a background */\r\n hasBackground?: boolean;\r\n\r\n // Rendering members that are simply passed through to the label for rendering purposes\r\n\r\n /** Text to be displayed in the label */\r\n text: string;\r\n\r\n /** A text that represent the label tooltip */\r\n tooltip?: string;\r\n\r\n /** Color to use for the data label if drawn inside */\r\n insideFill: string;\r\n\r\n /** Color to use for the data label if drawn outside */\r\n outsideFill: string;\r\n\r\n /** The identity of the data point associated with the data label */\r\n identity: powerbi.visuals.SelectionId;\r\n\r\n /** The key of the data point associated with the data label (used if identity is not unique to each expected label) */\r\n key?: string;\r\n\r\n /** The font size of the data point associated with the data label */\r\n fontSize?: number;\r\n\r\n /** Second row of text to be displayed in the label, for additional information */\r\n secondRowText?: string;\r\n\r\n /** The calculated weight of the data point associated with the data label */\r\n weight?: number;\r\n \r\n // Temporary state used internally by the Label Layout system\r\n\r\n /** Whether or not the data label has been rendered */\r\n hasBeenRendered?: boolean;\r\n\r\n /** Size of the label adjusted for the background, if necessary */\r\n labelSize?: ISize;\r\n }\r\n\r\n export interface LabelDataPointGroup {\r\n labelDataPoints: LabelDataPoint[];\r\n maxNumberOfLabels: number;\r\n }\r\n\r\n export interface Label extends SelectableDataPoint {\r\n /** Text to be displayed in the label */\r\n text: string;\r\n\r\n /** Second row of text to be displayed in the label */\r\n secondRowText?: string;\r\n\r\n /** The bounding box for the label */\r\n boundingBox: IRect;\r\n\r\n /** Whether or not the data label should be rendered */\r\n isVisible: boolean;\r\n\r\n /** The fill color of the data label */\r\n fill: string;\r\n\r\n /** A unique key for data points (used if key cannot be obtained from the identity) */\r\n key?: string;\r\n\r\n /** The text size of the data label */\r\n fontSize?: number;\r\n\r\n /** A text anchor used to override the default label text-anchor (middle) */\r\n textAnchor?: string; \r\n\r\n /** points for reference line rendering */\r\n leaderLinePoints?: number[][];\r\n\r\n /** Whether or not the label has a background (and text position needs to be adjusted to take that into account) */\r\n hasBackground: boolean;\r\n\r\n /** A text that represent the label tooltip */\r\n tooltip?: string;\r\n }\r\n\r\n export interface GridSubsection {\r\n xMin: number;\r\n xMax: number;\r\n yMin: number;\r\n yMax: number;\r\n }\r\n\r\n export class LabelArrangeGrid {\r\n private grid: IRect[][][];\r\n private viewport: IViewport;\r\n private cellSize: ISize;\r\n private columnCount: number;\r\n private rowCount: number;\r\n\r\n /** \r\n * A multiplier applied to the largest width height to attempt to balance # of\r\n * labels in each cell and number of cells each label belongs to\r\n */\r\n private static cellSizeMultiplier = 2;\r\n\r\n constructor(labelDataPointsGroups: LabelDataPointGroup[], viewport: IViewport) {\r\n this.viewport = viewport;\r\n\r\n let maxLabelWidth = 0;\r\n let maxLabelHeight = 0;\r\n\r\n for (let labelDataPointsGroup of labelDataPointsGroups) {\r\n for (let labelDataPoint of labelDataPointsGroup.labelDataPoints) {\r\n if (labelDataPoint.isPreferred) {\r\n let dataLabelSize: ISize = labelDataPoint.labelSize;\r\n if (dataLabelSize.width > maxLabelWidth) {\r\n maxLabelWidth = dataLabelSize.width;\r\n }\r\n if (dataLabelSize.height > maxLabelHeight) {\r\n maxLabelHeight = dataLabelSize.height;\r\n }\r\n }\r\n }\r\n }\r\n\r\n if (maxLabelWidth === 0) {\r\n maxLabelWidth = viewport.width;\r\n }\r\n if (maxLabelHeight === 0) {\r\n maxLabelHeight = viewport.height;\r\n }\r\n let cellSize = this.cellSize = { width: maxLabelWidth * LabelArrangeGrid.cellSizeMultiplier, height: maxLabelHeight * LabelArrangeGrid.cellSizeMultiplier };\r\n this.columnCount = LabelArrangeGrid.getCellCount(cellSize.width, viewport.width, 1, 100);\r\n this.rowCount = LabelArrangeGrid.getCellCount(cellSize.height, viewport.height, 1, 100);\r\n let grid: IRect[][][] = [];\r\n for (let i = 0, ilen = this.columnCount; i < ilen; i++) {\r\n grid[i] = [];\r\n for (let j = 0, jlen = this.rowCount; j < jlen; j++) {\r\n grid[i][j] = [];\r\n }\r\n }\r\n this.grid = grid;\r\n }\r\n\r\n /**\r\n * Add a rectangle to check collision against\r\n */\r\n public add(rect: IRect): void {\r\n let containingIndexRect = this.getContainingGridSubsection(rect);\r\n\r\n for (let x = containingIndexRect.xMin; x < containingIndexRect.xMax; x++) {\r\n for (let y = containingIndexRect.yMin; y < containingIndexRect.yMax; y++) {\r\n this.grid[x][y].push(rect);\r\n }\r\n }\r\n }\r\n\r\n /**\r\n * Check whether the rect conflicts with the grid, either bleeding outside the\r\n * viewport or colliding with another rect added to the grid.\r\n */\r\n public hasConflict(rect: IRect): boolean {\r\n if (!this.isWithinGridViewport(rect)) {\r\n return true;\r\n }\r\n\r\n return this.hasCollision(rect);\r\n }\r\n\r\n /**\r\n * Attempt to position the given rect within the viewport. Returns\r\n * the adjusted rectangle or null if the rectangle couldn't fit, \r\n * conflicts with the viewport, or is too far outside the viewport\r\n */\r\n public tryPositionInViewport(rect: IRect): IRect {\r\n // If it's too far outside the viewport, return null\r\n if (!this.isCloseToGridViewport(rect)) {\r\n return;\r\n }\r\n\r\n if (!this.isWithinGridViewport(rect)) {\r\n rect = this.tryMoveInsideViewport(rect);\r\n }\r\n\r\n if (rect && !this.hasCollision(rect)) {\r\n return rect;\r\n }\r\n }\r\n \r\n /**\r\n * Checks for a collision between the given rect and others in the grid.\r\n * Returns true if there is a collision.\r\n */\r\n private hasCollision(rect: IRect): boolean {\r\n let containingIndexRect = this.getContainingGridSubsection(rect);\r\n let grid = this.grid;\r\n let isIntersecting = shapes.Rect.isIntersecting;\r\n for (let x = containingIndexRect.xMin; x < containingIndexRect.xMax; x++) {\r\n for (let y = containingIndexRect.yMin; y < containingIndexRect.yMax; y++) {\r\n for (let currentGridRect of grid[x][y]) {\r\n if (isIntersecting(currentGridRect, rect)) {\r\n return true;\r\n }\r\n }\r\n }\r\n }\r\n return false;\r\n }\r\n \r\n /**\r\n * Check to see if the given rect is inside the grid's viewport\r\n */\r\n private isWithinGridViewport(rect: IRect): boolean {\r\n return rect.left >= 0 &&\r\n rect.top >= 0 &&\r\n rect.left + rect.width <= this.viewport.width &&\r\n rect.top + rect.height <= this.viewport.height;\r\n }\r\n\r\n /**\r\n * Checks to see if the rect is close enough to the viewport to be moved inside.\r\n * \"Close\" here is determined by the distance between the edge of the viewport\r\n * and the closest edge of the rect; if that distance is less than the appropriate\r\n * dimension of the rect, we will reposition the rect.\r\n */\r\n private isCloseToGridViewport(rect: IRect): boolean {\r\n return rect.left + rect.width >= 0 - rect.width &&\r\n rect.top + rect.height >= -rect.height &&\r\n rect.left <= this.viewport.width + rect.width &&\r\n rect.top <= this.viewport.height + rect.height;\r\n }\r\n\r\n /**\r\n * Attempt to move the rect inside the grid's viewport. Returns the resulting\r\n * rectangle with the same width/height adjusted to be inside the viewport or\r\n * null if it couldn't fit regardless.\r\n */\r\n private tryMoveInsideViewport(rect: IRect): IRect {\r\n let result: IRect = Rect.clone(rect);\r\n let viewport = this.viewport;\r\n\r\n // Return null if it's too big to fit regardless of positioning\r\n if (rect.width > viewport.width || rect.height > viewport.height) {\r\n return;\r\n }\r\n \r\n // Only one movement should be made in each direction, because we are only moving it inside enough for it to fit; there should be no overshooting.\r\n // Outside to the left\r\n if (rect.left < 0) {\r\n result.left = 0;\r\n }\r\n // Outside to the right\r\n else if (rect.left + rect.width > viewport.width) {\r\n result.left -= (rect.left + rect.width) - viewport.width;\r\n }\r\n // Outside above\r\n if (rect.top < 0) {\r\n result.top = 0;\r\n }\r\n // Outside below\r\n else if (rect.top + rect.height > viewport.height) {\r\n result.top -= (rect.top + rect.height) - viewport.height;\r\n }\r\n\r\n return result;\r\n }\r\n\r\n private getContainingGridSubsection(rect: IRect): GridSubsection {\r\n return {\r\n xMin: LabelArrangeGrid.bound(Math.floor(rect.left / this.cellSize.width), 0, this.columnCount),\r\n xMax: LabelArrangeGrid.bound(Math.ceil((rect.left + rect.width) / this.cellSize.width), 0, this.columnCount),\r\n yMin: LabelArrangeGrid.bound(Math.floor(rect.top / this.cellSize.height), 0, this.rowCount),\r\n yMax: LabelArrangeGrid.bound(Math.ceil((rect.top + rect.height) / this.cellSize.height), 0, this.rowCount),\r\n };\r\n }\r\n\r\n private static getCellCount(step: number, length: number, minCount: number, maxCount: number): number {\r\n return LabelArrangeGrid.bound(Math.ceil(length / step), minCount, maxCount);\r\n }\r\n\r\n private static bound(value: number, min: number, max: number): number {\r\n return Math.max(Math.min(value, max), min);\r\n }\r\n }\r\n\r\n export interface DataLabelLayoutOptions {\r\n /** The amount of offset to start with when the data label is not centered */\r\n startingOffset: number;\r\n /** Maximum distance labels will be offset by */\r\n maximumOffset: number;\r\n /** The amount to increase the offset each attempt while laying out labels */\r\n offsetIterationDelta?: number;\r\n /** Horizontal padding used for checking whether a label is inside a parent shape */\r\n horizontalPadding?: number;\r\n /** Vertical padding used for checking whether a label is inside a parent shape */\r\n verticalPadding?: number;\r\n /** Should we draw reference lines in case the label offset is greater then the default */\r\n allowLeaderLines?: boolean;\r\n /** Should the layout system attempt to move the label inside the viewport when it outside, but close */\r\n attemptToMoveLabelsIntoViewport?: boolean;\r\n }\r\n\r\n export class LabelLayout {\r\n /** Maximum distance labels will be offset by */\r\n private maximumOffset: number;\r\n /** The amount to increase the offset each attempt while laying out labels */\r\n private offsetIterationDelta: number;\r\n /** The amount of offset to start with when the data label is not centered */\r\n private startingOffset: number;\r\n /** Padding used for checking whether a label is inside a parent shape */\r\n private horizontalPadding: number;\r\n /** Padding used for checking whether a label is inside a parent shape */\r\n private verticalPadding: number;\r\n /** Should we draw leader lines in case the label offset is greater then the default */\r\n private allowLeaderLines: boolean;\r\n /** Should the layout system attempt to move the label inside the viewport when it outside, but close */\r\n private attemptToMoveLabelsIntoViewport: boolean;\r\n\r\n // Default values\r\n private static defaultOffsetIterationDelta = 2;\r\n private static defaultHorizontalPadding = 2;\r\n private static defaultVerticalPadding = 2;\r\n\r\n constructor(options: DataLabelLayoutOptions) {\r\n this.startingOffset = options.startingOffset;\r\n this.maximumOffset = options.maximumOffset;\r\n if (options.offsetIterationDelta != null) {\r\n debug.assert(options.offsetIterationDelta > 0, \"label offset delta must be greater than 0\");\r\n this.offsetIterationDelta = options.offsetIterationDelta;\r\n }\r\n else {\r\n this.offsetIterationDelta = LabelLayout.defaultOffsetIterationDelta;\r\n }\r\n if (options.horizontalPadding != null) {\r\n this.horizontalPadding = options.horizontalPadding;\r\n }\r\n else {\r\n this.horizontalPadding = LabelLayout.defaultHorizontalPadding;\r\n }\r\n if (options.verticalPadding != null) {\r\n this.verticalPadding = options.verticalPadding;\r\n }\r\n else {\r\n this.verticalPadding = LabelLayout.defaultVerticalPadding;\r\n }\r\n this.allowLeaderLines = !!options.allowLeaderLines;\r\n this.attemptToMoveLabelsIntoViewport = !!options.attemptToMoveLabelsIntoViewport;\r\n }\r\n\r\n /**\r\n * Arrange takes a set of data labels and lays them out in order, assuming that\r\n * the given array has already been sorted with the most preferred labels at the\r\n * front, taking into considiration a maximum number of labels that are alowed\r\n * to display.\r\n * \r\n * Details:\r\n * - We iterate over offsets from the target position, increasing from 0 while\r\n * verifiying the maximum number of labels to display hasn't been reached\r\n * - For each offset, we iterate over each data label\r\n * - For each data label, we iterate over each position that is valid for\r\n * both the specific label and this layout\r\n * - When a valid position is found, we position the label there and no longer\r\n * reposition it.\r\n * - This prioritizes the earlier labels to be positioned closer to their\r\n * target points in the position they prefer.\r\n * - This prioritizes putting data labels close to a valid position over\r\n * placing them at their preferred position (it will place it at a less\r\n * preferred position if it will be a smaller offset)\r\n */\r\n public layout(labelDataPointsGroups: LabelDataPointGroup[], viewport: IViewport): Label[] {\r\n // Clear data labels for a new layout\r\n for (let labelDataPointsGroup of labelDataPointsGroups) {\r\n for (let labelPoint of labelDataPointsGroup.labelDataPoints) {\r\n labelPoint.hasBeenRendered = false;\r\n if (labelPoint.hasBackground) {\r\n labelPoint.labelSize = {\r\n width: labelPoint.textSize.width + 2 * NewDataLabelUtils.horizontalLabelBackgroundPadding,\r\n height: labelPoint.textSize.height + 2 * NewDataLabelUtils.verticalLabelBackgroundPadding,\r\n };\r\n }\r\n else {\r\n labelPoint.labelSize = labelPoint.textSize;\r\n }\r\n }\r\n }\r\n\r\n let resultingDataLabels: Label[] = [];\r\n let grid = new LabelArrangeGrid(labelDataPointsGroups, viewport);\r\n\r\n // Iterates on every series\r\n for (let labelDataPointsGroup of labelDataPointsGroups) {\r\n let maxLabelsToRender = labelDataPointsGroup.maxNumberOfLabels;\r\n // NOTE: we create a copy and modify the copy to keep track of preferred vs. non-preferred labels.\r\n let labelDataPoints = _.clone(labelDataPointsGroup.labelDataPoints);\r\n let preferredLabels: LabelDataPoint[] = [];\r\n \r\n // Exclude preferred labels\r\n for (let j = labelDataPoints.length - 1, localMax = maxLabelsToRender; j >= 0 && localMax > 0; j--) {\r\n let labelPoint = labelDataPoints[j];\r\n if (labelPoint.isPreferred) {\r\n preferredLabels.unshift(labelDataPoints.splice(j, 1)[0]);\r\n localMax--;\r\n }\r\n }\r\n\r\n // First iterate all the preferred labels\r\n if (preferredLabels.length > 0) {\r\n let positionedLabels = this.positionDataLabels(preferredLabels, viewport, grid, maxLabelsToRender);\r\n maxLabelsToRender -= positionedLabels.length;\r\n resultingDataLabels = resultingDataLabels.concat(positionedLabels);\r\n }\r\n \r\n // While there are invisible not preferred labels and label distance is less than the max\r\n // allowed distance\r\n if (labelDataPoints.length > 0) {\r\n let labels = this.positionDataLabels(labelDataPoints, viewport, grid, maxLabelsToRender);\r\n resultingDataLabels = resultingDataLabels.concat(labels);\r\n }\r\n // TODO: Add reference lines if we want them\r\n }\r\n return resultingDataLabels;\r\n }\r\n\r\n private positionDataLabels(labelDataPoints: LabelDataPoint[], viewport: IViewport, grid: LabelArrangeGrid, maxLabelsToRender: number): Label[] {\r\n let resultingDataLabels: Label[] = [];\r\n let offsetDelta = this.offsetIterationDelta;\r\n let currentOffset = this.startingOffset;\r\n let currentCenteredOffset = 0;\r\n let drawLeaderLinesOnIteration: boolean;\r\n let labelsRendered: number = 0;\r\n\r\n while (currentOffset <= this.maximumOffset && labelsRendered < maxLabelsToRender) {\r\n drawLeaderLinesOnIteration = this.allowLeaderLines && currentOffset > this.startingOffset;\r\n for (let labelPoint of labelDataPoints) {\r\n if (labelPoint.hasBeenRendered) {\r\n continue;\r\n }\r\n let dataLabel;\r\n if (labelPoint.parentType === LabelDataPointParentType.Rectangle) {\r\n dataLabel = this.tryPositionForRectPositions(labelPoint, grid, currentOffset, currentCenteredOffset);\r\n }\r\n else {\r\n dataLabel = this.tryPositionForPointPositions(labelPoint, grid, currentOffset, drawLeaderLinesOnIteration);\r\n }\r\n\r\n if (dataLabel) {\r\n resultingDataLabels.push(dataLabel);\r\n labelsRendered++;\r\n }\r\n if (!(labelsRendered < maxLabelsToRender)) {\r\n break;\r\n }\r\n }\r\n currentOffset += offsetDelta;\r\n currentCenteredOffset += offsetDelta;\r\n }\r\n\r\n return resultingDataLabels;\r\n }\r\n\r\n private tryPositionForRectPositions(labelPoint: LabelDataPoint, grid: LabelArrangeGrid, currentLabelOffset: number, currentCenteredLabelOffset: number): Label {\r\n // Function declared and reused to reduce code duplication\r\n let tryPosition = (position: RectLabelPosition, adjustForViewport: boolean) => {\r\n let isPositionInside = position & RectLabelPosition.InsideAll;\r\n if (isPositionInside && !DataLabelRectPositioner.canFitWithinParent(labelPoint, this.horizontalPadding, this.verticalPadding)) {\r\n return;\r\n }\r\n\r\n let resultingBoundingBox = LabelLayout.tryPositionRect(grid, position, labelPoint, currentLabelOffset, currentCenteredLabelOffset, adjustForViewport);\r\n if (resultingBoundingBox) {\r\n if (isPositionInside && !DataLabelRectPositioner.isLabelWithinParent(resultingBoundingBox, labelPoint, this.horizontalPadding, this.verticalPadding)) {\r\n return;\r\n }\r\n grid.add(resultingBoundingBox);\r\n labelPoint.hasBeenRendered = true;\r\n return {\r\n boundingBox: resultingBoundingBox,\r\n text: labelPoint.text,\r\n tooltip: labelPoint.tooltip,\r\n isVisible: true,\r\n fill: isPositionInside ? labelPoint.insideFill : labelPoint.outsideFill,\r\n identity: labelPoint.identity,\r\n key: labelPoint.key,\r\n fontSize: labelPoint.fontSize,\r\n selected: false,\r\n hasBackground: !!labelPoint.hasBackground,\r\n };\r\n }\r\n };\r\n\r\n // Iterate over all positions that are valid for the data point\r\n for (let position of (<LabelParentRect>labelPoint.parentShape).validPositions) {\r\n let label = tryPosition(position, false /* adjustForViewport */);\r\n if (label)\r\n return label;\r\n }\r\n // If no position has been found and the option is enabled, try any outside positions while moving the label inside the viewport\r\n if (this.attemptToMoveLabelsIntoViewport) {\r\n for (let position of (<LabelParentRect>labelPoint.parentShape).validPositions) {\r\n let label = tryPosition(position, true /* adjustForViewport */);\r\n if (label)\r\n return label;\r\n }\r\n }\r\n\r\n return null;\r\n }\r\n\r\n /**\r\n * Tests a particular position/offset combination for the given data label.\r\n * If the label can be placed, returns the resulting bounding box for the data\r\n * label. If not, returns null.\r\n */\r\n private static tryPositionRect(grid: LabelArrangeGrid, position: RectLabelPosition, labelDataPoint: LabelDataPoint, offset: number, centerOffset: number, adjustForViewport: boolean): IRect {\r\n let offsetForPosition = offset;\r\n if (position & RectLabelPosition.InsideCenter) {\r\n offsetForPosition = centerOffset;\r\n }\r\n let labelRect = DataLabelRectPositioner.getLabelRect(labelDataPoint, position, offsetForPosition);\r\n\r\n if (position !== RectLabelPosition.InsideCenter || (<LabelParentRect>labelDataPoint.parentShape).orientation === NewRectOrientation.None) {\r\n if (!grid.hasConflict(labelRect)) {\r\n return labelRect;\r\n }\r\n if (adjustForViewport) {\r\n return grid.tryPositionInViewport(labelRect);\r\n }\r\n }\r\n else {\r\n // If the position is centered, attempt to offset in both a positive and negative direction\r\n if (!grid.hasConflict(labelRect)) {\r\n return labelRect;\r\n }\r\n labelRect = DataLabelRectPositioner.getLabelRect(labelDataPoint, position, -offsetForPosition);\r\n if (!grid.hasConflict(labelRect)) {\r\n return labelRect;\r\n }\r\n }\r\n\r\n return null;\r\n }\r\n\r\n private tryPositionForPointPositions(labelPoint: LabelDataPoint, grid: LabelArrangeGrid, currentLabelOffset: number, drawLeaderLines: boolean): Label {\r\n // Function declared and reused to reduce code duplication\r\n let tryPosition = (position: NewPointLabelPosition, parentShape: LabelParentPoint, adjustForViewport: boolean) => {\r\n let resultingBoundingBox = LabelLayout.tryPositionPoint(grid, position, labelPoint, currentLabelOffset, adjustForViewport);\r\n if (resultingBoundingBox) {\r\n grid.add(resultingBoundingBox);\r\n labelPoint.hasBeenRendered = true;\r\n return {\r\n boundingBox: resultingBoundingBox,\r\n text: labelPoint.text,\r\n tooltip: labelPoint.tooltip,\r\n isVisible: true,\r\n fill: position === NewPointLabelPosition.Center ? labelPoint.insideFill : labelPoint.outsideFill, // If we ever support \"inside\" for point-based labels, this needs to be updated\r\n isInsideParent: position === NewPointLabelPosition.Center,\r\n identity: labelPoint.identity,\r\n key: labelPoint.key,\r\n fontSize: labelPoint.fontSize,\r\n selected: false,\r\n leaderLinePoints: drawLeaderLines ? DataLabelPointPositioner.getLabelLeaderLineEndingPoint(resultingBoundingBox, position, parentShape) : null,\r\n hasBackground: !!labelPoint.hasBackground,\r\n };\r\n }\r\n };\r\n\r\n // Iterate over all positions that are valid for the data point\r\n let parentShape = (<LabelParentPoint>labelPoint.parentShape);\r\n let validPositions = parentShape.validPositions;\r\n for (let position of validPositions) {\r\n let label = tryPosition(position, parentShape, false /* adjustForViewport */);\r\n if (label)\r\n return label;\r\n }\r\n // Attempt to position at the most preferred position by simply moving it inside the viewport\r\n if (this.attemptToMoveLabelsIntoViewport && !_.isEmpty(validPositions)) {\r\n let label = tryPosition(validPositions[0], parentShape, true /* adjustForViewport */);\r\n if (label)\r\n return label;\r\n }\r\n return null;\r\n }\r\n\r\n private static tryPositionPoint(grid: LabelArrangeGrid, position: NewPointLabelPosition, labelDataPoint: LabelDataPoint, offset: number, adjustForViewport: boolean): IRect {\r\n let labelRect = DataLabelPointPositioner.getLabelRect(labelDataPoint.labelSize, <LabelParentPoint>labelDataPoint.parentShape, position, offset);\r\n\r\n if (!grid.hasConflict(labelRect)) {\r\n return labelRect;\r\n }\r\n if (adjustForViewport) {\r\n return grid.tryPositionInViewport(labelRect);\r\n }\r\n\r\n return null;\r\n }\r\n }\r\n \r\n /**\r\n * (Private) Contains methods for calculating the bounding box of a data label\r\n */\r\n export module DataLabelRectPositioner {\r\n\r\n export function getLabelRect(labelDataPoint: LabelDataPoint, position: RectLabelPosition, offset: number): IRect {\r\n let parentRect: LabelParentRect = <LabelParentRect>labelDataPoint.parentShape;\r\n if (parentRect != null) {\r\n // Each combination of position and orientation results in a different actual positioning, which is then called.\r\n switch (position) {\r\n case RectLabelPosition.InsideCenter:\r\n switch (parentRect.orientation) {\r\n case NewRectOrientation.VerticalBottomBased:\r\n case NewRectOrientation.VerticalTopBased:\r\n return DataLabelRectPositioner.middleVertical(labelDataPoint.labelSize, parentRect.rect, offset);\r\n case NewRectOrientation.HorizontalLeftBased:\r\n case NewRectOrientation.HorizontalRightBased:\r\n return DataLabelRectPositioner.middleHorizontal(labelDataPoint.labelSize, parentRect.rect, offset);\r\n case NewRectOrientation.None:\r\n // TODO: which of the above cases should we default to for rects with no orientation?\r\n }\r\n case RectLabelPosition.InsideBase:\r\n switch (parentRect.orientation) {\r\n case NewRectOrientation.VerticalBottomBased:\r\n return DataLabelRectPositioner.bottomInside(labelDataPoint.labelSize, parentRect.rect, offset);\r\n case NewRectOrientation.VerticalTopBased:\r\n return DataLabelRectPositioner.topInside(labelDataPoint.labelSize, parentRect.rect, offset);\r\n case NewRectOrientation.HorizontalLeftBased:\r\n return DataLabelRectPositioner.leftInside(labelDataPoint.labelSize, parentRect.rect, offset);\r\n case NewRectOrientation.HorizontalRightBased:\r\n return DataLabelRectPositioner.rightInside(labelDataPoint.labelSize, parentRect.rect, offset);\r\n case NewRectOrientation.None:\r\n // TODO: which of the above cases should we default to for rects with no orientation?\r\n }\r\n case RectLabelPosition.InsideEnd:\r\n switch (parentRect.orientation) {\r\n case NewRectOrientation.VerticalBottomBased:\r\n return DataLabelRectPositioner.topInside(labelDataPoint.labelSize, parentRect.rect, offset);\r\n case NewRectOrientation.VerticalTopBased:\r\n return DataLabelRectPositioner.bottomInside(labelDataPoint.labelSize, parentRect.rect, offset);\r\n case NewRectOrientation.HorizontalLeftBased:\r\n return DataLabelRectPositioner.rightInside(labelDataPoint.labelSize, parentRect.rect, offset);\r\n case NewRectOrientation.HorizontalRightBased:\r\n return DataLabelRectPositioner.leftInside(labelDataPoint.labelSize, parentRect.rect, offset);\r\n case NewRectOrientation.None:\r\n // TODO: which of the above cases should we default to for rects with no orientation?\r\n }\r\n case RectLabelPosition.OutsideBase:\r\n switch (parentRect.orientation) {\r\n case NewRectOrientation.VerticalBottomBased:\r\n return DataLabelRectPositioner.bottomOutside(labelDataPoint.labelSize, parentRect.rect, offset);\r\n case NewRectOrientation.VerticalTopBased:\r\n return DataLabelRectPositioner.topOutside(labelDataPoint.labelSize, parentRect.rect, offset);\r\n case NewRectOrientation.HorizontalLeftBased:\r\n return DataLabelRectPositioner.leftOutside(labelDataPoint.labelSize, parentRect.rect, offset);\r\n case NewRectOrientation.HorizontalRightBased:\r\n return DataLabelRectPositioner.rightOutside(labelDataPoint.labelSize, parentRect.rect, offset);\r\n case NewRectOrientation.None:\r\n // TODO: which of the above cases should we default to for rects with no orientation?\r\n }\r\n case RectLabelPosition.OutsideEnd:\r\n switch (parentRect.orientation) {\r\n case NewRectOrientation.VerticalBottomBased:\r\n return DataLabelRectPositioner.topOutside(labelDataPoint.labelSize, parentRect.rect, offset);\r\n case NewRectOrientation.VerticalTopBased:\r\n return DataLabelRectPositioner.bottomOutside(labelDataPoint.labelSize, parentRect.rect, offset);\r\n case NewRectOrientation.HorizontalLeftBased:\r\n return DataLabelRectPositioner.rightOutside(labelDataPoint.labelSize, parentRect.rect, offset);\r\n case NewRectOrientation.HorizontalRightBased:\r\n return DataLabelRectPositioner.leftOutside(labelDataPoint.labelSize, parentRect.rect, offset);\r\n case NewRectOrientation.None:\r\n // TODO: which of the above cases should we default to for rects with no orientation?\r\n }\r\n default:\r\n debug.assertFail(\"Unsupported label position\");\r\n }\r\n }\r\n else {\r\n // TODO: Data labels for non-rectangular visuals (line chart)\r\n }\r\n return null;\r\n }\r\n\r\n export function canFitWithinParent(labelDataPoint: LabelDataPoint, horizontalPadding: number, verticalPadding: number): boolean {\r\n return (labelDataPoint.labelSize.width + 2 * horizontalPadding < (<LabelParentRect>labelDataPoint.parentShape).rect.width) ||\r\n (labelDataPoint.labelSize.height + 2 * verticalPadding < (<LabelParentRect>labelDataPoint.parentShape).rect.height);\r\n }\r\n\r\n export function isLabelWithinParent(labelRect: IRect, labelPoint: LabelDataPoint, horizontalPadding: number, verticalPadding: number): boolean {\r\n let parentRect = (<LabelParentRect>labelPoint.parentShape).rect;\r\n let labelRectWithPadding = shapes.Rect.inflate(labelRect, { left: horizontalPadding, right: horizontalPadding, top: verticalPadding, bottom: verticalPadding });\r\n return shapes.Rect.containsPoint(parentRect, {\r\n x: labelRectWithPadding.left,\r\n y: labelRectWithPadding.top,\r\n }) && shapes.Rect.containsPoint(parentRect, {\r\n x: labelRectWithPadding.left + labelRectWithPadding.width,\r\n y: labelRectWithPadding.top + labelRectWithPadding.height,\r\n });\r\n }\r\n\r\n export function topInside(labelSize: ISize, parentRect: IRect, offset: number): IRect {\r\n return {\r\n left: parentRect.left + parentRect.width / 2.0 - labelSize.width / 2.0,\r\n top: parentRect.top + offset,\r\n width: labelSize.width,\r\n height: labelSize.height\r\n };\r\n }\r\n\r\n export function bottomInside(labelSize: ISize, parentRect: IRect, offset: number): IRect {\r\n return {\r\n left: parentRect.left + parentRect.width / 2.0 - labelSize.width / 2.0,\r\n top: (parentRect.top + parentRect.height) - offset - labelSize.height,\r\n width: labelSize.width,\r\n height: labelSize.height\r\n };\r\n }\r\n\r\n export function rightInside(labelSize: ISize, parentRect: IRect, offset: number): IRect {\r\n return {\r\n left: (parentRect.left + parentRect.width) - labelSize.width - offset,\r\n top: parentRect.top + parentRect.height / 2.0 - labelSize.height / 2.0,\r\n width: labelSize.width,\r\n height: labelSize.height\r\n };\r\n }\r\n\r\n export function leftInside(labelSize: ISize, parentRect: IRect, offset: number): IRect {\r\n return {\r\n left: parentRect.left + offset,\r\n top: parentRect.top + parentRect.height / 2.0 - labelSize.height / 2.0,\r\n width: labelSize.width,\r\n height: labelSize.height\r\n };\r\n }\r\n\r\n export function topOutside(labelSize: ISize, parentRect: IRect, offset: number): IRect {\r\n return {\r\n left: parentRect.left + parentRect.width / 2.0 - labelSize.width / 2.0,\r\n top: parentRect.top - labelSize.height - offset,\r\n width: labelSize.width,\r\n height: labelSize.height\r\n };\r\n }\r\n\r\n export function bottomOutside(labelSize: ISize, parentRect: IRect, offset: number): IRect {\r\n return {\r\n left: parentRect.left + parentRect.width / 2.0 - labelSize.width / 2.0,\r\n top: (parentRect.top + parentRect.height) + offset,\r\n width: labelSize.width,\r\n height: labelSize.height\r\n };\r\n }\r\n\r\n export function rightOutside(labelSize: ISize, parentRect: IRect, offset: number): IRect {\r\n return {\r\n left: (parentRect.left + parentRect.width) + offset,\r\n top: parentRect.top + parentRect.height / 2.0 - labelSize.height / 2.0,\r\n width: labelSize.width,\r\n height: labelSize.height\r\n };\r\n }\r\n\r\n export function leftOutside(labelSize: ISize, parentRect: IRect, offset: number): IRect {\r\n return {\r\n left: parentRect.left - labelSize.width - offset,\r\n top: parentRect.top + parentRect.height / 2.0 - labelSize.height / 2.0,\r\n width: labelSize.width,\r\n height: labelSize.height\r\n };\r\n }\r\n\r\n export function middleHorizontal(labelSize: ISize, parentRect: IRect, offset: number): IRect {\r\n return {\r\n left: parentRect.left + parentRect.width / 2.0 - labelSize.width / 2.0 + offset,\r\n top: parentRect.top + parentRect.height / 2.0 - labelSize.height / 2.0,\r\n width: labelSize.width,\r\n height: labelSize.height\r\n };\r\n }\r\n\r\n export function middleVertical(labelSize: ISize, parentRect: IRect, offset: number): IRect {\r\n return {\r\n left: parentRect.left + parentRect.width / 2.0 - labelSize.width / 2.0,\r\n top: parentRect.top + parentRect.height / 2.0 - labelSize.height / 2.0 + offset,\r\n width: labelSize.width,\r\n height: labelSize.height\r\n };\r\n }\r\n }\r\n\r\n export module DataLabelPointPositioner {\r\n export const cos45 = Math.cos(45);\r\n export const sin45 = Math.sin(45);\r\n\r\n export function getLabelRect(labelSize: ISize, parentPoint: LabelParentPoint, position: NewPointLabelPosition, offset: number): IRect {\r\n switch (position) {\r\n case NewPointLabelPosition.Above: {\r\n return DataLabelPointPositioner.above(labelSize, parentPoint.point, parentPoint.radius + offset);\r\n }\r\n case NewPointLabelPosition.Below: {\r\n return DataLabelPointPositioner.below(labelSize, parentPoint.point, parentPoint.radius + offset);\r\n }\r\n case NewPointLabelPosition.Left: {\r\n return DataLabelPointPositioner.left(labelSize, parentPoint.point, parentPoint.radius + offset);\r\n }\r\n case NewPointLabelPosition.Right: {\r\n return DataLabelPointPositioner.right(labelSize, parentPoint.point, parentPoint.radius + offset);\r\n }\r\n case NewPointLabelPosition.BelowLeft: {\r\n return DataLabelPointPositioner.belowLeft(labelSize, parentPoint.point, parentPoint.radius + offset);\r\n }\r\n case NewPointLabelPosition.BelowRight: {\r\n return DataLabelPointPositioner.belowRight(labelSize, parentPoint.point, parentPoint.radius + offset);\r\n }\r\n case NewPointLabelPosition.AboveLeft: {\r\n return DataLabelPointPositioner.aboveLeft(labelSize, parentPoint.point, parentPoint.radius + offset);\r\n }\r\n case NewPointLabelPosition.AboveRight: {\r\n return DataLabelPointPositioner.aboveRight(labelSize, parentPoint.point, parentPoint.radius + offset);\r\n }\r\n case NewPointLabelPosition.Center: {\r\n return DataLabelPointPositioner.center(labelSize, parentPoint.point);\r\n }\r\n default: {\r\n debug.assertFail(\"Unsupported label position\");\r\n }\r\n }\r\n return null;\r\n }\r\n\r\n export function above(labelSize: ISize, parentPoint: IPoint, offset: number): IRect {\r\n return {\r\n left: parentPoint.x - (labelSize.width / 2),\r\n top: parentPoint.y - offset - labelSize.height,\r\n width: labelSize.width,\r\n height: labelSize.height\r\n };\r\n }\r\n\r\n export function below(labelSize: ISize, parentPoint: IPoint, offset: number): IRect {\r\n return {\r\n left: parentPoint.x - (labelSize.width / 2),\r\n top: parentPoint.y + offset,\r\n width: labelSize.width,\r\n height: labelSize.height\r\n };\r\n }\r\n\r\n export function left(labelSize: ISize, parentPoint: IPoint, offset: number): IRect {\r\n return {\r\n left: parentPoint.x - offset - labelSize.width,\r\n top: parentPoint.y - (labelSize.height / 2),\r\n width: labelSize.width,\r\n height: labelSize.height\r\n };\r\n }\r\n\r\n export function right(labelSize: ISize, parentPoint: IPoint, offset: number): IRect {\r\n return {\r\n left: parentPoint.x + offset,\r\n top: parentPoint.y - (labelSize.height / 2),\r\n width: labelSize.width,\r\n height: labelSize.height\r\n };\r\n }\r\n\r\n export function belowLeft(labelSize: ISize, parentPoint: IPoint, offset: number): IRect {\r\n return {\r\n left: parentPoint.x - (sin45 * offset) - labelSize.width,\r\n top: parentPoint.y + (cos45 * offset),\r\n width: labelSize.width,\r\n height: labelSize.height\r\n };\r\n }\r\n\r\n export function belowRight(labelSize: ISize, parentPoint: IPoint, offset: number): IRect {\r\n return {\r\n left: parentPoint.x + (sin45 * offset),\r\n top: parentPoint.y + (cos45 * offset),\r\n width: labelSize.width,\r\n height: labelSize.height\r\n };\r\n }\r\n\r\n export function aboveLeft(labelSize: ISize, parentPoint: IPoint, offset: number): IRect {\r\n return {\r\n left: parentPoint.x - (sin45 * offset) - labelSize.width,\r\n top: parentPoint.y - (cos45 * offset) - labelSize.height,\r\n width: labelSize.width,\r\n height: labelSize.height\r\n };\r\n }\r\n\r\n export function aboveRight(labelSize: ISize, parentPoint: IPoint, offset: number): IRect {\r\n return {\r\n left: parentPoint.x + (sin45 * offset),\r\n top: parentPoint.y - (cos45 * offset) - labelSize.height,\r\n width: labelSize.width,\r\n height: labelSize.height\r\n };\r\n }\r\n export function center(labelSize: ISize, parentPoint: IPoint): IRect {\r\n return {\r\n left: parentPoint.x - (labelSize.width / 2),\r\n top: parentPoint.y - (labelSize.height / 2),\r\n width: labelSize.width,\r\n height: labelSize.height\r\n };\r\n }\r\n\r\n export function getLabelLeaderLineEndingPoint(boundingBox: IRect, position: NewPointLabelPosition, parentShape: LabelParentPoint): number[][] {\r\n let x = boundingBox.left;\r\n let y = boundingBox.top;\r\n switch (position) {\r\n case NewPointLabelPosition.Above:\r\n x += (boundingBox.width / 2);\r\n y += boundingBox.height;\r\n break;\r\n case NewPointLabelPosition.Below:\r\n x += (boundingBox.width / 2);\r\n break;\r\n case NewPointLabelPosition.Left:\r\n x += boundingBox.width;\r\n y += ((boundingBox.height * 2) / 3);\r\n break;\r\n case NewPointLabelPosition.Right:\r\n y += ((boundingBox.height * 2) / 3);\r\n break;\r\n case NewPointLabelPosition.BelowLeft:\r\n x += boundingBox.width;\r\n y += (boundingBox.height / 2);\r\n break;\r\n case NewPointLabelPosition.BelowRight:\r\n y += (boundingBox.height / 2);\r\n break;\r\n case NewPointLabelPosition.AboveLeft:\r\n x += boundingBox.width;\r\n y += boundingBox.height;\r\n break;\r\n case NewPointLabelPosition.AboveRight:\r\n y += boundingBox.height;\r\n break;\r\n }\r\n\r\n return [[parentShape.point.x, parentShape.point.y], [x, y]];\r\n }\r\n }\r\n}\r\n\n\n\n/** WEBPACK FOOTER **\n ** ./src/Clients/Visuals/formatting/labelLayout.ts\n **/","/*\r\n * Power BI Visualizations\r\n *\r\n * Copyright (c) Microsoft Corporation\r\n * All rights reserved. \r\n * MIT License\r\n *\r\n * Permission is hereby granted, free of charge, to any person obtaining a copy\r\n * of this software and associated documentation files (the \"\"Software\"\"), to deal\r\n * in the Software without restriction, including without limitation the rights\r\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\r\n * copies of the Software, and to permit persons to whom the Software is\r\n * furnished to do so, subject to the following conditions:\r\n * \r\n * The above copyright notice and this permission notice shall be included in \r\n * all copies or substantial portions of the Software.\r\n * \r\n * THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR \r\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, \r\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE \r\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER \r\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\r\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\r\n * THE SOFTWARE.\r\n */\r\n\r\n/// <reference path=\"../_references.ts\"/>\r\n\r\nmodule powerbi {\r\n import ISize = powerbi.visuals.shapes.ISize;\r\n import IVector = powerbi.visuals.shapes.IVector;\r\n import IRect = powerbi.visuals.IRect;\r\n import VisualDataLabelsSettings = powerbi.visuals.VisualDataLabelsSettings;\r\n import DonutArcDescriptor = powerbi.visuals.DonutArcDescriptor;\r\n import NewDataLabelUtils = powerbi.visuals.NewDataLabelUtils;\r\n import labelStyle = powerbi.visuals.labelStyle;\r\n import DonutLabelUtils = powerbi.visuals.DonutLabelUtils;\r\n\r\n export interface DonutChartProperties {\r\n viewport: IViewport;\r\n dataLabelsSettings: VisualDataLabelsSettings;\r\n radius: number;\r\n arc: D3.Svg.Arc;\r\n outerArc: D3.Svg.Arc;\r\n outerArcRadiusRatio: number;\r\n innerArcRadiusRatio: number;\r\n }\r\n\r\n export interface DonutLabelDataPoint extends LabelDataPoint {\r\n dataLabel: string;\r\n dataLabelSize: ISize;\r\n categoryLabel: string;\r\n categoryLabelSize: ISize;\r\n donutArcDescriptor: DonutArcDescriptor;\r\n alternativeScale: number;\r\n angle: number;\r\n linesSize: ISize[];\r\n leaderLinePoints: number[][];\r\n }\r\n\r\n interface LabelCandidate {\r\n angle: number;\r\n point: LabelParentPoint;\r\n score: number;\r\n labelRects: DonutLabelRect;\r\n labelDataPoint?: DonutLabelDataPoint;\r\n }\r\n\r\n export interface DonutLabelRect {\r\n textRect: IRect;\r\n diagonalLineRect: IRect;\r\n horizontalLineRect: IRect;\r\n }\r\n\r\n export class DonutLabelLayout {\r\n\r\n /** Maximum distance labels will be offset by */\r\n private maximumOffset: number;\r\n /** The amount to increase the offset each attempt while laying out labels */\r\n private offsetIterationDelta: number;\r\n /** The amount of offset to start with when the data label is not centered */\r\n private startingOffset: number;\r\n\r\n private donutChartProperties: DonutChartProperties;\r\n private center: IVector;\r\n private outerRadius: number;\r\n private innerRadius: number;\r\n private additionalCharsWidth: number;\r\n\r\n constructor(options: DataLabelLayoutOptions, donutChartProperties: DonutChartProperties) {\r\n this.startingOffset = options.startingOffset;\r\n this.maximumOffset = options.maximumOffset;\r\n if (options.offsetIterationDelta != null) {\r\n debug.assert(options.offsetIterationDelta > 0, \"label offset delta must be greater than 0\");\r\n this.offsetIterationDelta = options.offsetIterationDelta;\r\n }\r\n\r\n this.donutChartProperties = donutChartProperties;\r\n this.center = {\r\n x: donutChartProperties.viewport.width / 2,\r\n y: donutChartProperties.viewport.height / 2,\r\n };\r\n this.outerRadius = this.donutChartProperties.radius * this.donutChartProperties.outerArcRadiusRatio;\r\n this.innerRadius = (this.donutChartProperties.radius / 2) * this.donutChartProperties.innerArcRadiusRatio;\r\n this.additionalCharsWidth = TextMeasurementService.measureSvgTextWidth({\r\n text: \" ()\",\r\n fontFamily: NewDataLabelUtils.LabelTextProperties.fontFamily,\r\n fontSize: jsCommon.PixelConverter.fromPoint(donutChartProperties.dataLabelsSettings.fontSize),\r\n fontWeight: NewDataLabelUtils.LabelTextProperties.fontWeight,\r\n });\r\n }\r\n\r\n /**\r\n * Arrange takes a set of data labels and lays them out them in order, assuming that\r\n * the given array has already been sorted with the most preferred labels at the\r\n * front.\r\n * \r\n * Details:\r\n * - We iterate over offsets from the target position, increasing from 0\r\n * - For each offset, we iterate over each data label\r\n * - For each data label, we iterate over each position that is valid for\r\n * both the specific label and this layout\r\n * - When a valid position is found, we position the label there and no longer\r\n * reposition it.\r\n * - This prioritizes the earlier labels to be positioned closer to their\r\n * target points in the position they prefer.\r\n * - This prioritizes putting data labels close to a valid position over\r\n * placing them at their preferred position (it will place it at a less\r\n * preferred position if it will be a smaller offset)\r\n */\r\n public layout(labelDataPoints: DonutLabelDataPoint[]): Label[] {\r\n // Clear data labels for a new layout\r\n for (let donutLabel of labelDataPoints) {\r\n donutLabel.hasBeenRendered = false;\r\n donutLabel.labelSize = donutLabel.textSize;\r\n }\r\n\r\n let resultingLabels: Label[] = [];\r\n let preferredLabels: DonutLabelDataPoint[] = [];\r\n let viewport = this.donutChartProperties.viewport;\r\n let labelDataPointsGroup: LabelDataPointGroup = {\r\n labelDataPoints: labelDataPoints,\r\n maxNumberOfLabels: labelDataPoints.length\r\n };\r\n let grid = new LabelArrangeGrid([labelDataPointsGroup], viewport);\r\n for (let i = labelDataPoints.length - 1; i >= 0; i--) {\r\n let labelPoint = labelDataPoints[i];\r\n if (labelPoint.isPreferred) {\r\n let label = labelDataPoints.splice(i, 1);\r\n preferredLabels = label.concat(preferredLabels);\r\n }\r\n }\r\n\r\n // first iterate all the preferred labels\r\n if (preferredLabels.length > 0)\r\n resultingLabels = this.positionLabels(preferredLabels, grid);\r\n \r\n // While there are invisible not preferred labels and label distance is less than the max\r\n // allowed distance\r\n if (labelDataPoints.length > 0) {\r\n let labels = this.positionLabels(labelDataPoints, grid);\r\n resultingLabels = resultingLabels.concat(labels);\r\n }\r\n\r\n return resultingLabels;\r\n }\r\n\r\n private positionLabels(labelDataPoints: DonutLabelDataPoint[], grid: LabelArrangeGrid): Label[] {\r\n let resultingLabels: Label[] = [];\r\n let offsetDelta = this.offsetIterationDelta;\r\n let currentOffset = this.startingOffset;\r\n let currentCenteredOffset = 0;\r\n\r\n while (currentOffset <= this.maximumOffset) {\r\n for (let labelPoint of labelDataPoints) {\r\n if (labelPoint.hasBeenRendered)\r\n continue;\r\n let label = this.tryPositionForDonut(labelPoint, grid, currentOffset);\r\n if (label)\r\n resultingLabels.push(label);\r\n }\r\n\r\n currentOffset += offsetDelta;\r\n currentCenteredOffset += offsetDelta;\r\n }\r\n\r\n return resultingLabels;\r\n }\r\n\r\n /**\r\n * We try to move the label 25% up/down if the label is truncated or it collides with other labels.\r\n * after we moved it once we check that the new position doesn't failed (collides with other labels).\r\n */\r\n private tryPositionForDonut(labelPoint: DonutLabelDataPoint, grid: LabelArrangeGrid, currentLabelOffset: number): Label {\r\n let parentShape: LabelParentPoint = <LabelParentPoint>labelPoint.parentShape;\r\n if (_.isEmpty(parentShape.validPositions) || parentShape.validPositions[0] === NewPointLabelPosition.None)\r\n return;\r\n\r\n let defaultPosition: NewPointLabelPosition = parentShape.validPositions[0];\r\n\r\n let bestCandidate = this.tryAllPositions(labelPoint, grid, defaultPosition, currentLabelOffset);\r\n\r\n if (bestCandidate && bestCandidate.score === 0) {\r\n return this.buildLabel(bestCandidate, grid);\r\n }\r\n // If we haven't found a non-truncated label, try to split into 2 lines.\r\n if (this.donutChartProperties.dataLabelsSettings.labelStyle === labelStyle.both) {\r\n // Try to split the label to two lines if both data and category label are on\r\n let splitLabelDataPoint = this.splitDonutDataPoint(labelPoint);\r\n let bestSplitCandidate = this.tryAllPositions(splitLabelDataPoint, grid, defaultPosition, currentLabelOffset);\r\n // If the best candidate with a split line is better than the best candidate with a single line, return the former.\r\n if (bestSplitCandidate && (!bestCandidate || (bestSplitCandidate.score < bestCandidate.score))) {\r\n return this.buildLabel(bestSplitCandidate, grid);\r\n }\r\n }\r\n\r\n // We didn't find a better candidate by splitting the label lines, so return our best single-line candidate.\r\n if (bestCandidate) {\r\n return this.buildLabel(bestCandidate, grid);\r\n }\r\n }\r\n\r\n private generateCandidate(labelDataPoint: DonutLabelDataPoint, candidatePosition: NewPointLabelPosition, grid: LabelArrangeGrid, currentLabelOffset: number): LabelCandidate {\r\n let angle = this.generateCandidateAngleForPosition(labelDataPoint.donutArcDescriptor, candidatePosition);\r\n let parentShape = this.getPointPositionForAngle(angle);\r\n let parentPoint = parentShape.point;\r\n let score = this.score(labelDataPoint, parentPoint);\r\n let leaderLinePoints = DonutLabelUtils.getLabelLeaderLineForDonutChart(labelDataPoint.donutArcDescriptor, this.donutChartProperties, parentPoint, angle);\r\n let leaderLinesSize: ISize[] = DonutLabelUtils.getLabelLeaderLinesSizeForDonutChart(leaderLinePoints);\r\n\r\n let newLabelDataPoint = _.clone(labelDataPoint);\r\n newLabelDataPoint.angle = angle;\r\n newLabelDataPoint.parentShape = parentShape;\r\n newLabelDataPoint.leaderLinePoints = leaderLinePoints;\r\n newLabelDataPoint.linesSize = leaderLinesSize;\r\n\r\n let boundingBoxs: DonutLabelRect = DonutLabelLayout.tryPositionPoint(grid, parentShape.validPositions[0], newLabelDataPoint, currentLabelOffset, this.center, this.donutChartProperties.viewport);\r\n\r\n return {\r\n angle: angle,\r\n point: parentShape,\r\n score: score,\r\n labelRects: boundingBoxs,\r\n labelDataPoint: newLabelDataPoint,\r\n };\r\n }\r\n\r\n private tryAllPositions(labelDataPoint: DonutLabelDataPoint, grid: LabelArrangeGrid, defaultPosition: NewPointLabelPosition, currentLabelOffset: number): LabelCandidate {\r\n let boundingBoxs = DonutLabelLayout.tryPositionPoint(grid, defaultPosition, labelDataPoint, currentLabelOffset, this.center, this.donutChartProperties.viewport);\r\n\r\n let originalPoint: LabelParentPoint = <LabelParentPoint>labelDataPoint.parentShape;\r\n let originalCandidate: LabelCandidate = {\r\n point: originalPoint,\r\n angle: labelDataPoint.angle,\r\n score: this.score(labelDataPoint, originalPoint.point),\r\n labelRects: boundingBoxs,\r\n labelDataPoint: labelDataPoint,\r\n };\r\n\r\n if (boundingBoxs && boundingBoxs.textRect && originalCandidate.score === 0) {\r\n return originalCandidate;\r\n }\r\n\r\n let positions: NewPointLabelPosition[] = [];\r\n let bestCandidate: LabelCandidate;\r\n if (boundingBoxs && boundingBoxs.textRect) {\r\n // We have a truncated label here, otherwised we would have returned already\r\n positions = this.getLabelPointPositions(labelDataPoint, /* isTruncated */ true);\r\n bestCandidate = originalCandidate;\r\n }\r\n else {\r\n positions = this.getLabelPointPositions(labelDataPoint, /* isTruncated */ false);\r\n }\r\n\r\n // Try to reposition the label if necessary\r\n for (let position of positions) {\r\n let candidate = this.generateCandidate(labelDataPoint, position, grid, currentLabelOffset);\r\n if (candidate.labelRects && candidate.labelRects.textRect) {\r\n if (bestCandidate == null || candidate.score < bestCandidate.score) {\r\n bestCandidate = candidate;\r\n if (bestCandidate.score === 0)\r\n return bestCandidate;\r\n }\r\n }\r\n }\r\n\r\n return bestCandidate;\r\n }\r\n\r\n private buildLabel(labelLayout: LabelCandidate, grid: LabelArrangeGrid): Label {\r\n let resultingBoundingBox = labelLayout.labelRects.textRect;\r\n let labelPoint = labelLayout.labelDataPoint;\r\n\r\n grid.add(resultingBoundingBox);\r\n grid.add(labelLayout.labelRects.horizontalLineRect);\r\n grid.add(labelLayout.labelRects.diagonalLineRect);\r\n labelPoint.hasBeenRendered = true;\r\n let left = resultingBoundingBox.left - this.center.x;\r\n //We need to add or subtract half resultingBoundingBox.width because Donut chart labels get text anchor start/end\r\n if (left < 0)\r\n left += resultingBoundingBox.width / 2;\r\n else\r\n left -= resultingBoundingBox.width / 2;\r\n let textAnchor = labelPoint.parentShape.validPositions[0] === NewPointLabelPosition.Right ? 'start' : 'end';\r\n let boundingBox: IRect = {\r\n left: left,\r\n top: resultingBoundingBox.top - this.center.y,\r\n height: resultingBoundingBox.height,\r\n width: resultingBoundingBox.width,\r\n };\r\n\r\n // After repositioning the label we need to recalculate its size and format it according to the current available space\r\n let labelSettingsStyle = this.donutChartProperties.dataLabelsSettings.labelStyle;\r\n let spaceAvailableForLabels = DonutLabelUtils.getSpaceAvailableForDonutLabels((<LabelParentPoint>labelPoint.parentShape).point.x, this.donutChartProperties.viewport);\r\n let formattedDataLabel: string;\r\n let formattedCategoryLabel: string;\r\n let text: string;\r\n let getLabelFormattedText = powerbi.visuals.dataLabelUtils.getLabelFormattedText;\r\n let fontSize = labelPoint.fontSize;\r\n let hasOneLabelRow = labelSettingsStyle === labelStyle.both && labelPoint.secondRowText == null;\r\n\r\n // Giving 50/50 space when both category and measure are on\r\n if (hasOneLabelRow) {\r\n labelPoint.dataLabel = \" (\" + labelPoint.dataLabel + \")\";\r\n spaceAvailableForLabels /= 2;\r\n }\r\n\r\n if (labelSettingsStyle === labelStyle.both || labelSettingsStyle === labelStyle.data) {\r\n formattedDataLabel = getLabelFormattedText({\r\n label: labelPoint.dataLabel,\r\n maxWidth: spaceAvailableForLabels,\r\n fontSize: fontSize\r\n });\r\n }\r\n\r\n if (labelSettingsStyle === labelStyle.both || labelSettingsStyle === labelStyle.category) {\r\n formattedCategoryLabel = getLabelFormattedText({\r\n label: labelPoint.categoryLabel,\r\n maxWidth: spaceAvailableForLabels,\r\n fontSize: fontSize\r\n });\r\n }\r\n\r\n switch (labelSettingsStyle) {\r\n case labelStyle.both:\r\n if (labelPoint.secondRowText == null) {\r\n text = formattedCategoryLabel + formattedDataLabel;\r\n }\r\n else {\r\n text = formattedDataLabel;\r\n labelPoint.secondRowText = formattedCategoryLabel; \r\n }\r\n break;\r\n case labelStyle.data:\r\n text = formattedDataLabel;\r\n break;\r\n case labelStyle.category:\r\n text = formattedCategoryLabel;\r\n break;\r\n }\r\n\r\n // Limit text size width for correct leader line calculation\r\n labelPoint.textSize.width = Math.min(labelPoint.textSize.width, hasOneLabelRow ? spaceAvailableForLabels * 2 : spaceAvailableForLabels);\r\n\r\n return {\r\n boundingBox: boundingBox,\r\n text: text,\r\n tooltip: labelPoint.tooltip,\r\n isVisible: true,\r\n fill: labelPoint.outsideFill,\r\n identity: labelPoint.identity,\r\n fontSize: fontSize,\r\n selected: false,\r\n textAnchor: textAnchor,\r\n leaderLinePoints: labelPoint.leaderLinePoints,\r\n hasBackground: false,\r\n secondRowText: labelPoint.secondRowText,\r\n };\r\n }\r\n\r\n private static tryPositionPoint(grid: LabelArrangeGrid, position: NewPointLabelPosition, labelDataPoint: DonutLabelDataPoint, offset: number, center: IVector, viewport: IViewport): DonutLabelRect {\r\n let parentPoint: LabelParentPoint = <LabelParentPoint>labelDataPoint.parentShape;\r\n \r\n // Limit label width to fit the availabe space for labels\r\n let textSize: ISize = _.clone(labelDataPoint.textSize);\r\n textSize.width = Math.min(textSize.width, DonutLabelUtils.getSpaceAvailableForDonutLabels(parentPoint.point.x, viewport));\r\n\r\n // Create label rectangle\r\n let labelRect = DataLabelPointPositioner.getLabelRect(textSize, parentPoint, position, offset);\r\n\r\n // Create label diagonal line rectangle\r\n let diagonalLineParentPoint: LabelParentPoint = {\r\n point: {\r\n x: labelDataPoint.leaderLinePoints[0][0],\r\n y: labelDataPoint.leaderLinePoints[0][1] < 0 ? labelDataPoint.leaderLinePoints[1][1] : labelDataPoint.leaderLinePoints[0][1]\r\n },\r\n radius: 0,\r\n validPositions: null\r\n };\r\n let diagonalLineRect = DataLabelPointPositioner.getLabelRect(labelDataPoint.linesSize[DonutLabelUtils.DiagonalLineIndex], diagonalLineParentPoint, position, offset);\r\n\r\n // Create label horizontal line rectangle\r\n let horizontalLineParentPoint: LabelParentPoint = {\r\n point: {\r\n x: labelDataPoint.leaderLinePoints[1][0],\r\n y: labelDataPoint.leaderLinePoints[1][1]\r\n },\r\n radius: 0,\r\n validPositions: null\r\n };\r\n let horizontalLineRect = DataLabelPointPositioner.getLabelRect(labelDataPoint.linesSize[DonutLabelUtils.HorizontalLineIndex], horizontalLineParentPoint, position, offset);\r\n\r\n if (!labelRect || !diagonalLineRect || !horizontalLineRect)\r\n return;\r\n\r\n labelRect.left += center.x;\r\n labelRect.top += center.y;\r\n let centerForLinesWidth = center.x - labelRect.width / 2;\r\n diagonalLineRect.left += centerForLinesWidth;\r\n diagonalLineRect.top += center.y;\r\n horizontalLineRect.left += centerForLinesWidth;\r\n horizontalLineRect.top += center.y;\r\n\r\n if (!grid.hasConflict(labelRect) && !grid.hasConflict(diagonalLineRect) && !grid.hasConflict(horizontalLineRect))\r\n return { textRect: labelRect, diagonalLineRect: diagonalLineRect, horizontalLineRect: horizontalLineRect };\r\n }\r\n\r\n /**\r\n * Returns an array of valid positions for hidden and truncated labels.\r\n * For truncated labels will return positions with more available space. \r\n * For hidden labels will return all possible positions by the order we draw labels (clockwise) \r\n */\r\n private getLabelPointPositions(labelPoint: DonutLabelDataPoint, isTruncated: boolean): NewPointLabelPosition[] {\r\n let parentShape: LabelParentPoint = <LabelParentPoint>labelPoint.parentShape;\r\n let position = parentShape.validPositions[0];\r\n\r\n if (!isTruncated) {\r\n return position === NewPointLabelPosition.Left\r\n ? [NewPointLabelPosition.AboveLeft, NewPointLabelPosition.BelowLeft]\r\n : [NewPointLabelPosition.BelowRight, NewPointLabelPosition.AboveRight];\r\n }\r\n\r\n if (parentShape.point.y < 0) {\r\n return position === NewPointLabelPosition.Right\r\n ? [NewPointLabelPosition.AboveRight]\r\n : [NewPointLabelPosition.AboveLeft];\r\n }\r\n else {\r\n return position === NewPointLabelPosition.Right\r\n ? [NewPointLabelPosition.BelowRight]\r\n : [NewPointLabelPosition.BelowLeft];\r\n }\r\n }\r\n\r\n /**\r\n * Returns a new DonutLabelDataPoint after splitting it into two lines\r\n */\r\n private splitDonutDataPoint(labelPoint: DonutLabelDataPoint): DonutLabelDataPoint {\r\n let textSize: ISize = {\r\n width: Math.max(labelPoint.categoryLabelSize.width, labelPoint.dataLabelSize.width),\r\n height: labelPoint.dataLabelSize.height * 2,\r\n };\r\n\r\n let newLabelPoint: DonutLabelDataPoint = _.clone(labelPoint);\r\n newLabelPoint.textSize = textSize;\r\n newLabelPoint.secondRowText = labelPoint.categoryLabel;\r\n return newLabelPoint;\r\n }\r\n\r\n private generateCandidateAngleForPosition(d: DonutArcDescriptor, position: NewPointLabelPosition): number {\r\n let midAngle = d.startAngle + ((d.endAngle - d.startAngle) / 2);\r\n\r\n switch (position) {\r\n case NewPointLabelPosition.AboveRight:\r\n case NewPointLabelPosition.BelowLeft:\r\n return ((d.startAngle + midAngle) - Math.PI) / 2;\r\n case NewPointLabelPosition.AboveLeft:\r\n case NewPointLabelPosition.BelowRight:\r\n return ((midAngle + d.endAngle) - Math.PI) / 2;\r\n default:\r\n debug.assertFail(\"Unsupported label position\");\r\n }\r\n }\r\n\r\n private getPointPositionForAngle(angle: number): LabelParentPoint {\r\n // Calculate the new label coordinates\r\n let labelX = DonutLabelUtils.getXPositionForDonutLabel(Math.cos(angle) * this.outerRadius);\r\n let labelY = Math.sin(angle) * this.outerRadius;\r\n\r\n let newPosition = labelX < 0 ? NewPointLabelPosition.Left : NewPointLabelPosition.Right;\r\n let pointPosition: LabelParentPoint = {\r\n point: {\r\n x: labelX,\r\n y: labelY,\r\n },\r\n validPositions: [newPosition],\r\n radius: 0,\r\n };\r\n return pointPosition;\r\n }\r\n\r\n private score(labelPoint: DonutLabelDataPoint, point: powerbi.visuals.shapes.IPoint): number {\r\n let spaceAvailableForLabels = DonutLabelUtils.getSpaceAvailableForDonutLabels(point.x, this.donutChartProperties.viewport);\r\n let textWidth: number;\r\n\r\n // Check if we show category and data labels in one row\r\n if (this.donutChartProperties.dataLabelsSettings.labelStyle === labelStyle.both && labelPoint.secondRowText == null) {\r\n // Each of the labels gets half of the available space for labels so we take this into consideration in the score\r\n textWidth = Math.max(labelPoint.categoryLabelSize.width, labelPoint.dataLabelSize.width + this.additionalCharsWidth);\r\n spaceAvailableForLabels /= 2;\r\n }\r\n else {\r\n textWidth = labelPoint.textSize.width;\r\n }\r\n\r\n return Math.max(textWidth - spaceAvailableForLabels, 0);\r\n }\r\n\r\n }\r\n}\r\n\n\n\n/** WEBPACK FOOTER **\n ** ./src/Clients/Visuals/formatting/donutLabelLayout.ts\n **/","/*\r\n * Power BI Visualizations\r\n *\r\n * Copyright (c) Microsoft Corporation\r\n * All rights reserved.\r\n * MIT License\r\n *\r\n * Permission is hereby granted, free of charge, to any person obtaining a copy\r\n * of this software and associated documentation files (the \"\"Software\"\"), to deal\r\n * in the Software without restriction, including without limitation the rights\r\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\r\n * copies of the Software, and to permit persons to whom the Software is\r\n * furnished to do so, subject to the following conditions:\r\n *\r\n * The above copyright notice and this permission notice shall be included in\r\n * all copies or substantial portions of the Software.\r\n *\r\n * THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\r\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\r\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\r\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\r\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\r\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\r\n * THE SOFTWARE.\r\n */\r\n\r\n/// <reference path=\"../_references.ts\"/>\r\n\r\nmodule powerbi {\r\n\r\n import ISize = powerbi.visuals.shapes.ISize;\r\n import IPoint = powerbi.visuals.IPoint;\r\n import IRect = powerbi.visuals.IRect;\r\n import Polygon = powerbi.visuals.shapes.Polygon;\r\n import Transform = powerbi.visuals.Transform;\r\n import NewDataLabelUtils = powerbi.visuals.NewDataLabelUtils;\r\n\r\n const DefaultCentroidOffset = 5;\r\n const OffsetDelta = 10;\r\n const MaximumOffset = 60;\r\n const stemExtension = 5;\r\n\r\n export interface LabelParentPolygon {\r\n /** The point this data label belongs to */\r\n polygon: Polygon;\r\n\r\n /** Valid positions to place the label ordered by preference */\r\n validPositions: NewPointLabelPosition[];\r\n }\r\n\r\n export interface FilledMapLabel extends Label {\r\n absoluteBoundingBoxCenter: IPoint;\r\n originalPixelOffset: number;\r\n originalPosition?: NewPointLabelPosition;\r\n originalAbsoluteCentroid?: IPoint;\r\n absoluteStemSource?: IPoint;\r\n isPlacedInsidePolygon: boolean;\r\n }\r\n\r\n export class FilledMapLabelLayout {\r\n private labels: FilledMapLabel[];\r\n\r\n public layout(labelDataPoints: LabelDataPoint[], viewport: IViewport, polygonInfoTransform: Transform, redrawDataLabels: boolean): Label[] {\r\n if (redrawDataLabels || this.labels === undefined) {\r\n let labelDataPointsGroup: LabelDataPointGroup = {\r\n labelDataPoints: labelDataPoints,\r\n maxNumberOfLabels: labelDataPoints.length\r\n };\r\n\r\n for (let labelPoint of labelDataPoints) {\r\n labelPoint.labelSize = {\r\n width: labelPoint.textSize.width + 2 * NewDataLabelUtils.horizontalLabelBackgroundPadding,\r\n height: labelPoint.textSize.height + 2 * NewDataLabelUtils.verticalLabelBackgroundPadding,\r\n };\r\n }\r\n\r\n let grid = new LabelArrangeGrid([labelDataPointsGroup], viewport);\r\n let resultingDataLabels: FilledMapLabel[] = [];\r\n let allPolygons: Polygon[] = [];\r\n\r\n for (let labelPoint of labelDataPoints) {\r\n let polygon = (<LabelParentPolygon>labelPoint.parentShape).polygon;\r\n allPolygons.push(polygon);\r\n polygon.pixelBoundingRect = polygonInfoTransform.applyToRect(polygon.absoluteBoundingRect());\r\n }\r\n\r\n let shapesgrid = new LabelPolygonArrangeGrid(allPolygons, viewport);\r\n\r\n for (let labelPoint of labelDataPoints) {\r\n let dataLabel = this.getLabelByPolygonPositions(labelPoint, polygonInfoTransform, grid, shapesgrid);\r\n if (dataLabel != null) {\r\n resultingDataLabels.push(dataLabel);\r\n }\r\n }\r\n this.labels = resultingDataLabels;\r\n }\r\n else {\r\n this.updateLabelOffsets(polygonInfoTransform);\r\n }\r\n return this.labels;\r\n }\r\n\r\n public getLabelPolygon(mapDataPoint: LabelDataPoint, position: NewPointLabelPosition, pointPosition: IPoint, offset: number): IRect {\r\n let dataPointSize: ISize = {\r\n width: mapDataPoint.textSize.width,\r\n height: (mapDataPoint.textSize.height)\r\n };\r\n\r\n return this.getLabelBoundingBox(dataPointSize, position, pointPosition, offset);\r\n }\r\n\r\n private getLabelBoundingBox(dataPointSize: ISize, position: NewPointLabelPosition, pointPosition: IPoint, offset: number): IRect {\r\n switch (position) {\r\n case NewPointLabelPosition.Above: {\r\n return DataLabelPointPositioner.above(dataPointSize, pointPosition, offset);\r\n }\r\n case NewPointLabelPosition.Below: {\r\n return DataLabelPointPositioner.below(dataPointSize, pointPosition, offset);\r\n }\r\n case NewPointLabelPosition.Left: {\r\n return DataLabelPointPositioner.left(dataPointSize, pointPosition, offset);\r\n }\r\n case NewPointLabelPosition.Right: {\r\n return DataLabelPointPositioner.right(dataPointSize, pointPosition, offset);\r\n }\r\n case NewPointLabelPosition.AboveLeft: {\r\n return DataLabelPointPositioner.aboveLeft(dataPointSize, pointPosition, offset);\r\n }\r\n case NewPointLabelPosition.AboveRight: {\r\n return DataLabelPointPositioner.aboveRight(dataPointSize, pointPosition, offset);\r\n }\r\n case NewPointLabelPosition.BelowLeft: {\r\n return DataLabelPointPositioner.belowLeft(dataPointSize, pointPosition, offset);\r\n }\r\n case NewPointLabelPosition.BelowRight: {\r\n return DataLabelPointPositioner.belowRight(dataPointSize, pointPosition, offset);\r\n }\r\n case NewPointLabelPosition.Center: {\r\n return DataLabelPointPositioner.center(dataPointSize, pointPosition);\r\n }\r\n default: {\r\n debug.assertFail(\"Unsupported label position\");\r\n }\r\n }\r\n return null;\r\n }\r\n\r\n private getLabelByPolygonPositions(labelPoint: LabelDataPoint, polygonInfoTransform: Transform, grid: LabelArrangeGrid, shapesGrid: LabelPolygonArrangeGrid): FilledMapLabel {\r\n let offset: number = 0;\r\n let inverseTransorm = polygonInfoTransform.getInverse();\r\n for (let i: number = 0; i < 2; i++) {\r\n if (i === 1) {\r\n offset = DefaultCentroidOffset;\r\n }\r\n for (let position of (<LabelParentPolygon>labelPoint.parentShape).validPositions) {\r\n let resultingAbsoluteBoundingBox = this.tryPositionForPolygonPosition(position, labelPoint, polygonInfoTransform, offset, inverseTransorm);\r\n if (position === NewPointLabelPosition.Center && i !== 0) {\r\n continue;\r\n }\r\n if (resultingAbsoluteBoundingBox) {\r\n let resultingBoundingBox = polygonInfoTransform.applyToRect(resultingAbsoluteBoundingBox);\r\n let dataLabel: FilledMapLabel = {\r\n text: labelPoint.text,\r\n secondRowText: labelPoint.secondRowText,\r\n boundingBox: resultingBoundingBox,\r\n isVisible: true,\r\n fill: labelPoint.insideFill,\r\n identity: null,\r\n selected: false,\r\n hasBackground: true,\r\n textAnchor: \"middle\",\r\n originalPixelOffset: offset,\r\n isPlacedInsidePolygon: true,\r\n absoluteBoundingBoxCenter: {\r\n x: resultingAbsoluteBoundingBox.left + resultingAbsoluteBoundingBox.width / 2,\r\n y: resultingAbsoluteBoundingBox.top + resultingAbsoluteBoundingBox.height / 2\r\n }\r\n };\r\n\r\n return dataLabel;\r\n }\r\n }\r\n }\r\n\r\n let currentOffset = 6;\r\n\r\n while (currentOffset <= MaximumOffset) {\r\n for (let position of (<powerbi.LabelParentPolygon>labelPoint.parentShape).validPositions) {\r\n if (position === NewPointLabelPosition.Center) {\r\n continue;\r\n }\r\n let polygon = (<LabelParentPolygon>labelPoint.parentShape).polygon;\r\n let pixelCentroid = polygonInfoTransform.applyToPoint(polygon.absoluteCentroid());\r\n let resultingAbsolutBoundingBox = this.tryPlaceLabelOutsidePolygon(grid, position, labelPoint, currentOffset, pixelCentroid, shapesGrid, inverseTransorm);\r\n\r\n if (resultingAbsolutBoundingBox) {\r\n let resultingBoundingBox = polygonInfoTransform.applyToRect(resultingAbsolutBoundingBox);\r\n let dataLabel: FilledMapLabel = {\r\n text: labelPoint.text,\r\n secondRowText: labelPoint.secondRowText,\r\n boundingBox: resultingBoundingBox,\r\n isVisible: true,\r\n fill: labelPoint.insideFill,\r\n identity: null,\r\n selected: false,\r\n hasBackground: true,\r\n isPlacedInsidePolygon: false,\r\n textAnchor: \"middle\",\r\n originalPixelOffset: currentOffset,\r\n originalPosition: position,\r\n originalAbsoluteCentroid: polygon.absoluteCentroid(),\r\n absoluteBoundingBoxCenter: {\r\n x: resultingAbsolutBoundingBox.left + resultingAbsolutBoundingBox.width / 2,\r\n y: resultingAbsolutBoundingBox.top + resultingAbsolutBoundingBox.height / 2\r\n }\r\n };\r\n\r\n let pixelStemSource = this.calculateStemSource(polygonInfoTransform, inverseTransorm, polygon, resultingBoundingBox, position, pixelCentroid);\r\n\r\n dataLabel.leaderLinePoints = this.setLeaderLinePoints(pixelStemSource, this.calculateStemDestination(resultingBoundingBox, position));\r\n\r\n dataLabel.absoluteStemSource = inverseTransorm.applyToPoint(pixelStemSource);\r\n\r\n grid.add(resultingBoundingBox);\r\n return dataLabel;\r\n }\r\n }\r\n currentOffset += OffsetDelta;\r\n }\r\n return null;\r\n }\r\n\r\n private setLeaderLinePoints(stemSource: IPoint, stemDestination: IPoint): number[][] {\r\n return [[stemSource.x, stemSource.y], [stemDestination.x, stemDestination.y]];\r\n }\r\n\r\n private calculateStemSource(polygonInfoTransform: Transform, inverseTransorm: Transform, polygon: Polygon, labelBoundingBox: IRect, position: NewPointLabelPosition, pixelCentroid: IPoint): IPoint {\r\n let absoluteStemSource = polygon.lineIntersectionPoint(polygon.absoluteCentroid(),\r\n inverseTransorm.applyToPoint({ x: labelBoundingBox.left + labelBoundingBox.width / 2, y: labelBoundingBox.top + labelBoundingBox.height / 2 }));\r\n\r\n if (absoluteStemSource == null) {\r\n return pixelCentroid;\r\n }\r\n\r\n let stemSource = polygonInfoTransform.applyToPoint(absoluteStemSource);\r\n\r\n switch (position) {\r\n case NewPointLabelPosition.Above: {\r\n stemSource.y += stemExtension;\r\n break;\r\n }\r\n case NewPointLabelPosition.Below: {\r\n stemSource.y -= stemExtension;\r\n break;\r\n }\r\n case NewPointLabelPosition.Left: {\r\n stemSource.x += stemExtension;\r\n break;\r\n }\r\n case NewPointLabelPosition.Right: {\r\n stemSource.x -= stemExtension;\r\n break;\r\n }\r\n case NewPointLabelPosition.AboveLeft: {\r\n stemSource.x += (stemExtension / DataLabelPointPositioner.cos45);\r\n stemSource.y += (stemExtension / DataLabelPointPositioner.sin45);\r\n break;\r\n }\r\n case NewPointLabelPosition.AboveRight: {\r\n stemSource.x -= (stemExtension / DataLabelPointPositioner.cos45);\r\n stemSource.y += (stemExtension / DataLabelPointPositioner.sin45);\r\n break;\r\n }\r\n case NewPointLabelPosition.BelowLeft: {\r\n stemSource.x += (stemExtension / DataLabelPointPositioner.cos45);\r\n stemSource.y -= (stemExtension / DataLabelPointPositioner.sin45);\r\n break;\r\n }\r\n case NewPointLabelPosition.BelowRight: {\r\n stemSource.x -= (stemExtension / DataLabelPointPositioner.cos45);\r\n stemSource.y -= (stemExtension / DataLabelPointPositioner.sin45);\r\n break;\r\n }\r\n case NewPointLabelPosition.Center: {\r\n break;\r\n }\r\n default: {\r\n debug.assertFail(\"Unsupported label position\");\r\n }\r\n }\r\n\r\n return stemSource;\r\n }\r\n\r\n private calculateStemDestination(labelBoundingBox: IRect, position: NewPointLabelPosition): IPoint {\r\n let x: number;\r\n let y: number;\r\n switch (position) {\r\n case NewPointLabelPosition.Above: {\r\n x = labelBoundingBox.left + labelBoundingBox.width / 2;\r\n y = labelBoundingBox.top + labelBoundingBox.height;\r\n break;\r\n }\r\n case NewPointLabelPosition.Below: {\r\n x = labelBoundingBox.left + labelBoundingBox.width / 2;\r\n y = labelBoundingBox.top;\r\n break;\r\n }\r\n case NewPointLabelPosition.Left: {\r\n x = labelBoundingBox.left + labelBoundingBox.width;\r\n y = labelBoundingBox.top + labelBoundingBox.height / 2;\r\n break;\r\n }\r\n case NewPointLabelPosition.Right: {\r\n x = labelBoundingBox.left;\r\n y = labelBoundingBox.top + labelBoundingBox.height / 2;\r\n break;\r\n }\r\n case NewPointLabelPosition.AboveLeft: {\r\n x = labelBoundingBox.left + labelBoundingBox.width;\r\n y = labelBoundingBox.top + labelBoundingBox.height;\r\n break;\r\n }\r\n case NewPointLabelPosition.AboveRight: {\r\n x = labelBoundingBox.left;\r\n y = labelBoundingBox.top + labelBoundingBox.height;\r\n break;\r\n }\r\n case NewPointLabelPosition.BelowLeft: {\r\n x = labelBoundingBox.left + labelBoundingBox.width;\r\n y = labelBoundingBox.top;\r\n break;\r\n }\r\n case NewPointLabelPosition.BelowRight: {\r\n x = labelBoundingBox.left;\r\n y = labelBoundingBox.top;\r\n break;\r\n }\r\n case NewPointLabelPosition.Center: {\r\n break;\r\n }\r\n default: {\r\n debug.assertFail(\"Unsupported label position\");\r\n }\r\n }\r\n\r\n return { x: x, y: y };\r\n }\r\n\r\n private tryPositionForPolygonPosition(position: NewPointLabelPosition, labelDataPoint: LabelDataPoint, polygonInfoTransform: Transform, offset: number, inverseTransorm: Transform) {\r\n let polygon = (<LabelParentPolygon>labelDataPoint.parentShape).polygon;\r\n let pixelCentroid = polygonInfoTransform.applyToPoint(polygon.absoluteCentroid());\r\n let labelRect = this.getLabelPolygon(labelDataPoint, position, pixelCentroid, offset);\r\n let absoluteLabelRect = this.getAbsoluteRectangle(inverseTransorm, labelRect);\r\n\r\n return polygon.contains(absoluteLabelRect) ? absoluteLabelRect : null;\r\n }\r\n\r\n /**\r\n * Tests a particular position/offset combination for the given data label.\r\n * If the label can be placed, returns the resulting bounding box for the data\r\n * label. If not, returns null.\r\n */\r\n private tryPlaceLabelOutsidePolygon(grid: LabelArrangeGrid, position: NewPointLabelPosition, labelDataPoint: LabelDataPoint, offset: number, pixelCentroid: IPoint, shapesGrid: LabelPolygonArrangeGrid, inverseTransform: powerbi.visuals.Transform): IRect {\r\n let offsetForPosition = offset;\r\n let labelRect = this.getLabelPolygon(labelDataPoint, position, pixelCentroid, offsetForPosition);\r\n\r\n let otherLabelsConflict = grid.hasConflict(labelRect);\r\n\r\n if (!otherLabelsConflict) {\r\n let absoluteLabelRect = this.getAbsoluteRectangle(inverseTransform, labelRect);\r\n\r\n if (!shapesGrid.hasConflict(absoluteLabelRect, labelRect))\r\n return absoluteLabelRect;\r\n }\r\n return null;\r\n }\r\n\r\n private updateLabelOffsets(polygonInfoTransform: Transform): void {\r\n for (let label of this.labels) {\r\n if (!label.isVisible)\r\n continue;\r\n\r\n if (label.isPlacedInsidePolygon) {\r\n var newOffset = polygonInfoTransform.applyToPoint(label.absoluteBoundingBoxCenter);\r\n\r\n let xDelta = (label.boundingBox.left + label.boundingBox.width / 2) - newOffset.x;\r\n let yDelta = (label.boundingBox.top + label.boundingBox.height / 2) - newOffset.y;\r\n\r\n label.boundingBox.top -= yDelta;\r\n label.boundingBox.left -= xDelta;\r\n } else {\r\n var stemSourcePoint = polygonInfoTransform.applyToPoint(label.absoluteStemSource);\r\n var pixelCentroid = polygonInfoTransform.applyToPoint(label.originalAbsoluteCentroid);\r\n label.boundingBox = this.getLabelBoundingBox({ width: label.boundingBox.width, height: label.boundingBox.height }, label.originalPosition, pixelCentroid, label.originalPixelOffset);\r\n\r\n if (label.leaderLinePoints !== undefined)\r\n label.leaderLinePoints = this.setLeaderLinePoints(stemSourcePoint, this.calculateStemDestination(label.boundingBox, label.originalPosition));\r\n }\r\n }\r\n }\r\n\r\n private getAbsoluteRectangle(inverseTransorm: Transform, rect: IRect) {\r\n return inverseTransorm.applyToRect(rect);\r\n }\r\n }\r\n\r\n export class LabelPolygonArrangeGrid {\r\n private grid: Polygon[][][];\r\n private viewport: IViewport;\r\n private cellSize: ISize;\r\n private columnCount: number;\r\n private rowCount: number;\r\n\r\n /** \r\n * A multiplier applied to the largest width height to attempt to balance # of\r\n * polygons in each cell and number of cells each polygon belongs to\r\n */\r\n private static cellSizeMultiplier = 2;\r\n\r\n constructor(polygons: Polygon[], viewport: IViewport) {\r\n this.viewport = viewport;\r\n let maxPolygonWidth = 0;\r\n let maxPolygonHeight = 0;\r\n\r\n for (let polygon of polygons) {\r\n let polygonSize: ISize = polygon.pixelBoundingRect;\r\n if (polygonSize.width > maxPolygonWidth) {\r\n maxPolygonWidth = polygonSize.width;\r\n }\r\n if (polygonSize.height > maxPolygonHeight) {\r\n maxPolygonHeight = polygonSize.height;\r\n }\r\n }\r\n\r\n if (maxPolygonWidth === 0) {\r\n maxPolygonWidth = viewport.width;\r\n }\r\n if (maxPolygonHeight === 0) {\r\n maxPolygonHeight = viewport.height;\r\n }\r\n\r\n let cellSize = this.cellSize = { width: maxPolygonWidth * LabelPolygonArrangeGrid.cellSizeMultiplier, height: maxPolygonHeight * LabelPolygonArrangeGrid.cellSizeMultiplier };\r\n this.columnCount = LabelPolygonArrangeGrid.getCellCount(cellSize.width, viewport.width, 1, 100);\r\n this.rowCount = LabelPolygonArrangeGrid.getCellCount(cellSize.height, viewport.height, 1, 100);\r\n let grid: Polygon[][][] = [];\r\n for (let i = 0, ilen = this.columnCount; i < ilen; i++) {\r\n grid[i] = [];\r\n for (let j = 0, jlen = this.rowCount; j < jlen; j++) {\r\n grid[i][j] = [];\r\n }\r\n }\r\n\r\n this.grid = grid;\r\n\r\n for (let polygon of polygons) {\r\n this.add(polygon);\r\n }\r\n\r\n }\r\n\r\n public hasConflict(absolutLabelRect: IRect, pixelLabelRect: IRect): boolean {\r\n let containingIndexRect = this.getContainingGridSubsection(pixelLabelRect);\r\n let grid = this.grid;\r\n for (let x = containingIndexRect.xMin; x < containingIndexRect.xMax; x++) {\r\n for (let y = containingIndexRect.yMin; y < containingIndexRect.yMax; y++) {\r\n for (let currentPolygon of grid[x][y]) {\r\n if (currentPolygon.conflicts(absolutLabelRect)) {\r\n return true;\r\n }\r\n }\r\n }\r\n }\r\n return false;\r\n }\r\n\r\n private add(polygon: Polygon): void {\r\n let containingIndexRect = this.getContainingGridSubsection(polygon.pixelBoundingRect);\r\n for (let x = containingIndexRect.xMin; x < containingIndexRect.xMax; x++) {\r\n for (let y = containingIndexRect.yMin; y < containingIndexRect.yMax; y++) {\r\n this.grid[x][y].push(polygon);\r\n }\r\n }\r\n }\r\n\r\n private getContainingGridSubsection(rect: IRect): GridSubsection {\r\n return {\r\n xMin: LabelPolygonArrangeGrid.bound(Math.floor(rect.left / this.cellSize.width), 0, this.columnCount),\r\n xMax: LabelPolygonArrangeGrid.bound(Math.ceil((rect.left + rect.width) / this.cellSize.width), 0, this.columnCount),\r\n yMin: LabelPolygonArrangeGrid.bound(Math.floor(rect.top / this.cellSize.height), 0, this.rowCount),\r\n yMax: LabelPolygonArrangeGrid.bound(Math.ceil((rect.top + rect.height) / this.cellSize.height), 0, this.rowCount),\r\n };\r\n }\r\n\r\n private static getCellCount(step: number, length: number, minCount: number, maxCount: number): number {\r\n return LabelPolygonArrangeGrid.bound(Math.ceil(length / step), minCount, maxCount);\r\n }\r\n\r\n private static bound(value: number, min: number, max: number): number {\r\n return Math.max(Math.min(value, max), min);\r\n }\r\n\r\n }\r\n\r\n}\n\n\n/** WEBPACK FOOTER **\n ** ./src/Clients/Visuals/formatting/filledMapLabelLayout.ts\n **/","/*\r\n * Power BI Visualizations\r\n *\r\n * Copyright (c) Microsoft Corporation\r\n * All rights reserved. \r\n * MIT License\r\n *\r\n * Permission is hereby granted, free of charge, to any person obtaining a copy\r\n * of this software and associated documentation files (the \"\"Software\"\"), to deal\r\n * in the Software without restriction, including without limitation the rights\r\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\r\n * copies of the Software, and to permit persons to whom the Software is\r\n * furnished to do so, subject to the following conditions:\r\n * \r\n * The above copyright notice and this permission notice shall be included in \r\n * all copies or substantial portions of the Software.\r\n * \r\n * THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR \r\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, \r\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE \r\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER \r\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\r\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\r\n * THE SOFTWARE.\r\n */\r\n\r\n/// <reference path=\"../_references.ts\"/>\r\n\r\nmodule powerbi.visuals {\r\n export function createColorAllocatorFactory(): IColorAllocatorFactory {\r\n return new ColorAllocatorFactory();\r\n }\r\n\r\n class ColorAllocatorFactory implements IColorAllocatorFactory {\r\n public linearGradient2(options: LinearGradient2): IColorAllocator {\r\n return new LinearGradient2Allocator(options);\r\n }\r\n\r\n public linearGradient3(options: LinearGradient3, splitScales: boolean): IColorAllocator {\r\n if (splitScales)\r\n return new LinearGradient3AllocatorWithSplittedScales(options);\r\n return new LinearGradient3Allocator(options);\r\n }\r\n }\r\n\r\n class LinearGradient2Allocator implements IColorAllocator {\r\n private options: LinearGradient2;\r\n private scale: D3.Scale.LinearScale;\r\n \r\n constructor(options: LinearGradient2) {\r\n debug.assertValue(options, 'options');\r\n\r\n this.options = options;\r\n\r\n let min = options.min,\r\n max = options.max;\r\n this.scale = d3.scale.linear()\r\n .domain([min.value, max.value])\r\n .range([min.color, max.color])\r\n .clamp(true); // process a value outside of the domain - set to extremum values\r\n }\r\n\r\n public color(value: number): string {\r\n let min = this.options.min,\r\n max = this.options.max;\r\n if (min.value === max.value) {\r\n if (value >= max.value)\r\n return max.color;\r\n return min.color;\r\n }\r\n return <any>this.scale(value);\r\n }\r\n }\r\n\r\n class LinearGradient3Allocator implements IColorAllocator {\r\n private options: LinearGradient3;\r\n private scale: D3.Scale.LinearScale;\r\n\r\n constructor(options: LinearGradient3) {\r\n debug.assertValue(options, 'options');\r\n\r\n this.options = options;\r\n\r\n let min = options.min,\r\n mid = options.mid,\r\n max = options.max;\r\n this.scale = d3.scale.linear()\r\n .domain([min.value, mid.value, max.value])\r\n .range([min.color, mid.color, max.color])\r\n .clamp(true); // process a value outside of the domain- set to extremum values\r\n }\r\n\r\n public color(value: number): string {\r\n let min = this.options.min,\r\n mid = this.options.mid,\r\n max = this.options.max;\r\n\r\n if (max.value === mid.value || mid.value === min.value || (max.value === mid.value && max.value === min.value)) {\r\n if (value >= max.value)\r\n return max.color;\r\n else if (value >= mid.value)\r\n return mid.color;\r\n return min.color;\r\n }\r\n return <any>this.scale(value);\r\n }\r\n }\r\n\r\n class LinearGradient3AllocatorWithSplittedScales implements IColorAllocator {\r\n private options: LinearGradient3;\r\n private scale1: D3.Scale.LinearScale;\r\n private scale2: D3.Scale.LinearScale;\r\n\r\n constructor(options: LinearGradient3) {\r\n debug.assertValue(options, 'options');\r\n\r\n this.options = options;\r\n\r\n let min = options.min,\r\n mid = options.mid,\r\n max = options.max;\r\n /*\r\n If the center value is overridden, but the max and min remain automatic,\r\n colors are then assigned on a scale between the overridden center value and the max/min values in the data.\r\n Each side of the center value is assigned separately, independent of the relative scales.\r\n */\r\n this.scale1 = d3.scale.linear()\r\n .domain([min.value, mid.value])\r\n .range([min.color, mid.color])\r\n .clamp(true); // process a value outside of the domain- set to extremum values\r\n this.scale2 = d3.scale.linear()\r\n .domain([mid.value, max.value])\r\n .range([mid.color, max.color])\r\n .clamp(true); // process a value outside of the domain- set to extremum values\r\n }\r\n\r\n public color(value: number): string {\r\n let min = this.options.min,\r\n mid = this.options.mid,\r\n max = this.options.max;\r\n\r\n if (max.value === mid.value || mid.value === min.value || (max.value === mid.value && max.value === min.value)) {\r\n if (value >= max.value)\r\n return max.color;\r\n else if (value >= mid.value)\r\n return mid.color;\r\n return min.color;\r\n }\r\n else if (value <= mid.value) {\r\n return <any>this.scale1(value);\r\n }\r\n return <any>this.scale2(value);\r\n }\r\n }\r\n}\n\n\n/** WEBPACK FOOTER **\n ** ./src/Clients/Visuals/services/colorAllocatorFactory.ts\n **/","/*\r\n * Power BI Visualizations\r\n *\r\n * Copyright (c) Microsoft Corporation\r\n * All rights reserved. \r\n * MIT License\r\n *\r\n * Permission is hereby granted, free of charge, to any person obtaining a copy\r\n * of this software and associated documentation files (the \"\"Software\"\"), to deal\r\n * in the Software without restriction, including without limitation the rights\r\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\r\n * copies of the Software, and to permit persons to whom the Software is\r\n * furnished to do so, subject to the following conditions:\r\n * \r\n * The above copyright notice and this permission notice shall be included in \r\n * all copies or substantial portions of the Software.\r\n * \r\n * THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR \r\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, \r\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE \r\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER \r\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\r\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\r\n * THE SOFTWARE.\r\n */\r\n\r\n/// <reference path=\"../_references.ts\"/>\r\n\r\nmodule powerbi.visuals {\r\n\r\n const BeautifiedFormat: { [x: string]: string } = {\r\n '0.00 %;-0.00 %;0.00 %': 'Percentage',\r\n '0.0 %;-0.0 %;0.0 %': 'Percentage1',\r\n };\r\n\r\n const defaultLocalizedStrings = {\r\n 'NullValue': '(Blank)',\r\n 'BooleanTrue': 'True',\r\n 'BooleanFalse': 'False',\r\n 'NaNValue': 'NaN',\r\n 'InfinityValue': '+Infinity',\r\n 'NegativeInfinityValue': '-Infinity',\r\n 'Restatement_Comma': '{0}, {1}',\r\n 'Restatement_CompoundAnd': '{0} and {1}',\r\n 'DisplayUnitSystem_EAuto_Title': 'Auto',\r\n 'DisplayUnitSystem_E0_Title': 'None',\r\n 'DisplayUnitSystem_E3_LabelFormat': '{0}K',\r\n 'DisplayUnitSystem_E3_Title': 'Thousands',\r\n 'DisplayUnitSystem_E6_LabelFormat': '{0}M',\r\n 'DisplayUnitSystem_E6_Title': 'Millions',\r\n 'DisplayUnitSystem_E9_LabelFormat': '{0}bn',\r\n 'DisplayUnitSystem_E9_Title': 'Billions',\r\n 'DisplayUnitSystem_E12_LabelFormat': '{0}T',\r\n 'DisplayUnitSystem_E12_Title': 'Trillions',\r\n 'Percentage': '#,0.##%',\r\n 'Percentage1': '#,0.#%',\r\n 'RichTextbox_Link_DefaultText': 'Link',\r\n 'TableTotalLabel': 'Total',\r\n 'ListJoin_Separator': ', ',\r\n 'Tooltip_HighlightedValueDisplayName': 'Highlighted',\r\n 'Funnel_PercentOfFirst': 'Percent of first',\t\r\n 'Funnel_PercentOfPrevious': 'Percent of previous',\r\n 'Funnel_PercentOfFirst_Highlight': 'Percent of first (highlighted)',\r\n 'Funnel_PercentOfPrevious_Highlight': 'Percent of previous (highlighted)',\r\n // Geotagging strings\r\n 'GeotaggingString_Continent': 'continent',\r\n 'GeotaggingString_Continents': 'continents',\r\n 'GeotaggingString_Country': 'country',\r\n 'GeotaggingString_Countries': 'countries',\r\n 'GeotaggingString_State': 'state',\r\n 'GeotaggingString_States': 'states',\r\n 'GeotaggingString_City': 'city',\r\n 'GeotaggingString_Cities': 'cities',\r\n 'GeotaggingString_Town': 'town',\r\n 'GeotaggingString_Towns': 'towns',\r\n 'GeotaggingString_Province': 'province',\r\n 'GeotaggingString_Provinces': 'provinces',\r\n 'GeotaggingString_County': 'county',\r\n 'GeotaggingString_Counties': 'counties',\r\n 'GeotaggingString_Village': 'village',\r\n 'GeotaggingString_Villages': 'villages',\r\n 'GeotaggingString_Post': 'post',\r\n 'GeotaggingString_Zip': 'zip',\r\n 'GeotaggingString_Code': 'code',\r\n 'GeotaggingString_Place': 'place',\r\n 'GeotaggingString_Places': 'places',\r\n 'GeotaggingString_Address': 'address',\r\n 'GeotaggingString_Addresses': 'addresses',\r\n 'GeotaggingString_Street': 'street',\r\n 'GeotaggingString_Streets': 'streets',\r\n 'GeotaggingString_Longitude': 'longitude',\r\n 'GeotaggingString_Longitude_Short': 'lon',\r\n 'GeotaggingString_Longitude_Short2': 'long',\r\n 'GeotaggingString_Latitude': 'latitude',\r\n 'GeotaggingString_Latitude_Short': 'lat',\r\n 'GeotaggingString_PostalCode': 'postal code',\r\n 'GeotaggingString_PostalCodes': 'postal codes',\r\n 'GeotaggingString_ZipCode': 'zip code',\r\n 'GeotaggingString_ZipCodes': 'zip codes',\r\n 'GeotaggingString_Territory': 'territory',\r\n 'GeotaggingString_Territories': 'territories',\r\n 'Waterfall_IncreaseLabel': 'Increase',\r\n 'Waterfall_DecreaseLabel': 'Decrease',\r\n 'Waterfall_TotalLabel': 'Total',\r\n 'Slicer_SelectAll': 'Select All',\r\n };\r\n\r\n export class DefaultVisualHostServices implements IVisualHostServices {\r\n // TODO: Add locale-awareness to this host service. Currently default/english functionality only.\r\n public static initialize(): void {\r\n visuals.valueFormatter.setLocaleOptions(DefaultVisualHostServices.createLocaleOptions());\r\n visuals.TooltipManager.setLocalizedStrings(DefaultVisualHostServices.createTooltipLocaleOptions());\r\n }\r\n \r\n /**\r\n * Create locale options.\r\n * \r\n * Note: Public for testability.\r\n */\r\n public static createLocaleOptions(): visuals.ValueFormatterLocalizationOptions {\r\n return {\r\n null: defaultLocalizedStrings['NullValue'],\r\n true: defaultLocalizedStrings['BooleanTrue'],\r\n false: defaultLocalizedStrings['BooleanFalse'],\r\n NaN: defaultLocalizedStrings['NaNValue'],\r\n infinity: defaultLocalizedStrings['InfinityValue'],\r\n negativeInfinity: defaultLocalizedStrings['NegativeInfinityValue'],\r\n beautify: format => DefaultVisualHostServices.beautify(format),\r\n describe: exponent => DefaultVisualHostServices.describeUnit(exponent),\r\n restatementComma: defaultLocalizedStrings['Restatement_Comma'],\r\n restatementCompoundAnd: defaultLocalizedStrings['Restatement_CompoundAnd'],\r\n restatementCompoundOr: defaultLocalizedStrings['Restatement_CompoundOr']\r\n };\r\n }\r\n\r\n public static createTooltipLocaleOptions(): powerbi.visuals.TooltipLocalizationOptions {\r\n return {\r\n highlightedValueDisplayName: defaultLocalizedStrings['Tooltip_HighlightedValueDisplayName']\r\n };\r\n }\r\n\r\n public getLocalizedString(stringId: string): string {\r\n return defaultLocalizedStrings[stringId];\r\n }\r\n\r\n // NO-OP IHostServices methods\r\n public onDragStart(): void { }\r\n public canSelect(): boolean { return false; }\r\n public onSelect(): void { }\r\n public onContextMenu(): void { }\r\n public loadMoreData(): void { }\r\n public persistProperties(changes: VisualObjectInstance[] | VisualObjectInstancesToPersist): void { }\r\n public onCustomSort(args: CustomSortEventArgs) { }\r\n public getViewMode(): powerbi.ViewMode { return ViewMode.View; }\r\n public setWarnings(warnings: IVisualWarning[]): void { }\r\n public setToolbar($toolbar: JQuery): void { }\r\n public shouldRetainSelection(): boolean { return false; }\r\n public geocoder(): IGeocoder { return services.createGeocoder(); }\r\n public geolocation(): IGeolocation { return services.createGeolocation(); }\r\n public promiseFactory(): IPromiseFactory { return createJQueryPromiseFactory(); }\r\n public visualCapabilitiesChanged(): void { }\r\n public analyzeFilter(options: FilterAnalyzerOptions): AnalyzedFilter {\r\n return {\r\n isNotFilter: false,\r\n selectedIdentities: [],\r\n filter: undefined,\r\n defaultValue: undefined,\r\n };\r\n }\r\n public getIdentityDisplayNames(dentities: DataViewScopeIdentity[]): DisplayNameIdentityPair[] { return; } \r\n public setIdentityDisplayNames(displayNamesIdentityPairs: DisplayNameIdentityPair[]): void { }\r\n\r\n private static beautify(format: string): string {\r\n let key = BeautifiedFormat[format];\r\n if (key)\r\n return defaultLocalizedStrings[key] || format;\r\n return format;\r\n }\r\n\r\n private static describeUnit(exponent: number): DisplayUnitSystemNames {\r\n let exponentLookup = (exponent === -1) ? 'Auto' : exponent.toString();\r\n\r\n let title: string = defaultLocalizedStrings[\"DisplayUnitSystem_E\" + exponentLookup + \"_Title\"];\r\n let format: string = (exponent <= 0) ? '{0}' : defaultLocalizedStrings[\"DisplayUnitSystem_E\" + exponentLookup + \"_LabelFormat\"];\r\n\r\n if (title || format)\r\n return { title: title, format: format };\r\n }\r\n }\r\n\r\n export const defaultVisualHostServices: IVisualHostServices = new DefaultVisualHostServices();\r\n} \n\n\n/** WEBPACK FOOTER **\n ** ./src/Clients/Visuals/services/defaultVisualHostService.ts\n **/","/*\r\n * Power BI Visualizations\r\n *\r\n * Copyright (c) Microsoft Corporation\r\n * All rights reserved. \r\n * MIT License\r\n *\r\n * Permission is hereby granted, free of charge, to any person obtaining a copy\r\n * of this software and associated documentation files (the \"\"Software\"\"), to deal\r\n * in the Software without restriction, including without limitation the rights\r\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\r\n * copies of the Software, and to permit persons to whom the Software is\r\n * furnished to do so, subject to the following conditions:\r\n * \r\n * The above copyright notice and this permission notice shall be included in \r\n * all copies or substantial portions of the Software.\r\n * \r\n * THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR \r\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, \r\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE \r\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER \r\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\r\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\r\n * THE SOFTWARE.\r\n */\r\n\r\n/// <reference path=\"../_references.ts\"/>\r\n\r\nmodule powerbi.visuals {\r\n import ArrayExtensions = jsCommon.ArrayExtensions;\r\n import SemanticFilter = powerbi.data.SemanticFilter;\r\n\r\n export interface SelectableDataPoint {\r\n selected: boolean;\r\n identity: SelectionId;\r\n }\r\n\r\n /**\r\n * Factory method to create an IInteractivityService instance.\r\n */\r\n export function createInteractivityService(hostServices: IVisualHostServices): IInteractivityService {\r\n return new InteractivityService(hostServices);\r\n }\r\n\r\n /**\r\n * Creates a clear an svg rect to catch clear clicks.\r\n */\r\n export function appendClearCatcher(selection: D3.Selection): D3.Selection {\r\n return selection\r\n .append(\"rect\")\r\n .classed(\"clearCatcher\", true)\r\n .attr({ width: \"100%\", height: \"100%\" });\r\n }\r\n\r\n export function isCategoryColumnSelected(propertyId: DataViewObjectPropertyIdentifier, categories: DataViewCategoricalColumn, idx: number): boolean {\r\n return categories.objects != null\r\n && categories.objects[idx]\r\n && DataViewObjects.getValue<boolean>(categories.objects[idx], propertyId);\r\n }\r\n\r\n export function dataHasSelection(data: SelectableDataPoint[]): boolean {\r\n for (let i = 0, ilen = data.length; i < ilen; i++) {\r\n if (data[i].selected)\r\n return true;\r\n }\r\n return false;\r\n }\r\n\r\n export interface IInteractiveBehavior {\r\n bindEvents(behaviorOptions: any, selectionHandler: ISelectionHandler): void;\r\n renderSelection(hasSelection: boolean): void;\r\n }\r\n\r\n /**\r\n * An optional options bag for binding to the interactivityService\r\n */\r\n export interface InteractivityServiceOptions {\r\n isLegend?: boolean;\r\n isLabels?: boolean;\r\n overrideSelectionFromData?: boolean;\r\n hasSelectionOverride?: boolean;\r\n slicerValueHandler?: SlicerValueHandler;\r\n }\r\n\r\n /**\r\n * Responsible for managing interactivity between the hosting visual and its peers\r\n */\r\n export interface IInteractivityService {\r\n /** Binds the visual to the interactivityService */\r\n bind(dataPoints: SelectableDataPoint[], behavior: IInteractiveBehavior, behaviorOptions: any, iteractivityServiceOptions?: InteractivityServiceOptions);\r\n\r\n /** Clears the selection */\r\n clearSelection(): void;\r\n\r\n /** Sets the selected state on the given data points. */\r\n applySelectionStateToData(dataPoints: SelectableDataPoint[]): boolean;\r\n\r\n /** Checks whether there is at least one item selected */\r\n hasSelection(): boolean;\r\n\r\n /** Checks whether there is at least one item selected within the legend */\r\n legendHasSelection(): boolean;\r\n\r\n /** Checks whether the selection mode is inverted or normal */\r\n isSelectionModeInverted(): boolean;\r\n\r\n /** Sets whether the selection mode is inverted or normal */\r\n setSelectionModeInverted(inverted: boolean): void;\r\n\r\n setDefaultValueMode(useDefaultValue: boolean): void;\r\n\r\n isDefaultValueEnabled(): boolean;\r\n }\r\n\r\n export interface ISelectionHandler {\r\n /** Handles a selection event by selecting the given data point */\r\n handleSelection(dataPoint: SelectableDataPoint, multiSelect: boolean): void;\r\n\r\n /** Handles a request for a context menu. */\r\n handleContextMenu(dataPoint: SelectableDataPoint, position: IPoint): void;\r\n\r\n /** Handles a selection clear, clearing all selection state */\r\n handleClearSelection(): void;\r\n\r\n /** Toggles the selection mode between normal and inverted; returns true if the new mode is inverted */\r\n toggleSelectionModeInversion(): boolean;\r\n\r\n /** Sends the selection state to the host */\r\n persistSelectionFilter(filterPropertyIdentifier: DataViewObjectPropertyIdentifier): void;\r\n\r\n /** Sends selfFilter to the host */\r\n persistSelfFilter(filterPropertyIdentifier: DataViewObjectPropertyIdentifier, selfFilter: SemanticFilter): void;\r\n }\r\n\r\n export class InteractivityService implements IInteractivityService, ISelectionHandler {\r\n // References\r\n private hostService: IVisualHostServices;\r\n private renderSelectionInVisual = _.noop;\r\n private renderSelectionInLegend = _.noop;\r\n private renderSelectionInLabels = _.noop;\r\n\r\n // Selection state\r\n private selectedIds: SelectionId[] = [];\r\n private isInvertedSelectionMode: boolean = false;\r\n private hasSelectionOverride: boolean;\r\n private behavior: any;\r\n\r\n // TODO: It is very strange to expose a special handler for slicer in the interactivityService.\r\n // We should consider introduce another interface for visuals to implement their own filtering handlers.\r\n private slicerValueHandler: SlicerValueHandler;\r\n\r\n // undefined means no default value is set\r\n // True: apply default value. False: apply AnyValue.\r\n private useDefaultValue: boolean;\r\n\r\n public selectableDataPoints: SelectableDataPoint[];\r\n public selectableLegendDataPoints: SelectableDataPoint[];\r\n public selectableLabelsDataPoints: SelectableDataPoint[];\r\n\r\n constructor(hostServices: IVisualHostServices) {\r\n debug.assertValue(hostServices, 'hostServices');\r\n\r\n this.hostService = hostServices;\r\n }\r\n\r\n // IInteractivityService Implementation\r\n\r\n /** Binds the vsiual to the interactivityService */\r\n public bind(dataPoints: SelectableDataPoint[], behavior: IInteractiveBehavior, behaviorOptions: any, options?: InteractivityServiceOptions): void {\r\n // Bind the data\r\n if (options && options.overrideSelectionFromData) {\r\n // Override selection state from data points if needed\r\n this.takeSelectionStateFromDataPoints(dataPoints);\r\n }\r\n\r\n if (options){\r\n if (options.isLegend) {\r\n // Bind to legend data instead of normal data if isLegend\r\n this.selectableLegendDataPoints = dataPoints;\r\n this.renderSelectionInLegend = () => behavior.renderSelection(this.legendHasSelection());\r\n }\r\n else if (options.isLabels) {\r\n //Bind to label data instead of normal data if isLabels\r\n this.selectableLabelsDataPoints = dataPoints;\r\n this.renderSelectionInLabels = () => behavior.renderSelection(this.labelsHasSelection());\r\n }\r\n else {\r\n this.selectableDataPoints = dataPoints;\r\n this.renderSelectionInVisual = () => behavior.renderSelection(this.hasSelection());\r\n }\r\n\r\n if (options.hasSelectionOverride != null)\r\n this.hasSelectionOverride = options.hasSelectionOverride;\r\n\r\n if (options.slicerValueHandler)\r\n this.slicerValueHandler = options.slicerValueHandler;\r\n\r\n } \r\n else {\r\n this.selectableDataPoints = dataPoints;\r\n this.renderSelectionInVisual = () => behavior.renderSelection(this.hasSelection());\r\n }\r\n\r\n // Bind to the behavior\r\n this.behavior = behavior;\r\n behavior.bindEvents(behaviorOptions, this);\r\n // Sync data points with current selection state\r\n this.syncSelectionState();\r\n }\r\n\r\n /**\r\n * Sets the selected state of all selectable data points to false and invokes the behavior's select command.\r\n */\r\n public clearSelection(): void {\r\n // if default value is already applied, don't clear the default selection\r\n if (this.slicerValueHandler && this.slicerValueHandler.getDefaultValue() && this.useDefaultValue) {\r\n this.isInvertedSelectionMode = false;\r\n return;\r\n }\r\n\r\n this.hasSelectionOverride = undefined;\r\n ArrayExtensions.clear(this.selectedIds);\r\n this.isInvertedSelectionMode = false;\r\n this.applyToAllSelectableDataPoints((dataPoint: SelectableDataPoint) => dataPoint.selected = false);\r\n this.renderAll();\r\n }\r\n\r\n public applySelectionStateToData(dataPoints: SelectableDataPoint[]): boolean {\r\n for (let dataPoint of dataPoints) {\r\n dataPoint.selected = InteractivityService.checkDatapointAgainstSelectedIds(dataPoint, this.selectedIds);\r\n }\r\n\r\n return this.hasSelection();\r\n }\r\n\r\n /**\r\n * Checks whether there is at least one item selected.\r\n */\r\n public hasSelection(): boolean {\r\n return this.selectedIds.length > 0;\r\n }\r\n\r\n public legendHasSelection(): boolean {\r\n return this.selectableLegendDataPoints ? dataHasSelection(this.selectableLegendDataPoints) : false;\r\n }\r\n\r\n public labelsHasSelection(): boolean {\r\n return this.selectableLabelsDataPoints ? dataHasSelection(this.selectableLabelsDataPoints) : false;\r\n }\r\n\r\n public isSelectionModeInverted(): boolean {\r\n return this.isInvertedSelectionMode;\r\n }\r\n\r\n public setSelectionModeInverted(inverted: boolean): void {\r\n this.isInvertedSelectionMode = inverted;\r\n }\r\n\r\n // ISelectionHandler Implementation\r\n\r\n public handleSelection(dataPoint: SelectableDataPoint, multiSelect: boolean): void {\r\n // defect 7067397: should not happen so assert but also don't continue as it's\r\n // causing a lot of error telemetry in desktop.\r\n debug.assertValue(dataPoint, 'dataPoint');\r\n if (!dataPoint)\r\n return;\r\n\r\n this.useDefaultValue = false;\r\n this.select(dataPoint, multiSelect);\r\n this.sendSelectionToHost();\r\n this.renderAll();\r\n }\r\n\r\n public handleContextMenu(dataPoint: SelectableDataPoint, point: IPoint): void {\r\n this.sendContextMenuToHost(dataPoint, point);\r\n }\r\n\r\n public handleClearSelection(): void {\r\n this.useDefaultValue = true;\r\n this.clearSelection();\r\n this.sendSelectionToHost();\r\n }\r\n\r\n public toggleSelectionModeInversion(): boolean {\r\n this.useDefaultValue = false;\r\n this.isInvertedSelectionMode = !this.isInvertedSelectionMode;\r\n ArrayExtensions.clear(this.selectedIds);\r\n this.applyToAllSelectableDataPoints((dataPoint: SelectableDataPoint) => dataPoint.selected = false);\r\n this.sendSelectionToHost();\r\n this.isInvertedSelectionMode ? this.syncSelectionStateInverted() : this.syncSelectionState();\r\n this.renderAll();\r\n return this.isInvertedSelectionMode;\r\n }\r\n\r\n public persistSelectionFilter(filterPropertyIdentifier: DataViewObjectPropertyIdentifier): void {\r\n this.hostService.persistProperties(InteractivityService.createChangeForFilterProperty(filterPropertyIdentifier, this.getFilterFromSelectors()));\r\n }\r\n\r\n public persistSelfFilter(filterPropertyIdentifier: DataViewObjectPropertyIdentifier, selfFilter: SemanticFilter): void {\r\n this.hostService.persistProperties(InteractivityService.createChangeForFilterProperty(filterPropertyIdentifier, selfFilter));\r\n }\r\n\r\n public setDefaultValueMode(useDefaultValue: boolean): void {\r\n this.useDefaultValue = useDefaultValue;\r\n }\r\n\r\n public isDefaultValueEnabled(): boolean {\r\n return this.useDefaultValue;\r\n }\r\n\r\n // Private utility methods\r\n\r\n private renderAll(): void {\r\n this.renderSelectionInVisual();\r\n this.renderSelectionInLegend();\r\n this.renderSelectionInLabels();\r\n }\r\n\r\n /** Marks a data point as selected and syncs selection with the host. */\r\n private select(d: SelectableDataPoint, multiSelect: boolean): void {\r\n // If we're in inverted mode, use the invertedSelect instead\r\n if (this.isInvertedSelectionMode) {\r\n return this.selectInverted(d, multiSelect);\r\n }\r\n\r\n // For highlight data points we actually want to select the non-highlight data point\r\n if (d.identity && d.identity.highlight) {\r\n d = _.find(this.selectableDataPoints, (dp: SelectableDataPoint) => !dp.identity.highlight && d.identity.includes(dp.identity, /* ignoreHighlight */ true));\r\n debug.assertValue(d, 'Expected to find a non-highlight data point');\r\n }\r\n\r\n let id = d.identity;\r\n\r\n if (!id)\r\n return;\r\n\r\n let selected = !d.selected || (!multiSelect && this.selectedIds.length > 1);\r\n\r\n // If we have a multiselect flag, we attempt a multiselect\r\n if (multiSelect) {\r\n if (selected) {\r\n d.selected = true;\r\n this.selectedIds.push(id);\r\n if (id.hasIdentity()) {\r\n this.removeSelectionIdsWithOnlyMeasures();\r\n }\r\n else {\r\n this.removeSelectionIdsExceptOnlyMeasures();\r\n }\r\n }\r\n else {\r\n d.selected = false;\r\n this.removeId(id);\r\n }\r\n }\r\n // We do a single select if we didn't do a multiselect or if we find out that the multiselect is invalid.\r\n if (!multiSelect || !this.hostService.canSelect({ data: this.selectedIds.map((value: SelectionId) => value.getSelector()) })) {\r\n this.clearSelection();\r\n if (selected) {\r\n d.selected = true;\r\n this.selectedIds.push(id);\r\n }\r\n }\r\n\r\n this.syncSelectionState();\r\n }\r\n\r\n private selectInverted(d: SelectableDataPoint, multiSelect: boolean): void {\r\n let wasSelected = d.selected;\r\n let id = d.identity;\r\n debug.assert(!!multiSelect, \"inverted selections are only supported in multiselect mode\");\r\n\r\n // the current datapoint state has to be inverted\r\n d.selected = !wasSelected;\r\n\r\n if (wasSelected) {\r\n this.removeId(id);\r\n }\r\n else {\r\n this.selectedIds.push(id);\r\n if (id.hasIdentity()) {\r\n this.removeSelectionIdsWithOnlyMeasures();\r\n }\r\n else {\r\n this.removeSelectionIdsExceptOnlyMeasures();\r\n }\r\n }\r\n\r\n this.syncSelectionStateInverted();\r\n }\r\n\r\n private removeId(toRemove: SelectionId): void {\r\n let selectedIds = this.selectedIds;\r\n for (let i = selectedIds.length - 1; i > -1; i--) {\r\n let currentId = selectedIds[i];\r\n\r\n if (toRemove.includes(currentId))\r\n selectedIds.splice(i, 1);\r\n }\r\n }\r\n\r\n private getFilterFromSelectors(): SemanticFilter {\r\n let selectors: data.Selector[] = [];\r\n\r\n if (this.selectedIds.length > 0) {\r\n selectors = _.chain(this.selectedIds)\r\n .filter((value: SelectionId) => value.hasIdentity())\r\n .map((value: SelectionId) => value.getSelector())\r\n .value();\r\n }\r\n\r\n let filter = powerbi.data.Selector.filterFromSelector(selectors, this.isInvertedSelectionMode);\r\n if (this.slicerValueHandler && this.slicerValueHandler.getDefaultValue()) {\r\n // we explicitly check for true/false because undefine means no default value\r\n if (this.useDefaultValue === true)\r\n filter = SemanticFilter.getDefaultValueFilter(this.slicerValueHandler.getIdentityFields());\r\n else if (_.isEmpty(selectors))\r\n filter = SemanticFilter.getAnyValueFilter(this.slicerValueHandler.getIdentityFields());\r\n }\r\n\r\n return filter;\r\n }\r\n\r\n private static createChangeForFilterProperty(filterPropertyIdentifier: DataViewObjectPropertyIdentifier, filter: SemanticFilter): VisualObjectInstancesToPersist {\r\n let properties: { [propertyName: string]: DataViewPropertyValue } = {};\r\n let instance = {\r\n objectName: filterPropertyIdentifier.objectName,\r\n selector: undefined,\r\n properties: properties\r\n };\r\n\r\n if (filter == null) {\r\n properties[filterPropertyIdentifier.propertyName] = {};\r\n return <VisualObjectInstancesToPersist> {\r\n remove: [instance]\r\n };\r\n }\r\n else {\r\n properties[filterPropertyIdentifier.propertyName] = filter;\r\n return <VisualObjectInstancesToPersist> {\r\n merge: [instance]\r\n };\r\n }\r\n }\r\n\r\n private sendContextMenuToHost(dataPoint: SelectableDataPoint, position: IPoint): void {\r\n let host = this.hostService;\r\n if (!host.onContextMenu)\r\n return;\r\n\r\n let selectors = this.getSelectorsByColumn([dataPoint.identity]);\r\n if (_.isEmpty(selectors))\r\n return;\r\n\r\n let args: ContextMenuArgs = {\r\n data: selectors,\r\n position: position\r\n };\r\n\r\n host.onContextMenu(args);\r\n }\r\n\r\n private sendSelectionToHost() {\r\n let host = this.hostService;\r\n if (host.onSelect) {\r\n let selectArgs: SelectEventArgs = {\r\n data: this.selectedIds.map((value: SelectionId) => value.getSelector())\r\n };\r\n\r\n let data2 = this.getSelectorsByColumn(this.selectedIds);\r\n\r\n if (!_.isEmpty(data2))\r\n selectArgs.data2 = data2;\r\n\r\n host.onSelect(selectArgs);\r\n }\r\n }\r\n\r\n private getSelectorsByColumn(selectionIds: SelectionId[]): SelectorsByColumn[] {\r\n return _(selectionIds)\r\n .filter(value => value.hasIdentity)\r\n .map(value => value.getSelectorsByColumn())\r\n .compact()\r\n .value();\r\n }\r\n\r\n private takeSelectionStateFromDataPoints(dataPoints: SelectableDataPoint[]): void {\r\n debug.assertValue(dataPoints, \"dataPoints\");\r\n\r\n let selectedIds: SelectionId[] = this.selectedIds;\r\n\r\n // Replace the existing selectedIds rather than merging.\r\n ArrayExtensions.clear(selectedIds);\r\n\r\n for (let dataPoint of dataPoints) {\r\n if (dataPoint.selected)\r\n selectedIds.push(dataPoint.identity);\r\n }\r\n }\r\n\r\n /**\r\n * Syncs the selection state for all data points that have the same category. Returns\r\n * true if the selection state was out of sync and corrections were made; false if\r\n * the data is already in sync with the service.\r\n *\r\n * If the data is not compatible with the current service's current selection state,\r\n * the state is cleared and the cleared selection is sent to the host.\r\n * \r\n * Ignores series for now, since we don't support series selection at the moment.\r\n */\r\n private syncSelectionState(): void {\r\n if (this.isInvertedSelectionMode) {\r\n return this.syncSelectionStateInverted();\r\n }\r\n\r\n let selectedIds = this.selectedIds;\r\n let selectableDataPoints = this.selectableDataPoints;\r\n let selectableLegendDataPoints = this.selectableLegendDataPoints;\r\n let selectableLabelsDataPoints = this.selectableLabelsDataPoints;\r\n let foundMatchingId = false; // Checked only against the visual's data points; it's possible to have stuff selected in the visual that's not in the legend, but not vice-verse\r\n\r\n if (!selectableDataPoints && !selectableLegendDataPoints)\r\n return;\r\n\r\n if (selectableDataPoints) {\r\n if (InteractivityService.updateSelectableDataPointsBySelectedIds(selectableDataPoints, selectedIds))\r\n foundMatchingId = true;\r\n }\r\n\r\n if (selectableLegendDataPoints) {\r\n if (InteractivityService.updateSelectableDataPointsBySelectedIds(selectableLegendDataPoints, selectedIds))\r\n foundMatchingId = true;\r\n }\r\n\r\n if (selectableLabelsDataPoints) {\r\n let labelsDataPoint: SelectableDataPoint;\r\n for (let i = 0, ilen = selectableLabelsDataPoints.length; i < ilen; i++) {\r\n labelsDataPoint = selectableLabelsDataPoints[i];\r\n if (selectedIds.some((value: SelectionId) => value.includes(labelsDataPoint.identity)))\r\n labelsDataPoint.selected = true;\r\n else\r\n labelsDataPoint.selected = false;\r\n }\r\n }\r\n\r\n if (!foundMatchingId && selectedIds.length > 0) {\r\n this.clearSelection();\r\n this.sendSelectionToHost();\r\n }\r\n }\r\n\r\n private syncSelectionStateInverted(): void {\r\n let selectedIds = this.selectedIds;\r\n let selectableDataPoints = this.selectableDataPoints;\r\n if (!selectableDataPoints)\r\n return;\r\n\r\n if (selectedIds.length === 0) {\r\n for (let dataPoint of selectableDataPoints) {\r\n dataPoint.selected = false;\r\n }\r\n }\r\n else {\r\n for (var dataPoint of selectableDataPoints) {\r\n if (selectedIds.some((value: SelectionId) => value.includes(dataPoint.identity)))\r\n dataPoint.selected = true;\r\n else if (dataPoint.selected)\r\n dataPoint.selected = false;\r\n }\r\n }\r\n }\r\n\r\n private applyToAllSelectableDataPoints(action: (selectableDataPoint: SelectableDataPoint) => void) {\r\n let selectableDataPoints = this.selectableDataPoints;\r\n let selectableLegendDataPoints = this.selectableLegendDataPoints;\r\n let selectableLabelsDataPoints = this.selectableLabelsDataPoints;\r\n if (selectableDataPoints) {\r\n for (let dataPoint of selectableDataPoints) {\r\n action(dataPoint);\r\n }\r\n }\r\n\r\n if (selectableLegendDataPoints) {\r\n for (let dataPoint of selectableLegendDataPoints) {\r\n action(dataPoint);\r\n }\r\n }\r\n\r\n if (selectableLabelsDataPoints) {\r\n for (let dataPoint of selectableLabelsDataPoints) {\r\n action(dataPoint);\r\n }\r\n }\r\n }\r\n\r\n private static updateSelectableDataPointsBySelectedIds(selectableDataPoints: SelectableDataPoint[], selectedIds: SelectionId[]): boolean {\r\n let foundMatchingId = false;\r\n\r\n for (let datapoint of selectableDataPoints) {\r\n datapoint.selected = InteractivityService.checkDatapointAgainstSelectedIds(datapoint, selectedIds);\r\n\r\n if (datapoint.selected)\r\n foundMatchingId = true;\r\n }\r\n\r\n return foundMatchingId;\r\n }\r\n\r\n private static checkDatapointAgainstSelectedIds(datapoint: SelectableDataPoint, selectedIds: SelectionId[]): boolean {\r\n return selectedIds.some((value: SelectionId) => value.includes(datapoint.identity));\r\n }\r\n\r\n private removeSelectionIdsWithOnlyMeasures() {\r\n this.selectedIds = _.filter(this.selectedIds, (identity) => identity.hasIdentity());\r\n }\r\n\r\n private removeSelectionIdsExceptOnlyMeasures() {\r\n this.selectedIds = _.filter(this.selectedIds, (identity) => !identity.hasIdentity());\r\n }\r\n };\r\n}\n\n\n/** WEBPACK FOOTER **\n ** ./src/Clients/Visuals/services/interactivityService.ts\n **/","/*\r\n * Power BI Visualizations\r\n *\r\n * Copyright (c) Microsoft Corporation\r\n * All rights reserved. \r\n * MIT License\r\n *\r\n * Permission is hereby granted, free of charge, to any person obtaining a copy\r\n * of this software and associated documentation files (the \"\"Software\"\"), to deal\r\n * in the Software without restriction, including without limitation the rights\r\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\r\n * copies of the Software, and to permit persons to whom the Software is\r\n * furnished to do so, subject to the following conditions:\r\n * \r\n * The above copyright notice and this permission notice shall be included in \r\n * all copies or substantial portions of the Software.\r\n * \r\n * THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR \r\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, \r\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE \r\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER \r\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\r\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\r\n * THE SOFTWARE.\r\n */\r\n\r\n/// <reference path=\"../_references.ts\"/>\r\n\r\nmodule powerbi.visuals.services {\r\n import CategoryTypes = MapUtil.CategoryTypes;\r\n import Settings = MapUtil.Settings;\r\n\r\n export function createGeocoder(): IGeocoder {\r\n return {\r\n geocode: geocode,\r\n geocodeBoundary: geocodeBoundary,\r\n geocodePoint: geocodePoint,\r\n tryGeocodeImmediate: tryGeocodeImmediate,\r\n tryGeocodeBoundaryImmediate: tryGeocodeBoundaryImmediate,\r\n };\r\n }\r\n\r\n export interface BingAjaxService {\r\n (url: string, settings: JQueryAjaxSettings): any;\r\n }\r\n export const safeCharacters: string = \"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789_-\";\r\n \r\n /** Note: Used for test mockup */\r\n export let BingAjaxCall: BingAjaxService = $.ajax;\r\n\r\n export const CategoryTypeArray = [\r\n \"Address\",\r\n \"City\",\r\n \"Continent\",\r\n \"Country\",\r\n \"County\",\r\n \"Longitude\",\r\n \"Latitude\",\r\n \"Place\",\r\n \"PostalCode\",\r\n \"StateOrProvince\"\r\n ];\r\n\r\n export function isCategoryType(value: string): boolean {\r\n return CategoryTypeArray.indexOf(value) > -1;\r\n }\r\n\r\n export const BingEntities = {\r\n Continent: \"Continent\",\r\n Sovereign: \"Sovereign\",\r\n CountryRegion: \"CountryRegion\",\r\n AdminDivision1: \"AdminDivision1\",\r\n AdminDivision2: \"AdminDivision2\",\r\n PopulatedPlace: \"PopulatedPlace\",\r\n Postcode: \"Postcode\",\r\n Postcode1: \"Postcode1\",\r\n Neighborhood: \"Neighborhood\",\r\n Address: \"Address\",\r\n };\r\n\r\n export interface ILocation {\r\n latitude: number;\r\n longitude: number;\r\n }\r\n\r\n export interface ILocationRect {\r\n northWest: ILocation;\r\n southEast: ILocation;\r\n }\r\n\r\n export interface GeocodeCallback {\r\n (error: Error, coordinate: IGeocodeCoordinate): void;\r\n }\r\n\r\n export interface IGeocodeQuery {\r\n query: string;\r\n category: string;\r\n levelOfDetail?: number;\r\n longitude?: number;\r\n latitude?: number;\r\n }\r\n\r\n interface IGeocodeQueueItem {\r\n query: GeocodeQuery;\r\n deferred: JQueryDeferred<any>;\r\n isResolved: boolean;\r\n }\r\n\r\n // Static variables for caching, maps, etc.\r\n let geocodeQueue: IGeocodeQueueItem[];\r\n let activeRequests;\r\n let categoryToBingEntity: { [key: string]: string; };\r\n let categoryToBingEntityGeodata: { [key: string]: string; };\r\n let geocodingCache: IGeocodingCache;\r\n\r\n export class GeocodeQuery implements IGeocodeQuery {\r\n public query: string;\r\n public category: string;\r\n public key: string;\r\n\r\n constructor(query: string, category: string) {\r\n this.query = query != null ? query : \"\";\r\n this.category = category != null ? category : \"\";\r\n this.key = (this.query + \"/\" + this.category).toLowerCase();\r\n if (!geocodingCache) {\r\n geocodingCache = createGeocodingCache(Settings.MaxCacheSize, Settings.MaxCacheSizeOverflow);\r\n }\r\n }\r\n\r\n public getBingEntity(): string {\r\n let category = this.category.toLowerCase();\r\n if (!categoryToBingEntity) {\r\n categoryToBingEntity = {};\r\n categoryToBingEntity[CategoryTypes.Continent.toLowerCase()] = BingEntities.Continent;\r\n categoryToBingEntity[CategoryTypes.CountryRegion.toLowerCase()] = BingEntities.Sovereign;\r\n categoryToBingEntity[CategoryTypes.StateOrProvince.toLowerCase()] = BingEntities.AdminDivision1;\r\n categoryToBingEntity[CategoryTypes.County.toLowerCase()] = BingEntities.AdminDivision2;\r\n categoryToBingEntity[CategoryTypes.City.toLowerCase()] = BingEntities.PopulatedPlace;\r\n categoryToBingEntity[CategoryTypes.PostalCode.toLowerCase()] = BingEntities.Postcode;\r\n categoryToBingEntity[CategoryTypes.Address.toLowerCase()] = BingEntities.Address;\r\n }\r\n return categoryToBingEntity[category] || \"\";\r\n }\r\n\r\n public getUrl(): string {\r\n let url = Settings.BingUrl + \"?key=\" + Settings.BingKey;\r\n let entityType = this.getBingEntity();\r\n let queryAdded = false;\r\n if (entityType) {\r\n if (entityType === BingEntities.Postcode) {\r\n url += \"&includeEntityTypes=Postcode,Postcode1,Postcode2,Postcode3,Postcode4\";\r\n }\r\n else if (this.query.indexOf(\",\") === -1 && (entityType === BingEntities.AdminDivision1 || entityType === BingEntities.AdminDivision2)) {\r\n queryAdded = true;\r\n try {\r\n url += \"&adminDistrict=\" + decodeURIComponent(this.query);\r\n } catch (e) {\r\n return null;\r\n }\r\n }\r\n else {\r\n url += \"&includeEntityTypes=\" + entityType;\r\n }\r\n }\r\n\r\n if (!queryAdded) {\r\n try {\r\n url += \"&q=\" + decodeURIComponent(this.query);\r\n } catch (e) {\r\n return null;\r\n }\r\n }\r\n\r\n let cultureName = navigator.userLanguage || navigator[\"language\"];\r\n cultureName = mapLocalesForBing(cultureName);\r\n if (cultureName) {\r\n url += \"&c=\" + cultureName;\r\n }\r\n\r\n url += \"&maxRes=20\";\r\n\r\n // If the query is of length 2, request the ISO 2-letter country code to be returned with the result to be compared against the query so that such results can be preferred.\r\n if (this.query.length === 2 && this.category === CategoryTypes.CountryRegion) {\r\n url += \"&include=ciso2\";\r\n }\r\n\r\n return url;\r\n }\r\n }\r\n\r\n export class GeocodePointQuery extends GeocodeQuery {\r\n public latitude: number;\r\n public longitude: number;\r\n public entities: string[];\r\n \r\n constructor(latitude: number, longitude: number, entities:string[]) {\r\n super([latitude, longitude].join(), \"Point\");\r\n this.latitude = latitude;\r\n this.longitude = longitude;\r\n this.entities = entities;\r\n } \r\n\r\n public getUrl(): string {\r\n debug.assert(\r\n this.entities.every(e => !!_.findKey(BingEntities, be => be === e)),\r\n \"All entities should match one of the allowed BingEntities\");\r\n let url = Settings.BingUrl + \"/\" +\r\n [this.latitude, this.longitude].join() + \"?\" +\r\n \"key=\" + Settings.BingKey +\r\n (_.isEmpty(this.entities) ? \"\" : \"&includeEntityTypes=\" + this.entities.join()) +\r\n \"&include=ciso2\"; \r\n return url;\r\n }\r\n }\r\n\r\n export class GeocodeBoundaryQuery extends GeocodeQuery {\r\n public latitude: number;\r\n public longitude: number;\r\n public levelOfDetail: number;\r\n public maxGeoData: number;\r\n\r\n constructor(latitude: number, longitude: number, category, levelOfDetail, maxGeoData = 3) {\r\n super([latitude, longitude, levelOfDetail, maxGeoData].join(\",\"), category);\r\n this.latitude = latitude;\r\n this.longitude = longitude;\r\n this.levelOfDetail = levelOfDetail;\r\n this.maxGeoData = maxGeoData;\r\n }\r\n\r\n public getBingEntity(): string {\r\n let category = this.category.toLowerCase();\r\n if (!categoryToBingEntityGeodata) {\r\n categoryToBingEntityGeodata = {};\r\n categoryToBingEntityGeodata[CategoryTypes.CountryRegion.toLowerCase()] = BingEntities.CountryRegion;\r\n categoryToBingEntityGeodata[CategoryTypes.StateOrProvince.toLowerCase()] = BingEntities.AdminDivision1;\r\n categoryToBingEntityGeodata[CategoryTypes.County.toLowerCase()] = BingEntities.AdminDivision2;\r\n categoryToBingEntityGeodata[CategoryTypes.City.toLowerCase()] = BingEntities.PopulatedPlace;\r\n categoryToBingEntityGeodata[CategoryTypes.PostalCode.toLowerCase()] = BingEntities.Postcode1;\r\n }\r\n return categoryToBingEntityGeodata[category] || \"\";\r\n }\r\n\r\n public getUrl(): string {\r\n let url = Settings.BingUrlGeodata + \"key=\" + Settings.BingKey + \"&$format=json\";\r\n let entityType = this.getBingEntity();\r\n if (!entityType) {\r\n return null;\r\n }\r\n\r\n let cultureName = navigator.userLanguage || navigator[\"language\"];\r\n cultureName = mapLocalesForBing(cultureName);\r\n let cultures = cultureName.split(\"-\");\r\n let data = [this.latitude, this.longitude, this.levelOfDetail, \"'\" + entityType + \"'\", 1, 0, \"'\" + cultureName + \"'\"];\r\n if (cultures.length > 1) {\r\n data.push(\"'\" + cultures[1] + \"'\");\r\n }\r\n return url + \"&SpatialFilter=GetBoundary(\" + data.join(\", \") + \")\";\r\n }\r\n }\r\n\r\n /**\r\n * Map locales that cause failures to similar locales that work\r\n */\r\n function mapLocalesForBing(locale: string): string {\r\n switch (locale.toLowerCase()) {\r\n case 'fr': // Bing gives a 404 error when this language code is used (fr is only obtained from Chrome). Use fr-FR for a near-identical version that works. Defect # 255717 opened with Bing.\r\n return 'fr-FR';\r\n default:\r\n return locale;\r\n }\r\n }\r\n\r\n function tryGeocodeImmediate(query: string, category?: string): IGeocodeCoordinate {\r\n let result = geocodingCache ? geocodingCache.getCoordinates(new GeocodeQuery(query, category).key) : undefined;\r\n return result;\r\n }\r\n\r\n function tryGeocodeBoundaryImmediate(latitude: number, longitude: number, category: string, levelOfDetail?: number, maxGeoData: number = 3): IGeocodeBoundaryCoordinate {\r\n let result = geocodingCache ? geocodingCache.getCoordinates(new GeocodeBoundaryQuery(latitude, longitude, category, levelOfDetail, maxGeoData).key) : undefined;\r\n return <IGeocodeBoundaryCoordinate>result;\r\n }\r\n\r\n export function geocodeCore(geocodeQuery: GeocodeQuery, options?: GeocodeOptions): any {\r\n let result = geocodingCache ? geocodingCache.getCoordinates(geocodeQuery.key) : undefined;\r\n let deferred = $.Deferred();\r\n\r\n if (result) {\r\n deferred.resolve(result);\r\n } else {\r\n let item: IGeocodeQueueItem = { query: geocodeQuery, deferred: deferred, isResolved: false };\r\n\r\n if (options && options.timeout) {\r\n options.timeout.finally(() => {\r\n if (!item.isResolved) {\r\n item.deferred.reject();\r\n item.isResolved = true;\r\n }\r\n });\r\n }\r\n\r\n geocodeQueue.push(item);\r\n dequeue();\r\n }\r\n return deferred;\r\n }\r\n\r\n export function geocode(query: string, category: string = \"\", options?: GeocodeOptions): any {\r\n return geocodeCore(new GeocodeQuery(query, category), options);\r\n }\r\n\r\n export function geocodeBoundary(latitude: number, longitude: number, category: string = \"\", levelOfDetail: number = 2, maxGeoData: number = 3, options?: GeocodeOptions): any {\r\n return geocodeCore(new GeocodeBoundaryQuery(latitude, longitude, category, levelOfDetail, maxGeoData), options);\r\n }\r\n\r\n export function geocodePoint(latitude: number, longitude: number, entities: string[], options?: GeocodeOptions): any {\r\n return geocodeCore(new GeocodePointQuery(latitude, longitude, entities), options);\r\n }\r\n\r\n function dequeue(decrement: number = 0) {\r\n activeRequests -= decrement;\r\n while (activeRequests < Settings.MaxBingRequest) {\r\n\r\n if (geocodeQueue.length === 0) {\r\n break;\r\n }\r\n\r\n activeRequests++;\r\n makeRequest(geocodeQueue.shift());\r\n }\r\n }\r\n\r\n function makeRequest(item: IGeocodeQueueItem) {\r\n if (!item.isResolved) {\r\n let result = geocodingCache ? geocodingCache.getCoordinates(item.query.key) : undefined;\r\n if (result) {\r\n item.deferred.resolve(result);\r\n item.isResolved = true;\r\n }\r\n }\r\n\r\n if (item.isResolved) {\r\n setTimeout(() => dequeue(1));\r\n return;\r\n }\r\n\r\n // Unfortunately the Bing service doesn't support CORS, only jsonp. This issue must be raised and revised.\r\n // VSTS: 1396088 - Tracking: Ask: Bing geocoding to support CORS\r\n let config: JQueryAjaxSettings = {\r\n type: \"GET\",\r\n dataType: \"jsonp\",\r\n jsonp: \"jsonp\"\r\n };\r\n\r\n let url = item.query.getUrl();\r\n if (!url) {\r\n completeRequest(item, new Error(\"Unsupported query. \" + item.query.query));\r\n }\r\n\r\n BingAjaxCall(url, config).then(\r\n (data) => {\r\n try {\r\n if (item.query instanceof GeocodeBoundaryQuery) {\r\n let result = data;\r\n if (result && result.d && Array.isArray(result.d.results) && result.d.results.length > 0) {\r\n let entity = result.d.results[0];\r\n let primitives = entity.Primitives;\r\n if (primitives && primitives.length > 0) {\r\n let coordinates: IGeocodeBoundaryCoordinate = {\r\n latitude: (<GeocodeBoundaryQuery>item.query).latitude,\r\n longitude: (<GeocodeBoundaryQuery>item.query).longitude,\r\n locations: []\r\n };\r\n\r\n primitives.sort((a, b) => {\r\n if (a.Shape.length < b.Shape.length) {\r\n return 1;\r\n }\r\n if (a.Shape.length > b.Shape.length) {\r\n return -1;\r\n }\r\n return 0;\r\n });\r\n\r\n let maxGeoData = Math.min(primitives.length, (<GeocodeBoundaryQuery>item.query).maxGeoData);\r\n\r\n for (let i = 0; i < maxGeoData; i++) {\r\n let ringStr = primitives[i].Shape;\r\n let ringArray = ringStr.split(\",\");\r\n\r\n for (let j = 1; j < ringArray.length; j++) {\r\n coordinates.locations.push({ nativeBing: ringArray[j] });\r\n }\r\n }\r\n\r\n completeRequest(item, null, coordinates);\r\n }\r\n else {\r\n completeRequest(item, new Error(\"Geocode result is empty.\"));\r\n }\r\n }\r\n else {\r\n completeRequest(item, new Error(\"Geocode result is empty.\"));\r\n }\r\n }\r\n else if (item.query instanceof GeocodePointQuery){\r\n let resources = data.resourceSets[0].resources;\r\n if (Array.isArray(resources) && resources.length > 0) {\r\n let index = getBestResultIndex(resources, item.query);\r\n let pointData = resources[index].point.coordinates;\r\n let addressData = resources[index].address;\r\n let name = resources[index].name;\r\n let coordinates: IGeocodeResource = {\r\n latitude: parseFloat(pointData[0]),\r\n longitude: parseFloat(pointData[1]),\r\n addressLine: addressData.addressLine,\r\n locality: addressData.locality,\r\n neighborhood: addressData.neighborhood,\r\n adminDistrict: addressData.adminDistrict, \r\n adminDistrict2: addressData.adminDistrict2, \r\n formattedAddress: addressData.formattedAddress, \r\n postalCode: addressData.postalCode,\r\n countryRegionIso2: addressData.countryRegionIso2, \r\n countryRegion: addressData.countryRegion, \r\n landmark: addressData.landmark, \r\n name: name,\r\n };\r\n completeRequest(item, null, coordinates);\r\n }\r\n else {\r\n completeRequest(item, null, null);\r\n }\r\n }\r\n else {\r\n let resources = data.resourceSets[0].resources;\r\n if (Array.isArray(resources) && resources.length > 0) {\r\n let index = getBestResultIndex(resources, item.query);\r\n let pointData = resources[index].point.coordinates;\r\n let coordinates: IGeocodeCoordinate = {\r\n latitude: parseFloat(pointData[0]),\r\n longitude: parseFloat(pointData[1])\r\n };\r\n completeRequest(item, null, coordinates);\r\n }\r\n else {\r\n completeRequest(item, new Error(\"Geocode result is empty.\"));\r\n }\r\n }\r\n }\r\n catch (error) {\r\n completeRequest(item, error);\r\n }\r\n },\r\n (error) => {\r\n completeRequest(item, error);\r\n });\r\n }\r\n\r\n let dequeueTimeoutId;\r\n\r\n function completeRequest(item: IGeocodeQueueItem, error: Error, coordinate: IGeocodeCoordinate | IGeocodeBoundaryCoordinate = null) {\r\n if (!item.isResolved) {\r\n if (error) {\r\n item.deferred.reject(error);\r\n }\r\n else {\r\n if (geocodingCache && !(item.query instanceof GeocodePointQuery))\r\n geocodingCache.registerCoordinates(item.query.key, coordinate);\r\n item.deferred.resolve(coordinate);\r\n }\r\n\r\n item.isResolved = true;\r\n }\r\n\r\n dequeueTimeoutId = setTimeout(() => dequeue(1), Settings.UseDoubleArrayGeodataResult ? Settings.UseDoubleArrayDequeueTimeout : 0);\r\n }\r\n\r\n function getBestResultIndex(resources: any[], query: GeocodeQuery) {\r\n let queryString = query.query.toLowerCase();\r\n // If string is of length 2 and is a country, check against the ISO country code of results, prefering exact matches\r\n if (queryString.length === 2 && query.category === CategoryTypes.CountryRegion) {\r\n for (let index = 0; index < resources.length; index++) {\r\n let iso2: string = resources[index].address && resources[index].address.countryRegionIso2;\r\n if (iso2 && queryString === iso2.toLowerCase()) {\r\n return index;\r\n }\r\n }\r\n }\r\n // Prefer results that match the targetEntity (geotagged category) on the query\r\n let targetEntity = query.getBingEntity().toLowerCase();\r\n for (let index = 0; index < resources.length; index++) {\r\n let resultEntity = (resources[index].entityType || \"\").toLowerCase();\r\n if (resultEntity === targetEntity) {\r\n return index;\r\n }\r\n }\r\n return 0;\r\n }\r\n\r\n export function resetStaticGeocoderState(cache?: IGeocodingCache): void {\r\n if (cache !== undefined)\r\n geocodingCache = cache;\r\n geocodeQueue = [];\r\n activeRequests = 0;\r\n categoryToBingEntity = null;\r\n clearTimeout(dequeueTimeoutId);\r\n }\r\n\r\n resetStaticGeocoderState();\r\n}\n\n\n/** WEBPACK FOOTER **\n ** ./src/Clients/Visuals/services/geocoder.ts\n **/","/*\r\n * Power BI Visualizations\r\n *\r\n * Copyright (c) Microsoft Corporation\r\n * All rights reserved. \r\n * MIT License\r\n *\r\n * Permission is hereby granted, free of charge, to any person obtaining a copy\r\n * of this software and associated documentation files (the \"\"Software\"\"), to deal\r\n * in the Software without restriction, including without limitation the rights\r\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\r\n * copies of the Software, and to permit persons to whom the Software is\r\n * furnished to do so, subject to the following conditions:\r\n * \r\n * The above copyright notice and this permission notice shall be included in \r\n * all copies or substantial portions of the Software.\r\n * \r\n * THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR \r\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, \r\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE \r\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER \r\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\r\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\r\n * THE SOFTWARE.\r\n */\r\n\r\n/// <reference path=\"../_references.ts\"/>\r\n\r\nmodule powerbi.visuals.services {\r\n interface GeocodeCacheEntry {\r\n coordinate: IGeocodeCoordinate;\r\n hitCount: number;\r\n }\r\n\r\n export interface IGeocodingCache {\r\n getCoordinates(key: string): IGeocodeCoordinate;\r\n registerCoordinates(key: string, coordinate: IGeocodeCoordinate): void;\r\n registerCoordinates(key: string, coordinate: IGeocodeBoundaryCoordinate): void;\r\n }\r\n\r\n export function createGeocodingCache(maxCacheSize: number, maxCacheSizeOverflow: number, localStorageService?: IStorageService): IGeocodingCache {\r\n if (!localStorageService)\r\n localStorageService = powerbi.localStorageService;\r\n return new GeocodingCache(maxCacheSize, maxCacheSizeOverflow, localStorageService);\r\n }\r\n\r\n class GeocodingCache implements IGeocodingCache {\r\n private geocodeCache: _.Dictionary<GeocodeCacheEntry>;\r\n private geocodeCacheCount: number;\r\n private maxCacheSize: number;\r\n private maxCacheSizeOverflow: number;\r\n private localStorageService: IStorageService;\r\n\r\n constructor(maxCacheSize: number, maxCacheSizeOverflow: number, localStorageService: IStorageService) {\r\n this.geocodeCache = {};\r\n this.geocodeCacheCount = 0;\r\n this.maxCacheSize = maxCacheSize;\r\n this.maxCacheSizeOverflow = maxCacheSizeOverflow;\r\n this.localStorageService = localStorageService;\r\n }\r\n \r\n /**\r\n * Retrieves the coordinate for the key from the cache, returning undefined on a cache miss.\r\n */\r\n public getCoordinates(key: string): IGeocodeCoordinate {\r\n // Check in-memory cache\r\n let pair = this.geocodeCache[key];\r\n if (pair) {\r\n ++pair.hitCount;\r\n return pair.coordinate;\r\n }\r\n // Check local storage cache\r\n pair = this.localStorageService.getData(key);\r\n if (pair) {\r\n this.registerInMemory(key, pair.coordinate);\r\n return pair.coordinate;\r\n }\r\n return undefined;\r\n }\r\n \r\n /**\r\n * Registers the query and coordinate to the cache.\r\n */\r\n public registerCoordinates(key: string, coordinate: IGeocodeCoordinate): void {\r\n this.registerInMemory(key, coordinate);\r\n this.registerInStorage(key, coordinate);\r\n }\r\n\r\n private registerInMemory(key: string, coordinate: IGeocodeCoordinate): void {\r\n let geocodeCache = this.geocodeCache;\r\n let maxCacheSize = this.maxCacheSize;\r\n let maxCacheCount = maxCacheSize + this.maxCacheSizeOverflow;\r\n\r\n // are we about to exceed the maximum?\r\n if (this.geocodeCacheCount >= maxCacheCount) {\r\n let keys = Object.keys(geocodeCache);\r\n let cacheSize = keys.length;\r\n\r\n // sort keys in *descending* hitCount order\r\n keys.sort((a: string, b: string) => {\r\n let cachedA = geocodeCache[a];\r\n let cachedB = geocodeCache[b];\r\n let ca = cachedA ? cachedA.hitCount : 0;\r\n let cb = cachedB ? cachedB.hitCount : 0;\r\n return ca < cb ? 1 : (ca > cb ? -1 : 0);\r\n });\r\n\r\n // whack ones with the lower hitCounts.\r\n // - while # whacked keys is small, do a quick wipe\r\n // - after awhile we get lots of keys whose cached value is undefined. \r\n // when there are \"too many,\" make a whole new memory cache.\r\n if (cacheSize < 2 * maxCacheCount) {\r\n for (let i = maxCacheSize; i < cacheSize; i++)\r\n geocodeCache[keys[i]] = undefined;\r\n }\r\n else {\r\n let newGeocodeCache: _.Dictionary<GeocodeCacheEntry> = {};\r\n for (let i = 0; i < maxCacheSize; ++i)\r\n newGeocodeCache[keys[i]] = geocodeCache[keys[i]];\r\n\r\n geocodeCache = this.geocodeCache = newGeocodeCache;\r\n }\r\n\r\n this.geocodeCacheCount = maxCacheSize;\r\n }\r\n\r\n geocodeCache[key] = { coordinate: coordinate, hitCount: 1 };\r\n ++this.geocodeCacheCount;\r\n }\r\n\r\n private registerInStorage(key: string, coordinate: IGeocodeCoordinate): void {\r\n this.localStorageService.setData(key, { coordinate: coordinate });\r\n }\r\n }\r\n}\n\n\n/** WEBPACK FOOTER **\n ** ./src/Clients/Visuals/services/geocodingCache.ts\n **/","/*\r\n * Power BI Visualizations\r\n *\r\n * Copyright (c) Microsoft Corporation\r\n * All rights reserved. \r\n * MIT License\r\n *\r\n * Permission is hereby granted, free of charge, to any person obtaining a copy\r\n * of this software and associated documentation files (the \"\"Software\"\"), to deal\r\n * in the Software without restriction, including without limitation the rights\r\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\r\n * copies of the Software, and to permit persons to whom the Software is\r\n * furnished to do so, subject to the following conditions:\r\n * \r\n * The above copyright notice and this permission notice shall be included in \r\n * all copies or substantial portions of the Software.\r\n * \r\n * THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR \r\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, \r\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE \r\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER \r\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\r\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\r\n * THE SOFTWARE.\r\n */\r\n\r\n/// <reference path=\"../_references.ts\"/>\r\n\r\nmodule powerbi.visuals.services {\r\n\r\n export function createGeolocation(): IGeolocation {\r\n return new GeolocationService();\r\n }\r\n\r\n /**\r\n * HTML5 Implementation of IGeolocation\r\n */\r\n class GeolocationService implements IGeolocation {\r\n private webGeolocation: Geolocation;\r\n \r\n constructor() {\r\n this.webGeolocation = navigator.geolocation;\r\n }\r\n \r\n public watchPosition(successCallback: IPositionCallback, errorCallback?: IPositionErrorCallback): number {\r\n return this.webGeolocation.watchPosition(\r\n (position: Position) => {\r\n successCallback(position);\r\n },\r\n (error: PositionError) => {\r\n if (errorCallback != null) {\r\n errorCallback(error);\r\n }\r\n }\r\n );\r\n }\r\n\r\n public clearWatch(watchId: number): void {\r\n this.webGeolocation.clearWatch(watchId);\r\n }\r\n\r\n public getCurrentPosition(successCallback: IPositionCallback, errorCallback?: IPositionErrorCallback): void {\r\n this.webGeolocation.getCurrentPosition(\r\n (position: Position) => {\r\n successCallback(position);\r\n },\r\n (error: PositionError) => {\r\n if (errorCallback != null) {\r\n errorCallback(error);\r\n }\r\n }\r\n );\r\n }\r\n\r\n }\r\n}\n\n\n/** WEBPACK FOOTER **\n ** ./src/Clients/Visuals/services/geolocationService.ts\n **/","/*\r\n * Power BI Visualizations\r\n *\r\n * Copyright (c) Microsoft Corporation\r\n * All rights reserved. \r\n * MIT License\r\n *\r\n * Permission is hereby granted, free of charge, to any person obtaining a copy\r\n * of this software and associated documentation files (the \"\"Software\"\"), to deal\r\n * in the Software without restriction, including without limitation the rights\r\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\r\n * copies of the Software, and to permit persons to whom the Software is\r\n * furnished to do so, subject to the following conditions:\r\n * \r\n * The above copyright notice and this permission notice shall be included in \r\n * all copies or substantial portions of the Software.\r\n * \r\n * THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR \r\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, \r\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE \r\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER \r\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\r\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\r\n * THE SOFTWARE.\r\n */\r\n\r\n/// <reference path=\"../../_references.ts\"/>\r\n\r\nmodule powerbi.visuals.controls {\r\n\r\n const UNSELECTABLE_CLASS_NAME = \"unselectable\";\r\n\r\n export function fire(eventHandlers, eventArgs) {\r\n if (eventHandlers) {\r\n for (let i = 0; i < eventHandlers.length; i++) {\r\n let h = eventHandlers[i];\r\n h(eventArgs);\r\n }\r\n }\r\n }\r\n\r\n export class ScrollbarButton {\r\n // Const\r\n // TODO: Move to style\r\n static MIN_WIDTH = 26;\r\n static ARROW_COLOR: string = \"#404040\";\r\n\r\n // Fields\r\n private _element: HTMLDivElement;\r\n private _polygon: SVGElement;\r\n private _svg: HTMLElement;\r\n private _owner: Scrollbar;\r\n private _direction: number;\r\n private _timerHandle: number;\r\n private _mouseUpWrapper: any;\r\n\r\n // Constructor\r\n constructor(owner: Scrollbar, direction: number) {\r\n this._owner = owner;\r\n this._direction = direction;\r\n this._timerHandle = undefined;\r\n this.createView();\r\n let that = this;\r\n this._element.addEventListener(\"mousedown\", function (e) { that.onMouseDown(<MouseEvent>e); });\r\n $(this._element).addClass(UNSELECTABLE_CLASS_NAME);\r\n $(this._svg).addClass(UNSELECTABLE_CLASS_NAME);\r\n $(this._polygon).addClass(UNSELECTABLE_CLASS_NAME);\r\n }\r\n\r\n // Properties\r\n public get element(): HTMLDivElement {\r\n return this._element;\r\n }\r\n\r\n // Methods\r\n private createView(): void {\r\n let svgns = \"http://www.w3.org/2000/svg\";\r\n\r\n this._polygon = <SVGElement>document.createElementNS(svgns, \"polygon\");\r\n this._polygon.setAttributeNS(null, \"points\", \"3,3 6,3 13,8 6,13 3,13 10,8\");\r\n this._polygon.setAttributeNS(null, \"fill\", ScrollbarButton.ARROW_COLOR);\r\n\r\n this._svg = <HTMLElement>document.createElementNS(svgns, \"svg\");\r\n let svgStyle = this._svg.style;\r\n svgStyle.position = \"absolute\";\r\n svgStyle.left = \"0px\";\r\n svgStyle.top = \"0px\";\r\n this._svg.appendChild(this._polygon);\r\n\r\n this._element = <HTMLDivElement>document.createElement(\"div\");\r\n this._element.className = Scrollbar.arrowClassName;\r\n this._element.appendChild(this._svg);\r\n\r\n this._owner.element.appendChild(this._element);\r\n }\r\n\r\n private onMouseDown(event: MouseEvent): void {\r\n let that = this;\r\n clearTimeout(this._timerHandle);\r\n if (!this._mouseUpWrapper) {\r\n event.cancelBubble = true;\r\n let that = this;\r\n this._mouseUpWrapper = function (event) { that.onMouseUp(event); };\r\n Scrollbar.addDocumentMouseUpEvent(this._mouseUpWrapper);\r\n }\r\n this._owner._scrollSmallIncrement(this._direction);\r\n this._owner.refresh();\r\n this._timerHandle = setTimeout(function () { that.onMouseDown(event); }, 100);\r\n if (event.preventDefault) {\r\n event.preventDefault(); // prevent dragging\r\n }\r\n }\r\n\r\n private onMouseUp(event: MouseEvent): void {\r\n clearTimeout(this._timerHandle);\r\n Scrollbar.removeDocumentMouseUpEvent(this._mouseUpWrapper);\r\n this._mouseUpWrapper = undefined;\r\n }\r\n\r\n public arrange(width: number, height: number, angle: number): void {\r\n let size = Math.min(width, height);\r\n let scale = size / 16;\r\n let x = (width - size) / 2;\r\n let y = (height - size) / 2;\r\n this._polygon.setAttributeNS(null, \"transform\", \"translate(\" + x + \", \" + y + \") scale(\" + scale + \") rotate(\" + angle + \",8,8)\");\r\n this._svg.setAttributeNS(null, \"width\", width + \"px\");\r\n this._svg.setAttributeNS(null, \"height\", height + \"px\");\r\n\r\n HTMLElementUtils.setElementWidth(this._element, width);\r\n HTMLElementUtils.setElementHeight(this._element, height);\r\n }\r\n }\r\n\r\n /** Scrollbar base class */\r\n export class Scrollbar {\r\n public static DefaultScrollbarWidth = \"15px\"; // protected\r\n private static ScrollbarBackgroundFirstTimeMousedownHoldDelay = 500;\r\n private static ScrollbarBackgroundMousedownHoldDelay = 50;\r\n private static MouseWheelRange = 120;\r\n\r\n static className = \"scroll-bar-div\";\r\n static barClassName = \"scroll-bar-part-bar\";\r\n static arrowClassName = \"scroll-bar-part-arrow\";\r\n\r\n public MIN_BAR_SIZE = 10;\r\n\r\n public min: number = 0;\r\n public max: number = 10;\r\n public viewMin: number = 0;\r\n public viewSize: number = 2;\r\n public smallIncrement: number = 1;\r\n\r\n public _onscroll: any[] = [];\r\n\r\n private _actualWidth: number;\r\n private _actualHeight: number;\r\n private _actualButtonWidth: number;\r\n private _actualButtonHeight: number;\r\n\r\n private _width: string;\r\n private _height: string;\r\n private _visible: boolean;\r\n \r\n private _element: HTMLDivElement;\r\n private _minButton: ScrollbarButton;\r\n private _maxButton: ScrollbarButton;\r\n private _middleBar: HTMLDivElement;\r\n\r\n private _timerHandle: number;\r\n private _screenToOffsetScale: number = 1.0;\r\n\r\n private _screenPrevMousePos: { x: number; y: number; };\r\n private _screenMinMousePos: number;\r\n private _screenMaxMousePos: number;\r\n\r\n private _backgroundMouseUpWrapper: any;\r\n private _middleBarMouseMoveWrapper: any;\r\n private _middleBarMouseUpWrapper: any;\r\n\r\n // Touch memmbers\r\n private _touchPanel: HTMLElement;\r\n private _offsetTouchStartPos: { x: number; y: number; };\r\n private _offsetTouchPrevPos: { x: number; y: number; };\r\n private _touchStarted: boolean;\r\n private _allowMouseDrag: boolean;\r\n\r\n constructor(parentElement: HTMLElement, layoutKind: TablixLayoutKind) {\r\n this.createView(parentElement, layoutKind);\r\n let that = this;\r\n this._element.addEventListener(\"mousedown\", function (e) { that.onBackgroundMouseDown(<MouseEvent>e); });\r\n this._middleBar.addEventListener(\"mousedown\", function (e) { that.onMiddleBarMouseDown(<MouseEvent>e); });\r\n this._timerHandle = undefined;\r\n this._visible = true;\r\n this.element[\"winControl\"] = this;\r\n $(this._touchPanel).addClass(UNSELECTABLE_CLASS_NAME);\r\n }\r\n\r\n public scrollBy(delta: number) {\r\n this.scrollTo(this.viewMin + delta);\r\n }\r\n\r\n public scrollUp(): void {\r\n this.scrollBy(-this.smallIncrement);\r\n }\r\n\r\n public scrollDown(): void {\r\n this.scrollBy(this.smallIncrement);\r\n }\r\n\r\n public scrollPageUp(): void {\r\n this.scrollBy(-this.viewSize);\r\n }\r\n\r\n public scrollPageDown(): void {\r\n this.scrollBy(this.viewSize);\r\n }\r\n\r\n public set width(value: string) {\r\n this._width = value;\r\n this._element.style.width = value;\r\n this.invalidateArrange();\r\n }\r\n\r\n public get width(): string {\r\n return this._width;\r\n }\r\n\r\n public set height(value: string) {\r\n this._height = value;\r\n this._element.style.height = value;\r\n this.invalidateArrange();\r\n }\r\n\r\n public get height(): string {\r\n return this._height;\r\n }\r\n\r\n public refresh(): void {\r\n debug.assertFail(\"PureVirtualMethod: Scrollbar.refresh()\");\r\n }\r\n\r\n public get element(): HTMLDivElement {\r\n return this._element;\r\n }\r\n\r\n public get maxButton(): ScrollbarButton {\r\n return this._maxButton;\r\n }\r\n\r\n public get middleBar(): HTMLDivElement {\r\n return this._middleBar;\r\n }\r\n\r\n public _scrollSmallIncrement(direction): void { // intent to be internal\r\n this.scrollBy(this.smallIncrement * direction);\r\n }\r\n\r\n public get visible(): boolean {\r\n return this._visible;\r\n }\r\n\r\n public get isInMouseCapture(): boolean {\r\n return this._timerHandle !== undefined;\r\n }\r\n\r\n public show(value: boolean): void {\r\n this._visible = value;\r\n this.element.style.visibility = value ? \"visible\" : \"hidden\";\r\n this.invalidateArrange();\r\n }\r\n\r\n public _getMouseOffset(event: MouseEvent): { x: number; y: number; } { // protected\r\n if (event.offsetX !== undefined)\r\n return { x: event.offsetX, y: event.offsetY };\r\n if (event.layerX !== undefined)\r\n return { x: event.layerX, y: event.layerY };\r\n return { x: event.screenX, y: event.screenY };\r\n }\r\n\r\n public _getOffsetXDelta(event: MouseEvent): number { // protected\r\n return (event.screenX - this._screenPrevMousePos.x) / this._screenToOffsetScale;\r\n }\r\n\r\n public _getOffsetYDelta(event: MouseEvent): number { // protected\r\n return (event.screenY - this._screenPrevMousePos.y) / this._screenToOffsetScale;\r\n }\r\n\r\n public _getOffsetXTouchDelta(event: MouseEvent): number { // protected\r\n return this._getMouseOffset(event).x - this._offsetTouchPrevPos.x;\r\n }\r\n\r\n public _getOffsetYTouchDelta(event: MouseEvent): number { // protected\r\n return this._getMouseOffset(event).y - this._offsetTouchPrevPos.y;\r\n }\r\n\r\n public initTouch(panel: HTMLElement, allowMouseDrag?: boolean): void {\r\n this._touchPanel = panel;\r\n this._allowMouseDrag = allowMouseDrag === undefined ? true : allowMouseDrag;\r\n if (\"ontouchmove\" in panel) {\r\n panel.addEventListener(\"touchstart\", e => this.onTouchStart(e));\r\n panel.addEventListener(\"touchmove\", e => this.onTouchMove(e));\r\n panel.addEventListener(\"touchend\", e => this.onTouchEnd(e));\r\n }\r\n else {\r\n panel.addEventListener(\"mousedown\", e => this.onTouchMouseDown(<MouseEvent>e));\r\n panel.addEventListener(\"mousemove\", e => this.onTouchMouseMove(<MouseEvent>e));\r\n panel.addEventListener(\"mouseup\", e => this.onTouchMouseUp(<MouseEvent>e));\r\n }\r\n }\r\n\r\n public onTouchStart(e: any) {\r\n if (e.touches.length === 1) {\r\n this.onTouchMouseDown(e.touches[0]);\r\n }\r\n }\r\n\r\n public onTouchMove(e: any) {\r\n if (e.touches.length === 1) {\r\n if (e.preventDefault)\r\n e.preventDefault();\r\n this.onTouchMouseMove(e.touches[0]);\r\n }\r\n }\r\n\r\n public onTouchEnd(e: any) {\r\n this.onTouchMouseUp(e.touches.length === 1 ? e.touches[0] : e, true);\r\n }\r\n\r\n public onTouchMouseDown(e: MouseEvent) {\r\n // except IE touch cancels mouse so not need for detection. For IE touch and mouse difference is detected by a flag.\r\n if (!this._allowMouseDrag &&\r\n e[\"pointerType\"] === (<MSPointerEventExtension>MSPointerEvent).MSPOINTER_TYPE_MOUSE) {\r\n return;\r\n }\r\n if (\"setCapture\" in this._touchPanel) {\r\n this._touchPanel.setCapture(true);\r\n }\r\n this._offsetTouchPrevPos = this._offsetTouchStartPos = null;\r\n this._touchStarted = true;\r\n }\r\n\r\n public _getOffsetTouchDelta(e: MouseEvent): number {\r\n debug.assertFail(\"PureVirtualMethod: Scrollbar._getOffsetTouchDelta()\");\r\n return null;\r\n }\r\n\r\n public onTouchMouseMove(e: MouseEvent) {\r\n if (this._touchStarted) {\r\n if (!this._offsetTouchStartPos) {\r\n this._offsetTouchPrevPos = this._offsetTouchStartPos = this._getMouseOffset(e);\r\n }\r\n\r\n let delta = this._getOffsetTouchDelta(e);\r\n if (delta !== 0) {\r\n this.scrollBy(-delta / this._getRunningSize(false) * this.viewSize);\r\n this._offsetTouchPrevPos = this._getMouseOffset(e);\r\n }\r\n if (e.preventDefault)\r\n e.preventDefault();\r\n\r\n e.cancelBubble = true;\r\n }\r\n }\r\n\r\n public onTouchMouseUp(e: MouseEvent, bubble?: boolean) {\r\n if (this._touchStarted) {\r\n if (this._offsetTouchStartPos) {\r\n let end = this._getMouseOffset(e);\r\n if (!bubble && (Math.abs(this._offsetTouchStartPos.x - end.x) > 3 || Math.abs(this._offsetTouchStartPos.y - end.y) > 3)) {\r\n if (e.preventDefault)\r\n e.preventDefault();\r\n\r\n e.cancelBubble = true;\r\n }\r\n }\r\n }\r\n if (\"releaseCapture\" in this._touchPanel) {\r\n this._touchPanel.releaseCapture();\r\n }\r\n this._touchStarted = false;\r\n }\r\n\r\n private createView(parentElement: HTMLElement, layoutKind: TablixLayoutKind): void {\r\n this._element = <HTMLDivElement>document.createElement(\"div\");\r\n this._element.className = Scrollbar.className;\r\n this._element.setAttribute(\"drag-resize-disabled\", \"true\");\r\n\r\n if (layoutKind === TablixLayoutKind.Canvas)\r\n parentElement.appendChild(this._element);\r\n\r\n this._minButton = new ScrollbarButton(this, -1);\r\n this._maxButton = new ScrollbarButton(this, 1);\r\n\r\n this._middleBar = <HTMLDivElement>document.createElement(\"div\");\r\n this._middleBar.className = Scrollbar.barClassName;\r\n this._element.appendChild(this._middleBar);\r\n }\r\n\r\n private scrollTo(pos: number): void {\r\n let viewMin = Math.min(this.max - this.viewSize, Math.max(this.min, pos));\r\n if (viewMin !== this.viewMin) {\r\n this.viewMin = viewMin;\r\n fire(this._onscroll, null);\r\n }\r\n }\r\n\r\n public _scrollByPage(event: MouseEvent): void {\r\n debug.assertFail(\"PureVirtualMethod: Scrollbar._scrollByPage()\");\r\n }\r\n\r\n public _getRunningSize(net: boolean): number {\r\n debug.assertFail(\"PureVirtualMethod: Scrollbar._getRunningSize()\");\r\n return null;\r\n }\r\n\r\n public _getOffsetDelta(event: MouseEvent): number {\r\n debug.assertFail(\"PureVirtualMethod: Scrollbar._getOffsetDelta()\");\r\n return null;\r\n }\r\n\r\n private scroll(event: MouseEvent): void {\r\n let delta: number = this._getOffsetDelta(event) / this._getRunningSize(true) * (this.max - this.min);\r\n\r\n if (delta < 0) {\r\n if (this._getScreenMousePos(event) >= this._screenMaxMousePos) {\r\n return;\r\n }\r\n }\r\n else if (delta > 0) {\r\n if (this._getScreenMousePos(event) <= this._screenMinMousePos) {\r\n return;\r\n }\r\n }\r\n\r\n this.scrollBy(delta);\r\n }\r\n\r\n public get actualWidth(): number {\r\n if (this._actualWidth === undefined) {\r\n this.arrange();\r\n }\r\n return this._actualWidth;\r\n }\r\n\r\n public get actualHeight(): number {\r\n if (!this._actualHeight === undefined) {\r\n this.arrange();\r\n }\r\n return this._actualHeight;\r\n }\r\n\r\n public get actualButtonWidth(): number {\r\n if (!this._actualButtonWidth === undefined) {\r\n this.arrange();\r\n }\r\n return this._actualButtonWidth;\r\n }\r\n\r\n public get actualButtonHeight(): number {\r\n if (!this._actualButtonHeight === undefined) {\r\n this.arrange();\r\n }\r\n return this._actualButtonHeight;\r\n }\r\n\r\n public arrange(): void {\r\n if (!this._actualWidth) {\r\n this._actualWidth = this._element.offsetWidth;\r\n this._actualHeight = this._element.offsetHeight;\r\n this._actualButtonWidth = this._calculateButtonWidth();\r\n this._actualButtonHeight = this._calculateButtonHeight();\r\n this._minButton.arrange(this._actualButtonWidth, this._actualButtonHeight, this._getMinButtonAngle());\r\n this._maxButton.arrange(this._actualButtonWidth, this._actualButtonHeight, this._getMaxButtonAngle());\r\n this._setMaxButtonPosition();\r\n }\r\n }\r\n\r\n public _calculateButtonWidth(): number {\r\n debug.assertFail(\"PureVirtualMethod: Scrollbar._calculateButtonWidth()\");\r\n return null;\r\n }\r\n\r\n public _calculateButtonHeight(): number {\r\n debug.assertFail(\"PureVirtualMethod: Scrollbar._calculateButtonHeight()\");\r\n return null;\r\n }\r\n\r\n public _getMinButtonAngle(): number {\r\n debug.assertFail(\"PureVirtualMethod: Scrollbar._getMinButtonAngle()\");\r\n return null;\r\n }\r\n\r\n public _getMaxButtonAngle(): number {\r\n debug.assertFail(\"PureVirtualMethod: Scrollbar._getMaxButtonAngle()\");\r\n return null;\r\n }\r\n\r\n public _setMaxButtonPosition() {\r\n debug.assertFail(\"PureVirtualMethod: Scrollbar._setMaxButtonPosition()\");\r\n }\r\n\r\n public invalidateArrange() {\r\n this._actualWidth = undefined;\r\n this._actualHeight = undefined;\r\n this._actualButtonWidth = undefined;\r\n this._actualButtonHeight = undefined;\r\n }\r\n\r\n private onHoldBackgroundMouseDown(event: MouseEvent): void {\r\n let holdDelay = this._timerHandle ?\r\n Scrollbar.ScrollbarBackgroundMousedownHoldDelay :\r\n Scrollbar.ScrollbarBackgroundFirstTimeMousedownHoldDelay;\r\n this._timerHandle = setTimeout(() => {\r\n this.onBackgroundMouseDown(event);\r\n }, holdDelay);\r\n }\r\n\r\n private onBackgroundMouseDown(event: MouseEvent): void {\r\n let that = this;\r\n clearTimeout(this._timerHandle);\r\n if (!this._backgroundMouseUpWrapper) {\r\n event.cancelBubble = true;\r\n this._backgroundMouseUpWrapper = function (event) { that.onBackgroundMouseUp(event); };\r\n Scrollbar.addDocumentMouseUpEvent(this._backgroundMouseUpWrapper);\r\n }\r\n this._scrollByPage(event);\r\n this.refresh();\r\n this.onHoldBackgroundMouseDown(event);\r\n if (event.preventDefault)\r\n event.preventDefault(); // prevent dragging\r\n }\r\n\r\n private onBackgroundMouseUp(event: MouseEvent): void {\r\n clearTimeout(this._timerHandle);\r\n this._timerHandle = undefined;\r\n Scrollbar.removeDocumentMouseUpEvent(this._backgroundMouseUpWrapper);\r\n this._backgroundMouseUpWrapper = undefined;\r\n }\r\n\r\n private getPinchZoomY(): number {\r\n return document.documentElement.clientHeight / window.innerHeight;\r\n }\r\n\r\n private onMiddleBarMouseDown(event: MouseEvent): void {\r\n event.cancelBubble = true;\r\n this._screenPrevMousePos = { x: event.screenX, y: event.screenY };\r\n this._screenMinMousePos = this._getScreenMousePos(event) - (this._getScreenContextualLeft(this._middleBar) - this._getScreenContextualRight(this._minButton.element));\r\n this._screenMaxMousePos = this._getScreenMousePos(event) + (this._getScreenContextualLeft(this._maxButton.element) - this._getScreenContextualRight(this._middleBar));\r\n this._screenToOffsetScale = HTMLElementUtils.getAccumulatedScale(this.element) * this.getPinchZoomY();\r\n let that = this;\r\n this._middleBarMouseMoveWrapper = function (e) { that.onMiddleBarMouseMove(e); };\r\n Scrollbar.addDocumentMouseMoveEvent(this._middleBarMouseMoveWrapper);\r\n this._middleBarMouseUpWrapper = function (e) { that.onMiddleBarMouseUp(e); };\r\n Scrollbar.addDocumentMouseUpEvent(this._middleBarMouseUpWrapper);\r\n if (event.preventDefault)\r\n event.preventDefault(); // prevent dragging\r\n }\r\n\r\n private onMiddleBarMouseMove(event: MouseEvent): void {\r\n if (!this._screenPrevMousePos) {\r\n return;\r\n }\r\n this.scroll(event);\r\n this.refresh();\r\n this._screenPrevMousePos = { x: event.screenX, y: event.screenY };\r\n }\r\n\r\n private onMiddleBarMouseUp(event: MouseEvent): void {\r\n this._screenPrevMousePos = undefined;\r\n Scrollbar.removeDocumentMouseMoveEvent(this._middleBarMouseMoveWrapper);\r\n this._middleBarMouseMoveWrapper = undefined;\r\n Scrollbar.removeDocumentMouseUpEvent(this._middleBarMouseUpWrapper);\r\n this._middleBarMouseUpWrapper = undefined;\r\n if (event.preventDefault)\r\n event.preventDefault(); // prevent other events\r\n }\r\n\r\n public _getScreenContextualLeft(element: HTMLElement): number {\r\n debug.assertFail(\"PureVirtualMethod: Scrollbar._getScreenContextualLeft()\");\r\n return null;\r\n }\r\n\r\n public _getScreenContextualRight(element: HTMLElement): number {\r\n debug.assertFail(\"PureVirtualMethod: Scrollbar._getScreenContextualRight()\");\r\n return null;\r\n }\r\n\r\n public onMouseWheel(delta: number): void {\r\n if (delta) {\r\n this.mouseWheel(delta);\r\n }\r\n }\r\n\r\n private mouseWheel(delta: number): void {\r\n if (this.visible) {\r\n if (delta < 0) { // fix for issue 786411 (Some machines won't have the delta as multiple of 120)\r\n delta = Math.min(-Scrollbar.MouseWheelRange, delta);\r\n }\r\n else if (delta > 0) {\r\n delta = Math.max(Scrollbar.MouseWheelRange, delta);\r\n }\r\n\r\n this.scrollBy(-delta / Scrollbar.MouseWheelRange * this.smallIncrement);\r\n }\r\n }\r\n\r\n public _getScreenMousePos(event: MouseEvent) {\r\n debug.assertFail(\"PureVirtualMethod: Scrollbar._getScreenMousePos()\");\r\n return null;\r\n }\r\n\r\n static addDocumentMouseUpEvent(func: any): void {\r\n document.addEventListener(\"mouseup\", func);\r\n }\r\n\r\n static removeDocumentMouseUpEvent(func: any): void {\r\n document.removeEventListener(\"mouseup\", func);\r\n }\r\n\r\n static addDocumentMouseMoveEvent(func): void {\r\n document.addEventListener(\"mousemove\", func);\r\n }\r\n\r\n static removeDocumentMouseMoveEvent(func): void {\r\n document.removeEventListener(\"mousemove\", func);\r\n }\r\n }\r\n\r\n /** Horizontal Scrollbar */\r\n export class HorizontalScrollbar extends Scrollbar {\r\n constructor(parentElement: HTMLElement, layoutKind: TablixLayoutKind) {\r\n super(parentElement, layoutKind);\r\n this.height = Scrollbar.DefaultScrollbarWidth;\r\n }\r\n\r\n public _calculateButtonWidth() {\r\n return Math.min(this.actualWidth / 2, Math.max(this.actualHeight, ScrollbarButton.MIN_WIDTH));\r\n }\r\n\r\n public _calculateButtonHeight() {\r\n return this.actualHeight;\r\n }\r\n\r\n public _getMinButtonAngle(): number {\r\n return -180;\r\n }\r\n\r\n public _getMaxButtonAngle(): number {\r\n return 0;\r\n }\r\n\r\n public _setMaxButtonPosition() {\r\n HTMLElementUtils.setElementLeft(this.maxButton.element, this.actualWidth - this.actualButtonWidth);\r\n }\r\n\r\n public refresh(): void {\r\n this.arrange();\r\n let runningSize = this.actualWidth - this.actualButtonWidth * 2 - 2;\r\n let barSize = this.viewSize / (this.max - this.min) * runningSize;\r\n if (barSize < this.MIN_BAR_SIZE) {\r\n runningSize -= this.MIN_BAR_SIZE - barSize;\r\n barSize = this.MIN_BAR_SIZE;\r\n }\r\n if (runningSize < 0) {\r\n runningSize = 0;\r\n barSize = 0;\r\n }\r\n barSize = Math.min(barSize, runningSize);\r\n let barPos = this.viewMin / (this.max - this.min) * runningSize;\r\n HTMLElementUtils.setElementWidth(this.middleBar, barSize);\r\n HTMLElementUtils.setElementHeight(this.middleBar, this.actualHeight);\r\n HTMLElementUtils.setElementLeft(this.middleBar, this.actualButtonWidth + 1 + barPos);\r\n }\r\n\r\n public show(visible: boolean): void {\r\n if (visible === this.visible)\r\n return;\r\n\r\n super.show(visible);\r\n if (visible) {\r\n this.element.style.height = this.height;\r\n }\r\n else {\r\n HTMLElementUtils.setElementHeight(this.element, 0);\r\n }\r\n }\r\n\r\n public _scrollByPage(event: MouseEvent): void {\r\n let left = this.middleBar.offsetLeft;\r\n let right = left + this.middleBar.offsetWidth;\r\n let x = (event.offsetX === undefined) ? event.layerX : event.offsetX;\r\n if (x > right) {\r\n this.scrollPageDown();\r\n } else if (x < left) {\r\n this.scrollPageUp();\r\n }\r\n }\r\n\r\n public _getRunningSize(net: boolean): number {\r\n let result = this.actualWidth;\r\n if (net) {\r\n let barMinPos = this.actualButtonWidth + 1;\r\n result -= barMinPos * 2;\r\n let barSize = result * (this.viewSize / (this.max - this.min));\r\n if (barSize < this.MIN_BAR_SIZE)\r\n result -= this.MIN_BAR_SIZE - barSize;\r\n }\r\n return result;\r\n }\r\n\r\n public _getOffsetDelta(event: MouseEvent): number {\r\n return this._getOffsetXDelta(event);\r\n }\r\n\r\n public _getOffsetTouchDelta(e: MouseEvent): number {\r\n return this._getOffsetXTouchDelta(e);\r\n }\r\n\r\n public _getScreenContextualLeft(element: HTMLElement): number {\r\n return element.getBoundingClientRect().left;\r\n }\r\n\r\n public _getScreenContextualRight(element: HTMLElement): number {\r\n return element.getBoundingClientRect().right;\r\n }\r\n\r\n public _getScreenMousePos(event: MouseEvent) {\r\n return event.screenX;\r\n }\r\n }\r\n\r\n /** Vertical Scrollbar */\r\n export class VerticalScrollbar extends Scrollbar {\r\n constructor(parentElement: HTMLElement, layoutKind: TablixLayoutKind) {\r\n super(parentElement, layoutKind);\r\n this.width = Scrollbar.DefaultScrollbarWidth;\r\n }\r\n\r\n public _calculateButtonWidth() {\r\n return this.actualWidth;\r\n }\r\n\r\n public _calculateButtonHeight() {\r\n return Math.min(this.actualHeight / 2, Math.max(this.actualWidth, ScrollbarButton.MIN_WIDTH));\r\n }\r\n\r\n public _getMinButtonAngle(): number {\r\n return -90;\r\n }\r\n\r\n public _getMaxButtonAngle(): number {\r\n return 90;\r\n }\r\n\r\n public _setMaxButtonPosition() {\r\n HTMLElementUtils.setElementTop(this.maxButton.element, this.actualHeight - this.actualButtonHeight);\r\n }\r\n\r\n public refresh(): void {\r\n this.arrange();\r\n let runningSize = this.actualHeight - this.actualButtonHeight * 2 - 2;\r\n let barSize = this.viewSize / (this.max - this.min) * runningSize;\r\n if (barSize < this.MIN_BAR_SIZE) {\r\n runningSize -= this.MIN_BAR_SIZE - barSize;\r\n barSize = this.MIN_BAR_SIZE;\r\n }\r\n if (runningSize < 0) {\r\n runningSize = 0;\r\n barSize = 0;\r\n }\r\n let barPos = this.viewMin / (this.max - this.min) * runningSize;\r\n HTMLElementUtils.setElementWidth(this.middleBar, this.actualWidth);\r\n HTMLElementUtils.setElementHeight(this.middleBar, barSize);\r\n HTMLElementUtils.setElementTop(this.middleBar, this.actualButtonHeight + 1 + barPos);\r\n }\r\n\r\n public show(visible: boolean): void {\r\n if (visible === this.visible)\r\n return;\r\n\r\n super.show(visible);\r\n if (visible) {\r\n this.element.style.width = this.width;\r\n }\r\n else {\r\n HTMLElementUtils.setElementWidth(this.element, 0);\r\n }\r\n }\r\n\r\n public _scrollByPage(event: MouseEvent): void {\r\n let top = this.middleBar.offsetTop;\r\n let bottom = top + this.middleBar.offsetHeight;\r\n let y = (event.offsetY === undefined) ? event.layerY : event.offsetY;\r\n if (y > bottom) {\r\n this.scrollPageDown();\r\n } else if (y < top) {\r\n this.scrollPageUp();\r\n }\r\n }\r\n\r\n public _getRunningSize(net: boolean): number {\r\n let result = this.actualHeight;\r\n if (net) {\r\n let barMinPos = this.actualButtonHeight + 1;\r\n result -= barMinPos * 2;\r\n let barSize = result * (this.viewSize / (this.max - this.min));\r\n if (barSize < this.MIN_BAR_SIZE)\r\n result -= this.MIN_BAR_SIZE - barSize;\r\n }\r\n return result;\r\n }\r\n\r\n public _getOffsetDelta(event: MouseEvent): number {\r\n return this._getOffsetYDelta(event);\r\n }\r\n\r\n public _getOffsetTouchDelta(e: MouseEvent): number {\r\n return this._getOffsetYTouchDelta(e);\r\n }\r\n\r\n public _getScreenContextualLeft(element: HTMLElement): number {\r\n return element.getBoundingClientRect().top;\r\n }\r\n\r\n public _getScreenContextualRight(element: HTMLElement): number {\r\n return element.getBoundingClientRect().bottom;\r\n }\r\n\r\n public _getScreenMousePos(event: MouseEvent) {\r\n return event.screenY;\r\n }\r\n }\r\n}\n\n\n/** WEBPACK FOOTER **\n ** ./src/Clients/Visuals/controls/scrollbar/scrollbar.ts\n **/","/*\r\n * Power BI Visualizations\r\n *\r\n * Copyright (c) Microsoft Corporation\r\n * All rights reserved. \r\n * MIT License\r\n *\r\n * Permission is hereby granted, free of charge, to any person obtaining a copy\r\n * of this software and associated documentation files (the \"\"Software\"\"), to deal\r\n * in the Software without restriction, including without limitation the rights\r\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\r\n * copies of the Software, and to permit persons to whom the Software is\r\n * furnished to do so, subject to the following conditions:\r\n * \r\n * The above copyright notice and this permission notice shall be included in \r\n * all copies or substantial portions of the Software.\r\n * \r\n * THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR \r\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, \r\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE \r\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER \r\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\r\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\r\n * THE SOFTWARE.\r\n */\r\n\r\n/// <reference path=\"../../../_references.ts\"/>\r\n\r\nmodule powerbi.visuals.controls.internal {\r\n\r\n const UNSELECTABLE_CLASS_NAME = \"unselectable\";\r\n\r\n /** This class is responsible for tablix header resizing */\r\n export class TablixResizer {\r\n private _element: HTMLElement;\r\n private _handler: ITablixResizeHandler;\r\n private _elementMouseDownWrapper: any;\r\n private _elementMouseMoveWrapper: any;\r\n private _elementMouseOutWrapper: any;\r\n private _elementMouseDoubleClickOutWrapper: any;\r\n private _documentMouseMoveWrapper: any;\r\n private _documentMouseUpWrapper: any;\r\n private _startMousePosition: { x: number; y: number; };\r\n private _originalCursor: string;\r\n\r\n static resizeHandleSize = 4;\r\n static resizeCursor = \"e-resize\";\r\n\r\n constructor(element: HTMLElement, handler: ITablixResizeHandler) {\r\n this._element = element;\r\n this._handler = handler;\r\n this._elementMouseDownWrapper = null;\r\n this._elementMouseMoveWrapper = null;\r\n this._elementMouseOutWrapper = null;\r\n this._documentMouseMoveWrapper = null;\r\n this._documentMouseUpWrapper = null;\r\n this._startMousePosition = null;\r\n this._originalCursor = null;\r\n }\r\n\r\n static addDocumentMouseUpEvent(listener: EventListener): void {\r\n document.addEventListener(\"mouseup\", listener);\r\n }\r\n\r\n static removeDocumentMouseUpEvent(listener: EventListener): void {\r\n document.removeEventListener(\"mouseup\", listener);\r\n }\r\n\r\n static addDocumentMouseMoveEvent(listener: EventListener): void {\r\n document.addEventListener(\"mousemove\", listener);\r\n }\r\n\r\n static removeDocumentMouseMoveEvent(listener: EventListener): void {\r\n document.removeEventListener(\"mousemove\", listener);\r\n }\r\n\r\n static getMouseCoordinates(event: MouseEvent): { x: number; y: number; } {\r\n return { x: event.pageX, y: event.pageY };\r\n }\r\n\r\n static getMouseCoordinateDelta(previous: { x: number; y: number; }, current: { x: number; y: number; }): { x: number; y: number; } {\r\n return { x: current.x - previous.x, y: current.y - previous.y };\r\n }\r\n\r\n public initialize(): void {\r\n this._elementMouseDownWrapper = e => this.onElementMouseDown(<MouseEvent>e);\r\n this._element.addEventListener(\"mousedown\", this._elementMouseDownWrapper);\r\n this._elementMouseMoveWrapper = e => this.onElementMouseMove(<MouseEvent>e);\r\n this._element.addEventListener(\"mousemove\", this._elementMouseMoveWrapper);\r\n this._elementMouseOutWrapper = e => this.onElementMouseOut(<MouseEvent>e);\r\n this._element.addEventListener(\"mouseout\", this._elementMouseOutWrapper);\r\n this._elementMouseDoubleClickOutWrapper = e => this.onElementMouseDoubleClick(<MouseEvent>e);\r\n this._element.addEventListener(\"dblclick\", this._elementMouseDoubleClickOutWrapper);\r\n }\r\n\r\n public uninitialize(): void {\r\n this._element.removeEventListener(\"mousedown\", this._elementMouseDownWrapper);\r\n this._elementMouseDownWrapper = null;\r\n this._element.removeEventListener(\"mousemove\", this._elementMouseMoveWrapper);\r\n this._elementMouseMoveWrapper = null;\r\n this._element.removeEventListener(\"mouseout\", this._elementMouseOutWrapper);\r\n this._elementMouseOutWrapper = null;\r\n this._element.removeEventListener(\"dblclick\", this._elementMouseDoubleClickOutWrapper);\r\n this._elementMouseDoubleClickOutWrapper = null;\r\n }\r\n\r\n public get cell(): TablixCell {\r\n // abstract\r\n debug.assertFail(\"PureVirtualMethod: TablixResizer.cell\");\r\n return null;\r\n }\r\n\r\n public get element(): HTMLElement {\r\n return this._element;\r\n }\r\n\r\n // Protected\r\n public _hotSpot(position: { x: number; y: number; }) {\r\n // abstract\r\n debug.assertFail(\"PureVirtualMethod: TablixResizer._hotSpot\");\r\n return false;\r\n }\r\n\r\n private onElementMouseDown(event: MouseEvent): void {\r\n let position = TablixResizer.getMouseCoordinates(event);\r\n if (!this._hotSpot(position))\r\n return;\r\n\r\n if (\"setCapture\" in this._element) {\r\n this._element.setCapture();\r\n }\r\n\r\n event.cancelBubble = true;\r\n this._startMousePosition = position;\r\n this._documentMouseMoveWrapper = e => this.onDocumentMouseMove(e);\r\n TablixResizer.addDocumentMouseMoveEvent(this._documentMouseMoveWrapper);\r\n this._documentMouseUpWrapper = e => this.onDocumentMouseUp(e);\r\n TablixResizer.addDocumentMouseUpEvent(this._documentMouseUpWrapper);\r\n\r\n if (document.documentElement) {\r\n this._originalCursor = document.documentElement.style.cursor;\r\n document.documentElement.style.cursor = TablixResizer.resizeCursor;\r\n }\r\n\r\n this._handler.onStartResize(this.cell, this._startMousePosition.x, this._startMousePosition.y);\r\n }\r\n\r\n private onElementMouseMove(event: MouseEvent) {\r\n if (!this._startMousePosition) {\r\n if (this._hotSpot(TablixResizer.getMouseCoordinates(event))) {\r\n if (this._originalCursor === null) {\r\n this._originalCursor = this._element.style.cursor;\r\n this._element.style.cursor = TablixResizer.resizeCursor;\r\n }\r\n } else {\r\n if (this._originalCursor !== null) {\r\n this._element.style.cursor = this._originalCursor;\r\n this._originalCursor = null;\r\n }\r\n }\r\n }\r\n }\r\n\r\n private onElementMouseOut(event: MouseEvent) {\r\n if (!this._startMousePosition) {\r\n if (this._originalCursor !== null) {\r\n this._element.style.cursor = this._originalCursor;\r\n this._originalCursor = null;\r\n }\r\n }\r\n }\r\n\r\n private onElementMouseDoubleClick(event: MouseEvent) {\r\n if (!this._hotSpot(TablixResizer.getMouseCoordinates(event)))\r\n return;\r\n\r\n this._handler.onReset(this.cell);\r\n }\r\n\r\n private onDocumentMouseMove(event: MouseEvent): void {\r\n if (!this._startMousePosition)\r\n return;\r\n\r\n let delta = TablixResizer.getMouseCoordinateDelta(this._startMousePosition, TablixResizer.getMouseCoordinates(event));\r\n this._handler.onResize(this.cell, delta.x, delta.y);\r\n // Need to prevent default to prevent mouse move from triggering other effects (VSTS 6720639)\r\n event.preventDefault();\r\n }\r\n\r\n private onDocumentMouseUp(event: MouseEvent): void {\r\n this._startMousePosition = null;\r\n\r\n if (\"releaseCapture\" in this._element) {\r\n this._element.releaseCapture();\r\n }\r\n\r\n TablixResizer.removeDocumentMouseMoveEvent(this._documentMouseMoveWrapper);\r\n this._documentMouseMoveWrapper = null;\r\n\r\n TablixResizer.removeDocumentMouseUpEvent(this._documentMouseUpWrapper);\r\n this._documentMouseUpWrapper = null;\r\n\r\n if (document.documentElement)\r\n document.documentElement.style.cursor = this._originalCursor;\r\n\r\n if (event.preventDefault)\r\n event.preventDefault(); // prevent other events\r\n\r\n this._handler.onEndResize(this.cell);\r\n }\r\n }\r\n\r\n export class TablixDomResizer extends TablixResizer {\r\n private _cell: TablixCell;\r\n constructor(cell: TablixCell, element: HTMLElement, handler: ITablixResizeHandler) {\r\n super(element, handler);\r\n this._cell = cell;\r\n }\r\n\r\n public get cell(): TablixCell {\r\n return this._cell;\r\n }\r\n\r\n // Protected\r\n public _hotSpot(position: { x: number; y: number; }) {\r\n return position.x >= this.element.getBoundingClientRect().right - TablixResizer.resizeHandleSize;\r\n }\r\n }\r\n\r\n export class TablixCellPresenter {\r\n // Attribute used to disable dragging in order to have cell resizing work.\r\n static _dragResizeDisabledAttributeName = \"drag-resize-disabled\";\r\n\r\n private _owner: TablixCell;\r\n\r\n private _tableCell: HTMLTableCellElement;\r\n /** Outer DIV */\r\n private _contentElement: HTMLDivElement;\r\n /** Inner DIV */\r\n private _contentHost: HTMLDivElement;\r\n\r\n private _resizer: TablixResizer;\r\n\r\n public layoutKind: TablixLayoutKind;\r\n\r\n constructor(fitProportionally: boolean, layoutKind: TablixLayoutKind) {\r\n // Table cell will be created once needed\r\n this._tableCell = null; \r\n\r\n // Content element\r\n this._contentElement = TablixUtils.createDiv();\r\n\r\n // Content Host\r\n this._contentHost = TablixUtils.createDiv();\r\n\r\n this.layoutKind = layoutKind;\r\n\r\n this._contentElement.appendChild(this._contentHost);\r\n\r\n this._resizer = null;\r\n }\r\n\r\n public initialize(owner: TablixCell) {\r\n this._owner = owner;\r\n }\r\n\r\n public get owner(): TablixCell {\r\n return this._owner;\r\n }\r\n\r\n public registerTableCell(tableCell: HTMLTableCellElement) {\r\n this._tableCell = tableCell;\r\n\r\n tableCell.appendChild(this._contentElement);\r\n }\r\n\r\n public get tableCell(): HTMLTableCellElement {\r\n return this._tableCell;\r\n }\r\n\r\n /**\r\n * Outer DIV\r\n */\r\n public get contentElement(): HTMLElement {\r\n return this._contentElement;\r\n }\r\n\r\n /**\r\n * Inner DIV\r\n */\r\n public get contentHost(): HTMLElement {\r\n return this._contentHost;\r\n }\r\n\r\n public registerClickHandler(handler: (e: MouseEvent) => any): void {\r\n this._contentElement.onclick = handler;\r\n }\r\n\r\n public unregisterClickHandler(): void {\r\n this._contentElement.onclick = null;\r\n }\r\n\r\n public onContainerWidthChanged(value: number): void {\r\n HTMLElementUtils.setElementWidth(this._contentElement, value);\r\n }\r\n\r\n public onContinerHeightChanged(height: number): void {\r\n HTMLElementUtils.setElementHeight(this._contentElement, height);\r\n }\r\n\r\n public onColumnSpanChanged(value: number): void {\r\n this._tableCell.colSpan = value;\r\n }\r\n\r\n public onRowSpanChanged(value: number): void {\r\n this._tableCell.rowSpan = value;\r\n }\r\n\r\n public onTextAlignChanged(value: string): void {\r\n this._tableCell.style.textAlign = value;\r\n }\r\n\r\n public onClear(): void {\r\n this._contentHost.className = \"\";\r\n this._tableCell.className = \"\";\r\n }\r\n\r\n public onHorizontalScroll(width: number, offset: number): void {\r\n HTMLElementUtils.setElementLeft(this._contentHost, offset);\r\n HTMLElementUtils.setElementWidth(this._contentHost, width);\r\n }\r\n\r\n public onVerticalScroll(height: number, offset: number): void {\r\n HTMLElementUtils.setElementTop(this._contentHost, offset);\r\n HTMLElementUtils.setElementHeight(this._contentHost, height);\r\n }\r\n\r\n public onInitializeScrolling(): void {\r\n HTMLElementUtils.setElementLeft(this._contentHost, 0);\r\n HTMLElementUtils.setElementTop(this._contentHost, 0);\r\n HTMLElementUtils.setElementWidth(this._contentHost, -1);\r\n HTMLElementUtils.setElementHeight(this._contentHost, -1);\r\n }\r\n\r\n public enableHorizontalResize(enable: boolean, handler: ITablixResizeHandler): void {\r\n if (enable === (this._resizer !== null))\r\n return;\r\n\r\n if (enable) {\r\n this._resizer = new TablixDomResizer(this._owner, this._tableCell, handler);\r\n this._resizer.initialize();\r\n } else {\r\n this._resizer.uninitialize();\r\n this._resizer = null;\r\n }\r\n }\r\n\r\n /**\r\n * In order to allow dragging of the tableCell we need to\r\n * disable dragging of the container of the cell in IE.\r\n */\r\n public disableDragResize() {\r\n this._tableCell.setAttribute(TablixCellPresenter._dragResizeDisabledAttributeName, \"true\");\r\n }\r\n }\r\n\r\n export class TablixRowPresenter {\r\n private _row: TablixRow;\r\n private _tableRow: HTMLTableRowElement;\r\n private _fitProportionally: boolean;\r\n\r\n constructor(fitProportionally: boolean) {\r\n // Table row will be created once needed\r\n this._tableRow = null;\r\n this._fitProportionally = fitProportionally;\r\n }\r\n\r\n public initialize(row: TablixRow) {\r\n this._row = row;\r\n }\r\n\r\n public createCellPresenter(layoutKind: controls.TablixLayoutKind): TablixCellPresenter {\r\n return new TablixCellPresenter(this._fitProportionally, layoutKind);\r\n }\r\n\r\n public registerRow(tableRow: HTMLTableRowElement) {\r\n this._tableRow = tableRow;\r\n }\r\n\r\n public onAppendCell(cell: TablixCell): void {\r\n let presenter = cell._presenter;\r\n\r\n if (presenter.tableCell === null) {\r\n // For performance reason we use InsertCell() to create new table cells instead of AppendChild()\r\n // We use -1 to insert at the end (that's the cross-browser way of doing it)\r\n let tableCell = this._tableRow.insertCell(-1);\r\n presenter.registerTableCell(<HTMLTableCellElement>tableCell);\r\n }\r\n else {\r\n this._tableRow.appendChild(presenter.tableCell);\r\n }\r\n }\r\n\r\n public onInsertCellBefore(cell: TablixCell, refCell: TablixCell): void {\r\n debug.assertValue(refCell._presenter.tableCell, 'refTableCell');\r\n\r\n let presenter = cell._presenter;\r\n\r\n if (presenter.tableCell === null) {\r\n // For performance reasons we use InsertCell() to create new table cells instead of AppendChild()\r\n let tableCell = this._tableRow.insertCell(Math.max(0, refCell._presenter.tableCell.cellIndex - 1));\r\n presenter.registerTableCell(<HTMLTableCellElement>tableCell);\r\n }\r\n else {\r\n this._tableRow.insertBefore(cell._presenter.tableCell, refCell._presenter.tableCell);\r\n }\r\n }\r\n\r\n public onRemoveCell(cell: TablixCell): void {\r\n this._tableRow.removeChild(cell._presenter.tableCell);\r\n }\r\n\r\n public getHeight(): number {\r\n return this.getCellHeight(this._row.getTablixCell());\r\n }\r\n\r\n public getCellHeight(cell: ITablixCell): number {\r\n debug.assertFail(\"PureVirtualMethod: TablixRowPresenter.getCellHeight\");\r\n return -1;\r\n }\r\n\r\n public getCellContentHeight(cell: ITablixCell): number {\r\n debug.assertFail(\"PureVirtualMethod: TablixRowPresenter.getCellHeight\");\r\n return -1;\r\n }\r\n\r\n public get tableRow(): HTMLTableRowElement {\r\n return this._tableRow;\r\n }\r\n }\r\n\r\n export class DashboardRowPresenter extends TablixRowPresenter {\r\n private _gridPresenter: DashboardTablixGridPresenter;\r\n\r\n constructor(gridPresenter: DashboardTablixGridPresenter, fitProportionally: boolean) {\r\n super(fitProportionally);\r\n\r\n this._gridPresenter = gridPresenter;\r\n }\r\n\r\n public getCellHeight(cell: ITablixCell): number {\r\n return cell.containerHeight;\r\n }\r\n\r\n public getCellContentHeight(cell: ITablixCell): number {\r\n return cell.contentHeight;\r\n }\r\n\r\n }\r\n\r\n export class CanvasRowPresenter extends TablixRowPresenter {\r\n public getCellHeight(cell: ITablixCell): number {\r\n return cell.containerHeight;\r\n }\r\n\r\n public getCellContentHeight(cell: ITablixCell): number {\r\n return cell.contentHeight;\r\n }\r\n\r\n }\r\n\r\n export class TablixColumnPresenter {\r\n protected _column: TablixColumn;\r\n\r\n public initialize(column: TablixColumn) {\r\n this._column = column;\r\n }\r\n\r\n public getWidth(): number {\r\n let width = this.getPersistedWidth();\r\n if (width == null)\r\n width = this.getCellWidth(this._column.getTablixCell());\r\n\r\n return width;\r\n }\r\n\r\n public getPersistedWidth(): number {\r\n debug.assertFail(\"PureVirtualMethod: TablixColumnPresenter.getPersistedWidth\");\r\n return -1;\r\n }\r\n\r\n public getCellWidth(cell: ITablixCell): number {\r\n debug.assertFail(\"PureVirtualMethod: TablixColumnPresenter.getCellWidth\");\r\n return -1;\r\n }\r\n }\r\n\r\n export class DashboardColumnPresenter extends TablixColumnPresenter {\r\n private _gridPresenter: DashboardTablixGridPresenter;\r\n\r\n constructor(gridPresenter: DashboardTablixGridPresenter) {\r\n super();\r\n\r\n this._gridPresenter = gridPresenter;\r\n }\r\n\r\n public getPersistedWidth(): number {\r\n return this._gridPresenter.sizeComputationManager.cellWidth;\r\n }\r\n\r\n public getCellWidth(cell: ITablixCell): number {\r\n return this._gridPresenter.sizeComputationManager.cellWidth;\r\n }\r\n }\r\n\r\n export class CanvasColumnPresenter extends TablixColumnPresenter {\r\n private _gridPresenter: CanvasTablixGridPresenter;\r\n private _columnIndex: number;\r\n\r\n constructor(gridPresenter: CanvasTablixGridPresenter, index: number) {\r\n super();\r\n this._gridPresenter = gridPresenter;\r\n this._columnIndex = index;\r\n }\r\n\r\n public getPersistedWidth(): number {\r\n return this._gridPresenter.getPersistedColumnWidth(this._column);\r\n }\r\n\r\n public getCellWidth(cell: ITablixCell): number {\r\n let tablixCell = <TablixCell>cell;\r\n\r\n if (!tablixCell._presenter)\r\n return 0;\r\n\r\n return cell.contentWidth;\r\n }\r\n }\r\n\r\n export class TablixGridPresenter {\r\n protected _table: HTMLTableElement;\r\n protected _owner: TablixGrid;\r\n\r\n private _footerTable: HTMLTableElement;\r\n private _columnWidthManager: TablixColumnWidthManager;\r\n\r\n constructor(columnWidthManager?: TablixColumnWidthManager) {\r\n // Main Table\r\n this._table = TablixUtils.createTable();\r\n this._table.className = UNSELECTABLE_CLASS_NAME;\r\n\r\n // Footer Table\r\n this._footerTable = TablixUtils.createTable();\r\n this._footerTable.className = UNSELECTABLE_CLASS_NAME;\r\n\r\n // ColumnWidthManager\r\n this._columnWidthManager = columnWidthManager;\r\n }\r\n\r\n public initialize(owner: TablixGrid, gridHost: HTMLElement, footerHost: HTMLElement, control: TablixControl) {\r\n this._owner = owner;\r\n gridHost.appendChild(this._table);\r\n footerHost.appendChild(this._footerTable);\r\n }\r\n\r\n public getWidth(): number {\r\n debug.assertFail(\"PureVirtualMethod: TablixGridPresenter.getWidth\");\r\n return -1;\r\n }\r\n\r\n public getHeight(): number {\r\n debug.assertFail(\"PureVirtualMethod: TablixGridPresenter.getHeight\");\r\n return -1;\r\n }\r\n\r\n public getScreenToCssRatioX(): number {\r\n return 1;\r\n }\r\n\r\n public getScreenToCssRatioY(): number {\r\n return 1;\r\n }\r\n\r\n public createRowPresenter(): TablixRowPresenter {\r\n debug.assertFail(\"PureVirtualMethod: TablixGridPresenter.createRowPresenter\");\r\n return null;\r\n }\r\n\r\n public createColumnPresenter(index: number): TablixColumnPresenter {\r\n debug.assertFail(\"PureVirtualMethod: TablixGridPresenter.createColumnPresenter\");\r\n return null;\r\n }\r\n\r\n public onAppendRow(row: TablixRow): void {\r\n let presenter = row.presenter;\r\n\r\n if (presenter.tableRow === null) {\r\n // For performance reason we use InsertRow() to create new table cells instead of AppendChild()\r\n // We use -1 to insert at the end (that's the cross-browser way of doing it)\r\n let tableRow = this._table.insertRow(-1);\r\n presenter.registerRow(<HTMLTableRowElement>tableRow);\r\n }\r\n else {\r\n this._table.tBodies[0].appendChild(row.presenter.tableRow);\r\n }\r\n }\r\n\r\n public onInsertRowBefore(row: TablixRow, refRow: TablixRow): void {\r\n debug.assertValue(refRow.presenter.tableRow, 'refTableRow');\r\n\r\n let presenter = row.presenter;\r\n\r\n if (presenter.tableRow === null) {\r\n // For performance reason we use InsertRow() to create new table cells instead of AppendChild()\r\n let tableRow = this._table.insertRow(Math.max(0, refRow.presenter.tableRow.rowIndex - 1));\r\n presenter.registerRow(<HTMLTableRowElement>tableRow);\r\n }\r\n else {\r\n this._table.tBodies[0].insertBefore(row.presenter.tableRow, refRow.presenter.tableRow);\r\n }\r\n }\r\n\r\n public onRemoveRow(row: TablixRow): void {\r\n this._table.tBodies[0].removeChild(row.presenter.tableRow);\r\n }\r\n\r\n public onAddFooterRow(row: TablixRow): void {\r\n let presenter = row.presenter;\r\n\r\n if (presenter.tableRow === null) {\r\n // For performance reason we use InsertRow() to create new table cells instead of AppendChild()\r\n // We use -1 to insert at the end (that's the cross-browser way of doing it)\r\n let tableRow = this._footerTable.insertRow(-1);\r\n presenter.registerRow(<HTMLTableRowElement>tableRow);\r\n }\r\n else {\r\n this._footerTable.tBodies[0].appendChild(row.presenter.tableRow);\r\n }\r\n }\r\n\r\n public onClear(): void {\r\n HTMLElementUtils.clearChildren(this._table);\r\n HTMLElementUtils.clearChildren(this._footerTable);\r\n }\r\n\r\n public onFillColumnsProportionallyChanged(value: boolean): void {\r\n if (value) {\r\n this._table.style.width = \"100%\";\r\n this._footerTable.style.width = \"100%\";\r\n }\r\n else {\r\n this._table.style.width = \"auto\";\r\n this._footerTable.style.width = \"auto\";\r\n }\r\n }\r\n\r\n public invokeColumnResizeEndCallback(column: TablixColumn, width: number): void {\r\n if (this._columnWidthManager)\r\n this._columnWidthManager.onColumnWidthChanged(TablixColumnWidthManager.getColumnQueryName(column), width);\r\n }\r\n\r\n public getPersistedColumnWidth(column: TablixColumn): number {\r\n if (this._columnWidthManager) {\r\n return this._columnWidthManager.getPersistedColumnWidth(TablixColumnWidthManager.getColumnQueryName(column));\r\n }\r\n }\r\n }\r\n\r\n export class DashboardTablixGridPresenter extends TablixGridPresenter {\r\n private _sizeComputationManager: SizeComputationManager;\r\n\r\n constructor(sizeComputationManager: SizeComputationManager) {\r\n super();\r\n\r\n this._sizeComputationManager = sizeComputationManager;\r\n }\r\n\r\n public createRowPresenter(): TablixRowPresenter {\r\n return new DashboardRowPresenter(this, this._owner.fillColumnsProportionally);\r\n }\r\n\r\n public createColumnPresenter(index: number): TablixColumnPresenter {\r\n return new DashboardColumnPresenter(this);\r\n }\r\n\r\n public get sizeComputationManager(): SizeComputationManager {\r\n return this._sizeComputationManager;\r\n }\r\n\r\n public getWidth(): number {\r\n return this._sizeComputationManager.gridWidth;\r\n }\r\n\r\n public getHeight(): number {\r\n return this._sizeComputationManager.gridHeight;\r\n }\r\n }\r\n\r\n export class CanvasTablixGridPresenter extends TablixGridPresenter {\r\n\r\n constructor(columnWidthManager: TablixColumnWidthManager) {\r\n super(columnWidthManager);\r\n }\r\n\r\n public createRowPresenter(): TablixRowPresenter {\r\n return new CanvasRowPresenter(this._owner.fillColumnsProportionally);\r\n }\r\n\r\n public createColumnPresenter(index: number): TablixColumnPresenter {\r\n return new CanvasColumnPresenter(this, index);\r\n }\r\n\r\n public getWidth(): number {\r\n return HTMLElementUtils.getElementWidth(this._table);\r\n }\r\n\r\n public getHeight(): number {\r\n return HTMLElementUtils.getElementHeight(this._table);\r\n }\r\n }\r\n}\n\n\n/** WEBPACK FOOTER **\n ** ./src/Clients/Visuals/controls/tablix/internal/tablixGridPresenter.ts\n **/","/*\r\n * Power BI Visualizations\r\n *\r\n * Copyright (c) Microsoft Corporation\r\n * All rights reserved. \r\n * MIT License\r\n *\r\n * Permission is hereby granted, free of charge, to any person obtaining a copy\r\n * of this software and associated documentation files (the \"\"Software\"\"), to deal\r\n * in the Software without restriction, including without limitation the rights\r\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\r\n * copies of the Software, and to permit persons to whom the Software is\r\n * furnished to do so, subject to the following conditions:\r\n * \r\n * The above copyright notice and this permission notice shall be included in \r\n * all copies or substantial portions of the Software.\r\n * \r\n * THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR \r\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, \r\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE \r\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER \r\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\r\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\r\n * THE SOFTWARE.\r\n */\r\n\r\n/// <reference path=\"../../../_references.ts\"/>\r\n\r\nmodule powerbi.visuals.controls.internal {\r\n\r\n /**\r\n * Base class for Tablix realization manager.\r\n */\r\n export class TablixDimensionRealizationManager {\r\n private _realizedLeavesCount: number;\r\n private _adjustmentFactor: number;\r\n private _itemsToRealizeCount: number;\r\n private _itemsEstimatedContextualWidth: number;\r\n private _binder: ITablixBinder;\r\n\r\n constructor(binder: ITablixBinder) {\r\n this._binder = binder;\r\n this._adjustmentFactor = 1;\r\n }\r\n\r\n public _getOwner(): DimensionLayoutManager {\r\n debug.assertFail(\"PureVirtualMethod: DimensionLayoutManager.getOwner\");\r\n return null;\r\n }\r\n\r\n public get binder(): ITablixBinder {\r\n return this._binder;\r\n }\r\n\r\n public get adjustmentFactor(): number {\r\n return this._adjustmentFactor;\r\n }\r\n\r\n public get itemsToRealizeCount(): number {\r\n return this._itemsToRealizeCount;\r\n }\r\n\r\n public set itemsToRealizeCount(count: number) {\r\n this._itemsToRealizeCount = count;\r\n }\r\n\r\n public get itemsEstimatedContextualWidth(): number {\r\n return this._itemsEstimatedContextualWidth;\r\n }\r\n\r\n public set itemsEstimatedContextualWidth(contextualWidth: number) {\r\n this._itemsEstimatedContextualWidth = contextualWidth;\r\n }\r\n\r\n public onStartRenderingIteration(): void {\r\n let owner = this._getOwner();\r\n if (owner.measureEnabled && !owner.done) {\r\n this._getEstimatedItemsToRealizeCount();\r\n }\r\n this._realizedLeavesCount = 0;\r\n }\r\n\r\n public onEndRenderingIteration(gridContextualWidth: number, filled: boolean): void {\r\n if (!filled && !this._getOwner().allItemsRealized)\r\n this._adjustmentFactor *= this._getSizeAdjustment(gridContextualWidth);\r\n }\r\n\r\n public onEndRenderingSession(): void {\r\n this._adjustmentFactor = 1;\r\n }\r\n\r\n public onCornerCellRealized(item: any, cell: ITablixCell): void {\r\n }\r\n \r\n public onHeaderRealized(item: any, cell: ITablixCell, leaf: boolean): void {\r\n if (leaf) {\r\n this._realizedLeavesCount++;\r\n }\r\n }\r\n\r\n public get needsToRealize(): boolean {\r\n return this._realizedLeavesCount < this._itemsToRealizeCount;\r\n }\r\n\r\n public _getEstimatedItemsToRealizeCount(): void {\r\n debug.assertFail(\"PureVirtualMethod: TablixDimensionRealizationManager._calculateItemsToRealize\");\r\n }\r\n\r\n public _getSizeAdjustment(gridContextualWidth: number): number {\r\n debug.assertFail(\"PureVirtualMethod: TablixDimensionRealizationManager._getSizeAdjustment\");\r\n return 1;\r\n }\r\n }\r\n\r\n /** \r\n * DOM implementation for Row Tablix realization manager.\r\n */\r\n export class RowRealizationManager extends TablixDimensionRealizationManager {\r\n private _owner: RowLayoutManager;\r\n\r\n public set owner(owner: RowLayoutManager) {\r\n this._owner = owner;\r\n }\r\n\r\n public _getOwner(): DimensionLayoutManager {\r\n return this._owner;\r\n }\r\n\r\n public _getEstimatedItemsToRealizeCount(): void {\r\n this.estimateRowsToRealizeCount();\r\n }\r\n\r\n private estimateRowsToRealizeCount(): void {\r\n debug.assertValue(this._owner, '_owner');\r\n\r\n if (!this._owner.dimension.model) {\r\n this.itemsToRealizeCount = 0;\r\n return;\r\n }\r\n\r\n if (this._owner.alignToEnd)\r\n this.itemsToRealizeCount = this._owner.dimension.getItemsCount() - this._owner.dimension.getIntegerScrollOffset() + 1;\r\n else\r\n this.itemsToRealizeCount = Math.ceil((this._owner.contextualWidthToFill / (this._owner.owner.getEstimatedRowHeight() * this.adjustmentFactor)) + this._owner.dimension.getFractionScrollOffset()) - this._owner.otherLayoutManager.dimension.getDepth() + 1;\r\n }\r\n\r\n public getEstimatedRowHierarchyWidth(): number {\r\n if (!this._owner.dimension.model || this._owner.dimension.getItemsCount() === 0)\r\n return 0;\r\n\r\n let levels: RowWidths = new RowWidths();\r\n this.updateRowHiearchyEstimatedWidth(this._owner.dimension.model, this._owner.dimension._hierarchyNavigator.getIndex(this._owner.dimension.getFirstVisibleItem(0)), levels);\r\n\r\n let levelsArray: RowWidth[] = levels.items;\r\n let levelCount: number = levelsArray.length;\r\n\r\n let width = 0;\r\n\r\n for (let i = 0; i < levelCount; i++) {\r\n let level = levelsArray[i];\r\n\r\n if (level.maxNonLeafWidth !== 0)\r\n width += level.maxNonLeafWidth;\r\n else\r\n width += level.maxLeafWidth;\r\n }\r\n\r\n return width;\r\n }\r\n\r\n private updateRowHiearchyEstimatedWidth(items: any, firstVisibleIndex: number, levels: RowWidths) {\r\n if (firstVisibleIndex < 0)\r\n return;\r\n\r\n let hierarchyNavigator: ITablixHierarchyNavigator = this._owner.owner.owner.hierarchyNavigator;\r\n let binder: ITablixBinder = this.binder;\r\n let length = hierarchyNavigator.getCount(items);\r\n\r\n for (let i = firstVisibleIndex; i < length; i++) {\r\n if (levels.leafCount === this.itemsToRealizeCount)\r\n return;\r\n let item: any = hierarchyNavigator.getAt(items, i);\r\n let label = binder.getHeaderLabel(item);\r\n let itemWidth = this._owner.getEstimatedHeaderWidth(label, firstVisibleIndex);\r\n let isLeaf: boolean = hierarchyNavigator.isLeaf(item);\r\n let l: number = hierarchyNavigator.getLevel(item);\r\n\r\n let level = levels.items[l];\r\n if (!level) {\r\n level = new RowWidth();\r\n levels.items[l] = level;\r\n }\r\n\r\n if (isLeaf) {\r\n level.maxLeafWidth = Math.max(level.maxLeafWidth, itemWidth);\r\n levels.leafCount = levels.leafCount + 1;\r\n }\r\n else {\r\n level.maxNonLeafWidth = Math.max(level.maxNonLeafWidth, itemWidth);\r\n this.updateRowHiearchyEstimatedWidth(hierarchyNavigator.getChildren(item), this._owner.dimension.getFirstVisibleChildIndex(item), levels);\r\n }\r\n }\r\n }\r\n\r\n public _getSizeAdjustment(gridContextualWidth: number): number {\r\n return gridContextualWidth / ((this._owner.getRealizedItemsCount() - this._owner.dimension.getFractionScrollOffset()) * this._owner.owner.getEstimatedRowHeight());\r\n }\r\n }\r\n\r\n /**\r\n * DOM implementation for Column Tablix realization manager.\r\n */\r\n export class ColumnRealizationManager extends TablixDimensionRealizationManager {\r\n private _owner: ColumnLayoutManager;\r\n\r\n public set owner(owner: ColumnLayoutManager) {\r\n this._owner = owner;\r\n }\r\n\r\n public _getOwner(): DimensionLayoutManager {\r\n return this._owner;\r\n }\r\n\r\n public _getEstimatedItemsToRealizeCount(): void {\r\n this.estimateColumnsToRealizeCount(this.getEstimatedRowHierarchyWidth());\r\n }\r\n\r\n private get rowRealizationManager(): RowRealizationManager {\r\n return <RowRealizationManager>this._owner.otherLayoutManager.realizationManager;\r\n }\r\n\r\n private getEstimatedRowHierarchyWidth(): number {\r\n if (this._owner.otherLayoutManager.done)\r\n return this._owner.getOtherHierarchyContextualHeight();\r\n\r\n return this.rowRealizationManager.getEstimatedRowHierarchyWidth() * this.adjustmentFactor;\r\n }\r\n\r\n private estimateColumnsToRealizeCount(rowHierarchyWidth: number): void {\r\n let widthToFill: number = this._owner.contextualWidthToFill - rowHierarchyWidth;\r\n\r\n if (!this._owner.dimension.model || Double.lessOrEqualWithPrecision(widthToFill, 0, DimensionLayoutManager._pixelPrecision)) {\r\n this.itemsToRealizeCount = 0;\r\n return;\r\n }\r\n\r\n let binder: ITablixBinder = this.binder;\r\n let hierarchyNavigator: ITablixHierarchyNavigator = this._owner.owner.owner.hierarchyNavigator;\r\n\r\n let startColumnIndex: number = this._owner.dimension.getIntegerScrollOffset();\r\n let endColumnIndex: number = this._owner.dimension.getItemsCount();\r\n let columnCount = endColumnIndex - startColumnIndex;\r\n\r\n let startRowIndex: number = this._owner.otherLayoutManager.dimension.getIntegerScrollOffset();\r\n let endRowIndex = this._owner.otherLayoutManager.dimension.getItemsCount();\r\n\r\n this.itemsEstimatedContextualWidth = 0;\r\n\r\n if (this._owner.alignToEnd) {\r\n this.itemsToRealizeCount = columnCount;\r\n return;\r\n }\r\n\r\n for (let i = startColumnIndex; i < endColumnIndex; i++) {\r\n if (Double.greaterOrEqualWithPrecision(this.itemsEstimatedContextualWidth, widthToFill, DimensionLayoutManager._pixelPrecision)) {\r\n this.itemsToRealizeCount = i - startColumnIndex;\r\n return;\r\n }\r\n\r\n let maxWidth = 0;\r\n let visibleSizeRatio;\r\n\r\n if (i === startColumnIndex) {\r\n visibleSizeRatio = this._owner.getVisibleSizeRatio();\r\n }\r\n else {\r\n visibleSizeRatio = 1;\r\n }\r\n\r\n let columnMember: any = hierarchyNavigator.getLeafAt(this._owner.dimension.model, i);\r\n let label = binder.getHeaderLabel(columnMember);\r\n maxWidth = Math.max(maxWidth, this._owner.getEstimatedHeaderWidth(label, i));\r\n\r\n for (let j = startRowIndex; j < endRowIndex; j++) {\r\n let intersection = hierarchyNavigator.getIntersection(hierarchyNavigator.getLeafAt(this._owner.otherLayoutManager.dimension.model, j), columnMember);\r\n label = binder.getCellContent(intersection);\r\n maxWidth = Math.max(maxWidth, this._owner.getEstimatedBodyCellWidth(label));\r\n }\r\n\r\n this.itemsEstimatedContextualWidth += maxWidth * visibleSizeRatio * this.adjustmentFactor;\r\n }\r\n\r\n this.itemsToRealizeCount = columnCount;\r\n }\r\n\r\n public _getSizeAdjustment(gridContextualWidth: number): number {\r\n return gridContextualWidth / (this.getEstimatedRowHierarchyWidth() + this.itemsEstimatedContextualWidth);\r\n }\r\n }\r\n\r\n export class RowWidths {\r\n public items: RowWidth[];\r\n public leafCount;\r\n\r\n constructor() {\r\n this.items = [];\r\n this.leafCount = 0;\r\n }\r\n }\r\n\r\n export class RowWidth {\r\n public maxLeafWidth: number = 0;\r\n public maxNonLeafWidth: number = 0;\r\n }\r\n}\n\n\n/** WEBPACK FOOTER **\n ** ./src/Clients/Visuals/controls/tablix/internal/tablixRealizationManager.ts\n **/","/*\r\n * Power BI Visualizations\r\n *\r\n * Copyright (c) Microsoft Corporation\r\n * All rights reserved. \r\n * MIT License\r\n *\r\n * Permission is hereby granted, free of charge, to any person obtaining a copy\r\n * of this software and associated documentation files (the \"\"Software\"\"), to deal\r\n * in the Software without restriction, including without limitation the rights\r\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\r\n * copies of the Software, and to permit persons to whom the Software is\r\n * furnished to do so, subject to the following conditions:\r\n * \r\n * The above copyright notice and this permission notice shall be included in \r\n * all copies or substantial portions of the Software.\r\n * \r\n * THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR \r\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, \r\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE \r\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER \r\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\r\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\r\n * THE SOFTWARE.\r\n */\r\n\r\n/// <reference path=\"../../../_references.ts\"/>\r\n\r\nmodule powerbi.visuals.controls.internal {\r\n\r\n export interface ITablixResizeHandler {\r\n onStartResize(cell: TablixCell, currentX: number, currentY: number): void;\r\n onResize(cell: TablixCell, deltaX: number, deltaY: number): void;\r\n onEndResize(cell: TablixCell);\r\n onReset(cell: TablixCell);\r\n }\r\n\r\n /** \r\n * Internal interface to abstract the tablix row/column.\r\n */\r\n export interface ITablixGridItem {\r\n calculateSize(): number;\r\n onResize(size: number): void;\r\n onResizeEnd(size: number): void;\r\n fixSize(): void;\r\n\r\n /**\r\n * In case the parent column/row header size is bigger than the sum of the children,\r\n * the size of the last item is adjusted to compensate the difference.\r\n */\r\n setAligningContextualWidth(size: number): void;\r\n getAligningContextualWidth(): number;\r\n\r\n getContextualWidth(): number;\r\n getContentContextualWidth(): number;\r\n\r\n getIndex(grid: TablixGrid): number;\r\n\r\n getHeaders(): TablixCell[];\r\n getOtherDimensionHeaders(): TablixCell[];\r\n\r\n getOtherDimensionOwner(cell: TablixCell): ITablixGridItem;\r\n\r\n getCellIContentContextualWidth(cell: TablixCell): number;\r\n getCellContextualSpan(cell: TablixCell): number;\r\n }\r\n\r\n export class TablixCell implements ITablixCell {\r\n private _horizontalOffset: number;\r\n private _verticalOffset: number;\r\n private _colSpan: number;\r\n private _rowSpan: number;\r\n private _textAlign: string;\r\n private _containerWidth: number;\r\n private _containerHeight: number;\r\n private _scrollable = false;\r\n\r\n public _column: TablixColumn; // internal\r\n public _row: TablixRow; // internal\r\n\r\n public type: TablixCellType;\r\n public item: any;\r\n\r\n public _presenter: TablixCellPresenter; // internal\r\n public extension: TablixCellPresenter;\r\n\r\n public position: internal.TablixUtils.CellPosition;\r\n\r\n // Height of the content, set in Binder\r\n public contentHeight: number;\r\n\r\n // Width of the content, set in Binder\r\n public contentWidth: number;\r\n\r\n constructor(presenter: TablixCellPresenter, extension: TablixCellPresenter, row: TablixRow) {\r\n this._presenter = presenter;\r\n this.extension = extension;\r\n this._presenter.initialize(this);\r\n this._row = row;\r\n this.item = null;\r\n this.type = null;\r\n this._horizontalOffset = 0;\r\n this._verticalOffset = 0;\r\n this._colSpan = 1;\r\n this._rowSpan = 1;\r\n this._containerWidth = -1;\r\n this._containerHeight = -1;\r\n this.contentHeight = this.contentWidth = 0;\r\n this.position = new internal.TablixUtils.CellPosition();\r\n }\r\n\r\n public unfixRowHeight() {\r\n this._row.unfixSize();\r\n }\r\n\r\n public get colSpan(): number {\r\n return this._colSpan;\r\n }\r\n\r\n public set colSpan(value: number) {\r\n if (this._colSpan !== value) {\r\n this._presenter.onColumnSpanChanged(value);\r\n this._colSpan = value;\r\n this._presenter.onContainerWidthChanged(-1);\r\n }\r\n }\r\n\r\n public get rowSpan(): number {\r\n return this._rowSpan;\r\n }\r\n\r\n public set rowSpan(value: number) {\r\n if (this._rowSpan !== value) {\r\n this._presenter.onRowSpanChanged(value);\r\n this._rowSpan = value;\r\n this._row.unfixSize();\r\n }\r\n }\r\n\r\n public getCellSpanningHeight(): number {\r\n return this._row.getCellSpanningHeight(this);\r\n }\r\n\r\n public get textAlign(): string {\r\n return this._textAlign;\r\n }\r\n\r\n public set textAlign(value: string) {\r\n if (value !== this._textAlign) {\r\n this._presenter.onTextAlignChanged(value);\r\n this._textAlign = value;\r\n }\r\n }\r\n\r\n public get horizontalOffset(): number {\r\n return this._horizontalOffset;\r\n }\r\n\r\n public get verticalOffset(): number {\r\n return this._verticalOffset;\r\n }\r\n\r\n private isScrollable(): boolean {\r\n return this._scrollable;\r\n }\r\n\r\n public clear(): void {\r\n if (this.isScrollable()) {\r\n this.initializeScrolling();\r\n }\r\n\r\n this._presenter.onClear();\r\n this.setContainerWidth(-1);\r\n this.setContainerHeight(-1);\r\n this.contentHeight = this.contentWidth = 0;\r\n }\r\n\r\n private initializeScrolling() {\r\n this._presenter.onInitializeScrolling();\r\n this._horizontalOffset = 0;\r\n this._verticalOffset = 0;\r\n if (this.colSpan === 1)\r\n this.setContainerWidth(-1);\r\n if (this.rowSpan === 1)\r\n this.setContainerHeight(-1);\r\n }\r\n\r\n public prepare(scrollable: boolean): void {\r\n if (this.isScrollable())\r\n this.initializeScrolling();\r\n\r\n this._scrollable = scrollable;\r\n }\r\n\r\n public scrollVertically(height: number, offset: number): void {\r\n // Ceiling the offset because setting a fraction Width on the TD will ceil it\r\n // We need to let the TD and the OuterDiv to align in order for Borders to touch\r\n let offsetInPixels = Math.ceil(- height * offset);\r\n this._verticalOffset = offsetInPixels;\r\n\r\n if (this.isScrollable()) {\r\n this._presenter.onVerticalScroll(height, offsetInPixels);\r\n this.setContainerHeight(height + offsetInPixels);\r\n }\r\n else {\r\n this.setContainerHeight(this._row.getCellSpanningHeight(this) + offsetInPixels);\r\n }\r\n }\r\n\r\n public scrollHorizontally(width: number, offset: number) {\r\n if (!this.isScrollable()) {\r\n return;\r\n }\r\n\r\n this._presenter.onHorizontalScroll(width, offset);\r\n this.setContainerWidth(width + offset);\r\n }\r\n\r\n public setContainerWidth(value: number): void {\r\n if (value === this._containerWidth)\r\n return;\r\n\r\n this._containerWidth = value;\r\n this._presenter.onContainerWidthChanged(value);\r\n }\r\n\r\n public get containerWidth(): number {\r\n return this._containerWidth;\r\n }\r\n\r\n public setContainerHeight(value: number): void {\r\n if (value < 0)\r\n value = -1;\r\n\r\n if (value === this._containerHeight)\r\n return;\r\n\r\n this._containerHeight = value;\r\n this._presenter.onContinerHeightChanged(value);\r\n }\r\n\r\n public get containerHeight(): number {\r\n return this._containerHeight;\r\n }\r\n\r\n public applyStyle(style: TablixUtils.CellStyle): void {\r\n if (style) {\r\n style.applyStyle(this);\r\n this.contentHeight += style.getExtraBottom() + style.getExtraTop();\r\n this.contentWidth += style.getExtraLeft() + style.getExtraRight();\r\n }\r\n }\r\n\r\n public enableHorizontalResize(enable: boolean, handler: ITablixResizeHandler): void {\r\n this._presenter.enableHorizontalResize(enable, handler);\r\n }\r\n }\r\n\r\n export class TablixColumn implements ITablixGridItem {\r\n public _realizedColumnHeaders: TablixCell[]; // internal\r\n public _realizedCornerCells: TablixCell[]; // internal\r\n public _realizedRowHeaders: TablixCell[]; // internal\r\n public _realizedBodyCells: TablixCell[]; // internal\r\n private _items: any[];\r\n private _itemType: TablixCellType;\r\n private _footerCell: TablixCell;\r\n\r\n private _containerWidth: number;\r\n private _width: number;\r\n private _sizeFixed: boolean;\r\n\r\n private _aligningWidth: number;\r\n private _fixedToAligningWidth: boolean; // move to base class to have it for both rows and columns\r\n\r\n private _presenter: TablixColumnPresenter;\r\n private _owner: TablixGrid;\r\n private _columnIndex: number;\r\n\r\n constructor(presenter: TablixColumnPresenter, columnIndex: number) {\r\n this._presenter = presenter;\r\n this._presenter.initialize(this);\r\n this._containerWidth = -1;\r\n this._width = -1;\r\n this._sizeFixed = false;\r\n this._aligningWidth = -1;\r\n this._fixedToAligningWidth = false;\r\n this._items = [];\r\n this._itemType = null;\r\n this._footerCell = null;\r\n this._columnIndex = columnIndex;\r\n }\r\n\r\n public initialize(owner: TablixGrid): void {\r\n this._owner = owner;\r\n this._realizedRowHeaders = [];\r\n this._realizedColumnHeaders = [];\r\n this._realizedCornerCells = [];\r\n this._realizedBodyCells = [];\r\n }\r\n\r\n public get owner(): TablixGrid {\r\n return this._owner;\r\n }\r\n\r\n private getType(): TablixCellType {\r\n if (this._realizedCornerCells.length > 0)\r\n return TablixCellType.CornerCell;\r\n\r\n return TablixCellType.ColumnHeader;\r\n }\r\n\r\n private getColumnHeadersOrCorners(): TablixCell[] {\r\n if (this._realizedCornerCells.length > 0)\r\n return this._realizedCornerCells;\r\n\r\n return this._realizedColumnHeaders;\r\n }\r\n\r\n private columnHeadersOrCornersEqual(newType: TablixCellType, headers: TablixCell[], hierarchyNavigator: ITablixHierarchyNavigator): boolean {\r\n if (this._items.length !== headers.length)\r\n return false;\r\n\r\n let count = this._items.length;\r\n\r\n for (let i = 0; i < count; i++) {\r\n if (!this.columnHeaderOrCornerEquals(this._itemType, this._items[i], newType, headers[i].item, hierarchyNavigator))\r\n return false;\r\n }\r\n\r\n return true;\r\n }\r\n\r\n public get itemType(): TablixCellType {\r\n return this._itemType;\r\n }\r\n\r\n public getLeafItem(): any {\r\n if (this._items.length === 0)\r\n return null;\r\n\r\n return this._items[this._items.length - 1];\r\n }\r\n\r\n public columnHeaderOrCornerEquals(type1: TablixCellType, item1: any, type2: TablixCellType, item2: any, hierarchyNavigator: ITablixHierarchyNavigator): boolean {\r\n if (type1 !== type2)\r\n return false;\r\n\r\n if (type1 === TablixCellType.CornerCell) {\r\n if (!hierarchyNavigator.cornerCellItemEquals(item1, item2))\r\n return false;\r\n } else {\r\n if (!hierarchyNavigator.headerItemEquals(item1, item2))\r\n return false;\r\n }\r\n\r\n return true;\r\n }\r\n\r\n public OnLeafRealized(hierarchyNavigator: ITablixHierarchyNavigator): void {\r\n // if the headers/corner have changed we should clear the column size to accomodate for the new content\r\n let type = this.getType();\r\n let columnHeadersOrCorners = this.getColumnHeadersOrCorners();\r\n\r\n if (this.columnHeadersOrCornersEqual(type, columnHeadersOrCorners, hierarchyNavigator)) {\r\n this.clearSpanningCellsWidth(this._realizedColumnHeaders);\r\n }\r\n else {\r\n let count = columnHeadersOrCorners.length;\r\n this._items = [];\r\n\r\n for (let i = 0; i < count; i++) {\r\n this._items.push(columnHeadersOrCorners[i].item);\r\n }\r\n\r\n this._itemType = type;\r\n this.clearSize();\r\n }\r\n }\r\n\r\n private clearSpanningCellsWidth(cells: TablixCell[]): void {\r\n for (let i = 0; i < cells.length; i++) {\r\n let cell = cells[i];\r\n if (cell.colSpan > 1) {\r\n cell.setContainerWidth(-1);\r\n }\r\n }\r\n }\r\n\r\n public addCornerCell(cell: TablixCell) {\r\n cell._column = this;\r\n this._realizedCornerCells.push(cell);\r\n cell.setContainerWidth(this._containerWidth);\r\n }\r\n\r\n public addRowHeader(cell: TablixCell) {\r\n cell._column = this;\r\n this._realizedRowHeaders.push(cell);\r\n cell.setContainerWidth(this._containerWidth);\r\n }\r\n\r\n public addColumnHeader(cell: TablixCell, isLeaf: boolean) {\r\n cell._column = this;\r\n this._realizedColumnHeaders.push(cell);\r\n if (isLeaf) {\r\n cell.setContainerWidth(this._containerWidth);\r\n }\r\n }\r\n\r\n public addBodyCell(cell: TablixCell) {\r\n cell._column = this;\r\n this._realizedBodyCells.push(cell);\r\n cell.setContainerWidth(this._containerWidth);\r\n }\r\n\r\n public set footer(footerCell: TablixCell) {\r\n this._footerCell = footerCell;\r\n footerCell._column = this;\r\n footerCell.setContainerWidth(this._containerWidth);\r\n }\r\n\r\n public get footer(): TablixCell {\r\n return this._footerCell;\r\n }\r\n\r\n public onResize(width: number): void {\r\n if (width === this.getContentContextualWidth())\r\n return;\r\n\r\n this._containerWidth = width;\r\n this.setContainerWidth(this._containerWidth);\r\n this._sizeFixed = true;\r\n this._fixedToAligningWidth = false;\r\n this._aligningWidth = -1;\r\n }\r\n\r\n public onResizeEnd(width: number): void {\r\n // Invoke resize callback\r\n let gridPresenter = this.owner._presenter;\r\n if (gridPresenter)\r\n gridPresenter.invokeColumnResizeEndCallback(this, width);\r\n }\r\n\r\n public fixSize(): void {\r\n let shouldAlign: boolean = this._aligningWidth !== -1;\r\n let switched: boolean = shouldAlign !== this._fixedToAligningWidth;\r\n\r\n if ((this._sizeFixed && !switched && !shouldAlign))\r\n return;\r\n\r\n if (this._aligningWidth === -1) {\r\n this.setContainerWidth(this._containerWidth);\r\n }\r\n else {\r\n this.setContainerWidth(this._aligningWidth);\r\n }\r\n\r\n this._sizeFixed = true;\r\n this._fixedToAligningWidth = this._aligningWidth !== -1;\r\n }\r\n\r\n public clearSize(): void {\r\n this._containerWidth = -1;\r\n this.setContainerWidth(this._containerWidth);\r\n this._sizeFixed = false;\r\n }\r\n\r\n public getContentContextualWidth(): number {\r\n return this._containerWidth;\r\n }\r\n\r\n public getCellIContentContextualWidth(cell: TablixCell): number {\r\n return this._presenter.getCellWidth(cell);\r\n }\r\n\r\n public getCellSpanningWidthWithScrolling(cell: ITablixCell, tablixGrid: TablixGrid): number {\r\n let width = this.getContextualWidth() + this.getScrollingOffset();\r\n\r\n if (cell.colSpan > 1) {\r\n let index = this.getIndex(tablixGrid);\r\n let columns = tablixGrid.realizedColumns;\r\n for (let i = 1; i < cell.colSpan; i++)\r\n width += columns[i + index].getContextualWidth();\r\n }\r\n\r\n return width;\r\n }\r\n\r\n public getScrollingOffset(): number {\r\n let offset = 0;\r\n\r\n if (this._realizedColumnHeaders.length > 0)\r\n offset = _.last(this._realizedColumnHeaders).horizontalOffset;\r\n\r\n return offset;\r\n }\r\n\r\n public getContextualWidth(): number {\r\n if (this._width === -1 || this._containerWidth === -1)\r\n this._width = this._presenter.getWidth();\r\n\r\n return this._width;\r\n }\r\n\r\n public calculateSize(): number {\r\n if (this._sizeFixed)\r\n return this._containerWidth;\r\n\r\n let contentWidth: number = 0;\r\n\r\n // Check for persisted width\r\n let persistedWidth: number = this._presenter.getPersistedWidth();\r\n if (persistedWidth != null) {\r\n // If yes, Set the width to the persisted width\r\n contentWidth = persistedWidth;\r\n\r\n // Handle special case of a single-child non-leaf column header, we need to show that wholly\r\n for (let i = 0, len = this._realizedColumnHeaders.length; i < len - 1; i++) {\r\n let cell = this._realizedColumnHeaders[i];\r\n if (cell.colSpan === 1)\r\n contentWidth = Math.max(contentWidth, this._presenter.getCellWidth(cell));\r\n }\r\n\r\n return this._containerWidth = contentWidth;\r\n }\r\n\r\n // If no persisted width, we get the maximum width of the visible cells\r\n\r\n for (let cell of this._realizedColumnHeaders) {\r\n if (cell.colSpan === 1)\r\n contentWidth = Math.max(contentWidth, this._presenter.getCellWidth(cell));\r\n }\r\n\r\n for (let cell of this._realizedRowHeaders) {\r\n if (cell.colSpan === 1)\r\n contentWidth = Math.max(contentWidth, this._presenter.getCellWidth(cell));\r\n }\r\n\r\n for (let cell of this._realizedCornerCells) {\r\n contentWidth = Math.max(contentWidth, this._presenter.getCellWidth(cell));\r\n }\r\n\r\n for (let cell of this._realizedBodyCells) {\r\n contentWidth = Math.max(contentWidth, this._presenter.getCellWidth(cell));\r\n }\r\n\r\n if (this._footerCell !== null && this._footerCell.colSpan === 1)\r\n contentWidth = Math.max(contentWidth, this._presenter.getCellWidth(this._footerCell));\r\n\r\n return this._containerWidth = contentWidth;\r\n }\r\n\r\n public setAligningContextualWidth(size: number): void {\r\n this._aligningWidth = size;\r\n }\r\n\r\n public getAligningContextualWidth(): number {\r\n return this._aligningWidth;\r\n }\r\n\r\n private setContainerWidth(value: number): void {\r\n for (let cell of this._realizedColumnHeaders) {\r\n if (cell.colSpan === 1)\r\n cell.setContainerWidth(value);\r\n }\r\n\r\n for (let cell of this._realizedRowHeaders) {\r\n if (cell.colSpan === 1)\r\n cell.setContainerWidth(value);\r\n }\r\n\r\n for (let cell of this._realizedCornerCells) {\r\n cell.setContainerWidth(value);\r\n }\r\n\r\n for (let cell of this._realizedBodyCells) {\r\n cell.setContainerWidth(value);\r\n }\r\n\r\n if (this._footerCell !== null) {\r\n if (this._footerCell.colSpan === 1)\r\n this._footerCell.setContainerWidth(value);\r\n }\r\n\r\n this._width = value; // set cell width to new value\r\n }\r\n\r\n public getTablixCell(): TablixCell {\r\n let realizedCells: TablixCell[] = this._realizedColumnHeaders.length > 0 ? this._realizedColumnHeaders : this._realizedCornerCells;\r\n //Debug.assert(realizedCells.length !== 0, \"At least on header should have been realized\");\r\n return realizedCells[realizedCells.length - 1];\r\n }\r\n\r\n public getIndex(grid: TablixGrid): number {\r\n return grid.realizedColumns.indexOf(this);\r\n }\r\n\r\n public getHeaders(): TablixCell[] {\r\n return this._realizedColumnHeaders;\r\n }\r\n\r\n public getOtherDimensionHeaders(): TablixCell[] {\r\n return this._realizedRowHeaders;\r\n }\r\n\r\n public getCellContextualSpan(cell: TablixCell): number {\r\n return cell.colSpan;\r\n }\r\n\r\n public getOtherDimensionOwner(cell: TablixCell): ITablixGridItem {\r\n return cell._row;\r\n }\r\n }\r\n\r\n export class TablixRow implements ITablixGridItem {\r\n private _allocatedCells: TablixCell[];\r\n\r\n public _realizedRowHeaders: TablixCell[]; // internal\r\n public _realizedColumnHeaders: TablixCell[]; // internal\r\n public _realizedBodyCells: TablixCell[]; // internal\r\n public _realizedCornerCells: TablixCell[]; // internal\r\n\r\n private _realizedCellsCount: number;\r\n private _heightFixed: boolean;\r\n private _containerHeight = -1;\r\n private _height: number;\r\n\r\n private _presenter: TablixRowPresenter;\r\n private _owner: TablixGrid;\r\n\r\n constructor(presenter: TablixRowPresenter) {\r\n this._presenter = presenter;\r\n this._presenter.initialize(this);\r\n this._allocatedCells = [];\r\n this._heightFixed = false;\r\n this._containerHeight = -1;\r\n this._height = -1;\r\n }\r\n\r\n public initialize(owner: TablixGrid): void {\r\n this._owner = owner;\r\n this._realizedRowHeaders = [];\r\n this._realizedBodyCells = [];\r\n this._realizedCornerCells = [];\r\n this._realizedColumnHeaders = [];\r\n this._realizedCellsCount = 0;\r\n }\r\n\r\n public get presenter(): TablixRowPresenter {\r\n return this._presenter;\r\n }\r\n\r\n public get owner(): TablixGrid {\r\n return this._owner;\r\n }\r\n\r\n public releaseUnusedCells(owner: TablixControl) {\r\n this.releaseCells(owner, this._realizedCellsCount);\r\n }\r\n\r\n public releaseAllCells(owner: TablixControl) {\r\n this.releaseCells(owner, 0);\r\n }\r\n\r\n private releaseCells(owner: TablixControl, startIndex: number): void {\r\n let cells: TablixCell[] = this._allocatedCells;\r\n let length = cells.length;\r\n\r\n for (let i = startIndex; i < length; i++) {\r\n let cell = cells[i];\r\n owner._unbindCell(cell);\r\n cell.clear();\r\n }\r\n }\r\n\r\n public moveScrollableCellsToEnd(count: number): void {\r\n let frontIndex: number = Math.max(this._realizedRowHeaders.length, this._realizedCornerCells.length);\r\n\r\n for (let i = frontIndex; i < frontIndex + count; i++) {\r\n let cell = this._allocatedCells[i];\r\n this._presenter.onRemoveCell(cell);\r\n this._presenter.onAppendCell(cell);\r\n this._allocatedCells.push(cell);\r\n }\r\n\r\n this._allocatedCells.splice(frontIndex, count);\r\n }\r\n\r\n public moveScrollableCellsToStart(count: number): void {\r\n let frontIndex: number = Math.max(this._realizedRowHeaders.length, this._realizedCornerCells.length);\r\n\r\n for (let i = frontIndex; i < frontIndex + count; i++) {\r\n let cell = this._allocatedCells.pop();\r\n this._presenter.onRemoveCell(cell);\r\n this._presenter.onInsertCellBefore(cell, this._allocatedCells[frontIndex]);\r\n this._allocatedCells.splice(frontIndex, 0, cell);\r\n }\r\n }\r\n\r\n public getOrCreateCornerCell(column: TablixColumn): TablixCell {\r\n let cell: TablixCell = this.getOrCreateCell();\r\n cell.prepare(false);\r\n column.addCornerCell(cell);\r\n this._realizedCornerCells.push(cell);\r\n cell.setContainerHeight(this._containerHeight);\r\n return cell;\r\n }\r\n\r\n public getOrCreateRowHeader(column: TablixColumn, scrollable: boolean, leaf: boolean): TablixCell {\r\n let cell: TablixCell = this.getOrCreateCell();\r\n cell.prepare(scrollable);\r\n column.addRowHeader(cell);\r\n this._realizedRowHeaders.push(cell);\r\n if (leaf)\r\n cell.setContainerHeight(this._containerHeight);\r\n return cell;\r\n }\r\n\r\n public getOrCreateColumnHeader(column: TablixColumn, scrollable: boolean, leaf: boolean): TablixCell {\r\n let cell: TablixCell = this.getOrCreateCell();\r\n cell.prepare(scrollable);\r\n column.addColumnHeader(cell, leaf);\r\n this._realizedColumnHeaders.push(cell);\r\n cell.setContainerHeight(this._containerHeight);\r\n return cell;\r\n }\r\n\r\n public getOrCreateBodyCell(column: TablixColumn, scrollable: boolean): TablixCell {\r\n let cell: TablixCell = this.getOrCreateCell();\r\n cell.prepare(scrollable);\r\n column.addBodyCell(cell);\r\n this._realizedBodyCells.push(cell);\r\n cell.setContainerHeight(this._containerHeight);\r\n return cell;\r\n }\r\n\r\n public getOrCreateFooterRowHeader(column: TablixColumn): TablixCell {\r\n let cell: TablixCell = this.getOrCreateCell();\r\n cell.prepare(false);\r\n column.footer = cell;\r\n this._realizedRowHeaders.push(cell);\r\n cell.setContainerHeight(this._containerHeight);\r\n return cell;\r\n }\r\n\r\n public getOrCreateFooterBodyCell(column: TablixColumn, scrollable: boolean): TablixCell {\r\n let cell: TablixCell = this.getOrCreateCell();\r\n cell.prepare(scrollable);\r\n column.footer = cell;\r\n this._realizedBodyCells.push(cell);\r\n cell.setContainerHeight(this._containerHeight);\r\n return cell;\r\n }\r\n\r\n public getRowHeaderLeafIndex(): number {\r\n let index = -1;\r\n let count = this._allocatedCells.length;\r\n for (let i = 0; i < count; i++) {\r\n if (this._allocatedCells[i].type !== TablixCellType.RowHeader)\r\n break;\r\n index++;\r\n }\r\n\r\n return index;\r\n }\r\n\r\n public getAllocatedCellAt(index: number) {\r\n return this._allocatedCells[index];\r\n }\r\n\r\n public moveCellsBy(delta: number) {\r\n if (this._allocatedCells.length === 0)\r\n return;\r\n\r\n if (delta > 0) {\r\n let refCell = this._allocatedCells[0];\r\n for (let i = 0; i < delta; i++) {\r\n let cell: TablixCell = this.createCell(this);\r\n this._presenter.onInsertCellBefore(cell, refCell);\r\n this._allocatedCells.unshift(cell);\r\n refCell = cell;\r\n }\r\n }\r\n else {\r\n delta = -delta;\r\n for (let i = 0; i < delta; i++) {\r\n this._presenter.onRemoveCell(this._allocatedCells[i]);\r\n }\r\n this._allocatedCells.splice(0, delta);\r\n }\r\n }\r\n\r\n public getRealizedCellCount() {\r\n return this._realizedCellsCount;\r\n }\r\n\r\n public getRealizedHeadersCount(): number {\r\n return this._realizedRowHeaders.length;\r\n }\r\n\r\n public getRealizedHeaderAt(index: number) {\r\n return this._realizedRowHeaders[index];\r\n }\r\n\r\n public getTablixCell(): TablixCell {\r\n let realizedCells: TablixCell[];\r\n\r\n if (this._realizedRowHeaders.length > 0) {\r\n realizedCells = this._realizedRowHeaders;\r\n } else if (this._realizedCornerCells.length > 0) {\r\n realizedCells = this._realizedCornerCells;\r\n } else {\r\n realizedCells = this._realizedColumnHeaders;\r\n }\r\n\r\n //Debug.assert(realizedCells.length !== 0, \"At least on header should have been realized\");\r\n return realizedCells[realizedCells.length - 1];\r\n }\r\n\r\n public getOrCreateEmptySpaceCell(): TablixCell {\r\n let cell: TablixCell = this._allocatedCells[this._realizedCellsCount];\r\n if (cell === undefined) {\r\n cell = this.createCell(this);\r\n this._allocatedCells[this._realizedCellsCount] = cell;\r\n this._presenter.onAppendCell(cell);\r\n }\r\n return cell;\r\n }\r\n\r\n private createCell(row: TablixRow): TablixCell {\r\n let presenter = this._presenter.createCellPresenter(this._owner.owner.layoutManager.getLayoutKind());\r\n return new TablixCell(presenter, presenter, this);\r\n }\r\n\r\n private getOrCreateCell(): TablixCell {\r\n let cell: TablixCell = this._allocatedCells[this._realizedCellsCount];\r\n if (cell === undefined) {\r\n cell = this.createCell(this);\r\n this._allocatedCells[this._realizedCellsCount] = cell;\r\n this._presenter.onAppendCell(cell);\r\n } else {\r\n cell.colSpan = 1;\r\n cell.rowSpan = 1;\r\n }\r\n\r\n this._realizedCellsCount = this._realizedCellsCount + 1;\r\n return cell;\r\n }\r\n\r\n public onResize(height: number): void {\r\n if (height === this.getContentContextualWidth())\r\n return;\r\n\r\n this._containerHeight = height;\r\n this.setContentHeight();\r\n this._heightFixed = true;\r\n this.setAligningContextualWidth(-1);\r\n }\r\n\r\n public onResizeEnd(height: number): void { }\r\n\r\n public fixSize(): void {\r\n if (this.sizeFixed())\r\n return;\r\n\r\n this.setContentHeight();\r\n\r\n this._heightFixed = true;\r\n }\r\n\r\n public unfixSize(): void {\r\n this._heightFixed = false;\r\n this._height = -1;\r\n }\r\n\r\n public getContentContextualWidth(): number {\r\n return this._containerHeight;\r\n }\r\n\r\n public getCellIContentContextualWidth(cell: TablixCell): number {\r\n return this.presenter.getCellContentHeight(cell);\r\n }\r\n\r\n public getCellSpanningHeight(cell: ITablixCell): number {\r\n let height = this.getContextualWidth();\r\n\r\n if (cell.rowSpan > 1) {\r\n let index = this.getIndex(this.owner);\r\n let rows = this.owner.realizedRows;\r\n for (let i = 1; i < cell.rowSpan; i++)\r\n height += rows[i + index].getContextualWidth();\r\n }\r\n\r\n return height;\r\n }\r\n\r\n public getContextualWidth(): number {\r\n if (this._height === -1 || this._containerHeight === -1)\r\n this._height = this._presenter.getHeight();\r\n\r\n return this._height;\r\n }\r\n\r\n public sizeFixed(): boolean {\r\n return this._heightFixed;\r\n }\r\n\r\n public calculateSize(): number {\r\n if (this._heightFixed)\r\n return this._containerHeight;\r\n\r\n let contentHeight: number = 0;\r\n\r\n let count = this._realizedRowHeaders.length;\r\n for (let i = 0; i < count; i++) {\r\n let cell: TablixCell = this._realizedRowHeaders[i];\r\n if (cell.rowSpan === 1)\r\n contentHeight = Math.max(contentHeight, this._presenter.getCellContentHeight(cell));\r\n }\r\n\r\n count = this._realizedCornerCells.length;\r\n for (let i = 0; i < count; i++) {\r\n contentHeight = Math.max(contentHeight, this._presenter.getCellContentHeight(this._realizedCornerCells[i]));\r\n }\r\n\r\n count = this._realizedColumnHeaders.length;\r\n for (let i = 0; i < count; i++) {\r\n let cell: TablixCell = this._realizedColumnHeaders[i];\r\n if (cell.rowSpan === 1)\r\n contentHeight = Math.max(contentHeight, this._presenter.getCellContentHeight(cell));\r\n }\r\n\r\n count = this._realizedBodyCells.length;\r\n for (let i = 0; i < count; i++) {\r\n contentHeight = Math.max(contentHeight, this._presenter.getCellContentHeight(this._realizedBodyCells[i]));\r\n }\r\n\r\n return this._containerHeight = contentHeight;\r\n }\r\n\r\n public setAligningContextualWidth(size: number): void {\r\n // TODO should be implemented when we support variable row heights\r\n }\r\n\r\n public getAligningContextualWidth(): number {\r\n // TODO should be implemented when we support variable row heights\r\n return -1;\r\n }\r\n\r\n private setContentHeight(): void {\r\n let count = this._realizedRowHeaders.length;\r\n // Need to do them in reverse order so that leaf headers are set first\r\n for (let i = count - 1; i >= 0; i--) {\r\n let cell: TablixCell = this._realizedRowHeaders[i];\r\n cell.setContainerHeight(this._containerHeight);\r\n if (cell.rowSpan > 1)\r\n cell.setContainerHeight(this.getCellSpanningHeight(cell));\r\n }\r\n\r\n count = this._realizedCornerCells.length;\r\n for (let i = 0; i < count; i++) {\r\n this._realizedCornerCells[i].setContainerHeight(this._containerHeight);\r\n }\r\n\r\n count = this._realizedColumnHeaders.length;\r\n for (let i = 0; i < count; i++) {\r\n let cell: TablixCell = this._realizedColumnHeaders[i];\r\n cell.setContainerHeight(this._containerHeight);\r\n if (cell.rowSpan > 1)\r\n cell.setContainerHeight(this.getCellSpanningHeight(cell));\r\n }\r\n\r\n count = this._realizedBodyCells.length;\r\n for (let i = 0; i < count; i++) {\r\n this._realizedBodyCells[i].setContainerHeight(this._containerHeight);\r\n }\r\n\r\n this._height = -1;\r\n }\r\n\r\n public getIndex(grid: TablixGrid): number {\r\n return grid.realizedRows.indexOf(this);\r\n }\r\n\r\n public getHeaders(): TablixCell[] {\r\n return this._realizedRowHeaders;\r\n }\r\n\r\n public getOtherDimensionHeaders(): TablixCell[] {\r\n return this._realizedColumnHeaders;\r\n }\r\n\r\n public getCellContextualSpan(cell: TablixCell): number {\r\n return cell.rowSpan;\r\n }\r\n\r\n public getOtherDimensionOwner(cell: TablixCell): ITablixGridItem {\r\n return cell._column;\r\n }\r\n }\r\n\r\n export class TablixGrid {\r\n private _owner: TablixControl;\r\n\r\n private _rows: TablixRow[];\r\n private _realizedRows: TablixRow[];\r\n\r\n private _columns: TablixColumn[];\r\n private _realizedColumns: TablixColumn[];\r\n\r\n private _footerRow: TablixRow;\r\n\r\n private _emptySpaceHeaderCell: TablixCell;\r\n private _emptyFooterSpaceCell: TablixCell;\r\n\r\n public _presenter: TablixGridPresenter; // internal\r\n\r\n private _fillColumnsProportionally: boolean;\r\n\r\n constructor(presenter: TablixGridPresenter) {\r\n this._presenter = presenter;\r\n this._footerRow = null;\r\n }\r\n\r\n public initialize(owner: TablixControl, gridHost: HTMLElement, footerHost: HTMLElement) {\r\n this._owner = owner;\r\n this._presenter.initialize(this, gridHost, footerHost, owner);\r\n\r\n this.fillColumnsProportionally = false;\r\n this._realizedRows = [];\r\n this._realizedColumns = [];\r\n this._emptySpaceHeaderCell = null;\r\n this._emptyFooterSpaceCell = null;\r\n }\r\n\r\n public get owner(): TablixControl {\r\n return this._owner;\r\n }\r\n\r\n public set fillColumnsProportionally(value: boolean) {\r\n if (this._fillColumnsProportionally === value)\r\n return;\r\n\r\n this._fillColumnsProportionally = value;\r\n this._presenter.onFillColumnsProportionallyChanged(value);\r\n }\r\n\r\n public get fillColumnsProportionally(): boolean {\r\n return this._fillColumnsProportionally;\r\n }\r\n\r\n public get realizedColumns(): TablixColumn[] {\r\n return this._realizedColumns;\r\n }\r\n\r\n public set realizedColumns(columns: TablixColumn[]) {\r\n this._realizedColumns = columns;\r\n }\r\n\r\n public get realizedRows(): TablixRow[] {\r\n return this._realizedRows;\r\n }\r\n\r\n public set realizedRows(rows: TablixRow[]) {\r\n this._realizedRows = rows;\r\n }\r\n\r\n public get footerRow(): TablixRow {\r\n return this._footerRow;\r\n }\r\n\r\n public get emptySpaceHeaderCell(): TablixCell {\r\n return this._emptySpaceHeaderCell;\r\n }\r\n\r\n public get emptySpaceFooterCell(): TablixCell {\r\n return this._emptyFooterSpaceCell;\r\n }\r\n\r\n public ShowEmptySpaceCells(rowSpan: number, width: number): void {\r\n if (this._realizedRows.length === 0)\r\n return;\r\n\r\n if (this._realizedRows.length !== 0 && !this._emptySpaceHeaderCell) {\r\n this._emptySpaceHeaderCell = this._realizedRows[0].getOrCreateEmptySpaceCell();\r\n this._emptySpaceHeaderCell.rowSpan = rowSpan;\r\n this._emptySpaceHeaderCell.colSpan = 1;\r\n this._emptySpaceHeaderCell.setContainerWidth(width);\r\n }\r\n\r\n if (this._footerRow && (this._emptyFooterSpaceCell === null)) {\r\n this._emptyFooterSpaceCell = this._footerRow.getOrCreateEmptySpaceCell();\r\n this._emptyFooterSpaceCell.rowSpan = 1;\r\n this._emptyFooterSpaceCell.colSpan = 1;\r\n this._emptyFooterSpaceCell.setContainerWidth(width);\r\n }\r\n }\r\n\r\n public HideEmptySpaceCells(): void {\r\n if (this._emptySpaceHeaderCell) {\r\n this._emptySpaceHeaderCell.clear();\r\n this._emptySpaceHeaderCell = null;\r\n }\r\n\r\n if (this._emptyFooterSpaceCell) {\r\n this._emptyFooterSpaceCell.clear();\r\n this._emptyFooterSpaceCell = null;\r\n }\r\n }\r\n\r\n public onStartRenderingSession(clear: boolean): void {\r\n if (clear) {\r\n this.clearRows();\r\n this.clearColumns();\r\n }\r\n }\r\n\r\n public onStartRenderingIteration(): void {\r\n this.initializeRows();\r\n this.initializeColumns();\r\n }\r\n\r\n public onEndRenderingIteration(): void {\r\n let rows: TablixRow[] = this._rows;\r\n if (rows !== undefined) {\r\n let rowCount = rows.length;\r\n for (let i = 0; i < rowCount; i++) {\r\n rows[i].releaseUnusedCells(this._owner);\r\n }\r\n }\r\n\r\n if (this._footerRow) {\r\n this._footerRow.releaseUnusedCells(this._owner);\r\n }\r\n }\r\n\r\n public getOrCreateRow(rowIndex: number): TablixRow {\r\n let currentRow: TablixRow = this._rows[rowIndex];\r\n if (currentRow === undefined) {\r\n currentRow = new TablixRow(this._presenter.createRowPresenter());\r\n currentRow.initialize(this);\r\n this._presenter.onAppendRow(currentRow);\r\n this._rows[rowIndex] = currentRow;\r\n }\r\n\r\n if (this._realizedRows[rowIndex] === undefined) {\r\n this._realizedRows[rowIndex] = currentRow;\r\n }\r\n\r\n return currentRow;\r\n }\r\n\r\n public getOrCreateFootersRow(): TablixRow {\r\n if (this._footerRow === null) {\r\n this._footerRow = new TablixRow(this._presenter.createRowPresenter());\r\n this._footerRow.initialize(this);\r\n this._presenter.onAddFooterRow(this._footerRow);\r\n }\r\n return this._footerRow;\r\n }\r\n\r\n public moveRowsToEnd(moveFromIndex: number, count: number) {\r\n for (let i = 0; i < count; i++) {\r\n let row = this._rows[i + moveFromIndex];\r\n debug.assertValue(row, \"Invalid Row Index\");\r\n row.unfixSize();\r\n this._presenter.onRemoveRow(row);\r\n this._presenter.onAppendRow(row);\r\n this._rows.push(row);\r\n }\r\n\r\n this._rows.splice(moveFromIndex, count);\r\n }\r\n\r\n public moveRowsToStart(moveToIndex: number, count: number) {\r\n let refRow = this._rows[moveToIndex];\r\n debug.assertValue(refRow, \"Invalid Row Index\");\r\n\r\n for (let i = 0; i < count; i++) {\r\n let row = this._rows.pop();\r\n row.unfixSize();\r\n this._presenter.onRemoveRow(row);\r\n this._presenter.onInsertRowBefore(row, refRow);\r\n this._rows.splice(moveToIndex + i, 0, row);\r\n }\r\n }\r\n\r\n public moveColumnsToEnd(moveFromIndex: number, count: number) {\r\n let firstCol: number = this._rows[0]._realizedCornerCells.length;\r\n let leafStartDepth: number = Math.max(this._columns[firstCol]._realizedColumnHeaders.length - 1, 0);\r\n\r\n for (let i = leafStartDepth; i < this._rows.length; i++) {\r\n this._rows[i].moveScrollableCellsToEnd(count);\r\n }\r\n\r\n for (let i = 0; i < count; i++) {\r\n let column = this._columns[i + moveFromIndex];\r\n //Debug.assertValue(column, \"Invalid Column Index\");\r\n this._columns.push(column);\r\n }\r\n\r\n this._columns.splice(moveFromIndex, count);\r\n }\r\n\r\n public moveColumnsToStart(moveToIndex: number, count: number) {\r\n let firstCol: number = this._rows[0]._realizedCornerCells.length;\r\n let leafStartDepth: number = Math.max(this._columns[firstCol]._realizedColumnHeaders.length - 1, 0);\r\n\r\n for (let i = leafStartDepth; i < this._rows.length; i++) {\r\n this._rows[i].moveScrollableCellsToStart(count);\r\n }\r\n\r\n for (let i = 0; i < count; i++) {\r\n let column = this._columns.pop();\r\n this._columns.splice(moveToIndex + i, 0, column);\r\n }\r\n }\r\n\r\n public getOrCreateColumn(columnIndex: number): TablixColumn {\r\n let currentColumn: TablixColumn = this._columns[columnIndex];\r\n if (currentColumn === undefined) {\r\n currentColumn = new TablixColumn(this._presenter.createColumnPresenter(columnIndex), columnIndex);\r\n currentColumn.initialize(this);\r\n this._columns[columnIndex] = currentColumn;\r\n }\r\n\r\n if (this._realizedColumns[columnIndex] === undefined) {\r\n this._realizedColumns[columnIndex] = currentColumn;\r\n }\r\n\r\n return currentColumn;\r\n }\r\n\r\n private initializeColumns(): void {\r\n if (!this._columns)\r\n this._columns = [];\r\n\r\n let length: number = this._columns.length;\r\n\r\n for (let i = 0; i < length; i++) {\r\n this._columns[i].initialize(this);\r\n }\r\n\r\n this._realizedColumns = [];\r\n }\r\n\r\n private clearColumns() {\r\n this._columns = null;\r\n this._realizedColumns = null;\r\n }\r\n\r\n private initializeRows(): void {\r\n // make sure rowDimension confirms it and it's not null in the grid\r\n let hasFooter: boolean = this._owner.rowDimension.hasFooter() && (this._footerRow !== null);\r\n\r\n this._realizedRows = [];\r\n\r\n if (!this._rows) {\r\n this._rows = [];\r\n }\r\n\r\n let rows: TablixRow[] = this._rows;\r\n let length: number = rows.length;\r\n\r\n for (let i = 0; i < length; i++) {\r\n rows[i].initialize(this);\r\n }\r\n\r\n if (hasFooter) {\r\n if (!this._footerRow) {\r\n this.getOrCreateFootersRow();\r\n }\r\n this._footerRow.initialize(this);\r\n }\r\n }\r\n\r\n private clearRows() {\r\n let rows: TablixRow[] = this._rows;\r\n if (rows) {\r\n let length = rows.length;\r\n for (let i = 0; i < length; i++) {\r\n rows[i].releaseAllCells(this._owner);\r\n }\r\n\r\n if (this._footerRow)\r\n this._footerRow.releaseAllCells(this._owner);\r\n\r\n this._presenter.onClear();\r\n this._footerRow = null;\r\n this._rows = null;\r\n this._realizedRows = null;\r\n }\r\n }\r\n\r\n public getWidth(): number {\r\n return this._presenter.getWidth();\r\n }\r\n\r\n public getHeight(): number {\r\n return this._presenter.getHeight();\r\n }\r\n }\r\n}\n\n\n/** WEBPACK FOOTER **\n ** ./src/Clients/Visuals/controls/tablix/internal/tablixGrid.ts\n **/","/*\r\n * Power BI Visualizations\r\n *\r\n * Copyright (c) Microsoft Corporation\r\n * All rights reserved. \r\n * MIT License\r\n *\r\n * Permission is hereby granted, free of charge, to any person obtaining a copy\r\n * of this software and associated documentation files (the \"\"Software\"\"), to deal\r\n * in the Software without restriction, including without limitation the rights\r\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\r\n * copies of the Software, and to permit persons to whom the Software is\r\n * furnished to do so, subject to the following conditions:\r\n * \r\n * The above copyright notice and this permission notice shall be included in \r\n * all copies or substantial portions of the Software.\r\n * \r\n * THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR \r\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, \r\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE \r\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER \r\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\r\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\r\n * THE SOFTWARE.\r\n */\r\n\r\n/// <reference path=\"../../../_references.ts\"/>\r\n\r\nmodule powerbi.visuals.controls.internal {\r\n import PixelConverter = jsCommon.PixelConverter;\r\n\r\n /**\r\n * This class is used for layouts that don't or cannot\r\n * rely on DOM measurements. Instead they compute all required\r\n * widths and heights and store it in this structure.\r\n */\r\n export class SizeComputationManager {\r\n // Minimum size for a column, used to calculate layout\r\n private static TablixMinimumColumnWidth = 75;\r\n\r\n private _viewport: IViewport;\r\n private _columnCount: number;\r\n private _cellWidth: number;\r\n private _cellHeight: number;\r\n private _scalingFactor: number;\r\n\r\n public hasImageContent: boolean;\r\n\r\n public get visibleWidth(): number {\r\n return this._viewport ? this._viewport.width : 0;\r\n }\r\n\r\n public get visibleHeight(): number {\r\n return this._viewport ? this._viewport.height : 0;\r\n }\r\n\r\n public get gridWidth(): number {\r\n return this.visibleWidth;\r\n }\r\n\r\n public get gridHeight(): number {\r\n return this.visibleHeight;\r\n }\r\n\r\n public get rowHeight(): number {\r\n return this._cellHeight;\r\n }\r\n\r\n public get cellWidth(): number {\r\n return this._cellWidth;\r\n }\r\n\r\n public get cellHeight(): number {\r\n return this._cellHeight;\r\n }\r\n\r\n public get contentWidth(): number {\r\n return this._cellWidth;\r\n }\r\n\r\n public get contentHeight(): number {\r\n return this._cellHeight;\r\n }\r\n\r\n public updateColumnCount(columnCount: number): void {\r\n this._columnCount = columnCount;\r\n }\r\n\r\n public updateRowHeight(rowHeight: number): void {\r\n this._cellHeight = rowHeight;\r\n }\r\n\r\n public updateScalingFactor(scalingFactor: number): void {\r\n this._scalingFactor = scalingFactor;\r\n this._cellWidth = this.computeColumnWidth(this._columnCount);\r\n }\r\n\r\n public updateViewport(viewport: IViewport): void {\r\n this._viewport = viewport;\r\n\r\n this._cellWidth = this.computeColumnWidth(this._columnCount);\r\n this._cellHeight = this.computeColumnHeight();\r\n }\r\n\r\n private computeColumnWidth(totalColumnCount: number): number {\r\n let scalingFactor = this._scalingFactor;\r\n\r\n if (!scalingFactor)\r\n scalingFactor = 1;\r\n\r\n let minimumColumnWidth = scalingFactor * SizeComputationManager.TablixMinimumColumnWidth;\r\n let maxAllowedColumns = Math.floor(this._viewport.width / minimumColumnWidth);\r\n\r\n return this.fitToColumnCount(maxAllowedColumns, totalColumnCount);\r\n }\r\n\r\n private computeColumnHeight(): number {\r\n if (!this.hasImageContent)\r\n return this._cellHeight;\r\n\r\n let width = this._viewport.width;\r\n if (width <= 250) {\r\n // Small\r\n return 20;\r\n }\r\n else if (width <= 510) {\r\n // Medium\r\n return 51;\r\n }\r\n else if (width <= 770) {\r\n // Large\r\n return 52;\r\n }\r\n\r\n debug.assertFail(\"Fixed size is only for viewport up to 770px width.\");\r\n }\r\n\r\n private fitToColumnCount(maxAllowedColumnCount: number, totalColumnCount: number): number {\r\n let columnsToFit = Math.min(maxAllowedColumnCount, totalColumnCount);\r\n return Math.floor(this._viewport.width / columnsToFit);\r\n }\r\n }\r\n\r\n export class DimensionLayoutManager implements IDimensionLayoutManager {\r\n public static _pixelPrecision = 1.0001;\r\n public static _scrollOffsetPrecision = 0.01;\r\n\r\n public _grid: TablixGrid; // internal\r\n public _gridOffset: number; // internal\r\n\r\n protected _contextualWidthToFill: number;\r\n\r\n private _owner: TablixLayoutManager;\r\n private _realizationManager: TablixDimensionRealizationManager;\r\n private _alignToEnd: boolean;\r\n private _lastScrollOffset: number;\r\n private _isScrolling: boolean;\r\n private _fixedSizeEnabled: boolean;\r\n private _done: boolean;\r\n private _measureEnabled: boolean;\r\n\r\n constructor(owner: TablixLayoutManager, grid: TablixGrid, realizationManager: TablixDimensionRealizationManager) {\r\n //debug.assertValue(realizationManager, \"Realization Manager must be defined\");\r\n\r\n this._owner = owner;\r\n this._grid = grid;\r\n this._lastScrollOffset = null;\r\n this._isScrolling = false;\r\n this._fixedSizeEnabled = true;\r\n this._done = false;\r\n\r\n this._realizationManager = realizationManager;\r\n }\r\n\r\n public get owner(): TablixLayoutManager {\r\n return this._owner;\r\n }\r\n\r\n public set owner(owner: TablixLayoutManager) {\r\n this._owner = owner;\r\n }\r\n\r\n public get realizationManager(): TablixDimensionRealizationManager {\r\n return this._realizationManager;\r\n }\r\n\r\n public get fixedSizeEnabled(): boolean {\r\n return this._fixedSizeEnabled;\r\n }\r\n\r\n public set fixedSizeEnabled(enable: boolean) {\r\n this._fixedSizeEnabled = enable;\r\n }\r\n\r\n public onCornerCellRealized(item: any, cell: ITablixCell, leaf: boolean): void {\r\n this._realizationManager.onCornerCellRealized(item, cell);\r\n }\r\n\r\n public onHeaderRealized(item: any, cell: ITablixCell, leaf): void {\r\n this._realizationManager.onHeaderRealized(item, cell, leaf);\r\n }\r\n\r\n public get needsToRealize(): boolean {\r\n return this._realizationManager.needsToRealize;\r\n }\r\n\r\n public getVisibleSizeRatio(): number {\r\n return 1 - this.dimension.getFractionScrollOffset();\r\n }\r\n\r\n public get alignToEnd(): boolean {\r\n return this._alignToEnd;\r\n }\r\n\r\n public get done(): boolean {\r\n return this._done;\r\n }\r\n\r\n public _requiresMeasure(): boolean {\r\n debug.assertFail(\"PureVirtualMethod: DimensionLayoutManager._requiresMeasure\");\r\n return true;\r\n }\r\n\r\n public startScrollingSession(): void {\r\n this._isScrolling = true;\r\n }\r\n\r\n public endScrollingSession(): void {\r\n this._isScrolling = false;\r\n }\r\n\r\n public isScrolling(): boolean {\r\n return this._isScrolling;\r\n }\r\n\r\n public isResizing(): boolean {\r\n return false;\r\n }\r\n\r\n public getOtherHierarchyContextualHeight(): number {\r\n let otherDimension = this.dimension.otherDimension;\r\n let count = otherDimension.getDepth();\r\n\r\n let contextualHeight = 0;\r\n let items = this._getRealizedItems();\r\n\r\n if (items.length > 0) {\r\n for (let i = 0; i < count; i++) {\r\n contextualHeight += items[i].getContextualWidth();\r\n }\r\n }\r\n\r\n return contextualHeight;\r\n }\r\n\r\n public _isAutoSized(): boolean {\r\n debug.assertFail(\"PureVirtualMethod: DimensionLayoutManager._isAutoSized\");\r\n return false;\r\n }\r\n\r\n public onStartRenderingSession() {\r\n this._measureEnabled = this._requiresMeasure();\r\n this._gridOffset = this.dimension.otherDimension.getDepth();\r\n }\r\n\r\n public onEndRenderingSession() {\r\n this._realizationManager.onEndRenderingSession();\r\n this._alignToEnd = false;\r\n this._done = false;\r\n this._measureEnabled = true;\r\n this._sendDimensionsToControl();\r\n }\r\n\r\n /**\r\n * Implementing classes must override this to send dimentions to TablixControl.\r\n */\r\n public _sendDimensionsToControl(): void { // extending class overrides this\r\n debug.assertFail(\"PureVirtualMethod: DimensionLayoutManager._sendDimensionsToControl\");\r\n }\r\n\r\n public get measureEnabled() {\r\n return this._measureEnabled;\r\n }\r\n\r\n public getFooterContextualWidth(): number {\r\n return 0;\r\n }\r\n\r\n public onStartRenderingIteration(clear: boolean, contextualWidth: number) {\r\n if (this._measureEnabled && !this._done) {\r\n this._contextualWidthToFill = (contextualWidth - this.otherScrollbarContextualWidth) * this.getGridScale() - this.getFooterContextualWidth();\r\n }\r\n\r\n this._realizationManager.onStartRenderingIteration();\r\n\r\n if (clear) {\r\n this._lastScrollOffset = null;\r\n }\r\n else if (this._lastScrollOffset !== null) {\r\n this.swapElements();\r\n }\r\n }\r\n\r\n public get allItemsRealized(): boolean {\r\n return this.getRealizedItemsCount() - this._gridOffset === this.dimension.getItemsCount() || this.dimension.getItemsCount() === 0;\r\n }\r\n\r\n public onEndRenderingIteration(): void {\r\n if (this._done) {\r\n return;\r\n }\r\n\r\n if (!this._measureEnabled) {\r\n this._lastScrollOffset = this.dimension.scrollOffset;\r\n this._done = true;\r\n return;\r\n }\r\n\r\n let gridContextualWidth: number = this.getGridContextualWidth();\r\n\r\n let filled: boolean = Double.greaterOrEqualWithPrecision(gridContextualWidth, this._contextualWidthToFill, DimensionLayoutManager._pixelPrecision);\r\n let allRealized = this.allItemsRealized;\r\n\r\n let newScrollOffset;\r\n\r\n if (filled) {\r\n newScrollOffset = this.scrollForwardToAlignEnd(gridContextualWidth);\r\n }\r\n else {\r\n newScrollOffset = this.scrollBackwardToFill(gridContextualWidth);\r\n }\r\n\r\n this._realizationManager.onEndRenderingIteration(gridContextualWidth, filled);\r\n\r\n let originalScrollbarVisible: boolean = this.dimension.scrollbar.visible;\r\n\r\n this.updateScrollbar(gridContextualWidth);\r\n\r\n this._done = (filled || allRealized) &&\r\n this.dimension.scrollbar.visible === originalScrollbarVisible &&\r\n Double.equalWithPrecision(newScrollOffset, this.dimension.scrollOffset, DimensionLayoutManager._scrollOffsetPrecision);\r\n\r\n this.dimension.scrollOffset = newScrollOffset;\r\n this._lastScrollOffset = this.dimension.scrollOffset;\r\n }\r\n\r\n private getScrollDeltaWithinPage(): number {\r\n if (this._lastScrollOffset !== null) {\r\n let delta = this.dimension.getIntegerScrollOffset() - Math.floor(this._lastScrollOffset);\r\n if (Math.abs(delta) < this.getRealizedItemsCount() - this.dimension.otherDimension.getDepth()) {\r\n return delta;\r\n }\r\n }\r\n return null;\r\n }\r\n\r\n private swapElements() {\r\n let delta = this.getScrollDeltaWithinPage();\r\n if (delta !== null) {\r\n let otherHierarchyDepth = this.dimension.otherDimension.getDepth();\r\n\r\n if (Math.abs(delta) < this.getRealizedItemsCount() - otherHierarchyDepth) {\r\n if (delta > 0) {\r\n this._moveElementsToBottom(otherHierarchyDepth, delta);\r\n }\r\n else if (delta < 0) {\r\n this._moveElementsToTop(otherHierarchyDepth, -delta);\r\n }\r\n }\r\n }\r\n }\r\n\r\n public _getRealizedItems(): ITablixGridItem[] {\r\n // abstract\r\n debug.assertFail(\"PureVirtualMethod: DimensionLayoutManager._getRealizedItems\");\r\n return null;\r\n }\r\n\r\n public getRealizedItemsCount(): number {\r\n let realizedItems = this._getRealizedItems();\r\n return realizedItems.length;\r\n }\r\n\r\n public _moveElementsToBottom(moveFromIndex: number, count): void {\r\n debug.assertFail(\"PureVirtualMethod: DimensionLayoutManager._moveElementsToBottom\");\r\n }\r\n\r\n public _moveElementsToTop(moveToIndex: number, count): void {\r\n debug.assertFail(\"PureVirtualMethod: DimensionLayoutManager._moveElementsToTop\");\r\n }\r\n\r\n public isScrollingWithinPage(): boolean {\r\n return this.getScrollDeltaWithinPage() !== null;\r\n }\r\n\r\n public getGridContextualWidth(): number {\r\n debug.assertFail(\"PureVirtualMethod: DimensionLayoutManager.getGridContextualWidth\");\r\n return 0;\r\n }\r\n\r\n private updateScrollbar(gridContextualWidth: number): void {\r\n let scrollbar = this.dimension.scrollbar;\r\n scrollbar.viewMin = this.dimension.scrollOffset;\r\n scrollbar.min = 0;\r\n scrollbar.max = this.dimension.getItemsCount();\r\n scrollbar.viewSize = this.getViewSize(gridContextualWidth);\r\n this.dimension.scrollbar.show(this.canScroll(gridContextualWidth));\r\n }\r\n\r\n public getViewSize(gridContextualWidth: number): number {\r\n let count: number = this.getRealizedItemsCount();\r\n if (count === 0)\r\n return 0;\r\n\r\n let startIndex = this._gridOffset;\r\n let sizeInItems = 0;\r\n let sizeInPixels = 0;\r\n\r\n let widthToFill: number = this._contextualWidthToFill;\r\n let scrollableArea = widthToFill - this.getOtherHierarchyContextualHeight();\r\n\r\n let error = this.getMeaurementError(gridContextualWidth);\r\n\r\n for (let i = startIndex; i < count; i++) {\r\n let visibleRatio;\r\n if (i === startIndex) {\r\n visibleRatio = this.getVisibleSizeRatio();\r\n }\r\n else\r\n visibleRatio = 1;\r\n\r\n let itemContextualWidth = this.getItemContextualWidthWithScrolling(i) * error;\r\n\r\n sizeInPixels += itemContextualWidth;\r\n sizeInItems += visibleRatio;\r\n\r\n if (Double.greaterWithPrecision(sizeInPixels, scrollableArea, DimensionLayoutManager._pixelPrecision)) {\r\n sizeInItems -= ((sizeInPixels - scrollableArea) / itemContextualWidth) * visibleRatio;\r\n break;\r\n }\r\n }\r\n\r\n return sizeInItems;\r\n }\r\n\r\n public isScrollableHeader(item: any, items: any, index: number): boolean {\r\n if (index !== 0 || this.dimension.getFractionScrollOffset() === 0) {\r\n return false;\r\n }\r\n\r\n let hierarchyNavigator: ITablixHierarchyNavigator = this.dimension._hierarchyNavigator;\r\n\r\n if (hierarchyNavigator.isLeaf(item)) {\r\n return true;\r\n }\r\n\r\n let currentItem: any = item;\r\n let currentItems: any[] = items;\r\n\r\n do {\r\n currentItems = hierarchyNavigator.getChildren(currentItem);\r\n currentItem = this.dimension.getFirstVisibleItem(hierarchyNavigator.getLevel(currentItem) + 1);\r\n\r\n if (currentItem === undefined) {\r\n break;\r\n }\r\n\r\n if (!hierarchyNavigator.isLastItem(currentItem, currentItems)) {\r\n return false;\r\n }\r\n\r\n } while (!hierarchyNavigator.isLeaf(currentItem));\r\n\r\n return true;\r\n }\r\n\r\n public reachedEnd(): boolean {\r\n return this.dimension.getIntegerScrollOffset() + (this.getRealizedItemsCount() - this._gridOffset) >= this.dimension.getItemsCount();\r\n }\r\n\r\n public scrollBackwardToFill(gridContextualWidth: number): number {\r\n let newScrollOffset = this.dimension.scrollOffset;\r\n if (this.reachedEnd()) {\r\n let widthToFill = this._contextualWidthToFill - gridContextualWidth;\r\n if (this.dimension.getItemsCount() > 0) {\r\n let averageColumnwidth = gridContextualWidth / (this.getRealizedItemsCount() - this.dimension.getFractionScrollOffset());\r\n newScrollOffset = this.dimension.getValidScrollOffset(Math.floor(this.dimension.scrollOffset - (widthToFill / averageColumnwidth)));\r\n }\r\n this._alignToEnd = !Double.equalWithPrecision(newScrollOffset, this.dimension.scrollOffset, DimensionLayoutManager._scrollOffsetPrecision); // this is an aproximate scrolling back, we have to ensure it is aligned to the end of the control\r\n }\r\n return newScrollOffset;\r\n }\r\n\r\n private getItemContextualWidth(index: number): number {\r\n let realizedItems = this._getRealizedItems();\r\n if (index >= realizedItems.length)\r\n return null;\r\n\r\n return realizedItems[index].getContextualWidth();\r\n }\r\n\r\n private getItemContextualWidthWithScrolling(index: number): number {\r\n return this.getSizeWithScrolling(this.getItemContextualWidth(index), index);\r\n }\r\n\r\n public getSizeWithScrolling(size: number, index: number): number {\r\n let ratio;\r\n\r\n if (this._gridOffset === index) {\r\n ratio = this.getVisibleSizeRatio();\r\n }\r\n else {\r\n ratio = 1;\r\n }\r\n\r\n return size * ratio;\r\n }\r\n\r\n public getGridContextualWidthFromItems(): number {\r\n let count = this.getRealizedItemsCount();\r\n let contextualWidth = 0;\r\n for (let i = 0; i < count; i++) {\r\n contextualWidth += this.getItemContextualWidthWithScrolling(i);\r\n }\r\n return contextualWidth;\r\n }\r\n\r\n private getMeaurementError(gridContextualWidth: number): number {\r\n return gridContextualWidth / this.getGridContextualWidthFromItems();\r\n }\r\n\r\n private scrollForwardToAlignEnd(gridContextualWidth: number): number {\r\n let newScrollOffset = this.dimension.scrollOffset;\r\n if (this._alignToEnd) {\r\n let withinThreshold = Double.equalWithPrecision(gridContextualWidth, this._contextualWidthToFill, DimensionLayoutManager._pixelPrecision);\r\n if (!withinThreshold) { // if it is within the threshold we consider it aligned, skip aliging algorithm\r\n let count: number = this.getRealizedItemsCount();\r\n let startIndex = this._gridOffset;\r\n let widthToScroll = gridContextualWidth - this._contextualWidthToFill;\r\n\r\n let error = this.getMeaurementError(gridContextualWidth);\r\n\r\n for (let i = startIndex; i < count; i++) {\r\n let itemContextualWidth = this.getItemContextualWidth(i) * error;\r\n if (Double.lessWithPrecision(itemContextualWidth, widthToScroll, DimensionLayoutManager._pixelPrecision)) {\r\n widthToScroll -= itemContextualWidth;\r\n }\r\n else {\r\n let visibleRatio = startIndex === i ? 1 - this.dimension.getFractionScrollOffset() : 1;\r\n newScrollOffset = this.dimension.getValidScrollOffset(this.dimension.scrollOffset + (i - startIndex) + (widthToScroll * visibleRatio / itemContextualWidth));\r\n break;\r\n }\r\n }\r\n }\r\n this._alignToEnd = !withinThreshold;\r\n }\r\n return newScrollOffset;\r\n }\r\n\r\n public get dimension(): TablixDimension {\r\n debug.assertFail(\"PureVirtualMethod: DimensionLayoutManager.dimension\");\r\n return null;\r\n }\r\n\r\n public get otherLayoutManager(): DimensionLayoutManager {\r\n return <DimensionLayoutManager>this.dimension.otherDimension.layoutManager;\r\n }\r\n\r\n public get contextualWidthToFill(): number {\r\n return this._contextualWidthToFill;\r\n }\r\n\r\n public getGridScale(): number {\r\n debug.assertFail(\"PureVirtualMethod: DimensionLayoutManager.getGridScale\");\r\n return 0;\r\n }\r\n\r\n public get otherScrollbarContextualWidth(): number {\r\n debug.assertFail(\"PureVirtualMethod: DimensionLayoutManager.otherScrollbarContextualWidth\");\r\n return 0;\r\n }\r\n\r\n public getActualContextualWidth(gridContextualWidth: number): number {\r\n if (this._isAutoSized() && !this.canScroll(gridContextualWidth))\r\n return gridContextualWidth;\r\n\r\n return this._contextualWidthToFill;\r\n }\r\n\r\n protected canScroll(gridContextualWidth: number): boolean {\r\n debug.assertFail(\"PureVirtualMethod: DimensionLayoutManager.canScroll\");\r\n return false;\r\n }\r\n\r\n public calculateSizes(): void {\r\n if (this.fixedSizeEnabled) {\r\n this.calculateContextualWidths();\r\n this.calculateSpans();\r\n }\r\n }\r\n\r\n protected _calculateSize(item: ITablixGridItem): number {\r\n debug.assertFail(\"PureVirtualMethod: DimensionLayoutManager._calculateSize\");\r\n return null;\r\n }\r\n\r\n public calculateContextualWidths(): void {\r\n let items: ITablixGridItem[] = this._getRealizedItems();\r\n let count = items.length;\r\n\r\n for (let i = 0; i < count; i++) {\r\n let item: ITablixGridItem = items[i];\r\n\r\n if (this.measureEnabled)\r\n item.setAligningContextualWidth(-1);\r\n\r\n this._calculateSize(item);\r\n }\r\n }\r\n\r\n public calculateSpans(): void {\r\n if (this.measureEnabled) {\r\n this.updateNonScrollableItemsSpans();\r\n this.updateScrollableItemsSpans();\r\n }\r\n\r\n // TODO override in row layout manager to add footer to calculation, this is required for Matrix\r\n }\r\n\r\n public updateNonScrollableItemsSpans(): void {\r\n let otherDimensionItems = this.otherLayoutManager._getRealizedItems();\r\n let otherDimensionItemsCount = otherDimensionItems.length;\r\n let startIndex = this.dimension.getDepth();\r\n for (let i = startIndex; i < otherDimensionItemsCount; i++) {\r\n let otherDimensionItem = otherDimensionItems[i];\r\n this.updateSpans(otherDimensionItem, otherDimensionItem.getHeaders(), false);\r\n }\r\n }\r\n\r\n public updateScrollableItemsSpans(): void {\r\n let otherRealizedItems = this.otherLayoutManager._getRealizedItems();\r\n let otherRealizedItemsCount = Math.min(this.dimension.getDepth(), otherRealizedItems.length);\r\n for (let i = 0; i < otherRealizedItemsCount; i++) {\r\n let otherRealizedItem = otherRealizedItems[i];\r\n this.updateSpans(otherRealizedItem, otherRealizedItem.getOtherDimensionHeaders(), true);\r\n }\r\n }\r\n\r\n public fixSizes(): void {\r\n if (this.fixedSizeEnabled) {\r\n let items: ITablixGridItem[] = this._getRealizedItems();\r\n let count = items.length;\r\n for (let i = count - 1; i >= 0; i--) {\r\n items[i].fixSize();\r\n }\r\n }\r\n }\r\n\r\n private updateSpans(otherRealizedItem: ITablixGridItem, cells: TablixCell[], considerScrolling: boolean): void {\r\n let realizedItems = this._getRealizedItems();\r\n let cellCount = cells.length;\r\n for (let j = 0; j < cellCount; j++) {\r\n let cell = cells[j];\r\n let owner = otherRealizedItem.getOtherDimensionOwner(cell);\r\n let span = owner.getCellContextualSpan(cell);\r\n if (span > 1) {\r\n let totalSizeInSpan = 0;\r\n let startIndex = owner.getIndex(this._grid);\r\n for (let k = 0; k < span; k++) {\r\n let item = realizedItems[k + startIndex];\r\n\r\n let childWidth = item.getContentContextualWidth();\r\n\r\n // Considering scroll offset for first column only, as scroll has not been applied to the cells\r\n if (considerScrolling && j === 0 && k === 0) {\r\n childWidth = Math.floor((1 - this.dimension.getFractionScrollOffset()) * childWidth);\r\n }\r\n\r\n totalSizeInSpan += childWidth;\r\n\r\n if (k === span - 1)\r\n this.updateLastChildSize(cell, item, totalSizeInSpan);\r\n }\r\n }\r\n }\r\n }\r\n\r\n private updateLastChildSize(spanningCell: TablixCell, item: ITablixGridItem, totalSpanSize: number): void {\r\n let delta = item.getCellIContentContextualWidth(spanningCell) - totalSpanSize;\r\n if (delta > 0) // the parent width is wider than the sum of the children, stretch the last child to compensate the difference\r\n item.setAligningContextualWidth(Math.max(item.getAligningContextualWidth(), delta + item.getContentContextualWidth()));\r\n }\r\n }\r\n\r\n export class ResizeState {\r\n public item: any;\r\n public itemType: TablixCellType;\r\n public column: TablixColumn;\r\n public startColumnWidth: number;\r\n public resizingDelta: number;\r\n public animationFrame: number;\r\n public scale: number;\r\n\r\n constructor(column: TablixColumn, width: number, scale: number) {\r\n this.column = column;\r\n this.item = column.getLeafItem();\r\n this.itemType = column.itemType;\r\n this.startColumnWidth = width;\r\n this.resizingDelta = 0;\r\n this.animationFrame = null;\r\n this.scale = scale;\r\n }\r\n\r\n public getNewSize(): number {\r\n return this.startColumnWidth + this.resizingDelta;\r\n }\r\n }\r\n\r\n export class ColumnLayoutManager extends DimensionLayoutManager implements ITablixResizeHandler {\r\n static minColumnWidth = 10;\r\n private _resizeState: ResizeState;\r\n\r\n constructor(owner: TablixLayoutManager, grid: TablixGrid, realizationManager: ColumnRealizationManager) {\r\n super(owner, grid, realizationManager);\r\n\r\n realizationManager.owner = this;\r\n\r\n this.fillProportionally = false;\r\n this._resizeState = null;\r\n }\r\n\r\n public get dimension(): TablixDimension {\r\n return this.owner.owner.columnDimension;\r\n }\r\n\r\n public isResizing(): boolean {\r\n return this._resizeState !== null;\r\n }\r\n\r\n public set fillProportionally(value: boolean) {\r\n this._grid.fillColumnsProportionally = value;\r\n }\r\n\r\n public get fillProportionally(): boolean {\r\n return this._grid.fillColumnsProportionally;\r\n }\r\n\r\n public getGridScale(): number {\r\n return this._grid._presenter.getScreenToCssRatioX();\r\n }\r\n\r\n public get otherScrollbarContextualWidth(): number {\r\n if (this.dimension.otherDimension.scrollbar.visible) {\r\n return HTMLElementUtils.getElementWidth(this.dimension.otherDimension.scrollbar.element);\r\n }\r\n return 0;\r\n }\r\n\r\n public _getRealizedItems(): ITablixGridItem[] {\r\n if (!this._grid.realizedColumns) {\r\n this._grid.realizedColumns = [];\r\n }\r\n\r\n return this._grid.realizedColumns;\r\n }\r\n\r\n public _moveElementsToBottom(moveFromIndex: number, count): void {\r\n this._grid.moveColumnsToEnd(moveFromIndex, count);\r\n }\r\n\r\n public _moveElementsToTop(moveToIndex: number, count): void {\r\n this._grid.moveColumnsToStart(moveToIndex, count);\r\n }\r\n\r\n public _requiresMeasure(): boolean {\r\n // if the control is not scrolling in either dimension or is scrolling or is resizing\r\n return (!this.isScrolling() && !this.otherLayoutManager.isScrolling()) || this.isScrolling() || this.isResizing();\r\n }\r\n\r\n public getGridContextualWidth(): number {\r\n return this._grid.getWidth();\r\n }\r\n\r\n private getFirstVisibleColumn(): TablixColumn {\r\n return this._grid.realizedColumns[this._gridOffset];\r\n }\r\n\r\n public _isAutoSized(): boolean {\r\n return this.owner.owner.autoSizeWidth;\r\n }\r\n\r\n public applyScrolling() {\r\n let columnOffset: number = this.dimension.getFractionScrollOffset();\r\n let firstVisibleColumnWidth: number = 0;\r\n\r\n if (columnOffset !== 0) {\r\n let firstVisibleColumn: TablixColumn = this.getFirstVisibleColumn();\r\n if (firstVisibleColumn !== undefined) {\r\n firstVisibleColumnWidth = firstVisibleColumn.getContextualWidth();\r\n\r\n // Ceiling the offset because setting a fraction Width on the TD will ceil it\r\n // We need to let the TD and the OuterDiv to align in order for Borders to touch\r\n let offsetInPixels = Math.ceil(- firstVisibleColumnWidth * columnOffset);\r\n this.scroll(firstVisibleColumn, firstVisibleColumnWidth, offsetInPixels);\r\n }\r\n }\r\n }\r\n\r\n private scroll(firstVisibleColumn: TablixColumn, width: number, offset: number) {\r\n this.scrollCells(firstVisibleColumn._realizedColumnHeaders, width, offset);\r\n this.scrollBodyCells(this._grid.realizedRows, width, offset);\r\n\r\n if (firstVisibleColumn.footer !== null) {\r\n firstVisibleColumn.footer.scrollHorizontally(width, offset);\r\n }\r\n }\r\n\r\n private scrollCells(cells: TablixCell[], width: number, offset: number): void {\r\n let length: number = cells.length;\r\n for (let i = 0; i < length; i++) {\r\n cells[i].scrollHorizontally(width, offset);\r\n }\r\n }\r\n\r\n private scrollBodyCells(rows: TablixRow[], width: number, offset: number): void {\r\n let length: number = rows.length;\r\n let cells: TablixCell[];\r\n let cell: TablixCell;\r\n for (let i = 0; i < length; i++) {\r\n cells = rows[i]._realizedBodyCells;\r\n if (cells !== undefined) {\r\n cell = cells[0];\r\n if (cell !== undefined) {\r\n cell.scrollHorizontally(width, offset);\r\n }\r\n }\r\n }\r\n }\r\n\r\n public onStartResize(cell: TablixCell, currentX: number, currentY: number): void {\r\n this._resizeState = new ResizeState(cell._column, cell._column.getContentContextualWidth(), HTMLElementUtils.getAccumulatedScale(this.owner.owner.container));\r\n }\r\n\r\n public onResize(cell: TablixCell, deltaX: number, deltaY: number): void {\r\n if (this.isResizing()) {\r\n this._resizeState.resizingDelta = Math.round(Math.max(deltaX / this._resizeState.scale, ColumnLayoutManager.minColumnWidth - this._resizeState.startColumnWidth));\r\n if (this._resizeState.animationFrame === null)\r\n this._resizeState.animationFrame = requestAnimationFrame(() => this.performResizing());\r\n }\r\n }\r\n\r\n public onEndResize(cell: TablixCell): void {\r\n if (this.isResizing() && this._resizeState.animationFrame !== null) {\r\n this.performResizing(); // if we reached the end and we are still waiting for the last animation frame, perform the pending resizing and clear the state \r\n }\r\n\r\n this.endResizing();\r\n\r\n this._resizeState = null;\r\n }\r\n\r\n public onReset(cell: TablixCell) {\r\n this._resizeState = new ResizeState(cell._column, -1, 1);\r\n cell._column.clearSize();\r\n this.endResizing();\r\n this.owner.owner.refresh(false);\r\n this._resizeState = null;\r\n }\r\n\r\n public updateItemToResizeState(realizedColumns: TablixColumn[]): void {\r\n if (this._resizeState === null)\r\n return;\r\n\r\n let columnCount: number = realizedColumns.length;\r\n\r\n let hierarchyNavigator: ITablixHierarchyNavigator = this.owner.owner.hierarchyNavigator;\r\n\r\n // Only iterate over the columns that belong to column hierachy (i.e. skip the row hierarchy rows)\r\n // as this post-rendering adjustment only applies to them.\r\n let startIndex = this.otherLayoutManager.dimension.getDepth();\r\n for (let i = startIndex; i < columnCount; i++) {\r\n let column: TablixColumn = realizedColumns[i];\r\n\r\n if (!column.columnHeaderOrCornerEquals(this._resizeState.itemType, this._resizeState.item, column.itemType, column.getLeafItem(), hierarchyNavigator))\r\n continue;\r\n\r\n if (column !== this._resizeState.column) { // we moved the item of the column that is being resized to another one \r\n this._resizeState.column = column;\r\n column.onResize(this._resizeState.getNewSize());\r\n break;\r\n }\r\n }\r\n }\r\n\r\n private performResizing(): void {\r\n if (this._resizeState === null) // in case of FireFox we cannot cancel the animation frame request\r\n return;\r\n this._resizeState.animationFrame = null;\r\n let newSize = this._resizeState.getNewSize();\r\n this._resizeState.column.onResize(newSize);\r\n this.owner.owner.refresh(false);\r\n }\r\n\r\n private endResizing(): void {\r\n if (this._resizeState === null) // in case of FireFox we cannot cancel the animation frame request\r\n return;\r\n\r\n let newSize = this._resizeState.getNewSize();\r\n this._resizeState.column.onResizeEnd(newSize);\r\n }\r\n\r\n /**\r\n * Sends column related data (pixel size, column count, etc) to TablixControl.\r\n */\r\n public _sendDimensionsToControl(): void {\r\n let gridContextualWidth: number = this.getGridContextualWidth();\r\n let widthToFill: number = this.getActualContextualWidth(gridContextualWidth);\r\n let otherContextualHeight = this.getOtherHierarchyContextualHeight();\r\n let scale = this.getGridScale(); // in case of canvas we have to convert the size from device pixel to css pixel\r\n this.owner.owner.updateColumnDimensions(otherContextualHeight / scale,\r\n (widthToFill - otherContextualHeight) / scale,\r\n this.getViewSize(gridContextualWidth));\r\n }\r\n\r\n public getEstimatedHeaderWidth(label: string, headerIndex: number): number {\r\n debug.assertFail(\"PureVirtualMethod: ColumnLayoutManager.getEstimatedHeaderWidth\");\r\n return -1;\r\n }\r\n\r\n public getEstimatedBodyCellWidth(content: string): number {\r\n debug.assertFail(\"PureVirtualMethod: ColumnLayoutManager.getEstimatedBodyCellWidth\");\r\n return -1;\r\n }\r\n }\r\n\r\n export class DashboardColumnLayoutManager extends ColumnLayoutManager {\r\n public getEstimatedHeaderWidth(label: string, headerIndex: number): number {\r\n if (this.ignoreColumn(headerIndex))\r\n return 0;\r\n\r\n // for dashboard layout it does not matter whether we pass an actual cell or not\r\n return this.owner.getCellWidth(undefined);\r\n }\r\n\r\n public getEstimatedBodyCellWidth(content: string): number {\r\n // for dashboard layout it does not matter whether we pass an actual cell or not\r\n return this.owner.getCellWidth(undefined);\r\n }\r\n\r\n protected canScroll(gridContextualWidth: number): boolean {\r\n return false;\r\n }\r\n\r\n protected _calculateSize(item: ITablixGridItem): number {\r\n let headerIndex = item.getIndex(this._grid);\r\n let computedSize = 0;\r\n\r\n if (!this.ignoreColumn(headerIndex)) {\r\n // for dashboard layout it does not matter whether we pass an actual cell or not\r\n computedSize = this.owner.getContentWidth(undefined);\r\n }\r\n\r\n item.onResize(computedSize);\r\n item.onResizeEnd(computedSize);\r\n return computedSize;\r\n }\r\n\r\n private ignoreColumn(headerIndex: number): boolean {\r\n // On the dashboard, we need to return 0 if the row header is static\r\n // (a table or a matrix without row groups)\r\n return headerIndex === 0 && !this.owner.binder.hasRowGroups();\r\n }\r\n }\r\n\r\n export class CanvasColumnLayoutManager extends ColumnLayoutManager {\r\n public getEstimatedHeaderWidth(label: string, headerIndex: number): number {\r\n // On the canvas the header width depends on the size of the content\r\n return this.owner.getEstimatedTextWidth(label);\r\n }\r\n\r\n public getEstimatedBodyCellWidth(content: string): number {\r\n return this.owner.getEstimatedTextWidth(content);\r\n }\r\n\r\n public calculateContextualWidths(): void {\r\n let items: ITablixGridItem[] = this._getRealizedItems();\r\n let columnWidths: ColumnWidthObject[] = [];\r\n\r\n for (let i = 0, len = items.length; i < len; i++) {\r\n let item = items[i];\r\n if (this.measureEnabled)\r\n item.setAligningContextualWidth(-1);\r\n\r\n let queryName = TablixColumnWidthManager.getColumnQueryName(<TablixColumn>item);\r\n\r\n if (queryName != null) { // Hidden Corner cell for Table\r\n columnWidths.push({\r\n queryName: queryName,\r\n width: this._calculateSize(item),\r\n isFixed: false // Unused\r\n });\r\n }\r\n }\r\n\r\n // Save all column widths. Needed when user turns off auto-sizing for column widths.\r\n this.owner.columnWidthsToPersist = columnWidths;\r\n }\r\n\r\n protected canScroll(gridContextualWidth: number): boolean {\r\n return !Double.equalWithPrecision(this.dimension.scrollOffset, 0, DimensionLayoutManager._scrollOffsetPrecision) ||\r\n (((this.getRealizedItemsCount() - this._gridOffset) < this.dimension.getItemsCount()) && this._contextualWidthToFill > 0) ||\r\n Double.greaterWithPrecision(gridContextualWidth, this._contextualWidthToFill, DimensionLayoutManager._pixelPrecision);\r\n }\r\n\r\n protected _calculateSize(item: ITablixGridItem): number {\r\n return item.calculateSize();\r\n }\r\n }\r\n\r\n export class RowLayoutManager extends DimensionLayoutManager {\r\n constructor(owner: TablixLayoutManager, grid: TablixGrid, realizationManager: RowRealizationManager) {\r\n super(owner, grid, realizationManager);\r\n\r\n realizationManager.owner = this;\r\n }\r\n\r\n public get dimension(): TablixDimension {\r\n return this.owner.owner.rowDimension;\r\n }\r\n\r\n public getGridScale(): number {\r\n return this._grid._presenter.getScreenToCssRatioY();\r\n }\r\n\r\n public get otherScrollbarContextualWidth(): number {\r\n if (this.dimension.otherDimension.scrollbar.visible) {\r\n return HTMLElementUtils.getElementHeight(this.dimension.otherDimension.scrollbar.element);\r\n }\r\n return 0;\r\n }\r\n\r\n public startScrollingSession(): void {\r\n super.startScrollingSession();\r\n }\r\n\r\n public _getRealizedItems(): ITablixGridItem[] {\r\n if (!this._grid.realizedRows) {\r\n this._grid.realizedRows = [];\r\n }\r\n\r\n return this._grid.realizedRows;\r\n }\r\n\r\n public _moveElementsToBottom(moveFromIndex: number, count): void {\r\n this._grid.moveRowsToEnd(moveFromIndex, count);\r\n }\r\n\r\n public _moveElementsToTop(moveToIndex: number, count): void {\r\n this._grid.moveRowsToStart(moveToIndex, count);\r\n }\r\n\r\n public _requiresMeasure(): boolean {\r\n // if the control is not scrolling in either dimension and the column dimension is not resizing or row dimension is scrolling and reaching the end while scrolling \r\n return (!this.isScrolling() && !this.otherLayoutManager.isScrolling() && !this.otherLayoutManager.isResizing())\r\n || (this.isScrolling() && (this.dimension.getIntegerScrollOffset() + (this.getRealizedItemsCount() - this._gridOffset) >= this.dimension.getItemsCount()));\r\n }\r\n\r\n public getGridContextualWidth(): number {\r\n return this._grid.getHeight();\r\n }\r\n\r\n private getFirstVisibleRow(): TablixRow {\r\n return this._grid.realizedRows[this._gridOffset];\r\n }\r\n\r\n public _isAutoSized(): boolean {\r\n return this.owner.owner.autoSizeHeight;\r\n }\r\n\r\n public applyScrolling() {\r\n let rowOffset: number = this.dimension.getFractionScrollOffset();\r\n let firstVisibleRowHeight: number = 0;\r\n\r\n if (rowOffset !== 0) {\r\n let firstVisibleRow: TablixRow = this.getFirstVisibleRow();\r\n if (firstVisibleRow) {\r\n firstVisibleRowHeight = firstVisibleRow.getContextualWidth();\r\n this.scroll(firstVisibleRow, firstVisibleRowHeight, rowOffset);\r\n }\r\n }\r\n }\r\n\r\n private scroll(firstVisibleRow: TablixRow, height: number, offset: number) {\r\n this.scrollCells(firstVisibleRow._realizedRowHeaders, height, offset);\r\n this.scrollCells(firstVisibleRow._realizedBodyCells, height, offset);\r\n }\r\n\r\n private scrollCells(cells: TablixCell[], height: number, offset: number): void {\r\n let length: number = cells.length;\r\n for (let i = 0; i < length; i++) {\r\n cells[i].scrollVertically(height, offset);\r\n }\r\n }\r\n\r\n public getFooterContextualWidth(): number {\r\n if (this.owner.owner.rowDimension.hasFooter()) {\r\n if (this.owner.grid.footerRow) {\r\n return this.owner.grid.footerRow.getContextualWidth();\r\n }\r\n }\r\n\r\n return 0;\r\n }\r\n\r\n public calculateContextualWidths(): void {\r\n super.calculateContextualWidths();\r\n if (this.fixedSizeEnabled) {\r\n let footerRow: TablixRow = this._grid.footerRow;\r\n if (footerRow) {\r\n this._calculateSize(footerRow);\r\n }\r\n }\r\n }\r\n\r\n public fixSizes(): void {\r\n super.fixSizes();\r\n if (this.fixedSizeEnabled) {\r\n if (this._grid.footerRow) {\r\n this._grid.footerRow.fixSize();\r\n }\r\n }\r\n }\r\n\r\n /**\r\n * Sends row related data (pixel size, column count, etc) to TablixControl.\r\n */\r\n public _sendDimensionsToControl(): void {\r\n let gridContextualWidth: number = this.getGridContextualWidth();\r\n let widthToFill: number = this.getActualContextualWidth(gridContextualWidth);\r\n let otherContextualHeight = this.getOtherHierarchyContextualHeight();\r\n let scale = this.getGridScale();\r\n this.owner.owner.updateRowDimensions(otherContextualHeight / scale,\r\n (widthToFill - otherContextualHeight) / scale,\r\n gridContextualWidth / scale,\r\n this.getViewSize(gridContextualWidth),\r\n (this._grid.footerRow ? this._grid.footerRow.getContextualWidth() / scale : 0));\r\n }\r\n\r\n public getEstimatedHeaderWidth(label: string, headerIndex: number): number {\r\n debug.assertFail(\"PureVirtualMethod: RowLayoutManager.getEstimatedHeaderWidth\");\r\n return -1;\r\n }\r\n }\r\n\r\n export class DashboardRowLayoutManager extends RowLayoutManager {\r\n public getEstimatedHeaderWidth(label: string, headerIndex: number): number {\r\n return this.getHeaderWidth(headerIndex);\r\n }\r\n\r\n protected canScroll(gridContextualWidth: number): boolean {\r\n return false;\r\n }\r\n\r\n protected _calculateSize(item: ITablixGridItem): number {\r\n return item.calculateSize();\r\n }\r\n\r\n private getHeaderWidth(headerIndex: number): number {\r\n // On the dashboard, we need to return 0 if the row header is static\r\n // (a table or a matrix without row groups)\r\n if (headerIndex === 0 && !this.owner.binder.hasRowGroups())\r\n return 0;\r\n\r\n // for dashboard layout it does not matter whether we pass an actual text or not\r\n return this.owner.getEstimatedTextWidth(undefined);\r\n }\r\n }\r\n\r\n export class CanvasRowLayoutManager extends RowLayoutManager {\r\n public getEstimatedHeaderWidth(label: string, headerIndex: number): number {\r\n // On the canvas the header width depends on the size of the content\r\n return this.owner.getEstimatedTextWidth(label);\r\n }\r\n\r\n protected canScroll(gridContextualWidth: number): boolean {\r\n return !Double.equalWithPrecision(this.dimension.scrollOffset, 0, DimensionLayoutManager._scrollOffsetPrecision) ||\r\n (((this.getRealizedItemsCount() - this._gridOffset) < this.dimension.getItemsCount()) && this._contextualWidthToFill > 0) ||\r\n Double.greaterWithPrecision(gridContextualWidth, this._contextualWidthToFill, DimensionLayoutManager._pixelPrecision);\r\n }\r\n\r\n protected _calculateSize(item: ITablixGridItem): number {\r\n return item.calculateSize();\r\n }\r\n }\r\n\r\n export class TablixLayoutManager {\r\n protected _owner: TablixControl;\r\n protected _container: HTMLElement;\r\n protected _columnLayoutManager: ColumnLayoutManager;\r\n protected _rowLayoutManager: RowLayoutManager;\r\n private _binder: ITablixBinder;\r\n\r\n private _scrollingDimension: TablixDimension;\r\n private _gridHost: HTMLElement;\r\n private _footersHost: HTMLElement;\r\n private _grid: internal.TablixGrid;\r\n private _allowHeaderResize: boolean = true;\r\n private _columnWidthsToPersist: ColumnWidthObject[];\r\n\r\n constructor(\r\n binder: ITablixBinder,\r\n grid: TablixGrid,\r\n columnLayoutManager: ColumnLayoutManager,\r\n rowLayoutManager: RowLayoutManager) {\r\n this._binder = binder;\r\n this._grid = grid;\r\n this._columnLayoutManager = columnLayoutManager;\r\n this._rowLayoutManager = rowLayoutManager;\r\n this._columnWidthsToPersist = [];\r\n }\r\n\r\n public initialize(owner: TablixControl): void {\r\n this._owner = owner;\r\n this._container = owner.container;\r\n this._gridHost = owner.contentHost;\r\n this._footersHost = owner.footerHost;\r\n this._grid.initialize(owner, this._gridHost, this._footersHost);\r\n }\r\n\r\n public get owner(): TablixControl {\r\n return this._owner;\r\n }\r\n\r\n public get binder(): ITablixBinder {\r\n return this._binder;\r\n }\r\n\r\n public get columnWidthsToPersist(): ColumnWidthObject[] {\r\n return this._columnWidthsToPersist;\r\n }\r\n\r\n public set columnWidthsToPersist(columnWidths: ColumnWidthObject[]) {\r\n this._columnWidthsToPersist = columnWidths;\r\n }\r\n\r\n public getTablixClassName(): string {\r\n debug.assertFail(\"PureVirtualMethod: TablixLayoutManager.getTablixClassName\");\r\n return null;\r\n }\r\n\r\n public getLayoutKind(): TablixLayoutKind {\r\n debug.assertFail(\"PureVirtualMethod: TablixLayoutManager.getLayoutKind\");\r\n\r\n // TODO ckerer: this method should not be necessary when we are done refactoring!\r\n return null;\r\n }\r\n\r\n public getOrCreateColumnHeader(item: any, items: any, rowIndex: number, columnIndex: number): ITablixCell {\r\n let hierarchyNav = this.owner.hierarchyNavigator;\r\n let row: TablixRow = this._grid.getOrCreateRow(rowIndex);\r\n let column: TablixColumn = this._grid.getOrCreateColumn(columnIndex + this._columnLayoutManager._gridOffset);\r\n let isLeaf = hierarchyNav.isLeaf(item);\r\n let cell: TablixCell = row.getOrCreateColumnHeader(column, this._columnLayoutManager.isScrollableHeader(item, items, columnIndex), isLeaf);\r\n\r\n let rowIdx = hierarchyNav.getLevel(item);\r\n cell.position.row.index = cell.position.row.indexInSiblings = rowIdx;\r\n cell.position.row.isFirst = rowIdx === 0;\r\n cell.position.row.isLast = isLeaf;\r\n\r\n let colIdx = hierarchyNav.getIndex(item);\r\n cell.position.column.index = cell.position.row.indexInSiblings = colIdx;\r\n cell.position.column.isFirst = hierarchyNav.areAllParentsFirst(item, items);\r\n cell.position.column.isLast = hierarchyNav.areAllParentsLast(item, items);\r\n\r\n this.enableCellHorizontalResize(isLeaf, cell);\r\n return cell;\r\n }\r\n\r\n public getOrCreateRowHeader(item: any, items: any, rowIndex: number, columnIndex: number): ITablixCell {\r\n let hierarchyNav = this.owner.hierarchyNavigator;\r\n let row: TablixRow = this._grid.getOrCreateRow(rowIndex + this._rowLayoutManager._gridOffset);\r\n let column: TablixColumn = this._grid.getOrCreateColumn(columnIndex);\r\n let isLeaf = hierarchyNav.isLeaf(item);\r\n let scrollable: boolean = this._rowLayoutManager.isScrollableHeader(item, items, rowIndex);\r\n\r\n if (row.getRealizedCellCount() === 0) {\r\n this.alignRowHeaderCells(item, row);\r\n }\r\n\r\n let cell: TablixCell = row.getOrCreateRowHeader(column, scrollable, hierarchyNav.isLeaf(item));\r\n let rowIdx = hierarchyNav.getIndex(item);\r\n cell.position.row.index = cell.position.row.indexInSiblings = rowIdx;\r\n cell.position.row.isFirst = hierarchyNav.areAllParentsFirst(item, items);\r\n cell.position.row.isLast = hierarchyNav.areAllParentsLast(item, items);\r\n\r\n let colIdx = hierarchyNav.getLevel(item);\r\n cell.position.column.index = cell.position.column.indexInSiblings = colIdx;\r\n cell.position.column.isFirst = colIdx === 0;\r\n cell.position.column.isLast = isLeaf;\r\n\r\n cell.enableHorizontalResize(false, this._columnLayoutManager);\r\n return cell;\r\n }\r\n\r\n public getOrCreateCornerCell(item: any, rowLevel: number, columnLevel: number): ITablixCell {\r\n let row: TablixRow = this._grid.getOrCreateRow(columnLevel);\r\n let column: TablixColumn = this._grid.getOrCreateColumn(rowLevel);\r\n\r\n let columnDepth = this._columnLayoutManager.dimension.getDepth();\r\n let isLeaf = columnLevel === (columnDepth - 1);\r\n\r\n let cell: TablixCell = row.getOrCreateCornerCell(column);\r\n let rowIdx = columnLevel;\r\n cell.position.row.index = cell.position.row.indexInSiblings = rowIdx;\r\n cell.position.row.isFirst = rowIdx === 0;\r\n cell.position.row.isLast = isLeaf;\r\n\r\n let colIdx = rowLevel;\r\n cell.position.column.index = cell.position.column.indexInSiblings = colIdx;\r\n cell.position.column.isFirst = colIdx === 0;\r\n cell.position.column.isLast = colIdx === this._rowLayoutManager.dimension.getDepth() - 1;\r\n\r\n this.enableCellHorizontalResize(isLeaf, cell);\r\n return cell;\r\n }\r\n\r\n public getOrCreateBodyCell(cellItem: any, rowItem: any, rowItems: any, rowIndex: number, columnIndex: number): ITablixCell {\r\n let scrollable: boolean;\r\n let row: TablixRow = this._grid.getOrCreateRow(rowIndex + this._rowLayoutManager._gridOffset);\r\n let column: TablixColumn = this._grid.getOrCreateColumn(columnIndex + this._columnLayoutManager._gridOffset);\r\n\r\n if (row._realizedBodyCells.length === 0 && this._owner.columnDimension.getFractionScrollOffset() !== 0) {\r\n scrollable = true;\r\n }\r\n else {\r\n scrollable = this._rowLayoutManager.isScrollableHeader(rowItem, rowItems, rowIndex);\r\n }\r\n\r\n let cell: TablixCell = row.getOrCreateBodyCell(column, scrollable);\r\n cell.position = cellItem.position;\r\n cell.enableHorizontalResize(false, this._columnLayoutManager);\r\n return cell;\r\n }\r\n\r\n public getOrCreateFooterBodyCell(cellItem: any, columnIndex: number): ITablixCell {\r\n let scrollable: boolean;\r\n let row: TablixRow = this._grid.getOrCreateFootersRow();\r\n let column: TablixColumn = this._grid.getOrCreateColumn(columnIndex + this._columnLayoutManager._gridOffset);\r\n\r\n scrollable = (row._realizedBodyCells.length === 0 && this._owner.columnDimension.getFractionScrollOffset() !== 0);\r\n\r\n let cell: TablixCell = row.getOrCreateFooterBodyCell(column, scrollable);\r\n cell.position = cellItem.position;\r\n cell.enableHorizontalResize(false, this._columnLayoutManager);\r\n return cell;\r\n }\r\n\r\n public getOrCreateFooterRowHeader(item: any, items: any): ITablixCell {\r\n let row: TablixRow = this._grid.getOrCreateFootersRow();\r\n let column: TablixColumn = this._grid.getOrCreateColumn(0);\r\n\r\n //debug.assert(this.owner.hierarchyNavigator.isLeaf(item), \"Leaf item expected\");\r\n\r\n let cell: TablixCell = row.getOrCreateFooterRowHeader(column);\r\n cell.position = undefined;\r\n cell.enableHorizontalResize(false, this._columnLayoutManager);\r\n return cell;\r\n }\r\n\r\n public getVisibleWidth(): number {\r\n debug.assertFail(\"PureVirtualMethod: TablixLayoutManager.getVisibleWidth\");\r\n return -1;\r\n }\r\n\r\n public getVisibleHeight(): number {\r\n debug.assertFail(\"PureVirtualMethod: TablixLayoutManager.getVisibleHeight\");\r\n return -1;\r\n }\r\n\r\n public updateColumnCount(rowDimension: TablixRowDimension, columnDimension: TablixColumnDimension) {\r\n debug.assertFail(\"PureVirtualMethod: TablixLayoutManager.updateColumnCount\");\r\n }\r\n\r\n public updateViewport(viewport: IViewport) {\r\n debug.assertFail(\"PureVirtualMethod: TablixLayoutManager.updateViewport\");\r\n }\r\n\r\n public getEstimatedRowHeight(): number {\r\n debug.assertFail(\"PureVirtualMethod: TablixLayoutManager.getEstimatedRowHeight\");\r\n return -1;\r\n }\r\n\r\n public getCellWidth(cell: ITablixCell): number {\r\n debug.assertFail(\"PureVirtualMethod: TablixLayoutManager.getCellWidth\");\r\n return -1;\r\n }\r\n\r\n public getContentWidth(cell: ITablixCell): number {\r\n debug.assertFail(\"PureVirtualMethod: TablixLayoutManager.getContentWidth\");\r\n return -1;\r\n }\r\n\r\n public adjustContentSize(hasImage: boolean): void {\r\n // default implementation has no adjustment\r\n }\r\n\r\n /**\r\n * This call makes room for parent header cells where neccessary.\r\n * Since HTML cells that span vertically displace other rows,\r\n * room has to be made for spanning headers that leave an exiting \r\n * row to enter the new row that it starts from and removed when\r\n * returning to an entering row.\r\n */\r\n private alignRowHeaderCells(item: any, currentRow: TablixRow): void {\r\n let index = currentRow.getRowHeaderLeafIndex();\r\n\r\n if (index === -1) {\r\n return;\r\n }\r\n\r\n let rowDimension: TablixRowDimension = this._owner.rowDimension;\r\n let leaf = rowDimension.getFirstVisibleChildLeaf(item);\r\n\r\n if (!this.owner.hierarchyNavigator.headerItemEquals(leaf, currentRow.getAllocatedCellAt(index).item)) {\r\n return;\r\n }\r\n\r\n currentRow.moveCellsBy(this.owner.hierarchyNavigator.getLevel(leaf) - this.owner.hierarchyNavigator.getLevel(item) - index);\r\n }\r\n\r\n public get grid(): TablixGrid {\r\n return this._grid;\r\n }\r\n\r\n public get rowLayoutManager(): DimensionLayoutManager {\r\n return this._rowLayoutManager;\r\n }\r\n\r\n public get columnLayoutManager(): DimensionLayoutManager {\r\n return this._columnLayoutManager;\r\n }\r\n\r\n protected showEmptySpaceHeader(): boolean {\r\n debug.assertFail(\"PureVirtualMethod: TablixLayoutManager.showEmptySpaceHeader\");\r\n return false;\r\n }\r\n\r\n public onStartRenderingSession(scrollingDimension: TablixDimension, parentElement: HTMLElement, clear: boolean): void {\r\n if (this.showEmptySpaceHeader()) {\r\n let cell: ITablixCell = this._grid.emptySpaceHeaderCell;\r\n if (cell) {\r\n this._binder.unbindEmptySpaceHeaderCell(cell);\r\n }\r\n\r\n cell = this._grid.emptySpaceFooterCell;\r\n if (cell) {\r\n this._binder.unbindEmptySpaceFooterCell(cell);\r\n }\r\n\r\n this._grid.HideEmptySpaceCells();\r\n }\r\n\r\n this._scrollingDimension = scrollingDimension;\r\n\r\n if (this._scrollingDimension) {\r\n (<DimensionLayoutManager>this._scrollingDimension.layoutManager).startScrollingSession();\r\n }\r\n\r\n this._rowLayoutManager.onStartRenderingSession();\r\n this._columnLayoutManager.onStartRenderingSession();\r\n this._grid.onStartRenderingSession(clear);\r\n\r\n let measureEnabled = this._columnLayoutManager.measureEnabled || this._rowLayoutManager.measureEnabled;\r\n if (measureEnabled)\r\n this.measureSampleText(parentElement);\r\n }\r\n\r\n public onEndRenderingSession(): void {\r\n this._rowLayoutManager.onEndRenderingSession();\r\n this._columnLayoutManager.onEndRenderingSession();\r\n\r\n if (this._scrollingDimension) {\r\n (<DimensionLayoutManager>this._scrollingDimension.layoutManager).endScrollingSession();\r\n }\r\n\r\n this._scrollingDimension = null;\r\n\r\n if (this.showEmptySpaceHeader()) {\r\n let emptySpace = this._columnLayoutManager.contextualWidthToFill - this._columnLayoutManager.getGridContextualWidth();\r\n if (emptySpace > 0) {\r\n this._grid.ShowEmptySpaceCells(this._owner.columnDimension.getDepth(), emptySpace);\r\n\r\n let cell: ITablixCell = this._grid.emptySpaceHeaderCell;\r\n if (cell) {\r\n this._binder.bindEmptySpaceHeaderCell(cell);\r\n }\r\n cell = this._grid.emptySpaceFooterCell;\r\n if (cell) {\r\n this._binder.bindEmptySpaceFooterCell(cell);\r\n }\r\n }\r\n }\r\n }\r\n\r\n public onStartRenderingIteration(clear: boolean): void {\r\n this._rowLayoutManager.onStartRenderingIteration(clear, this.getVisibleHeight());\r\n this._columnLayoutManager.onStartRenderingIteration(clear, this.getVisibleWidth());\r\n this._grid.onStartRenderingIteration();\r\n }\r\n\r\n public onEndRenderingIteration(): boolean {\r\n this._grid.onEndRenderingIteration();\r\n\r\n // ANDREMI: Comment out for static tablix\r\n this._columnLayoutManager.calculateSizes(); // calculate the entire grid first without altering the tree to avoid multiple measure pass invoking\r\n this._rowLayoutManager.calculateSizes();\r\n this._columnLayoutManager.fixSizes(); // now assign the sizes\r\n this._rowLayoutManager.fixSizes();\r\n\r\n this._columnLayoutManager.updateItemToResizeState(this._grid.realizedColumns); // if we are in a middle of a resize, the column to resize might have been swaped during the render, restore its resize state\r\n\r\n this._columnLayoutManager.applyScrolling();\r\n this._rowLayoutManager.applyScrolling();\r\n this._columnLayoutManager.onEndRenderingIteration();\r\n this._rowLayoutManager.onEndRenderingIteration();\r\n\r\n return this._columnLayoutManager.done && this._rowLayoutManager.done;\r\n }\r\n\r\n public onCornerCellRealized(item: any, cell: ITablixCell): void {\r\n let columnLeaf: boolean = this.owner.hierarchyNavigator.isColumnHierarchyLeaf(item);\r\n let rowLeaf: boolean = this.owner.hierarchyNavigator.isRowHierarchyLeaf(item);\r\n if (columnLeaf)\r\n (<TablixCell>cell)._column.OnLeafRealized(this._owner.hierarchyNavigator);\r\n this._columnLayoutManager.onCornerCellRealized(item, cell, columnLeaf);\r\n this._rowLayoutManager.onCornerCellRealized(item, cell, rowLeaf);\r\n }\r\n\r\n public onRowHeaderRealized(item: any, cell: ITablixCell): void {\r\n let hierarchyNavigator: ITablixHierarchyNavigator = this._owner.hierarchyNavigator;\r\n let leaf = hierarchyNavigator.isLeaf(item);\r\n let tablixCell = (<TablixCell>cell);\r\n if (tablixCell.colSpan > 1)\r\n tablixCell.setContainerWidth(-1);\r\n this._rowLayoutManager.onHeaderRealized(item, cell, leaf);\r\n }\r\n\r\n public onRowHeaderFooterRealized(item: any, cell: ITablixCell): void {\r\n }\r\n\r\n public onColumnHeaderRealized(item: any, cell: ITablixCell): void {\r\n let hierarchyNavigator: ITablixHierarchyNavigator = this._owner.hierarchyNavigator;\r\n let leaf = hierarchyNavigator.isLeaf(item);\r\n if (leaf)\r\n (<TablixCell>cell)._column.OnLeafRealized(this._owner.hierarchyNavigator);\r\n this._columnLayoutManager.onHeaderRealized(item, cell, leaf);\r\n }\r\n\r\n public onBodyCellRealized(item: any, cell: ITablixCell): void {\r\n }\r\n\r\n public onBodyCellFooterRealized(item: any, cell: ITablixCell): void {\r\n }\r\n\r\n public setAllowHeaderResize(value: boolean) {\r\n this._allowHeaderResize = value;\r\n }\r\n\r\n public enableCellHorizontalResize(isLeaf: boolean, cell: TablixCell) {\r\n let enableCellHorizontalResize = isLeaf && this._allowHeaderResize;\r\n cell.enableHorizontalResize(enableCellHorizontalResize, this._columnLayoutManager);\r\n }\r\n\r\n public getEstimatedTextWidth(label: string): number {\r\n debug.assertFail(\"PureVirtualMethod: TablixLayoutManager.getEstimatedTextWidth\");\r\n return -1;\r\n }\r\n\r\n public measureSampleText(parentElement: HTMLElement): void {\r\n debug.assertFail(\"PureVirtualMethod: TablixLayoutManager.measureSampleText\");\r\n }\r\n }\r\n\r\n export class DashboardTablixLayoutManager extends TablixLayoutManager {\r\n private _characterHeight: number;\r\n private _sizeComputationManager: SizeComputationManager;\r\n\r\n constructor(\r\n binder: ITablixBinder,\r\n sizeComputationManager: SizeComputationManager,\r\n grid: TablixGrid,\r\n rowRealizationManager: RowRealizationManager,\r\n columnRealizationManager: ColumnRealizationManager) {\r\n\r\n let dashboardColumnLayoutManager = new DashboardColumnLayoutManager(null, grid, columnRealizationManager);\r\n let dashboardRowLayoutManager = new DashboardRowLayoutManager(null, grid, rowRealizationManager);\r\n\r\n super(binder, grid, dashboardColumnLayoutManager, dashboardRowLayoutManager);\r\n\r\n dashboardColumnLayoutManager.owner = this;\r\n dashboardRowLayoutManager.owner = this;\r\n\r\n this._sizeComputationManager = sizeComputationManager;\r\n }\r\n\r\n public static createLayoutManager(binder: ITablixBinder): DashboardTablixLayoutManager {\r\n // computed sizes are shared between layout manager and grid presenter\r\n let sizeComputationManager = new SizeComputationManager();\r\n return new DashboardTablixLayoutManager(\r\n binder,\r\n sizeComputationManager,\r\n new TablixGrid(new DashboardTablixGridPresenter(sizeComputationManager)),\r\n new RowRealizationManager(binder),\r\n new ColumnRealizationManager(binder));\r\n }\r\n\r\n public getTablixClassName(): string {\r\n return \"tablixDashboard\";\r\n }\r\n\r\n public getLayoutKind(): TablixLayoutKind {\r\n return TablixLayoutKind.DashboardTile;\r\n }\r\n\r\n protected showEmptySpaceHeader(): boolean {\r\n return false;\r\n }\r\n\r\n public measureSampleText(parentElement: HTMLElement): void {\r\n let textProperties = TextMeasurementService.getSvgMeasurementProperties(<any>parentElement);\r\n this._characterHeight = TextMeasurementService.estimateSvgTextHeight(textProperties);\r\n\r\n this._sizeComputationManager.updateRowHeight(this._characterHeight);\r\n\r\n let actualTextSize = PixelConverter.toPoint(parseFloat(textProperties.fontSize));\r\n let scalingFactor = actualTextSize / controls.TablixDefaultTextSize;\r\n\r\n this._sizeComputationManager.updateScalingFactor(Double.toIncrement(scalingFactor, 0.05));\r\n }\r\n\r\n public getVisibleWidth(): number {\r\n return this._sizeComputationManager.visibleWidth;\r\n }\r\n\r\n public getVisibleHeight(): number {\r\n return this._sizeComputationManager.visibleHeight;\r\n }\r\n\r\n public getCellWidth(cell: ITablixCell): number {\r\n return this._sizeComputationManager.cellWidth;\r\n }\r\n\r\n public getContentWidth(cell: ITablixCell): number {\r\n return this._sizeComputationManager.contentWidth;\r\n }\r\n\r\n public getEstimatedTextWidth(label: string): number {\r\n // On the dashboard it does not matter what text we render, \r\n // we always use the same content width\r\n return this._sizeComputationManager.contentWidth;\r\n }\r\n\r\n public adjustContentSize(hasImage: boolean): void {\r\n this._sizeComputationManager.hasImageContent = hasImage;\r\n }\r\n\r\n public updateColumnCount(rowDimension: TablixRowDimension, columnDimension: TablixColumnDimension): void {\r\n // The total number of columns is the number (depth) of row groups + the number of (leaf) column group instances\r\n let rowDimensionDepth = rowDimension ? rowDimension.getDepth() : 0;\r\n let columnInstances = columnDimension ? columnDimension.getItemsCount() : 0;\r\n let totalColumnCount = rowDimensionDepth + columnInstances;\r\n\r\n // Adjust the column count by the static row header (if any)\r\n if (!this.binder.hasRowGroups())\r\n totalColumnCount--;\r\n\r\n this._sizeComputationManager.updateColumnCount(totalColumnCount);\r\n }\r\n\r\n public updateViewport(viewport: IViewport) {\r\n this._sizeComputationManager.updateViewport(viewport);\r\n }\r\n\r\n public getEstimatedRowHeight(): number {\r\n return this._characterHeight;\r\n }\r\n }\r\n\r\n export class CanvasTablixLayoutManager extends TablixLayoutManager {\r\n private characterWidth: number;\r\n private characterHeight: number;\r\n\r\n constructor(\r\n binder: ITablixBinder,\r\n grid: TablixGrid,\r\n rowRealizationManager: RowRealizationManager,\r\n columnRealizationManager: ColumnRealizationManager) {\r\n\r\n let canvasColumnLayoutManager = new CanvasColumnLayoutManager(null, grid, columnRealizationManager);\r\n let canvasRowLayoutManager = new CanvasRowLayoutManager(null, grid, rowRealizationManager);\r\n\r\n super(binder, grid, canvasColumnLayoutManager, canvasRowLayoutManager);\r\n\r\n canvasColumnLayoutManager.owner = this;\r\n canvasRowLayoutManager.owner = this;\r\n }\r\n\r\n public static createLayoutManager(binder: ITablixBinder, columnWidthManager: TablixColumnWidthManager): CanvasTablixLayoutManager {\r\n return new CanvasTablixLayoutManager(\r\n binder,\r\n new TablixGrid(new controls.internal.CanvasTablixGridPresenter(columnWidthManager)),\r\n new RowRealizationManager(binder),\r\n new ColumnRealizationManager(binder));\r\n }\r\n\r\n public getTablixClassName(): string {\r\n return \"tablixCanvas\";\r\n }\r\n\r\n public getLayoutKind(): TablixLayoutKind {\r\n return TablixLayoutKind.Canvas;\r\n }\r\n\r\n public measureSampleText(parentElement: HTMLElement): void {\r\n // TODO: Use TextMeasurementService once the DOM methods are fixed (they are not working right now)\r\n let textDiv = controls.internal.TablixUtils.createDiv();\r\n textDiv.style.cssFloat = 'left';\r\n textDiv.style.whiteSpace = 'nowrap';\r\n textDiv.style.overflow = 'hidden';\r\n textDiv.style.lineHeight = 'normal';\r\n parentElement.appendChild(textDiv);\r\n\r\n let textNode = document.createTextNode(\"a\");\r\n textDiv.appendChild(textNode);\r\n\r\n this.characterWidth = controls.HTMLElementUtils.getElementWidth(textDiv);\r\n this.characterHeight = controls.HTMLElementUtils.getElementHeight(textDiv);\r\n\r\n textDiv.removeChild(textNode);\r\n parentElement.removeChild(textDiv);\r\n }\r\n\r\n protected showEmptySpaceHeader(): boolean {\r\n return !this._columnLayoutManager.fillProportionally;\r\n }\r\n\r\n public getVisibleWidth(): number {\r\n if (this._columnLayoutManager.measureEnabled) {\r\n if (this._owner.autoSizeWidth && this._owner.maxWidth) {\r\n return this._owner.maxWidth;\r\n } else {\r\n return HTMLElementUtils.getElementWidth(this._container);\r\n }\r\n }\r\n\r\n return -1;\r\n }\r\n\r\n public getVisibleHeight(): number {\r\n if (this._rowLayoutManager.measureEnabled) {\r\n if (this._owner.autoSizeHeight && this._owner.maxHeight) {\r\n return this._owner.maxHeight;\r\n } else {\r\n return HTMLElementUtils.getElementHeight(this._container);\r\n }\r\n }\r\n\r\n return -1;\r\n }\r\n\r\n public getCellWidth(cell: ITablixCell): number {\r\n return cell.containerWidth;\r\n }\r\n\r\n public getContentWidth(cell: ITablixCell): number {\r\n return cell.contentWidth;\r\n }\r\n\r\n public getEstimatedTextWidth(text: string): number {\r\n return text ? text.length * this.characterWidth : 0;\r\n }\r\n\r\n public updateColumnCount(rowDimension: TablixRowDimension, columnDimension: TablixColumnDimension) {\r\n // We currently only need to update model information when using dashboard layouts\r\n }\r\n\r\n public updateViewport(viewport: IViewport) {\r\n // We currently only need to update model information when using dashboard layouts\r\n }\r\n\r\n public getEstimatedRowHeight(): number {\r\n return this.characterHeight;\r\n }\r\n }\r\n}\n\n\n/** WEBPACK FOOTER **\n ** ./src/Clients/Visuals/controls/tablix/internal/tablixLayoutManager.ts\n **/","/*\r\n * Power BI Visualizations\r\n *\r\n * Copyright (c) Microsoft Corporation\r\n * All rights reserved.\r\n * MIT License\r\n *\r\n * Permission is hereby granted, free of charge, to any person obtaining a copy\r\n * of this software and associated documentation files (the \"\"Software\"\"), to deal\r\n * in the Software without restriction, including without limitation the rights\r\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\r\n * copies of the Software, and to permit persons to whom the Software is\r\n * furnished to do so, subject to the following conditions:\r\n *\r\n * The above copyright notice and this permission notice shall be included in\r\n * all copies or substantial portions of the Software.\r\n *\r\n * THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\r\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\r\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\r\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\r\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\r\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\r\n * THE SOFTWARE.\r\n */\r\n\r\n/// <reference path=\"../../../_references.ts\"/>\r\n\r\nmodule powerbi.visuals.controls {\r\n export module HTMLElementUtils {\r\n export function clearChildren(element: HTMLElement): void {\r\n if (!element) {\r\n return;\r\n }\r\n\r\n while (element.hasChildNodes()) {\r\n element.removeChild(element.firstChild);\r\n }\r\n }\r\n\r\n export function setElementTop(element: HTMLElement, top: number): void {\r\n element.style.top = top + \"px\";\r\n }\r\n\r\n export function setElementLeft(element: HTMLElement, left: number): void {\r\n element.style.left = left + \"px\";\r\n }\r\n\r\n export function setElementHeight(element: HTMLElement, height: number): void {\r\n if (HTMLElementUtils.isAutoSize(height))\r\n element.style.height = \"\";\r\n else\r\n element.style.height = height + \"px\";\r\n }\r\n\r\n export function setElementWidth(element: HTMLElement, width: number): void {\r\n if (HTMLElementUtils.isAutoSize(width))\r\n element.style.width = \"\";\r\n else\r\n element.style.width = width + \"px\";\r\n }\r\n\r\n export function getElementWidth(element: HTMLElement): number {\r\n return element.offsetWidth;\r\n }\r\n\r\n export function getElementHeight(element: HTMLElement): number {\r\n return element.offsetHeight;\r\n }\r\n\r\n export function isAutoSize(size: number): boolean {\r\n return size === -1;\r\n }\r\n\r\n export function getAccumulatedScale(element: HTMLElement): number {\r\n let scale: number = 1;\r\n while (element) {\r\n scale *= HTMLElementUtils.getScale(element);\r\n element = element.parentElement;\r\n }\r\n\r\n return scale;\r\n }\r\n\r\n /**\r\n * Get scale of element, return 1 when not scaled.\r\n */\r\n export function getScale(element: any): number {\r\n element = $(element);\r\n\r\n let str = element.css('-webkit-transform') ||\r\n element.css('-moz-transform') ||\r\n element.css('-ms-transform') ||\r\n element.css('-o-transform') ||\r\n element.css('transform');\r\n\r\n return (str && (\r\n str.match(/\\d*\\.\\d*/) && Number(str.match(/\\d*\\.\\d*/)[0]) ||\r\n str.match(/\\d+/) && Number(str.match(/\\d+/)[0]))\r\n ) || 1;\r\n }\r\n }\r\n}\r\n\r\nmodule powerbi.visuals.controls.internal {\r\n import DomFactory = InJs.DomFactory;\r\n import DataViewObjectDefinitions = powerbi.data.DataViewObjectDefinitions;\r\n import DataViewRoleWildCard = data.DataViewRoleWildcard;\r\n\r\n export module TablixObjects {\r\n export const ObjectGeneral: string = \"general\";\r\n export const ObjectGrid: string = \"grid\";\r\n export const ObjectColumnHeaders: string = \"columnHeaders\";\r\n export const ObjectRowHeaders: string = \"rowHeaders\";\r\n export const ObjectValues: string = \"values\";\r\n export const ObjectTotal: string = \"total\";\r\n export const ObjectSubTotals: string = \"subTotals\";\r\n\r\n export interface ObjectValueGetterFunction {\r\n <T>(objects: DataViewObjects, propertyId: DataViewObjectPropertyIdentifier, defaultValue?: T): T;\r\n }\r\n\r\n /**\r\n * Represents a DataViewObjects property related to the Tablix\r\n */\r\n export class TablixProperty {\r\n public objectName: string;\r\n public propertyName: string;\r\n public defaultValue: any;\r\n private getterFuntion: ObjectValueGetterFunction;\r\n\r\n /**\r\n * Creates a new TablixProperty\r\n * @param {string} objectName Object Name\r\n * @param {string} propertyName Property Name\r\n * @param {any} defaultValue Default value of the Property\r\n * @param {ObjectValueGetterFunction} getterFuntion Function used to get the Property value from the Objects\r\n */\r\n constructor(objectName: string, propertyName: string, defaultValue: any, getterFuntion: ObjectValueGetterFunction) {\r\n this.objectName = objectName;\r\n this.propertyName = propertyName;\r\n this.defaultValue = defaultValue;\r\n this.getterFuntion = getterFuntion;\r\n }\r\n\r\n /**\r\n * Gets the PropertyIdentifier for the Property\r\n * @returns PropertyIdentifier for the Property\r\n */\r\n public getPropertyID(): DataViewObjectPropertyIdentifier {\r\n return { objectName: this.objectName, propertyName: this.propertyName };\r\n }\r\n\r\n /**\r\n * Gets the value of the Property from the Objects\r\n * @param {DataViewObjects} objects DataView Objects to get the value from\r\n * @param {boolean} useDefault True to fall back to the Default value if the Property is missing from the objects. False to return undefined\r\n * @returns Value of the property\r\n */\r\n public getValue<T>(objects: DataViewObjects): T {\r\n // We use this when we intend to have undefined for missing properties. Useful in letting styles fallback to CSS if not defined\r\n return this.getterFuntion<T>(objects, this.getPropertyID(), this.defaultValue);\r\n }\r\n }\r\n\r\n // Per Column\r\n export const PropColumnFormatString = new TablixProperty(ObjectGeneral, 'formatString', undefined, DataViewObjects.getValue);\r\n\r\n // General\r\n export const PropGeneralAutoSizeColumns = new TablixProperty(ObjectGeneral, 'autoSizeColumnWidth', true, DataViewObjects.getValue);\r\n export const PropGeneralTextSize = new TablixProperty(ObjectGeneral, 'textSize', 8, DataViewObjects.getValue);\r\n export const PropGeneralTableTotals = new TablixProperty(ObjectGeneral, 'totals', true, DataViewObjects.getValue);\r\n export const PropGeneralMatrixRowSubtotals = new TablixProperty(ObjectGeneral, 'rowSubtotals', true, DataViewObjects.getValue);\r\n export const PropGeneralMatrixColumnSubtotals = new TablixProperty(ObjectGeneral, 'columnSubtotals', true, DataViewObjects.getValue);\r\n\r\n //Grid\r\n export const PropGridVertical = new TablixProperty(ObjectGrid, 'gridVertical', false, DataViewObjects.getValue);\r\n export const PropGridVerticalColor = new TablixProperty(ObjectGrid, 'gridVerticalColor', \"#E8E8E8\", DataViewObjects.getFillColor);\r\n export const PropGridVerticalWeight = new TablixProperty(ObjectGrid, 'gridVerticalWeight', 1, DataViewObjects.getValue);\r\n export const PropGridHorizontalTable = new TablixProperty(ObjectGrid, 'gridHorizontal', true, DataViewObjects.getValue);\r\n export const PropGridHorizontalMatrix = new TablixProperty(ObjectGrid, 'gridHorizontal', false, DataViewObjects.getValue);\r\n export const PropGridHorizontalColor = new TablixProperty(ObjectGrid, 'gridHorizontalColor', \"#E8E8E8\", DataViewObjects.getFillColor);\r\n export const PropGridHorizontalWeight = new TablixProperty(ObjectGrid, 'gridHorizontalWeight', 1, DataViewObjects.getValue);\r\n export const PropGridRowPadding = new TablixProperty(ObjectGrid, 'rowPadding', 0, DataViewObjects.getValue);\r\n export const PropGridOutlineColor = new TablixProperty(ObjectGrid, 'outlineColor', \"#CCC\", DataViewObjects.getFillColor);\r\n export const PropGridOutlineWeight = new TablixProperty(ObjectGrid, 'outlineWeight', 1, DataViewObjects.getValue);\r\n export const PropGridImageHeight = new TablixProperty(ObjectGrid, 'imageHeight', 75, DataViewObjects.getValue);\r\n\r\n // Column Headers\r\n export const PropColumnsFontColor = new TablixProperty(ObjectColumnHeaders, 'fontColor', \"#666\", DataViewObjects.getFillColor);\r\n export const PropColumnsBackColor = new TablixProperty(ObjectColumnHeaders, 'backColor', undefined, DataViewObjects.getFillColor);\r\n export const PropColumnsOutline = new TablixProperty(ObjectColumnHeaders, 'outline', \"BottomOnly\", DataViewObjects.getValue);\r\n\r\n // Row Headers\r\n export const PropRowsFontColor = new TablixProperty(ObjectRowHeaders, 'fontColor', \"#666\", DataViewObjects.getFillColor);\r\n export const PropRowsBackColor = new TablixProperty(ObjectRowHeaders, 'backColor', undefined, DataViewObjects.getFillColor);\r\n export const PropRowsOutline = new TablixProperty(ObjectRowHeaders, 'outline', \"RightOnly\", DataViewObjects.getValue);\r\n\r\n // Values\r\n export const PropValuesBackColor = new TablixProperty(ObjectValues, 'backColor', undefined, DataViewObjects.getFillColor);\r\n export const PropValuesFontColorPrimary = new TablixProperty(ObjectValues, 'fontColorPrimary', \"#333\", DataViewObjects.getFillColor);\r\n export const PropValuesBackColorPrimary = new TablixProperty(ObjectValues, 'backColorPrimary', undefined, DataViewObjects.getFillColor);\r\n export const PropValuesFontColorSecondary = new TablixProperty(ObjectValues, 'fontColorSecondary', \"#333\", DataViewObjects.getFillColor);\r\n export const PropValuesBackColorSecondary = new TablixProperty(ObjectValues, 'backColorSecondary', undefined, DataViewObjects.getFillColor);\r\n export const PropValuesOutline = new TablixProperty(ObjectValues, 'outline', \"None\", DataViewObjects.getValue);\r\n export const PropValuesUrlIconProp = new TablixProperty(ObjectValues, 'urlIcon', false, DataViewObjects.getValue);\r\n\r\n // Total\r\n export const PropTotalFontColor = new TablixProperty(ObjectTotal, 'fontColor', \"#333\", DataViewObjects.getFillColor);\r\n export const PropTotalBackColor = new TablixProperty(ObjectTotal, 'backColor', undefined, DataViewObjects.getFillColor);\r\n export const PropTotalOutline = new TablixProperty(ObjectTotal, 'outline', \"TopOnly\", DataViewObjects.getValue);\r\n\r\n // SubTotals\r\n export const PropSubTotalsFontColor = new TablixProperty(ObjectSubTotals, 'fontColor', \"#333\", DataViewObjects.getFillColor);\r\n export const PropSubTotalsBackColor = new TablixProperty(ObjectSubTotals, 'backColor', undefined, DataViewObjects.getFillColor);\r\n export const PropSubTotalsOutline = new TablixProperty(ObjectSubTotals, 'outline', \"TopOnly\", DataViewObjects.getValue);\r\n\r\n /**\r\n * Get the DataViewObject from the DataView\r\n * @param {DataView} dataview The DataView\r\n * @returns DataViewObjects (dataView.metadata.objects)\r\n */\r\n export function getMetadadataObjects(dataview: DataView): DataViewObjects {\r\n if (dataview && dataview.metadata)\r\n return dataview.metadata.objects;\r\n\r\n return null;\r\n }\r\n\r\n export function enumerateObjectRepetition(enumeration: VisualObjectRepetition[], dataView: DataView, tablixType: TablixType): void {\r\n debug.assertValue(enumeration, 'enumeration should be defined');\r\n debug.assertValue(dataView, \"dataView can't be undefined\");\r\n\r\n // We currently only support Table\r\n if (tablixType !== TablixType.Table)\r\n return;\r\n\r\n let columns = getTableColumnMetadata(dataView);\r\n for (let column of columns) {\r\n let repetition: VisualObjectRepetition = {\r\n selector: {\r\n data: [DataViewRoleWildCard.fromRoles(['Values'])],\r\n metadata: column.queryName,\r\n },\r\n objects: {\r\n [TablixObjects.ObjectValues]: {\r\n formattingProperties: [TablixObjects.PropValuesBackColor.propertyName]\r\n },\r\n }\r\n };\r\n\r\n enumeration.push(repetition);\r\n }\r\n }\r\n\r\n export function enumerateObjectInstances(options: EnumerateVisualObjectInstancesOptions, enumeration: ObjectEnumerationBuilder, dataView: DataView, tablixType: TablixType): void {\r\n debug.assertValue(dataView, \"dataView can't be undefined\");\r\n\r\n let objects = getMetadadataObjects(dataView);\r\n\r\n let totalsShown: boolean = true;\r\n\r\n if (tablixType === TablixType.Table) {\r\n totalsShown = shouldShowTableTotalsOption(dataView) && shouldShowTableTotals(objects);\r\n }\r\n else {\r\n totalsShown =\r\n (shouldShowColumnSubtotalsOption(dataView) && shouldShowColumnSubtotals(objects)) ||\r\n (shouldShowRowSubtotalsOption(dataView) && shouldShowRowSubtotals(objects));\r\n }\r\n\r\n switch (options.objectName) {\r\n case TablixObjects.ObjectGeneral:\r\n enumerateGeneralOptions(enumeration, objects, tablixType, dataView);\r\n break;\r\n case TablixObjects.ObjectGrid:\r\n enumerateGridOptions(enumeration, objects, tablixType);\r\n break;\r\n case TablixObjects.ObjectColumnHeaders:\r\n enumerateColumnHeadersOptions(enumeration, objects);\r\n break;\r\n case TablixObjects.ObjectRowHeaders:\r\n enumerateRowHeadersOptions(enumeration, objects);\r\n break;\r\n case TablixObjects.ObjectValues:\r\n enumerateValuesOptions(enumeration, objects, tablixType);\r\n break;\r\n case TablixObjects.ObjectTotal:\r\n if (totalsShown)\r\n enumerateTotalOptions(enumeration, objects);\r\n break;\r\n case TablixObjects.ObjectSubTotals:\r\n if (totalsShown)\r\n enumerateSubTotalsOptions(enumeration, objects);\r\n break;\r\n default:\r\n break;\r\n }\r\n }\r\n\r\n export function enumerateGeneralOptions(enumeration: ObjectEnumerationBuilder, objects: DataViewObjects, tablixType: TablixType, dataView: DataView): void {\r\n let visualObjectinstance: VisualObjectInstance = {\r\n selector: null,\r\n objectName: TablixObjects.ObjectGeneral,\r\n properties: {\r\n autoSizeColumnWidth: TablixObjects.PropGeneralAutoSizeColumns.getValue(objects),\r\n textSize: TablixObjects.PropGeneralTextSize.getValue<number>(objects),\r\n }\r\n };\r\n\r\n let properties = visualObjectinstance.properties;\r\n\r\n // Total and SubTotals\r\n switch (tablixType) {\r\n case TablixType.Table:\r\n if (shouldShowTableTotalsOption(dataView))\r\n properties[TablixObjects.PropGeneralTableTotals.propertyName] = shouldShowTableTotals(objects);\r\n break;\r\n\r\n case TablixType.Matrix:\r\n if (shouldShowRowSubtotalsOption(dataView))\r\n properties[TablixObjects.PropGeneralMatrixRowSubtotals.propertyName] = shouldShowRowSubtotals(objects);\r\n if (shouldShowColumnSubtotalsOption(dataView))\r\n properties[TablixObjects.PropGeneralMatrixColumnSubtotals.propertyName] = shouldShowColumnSubtotals(objects);\r\n break;\r\n }\r\n\r\n enumeration.pushInstance(visualObjectinstance);\r\n }\r\n\r\n export function enumerateGridOptions(enumeration: ObjectEnumerationBuilder, objects: DataViewObjects, tablixType: TablixType): void {\r\n let visualObjectinstance: VisualObjectInstance = {\r\n selector: null,\r\n objectName: TablixObjects.ObjectGeneral,\r\n properties: {}\r\n };\r\n let properties = visualObjectinstance.properties;\r\n\r\n // Vertical Grid\r\n let verticalGridEnabled = TablixObjects.PropGridVertical.getValue<boolean>(objects);\r\n properties[TablixObjects.PropGridVertical.propertyName] = verticalGridEnabled;\r\n if (verticalGridEnabled) {\r\n properties[TablixObjects.PropGridVerticalColor.propertyName] = TablixObjects.PropGridVerticalColor.getValue<string>(objects);\r\n properties[TablixObjects.PropGridVerticalWeight.propertyName] = TablixObjects.PropGridVerticalWeight.getValue<number>(objects);\r\n }\r\n\r\n // Horizontal Grid\r\n let horizontalGridEnabled = (tablixType === TablixType.Table ? TablixObjects.PropGridHorizontalTable : TablixObjects.PropGridHorizontalMatrix).getValue<boolean>(objects);\r\n properties[(tablixType === TablixType.Table ? TablixObjects.PropGridHorizontalTable : TablixObjects.PropGridHorizontalMatrix).propertyName] = horizontalGridEnabled;\r\n if (horizontalGridEnabled) {\r\n properties[TablixObjects.PropGridHorizontalColor.propertyName] = TablixObjects.PropGridHorizontalColor.getValue<string>(objects);\r\n properties[TablixObjects.PropGridHorizontalWeight.propertyName] = TablixObjects.PropGridHorizontalWeight.getValue<number>(objects);\r\n }\r\n\r\n // Row Padding\r\n properties[TablixObjects.PropGridRowPadding.propertyName] = TablixObjects.PropGridRowPadding.getValue<number>(objects);\r\n\r\n // Outline\r\n properties[TablixObjects.PropGridOutlineColor.propertyName] = TablixObjects.PropGridOutlineColor.getValue<string>(objects);\r\n properties[TablixObjects.PropGridOutlineWeight.propertyName] = TablixObjects.PropGridOutlineWeight.getValue<number>(objects);\r\n\r\n // Image Height\r\n properties[TablixObjects.PropGridImageHeight.propertyName] = TablixObjects.PropGridImageHeight.getValue<number>(objects);\r\n\r\n enumeration.pushInstance(visualObjectinstance);\r\n }\r\n\r\n export function enumerateColumnHeadersOptions(enumeration: ObjectEnumerationBuilder, objects: DataViewObjects): void {\r\n enumeration.pushInstance({\r\n selector: null,\r\n objectName: TablixObjects.ObjectColumnHeaders,\r\n properties: {\r\n fontColor: TablixObjects.PropColumnsFontColor.getValue<string>(objects),\r\n backColor: TablixObjects.PropColumnsBackColor.getValue<string>(objects),\r\n outline: TablixObjects.PropColumnsOutline.getValue<string>(objects),\r\n }\r\n });\r\n }\r\n\r\n export function enumerateRowHeadersOptions(enumeration: ObjectEnumerationBuilder, objects: DataViewObjects): void {\r\n enumeration.pushInstance({\r\n selector: null,\r\n objectName: TablixObjects.ObjectRowHeaders,\r\n properties: {\r\n fontColor: TablixObjects.PropRowsFontColor.getValue<string>(objects),\r\n backColor: TablixObjects.PropRowsBackColor.getValue<string>(objects),\r\n outline: TablixObjects.PropRowsOutline.getValue<string>(objects),\r\n }\r\n });\r\n }\r\n\r\n export function enumerateValuesOptions(enumeration: ObjectEnumerationBuilder, objects: DataViewObjects, tablixType: TablixType): void {\r\n let instance: VisualObjectInstance = {\r\n selector: null,\r\n objectName: TablixObjects.ObjectValues,\r\n properties: {\r\n fontColorPrimary: TablixObjects.PropValuesFontColorPrimary.getValue<string>(objects),\r\n backColorPrimary: TablixObjects.PropValuesBackColorPrimary.getValue<string>(objects),\r\n fontColorSecondary: TablixObjects.PropValuesFontColorSecondary.getValue<string>(objects),\r\n backColorSecondary: TablixObjects.PropValuesBackColorSecondary.getValue<string>(objects),\r\n outline: TablixObjects.PropValuesOutline.getValue<string>(objects),\r\n }\r\n };\r\n\r\n if (tablixType === TablixType.Table)\r\n instance.properties[TablixObjects.PropValuesUrlIconProp.propertyName] = TablixObjects.PropValuesUrlIconProp.getValue<boolean>(objects);\r\n\r\n enumeration.pushInstance(instance);\r\n }\r\n\r\n export function enumerateTotalOptions(enumeration: ObjectEnumerationBuilder, objects: DataViewObjects): void {\r\n enumeration.pushInstance({\r\n selector: null,\r\n objectName: TablixObjects.ObjectTotal,\r\n properties: {\r\n fontColor: TablixObjects.PropTotalFontColor.getValue<string>(objects),\r\n backColor: TablixObjects.PropTotalBackColor.getValue<string>(objects),\r\n outline: TablixObjects.PropTotalOutline.getValue<string>(objects),\r\n }\r\n });\r\n }\r\n\r\n export function enumerateSubTotalsOptions(enumeration: ObjectEnumerationBuilder, objects: DataViewObjects): void {\r\n enumeration.pushInstance({\r\n selector: null,\r\n objectName: TablixObjects.ObjectSubTotals,\r\n properties: {\r\n fontColor: TablixObjects.PropSubTotalsFontColor.getValue<string>(objects),\r\n backColor: TablixObjects.PropSubTotalsBackColor.getValue<string>(objects),\r\n }\r\n });\r\n }\r\n\r\n export function getTableObjects(dataView: DataView): TablixFormattingPropertiesTable {\r\n let objects = getMetadadataObjects(dataView);\r\n\r\n let formattingProperties: TablixFormattingPropertiesTable = {\r\n\r\n general: {\r\n autoSizeColumnWidth: TablixObjects.PropGeneralAutoSizeColumns.getValue<boolean>(objects),\r\n textSize: TablixObjects.PropGeneralTextSize.getValue<number>(objects),\r\n totals: shouldShowTableTotals(objects),\r\n },\r\n };\r\n\r\n formattingProperties.grid = {\r\n gridVertical: TablixObjects.PropGridVertical.getValue<boolean>(objects),\r\n gridVerticalColor: TablixObjects.PropGridVerticalColor.getValue<string>(objects),\r\n gridVerticalWeight: TablixObjects.PropGridVerticalWeight.getValue<number>(objects),\r\n gridHorizontal: TablixObjects.PropGridHorizontalTable.getValue<boolean>(objects),\r\n gridHorizontalColor: TablixObjects.PropGridHorizontalColor.getValue<string>(objects),\r\n gridHorizontalWeight: TablixObjects.PropGridHorizontalWeight.getValue<number>(objects),\r\n outlineColor: TablixObjects.PropGridOutlineColor.getValue<string>(objects),\r\n outlineWeight: TablixObjects.PropGridOutlineWeight.getValue<number>(objects),\r\n rowPadding: TablixObjects.PropGridRowPadding.getValue<number>(objects),\r\n imageHeight: TablixObjects.PropGridImageHeight.getValue<number>(objects),\r\n };\r\n\r\n formattingProperties.columnHeaders = {\r\n fontColor: TablixObjects.PropColumnsFontColor.getValue<string>(objects),\r\n backColor: TablixObjects.PropColumnsBackColor.getValue<string>(objects),\r\n outline: TablixObjects.PropColumnsOutline.getValue<string>(objects),\r\n };\r\n\r\n formattingProperties.values = {\r\n fontColorPrimary: TablixObjects.PropValuesFontColorPrimary.getValue<string>(objects),\r\n backColorPrimary: TablixObjects.PropValuesBackColorPrimary.getValue<string>(objects),\r\n fontColorSecondary: TablixObjects.PropValuesFontColorSecondary.getValue<string>(objects),\r\n backColorSecondary: TablixObjects.PropValuesBackColorSecondary.getValue<string>(objects),\r\n outline: TablixObjects.PropValuesOutline.getValue<string>(objects),\r\n urlIcon: TablixObjects.PropValuesUrlIconProp.getValue<boolean>(objects),\r\n };\r\n\r\n formattingProperties.total = {\r\n fontColor: TablixObjects.PropTotalFontColor.getValue<string>(objects),\r\n backColor: TablixObjects.PropTotalBackColor.getValue<string>(objects),\r\n outline: TablixObjects.PropTotalOutline.getValue<string>(objects),\r\n };\r\n\r\n return formattingProperties;\r\n }\r\n\r\n export function getMatrixObjects(dataView: DataView): TablixFormattingPropertiesMatrix {\r\n let objects = getMetadadataObjects(dataView);\r\n\r\n let formattingProperties: TablixFormattingPropertiesMatrix = {\r\n general: {\r\n autoSizeColumnWidth: TablixObjects.PropGeneralAutoSizeColumns.getValue<boolean>(objects),\r\n textSize: TablixObjects.PropGeneralTextSize.getValue<number>(objects),\r\n rowSubtotals: shouldShowRowSubtotals(objects),\r\n columnSubtotals: shouldShowColumnSubtotals(objects),\r\n },\r\n };\r\n\r\n formattingProperties.grid = {\r\n gridVertical: TablixObjects.PropGridVertical.getValue<boolean>(objects),\r\n gridVerticalColor: TablixObjects.PropGridVerticalColor.getValue<string>(objects),\r\n gridVerticalWeight: TablixObjects.PropGridVerticalWeight.getValue<number>(objects),\r\n gridHorizontal: TablixObjects.PropGridHorizontalMatrix.getValue<boolean>(objects),\r\n gridHorizontalColor: TablixObjects.PropGridHorizontalColor.getValue<string>(objects),\r\n gridHorizontalWeight: TablixObjects.PropGridHorizontalWeight.getValue<number>(objects),\r\n outlineColor: TablixObjects.PropGridOutlineColor.getValue<string>(objects),\r\n outlineWeight: TablixObjects.PropGridOutlineWeight.getValue<number>(objects),\r\n rowPadding: TablixObjects.PropGridRowPadding.getValue<number>(objects),\r\n imageHeight: TablixObjects.PropGridImageHeight.getValue<number>(objects),\r\n };\r\n\r\n formattingProperties.columnHeaders = {\r\n fontColor: TablixObjects.PropColumnsFontColor.getValue<string>(objects),\r\n backColor: TablixObjects.PropColumnsBackColor.getValue<string>(objects),\r\n outline: TablixObjects.PropColumnsOutline.getValue<string>(objects),\r\n };\r\n\r\n formattingProperties.rowHeaders = {\r\n fontColor: TablixObjects.PropRowsFontColor.getValue<string>(objects),\r\n backColor: TablixObjects.PropRowsBackColor.getValue<string>(objects),\r\n outline: TablixObjects.PropRowsOutline.getValue<string>(objects),\r\n };\r\n\r\n formattingProperties.values = {\r\n fontColorPrimary: TablixObjects.PropValuesFontColorPrimary.getValue<string>(objects),\r\n backColorPrimary: TablixObjects.PropValuesBackColorPrimary.getValue<string>(objects),\r\n fontColorSecondary: TablixObjects.PropValuesFontColorSecondary.getValue<string>(objects),\r\n backColorSecondary: TablixObjects.PropValuesBackColorSecondary.getValue<string>(objects),\r\n outline: TablixObjects.PropValuesOutline.getValue<string>(objects),\r\n };\r\n\r\n formattingProperties.subtotals = {\r\n fontColor: TablixObjects.PropSubTotalsFontColor.getValue<string>(objects),\r\n backColor: TablixObjects.PropSubTotalsBackColor.getValue<string>(objects),\r\n outline: TablixObjects.PropSubTotalsOutline.getValue<string>(objects),\r\n };\r\n\r\n return formattingProperties;\r\n }\r\n\r\n /**\r\n * Generate default objects for the Table/Matrix to set default styling\r\n * @param {TablixType} tablixType Tablix Type: table | matrix\r\n * @returns DataViewObjects that can be attached to the DataViewMetadata\r\n */\r\n export function generateTablixDefaultObjects(tablixType: TablixType): data.DataViewObjectDefinitions {\r\n return {\r\n general: [{\r\n selector: null,\r\n properties: {\r\n textSize: DataViewObjectDefinitions.encodePropertyValue(12, { numeric: true }),\r\n totals: DataViewObjectDefinitions.encodePropertyValue(false, { bool: true }),\r\n }\r\n }],\r\n };\r\n }\r\n\r\n export function getTextSizeInPx(textSize: number): string {\r\n return jsCommon.PixelConverter.fromPoint(textSize);\r\n }\r\n\r\n export function shouldShowTableTotals(objects: DataViewObjects): boolean {\r\n return TablixObjects.PropGeneralTableTotals.getValue<boolean>(objects);\r\n }\r\n\r\n function shouldShowTableTotalsOption(dataView: DataView): boolean {\r\n if (dataView && dataView.table && !_.isEmpty(dataView.table.columns)) {\r\n let columns = dataView.table.columns;\r\n if (_.some(columns, (column) => column.discourageAggregationAcrossGroups))\r\n return false;\r\n }\r\n\r\n return true;\r\n }\r\n\r\n function getTableColumnMetadata(dataView: DataView): DataViewMetadataColumn[] {\r\n if (!dataView || !dataView.table || _.isEmpty(dataView.table.columns))\r\n return;\r\n\r\n return dataView.table.columns;\r\n }\r\n\r\n export function shouldShowRowSubtotals(objects: DataViewObjects): boolean {\r\n return TablixObjects.PropGeneralMatrixRowSubtotals.getValue<boolean>(objects);\r\n }\r\n\r\n function shouldShowRowSubtotalsOption(dataView: DataView): boolean {\r\n return !(dataView &&\r\n dataView.matrix &&\r\n dataView.matrix.rows &&\r\n isDiscourageAggregationAcrossGroups(dataView.matrix.rows.levels));\r\n }\r\n\r\n export function shouldShowColumnSubtotals(objects: DataViewObjects): boolean {\r\n return TablixObjects.PropGeneralMatrixColumnSubtotals.getValue<boolean>(objects);\r\n }\r\n\r\n export function shouldShowColumnSubtotalsOption(dataView: DataView): boolean {\r\n return !(dataView &&\r\n dataView.matrix &&\r\n dataView.matrix.columns &&\r\n isDiscourageAggregationAcrossGroups(dataView.matrix.columns.levels));\r\n }\r\n\r\n export function isDiscourageAggregationAcrossGroups(levels: DataViewHierarchyLevel[]): boolean {\r\n let lastLevel = _.last(levels);\r\n // If the last item is not Aggregatable, disable totals option since there will be no totals at all to display\r\n // However, if the non-aggregatable filed is in the middle, there are totals showing up in matrix.\r\n // Therefore, we still allow users to turn it off\r\n return lastLevel && _.some(lastLevel.sources, source => source.discourageAggregationAcrossGroups);\r\n }\r\n }\r\n\r\n export module TablixUtils {\r\n export const CssClassTablixDiv = \"tablixDiv\"; // Any DIV inside the table (outer and inner)\r\n export const CssClassContentElement = \"tablixCellContentElement\"; // Outer DIV\r\n export const CssClassContentHost = \"tablixCellContentHost\"; // Inner DIV\r\n\r\n export const CssClassTablixHeader = \"tablixHeader\"; // Any Header in the Table/Matrix\r\n export const CssClassTablixColumnHeaderLeaf = \"tablixColumnHeaderLeaf\"; // Leaf Column Headers\r\n\r\n export const CssClassTablixValueNumeric = \"tablixValueNumeric\"; // Numeric cells, will also be applied to all Matrix body cells\r\n export const CssClassTablixValueTotal = \"tablixValueTotal\"; // Total cells, will also be applied to subtotal Matrix body cells\r\n export const CssClassValueURLIcon: string = \"powervisuals-glyph url-icon tablixUrlIconGlyph\"; // Any <a> Tag\r\n export const CssClassValueURLIconContainer: string = \"tablixValueUrlIcon\"; // Container for the <a> tag\r\n\r\n export const CssClassMatrixRowHeaderLeaf = \"matrixRowHeaderLeaf\"; // Matrix Leaf Row Headers\r\n export const CssClassMatrixRowHeaderSubTotal = \"matrixRowHeaderSubTotal\"; // Matrix SubTotal Row Headers\r\n\r\n export const CssClassTableFooter = 'tableFooterCell'; // Any cell in the Footer area\r\n export const CssClassTableBodyCell = 'tableBodyCell'; // Any cell in the Table Body\r\n export const CssClassTableBodyCellBottom = 'tableBodyCellBottom'; // Bottom-Most Body cell\r\n\r\n export const StringNonBreakingSpace = '&nbsp;';\r\n\r\n export const UnitOfMeasurement: string = 'px';\r\n const SortIconContainerClassName: string = \"tablixSortIconContainer\";\r\n export const CellPaddingLeft: number = 10;\r\n export const CellPaddingRight: number = 5;\r\n export const CellPaddingLeftMatrixTotal: number = 5;\r\n export const SortIconPadding: number = 5;\r\n export const ImageDefaultAspectRatio: number = 1;\r\n export const FontFamilyCell: string = Font.Family.regular.css;\r\n export const FontFamilyHeader: string = Font.Family.regular.css;\r\n export const FontFamilyTotal: string = Font.Family.bold.css;\r\n export const FontColorCells: string = \"#333\";\r\n export const FontColorHeaders: string = \"#666\";\r\n\r\n export interface Surround<T> {\r\n top?: T;\r\n right?: T;\r\n bottom?: T;\r\n left?: T;\r\n }\r\n\r\n export enum EdgeType { Outline, Gridline };\r\n\r\n export class EdgeSettings {\r\n /**\r\n * Weight in pixels. 0 to remove border. Undefined to fall back to CSS\r\n */\r\n public weight: number;\r\n public color: string;\r\n public type: EdgeType;\r\n\r\n constructor(weight?: number, color?: string) {\r\n this.applyParams(true, weight, color);\r\n }\r\n\r\n public applyParams(shown: boolean, weight: number, color?: string, type?: EdgeType) {\r\n if (shown) {\r\n this.weight = weight == null ? 0 : weight;\r\n this.color = color == null ? 'black' : color;\r\n this.type = type == null ? EdgeType.Gridline : type;\r\n }\r\n else {\r\n this.weight = 0;\r\n this.color = 'black';\r\n this.type = EdgeType.Gridline;\r\n }\r\n }\r\n\r\n public getCSS(): string {\r\n let css: string[] = [];\r\n\r\n if (_.isNumber(this.weight)) {\r\n css.push(this.weight + UnitOfMeasurement);\r\n if (this.color)\r\n css.push(this.color);\r\n\r\n css.push('solid');\r\n }\r\n\r\n return css.join(' ');\r\n }\r\n\r\n /**\r\n * Returns the priority of the current edge.\r\n * H. Grid = 0\r\n * V. Grid = 1\r\n * H. Outline = 2\r\n * V. Outline = 3\r\n * Uknown = -1\r\n * @param {Surround<EdgeSettings>} edges Edges. Used to determine the side of the current edge\r\n */\r\n public getPriority(edges: Surround<EdgeSettings>): number {\r\n if (this === edges.top || this === edges.bottom)\r\n if (this.type === EdgeType.Outline) return 2;\r\n else return 0;\r\n\r\n if (this === edges.right || this === edges.left)\r\n if (this.type === EdgeType.Outline) return 3;\r\n else return 1;\r\n\r\n return -1;\r\n }\r\n\r\n public getShadowCss(edges: Surround<EdgeSettings>): string {\r\n let output = \"inset \";\r\n\r\n if (this === edges.left)\r\n output += this.weight + UnitOfMeasurement + \" 0\";\r\n\r\n else if (this === edges.right)\r\n output += \"-\" + this.weight + UnitOfMeasurement + \" 0\";\r\n\r\n else if (this === edges.top)\r\n output += \"0 \" + this.weight + UnitOfMeasurement;\r\n\r\n else if (this === edges.bottom)\r\n output += \"0 -\" + this.weight + UnitOfMeasurement;\r\n else\r\n return \"\";\r\n\r\n return output + \" 0 0 \" + this.color;\r\n }\r\n }\r\n\r\n /**\r\n * Style parameters for each Cell\r\n */\r\n export class CellStyle {\r\n /**\r\n * Font family of the cell. If undefined, it will be cleared to fall back to table font family\r\n */\r\n public fontFamily: string;\r\n /**\r\n * Font color of the cell. If undefined, it will be cleared to fall back to table font color\r\n */\r\n public fontColor: string;\r\n /**\r\n * Background color of the cell. If undefined, it will be cleared to fall back to default (transparent)\r\n */\r\n public backColor: string;\r\n /**\r\n * Settings for Borders\r\n */\r\n public borders: Surround<EdgeSettings>;\r\n\r\n /**\r\n * Settings for Padding\r\n */\r\n public paddings: Surround<number>;\r\n\r\n constructor() {\r\n this.borders = {};\r\n this.paddings = { top: 0, left: TablixUtils.CellPaddingLeft, bottom: 0, right: TablixUtils.CellPaddingRight };\r\n\r\n // Initializing values with empty string would cause CSS attributes to not be set if they are undefined\r\n this.fontFamily = \"\";\r\n\r\n this.fontColor = \"\";\r\n this.backColor = \"\";\r\n }\r\n\r\n /**\r\n * Sets the Inline style for the Cell\r\n * @param {ITablixCell} cell Cell to set style to\r\n */\r\n public applyStyle(cell: ITablixCell): void {\r\n let div = cell.extension.contentHost;\r\n let style = div.style;\r\n\r\n style.fontFamily = this.fontFamily;\r\n\r\n style.color = this.fontColor;\r\n style.backgroundColor = this.backColor;\r\n\r\n let edges = [this.borders.top, this.borders.right, this.borders.bottom, this.borders.left];\r\n\r\n // Sorting edges by priority Descending\r\n edges = _.sortBy(edges, (e) => {\r\n return e ? e.getPriority(this.borders) : -1;\r\n }).reverse();\r\n\r\n /**\r\n * We are setting the borders as inset shadow\r\n * This way we can control how intersecting borders would look like when they have different colors\r\n */\r\n style.boxShadow = _.map(edges, (e) => {\r\n if (e) return e.getShadowCss(this.borders);\r\n }).join(', ');\r\n\r\n style.border = \"none\";\r\n\r\n style.paddingTop = ((this.paddings.top == null ? 0 : this.paddings.top) + (this.borders.top == null ? 0 : this.borders.top.weight)) + UnitOfMeasurement;\r\n style.paddingRight = ((this.paddings.right == null ? CellPaddingRight : this.paddings.right) + (this.borders.right == null ? 0 : this.borders.right.weight)) + UnitOfMeasurement;\r\n style.paddingBottom = ((this.paddings.bottom == null ? 0 : this.paddings.bottom) + (this.borders.bottom == null ? 0 : this.borders.bottom.weight)) + UnitOfMeasurement;\r\n style.paddingLeft = ((this.paddings.left == null ? CellPaddingLeft : this.paddings.left) + (this.borders.left == null ? 0 : this.borders.left.weight)) + UnitOfMeasurement;\r\n }\r\n\r\n public getExtraTop(): number {\r\n let extra = 0;\r\n\r\n if (this.paddings.top)\r\n extra += this.paddings.top;\r\n if (this.borders.top)\r\n extra += this.borders.top.weight;\r\n\r\n return extra;\r\n }\r\n\r\n public getExtraBottom(): number {\r\n let extra = 0;\r\n\r\n if (this.paddings.bottom)\r\n extra += this.paddings.bottom;\r\n if (this.borders.bottom)\r\n extra += this.borders.bottom.weight;\r\n\r\n return extra;\r\n }\r\n\r\n public getExtraRight(): number {\r\n let extra = 0;\r\n\r\n if (this.paddings.right)\r\n extra += this.paddings.right;\r\n if (this.borders.right)\r\n extra += this.borders.right.weight;\r\n\r\n return extra;\r\n }\r\n\r\n public getExtraLeft(): number {\r\n let extra = 0;\r\n\r\n if (this.paddings.left)\r\n extra += this.paddings.left;\r\n if (this.borders.left)\r\n extra += this.borders.left.weight;\r\n\r\n return extra;\r\n }\r\n }\r\n\r\n /**\r\n * Index within a dimension (row/column)\r\n */\r\n export class DimensionPosition {\r\n /**\r\n * Global index within all leaf nodes\r\n */\r\n public index: number;\r\n /**\r\n * Index within siblings for same parent\r\n */\r\n public indexInSiblings: number;\r\n /**\r\n * Is last globally\r\n */\r\n public isLast: boolean;\r\n /**\r\n * Is first globally\r\n */\r\n public isFirst: boolean;\r\n }\r\n /**\r\n * Poistion information about the cell\r\n */\r\n export class CellPosition {\r\n public row: DimensionPosition;\r\n public column: DimensionPosition;\r\n\r\n constructor() {\r\n this.row = new DimensionPosition();\r\n this.column = new DimensionPosition();\r\n }\r\n\r\n public isMatch(position: CellPosition) {\r\n return this.column.index === position.column.index &&\r\n this.row.index === position.row.index;\r\n }\r\n }\r\n\r\n export class TablixVisualCell {\r\n public dataPoint: any;\r\n public position: TablixUtils.CellPosition;\r\n public columnMetadata: DataViewMetadataColumn;\r\n public isTotal: boolean;\r\n public backColor: string;\r\n private formatter: ICustomValueColumnFormatter;\r\n private nullsAreBlank: boolean;\r\n\r\n constructor(dataPoint: any, isTotal: boolean, columnMetadata: DataViewMetadataColumn, formatter: ICustomValueColumnFormatter, nullsAreBlank: boolean) {\r\n this.dataPoint = dataPoint;\r\n this.columnMetadata = columnMetadata;\r\n this.formatter = formatter;\r\n this.isTotal = isTotal;\r\n this.nullsAreBlank = nullsAreBlank;\r\n\r\n this.position = new TablixUtils.CellPosition();\r\n }\r\n\r\n public get textContent(): string {\r\n if (this.formatter)\r\n return this.formatter(this.dataPoint, this.columnMetadata, TablixObjects.PropColumnFormatString.getPropertyID(), this.nullsAreBlank);\r\n else if (this.dataPoint != null)\r\n return this.dataPoint;\r\n else\r\n return '';\r\n };\r\n\r\n public get kpiContent(): JQuery {\r\n if (this.columnMetadata && isValidStatusGraphic(this.columnMetadata.kpi, this.textContent))\r\n return createKpiDom(this.columnMetadata.kpi, this.textContent);\r\n };\r\n\r\n public get isNumeric(): boolean {\r\n if (this.columnMetadata)\r\n return this.columnMetadata.type.numeric && !this.columnMetadata.kpi;\r\n };\r\n\r\n public get isUrl(): boolean {\r\n if (this.columnMetadata)\r\n return converterHelper.isWebUrlColumn(this.columnMetadata);\r\n };\r\n\r\n public get isImage(): boolean {\r\n if (this.columnMetadata)\r\n return converterHelper.isImageUrlColumn(this.columnMetadata);\r\n }\r\n\r\n public get isValidUrl(): boolean {\r\n return jsCommon.UrlUtils.isValidImageUrl(this.textContent);\r\n };\r\n\r\n public isMatch(item: TablixVisualCell) {\r\n return this.position.isMatch(item.position) && this.backColor === item.backColor;\r\n }\r\n }\r\n\r\n export function createTable(): HTMLTableElement {\r\n return <HTMLTableElement>document.createElement(\"table\");\r\n }\r\n\r\n export function createDiv(): HTMLDivElement {\r\n let div: HTMLDivElement = <HTMLDivElement>document.createElement(\"div\");\r\n div.className = \"tablixDiv\";\r\n return div;\r\n }\r\n\r\n export function resetCellCssClass(cell: controls.ITablixCell) {\r\n cell.extension.contentElement.className = TablixUtils.CssClassTablixDiv + \" \" + TablixUtils.CssClassContentElement;\r\n cell.extension.contentHost.className = TablixUtils.CssClassTablixDiv + \" \" + TablixUtils.CssClassContentHost;\r\n }\r\n\r\n export function addCellCssClass(cell: controls.ITablixCell, style: string): void {\r\n cell.extension.contentHost.className += \" \" + style;\r\n }\r\n\r\n /**\r\n * Clears all inline styles (border, fontColor, background) and resets CSS classes\r\n * Performed with unbind-<Cell>\r\n */\r\n export function clearCellStyle(cell: controls.ITablixCell): void {\r\n cell.extension.contentHost.className = \"\";\r\n cell.extension.contentHost.style.cssText = \"\";\r\n }\r\n\r\n export function clearCellTextAndTooltip(cell: controls.ITablixCell): void {\r\n cell.extension.contentHost.textContent = '';\r\n cell.extension.contentHost.removeAttribute('title');\r\n cell.contentHeight = cell.contentWidth = 0;\r\n HTMLElementUtils.clearChildren(cell.extension.contentHost);\r\n }\r\n\r\n /**\r\n * Sets text and tooltip for cell\r\n * @param {string} text Text to set\r\n * @param {HTMLElement} elementText Element to set text to\r\n * @param {HTMLElement} elementTooltip? Element to set tootltip to, if undefined, elementText will be used\r\n */\r\n export function setCellTextAndTooltip(text: string, elementText: HTMLElement, elementTooltip?: HTMLElement): void {\r\n let val = TextUtil.replaceSpaceWithNBSP(text);\r\n elementText.textContent = val;\r\n (elementTooltip || elementText).title = val;\r\n }\r\n\r\n export function isValidSortClick(e: MouseEvent) {\r\n let colHeader = <HTMLElement>e.target;\r\n let x = e.offsetX;\r\n return x >= 0 && x < colHeader.offsetWidth - TablixResizer.resizeHandleSize;\r\n }\r\n\r\n export function appendATagToBodyCell(value: string, cellElement: HTMLElement, urlIcon?: boolean): void {\r\n let atag: HTMLAnchorElement = null;\r\n if (cellElement.childElementCount === 0) {\r\n atag = document.createElement('a');\r\n cellElement.appendChild(atag);\r\n }\r\n else {\r\n atag = <HTMLAnchorElement>cellElement.children[0];\r\n }\r\n\r\n atag.href = value;\r\n atag.target = '_blank';\r\n atag.title = value;\r\n\r\n if (urlIcon === true) {\r\n atag.className = CssClassValueURLIcon;\r\n cellElement.className = CssClassValueURLIconContainer;\r\n }\r\n else {\r\n atag.innerText = value;\r\n }\r\n }\r\n\r\n export function appendImgTagToBodyCell(value: string, cellElement: HTMLElement, imageHeight: number): void {\r\n let imgContainer: HTMLDivElement = TablixUtils.createDiv();\r\n let imgTag: HTMLImageElement = document.createElement('img');\r\n\r\n imgContainer.style.height = imageHeight + \"px\";\r\n imgContainer.style.width = \"100%\";\r\n imgContainer.style.textAlign = \"center\";\r\n imgTag.src = value;\r\n imgTag.style.maxHeight = \"100%\";\r\n imgTag.style.maxWidth = \"100%\";\r\n imgContainer.appendChild(imgTag);\r\n cellElement.appendChild(imgContainer);\r\n cellElement.title = value;\r\n }\r\n\r\n export function createKpiDom(kpi: DataViewKpiColumnMetadata, kpiValue: string): JQuery {\r\n debug.assertValue(kpi, 'kpi');\r\n debug.assertValue(kpiValue, 'kpiValue');\r\n let className: string = KpiUtil.getClassForKpi(kpi, kpiValue) || '';\r\n return DomFactory.div()\r\n .addClass(className)\r\n .css({\r\n 'display': 'inline-block',\r\n 'vertical-align': 'bottom',\r\n 'margin': '0',\r\n });\r\n }\r\n\r\n export function isValidStatusGraphic(kpi: DataViewKpiColumnMetadata, kpiValue: string): boolean {\r\n if (!kpi || kpiValue === undefined) {\r\n return false;\r\n }\r\n\r\n return !!KpiUtil.getClassForKpi(kpi, kpiValue);\r\n }\r\n\r\n export function getCustomSortEventArgs(queryName: string, sortDirection: SortDirection): CustomSortEventArgs {\r\n let sortDescriptors: SortableFieldDescriptor[] = [{\r\n queryName: queryName,\r\n sortDirection: sortDirection\r\n }];\r\n return { sortDescriptors: sortDescriptors };\r\n }\r\n\r\n export function reverseSort(sortDirection: SortDirection): SortDirection {\r\n return sortDirection === SortDirection.Descending ? SortDirection.Ascending : SortDirection.Descending;\r\n }\r\n\r\n /**\r\n * Add sort icon to a table cell and return the element that should contain the contents\r\n * @param {SortDirection} itemSort SortDirection\r\n * @param {HTMLElement} cellDiv The inner DIV of the cell\r\n */\r\n export function addSortIconToColumnHeader(itemSort: SortDirection, cellDiv: HTMLElement): HTMLElement {\r\n let colHeaderContainer: HTMLDivElement = TablixUtils.createDiv();\r\n\r\n if (itemSort) {\r\n colHeaderContainer.appendChild(createSortIcon(itemSort, true));\r\n colHeaderContainer.appendChild(createSortIcon(reverseSort(itemSort), false));\r\n }\r\n else {\r\n colHeaderContainer.appendChild(createSortIcon(SortDirection.Descending, false));\r\n }\r\n\r\n let colHeaderTitle: HTMLDivElement = TablixUtils.createDiv();\r\n colHeaderContainer.appendChild(colHeaderTitle);\r\n cellDiv.appendChild(colHeaderContainer);\r\n\r\n return colHeaderTitle;\r\n }\r\n\r\n function createSortIcon(sort: SortDirection, isSorted: boolean): HTMLElement {\r\n let imgSort: HTMLPhraseElement = <HTMLPhraseElement>document.createElement('i');\r\n imgSort.className = SortIconContainerClassName +\r\n \" \" + (isSorted ? \"sorted\" : \"future\") +\r\n \" \" + (sort === SortDirection.Ascending ? \"powervisuals-glyph caret-up\" : \"powervisuals-glyph caret-down\");\r\n return imgSort;\r\n }\r\n\r\n function checkSortIconExists(cell: controls.ITablixCell): boolean {\r\n for (let i = 0, len = cell.extension.contentElement.childElementCount; i < len; i++) {\r\n let element = cell.extension.contentElement.children.item(i);\r\n if (element.classList.contains(SortIconContainerClassName))\r\n return true;\r\n }\r\n return false;\r\n }\r\n\r\n export function removeSortIcons(cell: controls.ITablixCell): void {\r\n if (!checkSortIconExists(cell))\r\n return;\r\n $((<HTMLElement>cell.extension.contentElement)).find('.' + SortIconContainerClassName).remove();\r\n }\r\n }\r\n}\n\n\n/** WEBPACK FOOTER **\n ** ./src/Clients/Visuals/controls/tablix/internal/tablixUtils.ts\n **/","/*\r\n * Power BI Visualizations\r\n *\r\n * Copyright (c) Microsoft Corporation\r\n * All rights reserved. \r\n * MIT License\r\n *\r\n * Permission is hereby granted, free of charge, to any person obtaining a copy\r\n * of this software and associated documentation files (the \"\"Software\"\"), to deal\r\n * in the Software without restriction, including without limitation the rights\r\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\r\n * copies of the Software, and to permit persons to whom the Software is\r\n * furnished to do so, subject to the following conditions:\r\n * \r\n * The above copyright notice and this permission notice shall be included in \r\n * all copies or substantial portions of the Software.\r\n * \r\n * THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR \r\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, \r\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE \r\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER \r\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\r\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\r\n * THE SOFTWARE.\r\n */\r\n\r\n/// <reference path=\"../../_references.ts\"/>\r\n\r\nmodule powerbi.visuals.controls {\r\n\r\n export interface ITablixHierarchyNavigator {\r\n /**\r\n * Returns the depth of the column hierarchy.\r\n */\r\n getColumnHierarchyDepth(): number;\r\n\r\n /**\r\n * Returns the depth of the Row hierarchy.\r\n */\r\n getRowHierarchyDepth(): number;\r\n\r\n /**\r\n * Returns the leaf count of a hierarchy.\r\n *\r\n * @param hierarchy Object representing the hierarchy.\r\n */\r\n getLeafCount(hierarchy: any): number;\r\n\r\n /**\r\n * Returns the leaf member of a hierarchy at the specified index.\r\n *\r\n * @param hierarchy Object representing the hierarchy.\r\n * @param index Index of leaf member.\r\n */\r\n getLeafAt(hierarchy: any, index: number): any;\r\n\r\n /**\r\n * Returns the specified hierarchy member parent.\r\n *\r\n * @param item Hierarchy member.\r\n */\r\n getParent(item: any): any;\r\n\r\n /**\r\n * Returns the index of the hierarchy member relative to its parent.\r\n *\r\n * @param item Hierarchy member.\r\n */\r\n getIndex(item: any): number;\r\n\r\n /**\r\n * Checks whether a hierarchy member is a leaf.\r\n *\r\n * @param item Hierarchy member.\r\n */\r\n isLeaf(item: any): boolean;\r\n\r\n isRowHierarchyLeaf(cornerItem: any): boolean;\r\n\r\n isColumnHierarchyLeaf(cornerItem: any): boolean;\r\n\r\n isFirstItem(item: any, items: any): boolean;\r\n\r\n /**\r\n * Checks whether a hierarchy member is the last item within its parent.\r\n *\r\n * @param item Hierarchy member.\r\n * @param items A collection of hierarchy members.\r\n */\r\n isLastItem(item: any, items: any): boolean;\r\n\r\n /**\r\n * Checks if the item and all its ancestors are the first items in their parent's children\r\n */\r\n areAllParentsFirst(item: any, items: any): boolean;\r\n\r\n /**\r\n * Checks if the item and all its ancestors are the last items in their parent's children\r\n */\r\n areAllParentsLast(item: any, items: any): boolean;\r\n\r\n /**\r\n * Gets the children members of a hierarchy member.\r\n *\r\n * @param item Hierarchy member.\r\n */\r\n getChildren(item: any): any;\r\n\r\n /**\r\n * Gets the difference between current level and min children level. Not necessarily 1\r\n *\r\n * @param item Hierarchy member.\r\n */\r\n getChildrenLevelDifference(item: any): number;\r\n\r\n /**\r\n * Gets the members count in a specified collection.\r\n *\r\n * @param items Hierarchy member.\r\n */\r\n getCount(items: any): number;\r\n\r\n /**\r\n * Gets the member at the specified index.\r\n *\r\n * @param items A collection of hierarchy members.\r\n * @param index Index of member to return.\r\n */\r\n getAt(items: any, index: number): any;\r\n\r\n /**\r\n * Gets the hierarchy member level.\r\n *\r\n * @param item Hierarchy member.\r\n */\r\n getLevel(item: any): number;\r\n\r\n /**\r\n * Returns the intersection between a row and a column item.\r\n *\r\n * @param rowItem A row member.\r\n * @param columnItem A column member.\r\n */\r\n getIntersection(rowItem: any, columnItem: any): any;\r\n\r\n /**\r\n * Returns the corner cell between a row and a column level.\r\n *\r\n * @param rowLevel A level in the row hierarchy.\r\n * @param columnLevel A level in the column hierarchy.\r\n */\r\n getCorner(rowLevel: number, columnLevel: number): any;\r\n\r\n headerItemEquals(item1: any, item2: any): boolean;\r\n\r\n bodyCellItemEquals(item1: any, item2: any): boolean;\r\n\r\n cornerCellItemEquals(item1: any, item2: any): boolean;\r\n }\r\n}\n\n\n/** WEBPACK FOOTER **\n ** ./src/Clients/Visuals/controls/tablix/iTablixHierarchyNavigator.ts\n **/","/*\r\n * Power BI Visualizations\r\n *\r\n * Copyright (c) Microsoft Corporation\r\n * All rights reserved. \r\n * MIT License\r\n *\r\n * Permission is hereby granted, free of charge, to any person obtaining a copy\r\n * of this software and associated documentation files (the \"\"Software\"\"), to deal\r\n * in the Software without restriction, including without limitation the rights\r\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\r\n * copies of the Software, and to permit persons to whom the Software is\r\n * furnished to do so, subject to the following conditions:\r\n * \r\n * The above copyright notice and this permission notice shall be included in \r\n * all copies or substantial portions of the Software.\r\n * \r\n * THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR \r\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, \r\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE \r\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER \r\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\r\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\r\n * THE SOFTWARE.\r\n */\r\n\r\n/// <reference path=\"../../_references.ts\"/>\r\n\r\nmodule powerbi.visuals.controls {\r\n\r\n export interface ITablixBinder {\r\n onStartRenderingSession(): void;\r\n onEndRenderingSession(): void;\r\n /** Binds the row hierarchy member to the DOM element. */\r\n bindRowHeader(item: any, cell: ITablixCell): void;\r\n unbindRowHeader(item: any, cell: ITablixCell): void;\r\n\r\n /** Binds the column hierarchy member to the DOM element. */\r\n bindColumnHeader(item: any, cell: ITablixCell): void;\r\n unbindColumnHeader(item: any, cell: ITablixCell): void;\r\n\r\n /** Binds the intersection between a row and a column hierarchy member to the DOM element. */\r\n bindBodyCell(item: any, cell: ITablixCell): void;\r\n unbindBodyCell(item: any, cell: ITablixCell): void;\r\n\r\n /** Binds the corner cell to the DOM element. */\r\n bindCornerCell(item: any, cell: ITablixCell): void;\r\n unbindCornerCell(item: any, cell: ITablixCell): void;\r\n\r\n bindEmptySpaceHeaderCell(cell: ITablixCell): void;\r\n unbindEmptySpaceHeaderCell(cell: ITablixCell): void;\r\n\r\n bindEmptySpaceFooterCell(cell: ITablixCell): void;\r\n unbindEmptySpaceFooterCell(cell: ITablixCell): void;\r\n\r\n /** Measurement Helper */\r\n getHeaderLabel(item: any): string;\r\n getCellContent(item: any): string;\r\n hasRowGroups(): boolean;\r\n }\r\n}\n\n\n/** WEBPACK FOOTER **\n ** ./src/Clients/Visuals/controls/tablix/iTablixBinder.ts\n **/","/*\r\n * Power BI Visualizations\r\n *\r\n * Copyright (c) Microsoft Corporation\r\n * All rights reserved. \r\n * MIT License\r\n *\r\n * Permission is hereby granted, free of charge, to any person obtaining a copy\r\n * of this software and associated documentation files (the \"\"Software\"\"), to deal\r\n * in the Software without restriction, including without limitation the rights\r\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\r\n * copies of the Software, and to permit persons to whom the Software is\r\n * furnished to do so, subject to the following conditions:\r\n * \r\n * The above copyright notice and this permission notice shall be included in \r\n * all copies or substantial portions of the Software.\r\n * \r\n * THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR \r\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, \r\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE \r\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER \r\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\r\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\r\n * THE SOFTWARE.\r\n */\r\n\r\n/// <reference path=\"../../_references.ts\"/>\r\n\r\nmodule powerbi.visuals.controls {\r\n\r\n export const enum TablixCellType {\r\n CornerCell,\r\n RowHeader,\r\n ColumnHeader,\r\n BodyCell\r\n }\r\n\r\n export interface ITablixCell {\r\n type: TablixCellType;\r\n item: any;\r\n colSpan: number;\r\n rowSpan: number;\r\n textAlign: string;\r\n extension: internal.TablixCellPresenter;\r\n position: internal.TablixUtils.CellPosition;\r\n contentHeight: number;\r\n contentWidth: number;\r\n containerHeight: number;\r\n containerWidth: number;\r\n\r\n unfixRowHeight();\r\n\r\n applyStyle(style: internal.TablixUtils.CellStyle): void;\r\n }\r\n \r\n export interface IDimensionLayoutManager {\r\n measureEnabled: boolean;\r\n getRealizedItemsCount(): number;\r\n needsToRealize: boolean;\r\n }\r\n}\n\n\n/** WEBPACK FOOTER **\n ** ./src/Clients/Visuals/controls/tablix/iTablixLayoutManager.ts\n **/","/*\r\n * Power BI Visualizations\r\n *\r\n * Copyright (c) Microsoft Corporation\r\n * All rights reserved. \r\n * MIT License\r\n *\r\n * Permission is hereby granted, free of charge, to any person obtaining a copy\r\n * of this software and associated documentation files (the \"\"Software\"\"), to deal\r\n * in the Software without restriction, including without limitation the rights\r\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\r\n * copies of the Software, and to permit persons to whom the Software is\r\n * furnished to do so, subject to the following conditions:\r\n * \r\n * The above copyright notice and this permission notice shall be included in \r\n * all copies or substantial portions of the Software.\r\n * \r\n * THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR \r\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, \r\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE \r\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER \r\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\r\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\r\n * THE SOFTWARE.\r\n */\r\n\r\n/// <reference path=\"../../_references.ts\"/>\r\n\r\nmodule powerbi.visuals.controls {\r\n export const TablixDefaultTextSize = jsCommon.TextSizeDefaults.TextSizeMin;\r\n\r\n export interface TablixRenderArgs {\r\n rowScrollOffset?: number;\r\n columnScrollOffset?: number;\r\n scrollingDimension?: TablixDimension;\r\n }\r\n\r\n export interface GridDimensions {\r\n rowCount?: number;\r\n columnCount?: number;\r\n rowHierarchyWidth?: number;\r\n rowHierarchyHeight?: number;\r\n rowHierarchyContentHeight?: number;\r\n columnHierarchyWidth?: number;\r\n columnHierarchyHeight?: number;\r\n footerHeight?: number;\r\n }\r\n\r\n export const enum TablixLayoutKind {\r\n /**\r\n * The default layout is based on DOM measurements and used on the canvas.\r\n */\r\n Canvas,\r\n \r\n /**\r\n * The DashboardTile layout must not rely on any kind of DOM measurements\r\n * since the tiles are created when the dashboard is not visible and the\r\n * visual is not rendered; thus no measurements are available.\r\n */\r\n DashboardTile,\r\n }\r\n\r\n export interface TablixOptions {\r\n interactive?: boolean;\r\n enableTouchSupport?: boolean;\r\n layoutKind?: TablixLayoutKind;\r\n fontSize?: string;\r\n }\r\n\r\n export class TablixControl {\r\n private static UnitOfMeasurement = 'px';\r\n private static TablixContainerClassName = 'tablixContainer';\r\n private static TablixTableAreaClassName = \"tablixTableArea\";\r\n private static TablixFooterClassName = \"tableFooterArea\";\r\n private static DefaultFontSize = jsCommon.PixelConverter.fromPoint(controls.TablixDefaultTextSize);\r\n /*\r\n * This is workaround for the infinite loop in rendering\r\n * BugID: 6518621\r\n * ToDo: Investigate the underlying cause for rendering to never report completion\r\n * Rendering typically require 3-5 iterations to complete, so 10 is enough\r\n */\r\n private static MaxRenderIterationCount = 10;\r\n\r\n private hierarchyTablixNavigator: ITablixHierarchyNavigator;\r\n private binder: ITablixBinder;\r\n\r\n private columnDim: TablixColumnDimension;\r\n private rowDim: TablixRowDimension;\r\n private controlLayoutManager: internal.TablixLayoutManager;\r\n\r\n private containerElement: HTMLDivElement;\r\n private mainDiv: HTMLDivElement;\r\n private footerDiv: HTMLDivElement;\r\n\r\n private scrollBarElementWidth = 9;\r\n\r\n private touchManager: TouchUtils.TouchManager;\r\n private columnTouchDelegate: ColumnTouchDelegate;\r\n private rowTouchDelegate: RowTouchDelegate;\r\n private bodyTouchDelegate: BodyTouchDelegate;\r\n private footerTouchDelegate: ColumnTouchDelegate;\r\n private touchInterpreter: TouchUtils.TouchEventInterpreter;\r\n private footerTouchInterpreter: TouchUtils.TouchEventInterpreter;\r\n\r\n private gridDimensions: GridDimensions;\r\n private lastRenderingArgs: TablixRenderArgs;\r\n\r\n /* tslint:disable:no-underscore-prefix-for-variables*/\r\n private _autoSizeWidth: boolean;\r\n private _autoSizeHeight: boolean;\r\n /* tslint:enable:no-underscore-prefix-for-variables*/\r\n private viewPort: IViewport;\r\n private maximumWidth: number;\r\n private maximumHeight: number;\r\n private minimumWidth: number;\r\n private minimumHeight: number;\r\n private textFontSize: string;\r\n private textFontFamily: string;\r\n private textFontColor: string;\r\n\r\n private options: TablixOptions;\r\n private isTouchEnabled: boolean;\r\n\r\n private renderIterationCount: number;\r\n\r\n constructor(\r\n hierarchyNavigator: ITablixHierarchyNavigator,\r\n layoutManager: internal.TablixLayoutManager,\r\n binder: ITablixBinder,\r\n parentDomElement: HTMLElement,\r\n options: TablixOptions) {\r\n\r\n // Options (fontSize set after container initialized)\r\n this.options = options;\r\n let isInteractive = options.interactive;\r\n this.isTouchEnabled = isInteractive && options.enableTouchSupport;\r\n\r\n // Main Div\r\n this.mainDiv = internal.TablixUtils.createDiv();\r\n this.mainDiv.classList.add(TablixControl.TablixTableAreaClassName);\r\n\r\n // Footer Div\r\n this.footerDiv = internal.TablixUtils.createDiv();\r\n this.footerDiv.classList.add(TablixControl.TablixFooterClassName);\r\n\r\n if (this.isTouchEnabled)\r\n this.InitializeTouchSupport();\r\n\r\n this.gridDimensions = {};\r\n\r\n this.containerElement = internal.TablixUtils.createDiv();\r\n this.className = layoutManager.getTablixClassName();\r\n this.autoSizeWidth = false;\r\n this.autoSizeHeight = false;\r\n this.fontFamily = internal.TablixUtils.FontFamilyCell;\r\n this.fontColor = internal.TablixUtils.FontColorCells;\r\n this.fontSize = options.fontSize;\r\n\r\n parentDomElement.className = TablixControl.TablixContainerClassName;\r\n parentDomElement.appendChild(this.containerElement);\r\n\r\n this.containerElement.addEventListener(\"mousewheel\", (e) => { this.onMouseWheel(<MouseWheelEvent>e); });\r\n this.containerElement.addEventListener(\"DOMMouseScroll\", (e) => { this.onFireFoxMouseWheel(<MouseWheelEvent>e); });\r\n this.containerElement.appendChild(this.mainDiv);\r\n this.containerElement.appendChild(this.footerDiv);\r\n\r\n if (this.isTouchEnabled) {\r\n this.touchInterpreter.initTouch(this.mainDiv, null, false);\r\n this.footerTouchInterpreter.initTouch(this.footerDiv, this.mainDiv, false);\r\n }\r\n\r\n this.controlLayoutManager = layoutManager;\r\n this.controlLayoutManager.initialize(this);\r\n\r\n this.hierarchyTablixNavigator = hierarchyNavigator;\r\n this.binder = binder;\r\n\r\n this.columnDim = new TablixColumnDimension(this);\r\n this.rowDim = new TablixRowDimension(this);\r\n this.columnDim._otherDimension = this.rowDimension;\r\n this.rowDim._otherDimension = this.columnDimension;\r\n\r\n this.InitializeScrollbars();\r\n if (!isInteractive) {\r\n this.scrollbarWidth = 0;\r\n }\r\n\r\n this.updateHorizontalPosition();\r\n this.updateVerticalPosition();\r\n\r\n this.updateFooterVisibility();\r\n\r\n this.lastRenderingArgs = {};\r\n }\r\n\r\n private InitializeTouchSupport(): void {\r\n this.touchManager = new TouchUtils.TouchManager();\r\n this.touchInterpreter = new TouchUtils.TouchEventInterpreter(this.touchManager);\r\n this.footerTouchInterpreter = new TouchUtils.TouchEventInterpreter(this.touchManager);\r\n this.columnTouchDelegate = new ColumnTouchDelegate(new TouchUtils.Rectangle());\r\n this.rowTouchDelegate = new RowTouchDelegate(new TouchUtils.Rectangle());\r\n this.bodyTouchDelegate = new BodyTouchDelegate(new TouchUtils.Rectangle());\r\n this.footerTouchDelegate = new ColumnTouchDelegate(new TouchUtils.Rectangle());\r\n\r\n this.columnTouchDelegate.setHandler(this, this.onTouchEvent);\r\n this.rowTouchDelegate.setHandler(this, this.onTouchEvent);\r\n this.bodyTouchDelegate.setHandler(this, this.onTouchEvent);\r\n this.footerTouchDelegate.setHandler(this, this.onTouchEvent);\r\n\r\n this.touchManager.addTouchRegion(this.columnTouchDelegate.dimension, this.columnTouchDelegate, this.columnTouchDelegate);\r\n this.touchManager.addTouchRegion(this.rowTouchDelegate.dimension, this.rowTouchDelegate, this.rowTouchDelegate);\r\n this.touchManager.addTouchRegion(this.bodyTouchDelegate.dimension, this.bodyTouchDelegate, this.bodyTouchDelegate);\r\n this.touchManager.addTouchRegion(this.footerTouchDelegate.dimension, this.footerTouchDelegate, this.footerTouchDelegate);\r\n }\r\n\r\n private InitializeScrollbars(): void {\r\n // Row Dimension\r\n this.rowDim._initializeScrollbar(this.containerElement, null, this.options.layoutKind);\r\n\r\n let rowDimensionScrollbarStyle = this.rowDim.scrollbar.element.style;\r\n rowDimensionScrollbarStyle.position = \"absolute\";\r\n rowDimensionScrollbarStyle.top = \"0\" + TablixControl.UnitOfMeasurement;\r\n rowDimensionScrollbarStyle.right = \"0\" + TablixControl.UnitOfMeasurement;\r\n this.rowDim.scrollbar.width = this.scrollBarElementWidth + TablixControl.UnitOfMeasurement;\r\n\r\n // Default to true which is the more common case to avoid an extra rendering iteration\r\n // when first rendering the visual\r\n this.rowDim.scrollbar.show(true);\r\n\r\n // Column Dimension\r\n this.columnDim._initializeScrollbar(this.containerElement, null, this.options.layoutKind);\r\n\r\n let columnDimensionScrollbarStyle = this.columnDim.scrollbar.element.style;\r\n columnDimensionScrollbarStyle.position = \"absolute\";\r\n columnDimensionScrollbarStyle.left = \"0\" + TablixControl.UnitOfMeasurement;\r\n columnDimensionScrollbarStyle.bottom = \"0\" + TablixControl.UnitOfMeasurement;\r\n this.columnDim.scrollbar.height = this.scrollBarElementWidth + TablixControl.UnitOfMeasurement;\r\n\r\n this.columnDim.scrollbar.show(false);\r\n }\r\n\r\n public get container(): HTMLElement {\r\n return this.containerElement;\r\n }\r\n\r\n public get contentHost(): HTMLElement {\r\n return this.mainDiv;\r\n }\r\n\r\n public get footerHost(): HTMLElement {\r\n return this.footerDiv;\r\n }\r\n\r\n public set className(value: string) {\r\n this.containerElement.className = value;\r\n }\r\n\r\n public get hierarchyNavigator(): ITablixHierarchyNavigator {\r\n return this.hierarchyTablixNavigator;\r\n }\r\n\r\n public getBinder(): ITablixBinder {\r\n return this.binder;\r\n }\r\n\r\n public get autoSizeWidth(): boolean {\r\n return this._autoSizeWidth;\r\n }\r\n\r\n public set autoSizeWidth(value: boolean) {\r\n this._autoSizeWidth = value;\r\n\r\n if (!value) {\r\n this.containerElement.style.minWidth = this.containerElement.style.maxWidth = \"none\";\r\n }\r\n }\r\n\r\n public get autoSizeHeight(): boolean {\r\n return this._autoSizeHeight;\r\n }\r\n\r\n public set autoSizeHeight(value: boolean) {\r\n if (!value) {\r\n this.containerElement.style.minHeight = this.containerElement.style.maxHeight = \"none\";\r\n }\r\n }\r\n\r\n public get maxWidth(): number {\r\n return this.maximumWidth;\r\n }\r\n\r\n public set maxWidth(value: number) {\r\n this.maximumWidth = value;\r\n this.containerElement.style.maxWidth = this.maximumWidth + TablixControl.UnitOfMeasurement;\r\n }\r\n\r\n public get viewport(): IViewport {\r\n return this.viewPort;\r\n }\r\n\r\n public set viewport(value: IViewport) {\r\n this.viewPort = value;\r\n this.containerElement.style.width = this.viewPort.width + TablixControl.UnitOfMeasurement;\r\n this.containerElement.style.height = this.viewPort.height + TablixControl.UnitOfMeasurement;\r\n\r\n this.rowDim.scrollbar.invalidateArrange();\r\n this.columnDim.scrollbar.invalidateArrange();\r\n\r\n this.controlLayoutManager.updateViewport(this.viewPort);\r\n }\r\n\r\n public get maxHeight(): number {\r\n return this.maximumHeight;\r\n }\r\n\r\n public set maxHeight(value: number) {\r\n this.maximumHeight = value;\r\n this.containerElement.style.maxHeight = this.maximumHeight + TablixControl.UnitOfMeasurement;\r\n }\r\n\r\n public get minWidth(): number {\r\n return this.minimumWidth;\r\n }\r\n\r\n public set minWidth(value: number) {\r\n this.minimumWidth = value;\r\n this.containerElement.style.minWidth = this.minimumWidth + TablixControl.UnitOfMeasurement;\r\n }\r\n\r\n public get minHeight(): number {\r\n return this.minimumHeight;\r\n }\r\n\r\n public set minHeight(value: number) {\r\n this.minimumHeight = value;\r\n this.containerElement.style.minHeight = this.minimumHeight + TablixControl.UnitOfMeasurement;\r\n }\r\n\r\n public get fontSize(): string {\r\n return this.textFontSize;\r\n }\r\n\r\n public set fontSize(value: string) {\r\n this.textFontSize = !value ? TablixControl.DefaultFontSize : value;\r\n this.containerElement.style.fontSize = this.textFontSize;\r\n }\r\n\r\n public get fontFamily(): string {\r\n return this.textFontFamily;\r\n }\r\n\r\n public set fontFamily(value: string) {\r\n this.textFontFamily = value;\r\n this.containerElement.style.fontFamily = value;\r\n }\r\n\r\n public get fontColor(): string {\r\n return this.textFontColor;\r\n }\r\n\r\n public set fontColor(value: string) {\r\n this.textFontColor = value;\r\n this.containerElement.style.color = value;\r\n }\r\n\r\n public set scrollbarWidth(value: number) {\r\n this.scrollBarElementWidth = value;\r\n this.rowDim.scrollbar.width = this.scrollBarElementWidth + TablixControl.UnitOfMeasurement;\r\n this.columnDim.scrollbar.height = this.scrollBarElementWidth + TablixControl.UnitOfMeasurement;\r\n }\r\n\r\n public updateModels(resetScrollOffsets: boolean, rowModel: any, columnModel: any): void {\r\n this.rowDim.model = rowModel;\r\n this.rowDim.modelDepth = this.hierarchyNavigator.getRowHierarchyDepth();\r\n this.columnDim.model = columnModel;\r\n this.columnDim.modelDepth = this.hierarchyNavigator.getColumnHierarchyDepth();\r\n\r\n if (resetScrollOffsets) {\r\n this.rowDim.scrollOffset = 0;\r\n this.columnDim.scrollOffset = 0;\r\n }\r\n\r\n this.layoutManager.updateColumnCount(this.rowDim, this.columnDim);\r\n }\r\n\r\n public updateColumnDimensions(rowHierarchyWidth: number, columnHierarchyWidth: number, count: number) {\r\n let gridDimensions = this.gridDimensions;\r\n\r\n gridDimensions.columnCount = count;\r\n gridDimensions.rowHierarchyWidth = rowHierarchyWidth;\r\n gridDimensions.columnHierarchyWidth = columnHierarchyWidth;\r\n }\r\n\r\n public updateRowDimensions(columnHierarchyHeight: number, rowHierarchyHeight: number, rowHierarchyContentHeight: number, count: number, footerHeight) {\r\n let gridDimensions = this.gridDimensions;\r\n\r\n gridDimensions.rowCount = count;\r\n gridDimensions.rowHierarchyHeight = rowHierarchyHeight;\r\n gridDimensions.rowHierarchyContentHeight = rowHierarchyContentHeight;\r\n gridDimensions.columnHierarchyHeight = columnHierarchyHeight;\r\n gridDimensions.footerHeight = footerHeight;\r\n }\r\n\r\n private updateTouchDimensions(): void {\r\n let gridDimensions = this.gridDimensions;\r\n\r\n this.columnTouchDelegate.resize(gridDimensions.rowHierarchyWidth, 0, gridDimensions.columnHierarchyWidth, gridDimensions.columnHierarchyHeight);\r\n this.columnTouchDelegate.setScrollDensity(gridDimensions.columnCount / gridDimensions.columnHierarchyWidth);\r\n\r\n this.rowTouchDelegate.resize(0, gridDimensions.columnHierarchyHeight, gridDimensions.rowHierarchyWidth, gridDimensions.rowHierarchyHeight);\r\n this.rowTouchDelegate.setScrollDensity(gridDimensions.rowCount / gridDimensions.rowHierarchyHeight);\r\n\r\n this.bodyTouchDelegate.resize(gridDimensions.rowHierarchyWidth, gridDimensions.columnHierarchyHeight,\r\n gridDimensions.columnHierarchyWidth, gridDimensions.rowHierarchyHeight);\r\n this.bodyTouchDelegate.setScrollDensity(gridDimensions.columnCount / gridDimensions.columnHierarchyWidth,\r\n gridDimensions.rowCount / gridDimensions.rowHierarchyHeight);\r\n\r\n this.footerTouchDelegate.resize(gridDimensions.rowHierarchyWidth, gridDimensions.columnHierarchyHeight + gridDimensions.rowHierarchyHeight, gridDimensions.columnHierarchyWidth, gridDimensions.footerHeight);\r\n this.footerTouchDelegate.setScrollDensity(gridDimensions.columnCount / gridDimensions.columnHierarchyWidth);\r\n }\r\n\r\n private onMouseWheel(e: MouseWheelEvent): void {\r\n this.determineDimensionToScroll(e,\r\n (dimension, delta) => { dimension.scrollbar.onMouseWheel(delta); });\r\n\r\n if (this.options.layoutKind === TablixLayoutKind.Canvas)\r\n e.preventDefault();\r\n }\r\n\r\n private onFireFoxMouseWheel(e: MouseWheelEvent): void {\r\n this.determineDimensionToScrollFirefox(e,\r\n (dimension, delta) => { dimension.scrollbar.onMouseWheel(delta); });\r\n\r\n if (this.options.layoutKind === TablixLayoutKind.Canvas)\r\n e.preventDefault();\r\n }\r\n\r\n private determineDimensionToScroll(e: MouseWheelEvent, scrollCallback: (dimension: TablixDimension, delta: number) => void): void {\r\n // If vertical scrollbar is shown, apply normal scrolling in X, Y\r\n if (this.rowDim.scrollbar.visible) {\r\n if (e.wheelDeltaY)\r\n scrollCallback(this.rowDim, e.wheelDeltaY);\r\n\r\n if (e.wheelDeltaX && this.columnDim.scrollbar.visible)\r\n scrollCallback(this.columnDim, e.wheelDeltaX);\r\n }\r\n\r\n // If vertical scrollbar is hidden, and horizontal scrollbar is shown\r\n // Apply whatever X or Y to it\r\n else if (this.columnDim.scrollbar.visible) {\r\n if (e.wheelDeltaX)\r\n scrollCallback(this.columnDim, e.wheelDeltaX);\r\n else if (e.wheelDeltaY)\r\n scrollCallback(this.columnDim, e.wheelDeltaY);\r\n }\r\n }\r\n\r\n private determineDimensionToScrollFirefox(e: MouseWheelEvent, scrollCallback: (dimension: TablixDimension, delta: number) => void): void {\r\n // Firefox\r\n if (e.detail) {\r\n if (this.rowDim.scrollbar.visible) {\r\n scrollCallback(this.rowDim, -e.detail);\r\n return;\r\n }\r\n\r\n // In the absence of the vertical scrollbar, we scroll the\r\n // horizontal scrollbar.\r\n if (this.columnDim.scrollbar.visible) {\r\n scrollCallback(this.columnDim, -e.detail);\r\n return;\r\n }\r\n }\r\n }\r\n\r\n public get layoutManager(): internal.TablixLayoutManager {\r\n return this.controlLayoutManager;\r\n }\r\n\r\n public get columnDimension(): TablixColumnDimension {\r\n return this.columnDim;\r\n }\r\n\r\n public get rowDimension(): TablixRowDimension {\r\n return this.rowDim;\r\n }\r\n\r\n public refresh(clear: boolean): void {\r\n this.render(clear, null);\r\n }\r\n\r\n public _onScrollAsync(dimension: TablixDimension): void { // The intent is to be internal\r\n requestAnimationFrame(() => { this.performPendingScroll(dimension); });\r\n }\r\n\r\n private performPendingScroll(dimension: TablixDimension): void {\r\n this.render(false, dimension);\r\n }\r\n\r\n private updateHorizontalPosition(): void {\r\n if (this.rowDim.scrollbar.visible) {\r\n this.columnDim.scrollbar.element.style.right = this.scrollBarElementWidth + TablixControl.UnitOfMeasurement;\r\n this.footerDiv.style.right = this.scrollBarElementWidth + TablixControl.UnitOfMeasurement;\r\n this.mainDiv.style.right = this.scrollBarElementWidth + TablixControl.UnitOfMeasurement;\r\n } else {\r\n this.columnDim.scrollbar.element.style.right = \"0\" + TablixControl.UnitOfMeasurement;\r\n this.mainDiv.style.right = \"0\" + TablixControl.UnitOfMeasurement;\r\n this.footerDiv.style.right = \"0\" + TablixControl.UnitOfMeasurement;\r\n }\r\n }\r\n\r\n public updateFooterVisibility() {\r\n if (this.rowDim.hasFooter() ? (this.footerDiv.style.display !== \"block\") : (this.footerDiv.style.display !== \"none\")) {\r\n if (this.rowDim.hasFooter()) {\r\n this.footerDiv.style.display = \"block\";\r\n } else {\r\n this.footerDiv.style.display = \"none\";\r\n }\r\n }\r\n }\r\n\r\n private updateVerticalPosition(): void {\r\n\r\n let hasVerticalScrollbar = this.rowDim.scrollbar.visible;\r\n // TODO: ideally the tablix control would not know about where it is rendered but the layout manager\r\n // would provider that information; we should refactor the layout manager so that getLayoutKind is not needed anymore.\r\n let isDashboardTile = this.controlLayoutManager.getLayoutKind() === TablixLayoutKind.DashboardTile;\r\n let showFooter = hasVerticalScrollbar || isDashboardTile;\r\n if (showFooter) {\r\n let mainBottom = this.footerDiv.offsetHeight;\r\n let footerBottom = 0;\r\n let verticalScrollbarBottom = 0;\r\n\r\n // If we have a horizontal scrollbar, we need to adjust the bottom\r\n // value by the scrollbar width\r\n let hasHorizontalScrollbar = this.columnDim.scrollbar.visible;\r\n if (hasHorizontalScrollbar) {\r\n mainBottom += this.scrollBarElementWidth;\r\n footerBottom += this.scrollBarElementWidth;\r\n verticalScrollbarBottom = this.scrollBarElementWidth;\r\n }\r\n\r\n this.mainDiv.style.bottom = mainBottom + TablixControl.UnitOfMeasurement;\r\n this.rowDim.scrollbar.element.style.bottom = verticalScrollbarBottom + TablixControl.UnitOfMeasurement;\r\n this.footerDiv.style.bottom = footerBottom + TablixControl.UnitOfMeasurement;\r\n\r\n // With a vertical scrollbar, the footer is always rendered at the bottom\r\n this.footerDiv.style.removeProperty(\"top\");\r\n }\r\n else {\r\n // Without a vertical scrollbar, the footer is rendered below the last row;\r\n // this is controlled by the top value only\r\n this.footerDiv.style.top = this.gridDimensions.rowHierarchyContentHeight + TablixControl.UnitOfMeasurement;\r\n this.footerDiv.style.removeProperty(\"bottom\");\r\n this.mainDiv.style.removeProperty(\"bottom\");\r\n }\r\n }\r\n\r\n private alreadyRendered(scrollingDimension: TablixDimension): boolean {\r\n if (scrollingDimension !== this.lastRenderingArgs.scrollingDimension ||\r\n this.rowDimension.scrollOffset !== this.lastRenderingArgs.rowScrollOffset ||\r\n this.columnDimension.scrollOffset !== this.lastRenderingArgs.columnScrollOffset) {\r\n return false;\r\n }\r\n\r\n return true;\r\n }\r\n\r\n private render(clear: boolean, scrollingDimension: TablixDimension): void {\r\n // at time of rendering always ensure the scroll offset is valid\r\n this.columnDim.makeScrollOffsetValid();\r\n this.rowDim.makeScrollOffsetValid();\r\n\r\n if (clear || scrollingDimension === null) {\r\n this.lastRenderingArgs = {};\r\n } else if (this.alreadyRendered(scrollingDimension)) {\r\n return;\r\n }\r\n\r\n let done = false;\r\n this.renderIterationCount = 0;\r\n\r\n this.controlLayoutManager.onStartRenderingSession(scrollingDimension, this.mainDiv, clear);\r\n let binder: ITablixBinder = this.binder;\r\n binder.onStartRenderingSession();\r\n\r\n let priorFooterHeight: number = this.gridDimensions.footerHeight;\r\n let priorRowHierarchyHeight: number = this.gridDimensions.rowHierarchyHeight;\r\n let priorRowHierarchyContentHeight: number = this.gridDimensions.rowHierarchyContentHeight;\r\n\r\n while (!done && this.renderIterationCount < TablixControl.MaxRenderIterationCount) {\r\n let hScrollbarVisibility = this.columnDim.scrollbar.visible;\r\n let vScrollbarVisibility = this.rowDim.scrollbar.visible;\r\n\r\n this.columnDim._onStartRenderingIteration();\r\n this.rowDim._onStartRenderingIteration();\r\n this.controlLayoutManager.onStartRenderingIteration(clear);\r\n\r\n // These calls add cells to the table.\r\n // Column needs to be rendered before rows as the row call will pair up with columns to produce the body cells.\r\n this.renderCorner();\r\n this.columnDim._render();\r\n this.rowDim._render();\r\n\r\n done = this.controlLayoutManager.onEndRenderingIteration();\r\n this.columnDim._onEndRenderingIteration();\r\n this.rowDim._onEndRenderingIteration();\r\n\r\n if ((hScrollbarVisibility !== this.columnDim.scrollbar.visible)) {\r\n this.updateVerticalPosition();\r\n }\r\n if (vScrollbarVisibility !== this.rowDim.scrollbar.visible) {\r\n this.updateHorizontalPosition();\r\n }\r\n\r\n this.renderIterationCount++;\r\n }\r\n\r\n this.controlLayoutManager.onEndRenderingSession();\r\n binder.onEndRenderingSession();\r\n\r\n if (this.isTouchEnabled)\r\n this.updateTouchDimensions();\r\n\r\n this.lastRenderingArgs.rowScrollOffset = this.rowDimension.scrollOffset;\r\n this.lastRenderingArgs.columnScrollOffset = this.columnDimension.scrollOffset;\r\n\r\n this.updateContainerDimensions();\r\n\r\n let lastRenderingArgs = this.lastRenderingArgs;\r\n lastRenderingArgs.rowScrollOffset = this.rowDimension.scrollOffset;\r\n lastRenderingArgs.columnScrollOffset = this.columnDimension.scrollOffset;\r\n lastRenderingArgs.scrollingDimension = scrollingDimension;\r\n\r\n if (priorFooterHeight !== this.gridDimensions.footerHeight ||\r\n priorRowHierarchyHeight !== this.gridDimensions.rowHierarchyHeight ||\r\n priorRowHierarchyContentHeight !== this.gridDimensions.rowHierarchyContentHeight) {\r\n this.updateVerticalPosition();\r\n }\r\n\r\n // NOTE: it is critical that we refresh the scrollbars only after the vertical\r\n // position was updated above; otherwise the measurements can be incorrect.\r\n if (this.options.interactive) {\r\n this.columnDim.scrollbar.refresh();\r\n this.rowDim.scrollbar.refresh();\r\n }\r\n }\r\n\r\n private updateContainerDimensions(): void {\r\n let gridDimensions = this.gridDimensions;\r\n\r\n if (this._autoSizeWidth) {\r\n let vScrollBarWidth: number = this.rowDim.scrollbar.visible ? this.scrollBarElementWidth : 0;\r\n this.containerElement.style.width =\r\n gridDimensions.rowHierarchyWidth +\r\n gridDimensions.columnHierarchyWidth +\r\n vScrollBarWidth +\r\n TablixControl.UnitOfMeasurement;\r\n }\r\n\r\n if (this._autoSizeHeight) {\r\n let hScrollBarHeight: number = this.columnDim.scrollbar.visible ? this.scrollBarElementWidth : 0;\r\n this.containerElement.style.height =\r\n gridDimensions.columnHierarchyHeight +\r\n gridDimensions.rowHierarchyHeight +\r\n gridDimensions.footerHeight +\r\n hScrollBarHeight +\r\n TablixControl.UnitOfMeasurement;\r\n }\r\n }\r\n\r\n private cornerCellMatch(item: any, cell: ITablixCell): boolean {\r\n let previousItem: any = cell.item;\r\n return cell.type === TablixCellType.CornerCell && previousItem && this.hierarchyTablixNavigator.cornerCellItemEquals(item, previousItem);\r\n }\r\n\r\n private renderCorner(): void {\r\n let columnDepth: number = this.columnDim.getDepth();\r\n let rowDepth: number = this.rowDim.getDepth();\r\n\r\n for (let i = 0; i < columnDepth; i++) {\r\n for (let j = 0; j < rowDepth; j++) {\r\n let item = this.hierarchyTablixNavigator.getCorner(j, i);\r\n let cell: ITablixCell = this.controlLayoutManager.getOrCreateCornerCell(item, j, i);\r\n let match = this.cornerCellMatch(item, cell);\r\n if (!match) {\r\n this._unbindCell(cell);\r\n cell.type = TablixCellType.CornerCell;\r\n cell.item = item;\r\n\r\n this.binder.bindCornerCell(item, cell);\r\n }\r\n this.controlLayoutManager.onCornerCellRealized(item, cell);\r\n }\r\n }\r\n }\r\n\r\n public _unbindCell(cell: ITablixCell): void { // The intent is to be internal\r\n switch (cell.type) {\r\n case TablixCellType.BodyCell:\r\n this.binder.unbindBodyCell(cell.item, cell);\r\n break;\r\n case TablixCellType.ColumnHeader:\r\n this.binder.unbindColumnHeader(cell.item, cell);\r\n break;\r\n case TablixCellType.RowHeader:\r\n this.binder.unbindRowHeader(cell.item, cell);\r\n break;\r\n case TablixCellType.CornerCell:\r\n this.binder.unbindCornerCell(cell.item, cell);\r\n }\r\n\r\n cell.item = null;\r\n cell.type = null;\r\n }\r\n\r\n private onTouchEvent(args: any[]): void {\r\n let colShift: number;\r\n let rowShift: number;\r\n let that: TablixControl;\r\n\r\n if ((args) && (args.length > 0)) {\r\n if ((\"columnDim\" in args[0]) && (\"rowDim\" in args[0])) {\r\n that = <TablixControl>args[0];\r\n colShift = that.columnDim.scrollbar.visible ? <number>args[1] : 0;\r\n rowShift = that.rowDim.scrollbar.visible ? <number>args[2] : 0;\r\n\r\n that.columnDim.scrollbar.viewMin = Math.max(0, that.columnDim.scrollbar.viewMin + colShift);\r\n that.columnDim.scrollOffset = Math.max(0, that.columnDim.scrollOffset + colShift);\r\n that.rowDim.scrollbar.viewMin = Math.max(0, that.rowDim.scrollbar.viewMin + rowShift);\r\n that.rowDim.scrollOffset = Math.max(0, that.rowDim.scrollOffset + rowShift);\r\n\r\n if (colShift === 0) {\r\n that._onScrollAsync(that.rowDim);\r\n } else if (rowShift === 0) {\r\n that._onScrollAsync(that.columnDim);\r\n } else {\r\n that._onScrollAsync(null);\r\n }\r\n }\r\n }\r\n }\r\n }\r\n}\n\n\n/** WEBPACK FOOTER **\n ** ./src/Clients/Visuals/controls/tablix/tablixControl.ts\n **/","/*\r\n * Power BI Visualizations\r\n *\r\n * Copyright (c) Microsoft Corporation\r\n * All rights reserved. \r\n * MIT License\r\n *\r\n * Permission is hereby granted, free of charge, to any person obtaining a copy\r\n * of this software and associated documentation files (the \"\"Software\"\"), to deal\r\n * in the Software without restriction, including without limitation the rights\r\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\r\n * copies of the Software, and to permit persons to whom the Software is\r\n * furnished to do so, subject to the following conditions:\r\n * \r\n * The above copyright notice and this permission notice shall be included in \r\n * all copies or substantial portions of the Software.\r\n * \r\n * THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR \r\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, \r\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE \r\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER \r\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\r\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\r\n * THE SOFTWARE.\r\n */\r\n\r\n/// <reference path=\"../../_references.ts\"/>\r\n\r\nmodule powerbi.visuals.controls {\r\n\r\n export class TablixDimension {\r\n public _hierarchyNavigator: ITablixHierarchyNavigator;\r\n public _otherDimension; // internal\r\n\r\n public _owner: TablixControl; // protected\r\n public _binder: ITablixBinder; // protected\r\n public _tablixLayoutManager: internal.TablixLayoutManager; // protected\r\n public _layoutManager: IDimensionLayoutManager; // protected\r\n\r\n public model: any;\r\n public modelDepth: number;\r\n\r\n public scrollOffset: number;\r\n private _scrollStep: number = 0.1;\r\n\r\n private _firstVisibleScrollIndex: number;\r\n private _scrollbar: Scrollbar;\r\n public _scrollItems: any[]; // protected\r\n\r\n constructor(tablixControl: TablixControl) {\r\n this._owner = tablixControl;\r\n this._hierarchyNavigator = tablixControl.hierarchyNavigator;\r\n this._binder = tablixControl.getBinder();\r\n this._tablixLayoutManager = tablixControl.layoutManager;\r\n this.scrollOffset = 0;\r\n }\r\n\r\n public _onStartRenderingIteration(): void { // The intent to be internal\r\n this.updateScrollPosition();\r\n }\r\n\r\n public _onEndRenderingIteration(): void { // The intent to be internal\r\n }\r\n\r\n public getValidScrollOffset(scrollOffset: number): number {\r\n return Math.min(Math.max(scrollOffset, 0), Math.max(this.getItemsCount() - this._scrollStep, 0));\r\n }\r\n\r\n public makeScrollOffsetValid(): void {\r\n this.scrollOffset = this.getValidScrollOffset(this.scrollOffset);\r\n }\r\n\r\n public getIntegerScrollOffset(): number {\r\n return Math.floor(this.scrollOffset);\r\n }\r\n\r\n public getFractionScrollOffset(): number {\r\n return this.scrollOffset - this.getIntegerScrollOffset();\r\n }\r\n\r\n public get scrollbar(): Scrollbar {\r\n return this._scrollbar;\r\n }\r\n\r\n public getFirstVisibleItem(level: number): any {\r\n return this._scrollItems[level];\r\n }\r\n\r\n public getFirstVisibleChild(item: any): any {\r\n return this._hierarchyNavigator.getAt(this._hierarchyNavigator.getChildren(item), this.getFirstVisibleChildIndex(item));\r\n }\r\n\r\n public getFirstVisibleChildIndex(item: any): number {\r\n let startItem: any = this.getFirstVisibleItem(this._hierarchyNavigator.getLevel(item) + 1);\r\n let firstVisibleIndex: number;\r\n\r\n if (startItem === undefined || (startItem !== undefined && this._hierarchyNavigator.getParent(startItem) !== item)) {\r\n firstVisibleIndex = 0;\r\n } else {\r\n firstVisibleIndex = this._hierarchyNavigator.getIndex(startItem);\r\n }\r\n return firstVisibleIndex;\r\n }\r\n\r\n public _initializeScrollbar(parentElement: HTMLElement, touchDiv: HTMLDivElement, layoutKind: TablixLayoutKind) { // The intent to be internal\r\n this._scrollbar = this._createScrollbar(parentElement, layoutKind);\r\n this._scrollbar._onscroll.push((e) => this.onScroll());\r\n\r\n if (touchDiv) {\r\n this.scrollbar.initTouch(touchDiv, true);\r\n touchDiv.style.setProperty(\"-ms-touch-action\", \"pinch-zoom\");\r\n }\r\n }\r\n\r\n public getItemsCount(): number {\r\n return this.model ? this._hierarchyNavigator.getLeafCount(this.model) : 0;\r\n }\r\n\r\n public getDepth(): number {\r\n return this.modelDepth;\r\n }\r\n\r\n private onScroll() {\r\n this.scrollOffset = this._scrollbar.viewMin;\r\n this._owner._onScrollAsync(this);\r\n }\r\n\r\n public get otherDimension(): TablixDimension {\r\n return this._otherDimension;\r\n }\r\n\r\n public get layoutManager(): IDimensionLayoutManager {\r\n return this._layoutManager;\r\n }\r\n\r\n public _createScrollbar(parentElement: HTMLElement, layoutKind: TablixLayoutKind): Scrollbar {\r\n // abstract\r\n debug.assertFail(\"PureVirtualMethod: TablixDimension._createScrollbar\");\r\n return null;\r\n }\r\n\r\n private updateScrollPosition(): void {\r\n this._scrollItems = [];\r\n\r\n if (!this.model) {\r\n return;\r\n }\r\n\r\n let firstVisibleScrollIndex: number = this.getIntegerScrollOffset();\r\n let firstVisible: any = this._hierarchyNavigator.getLeafAt(this.model, firstVisibleScrollIndex);\r\n if (!firstVisible) {\r\n return;\r\n }\r\n\r\n this._firstVisibleScrollIndex = firstVisibleScrollIndex;\r\n\r\n do {\r\n this._scrollItems[this._hierarchyNavigator.getLevel(firstVisible)] = firstVisible;\r\n firstVisible = this._hierarchyNavigator.getParent(firstVisible);\r\n } while (firstVisible !== null);\r\n }\r\n }\r\n\r\n export class TablixRowDimension extends TablixDimension {\r\n private _footer: any;\r\n\r\n constructor(tablixControl: TablixControl) {\r\n super(tablixControl);\r\n this._layoutManager = this._tablixLayoutManager.rowLayoutManager;\r\n this._footer = null;\r\n }\r\n\r\n public setFooter(footerHeader: any): void {\r\n this._footer = footerHeader;\r\n this._owner.updateFooterVisibility();\r\n }\r\n\r\n public hasFooter(): boolean {\r\n return (this._footer !== null);\r\n }\r\n\r\n /**\r\n * This method first populates the footer followed by each row and their correlating body cells from top to bottom.\r\n */\r\n public _render() { // The intent to be internal\r\n let firstVisibleRowItem: any = this.getFirstVisibleItem(0);\r\n\r\n if (this.hasFooter()) {\r\n this.addFooterRowHeader(this._footer);\r\n this.addFooterBodyCells(this._footer);\r\n }\r\n\r\n if (firstVisibleRowItem !== undefined) {\r\n this.addNodes(this.model, 0, this.getDepth(), this._hierarchyNavigator.getIndex(firstVisibleRowItem));\r\n }\r\n }\r\n\r\n public _createScrollbar(parentElement: HTMLElement, layoutKind: TablixLayoutKind): Scrollbar {\r\n return new VerticalScrollbar(parentElement, layoutKind);\r\n }\r\n\r\n /**\r\n * This function is a recursive call (with its recursive behavior in addNode()) that will navigate\r\n * through the row hierarchy in DFS (Depth First Search) order and continue into a single row\r\n * upto its estimated edge.\r\n */\r\n private addNodes(items: any, rowIndex: number, depth: number, firstVisibleIndex: number) {\r\n let count = this._hierarchyNavigator.getCount(items);\r\n\r\n //for loop explores children of current \"items\"\r\n for (let i = firstVisibleIndex; i < count; i++) {\r\n if (!this._layoutManager.needsToRealize) {\r\n return;\r\n }\r\n\r\n let item = this._hierarchyNavigator.getAt(items, i);\r\n let cell = this.addNode(item, items, rowIndex, depth);\r\n rowIndex += cell.rowSpan; //next node is bumped down according cells vertical span\r\n }\r\n }\r\n\r\n public getFirstVisibleChildLeaf(item: any): any {\r\n let leaf = item;\r\n\r\n while (!this._hierarchyNavigator.isLeaf(leaf)) {\r\n leaf = this.getFirstVisibleChild(leaf);\r\n }\r\n\r\n return leaf;\r\n }\r\n\r\n private bindRowHeader(item: any, cell: ITablixCell) {\r\n this._binder.bindRowHeader(item, cell);\r\n }\r\n\r\n /**\r\n * This method can be thought of as the continuation of addNodes() as it continues the DFS (Depth First Search)\r\n * started from addNodes(). This function also handles ending the recursion with \"_needsToRealize\" being set to \r\n * false.\r\n *\r\n * Once the body cells are reached, populating is done linearly with addBodyCells().\r\n */\r\n private addNode(item: any, items: any, rowIndex: number, depth: number): ITablixCell {\r\n let previousCount: number;\r\n let rowHeaderCell: ITablixCell = this._tablixLayoutManager.getOrCreateRowHeader(item, items, rowIndex, this._hierarchyNavigator.getLevel(item));\r\n let match = this.rowHeaderMatch(item, rowHeaderCell);\r\n\r\n if (!match) {\r\n this._owner._unbindCell(rowHeaderCell);\r\n rowHeaderCell.type = TablixCellType.RowHeader;\r\n rowHeaderCell.item = item;\r\n rowHeaderCell.unfixRowHeight();\r\n }\r\n\r\n if (this._hierarchyNavigator.isLeaf(item)) {\r\n rowHeaderCell.colSpan = depth - this._hierarchyNavigator.getLevel(item);\r\n rowHeaderCell.rowSpan = 1;\r\n if (!match)\r\n this.bindRowHeader(item, rowHeaderCell);\r\n\r\n this._tablixLayoutManager.onRowHeaderRealized(item, rowHeaderCell);\r\n this.addBodyCells(item, items, rowIndex);\r\n } else {\r\n previousCount = this._layoutManager.getRealizedItemsCount();\r\n this.addNodes(this._hierarchyNavigator.getChildren(item), rowIndex, depth, this.getFirstVisibleChildIndex(item));\r\n rowHeaderCell.colSpan = 1;\r\n rowHeaderCell.rowSpan = this._layoutManager.getRealizedItemsCount() - previousCount + 1;\r\n if (!match)\r\n this.bindRowHeader(item, rowHeaderCell);\r\n this._tablixLayoutManager.onRowHeaderRealized(item, rowHeaderCell);\r\n } \r\n\r\n return rowHeaderCell;\r\n }\r\n\r\n private rowHeaderMatch(item: any, cell: ITablixCell): boolean {\r\n let previousItem = cell.item;\r\n return cell.type === TablixCellType.RowHeader && previousItem && this._hierarchyNavigator.headerItemEquals(item, previousItem);\r\n }\r\n\r\n private addBodyCells(item: any, items: any, rowIndex: number) {\r\n let firstVisibleColumnIndex: number = this._otherDimension.getIntegerScrollOffset();\r\n let columnCount: number = this._otherDimension._layoutManager.getRealizedItemsCount() - this.getDepth();\r\n let hierarchyNavigator = this._hierarchyNavigator;\r\n let otherModel = this._otherDimension.model;\r\n let layoutManager = this._tablixLayoutManager;\r\n\r\n for (let i = 0; i < columnCount; i++) {\r\n //get column header \"item\" by index to pair up with row header to find corelating body cell\r\n let cellItem: any = hierarchyNavigator.getIntersection(item, hierarchyNavigator.getLeafAt(otherModel, firstVisibleColumnIndex + i));\r\n let cell: ITablixCell = layoutManager.getOrCreateBodyCell(cellItem, item, items, rowIndex, i);\r\n this.bindBodyCell(cellItem, cell);\r\n layoutManager.onBodyCellRealized(cellItem, cell);\r\n }\r\n }\r\n\r\n private bindBodyCell(item: any, cell: ITablixCell): void {\r\n let match = this.bodyCelMatch(item, cell);\r\n if (!match) {\r\n this._owner._unbindCell(cell);\r\n cell.type = TablixCellType.BodyCell;\r\n cell.item = item;\r\n cell.unfixRowHeight();\r\n this._binder.bindBodyCell(item, cell);\r\n }\r\n }\r\n\r\n private addFooterRowHeader(item: any) {\r\n let cell: ITablixCell = this._tablixLayoutManager.getOrCreateFooterRowHeader(item, this.model);\r\n cell.colSpan = this.getDepth();\r\n let match = this.rowHeaderMatch(item, cell);\r\n if (!match) {\r\n this._owner._unbindCell(cell);\r\n cell.type = TablixCellType.RowHeader;\r\n cell.item = item;\r\n cell.unfixRowHeight();\r\n this.bindRowHeader(item, cell);\r\n this._tablixLayoutManager.onRowHeaderFooterRealized(item, cell);\r\n }\r\n }\r\n\r\n private addFooterBodyCells(rowItem: any) {\r\n let firstVisibleColumnIndex: number = this._otherDimension.getIntegerScrollOffset();\r\n let columnCount: number = this._otherDimension.layoutManager.getRealizedItemsCount() - this.getDepth();\r\n let layoutManager = this._tablixLayoutManager;\r\n\r\n for (let i = 0; i < columnCount; i++) {\r\n //get column header \"item\" by index to pair up with row header to find corelating body cell\r\n let columnItem: any = this._hierarchyNavigator.getLeafAt(this._otherDimension.model, firstVisibleColumnIndex + i);\r\n //get corelating body cell and bind it\r\n let item: any = this._hierarchyNavigator.getIntersection(rowItem, columnItem);\r\n let cell: ITablixCell = layoutManager.getOrCreateFooterBodyCell(item, i);\r\n this.bindBodyCell(item, cell);\r\n layoutManager.onBodyCellFooterRealized(item, cell);\r\n }\r\n }\r\n\r\n private bodyCelMatch(item: any, cell: ITablixCell): boolean {\r\n let previousItem: any = cell.item;\r\n return cell.type === TablixCellType.BodyCell && previousItem && this._hierarchyNavigator.bodyCellItemEquals(item, previousItem);\r\n }\r\n }\r\n\r\n export class TablixColumnDimension extends TablixDimension {\r\n constructor(tablixControl: TablixControl) {\r\n super(tablixControl);\r\n this._layoutManager = this._tablixLayoutManager.columnLayoutManager;\r\n }\r\n\r\n public _render(): void { // The intent to be internal\r\n let firstVisibleColumnItem: any = this.getFirstVisibleItem(0);\r\n\r\n if (firstVisibleColumnItem !== undefined) {\r\n this.addNodes(this.model, 0, this.getDepth(), this._hierarchyNavigator.getIndex(firstVisibleColumnItem));\r\n }\r\n }\r\n\r\n public _createScrollbar(parentElement: HTMLElement, layoutKind: TablixLayoutKind): Scrollbar {\r\n let scrollbar: HorizontalScrollbar = new HorizontalScrollbar(parentElement, layoutKind);\r\n\r\n // Set smallest increment of the scrollbar to 0.2 rows\r\n scrollbar.smallIncrement = 0.2;\r\n\r\n return scrollbar;\r\n }\r\n\r\n private addNodes(items: any, columnIndex: number, depth: number, firstVisibleIndex: number): void {\r\n let count = this._hierarchyNavigator.getCount(items);\r\n for (let i = firstVisibleIndex; i < count; i++) {\r\n if (!this._layoutManager.needsToRealize) {\r\n return;\r\n }\r\n let cell: ITablixCell = this.addNode(this._hierarchyNavigator.getAt(items, i), items, columnIndex, depth);\r\n columnIndex += cell.colSpan;\r\n }\r\n }\r\n\r\n private addNode(item: any, items: any, columnIndex: number, depth: number) {\r\n let cell: ITablixCell = this._tablixLayoutManager.getOrCreateColumnHeader(item, items, this._hierarchyNavigator.getLevel(item), columnIndex);\r\n\r\n let match = this.columnHeaderMatch(item, cell);\r\n\r\n if (!match) {\r\n this._owner._unbindCell(cell);\r\n cell.type = TablixCellType.ColumnHeader;\r\n cell.item = item;\r\n cell.unfixRowHeight();\r\n }\r\n\r\n if (this._hierarchyNavigator.isLeaf(item)) {\r\n cell.rowSpan = depth - this._hierarchyNavigator.getLevel(item);\r\n }\r\n else {\r\n let previousCount: number = this._layoutManager.getRealizedItemsCount();\r\n this.addNodes(this._hierarchyNavigator.getChildren(item), columnIndex, depth, this.getFirstVisibleChildIndex(item));\r\n\r\n // In case we have a grand total with multiple measures, the multi-measures will be direct children\r\n // There can be difference in level > 1. In this case, we want the Total cell to have rowspan = the difference\r\n let childrenLevelDifference = this._hierarchyNavigator.getChildrenLevelDifference(item);\r\n if (childrenLevelDifference === Infinity) // No Children\r\n cell.rowSpan = 1;\r\n else\r\n cell.rowSpan = childrenLevelDifference;\r\n cell.colSpan = this._layoutManager.getRealizedItemsCount() - previousCount + 1;\r\n }\r\n\r\n if (!match)\r\n this._binder.bindColumnHeader(item, cell);\r\n \r\n this._tablixLayoutManager.onColumnHeaderRealized(item, cell);\r\n return cell;\r\n }\r\n\r\n public columnHeaderMatch(item: any, cell: ITablixCell): boolean {\r\n let previousItem: any = cell.item;\r\n return cell.type === TablixCellType.ColumnHeader && previousItem && this._hierarchyNavigator.headerItemEquals(item, previousItem);\r\n }\r\n }\r\n}\n\n\n/** WEBPACK FOOTER **\n ** ./src/Clients/Visuals/controls/tablix/tablixDimension.ts\n **/","/*\r\n * Power BI Visualizations\r\n *\r\n * Copyright (c) Microsoft Corporation\r\n * All rights reserved. \r\n * MIT License\r\n *\r\n * Permission is hereby granted, free of charge, to any person obtaining a copy\r\n * of this software and associated documentation files (the \"\"Software\"\"), to deal\r\n * in the Software without restriction, including without limitation the rights\r\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\r\n * copies of the Software, and to permit persons to whom the Software is\r\n * furnished to do so, subject to the following conditions:\r\n * \r\n * The above copyright notice and this permission notice shall be included in \r\n * all copies or substantial portions of the Software.\r\n * \r\n * THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR \r\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, \r\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE \r\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER \r\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\r\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\r\n * THE SOFTWARE.\r\n */\r\n\r\n/// <reference path=\"../../_references.ts\"/>\r\n\r\nmodule powerbi.visuals.controls {\r\n\r\n /**\r\n * This class represents the touch region of the column headers (this can also apply to footer/total).\r\n * This class is reponsible for interpreting gestures in terms of pixels to changes in column position.\r\n *\r\n * Unlike the table body, this can only scroll in one direction. \r\n */\r\n export class ColumnTouchDelegate implements TouchUtils.ITouchHandler, TouchUtils.IPixelToItem {\r\n\r\n /** \r\n * Used to termine if the touch event is within bounds.\r\n */\r\n private dim: TouchUtils.Rectangle;\r\n /**\r\n * Average pixel width of columns in table.\r\n */\r\n private averageSize: number;\r\n /**\r\n * Used for 'firing' a scroll event following a received gesture.\r\n */\r\n private tablixControl: TablixControl;\r\n /**\r\n * Stores the event handler of TablixControl for scroll events.\r\n */\r\n private handlers: any;\r\n\r\n /**\r\n * @constructor \r\n * @param region Location and area of the touch region in respect to its HTML element.\r\n */\r\n constructor(region: TouchUtils.Rectangle) {\r\n this.dim = region;\r\n this.averageSize = 1; //default\r\n this.handlers = null;\r\n this.tablixControl = null;\r\n }\r\n\r\n get dimension(): TouchUtils.Rectangle {\r\n return this.dim;\r\n }\r\n\r\n /** \r\n * Sets the amount of columns to be shifted per delta in pixels.\r\n *\r\n * @param xRatio Column to pixel ratio (# columns / # pixels).\r\n */\r\n public setScrollDensity(xRatio: number): void {\r\n this.averageSize = xRatio;\r\n }\r\n\r\n /**\r\n * Resize element.\r\n * \r\n * @param x X location from upper left of listened HTML element.\r\n * @param y Y location from upper left of listened HTML element.\r\n * @param width Width of area to listen for events.\r\n * @param height Height of area to listen for events.\r\n */\r\n public resize(x: number, y: number, width: number, height: number) {\r\n this.dim.x = x;\r\n this.dim.y = y;\r\n this.dim.width = width;\r\n this.dim.height = height;\r\n }\r\n\r\n /**\r\n * @see IPixelToItem.\r\n */\r\n public getPixelToItem(x: number, y: number, dx: number, dy: number, down: boolean): TouchUtils.TouchEvent {\r\n return new TouchUtils.TouchEvent(x * this.averageSize, 0, down, -dx * this.averageSize, 0);\r\n }\r\n\r\n /**\r\n * Fires event to Tablix Control to scroll with the event passed from the TouchManager.\r\n *\r\n * @param e Event recieved from touch manager.\r\n */\r\n public touchEvent(e: TouchUtils.TouchEvent): void {\r\n let args: any[] = [];\r\n\r\n args[0] = this.tablixControl;\r\n args[1] = e.dx;\r\n args[2] = e.dy;\r\n\r\n if (this.handlers) {\r\n fire([this.handlers], args);\r\n }\r\n }\r\n\r\n /**\r\n * Asigns handler for scrolling when scroll event is fired.\r\n *\r\n * @param tablixObj TablixControl that's handling the fired event.\r\n * @param handlerCall The call to be made (EXAMPLE: handlerCall = object.method;).\r\n */\r\n public setHandler(tablixObj: TablixControl, handlerCall: (args: any[]) => void): void {\r\n this.handlers = handlerCall;\r\n this.tablixControl = tablixObj;\r\n }\r\n }\r\n\r\n /**\r\n * This class represents the touch region of the row headers (left or right side aligned).\r\n * This class is reponsible for interpreting gestures in terms of pixels to changes in row position.\r\n *\r\n * Unlike the table body, this can only scroll in one direction. \r\n */\r\n export class RowTouchDelegate implements TouchUtils.ITouchHandler, TouchUtils.IPixelToItem {\r\n /**\r\n * Used to termine if the touch event is within bounds.\r\n */\r\n private dim: TouchUtils.Rectangle;\r\n \r\n /**\r\n * Average pixel height of rows in table.\r\n */\r\n private averageSize: number;\r\n \r\n /**\r\n * Used for 'firing' a scroll event following a recieved gesture.\r\n */\r\n private tablixControl: TablixControl;\r\n \r\n /**\r\n * Stores the event handler of TablixControl for scroll events.\r\n */\r\n private handlers: any;\r\n\r\n /**\r\n * @constructor\r\n * @param region Location and area of the touch region in respect to its HTML element.\r\n */\r\n constructor(region: TouchUtils.Rectangle) {\r\n this.dim = region;\r\n this.averageSize = 30; //default\r\n this.handlers = null;\r\n this.tablixControl = null;\r\n }\r\n\r\n get dimension(): TouchUtils.Rectangle {\r\n return this.dim;\r\n }\r\n\r\n /**\r\n * Sets the amount of rows to be shifted per delta in pixels.\r\n *\r\n * @param yRatio Row to pixel ratio (# rows / # pixels).\r\n */\r\n public setScrollDensity(yRatio: number): void {\r\n this.averageSize = yRatio;\r\n }\r\n\r\n /**\r\n * Resize element.\r\n * @param x X location from upper left of listened HTML element.\r\n * @param y Y location from upper left of listened HTML element.\r\n * @param width Width of area to listen for events.\r\n * @param height Height of area to listen for events.\r\n */\r\n public resize(x: number, y: number, width: number, height: number) {\r\n this.dim.x = x;\r\n this.dim.y = y;\r\n this.dim.width = width;\r\n this.dim.height = height;\r\n }\r\n\r\n /**\r\n * @see: IPixelToItem\r\n */\r\n public getPixelToItem(x: number, y: number, dx: number, dy: number, down: boolean): TouchUtils.TouchEvent {\r\n let event: TouchUtils.TouchEvent = new TouchUtils.TouchEvent(0, y * this.averageSize, down,\r\n 0, -dy * this.averageSize);\r\n return event;\r\n }\r\n\r\n /**\r\n * Fires event to Tablix Control to scroll with the event passed from the TouchManager.\r\n *\r\n * @param e Event recieved from touch manager.\r\n */\r\n public touchEvent(e: TouchUtils.TouchEvent): void {\r\n let args: any[] = [];\r\n\r\n args[0] = this.tablixControl;\r\n args[1] = e.dx;\r\n args[2] = e.dy;\r\n\r\n if (this.handlers) {\r\n fire([this.handlers], args);\r\n }\r\n }\r\n\r\n /**\r\n * Asigns handler for scrolling when scroll event is fired.\r\n *\r\n * @param tablixObj TablixControl that's handling the fired event.\r\n * @param handlerCall The call to be made (EXAMPLE: handlerCall = object.method;).\r\n */\r\n public setHandler(tablixObj: TablixControl, handlerCall: (args: any[]) => void): void {\r\n this.handlers = handlerCall;\r\n this.tablixControl = tablixObj;\r\n }\r\n }\r\n\r\n /**\r\n * This class represents the touch region covering the body of the table.\r\n * This class is reponsible for interpreting gestures in terms of pixels to\r\n * changes in row and column position.\r\n */\r\n export class BodyTouchDelegate implements TouchUtils.ITouchHandler, TouchUtils.IPixelToItem {\r\n private static DefaultAverageSizeX = 30;\r\n private static DefaultAverageSizeY = 30;\r\n \r\n /**\r\n * Used to termine if the touch event is within bounds.\r\n */\r\n private dim: TouchUtils.Rectangle;\r\n \r\n /**\r\n * Average pixel width of columns in table.\r\n */\r\n private averageSizeX: number;\r\n \r\n /**\r\n * Average pixel height of rows in table.\r\n */\r\n private averageSizeY: number;\r\n \r\n /**\r\n * Used for 'firing' a scroll event following a recieved gesture.\r\n */\r\n private tablixControl: TablixControl;\r\n \r\n /**\r\n * Stores the event handler of TablixControl for scroll events.\r\n */\r\n private handlers: any;\r\n\r\n /**\r\n * @constructor\r\n * @param region Location and area of the touch region in respect to its HTML element.\r\n */\r\n constructor(region: TouchUtils.Rectangle) {\r\n this.dim = region;\r\n this.averageSizeX = BodyTouchDelegate.DefaultAverageSizeX;\r\n this.averageSizeY = BodyTouchDelegate.DefaultAverageSizeY;\r\n this.handlers = null;\r\n this.tablixControl = null;\r\n }\r\n\r\n /**\r\n * Returns dimension.\r\n * \r\n * @return The dimentions of the region this delegate listens to.\r\n */\r\n get dimension(): TouchUtils.Rectangle {\r\n return this.dim;\r\n }\r\n\r\n /**\r\n * Sets the amount of rows and columns to be shifted per delta in pixels.\r\n *\r\n * @param xRatio Column to pixel ratio (# columns / # pixels)\r\n * @param yRatio Row to pixel ratio (# rows / # pixels)\r\n */\r\n public setScrollDensity(xRatio: number, yRatio: number): void {\r\n this.averageSizeX = xRatio;\r\n this.averageSizeY = yRatio;\r\n }\r\n\r\n /**\r\n * Resize element.\r\n * \r\n * @param x X location from upper left of listened HTML element.\r\n * @param y Y location from upper left of listened HTML element.\r\n * @param width Width of area to listen for events.\r\n * @param height Height of area to listen for events.\r\n */\r\n public resize(x: number, y: number, width: number, height: number) {\r\n let dimension = this.dim;\r\n\r\n dimension.x = x;\r\n dimension.y = y;\r\n dimension.width = width;\r\n dimension.height = height;\r\n }\r\n\r\n /**\r\n * @see: IPixelToItem.\r\n */\r\n public getPixelToItem(x: number, y: number, dx: number, dy: number, down: boolean): TouchUtils.TouchEvent {\r\n return new TouchUtils.TouchEvent(x * this.averageSizeX, y * this.averageSizeY, down, -dx * this.averageSizeX, -dy * this.averageSizeY);\r\n }\r\n\r\n /**\r\n * Fires event to Tablix Control to scroll with the event passed from the TouchManager.\r\n *\r\n * @param e Event recieved from touch manager.\r\n */\r\n public touchEvent(e: TouchUtils.TouchEvent): void {\r\n let args: any[] = [this.tablixControl, e.dx, e.dy];\r\n\r\n if (this.handlers) {\r\n fire([this.handlers], args);\r\n }\r\n }\r\n\r\n /**\r\n * Asigns handler for scrolling when scroll event is fired.\r\n *\r\n * @param tablixObj TablixControl that's handling the fired event.\r\n * @param handlerCall The call to be made (EXAMPLE: handlerCall = object.method;).\r\n */\r\n public setHandler(tablixObj: TablixControl, handlerCall: (args: any[]) => void): void {\r\n this.handlers = handlerCall;\r\n this.tablixControl = tablixObj;\r\n }\r\n }\r\n}\n\n\n/** WEBPACK FOOTER **\n ** ./src/Clients/Visuals/controls/tablix/tablixTouchDelegate.ts\n **/","/*\r\n * Power BI Visualizations\r\n *\r\n * Copyright (c) Microsoft Corporation\r\n * All rights reserved. \r\n * MIT License\r\n *\r\n * Permission is hereby granted, free of charge, to any person obtaining a copy\r\n * of this software and associated documentation files (the \"\"Software\"\"), to deal\r\n * in the Software without restriction, including without limitation the rights\r\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\r\n * copies of the Software, and to permit persons to whom the Software is\r\n * furnished to do so, subject to the following conditions:\r\n * \r\n * The above copyright notice and this permission notice shall be included in \r\n * all copies or substantial portions of the Software.\r\n * \r\n * THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR \r\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, \r\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE \r\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER \r\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\r\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\r\n * THE SOFTWARE.\r\n */\r\n\r\n/// <reference path=\"../../_references.ts\"/>\r\n\r\nmodule powerbi.visuals.controls.TouchUtils {\r\n\r\n export class Point {\r\n public x: number;\r\n public y: number;\r\n\r\n constructor(x?: number, y?: number) {\r\n this.x = x || 0;\r\n this.y = y || 0;\r\n }\r\n\r\n public offset(offsetX: number, offsetY: number) {\r\n this.x += offsetX;\r\n this.y += offsetY;\r\n }\r\n }\r\n\r\n export class Rectangle extends Point {\r\n public width: number;\r\n public height: number;\r\n\r\n constructor(x?: number, y?: number, width?: number, height?: number) {\r\n super(x, y);\r\n this.width = width || 0;\r\n this.height = height || 0;\r\n }\r\n\r\n get point(): Point {\r\n return new Point(this.x, this.y);\r\n }\r\n\r\n public contains(p: Point): boolean {\r\n return Rectangle.contains(this, p);\r\n }\r\n\r\n public static contains(rect: Rectangle, p: Point): boolean {\r\n if (p && !Rectangle.isEmpty(rect)) {\r\n return rect.x <= p.x && p.x < rect.x + rect.width && rect.y <= p.y && p.y < rect.y + rect.height;\r\n }\r\n return false;\r\n }\r\n\r\n public static isEmpty(rect: Rectangle): boolean {\r\n return !(rect !== undefined && rect.width >= 0 && rect.height >= 0);\r\n }\r\n }\r\n\r\n export const enum SwipeDirection {\r\n /**\r\n * Swipe gesture moves along the y-axis at an angle within an established threshold.\r\n */\r\n Vertical,\r\n \r\n /**\r\n * Swipe gesture moves along the x-axis at an angle within an established threshold.\r\n */\r\n Horizontal,\r\n \r\n /**\r\n * Swipe gesture does not stay within the thresholds of either x or y-axis.\r\n */\r\n FreeForm\r\n }\r\n\r\n export enum MouseButton {\r\n NoClick = 0,\r\n LeftClick = 1,\r\n RightClick = 2,\r\n CenterClick = 3\r\n }\r\n\r\n /** \r\n * Interface serves as a way to convert pixel point to any needed unit of\r\n * positioning over two axises such as row/column positioning.\r\n */\r\n export interface IPixelToItem {\r\n\r\n getPixelToItem(x: number, y: number, dx: number, dy: number, down: boolean): TouchEvent;\r\n }\r\n\r\n /**\r\n * Interface for listening to a simple touch event that's abstracted away\r\n * from any platform specific traits.\r\n */\r\n export interface ITouchHandler {\r\n touchEvent(e: TouchEvent): void;\r\n }\r\n\r\n /** \r\n * A simple touch event class that's abstracted away from any platform specific traits.\r\n */\r\n export class TouchEvent {\r\n /* tslint:disable:no-underscore-prefix-for-variables*/\r\n /**\r\n * X-axis (not neccessarily in pixels (see IPixelToItem)).\r\n */\r\n private _x: number;\r\n \r\n /**\r\n * Y-axis (not neccessarily in pixels (see IPixelToItem)).\r\n */\r\n private _y: number;\r\n \r\n /**\r\n * Delta of x-axis (not neccessarily in pixels (see IPixelToItem)).\r\n */\r\n private _dx: number;\r\n \r\n /**\r\n * Delta of y-axis (not neccessarily in pixels (see IPixelToItem)).\r\n */\r\n private _dy: number;\r\n /* tslint:enable:no-underscore-prefix-for-variables*/\r\n /**\r\n * Determines if the mouse button is pressed.\r\n */\r\n private isMouseButtonDown: boolean;\r\n\r\n /**\r\n * @constructor\r\n * @param x X Location of mouse.\r\n * @param y Y Location of mouse.\r\n * @param isMouseDown Indicates if the mouse button is held down or a finger press on screen.\r\n * @param dx (optional) The change in x of the gesture.\r\n * @param dy (optional) The change in y of the gesture.\r\n */\r\n constructor(x: number, y: number, isMouseDown: boolean, dx?: number, dy?: number) {\r\n this._x = x;\r\n this._y = y;\r\n this.isMouseButtonDown = isMouseDown;\r\n this._dx = dx || 0;\r\n this._dy = dy || 0;\r\n }\r\n\r\n public get x(): number {\r\n return this._x;\r\n }\r\n\r\n public get y(): number {\r\n return this._y;\r\n }\r\n\r\n public get dx(): number {\r\n return this._dx;\r\n }\r\n\r\n public get dy(): number {\r\n return this._dy;\r\n }\r\n\r\n /**\r\n * Returns a boolean indicating if the mouse button is held down.\r\n * \r\n * @return: True if the the mouse button is held down,\r\n * otherwise false.\r\n */\r\n public get isMouseDown(): boolean {\r\n return this.isMouseButtonDown;\r\n }\r\n }\r\n\r\n /**\r\n * This interface defines the datamembers stored for each touch region.\r\n */\r\n export interface ITouchHandlerSet {\r\n handler: ITouchHandler;\r\n region: Rectangle;\r\n\r\n lastPoint: TouchEvent;\r\n converter: IPixelToItem;\r\n }\r\n\r\n /** \r\n * This class \"listens\" to the TouchEventInterpreter to recieve touch events and sends it to all \r\n * \"Touch Delegates\" with TouchRegions that contain the mouse event. Prior to sending off the\r\n * event, its position is put in respect to the delegate's TouchRegion and converted to the appropriate\r\n * unit (see IPixelToItem).\r\n */\r\n export class TouchManager {\r\n /**\r\n * List of touch regions and their correlating data memebers.\r\n */\r\n private touchList: ITouchHandlerSet[];\r\n \r\n /**\r\n * Boolean to enable thresholds for fixing to an axis when scrolling.\r\n */\r\n private scrollThreshold: boolean;\r\n \r\n /**\r\n * Boolean to enable locking to an axis when gesture is fixed to an axis.\r\n */\r\n private lockThreshold: boolean;\r\n \r\n /**\r\n * The current direction of the swipe.\r\n */\r\n private swipeDirection: SwipeDirection;\r\n \r\n /**\r\n * The count of consecutive events match the current swipe direction.\r\n */\r\n private matchingDirectionCount: number;\r\n \r\n /**\r\n * The last recieved mouse event.\r\n */\r\n private lastTouchEvent: TouchEvent;\r\n\r\n /**\r\n * Default constructor.\r\n * \r\n * The default behavior is to enable thresholds and lock to axis.\r\n */\r\n constructor() {\r\n this.touchList = [];\r\n this.swipeDirection = SwipeDirection.FreeForm;\r\n this.matchingDirectionCount = 0;\r\n\r\n this.lockThreshold = true;\r\n this.scrollThreshold = true;\r\n this.lastTouchEvent = new TouchEvent(0, 0, false);\r\n }\r\n\r\n public get lastEvent(): TouchEvent {\r\n return this.lastTouchEvent;\r\n }\r\n\r\n /**\r\n * @param region Rectangle indicating the locations of the touch region.\r\n * @param handler Handler for recieved touch events.\r\n * @param converter Converts from pixels to the wanted item of measure (rows, columns, etc).\r\n * \r\n * EXAMPLE: dx -> from # of pixels to the right to # of columns moved to the right.\r\n */\r\n public addTouchRegion(region: Rectangle, handler: ITouchHandler, converter: IPixelToItem): void {\r\n let item: ITouchHandlerSet = <ITouchHandlerSet> {\r\n lastPoint: new TouchEvent(0, 0, false),\r\n handler: handler,\r\n region: region,\r\n converter: converter\r\n };\r\n\r\n this.touchList = this.touchList.concat([item]);\r\n }\r\n\r\n /**\r\n * Sends a mouse up event to all regions with their last event as a mouse down event.\r\n */\r\n public upAllTouches(): void {\r\n let eventPoint: TouchEvent;\r\n let length: number;\r\n\r\n length = this.touchList.length;\r\n for (let i = 0; i < length; i++) {\r\n if (this.touchList[i].lastPoint.isMouseDown) {\r\n eventPoint = this.touchList[i].converter.getPixelToItem(this.touchList[i].lastPoint.x,\r\n this.touchList[i].lastPoint.y,\r\n 0, 0, false);\r\n this.touchList[i].handler.touchEvent(eventPoint);\r\n }\r\n\r\n this.touchList[i].lastPoint = new TouchEvent(this.touchList[i].lastPoint.x,\r\n this.touchList[i].lastPoint.y, false);\r\n }\r\n\r\n this.lastTouchEvent = new TouchEvent(0, 0, false);\r\n }\r\n\r\n public touchEvent(e: TouchEvent): void {\r\n let list: ITouchHandlerSet[];\r\n let length: number;\r\n\r\n let x: number = 0;\r\n let y: number = 0;\r\n let dx: number = 0;\r\n let dy: number = 0;\r\n let angle: number = 0;\r\n\r\n let eventPoint: TouchEvent = null;\r\n\r\n //assume there are already regions in the middle of a drag event and get those regions\r\n list = this._getActive();\r\n\r\n //if this is the start of a mouse drag event, repopulate the list with touched regions\r\n if (!this.lastTouchEvent.isMouseDown && e.isMouseDown) {\r\n list = this._findRegions(e);\r\n }\r\n\r\n //determine the delta values and update last event (delta ignored on first mouse down event)\r\n dx = this.lastTouchEvent.x - e.x;\r\n dy = this.lastTouchEvent.y - e.y;\r\n this.lastTouchEvent = new TouchEvent(e.x, e.y, e.isMouseDown, dx, dy);\r\n\r\n //go through the list\r\n length = list.length;\r\n for (let i = 0; i < length; i++) {\r\n x = e.x - list[i].region.point.x;\r\n y = e.y - list[i].region.point.y;\r\n\r\n //is this in the middle of a drag?\r\n if (list[i].lastPoint.isMouseDown && e.isMouseDown) {\r\n dx = x - list[i].lastPoint.x;\r\n dy = y - list[i].lastPoint.y;\r\n\r\n //calculate the absolute angle from the horizontal axis\r\n angle = Math.abs(180 / Math.PI * Math.atan(dy / dx));\r\n\r\n if (this.scrollThreshold) {\r\n //is the gesture already locked? (6 prior events within the threshold)\r\n if (this.lockThreshold && (this.matchingDirectionCount > 5)) {\r\n if (this.swipeDirection === SwipeDirection.Horizontal) {\r\n dy = 0;\r\n }\r\n else if (this.swipeDirection === SwipeDirection.Vertical) {\r\n dx = 0;\r\n }\r\n }\r\n else {\r\n //is it within the horizontal threshold?\r\n if (angle < 20) {\r\n dy = 0;\r\n if (this.swipeDirection === SwipeDirection.Horizontal) {\r\n this.matchingDirectionCount++;\r\n }\r\n else {\r\n this.matchingDirectionCount = 1;\r\n this.swipeDirection = SwipeDirection.Horizontal;\r\n }\r\n }\r\n else {\r\n //calculate the absolute angle from the vertical axis\r\n angle = Math.abs(180 / Math.PI * Math.atan(dx / dy));\r\n\r\n //is it within the horizontal threshold?\r\n if (angle < 20) {\r\n dx = 0;\r\n\r\n if (this.swipeDirection === SwipeDirection.Vertical) {\r\n this.matchingDirectionCount++;\r\n }\r\n else {\r\n this.matchingDirectionCount = 1;\r\n this.swipeDirection = SwipeDirection.Vertical;\r\n }\r\n }\r\n else {\r\n if (this.swipeDirection === SwipeDirection.FreeForm) {\r\n this.matchingDirectionCount++;\r\n }\r\n else {\r\n this.swipeDirection = SwipeDirection.FreeForm;\r\n this.matchingDirectionCount = 1;\r\n }\r\n }\r\n }\r\n }\r\n }\r\n }\r\n\r\n else {\r\n dx = 0;\r\n dy = 0;\r\n this.swipeDirection = SwipeDirection.FreeForm;\r\n this.matchingDirectionCount = 0;\r\n }\r\n\r\n list[i].lastPoint = new TouchEvent(x, y, e.isMouseDown, dx, dy);\r\n\r\n eventPoint = list[i].converter.getPixelToItem(x, y, dx, dy, e.isMouseDown);\r\n list[i].handler.touchEvent(eventPoint);\r\n }\r\n }\r\n\r\n /**\r\n * @param e Position of event used to find touched regions\r\n * @return Array of regions that contain the event point.\r\n */\r\n private _findRegions(e: TouchEvent): ITouchHandlerSet[] {\r\n let list: ITouchHandlerSet[] = [];\r\n let length: number;\r\n\r\n length = this.touchList.length;\r\n for (let i = 0; i < length; i++) {\r\n if (this.touchList[i].region.contains(new Point(e.x, e.y))) {\r\n list = list.concat([this.touchList[i]]);\r\n }\r\n }\r\n\r\n return list;\r\n }\r\n\r\n /**\r\n * @return Array of regions that contain a mouse down event. (see ITouchHandlerSet.lastPoint).\r\n */\r\n private _getActive(): ITouchHandlerSet[] {\r\n let list: ITouchHandlerSet[] = [];\r\n let length: number;\r\n\r\n length = this.touchList.length;\r\n for (let i = 0; i < length; i++) {\r\n if (this.touchList[i].lastPoint.isMouseDown) {\r\n list = list.concat([this.touchList[i]]);\r\n }\r\n }\r\n\r\n return list;\r\n }\r\n }\r\n\r\n /**\r\n * This interface defines the swipe data.\r\n */\r\n interface ISwipeInfo {\r\n direction: number;\r\n distance: number;\r\n endTime: number;\r\n time: number;\r\n }\r\n\r\n const MinDistanceForSwipe = 80;\r\n const MaxTimeForSwipe = 600;\r\n\r\n /**\r\n * This class is responsible for establishing connections to handle touch events\r\n * and to interpret those events so they're compatible with the touch abstractions.\r\n *\r\n * Touch events with platform specific handles should be done here.\r\n */\r\n export class TouchEventInterpreter {\r\n /**\r\n * HTML element that touch events are drawn from.\r\n */\r\n private touchPanel: HTMLElement;\r\n \r\n /**\r\n * Boolean enabling mouse drag.\r\n */\r\n private allowMouseDrag: boolean;\r\n \r\n /**\r\n * Touch events are interpreted and passed on this manager.\r\n */\r\n private manager: TouchManager;\r\n \r\n /**\r\n * @see TablixLayoutManager. \r\n */\r\n private scale: number;\r\n \r\n /**\r\n * Used for mouse location when a secondary div is used along side the primary with this one being the primary.\r\n */\r\n private touchReferencePoint: HTMLElement;\r\n \r\n /** \r\n * Rectangle containing the targeted Div.\r\n */\r\n private rect: ClientRect;\r\n\r\n private documentMouseMoveWrapper: any;\r\n private documentMouseUpWrapper: any;\r\n\r\n /**\r\n * Those setting related to swipe detection \r\n * touchStartTime - the time that the user touched down the screen.\r\n */\r\n private touchStartTime: number;\r\n /**\r\n * The page y value of the touch event when the user touched down.\r\n */\r\n private touchStartPageY: number;\r\n /**\r\n * The last page y value befoer the user raised up his finger.\r\n */\r\n private touchLastPageY: number;\r\n /**\r\n * The last page x value befoer the user raised up his finger.\r\n */\r\n private touchLastPageX: number;\r\n /**\r\n * An indicator whether we are now running the slide affect.\r\n */\r\n private sliding: boolean;\r\n\r\n constructor(manager: TouchManager) {\r\n this.manager = manager;\r\n this.allowMouseDrag = true;\r\n this.touchPanel = null;\r\n this.scale = 1;\r\n this.documentMouseMoveWrapper = null;\r\n this.documentMouseUpWrapper = null;\r\n this.sliding = false;\r\n }\r\n\r\n public initTouch(panel: HTMLElement, touchReferencePoint?: HTMLElement, allowMouseDrag?: boolean): void {\r\n panel.style.setProperty(\"-ms-touch-action\", \"pinch-zoom\");\r\n\r\n this.touchReferencePoint = touchReferencePoint;\r\n\r\n this.touchPanel = panel;\r\n this.allowMouseDrag = allowMouseDrag === undefined ? true : allowMouseDrag;\r\n if (\"ontouchmove\" in panel) {\r\n panel.addEventListener(\"touchstart\", e => this.onTouchStart(e));\r\n panel.addEventListener(\"touchend\", e => this.onTouchEnd(e));\r\n }\r\n else {\r\n panel.addEventListener(\"mousedown\", e => this.onTouchMouseDown(<MouseEvent>e));\r\n panel.addEventListener(\"mouseup\", e => this.onTouchMouseUp(<MouseEvent>e));\r\n }\r\n }\r\n\r\n private getXYByClient(pageX: number, pageY: number, rect: ClientRect): Point {\r\n let x: number = rect.left;\r\n let y: number = rect.top;\r\n\r\n // Fix for Safari\r\n if (window[\"scrollX\"] !== undefined) {\r\n x += window[\"scrollX\"];\r\n y += window[\"scrollY\"];\r\n }\r\n\r\n let point: Point = new Point(0, 0);\r\n point.offset(pageX - x, pageY - y);\r\n\r\n return point;\r\n }\r\n\r\n public onTouchStart(e: any): void {\r\n if (e.touches.length === 1) {\r\n e.cancelBubble = true;\r\n\r\n let mouchEvent: MouseEvent = e.touches[0];\r\n this.touchStartTime = new Date().getTime();\r\n this.touchStartPageY = mouchEvent.pageY;\r\n\r\n this.onTouchMouseDown(mouchEvent);\r\n }\r\n }\r\n\r\n public onTouchMove(e: any): void {\r\n if (e.touches.length === 1) {\r\n if (e.preventDefault) {\r\n e.preventDefault();\r\n }\r\n\r\n let mouchEvent: MouseEvent = e.touches[0];\r\n this.touchLastPageY = mouchEvent.pageY;\r\n this.touchLastPageX = mouchEvent.pageX;\r\n // while sliding ignore the touch move event \r\n if (!this.sliding) {\r\n this.onTouchMouseMove(mouchEvent);\r\n }\r\n }\r\n }\r\n\r\n public onTouchEnd(e: any): void {\r\n this.clearTouchEvents();\r\n\r\n let swipeInfo = this.getSwipeInfo();\r\n if (this.didUserSwipe(swipeInfo)) {\r\n this.startSlideAffect(swipeInfo);\r\n }\r\n // in case this is not a swipe - we need to reset the rect and the touches \r\n else if (!this.sliding) {\r\n this.upAllTouches();\r\n }\r\n }\r\n\r\n public onTouchMouseDown(e: MouseEvent): void {\r\n this.scale = HTMLElementUtils.getAccumulatedScale(this.touchPanel);\r\n\r\n //any prior touch scrolling that produced a selection outside Tablix will prevent the next touch scroll (1262519)\r\n document.getSelection().removeAllRanges();\r\n\r\n this.rect = (this.touchReferencePoint ? this.touchReferencePoint : this.touchPanel).getBoundingClientRect();\r\n\r\n if (\"ontouchmove\" in this.touchPanel) {\r\n this.documentMouseMoveWrapper = e => this.onTouchMove(e);\r\n document.addEventListener(\"touchmove\", this.documentMouseMoveWrapper);\r\n this.documentMouseUpWrapper = e => this.onTouchEnd(e);\r\n document.addEventListener(\"touchend\", this.documentMouseUpWrapper);\r\n }\r\n else {\r\n this.documentMouseMoveWrapper = e => this.onTouchMouseMove(e);\r\n document.addEventListener(\"mousemove\", this.documentMouseMoveWrapper);\r\n this.documentMouseUpWrapper = e => this.onTouchMouseUp(e);\r\n document.addEventListener(\"mouseup\", this.documentMouseUpWrapper);\r\n }\r\n\r\n if (\"setCapture\" in this.touchPanel) {\r\n this.touchPanel.setCapture();\r\n }\r\n }\r\n\r\n public onTouchMouseMove(e: MouseEvent): void {\r\n let event: TouchEvent;\r\n let point: Point;\r\n\r\n let rect = this.rect;\r\n let validMouseDragEvent: boolean = (rect !== null) && (e.which !== MouseButton.NoClick);\r\n\r\n // Ignore events that are not part of a drag event\r\n if (!validMouseDragEvent || this.sliding)\r\n return;\r\n\r\n point = this.getXYByClient(e.pageX, e.pageY, rect);\r\n event = new TouchEvent(point.x / this.scale, point.y / this.scale, validMouseDragEvent);\r\n\r\n this.manager.touchEvent(event);\r\n\r\n if (e.preventDefault)\r\n e.preventDefault();\r\n else if (\"returnValue\" in e) //some browsers missing preventDefault() may still use this instead\r\n e[\"returnValue\"] = false;\r\n }\r\n\r\n public onTouchMouseUp(e: MouseEvent, bubble?: boolean): void {\r\n this.upAllTouches();\r\n this.clearTouchEvents();\r\n }\r\n\r\n private getSwipeInfo(): ISwipeInfo {\r\n let touchEndTime = new Date().getTime();\r\n let touchTime = touchEndTime - this.touchStartTime;\r\n let touchDist = this.touchLastPageY - this.touchStartPageY;\r\n let touchDirection = touchDist < 0 ? -1 : 1;\r\n\r\n return {\r\n direction: touchDirection,\r\n distance: touchDist,\r\n endTime: touchEndTime,\r\n time: touchTime,\r\n };\r\n }\r\n\r\n private didUserSwipe(swipeInfo: ISwipeInfo): boolean {\r\n return swipeInfo.time < MaxTimeForSwipe && swipeInfo.distance * swipeInfo.direction > MinDistanceForSwipe;\r\n }\r\n\r\n /**\r\n * In case of swipe - auto advance to the swipe direction in 2 steps.\r\n */\r\n private startSlideAffect(swipeInfo: ISwipeInfo): void {\r\n if (this.sliding) {\r\n return;\r\n }\r\n\r\n this.sliding = true;\r\n let point = this.getXYByClient(this.touchLastPageX, this.touchLastPageY, this.rect);\r\n this.slide(point, 300, swipeInfo);\r\n\r\n // second step\r\n requestAnimationFrame(() => {\r\n // in case the user is now scrolling in the opposite direction stop the slide\r\n if (!this.didUserChangeDirection(swipeInfo)) {\r\n this.slide(point, 200, swipeInfo);\r\n }\r\n this.clearSlide();\r\n });\r\n }\r\n\r\n private didUserChangeDirection(swipeInfo: ISwipeInfo): boolean {\r\n if (this.touchStartTime <= swipeInfo.endTime) {\r\n return false;\r\n }\r\n\r\n let updatedDist = this.touchLastPageY - this.touchStartPageY;\r\n let updatedDirection = updatedDist < 0 ? -1 : 1;\r\n return updatedDirection !== swipeInfo.direction;\r\n }\r\n\r\n private slide(point: Point, slideDist: number, swipeInfo: ISwipeInfo): void {\r\n let updatedDist = this.touchStartTime > swipeInfo.endTime ? this.touchLastPageY - this.touchStartPageY : 0;\r\n\r\n point.y += slideDist * swipeInfo.direction + updatedDist;\r\n let event = new TouchEvent(point.x / this.scale, point.y / this.scale, true);\r\n\r\n this.manager.touchEvent(event);\r\n }\r\n\r\n private clearSlide(): void {\r\n this.sliding = false;\r\n this.upAllTouches();\r\n }\r\n\r\n private upAllTouches(): void {\r\n if (this.documentMouseMoveWrapper !== null)\r\n return;\r\n\r\n this.rect = null;\r\n this.manager.upAllTouches();\r\n }\r\n\r\n private clearTouchEvents(): void {\r\n if (\"releaseCapture\" in this.touchPanel) {\r\n this.touchPanel.releaseCapture();\r\n }\r\n\r\n if (this.documentMouseMoveWrapper === null)\r\n return;\r\n\r\n if (\"ontouchmove\" in this.touchPanel) {\r\n document.removeEventListener(\"touchmove\", this.documentMouseMoveWrapper);\r\n document.removeEventListener(\"touchend\", this.documentMouseUpWrapper);\r\n }\r\n else {\r\n document.removeEventListener(\"mousemove\", this.documentMouseMoveWrapper);\r\n document.removeEventListener(\"mouseup\", this.documentMouseUpWrapper);\r\n }\r\n\r\n this.documentMouseMoveWrapper = null;\r\n this.documentMouseUpWrapper = null;\r\n }\r\n }\r\n}\n\n\n/** WEBPACK FOOTER **\n ** ./src/Clients/Visuals/controls/tablix/touchRegionAbstraction.ts\n **/","/*\r\n * Power BI Visualizations\r\n *\r\n * Copyright (c) Microsoft Corporation\r\n * All rights reserved.\r\n * MIT License\r\n *\r\n * Permission is hereby granted, free of charge, to any person obtaining a copy\r\n * of this software and associated documentation files (the \"\"Software\"\"), to deal\r\n * in the Software without restriction, including without limitation the rights\r\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\r\n * copies of the Software, and to permit persons to whom the Software is\r\n * furnished to do so, subject to the following conditions:\r\n *\r\n * The above copyright notice and this permission notice shall be included in\r\n * all copies or substantial portions of the Software.\r\n *\r\n * THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\r\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\r\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\r\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\r\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\r\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\r\n * THE SOFTWARE.\r\n */\r\n\r\n/// <reference path=\"../../_references.ts\"/>\r\n\r\nmodule powerbi.visuals.controls {\r\n export enum TablixType {\r\n Matrix,\r\n Table\r\n }\r\n\r\n /**\r\n * General section of Formatting Properties for Tablix\r\n */\r\n export interface TablixFormattingPropertiesGeneral {\r\n /** Property that drives whether columns should use automatically calculated (based on content) sizes for width or use persisted sizes.\r\n Default is true i.e. automatically calculate width based on column content */\r\n autoSizeColumnWidth: boolean;\r\n /**\r\n * Font size for the whole tablix\r\n * Default is 8\r\n */\r\n textSize: number;\r\n }\r\n\r\n /**\r\n * General section of Formatting Properties for Table\r\n */\r\n export interface TablixFormattingPropertiesGeneralTable extends TablixFormattingPropertiesGeneral {\r\n /*\r\n * Show/Hide Grand Total\r\n * Default is True\r\n */\r\n totals?: boolean;\r\n }\r\n\r\n /**\r\n * General section of Formatting Properties for Matrix\r\n */\r\n export interface TablixFormattingPropertiesGeneralMatrix extends TablixFormattingPropertiesGeneral {\r\n /**\r\n * Show/Hide Subtotal Rows\r\n */\r\n rowSubtotals?: boolean;\r\n /**\r\n * Show/Hide Subtotal Columns\r\n */\r\n columnSubtotals?: boolean;\r\n }\r\n\r\n /**\r\n * Grid section of Formatting Properties for Tablix\r\n */\r\n export interface TablixFormattingPropertiesGrid {\r\n /**\r\n * Show/Hide vertical gridlines\r\n */\r\n gridVertical?: boolean;\r\n\r\n /**\r\n * vertical gridlines color\r\n */\r\n gridVerticalColor?: string;\r\n\r\n /**\r\n * vertical gridlines Weight\r\n */\r\n gridVerticalWeight?: number;\r\n\r\n /**\r\n * Show/Hide horizontal gridlines\r\n */\r\n gridHorizontal?: boolean;\r\n\r\n /**\r\n * horizontal gridlines color\r\n */\r\n gridHorizontalColor?: string;\r\n\r\n /**\r\n * horizontal gridlines Weight\r\n */\r\n gridHorizontalWeight?: number;\r\n\r\n /**\r\n * Color of the outline. Shared across all regions\r\n */\r\n outlineColor?: string;\r\n\r\n /**\r\n * Weight outline. Shared across all regions\r\n */\r\n outlineWeight?: number;\r\n\r\n /**\r\n * Weight outline. Shared across all regions\r\n */\r\n rowPadding?: number;\r\n\r\n /**\r\n * Maximum height of images in pixels\r\n */\r\n imageHeight?: number;\r\n }\r\n\r\n /**\r\n * Common Formatting Properties for Tablix regions (Column Headers, Row Headers, Total, SubTotals)\r\n */\r\n export interface TablixFormattingPropertiesRegion {\r\n /*\r\n * Font color of all cells within the region\r\n * Default is <Undefined>\r\n */\r\n fontColor?: string;\r\n /*\r\n * Background color of all cells within the region\r\n * Default is <Undefined>\r\n */\r\n backColor?: string;\r\n /*\r\n * Outline style for the whole region. One of the values from powerbi.visuals.outline\r\n * Default is outline.none\r\n */\r\n outline: string;\r\n }\r\n\r\n export interface TablixFormattingPropertiesValues {\r\n /*\r\n * Font color of all cells for Odd Index rows\r\n * Default is <Undefined>\r\n */\r\n fontColorPrimary?: string;\r\n /*\r\n * Background color of all cells for Odd Index rows\r\n * Default is <Undefined>\r\n */\r\n backColorPrimary?: string;\r\n /*\r\n * Font color of all cells for even Index rows\r\n * Default is <Undefined>\r\n */\r\n fontColorSecondary?: string;\r\n /*\r\n * Background color of all cells for even Index rows\r\n * Default is <Undefined>\r\n */\r\n backColorSecondary?: string;\r\n /*\r\n * Outline style for the whole region. One of the values from powerbi.visuals.outline\r\n * Default is outline.none\r\n */\r\n outline: string;\r\n }\r\n\r\n /**\r\n * Formatting Properties for Table Values region\r\n */\r\n export interface TablixFormattingPropertiesValuesTable extends TablixFormattingPropertiesValues {\r\n /*\r\n * Use an icon instead of URL text\r\n * Default is False\r\n */\r\n urlIcon?: boolean;\r\n }\r\n\r\n /**\r\n * Formatting Properties for Table Visual\r\n */\r\n export interface TablixFormattingPropertiesTable {\r\n general?: TablixFormattingPropertiesGeneralTable;\r\n grid?: TablixFormattingPropertiesGrid;\r\n columnHeaders?: TablixFormattingPropertiesRegion;\r\n values?: TablixFormattingPropertiesValuesTable;\r\n total?: TablixFormattingPropertiesRegion;\r\n }\r\n\r\n /**\r\n * Formatting Properties for Matrix Visual\r\n */\r\n export interface TablixFormattingPropertiesMatrix {\r\n general?: TablixFormattingPropertiesGeneralMatrix;\r\n grid?: TablixFormattingPropertiesGrid;\r\n columnHeaders?: TablixFormattingPropertiesRegion;\r\n rowHeaders?: TablixFormattingPropertiesRegion;\r\n values?: TablixFormattingPropertiesValues;\r\n subtotals?: TablixFormattingPropertiesRegion;\r\n }\r\n}\n\n\n/** WEBPACK FOOTER **\n ** ./src/Clients/Visuals/controls/tablix/ITablixFormatting.ts\n **/","/*\r\n * Power BI Visualizations\r\n *\r\n * Copyright (c) Microsoft Corporation\r\n * All rights reserved. \r\n * MIT License\r\n *\r\n * Permission is hereby granted, free of charge, to any person obtaining a copy\r\n * of this software and associated documentation files (the \"\"Software\"\"), to deal\r\n * in the Software without restriction, including without limitation the rights\r\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\r\n * copies of the Software, and to permit persons to whom the Software is\r\n * furnished to do so, subject to the following conditions:\r\n * \r\n * The above copyright notice and this permission notice shall be included in \r\n * all copies or substantial portions of the Software.\r\n * \r\n * THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR \r\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, \r\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE \r\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER \r\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\r\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\r\n * THE SOFTWARE.\r\n */\r\n\r\n/// <reference path=\"../../_references.ts\"/>\r\n\r\nmodule powerbi.visuals.controls {\r\n import PropAutoSizeWidth = internal.TablixObjects.PropGeneralAutoSizeColumns;\r\n import getMetadataObjects = internal.TablixObjects.getMetadadataObjects;\r\n\r\n /**\r\n * Column Width Object identifying a certain column and its width\r\n */\r\n export interface ColumnWidthObject {\r\n /**\r\n * QueryName of the Column\r\n */\r\n queryName: string;\r\n\r\n /**\r\n * Flag indicating whether the Column should have a fixed size or fit its size to the contents\r\n */\r\n isFixed: boolean;\r\n\r\n /**\r\n * Width of the column in px.\r\n * If isFixed=False, undefined always\r\n * If isFixed=True, undefined if unknown\r\n */\r\n width?: number;\r\n }\r\n\r\n /**\r\n * Collection of Column Widths indexed by Column's queryName\r\n */\r\n export interface ColumnWidthCollection {\r\n [queryName: string]: ColumnWidthObject;\r\n }\r\n\r\n /**\r\n * Handler for Column Width Changed event\r\n */\r\n export interface ColumnWidthChangedCallback {\r\n (columnWidthChangedEventArgs: ColumnWidthObject): void;\r\n }\r\n\r\n /**\r\n * Handler for requesting host to persist Column Width Objects\r\n */\r\n export interface HostPersistCallBack {\r\n (visualObjectInstances: VisualObjectInstancesToPersist): void;\r\n }\r\n\r\n export class TablixColumnWidthManager {\r\n /**\r\n * PropertyID for Column Widths (General > columnWidth)\r\n */\r\n private static columnWidthProp: DataViewObjectPropertyIdentifier = { objectName: 'general', propertyName: 'columnWidth' };\r\n\r\n /**\r\n * Array holding widths for all columns. Index is the queryName of the column\r\n */\r\n private columnWidthObjects: ColumnWidthCollection;\r\n\r\n /**\r\n * Visual Object Instances to be persisted. Containing autoSizeProperty and any width to remove/merge\r\n */\r\n private visualObjectInstancesToPersist: VisualObjectInstancesToPersist;\r\n\r\n /**\r\n * True if the Tablix is a Matrix\r\n */\r\n private isMatrix: boolean;\r\n /**\r\n * Array of all leaf nodes (Row Groupings + Columns/Values instances)\r\n */\r\n private matrixLeafNodes: MatrixVisualNode[];\r\n /**\r\n * Current DataView\r\n */\r\n private currentDataView: DataView;\r\n /**\r\n * Current value of AutoSizeColumns after last DataView Update\r\n */\r\n private currentAutoColumnSizePropertyValue: boolean;\r\n\r\n /**\r\n * Previous DataView\r\n */\r\n private previousDataView: DataView;\r\n /**\r\n * Previous value of AutoSizeColumns before last DataView Update\r\n */\r\n private previousAutoColumnSizePropertyValue: boolean;\r\n\r\n /**\r\n * Handler for requesting host to persist Column Width Objects\r\n */\r\n private hostPersistCallBack: HostPersistCallBack;\r\n\r\n constructor(dataView: DataView, isMatrix: boolean, hostPersistCallBack: HostPersistCallBack, matrixLeafNodes?: MatrixVisualNode[]) {\r\n this.columnWidthObjects = {};\r\n this.isMatrix = isMatrix;\r\n this.updateDataView(dataView, matrixLeafNodes);\r\n this.hostPersistCallBack = hostPersistCallBack;\r\n this.visualObjectInstancesToPersist = { merge: [], remove: [] };\r\n }\r\n\r\n // #region Update DataView\r\n /**\r\n * Update the current DataView\r\n * @param {dataView} DataView new DataView\r\n * @param {MatrixVisualNode[]} matrixLeafNodes? (Optional)Matrix Leaf Nodes\r\n */\r\n public updateDataView(dataView: DataView, matrixLeafNodes?: MatrixVisualNode[]): void {\r\n this.previousDataView = this.currentDataView;\r\n if (this.previousDataView)\r\n this.previousAutoColumnSizePropertyValue = PropAutoSizeWidth.getValue<boolean>(getMetadataObjects(this.previousDataView));\r\n else\r\n this.previousAutoColumnSizePropertyValue = undefined;\r\n\r\n this.currentDataView = dataView;\r\n if (this.currentDataView)\r\n this.currentAutoColumnSizePropertyValue = PropAutoSizeWidth.getValue<boolean>(getMetadataObjects(this.currentDataView));\r\n else\r\n this.currentAutoColumnSizePropertyValue = undefined;\r\n\r\n this.matrixLeafNodes = matrixLeafNodes;\r\n \r\n this.updateColumnsMetadata();\r\n\r\n this.updateTablixColumnWidths();\r\n }\r\n\r\n /**\r\n * Destroy columnWidthObjects and construct it again from the currently displayed Columns with initial width undefined\r\n */\r\n private updateColumnsMetadata(): void {\r\n this.columnWidthObjects = {};\r\n\r\n if (this.isMatrix)\r\n this.updateMatrixColumnsMetadata();\r\n else\r\n this.updateTableColumnsMetadata();\r\n }\r\n\r\n private updateTableColumnsMetadata(): void {\r\n if (this.currentDataView && this.currentDataView.table) {\r\n let columnMetaData = this.currentDataView.table.columns;\r\n for (let i = 0, len = columnMetaData.length; i < len; i++) {\r\n let queryName = columnMetaData[i].queryName;\r\n if (queryName)\r\n this.columnWidthObjects[queryName] = {\r\n queryName: queryName,\r\n width: undefined,\r\n isFixed: false\r\n };\r\n }\r\n }\r\n }\r\n\r\n private updateMatrixColumnsMetadata(): void {\r\n // Matrix visual columns are row headers and column hierarchy leaves\r\n\r\n if (this.currentDataView && this.currentDataView.matrix && this.currentDataView.matrix.rows) {\r\n // Get query names of row groups (row headers)\r\n // queryName is undefined for composite-group\r\n for (let i = 0, len = this.currentDataView.matrix.rows.levels.length; i < len; i++) {\r\n let rowGroup = this.currentDataView.matrix.rows.levels[i];\r\n if (rowGroup.sources.length === 1) { // Only handle non-composite groups\r\n let queryName = rowGroup.sources[0].queryName;\r\n if (queryName)\r\n this.columnWidthObjects[queryName] = {\r\n queryName: queryName,\r\n width: undefined,\r\n isFixed: false\r\n };\r\n }\r\n }\r\n }\r\n\r\n // Get query names of columns leaves or values\r\n // queryName is undefined for composite-group\r\n if (this.matrixLeafNodes) {\r\n for (let i = 0, len = this.matrixLeafNodes.length; i < len; i++) {\r\n let queryName = this.matrixLeafNodes[i].queryName;\r\n if (queryName)\r\n this.columnWidthObjects[queryName] = {\r\n queryName: queryName,\r\n width: undefined,\r\n isFixed: false\r\n };\r\n }\r\n }\r\n }\r\n\r\n /**\r\n * Update the column widths after a dataViewChange\r\n */\r\n public updateTablixColumnWidths(): void {\r\n let columnMetaData = this.currentDataView && this.currentDataView.metadata && this.currentDataView.metadata.columns;\r\n if (columnMetaData) {\r\n // Auto-Size false to true.\r\n // Blow away any saved widths and revert back to default of calculating column sizes\r\n if (this.shouldClearAllColumnWidths()) {\r\n this.autoSizeAllColumns();\r\n return;\r\n }\r\n\r\n // Normal new data -> Get new column widths\r\n else {\r\n this.deserializeColumnsWidth(columnMetaData);\r\n }\r\n }\r\n }\r\n\r\n /**\r\n * Remove all persisted columns widths and Update visualObjectInstancesToPersist\r\n */\r\n private autoSizeAllColumns(): void {\r\n for (let queryName in this.columnWidthObjects) {\r\n this.visualObjectInstancesToPersist.remove.push(this.generateColumnWidthObjectToPersist(queryName, undefined));\r\n }\r\n\r\n this.callHostToPersist();\r\n }\r\n\r\n /**\r\n * Read the Column Widths from the Columns metadata\r\n * @param {DataViewMetadataColumn[]} columnMetadata Columns metadata\r\n */\r\n private deserializeColumnsWidth(columnsMetadata: DataViewMetadataColumn[]): void {\r\n // Clear existing widths\r\n for (let colObj in this.columnWidthObjects) {\r\n this.columnWidthObjects[colObj].isFixed = !this.currentAutoColumnSizePropertyValue;\r\n this.columnWidthObjects[colObj].width = undefined;\r\n }\r\n\r\n for (let i = 0, len = columnsMetadata.length; i < len; i++) {\r\n let column = columnsMetadata[i];\r\n let queryName = column.queryName;\r\n let width = DataViewObjects.getValue<number>(column.objects, TablixColumnWidthManager.columnWidthProp);\r\n\r\n if (this.columnWidthObjects.hasOwnProperty(queryName) && width != null) {\r\n this.columnWidthObjects[queryName].width = width;\r\n this.columnWidthObjects[queryName].isFixed = true;\r\n }\r\n }\r\n }\r\n // #endregion\r\n\r\n // #region AutoSize toggle\r\n /**\r\n * Returns a value indicating that autoSizeColumns was flipped from true to false\r\n */\r\n public shouldPersistAllColumnWidths(): boolean {\r\n // We don't have a previous DataView -> Don't persist\r\n if (!this.previousDataView)\r\n return false;\r\n\r\n // We had a DataView before -> return true if Autosize switched from true to false\r\n else\r\n return !this.currentAutoColumnSizePropertyValue && this.previousAutoColumnSizePropertyValue;\r\n }\r\n\r\n /**\r\n * Returns a value indicating that autoSizeColumns was flipped from false to true\r\n */\r\n public shouldClearAllColumnWidths(): boolean {\r\n return this.previousDataView != null && this.previousAutoColumnSizePropertyValue === false\r\n && this.currentDataView != null && this.currentAutoColumnSizePropertyValue === true;\r\n }\r\n // #endregion\r\n\r\n /**\r\n * Gets the QueryName associated with a Column (Column Header or Corner Item)\r\n * @param {internal.TablixColumn} column TablixColumn\r\n * @returns queryName\r\n */\r\n public static getColumnQueryName(column: internal.TablixColumn): string {\r\n let headerCell = column.getTablixCell();\r\n\r\n switch (headerCell.type) {\r\n case TablixCellType.CornerCell:\r\n if (headerCell.item == null // Corner item for Table hidden column\r\n || headerCell.item.metadata == null) // Corner item for Matrix with no row groups\r\n return undefined;\r\n\r\n return headerCell.item.metadata.queryName;\r\n\r\n case TablixCellType.ColumnHeader:\r\n debug.assert(headerCell.item != null, \"Tablix Column without a ColumnMetadata\");\r\n return headerCell.item.queryName;\r\n\r\n default:\r\n debug.assertFail(\"getColumnQueryName called with cellType: \" + headerCell.type);\r\n return undefined;\r\n }\r\n }\r\n\r\n /**\r\n * Returns the current columnWidthObjects\r\n * @returns current columnWidthObjects including undefined widths for autosized or unknown columns\r\n */\r\n public getColumnWidthObjects(): ColumnWidthCollection {\r\n return this.columnWidthObjects;\r\n }\r\n\r\n /**\r\n * Returns the current columnWidthObjects for only the fixed-size columns\r\n * @returns Returns the current columnWidthObjects excluding auto-sized columns\r\n */\r\n public getFixedColumnWidthObjects(): ColumnWidthCollection {\r\n let fixedOnly: ColumnWidthCollection = {};\r\n\r\n for (let queryName in this.columnWidthObjects) {\r\n let obj = this.columnWidthObjects[queryName];\r\n if (obj.isFixed) {\r\n fixedOnly[queryName] = obj;\r\n }\r\n }\r\n\r\n return fixedOnly;\r\n }\r\n\r\n /**\r\n * Get the persisted width of a certain column in px, or undefined if the columns is set to autosize or queryName is not found\r\n * @param {string} queryName queryName of the Column\r\n * @returns Column persisted width in pixel\r\n */\r\n public getPersistedColumnWidth(queryName: string): number {\r\n let obj = this.columnWidthObjects[queryName];\r\n return obj && obj.width;\r\n }\r\n\r\n /**\r\n * Call the host to persist the data\r\n * @param {boolean} generateInstances\r\n */\r\n private callHostToPersist() {\r\n if (this.hostPersistCallBack) {\r\n this.hostPersistCallBack(this.visualObjectInstancesToPersist);\r\n }\r\n\r\n // Clears persisted objects list\r\n this.visualObjectInstancesToPersist = {\r\n merge: [],\r\n remove: [],\r\n };\r\n }\r\n\r\n /**\r\n * Handler for a column width change by the user\r\n * @param {string} queryName queryName of the Column\r\n * @param {number} width new width\r\n */\r\n public onColumnWidthChanged(queryName: string, width: number): void {\r\n // Resizing an invalid column\r\n if (queryName == null || this.columnWidthObjects[queryName] == null)\r\n return;\r\n\r\n let resizedColumn = this.columnWidthObjects[queryName];\r\n\r\n if (width === -1) { // Column Autosize\r\n // If AutoSize option is ON, remove the persisted value\r\n // Else, set value to unknown and expect to be called again soon\r\n resizedColumn.width = undefined;\r\n resizedColumn.isFixed = !this.currentAutoColumnSizePropertyValue;\r\n\r\n // Call persist anyway, if isFixed is true, it will be assined to the rendered width\r\n this.visualObjectInstancesToPersist.remove.push(this.generateColumnWidthObjectToPersist(resizedColumn.queryName, undefined));\r\n this.callHostToPersist();\r\n }\r\n else { // Column Resize\r\n resizedColumn.width = width;\r\n resizedColumn.isFixed = true;\r\n\r\n this.visualObjectInstancesToPersist.merge.push(this.generateColumnWidthObjectToPersist(queryName, width));\r\n this.callHostToPersist();\r\n }\r\n }\r\n\r\n /**\r\n * Event handler after rendering all columns. Setting any unknown column width.\r\n * Returns True if it calls persist\r\n * @param renderedColumns Rendered Columns\r\n */\r\n public onColumnsRendered(renderedColumns: ColumnWidthObject[]): boolean {\r\n // Pick the maximum width for each queryName\r\n // This will ensure going from autoSize ON to OFF will not show any ellipsis\r\n let maxWidths: ColumnWidthCollection = {};\r\n for (let i = 0, len = renderedColumns.length; i < len; i++) {\r\n let queryName = renderedColumns[i].queryName;\r\n let newWidth = renderedColumns[i].width;\r\n\r\n if (maxWidths[queryName] == null) {\r\n maxWidths[queryName] = {\r\n queryName: queryName,\r\n width: newWidth,\r\n isFixed: false // Unused\r\n };\r\n }\r\n else if (newWidth > maxWidths[queryName].width) {\r\n maxWidths[queryName].width = newWidth;\r\n }\r\n }\r\n\r\n let widthChanged = false;\r\n for (let queryName in this.columnWidthObjects) {\r\n if (maxWidths[queryName]) { // Needs to check here. renderedColumns only have visualized ones\r\n let colWidthObj = this.columnWidthObjects[queryName];\r\n\r\n if (colWidthObj.isFixed && colWidthObj.width == null) {\r\n colWidthObj.width = maxWidths[queryName].width;\r\n this.visualObjectInstancesToPersist.merge.push(this.generateColumnWidthObjectToPersist(queryName, colWidthObj.width));\r\n widthChanged = true;\r\n }\r\n }\r\n }\r\n\r\n if (widthChanged)\r\n this.callHostToPersist();\r\n\r\n return widthChanged;\r\n }\r\n\r\n private generateColumnWidthObjectToPersist(queryName: string, width: number): VisualObjectInstance {\r\n return {\r\n selector: { metadata: queryName },\r\n objectName: 'general',\r\n properties: { columnWidth: width }\r\n };\r\n }\r\n }\r\n}\n\n\n/** WEBPACK FOOTER **\n ** ./src/Clients/Visuals/controls/tablix/tablixColumnWidthManager.ts\n **/","/*\r\n * Power BI Visualizations\r\n *\r\n * Copyright (c) Microsoft Corporation\r\n * All rights reserved.\r\n * MIT License\r\n *\r\n * Permission is hereby granted, free of charge, to any person obtaining a copy\r\n * of this software and associated documentation files (the \"\"Software\"\"), to deal\r\n * in the Software without restriction, including without limitation the rights\r\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\r\n * copies of the Software, and to permit persons to whom the Software is\r\n * furnished to do so, subject to the following conditions:\r\n *\r\n * The above copyright notice and this permission notice shall be included in\r\n * all copies or substantial portions of the Software.\r\n *\r\n * THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\r\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\r\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\r\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\r\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\r\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\r\n * THE SOFTWARE.\r\n */\r\n\r\n/// <reference path=\"../_references.ts\"/>\r\n\r\nmodule powerbi.visuals {\r\n export interface AnimatedTextConfigurationSettings {\r\n align?: string;\r\n maxFontSize?: number;\r\n }\r\n\r\n /**\r\n * Base class for values that are animated when resized.\r\n */\r\n export class AnimatedText {\r\n\r\n /** Note: Public for testability */\r\n public static formatStringProp: DataViewObjectPropertyIdentifier = {\r\n objectName: 'general',\r\n propertyName: 'formatString',\r\n };\r\n\r\n protected animator: IGenericAnimator;\r\n\r\n private name: string;\r\n\r\n /** Note: Public for testability */\r\n public svg: D3.Selection;\r\n public currentViewport: IViewport;\r\n public value: any;\r\n public hostServices: IVisualHostServices;\r\n public style: IVisualStyle;\r\n public visualConfiguration: AnimatedTextConfigurationSettings;\r\n public metaDataColumn: DataViewMetadataColumn;\r\n\r\n private mainText: jsCommon.CssConstants.ClassAndSelector = jsCommon.CssConstants.createClassAndSelector('mainText');\r\n\r\n public constructor(name: string) {\r\n this.name = name;\r\n this.visualConfiguration = { maxFontSize: 60 };\r\n }\r\n\r\n public getMetaDataColumn(dataView: DataView) {\r\n if (dataView && dataView.metadata && dataView.metadata.columns) {\r\n for (let i = 0, ilen = dataView.metadata.columns.length; i < ilen; i++) {\r\n let column = dataView.metadata.columns[i];\r\n if (column.isMeasure) {\r\n this.metaDataColumn = column;\r\n break;\r\n }\r\n }\r\n }\r\n }\r\n\r\n public getAdjustedFontHeight(\r\n availableWidth: number,\r\n textToMeasure: string,\r\n seedFontHeight: number): number {\r\n \r\n let textProperties: TextProperties= {\r\n fontFamily: null,\r\n fontSize: null,\r\n text: textToMeasure\r\n };\r\n \r\n let fontHeight = this.getAdjustedFontHeightCore(\r\n textProperties,\r\n availableWidth,\r\n seedFontHeight,\r\n 0);\r\n\r\n return fontHeight;\r\n }\r\n\r\n private getAdjustedFontHeightCore(\r\n textProperties: TextProperties,\r\n availableWidth: number,\r\n seedFontHeight: number,\r\n iteration: number): number {\r\n \r\n // Too many attempts - just return what we have so we don't sacrifice perf\r\n if (iteration > 10) {\r\n return seedFontHeight;\r\n }\r\n\r\n textProperties.fontSize = jsCommon.PixelConverter.toString(seedFontHeight);\r\n\r\n let candidateLength = TextMeasurementService.measureSvgTextWidth(textProperties);\r\n if (candidateLength < availableWidth)\r\n return seedFontHeight;\r\n\r\n return this.getAdjustedFontHeightCore(textProperties, availableWidth, seedFontHeight * 0.9, iteration + 1);\r\n }\r\n\r\n public clear() {\r\n this.svg.select(this.mainText.selector).text('');\r\n }\r\n\r\n public doValueTransition(\r\n startValue: any,\r\n endValue: any,\r\n displayUnitSystemType: DisplayUnitSystemType,\r\n animationOptions: AnimationOptions,\r\n duration: number,\r\n forceUpdate: boolean,\r\n formatter?: IValueFormatter): void {\r\n if (!forceUpdate && startValue === endValue && endValue != null)\r\n return;\r\n\r\n if (!startValue)\r\n startValue = 0;\r\n\r\n let svg = this.svg,\r\n viewport = this.currentViewport,\r\n height = viewport.height,\r\n width = viewport.width,\r\n endValueArr = [endValue],\r\n seedFontHeight = this.getSeedFontHeight(width, height),\r\n translateX = this.getTranslateX(width),\r\n translateY = this.getTranslateY(seedFontHeight),\r\n metaDataColumn = this.metaDataColumn;\r\n\r\n // Respect the formatter default value\r\n if (!formatter) {\r\n formatter = valueFormatter.create({\r\n format: this.getFormatString(metaDataColumn),\r\n value: endValue,\r\n displayUnitSystemType: displayUnitSystemType,\r\n formatSingleValues: true,\r\n allowFormatBeautification: true,\r\n columnType: metaDataColumn ? metaDataColumn.type : undefined\r\n });\r\n }\r\n let startText = formatter.format(startValue),\r\n endText = formatter.format(endValue);\r\n\r\n svg.attr('class', this.name);\r\n\r\n let textElement = svg\r\n .selectAll('text')\r\n .data(endValueArr);\r\n\r\n textElement\r\n .enter()\r\n .append('text')\r\n .attr('class', this.mainText.class);\r\n\r\n let fontHeight = this.getAdjustedFontHeight(width, endText, seedFontHeight);\r\n translateY = this.getTranslateY(fontHeight + (height - fontHeight) / 2);\r\n\r\n let textElementUpdate = textElement\r\n .text(startText)\r\n .attr({\r\n 'text-anchor': this.getTextAnchor(),\r\n 'font-size': fontHeight,\r\n 'transform': SVGUtil.translate(translateX, translateY),\r\n })\r\n .style({\r\n 'fill': this.style.titleText.color.value,\r\n })\r\n .call(tooltipUtils.tooltipUpdate, [startText]);\r\n\r\n if (endValue == null) {\r\n textElementUpdate.text(endText).call(tooltipUtils.tooltipUpdate, [endText]);\r\n }\r\n else if (metaDataColumn && AxisHelper.isDateTime(metaDataColumn.type)) {\r\n textElementUpdate.text(endText).call(tooltipUtils.tooltipUpdate, [endText]);\r\n }\r\n else {\r\n let interpolatedValue = startValue;\r\n textElementUpdate\r\n .transition()\r\n .duration(duration)\r\n .tween('text', function (d) {\r\n let i = d3.interpolate(interpolatedValue, d);\r\n return function (t) {\r\n let num = i(t);\r\n this.textContent = formatter.format(num);\r\n };\r\n });\r\n }\r\n\r\n SVGUtil.flushAllD3TransitionsIfNeeded(animationOptions);\r\n }\r\n\r\n public setTextColor(color: string): void {\r\n this.style.titleText.color.value = color;\r\n }\r\n\r\n public getSeedFontHeight(boundingWidth: number, boundingHeight: number) {\r\n // Simply an estimate - it should eventually be modified based on the actual text length\r\n let estimatedSize = Math.floor(Math.min(boundingWidth, boundingHeight) * 0.75);\r\n let maxFontSize = this.visualConfiguration.maxFontSize;\r\n\r\n if (maxFontSize)\r\n return Math.min(maxFontSize, estimatedSize);\r\n\r\n return estimatedSize;\r\n }\r\n\r\n public getTranslateX(width: number): number {\r\n if (this.visualConfiguration) {\r\n switch (this.visualConfiguration.align) {\r\n case 'left':\r\n return 0;\r\n case 'right':\r\n return width;\r\n }\r\n }\r\n return width / 2;\r\n }\r\n\r\n public getTranslateY(height: number): number {\r\n return height;\r\n }\r\n\r\n public getTextAnchor(): string {\r\n if (this.visualConfiguration) {\r\n switch (this.visualConfiguration.align) {\r\n case 'left':\r\n return 'start';\r\n case 'right':\r\n return 'end';\r\n }\r\n }\r\n return 'middle';\r\n }\r\n\r\n protected getFormatString(column: DataViewMetadataColumn): string {\r\n debug.assertAnyValue(column, 'column');\r\n\r\n return valueFormatter.getFormatString(column, AnimatedText.formatStringProp);\r\n }\r\n }\r\n}\n\n\n/** WEBPACK FOOTER **\n ** ./src/Clients/Visuals/visuals/animatedText.ts\n **/","/*\r\n * Power BI Visualizations\r\n *\r\n * Copyright (c) Microsoft Corporation\r\n * All rights reserved. \r\n * MIT License\r\n *\r\n * Permission is hereby granted, free of charge, to any person obtaining a copy\r\n * of this software and associated documentation files (the \"\"Software\"\"), to deal\r\n * in the Software without restriction, including without limitation the rights\r\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\r\n * copies of the Software, and to permit persons to whom the Software is\r\n * furnished to do so, subject to the following conditions:\r\n * \r\n * The above copyright notice and this permission notice shall be included in \r\n * all copies or substantial portions of the Software.\r\n * \r\n * THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR \r\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, \r\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE \r\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER \r\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\r\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\r\n * THE SOFTWARE.\r\n */\r\n\r\n/// <reference path=\"../_references.ts\"/>\r\n\r\nmodule powerbi.visuals {\r\n /**\r\n * Renders a number that can be animate change in value.\r\n */\r\n export class AnimatedNumber extends AnimatedText implements IVisual {\r\n private options: VisualInitOptions;\r\n\r\n // TODO: Remove this once all visuals have implemented update.\r\n private dataViews: DataView[];\r\n private formatter: IValueFormatter;\r\n\r\n public constructor(svg?: D3.Selection, animator?: IGenericAnimator) {\r\n super('animatedNumber');\r\n\r\n if (svg)\r\n this.svg = svg;\r\n if (animator)\r\n this.animator = animator;\r\n }\r\n\r\n public init(options: VisualInitOptions) {\r\n this.options = options;\r\n let element = options.element;\r\n\r\n if (!this.svg)\r\n this.svg = d3.select(element.get(0)).append('svg');\r\n\r\n this.currentViewport = options.viewport;\r\n this.hostServices = options.host;\r\n this.style = options.style;\r\n this.updateViewportDependantProperties();\r\n }\r\n\r\n public updateViewportDependantProperties() {\r\n let viewport = this.currentViewport;\r\n this.svg.attr('width', viewport.width)\r\n .attr('height', viewport.height);\r\n }\r\n\r\n public update(options: VisualUpdateOptions) {\r\n debug.assertValue(options, 'options');\r\n\r\n this.currentViewport = options.viewport;\r\n let dataViews = this.dataViews = options.dataViews;\r\n\r\n if (!dataViews || !dataViews[0]) {\r\n return;\r\n }\r\n\r\n let dataView = dataViews[0];\r\n this.updateViewportDependantProperties();\r\n this.getMetaDataColumn(dataView);\r\n let newValue = dataView && dataView.single ? dataView.single.value : 0;\r\n this.updateInternal(newValue, options.suppressAnimations, true, this.formatter);\r\n }\r\n\r\n public setFormatter(formatter?: IValueFormatter): void {\r\n this.formatter = formatter;\r\n }\r\n\r\n public onDataChanged(options: VisualDataChangedOptions): void {\r\n // TODO: Remove onDataChanged & onResizing once all visuals have implemented update.\r\n this.update({\r\n dataViews: options.dataViews,\r\n suppressAnimations: options.suppressAnimations,\r\n viewport: this.currentViewport\r\n });\r\n }\r\n\r\n public onResizing(viewport: IViewport): void {\r\n // TODO: Remove onDataChanged & onResizing once all visuals have implemented update.\r\n this.update({\r\n dataViews: this.dataViews,\r\n suppressAnimations: true,\r\n viewport: viewport\r\n });\r\n }\r\n\r\n public canResizeTo(viewport: IViewport): boolean {\r\n // Temporarily disabling resize restriction.\r\n return true;\r\n }\r\n\r\n private updateInternal(target: number, suppressAnimations: boolean, forceUpdate: boolean = false, formatter?: IValueFormatter) {\r\n let start = this.value;\r\n let duration = AnimatorCommon.GetAnimationDuration(this.animator, suppressAnimations);\r\n\r\n this.doValueTransition(\r\n start,\r\n target,\r\n /*displayUnitSystemType*/ null,\r\n this.options.animation,\r\n duration,\r\n forceUpdate,\r\n formatter);\r\n\r\n this.value = target;\r\n }\r\n }\r\n}\n\n\n/** WEBPACK FOOTER **\n ** ./src/Clients/Visuals/visuals/animatedNumber.ts\n **/","/*\r\n * Power BI Visualizations\r\n *\r\n * Copyright (c) Microsoft Corporation\r\n * All rights reserved. \r\n * MIT License\r\n *\r\n * Permission is hereby granted, free of charge, to any person obtaining a copy\r\n * of this software and associated documentation files (the \"\"Software\"\"), to deal\r\n * in the Software without restriction, including without limitation the rights\r\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\r\n * copies of the Software, and to permit persons to whom the Software is\r\n * furnished to do so, subject to the following conditions:\r\n * \r\n * The above copyright notice and this permission notice shall be included in \r\n * all copies or substantial portions of the Software.\r\n * \r\n * THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR \r\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, \r\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE \r\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER \r\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\r\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\r\n * THE SOFTWARE.\r\n */\r\n\r\n/// <reference path=\"../_references.ts\"/>\r\n\r\nmodule powerbi.visuals {\r\n export interface BasicShapeDataViewObjects extends DataViewObjects {\r\n general: BasicShapeDataViewObject;\r\n line: LineObject;\r\n fill: FillObject;\r\n rotation: RotationObject;\r\n }\r\n\r\n export interface LineObject extends DataViewObject {\r\n lineColor: Fill;\r\n roundEdge: number;\r\n weight: number;\r\n transparency: number;\r\n }\r\n\r\n export interface FillObject extends DataViewObject {\r\n transparency: number;\r\n fillColor: Fill;\r\n show: boolean;\r\n }\r\n\r\n export interface RotationObject extends DataViewObject {\r\n angle: number;\r\n }\r\n\r\n export interface BasicShapeDataViewObject extends DataViewObject {\r\n shapeType: string;\r\n shapeSvg: string;\r\n }\r\n\r\n export interface BasicShapeData {\r\n shapeType: string;\r\n lineColor: string;\r\n lineTransparency: number;\r\n lineWeight: number;\r\n showFill: boolean;\r\n fillColor: string;\r\n shapeTransparency: number;\r\n roundEdge: number;\r\n angle: number;\r\n }\r\n\r\n export class BasicShapeVisual implements IVisual {\r\n private currentViewport: IViewport;\r\n private element: JQuery;\r\n private data: BasicShapeData;\r\n private selection: D3.Selection;\r\n\r\n public static DefaultShape: string = basicShapeType.rectangle;\r\n public static DefaultStrokeColor: string = '#00B8AA';\r\n public static DefaultFillColor: string = '#E6E6E6';\r\n public static DefaultFillShowValue: boolean = true; \r\n public static DefaultFillTransValue: number = 0;\r\n public static DefaultWeightValue: number = 3;\r\n public static DefaultLineTransValue: number = 0;\r\n public static DefaultRoundEdgeValue: number = 0;\r\n public static DefaultAngle: number = 0;\r\n\r\n /**property for the shape line color */\r\n get shapeType(): string {\r\n return this.data.shapeType;\r\n }\r\n set shapeType(shapeType: string) {\r\n this.data.shapeType = shapeType;\r\n }\r\n\r\n /**property for the shape line color */\r\n get lineColor(): string {\r\n return this.data ? this.data.lineColor : BasicShapeVisual.DefaultStrokeColor;\r\n }\r\n set lineColor(color: string) {\r\n this.data.lineColor = color;\r\n }\r\n /**property for the shape line transparency */\r\n get lineTransparency(): number {\r\n return this.data ? this.data.lineTransparency : BasicShapeVisual.DefaultLineTransValue;\r\n }\r\n set lineTransparency(trans: number) {\r\n this.data.lineTransparency = trans;\r\n }\r\n /**property for the shape line weight */\r\n get lineWeight(): number {\r\n return this.data ? this.data.lineWeight : BasicShapeVisual.DefaultWeightValue;\r\n }\r\n set lineWeight(weight: number) {\r\n this.data.lineWeight = weight;\r\n }\r\n /**property for the shape round edge */\r\n get roundEdge(): number {\r\n return this.data ? this.data.roundEdge : BasicShapeVisual.DefaultRoundEdgeValue;\r\n }\r\n set roundEdge(roundEdge: number) {\r\n this.data.roundEdge = roundEdge;\r\n }\r\n /**property for showing the fill properties */\r\n get showFill(): boolean {\r\n return this.data ? this.data.showFill : BasicShapeVisual.DefaultFillShowValue;\r\n }\r\n set showFill(show: boolean) {\r\n this.data.showFill = show;\r\n }\r\n /**property for the shape line color */\r\n get fillColor(): string {\r\n return this.data ? this.data.fillColor : BasicShapeVisual.DefaultFillColor;\r\n }\r\n set fillColor(color: string) {\r\n this.data.fillColor = color;\r\n }\r\n /**property for the shape fill transparency */\r\n get shapeTransparency(): number {\r\n return this.data ? this.data.shapeTransparency : BasicShapeVisual.DefaultFillTransValue;\r\n }\r\n set shapeTransparency(trans: number) {\r\n this.data.shapeTransparency = trans;\r\n }\r\n /**property for the shape angle */\r\n get angle(): number {\r\n return this.data ? this.data.angle : BasicShapeVisual.DefaultAngle;\r\n }\r\n set angle(angle: number) {\r\n this.data.angle = this.scaleTo360Deg(angle);\r\n }\r\n\r\n public init(options: VisualInitOptions) {\r\n this.element = options.element;\r\n this.selection = d3.select(this.element.context);\r\n this.currentViewport = options.viewport;\r\n }\r\n\r\n public constructor(options?: VisualInitOptions) {\r\n }\r\n\r\n public update(options: VisualUpdateOptions): void {\r\n debug.assertValue(options, 'options');\r\n\r\n this.currentViewport = options.viewport;\r\n\r\n let dataViews = options.dataViews;\r\n if (!_.isEmpty(dataViews)) {\r\n let dataView = options.dataViews[0];\r\n if (dataView.metadata && dataView.metadata.objects) {\r\n let dataViewObject = <BasicShapeDataViewObjects>options.dataViews[0].metadata.objects;\r\n this.data = this.getDataFromDataView(dataViewObject);\r\n this.render();\r\n }\r\n }\r\n }\r\n\r\n private getDataFromDataView(dataViewObject: BasicShapeDataViewObjects): BasicShapeData {\r\n if (dataViewObject) {\r\n return {\r\n shapeType: DataViewObjects.getValue(dataViewObject, basicShapeProps.general.shapeType, BasicShapeVisual.DefaultShape),\r\n lineColor: this.getValueFromColor(DataViewObjects.getValue(dataViewObject, basicShapeProps.line.lineColor, BasicShapeVisual.DefaultStrokeColor)),\r\n lineTransparency: DataViewObjects.getValue(dataViewObject, basicShapeProps.line.transparency, BasicShapeVisual.DefaultLineTransValue),\r\n lineWeight: DataViewObjects.getValue(dataViewObject, basicShapeProps.line.weight, BasicShapeVisual.DefaultWeightValue),\r\n roundEdge: DataViewObjects.getValue(dataViewObject, basicShapeProps.line.roundEdge, BasicShapeVisual.DefaultRoundEdgeValue),\r\n shapeTransparency: DataViewObjects.getValue(dataViewObject, basicShapeProps.fill.transparency, BasicShapeVisual.DefaultFillTransValue),\r\n fillColor: this.getValueFromColor(DataViewObjects.getValue(dataViewObject, basicShapeProps.fill.fillColor, BasicShapeVisual.DefaultFillColor)),\r\n showFill: DataViewObjects.getValue(dataViewObject, basicShapeProps.fill.show, BasicShapeVisual.DefaultFillShowValue),\r\n angle: this.scaleTo360Deg(DataViewObjects.getValue(dataViewObject, basicShapeProps.rotation.angle, BasicShapeVisual.DefaultAngle))\r\n };\r\n }\r\n\r\n return null;\r\n }\r\n\r\n private scaleTo360Deg(angle: number): number {\r\n if (angle !== 0 && (Math.abs(angle) % 360) === 0) return angle;\r\n\r\n angle = angle % 360;\r\n angle = (angle + 360) % 360;\r\n\r\n return angle;\r\n }\r\n\r\n private getValueFromColor(color: any): string {\r\n return color.solid ? color.solid.color : color;\r\n }\r\n\r\n public enumerateObjectInstances(options: EnumerateVisualObjectInstancesOptions): VisualObjectInstance[] {\r\n let objectInstances: VisualObjectInstance[] = [];\r\n if (!this.data) {\r\n return objectInstances;\r\n }\r\n\r\n switch (options.objectName) {\r\n case 'line':\r\n let instance: VisualObjectInstance = {\r\n selector: null,\r\n properties: {\r\n lineColor: this.lineColor,\r\n transparency: this.lineTransparency,\r\n weight: this.lineWeight\r\n },\r\n objectName: options.objectName\r\n };\r\n if (this.shapeType === basicShapeType.rectangle) {\r\n instance.properties['roundEdge'] = this.roundEdge;\r\n }\r\n\r\n objectInstances.push(instance);\r\n return objectInstances;\r\n case 'fill':\r\n if (this.shapeType !== basicShapeType.line) {\r\n objectInstances.push({\r\n selector: null,\r\n properties: {\r\n show: this.showFill,\r\n fillColor: this.fillColor,\r\n transparency: this.shapeTransparency\r\n },\r\n objectName: options.objectName\r\n });\r\n }\r\n return objectInstances;\r\n case 'rotation':\r\n objectInstances.push({\r\n selector: null,\r\n properties: {\r\n angle: this.angle\r\n },\r\n objectName: options.objectName\r\n });\r\n\r\n return objectInstances;\r\n }\r\n\r\n return null;\r\n\r\n }\r\n\r\n public render(): void {\r\n this.selection.html('');\r\n \r\n switch (this.shapeType) {\r\n case basicShapeType.rectangle:\r\n ShapeFactory.createRectangle(\r\n this.data, this.currentViewport.height, this.currentViewport.width, this.selection, this.angle);\r\n break;\r\n case basicShapeType.oval:\r\n ShapeFactory.createOval(\r\n this.data, this.currentViewport.height, this.currentViewport.width, this.selection, this.angle);\r\n break;\r\n case basicShapeType.line:\r\n ShapeFactory.createLine(\r\n this.data, this.currentViewport.height, this.currentViewport.width, this.selection, this.angle);\r\n break;\r\n case basicShapeType.arrow:\r\n ShapeFactory.createUpArrow(\r\n this.data, this.currentViewport.height, this.currentViewport.width, this.selection, this.angle);\r\n break;\r\n case basicShapeType.triangle:\r\n ShapeFactory.createTriangle(\r\n this.data, this.currentViewport.height, this.currentViewport.width, this.selection, this.angle);\r\n break;\r\n default:\r\n break;\r\n }\r\n }\r\n }\r\n}\n\n\n/** WEBPACK FOOTER **\n ** ./src/Clients/Visuals/visuals/basicShape.ts\n **/","/*\r\n * Power BI Visualizations\r\n *\r\n * Copyright (c) Microsoft Corporation\r\n * All rights reserved. \r\n * MIT License\r\n *\r\n * Permission is hereby granted, free of charge, to any person obtaining a copy\r\n * of this software and associated documentation files (the \"\"Software\"\"), to deal\r\n * in the Software without restriction, including without limitation the rights\r\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\r\n * copies of the Software, and to permit persons to whom the Software is\r\n * furnished to do so, subject to the following conditions:\r\n * \r\n * The above copyright notice and this permission notice shall be included in \r\n * all copies or substantial portions of the Software.\r\n * \r\n * THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR \r\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, \r\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE \r\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER \r\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\r\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\r\n * THE SOFTWARE.\r\n */\r\n\r\n/// <reference path=\"../_references.ts\"/>\r\n\r\nmodule powerbi.visuals {\r\n import EnumExtensions = jsCommon.EnumExtensions;\r\n import ClassAndSelector = jsCommon.CssConstants.ClassAndSelector;\r\n import createClassAndSelector = jsCommon.CssConstants.createClassAndSelector;\r\n\r\n const DEFAULT_AXIS_SCALE_TYPE: string = axisScale.linear;\r\n const COMBOCHART_DOMAIN_OVERLAP_TRESHOLD_PERCENTAGE = 0.1;\r\n // the interactive right margin is set to be the circle selection radius of the hover line\r\n const INTERACTIVITY_RIGHT_MARGIN = 6;\r\n export const DEFAULT_AXIS_COLOR = '#777';\r\n\r\n export const enum CartesianChartType {\r\n Line,\r\n Area,\r\n StackedArea,\r\n ClusteredColumn,\r\n StackedColumn,\r\n ClusteredBar,\r\n StackedBar,\r\n HundredPercentStackedBar,\r\n HundredPercentStackedColumn,\r\n Scatter,\r\n ComboChart,\r\n DataDot,\r\n Waterfall,\r\n LineClusteredColumnCombo,\r\n LineStackedColumnCombo,\r\n DataDotClusteredColumnCombo,\r\n DataDotStackedColumnCombo,\r\n }\r\n\r\n export interface CalculateScaleAndDomainOptions {\r\n viewport: IViewport;\r\n margin: IMargin;\r\n showCategoryAxisLabel: boolean;\r\n showValueAxisLabel: boolean;\r\n forceMerge: boolean;\r\n categoryAxisScaleType: string;\r\n valueAxisScaleType: string;\r\n trimOrdinalDataOnOverflow: boolean;\r\n // optional\r\n playAxisControlLayout?: IRect;\r\n forcedTickCount?: number;\r\n forcedYDomain?: any[];\r\n forcedXDomain?: any[];\r\n ensureXDomain?: NumberRange;\r\n ensureYDomain?: NumberRange;\r\n categoryAxisDisplayUnits?: number;\r\n categoryAxisPrecision?: number;\r\n valueAxisDisplayUnits?: number;\r\n valueAxisPrecision?: number;\r\n }\r\n\r\n export interface MergedValueAxisResult {\r\n domain: number[];\r\n merged: boolean;\r\n tickCount: number;\r\n }\r\n\r\n export interface CartesianSmallViewPortProperties {\r\n hideLegendOnSmallViewPort: boolean;\r\n hideAxesOnSmallViewPort: boolean;\r\n MinHeightLegendVisible: number;\r\n MinHeightAxesVisible: number;\r\n }\r\n\r\n export interface AxisRenderingOptions {\r\n axisLabels: ChartAxesLabels;\r\n viewport: IViewport;\r\n margin: IMargin;\r\n hideXAxisTitle: boolean;\r\n hideYAxisTitle: boolean;\r\n hideY2AxisTitle?: boolean;\r\n xLabelColor?: Fill;\r\n yLabelColor?: Fill;\r\n y2LabelColor?: Fill;\r\n fontSize: number;\r\n }\r\n\r\n export interface CartesianConstructorOptions {\r\n chartType: CartesianChartType;\r\n isScrollable?: boolean;\r\n animator?: IGenericAnimator;\r\n cartesianSmallViewPortProperties?: CartesianSmallViewPortProperties;\r\n behavior?: IInteractiveBehavior;\r\n isLabelInteractivityEnabled?: boolean;\r\n tooltipsEnabled?: boolean;\r\n tooltipBucketEnabled?: boolean;\r\n cartesianLoadMoreEnabled?: boolean;\r\n trimOrdinalDataOnOverflow?: boolean;\r\n advancedLineLabelsEnabled?: boolean;\r\n }\r\n\r\n export interface ICartesianVisual {\r\n init(options: CartesianVisualInitOptions): void;\r\n setData(dataViews: DataView[]): void;\r\n calculateAxesProperties(options: CalculateScaleAndDomainOptions): IAxisProperties[];\r\n overrideXScale(xProperties: IAxisProperties): void;\r\n render(suppressAnimations: boolean, resizeMode?: ResizeMode): CartesianVisualRenderResult;\r\n calculateLegend(): LegendData;\r\n hasLegend(): boolean;\r\n onClearSelection(): void;\r\n enumerateObjectInstances?(enumeration: ObjectEnumerationBuilder, options: EnumerateVisualObjectInstancesOptions): void;\r\n getVisualCategoryAxisIsScalar?(): boolean;\r\n getSupportedCategoryAxisType?(): string;\r\n getPreferredPlotArea?(isScalar: boolean, categoryCount: number, categoryThickness: number): IViewport;\r\n setFilteredData?(startIndex: number, endIndex: number): CartesianData;\r\n supportsTrendLine?(): boolean;\r\n shouldSuppressAnimation?(): boolean;\r\n }\r\n\r\n export interface CartesianVisualConstructorOptions {\r\n isScrollable: boolean;\r\n interactivityService?: IInteractivityService;\r\n animator?: IGenericAnimator;\r\n isLabelInteractivityEnabled?: boolean;\r\n tooltipsEnabled?: boolean;\r\n tooltipBucketEnabled?: boolean;\r\n cartesianLoadMoreEnabled?: boolean;\r\n advancedLineLabelsEnabled?: boolean;\r\n }\r\n\r\n export interface CartesianVisualRenderResult {\r\n dataPoints: SelectableDataPoint[];\r\n behaviorOptions: any;\r\n labelDataPoints: LabelDataPoint[];\r\n labelsAreNumeric: boolean;\r\n labelDataPointGroups?: LabelDataPointGroup[];\r\n }\r\n\r\n export interface CartesianDataPoint {\r\n categoryValue: any;\r\n value: number;\r\n categoryIndex: number;\r\n seriesIndex: number;\r\n highlight?: boolean;\r\n }\r\n\r\n export interface CartesianSeries {\r\n data: CartesianDataPoint[];\r\n }\r\n\r\n export interface CartesianData {\r\n series: CartesianSeries[];\r\n categoryMetadata: DataViewMetadataColumn;\r\n categories: any[];\r\n hasHighlights?: boolean;\r\n }\r\n\r\n export interface CartesianVisualInitOptions extends VisualInitOptions {\r\n svg: D3.Selection;\r\n cartesianHost: ICartesianVisualHost;\r\n chartType?: CartesianChartType;\r\n labelsContext?: D3.Selection; //TEMPORARY - for PlayAxis\r\n }\r\n\r\n export interface ICartesianVisualHost {\r\n updateLegend(data: LegendData): void;\r\n getSharedColors(): IDataColorPalette;\r\n triggerRender(suppressAnimations: boolean): void;\r\n }\r\n\r\n export interface ChartAxesLabels {\r\n x: string;\r\n y: string;\r\n y2?: string;\r\n }\r\n\r\n export const enum AxisLinesVisibility {\r\n ShowLinesOnXAxis = 1,\r\n ShowLinesOnYAxis = 2,\r\n ShowLinesOnBothAxis = ShowLinesOnXAxis | ShowLinesOnYAxis,\r\n }\r\n\r\n export interface CategoryLayout {\r\n categoryCount: number;\r\n categoryThickness: number;\r\n outerPaddingRatio: number;\r\n isScalar?: boolean;\r\n }\r\n\r\n export interface CategoryLayoutOptions {\r\n availableWidth: number;\r\n categoryCount: number;\r\n domain: any;\r\n trimOrdinalDataOnOverflow: boolean;\r\n isScalar?: boolean;\r\n isScrollable?: boolean;\r\n }\r\n\r\n export interface CartesianAxisProperties {\r\n x: IAxisProperties;\r\n y1: IAxisProperties;\r\n y2?: IAxisProperties;\r\n }\r\n\r\n export interface ReferenceLineOptions {\r\n graphicContext: D3.Selection;\r\n referenceLineProperties: DataViewObject;\r\n axes: CartesianAxisProperties;\r\n viewport: IViewport;\r\n classAndSelector: ClassAndSelector;\r\n defaultColor: string;\r\n isHorizontal: boolean;\r\n }\r\n\r\n export interface ReferenceLineDataLabelOptions {\r\n referenceLineProperties: DataViewObject;\r\n axes: CartesianAxisProperties;\r\n viewport: IViewport;\r\n defaultColor: string;\r\n isHorizontal: boolean;\r\n key: string;\r\n }\r\n \r\n export interface ViewportDataRange {\r\n startIndex: number;\r\n endIndex: number;\r\n }\r\n\r\n export interface ScalarKeys {\r\n values: PrimitiveValueRange[];\r\n }\r\n\r\n type RenderPlotAreaDelegate = (\r\n layers: ICartesianVisual[],\r\n axesLayout: CartesianAxesLayout,\r\n suppressAnimations: boolean) => void;\r\n\r\n /** \r\n * Renders a data series as a cartestian visual.\r\n */\r\n export class CartesianChart implements IVisual {\r\n public static MinOrdinalRectThickness = 20;\r\n public static MinScalarRectThickness = 2;\r\n public static OuterPaddingRatio = 0.4;\r\n public static InnerPaddingRatio = 0.2;\r\n public static TickLabelPadding = 2; // between text labels, used by AxisHelper\r\n public static LoadMoreThreshold = 1; // Load more data 1 item before the last (so 2nd to last) item is shown\r\n\r\n private static ClassName = 'cartesianChart';\r\n private static PlayAxisBottomMargin = 80; //do not change unless we add dynamic label measurements for play slider\r\n private static FontSize = 11;\r\n private static FontSizeString = jsCommon.PixelConverter.toString(CartesianChart.FontSize);\r\n\r\n public static AxisTextProperties: TextProperties = {\r\n fontFamily: Font.Family.regular.css,\r\n fontSize: CartesianChart.FontSizeString,\r\n };\r\n\r\n private element: JQuery;\r\n private chartAreaSvg: D3.Selection;\r\n private clearCatcher: D3.Selection;\r\n private type: CartesianChartType;\r\n private hostServices: IVisualHostServices;\r\n private layers: ICartesianVisual[];\r\n private legend: ILegend;\r\n private legendMargins: IViewport;\r\n private layerLegendData: LegendData;\r\n private hasSetData: boolean;\r\n private visualInitOptions: VisualInitOptions;\r\n private legendObjectProperties: DataViewObject;\r\n private categoryAxisProperties: DataViewObject;\r\n private valueAxisProperties: DataViewObject;\r\n private xAxisReferenceLines: DataViewObjectMap;\r\n private y1AxisReferenceLines: DataViewObjectMap;\r\n private cartesianSmallViewPortProperties: CartesianSmallViewPortProperties;\r\n private interactivityService: IInteractivityService;\r\n private behavior: IInteractiveBehavior;\r\n private sharedColorPalette: SharedColorPalette;\r\n private isLabelInteractivityEnabled: boolean;\r\n private tooltipsEnabled: boolean;\r\n private tooltipBucketEnabled: boolean;\r\n private cartesianLoadMoreEnabled: boolean;\r\n private trimOrdinalDataOnOverflow: boolean;\r\n private isMobileChart: boolean;\r\n private advancedLineLabelsEnabled: boolean;\r\n\r\n private trendLines: TrendLine[];\r\n\r\n private xRefLine: ClassAndSelector = createClassAndSelector('x-ref-line');\r\n private y1RefLine: ClassAndSelector = createClassAndSelector('y1-ref-line');\r\n\r\n public animator: IGenericAnimator;\r\n\r\n private axes: CartesianAxes;\r\n private scrollableAxes: ScrollableAxes;\r\n private svgAxes: SvgCartesianAxes;\r\n private svgBrush: SvgBrush;\r\n private renderedPlotArea: IViewport; // to help disable animation when property changes result in layout changes (e.g. 'legend off' should not animate)\r\n\r\n // TODO: Remove onDataChanged & onResizing once all visuals have implemented update.\r\n private dataViews: DataView[];\r\n private currentViewport: IViewport;\r\n private background: VisualBackground;\r\n \r\n private loadMoreDataHandler: CartesianLoadMoreDataHandler;\r\n\r\n private static getAxisVisibility(type: CartesianChartType): AxisLinesVisibility {\r\n switch (type) {\r\n case CartesianChartType.StackedBar:\r\n case CartesianChartType.ClusteredBar:\r\n case CartesianChartType.HundredPercentStackedBar:\r\n return AxisLinesVisibility.ShowLinesOnXAxis;\r\n case CartesianChartType.Scatter:\r\n return AxisLinesVisibility.ShowLinesOnBothAxis;\r\n default:\r\n return AxisLinesVisibility.ShowLinesOnYAxis;\r\n }\r\n }\r\n\r\n constructor(options: CartesianConstructorOptions) {\r\n let isScrollable = false;\r\n this.trimOrdinalDataOnOverflow = true;\r\n if (options) {\r\n this.tooltipsEnabled = options.tooltipsEnabled;\r\n this.tooltipBucketEnabled = options.tooltipBucketEnabled;\r\n this.cartesianLoadMoreEnabled = options.cartesianLoadMoreEnabled;\r\n this.type = options.chartType;\r\n this.isLabelInteractivityEnabled = options.isLabelInteractivityEnabled;\r\n this.advancedLineLabelsEnabled = options.advancedLineLabelsEnabled;\r\n if (options.trimOrdinalDataOnOverflow !== undefined)\r\n this.trimOrdinalDataOnOverflow = options.trimOrdinalDataOnOverflow;\r\n if (options.isScrollable)\r\n isScrollable = options.isScrollable;\r\n this.animator = options.animator;\r\n if (options.cartesianSmallViewPortProperties) {\r\n this.cartesianSmallViewPortProperties = options.cartesianSmallViewPortProperties;\r\n }\r\n\r\n if (options.behavior) {\r\n this.behavior = options.behavior;\r\n }\r\n }\r\n\r\n this.axes = new CartesianAxes(isScrollable, ScrollableAxes.ScrollbarWidth, this.trimOrdinalDataOnOverflow);\r\n this.svgAxes = new SvgCartesianAxes(this.axes);\r\n this.svgBrush = new SvgBrush(ScrollableAxes.ScrollbarWidth);\r\n this.scrollableAxes = new ScrollableAxes(this.axes, this.svgBrush);\r\n }\r\n\r\n public init(options: VisualInitOptions) {\r\n this.visualInitOptions = options;\r\n this.layers = [];\r\n\r\n let element = this.element = options.element;\r\n\r\n this.currentViewport = options.viewport;\r\n this.hostServices = options.host;\r\n\r\n let chartAreaSvg = this.chartAreaSvg = d3.select(element.get(0)).append('svg');\r\n chartAreaSvg.classed(CartesianChart.ClassName, true);\r\n chartAreaSvg.style('position', 'absolute');\r\n\r\n if (this.behavior) {\r\n this.clearCatcher = appendClearCatcher(chartAreaSvg);\r\n this.interactivityService = createInteractivityService(this.hostServices);\r\n }\r\n\r\n if (options.style.maxMarginFactor != null)\r\n this.axes.setMaxMarginFactor(options.style.maxMarginFactor);\r\n\r\n let axisLinesVisibility = CartesianChart.getAxisVisibility(this.type);\r\n this.axes.setAxisLinesVisibility(axisLinesVisibility);\r\n\r\n this.svgAxes.init(chartAreaSvg);\r\n this.svgBrush.init(chartAreaSvg);\r\n\r\n this.sharedColorPalette = new SharedColorPalette(options.style.colorPalette.dataColors);\r\n\r\n this.legend = createLegend(\r\n element,\r\n options.interactivity && options.interactivity.isInteractiveLegend,\r\n this.type !== CartesianChartType.Waterfall ? this.interactivityService : undefined,\r\n this.axes.isScrollable);\r\n\r\n this.isMobileChart = options.interactivity && options.interactivity.isInteractiveLegend;\r\n }\r\n\r\n private isPlayAxis(): boolean {\r\n if (!this.dataViews || !this.dataViews[0])\r\n return false;\r\n\r\n let dataView = this.dataViews[0];\r\n let categoryRoleIsPlay: boolean = dataView.categorical\r\n && dataView.categorical.categories\r\n && dataView.categorical.categories[0]\r\n && dataView.categorical.categories[0].source\r\n && dataView.categorical.categories[0].source.roles\r\n && dataView.categorical.categories[0].source.roles['Play'];\r\n\r\n return this.type === CartesianChartType.Scatter\r\n && (this.animator || this.isMobileChart)\r\n && dataView.matrix != null\r\n && (!dataView.categorical || categoryRoleIsPlay);\r\n }\r\n\r\n public static getIsScalar(objects: DataViewObjects, propertyId: DataViewObjectPropertyIdentifier, type: ValueTypeDescriptor, scalarKeys?: ScalarKeys): boolean {\r\n if (!CartesianChart.supportsScalar(type, scalarKeys))\r\n return false;\r\n\r\n let axisTypeValue = DataViewObjects.getValue(objects, propertyId);\r\n if (!objects || axisTypeValue == null)\r\n return true;\r\n\r\n return (axisTypeValue === axisType.scalar);\r\n }\r\n\r\n private static supportsScalar(type: ValueTypeDescriptor, scalarKeys?: ScalarKeys): boolean {\r\n // if scalar key is present, it supports scalar\r\n if (scalarKeys && !_.isEmpty(scalarKeys.values))\r\n return true;\r\n\r\n // otherwise does not support scalar if the type is non-numeric.\r\n return !AxisHelper.isOrdinal(type);\r\n }\r\n\r\n public static getAdditionalTelemetry(dataView: DataView): any {\r\n let telemetry: any = {};\r\n\r\n let categoryColumn = dataView && dataView.categorical && _.first(dataView.categorical.categories);\r\n if (categoryColumn) {\r\n telemetry.axisType = visuals.CartesianChart.getIsScalar(dataView.metadata.objects, visuals.columnChartProps.categoryAxis.axisType, categoryColumn.source.type)\r\n ? 'scalar'\r\n : 'categorical';\r\n }\r\n\r\n return telemetry;\r\n }\r\n\r\n public static detectScalarMapping(dataViewMapping: data.CompiledDataViewMapping): boolean {\r\n if (!dataViewMapping || !dataViewMapping.categorical || !dataViewMapping.categorical.categories)\r\n return false;\r\n\r\n let dataViewCategories = <data.CompiledDataViewRoleForMappingWithReduction>dataViewMapping.categorical.categories;\r\n let categoryItems = dataViewCategories.for.in.items;\r\n if (_.isEmpty(categoryItems))\r\n return false;\r\n\r\n let categoryType = categoryItems[0].type;\r\n\r\n if (!dataViewMapping.metadata)\r\n return false;\r\n\r\n let objects = dataViewMapping.metadata.objects;\r\n\r\n return CartesianChart.getIsScalar(objects, columnChartProps.categoryAxis.axisType, categoryType);\r\n }\r\n\r\n private populateObjectProperties(dataViews: DataView[]) {\r\n if (dataViews && dataViews.length > 0) {\r\n let dataViewMetadata = dataViews[0].metadata;\r\n\r\n if (dataViewMetadata) {\r\n this.legendObjectProperties = DataViewObjects.getObject(dataViewMetadata.objects, 'legend', {});\r\n this.xAxisReferenceLines = DataViewObjects.getUserDefinedObjects(dataViewMetadata.objects, 'xAxisReferenceLine');\r\n this.y1AxisReferenceLines = DataViewObjects.getUserDefinedObjects(dataViewMetadata.objects, 'y1AxisReferenceLine');\r\n }\r\n else {\r\n this.legendObjectProperties = {};\r\n }\r\n\r\n this.categoryAxisProperties = CartesianHelper.getCategoryAxisProperties(dataViewMetadata);\r\n this.valueAxisProperties = CartesianHelper.getValueAxisProperties(dataViewMetadata);\r\n }\r\n }\r\n\r\n private updateInternal(options: VisualUpdateOptions, operationKind?: VisualDataChangeOperationKind): void {\r\n let dataViews = this.dataViews = options.dataViews;\r\n this.currentViewport = options.viewport;\r\n \r\n if (!dataViews) return;\r\n\r\n if (this.layers.length === 0) {\r\n // Lazily instantiate the chart layers on the first data load.\r\n let objects: DataViewObjects = this.extractMetadataObjects(dataViews);\r\n this.layers = this.createAndInitLayers(objects);\r\n\r\n debug.assert(this.layers.length > 0, 'createAndInitLayers should update the layers.');\r\n }\r\n let layers = this.layers;\r\n\r\n if (operationKind != null) {\r\n if (!_.isEmpty(dataViews)) {\r\n this.populateObjectProperties(dataViews);\r\n this.axes.update(dataViews);\r\n this.svgAxes.update(this.categoryAxisProperties, this.valueAxisProperties);\r\n let dataView = dataViews[0];\r\n if (dataView.metadata) {\r\n // flatten background data\r\n this.background = {\r\n image: DataViewObjects.getValue<ImageValue>(dataView.metadata.objects, scatterChartProps.plotArea.image),\r\n transparency: DataViewObjects.getValue(dataView.metadata.objects, scatterChartProps.plotArea.transparency, visualBackgroundHelper.getDefaultTransparency()),\r\n };\r\n \r\n if (this.cartesianLoadMoreEnabled) {\r\n let isScalar = true;\r\n let categoryColumn = dataView && dataView.categorical && _.first(dataView.categorical.categories);\r\n\r\n if (categoryColumn && categoryColumn.source) {\r\n isScalar = visuals.CartesianChart.getIsScalar(dataView.metadata.objects, visuals.columnChartProps.categoryAxis.axisType, categoryColumn.source.type);\r\n }\r\n\r\n // Clear the load more handler if we're scalar and there's an existing handler. \r\n // Setup a handler if we're categorical and don't have one.\r\n if (isScalar && this.loadMoreDataHandler) {\r\n this.loadMoreDataHandler = null;\r\n }\r\n else if (!isScalar && !this.loadMoreDataHandler) {\r\n this.loadMoreDataHandler = new CartesianLoadMoreDataHandler(null, this.hostServices.loadMoreData, CartesianChart.LoadMoreThreshold);\r\n }\r\n }\r\n }\r\n }\r\n\r\n this.sharedColorPalette.clearPreferredScale();\r\n let layerDataViews = getLayerDataViews(dataViews);\r\n let trendLineDataViews = _.filter(dataViews, (dataView) => TrendLineHelper.isDataViewForRegression(dataView));\r\n this.trendLines = [];\r\n\r\n for (let i = 0, layerCount = layers.length; i < layerCount; i++) {\r\n let layerDataView = layerDataViews[i];\r\n layers[i].setData(layerDataView ? [layerDataView] : []);\r\n\r\n if (this.supportsTrendLines(i)) {\r\n let trendLineDataView = trendLineDataViews[i];\r\n if (trendLineDataView) {\r\n let y2 = (i > 0);\r\n let trendLines = TrendLineHelper.readDataView(trendLineDataView, layerDataView, y2, this.sharedColorPalette);\r\n this.trendLines.push(...trendLines);\r\n }\r\n }\r\n\r\n if (layerCount > 1)\r\n this.sharedColorPalette.rotateScale();\r\n }\r\n }\r\n \r\n // If the data changed (there's an operationKind), say we're done loading data so logic \r\n // during the render phase can request more data if there is not enough.\r\n if (this.loadMoreDataHandler && operationKind != null) {\r\n this.loadMoreDataHandler.onLoadMoreDataCompleted();\r\n }\r\n\r\n this.render(!this.hasSetData || options.suppressAnimations, options.resizeMode, operationKind);\r\n\r\n this.hasSetData = this.hasSetData || (dataViews && dataViews.length > 0);\r\n\r\n if (dataViews && dataViews.length > 0) {\r\n let warnings = getInvalidValueWarnings(\r\n dataViews,\r\n false /*supportsNaN*/,\r\n false /*supportsNegativeInfinity*/,\r\n false /*supportsPositiveInfinity*/);\r\n\r\n this.axes.addWarnings(warnings);\r\n\r\n if (warnings && warnings.length > 0)\r\n this.hostServices.setWarnings(warnings);\r\n }\r\n }\r\n\r\n // TODO: Remove onDataChanged & onResizing once we have a flag to distinguish between resize and data changed events.\r\n public onDataChanged(options: VisualDataChangedOptions): void {\r\n this.updateInternal({\r\n dataViews: options.dataViews,\r\n suppressAnimations: options.suppressAnimations,\r\n viewport: this.currentViewport\r\n }, options.operationKind != null ? options.operationKind : VisualDataChangeOperationKind.Create);\r\n }\r\n\r\n // TODO: Remove onDataChanged & onResizing once we have a flag to distinguish between resize and data changed events.\r\n public onResizing(viewport: IViewport, resizeMode?: ResizeMode): void {\r\n this.updateInternal({\r\n dataViews: this.dataViews,\r\n suppressAnimations: true,\r\n viewport: viewport,\r\n resizeMode: resizeMode,\r\n });\r\n }\r\n\r\n public scrollTo(position: number): void {\r\n this.scrollableAxes.scrollTo(position);\r\n }\r\n\r\n public enumerateObjectInstances(options: EnumerateVisualObjectInstancesOptions): VisualObjectInstanceEnumeration {\r\n let enumeration = new ObjectEnumerationBuilder();\r\n let layersLength = this.layers ? this.layers.length : 0;\r\n\r\n if (options.objectName === 'legend') {\r\n if (!this.shouldShowLegendCard())\r\n return;\r\n\r\n let show = DataViewObject.getValue(this.legendObjectProperties, legendProps.show, this.legend.isVisible());\r\n let showTitle = DataViewObject.getValue(this.legendObjectProperties, legendProps.showTitle, true);\r\n let titleText = DataViewObject.getValue(this.legendObjectProperties, legendProps.titleText, this.layerLegendData ? this.layerLegendData.title : '');\r\n let labelColor = DataViewObject.getValue(this.legendObjectProperties, legendProps.labelColor, LegendData.DefaultLegendLabelFillColor);\r\n let fontSize = DataViewObject.getValue(this.legendObjectProperties, legendProps.fontSize, this.layerLegendData && this.layerLegendData.fontSize ? this.layerLegendData.fontSize : SVGLegend.DefaultFontSizeInPt);\r\n\r\n enumeration.pushInstance({\r\n selector: null,\r\n properties: {\r\n show: show,\r\n position: LegendPosition[this.legend.getOrientation()],\r\n showTitle: showTitle,\r\n titleText: titleText,\r\n labelColor: labelColor,\r\n fontSize: fontSize,\r\n },\r\n objectName: options.objectName\r\n });\r\n }\r\n else if (options.objectName === 'categoryAxis' && this.axes.hasCategoryAxis()) {\r\n this.getCategoryAxisValues(enumeration);\r\n }\r\n else if (options.objectName === 'valueAxis') {\r\n this.getValueAxisValues(enumeration);\r\n }\r\n else if (options.objectName === 'y1AxisReferenceLine') {\r\n let refLinedefaultColor = this.sharedColorPalette.getColorByIndex(0).value;\r\n ReferenceLineHelper.enumerateObjectInstances(enumeration, this.y1AxisReferenceLines, refLinedefaultColor, options.objectName);\r\n }\r\n else if (options.objectName === 'xAxisReferenceLine') {\r\n let refLinedefaultColor = this.sharedColorPalette.getColorByIndex(0).value;\r\n ReferenceLineHelper.enumerateObjectInstances(enumeration, this.xAxisReferenceLines, refLinedefaultColor, options.objectName);\r\n }\r\n else if (options.objectName === 'trend') {\r\n if (this.supportsTrendLines()) {\r\n TrendLineHelper.enumerateObjectInstances(enumeration, this.trendLines);\r\n }\r\n }\r\n else if (options.objectName === 'plotArea') {\r\n visualBackgroundHelper.enumeratePlot(enumeration, this.background);\r\n }\r\n\r\n if (options.objectName === 'dataPoint' &&\r\n ComboChart.isComboChart(this.type)) {\r\n ComboChart.enumerateDataPoints(enumeration, options, this.layers);\r\n }\r\n else {\r\n for (let i = 0, len = layersLength; i < len; i++) {\r\n let layer = this.layers[i];\r\n if (layer.enumerateObjectInstances) {\r\n layer.enumerateObjectInstances(enumeration, options);\r\n }\r\n }\r\n }\r\n\r\n return enumeration.complete();\r\n }\r\n\r\n private supportsTrendLines(layerIndex?: number): boolean {\r\n let layerDataViews = getLayerDataViews(this.dataViews);\r\n\r\n if (_.isEmpty(this.layers))\r\n return false;\r\n\r\n // If layerIndex was not given then check all layers.\r\n let layers = layerIndex == null ? this.layers : [this.layers[layerIndex]];\r\n\r\n return _.all(layers, (layer, index) => {\r\n if (!layerDataViews[index])\r\n return true;\r\n return layer.supportsTrendLine && layer.supportsTrendLine();\r\n });\r\n }\r\n\r\n private shouldShowLegendCard(): boolean {\r\n let layers = this.layers;\r\n let dataViews = this.dataViews;\r\n\r\n if (layers && dataViews) {\r\n let layersLength = layers.length;\r\n let layersWithValuesCtr = 0;\r\n\r\n for (let i = 0; i < layersLength; i++) {\r\n if (layers[i].hasLegend()) {\r\n return true;\r\n }\r\n\r\n // if there are at least two layers with values legend card should be shown (even if each of the individual layers don't have legend)\r\n let dataView = dataViews[i];\r\n if (dataView && dataView.categorical && dataView.categorical.values && dataView.categorical.values.length > 0) {\r\n layersWithValuesCtr++;\r\n if (layersWithValuesCtr > 1) {\r\n return true;\r\n }\r\n }\r\n }\r\n }\r\n\r\n return false;\r\n }\r\n\r\n private getAxisScaleOptions(axisType: AxisLocation): string[] {\r\n let scaleOptions = [DEFAULT_AXIS_SCALE_TYPE];\r\n if (this.axes.isLogScaleAllowed(axisType))\r\n scaleOptions.push(axisScale.log);\r\n return scaleOptions;\r\n }\r\n\r\n private getCategoryAxisValues(enumeration: ObjectEnumerationBuilder): void {\r\n if (!this.categoryAxisProperties) {\r\n return;\r\n }\r\n let supportedType = axisType.both;\r\n let isScalar = false;\r\n let scaleOptions = this.getAxisScaleOptions(AxisLocation.X);\r\n\r\n if (this.layers && this.layers[0].getSupportedCategoryAxisType) {\r\n supportedType = this.layers[0].getSupportedCategoryAxisType();\r\n if (supportedType === axisType.scalar) {\r\n isScalar = true;\r\n }\r\n else {\r\n isScalar = CartesianHelper.isScalar(supportedType === axisType.both, this.categoryAxisProperties);\r\n }\r\n }\r\n\r\n if (!isScalar) {\r\n this.categoryAxisProperties['start'] = null;\r\n this.categoryAxisProperties['end'] = null;\r\n }\r\n\r\n let instance: VisualObjectInstance = {\r\n selector: null,\r\n properties: {},\r\n objectName: 'categoryAxis',\r\n validValues: {\r\n axisScale: scaleOptions,\r\n axisStyle: this.axes.categoryAxisHasUnitType ? [axisStyle.showTitleOnly, axisStyle.showUnitOnly, axisStyle.showBoth] : [axisStyle.showTitleOnly]\r\n }\r\n };\r\n\r\n instance.properties['show'] = this.categoryAxisProperties['show'] != null ? this.categoryAxisProperties['show'] : true;\r\n if (this.axes.isYAxisCategorical())//in case of e.g. barChart\r\n instance.properties['position'] = this.valueAxisProperties && this.valueAxisProperties['position'] != null ? this.valueAxisProperties['position'] : yAxisPosition.left;\r\n if (supportedType === axisType.both) {\r\n instance.properties['axisType'] = isScalar ? axisType.scalar : axisType.categorical;\r\n }\r\n if (isScalar) {\r\n instance.properties['axisScale'] = this.categoryAxisProperties['axisScale'] || DEFAULT_AXIS_SCALE_TYPE;\r\n instance.properties['start'] = this.categoryAxisProperties['start'];\r\n instance.properties['end'] = this.categoryAxisProperties['end'];\r\n }\r\n instance.properties['showAxisTitle'] = this.categoryAxisProperties['showAxisTitle'] != null ? this.categoryAxisProperties['showAxisTitle'] : false;\r\n\r\n instance.properties['axisStyle'] = this.categoryAxisProperties['axisStyle'] ? this.categoryAxisProperties['axisStyle'] : axisStyle.showTitleOnly;\r\n instance.properties['labelColor'] = this.categoryAxisProperties['labelColor'] || DEFAULT_AXIS_COLOR;\r\n\r\n if (isScalar) {\r\n instance.properties['labelDisplayUnits'] = this.categoryAxisProperties['labelDisplayUnits'] ? this.categoryAxisProperties['labelDisplayUnits'] : 0;\r\n let labelPrecision = this.categoryAxisProperties['labelPrecision'];\r\n instance.properties['labelPrecision'] = (labelPrecision === undefined || labelPrecision < 0)\r\n ? dataLabelUtils.defaultLabelPrecision\r\n : labelPrecision;\r\n }\r\n enumeration.pushInstance(instance);\r\n }\r\n\r\n //TODO: wrap all these object getters and other related stuff into an interface.\r\n private getValueAxisValues(enumeration: ObjectEnumerationBuilder): void {\r\n if (!this.valueAxisProperties) {\r\n return;\r\n }\r\n let scaleOptions = this.getAxisScaleOptions(AxisLocation.Y1);\r\n let secScaleOption = this.getAxisScaleOptions(AxisLocation.Y2);\r\n\r\n let instance: VisualObjectInstance = {\r\n selector: null,\r\n properties: {},\r\n objectName: 'valueAxis',\r\n validValues: {\r\n axisScale: scaleOptions,\r\n secAxisScale: secScaleOption,\r\n axisStyle: this.axes.valueAxisHasUnitType ? [axisStyle.showTitleOnly, axisStyle.showUnitOnly, axisStyle.showBoth] : [axisStyle.showTitleOnly],\r\n secAxisStyle: this.axes.secondaryValueAxisHasUnitType ? [axisStyle.showTitleOnly, axisStyle.showUnitOnly, axisStyle.showBoth] : [axisStyle.showTitleOnly],\r\n }\r\n };\r\n\r\n instance.properties['show'] = this.valueAxisProperties['show'] != null ? this.valueAxisProperties['show'] : true;\r\n instance.properties['axisLabel'] = this.valueAxisProperties['axisLabel'];\r\n if (!this.axes.isYAxisCategorical()) {\r\n instance.properties['position'] = this.valueAxisProperties['position'] != null ? this.valueAxisProperties['position'] : yAxisPosition.left;\r\n }\r\n instance.properties['axisScale'] = this.valueAxisProperties['axisScale'] || DEFAULT_AXIS_SCALE_TYPE;\r\n instance.properties['start'] = this.valueAxisProperties['start'];\r\n instance.properties['end'] = this.valueAxisProperties['end'];\r\n instance.properties['showAxisTitle'] = this.valueAxisProperties['showAxisTitle'] != null ? this.valueAxisProperties['showAxisTitle'] : false;\r\n instance.properties['axisStyle'] = this.valueAxisProperties['axisStyle'] != null ? this.valueAxisProperties['axisStyle'] : axisStyle.showTitleOnly;\r\n instance.properties['labelColor'] = this.valueAxisProperties['labelColor'] || DEFAULT_AXIS_COLOR;\r\n\r\n if (this.type !== CartesianChartType.HundredPercentStackedBar && this.type !== CartesianChartType.HundredPercentStackedColumn) {\r\n instance.properties['labelDisplayUnits'] = this.valueAxisProperties['labelDisplayUnits'] ? this.valueAxisProperties['labelDisplayUnits'] : 0;\r\n let labelPrecision = this.valueAxisProperties['labelPrecision'];\r\n instance.properties['labelPrecision'] = (labelPrecision === undefined || labelPrecision < 0)\r\n ? dataLabelUtils.defaultLabelPrecision\r\n : labelPrecision;\r\n }\r\n\r\n if (this.layers.length === 2) {\r\n instance.properties['secShow'] = this.valueAxisProperties['secShow'] != null ? this.valueAxisProperties['secShow'] : this.axes.hasY2Axis();\r\n }\r\n\r\n if (this.axes.hasY2Axis() && instance.properties['secShow']) {\r\n instance.properties['secAxisLabel'] = '';\r\n instance.properties['secPosition'] = this.valueAxisProperties['secPosition'] != null ? this.valueAxisProperties['secPosition'] : yAxisPosition.right;\r\n instance.properties['secAxisScale'] = this.valueAxisProperties['secAxisScale'] || DEFAULT_AXIS_SCALE_TYPE;\r\n instance.properties['secStart'] = this.valueAxisProperties['secStart'];\r\n instance.properties['secEnd'] = this.valueAxisProperties['secEnd'];\r\n instance.properties['secShowAxisTitle'] = this.valueAxisProperties['secShowAxisTitle'] != null ? this.valueAxisProperties['secShowAxisTitle'] : false;\r\n instance.properties['secAxisStyle'] = this.valueAxisProperties['secAxisStyle'] ? this.valueAxisProperties['secAxisStyle'] : axisStyle.showTitleOnly;\r\n instance.properties['labelColor'] = this.valueAxisProperties['secLabelColor'];\r\n instance.properties['secLabelDisplayUnits'] = this.valueAxisProperties['secLabelDisplayUnits'] ? this.valueAxisProperties['secLabelDisplayUnits'] : 0;\r\n instance.properties['secLabelPrecision'] = this.valueAxisProperties['secLabelPrecision'] < 0 ? 0 : this.valueAxisProperties['secLabelPrecision'];\r\n }\r\n\r\n enumeration.pushInstance(instance);\r\n }\r\n\r\n public onClearSelection(): void {\r\n if (this.hasSetData) {\r\n for (let i = 0, len = this.layers.length; i < len; i++) {\r\n let layer = this.layers[i];\r\n layer.onClearSelection();\r\n layer.render(true /* suppressAnimations */);\r\n }\r\n }\r\n }\r\n\r\n private extractMetadataObjects(dataViews: DataView[]): DataViewObjects {\r\n let objects: DataViewObjects;\r\n if (dataViews && dataViews.length > 0) {\r\n let dataViewMetadata = dataViews[0].metadata;\r\n if (dataViewMetadata)\r\n objects = dataViewMetadata.objects;\r\n }\r\n return objects;\r\n }\r\n\r\n private createAndInitLayers(objects: DataViewObjects): ICartesianVisual[] {\r\n // Create the layers\r\n let layers = CartesianLayerFactory.createLayers(\r\n this.type,\r\n objects,\r\n this.interactivityService,\r\n this.animator,\r\n this.axes.isScrollable,\r\n this.tooltipsEnabled,\r\n this.tooltipBucketEnabled,\r\n this.advancedLineLabelsEnabled,\r\n this.cartesianLoadMoreEnabled);\r\n\r\n // Initialize the layers\r\n let cartesianOptions = <CartesianVisualInitOptions>Prototype.inherit(this.visualInitOptions);\r\n cartesianOptions.svg = this.svgAxes.getScrollableRegion();\r\n cartesianOptions.labelsContext = this.svgAxes.getLabelsRegion();\r\n cartesianOptions.cartesianHost = {\r\n updateLegend: data => this.legend.drawLegend(data, this.currentViewport),\r\n getSharedColors: () => this.sharedColorPalette,\r\n triggerRender: (suppressAnimations: boolean) => this.render(suppressAnimations),\r\n };\r\n cartesianOptions.chartType = this.type;\r\n\r\n for (let i = 0, len = layers.length; i < len; i++)\r\n layers[i].init(cartesianOptions);\r\n\r\n return layers;\r\n }\r\n\r\n private renderLegend(): void {\r\n let layers = this.layers;\r\n let legendData: LegendData = { title: \"\", dataPoints: [] };\r\n\r\n for (let i = 0, len = layers.length; i < len; i++) {\r\n this.layerLegendData = layers[i].calculateLegend();\r\n if (this.layerLegendData) {\r\n legendData.title = i === 0 ? this.layerLegendData.title || \"\"\r\n : legendData.title;\r\n legendData.labelColor = this.layerLegendData.labelColor;\r\n \r\n // Data points have have duplicate identities (ex. Combo Chart uses a measure in both line and column).\r\n // Add the layer number (if it's set) so the D3 keys are different.\r\n if (!_.isEmpty(this.layerLegendData.dataPoints)) {\r\n this.layerLegendData.dataPoints.forEach((dataPoint) => dataPoint.layerNumber = i);\r\n }\r\n\r\n legendData.dataPoints = legendData.dataPoints.concat(this.layerLegendData.dataPoints || []);\r\n legendData.fontSize = this.layerLegendData.fontSize || SVGLegend.DefaultFontSizeInPt;\r\n if (this.layerLegendData.grouped) {\r\n legendData.grouped = true;\r\n }\r\n }\r\n }\r\n\r\n let legendProperties = this.legendObjectProperties;\r\n\r\n if (legendProperties) {\r\n LegendData.update(legendData, legendProperties);\r\n let position = <string>legendProperties[legendProps.position];\r\n\r\n if (position)\r\n this.legend.changeOrientation(LegendPosition[position]);\r\n }\r\n else {\r\n this.legend.changeOrientation(LegendPosition.Top);\r\n }\r\n\r\n if ((legendData.dataPoints.length === 1 && !legendData.grouped) || this.hideLegends()) {\r\n legendData.dataPoints = [];\r\n }\r\n\r\n this.legend.drawLegend(legendData, this.currentViewport);\r\n }\r\n\r\n private hideLegends(): boolean {\r\n if (this.cartesianSmallViewPortProperties) {\r\n if (this.cartesianSmallViewPortProperties.hideLegendOnSmallViewPort && (this.currentViewport.height < this.cartesianSmallViewPortProperties.MinHeightLegendVisible)) {\r\n return true;\r\n }\r\n }\r\n return false;\r\n }\r\n\r\n private render(suppressAnimations: boolean, resizeMode?: ResizeMode, operationKind?: VisualDataChangeOperationKind): void {\r\n // Note: interactive legend shouldn't be rendered explicitly here\r\n // The interactive legend is being rendered in the render method of ICartesianVisual\r\n if (!(this.visualInitOptions.interactivity && this.visualInitOptions.interactivity.isInteractiveLegend)) {\r\n this.renderLegend();\r\n }\r\n\r\n let legendMargins = this.legendMargins = this.legend.getMargins();\r\n let legendOrientation = this.legend.getOrientation();\r\n let hideAxisLabels = this.hideAxisLabels(legendMargins);\r\n\r\n let plotAreaViewport: IViewport = {\r\n height: this.currentViewport.height - legendMargins.height,\r\n width: this.currentViewport.width - legendMargins.width\r\n };\r\n\r\n let padding = Prototype.inherit(SvgCartesianAxes.AxisPadding);\r\n let playAxisControlLayout: IRect;\r\n if (this.isPlayAxis()) {\r\n plotAreaViewport.height -= CartesianChart.PlayAxisBottomMargin;\r\n playAxisControlLayout = {\r\n left: Legend.isLeft(legendOrientation) ? legendMargins.width : 0,\r\n top: Legend.isTop(legendOrientation) ? legendMargins.height + plotAreaViewport.height : plotAreaViewport.height,\r\n height: CartesianChart.PlayAxisBottomMargin,\r\n width: plotAreaViewport.width\r\n };\r\n }\r\n\r\n this.chartAreaSvg.attr({\r\n 'width': plotAreaViewport.width,\r\n 'height': plotAreaViewport.height,\r\n });\r\n Legend.positionChartArea(this.chartAreaSvg, this.legend);\r\n\r\n let interactivityRightMargin = this.calculateInteractivityRightMargin();\r\n\r\n let [ensureXDomain, ensureYDomain] = this.getMinimumDomainExtents();\r\n\r\n let axesLayout = this.axes.negotiateAxes(\r\n this.layers,\r\n plotAreaViewport,\r\n padding,\r\n playAxisControlLayout,\r\n hideAxisLabels,\r\n CartesianChart.AxisTextProperties,\r\n interactivityRightMargin,\r\n ensureXDomain,\r\n ensureYDomain);\r\n \r\n let categoryAxis = axesLayout.axes.x.isCategoryAxis ? axesLayout.axes.x : axesLayout.axes.y1;\r\n \r\n if (this.loadMoreDataHandler) {\r\n this.loadMoreDataHandler.setScale(categoryAxis.scale);\r\n }\r\n\r\n // Even if the caller thinks animations are ok, now that we've laid out the axes and legend we should disable animations\r\n // if the plot area changed. Animations for property changes like legend on/off are not desired.\r\n let plotAreaHasChanged: boolean =\r\n !this.renderedPlotArea\r\n || (this.renderedPlotArea.height !== axesLayout.plotArea.height ||\r\n this.renderedPlotArea.width !== axesLayout.plotArea.width);\r\n suppressAnimations = suppressAnimations || plotAreaHasChanged;\r\n\r\n this.scrollableAxes.render(\r\n axesLayout,\r\n this.layers,\r\n suppressAnimations,\r\n (layers, axesLayout, suppressAnimations) => this.renderPlotArea(layers, axesLayout, suppressAnimations, legendMargins, resizeMode),\r\n this.loadMoreDataHandler,\r\n operationKind === VisualDataChangeOperationKind.Append /* preserveScrollbar */);\r\n\r\n // attach scroll event\r\n this.chartAreaSvg.on('wheel', () => {\r\n if (!(this.axes.isXScrollBarVisible || this.axes.isYScrollBarVisible)) return;\r\n TooltipManager.ToolTipInstance.hide();\r\n let wheelEvent: any = d3.event;\r\n let dy = wheelEvent.deltaY;\r\n this.scrollableAxes.scrollDelta(dy);\r\n (<MouseWheelEvent>wheelEvent).preventDefault();\r\n });\r\n\r\n this.renderedPlotArea = axesLayout.plotArea;\r\n }\r\n\r\n /**\r\n * Gets any minimum domain extents.\r\n * Reference lines and trend lines may enforce minimum extents on X and/or Y domains.\r\n */\r\n private getMinimumDomainExtents(): NumberRange[] {\r\n let xs: number[] = [];\r\n let ys: number[] = [];\r\n\r\n if (!_.isEmpty(this.xAxisReferenceLines)) {\r\n let xAxisReferenceLineProperties: DataViewObject = this.xAxisReferenceLines[0].object;\r\n let value = ReferenceLineHelper.extractReferenceLineValue(xAxisReferenceLineProperties);\r\n xs.push(value);\r\n }\r\n\r\n if (!_.isEmpty(this.y1AxisReferenceLines)) {\r\n let y1AxisReferenceLineProperties: DataViewObject = this.y1AxisReferenceLines[0].object;\r\n let value = ReferenceLineHelper.extractReferenceLineValue(y1AxisReferenceLineProperties);\r\n ys.push(value);\r\n }\r\n\r\n let ensureXDomain: NumberRange = {\r\n min: d3.min(xs),\r\n max: d3.max(xs)\r\n };\r\n\r\n let ensureYDomain: NumberRange = {\r\n min: d3.min(ys),\r\n max: d3.max(ys)\r\n };\r\n\r\n return [ensureXDomain, ensureYDomain];\r\n }\r\n\r\n private getPlotAreaRect(axesLayout: CartesianAxesLayout, legendMargins: IViewport): IRect {\r\n let rect: Rect = {\r\n left: axesLayout.margin.left,\r\n top: axesLayout.margin.top,\r\n width: axesLayout.plotArea.width,\r\n height: axesLayout.plotArea.height,\r\n };\r\n\r\n // Adjust the margins to the legend position \r\n if (this.legend) {\r\n let legendPosition = this.legend.getOrientation();\r\n\r\n if (Legend.isTop(legendPosition)) {\r\n rect.top += legendMargins.height;\r\n }\r\n else if (Legend.isLeft(legendPosition)) {\r\n rect.left += legendMargins.width;\r\n }\r\n }\r\n\r\n return rect;\r\n }\r\n\r\n private renderBackgroundImage(layout: IRect): void {\r\n visualBackgroundHelper.renderBackgroundImage(\r\n this.background,\r\n this.element,\r\n layout);\r\n }\r\n\r\n private hideAxisLabels(legendMargins: IViewport): boolean {\r\n if (this.cartesianSmallViewPortProperties) {\r\n if (this.cartesianSmallViewPortProperties.hideAxesOnSmallViewPort && (this.currentViewport.height < this.cartesianSmallViewPortProperties.MinHeightAxesVisible) && !this.visualInitOptions.interactivity.isInteractiveLegend) {\r\n return true;\r\n }\r\n }\r\n return false;\r\n }\r\n\r\n private calculateInteractivityRightMargin(): number {\r\n // add right margin in order not to cut the circle selection of the hover line \r\n if (this.visualInitOptions.interactivity && this.visualInitOptions.interactivity.isInteractiveLegend && !this.trimOrdinalDataOnOverflow) {\r\n return INTERACTIVITY_RIGHT_MARGIN;\r\n } else {\r\n return 0;\r\n }\r\n }\r\n\r\n private renderPlotArea(\r\n layers: ICartesianVisual[],\r\n axesLayout: CartesianAxesLayout,\r\n suppressAnimations: boolean,\r\n legendMargins: IViewport,\r\n resizeMode?: ResizeMode): void {\r\n debug.assertValue(layers, 'layers');\r\n\r\n let axes = axesLayout.axes;\r\n let plotArea = axesLayout.plotArea;\r\n let plotAreaRect = this.getPlotAreaRect(axesLayout, legendMargins);\r\n let duration = AnimatorCommon.GetAnimationDuration(this.animator, suppressAnimations);\r\n let easing = this.animator && this.animator.getEasing();\r\n\r\n this.renderBackgroundImage(plotAreaRect);\r\n\r\n if (!_.isEmpty(easing))\r\n this.svgAxes.renderAxes(axesLayout, duration, easing);\r\n else\r\n this.svgAxes.renderAxes(axesLayout, duration);\r\n\r\n this.renderReferenceLines(axesLayout);\r\n\r\n this.renderLayers(layers, plotArea, axes, suppressAnimations, resizeMode);\r\n\r\n this.renderTrendLines(axesLayout);\r\n }\r\n\r\n private renderTrendLines(axesLayout: CartesianAxesLayout): void {\r\n let scrollableRegion = this.svgAxes.getScrollableRegion();\r\n TrendLineHelper.render(this.trendLines, scrollableRegion, axesLayout.axes, axesLayout.plotArea);\r\n }\r\n\r\n private renderReferenceLines(axesLayout: CartesianAxesLayout): void {\r\n let axes = axesLayout.axes;\r\n let plotArea = axesLayout.plotArea;\r\n let scrollableRegion = this.svgAxes.getScrollableRegion();\r\n let refLineDefaultColor = this.sharedColorPalette.getColorByIndex(0).value;\r\n\r\n let showY1ReferenceLines = false;\r\n if (this.y1AxisReferenceLines) {\r\n for (let referenceLineProperties of this.y1AxisReferenceLines) {\r\n let object: DataViewObject = referenceLineProperties.object;\r\n if (object[ReferenceLineHelper.referenceLineProps.show]) {\r\n\r\n let isHorizontal = !axes.y1.isCategoryAxis;\r\n let y1RefLineOptions = {\r\n graphicContext: scrollableRegion,\r\n referenceLineProperties: object,\r\n axes: axes,\r\n viewport: plotArea,\r\n classAndSelector: this.y1RefLine,\r\n defaultColor: refLineDefaultColor,\r\n isHorizontal: isHorizontal\r\n };\r\n\r\n ReferenceLineHelper.render(y1RefLineOptions);\r\n showY1ReferenceLines = true;\r\n }\r\n }\r\n }\r\n\r\n if (!showY1ReferenceLines) {\r\n scrollableRegion.selectAll(this.y1RefLine.selector).remove();\r\n }\r\n\r\n let showXReferenceLines = false;\r\n if (this.xAxisReferenceLines) {\r\n for (let referenceLineProperties of this.xAxisReferenceLines) {\r\n let object: DataViewObject = referenceLineProperties.object;\r\n if (object[ReferenceLineHelper.referenceLineProps.show]) {\r\n let isHorizontal = false;\r\n let xRefLineOptions = {\r\n graphicContext: scrollableRegion,\r\n referenceLineProperties: object,\r\n axes: axes,\r\n viewport: plotArea,\r\n classAndSelector: this.xRefLine,\r\n defaultColor: refLineDefaultColor,\r\n isHorizontal: isHorizontal\r\n };\r\n\r\n ReferenceLineHelper.render(xRefLineOptions);\r\n showXReferenceLines = true;\r\n }\r\n }\r\n }\r\n\r\n if (!showXReferenceLines) {\r\n scrollableRegion.selectAll(this.xRefLine.selector).remove();\r\n }\r\n }\r\n\r\n private getReferenceLineLabels(axes: CartesianAxisProperties, plotArea: IViewport): LabelDataPoint[] {\r\n let refLineDefaultColor = this.sharedColorPalette.getColorByIndex(0).value;\r\n let referenceLineLabels: LabelDataPoint[] = [];\r\n if (this.y1AxisReferenceLines) {\r\n for (let referenceLineProperties of this.y1AxisReferenceLines) {\r\n let object: DataViewObject = referenceLineProperties.object;\r\n if (object[ReferenceLineHelper.referenceLineProps.show] && object[ReferenceLineHelper.referenceLineProps.dataLabelShow]) {\r\n let isHorizontal = !axes.y1.isCategoryAxis;\r\n let y1RefLineLabelOptions: ReferenceLineDataLabelOptions = {\r\n referenceLineProperties: object,\r\n axes: axes,\r\n viewport: plotArea,\r\n defaultColor: refLineDefaultColor,\r\n isHorizontal: isHorizontal,\r\n key: JSON.stringify({\r\n type: 'y1AxisReferenceLine',\r\n id: referenceLineProperties.id,\r\n }),\r\n };\r\n\r\n referenceLineLabels.push(ReferenceLineHelper.createLabelDataPoint(y1RefLineLabelOptions));\r\n }\r\n }\r\n }\r\n\r\n if (this.xAxisReferenceLines) {\r\n for (let referenceLineProperties of this.xAxisReferenceLines) {\r\n let object: DataViewObject = referenceLineProperties.object;\r\n if (object[ReferenceLineHelper.referenceLineProps.show] && object[ReferenceLineHelper.referenceLineProps.dataLabelShow]) {\r\n let isHorizontal = false;\r\n let xRefLineLabelOptions: ReferenceLineDataLabelOptions = {\r\n referenceLineProperties: object,\r\n axes: axes,\r\n viewport: plotArea,\r\n defaultColor: refLineDefaultColor,\r\n isHorizontal: isHorizontal,\r\n key: JSON.stringify({\r\n type: 'xAxisReferenceLine',\r\n id: referenceLineProperties.id,\r\n }),\r\n };\r\n\r\n referenceLineLabels.push(ReferenceLineHelper.createLabelDataPoint(xRefLineLabelOptions));\r\n }\r\n }\r\n }\r\n\r\n return referenceLineLabels;\r\n }\r\n\r\n private renderDataLabels(labelDataPointGroups: LabelDataPointGroup[], labelsAreNumeric: boolean, plotArea: IViewport, suppressAnimations: boolean, isCombo: boolean): void {\r\n let labelBackgroundRegion = this.svgAxes.getLabelBackground();\r\n let labelRegion = this.svgAxes.getLabelsRegion();\r\n\r\n if (this.behavior) {\r\n let labelLayoutOptions = NewDataLabelUtils.getDataLabelLayoutOptions(this.type);\r\n let labelLayout = new LabelLayout(labelLayoutOptions);\r\n let dataLabels = labelLayout.layout(labelDataPointGroups, plotArea);\r\n\r\n if (isCombo) {\r\n NewDataLabelUtils.drawLabelBackground(labelBackgroundRegion, dataLabels, \"#FFFFFF\", 0.7);\r\n }\r\n\r\n let svgLabels: D3.UpdateSelection;\r\n let animator = this.animator;\r\n if (animator && !suppressAnimations) {\r\n let isPlayAxis = this.isPlayAxis();\r\n let duration = isPlayAxis ? PlayChart.FrameAnimationDuration : animator.getDuration();\r\n svgLabels = NewDataLabelUtils.animateDefaultLabels(\r\n labelRegion,\r\n dataLabels,\r\n duration,\r\n labelsAreNumeric,\r\n isPlayAxis ? 'linear' : animator.getEasing());\r\n }\r\n else {\r\n svgLabels = NewDataLabelUtils.drawDefaultLabels(labelRegion, dataLabels, labelsAreNumeric);\r\n }\r\n\r\n if (labelLayoutOptions.allowLeaderLines) {\r\n let filteredLabels = _.filter(dataLabels, (d: Label) => d.leaderLinePoints != null && !_.isEmpty(d.leaderLinePoints) && d.identity != null);\r\n NewDataLabelUtils.drawLabelLeaderLines(labelRegion, filteredLabels, (d: Label) => d.identity.getKey());\r\n }\r\n\r\n if (this.interactivityService && this.isLabelInteractivityEnabled) {\r\n let labelsBehaviorOptions: LabelsBehaviorOptions = {\r\n labelItems: svgLabels,\r\n };\r\n this.interactivityService.bind(dataLabels, new LabelsBehavior(), labelsBehaviorOptions, { isLabels: true });\r\n }\r\n }\r\n else {\r\n let labelLayout = new LabelLayout({\r\n maximumOffset: NewDataLabelUtils.maxLabelOffset,\r\n startingOffset: NewDataLabelUtils.startingLabelOffset,\r\n attemptToMoveLabelsIntoViewport: true,\r\n });\r\n\r\n let dataLabels = labelLayout.layout(labelDataPointGroups, plotArea);\r\n\r\n if (isCombo) {\r\n NewDataLabelUtils.drawLabelBackground(labelBackgroundRegion, dataLabels, \"#FFFFFF\", 0.7);\r\n }\r\n NewDataLabelUtils.drawDefaultLabels(labelRegion, dataLabels, labelsAreNumeric);\r\n }\r\n }\r\n\r\n private renderLayers(layers: ICartesianVisual[], plotArea: IViewport, axes: CartesianAxisProperties, suppressAnimations: boolean, resizeMode?: ResizeMode): void {\r\n let labelDataPointGroups: LabelDataPointGroup[] = [];\r\n let dataPoints: SelectableDataPoint[] = [];\r\n let layerBehaviorOptions: any[] = [];\r\n let labelsAreNumeric: boolean = true;\r\n\r\n // some layer (e.g. scatterChart) may want to suppress animations. if any does, suppress for all.\r\n if (!suppressAnimations) {\r\n for (let layer of layers) {\r\n if (layer.shouldSuppressAnimation && layer.shouldSuppressAnimation()) {\r\n suppressAnimations = true;\r\n break;\r\n }\r\n }\r\n }\r\n\r\n for (let layer of layers) {\r\n let result = layer.render(suppressAnimations, resizeMode);\r\n if (result) {\r\n if (this.behavior) {\r\n // NOTE: these are not needed if we don't have interactivity\r\n dataPoints = dataPoints.concat(result.dataPoints);\r\n layerBehaviorOptions.push(result.behaviorOptions);\r\n }\r\n\r\n if (result.labelDataPointGroups) {\r\n let resultLabelDataPointsGroups = result.labelDataPointGroups;\r\n for (let resultLabelDataPointsGroup of resultLabelDataPointsGroups) {\r\n if (!resultLabelDataPointsGroup)\r\n continue;\r\n labelDataPointGroups.push({\r\n labelDataPoints: NewDataLabelUtils.removeDuplicates(resultLabelDataPointsGroup.labelDataPoints || []),\r\n maxNumberOfLabels: resultLabelDataPointsGroup.maxNumberOfLabels,\r\n });\r\n }\r\n }\r\n else {\r\n let resultsLabelDataPoints = result.labelDataPoints || [];\r\n labelDataPointGroups.push({\r\n labelDataPoints: NewDataLabelUtils.removeDuplicates(resultsLabelDataPoints),\r\n maxNumberOfLabels: resultsLabelDataPoints.length,\r\n });\r\n }\r\n\r\n labelsAreNumeric = labelsAreNumeric && result.labelsAreNumeric;\r\n }\r\n }\r\n\r\n let referenceLineLabels = this.getReferenceLineLabels(axes, plotArea);\r\n if (!_.isEmpty(referenceLineLabels)) {\r\n labelDataPointGroups.unshift({\r\n labelDataPoints: referenceLineLabels,\r\n maxNumberOfLabels: referenceLineLabels.length,\r\n });\r\n }\r\n\r\n this.renderDataLabels(\r\n labelDataPointGroups,\r\n labelsAreNumeric,\r\n plotArea,\r\n suppressAnimations,\r\n ComboChart.isComboChart(this.type));\r\n\r\n if (this.interactivityService) {\r\n let behaviorOptions: CartesianBehaviorOptions = {\r\n layerOptions: layerBehaviorOptions,\r\n clearCatcher: this.clearCatcher,\r\n };\r\n\r\n this.interactivityService.bind(dataPoints, this.behavior, behaviorOptions);\r\n }\r\n }\r\n\r\n /**\r\n * Returns the actual viewportWidth if visual is not scrollable.\r\n * @return If visual is scrollable, returns the plot area needed to draw all the datapoints.\r\n */\r\n public static getPreferredPlotArea(\r\n categoryCount: number,\r\n categoryThickness: number,\r\n viewport: IViewport,\r\n isScrollable: boolean,\r\n isScalar: boolean,\r\n margin?: IMargin,\r\n noOuterPadding?: boolean): IViewport {\r\n\r\n if (!margin)\r\n margin = { top: 0, right: 0, bottom: 0, left: 0 };\r\n\r\n let plotArea: IViewport = {\r\n height: viewport.height - margin.top - margin.bottom,\r\n width: viewport.width - margin.left - margin.right\r\n };\r\n if (!isScalar && isScrollable) {\r\n let preferredCategorySpan = CartesianChart.getPreferredCategorySpan(categoryCount, categoryThickness, noOuterPadding);\r\n plotArea.width = Math.max(preferredCategorySpan, plotArea.width);\r\n }\r\n return plotArea;\r\n }\r\n\r\n /**\r\n * Returns preferred Category span if the visual is scrollable.\r\n */\r\n public static getPreferredCategorySpan(categoryCount: number, categoryThickness: number, noOuterPadding?: boolean): number {\r\n let span = (categoryThickness * categoryCount);\r\n if (noOuterPadding)\r\n return span;\r\n return span + (categoryThickness * CartesianChart.OuterPaddingRatio * 2);\r\n }\r\n \r\n /**\r\n * Note: Public for testing access.\r\n */\r\n public static getLayout(data: ColumnChartData, options: CategoryLayoutOptions): CategoryLayout {\r\n let categoryCount = options.categoryCount,\r\n availableWidth = options.availableWidth,\r\n domain = options.domain,\r\n trimOrdinalDataOnOverflow = options.trimOrdinalDataOnOverflow,\r\n isScalar = !!options.isScalar,\r\n isScrollable = !!options.isScrollable;\r\n\r\n let categoryThickness = CartesianChart.getCategoryThickness(data ? data.series : null, categoryCount, availableWidth, domain, isScalar, trimOrdinalDataOnOverflow);\r\n\r\n // Total width of the outer padding, the padding that exist on the far right and far left of the chart.\r\n let totalOuterPadding = categoryThickness * CartesianChart.OuterPaddingRatio * 2;\r\n\r\n // visibleCategoryCount will be used to discard data that overflows on ordinal-axis charts.\r\n // Needed for dashboard visuals \r\n let calculatedBarCount = Double.floorWithPrecision((availableWidth - totalOuterPadding) / categoryThickness);\r\n let visibleCategoryCount = Math.min(calculatedBarCount, categoryCount);\r\n let willScroll = visibleCategoryCount < categoryCount && isScrollable;\r\n\r\n let outerPaddingRatio = CartesianChart.OuterPaddingRatio;\r\n if (!isScalar && !willScroll) {\r\n // use dynamic outer padding to improve spacing when we have few categories\r\n let oneOuterPadding = (availableWidth - (categoryThickness * visibleCategoryCount)) / 2;\r\n outerPaddingRatio = oneOuterPadding / categoryThickness;\r\n }\r\n\r\n // If scrollable, visibleCategoryCount will be total categories\r\n if (!isScalar && isScrollable)\r\n visibleCategoryCount = categoryCount;\r\n\r\n return {\r\n categoryCount: visibleCategoryCount,\r\n categoryThickness: categoryThickness,\r\n outerPaddingRatio: outerPaddingRatio,\r\n isScalar: isScalar\r\n };\r\n }\r\n\r\n /** \r\n * Returns the thickness for each category.\r\n * For clustered charts, you still need to divide by\r\n * the number of series to get column width after calling this method.\r\n * For linear or time scales, category thickness accomodates for\r\n * the minimum interval between consequtive points.\r\n * For all types, return value has accounted for outer padding,\r\n * but not inner padding.\r\n */\r\n public static getCategoryThickness(seriesList: CartesianSeries[], numCategories: number, plotLength: number, domain: number[], isScalar: boolean, trimOrdinalDataOnOverflow: boolean): number {\r\n let thickness;\r\n if (numCategories < 2)\r\n thickness = plotLength * (1 - CartesianChart.OuterPaddingRatio);\r\n else if (isScalar && domain && domain.length > 1) {\r\n // the smallest interval defines the column width.\r\n let minInterval = CartesianChart.getMinInterval(seriesList);\r\n let domainSpan = domain[domain.length - 1] - domain[0];\r\n // account for outside padding\r\n let ratio = minInterval / (domainSpan + (minInterval * CartesianChart.OuterPaddingRatio * 2));\r\n thickness = plotLength * ratio;\r\n thickness = Math.max(thickness, CartesianChart.MinScalarRectThickness);\r\n }\r\n else {\r\n // Divide the available width up including outer padding (in terms of category thickness) on\r\n // both sides of the chart, and categoryCount categories. Reverse math:\r\n // availableWidth = (categoryThickness * categoryCount) + (categoryThickness * (outerPadding * 2)),\r\n // availableWidth = categoryThickness * (categoryCount + (outerPadding * 2)),\r\n // categoryThickness = availableWidth / (categoryCount + (outerpadding * 2))\r\n thickness = plotLength / (numCategories + (CartesianChart.OuterPaddingRatio * 2));\r\n if (trimOrdinalDataOnOverflow) {\r\n thickness = Math.max(thickness, CartesianChart.MinOrdinalRectThickness);\r\n }\r\n }\r\n \r\n // spec calls for using the whole plot area, but the max rectangle thickness is \"as if there were three categories\"\r\n // (outerPaddingRatio has the same units as '# of categories' so they can be added)\r\n let maxRectThickness = plotLength / (3 + (CartesianChart.OuterPaddingRatio * 2));\r\n\r\n thickness = Math.min(thickness, maxRectThickness);\r\n\r\n if (!isScalar && numCategories >= 3 && trimOrdinalDataOnOverflow) {\r\n return Math.max(thickness, CartesianChart.MinOrdinalRectThickness);\r\n }\r\n\r\n return thickness;\r\n }\r\n\r\n private static getMinInterval(seriesList: CartesianSeries[]): number {\r\n let minInterval = Number.MAX_VALUE;\r\n if (seriesList.length > 0) {\r\n let series0data = seriesList[0].data.filter(d => !d.highlight);\r\n for (let i = 0, ilen = series0data.length - 1; i < ilen; i++) {\r\n minInterval = Math.min(minInterval, Math.abs(series0data[i + 1].categoryValue - series0data[i].categoryValue));\r\n }\r\n }\r\n return minInterval;\r\n }\r\n \r\n /**\r\n * Makes the necessary changes to the mapping if load more data is enabled for cartesian charts. Usually called during `customizeQuery`.\r\n */\r\n public static applyLoadMoreEnabledToMapping(cartesianLoadMoreEnabled: boolean, mapping: powerbi.data.CompiledDataViewMapping): void {\r\n const CartesianLoadMoreCategoryWindowCount: number = 100;\r\n const CartesianLoadMoreValueTopCount: number = 60;\r\n\r\n if (!cartesianLoadMoreEnabled) {\r\n return;\r\n }\r\n\r\n let categorical = mapping.categorical;\r\n\r\n if (!categorical) {\r\n return;\r\n }\r\n\r\n let categories = <data.CompiledDataViewRoleForMappingWithReduction>categorical.categories;\r\n let values = <data.CompiledDataViewGroupedRoleMapping>categorical.values;\r\n\r\n if (categories) {\r\n categories.dataReductionAlgorithm = {\r\n window: { count: CartesianLoadMoreCategoryWindowCount }\r\n };\r\n }\r\n\r\n if (values && values.group) {\r\n values.group.dataReductionAlgorithm = {\r\n top: { count: CartesianLoadMoreValueTopCount }\r\n };\r\n }\r\n }\r\n }\r\n\r\n function getLayerDataViews(dataViews: DataView[]): DataView[] {\r\n if (_.isEmpty(dataViews))\r\n return [];\r\n\r\n // TODO: figure out a more general way to correlate between layers and input data views.\r\n return _.filter(dataViews, (dataView) => !TrendLineHelper.isDataViewForRegression(dataView));\r\n }\r\n\r\n function hasMultipleYAxes(layers: ICartesianVisual[]): boolean {\r\n debug.assertValue(layers, 'layers');\r\n\r\n return layers.length > 1;\r\n }\r\n\r\n /**\r\n * Returns a boolean, that indicates if y axis title should be displayed.\r\n * @return True if y axis title should be displayed,\r\n * otherwise false.\r\n */\r\n function shouldShowYAxisLabel(layerNumber: number, valueAxisProperties: DataViewObject, yAxisWillMerge: boolean): boolean {\r\n return ((layerNumber === 0 && !!valueAxisProperties && !!valueAxisProperties['showAxisTitle']) ||\r\n (layerNumber === 1 && !yAxisWillMerge && !!valueAxisProperties && !!valueAxisProperties['secShowAxisTitle']));\r\n }\r\n\r\n function tryMergeYDomains(layers: ICartesianVisual[], visualOptions: CalculateScaleAndDomainOptions): MergedValueAxisResult {\r\n debug.assert(layers.length < 3, 'merging of more than 2 layers is not supported');\r\n\r\n let noMerge: MergedValueAxisResult = {\r\n domain: undefined,\r\n merged: false,\r\n tickCount: undefined\r\n };\r\n\r\n if (layers.length < 2)\r\n return noMerge;\r\n\r\n let min: number;\r\n let max: number;\r\n let minOfMax: number;\r\n let maxOfMin: number;\r\n\r\n // TODO: replace full calculateAxesProperties with just a data domain calc\r\n // we need to be aware of which chart require zero (column/bar) and which don't (line)\r\n let y1props = layers[0].calculateAxesProperties(visualOptions)[1];\r\n let y2props = layers[1].calculateAxesProperties(visualOptions)[1];\r\n let firstYDomain = y1props.scale.domain();\r\n let secondYDomain = y2props.scale.domain();\r\n\r\n if (y1props.values && y1props.values.length > 0 && y2props.values && y2props.values.length > 0) {\r\n noMerge.tickCount = Math.max(y1props.values.length, y2props.values.length);\r\n }\r\n\r\n min = Math.min(firstYDomain[0], secondYDomain[0]);\r\n max = Math.max(firstYDomain[1], secondYDomain[1]);\r\n\r\n if (visualOptions.forceMerge) {\r\n return {\r\n domain: [min, max],\r\n merged: true,\r\n tickCount: noMerge.tickCount\r\n };\r\n }\r\n\r\n // If domains don't intersect don't merge axis.\r\n if (firstYDomain[0] > secondYDomain[1] || firstYDomain[1] < secondYDomain[0])\r\n return noMerge;\r\n\r\n maxOfMin = Math.max(firstYDomain[0], secondYDomain[0]);\r\n minOfMax = Math.min(firstYDomain[1], secondYDomain[1]);\r\n\r\n let range = (max - min);\r\n\r\n if (range === 0) {\r\n return noMerge;\r\n }\r\n\r\n let intersection = Math.abs((minOfMax - maxOfMin) / range);\r\n\r\n // Only merge if intersection of domains greater than 10% of total range.\r\n if (intersection < COMBOCHART_DOMAIN_OVERLAP_TRESHOLD_PERCENTAGE)\r\n return noMerge;\r\n else\r\n return {\r\n domain: [min, max],\r\n merged: true,\r\n tickCount: noMerge.tickCount\r\n };\r\n }\r\n\r\n export const enum AxisLocation {\r\n X,\r\n Y1,\r\n Y2,\r\n }\r\n\r\n export interface CartesianAxesLayout {\r\n axes: CartesianAxisProperties;\r\n margin: IMargin;\r\n marginLimits: IMargin;\r\n axisLabels: ChartAxesLabels;\r\n viewport: IViewport;\r\n plotArea: IViewport;\r\n preferredPlotArea: IViewport;\r\n tickLabelMargins: any;\r\n tickPadding: IMargin;\r\n rotateXTickLabels90?: boolean;\r\n }\r\n\r\n class SvgBrush {\r\n private element: D3.Selection;\r\n private brushGraphicsContext: D3.Selection;\r\n private brush: D3.Svg.Brush;\r\n private brushWidth: number;\r\n private scrollCallback: () => void;\r\n private isHorizontal: boolean;\r\n private brushStartExtent: number[];\r\n\r\n private static Brush = createClassAndSelector('brush');\r\n\r\n constructor(brushWidth: number) {\r\n this.brush = d3.svg.brush();\r\n this.brushWidth = brushWidth;\r\n }\r\n\r\n public init(element: D3.Selection): void {\r\n this.element = element;\r\n }\r\n\r\n public remove(): void {\r\n this.element.selectAll(SvgBrush.Brush.selector).remove();\r\n this.brushGraphicsContext = undefined;\r\n }\r\n\r\n public getExtent(): number[] {\r\n return this.brush.extent();\r\n }\r\n\r\n public setExtent(extent: number[]): void {\r\n this.brush.extent(extent);\r\n }\r\n\r\n public setScale(scale: D3.Scale.OrdinalScale): void {\r\n if (this.isHorizontal)\r\n this.brush.x(scale);\r\n else\r\n this.brush.y(scale);\r\n }\r\n\r\n public setOrientation(isHorizontal: boolean): void {\r\n this.isHorizontal = isHorizontal;\r\n }\r\n\r\n public renderBrush(\r\n extentLength: number,\r\n brushX: number,\r\n brushY: number,\r\n scrollCallback: () => void): void {\r\n\r\n // create graphics context if it doesn't exist\r\n if (!this.brushGraphicsContext) {\r\n this.brushGraphicsContext = this.element.append(\"g\")\r\n .classed(SvgBrush.Brush.class, true);\r\n }\r\n\r\n this.scrollCallback = scrollCallback;\r\n \r\n // events\r\n this.brush\r\n .on(\"brushstart\", () => this.brushStartExtent = this.brush.extent())\r\n .on(\"brush\", () => {\r\n window.requestAnimationFrame(scrollCallback);\r\n })\r\n .on(\"brushend\", () => {\r\n this.resizeExtent(extentLength);\r\n this.updateExtentPosition(extentLength);\r\n this.brushStartExtent = null;\r\n });\r\n\r\n // position the graphics context\r\n let brushContext = this.brushGraphicsContext\r\n .attr({\r\n \"transform\": SVGUtil.translate(brushX, brushY),\r\n \"drag-resize-disabled\": \"true\" /* Disables resizing of the visual when dragging the scrollbar in edit mode */\r\n })\r\n .call(this.brush);\r\n \r\n // Disable the zooming feature by removing the resize elements\r\n brushContext.selectAll(\".resize\")\r\n .remove();\r\n\r\n if (this.isHorizontal)\r\n brushContext.selectAll(\"rect\").attr(\"height\", this.brushWidth);\r\n else\r\n brushContext.selectAll(\"rect\").attr(\"width\", this.brushWidth);\r\n }\r\n\r\n public scroll(scrollBarLength: number): void {\r\n this.updateExtentPosition(scrollBarLength);\r\n this.scrollCallback();\r\n }\r\n\r\n private updateExtentPosition(scrollBarLength: number): void {\r\n let extent = this.brush.extent();\r\n debug.assertNonEmpty(extent, 'updateExtentPosition, extent');\r\n let newStartPos = extent[0];\r\n let halfScrollBarLen = scrollBarLength / 2;\r\n\r\n if (extent[0] === extent[1]) {\r\n // user clicked on the brush background, width will be zero, offset x by half width\r\n newStartPos = newStartPos - halfScrollBarLen;\r\n }\r\n\r\n if (extent[1] - extent[0] > scrollBarLength) {\r\n // user is dragging one edge after mousedown in the background, figure out which side is moving\r\n // also, center up on the new extent center\r\n let halfDragLength = (extent[1] - extent[0]) / 2;\r\n if (extent[0] < this.brushStartExtent[0])\r\n newStartPos = extent[0] + halfDragLength - halfScrollBarLen;\r\n else\r\n newStartPos = extent[1] - halfDragLength - halfScrollBarLen;\r\n }\r\n\r\n if (this.isHorizontal)\r\n this.brushGraphicsContext.select(\".extent\").attr('x', newStartPos);\r\n else\r\n this.brushGraphicsContext.select(\".extent\").attr('y', newStartPos);\r\n }\r\n\r\n private resizeExtent(extentLength: number): void {\r\n if (this.isHorizontal)\r\n this.brushGraphicsContext.select(\".extent\").attr(\"width\", extentLength);\r\n else\r\n this.brushGraphicsContext.select(\".extent\").attr(\"height\", extentLength);\r\n }\r\n }\r\n\r\n class ScrollableAxes {\r\n public static ScrollbarWidth = 10;\r\n\r\n private brush: SvgBrush;\r\n private brushMinExtent: number;\r\n private scrollScale: D3.Scale.OrdinalScale;\r\n private axisScale: D3.Scale.OrdinalScale;\r\n\r\n private axes: CartesianAxes;\r\n\r\n constructor(axes: CartesianAxes, svgBrush: SvgBrush) {\r\n this.axes = axes;\r\n this.brush = svgBrush;\r\n }\r\n\r\n private filterDataToViewport(\r\n mainAxisScale: D3.Scale.OrdinalScale,\r\n layers: ICartesianVisual[],\r\n axes: CartesianAxisProperties,\r\n scrollScale: D3.Scale.OrdinalScale,\r\n extent: number[],\r\n visibleCategoryCount: number): ViewportDataRange {\r\n\r\n if (!scrollScale) {\r\n return;\r\n }\r\n\r\n let selected: number[];\r\n let data: CartesianData[] = [];\r\n\r\n // NOTE: using start + numVisibleCategories to make sure we don't have issues with exactness related to extent start/end\r\n // (don't use extent[1])\r\n /*\r\n When extent[0] and extent[1] are very close to the boundary of a new index, due to floating point err,\r\n the \"start\" might move to the next index but the \"end\" might not change until you slide one more pixel.\r\n It makes things really jittery during scrolling, sometimes you see N columns and sometimes you briefly see N+1.\r\n */\r\n let startIndex = AxisHelper.lookupOrdinalIndex(scrollScale, extent[0]);\r\n let endIndex = startIndex + visibleCategoryCount; // NOTE: intentionally 1 past end index\r\n\r\n let domain = scrollScale.domain();\r\n selected = domain.slice(startIndex, endIndex); // NOTE: Up to but not including 'end'\r\n if (selected && selected.length > 0) {\r\n for (let i = 0; i < layers.length; i++) {\r\n data[i] = layers[i].setFilteredData(selected[0], selected[selected.length - 1] + 1);\r\n }\r\n mainAxisScale.domain(selected);\r\n\r\n let axisPropsToUpdate: IAxisProperties;\r\n if (this.axes.isXScrollBarVisible) {\r\n axisPropsToUpdate = axes.x;\r\n }\r\n else {\r\n axisPropsToUpdate = axes.y1;\r\n }\r\n\r\n axisPropsToUpdate.axis.scale(mainAxisScale);\r\n axisPropsToUpdate.scale(mainAxisScale);\r\n\r\n // tick values are indices for ordinal axes\r\n axisPropsToUpdate.axis.ticks(selected.length);\r\n axisPropsToUpdate.axis.tickValues(selected);\r\n\r\n // use the original tick format to format the tick values\r\n let tickFormat = axisPropsToUpdate.axis.tickFormat();\r\n axisPropsToUpdate.values = _.map(selected, (d) => tickFormat(d));\r\n }\r\n\r\n return {\r\n startIndex: startIndex,\r\n endIndex: endIndex - 1 // Subtract 1 since it's actually 1 past the end index\r\n };\r\n }\r\n\r\n public render(\r\n axesLayout: CartesianAxesLayout,\r\n layers: ICartesianVisual[],\r\n suppressAnimations: boolean,\r\n renderDelegate: RenderPlotAreaDelegate,\r\n loadMoreDataHandler?: CartesianLoadMoreDataHandler,\r\n preserveScrollPosition?: boolean): void {\r\n\r\n let plotArea = axesLayout.plotArea;\r\n\r\n if (plotArea.width < 1 || plotArea.height < 1)\r\n return; //do nothing - too small\r\n\r\n this.axisScale = null;\r\n let brushX: number;\r\n let brushY: number;\r\n let scrollbarLength: number;\r\n let numVisibleCategories: number;\r\n let categoryThickness: number;\r\n let newAxisLength: number;\r\n\r\n let showingScrollBar = this.axes.isXScrollBarVisible || this.axes.isYScrollBarVisible;\r\n\r\n // If the scrollbars are visible, calculate values.\r\n // We also need to calculate values if we have a loadMoreData handler since they're used to make sure we have enough \r\n // data to fill the viewport (even if there aren't any scrollbars).\r\n if (loadMoreDataHandler || showingScrollBar) {\r\n if (!this.axes.isYAxisCategorical()) {\r\n this.axisScale = <D3.Scale.OrdinalScale>axesLayout.axes.x.scale;\r\n brushX = axesLayout.margin.left;\r\n brushY = axesLayout.viewport.height;\r\n categoryThickness = axesLayout.axes.x.categoryThickness;\r\n let outerPadding = axesLayout.axes.x.outerPadding;\r\n numVisibleCategories = Double.floorWithPrecision((plotArea.width - outerPadding * 2) / categoryThickness);\r\n scrollbarLength = (numVisibleCategories + 1) * categoryThickness;\r\n newAxisLength = plotArea.width;\r\n }\r\n else {\r\n this.axisScale = <D3.Scale.OrdinalScale>axesLayout.axes.y1.scale;\r\n brushX = axesLayout.viewport.width;\r\n brushY = axesLayout.margin.top;\r\n categoryThickness = axesLayout.axes.y1.categoryThickness;\r\n let outerPadding = axesLayout.axes.y1.outerPadding;\r\n numVisibleCategories = Double.floorWithPrecision((plotArea.height - outerPadding * 2) / categoryThickness);\r\n scrollbarLength = (numVisibleCategories + 1) * categoryThickness;\r\n newAxisLength = plotArea.height;\r\n }\r\n }\r\n\r\n // No scrollbars, render the chart normally.\r\n if (!showingScrollBar) {\r\n\r\n // Load more data if we don't have enough.\r\n // The window size should be big enough so we don't hit this code, but it's here as a backup.\r\n if (loadMoreDataHandler) {\r\n loadMoreDataHandler.viewportDataRange = { startIndex: 0, endIndex: numVisibleCategories };\r\n\r\n if (loadMoreDataHandler.shouldLoadMoreData()) {\r\n loadMoreDataHandler.loadMoreData();\r\n }\r\n }\r\n\r\n this.brush.remove();\r\n renderDelegate(layers, axesLayout, suppressAnimations);\r\n return;\r\n }\r\n\r\n // viewport is REALLY small\r\n if (numVisibleCategories < 1) {\r\n return; // don't do anything\r\n }\r\n\r\n this.scrollScale = this.axisScale.copy();\r\n this.scrollScale.rangeBands([0, scrollbarLength]); //no inner/outer padding, keep the math simple\r\n this.brushMinExtent = this.scrollScale(numVisibleCategories - 1);\r\n\r\n // Options: use newAxisLength to squeeze-pop and keep the chart balanced, \r\n // or use scrollbarLength to keep rects still - but it leaves unbalanced right edge\r\n // 1. newAxisLength ex: As you resize smaller we constantly adjust the inner/outer padding to keep things balanced with the same # of rects, \r\n // when we need to drop a rect we pop out the rectangle and the padding seems to jump (to keep things cenetered and balanced). \r\n // 2. scrollbarLenghth ex: As you resize smaller we can leave all rectangles in the exact same place, no squeezing inner/outer padding,\r\n // when we need to drop a rect we just remove it - but this leaves the right side with lots of empty room (bad for dashboard tiles)\r\n // we are using option 1 to squeeze pop and show balanced layout at all sizes, but this is the less ideal experience during resize.\r\n // we should consider using option 2 during resize, then switch to option 1 when resize ends.\r\n this.axisScale.rangeBands([0, newAxisLength], CartesianChart.InnerPaddingRatio, CartesianChart.OuterPaddingRatio);\r\n\r\n this.brush.setOrientation(this.axes.isXScrollBarVisible);\r\n this.brush.setScale(this.scrollScale);\r\n this.brush.setExtent([0, this.brushMinExtent]);\r\n\r\n // This function will be called whenever we scroll.\r\n let renderOnScroll = (extent: number[], suppressAnimations: boolean) => {\r\n let dataRange = this.filterDataToViewport(this.axisScale, layers, axesLayout.axes, this.scrollScale, extent, numVisibleCategories);\r\n \r\n if (loadMoreDataHandler) {\r\n loadMoreDataHandler.viewportDataRange = dataRange;\r\n \r\n if (loadMoreDataHandler.shouldLoadMoreData()) {\r\n loadMoreDataHandler.loadMoreData();\r\n }\r\n }\r\n\r\n renderDelegate(layers, axesLayout, suppressAnimations);\r\n };\r\n\r\n let scrollCallback = () => this.onBrushed(scrollbarLength, renderOnScroll);\r\n this.brush.renderBrush(this.brushMinExtent, brushX, brushY, scrollCallback);\r\n\r\n // Either scroll to the specified location or simply render the visual.\r\n if (preserveScrollPosition && loadMoreDataHandler) {\r\n let startIndex = loadMoreDataHandler.viewportDataRange ? loadMoreDataHandler.viewportDataRange.startIndex : 0;\r\n\r\n // Clamp 1st to update the size of the extent, then scroll to the new index\r\n ScrollableAxes.clampBrushExtent(this.brush, scrollbarLength, this.brushMinExtent);\r\n\r\n // ScrollTo takes care of the rendering\r\n this.scrollTo(startIndex);\r\n }\r\n else {\r\n renderOnScroll(this.brush.getExtent(), suppressAnimations);\r\n }\r\n }\r\n\r\n public scrollDelta(delta): void {\r\n if (this.axisScale && !_.isEmpty(this.axisScale.domain())) {\r\n let currentStartIndex = this.axisScale.domain()[0];\r\n let newStartIndex = currentStartIndex + Math.round(delta / CartesianChart.MinOrdinalRectThickness);\r\n this.scrollTo(newStartIndex);\r\n }\r\n }\r\n\r\n // PUBLIC FOR UNIT TESTING ONLY\r\n public scrollTo(startIndex: number): void {\r\n debug.assert(this.axes.isXScrollBarVisible || this.axes.isYScrollBarVisible, 'scrolling is not available');\r\n debug.assertValue(this.scrollScale, 'scrollScale');\r\n\r\n let lastIndex = _.last(this.scrollScale.domain());\r\n startIndex = Math.max(0, Math.min(startIndex, lastIndex));\r\n\r\n let extent = this.brush.getExtent();\r\n let extentLength = extent[1] - extent[0];\r\n let halfCategoryThickness = (this.scrollScale(1) - this.scrollScale(0)) / 2;\r\n extent[0] = this.scrollScale(startIndex) + halfCategoryThickness;\r\n extent[1] = extent[0] + extentLength + halfCategoryThickness;\r\n this.brush.setExtent(extent);\r\n\r\n let scrollbarLength = this.scrollScale.rangeExtent()[1];\r\n ScrollableAxes.clampBrushExtent(this.brush, scrollbarLength, this.brushMinExtent);\r\n this.brush.scroll(scrollbarLength);\r\n }\r\n\r\n private onBrushed(scrollbarLength: number, render: (extent: number[], suppressAnimations: boolean) => void): void {\r\n let brush = this.brush;\r\n\r\n ScrollableAxes.clampBrushExtent(this.brush, scrollbarLength, this.brushMinExtent);\r\n let extent = brush.getExtent();\r\n render(extent, /*suppressAnimations*/ true);\r\n }\r\n\r\n private static clampBrushExtent(brush: SvgBrush, scrollbarLength: number, minExtent: number): void {\r\n let extent = brush.getExtent();\r\n let width = extent[1] - extent[0];\r\n\r\n if (width === minExtent && extent[1] <= scrollbarLength && extent[0] >= 0)\r\n return;\r\n\r\n if (width > minExtent) {\r\n let padding = (width - minExtent) / 2;\r\n extent[0] += padding;\r\n extent[1] -= padding;\r\n }\r\n\r\n else if (width < minExtent) {\r\n let padding = (minExtent - width) / 2;\r\n extent[0] -= padding;\r\n extent[1] += padding;\r\n }\r\n\r\n if (extent[0] < 0) {\r\n extent[0] = 0;\r\n extent[1] = minExtent;\r\n }\r\n\r\n else if (extent[0] > scrollbarLength - minExtent) {\r\n extent[0] = scrollbarLength - minExtent;\r\n extent[1] = scrollbarLength;\r\n }\r\n\r\n brush.setExtent(extent);\r\n }\r\n }\r\n\r\n export class SvgCartesianAxes {\r\n // These match D3's internal axis padding values\r\n public static AxisPadding: IMargin = {\r\n left: 10,\r\n right: 10,\r\n top: 0,\r\n bottom: 13, // don't change this - fixes defect 6658705 when in \"Fit-to-Page\" view and the scale factor is greater than 1.0\r\n };\r\n\r\n private axisGraphicsContext: D3.Selection;\r\n private xAxisGraphicsContext: D3.Selection;\r\n private y1AxisGraphicsContext: D3.Selection;\r\n private y2AxisGraphicsContext: D3.Selection;\r\n private svgScrollable: D3.Selection;\r\n private axisGraphicsContextScrollable: D3.Selection;\r\n private labelRegion: D3.Selection;\r\n private labelBackgroundRegion: D3.Selection;\r\n\r\n private categoryAxisProperties: DataViewObject;\r\n private valueAxisProperties: DataViewObject;\r\n\r\n private static AxisGraphicsContext = createClassAndSelector('axisGraphicsContext');\r\n private static TickPaddingRotatedX = 5;\r\n private static AxisLabelFontSize = 11;\r\n private static Y2TickSize = -6;\r\n\r\n constructor(private axes: CartesianAxes) {\r\n }\r\n\r\n public getScrollableRegion(): D3.Selection {\r\n return this.axisGraphicsContextScrollable;\r\n }\r\n\r\n public getLabelsRegion(): D3.Selection {\r\n return this.labelRegion;\r\n }\r\n\r\n public getLabelBackground(): D3.Selection {\r\n return this.labelBackgroundRegion;\r\n }\r\n\r\n public getXAxis(): D3.Selection {\r\n return this.xAxisGraphicsContext;\r\n }\r\n\r\n public getY1Axis(): D3.Selection {\r\n return this.y1AxisGraphicsContext;\r\n }\r\n\r\n public getY2Axis(): D3.Selection {\r\n return this.y2AxisGraphicsContext;\r\n }\r\n\r\n public update(categoryAxisProperties: DataViewObject, valueAxisProperties: DataViewObject): void {\r\n this.categoryAxisProperties = categoryAxisProperties;\r\n this.valueAxisProperties = valueAxisProperties;\r\n }\r\n\r\n public init(svg: D3.Selection): void {\r\n /*\r\n The layout of the visual will look like:\r\n <svg>\r\n <g>\r\n <nonscrollable axis/>\r\n </g>\r\n <svgScrollable>\r\n <g>\r\n <scrollable axis/>\r\n </g>\r\n </svgScrollable>\r\n <g xbrush/>\r\n </svg>\r\n\r\n */\r\n\r\n let axisGraphicsContext = this.axisGraphicsContext = svg.append('g')\r\n .classed(SvgCartesianAxes.AxisGraphicsContext.class, true);\r\n\r\n this.svgScrollable = svg.append('svg')\r\n .classed('svgScrollable', true)\r\n .style('overflow', 'hidden');\r\n\r\n let axisGraphicsContextScrollable = this.axisGraphicsContextScrollable = this.svgScrollable.append('g')\r\n .classed(SvgCartesianAxes.AxisGraphicsContext.class, true);\r\n\r\n this.labelBackgroundRegion = this.svgScrollable.append('g')\r\n .classed(NewDataLabelUtils.labelBackgroundGraphicsContextClass.class, true);\r\n\r\n this.labelRegion = this.svgScrollable.append('g')\r\n .classed(NewDataLabelUtils.labelGraphicsContextClass.class, true);\r\n\r\n let showLinesOnX = this.axes.showLinesOnX;\r\n let showLinesOnY = this.axes.showLinesOnY;\r\n\r\n // NOTE: We infer the axis which should scroll based on whether or not we draw grid lines for the other axis, and\r\n // only allow one axis to scroll.\r\n let scrollX = showLinesOnY;\r\n let scrollY = !scrollX;\r\n if (scrollY) {\r\n this.y1AxisGraphicsContext = axisGraphicsContextScrollable.append('g').attr('class', 'y axis');\r\n this.y2AxisGraphicsContext = axisGraphicsContextScrollable.append('g').attr('class', 'y axis');\r\n }\r\n else {\r\n this.y1AxisGraphicsContext = axisGraphicsContext.append('g').attr('class', 'y axis');\r\n this.y2AxisGraphicsContext = axisGraphicsContext.append('g').attr('class', 'y axis');\r\n }\r\n\r\n if (scrollX) {\r\n this.xAxisGraphicsContext = axisGraphicsContextScrollable.append('g').attr('class', 'x axis');\r\n }\r\n else {\r\n this.xAxisGraphicsContext = axisGraphicsContext.append('g').attr('class', 'x axis');\r\n }\r\n\r\n this.xAxisGraphicsContext.classed('showLinesOnAxis', showLinesOnX);\r\n this.y1AxisGraphicsContext.classed('showLinesOnAxis', showLinesOnY);\r\n this.y2AxisGraphicsContext.classed('showLinesOnAxis', showLinesOnY);\r\n\r\n this.xAxisGraphicsContext.classed('hideLinesOnAxis', !showLinesOnX);\r\n this.y1AxisGraphicsContext.classed('hideLinesOnAxis', !showLinesOnY);\r\n this.y2AxisGraphicsContext.classed('hideLinesOnAxis', !showLinesOnY);\r\n }\r\n\r\n private static updateAnimatedTickTooltips(axisSelection: D3.Transition.Transition, values: any[]): void {\r\n axisSelection.each('end', function () {\r\n d3.select(this)\r\n .selectAll('text')\r\n .append('title')\r\n .text((d, i) => values[i]);\r\n });\r\n }\r\n\r\n private static updateTickTooltips(axisSelection: D3.Selection, values: any[]): void {\r\n axisSelection.selectAll('text').append('title').text((d, i) => values[i]);\r\n }\r\n\r\n public renderAxes(axesLayout: CartesianAxesLayout, duration: number, easing: string = 'cubic-in-out'): void {\r\n let marginLimits = axesLayout.marginLimits;\r\n let plotArea = axesLayout.plotArea;\r\n let viewport = axesLayout.viewport;\r\n let margin = axesLayout.margin;\r\n let axes = axesLayout.axes;\r\n let tickLabelMargins = axesLayout.tickLabelMargins;\r\n\r\n let bottomMarginLimit = marginLimits.bottom;\r\n let leftRightMarginLimit = marginLimits.left;\r\n\r\n let xLabelColor: Fill;\r\n let yLabelColor: Fill;\r\n let y2LabelColor: Fill;\r\n\r\n if (this.axes.shouldRenderAxis(axes.x)) {\r\n if (axes.x.isCategoryAxis) {\r\n xLabelColor = this.categoryAxisProperties && this.categoryAxisProperties['labelColor'] ? this.categoryAxisProperties['labelColor'] : null;\r\n } else {\r\n xLabelColor = this.valueAxisProperties && this.valueAxisProperties['labelColor'] ? this.valueAxisProperties['labelColor'] : null;\r\n }\r\n axes.x.axis.orient(\"bottom\");\r\n // we only rotate ordinal tick labels\r\n if (!axes.x.willLabelsFit && AxisHelper.isOrdinalScale(axes.x.scale))\r\n axes.x.axis.tickPadding(SvgCartesianAxes.TickPaddingRotatedX);\r\n\r\n let xAxisGraphicsElement = this.xAxisGraphicsContext;\r\n if (duration) {\r\n xAxisGraphicsElement\r\n .transition()\r\n .duration(duration)\r\n .ease(easing)\r\n .call(axes.x.axis)\r\n .call(SvgCartesianAxes.updateAnimatedTickTooltips, axes.x.values);\r\n }\r\n else {\r\n xAxisGraphicsElement\r\n .call(axes.x.axis);\r\n }\r\n\r\n xAxisGraphicsElement\r\n .call(SvgCartesianAxes.darkenZeroLine)\r\n .call(SvgCartesianAxes.setAxisLabelColor, xLabelColor);\r\n\r\n let xAxisTextNodes = xAxisGraphicsElement.selectAll('text');\r\n if (axes.x.willLabelsWordBreak) {\r\n xAxisTextNodes\r\n .call(AxisHelper.LabelLayoutStrategy.wordBreak, axes.x, bottomMarginLimit);\r\n } else {\r\n xAxisTextNodes\r\n .call(AxisHelper.LabelLayoutStrategy.rotate,\r\n bottomMarginLimit,\r\n TextMeasurementService.getTailoredTextOrDefault,\r\n CartesianChart.AxisTextProperties,\r\n !axes.x.willLabelsFit && AxisHelper.isOrdinalScale(axes.x.scale),\r\n bottomMarginLimit === tickLabelMargins.xMax,\r\n axes.x,\r\n margin,\r\n this.axes.isXScrollBarVisible || this.axes.isYScrollBarVisible);\r\n }\r\n\r\n if (!duration) {\r\n SvgCartesianAxes.updateTickTooltips(xAxisGraphicsElement, axes.x.values);\r\n }\r\n }\r\n else {\r\n this.xAxisGraphicsContext.selectAll('*').remove();\r\n }\r\n\r\n if (this.axes.shouldRenderAxis(axes.y1)) {\r\n if (axes.y1.isCategoryAxis) {\r\n yLabelColor = this.categoryAxisProperties && this.categoryAxisProperties['labelColor'] ? this.categoryAxisProperties['labelColor'] : null;\r\n } else {\r\n yLabelColor = this.valueAxisProperties && this.valueAxisProperties['labelColor'] ? this.valueAxisProperties['labelColor'] : null;\r\n }\r\n let showY1OnRight = this.axes.shouldShowY1OnRight();\r\n let y1TickPadding = showY1OnRight ? axesLayout.tickPadding.right : axesLayout.tickPadding.left;\r\n axes.y1.axis\r\n .tickSize(-plotArea.width)\r\n .tickPadding(y1TickPadding)\r\n .orient(this.axes.getYAxisOrientation().toLowerCase());\r\n\r\n let y1AxisGraphicsElement = this.y1AxisGraphicsContext;\r\n if (duration) {\r\n y1AxisGraphicsElement\r\n .transition()\r\n .duration(duration)\r\n .ease(easing)\r\n .call(axes.y1.axis)\r\n .call(SvgCartesianAxes.updateAnimatedTickTooltips, axes.y1.values);\r\n }\r\n else {\r\n y1AxisGraphicsElement\r\n .call(axes.y1.axis);\r\n }\r\n\r\n y1AxisGraphicsElement\r\n .call(SvgCartesianAxes.darkenZeroLine)\r\n .call(SvgCartesianAxes.setAxisLabelColor, yLabelColor);\r\n\r\n if (tickLabelMargins.yLeft >= leftRightMarginLimit) {\r\n y1AxisGraphicsElement.selectAll('text')\r\n .call(AxisHelper.LabelLayoutStrategy.clip,\r\n // Can't use padding space to render text, so subtract that from available space for ellipses calculations\r\n leftRightMarginLimit - y1TickPadding,\r\n TextMeasurementService.svgEllipsis);\r\n }\r\n\r\n if (!duration) {\r\n SvgCartesianAxes.updateTickTooltips(y1AxisGraphicsElement, axes.y1.values);\r\n }\r\n\r\n if (axes.y2 && (!this.valueAxisProperties || this.valueAxisProperties['secShow'] == null || this.valueAxisProperties['secShow'])) {\r\n y2LabelColor = this.valueAxisProperties && this.valueAxisProperties['secLabelColor'] ? this.valueAxisProperties['secLabelColor'] : null;\r\n\r\n let y2TickPadding = showY1OnRight ? axesLayout.tickPadding.left : axesLayout.tickPadding.right;\r\n axes.y2.axis\r\n .tickSize(SvgCartesianAxes.Y2TickSize)\r\n .tickPadding(y2TickPadding)\r\n .orient(showY1OnRight ? yAxisPosition.left.toLowerCase() : yAxisPosition.right.toLowerCase());\r\n\r\n let y2AxisGraphicsElement = this.y2AxisGraphicsContext;\r\n if (duration) {\r\n y2AxisGraphicsElement\r\n .transition()\r\n .duration(duration)\r\n .ease(easing)\r\n .call(axes.y2.axis)\r\n .call(SvgCartesianAxes.updateAnimatedTickTooltips, axes.y2.values);\r\n }\r\n else {\r\n y2AxisGraphicsElement\r\n .call(axes.y2.axis);\r\n }\r\n\r\n y2AxisGraphicsElement\r\n .call(SvgCartesianAxes.darkenZeroLine)\r\n .call(SvgCartesianAxes.setAxisLabelColor, y2LabelColor);\r\n\r\n if (tickLabelMargins.yRight >= leftRightMarginLimit) {\r\n y2AxisGraphicsElement.selectAll('text')\r\n .call(AxisHelper.LabelLayoutStrategy.clip,\r\n // Can't use padding space to render text, so subtract that from available space for ellipses calculations\r\n leftRightMarginLimit - y2TickPadding,\r\n TextMeasurementService.svgEllipsis);\r\n }\r\n\r\n if (!duration) {\r\n SvgCartesianAxes.updateTickTooltips(y2AxisGraphicsElement, axes.y2.values);\r\n }\r\n }\r\n else {\r\n this.y2AxisGraphicsContext.selectAll('*').remove();\r\n }\r\n }\r\n else {\r\n this.y1AxisGraphicsContext.selectAll('*').remove();\r\n this.y2AxisGraphicsContext.selectAll('*').remove();\r\n }\r\n\r\n // Axis labels\r\n //TODO: Add label for second Y axis for combo chart\r\n let axisLabels = axesLayout.axisLabels;\r\n let chartHasAxisLabels = (axisLabels.x != null) || (axisLabels.y != null || axisLabels.y2 != null);\r\n if (chartHasAxisLabels) {\r\n let hideXAxisTitle = !this.axes.shouldRenderAxisTitle(axes.x, /* defaultValue */ true, /* secondary */ false);\r\n let hideYAxisTitle = !this.axes.shouldRenderAxisTitle(axes.y1, /* defaultValue */ true, /* secondary */false);\r\n let hideY2AxisTitle = !this.axes.shouldRenderAxisTitle(axes.y2, /* defaultValue */ false, /* secondary */true);\r\n\r\n let renderAxisOptions: AxisRenderingOptions = {\r\n axisLabels: axisLabels,\r\n viewport: viewport,\r\n margin: margin,\r\n hideXAxisTitle: hideXAxisTitle,\r\n hideYAxisTitle: hideYAxisTitle,\r\n hideY2AxisTitle: hideY2AxisTitle,\r\n xLabelColor: xLabelColor,\r\n yLabelColor: yLabelColor,\r\n y2LabelColor: y2LabelColor,\r\n fontSize: SvgCartesianAxes.AxisLabelFontSize,\r\n };\r\n\r\n this.renderAxesLabels(renderAxisOptions);\r\n }\r\n else {\r\n this.axisGraphicsContext.selectAll('.xAxisLabel').remove();\r\n this.axisGraphicsContext.selectAll('.yAxisLabel').remove();\r\n }\r\n\r\n this.translateAxes(viewport, margin);\r\n }\r\n\r\n private renderAxesLabels(options: AxisRenderingOptions): void {\r\n debug.assertValue(options, 'options');\r\n debug.assertValue(options.viewport, 'options.viewport');\r\n debug.assertValue(options.axisLabels, 'options.axisLabels');\r\n\r\n this.axisGraphicsContext.selectAll('.xAxisLabel').remove();\r\n this.axisGraphicsContext.selectAll('.yAxisLabel').remove();\r\n\r\n let margin = options.margin;\r\n let width = options.viewport.width - (margin.left + margin.right);\r\n let height = options.viewport.height;\r\n let fontSize = options.fontSize;\r\n\r\n let axisTextProperties = _.clone(CartesianChart.AxisTextProperties);\r\n axisTextProperties.fontSize = fontSize + \"px\";\r\n\r\n let showOnRight = this.axes.shouldShowY1OnRight();\r\n\r\n if (!options.hideXAxisTitle) {\r\n axisTextProperties.text = options.axisLabels.x;\r\n let heightOffset = TextMeasurementService.estimateSvgTextHeight(axisTextProperties);\r\n\r\n let xAxisLabel = this.axisGraphicsContext.append(\"text\")\r\n .style(\"text-anchor\", \"middle\")\r\n .text(options.axisLabels.x)\r\n .call((text: D3.Selection) => {\r\n text.each(function () {\r\n let text = d3.select(this);\r\n text.attr({\r\n \"class\": \"xAxisLabel\",\r\n \"transform\": SVGUtil.translate(width / 2, height - heightOffset)\r\n });\r\n });\r\n });\r\n\r\n xAxisLabel.style(\"fill\", options.xLabelColor ? options.xLabelColor.solid.color : null);\r\n\r\n xAxisLabel.call(AxisHelper.LabelLayoutStrategy.clip,\r\n width,\r\n TextMeasurementService.svgEllipsis)\r\n .call(tooltipUtils.tooltipUpdate, [options.axisLabels.x]);\r\n }\r\n\r\n if (!options.hideYAxisTitle) {\r\n axisTextProperties.text = options.axisLabels.y;\r\n let textHeight = TextMeasurementService.estimateSvgTextHeight(axisTextProperties);\r\n\r\n let yAxisLabel = this.axisGraphicsContext.append(\"text\")\r\n .style(\"text-anchor\", \"middle\")\r\n .text(options.axisLabels.y)\r\n .call((text: D3.Selection) => {\r\n text.each(function () {\r\n let text = d3.select(this);\r\n text.attr({\r\n \"class\": \"yAxisLabel\",\r\n \"transform\": \"rotate(-90)\",\r\n \"y\": showOnRight ? width + margin.right - textHeight : -margin.left,\r\n \"x\": -((height - margin.top - margin.bottom) / 2),\r\n \"dy\": \"1em\",\r\n });\r\n });\r\n });\r\n\r\n yAxisLabel.style(\"fill\", options.yLabelColor ? options.yLabelColor.solid.color : null);\r\n\r\n yAxisLabel.call(AxisHelper.LabelLayoutStrategy.clip,\r\n height - (margin.bottom + margin.top),\r\n TextMeasurementService.svgEllipsis)\r\n .call(tooltipUtils.tooltipUpdate, [options.axisLabels.y]);\r\n }\r\n\r\n if (!options.hideY2AxisTitle && options.axisLabels.y2) {\r\n axisTextProperties.text = options.axisLabels.y2;\r\n let textHeight = TextMeasurementService.estimateSvgTextHeight(axisTextProperties);\r\n\r\n let y2AxisLabel = this.axisGraphicsContext.append(\"text\")\r\n .style(\"text-anchor\", \"middle\")\r\n .text(options.axisLabels.y2)\r\n .call((text: D3.Selection) => {\r\n text.each(function () {\r\n let text = d3.select(this);\r\n text.attr({\r\n \"class\": \"yAxisLabel\",\r\n \"transform\": \"rotate(-90)\",\r\n \"y\": showOnRight ? -margin.left : width + margin.right - textHeight,\r\n \"x\": -((height - margin.top - margin.bottom) / 2),\r\n \"dy\": \"1em\",\r\n });\r\n });\r\n });\r\n\r\n y2AxisLabel.style(\"fill\", options.y2LabelColor ? options.y2LabelColor.solid.color : null);\r\n\r\n y2AxisLabel.call(AxisHelper.LabelLayoutStrategy.clip,\r\n height - (margin.bottom + margin.top),\r\n TextMeasurementService.svgEllipsis)\r\n .call(tooltipUtils.tooltipUpdate, [options.axisLabels.y2]);\r\n }\r\n }\r\n\r\n // Margin convention: http://bl.ocks.org/mbostock/3019563\r\n private translateAxes(viewport: IViewport, margin: IMargin): void {\r\n let width = viewport.width - (margin.left + margin.right);\r\n let height = viewport.height - (margin.top + margin.bottom);\r\n\r\n let showY1OnRight = this.axes.shouldShowY1OnRight();\r\n\r\n this.xAxisGraphicsContext\r\n .attr('transform', SVGUtil.translate(0, height));\r\n\r\n this.y1AxisGraphicsContext\r\n .attr('transform', SVGUtil.translate(showY1OnRight ? width : 0, 0));\r\n\r\n this.y2AxisGraphicsContext\r\n .attr('transform', SVGUtil.translate(showY1OnRight ? 0 : width, 0));\r\n\r\n this.svgScrollable.attr({\r\n 'x': 0,\r\n 'width': viewport.width,\r\n 'height': viewport.height\r\n });\r\n\r\n this.axisGraphicsContext.attr('transform', SVGUtil.translate(margin.left, margin.top));\r\n this.axisGraphicsContextScrollable.attr('transform', SVGUtil.translate(margin.left, margin.top));\r\n this.labelRegion.attr('transform', SVGUtil.translate(margin.left, margin.top));\r\n this.labelBackgroundRegion.attr('transform', SVGUtil.translate(margin.left, margin.top));\r\n\r\n if (this.axes.isXScrollBarVisible) {\r\n this.svgScrollable.attr({\r\n 'x': margin.left\r\n });\r\n this.axisGraphicsContextScrollable.attr('transform', SVGUtil.translate(0, margin.top));\r\n this.labelRegion.attr('transform', SVGUtil.translate(0, margin.top));\r\n this.labelBackgroundRegion.attr('transform', SVGUtil.translate(0, margin.top));\r\n this.svgScrollable.attr('width', width);\r\n }\r\n else if (this.axes.isYScrollBarVisible) {\r\n this.svgScrollable.attr('height', height + margin.top);\r\n }\r\n }\r\n\r\n /**\r\n * Within the context of the given selection (g), find the offset of\r\n * the zero tick using the d3 attached datum of g.tick elements.\r\n * 'Classed' is undefined for transition selections\r\n */\r\n private static darkenZeroLine(g: D3.Selection): void {\r\n // remove zero-line class from all first, filtering can cause lines that are no longer zero to still be dark (since the key is index based)\r\n g.selectAll('g.tick line').classed('zero-line', false);\r\n let zeroTick = g.selectAll('g.tick').filter((data) => data === 0).node();\r\n if (zeroTick) {\r\n d3.select(zeroTick).select('line').classed('zero-line', true);\r\n }\r\n }\r\n\r\n private static setAxisLabelColor(g: D3.Selection, fill: Fill): void {\r\n g.selectAll('g.tick text').style('fill', fill ? fill.solid.color : null);\r\n }\r\n }\r\n\r\n export class CartesianAxes {\r\n private static YAxisLabelPadding = 20;\r\n private static XAxisLabelPadding = 18;\r\n private static MaxMarginFactor = 0.25;\r\n private static MinimumMargin: IMargin = {\r\n left: 1,\r\n right: 1,\r\n top: 8, //half of the default font height\r\n bottom: 25,\r\n };\r\n\r\n private categoryAxisProperties: DataViewObject;\r\n private valueAxisProperties: DataViewObject;\r\n\r\n private maxMarginFactor: number;\r\n private yAxisOrientation: string;\r\n private scrollbarWidth: number;\r\n private trimOrdinalDataOnOverflow: boolean;\r\n public showLinesOnX: boolean;\r\n public showLinesOnY: boolean;\r\n public isScrollable: boolean;\r\n public isXScrollBarVisible: boolean;\r\n public isYScrollBarVisible: boolean;\r\n public categoryAxisHasUnitType: boolean;\r\n public valueAxisHasUnitType: boolean;\r\n public secondaryValueAxisHasUnitType: boolean;\r\n\r\n private layout: CartesianAxesLayout;\r\n\r\n constructor(isScrollable: boolean, scrollbarWidth: number, trimOrdinalDataOnOverflow: boolean) {\r\n this.scrollbarWidth = scrollbarWidth;\r\n this.isScrollable = isScrollable;\r\n this.maxMarginFactor = CartesianAxes.MaxMarginFactor;\r\n this.yAxisOrientation = yAxisPosition.left;\r\n this.trimOrdinalDataOnOverflow = trimOrdinalDataOnOverflow;\r\n }\r\n\r\n public shouldShowY1OnRight(): boolean {\r\n return this.yAxisOrientation === yAxisPosition.right;\r\n }\r\n\r\n public isYAxisCategorical(): boolean {\r\n return this.layout && this.layout.axes.y1.isCategoryAxis;\r\n }\r\n\r\n public hasCategoryAxis(): boolean {\r\n let axes = this.layout && this.layout.axes;\r\n if (!axes)\r\n return false;\r\n\r\n return this.isYAxisCategorical()\r\n ? axes.y1 && axes.y1.axis != null\r\n : axes.x && axes.x.axis != null;\r\n }\r\n\r\n public hasY2Axis(): boolean {\r\n return this.layout && this.layout.axes.y2 != null;\r\n }\r\n\r\n public getYAxisOrientation(): string {\r\n return this.yAxisOrientation;\r\n }\r\n\r\n public setAxisLinesVisibility(axisLinesVisibility: AxisLinesVisibility): void {\r\n this.showLinesOnX = EnumExtensions.hasFlag(axisLinesVisibility, AxisLinesVisibility.ShowLinesOnBothAxis) ||\r\n EnumExtensions.hasFlag(axisLinesVisibility, AxisLinesVisibility.ShowLinesOnXAxis);\r\n\r\n this.showLinesOnY = EnumExtensions.hasFlag(axisLinesVisibility, AxisLinesVisibility.ShowLinesOnBothAxis) ||\r\n EnumExtensions.hasFlag(axisLinesVisibility, AxisLinesVisibility.ShowLinesOnYAxis);\r\n }\r\n\r\n public setMaxMarginFactor(factor: number): void {\r\n this.maxMarginFactor = factor;\r\n }\r\n\r\n public update(dataViews: DataView[]) {\r\n if (dataViews && dataViews.length > 0) {\r\n let dataViewMetadata = dataViews[0].metadata;\r\n this.categoryAxisProperties = CartesianHelper.getCategoryAxisProperties(dataViewMetadata);\r\n this.valueAxisProperties = CartesianHelper.getValueAxisProperties(dataViewMetadata);\r\n }\r\n\r\n let axisPosition = this.valueAxisProperties['position'];\r\n this.yAxisOrientation = axisPosition ? axisPosition.toString() : yAxisPosition.left;\r\n }\r\n\r\n public addWarnings(warnings: IVisualWarning[]): void {\r\n let axes = this.layout && this.layout.axes;\r\n if (axes && axes.x && axes.x.hasDisallowedZeroInDomain\r\n || axes.y1 && axes.y1.hasDisallowedZeroInDomain\r\n || axes.y2 && axes.y2.hasDisallowedZeroInDomain) {\r\n warnings.unshift(new ZeroValueWarning());\r\n }\r\n }\r\n\r\n /** \r\n * Computes the Cartesian Chart axes from the set of layers.\r\n */\r\n private calculateAxes(\r\n layers: ICartesianVisual[],\r\n viewport: IViewport,\r\n margin: IMargin,\r\n playAxisControlLayout: IRect,\r\n textProperties: TextProperties,\r\n scrollbarVisible: boolean,\r\n existingAxisProperties: CartesianAxisProperties,\r\n hideAxisTitles: boolean,\r\n ensureXDomain?: NumberRange,\r\n ensureYDomain?: NumberRange): CartesianAxisProperties {\r\n debug.assertValue(layers, 'layers');\r\n\r\n let visualOptions: CalculateScaleAndDomainOptions = {\r\n viewport: viewport,\r\n margin: margin,\r\n forcedXDomain: [this.categoryAxisProperties ? this.categoryAxisProperties['start'] : null, this.categoryAxisProperties ? this.categoryAxisProperties['end'] : null],\r\n forceMerge: this.valueAxisProperties && this.valueAxisProperties['secShow'] === false,\r\n showCategoryAxisLabel: false,\r\n showValueAxisLabel: false,\r\n trimOrdinalDataOnOverflow: this.trimOrdinalDataOnOverflow,\r\n categoryAxisScaleType: this.categoryAxisProperties && this.categoryAxisProperties['axisScale'] != null ? <string>this.categoryAxisProperties['axisScale'] : DEFAULT_AXIS_SCALE_TYPE,\r\n valueAxisScaleType: this.valueAxisProperties && this.valueAxisProperties['axisScale'] != null ? <string>this.valueAxisProperties['axisScale'] : DEFAULT_AXIS_SCALE_TYPE,\r\n categoryAxisDisplayUnits: this.categoryAxisProperties && this.categoryAxisProperties['labelDisplayUnits'] != null ? <number>this.categoryAxisProperties['labelDisplayUnits'] : 0,\r\n valueAxisDisplayUnits: this.valueAxisProperties && this.valueAxisProperties['labelDisplayUnits'] != null ? <number>this.valueAxisProperties['labelDisplayUnits'] : 0,\r\n categoryAxisPrecision: this.categoryAxisProperties ? CartesianHelper.getPrecision(this.categoryAxisProperties['labelPrecision']) : null,\r\n valueAxisPrecision: this.valueAxisProperties ? CartesianHelper.getPrecision(this.valueAxisProperties['labelPrecision']) : null,\r\n playAxisControlLayout: playAxisControlLayout,\r\n ensureXDomain: ensureXDomain,\r\n ensureYDomain: ensureYDomain,\r\n };\r\n\r\n let skipMerge = this.valueAxisProperties && this.valueAxisProperties['secShow'] === true;\r\n\r\n let yAxisWillMerge = false;\r\n let mergeResult: MergedValueAxisResult;\r\n if (hasMultipleYAxes(layers) && !skipMerge) {\r\n mergeResult = tryMergeYDomains(layers, visualOptions);\r\n yAxisWillMerge = mergeResult.merged;\r\n if (yAxisWillMerge) {\r\n visualOptions.forcedYDomain = mergeResult.domain;\r\n }\r\n else {\r\n visualOptions.forcedTickCount = mergeResult.tickCount;\r\n }\r\n }\r\n\r\n if (this.valueAxisProperties) {\r\n visualOptions.forcedYDomain = AxisHelper.applyCustomizedDomain([this.valueAxisProperties['start'], this.valueAxisProperties['end']], visualOptions.forcedYDomain);\r\n }\r\n\r\n let result: CartesianAxisProperties;\r\n for (let layerNumber = 0, len = layers.length; layerNumber < len; layerNumber++) {\r\n let currentlayer = layers[layerNumber];\r\n\r\n if (layerNumber === 1 && !yAxisWillMerge) {\r\n visualOptions.forcedYDomain = this.valueAxisProperties ? [this.valueAxisProperties['secStart'], this.valueAxisProperties['secEnd']] : null;\r\n visualOptions.valueAxisScaleType = this.valueAxisProperties && this.valueAxisProperties['secAxisScale'] != null ? <string>this.valueAxisProperties['secAxisScale'] : DEFAULT_AXIS_SCALE_TYPE;\r\n visualOptions.valueAxisDisplayUnits = this.valueAxisProperties && this.valueAxisProperties['secLabelDisplayUnits'] != null ? <number>this.valueAxisProperties['secLabelDisplayUnits'] : 0;\r\n visualOptions.valueAxisPrecision = this.valueAxisProperties ? CartesianHelper.getPrecision(this.valueAxisProperties['secLabelPrecision']) : null;\r\n }\r\n visualOptions.showCategoryAxisLabel = (!!this.categoryAxisProperties && !!this.categoryAxisProperties['showAxisTitle']);//here\r\n\r\n visualOptions.showValueAxisLabel = shouldShowYAxisLabel(layerNumber, this.valueAxisProperties, yAxisWillMerge);\r\n\r\n let axes = currentlayer.calculateAxesProperties(visualOptions);\r\n\r\n if (layerNumber === 0) {\r\n result = {\r\n x: axes[0],\r\n y1: axes[1]\r\n };\r\n }\r\n else if (axes && !result.y2) {\r\n if (result.x.usingDefaultDomain || _.isEmpty(result.x.dataDomain)) {\r\n visualOptions.showValueAxisLabel = (!!this.valueAxisProperties && !!this.valueAxisProperties['showAxisTitle']);\r\n\r\n let axes = currentlayer.calculateAxesProperties(visualOptions);\r\n // no categories returned for the first layer, use second layer x-axis properties\r\n result.x = axes[0];\r\n // and 2nd value axis to be the primary\r\n result.y1 = axes[1];\r\n }\r\n else {\r\n // make sure all layers use the same x-axis/scale for drawing\r\n currentlayer.overrideXScale(result.x);\r\n if (!yAxisWillMerge && !axes[1].usingDefaultDomain)\r\n result.y2 = axes[1];\r\n }\r\n }\r\n\r\n if (existingAxisProperties && existingAxisProperties.x) {\r\n result.x.willLabelsFit = existingAxisProperties.x.willLabelsFit;\r\n result.x.willLabelsWordBreak = existingAxisProperties.x.willLabelsWordBreak;\r\n } else {\r\n let width = viewport.width - (margin.left + margin.right);\r\n result.x.willLabelsFit = AxisHelper.LabelLayoutStrategy.willLabelsFit(\r\n result.x,\r\n width,\r\n TextMeasurementService.measureSvgTextWidth,\r\n textProperties);\r\n\r\n // If labels do not fit and we are not scrolling, try word breaking\r\n result.x.willLabelsWordBreak = (!result.x.willLabelsFit && !scrollbarVisible) && AxisHelper.LabelLayoutStrategy.willLabelsWordBreak(\r\n result.x,\r\n margin,\r\n width,\r\n TextMeasurementService.measureSvgTextWidth,\r\n TextMeasurementService.estimateSvgTextHeight,\r\n TextMeasurementService.getTailoredTextOrDefault,\r\n textProperties);\r\n }\r\n }\r\n\r\n // Adjust for axis titles\r\n if (hideAxisTitles) {\r\n result.x.axisLabel = null;\r\n result.y1.axisLabel = null;\r\n if (result.y2) {\r\n result.y2.axisLabel = null;\r\n }\r\n }\r\n this.addUnitTypeToAxisLabels(result);\r\n\r\n return result;\r\n }\r\n\r\n /**\r\n * Negotiate the axes regions, the plot area, and determine if we need a scrollbar for ordinal categories.\r\n * @param layers an array of Cartesian layout layers (column, line, etc.)\r\n * @param parentViewport the full viewport for the visual\r\n * @param padding the D3 axis padding values\r\n * @param playAxisControlLayout if this is a playable Cartesian chart, includes the layout for the play controls (start/stop, time slider)\r\n * @param hideAxisLabels forces axis titles to be hidden\r\n * @param textProperties text properties to be used by text measurement\r\n * @param interactivityRightMargin extra right margin for the interactivity\r\n * @param ensureXDomain if non null, includes values that must be part of the axis domain\r\n * @param ensureYDomain if non null, includes values that must be part of the axis domain\r\n */\r\n public negotiateAxes(\r\n layers: ICartesianVisual[],\r\n parentViewport: IViewport,\r\n padding: IMargin,\r\n playAxisControlLayout: IRect,\r\n hideAxisLabels: boolean,\r\n textProperties: TextProperties,\r\n interactivityRightMargin: number,\r\n ensureXDomain?: NumberRange,\r\n ensureYDomain?: NumberRange): CartesianAxesLayout {\r\n\r\n // 1> MinMargins -> some initial axis properties / text\r\n // 2> Get axis margins for the initial text, no rotateXTickLabels90. margins grown? -> axis properties / text again (possibly more tick labels now)\r\n // ?> do we have more labels? do we need rotate? are we done?\r\n // 3> margins again (rotate? margins grow?) -> text again (less tick labls now?)\r\n // FREEZE PROPERTIES THAT CAN CHANGE\r\n // 4> margins (final), axes (final)\r\n \r\n // 1.a) initialize margins\r\n let margin: IMargin = Prototype.inherit(CartesianAxes.MinimumMargin);\r\n let viewport: IViewport = Prototype.inherit(parentViewport);\r\n let leftRightMarginLimit = viewport.width * this.maxMarginFactor;\r\n let bottomMarginLimit = Math.max(CartesianAxes.MinimumMargin.bottom, Math.ceil(viewport.height * this.maxMarginFactor));\r\n let marginLimits = {\r\n left: leftRightMarginLimit,\r\n right: leftRightMarginLimit,\r\n top: 0,\r\n bottom: bottomMarginLimit,\r\n };\r\n\r\n // 1.b) Calculate axis properties using initial margins\r\n let axes = this.calculateAxes(\r\n layers,\r\n viewport,\r\n margin,\r\n playAxisControlLayout,\r\n textProperties,\r\n /*scrollbarVisible*/ false,\r\n /*previousAxisProperties*/ null,\r\n hideAxisLabels,\r\n ensureXDomain,\r\n ensureYDomain);\r\n\r\n // these are used by getTickLabelMargins\r\n let renderXAxis = this.shouldRenderAxis(axes.x);\r\n let renderY1Axis = this.shouldRenderAxis(axes.y1);\r\n let renderY2Axis = this.shouldRenderAxis(axes.y2, true);\r\n let showY1OnRight = this.shouldShowY1OnRight();\r\n\r\n let plotArea: IViewport = {\r\n width: viewport.width - (margin.left + margin.right),\r\n height: viewport.height - (margin.top + margin.bottom)\r\n };\r\n\r\n let isScalar = false;\r\n if (!_.isEmpty(layers)) {\r\n if (layers[0].getVisualCategoryAxisIsScalar)\r\n isScalar = layers[0].getVisualCategoryAxisIsScalar();\r\n }\r\n\r\n // 2.a) calculate axis tick margins\r\n let tickLabelMargins = undefined;\r\n tickLabelMargins = AxisHelper.getTickLabelMargins(\r\n plotArea,\r\n marginLimits.left,\r\n TextMeasurementService.measureSvgTextWidth,\r\n TextMeasurementService.estimateSvgTextHeight,\r\n axes,\r\n marginLimits.bottom,\r\n textProperties,\r\n /*scrolling*/ false,\r\n showY1OnRight,\r\n renderXAxis,\r\n renderY1Axis,\r\n renderY2Axis);\r\n\r\n margin = this.updateAxisMargins(axes, tickLabelMargins, padding, showY1OnRight, renderY1Axis, renderY2Axis, isScalar ? 0 : interactivityRightMargin);\r\n\r\n // if any of these change, we need to calculate margins again\r\n let previousTickCountY1 = axes.y1 && axes.y1.values.length;\r\n let previousTickCountY2 = axes.y2 && axes.y2.values.length;\r\n let previousWillFitX = axes.x && axes.x.willLabelsFit;\r\n let previousWillBreakX = axes.x && axes.x.willLabelsWordBreak;\r\n\r\n // 2.b) Re-calculate the axes with the new margins.\r\n axes = this.calculateAxes(\r\n layers,\r\n viewport,\r\n margin,\r\n playAxisControlLayout,\r\n textProperties,\r\n /*scrollbarVisible*/ false,\r\n /*previousAxes*/ null,\r\n hideAxisLabels,\r\n ensureXDomain,\r\n ensureYDomain);\r\n\r\n plotArea.width = viewport.width - (margin.left + margin.right);\r\n plotArea.height = viewport.height - (margin.top + margin.bottom);\r\n\r\n // check properties that affect getTickLabelMargin - if these are the same, we don't need to calculate axis margins again\r\n let preferredPlotArea: IViewport = this.getPreferredPlotArea(axes, layers, isScalar);\r\n let rotateXTickLabels90 = !this.willAllCategoriesFitInPlotArea(plotArea, preferredPlotArea);\r\n let allDone = ((!axes.y1 || axes.y1.values.length === previousTickCountY1)\r\n && (!axes.y2 || axes.y2.values.length === previousTickCountY2)\r\n && (!axes.x || axes.x.willLabelsFit === previousWillFitX)\r\n && (!axes.x || axes.x.willLabelsWordBreak === previousWillBreakX)\r\n && !rotateXTickLabels90);\r\n\r\n this.isXScrollBarVisible = false;\r\n this.isYScrollBarVisible = false;\r\n if (!allDone) {\r\n // 3.a) calculate axis tick margins\r\n tickLabelMargins = AxisHelper.getTickLabelMargins(\r\n plotArea,\r\n marginLimits.left,\r\n TextMeasurementService.measureSvgTextWidth,\r\n TextMeasurementService.estimateSvgTextHeight,\r\n axes,\r\n marginLimits.bottom,\r\n textProperties,\r\n rotateXTickLabels90,\r\n showY1OnRight,\r\n renderXAxis,\r\n renderY1Axis,\r\n renderY2Axis);\r\n\r\n margin = this.updateAxisMargins(axes, tickLabelMargins, padding, showY1OnRight, renderY1Axis, renderY2Axis, isScalar ? 0 : interactivityRightMargin);\r\n\r\n // 3.b) Re-calculate the axes with the new final margins\r\n axes = this.calculateAxes(\r\n layers,\r\n viewport,\r\n margin,\r\n playAxisControlLayout,\r\n textProperties,\r\n /*scrollbarVisible*/ rotateXTickLabels90,\r\n axes,\r\n hideAxisLabels,\r\n ensureXDomain,\r\n ensureYDomain);\r\n\r\n // now we can determine if we need actual scrolling\r\n // rotateXTickLabels90 will give more plotArea to categories since the left-overflow of a rotated category label doesn't exist anymore\r\n plotArea.width = viewport.width - (margin.left + margin.right);\r\n plotArea.height = viewport.height - (margin.top + margin.bottom);\r\n preferredPlotArea = this.getPreferredPlotArea(axes, layers, isScalar);\r\n let willScroll = !this.willAllCategoriesFitInPlotArea(plotArea, preferredPlotArea);\r\n if (willScroll) {\r\n if (this.showLinesOnY) {\r\n this.isXScrollBarVisible = true;\r\n plotArea.height -= this.scrollbarWidth;\r\n viewport.height -= this.scrollbarWidth;\r\n }\r\n if (this.showLinesOnX) {\r\n this.isYScrollBarVisible = true;\r\n plotArea.width -= this.scrollbarWidth;\r\n viewport.width -= this.scrollbarWidth;\r\n }\r\n\r\n // 3.c) Re-calculate the axes with the final margins (and the updated viewport - scrollbarWidth)\r\n axes = this.calculateAxes(\r\n layers,\r\n viewport,\r\n margin,\r\n playAxisControlLayout,\r\n textProperties,\r\n /*scrollbarVisible*/ true,\r\n axes,\r\n hideAxisLabels,\r\n ensureXDomain,\r\n ensureYDomain);\r\n }\r\n }\r\n\r\n ///////DONE\r\n let axisLabels = hideAxisLabels ?\r\n { x: null, y: null, y2: null } :\r\n { x: axes.x.axisLabel, y: axes.y1.axisLabel, y2: axes.y2 ? axes.y2.axisLabel : null };\r\n\r\n this.layout = {\r\n axes: axes,\r\n axisLabels: axisLabels,\r\n margin: margin,\r\n marginLimits: marginLimits,\r\n viewport: viewport,\r\n plotArea: plotArea,\r\n preferredPlotArea: preferredPlotArea,\r\n tickLabelMargins: tickLabelMargins,\r\n tickPadding: padding,\r\n rotateXTickLabels90: rotateXTickLabels90,\r\n };\r\n\r\n return this.layout;\r\n }\r\n\r\n private getPreferredPlotArea(axes, layers, isScalar): IViewport {\r\n let preferredPlotArea: IViewport;\r\n if (!isScalar && this.isScrollable && !_.isEmpty(layers) && layers[0].getPreferredPlotArea) {\r\n let categoryThickness = this.showLinesOnY ? axes.x.categoryThickness : axes.y1.categoryThickness;\r\n let categoryCount = this.showLinesOnY ? axes.x.dataDomain.length : axes.y1.dataDomain.length;\r\n preferredPlotArea = layers[0].getPreferredPlotArea(isScalar, categoryCount, categoryThickness);\r\n }\r\n return preferredPlotArea;\r\n }\r\n\r\n private willAllCategoriesFitInPlotArea(plotArea: IViewport, preferredPlotArea: IViewport): boolean {\r\n if (this.showLinesOnY && preferredPlotArea && Double.greaterWithPrecision(preferredPlotArea.width, plotArea.width)) {\r\n return false;\r\n }\r\n if (this.showLinesOnX && preferredPlotArea && Double.greaterWithPrecision(preferredPlotArea.height, plotArea.height)) {\r\n return false;\r\n }\r\n return true;\r\n }\r\n\r\n private updateAxisMargins(\r\n axes: CartesianAxisProperties,\r\n tickLabelMargins: TickLabelMargins,\r\n padding: IMargin,\r\n showY1OnRight: boolean,\r\n renderY1Axis: boolean,\r\n renderY2Axis: boolean,\r\n interactivityRightMargin: number): IMargin {\r\n\r\n // We look at the y axes as main and second sides, if the y axis orientation is right then the main side represents the right side.\r\n let maxY1Padding = showY1OnRight ? tickLabelMargins.yRight : tickLabelMargins.yLeft,\r\n maxY2Padding = showY1OnRight ? tickLabelMargins.yLeft : tickLabelMargins.yRight,\r\n maxXAxisBottom = tickLabelMargins.xMax;\r\n\r\n maxY1Padding += padding.left;\r\n if ((renderY2Axis && !showY1OnRight) || (showY1OnRight && renderY1Axis))\r\n maxY2Padding += padding.right;\r\n maxXAxisBottom += padding.bottom;\r\n\r\n let axisLabels = { x: axes.x.axisLabel, y: axes.y1.axisLabel, y2: axes.y2 ? axes.y2.axisLabel : null };\r\n if (axisLabels.x != null)\r\n maxXAxisBottom += CartesianAxes.XAxisLabelPadding;\r\n if (axisLabels.y != null)\r\n maxY1Padding += CartesianAxes.YAxisLabelPadding;\r\n if (axisLabels.y2 != null)\r\n maxY2Padding += CartesianAxes.YAxisLabelPadding;\r\n\r\n let margin: IMargin = Prototype.inherit(CartesianAxes.MinimumMargin);\r\n margin.left = showY1OnRight ? maxY2Padding : maxY1Padding;\r\n margin.right = showY1OnRight ? maxY1Padding : maxY2Padding;\r\n margin.right += interactivityRightMargin; // for mobile interactive legend\r\n margin.bottom = maxXAxisBottom;\r\n\r\n return margin;\r\n }\r\n\r\n public isLogScaleAllowed(axisType: AxisLocation): boolean {\r\n let axes = this.layout && this.layout.axes;\r\n if (!axes)\r\n return false;\r\n\r\n switch (axisType) {\r\n case AxisLocation.X:\r\n return axes.x.isLogScaleAllowed;\r\n case AxisLocation.Y1:\r\n return axes.y1.isLogScaleAllowed;\r\n case AxisLocation.Y2:\r\n return axes.y2 && axes.y2.isLogScaleAllowed;\r\n }\r\n }\r\n\r\n public axesHaveTicks(viewport: IViewport): boolean {\r\n if (!this.layout)\r\n return false;\r\n\r\n let margin = this.layout.margin;\r\n\r\n let width = viewport.width - (margin.left + margin.right);\r\n let height = viewport.height - (margin.top + margin.bottom);\r\n\r\n // TODO: this is never the case, remove.\r\n if (AxisHelper.getRecommendedNumberOfTicksForXAxis(width) === 0\r\n && AxisHelper.getRecommendedNumberOfTicksForYAxis(height) === 0) {\r\n return false;\r\n }\r\n\r\n return true;\r\n }\r\n\r\n public shouldRenderAxisTitle(axisProperties: IAxisProperties, defaultValue: boolean, secondary: boolean): boolean {\r\n let propertyName = secondary ? 'secShowAxisTitle' : 'showAxisTitle';\r\n\r\n return !!this.getAxisProperty(axisProperties, propertyName, defaultValue);\r\n }\r\n\r\n public shouldRenderAxis(axisProperties: IAxisProperties, secondary: boolean = false): boolean {\r\n if (!axisProperties)\r\n return false;\r\n\r\n let propertyName = secondary ? 'secShow' : 'show';\r\n\r\n return this.getAxisProperty(axisProperties, propertyName, true)\r\n && axisProperties.values\r\n && axisProperties.values.length > 0;\r\n }\r\n\r\n private getAxisProperty(axisProperties: IAxisProperties, propertyName: string, defaultValue: DataViewPropertyValue): DataViewPropertyValue {\r\n if (!axisProperties)\r\n return defaultValue;\r\n\r\n let properties = axisProperties.isCategoryAxis\r\n ? this.categoryAxisProperties\r\n : this.valueAxisProperties;\r\n\r\n if (properties && properties[propertyName] != null)\r\n return properties[propertyName];\r\n else\r\n return defaultValue;\r\n }\r\n\r\n // TODO: clean this up\r\n private addUnitTypeToAxisLabels(axes: CartesianAxisProperties): void {\r\n let unitType = CartesianAxes.getUnitType(axes.x.formatter);\r\n if (axes.x.isCategoryAxis) {\r\n this.categoryAxisHasUnitType = unitType != null;\r\n }\r\n else {\r\n this.valueAxisHasUnitType = unitType != null;\r\n }\r\n\r\n if (axes.x.axisLabel && unitType) {\r\n if (axes.x.isCategoryAxis) {\r\n axes.x.axisLabel = AxisHelper.createAxisLabel(this.categoryAxisProperties, axes.x.axisLabel, unitType);\r\n }\r\n else {\r\n axes.x.axisLabel = AxisHelper.createAxisLabel(this.valueAxisProperties, axes.x.axisLabel, unitType);\r\n }\r\n }\r\n\r\n unitType = CartesianAxes.getUnitType(axes.y1.formatter);\r\n\r\n if (!axes.y1.isCategoryAxis) {\r\n this.valueAxisHasUnitType = unitType != null;\r\n }\r\n else {\r\n this.categoryAxisHasUnitType = unitType != null;\r\n }\r\n\r\n if (axes.y1.axisLabel && unitType) {\r\n if (!axes.y1.isCategoryAxis) {\r\n axes.y1.axisLabel = AxisHelper.createAxisLabel(this.valueAxisProperties, axes.y1.axisLabel, unitType);\r\n }\r\n else {\r\n axes.y1.axisLabel = AxisHelper.createAxisLabel(this.categoryAxisProperties, axes.y1.axisLabel, unitType);\r\n }\r\n }\r\n\r\n if (axes.y2) {\r\n let unitType = CartesianAxes.getUnitType(axes.y2.formatter);\r\n this.secondaryValueAxisHasUnitType = unitType != null;\r\n if (axes.y2.axisLabel && unitType) {\r\n axes.y2.axisLabel = AxisHelper.createAxisLabel(this.valueAxisProperties, axes.y2.axisLabel, unitType, true);\r\n }\r\n }\r\n }\r\n\r\n private static getUnitType(formatter: IValueFormatter) {\r\n if (formatter &&\r\n formatter.displayUnit &&\r\n formatter.displayUnit.value > 1)\r\n return formatter.displayUnit.title;\r\n }\r\n }\r\n\r\n module CartesianLayerFactory {\r\n\r\n export function createLayers(\r\n type: CartesianChartType,\r\n objects: DataViewObjects,\r\n interactivityService: IInteractivityService,\r\n animator?: any,\r\n isScrollable: boolean = false,\r\n tooltipsEnabled?: boolean,\r\n tooltipBucketEnabled?: boolean,\r\n advancedLineLabelsEnabled?: boolean,\r\n cartesianLoadMoreEnabled?: boolean): ICartesianVisual[] {\r\n\r\n let layers: ICartesianVisual[] = [];\r\n\r\n let cartesianOptions: CartesianVisualConstructorOptions = {\r\n isScrollable: isScrollable,\r\n animator: animator,\r\n interactivityService: interactivityService,\r\n tooltipsEnabled: tooltipsEnabled,\r\n tooltipBucketEnabled: tooltipBucketEnabled,\r\n advancedLineLabelsEnabled: advancedLineLabelsEnabled,\r\n cartesianLoadMoreEnabled: cartesianLoadMoreEnabled\r\n };\r\n\r\n switch (type) {\r\n case CartesianChartType.Area:\r\n layers.push(createLineChartLayer(LineChartType.area, /* inComboChart */ false, cartesianOptions));\r\n //layers.push(createLineChartLayer(LineChartType.default, /* inComboChart */ false, cartesianOptions, true));\r\n break;\r\n case CartesianChartType.Line:\r\n layers.push(createLineChartLayer(LineChartType.default, /* inComboChart */ false, cartesianOptions));\r\n //layers.push(createLineChartLayer(LineChartType.default, /* inComboChart */ false, cartesianOptions, true));\r\n break;\r\n case CartesianChartType.StackedArea:\r\n layers.push(createLineChartLayer(LineChartType.stackedArea, /* inComboChart */ false, cartesianOptions));\r\n break;\r\n case CartesianChartType.Scatter:\r\n layers.push(createScatterChartLayer(cartesianOptions));\r\n //layers.push(createLineChartLayer(LineChartType.default, /* inComboChart */ false, cartesianOptions, true));\r\n break;\r\n case CartesianChartType.Waterfall:\r\n layers.push(createWaterfallChartLayer(cartesianOptions));\r\n break;\r\n case CartesianChartType.DataDot:\r\n layers.push(createDataDotChartLayer(cartesianOptions));\r\n break;\r\n case CartesianChartType.StackedColumn:\r\n layers.push(createColumnChartLayer(ColumnChartType.stackedColumn, cartesianOptions));\r\n break;\r\n case CartesianChartType.ClusteredColumn:\r\n layers.push(createColumnChartLayer(ColumnChartType.clusteredColumn, cartesianOptions));\r\n break;\r\n case CartesianChartType.HundredPercentStackedColumn:\r\n layers.push(createColumnChartLayer(ColumnChartType.hundredPercentStackedColumn, cartesianOptions));\r\n break;\r\n case CartesianChartType.StackedBar:\r\n layers.push(createColumnChartLayer(ColumnChartType.stackedBar, cartesianOptions));\r\n break;\r\n case CartesianChartType.ClusteredBar:\r\n layers.push(createColumnChartLayer(ColumnChartType.clusteredBar, cartesianOptions));\r\n break;\r\n case CartesianChartType.HundredPercentStackedBar:\r\n layers.push(createColumnChartLayer(ColumnChartType.hundredPercentStackedBar, cartesianOptions));\r\n break;\r\n case CartesianChartType.ComboChart:\r\n let columnType = getComboColumnType();\r\n layers.push(createColumnChartLayer(columnType, cartesianOptions));\r\n layers.push(createLineChartLayer(LineChartType.default, /* inComboChart */ true, cartesianOptions));\r\n break;\r\n case CartesianChartType.LineClusteredColumnCombo:\r\n layers.push(createColumnChartLayer(ColumnChartType.clusteredColumn, cartesianOptions));\r\n layers.push(createLineChartLayer(LineChartType.default, /* inComboChart */ true, cartesianOptions));\r\n break;\r\n case CartesianChartType.LineStackedColumnCombo:\r\n layers.push(createColumnChartLayer(ColumnChartType.stackedColumn, cartesianOptions));\r\n layers.push(createLineChartLayer(LineChartType.default, /* inComboChart */ true, cartesianOptions));\r\n break;\r\n case CartesianChartType.DataDotClusteredColumnCombo:\r\n layers.push(createColumnChartLayer(ColumnChartType.clusteredColumn, cartesianOptions));\r\n layers.push(createDataDotChartLayer(cartesianOptions));\r\n break;\r\n case CartesianChartType.DataDotStackedColumnCombo:\r\n layers.push(createColumnChartLayer(ColumnChartType.stackedColumn, cartesianOptions));\r\n layers.push(createDataDotChartLayer(cartesianOptions));\r\n break;\r\n }\r\n\r\n return layers;\r\n }\r\n\r\n function createLineChartLayer(type: LineChartType, inComboChart: boolean, defaultOptions: CartesianVisualConstructorOptions, isTrendLayer?: boolean): LineChart {\r\n let options: LineChartConstructorOptions = {\r\n animator: defaultOptions.animator,\r\n interactivityService: defaultOptions.interactivityService,\r\n isScrollable: defaultOptions.isScrollable,\r\n tooltipsEnabled: !isTrendLayer && defaultOptions.tooltipsEnabled,\r\n tooltipBucketEnabled: defaultOptions.tooltipBucketEnabled,\r\n chartType: type,\r\n advancedLineLabelsEnabled: defaultOptions.advancedLineLabelsEnabled,\r\n cartesianLoadMoreEnabled: defaultOptions.cartesianLoadMoreEnabled,\r\n };\r\n\r\n if (inComboChart) {\r\n options.chartType = options.chartType | LineChartType.lineShadow;\r\n }\r\n\r\n return new LineChart(options);\r\n }\r\n\r\n function createScatterChartLayer(defaultOptions: CartesianVisualConstructorOptions): ScatterChart {\r\n defaultOptions.isScrollable = false;\r\n return new ScatterChart(defaultOptions);\r\n }\r\n\r\n function createWaterfallChartLayer(defaultOptions: CartesianVisualConstructorOptions): WaterfallChart {\r\n return new WaterfallChart(defaultOptions);\r\n }\r\n\r\n function createDataDotChartLayer(defaultOptions: CartesianVisualConstructorOptions): DataDotChart {\r\n return new DataDotChart(defaultOptions);\r\n }\r\n\r\n function createColumnChartLayer(type: ColumnChartType, defaultOptions: CartesianVisualConstructorOptions): ColumnChart {\r\n let options: ColumnChartConstructorOptions = {\r\n animator: <IColumnChartAnimator>defaultOptions.animator,\r\n interactivityService: defaultOptions.interactivityService,\r\n isScrollable: defaultOptions.isScrollable,\r\n tooltipsEnabled: defaultOptions.tooltipsEnabled,\r\n tooltipBucketEnabled: defaultOptions.tooltipBucketEnabled,\r\n cartesianLoadMoreEnabled: defaultOptions.cartesianLoadMoreEnabled,\r\n chartType: type\r\n };\r\n return new ColumnChart(options);\r\n }\r\n\r\n function getComboColumnType(objects?: DataViewObjects): ColumnChartType {\r\n // This supports existing serialized forms of pinned combo-chart visuals\r\n let columnType: ColumnChartType = ColumnChartType.clusteredColumn;\r\n if (objects) {\r\n let comboChartTypes: ComboChartDataViewObject = (<ComboChartDataViewObjects>objects).general;\r\n if (comboChartTypes) {\r\n switch (comboChartTypes.visualType1) {\r\n case 'Column':\r\n columnType = ColumnChartType.clusteredColumn;\r\n break;\r\n case 'ColumnStacked':\r\n columnType = ColumnChartType.stackedColumn;\r\n break;\r\n default:\r\n debug.assertFail('Unsupported cartesian chart type ' + comboChartTypes.visualType1);\r\n }\r\n\r\n // second visual is always LineChart (for now)\r\n if (comboChartTypes.visualType2) {\r\n debug.assert(comboChartTypes.visualType2 === 'Line', 'expecting a LineChart for VisualType2');\r\n }\r\n }\r\n }\r\n\r\n return columnType;\r\n }\r\n }\r\n\r\n export class SharedColorPalette implements IDataColorPalette {\r\n private palette: IDataColorPalette;\r\n private preferredScale: IColorScale;\r\n private rotated: boolean;\r\n\r\n constructor(palette: IDataColorPalette) {\r\n this.palette = palette;\r\n this.clearPreferredScale();\r\n }\r\n\r\n public getColorScaleByKey(scaleKey: string): IColorScale {\r\n this.setPreferredScale(scaleKey);\r\n return this.preferredScale;\r\n }\r\n\r\n public getNewColorScale(): IColorScale {\r\n return this.preferredScale;\r\n }\r\n\r\n public getColorByIndex(index: number): IColorInfo {\r\n return this.palette.getColorByIndex(index);\r\n }\r\n\r\n public getSentimentColors(): IColorInfo[] {\r\n return this.palette.getSentimentColors();\r\n }\r\n\r\n public getBasePickerColors(): IColorInfo[] {\r\n return this.palette.getBasePickerColors();\r\n }\r\n\r\n public clearPreferredScale(): void {\r\n this.preferredScale = this.palette.getNewColorScale();\r\n this.rotated = false;\r\n }\r\n\r\n public rotateScale(): void {\r\n // We create a new rotated the scale such that the first color of the new scale is the first\r\n // free color of the previous scale. Note that the new scale does not have any colors allocated\r\n // to particular keys.\r\n this.preferredScale = this.preferredScale.clone();\r\n this.preferredScale.clearAndRotateScale();\r\n this.rotated = true;\r\n }\r\n\r\n private setPreferredScale(scaleKey: string): void {\r\n if (!this.rotated) {\r\n // The first layer to express a preference sets the preferred scale.\r\n this.preferredScale = this.palette.getColorScaleByKey(scaleKey);\r\n }\r\n }\r\n }\r\n \r\n export class CartesianLoadMoreDataHandler {\r\n\r\n public viewportDataRange: ViewportDataRange;\r\n \r\n private loadMoreThresholdIndex: number;\r\n private loadingMoreData: boolean;\r\n private loadMoreThreshold: number;\r\n private loadMoreCallback: () => void;\r\n\r\n /**\r\n * Constructs the handler.\r\n * @param scale - The scale for the loaded data.\r\n * @param loadMoreCallback - The callback to execute to load more data.\r\n * @param loadMoreThreshold - How many indexes before the last index loading more data will be triggered.\r\n * Ex: loadMoreThreshold = 2, dataLength = 10 (last index = 9) will trigger a load when item with index 9 - 2 = 7 or greater is displayed.\r\n */\r\n constructor(scale: D3.Scale.GenericScale<any>, loadMoreCallback: () => void, loadMoreThreshold: number = 0) {\r\n debug.assertValue(loadMoreCallback, 'loadMoreCallback');\r\n debug.assert(loadMoreThreshold >= 0, 'loadMoreThreshold must be greater than or equal to 0');\r\n this.loadMoreThreshold = loadMoreThreshold;\r\n this.loadMoreCallback = loadMoreCallback;\r\n this.setScale(scale);\r\n }\r\n\r\n public setScale(scale: D3.Scale.GenericScale<any>): void {\r\n if (!scale) {\r\n return;\r\n }\r\n\r\n // Length of the scale is the amount of data we have loaded. Subtract 1 to get the index.\r\n // Subtract the threshold to calculate the index at which we need to load more data.\r\n this.loadMoreThresholdIndex = scale.domain().length - 1 - this.loadMoreThreshold;\r\n }\r\n\r\n public isLoadingMoreData(): boolean {\r\n return this.loadingMoreData;\r\n }\r\n\r\n public onLoadMoreDataCompleted(): void {\r\n this.loadingMoreData = false;\r\n }\r\n\r\n public shouldLoadMoreData(): boolean {\r\n let viewportDataRange = this.viewportDataRange;\r\n \r\n if (!viewportDataRange || this.isLoadingMoreData()) {\r\n return false;\r\n }\r\n\r\n // If the index of the data we're displaying is more than the threshold, return true.\r\n return viewportDataRange.endIndex >= this.loadMoreThresholdIndex;\r\n }\r\n\r\n public loadMoreData(): void {\r\n if (this.isLoadingMoreData()) {\r\n return;\r\n }\r\n\r\n this.loadingMoreData = true;\r\n this.loadMoreCallback();\r\n }\r\n }\r\n}\r\n\n\n\n/** WEBPACK FOOTER **\n ** ./src/Clients/Visuals/cartesian/cartesianChart.ts\n **/","/*\r\n * Power BI Visualizations\r\n *\r\n * Copyright (c) Microsoft Corporation\r\n * All rights reserved.\r\n * MIT License\r\n *\r\n * Permission is hereby granted, free of charge, to any person obtaining a copy\r\n * of this software and associated documentation files (the \"\"Software\"\"), to deal\r\n * in the Software without restriction, including without limitation the rights\r\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\r\n * copies of the Software, and to permit persons to whom the Software is\r\n * furnished to do so, subject to the following conditions:\r\n *\r\n * The above copyright notice and this permission notice shall be included in\r\n * all copies or substantial portions of the Software.\r\n *\r\n * THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\r\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\r\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\r\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\r\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\r\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\r\n * THE SOFTWARE.\r\n */\r\n\r\n/// <reference path=\"../_references.ts\"/>\r\n\r\nmodule powerbi.visuals {\r\n import EnumExtensions = jsCommon.EnumExtensions;\r\n import DataRoleHelper = powerbi.data.DataRoleHelper;\r\n\r\n export interface ColumnChartConstructorOptions extends CartesianVisualConstructorOptions {\r\n chartType: ColumnChartType;\r\n animator: IColumnChartAnimator;\r\n }\r\n\r\n export interface ColumnChartData extends CartesianData {\r\n categoryFormatter: IValueFormatter;\r\n series: ColumnChartSeries[];\r\n valuesMetadata: DataViewMetadataColumn[];\r\n legendData: LegendData;\r\n hasHighlights: boolean;\r\n categoryMetadata: DataViewMetadataColumn;\r\n scalarCategoryAxis: boolean;\r\n labelSettings: VisualDataLabelsSettings;\r\n axesLabels: ChartAxesLabels;\r\n hasDynamicSeries: boolean;\r\n isMultiMeasure: boolean;\r\n defaultDataPointColor?: string;\r\n showAllDataPoints?: boolean;\r\n }\r\n\r\n export interface ColumnChartSeries extends CartesianSeries {\r\n displayName: string;\r\n key: string;\r\n index: number;\r\n data: ColumnChartDataPoint[];\r\n identity: SelectionId;\r\n color: string;\r\n labelSettings: VisualDataLabelsSettings;\r\n }\r\n\r\n export interface ColumnChartDataPoint extends CartesianDataPoint, SelectableDataPoint, TooltipEnabledDataPoint, LabelEnabledDataPoint {\r\n categoryValue: number;\r\n /** Adjusted for 100% stacked if applicable */\r\n value: number;\r\n /** The top (column) or right (bar) of the rectangle, used for positioning stacked rectangles */\r\n position: number;\r\n valueAbsolute: number;\r\n /** Not adjusted for 100% stacked */\r\n valueOriginal: number;\r\n seriesIndex: number;\r\n labelSettings: VisualDataLabelsSettings;\r\n categoryIndex: number;\r\n color: string;\r\n /** The original values from the highlighted rect, used in animations */\r\n originalValue: number;\r\n originalPosition: number;\r\n originalValueAbsolute: number;\r\n\r\n /**\r\n * True if this data point is a highlighted portion and overflows (whether due to the highlight\r\n * being greater than original or of a different sign), so it needs to be thinner to accomodate.\r\n */\r\n drawThinner?: boolean;\r\n key: string;\r\n lastSeries?: boolean;\r\n chartType: ColumnChartType;\r\n }\r\n\r\n const flagBar: number = 1 << 1;\r\n const flagColumn: number = 1 << 2;\r\n const flagClustered: number = 1 << 3;\r\n const flagStacked: number = 1 << 4;\r\n const flagStacked100: number = flagStacked | (1 << 5);\r\n\r\n export enum ColumnChartType {\r\n clusteredBar = flagBar | flagClustered,\r\n clusteredColumn = flagColumn | flagClustered,\r\n hundredPercentStackedBar = flagBar | flagStacked100,\r\n hundredPercentStackedColumn = flagColumn | flagStacked100,\r\n stackedBar = flagBar | flagStacked,\r\n stackedColumn = flagColumn | flagStacked,\r\n }\r\n\r\n export interface ColumnAxisOptions {\r\n xScale: D3.Scale.Scale;\r\n yScale: D3.Scale.Scale;\r\n seriesOffsetScale?: D3.Scale.Scale;\r\n columnWidth: number;\r\n /** Used by clustered only since categoryWidth !== columnWidth */\r\n categoryWidth?: number;\r\n isScalar: boolean;\r\n margin: IMargin;\r\n }\r\n\r\n export interface IColumnLayout {\r\n shapeLayout: {\r\n width: (d: ColumnChartDataPoint) => number;\r\n x: (d: ColumnChartDataPoint) => number;\r\n y: (d: ColumnChartDataPoint) => number;\r\n height: (d: ColumnChartDataPoint) => number;\r\n };\r\n shapeLayoutWithoutHighlights: {\r\n width: (d: ColumnChartDataPoint) => number;\r\n x: (d: ColumnChartDataPoint) => number;\r\n y: (d: ColumnChartDataPoint) => number;\r\n height: (d: ColumnChartDataPoint) => number;\r\n };\r\n zeroShapeLayout: {\r\n width: (d: ColumnChartDataPoint) => number;\r\n x: (d: ColumnChartDataPoint) => number;\r\n y: (d: ColumnChartDataPoint) => number;\r\n height: (d: ColumnChartDataPoint) => number;\r\n };\r\n }\r\n\r\n export interface ColumnChartContext {\r\n height: number;\r\n width: number;\r\n duration: number;\r\n hostService: IVisualHostServices;\r\n margin: IMargin;\r\n /** A group for graphics can be placed that won't be clipped to the data area of the chart. */\r\n unclippedGraphicsContext: D3.Selection;\r\n /** A SVG for graphics that should be clipped to the data area, e.g. data bars, columns, lines */\r\n mainGraphicsContext: D3.Selection;\r\n layout: CategoryLayout;\r\n animator: IColumnChartAnimator;\r\n onDragStart?: (datum: ColumnChartDataPoint) => void;\r\n interactivityService: IInteractivityService;\r\n viewportHeight: number;\r\n viewportWidth: number;\r\n is100Pct: boolean;\r\n isComboChart: boolean;\r\n }\r\n\r\n export interface IColumnChartStrategy {\r\n setData(data: ColumnChartData): void;\r\n setupVisualProps(columnChartProps: ColumnChartContext): void;\r\n setXScale(is100Pct: boolean, forcedTickCount?: number, forcedXDomain?: any[], axisScaleType?: string, axisDisplayUnits?: number, axisPrecision?: number, ensureXDomain?: NumberRange): IAxisProperties;\r\n setYScale(is100Pct: boolean, forcedTickCount?: number, forcedYDomain?: any[], axisScaleType?: string, axisDisplayUnits?: number, axisPrecision?: number, ensureYDomain?: NumberRange): IAxisProperties;\r\n drawColumns(useAnimation: boolean): ColumnChartDrawInfo;\r\n selectColumn(selectedColumnIndex: number, lastSelectedColumnIndex: number): void;\r\n getClosestColumnIndex(x: number, y: number): number;\r\n }\r\n\r\n export interface IColumnChartConverterStrategy {\r\n getLegend(colors: IDataColorPalette, defaultLegendLabelColor: string, defaultColor?: string): LegendSeriesInfo;\r\n getValueBySeriesAndCategory(series: number, category: number): number;\r\n getMeasureNameByIndex(series: number, category: number): string;\r\n hasHighlightValues(series: number): boolean;\r\n getHighlightBySeriesAndCategory(series: number, category: number): number;\r\n }\r\n\r\n export interface LegendSeriesInfo {\r\n legend: LegendData;\r\n seriesSources: DataViewMetadataColumn[];\r\n seriesObjects: DataViewObjects[][];\r\n }\r\n\r\n export interface ColumnChartDrawInfo {\r\n eventGroup: D3.Selection;\r\n shapesSelection: D3.Selection;\r\n viewport: IViewport;\r\n axisOptions: ColumnAxisOptions;\r\n labelDataPoints: LabelDataPoint[];\r\n }\r\n const RoleNames = {\r\n category: 'Category',\r\n series: 'Series',\r\n y: 'Y',\r\n };\r\n\r\n /**\r\n * Renders a stacked and clustered column chart.\r\n */\r\n export class ColumnChart implements ICartesianVisual {\r\n private static ColumnChartClassName = 'columnChart';\r\n public static clusteredValidLabelPositions: RectLabelPosition[] = [RectLabelPosition.OutsideEnd, RectLabelPosition.InsideEnd, RectLabelPosition.InsideCenter, RectLabelPosition.InsideBase];\r\n public static stackedValidLabelPositions: RectLabelPosition[] = [RectLabelPosition.InsideCenter, RectLabelPosition.InsideEnd, RectLabelPosition.InsideBase];\r\n public static SeriesClasses: jsCommon.CssConstants.ClassAndSelector = jsCommon.CssConstants.createClassAndSelector('series');\r\n\r\n private svg: D3.Selection;\r\n private unclippedGraphicsContext: D3.Selection;\r\n private mainGraphicsContext: D3.Selection;\r\n private xAxisProperties: IAxisProperties;\r\n private yAxisProperties: IAxisProperties;\r\n private currentViewport: IViewport;\r\n private data: ColumnChartData;\r\n private style: IVisualStyle;\r\n private colors: IDataColorPalette;\r\n private chartType: ColumnChartType;\r\n private columnChart: IColumnChartStrategy;\r\n private hostService: IVisualHostServices;\r\n private cartesianVisualHost: ICartesianVisualHost;\r\n private interactivity: InteractivityOptions;\r\n private margin: IMargin;\r\n private options: CartesianVisualInitOptions;\r\n private lastInteractiveSelectedColumnIndex: number;\r\n private interactivityService: IInteractivityService;\r\n private dataView: DataView;\r\n private categoryAxisType: string;\r\n private animator: IColumnChartAnimator;\r\n private isScrollable: boolean;\r\n private tooltipsEnabled: boolean;\r\n private tooltipBucketEnabled: boolean;\r\n private element: JQuery;\r\n private isComboChart: boolean;\r\n\r\n constructor(options: ColumnChartConstructorOptions) {\r\n debug.assertValue(options, 'options');\r\n\r\n let chartType = options.chartType;\r\n debug.assertValue(chartType, 'chartType');\r\n this.chartType = chartType;\r\n this.categoryAxisType = null;\r\n this.animator = options.animator;\r\n this.isScrollable = options.isScrollable;\r\n this.tooltipsEnabled = options.tooltipsEnabled;\r\n this.tooltipBucketEnabled = options.tooltipBucketEnabled;\r\n this.interactivityService = options.interactivityService;\r\n }\r\n\r\n public static customizeQuery(options: CustomizeQueryOptions): void {\r\n let dataViewMapping = options.dataViewMappings[0];\r\n if (!dataViewMapping || !dataViewMapping.categorical || !dataViewMapping.categorical.categories)\r\n return;\r\n\r\n dataViewMapping.categorical.dataVolume = 4;\r\n\r\n if (CartesianChart.detectScalarMapping(dataViewMapping)) {\r\n let dataViewCategories = <data.CompiledDataViewRoleForMappingWithReduction>dataViewMapping.categorical.categories;\r\n dataViewCategories.dataReductionAlgorithm = { sample: {} };\r\n }\r\n else {\r\n CartesianChart.applyLoadMoreEnabledToMapping(options.cartesianLoadMoreEnabled, dataViewMapping);\r\n }\r\n }\r\n\r\n public static getSortableRoles(options: VisualSortableOptions): string[] {\r\n let dataViewMapping = options.dataViewMappings[0];\r\n if (!dataViewMapping || !dataViewMapping.categorical || !dataViewMapping.categorical.categories)\r\n return null;\r\n\r\n let dataViewCategories = <data.CompiledDataViewRoleForMappingWithReduction>dataViewMapping.categorical.categories;\r\n let categoryItems = dataViewCategories.for.in.items;\r\n if (!_.isEmpty(categoryItems)) {\r\n let categoryType = categoryItems[0].type;\r\n\r\n let objects: DataViewObjects;\r\n if (dataViewMapping.metadata)\r\n objects = dataViewMapping.metadata.objects;\r\n\r\n //TODO: column chart should be sortable by X if it has scalar axis\r\n // But currenly it doesn't support this. Return 'category' once\r\n // it is supported.\r\n if (!CartesianChart.getIsScalar(objects, columnChartProps.categoryAxis.axisType, categoryType)) {\r\n return ['Category', 'Y'];\r\n }\r\n }\r\n\r\n return null;\r\n }\r\n\r\n public updateVisualMetadata(x: IAxisProperties, y: IAxisProperties, margin) {\r\n this.xAxisProperties = x;\r\n this.yAxisProperties = y;\r\n this.margin = margin;\r\n }\r\n\r\n public init(options: CartesianVisualInitOptions) {\r\n this.svg = options.svg;\r\n this.svg.classed(ColumnChart.ColumnChartClassName, true);\r\n\r\n this.unclippedGraphicsContext = this.svg.append('g').classed('columnChartUnclippedGraphicsContext', true);\r\n this.mainGraphicsContext = this.unclippedGraphicsContext.append('svg').classed('mainGraphicsContext', true);\r\n this.style = options.style;\r\n this.currentViewport = options.viewport;\r\n this.hostService = options.host;\r\n this.interactivity = options.interactivity;\r\n this.colors = this.style.colorPalette.dataColors;\r\n this.cartesianVisualHost = options.cartesianHost;\r\n this.options = options;\r\n this.isComboChart = ComboChart.isComboChart(options.chartType);\r\n this.element = options.element;\r\n }\r\n\r\n private getCategoryLayout(numCategoryValues: number, options: CalculateScaleAndDomainOptions): CategoryLayout {\r\n let availableWidth: number;\r\n if (ColumnChart.isBar(this.chartType)) {\r\n availableWidth = this.currentViewport.height - (this.margin.top + this.margin.bottom);\r\n }\r\n else {\r\n availableWidth = this.currentViewport.width - (this.margin.left + this.margin.right);\r\n }\r\n\r\n let metaDataColumn = this.data ? this.data.categoryMetadata : undefined;\r\n let categoryDataType: ValueTypeDescriptor = AxisHelper.getCategoryValueType(metaDataColumn);\r\n let isScalar = this.data ? this.data.scalarCategoryAxis : false;\r\n let domain = AxisHelper.createDomain(this.data.series, categoryDataType, isScalar, options.forcedXDomain, options.ensureXDomain);\r\n return CartesianChart.getLayout(\r\n this.data,\r\n {\r\n availableWidth: availableWidth,\r\n categoryCount: numCategoryValues,\r\n domain: domain,\r\n isScalar: isScalar,\r\n isScrollable: this.isScrollable,\r\n trimOrdinalDataOnOverflow: options.trimOrdinalDataOnOverflow\r\n });\r\n }\r\n\r\n public static converter(\r\n dataView: DataView,\r\n colors: IDataColorPalette,\r\n is100PercentStacked: boolean = false,\r\n isScalar: boolean = false,\r\n dataViewMetadata: DataViewMetadata = null,\r\n chartType?: ColumnChartType,\r\n interactivityService?: IInteractivityService,\r\n tooltipsEnabled: boolean = true,\r\n tooltipBucketEnabled?: boolean): ColumnChartData {\r\n debug.assertValue(dataView, 'dataView');\r\n debug.assertValue(colors, 'colors');\r\n\r\n let xAxisCardProperties = CartesianHelper.getCategoryAxisProperties(dataViewMetadata);\r\n let valueAxisProperties = CartesianHelper.getValueAxisProperties(dataViewMetadata);\r\n isScalar = CartesianHelper.isScalar(isScalar, xAxisCardProperties);\r\n let dataViewCat = ColumnUtil.applyUserMinMax(isScalar, dataView.categorical, xAxisCardProperties);\r\n\r\n let converterStrategy = new ColumnChartConverterHelper(dataView);\r\n\r\n let categoryInfo = converterHelper.getPivotedCategories(dataViewCat, columnChartProps.general.formatString);\r\n let categories = categoryInfo.categories,\r\n categoryFormatter: IValueFormatter = categoryInfo.categoryFormatter,\r\n categoryIdentities: DataViewScopeIdentity[] = categoryInfo.categoryIdentities,\r\n categoryMetadata: DataViewMetadataColumn = dataViewCat && dataViewCat.categories && dataViewCat.categories.length > 0 ? dataViewCat.categories[0].source : undefined;\r\n\r\n let labelSettings: VisualDataLabelsSettings = dataLabelUtils.getDefaultColumnLabelSettings(is100PercentStacked || ColumnChart.isStacked(chartType));\r\n let defaultLegendLabelColor = LegendData.DefaultLegendLabelFillColor;\r\n let defaultDataPointColor = undefined;\r\n let showAllDataPoints = undefined;\r\n if (dataViewMetadata && dataViewMetadata.objects) {\r\n let objects = dataViewMetadata.objects;\r\n\r\n defaultDataPointColor = DataViewObjects.getFillColor(objects, columnChartProps.dataPoint.defaultColor);\r\n showAllDataPoints = DataViewObjects.getValue<boolean>(objects, columnChartProps.dataPoint.showAllDataPoints);\r\n defaultLegendLabelColor = DataViewObjects.getFillColor(objects, columnChartProps.legend.labelColor, LegendData.DefaultLegendLabelFillColor);\r\n\r\n let labelsObj = <DataLabelObject>objects['labels'];\r\n dataLabelUtils.updateLabelSettingsFromLabelsObject(labelsObj, labelSettings);\r\n }\r\n\r\n // Allocate colors\r\n let legendAndSeriesInfo = converterStrategy.getLegend(colors, defaultLegendLabelColor, defaultDataPointColor);\r\n let legend: LegendDataPoint[] = legendAndSeriesInfo.legend.dataPoints;\r\n let seriesSources: DataViewMetadataColumn[] = legendAndSeriesInfo.seriesSources;\r\n\r\n // Determine data points\r\n let result = ColumnChart.createDataPoints(\r\n dataView,\r\n categories,\r\n categoryIdentities,\r\n legend,\r\n legendAndSeriesInfo.seriesObjects,\r\n converterStrategy,\r\n labelSettings,\r\n is100PercentStacked,\r\n isScalar,\r\n converterHelper.categoryIsAlsoSeriesRole(dataView, RoleNames.series, RoleNames.category),\r\n categoryInfo.categoryObjects,\r\n defaultDataPointColor,\r\n chartType,\r\n categoryMetadata,\r\n tooltipsEnabled,\r\n tooltipBucketEnabled);\r\n let columnSeries: ColumnChartSeries[] = result.series;\r\n\r\n let valuesMetadata: DataViewMetadataColumn[] = [];\r\n for (let j = 0, jlen = legend.length; j < jlen; j++) {\r\n valuesMetadata.push(seriesSources[j]);\r\n }\r\n\r\n let labels = converterHelper.createAxesLabels(xAxisCardProperties, valueAxisProperties, categoryMetadata, valuesMetadata);\r\n\r\n if (!ColumnChart.isColumn(chartType)) {\r\n // Replace between x and y axes\r\n let temp = labels.xAxisLabel;\r\n labels.xAxisLabel = labels.yAxisLabel;\r\n labels.yAxisLabel = temp;\r\n }\r\n\r\n if (interactivityService) {\r\n for (let series of columnSeries) {\r\n interactivityService.applySelectionStateToData(series.data);\r\n }\r\n\r\n interactivityService.applySelectionStateToData(legendAndSeriesInfo.legend.dataPoints);\r\n }\r\n\r\n return {\r\n categories: categories,\r\n categoryFormatter: categoryFormatter,\r\n series: columnSeries,\r\n valuesMetadata: valuesMetadata,\r\n legendData: legendAndSeriesInfo.legend,\r\n hasHighlights: result.hasHighlights,\r\n categoryMetadata: categoryMetadata,\r\n scalarCategoryAxis: isScalar,\r\n labelSettings: labelSettings,\r\n axesLabels: { x: labels.xAxisLabel, y: labels.yAxisLabel },\r\n hasDynamicSeries: result.hasDynamicSeries,\r\n isMultiMeasure: result.isMultiMeasure,\r\n defaultDataPointColor: defaultDataPointColor,\r\n showAllDataPoints: showAllDataPoints,\r\n };\r\n }\r\n\r\n private static canSupportOverflow(chartType: ColumnChartType, seriesCount: number): boolean {\r\n return !ColumnChart.isStacked(chartType) || seriesCount === 1;\r\n }\r\n\r\n private static createDataPoints(\r\n dataView: DataView,\r\n categories: any[],\r\n categoryIdentities: DataViewScopeIdentity[],\r\n legend: LegendDataPoint[],\r\n seriesObjectsList: DataViewObjects[][],\r\n converterStrategy: ColumnChartConverterHelper,\r\n defaultLabelSettings: VisualDataLabelsSettings,\r\n is100PercentStacked: boolean = false,\r\n isScalar: boolean = false,\r\n isCategoryAlsoSeries?: boolean,\r\n categoryObjectsList?: DataViewObjects[],\r\n defaultDataPointColor?: string,\r\n chartType?: ColumnChartType,\r\n categoryMetadata?: DataViewMetadataColumn,\r\n tooltipsEnabled?: boolean,\r\n tooltipBucketEnabled?: boolean): { series: ColumnChartSeries[]; hasHighlights: boolean; hasDynamicSeries: boolean; isMultiMeasure: boolean } {\r\n let dataViewCat = dataView.categorical;\r\n\r\n let reader = powerbi.data.createIDataViewCategoricalReader(dataView);\r\n let grouped = dataViewCat && dataViewCat.values ? dataViewCat.values.grouped() : undefined;\r\n let categoryCount = categories.length;\r\n let seriesCount = reader.hasValues('Y') ? reader.getSeriesCount('Y') : undefined;\r\n let columnSeries: ColumnChartSeries[] = [];\r\n\r\n if (seriesCount < 1 || categoryCount < 1)\r\n return { series: columnSeries, hasHighlights: false, hasDynamicSeries: false, isMultiMeasure: false };\r\n\r\n let dvCategories = dataViewCat.categories;\r\n categoryMetadata = !_.isEmpty(dvCategories) ? dvCategories[0].source : null;\r\n let categoryType = AxisHelper.getCategoryValueType(categoryMetadata);\r\n let isDateTime = AxisHelper.isDateTime(categoryType);\r\n let baseValuesPos = [], baseValuesNeg = [];\r\n\r\n let rawValues: number[][] = [];\r\n let rawHighlightValues: number[][] = [];\r\n\r\n let hasDynamicSeries = !!(dataViewCat.values && dataViewCat.values.source);\r\n let isMultiMeasure = !hasDynamicSeries && seriesCount > 1;\r\n\r\n let highlightsOverflow = false; // Overflow means the highlight larger than value or the signs being different\r\n let hasHighlights = converterStrategy.hasHighlightValues(0);\r\n for (let seriesIndex = 0; seriesIndex < seriesCount; seriesIndex++) {\r\n let seriesValues = [];\r\n let seriesHighlightValues = [];\r\n for (let categoryIndex = 0; categoryIndex < categoryCount; categoryIndex++) {\r\n let value = converterStrategy.getValueBySeriesAndCategory(seriesIndex, categoryIndex);\r\n seriesValues[categoryIndex] = value;\r\n if (hasHighlights) {\r\n let highlightValue = converterStrategy.getHighlightBySeriesAndCategory(seriesIndex, categoryIndex);\r\n seriesHighlightValues[categoryIndex] = highlightValue;\r\n // There are two cases where we don't use overflow logic; if all are false, use overflow logic appropriate for the chart.\r\n if (!((value >= 0 && highlightValue >= 0 && value >= highlightValue) || // Both positive; value greater than highlight\r\n (value <= 0 && highlightValue <= 0 && value <= highlightValue))) { // Both negative; value less than highlight\r\n highlightsOverflow = true;\r\n }\r\n }\r\n }\r\n rawValues.push(seriesValues);\r\n if (hasHighlights) {\r\n rawHighlightValues.push(seriesHighlightValues);\r\n }\r\n }\r\n\r\n if (highlightsOverflow && !ColumnChart.canSupportOverflow(chartType, seriesCount)) {\r\n highlightsOverflow = false;\r\n hasHighlights = false;\r\n rawValues = rawHighlightValues;\r\n }\r\n\r\n let dataPointObjects: DataViewObjects[] = categoryObjectsList,\r\n formatStringProp = columnChartProps.general.formatString;\r\n for (let seriesIndex = 0; seriesIndex < seriesCount; seriesIndex++) {\r\n let seriesDataPoints: ColumnChartDataPoint[] = [],\r\n legendItem = legend[seriesIndex],\r\n seriesLabelSettings: VisualDataLabelsSettings;\r\n\r\n if (!hasDynamicSeries) {\r\n let labelsSeriesGroup = !_.isEmpty(grouped) && grouped[0].values ? grouped[0].values[seriesIndex] : null;\r\n let labelObjects = (labelsSeriesGroup && labelsSeriesGroup.source && labelsSeriesGroup.source.objects) ? <DataLabelObject>labelsSeriesGroup.source.objects['labels'] : null;\r\n if (labelObjects) {\r\n seriesLabelSettings = Prototype.inherit(defaultLabelSettings);\r\n dataLabelUtils.updateLabelSettingsFromLabelsObject(labelObjects, seriesLabelSettings);\r\n }\r\n }\r\n\r\n columnSeries.push({\r\n displayName: legendItem.label,\r\n key: 'series' + seriesIndex,\r\n index: seriesIndex,\r\n data: seriesDataPoints,\r\n identity: legendItem.identity,\r\n color: legendItem.color,\r\n labelSettings: seriesLabelSettings,\r\n });\r\n\r\n if (seriesCount > 1)\r\n dataPointObjects = seriesObjectsList[seriesIndex];\r\n let valueColumnMetadata = reader.getValueMetadataColumn('Y', seriesIndex);\r\n let gradientMeasureIndex: number = GradientUtils.getGradientMeasureIndex(dataViewCat);\r\n let gradientValueColumn: DataViewValueColumn = GradientUtils.getGradientValueColumn(dataViewCat);\r\n let valueMeasureIndex: number = DataRoleHelper.getMeasureIndexOfRole(grouped, \"Y\");\r\n let pctFormatString = valueFormatter.getLocalizedString('Percentage');\r\n\r\n for (let categoryIndex = 0; categoryIndex < categoryCount; categoryIndex++) {\r\n if (seriesIndex === 0) {\r\n baseValuesPos.push(0);\r\n baseValuesNeg.push(0);\r\n }\r\n\r\n let value = AxisHelper.normalizeNonFiniteNumber(rawValues[seriesIndex][categoryIndex]);\r\n if (value == null) {\r\n // Optimization: Ignore null dataPoints from the fabricated category/series combination in the self cross-join.\r\n // However, we must retain the first series because it is used to compute things like axis scales, and value lookups.\r\n if (seriesIndex > 0)\r\n continue;\r\n }\r\n\r\n let originalValue: number = value;\r\n let categoryValue = categories[categoryIndex];\r\n\r\n // ignore variant measures\r\n if (isDateTime && categoryValue != null && !(categoryValue instanceof Date))\r\n continue;\r\n\r\n if (isDateTime && categoryValue)\r\n categoryValue = categoryValue.getTime();\r\n if (isScalar && (categoryValue == null || isNaN(categoryValue)))\r\n continue;\r\n\r\n let multipliers: ValueMultiplers;\r\n if (is100PercentStacked)\r\n multipliers = StackedUtil.getStackedMultiplier(dataViewCat, categoryIndex, seriesCount, categoryCount, converterStrategy);\r\n\r\n let unadjustedValue = value,\r\n isNegative = value < 0;\r\n\r\n if (multipliers) {\r\n if (isNegative)\r\n value *= multipliers.neg;\r\n else\r\n value *= multipliers.pos;\r\n }\r\n\r\n let valueAbsolute = Math.abs(value);\r\n let position: number;\r\n if (isNegative) {\r\n position = baseValuesNeg[categoryIndex];\r\n\r\n if (!isNaN(valueAbsolute))\r\n baseValuesNeg[categoryIndex] -= valueAbsolute;\r\n }\r\n else {\r\n if (!isNaN(valueAbsolute))\r\n baseValuesPos[categoryIndex] += valueAbsolute;\r\n\r\n position = baseValuesPos[categoryIndex];\r\n }\r\n\r\n let seriesGroup = grouped && grouped.length > seriesIndex && grouped[seriesIndex].values ? grouped[seriesIndex].values[valueMeasureIndex] : null;\r\n let category = !_.isEmpty(dataViewCat.categories) ? dataViewCat.categories[0] : null;\r\n let identity = SelectionIdBuilder.builder()\r\n .withCategory(category, categoryIndex)\r\n .withSeries(dataViewCat.values, seriesGroup)\r\n .withMeasure(converterStrategy.getMeasureNameByIndex(seriesIndex))\r\n .createSelectionId();\r\n\r\n let rawCategoryValue = reader.getCategoryValue('Category', categoryIndex);\r\n let color = ColumnChart.getDataPointColor(legendItem, categoryIndex, dataPointObjects);\r\n let gradientColumnForTooltip = gradientMeasureIndex === 0 ? null : gradientValueColumn;\r\n\r\n let valueHighlight: any;\r\n let unadjustedValueHighlight: any;\r\n let absoluteValueHighlight: number;\r\n let highlightValue: string;\r\n let highlightPosition: number;\r\n let highlightIdentity: SelectionId;\r\n if (hasHighlights) {\r\n valueHighlight = reader.getHighlight('Y', categoryIndex, seriesIndex);\r\n unadjustedValueHighlight = valueHighlight;\r\n\r\n let highlightedTooltip: boolean = true;\r\n if (valueHighlight === null) {\r\n valueHighlight = 0;\r\n highlightedTooltip = false;\r\n }\r\n\r\n if (is100PercentStacked) {\r\n valueHighlight *= multipliers.pos;\r\n }\r\n absoluteValueHighlight = Math.abs(valueHighlight);\r\n highlightPosition = position;\r\n\r\n if (valueHighlight > 0) {\r\n highlightPosition -= valueAbsolute - absoluteValueHighlight;\r\n }\r\n else if (valueHighlight === 0 && value > 0) {\r\n highlightPosition -= valueAbsolute;\r\n }\r\n\r\n highlightIdentity = SelectionId.createWithHighlight(identity);\r\n\r\n let highlightedValueAndPct: string;\r\n let highlightedValueFormat: string;\r\n if (highlightedTooltip && unadjustedValueHighlight != null && valueHighlight != null) {\r\n let highlightedPct: string = valueFormatter.format(valueHighlight, pctFormatString);\r\n highlightedValueFormat = converterHelper.formatFromMetadataColumn(unadjustedValueHighlight, valueColumnMetadata, formatStringProp);\r\n highlightedValueAndPct = highlightedValueFormat + ' (' + highlightedPct + ')';\r\n }\r\n if (is100PercentStacked) {\r\n highlightValue = highlightedValueAndPct;\r\n }\r\n else {\r\n highlightValue = highlightedValueFormat;\r\n }\r\n }\r\n\r\n let tooltipInfo: TooltipDataItem[];\r\n\r\n if (tooltipsEnabled) {\r\n tooltipInfo = [];\r\n if (category) {\r\n tooltipInfo.push({\r\n displayName: reader.getCategoryDisplayName('Category'),\r\n value: converterHelper.formatFromMetadataColumn(rawCategoryValue, category.source, formatStringProp),\r\n });\r\n }\r\n\r\n if (hasDynamicSeries) {\r\n if (!category || category.source !== dataViewCat.values.source) {\r\n // Category/series on the same column -- don't repeat its value in the tooltip.\r\n tooltipInfo.push({\r\n displayName: dataViewCat.values.source.displayName,\r\n value: converterHelper.formatFromMetadataColumn(grouped[seriesIndex].name, dataViewCat.values.source, formatStringProp),\r\n });\r\n }\r\n }\r\n\r\n if (originalValue != null) {\r\n let valueString: string;\r\n let formattedOriginalValue = converterHelper.formatFromMetadataColumn(originalValue, valueColumnMetadata, formatStringProp);\r\n if (is100PercentStacked) {\r\n let originalPct: string = valueFormatter.format(valueAbsolute, pctFormatString);\r\n valueString = formattedOriginalValue + ' (' + originalPct + ')';\r\n }\r\n else {\r\n valueString = formattedOriginalValue;\r\n }\r\n tooltipInfo.push({\r\n displayName: valueColumnMetadata.displayName,\r\n value: valueString,\r\n });\r\n }\r\n\r\n if (highlightValue != null) {\r\n tooltipInfo.push({\r\n displayName: ToolTipComponent.localizationOptions.highlightedValueDisplayName,\r\n value: highlightValue,\r\n });\r\n }\r\n\r\n if (gradientColumnForTooltip && gradientColumnForTooltip.values[categoryIndex] != null) {\r\n tooltipInfo.push({\r\n displayName: gradientColumnForTooltip.source.displayName,\r\n value: converterHelper.formatFromMetadataColumn(gradientColumnForTooltip.values[categoryIndex], gradientColumnForTooltip.source, formatStringProp),\r\n });\r\n }\r\n\r\n if (tooltipBucketEnabled) {\r\n TooltipBuilder.addTooltipBucketItem(reader, tooltipInfo, categoryIndex, hasDynamicSeries ? seriesIndex : undefined);\r\n }\r\n }\r\n\r\n let series = columnSeries[seriesIndex];\r\n let dataPointLabelSettings = (series.labelSettings) ? series.labelSettings : defaultLabelSettings;\r\n let labelColor = dataPointLabelSettings.labelColor;\r\n let lastValue = undefined;\r\n //Stacked column/bar label color is white by default (except last series)\r\n if ((ColumnChart.isStacked(chartType))) {\r\n lastValue = this.getStackedLabelColor(isNegative, seriesIndex, seriesCount, categoryIndex, rawValues);\r\n labelColor = (lastValue || (seriesIndex === seriesCount - 1 && !isNegative)) ? labelColor : dataLabelUtils.defaultInsideLabelColor;\r\n }\r\n\r\n let dataPoint: ColumnChartDataPoint = {\r\n categoryValue: categoryValue,\r\n value: value,\r\n position: position,\r\n valueAbsolute: valueAbsolute,\r\n valueOriginal: unadjustedValue,\r\n seriesIndex: seriesIndex,\r\n labelSettings: dataPointLabelSettings,\r\n categoryIndex: categoryIndex,\r\n color: color,\r\n selected: false,\r\n originalValue: value,\r\n originalPosition: position,\r\n originalValueAbsolute: valueAbsolute,\r\n identity: identity,\r\n key: identity.getKey(),\r\n tooltipInfo: tooltipInfo,\r\n labelFill: labelColor,\r\n labelFormatString: valueColumnMetadata.format,\r\n lastSeries: lastValue,\r\n chartType: chartType\r\n };\r\n\r\n seriesDataPoints.push(dataPoint);\r\n\r\n if (hasHighlights) {\r\n let highlightDataPoint: ColumnChartDataPoint = {\r\n categoryValue: categoryValue,\r\n value: valueHighlight,\r\n position: highlightPosition,\r\n valueAbsolute: absoluteValueHighlight,\r\n valueOriginal: unadjustedValueHighlight,\r\n seriesIndex: seriesIndex,\r\n labelSettings: dataPointLabelSettings,\r\n categoryIndex: categoryIndex,\r\n color: color,\r\n selected: false,\r\n highlight: true,\r\n originalValue: value,\r\n originalPosition: position,\r\n originalValueAbsolute: valueAbsolute,\r\n drawThinner: highlightsOverflow,\r\n identity: highlightIdentity,\r\n key: highlightIdentity.getKey(),\r\n tooltipInfo: tooltipInfo,\r\n labelFormatString: valueColumnMetadata.format,\r\n labelFill: labelColor,\r\n lastSeries: lastValue,\r\n chartType: chartType\r\n };\r\n\r\n seriesDataPoints.push(highlightDataPoint);\r\n }\r\n }\r\n }\r\n\r\n return {\r\n series: columnSeries,\r\n hasHighlights: hasHighlights,\r\n hasDynamicSeries: hasDynamicSeries,\r\n isMultiMeasure: isMultiMeasure,\r\n };\r\n }\r\n\r\n private static getDataPointColor(\r\n legendItem: LegendDataPoint,\r\n categoryIndex: number,\r\n dataPointObjects?: DataViewObjects[]): string {\r\n debug.assertValue(legendItem, 'legendItem');\r\n debug.assertValue(categoryIndex, 'categoryIndex');\r\n debug.assertAnyValue(dataPointObjects, 'dataPointObjects');\r\n\r\n if (dataPointObjects) {\r\n let colorOverride = DataViewObjects.getFillColor(dataPointObjects[categoryIndex], columnChartProps.dataPoint.fill);\r\n if (colorOverride)\r\n return colorOverride;\r\n }\r\n\r\n return legendItem.color;\r\n }\r\n\r\n private static getStackedLabelColor(isNegative: boolean, seriesIndex: number, seriesCount: number, categoryIndex: number, rawValues: number[][]): boolean {\r\n let lastValue = !(isNegative && seriesIndex === seriesCount - 1 && seriesCount !== 1);\r\n //run for the next series and check if current series is last\r\n for (let i = seriesIndex + 1; i < seriesCount; i++) {\r\n let nextValues = AxisHelper.normalizeNonFiniteNumber(rawValues[i][categoryIndex]);\r\n if ((nextValues !== null) && (((!isNegative || (isNegative && seriesIndex === 0)) && nextValues > 0) || (isNegative && seriesIndex !== 0))) {\r\n lastValue = false;\r\n break;\r\n }\r\n }\r\n return lastValue;\r\n }\r\n\r\n public static sliceSeries(series: ColumnChartSeries[], endIndex: number, startIndex: number = 0): ColumnChartSeries[] {\r\n let newSeries: ColumnChartSeries[] = [];\r\n if (series && series.length > 0) {\r\n for (let i = 0, len = series.length; i < len; i++) {\r\n let iNewSeries = newSeries[i] = Prototype.inherit(series[i]);\r\n // TODO: [investigate] possible perf improvement.\r\n // if data[n].categoryIndex > endIndex implies data[n+1].categoryIndex > endIndex\r\n // then we could short circuit the filter loop.\r\n iNewSeries.data = series[i].data.filter(d => d.categoryIndex >= startIndex && d.categoryIndex < endIndex);\r\n }\r\n }\r\n return newSeries;\r\n }\r\n\r\n public static getInteractiveColumnChartDomElement(element: JQuery): HTMLElement {\r\n return element.children(\"svg\").get(0);\r\n }\r\n\r\n public setData(dataViews: DataView[]): void {\r\n debug.assertValue(dataViews, \"dataViews\");\r\n let is100PctStacked = ColumnChart.isStacked100(this.chartType);\r\n this.data = {\r\n categories: [],\r\n categoryFormatter: null,\r\n series: [],\r\n valuesMetadata: [],\r\n legendData: null,\r\n hasHighlights: false,\r\n categoryMetadata: null,\r\n scalarCategoryAxis: false,\r\n labelSettings: dataLabelUtils.getDefaultColumnLabelSettings(is100PctStacked || ColumnChart.isStacked(this.chartType)),\r\n axesLabels: { x: null, y: null },\r\n hasDynamicSeries: false,\r\n defaultDataPointColor: null,\r\n isMultiMeasure: false,\r\n };\r\n\r\n if (dataViews.length > 0) {\r\n let dataView = this.dataView = dataViews[0];\r\n\r\n if (dataView && dataView.categorical) {\r\n let dvCategories = dataView.categorical.categories;\r\n let categoryMetadata = (dvCategories && dvCategories.length > 0)\r\n ? dvCategories[0].source\r\n : null;\r\n let categoryType = AxisHelper.getCategoryValueType(categoryMetadata);\r\n\r\n this.data = ColumnChart.converter(\r\n dataView,\r\n this.cartesianVisualHost.getSharedColors(),\r\n is100PctStacked,\r\n CartesianChart.getIsScalar(dataView.metadata ? dataView.metadata.objects : null, columnChartProps.categoryAxis.axisType, categoryType),\r\n dataView.metadata,\r\n this.chartType,\r\n this.interactivityService,\r\n this.tooltipsEnabled,\r\n this.tooltipBucketEnabled);\r\n }\r\n }\r\n\r\n this.setChartStrategy();\r\n }\r\n\r\n private setChartStrategy(): void {\r\n switch (this.chartType) {\r\n case ColumnChartType.clusteredBar:\r\n this.columnChart = new ClusteredBarChartStrategy();\r\n break;\r\n case ColumnChartType.clusteredColumn:\r\n this.columnChart = new ClusteredColumnChartStrategy();\r\n break;\r\n case ColumnChartType.stackedBar:\r\n case ColumnChartType.hundredPercentStackedBar:\r\n this.columnChart = new StackedBarChartStrategy();\r\n break;\r\n case ColumnChartType.stackedColumn:\r\n case ColumnChartType.hundredPercentStackedColumn:\r\n default:\r\n this.columnChart = new StackedColumnChartStrategy();\r\n break;\r\n }\r\n\r\n // For single series, render stacked as a clustered\r\n if (ColumnChart.isStacked(this.chartType) && this.data.series.length === 1) {\r\n switch (this.chartType) {\r\n case (ColumnChartType.stackedBar):\r\n this.columnChart = new ClusteredBarChartStrategy();\r\n break;\r\n case (ColumnChartType.stackedColumn):\r\n this.columnChart = new ClusteredColumnChartStrategy();\r\n break;\r\n }\r\n }\r\n }\r\n\r\n public calculateLegend(): LegendData {\r\n // if we're in interactive mode, return the interactive legend\r\n if (this.interactivity && this.interactivity.isInteractiveLegend) {\r\n return this.createInteractiveLegendDataPoints(0);\r\n }\r\n let legendData = this.data ? this.data.legendData : null;\r\n let legendDataPoints = legendData ? legendData.dataPoints : [];\r\n\r\n if (_.isEmpty(legendDataPoints))\r\n return null;\r\n\r\n return legendData;\r\n }\r\n\r\n public hasLegend(): boolean {\r\n return this.data && (this.data.hasDynamicSeries || (this.data.series && this.data.series.length > 1));\r\n }\r\n\r\n public enumerateObjectInstances(enumeration: ObjectEnumerationBuilder, options: EnumerateVisualObjectInstancesOptions): void {\r\n switch (options.objectName) {\r\n case 'dataPoint':\r\n if (this.dataView && !GradientUtils.hasGradientRole(this.dataView.categorical))\r\n this.enumerateDataPoints(enumeration);\r\n break;\r\n case 'labels':\r\n this.enumerateDataLabels(enumeration);\r\n break;\r\n }\r\n }\r\n\r\n private enumerateDataLabels(enumeration: ObjectEnumerationBuilder): void {\r\n let data = this.data,\r\n labelSettings = this.data.labelSettings,\r\n seriesCount = data.series.length,\r\n showLabelPerSeries = !data.hasDynamicSeries && (seriesCount > 1 || !data.categoryMetadata);\r\n\r\n //Draw default settings\r\n dataLabelUtils.enumerateDataLabels(this.getLabelSettingsOptions(enumeration, labelSettings, null, showLabelPerSeries));\r\n\r\n if (seriesCount === 0)\r\n return;\r\n\r\n //Draw series settings\r\n if (showLabelPerSeries && labelSettings.showLabelPerSeries) {\r\n for (let i = 0; i < seriesCount; i++) {\r\n let series = data.series[i],\r\n labelSettings: VisualDataLabelsSettings = (series.labelSettings) ? series.labelSettings : this.data.labelSettings;\r\n\r\n enumeration.pushContainer({ displayName: series.displayName });\r\n dataLabelUtils.enumerateDataLabels(this.getLabelSettingsOptions(enumeration, labelSettings, series));\r\n enumeration.popContainer();\r\n }\r\n }\r\n }\r\n\r\n private getLabelSettingsOptions(enumeration: ObjectEnumerationBuilder, labelSettings: VisualDataLabelsSettings, series?: ColumnChartSeries, showAll?: boolean): VisualDataLabelsSettingsOptions {\r\n return {\r\n enumeration: enumeration,\r\n dataLabelsSettings: labelSettings,\r\n show: true,\r\n displayUnits: !ColumnChart.isStacked100(this.chartType),\r\n precision: true,\r\n selector: series && series.identity ? series.identity.getSelector() : null,\r\n showAll: showAll,\r\n fontSize: true,\r\n };\r\n }\r\n\r\n private enumerateDataPoints(enumeration: ObjectEnumerationBuilder): void {\r\n let data = this.data;\r\n if (!data)\r\n return;\r\n\r\n let seriesCount = data.series.length;\r\n\r\n if (seriesCount === 0)\r\n return;\r\n\r\n if (data.hasDynamicSeries || seriesCount > 1 || !data.categoryMetadata) {\r\n for (let i = 0; i < seriesCount; i++) {\r\n let series = data.series[i];\r\n enumeration.pushInstance({\r\n objectName: 'dataPoint',\r\n displayName: series.displayName,\r\n selector: ColorHelper.normalizeSelector(series.identity.getSelector()),\r\n properties: {\r\n fill: { solid: { color: series.color } }\r\n },\r\n });\r\n }\r\n }\r\n else {\r\n // For single-category, single-measure column charts, the user can color the individual bars.\r\n let singleSeriesData = data.series[0].data;\r\n let categoryFormatter = data.categoryFormatter;\r\n\r\n // Add default color and show all slices\r\n enumeration.pushInstance({\r\n objectName: 'dataPoint',\r\n selector: null,\r\n properties: {\r\n defaultColor: { solid: { color: data.defaultDataPointColor || this.colors.getColorByIndex(0).value } }\r\n }\r\n }).pushInstance({\r\n objectName: 'dataPoint',\r\n selector: null,\r\n properties: {\r\n showAllDataPoints: !!data.showAllDataPoints\r\n }\r\n });\r\n\r\n if (data.showAllDataPoints) {\r\n for (let i = 0; i < singleSeriesData.length; i++) {\r\n let singleSeriesDataPoints = singleSeriesData[i],\r\n categoryValue: any = data.categories[i];\r\n enumeration.pushInstance({\r\n objectName: 'dataPoint',\r\n displayName: categoryFormatter ? categoryFormatter.format(categoryValue) : categoryValue,\r\n selector: ColorHelper.normalizeSelector(singleSeriesDataPoints.identity.getSelector(), /*isSingleSeries*/true),\r\n properties: {\r\n fill: { solid: { color: singleSeriesDataPoints.color } }\r\n },\r\n });\r\n }\r\n }\r\n }\r\n }\r\n\r\n public calculateAxesProperties(options: CalculateScaleAndDomainOptions): IAxisProperties[] {\r\n let data = this.data;\r\n this.currentViewport = options.viewport;\r\n let margin = this.margin = options.margin;\r\n\r\n let origCatgSize = (data && data.categories) ? data.categories.length : 0;\r\n let chartLayout: CategoryLayout = data ? this.getCategoryLayout(origCatgSize, options) : {\r\n categoryCount: 0,\r\n categoryThickness: CartesianChart.MinOrdinalRectThickness,\r\n outerPaddingRatio: CartesianChart.OuterPaddingRatio,\r\n isScalar: false\r\n };\r\n this.categoryAxisType = chartLayout.isScalar ? axisType.scalar : null;\r\n\r\n if (data && !chartLayout.isScalar && !this.isScrollable && options.trimOrdinalDataOnOverflow) {\r\n // trim data that doesn't fit on dashboard\r\n let catgSize = Math.min(origCatgSize, chartLayout.categoryCount);\r\n if (catgSize !== origCatgSize) {\r\n data = Prototype.inherit(data);\r\n data.series = ColumnChart.sliceSeries(data.series, catgSize);\r\n data.categories = data.categories.slice(0, catgSize);\r\n }\r\n }\r\n this.columnChart.setData(data);\r\n\r\n let preferredPlotArea = this.getPreferredPlotArea(chartLayout.isScalar, chartLayout.categoryCount, chartLayout.categoryThickness);\r\n let is100Pct = ColumnChart.isStacked100(this.chartType);\r\n\r\n let chartContext: ColumnChartContext = {\r\n height: preferredPlotArea.height,\r\n width: preferredPlotArea.width,\r\n duration: 0,\r\n hostService: this.hostService,\r\n unclippedGraphicsContext: this.unclippedGraphicsContext,\r\n mainGraphicsContext: this.mainGraphicsContext,\r\n margin: this.margin,\r\n layout: chartLayout,\r\n animator: this.animator,\r\n interactivityService: this.interactivityService,\r\n viewportHeight: this.currentViewport.height - (margin.top + margin.bottom),\r\n viewportWidth: this.currentViewport.width - (margin.left + margin.right),\r\n is100Pct: is100Pct,\r\n isComboChart: this.isComboChart,\r\n };\r\n this.ApplyInteractivity(chartContext);\r\n this.columnChart.setupVisualProps(chartContext);\r\n\r\n let ensureXDomain: NumberRange;\r\n let ensureYDomain: NumberRange;\r\n\r\n let isBarChart = ColumnChart.isBar(this.chartType);\r\n\r\n if (isBarChart) {\r\n let temp = options.forcedXDomain;\r\n options.forcedXDomain = options.forcedYDomain;\r\n options.forcedYDomain = temp;\r\n\r\n // In the case of clustered and stacked bar charts, the y1 reference line is a vertical line\r\n ensureXDomain = options.ensureYDomain;\r\n }\r\n else {\r\n ensureYDomain = options.ensureYDomain;\r\n }\r\n\r\n this.xAxisProperties = this.columnChart.setXScale(\r\n is100Pct,\r\n options.forcedTickCount,\r\n options.forcedXDomain,\r\n isBarChart ? options.valueAxisScaleType : options.categoryAxisScaleType,\r\n isBarChart ? options.valueAxisDisplayUnits : options.categoryAxisDisplayUnits,\r\n isBarChart ? options.valueAxisPrecision : options.categoryAxisPrecision,\r\n ensureXDomain);\r\n\r\n this.yAxisProperties = this.columnChart.setYScale(\r\n is100Pct,\r\n options.forcedTickCount,\r\n options.forcedYDomain,\r\n isBarChart ? options.categoryAxisScaleType : options.valueAxisScaleType,\r\n isBarChart ? options.categoryAxisDisplayUnits : options.valueAxisDisplayUnits,\r\n isBarChart ? options.categoryAxisPrecision : options.valueAxisPrecision,\r\n ensureYDomain);\r\n\r\n if (options.showCategoryAxisLabel && this.xAxisProperties.isCategoryAxis || options.showValueAxisLabel && !this.xAxisProperties.isCategoryAxis) {\r\n this.xAxisProperties.axisLabel = data.axesLabels.x;\r\n }\r\n else {\r\n this.xAxisProperties.axisLabel = null;\r\n }\r\n if (options.showValueAxisLabel && !this.yAxisProperties.isCategoryAxis || options.showCategoryAxisLabel && this.yAxisProperties.isCategoryAxis) {\r\n this.yAxisProperties.axisLabel = data.axesLabels.y;\r\n }\r\n else {\r\n this.yAxisProperties.axisLabel = null;\r\n }\r\n\r\n return [this.xAxisProperties, this.yAxisProperties];\r\n }\r\n\r\n public getPreferredPlotArea(isScalar: boolean, categoryCount: number, categoryThickness: number): IViewport {\r\n let plotArea: IViewport = {\r\n height: this.currentViewport.height - this.margin.top - this.margin.bottom,\r\n width: this.currentViewport.width - this.margin.left - this.margin.right\r\n };\r\n\r\n if (this.isScrollable && !isScalar) {\r\n let preferredCategorySpan = CartesianChart.getPreferredCategorySpan(categoryCount, categoryThickness);\r\n if (ColumnChart.isBar(this.chartType)) {\r\n plotArea.height = Math.max(preferredCategorySpan, plotArea.height);\r\n }\r\n else\r\n plotArea.width = Math.max(preferredCategorySpan, plotArea.width);\r\n }\r\n return plotArea;\r\n }\r\n\r\n private ApplyInteractivity(chartContext: ColumnChartContext): void {\r\n let interactivity = this.interactivity;\r\n if (interactivity) {\r\n if (interactivity.dragDataPoint) {\r\n chartContext.onDragStart = (datum: ColumnChartDataPoint) => {\r\n if (!datum.identity)\r\n return;\r\n\r\n this.hostService.onDragStart({\r\n event: <any>d3.event,\r\n data: {\r\n data: datum.identity.getSelector()\r\n }\r\n });\r\n };\r\n }\r\n\r\n if (interactivity.isInteractiveLegend) {\r\n let dragMove = () => {\r\n let mousePoint = d3.mouse(this.mainGraphicsContext[0][0]); // get the x and y for the column area itself\r\n let x: number = mousePoint[0];\r\n let y: number = mousePoint[1];\r\n let index: number = this.columnChart.getClosestColumnIndex(x, y);\r\n this.selectColumn(index);\r\n };\r\n\r\n let ColumnChartSvg: EventTarget = ColumnChart.getInteractiveColumnChartDomElement(this.element);\r\n\r\n //set click interaction on the visual\r\n this.svg.on('click', dragMove);\r\n //set click interaction on the background\r\n d3.select(ColumnChartSvg)\r\n .on('click', dragMove)\r\n .style('touch-action', 'none');\r\n let drag = d3.behavior.drag()\r\n .origin(Object)\r\n .on(\"drag\", dragMove);\r\n //set drag interaction on the visual\r\n this.svg.call(drag);\r\n //set drag interaction on the background\r\n d3.select(ColumnChartSvg).call(drag);\r\n }\r\n }\r\n }\r\n\r\n private selectColumn(indexOfColumnSelected: number, force: boolean = false): void {\r\n if (!force && this.lastInteractiveSelectedColumnIndex === indexOfColumnSelected) return; // same column, nothing to do here\r\n\r\n let legendData: LegendData = this.createInteractiveLegendDataPoints(indexOfColumnSelected);\r\n let legendDataPoints: LegendDataPoint[] = legendData.dataPoints;\r\n this.cartesianVisualHost.updateLegend(legendData);\r\n if (legendDataPoints.length > 0) {\r\n this.columnChart.selectColumn(indexOfColumnSelected, this.lastInteractiveSelectedColumnIndex);\r\n }\r\n this.lastInteractiveSelectedColumnIndex = indexOfColumnSelected;\r\n }\r\n\r\n private createInteractiveLegendDataPoints(columnIndex: number): LegendData {\r\n let data = this.data;\r\n if (!data || _.isEmpty(data.series))\r\n return { dataPoints: [] };\r\n\r\n let formatStringProp = columnChartProps.general.formatString;\r\n let legendDataPoints: LegendDataPoint[] = [];\r\n let category = data.categories && data.categories[columnIndex];\r\n let allSeries = data.series;\r\n let dataPoints = data.legendData && data.legendData.dataPoints;\r\n let converterStrategy = new ColumnChartConverterHelper(this.dataView);\r\n\r\n for (let i = 0, len = allSeries.length; i < len; i++) {\r\n let measure = converterStrategy.getValueBySeriesAndCategory(i, columnIndex);\r\n let valueMetadata = data.valuesMetadata[i];\r\n let formattedLabel = converterHelper.getFormattedLegendLabel(valueMetadata, this.dataView.categorical.values, formatStringProp);\r\n let dataPointColor: string;\r\n if (allSeries.length === 1) {\r\n let series = allSeries[0];\r\n dataPointColor = series.data.length > columnIndex && series.data[columnIndex].color;\r\n } else {\r\n dataPointColor = dataPoints.length > i && dataPoints[i].color;\r\n }\r\n\r\n legendDataPoints.push({\r\n color: dataPointColor,\r\n icon: LegendIcon.Box,\r\n label: formattedLabel,\r\n category: data.categoryFormatter ? data.categoryFormatter.format(category) : category,\r\n measure: valueFormatter.format(measure, valueFormatter.getFormatString(valueMetadata, formatStringProp)),\r\n identity: SelectionId.createNull(),\r\n selected: false\r\n });\r\n }\r\n\r\n return { dataPoints: legendDataPoints };\r\n }\r\n\r\n public overrideXScale(xProperties: IAxisProperties): void {\r\n this.xAxisProperties = xProperties;\r\n }\r\n\r\n public render(suppressAnimations: boolean): CartesianVisualRenderResult {\r\n let columnChartDrawInfo = this.columnChart.drawColumns(!suppressAnimations /* useAnimations */);\r\n let data = this.data;\r\n\r\n let margin = this.margin;\r\n let viewport = this.currentViewport;\r\n let height = viewport.height - (margin.top + margin.bottom);\r\n let width = viewport.width - (margin.left + margin.right);\r\n\r\n this.mainGraphicsContext\r\n .attr('height', height)\r\n .attr('width', width);\r\n\r\n if (this.tooltipsEnabled)\r\n TooltipManager.addTooltip(columnChartDrawInfo.eventGroup, (tooltipEvent: TooltipEvent) => tooltipEvent.data.tooltipInfo);\r\n\r\n let allDataPoints: ColumnChartDataPoint[] = [];\r\n let behaviorOptions: ColumnBehaviorOptions = undefined;\r\n if (this.interactivityService) {\r\n for (let i = 0, ilen = data.series.length; i < ilen; i++) {\r\n allDataPoints = allDataPoints.concat(data.series[i].data);\r\n }\r\n behaviorOptions = {\r\n datapoints: allDataPoints,\r\n eventGroup: columnChartDrawInfo.eventGroup,\r\n bars: columnChartDrawInfo.shapesSelection,\r\n hasHighlights: data.hasHighlights,\r\n mainGraphicsContext: this.mainGraphicsContext,\r\n viewport: columnChartDrawInfo.viewport,\r\n axisOptions: columnChartDrawInfo.axisOptions,\r\n showLabel: data.labelSettings.show\r\n };\r\n }\r\n\r\n if (this.interactivity && this.interactivity.isInteractiveLegend) {\r\n if (this.data.series.length > 0) {\r\n this.selectColumn(CartesianHelper.findMaxCategoryIndex(this.data.series), true); // start with the last column\r\n }\r\n }\r\n SVGUtil.flushAllD3TransitionsIfNeeded(this.options);\r\n return { dataPoints: allDataPoints, behaviorOptions: behaviorOptions, labelDataPoints: columnChartDrawInfo.labelDataPoints, labelsAreNumeric: true };\r\n }\r\n\r\n public onClearSelection(): void {\r\n if (this.interactivityService) {\r\n this.interactivityService.clearSelection();\r\n }\r\n }\r\n\r\n public getVisualCategoryAxisIsScalar(): boolean {\r\n return this.data ? this.data.scalarCategoryAxis : false;\r\n }\r\n\r\n public getSupportedCategoryAxisType(): string {\r\n let metaDataColumn = this.data ? this.data.categoryMetadata : undefined;\r\n let valueType = AxisHelper.getCategoryValueType(metaDataColumn);\r\n let isOrdinal = AxisHelper.isOrdinal(valueType);\r\n return isOrdinal ? axisType.categorical : axisType.both;\r\n }\r\n\r\n public setFilteredData(startIndex: number, endIndex: number): CartesianData {\r\n let data = Prototype.inherit(this.data);\r\n data.series = ColumnChart.sliceSeries(data.series, endIndex, startIndex);\r\n data.categories = data.categories.slice(startIndex, endIndex);\r\n this.columnChart.setData(data);\r\n return data;\r\n }\r\n\r\n public static getLabelFill(labelColor: string, isInside: boolean, isCombo: boolean): string {\r\n if (labelColor) {\r\n return labelColor;\r\n }\r\n if (isInside && !isCombo) {\r\n return NewDataLabelUtils.defaultInsideLabelColor;\r\n }\r\n return NewDataLabelUtils.defaultLabelColor;\r\n }\r\n\r\n public supportsTrendLine(): boolean {\r\n let dataView = this.dataView;\r\n let reader = powerbi.data.createIDataViewCategoricalReader(dataView);\r\n let isScalar = this.data ? this.data.scalarCategoryAxis : false;\r\n return this.chartType === ColumnChartType.clusteredColumn && isScalar && reader.hasValues(\"Y\");\r\n }\r\n\r\n public static isBar(chartType: ColumnChartType): boolean {\r\n return EnumExtensions.hasFlag(chartType, flagBar);\r\n }\r\n\r\n public static isColumn(chartType: ColumnChartType): boolean {\r\n return EnumExtensions.hasFlag(chartType, flagColumn);\r\n }\r\n\r\n public static isClustered(chartType: ColumnChartType): boolean {\r\n return EnumExtensions.hasFlag(chartType, flagClustered);\r\n }\r\n\r\n public static isStacked(chartType: ColumnChartType): boolean {\r\n return EnumExtensions.hasFlag(chartType, flagStacked);\r\n }\r\n\r\n public static isStacked100(chartType: ColumnChartType): boolean {\r\n return EnumExtensions.hasFlag(chartType, flagStacked100);\r\n }\r\n }\r\n\r\n class ColumnChartConverterHelper implements IColumnChartConverterStrategy {\r\n private dataView: DataViewCategorical;\r\n private reader: powerbi.data.IDataViewCategoricalReader;\r\n\r\n constructor(dataView: DataView) {\r\n this.dataView = dataView && dataView.categorical;\r\n this.reader = powerbi.data.createIDataViewCategoricalReader(dataView);\r\n }\r\n\r\n public getLegend(colors: IDataColorPalette, defaultLegendLabelColor: string, defaultColor?: string): LegendSeriesInfo {\r\n let legend: LegendDataPoint[] = [];\r\n let seriesSources: DataViewMetadataColumn[] = [];\r\n let seriesObjects: DataViewObjects[][] = [];\r\n let grouped: boolean = false;\r\n\r\n let colorHelper = new ColorHelper(colors, columnChartProps.dataPoint.fill, defaultColor);\r\n let legendTitle = undefined;\r\n if (this.dataView && this.dataView.values) {\r\n let allValues = this.dataView.values;\r\n let valueGroups = allValues.grouped();\r\n\r\n let hasDynamicSeries = !!(allValues && allValues.source);\r\n\r\n let formatStringProp = columnChartProps.general.formatString;\r\n for (let valueGroupsIndex = 0, valueGroupsLen = valueGroups.length; valueGroupsIndex < valueGroupsLen; valueGroupsIndex++) {\r\n let valueGroup = valueGroups[valueGroupsIndex],\r\n valueGroupObjects = valueGroup.objects,\r\n values = valueGroup.values;\r\n\r\n for (let valueIndex = 0, valuesLen = values.length; valueIndex < valuesLen; valueIndex++) {\r\n let series = values[valueIndex];\r\n let source = series.source;\r\n // Gradient and tooltips measures do not create series.\r\n if ((DataRoleHelper.hasRole(source, 'Gradient') || DataRoleHelper.hasRole(source, 'Tooltips')) && !DataRoleHelper.hasRole(source, 'Y'))\r\n continue;\r\n\r\n seriesSources.push(source);\r\n seriesObjects.push(series.objects);\r\n\r\n let selectionId = series.identity ?\r\n SelectionId.createWithIdAndMeasure(series.identity, source.queryName) :\r\n SelectionId.createWithMeasure(this.getMeasureNameByIndex(valueIndex));\r\n\r\n let label = converterHelper.getFormattedLegendLabel(source, allValues, formatStringProp);\r\n\r\n let color = hasDynamicSeries\r\n ? colorHelper.getColorForSeriesValue(valueGroupObjects, allValues.identityFields, source.groupName)\r\n : colorHelper.getColorForMeasure(source.objects, source.queryName);\r\n\r\n legend.push({\r\n icon: LegendIcon.Box,\r\n color: color,\r\n label: label,\r\n identity: selectionId,\r\n selected: false,\r\n });\r\n\r\n if (series.identity && source.groupName !== undefined) {\r\n grouped = true;\r\n }\r\n }\r\n }\r\n\r\n let dvValues = this.dataView.values;\r\n legendTitle = dvValues && dvValues.source ? dvValues.source.displayName : \"\";\r\n }\r\n\r\n let legendData: LegendData = {\r\n title: legendTitle,\r\n dataPoints: legend,\r\n grouped: grouped,\r\n labelColor: defaultLegendLabelColor,\r\n };\r\n\r\n return {\r\n legend: legendData,\r\n seriesSources: seriesSources,\r\n seriesObjects: seriesObjects,\r\n };\r\n }\r\n\r\n public getValueBySeriesAndCategory(series: number, category: number): number {\r\n return this.reader.getValue('Y', category, series);\r\n }\r\n\r\n public getMeasureNameByIndex(index: number): string {\r\n return this.dataView.values[index].source.queryName;\r\n }\r\n\r\n public hasHighlightValues(series: number): boolean {\r\n let column = this.dataView && this.dataView.values ? this.dataView.values[series] : undefined;\r\n return column && !!column.highlights;\r\n }\r\n\r\n public getHighlightBySeriesAndCategory(series: number, category: number): number {\r\n return this.dataView.values[series].highlights[category];\r\n }\r\n }\r\n}\r\n\n\n\n/** WEBPACK FOOTER **\n ** ./src/Clients/Visuals/cartesian/columnChart.ts\n **/","/*\r\n * Power BI Visualizations\r\n *\r\n * Copyright (c) Microsoft Corporation\r\n * All rights reserved. \r\n * MIT License\r\n *\r\n * Permission is hereby granted, free of charge, to any person obtaining a copy\r\n * of this software and associated documentation files (the \"\"Software\"\"), to deal\r\n * in the Software without restriction, including without limitation the rights\r\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\r\n * copies of the Software, and to permit persons to whom the Software is\r\n * furnished to do so, subject to the following conditions:\r\n * \r\n * The above copyright notice and this permission notice shall be included in \r\n * all copies or substantial portions of the Software.\r\n * \r\n * THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR \r\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, \r\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE \r\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER \r\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\r\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\r\n * THE SOFTWARE.\r\n */\r\n\r\n/// <reference path=\"../_references.ts\"/>\r\n\r\nmodule powerbi.visuals {\r\n import PixelConverter = jsCommon.PixelConverter;\r\n\r\n export class ClusteredColumnChartStrategy implements IColumnChartStrategy {\r\n private static classes = {\r\n item: {\r\n class: 'column',\r\n selector: '.column',\r\n },\r\n };\r\n\r\n private data: ColumnChartData;\r\n private graphicsContext: ColumnChartContext;\r\n private seriesOffsetScale: D3.Scale.OrdinalScale;\r\n private width: number;\r\n private height: number;\r\n private margin: IMargin;\r\n private xProps: IAxisProperties;\r\n private yProps: IAxisProperties;\r\n private categoryLayout: CategoryLayout;\r\n private viewportHeight: number;\r\n private viewportWidth: number;\r\n private columnsCenters: number[];\r\n private columnSelectionLineHandle: D3.Selection;\r\n private animator: IColumnChartAnimator;\r\n private interactivityService: IInteractivityService;\r\n private layout: IColumnLayout;\r\n private isComboChart: boolean;\r\n\r\n public setupVisualProps(columnChartProps: ColumnChartContext): void {\r\n this.graphicsContext = columnChartProps;\r\n this.margin = columnChartProps.margin;\r\n this.width = this.graphicsContext.width;\r\n this.height = this.graphicsContext.height;\r\n this.categoryLayout = columnChartProps.layout;\r\n this.animator = columnChartProps.animator;\r\n this.interactivityService = columnChartProps.interactivityService;\r\n this.viewportHeight = columnChartProps.viewportHeight;\r\n this.viewportWidth = columnChartProps.viewportWidth;\r\n this.isComboChart = columnChartProps.isComboChart;\r\n }\r\n\r\n public setData(data: ColumnChartData) {\r\n this.data = data;\r\n }\r\n\r\n public setXScale(is100Pct: boolean, forcedTickCount?: number, forcedXDomain?: any[], axisScaleType?: string, axisDisplayUnits?: number, axisPrecision?: number, ensureXDomain?: NumberRange): IAxisProperties {\r\n let width = this.width;\r\n\r\n let forcedXMin, forcedXMax;\r\n\r\n if (forcedXDomain && forcedXDomain.length === 2) {\r\n forcedXMin = forcedXDomain[0];\r\n forcedXMax = forcedXDomain[1];\r\n }\r\n\r\n let props = this.xProps = ColumnUtil.getCategoryAxis(\r\n this.data,\r\n width,\r\n this.categoryLayout,\r\n false,\r\n forcedXMin,\r\n forcedXMax,\r\n axisScaleType,\r\n axisDisplayUnits,\r\n axisPrecision,\r\n ensureXDomain);\r\n\r\n // create clustered offset scale\r\n let seriesLength = this.data.series.length;\r\n let columnWidth = (this.categoryLayout.categoryThickness * (1 - CartesianChart.InnerPaddingRatio)) / seriesLength;\r\n this.seriesOffsetScale = d3.scale.ordinal()\r\n .domain(this.data.series.map(s => s.index))\r\n .rangeBands([0, seriesLength * columnWidth]);\r\n\r\n return props;\r\n }\r\n\r\n public setYScale(is100Pct: boolean, forcedTickCount?: number, forcedYDomain?: any[], axisScaleType?: string, axisDisplayUnits?: number, axisPrecision?: number, ensureYDomain?: NumberRange): IAxisProperties {\r\n debug.assert(!is100Pct, 'Cannot have 100% clustered chart.');\r\n\r\n let height = this.viewportHeight;\r\n let valueDomain = AxisHelper.createValueDomain(this.data.series, true) || emptyDomain;\r\n let combinedDomain = AxisHelper.combineDomain(forcedYDomain, valueDomain, ensureYDomain);\r\n let shouldClamp = AxisHelper.scaleShouldClamp(combinedDomain, valueDomain);\r\n\r\n this.yProps = AxisHelper.createAxis({\r\n pixelSpan: height,\r\n dataDomain: combinedDomain,\r\n metaDataColumn: this.data.valuesMetadata[0],\r\n formatString: valueFormatter.getFormatString(this.data.valuesMetadata[0], columnChartProps.general.formatString),\r\n outerPadding: 0,\r\n isScalar: true,\r\n isVertical: true,\r\n forcedTickCount: forcedTickCount,\r\n useTickIntervalForDisplayUnits: true,\r\n isCategoryAxis: false,\r\n scaleType: axisScaleType,\r\n axisDisplayUnits: axisDisplayUnits,\r\n axisPrecision: axisPrecision,\r\n shouldClamp: shouldClamp,\r\n });\r\n\r\n return this.yProps;\r\n }\r\n\r\n public drawColumns(useAnimation: boolean): ColumnChartDrawInfo {\r\n let data = this.data;\r\n debug.assertValue(data, 'data could not be null or undefined');\r\n\r\n this.columnsCenters = null; // invalidate the columnsCenters so that will be calculated again\r\n\r\n let categoryWidth = (this.categoryLayout.categoryThickness * (1 - CartesianChart.InnerPaddingRatio));\r\n let columnWidth = categoryWidth / data.series.length;\r\n let axisOptions: ColumnAxisOptions = {\r\n columnWidth: columnWidth,\r\n categoryWidth: categoryWidth,\r\n xScale: this.xProps.scale,\r\n yScale: this.yProps.scale,\r\n seriesOffsetScale: this.seriesOffsetScale,\r\n isScalar: this.categoryLayout.isScalar,\r\n margin: this.margin,\r\n };\r\n let clusteredColumnLayout = this.layout = ClusteredColumnChartStrategy.getLayout(data, axisOptions);\r\n let dataLabelSettings = data.labelSettings;\r\n let labelDataPoints: LabelDataPoint[] = [];\r\n if (dataLabelSettings && dataLabelSettings.show) {\r\n labelDataPoints = this.createLabelDataPoints();\r\n }\r\n\r\n let result: ColumnChartAnimationResult;\r\n let shapes: D3.UpdateSelection;\r\n let series = ColumnUtil.drawSeries(data, this.graphicsContext.mainGraphicsContext, axisOptions);\r\n if (this.animator && useAnimation) {\r\n result = this.animator.animate({\r\n viewModel: data,\r\n series: series,\r\n layout: clusteredColumnLayout,\r\n itemCS: ClusteredColumnChartStrategy.classes.item,\r\n interactivityService: this.interactivityService,\r\n mainGraphicsContext: this.graphicsContext.mainGraphicsContext,\r\n viewPort: { height: this.height, width: this.width }\r\n });\r\n shapes = result.shapes;\r\n }\r\n if (!this.animator || !useAnimation || result.failed) {\r\n shapes = ColumnUtil.drawDefaultShapes(data, series, clusteredColumnLayout, ClusteredColumnChartStrategy.classes.item, !this.animator, this.interactivityService && this.interactivityService.hasSelection());\r\n }\r\n\r\n ColumnUtil.applyInteractivity(shapes, this.graphicsContext.onDragStart);\r\n\r\n return {\r\n eventGroup: this.graphicsContext.mainGraphicsContext,\r\n shapesSelection: shapes,\r\n viewport: { height: this.height, width: this.width },\r\n axisOptions: axisOptions,\r\n labelDataPoints: labelDataPoints,\r\n };\r\n }\r\n\r\n public selectColumn(selectedColumnIndex: number, lastSelectedColumnIndex: number): void {\r\n ColumnUtil.setChosenColumnOpacity(this.graphicsContext.mainGraphicsContext, ClusteredColumnChartStrategy.classes.item.selector, selectedColumnIndex, lastSelectedColumnIndex);\r\n this.moveHandle(selectedColumnIndex);\r\n }\r\n\r\n public getClosestColumnIndex(x: number, y: number): number {\r\n return ColumnUtil.getClosestColumnIndex(x, this.getColumnsCenters());\r\n }\r\n\r\n /**\r\n * Get the chart's columns centers (x value).\r\n */\r\n private getColumnsCenters(): number[] {\r\n if (!this.columnsCenters) { // lazy creation\r\n let categoryWidth: number = this.categoryLayout.categoryThickness * (1 - CartesianChart.InnerPaddingRatio);\r\n // use the axis scale and first series data to get category centers\r\n if (this.data.series.length > 0) {\r\n let xScaleOffset = 0;\r\n if (!this.categoryLayout.isScalar)\r\n xScaleOffset = categoryWidth / 2;\r\n let firstSeries = this.data.series[0];\r\n this.columnsCenters = firstSeries.data.map(d => this.xProps.scale(this.categoryLayout.isScalar ? d.categoryValue : d.categoryIndex) + xScaleOffset);\r\n }\r\n }\r\n return this.columnsCenters;\r\n }\r\n\r\n private moveHandle(selectedColumnIndex: number) {\r\n let columnCenters = this.getColumnsCenters();\r\n let x = columnCenters[selectedColumnIndex];\r\n\r\n let hoverLine = d3.select('.interactive-hover-line');\r\n if (!hoverLine.empty() && !this.columnSelectionLineHandle) {\r\n\r\n this.columnSelectionLineHandle = d3.select(hoverLine.node().parentNode);\r\n }\r\n\r\n if (!this.columnSelectionLineHandle) {\r\n let handle = this.columnSelectionLineHandle = this.graphicsContext.unclippedGraphicsContext.append('g');\r\n handle.append('line')\r\n .classed('interactive-hover-line', true)\r\n .attr({\r\n x1: x,\r\n x2: x,\r\n y1: 0,\r\n y2: this.height,\r\n });\r\n\r\n handle.append('circle')\r\n .attr({\r\n cx: x,\r\n cy: this.height,\r\n r: '6px',\r\n })\r\n .classed('drag-handle', true);\r\n }\r\n else {\r\n let handle = this.columnSelectionLineHandle;\r\n handle.select('line').attr({ x1: x, x2: x });\r\n handle.select('circle').attr({ cx: x });\r\n }\r\n }\r\n\r\n public static getLayout(data: ColumnChartData, axisOptions: ColumnAxisOptions): IColumnLayout {\r\n let columnWidth = axisOptions.columnWidth;\r\n let halfColumnWidth = 0.5 * columnWidth;\r\n let quarterColumnWidth = halfColumnWidth / 2;\r\n let isScalar = axisOptions.isScalar;\r\n let xScale = axisOptions.xScale;\r\n let yScale = axisOptions.yScale;\r\n let seriesOffsetScale = axisOptions.seriesOffsetScale;\r\n let scaledY0 = yScale(0);\r\n let xScaleOffset = 0;\r\n\r\n if (isScalar)\r\n xScaleOffset = axisOptions.categoryWidth / 2;\r\n\r\n return {\r\n shapeLayout: {\r\n width: (d: ColumnChartDataPoint) => d.drawThinner ? halfColumnWidth : columnWidth,\r\n x: (d: ColumnChartDataPoint) => xScale(isScalar ? d.categoryValue : d.categoryIndex) + seriesOffsetScale(d.seriesIndex) - xScaleOffset + (d.drawThinner ? quarterColumnWidth : 0),\r\n y: (d: ColumnChartDataPoint) => scaledY0 + AxisHelper.diffScaled(yScale, Math.max(0, d.value), 0),\r\n height: (d: ColumnChartDataPoint) => Math.abs(AxisHelper.diffScaled(yScale, 0, d.value)),\r\n },\r\n shapeLayoutWithoutHighlights: {\r\n width: (d: ColumnChartDataPoint) => columnWidth,\r\n x: (d: ColumnChartDataPoint) => xScale(isScalar ? d.categoryValue : d.categoryIndex) + seriesOffsetScale(d.seriesIndex) - xScaleOffset,\r\n y: (d: ColumnChartDataPoint) => scaledY0 + AxisHelper.diffScaled(yScale, Math.max(0, d.originalValue), 0),\r\n height: (d: ColumnChartDataPoint) => Math.abs(AxisHelper.diffScaled(yScale, 0, d.originalValue)),\r\n },\r\n zeroShapeLayout: {\r\n width: (d: ColumnChartDataPoint) => d.drawThinner ? halfColumnWidth : columnWidth,\r\n x: (d: ColumnChartDataPoint) => xScale(isScalar ? d.categoryValue : d.categoryIndex) + seriesOffsetScale(d.seriesIndex) - xScaleOffset + (d.drawThinner ? quarterColumnWidth : 0),\r\n y: (d: ColumnChartDataPoint) => scaledY0,\r\n height: (d: ColumnChartDataPoint) => 0,\r\n },\r\n };\r\n }\r\n\r\n private createLabelDataPoints(): LabelDataPoint[] {\r\n let labelDataPoints: LabelDataPoint[] = [];\r\n let data = this.data;\r\n let series = data.series;\r\n let formattersCache = NewDataLabelUtils.createColumnFormatterCacheManager();\r\n let shapeLayout = this.layout.shapeLayout;\r\n\r\n for (let currentSeries of series) {\r\n let labelSettings = currentSeries.labelSettings ? currentSeries.labelSettings : data.labelSettings;\r\n if (!labelSettings.show)\r\n continue;\r\n\r\n let axisFormatter: number = NewDataLabelUtils.getDisplayUnitValueFromAxisFormatter(this.yProps.formatter, labelSettings);\r\n for (let dataPoint of currentSeries.data) {\r\n if ((data.hasHighlights && !dataPoint.highlight) || dataPoint.value == null) {\r\n continue;\r\n }\r\n\r\n // Calculate parent rectangle\r\n let parentRect: IRect = {\r\n left: shapeLayout.x(dataPoint),\r\n top: shapeLayout.y(dataPoint),\r\n width: shapeLayout.width(dataPoint),\r\n height: shapeLayout.height(dataPoint),\r\n };\r\n\r\n // Calculate label text\r\n let formatString = dataPoint.labelFormatString;\r\n let formatter = formattersCache.getOrCreate(formatString, labelSettings, axisFormatter);\r\n let text = NewDataLabelUtils.getLabelFormattedText(formatter.format(dataPoint.value));\r\n\r\n // Calculate text size\r\n let properties: TextProperties = {\r\n text: text,\r\n fontFamily: NewDataLabelUtils.LabelTextProperties.fontFamily,\r\n fontSize: PixelConverter.fromPoint(labelSettings.fontSize || NewDataLabelUtils.DefaultLabelFontSizeInPt),\r\n fontWeight: NewDataLabelUtils.LabelTextProperties.fontWeight,\r\n };\r\n let textWidth = TextMeasurementService.measureSvgTextWidth(properties);\r\n let textHeight = TextMeasurementService.estimateSvgTextHeight(properties, true /* tightFitForNumeric */);\r\n\r\n labelDataPoints.push({\r\n isPreferred: true,\r\n text: text,\r\n textSize: {\r\n width: textWidth,\r\n height: textHeight,\r\n },\r\n outsideFill: ColumnChart.getLabelFill(labelSettings.labelColor, false, this.isComboChart),\r\n insideFill: ColumnChart.getLabelFill(labelSettings.labelColor, true, this.isComboChart),\r\n parentType: LabelDataPointParentType.Rectangle,\r\n parentShape: {\r\n rect: parentRect,\r\n orientation: dataPoint.value >= 0 ? NewRectOrientation.VerticalBottomBased : NewRectOrientation.VerticalTopBased,\r\n validPositions: ColumnChart.clusteredValidLabelPositions,\r\n },\r\n identity: dataPoint.identity,\r\n fontSize: labelSettings.fontSize || NewDataLabelUtils.DefaultLabelFontSizeInPt,\r\n });\r\n }\r\n }\r\n\r\n return labelDataPoints;\r\n }\r\n }\r\n\r\n export class ClusteredBarChartStrategy implements IColumnChartStrategy {\r\n private static classes = {\r\n item: {\r\n class: 'bar',\r\n selector: '.bar'\r\n },\r\n };\r\n\r\n private data: ColumnChartData;\r\n private graphicsContext: ColumnChartContext;\r\n private seriesOffsetScale: D3.Scale.OrdinalScale;\r\n private width: number;\r\n private height: number;\r\n private margin: IMargin;\r\n private xProps: IAxisProperties;\r\n private yProps: IAxisProperties;\r\n private categoryLayout: CategoryLayout;\r\n private viewportHeight: number;\r\n private viewportWidth: number;\r\n private barsCenters: number[];\r\n private columnSelectionLineHandle: D3.Selection;\r\n private animator: IColumnChartAnimator;\r\n private interactivityService: IInteractivityService;\r\n private layout: IColumnLayout;\r\n private isComboChart: boolean;\r\n\r\n public setupVisualProps(barChartProps: ColumnChartContext): void {\r\n this.graphicsContext = barChartProps;\r\n this.margin = barChartProps.margin;\r\n this.width = this.graphicsContext.width;\r\n this.height = this.graphicsContext.height;\r\n this.categoryLayout = barChartProps.layout;\r\n this.animator = barChartProps.animator;\r\n this.interactivityService = barChartProps.interactivityService;\r\n this.viewportHeight = barChartProps.viewportHeight;\r\n this.viewportWidth = barChartProps.viewportWidth;\r\n this.isComboChart = barChartProps.isComboChart;\r\n }\r\n\r\n public setData(data: ColumnChartData) {\r\n this.data = data;\r\n }\r\n\r\n public setYScale(is100Pct: boolean, forcedTickCount?: number, forcedYDomain?: any[], axisScaleType?: string, axisDisplayUnits?: number, axisPrecision?: number, ensureYDomain?: NumberRange): IAxisProperties {\r\n let height = this.height;\r\n let forcedYMin, forcedYMax;\r\n\r\n if (forcedYDomain && forcedYDomain.length === 2) {\r\n forcedYMin = forcedYDomain[0];\r\n forcedYMax = forcedYDomain[1];\r\n }\r\n\r\n let props = this.yProps = ColumnUtil.getCategoryAxis(\r\n this.data,\r\n height,\r\n this.categoryLayout,\r\n true,\r\n forcedYMin,\r\n forcedYMax,\r\n axisScaleType,\r\n axisDisplayUnits,\r\n axisPrecision,\r\n ensureYDomain\r\n );\r\n\r\n // create clustered offset scale\r\n let seriesLength = this.data.series.length;\r\n let columnWidth = (this.categoryLayout.categoryThickness * (1 - CartesianChart.InnerPaddingRatio)) / seriesLength;\r\n this.seriesOffsetScale = d3.scale.ordinal()\r\n .domain(this.data.series.map(s => s.index))\r\n .rangeBands([0, seriesLength * columnWidth]);\r\n\r\n return props;\r\n }\r\n\r\n public setXScale(is100Pct: boolean, forcedTickCount?: number, forcedXDomain?: any[], axisScaleType?: string, axisDisplayUnits?: number, axisPrecision?: number, ensureXDomain?: NumberRange): IAxisProperties {\r\n debug.assert(!is100Pct, 'Cannot have 100% clustered chart.');\r\n debug.assert(forcedTickCount === undefined, 'Cannot have clustered bar chart as combo chart.');\r\n\r\n let width = this.width;\r\n let valueDomain = AxisHelper.createValueDomain(this.data.series, true) || emptyDomain;\r\n let combinedDomain = AxisHelper.combineDomain(forcedXDomain, valueDomain, ensureXDomain);\r\n let shouldClamp = AxisHelper.scaleShouldClamp(combinedDomain, valueDomain);\r\n\r\n this.xProps = AxisHelper.createAxis({\r\n pixelSpan: width,\r\n dataDomain: combinedDomain,\r\n metaDataColumn: this.data.valuesMetadata[0],\r\n formatString: valueFormatter.getFormatString(this.data.valuesMetadata[0], columnChartProps.general.formatString),\r\n outerPadding: 0,\r\n isScalar: true,\r\n isVertical: false,\r\n forcedTickCount: forcedTickCount,\r\n useTickIntervalForDisplayUnits: true,\r\n isCategoryAxis: false,\r\n scaleType: axisScaleType,\r\n axisDisplayUnits: axisDisplayUnits,\r\n axisPrecision: axisPrecision,\r\n shouldClamp: shouldClamp,\r\n });\r\n\r\n this.xProps.axis.tickSize(-this.viewportHeight, 0);\r\n\r\n return this.xProps;\r\n }\r\n\r\n public drawColumns(useAnimation: boolean): ColumnChartDrawInfo {\r\n let data = this.data;\r\n debug.assertValue(data, 'data could not be null or undefined');\r\n\r\n this.barsCenters = null; // invalidate the columnsCenters so that will be calculated again\r\n\r\n let categoryWidth = (this.categoryLayout.categoryThickness * (1 - CartesianChart.InnerPaddingRatio));\r\n let columnWidth = categoryWidth / data.series.length;\r\n let axisOptions: ColumnAxisOptions = {\r\n columnWidth: columnWidth,\r\n categoryWidth: categoryWidth,\r\n xScale: this.xProps.scale,\r\n yScale: this.yProps.scale,\r\n seriesOffsetScale: this.seriesOffsetScale,\r\n isScalar: this.categoryLayout.isScalar,\r\n margin: this.margin,\r\n };\r\n let clusteredBarLayout = this.layout = ClusteredBarChartStrategy.getLayout(data, axisOptions);\r\n let dataLabelSettings = data.labelSettings;\r\n let labelDataPoints: LabelDataPoint[] = [];\r\n if (dataLabelSettings && dataLabelSettings.show) {\r\n labelDataPoints = this.createLabelDataPoints();\r\n }\r\n\r\n let result: ColumnChartAnimationResult;\r\n let shapes: D3.UpdateSelection;\r\n let series = ColumnUtil.drawSeries(data, this.graphicsContext.mainGraphicsContext, axisOptions);\r\n if (this.animator && useAnimation) {\r\n result = this.animator.animate({\r\n viewModel: data,\r\n series: series,\r\n layout: clusteredBarLayout,\r\n itemCS: ClusteredBarChartStrategy.classes.item,\r\n interactivityService: this.interactivityService,\r\n mainGraphicsContext: this.graphicsContext.mainGraphicsContext,\r\n viewPort: { height: this.height, width: this.width }\r\n });\r\n shapes = result.shapes;\r\n }\r\n if (!this.animator || !useAnimation || result.failed) {\r\n shapes = ColumnUtil.drawDefaultShapes(data, series, clusteredBarLayout, ClusteredBarChartStrategy.classes.item, !this.animator, this.interactivityService && this.interactivityService.hasSelection());\r\n }\r\n\r\n ColumnUtil.applyInteractivity(shapes, this.graphicsContext.onDragStart);\r\n\r\n return {\r\n eventGroup: this.graphicsContext.mainGraphicsContext,\r\n shapesSelection: shapes,\r\n viewport: { height: this.height, width: this.width },\r\n axisOptions: axisOptions,\r\n labelDataPoints: labelDataPoints,\r\n };\r\n }\r\n\r\n public selectColumn(selectedColumnIndex: number, lastSelectedColumnIndex: number): void {\r\n ColumnUtil.setChosenColumnOpacity(this.graphicsContext.mainGraphicsContext, ClusteredBarChartStrategy.classes.item.selector, selectedColumnIndex, lastSelectedColumnIndex);\r\n this.moveHandle(selectedColumnIndex);\r\n }\r\n\r\n public getClosestColumnIndex(x: number, y: number): number {\r\n return ColumnUtil.getClosestColumnIndex(y, this.getBarsCenters());\r\n }\r\n\r\n /** \r\n * Get the chart's columns centers (y value).\r\n */\r\n private getBarsCenters(): number[] {\r\n if (!this.barsCenters) { // lazy creation\r\n let barWidth: number = this.categoryLayout.categoryThickness * (1 - CartesianChart.InnerPaddingRatio);\r\n // use the axis scale and first series data to get category centers\r\n if (this.data.series.length > 0) {\r\n let yScaleOffset = 0;\r\n if (!this.categoryLayout.isScalar)\r\n yScaleOffset = barWidth / 2;\r\n let firstSeries = this.data.series[0];\r\n this.barsCenters = firstSeries.data.map(d => this.yProps.scale(this.categoryLayout.isScalar ? d.categoryValue : d.categoryIndex) + yScaleOffset);\r\n }\r\n }\r\n return this.barsCenters;\r\n }\r\n\r\n private moveHandle(selectedColumnIndex: number) {\r\n let barCenters = this.getBarsCenters();\r\n let y = barCenters[selectedColumnIndex];\r\n\r\n let hoverLine = d3.select('.interactive-hover-line');\r\n if (!hoverLine.empty() && !this.columnSelectionLineHandle) {\r\n\r\n this.columnSelectionLineHandle = d3.select(hoverLine.node().parentNode);\r\n }\r\n\r\n if (!this.columnSelectionLineHandle) {\r\n let handle = this.columnSelectionLineHandle = this.graphicsContext.unclippedGraphicsContext.append('g');\r\n handle.append('line')\r\n .classed('interactive-hover-line', true)\r\n .attr({\r\n x1: 0,\r\n x2: this.width,\r\n y1: y,\r\n y2: y,\r\n });\r\n handle.append('circle')\r\n .attr({\r\n cx: 0,\r\n cy: y,\r\n r: '6px',\r\n })\r\n .classed('drag-handle', true);\r\n }\r\n else {\r\n let handle = this.columnSelectionLineHandle;\r\n handle.select('line').attr({ y1: y, y2: y });\r\n handle.select('circle').attr({ cy: y });\r\n }\r\n }\r\n\r\n public static getLayout(data: ColumnChartData, axisOptions: ColumnAxisOptions): IColumnLayout {\r\n let columnWidth = axisOptions.columnWidth;\r\n let halfColumnWidth = 0.5 * columnWidth;\r\n let quarterColumnWidth = halfColumnWidth / 2;\r\n let isScalar = axisOptions.isScalar;\r\n let xScale = axisOptions.xScale;\r\n let yScale = axisOptions.yScale;\r\n let seriesOffsetScale = axisOptions.seriesOffsetScale;\r\n let scaledX0 = xScale(0);\r\n let yScaleOffset = 0;\r\n\r\n if (isScalar)\r\n yScaleOffset = axisOptions.categoryWidth / 2;\r\n\r\n return {\r\n shapeLayout: {\r\n width: (d: ColumnChartDataPoint) => Math.abs(AxisHelper.diffScaled(xScale, 0, d.value)),\r\n x: (d: ColumnChartDataPoint) => scaledX0 + AxisHelper.diffScaled(xScale, Math.min(0, d.value), 0),\r\n y: (d: ColumnChartDataPoint) => yScale(isScalar ? d.categoryValue : d.categoryIndex) + seriesOffsetScale(d.seriesIndex) - yScaleOffset + (d.drawThinner ? quarterColumnWidth : 0),\r\n height: (d: ColumnChartDataPoint) => d.drawThinner ? halfColumnWidth : columnWidth,\r\n },\r\n shapeLayoutWithoutHighlights: {\r\n width: (d: ColumnChartDataPoint) => Math.abs(AxisHelper.diffScaled(xScale, 0, d.originalValue)),\r\n x: (d: ColumnChartDataPoint) => scaledX0 + AxisHelper.diffScaled(xScale, Math.min(0, d.originalValue), 0),\r\n y: (d: ColumnChartDataPoint) => yScale(isScalar ? d.categoryValue : d.categoryIndex) + seriesOffsetScale(d.seriesIndex) - yScaleOffset,\r\n height: (d: ColumnChartDataPoint) => columnWidth,\r\n },\r\n zeroShapeLayout: {\r\n width: (d: ColumnChartDataPoint) => 0,\r\n x: (d: ColumnChartDataPoint) => scaledX0 + AxisHelper.diffScaled(xScale, Math.min(0, d.value), 0),\r\n y: (d: ColumnChartDataPoint) => yScale(isScalar ? d.categoryValue : d.categoryIndex) + seriesOffsetScale(d.seriesIndex) - yScaleOffset + (d.drawThinner ? quarterColumnWidth : 0),\r\n height: (d: ColumnChartDataPoint) => d.drawThinner ? halfColumnWidth : columnWidth,\r\n },\r\n };\r\n }\r\n\r\n private createLabelDataPoints(): LabelDataPoint[] {\r\n let labelDataPoints: LabelDataPoint[] = [];\r\n let data = this.data;\r\n let series = data.series;\r\n let formattersCache = NewDataLabelUtils.createColumnFormatterCacheManager();\r\n let shapeLayout = this.layout.shapeLayout;\r\n\r\n for (let currentSeries of series) {\r\n let labelSettings = currentSeries.labelSettings ? currentSeries.labelSettings : data.labelSettings;\r\n if (!labelSettings.show)\r\n continue;\r\n\r\n let axisFormatter: number = NewDataLabelUtils.getDisplayUnitValueFromAxisFormatter(this.yProps.formatter, labelSettings);\r\n for (let dataPoint of currentSeries.data) {\r\n if ((data.hasHighlights && !dataPoint.highlight) || dataPoint.value == null) {\r\n continue;\r\n }\r\n\r\n // Calculate label text\r\n let formatString = dataPoint.labelFormatString;\r\n let formatter = formattersCache.getOrCreate(formatString, labelSettings, axisFormatter);\r\n let text = NewDataLabelUtils.getLabelFormattedText(formatter.format(dataPoint.value));\r\n\r\n // Calculate text size\r\n let properties: TextProperties = {\r\n text: text,\r\n fontFamily: NewDataLabelUtils.LabelTextProperties.fontFamily,\r\n fontSize: PixelConverter.fromPoint(labelSettings.fontSize || NewDataLabelUtils.DefaultLabelFontSizeInPt),\r\n fontWeight: NewDataLabelUtils.LabelTextProperties.fontWeight,\r\n };\r\n let textWidth = TextMeasurementService.measureSvgTextWidth(properties);\r\n let textHeight = TextMeasurementService.estimateSvgTextHeight(properties, true /* tightFitForNumeric */);\r\n\r\n // Calculate parent rectangle\r\n let parentRect: IRect = {\r\n left: shapeLayout.x(dataPoint),\r\n top: shapeLayout.y(dataPoint),\r\n width: shapeLayout.width(dataPoint),\r\n height: shapeLayout.height(dataPoint),\r\n };\r\n\r\n labelDataPoints.push({\r\n isPreferred: true,\r\n text: text,\r\n textSize: {\r\n width: textWidth,\r\n height: textHeight,\r\n },\r\n outsideFill: ColumnChart.getLabelFill(labelSettings.labelColor, false, this.isComboChart),\r\n insideFill: ColumnChart.getLabelFill(labelSettings.labelColor, true, this.isComboChart),\r\n parentType: LabelDataPointParentType.Rectangle,\r\n parentShape: {\r\n rect: parentRect,\r\n orientation: dataPoint.value >= 0 ? NewRectOrientation.HorizontalLeftBased : NewRectOrientation.HorizontalRightBased,\r\n validPositions: ColumnChart.clusteredValidLabelPositions,\r\n },\r\n identity: dataPoint.identity,\r\n fontSize: labelSettings.fontSize || NewDataLabelUtils.DefaultLabelFontSizeInPt,\r\n });\r\n }\r\n }\r\n\r\n return labelDataPoints;\r\n }\r\n }\r\n} \n\n\n/** WEBPACK FOOTER **\n ** ./src/Clients/Visuals/cartesian/columnChartClustered.ts\n **/","/*\r\n * Power BI Visualizations\r\n *\r\n * Copyright (c) Microsoft Corporation\r\n * All rights reserved. \r\n * MIT License\r\n *\r\n * Permission is hereby granted, free of charge, to any person obtaining a copy\r\n * of this software and associated documentation files (the \"\"Software\"\"), to deal\r\n * in the Software without restriction, including without limitation the rights\r\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\r\n * copies of the Software, and to permit persons to whom the Software is\r\n * furnished to do so, subject to the following conditions:\r\n * \r\n * The above copyright notice and this permission notice shall be included in \r\n * all copies or substantial portions of the Software.\r\n * \r\n * THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR \r\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, \r\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE \r\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER \r\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\r\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\r\n * THE SOFTWARE.\r\n */\r\n\r\n/// <reference path=\"../_references.ts\"/>\r\n\r\nmodule powerbi.visuals {\r\n import PixelConverter = jsCommon.PixelConverter;\r\n\r\n export class StackedColumnChartStrategy implements IColumnChartStrategy {\r\n private static classes = {\r\n item: {\r\n class: 'column',\r\n selector: '.column'\r\n },\r\n highlightItem: {\r\n class: 'highlightColumn',\r\n selector: '.highlightColumn'\r\n },\r\n };\r\n\r\n private data: ColumnChartData;\r\n private graphicsContext: ColumnChartContext;\r\n private width: number;\r\n private height: number;\r\n private margin: IMargin;\r\n private xProps: IAxisProperties;\r\n private yProps: IAxisProperties;\r\n private categoryLayout: CategoryLayout;\r\n private columnsCenters: number[];\r\n private columnSelectionLineHandle: D3.Selection;\r\n private animator: IColumnChartAnimator;\r\n private interactivityService: IInteractivityService;\r\n private viewportHeight: number;\r\n private viewportWidth: number;\r\n private layout: IColumnLayout;\r\n private isComboChart: boolean;\r\n\r\n public setupVisualProps(columnChartProps: ColumnChartContext): void {\r\n this.graphicsContext = columnChartProps;\r\n this.margin = columnChartProps.margin;\r\n this.width = this.graphicsContext.width;\r\n this.height = this.graphicsContext.height;\r\n this.categoryLayout = columnChartProps.layout;\r\n this.animator = columnChartProps.animator;\r\n this.interactivityService = columnChartProps.interactivityService;\r\n this.viewportHeight = columnChartProps.viewportHeight;\r\n this.viewportWidth = columnChartProps.viewportWidth;\r\n this.isComboChart = columnChartProps.isComboChart;\r\n }\r\n\r\n public setData(data: ColumnChartData) {\r\n this.data = data;\r\n }\r\n\r\n public setXScale(is100Pct: boolean, forcedTickCount?: number, forcedXDomain?: any[], axisScaleType?: string, axisDisplayUnits?: number, axisPrecision?: number, xReferenceLineValue?: number): IAxisProperties {\r\n let width = this.width;\r\n\r\n let forcedXMin, forcedXMax;\r\n\r\n if (forcedXDomain && forcedXDomain.length === 2) {\r\n forcedXMin = forcedXDomain[0];\r\n forcedXMax = forcedXDomain[1];\r\n }\r\n\r\n let props = this.xProps = ColumnUtil.getCategoryAxis(\r\n this.data,\r\n width,\r\n this.categoryLayout,\r\n false,\r\n forcedXMin,\r\n forcedXMax,\r\n axisScaleType,\r\n axisDisplayUnits,\r\n axisPrecision,\r\n xReferenceLineValue);\r\n\r\n return props;\r\n }\r\n\r\n public setYScale(is100Pct: boolean, forcedTickCount?: number, forcedYDomain?: any[], axisScaleType?: string, axisDisplayUnits?: number, axisPrecision?: number, y1ReferenceLineValue?: number): IAxisProperties {\r\n let height = this.viewportHeight;\r\n let valueDomain = StackedUtil.calcValueDomain(this.data.series, is100Pct);\r\n let valueDomainArr = [valueDomain.min, valueDomain.max];\r\n let combinedDomain = AxisHelper.combineDomain(forcedYDomain, valueDomainArr, y1ReferenceLineValue);\r\n let shouldClamp = AxisHelper.scaleShouldClamp(combinedDomain, valueDomainArr);\r\n let metadataColumn = this.data.valuesMetadata[0];\r\n let formatString = is100Pct ?\r\n this.graphicsContext.hostService.getLocalizedString('Percentage')\r\n : valueFormatter.getFormatString(metadataColumn, columnChartProps.general.formatString);\r\n\r\n this.yProps = AxisHelper.createAxis({\r\n pixelSpan: height,\r\n dataDomain: combinedDomain,\r\n metaDataColumn: metadataColumn,\r\n formatString: formatString,\r\n outerPadding: 0,\r\n isScalar: true,\r\n isVertical: true,\r\n forcedTickCount: forcedTickCount,\r\n useTickIntervalForDisplayUnits: true,\r\n isCategoryAxis: false,\r\n scaleType: axisScaleType,\r\n axisDisplayUnits: axisDisplayUnits,\r\n axisPrecision: axisPrecision,\r\n is100Pct: is100Pct,\r\n shouldClamp: shouldClamp,\r\n });\r\n\r\n return this.yProps;\r\n }\r\n\r\n public drawColumns(useAnimation: boolean): ColumnChartDrawInfo {\r\n let data = this.data;\r\n debug.assertValue(data, 'data should not be null or undefined');\r\n\r\n this.columnsCenters = null; // invalidate the columnsCenters so that will be calculated again\r\n\r\n let axisOptions: ColumnAxisOptions = {\r\n columnWidth: this.categoryLayout.categoryThickness * (1 - CartesianChart.InnerPaddingRatio),\r\n xScale: this.xProps.scale,\r\n yScale: this.yProps.scale,\r\n isScalar: this.categoryLayout.isScalar,\r\n margin: this.margin,\r\n };\r\n let stackedColumnLayout = this.layout = StackedColumnChartStrategy.getLayout(data, axisOptions);\r\n let dataLabelSettings = data.labelSettings;\r\n let labelDataPoints: LabelDataPoint[] = [];\r\n if (dataLabelSettings && dataLabelSettings.show) {\r\n labelDataPoints = this.createLabelDataPoints();\r\n }\r\n\r\n let result: ColumnChartAnimationResult;\r\n let shapes: D3.UpdateSelection;\r\n let series = ColumnUtil.drawSeries(data, this.graphicsContext.mainGraphicsContext, axisOptions);\r\n if (this.animator && useAnimation) {\r\n result = this.animator.animate({\r\n viewModel: data,\r\n series: series,\r\n layout: stackedColumnLayout,\r\n itemCS: StackedColumnChartStrategy.classes.item,\r\n interactivityService: this.interactivityService,\r\n mainGraphicsContext: this.graphicsContext.mainGraphicsContext,\r\n viewPort: { height: this.height, width: this.width },\r\n });\r\n shapes = result.shapes;\r\n }\r\n if (!this.animator || !useAnimation || result.failed) {\r\n shapes = ColumnUtil.drawDefaultShapes(data, series, stackedColumnLayout, StackedColumnChartStrategy.classes.item, !this.animator, this.interactivityService && this.interactivityService.hasSelection());\r\n }\r\n\r\n ColumnUtil.applyInteractivity(shapes, this.graphicsContext.onDragStart);\r\n\r\n return {\r\n eventGroup: this.graphicsContext.mainGraphicsContext,\r\n shapesSelection: shapes,\r\n viewport: { height: this.height, width: this.width },\r\n axisOptions,\r\n labelDataPoints: labelDataPoints,\r\n };\r\n }\r\n\r\n public selectColumn(selectedColumnIndex: number, lastSelectedColumnIndex: number): void {\r\n ColumnUtil.setChosenColumnOpacity(this.graphicsContext.mainGraphicsContext, StackedColumnChartStrategy.classes.item.selector, selectedColumnIndex, lastSelectedColumnIndex);\r\n this.moveHandle(selectedColumnIndex);\r\n }\r\n\r\n public getClosestColumnIndex(x: number, y: number): number {\r\n return ColumnUtil.getClosestColumnIndex(x, this.getColumnsCenters());\r\n }\r\n\r\n /**\r\n * Get the chart's columns centers (x value).\r\n */\r\n private getColumnsCenters(): number[] {\r\n if (!this.columnsCenters) { // lazy creation\r\n let categoryWidth: number = this.categoryLayout.categoryThickness * (1 - CartesianChart.InnerPaddingRatio);\r\n // use the axis scale and first series data to get category centers\r\n if (this.data.series.length > 0) {\r\n let xScaleOffset = 0;\r\n if (!this.categoryLayout.isScalar)\r\n xScaleOffset = categoryWidth / 2;\r\n let firstSeries = this.data.series[0];\r\n this.columnsCenters = firstSeries.data.map(d => this.xProps.scale(this.categoryLayout.isScalar ? d.categoryValue : d.categoryIndex) + xScaleOffset);\r\n }\r\n }\r\n return this.columnsCenters;\r\n }\r\n\r\n private moveHandle(selectedColumnIndex: number) {\r\n let columnCenters = this.getColumnsCenters();\r\n let x = columnCenters[selectedColumnIndex];\r\n\r\n let hoverLine = d3.select('.interactive-hover-line');\r\n if (!hoverLine.empty() && !this.columnSelectionLineHandle) {\r\n\r\n this.columnSelectionLineHandle = d3.select(hoverLine.node().parentNode);\r\n }\r\n\r\n if (!this.columnSelectionLineHandle) {\r\n let handle = this.columnSelectionLineHandle = this.graphicsContext.unclippedGraphicsContext.append('g');\r\n handle.append('line')\r\n .classed('interactive-hover-line', true)\r\n .attr({\r\n x1: x,\r\n x2: x,\r\n y1: 0,\r\n y2: this.height,\r\n });\r\n\r\n handle.append('circle')\r\n .attr({\r\n cx: x,\r\n cy: this.height,\r\n r: '6px',\r\n })\r\n .classed('drag-handle', true);\r\n }\r\n else {\r\n let handle = this.columnSelectionLineHandle;\r\n handle.select('line').attr({ x1: x, x2: x });\r\n handle.select('circle').attr({ cx: x });\r\n }\r\n }\r\n\r\n public static getLayout(data: ColumnChartData, axisOptions: ColumnAxisOptions): IColumnLayout {\r\n let columnWidth = axisOptions.columnWidth;\r\n let isScalar = axisOptions.isScalar;\r\n let xScale = axisOptions.xScale;\r\n let yScale = axisOptions.yScale;\r\n let xScaleOffset = 0;\r\n if (isScalar)\r\n xScaleOffset = columnWidth / 2;\r\n\r\n // d.position is the top left corner (for drawing) - set in columnChart.converter\r\n // for positive values, this is the previous stack position + the new value,\r\n // for negative values it is just the previous stack position\r\n\r\n return {\r\n shapeLayout: {\r\n width: (d: ColumnChartDataPoint) => columnWidth,\r\n x: (d: ColumnChartDataPoint) => xScale(isScalar ? d.categoryValue : d.categoryIndex) - xScaleOffset,\r\n y: (d: ColumnChartDataPoint) => yScale(d.position),\r\n height: (d: ColumnChartDataPoint) => yScale(d.position - d.valueAbsolute) - yScale(d.position),\r\n },\r\n shapeLayoutWithoutHighlights: {\r\n width: (d: ColumnChartDataPoint) => columnWidth,\r\n x: (d: ColumnChartDataPoint) => xScale(isScalar ? d.categoryValue : d.categoryIndex) - xScaleOffset,\r\n y: (d: ColumnChartDataPoint) => yScale(d.originalPosition),\r\n height: (d: ColumnChartDataPoint) => yScale(d.originalPosition - d.originalValueAbsolute) - yScale(d.originalPosition),\r\n },\r\n zeroShapeLayout: {\r\n width: (d: ColumnChartDataPoint) => columnWidth,\r\n x: (d: ColumnChartDataPoint) => xScale(isScalar ? d.categoryValue : d.categoryIndex) - xScaleOffset,\r\n y: (d: ColumnChartDataPoint) => d.value >= 0 ? yScale(d.position - d.valueAbsolute) : yScale(d.position),\r\n height: (d: ColumnChartDataPoint) => 0\r\n },\r\n };\r\n }\r\n\r\n private createLabelDataPoints(): LabelDataPoint[]{\r\n let labelDataPoints: LabelDataPoint[] = [];\r\n let data = this.data;\r\n let series = data.series;\r\n let formattersCache = NewDataLabelUtils.createColumnFormatterCacheManager();\r\n let shapeLayout = this.layout.shapeLayout;\r\n\r\n for (let currentSeries of series) {\r\n let labelSettings = currentSeries.labelSettings ? currentSeries.labelSettings : data.labelSettings;\r\n if (!labelSettings.show)\r\n continue;\r\n\r\n let axisFormatter: number = NewDataLabelUtils.getDisplayUnitValueFromAxisFormatter(this.yProps.formatter, labelSettings);\r\n for (let dataPoint of currentSeries.data) {\r\n if ((data.hasHighlights && !dataPoint.highlight) || dataPoint.value == null) {\r\n continue;\r\n }\r\n\r\n // Calculate parent rectangle\r\n let parentRect: IRect = {\r\n left: shapeLayout.x(dataPoint),\r\n top: shapeLayout.y(dataPoint),\r\n width: shapeLayout.width(dataPoint),\r\n height: shapeLayout.height(dataPoint),\r\n };\r\n\r\n // Calculate label text\r\n let formatString = \"\";\r\n if (this.graphicsContext.is100Pct) {\r\n formatString = NewDataLabelUtils.hundredPercentFormat;\r\n }\r\n else {\r\n formatString = dataPoint.labelFormatString;\r\n }\r\n let formatter = formattersCache.getOrCreate(formatString, labelSettings, axisFormatter);\r\n let text = NewDataLabelUtils.getLabelFormattedText(formatter.format(dataPoint.value));\r\n\r\n // Calculate text size\r\n let properties: TextProperties = {\r\n text: text,\r\n fontFamily: NewDataLabelUtils.LabelTextProperties.fontFamily,\r\n fontSize: PixelConverter.fromPoint(labelSettings.fontSize || NewDataLabelUtils.DefaultLabelFontSizeInPt),\r\n fontWeight: NewDataLabelUtils.LabelTextProperties.fontWeight,\r\n };\r\n let textWidth = TextMeasurementService.measureSvgTextWidth(properties);\r\n let textHeight = TextMeasurementService.estimateSvgTextHeight(properties, true /* tightFitForNumeric */);\r\n\r\n labelDataPoints.push({\r\n isPreferred: true,\r\n text: text,\r\n textSize: {\r\n width: textWidth,\r\n height: textHeight,\r\n },\r\n outsideFill: ColumnChart.getLabelFill(labelSettings.labelColor, false, this.isComboChart),\r\n insideFill: ColumnChart.getLabelFill(labelSettings.labelColor, true, this.isComboChart),\r\n parentType: LabelDataPointParentType.Rectangle,\r\n parentShape: {\r\n rect: parentRect,\r\n orientation: dataPoint.value >= 0 ? NewRectOrientation.VerticalBottomBased : NewRectOrientation.VerticalTopBased,\r\n validPositions: ColumnChart.stackedValidLabelPositions,\r\n },\r\n identity: dataPoint.identity,\r\n fontSize: labelSettings.fontSize || NewDataLabelUtils.DefaultLabelFontSizeInPt,\r\n });\r\n }\r\n }\r\n \r\n return labelDataPoints;\r\n }\r\n }\r\n\r\n export class StackedBarChartStrategy implements IColumnChartStrategy {\r\n private static classes = {\r\n item: {\r\n class: 'bar',\r\n selector: '.bar'\r\n },\r\n highlightItem: {\r\n class: 'highlightBar',\r\n selector: '.highlightBar'\r\n },\r\n };\r\n\r\n private data: ColumnChartData;\r\n private graphicsContext: ColumnChartContext;\r\n private width: number; height: number;\r\n private margin: IMargin;\r\n private xProps: IAxisProperties;\r\n private yProps: IAxisProperties;\r\n private categoryLayout: CategoryLayout;\r\n private barsCenters: number[];\r\n private columnSelectionLineHandle: D3.Selection;\r\n private animator: IColumnChartAnimator;\r\n private interactivityService: IInteractivityService;\r\n private viewportHeight: number;\r\n private viewportWidth: number;\r\n private layout: IColumnLayout;\r\n private isComboChart: boolean;\r\n\r\n public setupVisualProps(barChartProps: ColumnChartContext): void {\r\n this.graphicsContext = barChartProps;\r\n this.margin = barChartProps.margin;\r\n this.width = this.graphicsContext.width;\r\n this.height = this.graphicsContext.height;\r\n this.categoryLayout = barChartProps.layout;\r\n this.animator = barChartProps.animator;\r\n this.interactivityService = barChartProps.interactivityService;\r\n this.viewportHeight = barChartProps.viewportHeight;\r\n this.viewportWidth = barChartProps.viewportWidth;\r\n this.isComboChart = barChartProps.isComboChart;\r\n }\r\n\r\n public setData(data: ColumnChartData) {\r\n this.data = data;\r\n }\r\n\r\n public setYScale(is100Pct: boolean, forcedTickCount?: number, forcedYDomain?: any[], axisScaleType?: string, axisDisplayUnits?: number, axisPrecision?: number, ensureYDomain?: NumberRange): IAxisProperties {\r\n let height = this.height;\r\n\r\n let forcedYMin, forcedYMax;\r\n\r\n if (forcedYDomain && forcedYDomain.length === 2) {\r\n forcedYMin = forcedYDomain[0];\r\n forcedYMax = forcedYDomain[1];\r\n }\r\n\r\n let props = this.yProps = ColumnUtil.getCategoryAxis(\r\n this.data,\r\n height,\r\n this.categoryLayout,\r\n true,\r\n forcedYMin,\r\n forcedYMax,\r\n axisScaleType,\r\n axisDisplayUnits,\r\n axisPrecision,\r\n ensureYDomain);\r\n\r\n return props;\r\n }\r\n\r\n public setXScale(is100Pct: boolean, forcedTickCount?: number, forcedXDomain?: any[], axisScaleType?: string, axisDisplayUnits?: number, axisPrecision?: number, ensureXDomain?: NumberRange): IAxisProperties {\r\n debug.assert(forcedTickCount === undefined, 'Cannot have stacked bar chart as combo chart.');\r\n\r\n let width = this.width;\r\n let valueDomain = StackedUtil.calcValueDomain(this.data.series, is100Pct);\r\n let valueDomainArr = [valueDomain.min, valueDomain.max];\r\n let combinedDomain = AxisHelper.combineDomain(forcedXDomain, valueDomainArr, ensureXDomain);\r\n let shouldClamp = AxisHelper.scaleShouldClamp(combinedDomain, valueDomainArr);\r\n let metadataColumn = this.data.valuesMetadata[0];\r\n let formatString = is100Pct ?\r\n this.graphicsContext.hostService.getLocalizedString('Percentage')\r\n : valueFormatter.getFormatString(metadataColumn, columnChartProps.general.formatString);\r\n\r\n this.xProps = AxisHelper.createAxis({\r\n pixelSpan: width,\r\n dataDomain: combinedDomain,\r\n metaDataColumn: metadataColumn,\r\n formatString: formatString,\r\n outerPadding: 0,\r\n isScalar: true,\r\n isVertical: false,\r\n forcedTickCount: forcedTickCount,\r\n useTickIntervalForDisplayUnits: true,\r\n isCategoryAxis: false,\r\n scaleType: axisScaleType,\r\n axisDisplayUnits: axisDisplayUnits,\r\n axisPrecision: axisPrecision,\r\n is100Pct: is100Pct,\r\n shouldClamp: shouldClamp,\r\n });\r\n\r\n this.xProps.axis.tickSize(-this.viewportHeight, 0);\r\n\r\n return this.xProps;\r\n }\r\n\r\n public drawColumns(useAnimation: boolean): ColumnChartDrawInfo {\r\n let data = this.data;\r\n debug.assertValue(data, 'data should not be null or undefined');\r\n\r\n this.barsCenters = null; // invalidate the barsCenters so that will be calculated again\r\n\r\n let axisOptions: ColumnAxisOptions = {\r\n columnWidth: this.categoryLayout.categoryThickness * (1 - CartesianChart.InnerPaddingRatio),\r\n xScale: this.xProps.scale,\r\n yScale: this.yProps.scale,\r\n isScalar: this.categoryLayout.isScalar,\r\n margin: this.margin,\r\n };\r\n let stackedBarLayout = this.layout = StackedBarChartStrategy.getLayout(data, axisOptions);\r\n let dataLabelSettings = data.labelSettings;\r\n let labelDataPoints: LabelDataPoint[] = [];\r\n if (dataLabelSettings && dataLabelSettings.show) {\r\n labelDataPoints = this.createLabelDataPoints();\r\n }\r\n\r\n let result: ColumnChartAnimationResult;\r\n let shapes: D3.UpdateSelection;\r\n let series = ColumnUtil.drawSeries(data, this.graphicsContext.mainGraphicsContext, axisOptions);\r\n if (this.animator && useAnimation) {\r\n result = this.animator.animate({\r\n viewModel: data,\r\n series: series,\r\n layout: stackedBarLayout,\r\n itemCS: StackedBarChartStrategy.classes.item,\r\n interactivityService: this.interactivityService,\r\n mainGraphicsContext: this.graphicsContext.mainGraphicsContext,\r\n viewPort: { height: this.height, width: this.width },\r\n });\r\n shapes = result.shapes;\r\n }\r\n if (!this.animator || !useAnimation || result.failed) {\r\n shapes = ColumnUtil.drawDefaultShapes(data, series, stackedBarLayout, StackedBarChartStrategy.classes.item, !this.animator, this.interactivityService && this.interactivityService.hasSelection());\r\n }\r\n\r\n ColumnUtil.applyInteractivity(shapes, this.graphicsContext.onDragStart);\r\n\r\n return {\r\n eventGroup: this.graphicsContext.mainGraphicsContext,\r\n shapesSelection: shapes,\r\n viewport: { height: this.height, width: this.width },\r\n axisOptions: axisOptions,\r\n labelDataPoints: labelDataPoints,\r\n };\r\n }\r\n\r\n public selectColumn(selectedColumnIndex: number, lastInteractiveSelectedColumnIndex: number): void {\r\n ColumnUtil.setChosenColumnOpacity(this.graphicsContext.mainGraphicsContext, StackedBarChartStrategy.classes.item.selector, selectedColumnIndex, lastInteractiveSelectedColumnIndex);\r\n this.moveHandle(selectedColumnIndex);\r\n }\r\n\r\n public getClosestColumnIndex(x: number, y: number): number {\r\n return ColumnUtil.getClosestColumnIndex(y, this.getBarsCenters());\r\n }\r\n\r\n /** \r\n * Get the chart's columns centers (y value).\r\n */\r\n private getBarsCenters(): number[] {\r\n if (!this.barsCenters) { // lazy creation\r\n let barWidth: number = this.categoryLayout.categoryThickness * (1 - CartesianChart.InnerPaddingRatio);\r\n // use the axis scale and first series data to get category centers\r\n if (this.data.series.length > 0) {\r\n let yScaleOffset = 0;\r\n if (!this.categoryLayout.isScalar)\r\n yScaleOffset = barWidth / 2;\r\n let firstSeries = this.data.series[0];\r\n this.barsCenters = firstSeries.data.map(d => this.yProps.scale(this.categoryLayout.isScalar ? d.categoryValue : d.categoryIndex) + yScaleOffset);\r\n }\r\n }\r\n return this.barsCenters;\r\n }\r\n\r\n private moveHandle(selectedColumnIndex: number) {\r\n let barCenters = this.getBarsCenters();\r\n let y = barCenters[selectedColumnIndex];\r\n \r\n let hoverLine = d3.select('.interactive-hover-line');\r\n if (!hoverLine.empty() && !this.columnSelectionLineHandle) {\r\n\r\n this.columnSelectionLineHandle = d3.select(hoverLine.node().parentNode);\r\n }\r\n\r\n if (!this.columnSelectionLineHandle) {\r\n let handle = this.columnSelectionLineHandle = this.graphicsContext.unclippedGraphicsContext.append('g');\r\n handle.append('line')\r\n .classed('interactive-hover-line', true)\r\n .attr({\r\n x1: 0,\r\n x2: this.width,\r\n y1: y,\r\n y2: y\r\n });\r\n handle.append('circle')\r\n .classed('drag-handle', true)\r\n .attr({\r\n cx: 0,\r\n cy: y,\r\n r: '6px',\r\n });\r\n\r\n }\r\n else {\r\n let handle = this.columnSelectionLineHandle;\r\n handle.select('line').attr({ y1: y, y2: y });\r\n handle.select('circle').attr({ cy: y });\r\n }\r\n }\r\n\r\n public static getLayout(data: ColumnChartData, axisOptions: ColumnAxisOptions): IColumnLayout {\r\n let columnWidth = axisOptions.columnWidth;\r\n let isScalar = axisOptions.isScalar;\r\n let xScale = axisOptions.xScale;\r\n let yScale = axisOptions.yScale;\r\n let yScaleOffset = 0;\r\n if (isScalar)\r\n yScaleOffset = columnWidth / 2;\r\n\r\n // d.position is the top right corner for bars - set in columnChart.converter\r\n // for positive values, this is the previous stack position + the new value,\r\n // for negative values it is just the previous stack position\r\n\r\n return {\r\n shapeLayout: {\r\n width: (d: ColumnChartDataPoint) => xScale(d.position) - xScale(d.position - d.valueAbsolute),\r\n x: (d: ColumnChartDataPoint) => xScale(d.position - d.valueAbsolute),\r\n y: (d: ColumnChartDataPoint) => yScale(isScalar ? d.categoryValue : d.categoryIndex) - yScaleOffset,\r\n height: (d: ColumnChartDataPoint) => columnWidth,\r\n },\r\n shapeLayoutWithoutHighlights: {\r\n width: (d: ColumnChartDataPoint) => xScale(d.originalPosition) - xScale(d.originalPosition - d.originalValueAbsolute),\r\n x: (d: ColumnChartDataPoint) => xScale(d.originalPosition - d.originalValueAbsolute),\r\n y: (d: ColumnChartDataPoint) => yScale(isScalar ? d.categoryValue : d.categoryIndex) - yScaleOffset,\r\n height: (d: ColumnChartDataPoint) => columnWidth,\r\n },\r\n zeroShapeLayout: {\r\n width: (d: ColumnChartDataPoint) => 0,\r\n x: (d: ColumnChartDataPoint) => d.value >= 0 ? xScale(d.position - d.valueAbsolute) : xScale(d.position),\r\n y: (d: ColumnChartDataPoint) => yScale(isScalar ? d.categoryValue : d.categoryIndex) - yScaleOffset,\r\n height: (d: ColumnChartDataPoint) => columnWidth,\r\n },\r\n };\r\n }\r\n\r\n private createLabelDataPoints(): LabelDataPoint[] {\r\n let labelDataPoints: LabelDataPoint[] = [];\r\n let data = this.data;\r\n let series = data.series;\r\n let formattersCache = NewDataLabelUtils.createColumnFormatterCacheManager();\r\n let shapeLayout = this.layout.shapeLayout;\r\n\r\n for (let currentSeries of series) {\r\n let labelSettings = currentSeries.labelSettings ? currentSeries.labelSettings : data.labelSettings;\r\n if (!labelSettings.show)\r\n continue;\r\n\r\n let axisFormatter: number = NewDataLabelUtils.getDisplayUnitValueFromAxisFormatter(this.yProps.formatter, labelSettings);\r\n for (let dataPoint of currentSeries.data) {\r\n if ((data.hasHighlights && !dataPoint.highlight) || dataPoint.value == null) {\r\n continue;\r\n }\r\n\r\n // Calculate label text\r\n let formatString = undefined;\r\n if (this.graphicsContext.is100Pct) {\r\n formatString = NewDataLabelUtils.hundredPercentFormat;\r\n }\r\n else {\r\n formatString = dataPoint.labelFormatString;\r\n }\r\n let formatter = formattersCache.getOrCreate(formatString, labelSettings, axisFormatter);\r\n let text = NewDataLabelUtils.getLabelFormattedText(formatter.format(dataPoint.value));\r\n\r\n // Calculate text size\r\n let properties: TextProperties = {\r\n text: text,\r\n fontFamily: NewDataLabelUtils.LabelTextProperties.fontFamily,\r\n fontSize: PixelConverter.fromPoint(labelSettings.fontSize || NewDataLabelUtils.DefaultLabelFontSizeInPt),\r\n fontWeight: NewDataLabelUtils.LabelTextProperties.fontWeight,\r\n };\r\n let textWidth = TextMeasurementService.measureSvgTextWidth(properties);\r\n let textHeight = TextMeasurementService.estimateSvgTextHeight(properties, true /* tightFitForNumeric */);\r\n\r\n // Calculate parent rectangle\r\n let parentRect: IRect = {\r\n left: shapeLayout.x(dataPoint),\r\n top: shapeLayout.y(dataPoint),\r\n width: shapeLayout.width(dataPoint),\r\n height: shapeLayout.height(dataPoint),\r\n };\r\n\r\n labelDataPoints.push({\r\n isPreferred: true,\r\n text: text,\r\n textSize: {\r\n width: textWidth,\r\n height: textHeight,\r\n },\r\n outsideFill: ColumnChart.getLabelFill(labelSettings.labelColor, false, this.isComboChart),\r\n insideFill: ColumnChart.getLabelFill(labelSettings.labelColor, true, this.isComboChart),\r\n parentType: LabelDataPointParentType.Rectangle,\r\n parentShape: {\r\n rect: parentRect,\r\n orientation: dataPoint.value >= 0 ? NewRectOrientation.HorizontalLeftBased : NewRectOrientation.HorizontalRightBased,\r\n validPositions: ColumnChart.stackedValidLabelPositions,\r\n },\r\n identity: dataPoint.identity,\r\n fontSize: labelSettings.fontSize || NewDataLabelUtils.DefaultLabelFontSizeInPt,\r\n });\r\n }\r\n }\r\n\r\n return labelDataPoints;\r\n }\r\n }\r\n} \n\n\n/** WEBPACK FOOTER **\n ** ./src/Clients/Visuals/cartesian/columnChartStacked.ts\n **/","/*\r\n * Power BI Visualizations\r\n *\r\n * Copyright (c) Microsoft Corporation\r\n * All rights reserved. \r\n * MIT License\r\n *\r\n * Permission is hereby granted, free of charge, to any person obtaining a copy\r\n * of this software and associated documentation files (the \"\"Software\"\"), to deal\r\n * in the Software without restriction, including without limitation the rights\r\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\r\n * copies of the Software, and to permit persons to whom the Software is\r\n * furnished to do so, subject to the following conditions:\r\n * \r\n * The above copyright notice and this permission notice shall be included in \r\n * all copies or substantial portions of the Software.\r\n * \r\n * THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR \r\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, \r\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE \r\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER \r\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\r\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\r\n * THE SOFTWARE.\r\n */\r\n\r\n/// <reference path=\"../../_references.ts\"/>\r\n\r\nmodule powerbi.visuals.samples {\r\n import SelectionManager = utility.SelectionManager;\r\n export interface HelloViewModel {\r\n text: string;\r\n color: string;\r\n size: number;\r\n selector: SelectionId;\r\n toolTipInfo: TooltipDataItem[];\r\n }\r\n\r\n export class HelloIVisual implements IVisual {\r\n public static capabilities: VisualCapabilities = {\r\n dataRoles: [{\r\n displayName: 'Values',\r\n name: 'Values',\r\n kind: VisualDataRoleKind.GroupingOrMeasure\r\n }],\r\n dataViewMappings: [{\r\n table: {\r\n rows: {\r\n for: { in: 'Values' },\r\n dataReductionAlgorithm: { window: { count: 100 } }\r\n },\r\n rowCount: { preferred: { min: 1 } }\r\n },\r\n }],\r\n objects: {\r\n general: {\r\n displayName: data.createDisplayNameGetter('Visual_General'),\r\n properties: {\r\n fill: {\r\n type: { fill: { solid: { color: true } } },\r\n displayName: 'Fill'\r\n },\r\n size: {\r\n type: { numeric: true },\r\n displayName: 'Size'\r\n }\r\n },\r\n }\r\n },\r\n };\r\n\r\n private static DefaultText = 'Invalid DV';\r\n private root: D3.Selection;\r\n private svgText: D3.Selection;\r\n private dataView: DataView;\r\n private selectiionManager: SelectionManager;\r\n\r\n public static converter(dataView: DataView): HelloViewModel {\r\n var viewModel: HelloViewModel = {\r\n size: HelloIVisual.getSize(dataView),\r\n color: HelloIVisual.getFill(dataView).solid.color,\r\n text: HelloIVisual.DefaultText,\r\n toolTipInfo: [{\r\n displayName: 'Test',\r\n value: '1...2....3... can you see me? I am sending random strings to the tooltip',\r\n }],\r\n selector: SelectionId.createNull()\r\n };\r\n var table = dataView.table;\r\n if (!table) return viewModel;\r\n\r\n viewModel.text = table.rows[0][0];\r\n if (dataView.categorical) {\r\n viewModel.selector = dataView.categorical.categories[0].identity\r\n ? SelectionId.createWithId(dataView.categorical.categories[0].identity[0])\r\n : SelectionId.createNull();\r\n }\r\n\r\n return viewModel;\r\n }\r\n\r\n public init(options: VisualInitOptions): void {\r\n this.root = d3.select(options.element.get(0))\r\n .append('svg')\r\n .classed('hello', true);\r\n\r\n this.svgText = this.root\r\n .append('text')\r\n .style('cursor', 'pointer')\r\n .style('stroke', 'green')\r\n .style('stroke-width', '0px')\r\n .attr('text-anchor', 'middle');\r\n\r\n this.selectiionManager = new SelectionManager({ hostServices: options.host });\r\n }\r\n\r\n public update(options: VisualUpdateOptions) {\r\n if (!options.dataViews && !options.dataViews[0]) return;\r\n var dataView = this.dataView = options.dataViews[0];\r\n var viewport = options.viewport;\r\n var viewModel: HelloViewModel = HelloIVisual.converter(dataView);\r\n\r\n this.root.attr({\r\n 'height': viewport.height,\r\n 'width': viewport.width\r\n });\r\n\r\n var textProperties = {\r\n fontFamily: 'tahoma',\r\n fontSize: viewModel.size + 'px',\r\n text: viewModel.text\r\n };\r\n var textHeight = TextMeasurementService.estimateSvgTextHeight(textProperties);\r\n var selectionManager = this.selectiionManager;\r\n\r\n this.svgText.style({\r\n 'fill': viewModel.color,\r\n 'font-size': textProperties.fontSize,\r\n 'font-family': textProperties.fontFamily,\r\n }).attr({\r\n 'y': viewport.height / 2 + textHeight / 3 + 'px',\r\n 'x': viewport.width / 2,\r\n }).text(viewModel.text)\r\n .on('click', function () {\r\n selectionManager\r\n .select(viewModel.selector)\r\n .then(ids => d3.select(this).style('stroke-width', ids.length > 0 ? '2px' : '0px'));\r\n })\r\n .data([viewModel]);\r\n\r\n TooltipManager.addTooltip(this.svgText, (tooltipEvent: TooltipEvent) => tooltipEvent.data.toolTipInfo);\r\n }\r\n\r\n private static getFill(dataView: DataView): Fill {\r\n if (dataView) {\r\n var objects = dataView.metadata.objects;\r\n if (objects) {\r\n var general = objects['general'];\r\n if (general) {\r\n var fill = <Fill>general['fill'];\r\n if (fill)\r\n return fill;\r\n }\r\n }\r\n }\r\n return { solid: { color: 'red' } };\r\n }\r\n\r\n private static getSize(dataView: DataView): number {\r\n if (dataView) {\r\n var objects = dataView.metadata.objects;\r\n if (objects) {\r\n var general = objects['general'];\r\n if (general) {\r\n var size = <number>general['size'];\r\n if (size)\r\n return size;\r\n }\r\n }\r\n }\r\n return 100;\r\n }\r\n\r\n public enumerateObjectInstances(options: EnumerateVisualObjectInstancesOptions): VisualObjectInstance[] {\r\n var instances: VisualObjectInstance[] = [];\r\n var dataView = this.dataView;\r\n switch (options.objectName) {\r\n case 'general':\r\n var general: VisualObjectInstance = {\r\n objectName: 'general',\r\n displayName: 'General',\r\n selector: null,\r\n properties: {\r\n fill: HelloIVisual.getFill(dataView),\r\n size: HelloIVisual.getSize(dataView)\r\n }\r\n };\r\n instances.push(general);\r\n break;\r\n }\r\n\r\n return instances;\r\n }\r\n\r\n public destroy(): void {\r\n this.root = null;\r\n }\r\n }\r\n}\n\n\n/** WEBPACK FOOTER **\n ** ./src/Clients/Visuals/visuals/samples/helloIVisual.ts\n **/","/*\r\n * Power BI Visualizations\r\n *\r\n * Copyright (c) Microsoft Corporation\r\n * All rights reserved. \r\n * MIT License\r\n *\r\n * Permission is hereby granted, free of charge, to any person obtaining a copy\r\n * of this software and associated documentation files (the \"\"Software\"\"), to deal\r\n * in the Software without restriction, including without limitation the rights\r\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\r\n * copies of the Software, and to permit persons to whom the Software is\r\n * furnished to do so, subject to the following conditions:\r\n * \r\n * The above copyright notice and this permission notice shall be included in \r\n * all copies or substantial portions of the Software.\r\n * \r\n * THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR \r\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, \r\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE \r\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER \r\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\r\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\r\n * THE SOFTWARE.\r\n */\r\n\r\n/// <reference path=\"../_references.ts\"/>\r\n\r\nmodule powerbi.visuals {\r\n export interface ComboChartDataViewObjects extends DataViewObjects {\r\n general: ComboChartDataViewObject;\r\n }\r\n\r\n export interface ComboChartDataViewObject extends DataViewObject {\r\n visualType1: string;\r\n visualType2: string;\r\n }\r\n\r\n /**\r\n * This module only supplies the capabilities for comboCharts.\r\n * Implementation is in cartesianChart and the various ICartesianVisual implementations.\r\n */\r\n export module ComboChart {\r\n export const capabilities = comboChartCapabilities;\r\n\r\n /**\r\n * Handles the case of a column layer in a combo chart. In this case, the column layer is enumearated last.\r\n */\r\n export function enumerateDataPoints(enumeration: ObjectEnumerationBuilder, options: EnumerateVisualObjectInstancesOptions, layers: ICartesianVisual[]): void {\r\n if (!layers)\r\n return;\r\n\r\n let columnChartLayerIndex: number;\r\n let layersLength: number = layers.length;\r\n\r\n for (let layerIndex = 0; layerIndex < layersLength; layerIndex++) {\r\n let layer = layers[layerIndex];\r\n\r\n if (layer.enumerateObjectInstances) {\r\n if (layer instanceof ColumnChart) {\r\n columnChartLayerIndex = layerIndex;\r\n continue;\r\n }\r\n\r\n layer.enumerateObjectInstances(enumeration, options);\r\n }\r\n }\r\n\r\n if (columnChartLayerIndex !== undefined)\r\n layers[columnChartLayerIndex].enumerateObjectInstances(enumeration, options);\r\n }\r\n\r\n export function customizeQuery(options: CustomizeQueryOptions): void {\r\n // If there is a dynamic series but no values on the column data view mapping, remove the dynamic series\r\n let columnMapping = !_.isEmpty(options.dataViewMappings) && options.dataViewMappings[0];\r\n if (columnMapping) {\r\n let columnValuesMapping: data.CompiledDataViewGroupedRoleMapping = columnMapping.categorical && <data.CompiledDataViewGroupedRoleMapping>columnMapping.categorical.values;\r\n let seriesSelect = columnValuesMapping.group && !_.isEmpty(columnValuesMapping.group.select) && <data.CompiledDataViewRoleForMapping>columnValuesMapping.group.select[0];\r\n if (_.isEmpty(seriesSelect.for.in.items))\r\n columnValuesMapping.group.by.items = undefined;\r\n }\r\n \r\n let isScalar = CartesianChart.detectScalarMapping(columnMapping);\r\n\r\n if (columnMapping && columnMapping.categorical) {\r\n columnMapping.categorical.dataVolume = 4;\r\n if (isScalar) {\r\n let dataViewCategories = <data.CompiledDataViewRoleForMappingWithReduction>columnMapping.categorical.categories;\r\n dataViewCategories.dataReductionAlgorithm = { sample: {} };\r\n }\r\n else {\r\n CartesianChart.applyLoadMoreEnabledToMapping(options.cartesianLoadMoreEnabled, columnMapping); \r\n }\r\n }\r\n\r\n let lineMapping = options.dataViewMappings.length > 1 && options.dataViewMappings[1];\r\n if (lineMapping && lineMapping.categorical) {\r\n lineMapping.categorical.dataVolume = 4;\r\n if (isScalar) {\r\n let dataViewCategories = <data.CompiledDataViewRoleForMappingWithReduction>lineMapping.categorical.categories;\r\n dataViewCategories.dataReductionAlgorithm = { sample: {} };\r\n }\r\n else {\r\n CartesianChart.applyLoadMoreEnabledToMapping(options.cartesianLoadMoreEnabled, lineMapping);\r\n }\r\n }\r\n }\r\n\r\n export function getSortableRoles(options: VisualSortableOptions): string[] {\r\n if (options && options.dataViewMappings.length > 0) {\r\n let dataViewMapping = options.dataViewMappings[0];\r\n //TODO: column chart should be sortable by X if it has scalar axis\r\n // But currenly it doesn't support this. Return 'category' once\r\n // it is supported.\r\n if (!CartesianChart.detectScalarMapping(dataViewMapping))\r\n return ['Category', 'Y', 'Y2'];\r\n }\r\n\r\n return null;\r\n }\r\n\r\n export function isComboChart(chartType: CartesianChartType): boolean {\r\n return chartType === CartesianChartType.ComboChart\r\n || chartType === CartesianChartType.LineClusteredColumnCombo\r\n || chartType === CartesianChartType.LineStackedColumnCombo\r\n || chartType === CartesianChartType.DataDotClusteredColumnCombo\r\n || chartType === CartesianChartType.DataDotStackedColumnCombo;\r\n }\r\n }\r\n}\n\n\n/** WEBPACK FOOTER **\n ** ./src/Clients/Visuals/cartesian/comboChart.ts\n **/","/*\r\n * Power BI Visualizations\r\n *\r\n * Copyright (c) Microsoft Corporation\r\n * All rights reserved. \r\n * MIT License\r\n *\r\n * Permission is hereby granted, free of charge, to any person obtaining a copy\r\n * of this software and associated documentation files (the \"\"Software\"\"), to deal\r\n * in the Software without restriction, including without limitation the rights\r\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\r\n * copies of the Software, and to permit persons to whom the Software is\r\n * furnished to do so, subject to the following conditions:\r\n * \r\n * The above copyright notice and this permission notice shall be included in \r\n * all copies or substantial portions of the Software.\r\n * \r\n * THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR \r\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, \r\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE \r\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER \r\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\r\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\r\n * THE SOFTWARE.\r\n */\r\n\r\n/// <reference path=\"./_references.ts\"/>\r\n\r\nmodule powerbi.visuals {\r\n import ArrayExtensions = jsCommon.ArrayExtensions;\r\n\r\n export class DataColorPalette implements IDataColorPalette {\r\n private scales: { [index: string]: IColorScale };\r\n private colors: IColorInfo[];\r\n private sentimentColors: IColorInfo[];\r\n\r\n // Hardcoded values for Color Picker.\r\n private basePickerColors: IColorInfo[] = [\r\n { value: '#FFFFFF' },\r\n { value: '#000000' },\r\n { value: '#00B8AA' },\r\n { value: '#374649' },\r\n { value: '#FD625E' },\r\n { value: '#F2C811' },\r\n { value: '#5F6B6D' },\r\n { value: '#8AD4EB' },\r\n { value: '#FE9666' },\r\n { value: '#A66999' }\r\n ];\r\n\r\n /**\r\n * Creates a DataColorPalette using the given theme, or the default theme.\r\n */\r\n constructor(colors?: IColorInfo[], sentimentcolors?: IColorInfo[]) {\r\n // TODO: Default theme is currently hardcoded. Theme should eventually come from PV and be added as a parameter in the ctor. \r\n this.colors = colors || ThemeManager.getDefaultTheme();\r\n this.sentimentColors = sentimentcolors || ThemeManager.defaultSentimentColors;\r\n this.scales = {};\r\n }\r\n\r\n public getColorScaleByKey(key: string): IColorScale {\r\n let scale = this.scales[key];\r\n if (scale === undefined) {\r\n scale = this.createScale();\r\n this.scales[key] = scale;\r\n }\r\n\r\n return scale;\r\n }\r\n\r\n public getNewColorScale(): IColorScale {\r\n return this.createScale();\r\n }\r\n\r\n public getColorByIndex(index: number): IColorInfo {\r\n debug.assert(index >= 0 && index < this.colors.length, 'index is out of bounds');\r\n return this.colors[index];\r\n }\r\n\r\n public getSentimentColors(): IColorInfo[] {\r\n return this.sentimentColors;\r\n }\r\n\r\n public getBasePickerColors(): IColorInfo[] {\r\n return this.basePickerColors;\r\n }\r\n\r\n public getAllColors(): IColorInfo[] {\r\n return this.colors;\r\n }\r\n\r\n private createScale(): IColorScale {\r\n return D3ColorScale.createFromColors(this.colors);\r\n }\r\n }\r\n\r\n export class D3ColorScale implements IColorScale {\r\n private scale: D3.Scale.OrdinalScale;\r\n\r\n constructor(scale: D3.Scale.OrdinalScale) {\r\n this.scale = scale;\r\n }\r\n\r\n public getColor(key: any): IColorInfo {\r\n return this.scale(key);\r\n }\r\n\r\n public clearAndRotateScale(): void {\r\n let offset = this.scale.domain().length;\r\n let rotatedColors = ArrayExtensions.rotate(this.scale.range(), offset);\r\n this.scale = d3.scale.ordinal().range(rotatedColors);\r\n }\r\n\r\n public clone(): IColorScale {\r\n return new D3ColorScale(this.scale.copy());\r\n }\r\n\r\n public getDomain(): any[]{\r\n return this.scale.domain();\r\n }\r\n\r\n public static createFromColors(colors: IColorInfo[]): D3ColorScale {\r\n return new D3ColorScale(d3.scale.ordinal().range(colors));\r\n }\r\n }\r\n\r\n // TODO: When theming support is added, this should be changed into a fully fledged service. For now though we will\r\n export class ThemeManager {\r\n private static colorSectorCount = 12;\r\n \r\n // declare the Theme code as a private implementation detail inside the DataColorPalette so that the code stays hidden\r\n // until it's ready for wider use.\r\n private static defaultBaseColors: IColorInfo[] = [\r\n // First loop\r\n { value: '#01B8AA' },\r\n { value: '#374649' },\r\n { value: '#FD625E' },\r\n { value: '#F2C80F' },\r\n { value: '#5F6B6D' },\r\n { value: '#8AD4EB' },\r\n { value: '#FE9666' }, // Bethany's Mango\r\n { value: '#A66999' },\r\n { value: '#3599B8' },\r\n { value: '#DFBFBF' },\r\n\r\n // Second loop\r\n { value: '#4AC5BB' },\r\n { value: '#5F6B6D' },\r\n { value: '#FB8281' },\r\n { value: '#F4D25A' },\r\n { value: '#7F898A' },\r\n { value: '#A4DDEE' },\r\n { value: '#FDAB89' },\r\n { value: '#B687AC' },\r\n { value: '#28738A' },\r\n { value: '#A78F8F' },\r\n\r\n // Third loop\r\n { value: '#168980' },\r\n { value: '#293537' },\r\n { value: '#BB4A4A' },\r\n { value: '#B59525' },\r\n { value: '#475052' },\r\n { value: '#6A9FB0' },\r\n { value: '#BD7150' },\r\n { value: '#7B4F71' },\r\n { value: '#1B4D5C' },\r\n { value: '#706060' },\r\n\r\n // Fourth loop\r\n { value: '#0F5C55' },\r\n { value: '#1C2325' },\r\n { value: '#7D3231' },\r\n { value: '#796419' },\r\n { value: '#303637' },\r\n { value: '#476A75' },\r\n { value: '#7E4B36' },\r\n { value: '#52354C' },\r\n { value: '#0D262E' },\r\n { value: '#544848' },\r\n ];\r\n\r\n private static defaultTheme: IColorInfo[];\r\n \r\n public static defaultSentimentColors: IColorInfo[] = [\r\n { value: '#C0433A' }, // Red\r\n { value: '#E8D62E' }, // Yellow\r\n { value: '#79C75B' }, // Green\r\n ];\r\n\r\n public static getDefaultTheme(): IColorInfo[] {\r\n if (!ThemeManager.defaultTheme) {\r\n // Extend the list of available colors by cycling the base colors\r\n ThemeManager.defaultTheme = [];\r\n let baseColors = ThemeManager.defaultBaseColors;\r\n for (let i = 0; i < ThemeManager.colorSectorCount; ++i) {\r\n for (let j = 0, jlen = baseColors.length; j < jlen; ++j) {\r\n ThemeManager.defaultTheme.push(\r\n {\r\n value: jsCommon.Color.rotate(baseColors[j].value, i / ThemeManager.colorSectorCount)\r\n });\r\n }\r\n }\r\n }\r\n\r\n return ThemeManager.defaultTheme;\r\n }\r\n }\r\n} \n\n\n/** WEBPACK FOOTER **\n ** ./src/Clients/Visuals/dataColorPalette.ts\n **/","/*\r\n * Power BI Visualizations\r\n *\r\n * Copyright (c) Microsoft Corporation\r\n * All rights reserved. \r\n * MIT License\r\n *\r\n * Permission is hereby granted, free of charge, to any person obtaining a copy\r\n * of this software and associated documentation files (the \"\"Software\"\"), to deal\r\n * in the Software without restriction, including without limitation the rights\r\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\r\n * copies of the Software, and to permit persons to whom the Software is\r\n * furnished to do so, subject to the following conditions:\r\n * \r\n * The above copyright notice and this permission notice shall be included in \r\n * all copies or substantial portions of the Software.\r\n * \r\n * THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR \r\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, \r\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE \r\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER \r\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\r\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\r\n * THE SOFTWARE.\r\n */\r\n\r\n/**\r\n * IMPORTANT: This chart is not currently enabled in the PBI system and is under development.\r\n */\r\n\r\n/// <reference path=\"../_references.ts\"/>\r\n\r\nmodule powerbi.visuals {\r\n\r\n export interface IDataDotChartConfiguration {\r\n xAxisProperties: IAxisProperties;\r\n yAxisProperties: IAxisProperties;\r\n margin: any;\r\n }\r\n\r\n export interface DataDotChartData {\r\n series: DataDotChartSeries;\r\n hasHighlights: boolean;\r\n hasDynamicSeries: boolean;\r\n }\r\n\r\n export interface DataDotChartSeries extends CartesianSeries {\r\n xCol: DataViewMetadataColumn;\r\n yCol: DataViewMetadataColumn;\r\n data: DataDotChartDataPoint[]; //overridden type of array \r\n }\r\n\r\n export interface DataDotChartDataPoint extends CartesianDataPoint, SelectableDataPoint {\r\n highlight: boolean;\r\n }\r\n\r\n export interface DataDotChartConstructorOptions extends CartesianVisualConstructorOptions {\r\n }\r\n\r\n /**\r\n * The data dot chart shows a set of circles with the data value inside them.\r\n * The circles are regularly spaced similar to column charts.\r\n * The radius of all dots is the same across the chart.\r\n * This is most often combined with a column chart to create the 'chicken pox' chart.\r\n * If any of the data values do not fit within the circles, then the data values are hidden\r\n * and the y axis for the dots is displayed instead.\r\n * This chart only supports a single series of data.\r\n * This chart does not display a legend.\r\n */\r\n export class DataDotChart implements ICartesianVisual {\r\n public static formatStringProp: DataViewObjectPropertyIdentifier = { objectName: 'general', propertyName: 'formatString' };\r\n private static ClassName = 'dataDotChart';\r\n\r\n private static DotClassName = 'dot';\r\n private static DotClassSelector = '.dot';\r\n private static DotColorKey = 'dataDot';\r\n\r\n private static DotLabelClassName = 'label';\r\n private static DotLabelClassSelector = '.label';\r\n private static DotLabelVerticalOffset = '0.4em';\r\n private static DotLabelTextAnchor = 'middle';\r\n\r\n private options: CartesianVisualInitOptions;\r\n\r\n // Chart properties\r\n private svg: D3.Selection;\r\n private element: JQuery;\r\n private mainGraphicsG: D3.Selection;\r\n private mainGraphicsContext: D3.Selection;\r\n private currentViewport: IViewport;\r\n private hostService: IVisualHostServices;\r\n private cartesianVisualHost: ICartesianVisualHost;\r\n private style: IVisualStyle;\r\n private colors: IDataColorPalette;\r\n private isScrollable: boolean;\r\n\r\n // Cartesian chart properties\r\n private xAxisProperties: IAxisProperties;\r\n private yAxisProperties: IAxisProperties;\r\n private margin;\r\n\r\n // Data properties\r\n private data: DataDotChartData;\r\n private dataViewCategorical: DataViewCategorical;\r\n private clippedData: DataDotChartData;\r\n\r\n // Interactivity properties\r\n private interactivityService: IInteractivityService;\r\n private interactivity: InteractivityOptions;\r\n\r\n constructor(options: DataDotChartConstructorOptions) {\r\n this.isScrollable = options.isScrollable;\r\n this.interactivityService = options.interactivityService;\r\n }\r\n\r\n public init(options: CartesianVisualInitOptions): void {\r\n this.options = options;\r\n\r\n // Common properties\r\n this.svg = options.svg;\r\n this.svg.classed(DataDotChart.ClassName, true);\r\n this.mainGraphicsG = this.svg.append('g')\r\n .classed('dataDotChartMainGraphicsContext', true);\r\n this.mainGraphicsContext = this.mainGraphicsG.append('svg');\r\n this.currentViewport = options.viewport;\r\n this.hostService = options.host;\r\n this.cartesianVisualHost = options.cartesianHost;\r\n this.style = options.style;\r\n this.colors = this.style.colorPalette.dataColors;\r\n\r\n // Interactivity properties\r\n this.interactivity = options.interactivity;\r\n\r\n this.element = options.element;\r\n }\r\n\r\n public setData(dataViews: DataView[]): void {\r\n this.data = {\r\n series: <DataDotChartSeries>{\r\n data: <DataDotChartDataPoint[]>[]\r\n },\r\n hasHighlights: false,\r\n hasDynamicSeries: false,\r\n };\r\n\r\n if (dataViews.length > 0) {\r\n\r\n // I only handle a single data view\r\n let dataView = dataViews[0];\r\n if (dataView && dataView.categorical) {\r\n\r\n let dataViewCategorical = this.dataViewCategorical = dataView.categorical;\r\n let dvCategories = dataViewCategorical.categories;\r\n\r\n // I default to text unless there is a category type\r\n let categoryType = ValueType.fromDescriptor({ text: true });\r\n if (dvCategories && dvCategories.length > 0 && dvCategories[0].source && dvCategories[0].source.type)\r\n categoryType = <ValueType>dvCategories[0].source.type;\r\n\r\n this.data = DataDotChart.converter(dataView, valueFormatter.format(null), this.interactivityService);\r\n }\r\n }\r\n }\r\n\r\n public setFilteredData(startIndex: number, endIndex: number): any {\r\n let data = this.clippedData = Prototype.inherit(this.data);\r\n\r\n if (data && data.series && data.series.data)\r\n data.series = { data: data.series.data.slice(startIndex, endIndex), xCol: data.series.xCol, yCol: data.series.yCol };\r\n\r\n return data;\r\n }\r\n\r\n public calculateAxesProperties(options: CalculateScaleAndDomainOptions): IAxisProperties[] {\r\n this.currentViewport = options.viewport;\r\n this.margin = options.margin;\r\n\r\n let data = this.clippedData = this.data;\r\n let viewport = this.currentViewport;\r\n let margin = this.margin;\r\n let series: DataDotChartSeries = data ? data.series : null;\r\n let seriesArray = series && series.data && series.data.length > 0 ? [series] : [];\r\n let categoryCount = series && series.data ? series.data.length : 0;\r\n\r\n // If there are highlights, then the series is 2x in length and highlights are interwoven.\r\n if (data.hasHighlights) {\r\n categoryCount = categoryCount / 2;\r\n }\r\n\r\n let width = viewport.width - (margin.left + margin.right);\r\n let height = viewport.height - (margin.top + margin.bottom);\r\n\r\n let xMetaDataColumn: DataViewMetadataColumn;\r\n let yMetaDataColumn: DataViewMetadataColumn;\r\n\r\n if (DataDotChart.hasDataPoint(series)) {\r\n xMetaDataColumn = series.xCol;\r\n yMetaDataColumn = series.yCol;\r\n }\r\n\r\n let layout = CartesianChart.getLayout(\r\n null,\r\n {\r\n availableWidth: width,\r\n categoryCount: categoryCount,\r\n domain: null,\r\n isScalar: false,\r\n isScrollable: this.isScrollable,\r\n trimOrdinalDataOnOverflow: options.trimOrdinalDataOnOverflow\r\n });\r\n let outerPadding = layout.categoryThickness * CartesianChart.OuterPaddingRatio;\r\n\r\n // clip data that won't fit\r\n if (!this.isScrollable) {\r\n this.clippedData = DataDotChart.createClippedDataIfOverflowed(data, layout.categoryCount);\r\n }\r\n\r\n let yDomain = AxisHelper.createValueDomain(seriesArray, /*includeZero:*/ true) || emptyDomain;\r\n\r\n let combinedDomain = AxisHelper.combineDomain(options.forcedYDomain, yDomain, options.ensureYDomain);\r\n\r\n this.yAxisProperties = AxisHelper.createAxis({\r\n pixelSpan: height,\r\n dataDomain: combinedDomain,\r\n metaDataColumn: yMetaDataColumn,\r\n formatString: valueFormatter.getFormatString(yMetaDataColumn, DataDotChart.formatStringProp),\r\n outerPadding: 0,\r\n isScalar: true,\r\n isVertical: true,\r\n forcedTickCount: options.forcedTickCount,\r\n useTickIntervalForDisplayUnits: true,\r\n isCategoryAxis: false\r\n });\r\n\r\n let axisType = this.xAxisProperties ? this.xAxisProperties.axisType : ValueType.fromDescriptor({ text: true });\r\n let xDomain = AxisHelper.createDomain(seriesArray, axisType, /*isScalar:*/ false, options.forcedXDomain, options.ensureXDomain);\r\n this.xAxisProperties = AxisHelper.createAxis({\r\n pixelSpan: width,\r\n dataDomain: xDomain,\r\n metaDataColumn: xMetaDataColumn,\r\n formatString: valueFormatter.getFormatString(xMetaDataColumn, DataDotChart.formatStringProp),\r\n outerPadding: outerPadding,\r\n isScalar: false,\r\n isVertical: false,\r\n forcedTickCount: options.forcedTickCount,\r\n useTickIntervalForDisplayUnits: true,\r\n categoryThickness: layout.categoryThickness,\r\n getValueFn: (index, type) => this.lookupXValue(index, type),\r\n isCategoryAxis: true\r\n });\r\n\r\n return [this.xAxisProperties, this.yAxisProperties];\r\n }\r\n\r\n private static createClippedDataIfOverflowed(data: DataDotChartData, categoryCount: number): DataDotChartData {\r\n\r\n // If there are highlights, then the series is 2x in length and highlights are interwoven.\r\n let requiredLength = data.hasHighlights ? Math.min(data.series.data.length, categoryCount * 2) : Math.min(data.series.data.length, categoryCount);\r\n\r\n if (requiredLength >= data.series.data.length) {\r\n return data;\r\n }\r\n\r\n let clipped: DataDotChartData = Prototype.inherit(data);\r\n clipped.series = Prototype.inherit(data.series); // This prevents clipped and data from sharing the series object\r\n clipped.series.data = clipped.series.data.slice(0, requiredLength);\r\n return clipped;\r\n }\r\n\r\n private static hasDataPoint(series: DataDotChartSeries): boolean {\r\n return (series && series.data && series.data.length > 0);\r\n }\r\n\r\n private lookupXValue(index: number, type: ValueType): any {\r\n let data = this.data;\r\n\r\n let isDateTime = AxisHelper.isDateTime(type);\r\n if (isDateTime)\r\n return new Date(index);\r\n\r\n if (data && data.series) {\r\n let seriesData = data.series.data;\r\n\r\n if (seriesData) {\r\n let dataAtIndex = seriesData[index];\r\n if (dataAtIndex) {\r\n return dataAtIndex.categoryValue;\r\n }\r\n }\r\n }\r\n\r\n return index;\r\n }\r\n\r\n public overrideXScale(xProperties: IAxisProperties): void {\r\n this.xAxisProperties = xProperties;\r\n }\r\n\r\n public render(suppressAnimations: boolean): CartesianVisualRenderResult {\r\n if (!this.clippedData)\r\n return;\r\n let data = this.clippedData;\r\n let dataPoints = data.series.data;\r\n let hasHighlights = data.hasHighlights;\r\n\r\n let margin = this.margin;\r\n let viewport = this.currentViewport;\r\n let width = viewport.width - (margin.left + margin.right);\r\n let height = viewport.height - (margin.top + margin.bottom);\r\n let xScale = <D3.Scale.OrdinalScale>this.xAxisProperties.scale;\r\n let yScale = this.yAxisProperties.scale;\r\n let dotWidth = this.xAxisProperties.categoryThickness * (1 - CartesianChart.InnerPaddingRatio);\r\n let dotRadius = dotWidth / 2;\r\n let dotColor = this.cartesianVisualHost.getSharedColors().getNewColorScale().getColor(DataDotChart.DotColorKey);\r\n\r\n let hasSelection = this.interactivityService ? this.interactivityService.hasSelection() : false;\r\n\r\n this.mainGraphicsContext.attr('width', width)\r\n .attr('height', height);\r\n\r\n let dots = this.mainGraphicsContext.selectAll(DataDotChart.DotClassSelector).data(dataPoints, d => d.identity.getKey());\r\n\r\n dots.enter()\r\n .append('circle')\r\n .classed(DataDotChart.DotClassName, true);\r\n\r\n dots\r\n .style({ 'fill': dotColor.value })\r\n .style('fill-opacity', (d: DataDotChartDataPoint) => ColumnUtil.getFillOpacity(d.selected, d.highlight, hasSelection, hasHighlights))\r\n .classed('null-value', (d: DataDotChartDataPoint) => d.value === null)\r\n .attr({\r\n r: (d: DataDotChartDataPoint) => dotRadius,\r\n cx: d => xScale(d.categoryIndex) + dotRadius,\r\n cy: d => yScale(d.value)\r\n });\r\n\r\n dots.exit().remove();\r\n\r\n let dotLabels = this.mainGraphicsContext.selectAll(DataDotChart.DotLabelClassSelector).data(dataPoints, d => d.identity.getKey());\r\n\r\n dotLabels.enter()\r\n .append('text')\r\n .classed(DataDotChart.DotLabelClassName, true)\r\n .attr({\r\n 'text-anchor': DataDotChart.DotLabelTextAnchor,\r\n dy: DataDotChart.DotLabelVerticalOffset\r\n });\r\n\r\n dotLabels\r\n .classed('null-value', (d: DataDotChartDataPoint) => d.value === null)\r\n .classed('overflowed', false)\r\n .attr({\r\n x: d => xScale(d.categoryIndex) + dotRadius,\r\n y: d => yScale(d.value)\r\n })\r\n .text(d => this.yAxisProperties.formatter.format(d.value));\r\n\r\n let overflowed = false;\r\n dotLabels\r\n .each(function () {\r\n // jQuery fails to properly inspect SVG class elements, the $('<div>') notation works around it.\r\n if (!overflowed && !$(\"<div>\").addClass($(this).attr(\"class\")).hasClass(\"null-value\")) {\r\n let width = TextMeasurementService.measureSvgTextElementWidth(this);\r\n if (width > dotWidth) {\r\n dotLabels.classed('overflowed', true);\r\n overflowed = true;\r\n }\r\n }\r\n });\r\n\r\n dotLabels.exit().remove();\r\n let behaviorOptions: DataDotChartBehaviorOptions = undefined;\r\n if (this.interactivityService) {\r\n behaviorOptions = {\r\n dots: dots,\r\n dotLabels: dotLabels,\r\n datapoints: dataPoints\r\n };\r\n } \r\n\r\n // This should always be the last line in the render code.\r\n SVGUtil.flushAllD3TransitionsIfNeeded(this.options);\r\n\r\n return { dataPoints: dataPoints, behaviorOptions: behaviorOptions, labelDataPoints: [], labelsAreNumeric: true };\r\n }\r\n\r\n public calculateLegend(): LegendData {\r\n return this.createLegendDataPoints(0); // start with index 0\r\n }\r\n\r\n public hasLegend(): boolean {\r\n return this.data && this.data.hasDynamicSeries;\r\n }\r\n\r\n private createLegendDataPoints(columnIndex: number): LegendData {\r\n let data = this.data;\r\n if (!data)\r\n return null;\r\n\r\n let series = data.series;\r\n let seriesData = series.data;\r\n\r\n let legendDataPoints: LegendDataPoint[] = [];\r\n let category: any;\r\n\r\n let axisType = this.xAxisProperties ? this.xAxisProperties.axisType : ValueType.fromDescriptor({ text: true });\r\n\r\n // Category will be the same for all series. This is an optimization.\r\n if (data.series && data.series.data) {\r\n let firstDataPoint: DataDotChartDataPoint = data.series.data[0];\r\n category = firstDataPoint && this.lookupXValue(firstDataPoint.categoryValue, axisType);\r\n }\r\n\r\n // Create a legend data point for the specified column \r\n if (series.yCol) {\r\n\r\n let formatStringProp = DataDotChart.formatStringProp;\r\n let lineDataPoint = seriesData[columnIndex];\r\n let measure = lineDataPoint && lineDataPoint.value;\r\n\r\n let label = converterHelper.getFormattedLegendLabel(series.yCol, this.dataViewCategorical.values, formatStringProp);\r\n\r\n let dotColor = this.cartesianVisualHost.getSharedColors().getNewColorScale().getColor(DataDotChart.DotColorKey);\r\n let dataViewCategoricalValues = this.dataViewCategorical.values;\r\n let identity = dataViewCategoricalValues && dataViewCategoricalValues.length > columnIndex ?\r\n SelectionId.createWithIdAndMeasure(dataViewCategoricalValues[columnIndex].identity, dataViewCategoricalValues[columnIndex].source.queryName) :\r\n SelectionId.createWithMeasure(dataViewCategoricalValues.source.queryName);\r\n legendDataPoints.push({\r\n color: dotColor.value,\r\n icon: LegendIcon.Line,\r\n label: label,\r\n category: valueFormatter.format(category, valueFormatter.getFormatString(series.xCol, formatStringProp)),\r\n measure: valueFormatter.format(measure, valueFormatter.getFormatString(series.yCol, formatStringProp)),\r\n identity: identity,\r\n selected: false\r\n });\r\n }\r\n\r\n return { dataPoints: legendDataPoints };\r\n }\r\n\r\n public onClearSelection(): void {\r\n if (this.interactivityService)\r\n this.interactivityService.clearSelection();\r\n\r\n // cartesianChart handles calling render again.\r\n }\r\n\r\n public static converter(dataView: DataView, blankCategoryValue: string, interactivityService: IInteractivityService): DataDotChartData {\r\n let categorical = dataView.categorical;\r\n\r\n let category: DataViewCategoryColumn = categorical.categories && categorical.categories.length > 0\r\n ? categorical.categories[0]\r\n : {\r\n source: undefined,\r\n values: [blankCategoryValue],\r\n identity: undefined\r\n };\r\n\r\n let categoryType: ValueTypeDescriptor = AxisHelper.getCategoryValueType(category.source);\r\n let isDateTime = AxisHelper.isDateTime(categoryType);\r\n let categoryValues = category.values;\r\n\r\n // I only handle a single series\r\n if (!_.isEmpty(categorical.values)) {\r\n let measure = categorical.values[0];\r\n\r\n let hasHighlights: boolean = !!measure.highlights;\r\n\r\n let dataPoints: DataDotChartDataPoint[] = [];\r\n for (let categoryIndex = 0, len = measure.values.length; categoryIndex < len; categoryIndex++) {\r\n\r\n debug.assert(!category.identity || categoryIndex < category.identity.length, 'Category identities is smaller than category values.');\r\n\r\n // I create the identity from the category. If there is no category, then I use the measure name to create identity\r\n let identity = category.identity ?\r\n SelectionId.createWithIdAndMeasure(category.identity[categoryIndex], measure.source.queryName) :\r\n SelectionId.createWithMeasure(measure.source.queryName);\r\n\r\n let categoryValue = categoryValues[categoryIndex];\r\n\r\n // ignore variant measures\r\n if (isDateTime && categoryValue != null && !(categoryValue instanceof Date))\r\n continue;\r\n\r\n dataPoints.push({\r\n categoryValue: isDateTime && categoryValue ? categoryValue.getTime() : categoryValue,\r\n value: measure.values[categoryIndex],\r\n categoryIndex: categoryIndex,\r\n seriesIndex: 0,\r\n selected: false,\r\n identity: identity,\r\n highlight: false\r\n });\r\n\r\n if (hasHighlights) {\r\n\r\n let highlightIdentity = SelectionId.createWithHighlight(identity);\r\n let highlightValue = measure.highlights[categoryIndex];\r\n\r\n dataPoints.push({\r\n categoryValue: isDateTime && categoryValue ? categoryValue.getTime() : categoryValue,\r\n value: highlightValue,\r\n categoryIndex: categoryIndex,\r\n seriesIndex: 0,\r\n selected: false,\r\n identity: highlightIdentity,\r\n highlight: true\r\n });\r\n }\r\n }\r\n\r\n if (interactivityService)\r\n interactivityService.applySelectionStateToData(dataPoints);\r\n\r\n return {\r\n series: {\r\n xCol: category.source,\r\n yCol: measure.source,\r\n data: dataPoints\r\n },\r\n hasHighlights: hasHighlights,\r\n hasDynamicSeries: true,\r\n };\r\n }\r\n\r\n return {\r\n series: <DataDotChartSeries> {\r\n data: <DataDotChartDataPoint[]>[]\r\n },\r\n hasHighlights: false,\r\n hasDynamicSeries: false,\r\n };\r\n }\r\n }\r\n} \n\n\n/** WEBPACK FOOTER **\n ** ./src/Clients/Visuals/cartesian/dataDotChart.ts\n **/","/*\r\n * Power BI Visualizations\r\n *\r\n * Copyright (c) Microsoft Corporation\r\n * All rights reserved. \r\n * MIT License\r\n *\r\n * Permission is hereby granted, free of charge, to any person obtaining a copy\r\n * of this software and associated documentation files (the \"\"Software\"\"), to deal\r\n * in the Software without restriction, including without limitation the rights\r\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\r\n * copies of the Software, and to permit persons to whom the Software is\r\n * furnished to do so, subject to the following conditions:\r\n * \r\n * The above copyright notice and this permission notice shall be included in \r\n * all copies or substantial portions of the Software.\r\n * \r\n * THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR \r\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, \r\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE \r\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER \r\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\r\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\r\n * THE SOFTWARE.\r\n */\r\n\r\n/// <reference path=\"../_references.ts\"/>\r\n\r\nmodule powerbi.visuals {\r\n import ClassAndSelector = jsCommon.CssConstants.ClassAndSelector;\r\n import createClassAndSelector = jsCommon.CssConstants.createClassAndSelector;\r\n import PixelConverter = jsCommon.PixelConverter;\r\n import LabelUtils = NewDataLabelUtils;\r\n\r\n export interface FunnelChartConstructorOptions {\r\n animator?: IFunnelAnimator;\r\n funnelSmallViewPortProperties?: FunnelSmallViewPortProperties;\r\n behavior?: FunnelWebBehavior;\r\n tooltipsEnabled?: boolean;\r\n tooltipBucketEnabled?: boolean;\r\n }\r\n\r\n export interface FunnelPercent {\r\n value: number;\r\n percent: number;\r\n isTop: boolean;\r\n }\r\n\r\n /**\r\n * value and highlightValue may be modified in the converter to\r\n * allow rendering non-standard values, such as negatives.\r\n * Store the original values for non-rendering, user-facing elements\r\n * e.g. data labels\r\n */\r\n export interface FunnelDataPoint extends SelectableDataPoint, TooltipEnabledDataPoint, LabelEnabledDataPoint {\r\n value: number;\r\n originalValue: number;\r\n label: string;\r\n key: string;\r\n categoryOrMeasureIndex: number;\r\n highlight?: boolean;\r\n highlightValue?: number;\r\n originalHighlightValue?: number;\r\n color: string;\r\n }\r\n\r\n export interface FunnelData {\r\n dataPoints: FunnelDataPoint[];\r\n categoryLabels: string[];\r\n valuesMetadata: DataViewMetadataColumn[];\r\n hasHighlights: boolean;\r\n highlightsOverflow: boolean;\r\n dataLabelsSettings: VisualDataLabelsSettings;\r\n percentBarLabelSettings: VisualDataLabelsSettings;\r\n canShowDataLabels: boolean;\r\n hasNegativeValues: boolean;\r\n allValuesAreNegative: boolean;\r\n }\r\n\r\n export interface FunnelAxisOptions {\r\n maxScore: number;\r\n valueScale: D3.Scale.LinearScale;\r\n categoryScale: D3.Scale.OrdinalScale;\r\n maxWidth: number;\r\n margin: IMargin;\r\n rangeStart: number;\r\n rangeEnd: number;\r\n barToSpaceRatio: number;\r\n categoryLabels: string[];\r\n }\r\n \r\n export interface IFunnelRect {\r\n width: (d: FunnelDataPoint) => number;\r\n x: (d: FunnelDataPoint) => number;\r\n y: (d: FunnelDataPoint) => number;\r\n height: (d: FunnelDataPoint) => number;\r\n }\r\n\r\n export interface IFunnelLayout {\r\n percentBarLayout: {\r\n mainLine: {\r\n x2: (d: FunnelPercent) => number;\r\n transform: (d: FunnelPercent) => string;\r\n },\r\n leftTick: {\r\n y2: (d: FunnelPercent) => number;\r\n transform: (d: FunnelPercent) => string;\r\n },\r\n rightTick: {\r\n y2: (d: FunnelPercent) => number;\r\n transform: (d: FunnelPercent) => string;\r\n },\r\n text: {\r\n x: (d: FunnelPercent) => number;\r\n y: (d: FunnelPercent) => number;\r\n style: () => string;\r\n transform: (d: FunnelPercent) => string;\r\n fill: string;\r\n maxWidth: number,\r\n },\r\n };\r\n shapeLayout: IFunnelRect;\r\n shapeLayoutWithoutHighlights: IFunnelRect;\r\n zeroShapeLayout: IFunnelRect;\r\n interactorLayout: IFunnelRect;\r\n }\r\n\r\n export interface IFunnelChartSelectors {\r\n funnel: {\r\n bars: ClassAndSelector;\r\n highlights: ClassAndSelector;\r\n interactors: ClassAndSelector;\r\n };\r\n percentBar: {\r\n root: ClassAndSelector;\r\n mainLine: ClassAndSelector;\r\n leftTick: ClassAndSelector;\r\n rightTick: ClassAndSelector;\r\n text: ClassAndSelector;\r\n };\r\n }\r\n\r\n export interface FunnelSmallViewPortProperties {\r\n hideFunnelCategoryLabelsOnSmallViewPort: boolean;\r\n minHeightFunnelCategoryLabelsVisible: number;\r\n }\r\n\r\n /**\r\n * Renders a funnel chart.\r\n */\r\n export class FunnelChart implements IVisual {\r\n private static LabelInsidePosition = [powerbi.RectLabelPosition.InsideCenter, powerbi.RectLabelPosition.OutsideEnd];\r\n private static LabelOutsidePosition = [powerbi.RectLabelPosition.OutsideEnd, powerbi.RectLabelPosition.InsideEnd];\r\n private static LabelOrientation = NewRectOrientation.HorizontalLeftBased;\r\n \r\n public static DefaultBarOpacity = 1;\r\n public static DimmedBarOpacity = 0.4;\r\n public static PercentBarToBarRatio = 0.75;\r\n public static TickPadding = 0;\r\n public static InnerTickSize = 0;\r\n public static MinimumInteractorSize = 15;\r\n public static InnerTextClassName = 'labelSeries';\r\n public static Selectors: IFunnelChartSelectors = {\r\n funnel: {\r\n bars: createClassAndSelector('funnelBar'),\r\n highlights: createClassAndSelector('highlight'),\r\n interactors: createClassAndSelector('funnelBarInteractor'),\r\n },\r\n percentBar: {\r\n root: createClassAndSelector('percentBars'),\r\n mainLine: createClassAndSelector('mainLine'),\r\n leftTick: createClassAndSelector('leftTick'),\r\n rightTick: createClassAndSelector('rightTick'),\r\n text: createClassAndSelector('value'),\r\n },\r\n };\r\n public static FunnelBarHighlightClass = [FunnelChart.Selectors.funnel.bars.class, FunnelChart.Selectors.funnel.highlights.class].join(' ');\r\n public static YAxisPadding = 10;\r\n\r\n private static VisualClassName = 'funnelChart';\r\n private static DefaultFontFamily = Font.Family.regularSecondary.css;\r\n private static BarToSpaceRatio = 0.1;\r\n private static MaxBarHeight = 40;\r\n private static MinBarThickness = 12;\r\n private static LabelFunnelPadding = 6;\r\n private static OverflowingHighlightWidthRatio = 0.5;\r\n private static MaxMarginFactor = 0.25;\r\n\r\n private svg: D3.Selection;\r\n private funnelGraphicsContext: D3.Selection;\r\n private percentGraphicsContext: D3.Selection;\r\n private clearCatcher: D3.Selection;\r\n private axisGraphicsContext: D3.Selection;\r\n private labelGraphicsContext: D3.Selection;\r\n private currentViewport: IViewport;\r\n private colors: IDataColorPalette;\r\n private data: FunnelData;\r\n private hostServices: IVisualHostServices;\r\n private margin: IMargin;\r\n private options: VisualInitOptions;\r\n private interactivityService: IInteractivityService;\r\n private behavior: FunnelWebBehavior;\r\n private defaultDataPointColor: string;\r\n private labelPositionObjects: string[] = [labelPosition.outsideEnd, labelPosition.insideCenter];\r\n // TODO: Remove onDataChanged & onResizing once all visuals have implemented update.\r\n private dataViews: DataView[];\r\n private funnelSmallViewPortProperties: FunnelSmallViewPortProperties;\r\n private tooltipsEnabled: boolean;\r\n private tooltipBucketEnabled: boolean;\r\n\r\n /**\r\n * Note: Public for testing.\r\n */\r\n public animator: IFunnelAnimator;\r\n\r\n constructor(options?: FunnelChartConstructorOptions) {\r\n if (options) {\r\n this.tooltipsEnabled = options.tooltipsEnabled;\r\n this.tooltipBucketEnabled = options.tooltipBucketEnabled;\r\n if (options.funnelSmallViewPortProperties) {\r\n this.funnelSmallViewPortProperties = options.funnelSmallViewPortProperties;\r\n }\r\n if (options.animator) {\r\n this.animator = options.animator;\r\n }\r\n if (options.behavior) {\r\n this.behavior = options.behavior;\r\n }\r\n }\r\n }\r\n\r\n public static converter(dataView: DataView, colors: IDataColorPalette, hostServices: IVisualHostServices, defaultDataPointColor?: string, tooltipsEnabled: boolean = true, tooltipBucketEnabled?: boolean): FunnelData {\r\n let reader = data.createIDataViewCategoricalReader(dataView);\r\n let dataPoints: FunnelDataPoint[] = [];\r\n let formatStringProp = funnelChartProps.general.formatString;\r\n let categorical: DataViewCategorical = dataView.categorical;\r\n let hasHighlights = reader.hasHighlights(\"Y\");\r\n let valueMetaData: DataViewMetadataColumn[] = [];\r\n for (let seriesIndex = 0, seriesCount = reader.getSeriesCount(\"Y\"); seriesIndex < seriesCount; seriesIndex++) {\r\n valueMetaData.push(reader.getValueMetadataColumn(\"Y\", seriesIndex));\r\n }\r\n let highlightsOverflow = false;\r\n let hasNegativeValues = false;\r\n let allValuesAreNegative = false;\r\n let categoryLabels = [];\r\n let dataLabelsSettings: VisualDataLabelsSettings = this.getDefaultLabelSettings();\r\n let percentBarLabelSettings: VisualDataLabelsSettings = this.getDefaultPercentLabelSettings();\r\n let colorHelper = new ColorHelper(colors, funnelChartProps.dataPoint.fill, defaultDataPointColor);\r\n let firstValue: number;\r\n let firstHighlight: number;\r\n let previousValue: number;\r\n let previousHighlight: number;\r\n let gradientValueColumn: DataViewValueColumn = GradientUtils.getGradientValueColumn(categorical);\r\n\r\n if (dataView && dataView.metadata && dataView.metadata.objects) {\r\n let labelsObj = <DataLabelObject>dataView.metadata.objects['labels'];\r\n if (labelsObj)\r\n dataLabelUtils.updateLabelSettingsFromLabelsObject(labelsObj, dataLabelsSettings);\r\n\r\n let percentLabelsObj = <DataLabelObject>dataView.metadata.objects['percentBarLabel'];\r\n if (percentLabelsObj)\r\n dataLabelUtils.updateLabelSettingsFromLabelsObject(percentLabelsObj, percentBarLabelSettings);\r\n }\r\n\r\n // If we don't have a valid value column, just return\r\n if (!reader.hasValues(\"Y\"))\r\n return {\r\n dataPoints: dataPoints,\r\n categoryLabels: categoryLabels,\r\n valuesMetadata: valueMetaData,\r\n hasHighlights: hasHighlights,\r\n highlightsOverflow: highlightsOverflow,\r\n canShowDataLabels: true,\r\n dataLabelsSettings: dataLabelsSettings,\r\n hasNegativeValues: hasNegativeValues,\r\n allValuesAreNegative: allValuesAreNegative,\r\n percentBarLabelSettings: percentBarLabelSettings,\r\n };\r\n\r\n // Calculate the first value for percent tooltip values\r\n firstValue = reader.getValue(\"Y\", 0, 0);\r\n if (hasHighlights) {\r\n firstHighlight = reader.getHighlight(\"Y\", 0, 0);\r\n }\r\n let pctFormatString = valueFormatter.getLocalizedString('Percentage');\r\n\r\n if (reader.hasCategories()) {\r\n // Funnel chart with categories\r\n for (let categoryIndex = 0, categoryCount = reader.getCategoryCount(); categoryIndex < categoryCount; categoryIndex++) {\r\n let categoryColumn = reader.getCategoryColumn(\"Category\");\r\n let categoryValue = reader.getCategoryValue(\"Category\", categoryIndex);\r\n let valueMetadataColumn = reader.getValueMetadataColumn(\"Y\");\r\n\r\n let identity = SelectionIdBuilder.builder()\r\n .withCategory(categoryColumn, categoryIndex)\r\n .withMeasure(valueMetadataColumn.queryName)\r\n .createSelectionId();\r\n\r\n let value = reader.getValue(\"Y\", categoryIndex);\r\n let formattedCategoryValue = converterHelper.formatFromMetadataColumn(categoryValue, categoryColumn.source, formatStringProp);\r\n\r\n let tooltipInfo: TooltipDataItem[];\r\n if (tooltipsEnabled) {\r\n tooltipInfo = [];\r\n \r\n tooltipInfo.push({\r\n displayName: categoryColumn.source.displayName,\r\n value: formattedCategoryValue,\r\n });\r\n\r\n if (value != null) {\r\n tooltipInfo.push({\r\n displayName: valueMetadataColumn.displayName,\r\n value: converterHelper.formatFromMetadataColumn(value, valueMetadataColumn, formatStringProp),\r\n });\r\n }\r\n\r\n let highlightValue: number;\r\n if (hasHighlights) {\r\n highlightValue = reader.getHighlight(\"Y\", categoryIndex);\r\n if (highlightValue != null) {\r\n tooltipInfo.push({\r\n displayName: ToolTipComponent.localizationOptions.highlightedValueDisplayName,\r\n value: converterHelper.formatFromMetadataColumn(highlightValue, valueMetadataColumn, formatStringProp),\r\n });\r\n }\r\n }\r\n\r\n let gradientColumnMetadata = gradientValueColumn ? gradientValueColumn.source : undefined;\r\n if (gradientColumnMetadata && gradientColumnMetadata !== valueMetadataColumn && gradientValueColumn.values[categoryIndex] != null) {\r\n tooltipInfo.push({\r\n displayName: gradientColumnMetadata.displayName,\r\n value: converterHelper.formatFromMetadataColumn(reader.getValue(\"Gradient\", categoryIndex), gradientColumnMetadata, formatStringProp),\r\n });\r\n }\r\n\r\n if (hasHighlights) {\r\n FunnelChart.addFunnelPercentsToTooltip(pctFormatString, tooltipInfo, hostServices, firstHighlight ? highlightValue / firstHighlight : null, previousHighlight ? highlightValue / previousHighlight : null, true);\r\n }\r\n else {\r\n FunnelChart.addFunnelPercentsToTooltip(pctFormatString, tooltipInfo, hostServices, firstValue ? value / firstValue : null, previousValue ? value / previousValue : null);\r\n } \r\n\r\n if (tooltipBucketEnabled) {\r\n TooltipBuilder.addTooltipBucketItem(reader, tooltipInfo, categoryIndex);\r\n } \r\n }\r\n \r\n // Same color for all bars\r\n let color = colorHelper.getColorForMeasure(reader.getCategoryObjects(\"Category\", categoryIndex), '');\r\n\r\n dataPoints.push({\r\n label: formattedCategoryValue,\r\n value: value,\r\n originalValue: value,\r\n categoryOrMeasureIndex: categoryIndex,\r\n identity: identity,\r\n selected: false,\r\n key: identity.getKey(),\r\n tooltipInfo: tooltipInfo,\r\n color: color,\r\n labelFill: dataLabelsSettings.labelColor,\r\n });\r\n\r\n if (hasHighlights) {\r\n let highlightIdentity = SelectionId.createWithHighlight(identity);\r\n let highlightValue = reader.getHighlight(\"Y\", categoryIndex);\r\n dataPoints.push({\r\n label: formattedCategoryValue,\r\n value: value,\r\n originalValue: value,\r\n categoryOrMeasureIndex: categoryIndex,\r\n identity: highlightIdentity,\r\n selected: false,\r\n key: highlightIdentity.getKey(),\r\n highlight: true,\r\n highlightValue: highlightValue,\r\n originalHighlightValue: highlightValue,\r\n tooltipInfo: tooltipInfo,\r\n color: color,\r\n });\r\n previousHighlight = highlightValue;\r\n }\r\n previousValue = value;\r\n }\r\n }\r\n else {\r\n // Non-categorical static series\r\n let categoryIndex = 0; // For non-categorical data, we use categoryIndex = 0\r\n for (let seriesIndex = 0, seriesCount = reader.getSeriesCount(\"Y\"); seriesIndex < seriesCount; seriesIndex++) {\r\n let value = reader.getValue(\"Y\", categoryIndex, seriesIndex);\r\n let valueMetadataColumn = reader.getValueMetadataColumn(\"Y\", seriesIndex);\r\n let identity = SelectionId.createWithMeasure(valueMetadataColumn.queryName);\r\n\r\n let tooltipInfo: TooltipDataItem[];\r\n // Same color for all bars\r\n let color = colorHelper.getColorForMeasure(valueMetadataColumn.objects, '');\r\n\r\n if (tooltipsEnabled) {\r\n tooltipInfo = [];\r\n\r\n if (value != null) {\r\n tooltipInfo.push({\r\n displayName: valueMetadataColumn.displayName,\r\n value: converterHelper.formatFromMetadataColumn(value, valueMetadataColumn, formatStringProp),\r\n });\r\n }\r\n\r\n if (hasHighlights) {\r\n let highlightValue = reader.getHighlight(\"Y\", categoryIndex, seriesIndex);\r\n if (highlightValue != null) {\r\n tooltipInfo.push({\r\n displayName: ToolTipComponent.localizationOptions.highlightedValueDisplayName,\r\n value: converterHelper.formatFromMetadataColumn(highlightValue, valueMetadataColumn, formatStringProp),\r\n });\r\n }\r\n FunnelChart.addFunnelPercentsToTooltip(pctFormatString, tooltipInfo, hostServices, firstHighlight ? highlightValue / firstHighlight : null, previousHighlight ? highlightValue / previousHighlight : null, true);\r\n }\r\n else {\r\n FunnelChart.addFunnelPercentsToTooltip(pctFormatString, tooltipInfo, hostServices, firstValue ? value / firstValue : null, previousValue ? value / previousValue : null);\r\n }\r\n\r\n if (tooltipBucketEnabled) {\r\n let tooltipValues = reader.getAllValuesForRole(\"Tooltips\", categoryIndex, undefined);\r\n let tooltipMetadataColumns = reader.getAllValueMetadataColumnsForRole(\"Tooltips\", undefined);\r\n\r\n if (tooltipValues && tooltipMetadataColumns) {\r\n for (let j = 0; j < tooltipValues.length; j++) {\r\n if (tooltipValues[j] != null) {\r\n tooltipInfo.push({\r\n displayName: tooltipMetadataColumns[j].displayName,\r\n value: converterHelper.formatFromMetadataColumn(tooltipValues[j], tooltipMetadataColumns[j], formatStringProp),\r\n });\r\n }\r\n }\r\n }\r\n }\r\n }\r\n \r\n dataPoints.push({\r\n label: valueMetadataColumn.displayName,\r\n value: value,\r\n originalValue: value,\r\n categoryOrMeasureIndex: seriesIndex,\r\n identity: identity,\r\n selected: false,\r\n key: identity.getKey(),\r\n tooltipInfo: tooltipInfo,\r\n color: color,\r\n labelFill: dataLabelsSettings.labelColor,\r\n });\r\n if (hasHighlights) {\r\n let highlightIdentity = SelectionId.createWithHighlight(identity);\r\n let highlight = reader.getHighlight(\"Y\", categoryIndex, seriesIndex);\r\n dataPoints.push({\r\n label: valueMetadataColumn.displayName,\r\n value: value,\r\n originalValue: value,\r\n categoryOrMeasureIndex: seriesIndex,\r\n identity: highlightIdentity,\r\n key: highlightIdentity.getKey(),\r\n selected: false,\r\n highlight: true,\r\n originalHighlightValue: highlight,\r\n highlightValue: highlight,\r\n tooltipInfo: tooltipInfo,\r\n color: color,\r\n });\r\n previousHighlight = highlight;\r\n }\r\n previousValue = value;\r\n }\r\n }\r\n\r\n for (let i = 0; i < dataPoints.length; i += hasHighlights ? 2 : 1) {\r\n let dataPoint = dataPoints[i];\r\n categoryLabels.push(dataPoint.label);\r\n }\r\n\r\n // Calculate negative value warning flags\r\n allValuesAreNegative = dataPoints.length > 0 && _.every(dataPoints, (dataPoint: FunnelDataPoint) => (dataPoint.highlight ? dataPoint.highlightValue <= 0 : true) && dataPoint.value < 0);\r\n for (let dataPoint of dataPoints) {\r\n if (allValuesAreNegative) {\r\n dataPoint.value = Math.abs(dataPoint.value);\r\n if (dataPoint.highlight)\r\n dataPoint.highlightValue = Math.abs(dataPoint.highlightValue);\r\n }\r\n else {\r\n let value = dataPoint.value;\r\n let isValueNegative = value < 0;\r\n if (isValueNegative)\r\n dataPoint.value = 0;\r\n\r\n let isHighlightValueNegative = false;\r\n if (dataPoint.highlight) {\r\n let highlightValue = dataPoint.highlightValue;\r\n isHighlightValueNegative = highlightValue < 0;\r\n dataPoint.highlightValue = isHighlightValueNegative ? 0 : highlightValue;\r\n }\r\n\r\n if (!hasNegativeValues)\r\n hasNegativeValues = isValueNegative || isHighlightValueNegative;\r\n }\r\n\r\n if (dataPoint.highlightValue > dataPoint.value) {\r\n highlightsOverflow = true;\r\n }\r\n }\r\n\r\n return {\r\n dataPoints: dataPoints,\r\n categoryLabels: categoryLabels,\r\n valuesMetadata: valueMetaData,\r\n hasHighlights: hasHighlights,\r\n highlightsOverflow: highlightsOverflow,\r\n canShowDataLabels: true,\r\n dataLabelsSettings: dataLabelsSettings,\r\n hasNegativeValues: hasNegativeValues,\r\n allValuesAreNegative: allValuesAreNegative,\r\n percentBarLabelSettings: percentBarLabelSettings,\r\n };\r\n }\r\n\r\n public enumerateObjectInstances(options: EnumerateVisualObjectInstancesOptions): VisualObjectInstanceEnumeration {\r\n let enumeration = new ObjectEnumerationBuilder();\r\n\r\n switch (options.objectName) {\r\n case 'dataPoint':\r\n let dataViewCat: DataViewCategorical = this.dataViews && this.dataViews.length > 0 && this.dataViews[0] && this.dataViews[0].categorical;\r\n let hasGradientRole = GradientUtils.hasGradientRole(dataViewCat);\r\n if (!hasGradientRole) {\r\n this.enumerateDataPoints(enumeration);\r\n }\r\n break;\r\n case 'labels':\r\n let labelSettingsOptions = FunnelChart.getLabelSettingsOptions(enumeration, this.data.dataLabelsSettings, true, this.labelPositionObjects);\r\n dataLabelUtils.enumerateDataLabels(labelSettingsOptions);\r\n break;\r\n case 'percentBarLabel':\r\n let percentLabelSettingOptions = FunnelChart.getLabelSettingsOptions(enumeration, this.data.percentBarLabelSettings, false);\r\n dataLabelUtils.enumerateDataLabels(percentLabelSettingOptions);\r\n break;\r\n }\r\n\r\n return enumeration.complete();\r\n }\r\n\r\n private static getLabelSettingsOptions(enumeration: ObjectEnumerationBuilder, labelSettings: VisualDataLabelsSettings, isDataLabels: boolean, positionObject?: any): VisualDataLabelsSettingsOptions {\r\n return {\r\n enumeration: enumeration,\r\n dataLabelsSettings: labelSettings,\r\n show: true,\r\n displayUnits: isDataLabels,\r\n precision: isDataLabels,\r\n position: isDataLabels,\r\n positionObject: positionObject,\r\n fontSize: true,\r\n };\r\n }\r\n\r\n private enumerateDataPoints(enumeration: ObjectEnumerationBuilder): void {\r\n let data = this.data;\r\n if (!data)\r\n return;\r\n\r\n let dataPoints = data.dataPoints;\r\n\r\n enumeration.pushInstance({\r\n objectName: 'dataPoint',\r\n selector: null,\r\n properties: {\r\n defaultColor: { solid: { color: this.defaultDataPointColor || this.colors.getColorByIndex(0).value } }\r\n },\r\n });\r\n\r\n for (let i = 0; i < dataPoints.length; i++) {\r\n let dataPont = dataPoints[i];\r\n if (dataPont.highlight)\r\n continue;\r\n\r\n let color = dataPont.color;\r\n let selector = dataPont.identity.getSelector();\r\n let isSingleSeries = !!selector.data;\r\n\r\n enumeration.pushInstance({\r\n objectName: 'dataPoint',\r\n displayName: dataPont.label,\r\n selector: ColorHelper.normalizeSelector(selector, isSingleSeries),\r\n properties: {\r\n fill: { solid: { color: color } }\r\n },\r\n });\r\n }\r\n }\r\n\r\n public init(options: VisualInitOptions) {\r\n this.options = options;\r\n let element = options.element;\r\n let svg = this.svg = d3.select(element.get(0))\r\n .append('svg')\r\n .classed(FunnelChart.VisualClassName, true);\r\n\r\n if (this.behavior)\r\n this.clearCatcher = appendClearCatcher(this.svg);\r\n\r\n this.currentViewport = options.viewport;\r\n this.margin = {\r\n left: 5,\r\n right: 5,\r\n top: 0,\r\n bottom: 0\r\n };\r\n let style = options.style;\r\n this.colors = style.colorPalette.dataColors;\r\n this.hostServices = options.host;\r\n if (this.behavior) {\r\n this.interactivityService = createInteractivityService(this.hostServices);\r\n }\r\n this.percentGraphicsContext = svg.append('g').classed(FunnelChart.Selectors.percentBar.root.class, true);\r\n this.funnelGraphicsContext = svg.append('g');\r\n this.axisGraphicsContext = svg.append('g');\r\n this.labelGraphicsContext = svg\r\n .append(\"g\")\r\n .classed(LabelUtils.labelGraphicsContextClass.class, true);\r\n\r\n this.updateViewportProperties();\r\n }\r\n\r\n private updateViewportProperties() {\r\n let viewport = this.currentViewport;\r\n this.svg.attr('width', viewport.width)\r\n .attr('height', viewport.height);\r\n }\r\n\r\n public update(options: VisualUpdateOptions): void {\r\n debug.assertValue(options, 'options');\r\n this.data = {\r\n dataPoints: [],\r\n categoryLabels: [],\r\n valuesMetadata: [],\r\n hasHighlights: false,\r\n highlightsOverflow: false,\r\n canShowDataLabels: true,\r\n dataLabelsSettings: dataLabelUtils.getDefaultLabelSettings(),\r\n hasNegativeValues: false,\r\n allValuesAreNegative: false,\r\n percentBarLabelSettings: dataLabelUtils.getDefaultLabelSettings(true),\r\n };\r\n\r\n let dataViews = this.dataViews = options.dataViews;\r\n this.currentViewport = options.viewport;\r\n\r\n if (dataViews && dataViews.length > 0) {\r\n let dataView = dataViews[0];\r\n\r\n if (dataView.metadata && dataView.metadata.objects) {\r\n let defaultColor = DataViewObjects.getFillColor(dataView.metadata.objects, funnelChartProps.dataPoint.defaultColor);\r\n if (defaultColor)\r\n this.defaultDataPointColor = defaultColor;\r\n }\r\n\r\n if (dataView.categorical) {\r\n this.data = FunnelChart.converter(dataView, this.colors, this.hostServices, this.defaultDataPointColor, this.tooltipsEnabled, this.tooltipBucketEnabled);\r\n\r\n if (this.interactivityService) {\r\n this.interactivityService.applySelectionStateToData(this.data.dataPoints);\r\n }\r\n }\r\n\r\n let warnings = getInvalidValueWarnings(\r\n dataViews,\r\n false /*supportsNaN*/,\r\n false /*supportsNegativeInfinity*/,\r\n false /*supportsPositiveInfinity*/);\r\n\r\n if (this.data.allValuesAreNegative) {\r\n warnings.push(new AllNegativeValuesWarning());\r\n }\r\n else if (this.data.hasNegativeValues) {\r\n warnings.push(new NegativeValuesNotSupportedWarning());\r\n }\r\n\r\n this.hostServices.setWarnings(warnings);\r\n }\r\n\r\n this.updateViewportProperties();\r\n this.updateInternal(options.suppressAnimations);\r\n }\r\n\r\n // TODO: Remove onDataChanged & onResizing once all visuals have implemented update.\r\n public onDataChanged(options: VisualDataChangedOptions): void {\r\n this.update({\r\n dataViews: options.dataViews,\r\n suppressAnimations: options.suppressAnimations,\r\n viewport: this.currentViewport\r\n });\r\n }\r\n\r\n // TODO: Remove onDataChanged & onResizing once all visuals have implemented update.\r\n public onResizing(viewport: IViewport): void {\r\n this.currentViewport = viewport;\r\n this.update({\r\n dataViews: this.dataViews,\r\n suppressAnimations: true,\r\n viewport: this.currentViewport\r\n });\r\n }\r\n\r\n private getMaxLabelLength(labels: string[], properties: TextProperties): number {\r\n let max = 0;\r\n let textMeasurer: ITextAsSVGMeasurer = TextMeasurementService.measureSvgTextWidth;\r\n \r\n for (let i = 0, len = labels.length; i < len; i++) {\r\n properties.text = labels[i];\r\n max = Math.max(max, textMeasurer(properties));\r\n }\r\n \r\n return max + FunnelChart.LabelFunnelPadding;\r\n }\r\n\r\n private updateInternal(suppressAnimations: boolean) {\r\n if (this.data == null)\r\n return;\r\n\r\n let data = this.data;\r\n let dataPoints = data.dataPoints;\r\n let dataPointsWithoutHighlights = dataPoints.filter((d: FunnelDataPoint) => !d.highlight);\r\n let isHidingPercentBars = this.isHidingPercentBars();\r\n\r\n let axisOptions = this.setUpAxis();\r\n let margin = axisOptions.margin;\r\n\r\n let funnelContext = this.funnelGraphicsContext.attr('transform',\r\n SVGUtil.translate(margin.left, margin.top));\r\n \r\n let labelContext = this.labelGraphicsContext.attr('transform',\r\n SVGUtil.translate(margin.left, margin.top)); \r\n\r\n this.percentGraphicsContext.attr('transform',\r\n SVGUtil.translate(margin.left, margin.top));\r\n\r\n this.svg.style('font-family', dataLabelUtils.StandardFontFamily);\r\n \r\n let layout = FunnelChart.getLayout(data, axisOptions);\r\n let labels: Label[] = this.getLabels(layout); \r\n let result: FunnelAnimationResult;\r\n let shapes: D3.UpdateSelection;\r\n\r\n if (this.animator && !suppressAnimations) {\r\n let animationOptions: FunnelAnimationOptions = {\r\n viewModel: data,\r\n interactivityService: this.interactivityService,\r\n layout: layout,\r\n axisGraphicsContext: this.axisGraphicsContext,\r\n shapeGraphicsContext: funnelContext,\r\n percentGraphicsContext: this.percentGraphicsContext,\r\n labelGraphicsContext: this.labelGraphicsContext,\r\n axisOptions: axisOptions,\r\n dataPointsWithoutHighlights: dataPointsWithoutHighlights,\r\n labelLayout: labels,\r\n isHidingPercentBars: isHidingPercentBars,\r\n visualInitOptions: this.options,\r\n };\r\n result = this.animator.animate(animationOptions);\r\n shapes = result.shapes;\r\n }\r\n if (!this.animator || suppressAnimations || result.failed) {\r\n FunnelChart.drawDefaultAxis(this.axisGraphicsContext, axisOptions, isHidingPercentBars);\r\n shapes = FunnelChart.drawDefaultShapes(data, dataPoints, funnelContext, layout, this.interactivityService && this.interactivityService.hasSelection());\r\n FunnelChart.drawPercentBars(data, this.percentGraphicsContext, layout, isHidingPercentBars);\r\n LabelUtils.drawDefaultLabels(labelContext, labels, false);\r\n }\r\n\r\n if (this.interactivityService) {\r\n let interactors: D3.UpdateSelection = FunnelChart.drawInteractorShapes(dataPoints, funnelContext, layout);\r\n let behaviorOptions: FunnelBehaviorOptions = {\r\n bars: shapes,\r\n interactors: interactors,\r\n clearCatcher: this.clearCatcher,\r\n hasHighlights: data.hasHighlights,\r\n };\r\n\r\n this.interactivityService.bind(dataPoints, this.behavior, behaviorOptions);\r\n\r\n if (this.tooltipsEnabled) {\r\n TooltipManager.addTooltip(interactors, (tooltipEvent: TooltipEvent) => tooltipEvent.data.tooltipInfo);\r\n }\r\n }\r\n if (this.tooltipsEnabled) {\r\n TooltipManager.addTooltip(shapes, (tooltipEvent: TooltipEvent) => tooltipEvent.data.tooltipInfo);\r\n }\r\n\r\n SVGUtil.flushAllD3TransitionsIfNeeded(this.options);\r\n }\r\n\r\n private getUsableVerticalSpace(): number {\r\n let categoryLabels = this.data.categoryLabels;\r\n let margin = this.margin;\r\n let verticalSpace = this.currentViewport.height - (margin.top + margin.bottom);\r\n return verticalSpace - (FunnelChart.MinBarThickness * categoryLabels.length);\r\n }\r\n\r\n private isHidingPercentBars(): boolean {\r\n let data = this.data;\r\n\r\n if (data.percentBarLabelSettings.show) {\r\n let percentBarTextHeight = this.getPercentBarTextHeight();\r\n let verticalSpace = this.getUsableVerticalSpace() - (2 * FunnelChart.MinBarThickness * FunnelChart.PercentBarToBarRatio) - (2 * percentBarTextHeight);\r\n return verticalSpace <= 0;\r\n }\r\n return true;\r\n }\r\n\r\n private isSparklines(): boolean {\r\n return this.getUsableVerticalSpace() <= 0;\r\n }\r\n\r\n private setUpAxis(): FunnelAxisOptions {\r\n let data = this.data;\r\n let dataPoints = data.dataPoints;\r\n let categoryLabels = data.categoryLabels;\r\n let viewport = this.currentViewport;\r\n let margin = this.margin;\r\n let isSparklines = this.isSparklines();\r\n let isHidingPercentBars = this.isHidingPercentBars();\r\n let percentBarTextHeight = isHidingPercentBars ? 0 : this.getPercentBarTextHeight();\r\n let verticalRange = viewport.height - (margin.top + margin.bottom) - (2 * percentBarTextHeight);\r\n let maxMarginFactor = FunnelChart.MaxMarginFactor;\r\n\r\n if (categoryLabels.length > 0 && isSparklines) {\r\n categoryLabels = [];\r\n data.canShowDataLabels = false;\r\n } else if (this.showCategoryLabels()) {\r\n let textProperties = FunnelChart.getTextProperties();\r\n // Get the amount of space needed for the labels, then add the minimum level of padding for the axis.\r\n let longestLabelLength = this.getMaxLabelLength(categoryLabels, textProperties);\r\n let maxLabelLength = viewport.width * maxMarginFactor;\r\n let labelLength = Math.min(longestLabelLength, maxLabelLength);\r\n margin.left = labelLength + FunnelChart.YAxisPadding;\r\n } else {\r\n categoryLabels = [];\r\n }\r\n\r\n let horizontalRange = viewport.width - (margin.left + margin.right);\r\n let barToSpaceRatio = FunnelChart.BarToSpaceRatio;\r\n let maxScore = d3.max(dataPoints.map(d => d.value));\r\n\r\n if (data.hasHighlights) {\r\n let maxHighlight = d3.max(dataPoints.map(d => d.highlightValue));\r\n maxScore = d3.max([maxScore, maxHighlight]);\r\n }\r\n\r\n let minScore = 0;\r\n let rangeStart = 0;\r\n let rangeEnd = verticalRange;\r\n\r\n let delta: number;\r\n if (isHidingPercentBars)\r\n delta = verticalRange - (categoryLabels.length * FunnelChart.MaxBarHeight);\r\n else\r\n delta = verticalRange - (categoryLabels.length * FunnelChart.MaxBarHeight) - (2 * FunnelChart.MaxBarHeight * FunnelChart.PercentBarToBarRatio);\r\n\r\n if (categoryLabels.length > 0 && delta > 0) {\r\n rangeStart = Math.ceil(delta / 2);\r\n rangeEnd = Math.ceil(verticalRange - delta / 2);\r\n }\r\n\r\n // Offset funnel axis start and end by percent bar text height\r\n if (!isHidingPercentBars) {\r\n rangeStart += percentBarTextHeight;\r\n rangeEnd += percentBarTextHeight;\r\n }\r\n\r\n let valueScale = d3.scale.linear()\r\n .domain([minScore, maxScore])\r\n .range([horizontalRange, 0]);\r\n let categoryScale = d3.scale.ordinal()\r\n .domain(d3.range(0, data.categoryLabels.length))\r\n .rangeBands([rangeStart, rangeEnd], barToSpaceRatio, isHidingPercentBars ? barToSpaceRatio : FunnelChart.PercentBarToBarRatio);\r\n\r\n return {\r\n margin: margin,\r\n valueScale: valueScale,\r\n categoryScale: categoryScale,\r\n maxScore: maxScore,\r\n maxWidth: horizontalRange,\r\n rangeStart: rangeStart,\r\n rangeEnd: rangeEnd,\r\n barToSpaceRatio: barToSpaceRatio,\r\n categoryLabels: categoryLabels,\r\n };\r\n }\r\n\r\n private getPercentBarTextHeight(): number {\r\n let percentBarTextProperties = FunnelChart.getTextProperties(this.data.percentBarLabelSettings.fontSize);\r\n return TextMeasurementService.estimateSvgTextHeight(percentBarTextProperties);\r\n }\r\n\r\n public onClearSelection(): void {\r\n if (this.interactivityService)\r\n this.interactivityService.clearSelection();\r\n }\r\n\r\n public static getLayout(data: FunnelData, axisOptions: FunnelAxisOptions): IFunnelLayout {\r\n let highlightsOverflow = data.highlightsOverflow;\r\n let categoryScale = axisOptions.categoryScale;\r\n let valueScale = axisOptions.valueScale;\r\n let maxScore = axisOptions.maxScore;\r\n let columnHeight = categoryScale.rangeBand();\r\n let percentBarTickHeight = Math.ceil(columnHeight / 2);\r\n let overFlowHighlightColumnWidth = columnHeight * FunnelChart.OverflowingHighlightWidthRatio;\r\n let overFlowHighlightOffset = overFlowHighlightColumnWidth / 2;\r\n let lastCategoryIndex = axisOptions.categoryLabels.length - 1;\r\n let horizontalDistance = Math.abs(valueScale(maxScore) - valueScale(0));\r\n let emptyHorizontalSpace = (value: number): number => (horizontalDistance - Math.abs(valueScale(value) - valueScale(0))) /2;\r\n let getMinimumShapeSize = (value: number): number => Math.max(FunnelChart.MinimumInteractorSize, Math.abs(valueScale(value) - valueScale(0)));\r\n let percentBarFontSize = PixelConverter.fromPoint(data.percentBarLabelSettings.fontSize);\r\n let percentBarTextProperties = FunnelChart.getTextProperties(data.percentBarLabelSettings.fontSize);\r\n let baselineDelta = TextMeasurementService.estimateSvgTextBaselineDelta(percentBarTextProperties);\r\n let percentBarYOffset = TextMeasurementService.estimateSvgTextHeight(percentBarTextProperties) - baselineDelta;\r\n\r\n return {\r\n percentBarLayout: {\r\n mainLine: {\r\n x2: (d: FunnelPercent) => Math.abs(valueScale(d.value) - valueScale(0)),\r\n transform: (d: FunnelPercent) => {\r\n let xOffset = valueScale(d.value) - emptyHorizontalSpace(d.value);\r\n let yOffset = d.isTop\r\n ? categoryScale(0) - percentBarTickHeight\r\n : categoryScale(lastCategoryIndex) + columnHeight + percentBarTickHeight;\r\n return SVGUtil.translate(xOffset, yOffset);\r\n },\r\n },\r\n leftTick: {\r\n y2: (d: FunnelPercent) => percentBarTickHeight,\r\n transform: (d: FunnelPercent) => {\r\n let xOffset = valueScale(d.value) - emptyHorizontalSpace(d.value);\r\n let yOffset = d.isTop\r\n ? categoryScale(0) - percentBarTickHeight - (percentBarTickHeight / 2)\r\n : categoryScale(lastCategoryIndex) + columnHeight + percentBarTickHeight - (percentBarTickHeight / 2);\r\n return SVGUtil.translate(xOffset, yOffset);\r\n },\r\n },\r\n rightTick: {\r\n y2: (d: FunnelPercent) => percentBarTickHeight,\r\n transform: (d: FunnelPercent) => {\r\n let columnOffset = valueScale(d.value) - emptyHorizontalSpace(d.value);\r\n let columnWidth = Math.abs(valueScale(d.value) - valueScale(0));\r\n let xOffset = columnOffset + columnWidth;\r\n let yOffset = d.isTop\r\n ? categoryScale(0) - percentBarTickHeight - (percentBarTickHeight / 2)\r\n : categoryScale(lastCategoryIndex) + columnHeight + percentBarTickHeight - (percentBarTickHeight / 2);\r\n return SVGUtil.translate(xOffset, yOffset);\r\n },\r\n },\r\n text: {\r\n x: (d: FunnelPercent) => Math.ceil((Math.abs(valueScale(maxScore) - valueScale(0)) / 2)),\r\n y: (d: FunnelPercent) => {\r\n return d.isTop\r\n ? -percentBarTickHeight / 2 - baselineDelta\r\n : percentBarYOffset + (percentBarTickHeight / 2);\r\n },\r\n style: () => `font-size: ${percentBarFontSize};`,\r\n transform: (d: FunnelPercent) => {\r\n let xOffset = d.isTop\r\n ? categoryScale(0) - percentBarTickHeight\r\n : categoryScale(lastCategoryIndex) + columnHeight + percentBarTickHeight;\r\n return SVGUtil.translate(0, xOffset);\r\n },\r\n fill: data.percentBarLabelSettings.labelColor,\r\n maxWidth: horizontalDistance,\r\n },\r\n },\r\n shapeLayout: {\r\n height: ((d: FunnelDataPoint) => d.highlight && highlightsOverflow ? overFlowHighlightColumnWidth : columnHeight),\r\n width: (d: FunnelDataPoint) => {\r\n return Math.abs(valueScale(FunnelChart.getValueFromDataPoint(d)) - valueScale(0));\r\n },\r\n y: (d: FunnelDataPoint) => {\r\n return categoryScale(d.categoryOrMeasureIndex) + (d.highlight && highlightsOverflow ? overFlowHighlightOffset : 0);\r\n },\r\n x: (d: FunnelDataPoint) => {\r\n let value = FunnelChart.getValueFromDataPoint(d);\r\n return valueScale(value) - emptyHorizontalSpace(value);\r\n },\r\n },\r\n shapeLayoutWithoutHighlights: {\r\n height: ((d: FunnelDataPoint) => columnHeight),\r\n width: (d: FunnelDataPoint) => {\r\n return Math.abs(valueScale(d.value) - valueScale(0));\r\n },\r\n y: (d: FunnelDataPoint) => {\r\n return categoryScale(d.categoryOrMeasureIndex) + (0);\r\n },\r\n x: (d: FunnelDataPoint) => {\r\n return valueScale(d.value) - emptyHorizontalSpace(d.value);\r\n },\r\n },\r\n zeroShapeLayout: {\r\n height: ((d: FunnelDataPoint) => d.highlight && highlightsOverflow ? overFlowHighlightColumnWidth : columnHeight),\r\n width: (d: FunnelDataPoint) => 0,\r\n y: (d: FunnelDataPoint) => {\r\n return categoryScale(d.categoryOrMeasureIndex) + (d.highlight && highlightsOverflow ? overFlowHighlightOffset : 0);\r\n },\r\n x: (d: FunnelDataPoint) => {\r\n return valueScale((valueScale.domain()[0] + valueScale.domain()[1]) / 2);\r\n },\r\n },\r\n interactorLayout: {\r\n height: ((d: FunnelDataPoint) => d.highlight && highlightsOverflow ? overFlowHighlightColumnWidth : columnHeight),\r\n width: (d: FunnelDataPoint) => getMinimumShapeSize(FunnelChart.getValueFromDataPoint(d)),\r\n y: (d: FunnelDataPoint) => {\r\n return categoryScale(d.categoryOrMeasureIndex) + (d.highlight && highlightsOverflow ? overFlowHighlightOffset : 0);\r\n },\r\n x: (d: FunnelDataPoint) => {\r\n let size = getMinimumShapeSize(FunnelChart.getValueFromDataPoint(d));\r\n return (horizontalDistance - size) / 2;\r\n },\r\n },\r\n };\r\n }\r\n\r\n public static drawDefaultAxis(graphicsContext: D3.Selection, axisOptions: FunnelAxisOptions, isHidingPercentBars: boolean): void {\r\n //Generate ordinal domain\r\n var indices = d3.range(0, axisOptions.categoryLabels.length);\r\n let xScaleForAxis = d3.scale.ordinal()\r\n .domain(indices)\r\n .rangeBands([axisOptions.rangeStart, axisOptions.rangeEnd], axisOptions.barToSpaceRatio, isHidingPercentBars ? axisOptions.barToSpaceRatio : FunnelChart.PercentBarToBarRatio);\r\n let xAxis = d3.svg.axis()\r\n .scale(xScaleForAxis)\r\n .orient(\"right\")\r\n .tickPadding(FunnelChart.TickPadding)\r\n .innerTickSize(FunnelChart.InnerTickSize)\r\n .ticks(indices.length)\r\n .tickValues(indices)\r\n .tickFormat((i) => { return axisOptions.categoryLabels[i]; }); //To output the category label\r\n graphicsContext.attr('class', 'axis hideLinesOnAxis')\r\n .attr('transform', SVGUtil.translate(0, axisOptions.margin.top))\r\n .call(xAxis);\r\n\r\n graphicsContext.selectAll('.tick')\r\n .call(tooltipUtils.tooltipUpdate, axisOptions.categoryLabels);\r\n \r\n // Subtract the padding from the margin since we can't have text there. Then shorten the labels if necessary.\r\n let leftRightMarginLimit = axisOptions.margin.left - FunnelChart.LabelFunnelPadding;\r\n graphicsContext.selectAll('.tick text')\r\n .call(AxisHelper.LabelLayoutStrategy.clip, leftRightMarginLimit, TextMeasurementService.svgEllipsis);\r\n }\r\n\r\n public static drawDefaultShapes(data: FunnelData, dataPoints: FunnelDataPoint[], graphicsContext: D3.Selection, layout: IFunnelLayout, hasSelection: boolean): D3.UpdateSelection {\r\n let hasHighlights = data.hasHighlights;\r\n let columns = graphicsContext.selectAll(FunnelChart.Selectors.funnel.bars.selector).data(dataPoints, (d: FunnelDataPoint) => d.key);\r\n\r\n columns.enter()\r\n .append('rect')\r\n .attr(\"class\", (d: FunnelDataPoint) => d.highlight ? FunnelChart.FunnelBarHighlightClass : FunnelChart.Selectors.funnel.bars.class);\r\n\r\n columns\r\n .style(\"fill\", (d: FunnelDataPoint) => {\r\n return d.color;\r\n })\r\n .style(\"fill-opacity\", (d: FunnelDataPoint) => ColumnUtil.getFillOpacity(d.selected, d.highlight, hasSelection, hasHighlights))\r\n .attr(layout.shapeLayout);\r\n\r\n columns.exit().remove();\r\n\r\n return columns;\r\n }\r\n\r\n public static getValueFromDataPoint(dataPoint: FunnelDataPoint, asOriginal: boolean = false): number {\r\n if (asOriginal)\r\n return dataPoint.highlight ? (dataPoint.originalHighlightValue) : dataPoint.originalValue;\r\n else\r\n return dataPoint.highlight ? dataPoint.highlightValue : dataPoint.value;\r\n }\r\n\r\n public static drawInteractorShapes(dataPoints: FunnelDataPoint[], graphicsContext: D3.Selection, layout: IFunnelLayout): D3.UpdateSelection {\r\n // Draw invsible ineractors for just data points which are below threshold\r\n let interactorsData = dataPoints.filter((d: FunnelDataPoint) => {\r\n return !d.highlight && layout.interactorLayout.width(d) === FunnelChart.MinimumInteractorSize;\r\n });\r\n \r\n let columns = graphicsContext.selectAll(FunnelChart.Selectors.funnel.interactors.selector).data(interactorsData, (d: FunnelDataPoint) => d.key);\r\n columns.enter()\r\n .append('rect')\r\n .attr(\"class\", FunnelChart.Selectors.funnel.interactors.class);\r\n\r\n columns\r\n .style(\"fill-opacity\", 0)\r\n .attr(layout.interactorLayout);\r\n\r\n columns.exit().remove();\r\n\r\n return columns;\r\n }\r\n\r\n private static drawPercentBarComponents(graphicsContext: D3.Selection, data: FunnelPercent[], layout: IFunnelLayout, percentLabelSettings: VisualDataLabelsSettings) {\r\n // Main line\r\n let mainLine: D3.UpdateSelection = graphicsContext.selectAll(FunnelChart.Selectors.percentBar.mainLine.selector).data(data);\r\n mainLine.exit().remove();\r\n mainLine.enter()\r\n .append('line')\r\n .classed(FunnelChart.Selectors.percentBar.mainLine.class, true);\r\n mainLine\r\n .attr(layout.percentBarLayout.mainLine);\r\n\r\n // Left tick\r\n let leftTick: D3.UpdateSelection = graphicsContext.selectAll(FunnelChart.Selectors.percentBar.leftTick.selector).data(data);\r\n leftTick.exit().remove();\r\n leftTick.enter()\r\n .append('line')\r\n .classed(FunnelChart.Selectors.percentBar.leftTick.class, true);\r\n leftTick\r\n .attr(layout.percentBarLayout.leftTick);\r\n\r\n // Right tick\r\n let rightTick: D3.UpdateSelection = graphicsContext.selectAll(FunnelChart.Selectors.percentBar.rightTick.selector).data(data);\r\n rightTick.exit().remove();\r\n rightTick.enter()\r\n .append('line')\r\n .classed(FunnelChart.Selectors.percentBar.rightTick.class, true);\r\n rightTick\r\n .attr(layout.percentBarLayout.rightTick);\r\n\r\n // Text\r\n let text: D3.UpdateSelection = graphicsContext.selectAll(FunnelChart.Selectors.percentBar.text.selector).data(data);\r\n let localizedString: string = valueFormatter.getLocalizedString(\"Percentage1\");\r\n text.exit().remove();\r\n text.enter().append('text').classed(FunnelChart.Selectors.percentBar.text.class, true);\r\n text\r\n .attr(layout.percentBarLayout.text)\r\n .text((fp: FunnelPercent) => {\r\n return dataLabelUtils.getLabelFormattedText({\r\n label: fp.percent,\r\n format: localizedString,\r\n fontSize: percentLabelSettings.fontSize,\r\n maxWidth: layout.percentBarLayout.text.maxWidth,\r\n });\r\n })\r\n .append('title').text((d: FunnelPercent) => formattingService.formatValue(d.percent, localizedString)); \r\n }\r\n\r\n public static drawPercentBars(data: FunnelData, graphicsContext: D3.Selection, layout: IFunnelLayout, isHidingPercentBars: boolean): void {\r\n if (isHidingPercentBars || !data.dataPoints || (data.hasHighlights ? data.dataPoints.length / 2 : data.dataPoints.length) < 2) {\r\n FunnelChart.drawPercentBarComponents(graphicsContext, [], layout, data.percentBarLabelSettings);\r\n return;\r\n }\r\n\r\n let dataPoints = [data.dataPoints[data.hasHighlights ? 1 : 0], data.dataPoints[data.dataPoints.length - 1]];\r\n let baseline = FunnelChart.getValueFromDataPoint(dataPoints[0]);\r\n\r\n if (baseline <= 0) {\r\n FunnelChart.drawPercentBarComponents(graphicsContext, [], layout, data.percentBarLabelSettings);\r\n return;\r\n }\r\n\r\n let percentData: FunnelPercent[] = [\r\n {\r\n value: FunnelChart.getValueFromDataPoint(dataPoints[0]),\r\n percent: 1,\r\n isTop: true,\r\n },\r\n {\r\n value: FunnelChart.getValueFromDataPoint(dataPoints[1]),\r\n percent: FunnelChart.getValueFromDataPoint(dataPoints[1]) / baseline,\r\n isTop: false,\r\n },\r\n ];\r\n\r\n FunnelChart.drawPercentBarComponents(graphicsContext, percentData, layout, data.percentBarLabelSettings);\r\n }\r\n\r\n private showCategoryLabels(): boolean {\r\n if (this.funnelSmallViewPortProperties) {\r\n if ((this.funnelSmallViewPortProperties.hideFunnelCategoryLabelsOnSmallViewPort) && (this.currentViewport.height < this.funnelSmallViewPortProperties.minHeightFunnelCategoryLabelsVisible)) {\r\n return false;\r\n }\r\n }\r\n return true;\r\n }\r\n\r\n private static addFunnelPercentsToTooltip(pctFormatString: string, tooltipInfo: TooltipDataItem[], hostServices: IVisualHostServices, percentOfFirst?: number, percentOfPrevious?: number, highlight?: boolean): void {\r\n if (percentOfFirst != null) {\r\n tooltipInfo.push({\r\n displayName: hostServices.getLocalizedString(\"Funnel_PercentOfFirst\" + (highlight ? \"_Highlight\" : \"\")),\r\n value: valueFormatter.format(percentOfFirst, pctFormatString),\r\n });\r\n }\r\n if (percentOfPrevious != null) {\r\n tooltipInfo.push({\r\n displayName: hostServices.getLocalizedString(\"Funnel_PercentOfPrevious\" + (highlight ? \"_Highlight\" : \"\")),\r\n value: valueFormatter.format(percentOfPrevious, pctFormatString),\r\n });\r\n }\r\n }\r\n\r\n private static getTextProperties(fontSize?: number): TextProperties {\r\n return {\r\n fontSize: PixelConverter.fromPoint(fontSize || dataLabelUtils.DefaultFontSizeInPt),\r\n fontFamily: FunnelChart.DefaultFontFamily,\r\n };\r\n }\r\n \r\n private static getDefaultLabelSettings(): VisualDataLabelsSettings {\r\n return {\r\n show: true,\r\n position: powerbi.visuals.labelPosition.insideCenter,\r\n displayUnits: 0,\r\n labelColor: null,\r\n fontSize: LabelUtils.DefaultLabelFontSizeInPt,\r\n };\r\n }\r\n \r\n private static getDefaultPercentLabelSettings(): VisualDataLabelsSettings {\r\n return {\r\n show: true,\r\n position: PointLabelPosition.Above,\r\n displayUnits: 0,\r\n labelColor: LabelUtils.defaultLabelColor,\r\n fontSize: LabelUtils.DefaultLabelFontSizeInPt,\r\n };\r\n }\r\n \r\n /**\r\n * Creates labels layout.\r\n */\r\n private getLabels(layout: IFunnelLayout): Label[] {\r\n let labels: Label[] = [];\r\n if (this.data.dataLabelsSettings.show && this.data.canShowDataLabels) {\r\n let labelDataPoints: LabelDataPoint[] = this.createLabelDataPoints(layout.shapeLayout, this.data.dataLabelsSettings);\r\n let newLabelLayout = new LabelLayout({\r\n maximumOffset: LabelUtils.maxLabelOffset,\r\n startingOffset: LabelUtils.startingLabelOffset\r\n });\r\n let labelDataPointsGroup: LabelDataPointGroup = {\r\n labelDataPoints: labelDataPoints,\r\n maxNumberOfLabels: labelDataPoints.length\r\n };\r\n let labelViewport: IViewport = {\r\n width: this.currentViewport.width - this.margin.left,\r\n height: this.currentViewport.height - this.margin.top\r\n };\r\n \r\n labels = newLabelLayout.layout([labelDataPointsGroup], labelViewport);\r\n }\r\n \r\n return labels;\r\n }\r\n \r\n /**\r\n * Creates labelDataPoints for rendering labels\r\n */\r\n private createLabelDataPoints(shapeLayout: IFunnelRect, visualSettings: VisualDataLabelsSettings): LabelDataPoint[] {\r\n let data: FunnelData = this.data;\r\n let dataPoints = data.dataPoints;\r\n if(_.isEmpty(dataPoints)) {\r\n return [];\r\n }\r\n let points = new Array<LabelDataPoint>();\r\n // Because labels share the same formatting use the first one as default.\r\n let generalSettings = dataPoints[0];\r\n \r\n // Shape\r\n let validPositions = FunnelChart.LabelInsidePosition;\r\n let height: number = shapeLayout.height(generalSettings);\r\n if (visualSettings.position && visualSettings.position === labelPosition.outsideEnd) {\r\n validPositions = FunnelChart.LabelOutsidePosition;\r\n }\r\n \r\n // Formatter\r\n let maxAbsoluteValue = data.dataPoints.reduce((memo, value) => Math.abs(memo.value) > Math.abs(value.value) ? memo : value).value;\r\n let formatString = valueFormatter.getFormatString(data.valuesMetadata[0], funnelChartProps.general.formatString);\r\n let formattersCache = LabelUtils.createColumnFormatterCacheManager();\r\n \r\n // Text Properties\r\n let fontSize = visualSettings.fontSize;\r\n let properties: TextProperties = {\r\n fontFamily: LabelUtils.LabelTextProperties.fontFamily,\r\n fontSize: PixelConverter.fromPoint(fontSize || LabelUtils.DefaultLabelFontSizeInPt),\r\n fontWeight: LabelUtils.LabelTextProperties.fontWeight,\r\n };\r\n \r\n let outsideFill: string = generalSettings.labelFill || LabelUtils.defaultLabelColor;\r\n let insideFill: string = generalSettings.labelFill || LabelUtils.defaultInsideLabelColor;\r\n \r\n \r\n for (let dataPoint of dataPoints) {\r\n let value = FunnelChart.getValueFromDataPoint(dataPoint, true /* asOriginal */);\r\n if (value == null\r\n || (data.hasHighlights && !dataPoint.highlight)) {\r\n continue;\r\n }\r\n \r\n let labelFormatString = (formatString != null) ? formatString : generalSettings.labelFormatString;\r\n let formatter = formattersCache.getOrCreate(labelFormatString, visualSettings, maxAbsoluteValue);\r\n let labelText = formatter.format(value);\r\n properties.text = labelText;\r\n \r\n let textWidth = TextMeasurementService.measureSvgTextWidth(properties);\r\n let textHeight = TextMeasurementService.estimateSvgTextHeight(properties);\r\n let parentType = LabelDataPointParentType.Rectangle;\r\n let shape: any = {\r\n rect: {\r\n left: shapeLayout.x(dataPoint),\r\n top: shapeLayout.y(dataPoint),\r\n width: shapeLayout.width(dataPoint),\r\n height: height\r\n },\r\n orientation: FunnelChart.LabelOrientation,\r\n validPositions: validPositions\r\n };\r\n \r\n var point: LabelDataPoint = {\r\n isPreferred: true,\r\n // text\r\n text: labelText,\r\n textSize: {\r\n width: textWidth,\r\n height: textHeight\r\n },\r\n fontSize: fontSize,\r\n // parent shape\r\n parentType: parentType,\r\n parentShape: shape,\r\n // colors\r\n insideFill: insideFill,\r\n outsideFill: outsideFill,\r\n // additional properties\r\n identity: dataPoint.identity,\r\n hasBackground: false\r\n };\r\n \r\n // For zero value we are using point in order to center text position.\r\n if(dataPoint.value === 0) {\r\n shape = <LabelParentPoint> {\r\n validPositions: [ NewPointLabelPosition.Center ],\r\n point: {\r\n x: shapeLayout.x(dataPoint),\r\n y: shapeLayout.y(dataPoint) + height / 2\r\n }\r\n \r\n };\r\n parentType = LabelDataPointParentType.Point;\r\n point.parentShape = shape;\r\n point.parentType = parentType;\r\n point.insideFill = point.outsideFill;\r\n }\r\n \r\n points.push(point);\r\n }\r\n \r\n return points;\r\n }\r\n }\r\n}\n\n\n/** WEBPACK FOOTER **\n ** ./src/Clients/Visuals/visuals/funnelChart.ts\n **/","/*\r\n * Power BI Visualizations\r\n *\r\n * Copyright (c) Microsoft Corporation\r\n * All rights reserved. \r\n * MIT License\r\n *\r\n * Permission is hereby granted, free of charge, to any person obtaining a copy\r\n * of this software and associated documentation files (the \"\"Software\"\"), to deal\r\n * in the Software without restriction, including without limitation the rights\r\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\r\n * copies of the Software, and to permit persons to whom the Software is\r\n * furnished to do so, subject to the following conditions:\r\n * \r\n * The above copyright notice and this permission notice shall be included in \r\n * all copies or substantial portions of the Software.\r\n * \r\n * THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR \r\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, \r\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE \r\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER \r\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\r\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\r\n * THE SOFTWARE.\r\n */\r\n\r\n/// <reference path=\"../_references.ts\"/>\r\n\r\nmodule powerbi.visuals {\r\n import ClassAndSelector = jsCommon.CssConstants.ClassAndSelector;\r\n import createClassAndSelector = jsCommon.CssConstants.createClassAndSelector;\r\n import PixelConverter = jsCommon.PixelConverter;\r\n import DataRoleHelper = powerbi.data.DataRoleHelper;\r\n\r\n export interface GaugeData extends TooltipEnabledDataPoint {\r\n total: number;\r\n metadataColumn: DataViewMetadataColumn;\r\n minColumnMetadata: DataViewMetadataColumn;\r\n maxColumnMetadata: DataViewMetadataColumn;\r\n targetColumnMetadata: DataViewMetadataColumn;\r\n targetSettings: GaugeTargetSettings;\r\n dataLabelsSettings: VisualDataLabelsSettings;\r\n calloutValueLabelsSettings: VisualDataLabelsSettings;\r\n dataPointSettings: GaugeDataPointSettings;\r\n }\r\n\r\n interface KpiArcAttributes {\r\n start: number;\r\n end: number;\r\n fill: string;\r\n }\r\n\r\n export interface GaugeTargetSettings {\r\n min: number;\r\n max: number;\r\n target: number;\r\n }\r\n\r\n export interface GaugeTargetData extends GaugeTargetSettings {\r\n value: number;\r\n tooltipItems: TooltipDataItem[];\r\n }\r\n\r\n export interface GaugeDataPointSettings {\r\n fillColor: string;\r\n targetColor: string;\r\n }\r\n\r\n interface GaugeStyle {\r\n transition: {\r\n ease: string\r\n };\r\n arcColors: {\r\n background: string;\r\n foreground: string;\r\n };\r\n targetLine: {\r\n show: boolean;\r\n color: string;\r\n thickness: number;\r\n };\r\n labels: {\r\n count: number;\r\n padding: number;\r\n fontSize: number;\r\n };\r\n kpiBands: {\r\n show: boolean;\r\n separationRadians: number;\r\n thickness: number;\r\n };\r\n }\r\n\r\n export interface GaugeSmallViewPortProperties {\r\n hideGaugeSideNumbersOnSmallViewPort: boolean;\r\n smallGaugeMarginsOnSmallViewPort: boolean;\r\n MinHeightGaugeSideNumbersVisible: number;\r\n GaugeMarginsOnSmallViewPort: number;\r\n }\r\n\r\n export interface GaugeVisualProperties {\r\n radius: number;\r\n innerRadiusOfArc: number;\r\n innerRadiusFactor: number;\r\n left: number;\r\n top: number;\r\n height: number;\r\n width: number;\r\n margin: IMargin;\r\n transformString: string;\r\n }\r\n\r\n export interface AnimatedNumberProperties {\r\n transformString: string;\r\n viewport: IViewport;\r\n }\r\n\r\n export interface GaugeConstructorOptions {\r\n gaugeSmallViewPortProperties?: GaugeSmallViewPortProperties;\r\n animator?: IGenericAnimator;\r\n tooltipsEnabled?: boolean;\r\n tooltipBucketEnabled?: boolean;\r\n }\r\n\r\n export interface GaugeDataViewObjects extends DataViewObjects {\r\n axis: GaugeDataViewObject;\r\n }\r\n\r\n export interface GaugeDataViewObject extends DataViewObject {\r\n min?: number;\r\n max?: number;\r\n target?: number;\r\n }\r\n\r\n /** \r\n * Renders a number that can be animate change in value.\r\n */\r\n export class Gauge implements IVisual {\r\n private static MinDistanceFromBottom = 10;\r\n private static MinWidthForTargetLabel = 150;\r\n private static DefaultTopBottomMargin = 20;\r\n private static DefaultLeftRightMargin = 45;\r\n private static ReducedLeftRightMargin = 15;\r\n private static DEFAULT_MAX = 1;\r\n private static DEFAULT_MIN = 0;\r\n private static VisualClassName = 'gauge';\r\n private static DefaultStyleProperties: GaugeStyle = {\r\n transition: {\r\n ease: 'bounce'\r\n },\r\n arcColors: {\r\n background: '#e9e9e9',\r\n foreground: '#00B8AA'\r\n },\r\n targetLine: {\r\n show: true,\r\n color: '#666666',\r\n thickness: 2\r\n },\r\n labels: {\r\n count: 2,\r\n padding: 5,\r\n fontSize: NewDataLabelUtils.DefaultLabelFontSizeInPt,\r\n },\r\n kpiBands: {\r\n show: false,\r\n separationRadians: Math.PI / 128,\r\n thickness: 5\r\n },\r\n };\r\n private static DefaultTargetSettings: GaugeTargetSettings = {\r\n min: 0,\r\n max: 1,\r\n target: undefined\r\n };\r\n private static DefaultDataPointSettings: GaugeDataPointSettings = {\r\n fillColor: Gauge.DefaultStyleProperties.arcColors.foreground,\r\n targetColor: Gauge.DefaultStyleProperties.targetLine.color\r\n };\r\n\r\n private static InnerRadiusFactor = 0.7;\r\n private static KpiBandDistanceFromMainArc = 2;\r\n private static MainGaugeGroupClassName = 'mainGroup';\r\n private static LabelText: ClassAndSelector = createClassAndSelector('labelText');\r\n private static TargetConnector: ClassAndSelector = createClassAndSelector('targetConnector');\r\n private static TargetText: ClassAndSelector = createClassAndSelector('targetText');\r\n\r\n /** Note: Public for testability */\r\n public static formatStringProp: DataViewObjectPropertyIdentifier = {\r\n objectName: 'general',\r\n propertyName: 'formatString',\r\n };\r\n\r\n private svg: D3.Selection;\r\n private mainGraphicsContext: D3.Selection;\r\n private currentViewport: IViewport;\r\n private element: JQuery;\r\n private style: IVisualStyle;\r\n private data: GaugeData;\r\n private color: D3.Scale.OrdinalScale;\r\n\r\n private backgroundArc: D3.Svg.Arc;\r\n private foregroundArc: D3.Svg.Arc;\r\n private kpiArcs: D3.Svg.Arc[];\r\n\r\n private kpiArcPaths: D3.Selection[];\r\n private foregroundArcPath: D3.Selection;\r\n private backgroundArcPath: D3.Selection;\r\n private targetLine: D3.Selection;\r\n private targetConnector: D3.Selection;\r\n private targetText: D3.Selection;\r\n private options: VisualInitOptions;\r\n\r\n private lastAngle = -Math.PI / 2;\r\n private margin: IMargin;\r\n private animatedNumberGrapicsContext: D3.Selection;\r\n private animatedNumber: AnimatedNumber;\r\n private settings: GaugeStyle;\r\n private targetSettings: GaugeTargetSettings;\r\n private gaugeVisualProperties: GaugeVisualProperties;\r\n private gaugeSmallViewPortProperties: GaugeSmallViewPortProperties;\r\n private showTargetLabel: boolean;\r\n\r\n private tooltipsEnabled: boolean;\r\n private tooltipBucketEnabled: boolean;\r\n\r\n private hostService: IVisualHostServices;\r\n\r\n // TODO: Remove this once all visuals have implemented update.\r\n private dataView: DataView;\r\n\r\n public animator: IGenericAnimator;\r\n\r\n constructor(options?: GaugeConstructorOptions) {\r\n if (options) {\r\n if (options.gaugeSmallViewPortProperties) {\r\n this.gaugeSmallViewPortProperties = options.gaugeSmallViewPortProperties;\r\n }\r\n this.animator = options.animator;\r\n this.tooltipsEnabled = options.tooltipsEnabled;\r\n this.tooltipBucketEnabled = options.tooltipBucketEnabled;\r\n }\r\n }\r\n\r\n public enumerateObjectInstances(options: EnumerateVisualObjectInstancesOptions): VisualObjectInstanceEnumeration {\r\n let enumeration = new ObjectEnumerationBuilder();\r\n\r\n switch (options.objectName) {\r\n case 'axis':\r\n this.enumerateAxis(enumeration);\r\n break;\r\n case 'labels': {\r\n let labelSettings = this.data ? this.data.dataLabelsSettings : dataLabelUtils.getDefaultGaugeLabelSettings();\r\n dataLabelUtils.enumerateDataLabels(this.getDataLabelSettingsOptions(enumeration, labelSettings));\r\n break;\r\n }\r\n case 'calloutValue': {\r\n let labelSettings = this.data ? this.data.calloutValueLabelsSettings : dataLabelUtils.getDefaultGaugeLabelSettings();\r\n dataLabelUtils.enumerateDataLabels(this.getDataLabelSettingsOptions(enumeration, labelSettings));\r\n break;\r\n }\r\n case 'dataPoint': {\r\n this.enumerateDataPoint(enumeration);\r\n break;\r\n }\r\n }\r\n return enumeration.complete();\r\n }\r\n\r\n private getDataLabelSettingsOptions(enumeration: ObjectEnumerationBuilder, labelSettings: VisualDataLabelsSettings): VisualDataLabelsSettingsOptions {\r\n return {\r\n dataLabelsSettings: labelSettings,\r\n show: true,\r\n precision: true,\r\n displayUnits: true,\r\n fontSize: true,\r\n enumeration: enumeration,\r\n };\r\n }\r\n\r\n private enumerateAxis(enumeration: ObjectEnumerationBuilder): void {\r\n let dataView: DataView = this.dataView;\r\n\r\n if (dataView && dataView.metadata) {\r\n let properties: GaugeTargetSettings = Gauge.getGaugeObjectsProperties(dataView);\r\n enumeration.pushInstance({\r\n selector: null,\r\n objectName: 'axis',\r\n properties: <any>properties,\r\n });\r\n }\r\n }\r\n\r\n private enumerateDataPoint(enumeration: ObjectEnumerationBuilder): void {\r\n let dataPointSettings = this.data ? this.data.dataPointSettings : Gauge.DefaultDataPointSettings;\r\n let properties: any = {};\r\n\r\n properties.fill = { solid: { color: dataPointSettings.fillColor } };\r\n\r\n if (dataPointSettings.targetColor != null) {\r\n properties.target = { solid: { color: dataPointSettings.targetColor } };\r\n }\r\n\r\n enumeration.pushInstance({\r\n selector: null,\r\n objectName: gaugeProps.dataPoint.target.objectName,\r\n properties: properties\r\n });\r\n }\r\n\r\n private static getGaugeObjectsProperties(dataView: DataView): GaugeTargetSettings {\r\n let properties: any = {};\r\n let objects: GaugeDataViewObjects = <GaugeDataViewObjects>dataView.metadata.objects;\r\n let hasAxisObject: boolean = !!objects && !!objects.axis;\r\n\r\n if (!DataRoleHelper.hasRoleInDataView(dataView, gaugeRoleNames.minValue))\r\n properties.min = hasAxisObject ? objects.axis.min : undefined;\r\n\r\n if (!DataRoleHelper.hasRoleInDataView(dataView, gaugeRoleNames.maxValue))\r\n properties.max = hasAxisObject ? objects.axis.max : undefined;\r\n\r\n if (!DataRoleHelper.hasRoleInDataView(dataView, gaugeRoleNames.targetValue))\r\n properties.target = hasAxisObject ? objects.axis.target : undefined;\r\n\r\n return properties;\r\n }\r\n\r\n public init(options: VisualInitOptions) {\r\n this.element = options.element;\r\n this.currentViewport = options.viewport;\r\n this.style = options.style;\r\n this.options = options;\r\n this.settings = Gauge.DefaultStyleProperties;\r\n this.targetSettings = Gauge.DefaultTargetSettings;\r\n\r\n this.setMargins();\r\n\r\n this.color = d3.scale.ordinal().range(\r\n this.style.colorPalette.dataColors.getSentimentColors().map(\r\n color => color.value));\r\n\r\n this.hostService = options.host;\r\n let svg = this.svg = d3.select(this.element.get(0)).append('svg');\r\n svg.classed(Gauge.VisualClassName, true);\r\n let mainGraphicsContext = this.mainGraphicsContext = svg.append('g');\r\n mainGraphicsContext.attr('class', Gauge.MainGaugeGroupClassName);\r\n\r\n this.initKpiBands();\r\n\r\n let backgroundArc = this.backgroundArc = d3.svg.arc()\r\n .innerRadius(0)\r\n .outerRadius(0)\r\n .startAngle(-Math.PI / 2)\r\n .endAngle(Math.PI / 2);\r\n\r\n let foregroundArc = this.foregroundArc = d3.svg.arc()\r\n .innerRadius(0)\r\n .outerRadius(0)\r\n .startAngle(-Math.PI / 2);\r\n\r\n this.backgroundArcPath = mainGraphicsContext.append('path')\r\n .classed('backgroundArc', true)\r\n .attr('d', backgroundArc);\r\n\r\n this.foregroundArcPath = mainGraphicsContext.append('path')\r\n .datum({ endAngle: -Math.PI / 2 })\r\n .classed('foregroundArc', true)\r\n .attr('d', foregroundArc);\r\n\r\n let g = this.animatedNumberGrapicsContext = svg.append('g');\r\n\r\n this.animatedNumber = new AnimatedNumber(g);\r\n this.animatedNumber.init(options);\r\n\r\n let gaugeDrawingOptions = this.gaugeVisualProperties = this.getGaugeVisualProperties();\r\n let animatedNumberProperties = this.getAnimatedNumberProperties(\r\n gaugeDrawingOptions.radius,\r\n gaugeDrawingOptions.innerRadiusFactor,\r\n gaugeDrawingOptions.top,\r\n gaugeDrawingOptions.left);\r\n this.animatedNumberGrapicsContext.attr('transform', animatedNumberProperties.transformString);\r\n this.animatedNumber.onResizing(animatedNumberProperties.viewport);\r\n }\r\n\r\n public update(options: VisualUpdateOptions) {\r\n debug.assertValue(options, 'options');\r\n \r\n this.currentViewport = options.viewport;\r\n if (!options.dataViews || !options.dataViews[0]) {\r\n return;\r\n }\r\n \r\n let dataView = this.dataView = options.dataViews[0]; \r\n let reader = data.createIDataViewCategoricalReader(dataView);\r\n this.data = Gauge.converter(reader, this.tooltipBucketEnabled);\r\n this.targetSettings = this.data.targetSettings;\r\n this.dataView.single = { value: this.data.total };\r\n\r\n // Only show the target label if:\r\n // 1. There is a target\r\n // 2. The viewport width is big enough for a target\r\n // 3. We're showing label text for side numbers\r\n // 4. Data label settings specify to show\r\n this.showTargetLabel = this.targetSettings.target != null\r\n && (this.currentViewport.width > Gauge.MinWidthForTargetLabel || !this.showMinMaxLabelsOnBottom())\r\n && this.showSideNumbersLabelText()\r\n && this.data.dataLabelsSettings.show;\r\n\r\n this.setMargins();\r\n\r\n this.gaugeVisualProperties = this.getGaugeVisualProperties();\r\n this.drawViewPort(this.gaugeVisualProperties);\r\n this.updateInternal(options.suppressAnimations);\r\n this.updateCalloutValue(options.suppressAnimations);\r\n\r\n let warnings = getInvalidValueWarnings(\r\n [ dataView ],\r\n false /*supportsNaN*/,\r\n false /*supportsNegativeInfinity*/,\r\n false /*supportsPositiveInfinity*/);\r\n\r\n this.hostService.setWarnings(warnings);\r\n }\r\n\r\n private updateCalloutValue(suppressAnimations: boolean): void {\r\n if (this.data.calloutValueLabelsSettings.show) {\r\n let animatedNumberProperties = this.getAnimatedNumberProperties(\r\n this.gaugeVisualProperties.radius,\r\n this.gaugeVisualProperties.innerRadiusFactor,\r\n this.gaugeVisualProperties.top,\r\n this.gaugeVisualProperties.left);\r\n this.animatedNumberGrapicsContext.attr('transform', animatedNumberProperties.transformString);\r\n this.animatedNumber.setTextColor(this.data.calloutValueLabelsSettings.labelColor);\r\n\r\n let calloutValue: number = this.data ? this.data.total : null;\r\n let formatter = this.getFormatter(this.data.calloutValueLabelsSettings, this.data.metadataColumn, calloutValue);\r\n\r\n this.animatedNumber.setFormatter(formatter);\r\n this.animatedNumber.update({\r\n viewport: animatedNumberProperties.viewport,\r\n dataViews: [ this.dataView ],\r\n suppressAnimations: suppressAnimations,\r\n });\r\n\r\n this.animatedNumberGrapicsContext.selectAll('title').remove();\r\n this.animatedNumberGrapicsContext.append('title').text([formatter.format(calloutValue)]);\r\n }\r\n else {\r\n this.animatedNumber.clear();\r\n this.animatedNumberGrapicsContext.selectAll('title').remove();\r\n }\r\n }\r\n\r\n public onDataChanged(options: VisualDataChangedOptions): void {\r\n // TODO: Remove onDataChanged & onResizing once all visuals have implemented update.\r\n this.update({\r\n dataViews: options.dataViews,\r\n suppressAnimations: options.suppressAnimations,\r\n viewport: this.currentViewport\r\n });\r\n }\r\n\r\n public onResizing(viewport: IViewport): void {\r\n // TODO: Remove onDataChanged & onResizing once all visuals have implemented update.\r\n this.update({\r\n dataViews:[ this.dataView ],\r\n suppressAnimations: true,\r\n viewMode: ViewMode.View,\r\n viewport: viewport\r\n });\r\n }\r\n \r\n /**\r\n * Populates Gauge data based on roles or axis settings.\r\n */\r\n private static parseGaugeData(reader: data.IDataViewCategoricalReader, tooltipBucketEnabled?: boolean): GaugeTargetData {\r\n let dataViewObjects = <GaugeDataViewObjects>reader.getStaticObjects();\r\n let metadataColumn = reader.getCategoryMetadataColumn(gaugeRoleNames.y);\r\n let axisObject = dataViewObjects ? dataViewObjects.axis : null;\r\n let isValueDefined = reader.hasValues(gaugeRoleNames.y);\r\n let isMaxDefined = reader.hasValues(gaugeRoleNames.maxValue);\r\n let isMinDefined = reader.hasValues(gaugeRoleNames.minValue);\r\n let valueIndex = 0;\r\n \r\n let data: GaugeTargetData = {\r\n max: null,\r\n min: null,\r\n target: null,\r\n value: null,\r\n tooltipItems: []\r\n };\r\n \r\n // Set value\r\n if (isValueDefined) {\r\n let valueMetadata = reader.getValueMetadataColumn(gaugeRoleNames.y);\r\n data.value = reader.getValue(gaugeRoleNames.y, valueIndex);\r\n let value = converterHelper.formatFromMetadataColumn(data.value, valueMetadata, Gauge.formatStringProp);\r\n data.tooltipItems.push({ displayName: reader.getValueDisplayName(gaugeRoleNames.y), value: value });\r\n }\r\n \r\n // Set target\r\n if (reader.hasValues(gaugeRoleNames.targetValue)) {\r\n let targetMetadata = reader.getValueMetadataColumn(gaugeRoleNames.targetValue);\r\n data.target = reader.getValue(gaugeRoleNames.targetValue, valueIndex);\r\n let value = converterHelper.formatFromMetadataColumn(data.target, targetMetadata, Gauge.formatStringProp);\r\n data.tooltipItems.push({ displayName: reader.getValueDisplayName(gaugeRoleNames.targetValue), value: value });\r\n } \r\n else if (axisObject) {\r\n data.target = axisObject.target;\r\n }\r\n \r\n // For maxumum we set values in such priority: \r\n // 1. Maximum column\r\n // 2. Property pane axis settings\r\n // 3. If the value column is specified and it has no percent formatting and min is undefined: \r\n // a. 2 * value if value > 0\r\n // b. 0 if value < 0\r\n // 4. Use Default Max value what is 1 right now. \r\n if (isMaxDefined) {\r\n data.max = reader.getValue(gaugeRoleNames.maxValue, valueIndex);\r\n } \r\n else if (axisObject && axisObject.max != null) {\r\n data.max = axisObject.max;\r\n } \r\n else {\r\n data.max = Gauge.DEFAULT_MAX;\r\n if (isValueDefined && data.value && data.value !== 0) {\r\n let hasPercent = false;\r\n if (metadataColumn) {\r\n let formatString = valueFormatter.getFormatString(metadataColumn, Gauge.formatStringProp, true);\r\n if (formatString != null) {\r\n hasPercent = valueFormatter.getFormatMetadata(formatString).hasPercent;\r\n } \r\n }\r\n \r\n if (!hasPercent && !isMinDefined) {\r\n data.max = data.value < 0 ? Gauge.DEFAULT_MIN : 2 * data.value;\r\n }\r\n }\r\n }\r\n \r\n // For minimum we set values in such priority: \r\n // 1. Minimum column.\r\n // 2. Property pane axis settings.\r\n // 3. Use Default Min value what is 0 right now for value >= 0.\r\n // 4. Use value * 2 for value < 0 and max hasn't been specified.\r\n if (isMinDefined) {\r\n data.min = reader.getValue(gaugeRoleNames.minValue, valueIndex);\r\n } \r\n else if (axisObject && axisObject.min != null) {\r\n data.min = axisObject.min;\r\n } \r\n else {\r\n data.min = Gauge.DEFAULT_MIN;\r\n if (!isMaxDefined && isValueDefined && data.value != null && data.value < 0) {\r\n data.min = 2 * data.value;\r\n }\r\n }\r\n\r\n if (tooltipBucketEnabled) {\r\n TooltipBuilder.addTooltipBucketItem(reader, data.tooltipItems, 0);\r\n }\r\n\r\n return data;\r\n }\r\n \r\n /** Note: Made public for testability */\r\n public static converter(reader: data.IDataViewCategoricalReader, tooltipBucketEnabled: boolean = true): GaugeData {\r\n let objectSettings = reader.getStaticObjects();\r\n let metadataColumn = reader.getValueMetadataColumn(gaugeRoleNames.y);\r\n let gaugeData = Gauge.parseGaugeData(reader, tooltipBucketEnabled);\r\n let value = gaugeData.value;\r\n \r\n return {\r\n total: value,\r\n tooltipInfo: gaugeData.tooltipItems,\r\n maxColumnMetadata: reader.getValueMetadataColumn(gaugeRoleNames.maxValue),\r\n minColumnMetadata: reader.getValueMetadataColumn(gaugeRoleNames.minValue),\r\n targetColumnMetadata:reader.getValueMetadataColumn(gaugeRoleNames.targetValue),\r\n metadataColumn: metadataColumn,\r\n targetSettings: { min: gaugeData.min, max: gaugeData.max, target: gaugeData.target },\r\n dataLabelsSettings: Gauge.convertDataLabelSettings(objectSettings, \"labels\"),\r\n calloutValueLabelsSettings: Gauge.convertDataLabelSettings(objectSettings, \"calloutValue\"),\r\n dataPointSettings: Gauge.convertDataPointSettings(objectSettings, gaugeData)\r\n };\r\n }\r\n \r\n private static convertDataLabelSettings(objects: DataViewObjects, objectName: string): VisualDataLabelsSettings {\r\n let dataLabelsSettings = dataLabelUtils.getDefaultGaugeLabelSettings();\r\n if (objects) {\r\n // Handle label settings\r\n let labelsObj = <DataLabelObject>objects[objectName];\r\n dataLabelUtils.updateLabelSettingsFromLabelsObject(labelsObj, dataLabelsSettings);\r\n }\r\n \r\n return dataLabelsSettings;\r\n }\r\n\r\n private static convertDataPointSettings(objects: DataViewObjects, targetSettings: GaugeTargetSettings): GaugeDataPointSettings {\r\n \r\n // Default the fill color the the default fill color. Default the target to undefined as it's only used if there's a target.\r\n let fillColor = Gauge.DefaultDataPointSettings.fillColor;\r\n let targetColor: string;\r\n\r\n if (objects) {\r\n\r\n fillColor = DataViewObjects.getFillColor(objects, gaugeProps.dataPoint.fill, Gauge.DefaultDataPointSettings.fillColor);\r\n\r\n if (targetSettings && (targetSettings.target != null)) {\r\n targetColor = DataViewObjects.getFillColor(objects, gaugeProps.dataPoint.target, Gauge.DefaultDataPointSettings.targetColor);\r\n }\r\n }\r\n else if (targetSettings && (targetSettings.target != null)) {\r\n // If there isn't metadata, but a target is set, default to the default target color\r\n targetColor = Gauge.DefaultDataPointSettings.targetColor;\r\n }\r\n\r\n return {\r\n fillColor: fillColor,\r\n targetColor: targetColor\r\n };\r\n }\r\n\r\n private initKpiBands() {\r\n if (!this.settings.kpiBands.show)\r\n return;\r\n let kpiArcs = this.kpiArcs = [];\r\n let kpiArcPaths = this.kpiArcPaths = [];\r\n let mainGraphicsContext = this.mainGraphicsContext;\r\n\r\n for (let i = 0; i < 3; i++) {\r\n let arc = d3.svg.arc()\r\n .innerRadius(0)\r\n .outerRadius(0)\r\n .startAngle(0)\r\n .endAngle(0);\r\n\r\n kpiArcs.push(arc);\r\n\r\n let arcPath = mainGraphicsContext.append('path')\r\n .attr(\"d\", arc);\r\n\r\n kpiArcPaths.push(arcPath);\r\n }\r\n }\r\n \r\n /**\r\n * Indicates whether gauge arc is valid.\r\n */\r\n private isValid(): boolean {\r\n if (!this.data || !this.data.targetSettings)\r\n return false;\r\n \r\n let targetSettings = this.data.targetSettings;\r\n \r\n return $.isNumeric(targetSettings.min) && $.isNumeric(targetSettings.max) || targetSettings.min > targetSettings.max;\r\n }\r\n\r\n private updateKpiBands(radius: number, innerRadiusFactor: number, tString: string, kpiAngleAttr: KpiArcAttributes[]) {\r\n if (!this.settings.kpiBands.show)\r\n return;\r\n\r\n for (let i = 0; i < kpiAngleAttr.length; i++) {\r\n this.kpiArcs[i]\r\n .innerRadius(radius * innerRadiusFactor - (Gauge.KpiBandDistanceFromMainArc + this.settings.kpiBands.thickness))\r\n .outerRadius(radius * innerRadiusFactor - Gauge.KpiBandDistanceFromMainArc)\r\n .startAngle(kpiAngleAttr[i].start)\r\n .endAngle(kpiAngleAttr[i].end);\r\n\r\n this.kpiArcPaths[i]\r\n .attr('fill', kpiAngleAttr[i].fill)\r\n .attr('d', this.kpiArcs[i])\r\n .attr('transform', tString);\r\n }\r\n }\r\n\r\n private removeTargetElements() {\r\n if (this.targetLine) {\r\n this.targetLine.remove();\r\n this.targetText.remove();\r\n this.targetConnector.remove();\r\n this.targetLine = this.targetConnector = this.targetText = null;\r\n }\r\n }\r\n\r\n private getTargetRatio(): number {\r\n let targetSettings = this.targetSettings;\r\n let range = targetSettings.max - targetSettings.min;\r\n if (range !== 0)\r\n return (targetSettings.target - targetSettings.min) / range;\r\n\r\n return 0;\r\n }\r\n\r\n private updateTargetLine(radius: number, innerRadius: number, left, top) {\r\n if (!this.targetLine) {\r\n this.targetLine = this.mainGraphicsContext.append('line');\r\n }\r\n\r\n let angle = this.getTargetRatio() * Math.PI;\r\n\r\n let outY = top - radius * Math.sin(angle);\r\n let outX = left - radius * Math.cos(angle);\r\n\r\n let inY = top - innerRadius * Math.sin(angle);\r\n let inX = left - innerRadius * Math.cos(angle);\r\n\r\n this.targetLine.attr({\r\n x1: inX,\r\n y1: inY,\r\n x2: outX,\r\n y2: outY\r\n });\r\n }\r\n\r\n /** Note: public for testability */\r\n public getAnimatedNumberProperties(radius: number,\r\n innerRadiusFactor: number,\r\n top: number, left: number): AnimatedNumberProperties {\r\n let boxAngle = Math.PI / 4;\r\n let scale = 1;\r\n let innerRadiusOfArc = radius * innerRadiusFactor;\r\n let innerRadiusForTextBoundingBox = innerRadiusOfArc - (this.settings.kpiBands.show\r\n ? (Gauge.KpiBandDistanceFromMainArc + this.settings.kpiBands.thickness)\r\n : 0);\r\n let innerRCos = innerRadiusForTextBoundingBox * Math.cos(boxAngle);\r\n let innerRSin = innerRadiusForTextBoundingBox * Math.sin(boxAngle);\r\n let innerY = top - innerRSin;\r\n let innerX = left - innerRCos;\r\n innerY = innerY * scale;\r\n innerX = innerX * scale;\r\n let animatedNumberWidth = innerRCos * 2;\r\n\r\n let properties: AnimatedNumberProperties = {\r\n transformString: SVGUtil.translate(innerX, innerY),\r\n viewport: { height: innerRSin, width: animatedNumberWidth }\r\n };\r\n return properties;\r\n }\r\n\r\n /** Note: public for testability */\r\n public getGaugeVisualProperties(): GaugeVisualProperties {\r\n let viewport = this.currentViewport;\r\n let margin: IMargin = this.margin;\r\n let width = viewport.width - margin.right - margin.left;\r\n let halfWidth = width / 2;\r\n let height = viewport.height - margin.top - margin.bottom;\r\n let radius = Math.min(halfWidth, height);\r\n let innerRadiusFactor = Gauge.InnerRadiusFactor;\r\n let left = margin.left + halfWidth;\r\n let top = radius + (height - radius) / 2 + margin.top;\r\n let tString = SVGUtil.translate(left, top);\r\n let innerRadiusOfArc = radius * innerRadiusFactor;\r\n\r\n let gaugeData: GaugeVisualProperties = {\r\n radius: radius,\r\n innerRadiusOfArc: innerRadiusOfArc,\r\n left: left,\r\n top: top,\r\n height: height,\r\n width: width,\r\n margin: margin,\r\n transformString: tString,\r\n innerRadiusFactor: innerRadiusFactor\r\n };\r\n\r\n return gaugeData;\r\n }\r\n\r\n /** Note: public for testability */\r\n public drawViewPort(drawOptions: GaugeVisualProperties): void {\r\n debug.assertAnyValue(drawOptions, \"Gauge options\");\r\n\r\n let separation = this.settings.kpiBands.separationRadians;\r\n let innerRadiusFactor = Gauge.InnerRadiusFactor;\r\n\r\n let backgroudArc = this.backgroundArc;\r\n let color = this.color;\r\n\r\n let attrs: KpiArcAttributes[] = [{\r\n fill: color(0),\r\n start: -Math.PI / 2,\r\n end: -Math.PI / 2 + Math.PI / 4 - separation\r\n }, {\r\n fill: color(1),\r\n start: -Math.PI / 2 + Math.PI * 1 / 4 + separation,\r\n end: -Math.PI / 2 + Math.PI * 3 / 4 - separation\r\n }, {\r\n fill: color(2),\r\n start: -Math.PI / 2 + Math.PI * 3 / 4 + separation,\r\n end: Math.PI / 2\r\n }];\r\n\r\n let radius = drawOptions.radius;\r\n let transformString = drawOptions.transformString;\r\n this.updateKpiBands(radius, innerRadiusFactor, transformString, attrs);\r\n\r\n backgroudArc\r\n .innerRadius(radius * innerRadiusFactor)\r\n .outerRadius(radius)\r\n .startAngle(-Math.PI / 2)\r\n .endAngle(Math.PI / 2);\r\n\r\n this.backgroundArcPath\r\n .attr(\"d\", backgroudArc)\r\n .attr(\"transform\", transformString);\r\n\r\n let foregroundArc = this.foregroundArc;\r\n\r\n foregroundArc\r\n .innerRadius(radius * innerRadiusFactor)\r\n .outerRadius(radius)\r\n .startAngle(-Math.PI / 2);\r\n\r\n this.foregroundArcPath\r\n .datum({ endAngle: this.lastAngle })\r\n .attr(\"transform\", transformString)\r\n .attr(\"d\", foregroundArc);\r\n\r\n let innerRadiusOfArc = drawOptions.innerRadiusOfArc;\r\n let left = drawOptions.left;\r\n let top = drawOptions.top;\r\n let margin = drawOptions.margin;\r\n let height = drawOptions.height;\r\n let targetSettings = this.targetSettings;\r\n \r\n if (!this.settings.targetLine.show || !this.isValid() || !$.isNumeric(targetSettings.target)) {\r\n this.removeTargetElements();\r\n } else {\r\n if (targetSettings.min > targetSettings.target || targetSettings.max < targetSettings.target) {\r\n this.removeTargetElements();\r\n } else {\r\n this.updateTargetLine(radius, innerRadiusOfArc, left, top);\r\n this.renderTarget(radius, height, drawOptions.width, margin);\r\n }\r\n }\r\n this.svg.attr('height', this.currentViewport.height).attr('width', this.currentViewport.width);\r\n }\r\n \r\n public getValueAngle(): number {\r\n let settings = this.data.targetSettings;\r\n let total = this.data.total;\r\n if (!this.isValid() || !$.isNumeric(total)) {\r\n return 0;\r\n }\r\n \r\n let adjustedTotal = Math.min(Math.max(total, settings.min), settings.max);\r\n let angle: number = (adjustedTotal - settings.min) / (settings.max - settings.min);\r\n \r\n return angle;\r\n }\r\n\r\n private createTicks(): string[] {\r\n let targetSettings = this.targetSettings;\r\n let arr: string[] = [];\r\n \r\n let minFormatter = this.getFormatter(this.data.dataLabelsSettings, this.data.minColumnMetadata, targetSettings.max);\r\n arr.push(minFormatter.format(targetSettings.min));\r\n \r\n let maxFormatter = this.getFormatter(this.data.dataLabelsSettings, this.data.maxColumnMetadata, targetSettings.max);\r\n arr.push(maxFormatter.format(targetSettings.max));\r\n \r\n return arr;\r\n }\r\n\r\n private updateInternal(suppressAnimations: boolean) {\r\n let height = this.gaugeVisualProperties.height;\r\n let width = this.gaugeVisualProperties.width;\r\n let radius = this.gaugeVisualProperties.radius;\r\n let margin: IMargin = this.margin;\r\n let duration = AnimatorCommon.GetAnimationDuration(this.animator, suppressAnimations);\r\n\r\n let data = this.data;\r\n let lastAngle = this.lastAngle = -Math.PI / 2 + Math.PI * this.getValueAngle();\r\n\r\n let ticks = this.createTicks();\r\n \r\n this.foregroundArcPath\r\n .transition()\r\n .ease(this.settings.transition.ease)\r\n .duration(duration)\r\n .call(this.arcTween, [lastAngle, this.foregroundArc]);\r\n\r\n this.renderMinMaxLabels(ticks, radius, height, width, margin);\r\n this.updateVisualConfigurations();\r\n this.updateVisualStyles();\r\n if (this.tooltipsEnabled) {\r\n TooltipManager.addTooltip(this.foregroundArcPath, (tooltipEvent: TooltipEvent) => data.tooltipInfo);\r\n TooltipManager.addTooltip(this.backgroundArcPath, (tooltipEvent: TooltipEvent) => data.tooltipInfo);\r\n }\r\n }\r\n\r\n private updateVisualStyles() {\r\n let fillColor: string = this.data.dataLabelsSettings.labelColor || this.style.labelText.color.value;\r\n this.mainGraphicsContext.selectAll('text')\r\n .style({\r\n 'fill': fillColor,\r\n });\r\n }\r\n\r\n private updateVisualConfigurations() {\r\n let configOptions = this.settings;\r\n let dataPointSettings = this.data.dataPointSettings;\r\n\r\n this.mainGraphicsContext\r\n .select('line')\r\n .attr({\r\n stroke: dataPointSettings.targetColor,\r\n 'stroke-width': configOptions.targetLine.thickness,\r\n });\r\n\r\n this.backgroundArcPath.style('fill', configOptions.arcColors.background);\r\n this.foregroundArcPath.style('fill', dataPointSettings.fillColor);\r\n }\r\n\r\n private renderMinMaxLabels(ticks: string[], radius: number, height: number, width: number, margin: IMargin) {\r\n this.svg.selectAll(Gauge.LabelText.selector).remove();\r\n if (!this.data.dataLabelsSettings.show) return;\r\n\r\n let total = ticks.length;\r\n let divisor = total - 1;\r\n let top = (radius + (height - radius) / 2 + margin.top);\r\n let showMinMaxLabelsOnBottom = this.showMinMaxLabelsOnBottom();\r\n let fontSize = PixelConverter.fromPoint(this.data.dataLabelsSettings.fontSize || NewDataLabelUtils.DefaultLabelFontSizeInPt);\r\n let padding = this.settings.labels.padding;\r\n\r\n for (let index = 0; index < total; index++) {\r\n let textProperties: TextProperties = {\r\n text: ticks[index],\r\n fontFamily: dataLabelUtils.LabelTextProperties.fontFamily,\r\n fontSize: dataLabelUtils.LabelTextProperties.fontSize,\r\n fontWeight: dataLabelUtils.LabelTextProperties.fontWeight,\r\n };\r\n\r\n if (this.showSideNumbersLabelText()) {\r\n\r\n let x = (margin.left + width / 2) - (radius * Math.cos(Math.PI * index / divisor));\r\n let y = top - (radius * Math.sin(Math.PI * index / divisor));\r\n let anchor: string;\r\n let onRight = index === 1;\r\n let onBottom = false;\r\n\r\n if (showMinMaxLabelsOnBottom) {\r\n // If this is a min or max label and we're showing them on the bottom rather than the sides\r\n // Adjust the label display properties to appear under the arc\r\n onBottom = true;\r\n y += padding / 2;\r\n\r\n // Align the labels with the outer edge of the arc\r\n anchor = onRight ? 'end' : 'start';\r\n textProperties.text = TextMeasurementService.getTailoredTextOrDefault(textProperties, radius);\r\n }\r\n else {\r\n // For all other labels, display around the arc\r\n anchor = onRight ? 'start' : 'end';\r\n x += padding * (onRight ? 1 : -1);\r\n }\r\n\r\n let text = this.mainGraphicsContext\r\n .append('text')\r\n .attr({\r\n 'x': x,\r\n 'y': y,\r\n 'dy': onBottom ? fontSize : 0,\r\n 'class': Gauge.LabelText.class\r\n })\r\n .style({\r\n 'text-anchor': anchor,\r\n 'font-size': fontSize\r\n })\r\n .text(textProperties.text)\r\n .append('title').text(textProperties.text);\r\n\r\n if (!onBottom)\r\n this.truncateTextIfNeeded(text, x, onRight);\r\n }\r\n }\r\n }\r\n\r\n private truncateTextIfNeeded(text: D3.Selection, positionX: number, onRight: boolean) {\r\n let availableSpace = (onRight ? this.currentViewport.width - positionX : positionX);\r\n text.call(AxisHelper.LabelLayoutStrategy.clip,\r\n availableSpace,\r\n TextMeasurementService.svgEllipsis);\r\n }\r\n\r\n private getFormatter(dataLabelSettings: VisualDataLabelsSettings, metadataColumn: DataViewMetadataColumn, maxValue?: number): IValueFormatter {\r\n let max = dataLabelSettings.displayUnits === 0 ? maxValue : null;\r\n if (!metadataColumn || !metadataColumn.objects || _.isEmpty(DataViewObjects.getValue<string>(metadataColumn.objects, Gauge.formatStringProp))) {\r\n metadataColumn = this.data.metadataColumn;\r\n }\r\n \r\n let formatString: string = valueFormatter.getFormatString(metadataColumn, Gauge.formatStringProp);\r\n let precision = dataLabelUtils.getLabelPrecision(dataLabelSettings.precision, formatString);\r\n let valueFormatterOptions: ValueFormatterOptions = dataLabelUtils.getOptionsForLabelFormatter(dataLabelSettings, formatString, max, precision);\r\n valueFormatterOptions.formatSingleValues = dataLabelSettings.displayUnits > 0 ? false : true;\r\n return valueFormatter.create(valueFormatterOptions);\r\n }\r\n\r\n private renderTarget(radius: number, height: number, width: number, margin: IMargin) {\r\n let targetSettings = this.targetSettings;\r\n\r\n let target = targetSettings.target;\r\n let tRatio = this.getTargetRatio();\r\n let top = (radius + (height - radius) / 2 + margin.top);\r\n let flag = tRatio > 0.5;\r\n let padding = this.settings.labels.padding;\r\n let anchor = flag ? 'start' : 'end';\r\n let formatter = this.getFormatter(this.data.dataLabelsSettings, this.data.targetColumnMetadata, targetSettings.max);\r\n let maxRatio = Math.asin(Gauge.MinDistanceFromBottom / radius) / Math.PI;\r\n\r\n let finalRatio = tRatio < maxRatio || tRatio > (1 - maxRatio)\r\n ? flag\r\n ? 1 - maxRatio\r\n : maxRatio\r\n : tRatio;\r\n\r\n let targetX = (margin.left + width / 2) - ((radius + padding) * Math.cos(Math.PI * finalRatio));\r\n let targetY = top - ((radius + padding) * Math.sin(Math.PI * finalRatio));\r\n\r\n if (!this.targetText) {\r\n this.targetText = this.mainGraphicsContext\r\n .append('text')\r\n .classed(Gauge.TargetText.class, true);\r\n }\r\n\r\n this.targetText\r\n .attr({\r\n 'x': targetX,\r\n 'y': targetY,\r\n })\r\n .style({\r\n 'text-anchor': anchor,\r\n 'display': this.showTargetLabel ? '' : 'none',\r\n 'font-size': this.style.labelText.fontSize\r\n })\r\n .text(formatter.format(target));\r\n\r\n this.truncateTextIfNeeded(this.targetText, targetX, flag);\r\n this.targetText.call(tooltipUtils.tooltipUpdate, [formatter.format(target)]);\r\n\r\n if (!this.targetConnector) {\r\n this.targetConnector = this.mainGraphicsContext\r\n .append('line')\r\n .classed(Gauge.TargetConnector.class, true);\r\n }\r\n\r\n // Hide the target connector if the text is going to align with the target line in the arc\r\n // It should only be shown if the target text is displaced (ex. when the target is very close to min/max)\r\n if (tRatio === finalRatio) {\r\n this.targetConnector.style('display', 'none');\r\n }\r\n else {\r\n this.targetConnector\r\n .attr({\r\n 'x1': (margin.left + width / 2) - (radius * Math.cos(Math.PI * tRatio)),\r\n 'y1': top - (radius * Math.sin(Math.PI * tRatio)),\r\n 'x2': targetX,\r\n 'y2': targetY\r\n })\r\n .style({\r\n 'stroke-width': this.settings.targetLine.thickness,\r\n 'stroke': this.settings.targetLine.color,\r\n 'display': ''\r\n });\r\n }\r\n }\r\n\r\n private arcTween(transition, arr): void {\r\n transition.attrTween('d', (d) => {\r\n let interpolate = d3.interpolate(d.endAngle, arr[0]);\r\n return (t) => {\r\n d.endAngle = interpolate(t);\r\n return arr[1](d);\r\n };\r\n });\r\n }\r\n\r\n private showMinMaxLabelsOnBottom(): boolean {\r\n // More vertical space, put labels on bottom\r\n if (this.currentViewport.height > this.currentViewport.width)\r\n return true;\r\n\r\n // We want to show the start/end ticks on the bottom when there\r\n // is insufficient space for the left and right label text\r\n if (this.data && this.gaugeVisualProperties) {\r\n let ticks = this.createTicks();\r\n let visualWhitespace = (this.currentViewport.width - (this.gaugeVisualProperties.radius * 2)) / 2;\r\n let maxLabelWidth = visualWhitespace - this.settings.labels.padding;\r\n let textProperties: TextProperties = TextMeasurementService.getMeasurementProperties($(this.svg.node()));\r\n textProperties.fontSize = PixelConverter.fromPoint(this.data.dataLabelsSettings.fontSize || NewDataLabelUtils.DefaultLabelFontSizeInPt);\r\n\r\n let width: number;\r\n for (let tickValue of [ticks[0], ticks[ticks.length - 1]]) {\r\n textProperties.text = tickValue;\r\n width = TextMeasurementService.measureSvgTextWidth(textProperties);\r\n if (width > maxLabelWidth)\r\n return true;\r\n }\r\n }\r\n\r\n return false;\r\n }\r\n\r\n private setMargins(): void {\r\n if (this.gaugeSmallViewPortProperties) {\r\n if (this.gaugeSmallViewPortProperties.smallGaugeMarginsOnSmallViewPort && (this.currentViewport.height < this.gaugeSmallViewPortProperties.MinHeightGaugeSideNumbersVisible)) {\r\n let margins = this.gaugeSmallViewPortProperties.GaugeMarginsOnSmallViewPort;\r\n this.margin = { top: margins, bottom: margins, left: margins, right: margins };\r\n return;\r\n }\r\n }\r\n\r\n this.margin = {\r\n top: Gauge.DefaultTopBottomMargin,\r\n bottom: Gauge.DefaultTopBottomMargin,\r\n left: Gauge.DefaultLeftRightMargin,\r\n right: Gauge.DefaultLeftRightMargin\r\n };\r\n\r\n // If we're not showing side labels, reduce the margin so that the gauge has more room to display\r\n if (!this.showSideNumbersLabelText() || this.showMinMaxLabelsOnBottom()) {\r\n let targetSettings = this.targetSettings;\r\n\r\n if (this.showTargetLabel) {\r\n // If we're showing the target label, only reduce the margin on the side that doesn't have a target label\r\n let tRatio = (targetSettings.target - targetSettings.min) / (targetSettings.max - targetSettings.min);\r\n\r\n if (tRatio > 0.5)\r\n this.margin.left = Gauge.ReducedLeftRightMargin;\r\n else\r\n this.margin.right = Gauge.ReducedLeftRightMargin;\r\n }\r\n else {\r\n // Otherwise, reduce both margins\r\n this.margin.left = this.margin.right = Gauge.ReducedLeftRightMargin;\r\n }\r\n }\r\n\r\n let fontSize = 0;\r\n if (this.data && this.data.dataLabelsSettings && this.data.dataLabelsSettings.fontSize && this.data.dataLabelsSettings.fontSize >= NewDataLabelUtils.DefaultLabelFontSizeInPt) {\r\n fontSize = PixelConverter.fromPointToPixel(this.data.dataLabelsSettings.fontSize - NewDataLabelUtils.DefaultLabelFontSizeInPt);\r\n }\r\n\r\n if (fontSize !== 0) {\r\n this.margin.bottom += fontSize;\r\n this.margin.left += fontSize;\r\n this.margin.right += fontSize;\r\n }\r\n }\r\n\r\n private showSideNumbersLabelText(): boolean {\r\n if (this.gaugeSmallViewPortProperties) {\r\n if (this.gaugeSmallViewPortProperties.hideGaugeSideNumbersOnSmallViewPort) {\r\n if (this.currentViewport.height < this.gaugeSmallViewPortProperties.MinHeightGaugeSideNumbersVisible) {\r\n return false;\r\n }\r\n }\r\n }\r\n\r\n return true;\r\n }\r\n }\r\n}\n\n\n/** WEBPACK FOOTER **\n ** ./src/Clients/Visuals/visuals/gauge.ts\n **/","/*\r\n * Power BI Visualizations\r\n *\r\n * Copyright (c) Microsoft Corporation\r\n * All rights reserved. \r\n * MIT License\r\n *\r\n * Permission is hereby granted, free of charge, to any person obtaining a copy\r\n * of this software and associated documentation files (the \"\"Software\"\"), to deal\r\n * in the Software without restriction, including without limitation the rights\r\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\r\n * copies of the Software, and to permit persons to whom the Software is\r\n * furnished to do so, subject to the following conditions:\r\n * \r\n * The above copyright notice and this permission notice shall be included in \r\n * all copies or substantial portions of the Software.\r\n * \r\n * THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR \r\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, \r\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE \r\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER \r\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\r\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\r\n * THE SOFTWARE.\r\n */\r\n\r\n/// <reference path=\"../_references.ts\"/>\r\n\r\nmodule powerbi.visuals {\r\n import Utility = jsCommon.Utility;\r\n\r\n export interface ImageDataViewObjects extends DataViewObjects {\r\n general: ImageDataViewObject;\r\n imageScaling: ImageScalingDataViewObject;\r\n }\r\n\r\n export interface ImageDataViewObject extends DataViewObject {\r\n imageUrl: string;\r\n }\r\n\r\n export interface ImageScalingDataViewObject extends DataViewObject {\r\n imageScalingType: string;\r\n }\r\n\r\n export class ImageVisual implements IVisual {\r\n\r\n private element: JQuery;\r\n private imageBackgroundElement: JQuery;\r\n private scalingType: string = imageScalingType.normal;\r\n\r\n public init(options: VisualInitOptions) {\r\n this.element = options.element;\r\n }\r\n\r\n public enumerateObjectInstances(options: EnumerateVisualObjectInstancesOptions): VisualObjectInstance[] {\r\n switch (options.objectName) {\r\n case 'imageScaling':\r\n return this.enumerateImageScaling();\r\n }\r\n return null;\r\n }\r\n\r\n private enumerateImageScaling(): VisualObjectInstance[] {\r\n return [{\r\n selector: null,\r\n objectName: 'imageScaling',\r\n properties: {\r\n imageScalingType: this.scalingType,\r\n }\r\n }];\r\n }\r\n\r\n public update(options: VisualUpdateOptions): void {\r\n let dataViews = options.dataViews;\r\n if (!dataViews || dataViews.length === 0)\r\n return;\r\n\r\n let objects = <ImageDataViewObjects>dataViews[0].metadata.objects;\r\n if (!objects || !objects.general)\r\n return;\r\n\r\n let div: JQuery = this.imageBackgroundElement;\r\n if (!div) {\r\n div = $(\"<div class='imageBackground' />\");\r\n this.imageBackgroundElement = div;\r\n this.imageBackgroundElement.appendTo(this.element);\r\n }\r\n\r\n let viewport = options.viewport;\r\n div.css('height', viewport.height);\r\n\r\n if (objects.imageScaling)\r\n this.scalingType = objects.imageScaling.imageScalingType.toString();\r\n else\r\n this.scalingType = imageScalingType.normal;\r\n\r\n let imageUrl = objects.general.imageUrl;\r\n if (Utility.isLocalUrl(imageUrl))\r\n div.css(\"backgroundImage\", \"url(\" + imageUrl + \")\");\r\n\r\n if (this.scalingType === imageScalingType.fit)\r\n div.css(\"background-size\", \"100% 100%\");\r\n else if (this.scalingType === imageScalingType.fill)\r\n div.css(\"background-size\", \"cover\");\r\n else\r\n div.css(\"background-size\", \"contain\");\r\n }\r\n }\r\n} \n\n\n/** WEBPACK FOOTER **\n ** ./src/Clients/Visuals/visuals/imageVisual.ts\n **/","/*\r\n * Power BI Visualizations\r\n *\r\n * Copyright (c) Microsoft Corporation\r\n * All rights reserved. \r\n * MIT License\r\n *\r\n * Permission is hereby granted, free of charge, to any person obtaining a copy\r\n * of this software and associated documentation files (the \"\"Software\"\"), to deal\r\n * in the Software without restriction, including without limitation the rights\r\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\r\n * copies of the Software, and to permit persons to whom the Software is\r\n * furnished to do so, subject to the following conditions:\r\n * \r\n * The above copyright notice and this permission notice shall be included in \r\n * all copies or substantial portions of the Software.\r\n * \r\n * THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR \r\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, \r\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE \r\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER \r\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\r\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\r\n * THE SOFTWARE.\r\n */\r\n\r\n/// <reference path=\"../_references.ts\"/>\r\n\r\nmodule powerbi.visuals {\r\n import DataRoleHelper = powerbi.data.DataRoleHelper;\r\n\r\n export interface KPIStatusWithHistoryData {\r\n dataPoints: KPIStatusWithHistoryDataPoint[];\r\n directionType: string;\r\n goals: number[];\r\n formattedGoalString: string;\r\n actual: number;\r\n targetExists: boolean;\r\n historyExists: boolean;\r\n indicatorExists: boolean;\r\n trendExists: boolean;\r\n formattedValue: string;\r\n showGoal: boolean;\r\n showDistanceFromGoal: boolean;\r\n showTrendLine: boolean;\r\n }\r\n\r\n export interface KPIStatusWithHistoryDataPoint {\r\n x: number;\r\n y: number;\r\n actual: number;\r\n goals: number[];\r\n }\r\n\r\n export class KPIStatusWithHistory implements IVisual {\r\n\r\n public static directionTypeStringProp: DataViewObjectPropertyIdentifier = { objectName: 'status', propertyName: 'direction' };\r\n public static showKPIGoal: DataViewObjectPropertyIdentifier = { objectName: 'goals', propertyName: 'showGoal' };\r\n public static showKPIDistance: DataViewObjectPropertyIdentifier = { objectName: 'goals', propertyName: 'showDistance' };\r\n public static showKPITrendLine: DataViewObjectPropertyIdentifier = { objectName: 'trendline', propertyName: 'show' };\r\n public static indicatorDisplayUnitsProp: DataViewObjectPropertyIdentifier = { objectName: 'indicator', propertyName: 'indicatorDisplayUnits' };\r\n public static indicatorPrecisionProp: DataViewObjectPropertyIdentifier = { objectName: 'indicator', propertyName: 'indicatorPrecision' };\r\n\r\n public static status = { INCREASE: \"increase\", DROP: \"drop\", IN_BETWEEN: \"in-between\", NOGOAL: \"no-goal\" };\r\n public static statusBandingType = { Below: \"BELOW\", Above: \"ABOVE\" };\r\n public static actualTextConsts = { VERTICAL_OFFSET_FROM_HALF_HEIGHT: 20, FONT_WIDTH_FACTOR: 14, RIGHT_MARGIN: 10 };\r\n\r\n public static kpiRedClass = 'kpi-visual-red';\r\n public static kpiYellowClass = 'kpi-visual-yellow';\r\n public static kpiGreenClass = 'kpi-visual-green';\r\n public static kpiTextGreyClass = 'kpi-visual-text-grey';\r\n public static kpiGraphGreyClass = 'kpi-visual-graph-grey';\r\n\r\n public static allColorClasses = KPIStatusWithHistory.kpiRedClass + ' ' + KPIStatusWithHistory.kpiYellowClass + ' ' + KPIStatusWithHistory.kpiGreenClass + ' ' + KPIStatusWithHistory.kpiTextGreyClass + ' ' + KPIStatusWithHistory.kpiGraphGreyClass;\r\n\r\n public static trendAreaFilePercentage = 1;\r\n\r\n public static estimatedIconHeightInPx = 9;\r\n\r\n public static indicatorTextSizeInPx = 60;\r\n\r\n private svg: D3.Selection;\r\n private dataView: DataView;\r\n private mainGroupElement: D3.Selection;\r\n private kpiActualText: D3.Selection;\r\n private absoluteGoalDistanceText: D3.Selection;\r\n private areaFill: D3.Selection;\r\n private host: IVisualHostServices;\r\n private exclamationMarkIcon: D3.Selection;\r\n private successMarkIcon: D3.Selection;\r\n private betweenIcon: D3.Selection;\r\n private rootElement: D3.Selection;\r\n private indicatorTextContainer: D3.Selection;\r\n private textContainer: D3.Selection;\r\n\r\n private static getLocalizedString: (stringId: string) => string;\r\n\r\n private static defaultCardFormatSetting: CardFormatSetting;\r\n private static defaultLabelSettings;\r\n\r\n public init(options: VisualInitOptions): void {\r\n KPIStatusWithHistory.getLocalizedString = options.host.getLocalizedString;\r\n this.rootElement = d3.select(options.element.get(0)).append('div').attr('text-align', 'center').classed('kpiVisual', true);\r\n this.svg = this.rootElement.append('svg');\r\n let mainGroupElement = this.mainGroupElement = this.svg.append('g');\r\n this.areaFill = mainGroupElement.append(\"path\");\r\n\r\n this.textContainer = this.rootElement.append(\"div\").classed('textContainer', true);\r\n\r\n this.indicatorTextContainer = this.textContainer.append(\"div\").classed('indicatorText', true);\r\n\r\n this.absoluteGoalDistanceText = this.textContainer.append(\"div\").classed('goalText', true);\r\n\r\n this.kpiActualText = this.indicatorTextContainer.append(\"div\").attr('id', 'indicatorText');\r\n\r\n this.initIcons();\r\n\r\n this.host = options.host;\r\n }\r\n\r\n public update(options: VisualUpdateOptions) {\r\n if (!options.dataViews || !options.dataViews[0]) return;\r\n let dataView = this.dataView = options.dataViews[0];\r\n let viewport = options.viewport;\r\n \r\n // We must have at least one measure\r\n if ((!dataView.categorical || !dataView.categorical.values || dataView.categorical.values.length < 1) &&\r\n (!dataView.categorical || !dataView.categorical.categories || dataView.categorical.categories.length < 1)) {\r\n this.svg.attr(\"visibility\", \"hidden\");\r\n this.textContainer.attr(\"style\", \"display:none\");\r\n return;\r\n }\r\n this.svg.attr(\"visibility\", \"visible\");\r\n\r\n let kpiViewModel: KPIStatusWithHistoryData = KPIStatusWithHistory.converter(\r\n dataView,\r\n viewport,\r\n KPIStatusWithHistory.getProp_KPIDirection(dataView));\r\n\r\n this.render(kpiViewModel, viewport);\r\n }\r\n\r\n private initIcons() {\r\n this.successMarkIcon = this.indicatorTextContainer.append(\"div\").classed('powervisuals-glyph checkmark kpi-visual-green', true);\r\n this.betweenIcon = this.indicatorTextContainer.append('div').classed('powervisuals-glyph circle-small kpi-visual-yellow', true);\r\n this.exclamationMarkIcon = this.indicatorTextContainer.append(\"div\").classed('powervisuals-glyph exclamation kpi-visual-red', true);\r\n \r\n this.successMarkIcon.attr('style', 'display:none');\r\n this.betweenIcon.attr('style', 'display:none');\r\n this.exclamationMarkIcon.attr('style', 'display:none');\r\n }\r\n\r\n private render(kpiViewModel: KPIStatusWithHistoryData, viewport: IViewport) {\r\n\r\n this.setShowDataMissingWarning(!(kpiViewModel.indicatorExists && kpiViewModel.trendExists));\r\n\r\n if (kpiViewModel.dataPoints.length === 0 || !kpiViewModel.indicatorExists || !kpiViewModel.trendExists) {\r\n this.areaFill.attr(\"visibility\", \"hidden\");\r\n this.svg.attr(\"visibility\", \"hidden\");\r\n this.textContainer.attr(\"style\", \"display:none\");\r\n return;\r\n }\r\n\r\n this.svg.attr({\r\n 'height': viewport.height,\r\n 'width': viewport.width,\r\n });\r\n\r\n let status = KPIStatusWithHistory.status.NOGOAL;\r\n if (kpiViewModel.targetExists && kpiViewModel.indicatorExists && kpiViewModel.trendExists) {\r\n status = GetStatus(kpiViewModel.actual, kpiViewModel.goals, kpiViewModel.directionType);\r\n }\r\n\r\n let actualText = kpiViewModel.formattedValue;\r\n\r\n let calculatedHeight = KPIStatusWithHistory.indicatorTextSizeInPx;\r\n\r\n this.textContainer\r\n .attr('style', \"width:\" + viewport.width + \"px;\" +\r\n \"top:\" + ((viewport.height - calculatedHeight) / 2) + \"px\");\r\n\r\n this.kpiActualText\r\n .classed(KPIStatusWithHistory.allColorClasses, false)\r\n .classed(GetTextColorClassByStatus(status), true)\r\n .attr(\"text-anchor\", \"middle\")\r\n .text(actualText);\r\n\r\n let icon: D3.Selection = null;\r\n\r\n switch (status) {\r\n case KPIStatusWithHistory.status.INCREASE:\r\n icon = this.successMarkIcon;\r\n this.exclamationMarkIcon.attr(\"style\", \"display:none\");\r\n this.betweenIcon.attr(\"style\", \"display:none\");\r\n break;\r\n case KPIStatusWithHistory.status.IN_BETWEEN:\r\n icon = this.betweenIcon;\r\n this.exclamationMarkIcon.attr(\"style\", \"display:none\");\r\n this.successMarkIcon.attr(\"style\", \"display:none\");\r\n break;\r\n case KPIStatusWithHistory.status.DROP:\r\n icon = this.exclamationMarkIcon;\r\n this.successMarkIcon.attr(\"style\", \"display:none\");\r\n this.betweenIcon.attr(\"style\", \"display:none\");\r\n break;\r\n default:\r\n this.exclamationMarkIcon.attr(\"style\", \"display:none\");\r\n this.successMarkIcon.attr(\"style\", \"display:none\");\r\n this.betweenIcon.attr(\"style\", \"display:none\");\r\n }\r\n\r\n if (icon) {\r\n icon.attr('style', 'font-size:12px');\r\n }\r\n\r\n let shownGoalString = kpiViewModel.showGoal ? kpiViewModel.formattedGoalString + \" \" : \"\";\r\n let shownDistanceFromGoalString = kpiViewModel.showDistanceFromGoal ? getDistanceFromGoalInPercentageString(kpiViewModel.actual, kpiViewModel.goals, kpiViewModel.directionType) : \"\";\r\n\r\n this.absoluteGoalDistanceText\r\n .attr(\"text-anchor\", \"middle\")\r\n .text(shownGoalString + shownDistanceFromGoalString);\r\n\r\n if (kpiViewModel.showTrendLine && kpiViewModel.historyExists) {\r\n let area = d3.svg.area()\r\n .x(function (d) { return d.x; })\r\n .y0(viewport.height)\r\n .y1(function (d) { return d.y; });\r\n\r\n this.areaFill\r\n .classed(KPIStatusWithHistory.allColorClasses, false)\r\n .classed(GetGraphColorClassByStatus(status), true)\r\n .attr(\"d\", area(kpiViewModel.dataPoints))\r\n .attr(\"stroke\", \"none\")\r\n .attr(\"visibility\", \"visible\")\r\n .attr('fill-opacity', 0.2);\r\n } else {\r\n this.areaFill.attr(\"visibility\", \"hidden\");\r\n }\r\n\r\n }\r\n\r\n private setShowDataMissingWarning(show: boolean) {\r\n this.host.setWarnings(show ? [new VisualKPIDataMissingWarning()] : []);\r\n }\r\n\r\n private static getDefaultFormatSettings(): CardFormatSetting {\r\n return {\r\n labelSettings: dataLabelUtils.getDefaultLabelSettings(true, Card.DefaultStyle.value.color),\r\n textSize: 27,\r\n wordWrap: false\r\n };\r\n }\r\n\r\n private static getFormatString(column: DataViewMetadataColumn): string {\r\n debug.assertAnyValue(column, 'column');\r\n return valueFormatter.getFormatString(column, AnimatedText.formatStringProp);\r\n }\r\n\r\n private static getProp_Show_KPIGoal(dataView: DataView) {\r\n if (dataView && dataView.metadata) {\r\n return DataViewObjects.getValue<boolean>(dataView.metadata.objects, KPIStatusWithHistory.showKPIGoal, true);\r\n }\r\n\r\n return true;\r\n }\r\n\r\n private static getProp_Show_KPITrendLine(dataView: DataView) {\r\n if (dataView && dataView.metadata) {\r\n return DataViewObjects.getValue<boolean>(dataView.metadata.objects, KPIStatusWithHistory.showKPITrendLine, true);\r\n }\r\n\r\n return true;\r\n }\r\n\r\n private static getProp_Show_KPIDistance(dataView: DataView) {\r\n if (dataView && dataView.metadata) {\r\n return DataViewObjects.getValue<boolean>(dataView.metadata.objects, KPIStatusWithHistory.showKPIDistance, true);\r\n }\r\n\r\n return true;\r\n }\r\n\r\n private static getProp_KPIDirection(dataView: DataView) {\r\n if (dataView && dataView.metadata) {\r\n return DataViewObjects.getValue<string>(dataView.metadata.objects, KPIStatusWithHistory.directionTypeStringProp, kpiDirection.positive);\r\n }\r\n\r\n return kpiDirection.positive;\r\n }\r\n\r\n private static getProp_Indicator_DisplayUnits(dataView: DataView) {\r\n KPIStatusWithHistory.initDefaultLabelSettings();\r\n if (dataView && dataView.metadata) {\r\n return DataViewObjects.getValue<number>(dataView.metadata.objects, KPIStatusWithHistory.indicatorDisplayUnitsProp, KPIStatusWithHistory.defaultLabelSettings.displayUnits);\r\n }\r\n\r\n return KPIStatusWithHistory.defaultLabelSettings.displayUnits;\r\n }\r\n\r\n private static getProp_Indicator_Precision(dataView: DataView) {\r\n KPIStatusWithHistory.initDefaultLabelSettings();\r\n if (dataView && dataView.metadata) {\r\n return DataViewObjects.getValue<number>(dataView.metadata.objects, KPIStatusWithHistory.indicatorPrecisionProp, KPIStatusWithHistory.defaultLabelSettings.precision);\r\n }\r\n\r\n return KPIStatusWithHistory.defaultLabelSettings.precision;\r\n }\r\n\r\n private static initDefaultLabelSettings() {\r\n if (!KPIStatusWithHistory.defaultCardFormatSetting) {\r\n KPIStatusWithHistory.defaultCardFormatSetting = KPIStatusWithHistory.getDefaultFormatSettings();\r\n KPIStatusWithHistory.defaultLabelSettings = KPIStatusWithHistory.defaultCardFormatSetting.labelSettings;\r\n }\r\n }\r\n\r\n private static getFormattedValue(metaDataColumn: DataViewMetadataColumn, theValue: number, precision: number, displayUnits: number, displayUnitSystemType: DisplayUnitSystemType = DisplayUnitSystemType.WholeUnits): string {\r\n let isDefaultDisplayUnit = displayUnits === 0;\r\n let formatter = valueFormatter.create({\r\n format: KPIStatusWithHistory.getFormatString(metaDataColumn),\r\n value: displayUnits,\r\n precision: precision,\r\n displayUnitSystemType: displayUnitSystemType,\r\n formatSingleValues: isDefaultDisplayUnit ? true : false,\r\n allowFormatBeautification: true,\r\n columnType: metaDataColumn ? metaDataColumn.type : undefined\r\n });\r\n return formatter.format(theValue);\r\n }\r\n\r\n private static getFormattedGoalString(metaDataColumn: DataViewMetadataColumn, goals: any[], precision: number, displayUnits: number): string {\r\n if (!goals || goals.length === 0) {\r\n return \"\";\r\n }\r\n\r\n let goalsString = KPIStatusWithHistory.getLocalizedString('Visual_KPI_Goal_Title') + \": \" + KPIStatusWithHistory.getFormattedValue(metaDataColumn, goals[0], precision, displayUnits);\r\n\r\n if (goals.length === 2) {\r\n goalsString += \", \" + KPIStatusWithHistory.getFormattedValue(metaDataColumn, goals[1], precision, displayUnits);\r\n }\r\n\r\n return goalsString;\r\n }\r\n\r\n public static converter(dataView: DataView, viewPort: powerbi.IViewport, directionType: string): KPIStatusWithHistoryData {\r\n let dataPoints: KPIStatusWithHistoryDataPoint[] = [];\r\n let catDv: DataViewCategorical = dataView.categorical;\r\n let indicatorMetadataColumn: DataViewMetadataColumn = null;\r\n let goalMetadataColumn: DataViewMetadataColumn = null;\r\n let formattedGoalString = \"\";\r\n let formattedValue = \"\";\r\n let targetExists = false;\r\n let indicatorExists = false;\r\n let trendExists = false;\r\n\r\n let historyExists = true;\r\n if (!dataView.categorical.categories) {\r\n historyExists = false;\r\n }\r\n\r\n let values = catDv.values;\r\n\r\n let columns = dataView.metadata.columns;\r\n\r\n for (let column of columns) {\r\n if (DataRoleHelper.hasRole(column, 'Indicator')) {\r\n indicatorExists = true;\r\n indicatorMetadataColumn = column;\r\n }\r\n\r\n if (DataRoleHelper.hasRole(column, 'TrendLine')) {\r\n trendExists = true;\r\n }\r\n\r\n if (DataRoleHelper.hasRole(column, 'Goal')) {\r\n targetExists = true;\r\n goalMetadataColumn = column;\r\n }\r\n\r\n }\r\n\r\n if (!indicatorExists || !trendExists || !values || values.length === 0 || !values[0].values || !dataView.categorical.values) {\r\n return {\r\n dataPoints: dataPoints,\r\n directionType: directionType,\r\n actual: 0,\r\n goals: [],\r\n formattedGoalString,\r\n targetExists: targetExists,\r\n historyExists: historyExists,\r\n indicatorExists,\r\n trendExists,\r\n formattedValue,\r\n showGoal: false,\r\n showDistanceFromGoal: false,\r\n showTrendLine: false\r\n };\r\n }\r\n\r\n var category, categoryValues;\r\n if (historyExists) {\r\n category = catDv.categories[0]; // This only works if we have a category axis\r\n categoryValues = category.values;\r\n }\r\n\r\n let historyActualData = [];\r\n let historyGoalData = [];\r\n\r\n let indicatorColumns: DataViewValueColumn[] = KPIStatusWithHistory.getColumnsByRole(values, \"Indicator\");\r\n\r\n let goalColumns: DataViewValueColumn[] = KPIStatusWithHistory.getColumnsByRole(values, \"Goal\");\r\n\r\n let actualValue;\r\n\r\n for (let i = 0, len = values[0].values.length; i < len; i++) {\r\n\r\n actualValue = indicatorColumns[0].values[i];\r\n\r\n let goals = [];\r\n\r\n for (let goalCnt = 0; goalCnt < goalColumns.length; goalCnt++) {\r\n goals.push(goalColumns[goalCnt].values[i]);\r\n }\r\n\r\n historyGoalData.push(goals);\r\n\r\n historyActualData.push(actualValue);\r\n }\r\n\r\n let maxActualData = Math.max.apply(Math, historyActualData);\r\n let minActualData = Math.min.apply(Math, historyActualData);\r\n let areaMaxHight = viewPort.height * KPIStatusWithHistory.trendAreaFilePercentage;\r\n\r\n let precision = KPIStatusWithHistory.getProp_Indicator_Precision(dataView);\r\n let displayUnits = KPIStatusWithHistory.getProp_Indicator_DisplayUnits(dataView);\r\n\r\n for (let i = 0; i < historyActualData.length; i++) {\r\n let yPos = areaMaxHight * (historyActualData[i] - minActualData) / (maxActualData - minActualData);\r\n\r\n let selectorId = null;\r\n if (historyExists) {\r\n selectorId = SelectionId.createWithId(category.identity[i]).getSelector();\r\n }\r\n\r\n dataPoints.push({\r\n x: i * viewPort.width / (historyActualData.length - 1),\r\n y: viewPort.height - yPos,\r\n actual: historyActualData[i],\r\n goals: historyGoalData[i],\r\n });\r\n }\r\n\r\n var actual, goals;\r\n if (dataPoints.length > 0) {\r\n actual = dataPoints[dataPoints.length - 1].actual;\r\n goals = dataPoints[dataPoints.length - 1].goals;\r\n }\r\n\r\n if (dataPoints.length === 1) {\r\n historyExists = false;\r\n }\r\n\r\n formattedValue = KPIStatusWithHistory.getFormattedValue(indicatorMetadataColumn, actual, precision, displayUnits, DisplayUnitSystemType.DataLabels);\r\n\r\n formattedGoalString = KPIStatusWithHistory.getFormattedGoalString(goalMetadataColumn, goals, precision, displayUnits);\r\n\r\n let showGoal = KPIStatusWithHistory.getProp_Show_KPIGoal(dataView);\r\n\r\n let showDistanceFromGoal = KPIStatusWithHistory.getProp_Show_KPIDistance(dataView);\r\n\r\n let showTrendLine = KPIStatusWithHistory.getProp_Show_KPITrendLine(dataView);\r\n\r\n return {\r\n dataPoints: dataPoints,\r\n directionType: directionType,\r\n actual: actual,\r\n goals: goals,\r\n formattedGoalString,\r\n targetExists: targetExists,\r\n historyExists: historyExists,\r\n indicatorExists,\r\n trendExists,\r\n formattedValue,\r\n showGoal,\r\n showDistanceFromGoal,\r\n showTrendLine\r\n };\r\n }\r\n\r\n public static getColumnsByRole(values: DataViewValueColumns, roleString: string): DataViewValueColumn[] {\r\n let retval: DataViewValueColumn[] = [];\r\n\r\n for (let i = 0; i < values.length; i++) {\r\n if (DataRoleHelper.hasRole(values[i].source, roleString)) {\r\n retval.push(values[i]);\r\n }\r\n }\r\n\r\n return retval;\r\n }\r\n\r\n public enumerateObjectInstances(options: EnumerateVisualObjectInstancesOptions): VisualObjectInstance[] {\r\n let instances: VisualObjectInstance[] = [];\r\n let dataView = this.dataView;\r\n switch (options.objectName) {\r\n case 'indicator':\r\n instances.push({\r\n selector: null,\r\n objectName: 'indicator',\r\n properties: {\r\n indicatorDisplayUnits: KPIStatusWithHistory.getProp_Indicator_DisplayUnits(dataView),\r\n indicatorPrecision: KPIStatusWithHistory.getProp_Indicator_Precision(dataView)\r\n }\r\n });\r\n case 'trendline':\r\n instances.push({\r\n selector: null,\r\n objectName: 'trendline',\r\n properties: {\r\n show: KPIStatusWithHistory.getProp_Show_KPITrendLine(dataView)\r\n }\r\n });\r\n case 'goals':\r\n instances.push({\r\n selector: null,\r\n objectName: 'goals',\r\n properties: {\r\n showGoal: KPIStatusWithHistory.getProp_Show_KPIGoal(dataView),\r\n showDistance: KPIStatusWithHistory.getProp_Show_KPIDistance(dataView)\r\n }\r\n });\r\n case 'status':\r\n instances.push({\r\n selector: null,\r\n objectName: 'status',\r\n properties: {\r\n direction: KPIStatusWithHistory.getProp_KPIDirection(dataView)\r\n }\r\n });\r\n }\r\n return instances;\r\n }\r\n\r\n public destroy(): void {\r\n this.svg = null;\r\n }\r\n }\r\n\r\n function GetStatus(actual, goals: any[], directionType) {\r\n if (!goals || goals.length === 0) {\r\n return KPIStatusWithHistory.status.NOGOAL;\r\n }\r\n\r\n let maxGoal, minGoal;\r\n\r\n if (goals.length === 2) {\r\n maxGoal = Math.max.apply(Math, goals);\r\n minGoal = Math.min.apply(Math, goals);\r\n\r\n if (actual >= minGoal && actual <= maxGoal) {\r\n return KPIStatusWithHistory.status.IN_BETWEEN;\r\n }\r\n }\r\n else {\r\n maxGoal = goals[0];\r\n minGoal = goals[0];\r\n }\r\n\r\n switch (directionType) {\r\n case kpiDirection.positive:\r\n if (actual < minGoal) {\r\n return KPIStatusWithHistory.status.DROP;\r\n }\r\n break;\r\n\r\n case kpiDirection.negative:\r\n if (actual > maxGoal) {\r\n return KPIStatusWithHistory.status.DROP;\r\n }\r\n break;\r\n\r\n default:\r\n break;\r\n }\r\n\r\n return KPIStatusWithHistory.status.INCREASE;\r\n }\r\n\r\n function getDistanceFromGoalInPercentageString(actual, goals: any[], directionType) {\r\n if (!goals || goals.length !== 1 || goals[0] === 0) {\r\n return \"\";\r\n }\r\n\r\n let sign = \"+\";\r\n let distance;\r\n\r\n let goal: number = goals[0];\r\n\r\n distance = Math.abs(actual - goal);\r\n\r\n switch (directionType) {\r\n case kpiDirection.positive:\r\n if (actual < goal) {\r\n sign = \"-\";\r\n }\r\n break;\r\n\r\n case kpiDirection.negative:\r\n if (actual > goal) {\r\n sign = \"-\";\r\n }\r\n break;\r\n }\r\n\r\n let percent = Number((100 * distance / goal).toFixed(2));\r\n\r\n return \"(\" + sign + percent + \"%)\";\r\n }\r\n\r\n function GetTextColorClassByStatus(status) {\r\n switch (status) {\r\n case KPIStatusWithHistory.status.NOGOAL:\r\n return KPIStatusWithHistory.kpiTextGreyClass;\r\n\r\n case KPIStatusWithHistory.status.INCREASE:\r\n return KPIStatusWithHistory.kpiGreenClass;\r\n\r\n case KPIStatusWithHistory.status.IN_BETWEEN:\r\n return KPIStatusWithHistory.kpiYellowClass;\r\n\r\n case KPIStatusWithHistory.status.DROP:\r\n return KPIStatusWithHistory.kpiRedClass;\r\n }\r\n }\r\n\r\n function GetGraphColorClassByStatus(status) {\r\n switch (status) {\r\n case KPIStatusWithHistory.status.NOGOAL:\r\n return KPIStatusWithHistory.kpiGraphGreyClass;\r\n\r\n case KPIStatusWithHistory.status.INCREASE:\r\n return KPIStatusWithHistory.kpiGreenClass;\r\n\r\n case KPIStatusWithHistory.status.IN_BETWEEN:\r\n return KPIStatusWithHistory.kpiYellowClass;\r\n\r\n case KPIStatusWithHistory.status.DROP:\r\n return KPIStatusWithHistory.kpiRedClass;\r\n }\r\n }\r\n}\n\n\n/** WEBPACK FOOTER **\n ** ./src/Clients/Visuals/visuals/kpiStatusWithHistory.ts\n **/","/*\r\n * Power BI Visualizations\r\n *\r\n * Copyright (c) Microsoft Corporation\r\n * All rights reserved.\r\n * MIT License\r\n *\r\n * Permission is hereby granted, free of charge, to any person obtaining a copy\r\n * of this software and associated documentation files (the \"\"Software\"\"), to deal\r\n * in the Software without restriction, including without limitation the rights\r\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\r\n * copies of the Software, and to permit persons to whom the Software is\r\n * furnished to do so, subject to the following conditions:\r\n *\r\n * The above copyright notice and this permission notice shall be included in\r\n * all copies or substantial portions of the Software.\r\n *\r\n * THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\r\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\r\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\r\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\r\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\r\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\r\n * THE SOFTWARE.\r\n */\r\n\r\n/// <reference path=\"../_references.ts\"/>\r\n\r\nmodule powerbi.visuals {\r\n import EnumExtensions = jsCommon.EnumExtensions;\r\n import ClassAndSelector = jsCommon.CssConstants.ClassAndSelector;\r\n import createClassAndSelector = jsCommon.CssConstants.createClassAndSelector;\r\n import PixelConverter = jsCommon.PixelConverter;\r\n\r\n export interface LineChartConstructorOptions extends CartesianVisualConstructorOptions {\r\n chartType?: LineChartType;\r\n tooltipBucketEnabled?: boolean;\r\n advancedLineLabelsEnabled?: boolean;\r\n }\r\n\r\n export interface LineChartDataLabelsSettings extends PointDataLabelsSettings {\r\n labelDensity: string;\r\n }\r\n\r\n export interface ILineChartConfiguration {\r\n xAxisProperties: IAxisProperties;\r\n yAxisProperties: IAxisProperties;\r\n margin: any;\r\n }\r\n\r\n export interface LineChartCategoriesData extends LineChartDataPoint { }\r\n\r\n export interface LineChartData extends CartesianData {\r\n series: LineChartSeries[];\r\n isScalar?: boolean;\r\n scalarMetadata?: DataViewMetadataColumn;\r\n scalarKeyCount?: number;\r\n dataLabelsSettings: LineChartDataLabelsSettings;\r\n axesLabels: ChartAxesLabels;\r\n hasDynamicSeries?: boolean;\r\n defaultSeriesColor?: string;\r\n categoryData?: LineChartCategoriesData[];\r\n seriesDisplayName?: string;\r\n hasValues?: boolean;\r\n }\r\n\r\n export interface LineChartSeries extends CartesianSeries, SelectableDataPoint {\r\n displayName: string;\r\n dynamicDisplayName?: string;\r\n key: string;\r\n lineIndex: number;\r\n color: string;\r\n xCol: DataViewMetadataColumn;\r\n yCol: DataViewMetadataColumn;\r\n data: LineChartDataPoint[];\r\n labelSettings: LineChartDataLabelsSettings;\r\n }\r\n\r\n export interface LineChartDataPoint extends CartesianDataPoint, TooltipEnabledDataPoint, SelectableDataPoint, LabelEnabledDataPoint {\r\n value: number;\r\n categoryIndex: number;\r\n seriesIndex: number;\r\n key: string;\r\n labelSettings: LineChartDataLabelsSettings;\r\n pointColor?: string;\r\n stackedValue?: number;\r\n extraTooltipInfo?: TooltipDataItem[];\r\n }\r\n\r\n export interface HoverLineDataPoint {\r\n color: string;\r\n seriesDisplayName?: string;\r\n seriesName?: string;\r\n category: string;\r\n measureDisplayName: string;\r\n measure: any;\r\n value: number;\r\n stackedValue: number;\r\n extraTooltipInfo: TooltipDataItem[];\r\n }\r\n\r\n export const enum LineChartType {\r\n default = 1 << 0,\r\n area = 1 << 1,\r\n smooth = 1 << 2,\r\n lineShadow = 1 << 3,\r\n stackedArea = 1 << 4\r\n }\r\n\r\n const enum LineChartRelativePosition {\r\n None,\r\n Equal,\r\n Lesser,\r\n Greater,\r\n };\r\n\r\n /**\r\n * Renders a data series as a line visual.\r\n */\r\n export class LineChart implements ICartesianVisual {\r\n private static ClassName = 'lineChart';\r\n private static MainGraphicsContextClassName = 'mainGraphicsContext';\r\n private static CategorySelector: ClassAndSelector = createClassAndSelector('cat');\r\n private static CategoryValuePoint: ClassAndSelector = createClassAndSelector('dot');\r\n private static CategoryPointSelector: ClassAndSelector = createClassAndSelector('point');\r\n private static CategoryAreaSelector: ClassAndSelector = createClassAndSelector('catArea');\r\n private static HoverLineCircleDot: ClassAndSelector = createClassAndSelector('circle-item');\r\n private static LineClassSelector: ClassAndSelector = createClassAndSelector('line');\r\n private static PointRadius = 5;\r\n private static CircleRadius = 4;\r\n private static PathElementName = 'path';\r\n private static CircleElementName = 'circle';\r\n private static CircleClassName = 'selection-circle';\r\n private static LineElementName = 'line';\r\n private static RectOverlayName = 'rect';\r\n private static ScalarOuterPadding = 10;\r\n private static interactivityStrokeWidth = 10;\r\n private static minimumLabelsToRender = 4;\r\n public static AreaFillOpacity = 0.4;\r\n public static DimmedAreaFillOpacity = 0.2;\r\n\r\n private isInteractiveChart: boolean;\r\n private isScrollable: boolean;\r\n private tooltipsEnabled: boolean;\r\n private lineClassAndSelector: ClassAndSelector;\r\n\r\n private element: JQuery;\r\n private cartesainSVG: D3.Selection;\r\n private mainGraphicsContext: D3.Selection;\r\n private mainGraphicsSVG: D3.Selection;\r\n private hoverLineContext: D3.Selection;\r\n private options: CartesianVisualInitOptions;\r\n private dataViewCat: DataViewCategorical;\r\n\r\n private colors: IDataColorPalette;\r\n private host: IVisualHostServices;\r\n private data: LineChartData;\r\n private clippedData: LineChartData;\r\n private lineType: LineChartType;\r\n private cartesianVisualHost: ICartesianVisualHost;\r\n\r\n private xAxisProperties: IAxisProperties;\r\n private yAxisProperties: IAxisProperties;\r\n private margin: IMargin;\r\n private currentViewport: IViewport;\r\n\r\n private selectionCircles: D3.Selection[];\r\n private dragHandle: D3.Selection;\r\n private hoverLine: D3.Selection;\r\n private lastInteractiveSelectedColumnIndex: number;\r\n private scaleDetector: SVGScaleDetector;\r\n\r\n private interactivityService: IInteractivityService;\r\n private animator: IGenericAnimator;\r\n\r\n private previousCategoryCount: number;\r\n private pathXAdjustment: number = 0;\r\n\r\n private tooltipBucketEnabled: boolean;\r\n private advancedLineLabelsEnabled: boolean;\r\n\r\n private static validStackedLabelPositions = [RectLabelPosition.InsideCenter, RectLabelPosition.InsideEnd, RectLabelPosition.InsideBase];\r\n\r\n private overlayRect: D3.Selection;\r\n private isComboChart: boolean;\r\n\r\n private lastDragMoveXPosition: number;\r\n private deferDragMoveOperation = jsCommon.DeferUtility.deferUntilNextFrame(() => {\r\n if (this.lastDragMoveXPosition) {\r\n let index: number = this.findIndex(this.lastDragMoveXPosition - this.margin.left);\r\n this.selectColumn(index);\r\n this.lastDragMoveXPosition = undefined;\r\n }\r\n });\r\n\r\n public static customizeQuery(options: CustomizeQueryOptions): void {\r\n let dataViewMapping = options.dataViewMappings[0];\r\n if (!dataViewMapping || !dataViewMapping.categorical || !dataViewMapping.categorical.categories)\r\n return;\r\n\r\n dataViewMapping.categorical.dataVolume = 4;\r\n\r\n if (CartesianChart.detectScalarMapping(dataViewMapping)) {\r\n let dataViewCategories = <data.CompiledDataViewRoleForMappingWithReduction>dataViewMapping.categorical.categories;\r\n dataViewCategories.dataReductionAlgorithm = { sample: {} };\r\n }\r\n else {\r\n CartesianChart.applyLoadMoreEnabledToMapping(options.cartesianLoadMoreEnabled, dataViewMapping);\r\n }\r\n }\r\n\r\n public static getSortableRoles(options: VisualSortableOptions): string[] {\r\n let dataViewMapping = options.dataViewMappings[0];\r\n if (!dataViewMapping || !dataViewMapping.categorical || !dataViewMapping.categorical.categories)\r\n return null;\r\n\r\n let dataViewCategories = <data.CompiledDataViewRoleForMappingWithReduction>dataViewMapping.categorical.categories;\r\n let categoryItems = dataViewCategories.for.in.items;\r\n\r\n if (!_.isEmpty(categoryItems)) {\r\n let categoryType = categoryItems[0].type;\r\n\r\n let objects: DataViewObjects;\r\n if (dataViewMapping.metadata)\r\n objects = dataViewMapping.metadata.objects;\r\n\r\n //TODO: line chart should be sortable by X if it has scalar axis\r\n // But currently it doesn't support this. Always return 'category'\r\n // once it is supported.\r\n if (!CartesianChart.getIsScalar(objects, lineChartProps.categoryAxis.axisType, categoryType))\r\n return ['Category', 'Y'];\r\n }\r\n\r\n return null;\r\n }\r\n\r\n public static converter(\r\n dataView: DataView,\r\n blankCategoryValue: string,\r\n colors: IDataColorPalette,\r\n isScalar: boolean,\r\n interactivityService?: IInteractivityService,\r\n shouldCalculateStacked?: boolean,\r\n isComboChart?: boolean,\r\n tooltipsEnabled: boolean = true,\r\n tooltipBucketEnabled: boolean = false): LineChartData {\r\n let reader = powerbi.data.createIDataViewCategoricalReader(dataView);\r\n let valueRoleName = reader.hasValues('Y') ? 'Y' : 'Y2';\r\n let categorical = dataView.categorical;\r\n let category = categorical.categories && categorical.categories.length > 0\r\n ? categorical.categories[0]\r\n : {\r\n source: undefined,\r\n values: [blankCategoryValue],\r\n identity: undefined,\r\n };\r\n\r\n let xAxisCardProperties = CartesianHelper.getCategoryAxisProperties(dataView.metadata);\r\n isScalar = CartesianHelper.isScalar(isScalar, xAxisCardProperties);\r\n let scalarKeys = LineChart.getScalarKeys(category);\r\n let useScalarKeys = isScalar && scalarKeys && !_.isEmpty(scalarKeys.values);\r\n\r\n // if scalar and scalar keys defined, use scalar keys for xAxis labels\r\n let scalarMetadataColumn: DataViewMetadataColumn;\r\n if (useScalarKeys)\r\n scalarMetadataColumn = {\r\n displayName: null,\r\n type: {\r\n dateTime: true\r\n },\r\n };\r\n\r\n categorical = ColumnUtil.applyUserMinMax(isScalar, categorical, xAxisCardProperties);\r\n\r\n let formatStringProp = lineChartProps.general.formatString;\r\n let categoryType: ValueType = AxisHelper.getCategoryValueType(category.source, isScalar);\r\n let isDateTime = AxisHelper.isDateTime(categoryType);\r\n let categoryValues = category.values;\r\n let categoryData = [];\r\n let series: LineChartSeries[] = [];\r\n let seriesCount = reader.getSeriesCount(valueRoleName);\r\n let hasDynamicSeries = !!(categorical.values && categorical.values.source);\r\n let values = categorical.values;\r\n let defaultLabelSettings: LineChartDataLabelsSettings = dataLabelUtils.getDefaultLineChartLabelSettings(isComboChart);\r\n let defaultSeriesColor: string;\r\n\r\n if (dataView.metadata && dataView.metadata.objects) {\r\n let objects = dataView.metadata.objects;\r\n\r\n // If the line layer is in a combo chart, the \"Default Column Color\" slice's value (lineChartProps.dataPoint.defaultColor) will not affect the line series as well\r\n defaultSeriesColor = isComboChart ? undefined : DataViewObjects.getFillColor(objects, lineChartProps.dataPoint.defaultColor);\r\n\r\n let labelsObj = <DataLabelObject>objects['labels'];\r\n dataLabelUtils.updateLineChartLabelSettingsFromLabelsObject(labelsObj, defaultLabelSettings);\r\n }\r\n\r\n let colorHelper = new ColorHelper(colors, lineChartProps.dataPoint.fill, defaultSeriesColor);\r\n\r\n let grouped: DataViewValueColumnGroup[];\r\n if (dataView.categorical.values)\r\n grouped = dataView.categorical.values.grouped();\r\n\r\n let stackedValues;\r\n if (shouldCalculateStacked) {\r\n //initialize array with zeros\r\n stackedValues = categorical.values && categorical.values.length > 0 ? _.times(categorical.values[0].values.length, () => 0) : [];\r\n }\r\n\r\n for (let seriesIndex = 0; seriesIndex < seriesCount; seriesIndex++) {\r\n let column = categorical.values[seriesIndex];\r\n let valuesMetadata = column.source;\r\n let dataPoints: LineChartDataPoint[] = [];\r\n let groupedIdentity = grouped[seriesIndex];\r\n let identity = hasDynamicSeries && groupedIdentity\r\n ? SelectionId.createWithIdAndMeasure(groupedIdentity.identity, column.source.queryName)\r\n : SelectionId.createWithMeasure(column.source.queryName);\r\n let key = identity.getKey();\r\n let color = this.getColor(colorHelper, hasDynamicSeries, values, grouped, seriesIndex, groupedIdentity);\r\n let seriesLabelSettings: LineChartDataLabelsSettings;\r\n\r\n if (!hasDynamicSeries) {\r\n let labelsSeriesGroup = grouped && grouped.length > 0 && grouped[0].values ? grouped[0].values[seriesIndex] : null;\r\n let labelObjects = (labelsSeriesGroup && labelsSeriesGroup.source && labelsSeriesGroup.source.objects) ? <DataLabelObject>labelsSeriesGroup.source.objects['labels'] : null;\r\n if (labelObjects) {\r\n seriesLabelSettings = Prototype.inherit(defaultLabelSettings);\r\n dataLabelUtils.updateLineChartLabelSettingsFromLabelsObject(labelObjects, seriesLabelSettings);\r\n }\r\n }\r\n\r\n let dataPointLabelSettings = (seriesLabelSettings) ? seriesLabelSettings : defaultLabelSettings;\r\n\r\n let useHighlightValues = column.highlights && column.highlights.length > 0;\r\n let categoryCount = reader.hasCategories() ? reader.getCategoryCount() : 1;\r\n // NOTE: line capabilities don't allow highlights, but comboChart does - so only use highlight values if we are in \"combo\" mode\r\n for (let categoryIndex = 0; categoryIndex < categoryCount; categoryIndex++) {\r\n let categoryValue = categoryValues[categoryIndex];\r\n let xAxisValue = useScalarKeys ? scalarKeys.values[categoryIndex].min : categoryValue;\r\n let value = AxisHelper.normalizeNonFiniteNumber(useHighlightValues ? reader.getHighlight(valueRoleName, categoryIndex, seriesIndex) : reader.getValue(valueRoleName, categoryIndex, seriesIndex));\r\n\r\n // When Scalar, skip null categories and null values so we draw connected lines and never draw isolated dots.\r\n if (isScalar && (categoryValue == null || value == null))\r\n continue;\r\n\r\n // ignore variant measures\r\n if (isDateTime && categoryValue != null && !(categoryValue instanceof Date))\r\n continue;\r\n\r\n let categorical: DataViewCategorical = dataView.categorical;\r\n let tooltipInfo: TooltipDataItem[];\r\n let extraTooltipInfo: TooltipDataItem[];\r\n\r\n if (tooltipsEnabled) {\r\n // This tooltip is using in combo chart and mobile tooltip.\r\n tooltipInfo = [];\r\n\r\n if (category.source) {\r\n tooltipInfo.push({\r\n displayName: category.source.displayName,\r\n value: converterHelper.formatFromMetadataColumn(categoryValue, category.source, formatStringProp),\r\n });\r\n }\r\n\r\n // This dynamicSeries tooltip is only using in mobile tooltip.\r\n if (hasDynamicSeries) {\r\n if (!category.source || category.source !== categorical.values.source) {\r\n // Category/series on the same column -- don't repeat its value in the tooltip.\r\n tooltipInfo.push({\r\n displayName: categorical.values.source.displayName,\r\n value: converterHelper.formatFromMetadataColumn(grouped[seriesIndex].name, categorical.values.source, formatStringProp),\r\n });\r\n }\r\n }\r\n if (value != null) {\r\n tooltipInfo.push({\r\n displayName: valuesMetadata.displayName,\r\n value: converterHelper.formatFromMetadataColumn(value, valuesMetadata, formatStringProp),\r\n });\r\n }\r\n\r\n if (tooltipBucketEnabled) {\r\n extraTooltipInfo = [];\r\n TooltipBuilder.addTooltipBucketItem(reader, extraTooltipInfo, categoryIndex, hasDynamicSeries ? seriesIndex : undefined);\r\n }\r\n }\r\n\r\n let categoryKey = category && !_.isEmpty(category.identity) && category.identity[categoryIndex] ? category.identity[categoryIndex].key : categoryIndex;\r\n\r\n let dataPoint: LineChartDataPoint = {\r\n categoryValue: ((isDateTime || useScalarKeys) && xAxisValue) ? xAxisValue.getTime() : xAxisValue,\r\n value: value,\r\n categoryIndex: categoryIndex,\r\n seriesIndex: seriesIndex,\r\n tooltipInfo: tooltipInfo,\r\n selected: false,\r\n identity: identity,\r\n key: JSON.stringify({ series: key, category: categoryKey }),\r\n labelFill: dataPointLabelSettings.labelColor,\r\n labelFormatString: valuesMetadata.format,\r\n labelSettings: dataPointLabelSettings,\r\n extraTooltipInfo: extraTooltipInfo,\r\n };\r\n\r\n if (shouldCalculateStacked) {\r\n stackedValues[categoryIndex] += value;\r\n dataPoint.stackedValue = stackedValues[categoryIndex];\r\n }\r\n\r\n if (category.objects && category.objects[categoryIndex]) {\r\n dataPoint['pointColor'] = DataViewObjects.getFillColor(category.objects[categoryIndex], lineChartProps.dataPoint.fill);\r\n }\r\n\r\n dataPoints.push(dataPoint);\r\n\r\n if (!categoryData[categoryIndex]) {\r\n categoryData[categoryIndex] = dataPoint;\r\n }\r\n }\r\n\r\n if (interactivityService) {\r\n interactivityService.applySelectionStateToData(dataPoints);\r\n }\r\n\r\n if (dataPoints.length > 0) {\r\n series.push({\r\n displayName: converterHelper.formatFromMetadataColumn(reader.getValueDisplayName(valueRoleName, seriesIndex), reader.getValueMetadataColumn(valueRoleName, seriesIndex), formatStringProp),\r\n dynamicDisplayName: hasDynamicSeries ? converterHelper.formatFromMetadataColumn(reader.getSeriesName(seriesIndex), reader.getSeriesMetadataColumn(), formatStringProp) : undefined,\r\n key: key,\r\n lineIndex: seriesIndex,\r\n color: color,\r\n xCol: useScalarKeys ? scalarMetadataColumn : category.source,\r\n yCol: reader.getValueMetadataColumn(valueRoleName, seriesIndex),\r\n data: dataPoints,\r\n identity: identity,\r\n selected: false,\r\n labelSettings: seriesLabelSettings,\r\n });\r\n }\r\n }\r\n\r\n xAxisCardProperties = CartesianHelper.getCategoryAxisProperties(dataView.metadata);\r\n let valueAxisProperties = CartesianHelper.getValueAxisProperties(dataView.metadata);\r\n\r\n // Convert to DataViewMetadataColumn\r\n let valuesMetadataArray: powerbi.DataViewMetadataColumn[] = [];\r\n if (values) {\r\n for (let i = 0; i < values.length; i++) {\r\n\r\n if (values[i] && values[i].source && values[i].source.displayName) {\r\n valuesMetadataArray.push({ displayName: values[i].source.displayName });\r\n }\r\n }\r\n }\r\n\r\n let axesLabels = converterHelper.createAxesLabels(xAxisCardProperties, valueAxisProperties, category.source, valuesMetadataArray);\r\n if (interactivityService) {\r\n interactivityService.applySelectionStateToData(series);\r\n }\r\n\r\n return {\r\n series: series,\r\n isScalar: isScalar,\r\n dataLabelsSettings: defaultLabelSettings,\r\n axesLabels: { x: axesLabels.xAxisLabel, y: axesLabels.yAxisLabel },\r\n hasDynamicSeries: hasDynamicSeries,\r\n categoryMetadata: category.source,\r\n scalarMetadata: scalarMetadataColumn,\r\n scalarKeyCount: useScalarKeys ? scalarKeys.values.length : undefined,\r\n categories: categoryValues,\r\n categoryData: categoryData,\r\n seriesDisplayName: hasDynamicSeries ? converterHelper.formatFromMetadataColumn(reader.getSeriesDisplayName(), reader.getSeriesMetadataColumn(), formatStringProp) : undefined,\r\n hasValues: reader.hasValues(valueRoleName),\r\n };\r\n }\r\n\r\n public static getInteractiveLineChartDomElement(element: JQuery): HTMLElement {\r\n return element.children(\"svg\").get(0);\r\n }\r\n\r\n private static getColor(\r\n colorHelper: ColorHelper,\r\n hasDynamicSeries: boolean,\r\n values: DataViewValueColumns,\r\n grouped: DataViewValueColumnGroup[],\r\n seriesIndex: number,\r\n groupedIdentity: DataViewValueColumnGroup): string {\r\n\r\n let objects: DataViewObjects;\r\n if (hasDynamicSeries) {\r\n if (grouped && grouped[seriesIndex])\r\n objects = grouped[seriesIndex].objects;\r\n }\r\n else if (values[seriesIndex]) {\r\n objects = values[seriesIndex].source.objects;\r\n }\r\n\r\n return hasDynamicSeries && groupedIdentity\r\n ? colorHelper.getColorForSeriesValue(objects, values.identityFields, groupedIdentity.name)\r\n : colorHelper.getColorForMeasure(objects, values[seriesIndex].source.queryName);\r\n }\r\n\r\n private static createStackedValueDomain(data: LineChartSeries[]): number[] {\r\n debug.assertValue(data, 'data');\r\n if (data.length === 0)\r\n return null;\r\n\r\n let minY = <number>d3.min(data, (kv) => { return d3.min(kv.data, d => { return d.stackedValue; }); });\r\n let maxY = <number>d3.max(data, (kv) => { return d3.max(kv.data, d => { return d.stackedValue; }); });\r\n\r\n return [minY, maxY];\r\n }\r\n\r\n constructor(options: LineChartConstructorOptions) {\r\n this.isScrollable = options.isScrollable ? options.isScrollable : false;\r\n this.tooltipsEnabled = options.tooltipsEnabled;\r\n this.lineType = options.chartType ? options.chartType : LineChartType.default;\r\n this.interactivityService = options.interactivityService;\r\n this.animator = options.animator;\r\n this.lineClassAndSelector = LineChart.LineClassSelector;\r\n this.tooltipBucketEnabled = options.tooltipBucketEnabled;\r\n this.advancedLineLabelsEnabled = options.advancedLineLabelsEnabled;\r\n }\r\n\r\n public init(options: CartesianVisualInitOptions) {\r\n this.options = options;\r\n this.element = options.element;\r\n this.cartesainSVG = options.svg;\r\n this.host = options.host;\r\n this.currentViewport = options.viewport;\r\n this.colors = options.style.colorPalette.dataColors;\r\n this.isInteractiveChart = options.interactivity && options.interactivity.isInteractiveLegend;\r\n this.cartesianVisualHost = options.cartesianHost;\r\n this.scaleDetector = new SVGScaleDetector(this.cartesainSVG);\r\n\r\n let chartType = options.chartType;\r\n this.isComboChart = chartType === CartesianChartType.ComboChart || chartType === CartesianChartType.LineClusteredColumnCombo || chartType === CartesianChartType.LineStackedColumnCombo;\r\n\r\n let svg = options.svg;\r\n svg.classed(LineChart.ClassName, true);\r\n\r\n let graphicsContextParent = this.mainGraphicsSVG = svg.append('svg')\r\n .classed('lineChartSVG', true);\r\n\r\n if (!this.isComboChart && !this.isInteractiveChart) {\r\n this.overlayRect = graphicsContextParent\r\n .append(LineChart.RectOverlayName)\r\n .style(\"opacity\", SVGUtil.AlmostZero);\r\n }\r\n this.mainGraphicsContext = graphicsContextParent\r\n .append('g')\r\n .classed(LineChart.MainGraphicsContextClassName, true);\r\n\r\n this.hoverLineContext = svg.append('g')\r\n .classed('hover-line', true);\r\n\r\n this.hoverLineContext.append(LineChart.LineElementName)\r\n .attr(\"x1\", 0).attr(\"x2\", 0)\r\n .attr(\"y1\", 0).attr(\"y2\", 0);\r\n\r\n let hoverLine = this.hoverLine = this.hoverLineContext.select(LineChart.LineElementName);\r\n if (this.isInteractiveChart) {\r\n hoverLine.classed('interactive', true);\r\n }\r\n hoverLine.style('opacity', SVGUtil.AlmostZero);\r\n\r\n // define circles object - which will hold the handle circles.\r\n // this object will be populated on render() function, with number of circles which matches the nubmer of lines.\r\n this.selectionCircles = [];\r\n\r\n this.xAxisProperties = {\r\n axis: null,\r\n scale: null,\r\n axisType: null,\r\n formatter: null,\r\n graphicsContext: null,\r\n values: null,\r\n axisLabel: null,\r\n isCategoryAxis: true\r\n };\r\n\r\n if (this.isInteractiveChart) {\r\n let rootSvg: EventTarget = LineChart.getInteractiveLineChartDomElement(this.element);\r\n let dragMove = () => {\r\n this.lastDragMoveXPosition = d3.mouse(rootSvg)[0];\r\n this.deferDragMoveOperation();\r\n };\r\n\r\n // assign drag and onClick events\r\n let drag = d3.behavior.drag()\r\n .origin(Object)\r\n .on(\"drag\", dragMove);\r\n d3.select(rootSvg)\r\n .style('touch-action', 'none')\r\n .call(drag)\r\n .on('click', dragMove);\r\n }\r\n\r\n // Internet Explorer and Edge use the stroke edge, not the path edge for the mouse coordinate's origin.\r\n // We need to adjust mouse events on the interactivity lines to account for this.\r\n if (jsCommon.BrowserUtils.isInternetExplorerOrEdge()) {\r\n this.pathXAdjustment = 5;\r\n }\r\n else if (jsCommon.BrowserUtils.isFirefox()) {\r\n this.pathXAdjustment = LineChart.interactivityStrokeWidth * 2;\r\n }\r\n }\r\n\r\n public setData(dataViews: DataView[]): void {\r\n this.data = {\r\n series: [],\r\n dataLabelsSettings: dataLabelUtils.getDefaultLineChartLabelSettings(this.isComboChart),\r\n axesLabels: { x: null, y: null },\r\n hasDynamicSeries: false,\r\n categories: [],\r\n categoryMetadata: undefined,\r\n categoryData: [],\r\n };\r\n\r\n if (dataViews.length > 0) {\r\n let dataView = dataViews[0];\r\n\r\n if (dataView) {\r\n if (dataView.categorical) {\r\n let dataViewCat = this.dataViewCat = dataView.categorical;\r\n let dvCategories = dataViewCat.categories;\r\n let categoryType: ValueTypeDescriptor = { text: true };\r\n let scalarKeys: ScalarKeys;\r\n if (dvCategories && !_.isEmpty(dvCategories)) {\r\n if (dvCategories[0].source && dvCategories[0].source.type)\r\n categoryType = dvCategories[0].source.type;\r\n scalarKeys = LineChart.getScalarKeys(dvCategories[0]);\r\n }\r\n\r\n let convertedData = LineChart.converter(\r\n dataView,\r\n valueFormatter.format(null),\r\n this.cartesianVisualHost.getSharedColors(),\r\n CartesianChart.getIsScalar(dataView.metadata ? dataView.metadata.objects : null, lineChartProps.categoryAxis.axisType, categoryType, scalarKeys),\r\n this.interactivityService,\r\n EnumExtensions.hasFlag(this.lineType, LineChartType.stackedArea),\r\n this.isComboChart,\r\n this.tooltipsEnabled,\r\n this.tooltipBucketEnabled);\r\n this.data = convertedData;\r\n }\r\n }\r\n }\r\n }\r\n\r\n public calculateLegend(): LegendData {\r\n return this.createLegendDataPoints(0); // start with index 0\r\n }\r\n\r\n public hasLegend(): boolean {\r\n return this.data && (this.data.hasDynamicSeries || (this.data.series && this.data.series.length > 1));\r\n }\r\n\r\n public setFilteredData(startIndex: number, endIndex: number): CartesianData {\r\n let catgSize = endIndex - startIndex;\r\n let data = this.clippedData = Prototype.inherit(this.data);\r\n data.series = LineChart.sliceSeries(data.series, catgSize, startIndex);\r\n data.categories = data.categories.slice(startIndex, endIndex);\r\n return data;\r\n }\r\n\r\n public calculateAxesProperties(options: CalculateScaleAndDomainOptions): IAxisProperties[] {\r\n let data = this.data;\r\n let viewport = options.viewport;\r\n let margin = options.margin;\r\n this.currentViewport = viewport;\r\n this.margin = margin;\r\n\r\n let origCatgSize = data.series && data.series.length > 0 ? data.series[0].data.length : 0;\r\n let categoryWidth = CartesianChart.MinOrdinalRectThickness;\r\n let isScalar = this.data.isScalar;\r\n let trimOrdinalDataOnOverflow = options.trimOrdinalDataOnOverflow;\r\n let preferredPlotArea = this.getPreferredPlotArea(isScalar, origCatgSize, categoryWidth);\r\n\r\n this.clippedData = undefined;\r\n if (data && !isScalar && !this.isScrollable && trimOrdinalDataOnOverflow) {\r\n // trim data that doesn't fit on dashboard\r\n let categoryCount = this.getCategoryCount(origCatgSize);\r\n let catgSize = Math.min(origCatgSize, categoryCount);\r\n\r\n if (catgSize !== origCatgSize) {\r\n data = this.clippedData = Prototype.inherit(data);\r\n this.clippedData.series = LineChart.sliceSeries(data.series, catgSize);\r\n }\r\n }\r\n\r\n let xMetaDataColumn: DataViewMetadataColumn;\r\n let yMetaDataColumn: DataViewMetadataColumn;\r\n if (data.series && data.series.length > 0) {\r\n xMetaDataColumn = data.series[0].xCol;\r\n yMetaDataColumn = data.series[0].yCol;\r\n }\r\n\r\n let valueDomain = EnumExtensions.hasFlag(this.lineType, LineChartType.stackedArea) ? LineChart.createStackedValueDomain(data.series) : AxisHelper.createValueDomain(data.series, false);\r\n let hasZeroValueInYDomain = options.valueAxisScaleType === axisScale.log && !AxisHelper.isLogScalePossible(valueDomain);\r\n let combinedDomain = AxisHelper.combineDomain(options.forcedYDomain, valueDomain, options.ensureYDomain);\r\n this.yAxisProperties = AxisHelper.createAxis({\r\n pixelSpan: preferredPlotArea.height,\r\n dataDomain: combinedDomain,\r\n metaDataColumn: yMetaDataColumn,\r\n formatString: valueFormatter.getFormatString(yMetaDataColumn, lineChartProps.general.formatString),\r\n outerPadding: 0,\r\n isScalar: true,\r\n isVertical: true,\r\n forcedTickCount: options.forcedTickCount,\r\n useTickIntervalForDisplayUnits: true,\r\n isCategoryAxis: false,\r\n scaleType: options.valueAxisScaleType,\r\n axisDisplayUnits: options.valueAxisDisplayUnits,\r\n axisPrecision: options.valueAxisPrecision,\r\n shouldClamp: false, // clamping causes incorrect lines when you have axis extents specified, do not enable this.\r\n });\r\n\r\n let metaDataColumn = data.scalarMetadata ? data.scalarMetadata : data.categoryMetadata;\r\n let xAxisDataType: ValueTypeDescriptor = AxisHelper.getCategoryValueType(metaDataColumn);\r\n let xDomain = AxisHelper.createDomain(data.series, xAxisDataType, this.data.isScalar, options.forcedXDomain, options.ensureXDomain);\r\n let hasZeroValueInXDomain = options.categoryAxisScaleType === axisScale.log && !AxisHelper.isLogScalePossible(xDomain);\r\n this.xAxisProperties = AxisHelper.createAxis({\r\n pixelSpan: preferredPlotArea.width,\r\n dataDomain: xDomain,\r\n metaDataColumn: xMetaDataColumn,\r\n formatString: valueFormatter.getFormatString(xMetaDataColumn, lineChartProps.general.formatString),\r\n outerPadding: this.data.isScalar ? LineChart.ScalarOuterPadding : 0,\r\n isScalar: this.data.isScalar,\r\n isVertical: false,\r\n forcedTickCount: options.forcedTickCount,\r\n maxTickCount: data.scalarKeyCount,\r\n useTickIntervalForDisplayUnits: true,\r\n getValueFn: (index, type) => CartesianHelper.lookupXValue(this.data, index, type, this.data.isScalar),\r\n categoryThickness: CartesianChart.getCategoryThickness(data.series, origCatgSize, this.getAvailableWidth(), xDomain, isScalar, trimOrdinalDataOnOverflow),\r\n isCategoryAxis: true,\r\n scaleType: options.categoryAxisScaleType,\r\n axisDisplayUnits: options.categoryAxisDisplayUnits,\r\n axisPrecision: options.categoryAxisPrecision\r\n });\r\n\r\n this.xAxisProperties.axisLabel = options.showCategoryAxisLabel ? data.axesLabels.x : null;\r\n this.yAxisProperties.axisLabel = options.showValueAxisLabel ? data.axesLabels.y : null;\r\n\r\n this.xAxisProperties.hasDisallowedZeroInDomain = hasZeroValueInXDomain;\r\n this.yAxisProperties.hasDisallowedZeroInDomain = hasZeroValueInYDomain;\r\n\r\n return [this.xAxisProperties, this.yAxisProperties];\r\n }\r\n\r\n public enumerateObjectInstances(enumeration: ObjectEnumerationBuilder, options: EnumerateVisualObjectInstancesOptions): void {\r\n switch (options.objectName) {\r\n case 'dataPoint':\r\n this.enumerateDataPoints(enumeration);\r\n break;\r\n case 'labels':\r\n this.enumerateDataLabels(enumeration);\r\n break;\r\n }\r\n }\r\n\r\n private enumerateDataPoints(enumeration: ObjectEnumerationBuilder): void {\r\n let data = this.data;\r\n if (!data || !data.series || data.series.length === 0)\r\n return;\r\n\r\n let formatStringProp = lineChartProps.general.formatString;\r\n let singleSeriesData = data.series;\r\n let seriesLength = singleSeriesData.length;\r\n\r\n for (let i = 0; i < seriesLength; i++) {\r\n let selector = ColorHelper.normalizeSelector(singleSeriesData[i].identity.getSelector());\r\n\r\n let label = converterHelper.getFormattedLegendLabel(singleSeriesData[i].yCol, this.dataViewCat.values, formatStringProp);\r\n enumeration.pushInstance({\r\n objectName: 'dataPoint',\r\n displayName: label,\r\n selector: selector,\r\n properties: {\r\n fill: { solid: { color: data.defaultSeriesColor || singleSeriesData[i].color } }\r\n },\r\n });\r\n }\r\n }\r\n\r\n private enumerateDataLabels(enumeration: ObjectEnumerationBuilder): void {\r\n let data = this.data,\r\n labelSettings = this.data.dataLabelsSettings,\r\n seriesCount = data.series.length,\r\n showLabelPerSeries = this.showLabelPerSeries();\r\n\r\n //Draw default settings\r\n dataLabelUtils.enumerateDataLabels(this.getLabelSettingsOptions(enumeration, labelSettings, null, showLabelPerSeries));\r\n\r\n if (seriesCount === 0)\r\n return;\r\n\r\n //Draw series settings\r\n if (showLabelPerSeries && labelSettings.showLabelPerSeries) {\r\n for (let i = 0; i < seriesCount; i++) {\r\n let series = data.series[i],\r\n labelSettings: LineChartDataLabelsSettings = (series.labelSettings) ? series.labelSettings : this.data.dataLabelsSettings;\r\n\r\n enumeration.pushContainer({ displayName: series.displayName });\r\n dataLabelUtils.enumerateDataLabels(this.getLabelSettingsOptions(enumeration, labelSettings, series));\r\n enumeration.popContainer();\r\n }\r\n }\r\n }\r\n\r\n public supportsTrendLine(): boolean {\r\n let isScalar = this.data ? this.data.isScalar : false;\r\n return !EnumExtensions.hasFlag(this.lineType, LineChartType.stackedArea) && isScalar && this.data.hasValues;\r\n }\r\n\r\n private showLabelPerSeries(): boolean {\r\n let data = this.data;\r\n return !data.hasDynamicSeries && (data.series.length > 1 || !data.categoryMetadata);\r\n }\r\n\r\n private getLabelSettingsOptions(enumeration: ObjectEnumerationBuilder, labelSettings: LineChartDataLabelsSettings, series?: LineChartSeries, showAll?: boolean): VisualDataLabelsSettingsOptions {\r\n return {\r\n enumeration: enumeration,\r\n dataLabelsSettings: labelSettings,\r\n show: true,\r\n displayUnits: true,\r\n precision: true,\r\n selector: series && series.identity ? series.identity.getSelector() : null,\r\n showAll: showAll,\r\n fontSize: true,\r\n labelDensity: (this.isComboChart ? this.data.series.length > 0 : true) && this.advancedLineLabelsEnabled && this.data.isScalar,\r\n };\r\n }\r\n\r\n public overrideXScale(xProperties: IAxisProperties): void {\r\n this.xAxisProperties = xProperties;\r\n }\r\n\r\n public onClearSelection(): void {\r\n if (this.interactivityService)\r\n this.interactivityService.clearSelection();\r\n }\r\n\r\n public render(suppressAnimations: boolean): CartesianVisualRenderResult {\r\n let duration = AnimatorCommon.GetAnimationDuration(this.animator, suppressAnimations);\r\n if (this.data.categoryData.length !== this.previousCategoryCount) {\r\n duration = 0;\r\n }\r\n this.previousCategoryCount = this.data.categoryData.length;\r\n let result: CartesianVisualRenderResult;\r\n if (!this.isInteractiveChart) // If we're not a mobile interactive chart, use the new render path\r\n result = this.renderNew(duration);\r\n else // If not, use the old path kept around for mobile compatibility until mobile code can be moved and tested within the new render path\r\n result = this.renderOld(duration);\r\n\r\n // This should always be the last line in the render code.\r\n SVGUtil.flushAllD3TransitionsIfNeeded(this.options);\r\n\r\n return result;\r\n }\r\n\r\n private renderNew(duration: number): CartesianVisualRenderResult {\r\n let data = this.clippedData ? this.clippedData : this.data;\r\n if (!data)\r\n return;\r\n\r\n let dataPointCount = data.categories.length * data.series.length;\r\n if (dataPointCount > AnimatorCommon.MaxDataPointsToAnimate) {\r\n // Too many data points to animate.\r\n duration = 0;\r\n }\r\n\r\n let isStackedArea = EnumExtensions.hasFlag(this.lineType, LineChartType.stackedArea);\r\n let margin = this.margin;\r\n let viewport = this.currentViewport;\r\n let height = viewport.height - (margin.top + margin.bottom);\r\n let width = viewport.width - (margin.left + margin.right);\r\n let xScale = this.xAxisProperties.scale;\r\n let yScale = this.yAxisProperties.scale;\r\n let horizontalOffset = this.getXOfFirstCategory();\r\n\r\n let hasSelection = this.interactivityService && this.interactivityService.hasSelection();\r\n let renderAreas: boolean = EnumExtensions.hasFlag(this.lineType, LineChartType.area) || EnumExtensions.hasFlag(this.lineType, LineChartType.stackedArea);\r\n let xPosition = (d: LineChartDataPoint) => { return xScale(this.getXValue(d)) + horizontalOffset; };\r\n\r\n let y0Position, yPosition;\r\n if (isStackedArea) {\r\n y0Position = (d: LineChartDataPoint) => { return yScale(d.stackedValue - d.value); };\r\n yPosition = (d: LineChartDataPoint) => { return yScale(d.stackedValue); };\r\n }\r\n else {\r\n y0Position = yScale(0);\r\n yPosition = (d: LineChartDataPoint) => { return yScale(d.value); };\r\n }\r\n\r\n let area;\r\n if (renderAreas) {\r\n area = d3.svg.area()\r\n .x(xPosition)\r\n .y0(y0Position)\r\n .y1(yPosition)\r\n .defined((d: LineChartDataPoint) => { return d.value !== null; });\r\n }\r\n\r\n let line = d3.svg.line()\r\n .x(xPosition)\r\n .y(yPosition)\r\n .defined((d: LineChartDataPoint) => {\r\n return d.value !== null;\r\n });\r\n\r\n if (EnumExtensions.hasFlag(this.lineType, LineChartType.smooth)) {\r\n line.interpolate('basis');\r\n if (area) {\r\n area.interpolate('basis');\r\n }\r\n }\r\n\r\n this.mainGraphicsSVG\r\n .attr('height', height)\r\n .attr('width', width);\r\n let areas = undefined;\r\n // Render Areas\r\n if (renderAreas) {\r\n areas = this.mainGraphicsContext.selectAll(LineChart.CategoryAreaSelector.selector).data(data.series, (d: LineChartSeries) => d.identity.getKey());\r\n areas.enter()\r\n .append(LineChart.PathElementName)\r\n .classed(LineChart.CategoryAreaSelector.class, true);\r\n areas\r\n .style('fill', (d: LineChartSeries) => d.color)\r\n .style('fill-opacity', (d: LineChartSeries) => (hasSelection && !d.selected) ? LineChart.DimmedAreaFillOpacity : LineChart.AreaFillOpacity)\r\n .transition()\r\n .ease('linear')\r\n .duration(duration)\r\n .attr('d', (d: LineChartSeries) => area(d.data));\r\n areas.exit()\r\n .remove();\r\n }\r\n\r\n // Render Lines\r\n let lines = this.mainGraphicsContext.selectAll(this.lineClassAndSelector.selector).data(data.series, (d: LineChartSeries) => d.identity.getKey());\r\n lines.enter()\r\n .append(LineChart.PathElementName)\r\n .classed(this.lineClassAndSelector.class, true);\r\n lines\r\n .style('stroke', (d: LineChartSeries) => d.color)\r\n .style('stroke-opacity', (d: LineChartSeries) => ColumnUtil.getFillOpacity(d.selected, false, hasSelection, false))\r\n .transition()\r\n .ease('linear')\r\n .duration(duration)\r\n .attr('d', (d: LineChartSeries) => {\r\n return line(d.data);\r\n });\r\n lines.exit()\r\n .remove();\r\n\r\n // Render extra lines that are wider and invisible used for better interactivity\r\n let interactivityLines = this.mainGraphicsContext.selectAll(\".interactivity-line\").data(data.series, (d: LineChartSeries) => d.identity.getKey());\r\n interactivityLines.enter()\r\n .append(LineChart.PathElementName)\r\n .classed('interactivity-line', true)\r\n .style('stroke-width', LineChart.interactivityStrokeWidth);\r\n interactivityLines\r\n .attr('d', (d: LineChartSeries) => {\r\n return line(d.data);\r\n });\r\n interactivityLines.exit()\r\n .remove();\r\n\r\n // Prepare grouping for dots\r\n let dotGroups = this.mainGraphicsContext.selectAll(LineChart.CategorySelector.selector)\r\n .data(data.series, (d: LineChartSeries) => d.identity.getKey());\r\n\r\n dotGroups.enter()\r\n .append('g')\r\n .classed(LineChart.CategorySelector.class, true);\r\n\r\n dotGroups.exit()\r\n .remove();\r\n\r\n // Render dots\r\n let dots = dotGroups.selectAll(LineChart.CategoryValuePoint.selector)\r\n .data((series: LineChartSeries) => {\r\n return series.data.filter((value: LineChartDataPoint, i: number) => {\r\n return this.shouldDrawCircle(series, i);\r\n });\r\n }, (d: LineChartDataPoint) => d.key);\r\n dots.enter()\r\n .append(LineChart.CircleElementName)\r\n .classed(LineChart.CategoryValuePoint.class, true);\r\n dots\r\n .style('fill', function () {\r\n let lineSeries = d3.select(this.parentNode).datum();\r\n return lineSeries.color;\r\n })\r\n .style('fill-opacity', function () {\r\n let lineSeries = d3.select(this.parentNode).datum();\r\n return ColumnUtil.getFillOpacity(lineSeries.selected, false, hasSelection, false);\r\n })\r\n .transition()\r\n .duration(duration)\r\n .attr({\r\n cx: (d: LineChartDataPoint, i: number) => xScale(this.getXValue(d)) + horizontalOffset,\r\n cy: (d: LineChartDataPoint, i: number) => yScale(isStackedArea ? d.stackedValue : d.value),\r\n r: LineChart.CircleRadius\r\n });\r\n dots.exit()\r\n .remove();\r\n\r\n // Render explicit dots\r\n let explicitDots: D3.UpdateSelection;\r\n if (!this.isComboChart) {\r\n explicitDots = dotGroups.selectAll(LineChart.CategoryPointSelector.selector)\r\n .data((series: LineChartSeries) => {\r\n return _.filter(series.data, (value: LineChartDataPoint) => { return value.pointColor != null; });\r\n }, (d: LineChartDataPoint) => d.key);\r\n explicitDots.enter()\r\n .append(LineChart.CircleElementName)\r\n .classed(LineChart.CategoryPointSelector.class, true);\r\n explicitDots\r\n .style('fill', (d: LineChartDataPoint) => d.pointColor)\r\n .transition()\r\n .duration(duration)\r\n .attr({\r\n cx: (d: LineChartDataPoint) => xScale(this.getXValue(d)),\r\n cy: (d: LineChartDataPoint) => yScale(isStackedArea ? d.stackedValue : d.value),\r\n r: LineChart.PointRadius\r\n });\r\n explicitDots.exit()\r\n .remove();\r\n }\r\n\r\n // Add data labels\r\n let labelDataPointGroups: LabelDataPointGroup[];\r\n if (data.dataLabelsSettings.show)\r\n labelDataPointGroups = this.createLabelDataPoints();\r\n\r\n if (this.tooltipsEnabled) {\r\n if (!this.isComboChart) {\r\n this.overlayRect\r\n .attr({\r\n x: 0,\r\n width: width,\r\n height: height\r\n });\r\n\r\n let seriesTooltipApplier = (tooltipEvent: TooltipEvent) => {\r\n let pointX: number = tooltipEvent.elementCoordinates[0];\r\n let index = this.getCategoryIndexFromTooltipEvent(tooltipEvent, pointX);\r\n let categoryData = this.selectColumnForTooltip(index);\r\n return this.getSeriesTooltipInfo(categoryData);\r\n };\r\n\r\n let clearHoverLine = () => {\r\n this.hoverLine.style('opacity', SVGUtil.AlmostZero);\r\n this.hoverLineContext.selectAll(LineChart.HoverLineCircleDot.selector).remove();\r\n };\r\n TooltipManager.addTooltip(this.mainGraphicsSVG, seriesTooltipApplier, true, clearHoverLine);\r\n } else {\r\n let seriesTooltipApplier = (tooltipEvent: TooltipEvent) => {\r\n let pointX: number = tooltipEvent.elementCoordinates[0];\r\n return this.getTooltipInfoForCombo(tooltipEvent, pointX);\r\n };\r\n\r\n if (interactivityLines)\r\n TooltipManager.addTooltip(interactivityLines, seriesTooltipApplier, true);\r\n\r\n TooltipManager.addTooltip(dots, (tooltipEvent: TooltipEvent) => tooltipEvent.data.tooltipInfo, true);\r\n if (explicitDots)\r\n TooltipManager.addTooltip(explicitDots, (tooltipEvent: TooltipEvent) => tooltipEvent.data.tooltipInfo, true);\r\n }\r\n }\r\n\r\n let dataPointsToBind: SelectableDataPoint[] = undefined;\r\n let behaviorOptions: LineChartBehaviorOptions = undefined;\r\n if (this.interactivityService) {\r\n // Register interactivity\r\n dataPointsToBind = data.series.slice();\r\n\r\n for (let i = 0, ilen = data.series.length; i < ilen; i++) {\r\n dataPointsToBind = dataPointsToBind.concat(data.series[i].data);\r\n }\r\n\r\n behaviorOptions = {\r\n lines: lines,\r\n interactivityLines: interactivityLines,\r\n dots: dots,\r\n areas: areas,\r\n tooltipOverlay: this.overlayRect,\r\n };\r\n }\r\n\r\n return {\r\n dataPoints: dataPointsToBind,\r\n behaviorOptions: behaviorOptions,\r\n labelDataPoints: [],\r\n labelsAreNumeric: true,\r\n labelDataPointGroups: labelDataPointGroups,\r\n };\r\n }\r\n\r\n private renderOld(duration: number): CartesianVisualRenderResult {\r\n let data = this.clippedData ? this.clippedData : this.data;\r\n if (!data)\r\n return;\r\n\r\n let margin = this.margin;\r\n let viewport = this.currentViewport;\r\n let height = viewport.height - (margin.top + margin.bottom);\r\n let xScale = this.xAxisProperties.scale;\r\n let yScale = this.yAxisProperties.scale;\r\n\r\n let hasSelection = this.interactivityService && this.interactivityService.hasSelection();\r\n\r\n let area;\r\n if (EnumExtensions.hasFlag(this.lineType, LineChartType.area)) {\r\n area = d3.svg.area()\r\n .x((d: LineChartDataPoint) => { return xScale(this.getXValue(d)); })\r\n .y0(height)\r\n .y1((d: LineChartDataPoint) => { return yScale(d.value); })\r\n .defined((d: LineChartDataPoint) => { return d.value !== null; });\r\n }\r\n\r\n let line = d3.svg.line()\r\n .x((d: LineChartDataPoint) => {\r\n return xScale(this.getXValue(d));\r\n })\r\n .y((d: LineChartDataPoint) => {\r\n return yScale(d.value);\r\n })\r\n .defined((d: LineChartDataPoint) => {\r\n return d.value !== null;\r\n });\r\n\r\n if (EnumExtensions.hasFlag(this.lineType, LineChartType.smooth)) {\r\n line.interpolate('basis');\r\n if (area) {\r\n area.interpolate('basis');\r\n }\r\n }\r\n\r\n let firstCategoryOffset = this.getXOfFirstCategory();\r\n\r\n this.mainGraphicsContext.attr('transform', SVGUtil.translate(firstCategoryOffset, 0));\r\n\r\n this.mainGraphicsSVG.attr('height', this.getAvailableHeight())\r\n .attr('width', this.getAvailableWidth());\r\n this.hoverLineContext.attr('transform', SVGUtil.translate(firstCategoryOffset, 0));\r\n\r\n if (EnumExtensions.hasFlag(this.lineType, LineChartType.area)) {\r\n let catAreaSelect = this.mainGraphicsContext.selectAll(LineChart.CategoryAreaSelector.selector)\r\n .data(data.series, (d: LineChartDataPoint) => d.identity.getKey());\r\n\r\n let catAreaEnter =\r\n catAreaSelect\r\n .enter().append('g')\r\n .classed(LineChart.CategoryAreaSelector.class, true);\r\n\r\n catAreaEnter.append(LineChart.PathElementName);\r\n\r\n let catAreaUpdate = this.mainGraphicsContext.selectAll(LineChart.CategoryAreaSelector.selector);\r\n\r\n catAreaUpdate.select(LineChart.PathElementName)\r\n .transition()\r\n .ease('linear')\r\n .duration(duration)\r\n .attr('d', (d: LineChartSeries) => area(d.data))\r\n .style('fill', (d: LineChartSeries) => d.color)\r\n .style('fill-opacity', (d: LineChartSeries) => (hasSelection && !d.selected) ? LineChart.DimmedAreaFillOpacity : LineChart.AreaFillOpacity);\r\n\r\n catAreaSelect.exit().remove();\r\n }\r\n\r\n let catSelect = this.mainGraphicsContext.selectAll(LineChart.CategorySelector.selector)\r\n .data(data.series, (d: LineChartDataPoint) => d.identity.getKey());\r\n\r\n let catEnter = catSelect\r\n .enter()\r\n .append('g')\r\n .classed(LineChart.CategorySelector.class, true);\r\n\r\n catEnter.append(LineChart.PathElementName);\r\n catEnter.selectAll(LineChart.CategoryValuePoint.selector)\r\n .data((d: LineChartSeries) => d.data)\r\n .enter()\r\n .append(LineChart.CircleElementName)\r\n .classed(LineChart.CategoryValuePoint.class, true);\r\n\r\n // moving this up to avoid using the svg path generator with NaN values\r\n // do not move this without validating that no errors are thrown in the browser console\r\n catSelect.exit().remove();\r\n\r\n // add the drag handle, if needed\r\n if (this.isInteractiveChart && !this.dragHandle) {\r\n let handleTop = this.getAvailableHeight();\r\n this.dragHandle = this.hoverLineContext.append('circle')\r\n .attr('cx', 0)\r\n .attr('cy', handleTop)\r\n .attr('r', '6px')\r\n .classed('drag-handle', true);\r\n }\r\n\r\n // Create the selection circles\r\n let linesCount = catSelect.data().length; // number of lines plotted\r\n while (this.selectionCircles.length < linesCount) {\r\n let addedCircle = this.hoverLineContext.append(LineChart.CircleElementName)\r\n .classed(LineChart.CircleClassName, true)\r\n .attr('r', LineChart.CircleRadius).style('opacity', 0);\r\n this.selectionCircles.push(addedCircle);\r\n }\r\n\r\n while (this.selectionCircles.length > linesCount) {\r\n this.selectionCircles.pop().remove();\r\n }\r\n\r\n let catUpdate = this.mainGraphicsContext.selectAll(LineChart.CategorySelector.selector);\r\n\r\n let lineSelection = catUpdate.select(LineChart.PathElementName)\r\n .classed(this.lineClassAndSelector.class, true)\r\n .style('stroke', (d: LineChartSeries) => d.color)\r\n .style('stroke-opacity', (d: LineChartSeries) => ColumnUtil.getFillOpacity(d.selected, false, hasSelection, false));\r\n lineSelection\r\n .transition()\r\n .ease('linear')\r\n .duration(duration)\r\n .attr('d', (d: LineChartSeries) => {\r\n return line(d.data);\r\n });\r\n\r\n let that = this;\r\n let updateSelection = catUpdate.selectAll(LineChart.CategoryValuePoint.selector);\r\n let transitions = updateSelection\r\n .style('fill', function () {\r\n let lineSeries = d3.select(this.parentNode).datum();\r\n return lineSeries.color;\r\n })\r\n .style('fill-opacity', function () {\r\n let lineSeries = d3.select(this.parentNode).datum();\r\n return ColumnUtil.getFillOpacity(lineSeries.selected, false, hasSelection, false);\r\n })\r\n .transition()\r\n .duration(duration)\r\n .attr({\r\n 'cx': function (d: LineChartDataPoint, i: number) {\r\n let lineSeries = d3.select(this.parentNode).datum();\r\n let circleIndex = that.shouldDrawCircle(lineSeries, i);\r\n return circleIndex ? xScale(that.getXValue(d)) : 0;\r\n },\r\n 'cy': function (d: LineChartDataPoint, i: number) {\r\n let lineSeries = d3.select(this.parentNode).datum();\r\n let circleIndex = that.shouldDrawCircle(lineSeries, i);\r\n return circleIndex ? yScale(d.value) : 0;\r\n },\r\n 'r': function (d: LineChartDataPoint, i: number) {\r\n let lineSeries = d3.select(this.parentNode).datum();\r\n let circleIndex = that.shouldDrawCircle(lineSeries, i);\r\n return circleIndex ? LineChart.CircleRadius : 0;\r\n }\r\n });\r\n if (this.isInteractiveChart && this.hasDataPoint(data.series)) {\r\n let selectionSize = updateSelection.size();\r\n let endedTransitionCount = 0;\r\n transitions.each('end', () => {\r\n // When transitions finish, and it's an interactive chart - select the last column (draw the legend and the handle)\r\n endedTransitionCount++;\r\n if (endedTransitionCount === selectionSize) { // all transitions had finished\r\n this.selectColumn(CartesianHelper.findMaxCategoryIndex(data.series), true);\r\n }\r\n });\r\n }\r\n\r\n let dataPoints: LineChartDataPoint[] = null;\r\n if (data.dataLabelsSettings.show) {\r\n dataPoints = [];\r\n for (let i = 0, ilen = data.series.length; i < ilen; i++) {\r\n Array.prototype.push.apply(dataPoints, data.series[i].data);\r\n }\r\n }\r\n\r\n catSelect.exit().remove();\r\n\r\n // # Code from here is taken from renderNew:\r\n\r\n // Add data labels\r\n let labelDataPointsGroups: LabelDataPointGroup[];\r\n if (data.dataLabelsSettings.show)\r\n labelDataPointsGroups = this.createLabelDataPoints();\r\n\r\n return dataPoints == null ? null : {\r\n dataPoints: dataPoints,\r\n behaviorOptions: null,\r\n labelDataPoints: null,\r\n labelsAreNumeric: null,\r\n labelDataPointGroups: labelDataPointsGroups\r\n };\r\n }\r\n\r\n /**\r\n * Note: Public for tests.\r\n */\r\n public getSeriesTooltipInfo(pointData: HoverLineDataPoint[]): TooltipDataItem[] {\r\n if (_.isEmpty(pointData)) {\r\n return null;\r\n }\r\n let transparentColor = \"#000000\";\r\n let hiddenItemOpacity = \"0\";\r\n let tooltipinfo: TooltipDataItem[] = [];\r\n const maxNumberOfItems = 10; // to limit the number of rows we display\r\n let hasDynamicSeries = !_.isEmpty(pointData) && pointData[0].seriesDisplayName;\r\n\r\n // count to the maximum number of rows we can display\r\n let count = 0;\r\n\r\n if (!_.any(pointData, (point: HoverLineDataPoint) => !_.isEmpty(point.extraTooltipInfo))) {\r\n for (let point of pointData) {\r\n if (count >= maxNumberOfItems) break;\r\n if (point.value != null) {\r\n tooltipinfo.push({\r\n header: point.category,\r\n color: point.color,\r\n displayName: point.seriesName || point.measureDisplayName,\r\n value: point.measure\r\n });\r\n count++;\r\n }\r\n }\r\n }\r\n else {\r\n for (let point of pointData) {\r\n if (count >= maxNumberOfItems) break;\r\n if (point.value != null) {\r\n // Add series data\r\n if (hasDynamicSeries) {\r\n tooltipinfo.push({\r\n header: point.category,\r\n displayName: point.seriesDisplayName,\r\n value: point.seriesName,\r\n });\r\n }\r\n // Add value data\r\n tooltipinfo.push({\r\n header: point.category,\r\n color: point.color,\r\n displayName: point.measureDisplayName,\r\n value: point.measure\r\n });\r\n count += 2;\r\n // Add tooltip bucket data for each point for dynamic series\r\n if (hasDynamicSeries && !_.isEmpty(point.extraTooltipInfo)) {\r\n for (let extraTooltipInfo of point.extraTooltipInfo) {\r\n if (count >= maxNumberOfItems) break;\r\n tooltipinfo.push({\r\n header: point.category,\r\n color: transparentColor,\r\n displayName: extraTooltipInfo.displayName,\r\n value: extraTooltipInfo.value,\r\n opacity: hiddenItemOpacity,\r\n });\r\n count++;\r\n }\r\n }\r\n }\r\n }\r\n\r\n // Add tooltip bucket data to the end once for static series\r\n if (!hasDynamicSeries) {\r\n for (let extraTooltipInfo of pointData[0].extraTooltipInfo) {\r\n if (count >= maxNumberOfItems) break;\r\n tooltipinfo.push({\r\n header: pointData[0].category,\r\n color: transparentColor,\r\n displayName: extraTooltipInfo.displayName,\r\n value: extraTooltipInfo.value,\r\n opacity: hiddenItemOpacity,\r\n });\r\n count++;\r\n }\r\n }\r\n }\r\n\r\n if (tooltipinfo.length === 0)\r\n return null; //don't draw an empty tooltip container\r\n\r\n return tooltipinfo;\r\n }\r\n\r\n /**\r\n * Note: Public for tests.\r\n */\r\n public getTooltipInfoForCombo(tooltipEvent: TooltipEvent, pointX: number): TooltipDataItem[] {\r\n // update pointX, the mouse coordinate, with the left-offset of the SVGRect from the x-scale space so we can use the d3.scale to get the index.\r\n let categoryIndex = this.getCategoryIndexFromTooltipEvent(tooltipEvent, pointX);\r\n let seriesData = <LineChartSeries>tooltipEvent.data;\r\n\r\n let dataPoint: LineChartDataPoint;\r\n if (seriesData && seriesData.data && seriesData.data.length) {\r\n dataPoint = _.find(seriesData.data, (dp: LineChartDataPoint) => dp.categoryIndex === categoryIndex);\r\n }\r\n if (dataPoint)\r\n return dataPoint.tooltipInfo;\r\n // return undefined so we don't show an empty tooltip\r\n }\r\n\r\n /**\r\n * Note: Public for tests.\r\n */\r\n public getCategoryIndexFromTooltipEvent(tooltipEvent: TooltipEvent, pointX: number): number {\r\n if (tooltipEvent.data && tooltipEvent.data.categoryIndex != null) {\r\n // Tooltip originated with a dot; simply return the categoryIndex from the dot's bound data\r\n return tooltipEvent.data.categoryIndex;\r\n }\r\n\r\n let seriesData = <LineChartSeries>tooltipEvent.data;\r\n let offsetX = 0; // Offset based on the firstCategoryOffset (since lines don't start at x = 0) as well as the offset due to lines that may not start at the first category\r\n if (seriesData && !_.isEmpty(seriesData.data) && this.xAxisProperties) {\r\n // Tooltip originated from a path; determine series offset from the first point that is part of a path\r\n pointX = this.adjustPathXCoordinate(pointX);\r\n let firstPathPoint = _.find(seriesData.data, (dataPoint: LineChartDataPoint, index: number, dataPoints: LineChartDataPoint[]) => {\r\n let nextDataPoint = dataPoints[index + 1];\r\n return dataPoint.value != null && nextDataPoint && nextDataPoint.value != null;\r\n });\r\n debug.assertValue(firstPathPoint, \"If there is data on the tooltipEvent but no categoryIndex, there should always be two consecutive non-null values\");\r\n offsetX = this.xAxisProperties.scale(this.getXValue(firstPathPoint)) + this.getXOfFirstCategory();\r\n }\r\n // else: Tooltip originated from the background; no offsetX is needed\r\n return this.findIndex(pointX, offsetX);\r\n }\r\n\r\n public getVisualCategoryAxisIsScalar(): boolean {\r\n return this.data ? this.data.isScalar : false;\r\n }\r\n\r\n public getSupportedCategoryAxisType(): string {\r\n let dvCategories = this.dataViewCat ? this.dataViewCat.categories : undefined;\r\n let categoryType: ValueTypeDescriptor = { text: true };\r\n if (!_.isEmpty(dvCategories)) {\r\n let scalarKeys = LineChart.getScalarKeys(dvCategories[0]);\r\n if (scalarKeys && !_.isEmpty(scalarKeys.values))\r\n return axisType.both;\r\n if (dvCategories[0].source && dvCategories[0].source.type)\r\n categoryType = dvCategories[0].source.type;\r\n }\r\n let isOrdinal = AxisHelper.isOrdinal(categoryType);\r\n return isOrdinal ? axisType.categorical : axisType.both;\r\n }\r\n\r\n public getPreferredPlotArea(isScalar: boolean, categoryCount: number, categoryThickness: number): IViewport {\r\n return CartesianChart.getPreferredPlotArea(\r\n categoryCount,\r\n categoryThickness,\r\n this.currentViewport,\r\n this.isScrollable,\r\n isScalar,\r\n this.margin,\r\n true);\r\n }\r\n\r\n private getCategoryCount(origCatgSize): number {\r\n let availableWidth = this.getAvailableWidth();\r\n let categoryThickness = CartesianChart.MinOrdinalRectThickness;\r\n return Math.min(Math.round((availableWidth - categoryThickness * CartesianChart.OuterPaddingRatio * 2) / categoryThickness), origCatgSize);\r\n }\r\n\r\n private getAvailableWidth(): number {\r\n return this.currentViewport.width - (this.margin.left + this.margin.right);\r\n }\r\n\r\n private getAvailableHeight(): number {\r\n return this.currentViewport.height - (this.margin.top + this.margin.bottom);\r\n }\r\n\r\n private static sliceSeries(series: LineChartSeries[], newLength: number, startIndex: number = 0): LineChartSeries[] {\r\n let newSeries: LineChartSeries[] = [];\r\n if (series && series.length > 0) {\r\n debug.assert(series[0].data.length >= newLength, \"invalid newLength\");\r\n for (let i = 0, len = series.length; i < len; i++) {\r\n newSeries[i] = Prototype.inherit(series[i]);\r\n newSeries[i].data = series[i].data.slice(startIndex, startIndex + newLength);\r\n }\r\n }\r\n return newSeries;\r\n }\r\n\r\n private static getScalarKeys(dataViewCategoryColumn: DataViewCategoryColumn): ScalarKeys {\r\n let categoryColumnObjects: DataViewObjects[];\r\n if (dataViewCategoryColumn)\r\n categoryColumnObjects = dataViewCategoryColumn.objects;\r\n\r\n if (!_.isEmpty(categoryColumnObjects)) {\r\n let scalarKeys: ScalarKeys;\r\n let categoryObjectsLength = categoryColumnObjects.length;\r\n\r\n for (let i = 0; i < categoryObjectsLength; i++) {\r\n let categoryObjects: DataViewObjects = categoryColumnObjects[i];\r\n let scalarKey = DataViewObjects.getValue<PrimitiveValue>(categoryObjects, lineChartProps.scalarKey.scalarKeyMin);\r\n if (scalarKey !== undefined) {\r\n if (!scalarKeys) {\r\n scalarKeys = {\r\n values: new Array<PrimitiveValueRange>(categoryObjectsLength)\r\n };\r\n }\r\n let key: PrimitiveValueRange = { min: scalarKey };\r\n scalarKeys.values[i] = key;\r\n }\r\n }\r\n return scalarKeys;\r\n }\r\n }\r\n\r\n private getXOfFirstCategory(): number {\r\n if (!this.data.isScalar) {\r\n // This will place the line points in the middle of the bands\r\n // So they center with Labels when scale is ordinal.\r\n let xScale = <D3.Scale.OrdinalScale>this.xAxisProperties.scale;\r\n if (xScale.rangeBand) {\r\n return xScale.rangeBand() / 2;\r\n }\r\n }\r\n return 0;\r\n }\r\n\r\n private hasDataPoint(series: LineChartSeries[]): boolean {\r\n if (series.length === 0)\r\n return false;\r\n for (let i = 0, len = series.length; i < len; i++) {\r\n if (series[i].data.length > 0)\r\n return true;\r\n }\r\n return false;\r\n }\r\n\r\n private getXValue(d: LineChartDataPoint): any {\r\n return this.data.isScalar ? d.categoryValue : d.categoryIndex;\r\n }\r\n\r\n /**\r\n * This checks to see if a data point is isolated, which means\r\n * the previous and next data point are both null.\r\n */\r\n private shouldDrawCircle(d: LineChartSeries, i: number): boolean {\r\n let dataLength = d.data.length;\r\n let isLastPoint = i === (dataLength - 1);\r\n let isFirstPoint = i === 0;\r\n\r\n if (i > dataLength - 1 || d.data[i] === null || d.data[i].value === null)\r\n return false;\r\n\r\n if (isFirstPoint && isLastPoint)\r\n return true;\r\n if (isFirstPoint && dataLength > 1 && d.data[i + 1].value === null)\r\n return true;\r\n if (!isFirstPoint && isLastPoint && d.data[i - 1].value === null)\r\n return true;\r\n if (!isFirstPoint && !isLastPoint && d.data[i - 1].value === null && d.data[i + 1].value === null)\r\n return true;\r\n return false;\r\n }\r\n\r\n public selectColumnForTooltip(columnIndex: number, force: boolean = false): HoverLineDataPoint[] {\r\n let x = this.getChartX(columnIndex) + this.getXOfFirstCategory();\r\n\r\n let dataPoints = this.createTooltipDataPoints(columnIndex);\r\n if (dataPoints.length > 0) {\r\n this.setHoverLineForTooltip(x);\r\n }\r\n this.setDotsForTooltip(x, dataPoints);\r\n\r\n return dataPoints;\r\n }\r\n\r\n private setHoverLineForTooltip(chartX: number) {\r\n chartX = chartX || 0;\r\n this.hoverLine\r\n .attr('x1', chartX)\r\n .attr('x2', chartX)\r\n .attr(\"y1\", 0)\r\n .attr(\"y2\", this.getAvailableHeight())\r\n .style('opacity', 1);\r\n }\r\n\r\n private setDotsForTooltip(chartX: number, dataPoints: HoverLineDataPoint[]) {\r\n let isStackedArea = EnumExtensions.hasFlag(this.lineType, LineChartType.stackedArea);\r\n let dotYPosition = isStackedArea ? d => this.yAxisProperties.scale(d.stackedValue) : d => this.yAxisProperties.scale(d.value);\r\n let tooltipDots = this.hoverLineContext.selectAll(LineChart.HoverLineCircleDot.selector).data(dataPoints);\r\n tooltipDots\r\n .enter()\r\n .append(LineChart.CircleElementName)\r\n .classed(LineChart.HoverLineCircleDot.class, true);\r\n tooltipDots\r\n .filter(d => d.value)\r\n .attr('fill', d => d.color)\r\n .attr(\"r\", 3)\r\n .attr(\"cx\", chartX)\r\n .attr(\"cy\", dotYPosition);\r\n tooltipDots.exit().remove();\r\n }\r\n\r\n /**\r\n * Updates the hover line and the legend with the selected colums (given by columnIndex).\r\n * This is for the Mobile renderer with InteractiveLegend\r\n */\r\n public selectColumn(columnIndex: number, force: boolean = false) {\r\n if (!force && this.lastInteractiveSelectedColumnIndex === columnIndex) return; // same column, nothing to do here\r\n\r\n this.lastInteractiveSelectedColumnIndex = columnIndex;\r\n let x = this.getChartX(columnIndex);\r\n this.setHoverLine(x, columnIndex);\r\n let legendItems = this.createLegendDataPoints(columnIndex);\r\n if (legendItems)\r\n this.options.cartesianHost.updateLegend(legendItems);\r\n }\r\n\r\n private setHoverLine(chartX: number, columnIndex: number) {\r\n this.hoverLine\r\n .attr('x1', chartX)\r\n .attr('x2', chartX)\r\n .attr(\"y1\", 0).attr(\"y2\", this.getAvailableHeight())\r\n .style('opacity', 1);\r\n\r\n let that = this;\r\n this.mainGraphicsContext\r\n .selectAll(LineChart.CategorySelector.selector)\r\n .selectAll(LineChart.PathElementName)\r\n .each(function (series: LineChartSeries) {\r\n // Get the item color for the handle dots\r\n let color = series.color;\r\n let circleToChange = that.selectionCircles[series.lineIndex];\r\n\r\n circleToChange\r\n .attr({\r\n 'cx': chartX,\r\n 'cy': () => {\r\n let pathElement = d3.select(this).node<D3.D3Element>();\r\n let pos = that.getPosition(chartX, pathElement);\r\n return pos.y;\r\n }\r\n })\r\n .style({\r\n 'opacity': () => _.some(series.data, (value) => value.categoryIndex === columnIndex) ? 1 : 0,\r\n 'fill': color\r\n });\r\n\r\n if (that.dragHandle) that.dragHandle.attr('cx', chartX);\r\n });\r\n }\r\n\r\n private getChartX(columnIndex: number): number {\r\n let x: number = 0;\r\n if (this.data.isScalar) {\r\n if (columnIndex >= 0 && columnIndex < this.data.categoryData.length)\r\n x = Math.max(0, this.xAxisProperties.scale(this.data.categoryData[columnIndex].categoryValue));\r\n } else {\r\n x = Math.max(0, this.xAxisProperties.scale(columnIndex));\r\n }\r\n\r\n let rangeEnd = powerbi.visuals.AxisHelper.extent(this.xAxisProperties.scale)[1];\r\n x = Math.min(x, rangeEnd);\r\n if (!isNaN(x))\r\n return x;\r\n return 0;\r\n }\r\n\r\n /**\r\n * Finds the index of the category of the given x coordinate given.\r\n * pointX is in non-scaled screen-space, and offsetX is in render-space.\r\n * offsetX does not need any scaling adjustment.\r\n * @param {number} pointX The mouse coordinate in screen-space, without scaling applied\r\n * @param {number} offsetX Any left offset in d3.scale render-space\r\n * @return {number}\r\n */\r\n private findIndex(pointX: number, offsetX?: number): number {\r\n // we are using mouse coordinates that do not know about any potential CSS transform scale\r\n let xScale = this.scaleDetector.getScale().x;\r\n if (!Double.equalWithPrecision(xScale, 1.0, 0.00001)) {\r\n pointX = pointX / xScale;\r\n }\r\n if (offsetX) {\r\n pointX += offsetX;\r\n }\r\n\r\n let index = powerbi.visuals.AxisHelper.invertScale(this.xAxisProperties.scale, pointX);\r\n if (this.data.isScalar) {\r\n // When we have scalar data the inverted scale produces a category value, so we need to search for the closest index.\r\n index = AxisHelper.findClosestXAxisIndex(index, this.data.categoryData);\r\n }\r\n\r\n return index;\r\n }\r\n\r\n private getPosition(x: number, pathElement: D3.D3Element): SVGPoint {\r\n let pathLength = pathElement.getTotalLength();\r\n let pos: SVGPoint;\r\n let beginning = 0, end = pathLength, target;\r\n\r\n while (true) {\r\n target = Math.floor((beginning + end) / 2);\r\n pos = pathElement.getPointAtLength(target);\r\n SVGUtil.ensureValidSVGPoint(pos);\r\n if ((target === end || target === beginning) && pos.x !== x)\r\n break;\r\n if (pos.x > x) end = target;\r\n else if (pos.x < x) beginning = target;\r\n else\r\n break;\r\n }\r\n return pos;\r\n }\r\n\r\n private createTooltipDataPoints(columnIndex: number): HoverLineDataPoint[] {\r\n let data = this.data;\r\n if (!data || data.series.length === 0 || !data.categories || !data.categoryData)\r\n return [];\r\n\r\n let dataPoints: HoverLineDataPoint[] = [];\r\n let category: any;\r\n\r\n debug.assert(columnIndex < data.categoryData.length, 'category index out of range (categoryData)');\r\n debug.assert(columnIndex < data.categories.length, 'category index out of range (categories)');\r\n\r\n let categoryDataPoint: LineChartDataPoint = data.categoryData[columnIndex];\r\n if (this.data.isScalar) {\r\n if (categoryDataPoint) {\r\n if (AxisHelper.isDateTime(this.xAxisProperties.axisType) && !data.scalarMetadata) {\r\n category = CartesianHelper.lookupXValue(this.data, categoryDataPoint.categoryValue, this.xAxisProperties.axisType, this.data.isScalar);\r\n }\r\n else {\r\n category = data.categories[columnIndex];\r\n }\r\n }\r\n }\r\n else {\r\n category = CartesianHelper.lookupXValue(this.data, columnIndex, this.xAxisProperties.axisType, this.data.isScalar);\r\n }\r\n\r\n let formatStringProp = lineChartProps.general.formatString;\r\n\r\n for (let series of data.series) {\r\n let lineData = series.data;\r\n let lineDataPoint: LineChartDataPoint;\r\n if (this.data.isScalar) {\r\n if (categoryDataPoint) {\r\n lineDataPoint = lineData.filter((data) => {\r\n return data.categoryValue === categoryDataPoint.categoryValue;\r\n })[0];\r\n }\r\n }\r\n else {\r\n lineDataPoint = lineData[columnIndex];\r\n }\r\n\r\n let value = lineDataPoint && lineDataPoint.value;\r\n if (value != null) {\r\n let dataPoint: HoverLineDataPoint = {\r\n color: series.color,\r\n category: valueFormatter.format(category, valueFormatter.getFormatString(series.xCol, formatStringProp)),\r\n measureDisplayName: series.displayName,\r\n measure: valueFormatter.format(value, valueFormatter.getFormatString(series.yCol, formatStringProp)),\r\n value: value,\r\n stackedValue: lineDataPoint.stackedValue,\r\n extraTooltipInfo: lineDataPoint.extraTooltipInfo,\r\n };\r\n if (data.hasDynamicSeries) {\r\n dataPoint.seriesDisplayName = data.seriesDisplayName;\r\n dataPoint.seriesName = series.dynamicDisplayName;\r\n }\r\n dataPoints.push(dataPoint);\r\n }\r\n }\r\n\r\n return dataPoints;\r\n }\r\n\r\n private createLegendDataPoints(columnIndex: number): LegendData {\r\n let data = this.data;\r\n if (!data || !data.series || data.series.length < 1)\r\n return;\r\n\r\n let legendDataPoints: LegendDataPoint[] = [];\r\n let category: any;\r\n\r\n // 'category' and 'measure' are only for Mobile interactive legend, Minerva legend does not need them\r\n let categoryDataPoint: LineChartDataPoint = data.categoryData[columnIndex];\r\n if (this.isInteractiveChart && categoryDataPoint) {\r\n if (this.data.isScalar) {\r\n category = categoryDataPoint.categoryValue;\r\n if (AxisHelper.isDateTime(this.xAxisProperties.axisType))\r\n category = new Date(category);\r\n }\r\n else {\r\n category = CartesianHelper.lookupXValue(this.data, columnIndex, this.xAxisProperties.axisType, this.data.isScalar);\r\n }\r\n }\r\n\r\n let formatStringProp = lineChartProps.general.formatString;\r\n let seriesYCol: DataViewMetadataColumn = null;\r\n // iterating over the line data (i is for a line)\r\n for (let i = 0, len = data.series.length; i < len; i++) {\r\n let series = data.series[i];\r\n let lineData = series.data;\r\n\r\n // 'category' and 'measure' are only for Mobile interactive legend, Minerva legend does not need them\r\n let measure: any;\r\n if (this.isInteractiveChart) {\r\n let lineDataPoint;\r\n if (this.data.isScalar) {\r\n // Scalar series skip null values, and therefore do not share the same category index\r\n // Search this series for the categoryValue - it may not exist\r\n if (categoryDataPoint) {\r\n let targetCategoryValue = categoryDataPoint.categoryValue;\r\n for (let i = 0; i < lineData.length; i++) {\r\n if (lineData[i].categoryValue === targetCategoryValue) {\r\n lineDataPoint = lineData[i];\r\n break;\r\n }\r\n }\r\n }\r\n }\r\n else {\r\n // ordinal series all share the same x-indicies\r\n lineDataPoint = lineData[columnIndex];\r\n }\r\n measure = lineDataPoint && lineDataPoint.value;\r\n }\r\n\r\n let label = converterHelper.getFormattedLegendLabel(series.yCol, this.dataViewCat.values, formatStringProp);\r\n seriesYCol = series.yCol;\r\n legendDataPoints.push({\r\n color: series.color,\r\n icon: LegendIcon.Line,\r\n label: label,\r\n // TODO: category: CartesianChartInteractiveLegend only needs one category value for part of the Title, we don't need to put it on each point.\r\n category: valueFormatter.format(category, valueFormatter.getFormatString(series.xCol, formatStringProp)),\r\n measure: valueFormatter.format(measure, valueFormatter.getFormatString(series.yCol, formatStringProp)),\r\n identity: series.identity,\r\n selected: series.selected,\r\n });\r\n }\r\n\r\n let dvValues = this.dataViewCat ? this.dataViewCat.values : null;\r\n let title = dvValues && dvValues.source ? dvValues.source.displayName : \"\";\r\n return {\r\n title: title,\r\n dataPoints: legendDataPoints,\r\n grouped: data.hasDynamicSeries,\r\n };\r\n }\r\n\r\n private createLabelDataPoints(): LabelDataPointGroup[] {\r\n let xScale = this.xAxisProperties.scale;\r\n let yScale = this.yAxisProperties.scale;\r\n let lineshift = this.getXOfFirstCategory();\r\n let data = this.data;\r\n let series = this.clippedData ? this.clippedData.series : data.series;\r\n let baseLabelSettings = data.dataLabelsSettings;\r\n let formattersCache = NewDataLabelUtils.createColumnFormatterCacheManager();\r\n let isStackedArea = EnumExtensions.hasFlag(this.lineType, LineChartType.stackedArea);\r\n let labelDataPointGroups: LabelDataPointGroup[] = [];\r\n let labelSettings: LineChartDataLabelsSettings;\r\n let axisFormatter: number;\r\n let seriesLabelDataPoints: LabelDataPoint[];\r\n let seriesDataPointsCandidates: LineChartDataPoint[];\r\n let seriesIndex: number;\r\n let seriesCount: number;\r\n let currentSeries: LineChartSeries;\r\n\r\n for (seriesIndex = 0, seriesCount = series.length; seriesIndex < seriesCount; seriesIndex++) {\r\n currentSeries = series[seriesIndex];\r\n labelSettings = currentSeries.labelSettings || baseLabelSettings;\r\n let densityAtMax = labelSettings.labelDensity === \"100\";\r\n let maxNumberOfLabels = this.advancedLineLabelsEnabled && !densityAtMax && data.isScalar ? LineChart.getNumberOfLabelsToRender(this.currentViewport.width, _.parseInt(labelSettings.labelDensity)) : currentSeries.data.length;\r\n if (!labelSettings.show) {\r\n labelDataPointGroups[seriesIndex] = {\r\n labelDataPoints: [],\r\n maxNumberOfLabels: 0,\r\n };\r\n continue;\r\n }\r\n\r\n axisFormatter = NewDataLabelUtils.getDisplayUnitValueFromAxisFormatter(this.yAxisProperties.formatter, labelSettings);\r\n let dataPoints = currentSeries.data;\r\n seriesLabelDataPoints = [];\r\n seriesDataPointsCandidates = [];\r\n\r\n let createLabelDataPoint: (dataPoint: LineChartDataPoint, categoryIndex: number) => LabelDataPoint = (dataPoint: LineChartDataPoint, categoryIndex: number) => {\r\n if (dataPoint.value == null)\r\n return null;\r\n\r\n let formatString = dataPoint.labelFormatString;\r\n let formatter = formattersCache.getOrCreate(formatString, labelSettings, axisFormatter);\r\n let text = NewDataLabelUtils.getLabelFormattedText(formatter.format(dataPoint.value));\r\n\r\n let properties: TextProperties = {\r\n text: text,\r\n fontFamily: NewDataLabelUtils.LabelTextProperties.fontFamily,\r\n fontSize: PixelConverter.fromPoint(labelSettings.fontSize),\r\n fontWeight: NewDataLabelUtils.LabelTextProperties.fontWeight,\r\n };\r\n let textWidth = TextMeasurementService.measureSvgTextWidth(properties) + NewDataLabelUtils.LabelDensityPadding;\r\n let textHeight = TextMeasurementService.estimateSvgTextHeight(properties, true /* tightFitForNumeric */);\r\n let parentShape: LabelParentRect | LabelParentPoint;\r\n let isParentRect: boolean = false;\r\n\r\n if (isStackedArea) {\r\n let bottomPos = Math.max(dataPoint.stackedValue - dataPoint.value, yScale.domain()[0]); //this is to make sure the bottom position doesn't go below the domain\r\n let areaWidth = this.currentViewport.width; // Conceptually, we allow line labels to fill the full plot area, so the width is equal to the plot area\r\n\r\n parentShape = {\r\n rect: {\r\n left: xScale(this.getXValue(dataPoint)) - areaWidth / 2,\r\n top: yScale(Math.max(dataPoint.stackedValue, dataPoint.stackedValue - dataPoint.value)),\r\n width: areaWidth,\r\n height: Math.abs(yScale(dataPoint.stackedValue) - yScale(bottomPos))\r\n },\r\n orientation: dataPoint.value >= 0 ? NewRectOrientation.VerticalBottomBased : NewRectOrientation.VerticalTopBased,\r\n validPositions: LineChart.validStackedLabelPositions,\r\n };\r\n\r\n isParentRect = true;\r\n }\r\n else {\r\n parentShape = {\r\n point: {\r\n x: xScale(this.getXValue(dataPoint)) + lineshift,\r\n y: yScale(dataPoint.value),\r\n },\r\n radius: 0,\r\n validPositions: densityAtMax ? [NewPointLabelPosition.Above] : this.getValidLabelPositions(currentSeries, categoryIndex),\r\n };\r\n }\r\n\r\n let labelDataPoint: LabelDataPoint = {\r\n isPreferred: false,\r\n text: text,\r\n textSize: {\r\n width: textWidth,\r\n height: textHeight,\r\n },\r\n outsideFill: labelSettings.labelColor ? labelSettings.labelColor : NewDataLabelUtils.defaultLabelColor,\r\n insideFill: labelSettings.labelColor && isStackedArea ? labelSettings.labelColor : NewDataLabelUtils.defaultInsideLabelColor,\r\n parentType: isParentRect ? LabelDataPointParentType.Rectangle : LabelDataPointParentType.Point,\r\n parentShape: parentShape,\r\n fontSize: labelSettings.fontSize,\r\n identity: dataPoint.identity,\r\n key: dataPoint.key,\r\n };\r\n return labelDataPoint;\r\n };\r\n\r\n if (!_.isEmpty(dataPoints)) {\r\n for (let categoryIndex = 0, categoryCount = dataPoints.length; categoryIndex < categoryCount; categoryIndex++) {\r\n let labelDataPoint = createLabelDataPoint(dataPoints[categoryIndex], categoryIndex);\r\n if (labelDataPoint)\r\n seriesLabelDataPoints.push(labelDataPoint);\r\n }\r\n }\r\n\r\n labelDataPointGroups[seriesIndex] = {\r\n labelDataPoints: seriesLabelDataPoints,\r\n maxNumberOfLabels: maxNumberOfLabels,\r\n };\r\n }\r\n\r\n if (this.advancedLineLabelsEnabled && this.lineType !== LineChartType.stackedArea) {\r\n let sorter = new MinMaxLabelDataPointSorter({\r\n unsortedLabelDataPointGroups: labelDataPointGroups,\r\n series: series,\r\n viewport: this.currentViewport,\r\n yAxisProperties: this.yAxisProperties,\r\n });\r\n\r\n return sorter.getSortedDataLabels();\r\n }\r\n return labelDataPointGroups;\r\n }\r\n\r\n private static getNumberOfLabelsToRender(viewPortWidth: number, labelDensity: number): number {\r\n if (labelDensity == null || labelDensity === 0) {\r\n return LineChart.minimumLabelsToRender;\r\n }\r\n let parsedAndNormalizedDensity = labelDensity / 100;\r\n let maxNumberForViewport = Math.ceil(viewPortWidth / MinMaxLabelDataPointSorter.estimatedLabelWidth);\r\n if (parsedAndNormalizedDensity === 1) {\r\n return maxNumberForViewport;\r\n }\r\n return LineChart.minimumLabelsToRender + Math.floor(parsedAndNormalizedDensity * (maxNumberForViewport - LineChart.minimumLabelsToRender));\r\n }\r\n\r\n /**\r\n * Adjust a mouse coordinate originating from a path; used to fix\r\n * an inconsistency between Internet Explorer and other browsers.\r\n *\r\n * Internet explorer places the origin for the coordinate system of\r\n * mouse events based on the stroke, so that the very edge of the stroke\r\n * is zero. Chrome places the 0 on the edge of the path so that the\r\n * edge of the stroke is -(strokeWidth / 2). We adjust coordinates\r\n * to match Chrome.\r\n *\r\n * @param value The x coordinate to be adjusted\r\n */\r\n private adjustPathXCoordinate(x: number): number {\r\n let xScale = this.scaleDetector.getScale().x;\r\n if (!Double.equalWithPrecision(xScale, 1.0, 0.00001)) {\r\n x -= this.pathXAdjustment * xScale;\r\n }\r\n else {\r\n x -= this.pathXAdjustment;\r\n }\r\n \r\n return x;\r\n }\r\n\r\n /**\r\n * Obtains the pointLabelPosition for the category index within the given series\r\n *\r\n * Rules for line chart data labels:\r\n * 1. Top and bottom > left and right\r\n * 2. Top > bottom unless we're at a local minimum\r\n * 3. Right > left unless:\r\n * a. There is no data point to the left and there is one to the right\r\n * b. There is an equal data point to the right, but not to the left\r\n */\r\n private getValidLabelPositions(series: LineChartSeries, categoryIndex: number): NewPointLabelPosition[] {\r\n if (!this.advancedLineLabelsEnabled) {\r\n return [NewPointLabelPosition.Above];\r\n }\r\n\r\n let data = series.data;\r\n let dataLength = data.length;\r\n let isLastPoint = categoryIndex === (dataLength - 1);\r\n let isFirstPoint = categoryIndex === 0;\r\n\r\n let currentValue = data[categoryIndex].value;\r\n let previousValue = !isFirstPoint ? data[categoryIndex - 1].value : undefined;\r\n let nextValue = !isLastPoint ? data[categoryIndex + 1].value : undefined;\r\n let previousRelativePosition = LineChartRelativePosition.Equal;\r\n let nextRelativePosition = LineChartRelativePosition.Equal;\r\n if (previousValue == null) {\r\n previousRelativePosition = LineChartRelativePosition.None;\r\n }\r\n else if (previousValue > currentValue) {\r\n previousRelativePosition = LineChartRelativePosition.Greater;\r\n }\r\n else if (previousValue < currentValue) {\r\n previousRelativePosition = LineChartRelativePosition.Lesser;\r\n }\r\n if (nextValue === null) {\r\n nextRelativePosition = LineChartRelativePosition.None;\r\n }\r\n else if (nextValue > currentValue) {\r\n nextRelativePosition = LineChartRelativePosition.Greater;\r\n }\r\n else if (nextValue < currentValue) {\r\n nextRelativePosition = LineChartRelativePosition.Lesser;\r\n }\r\n\r\n if (isFirstPoint) {\r\n switch (nextRelativePosition) {\r\n case LineChartRelativePosition.Greater:\r\n return [NewPointLabelPosition.Below, NewPointLabelPosition.Above];\r\n default:\r\n return [NewPointLabelPosition.Above, NewPointLabelPosition.Below];\r\n }\r\n }\r\n if (isLastPoint) {\r\n switch (previousRelativePosition) {\r\n case LineChartRelativePosition.Greater:\r\n return [NewPointLabelPosition.Below, NewPointLabelPosition.Above];\r\n default:\r\n return [NewPointLabelPosition.Above, NewPointLabelPosition.Below];\r\n }\r\n }\r\n\r\n switch (previousRelativePosition) {\r\n case LineChartRelativePosition.None:\r\n switch (nextRelativePosition) {\r\n case LineChartRelativePosition.None:\r\n return [NewPointLabelPosition.Above, NewPointLabelPosition.Below, NewPointLabelPosition.Right, NewPointLabelPosition.Left];\r\n case LineChartRelativePosition.Equal:\r\n return [NewPointLabelPosition.Above, NewPointLabelPosition.Below, NewPointLabelPosition.Left, NewPointLabelPosition.Right];\r\n case LineChartRelativePosition.Greater:\r\n return [NewPointLabelPosition.Below, NewPointLabelPosition.Above, NewPointLabelPosition.Left, NewPointLabelPosition.Right];\r\n case LineChartRelativePosition.Lesser:\r\n return [NewPointLabelPosition.Above, NewPointLabelPosition.Below, NewPointLabelPosition.Left, NewPointLabelPosition.Right];\r\n }\r\n case LineChartRelativePosition.Equal:\r\n switch (nextRelativePosition) {\r\n case LineChartRelativePosition.None:\r\n return [NewPointLabelPosition.Above, NewPointLabelPosition.Below, NewPointLabelPosition.Right, NewPointLabelPosition.Left];\r\n case LineChartRelativePosition.Equal:\r\n return [NewPointLabelPosition.Above, NewPointLabelPosition.Below, NewPointLabelPosition.Right, NewPointLabelPosition.Left];\r\n case LineChartRelativePosition.Greater:\r\n return [NewPointLabelPosition.Below, NewPointLabelPosition.Above, NewPointLabelPosition.Right, NewPointLabelPosition.Left];\r\n case LineChartRelativePosition.Lesser:\r\n return [NewPointLabelPosition.Above, NewPointLabelPosition.Below, NewPointLabelPosition.Right, NewPointLabelPosition.Left];\r\n }\r\n case LineChartRelativePosition.Greater:\r\n switch (nextRelativePosition) {\r\n case LineChartRelativePosition.None:\r\n return [NewPointLabelPosition.Below, NewPointLabelPosition.Above, NewPointLabelPosition.Right, NewPointLabelPosition.Left];\r\n case LineChartRelativePosition.Equal:\r\n return [NewPointLabelPosition.Below, NewPointLabelPosition.Above, NewPointLabelPosition.Left, NewPointLabelPosition.Right];\r\n case LineChartRelativePosition.Greater:\r\n return [NewPointLabelPosition.Below, NewPointLabelPosition.Above, NewPointLabelPosition.Right, NewPointLabelPosition.Left];\r\n case LineChartRelativePosition.Lesser:\r\n return [NewPointLabelPosition.Above, NewPointLabelPosition.Below, NewPointLabelPosition.Right, NewPointLabelPosition.Left];\r\n }\r\n case LineChartRelativePosition.Lesser:\r\n switch (nextRelativePosition) {\r\n case LineChartRelativePosition.None:\r\n return [NewPointLabelPosition.Above, NewPointLabelPosition.Below, NewPointLabelPosition.Right, NewPointLabelPosition.Left];\r\n case LineChartRelativePosition.Equal:\r\n return [NewPointLabelPosition.Above, NewPointLabelPosition.Below, NewPointLabelPosition.Left, NewPointLabelPosition.Right];\r\n case LineChartRelativePosition.Greater:\r\n return [NewPointLabelPosition.Above, NewPointLabelPosition.Below, NewPointLabelPosition.Right, NewPointLabelPosition.Left];\r\n case LineChartRelativePosition.Lesser:\r\n return [NewPointLabelPosition.Above, NewPointLabelPosition.Below, NewPointLabelPosition.Right, NewPointLabelPosition.Left];\r\n }\r\n }\r\n }\r\n }\r\n}\r\n\n\n\n/** WEBPACK FOOTER **\n ** ./src/Clients/Visuals/cartesian/lineChart.ts\n **/","/*\r\n * Power BI Visualizations\r\n *\r\n * Copyright (c) Microsoft Corporation\r\n * All rights reserved.\r\n * MIT License\r\n *\r\n * Permission is hereby granted, free of charge, to any person obtaining a copy\r\n * of this software and associated documentation files (the \"\"Software\"\"), to deal\r\n * in the Software without restriction, including without limitation the rights\r\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\r\n * copies of the Software, and to permit persons to whom the Software is\r\n * furnished to do so, subject to the following conditions:\r\n *\r\n * The above copyright notice and this permission notice shall be included in\r\n * all copies or substantial portions of the Software.\r\n *\r\n * THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\r\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\r\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\r\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\r\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\r\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\r\n * THE SOFTWARE.\r\n */\r\n\r\n/// <reference path=\"../_references.ts\"/>\r\n\r\nmodule powerbi.visuals {\r\n import Color = jsCommon.Color;\r\n import PixelConverter = jsCommon.PixelConverter;\r\n import Polygon = shapes.Polygon;\r\n import DataRoleHelper = powerbi.data.DataRoleHelper;\r\n\r\n export interface MapConstructionOptions {\r\n filledMap?: boolean;\r\n geocoder?: IGeocoder;\r\n mapControlFactory?: IMapControlFactory;\r\n behavior?: MapBehavior;\r\n tooltipsEnabled?: boolean;\r\n tooltipBucketEnabled?: boolean;\r\n filledMapDataLabelsEnabled?: boolean;\r\n disableZooming?: boolean;\r\n disablePanning?: boolean;\r\n isLegendScrollable?: boolean;\r\n viewChangeThrottleInterval?: number; // Minimum interval between viewChange events (in milliseconds)\r\n enableCurrentLocation?: boolean;\r\n }\r\n\r\n export interface IMapControlFactory {\r\n createMapControl(element: HTMLElement, options?: Microsoft.Maps.MapOptions): Microsoft.Maps.Map;\r\n ensureMap(locale: string, action: () => void): void;\r\n }\r\n\r\n export interface MapData {\r\n dataPoints: MapDataPoint[];\r\n geocodingCategory: string;\r\n hasDynamicSeries: boolean;\r\n hasSize: boolean;\r\n }\r\n\r\n /**\r\n * The main map data point, which exists for each category\r\n */\r\n export interface MapDataPoint {\r\n geocodingQuery: string;\r\n value: number;\r\n categoryValue: string;\r\n subDataPoints: MapSubDataPoint[];\r\n location?: IGeocodeCoordinate;\r\n paths?: IGeocodeBoundaryPolygon[];\r\n radius?: number;\r\n }\r\n\r\n /**\r\n * SubDataPoint that carries series-based data. For category only maps\r\n * there will only be one of these on each MapDataPoint; for dynamic series,\r\n * there will be one per series for each MapDataPoint.\r\n */\r\n export interface MapSubDataPoint {\r\n value: number;\r\n fill: string;\r\n stroke: string;\r\n identity: SelectionId;\r\n tooltipInfo: TooltipDataItem[];\r\n }\r\n\r\n export interface MapRendererData {\r\n bubbleData?: MapBubble[];\r\n sliceData?: MapSlice[][];\r\n shapeData?: MapShape[];\r\n }\r\n\r\n export interface MapVisualDataPoint extends TooltipEnabledDataPoint, SelectableDataPoint {\r\n x: number;\r\n y: number;\r\n radius: number;\r\n fill: string;\r\n stroke: string;\r\n strokeWidth: number;\r\n labeltext: string;\r\n labelFill: string;\r\n }\r\n\r\n export interface MapBubble extends MapVisualDataPoint {\r\n }\r\n\r\n export interface MapSlice extends MapVisualDataPoint {\r\n value: number;\r\n startAngle?: number;\r\n endAngle?: number;\r\n }\r\n\r\n export interface MapShape extends TooltipEnabledDataPoint, SelectableDataPoint {\r\n absolutePointArray: Float64Array;\r\n path: string;\r\n fill: string;\r\n stroke: string;\r\n strokeWidth: number;\r\n key: string;\r\n labeltext: string;\r\n displayLabel: boolean;\r\n catagoryLabeltext?: string;\r\n labelFormatString: string;\r\n }\r\n\r\n /**\r\n * Used because data points used in D3 pie layouts are placed within a container with pie information.\r\n */\r\n export interface MapSliceContainer {\r\n data: MapSlice;\r\n }\r\n\r\n /** Note: public for UnitTest */\r\n export interface IMapDataPointRenderer {\r\n init(mapControl: Microsoft.Maps.Map, mapDiv: JQuery, addClearCatcher: boolean): void;\r\n setData(data: MapData): void;\r\n getDataPointCount(): number;\r\n converter(viewPort: IViewport, dataView: DataView, labelSettings: PointDataLabelsSettings, interactivityService: IInteractivityService, tooltipsEnabled: boolean): MapRendererData;\r\n updateInternal(data: MapRendererData, viewport: IViewport, dataChanged: boolean, interactivityService: IInteractivityService, redrawDataLabels: boolean): MapBehaviorOptions;\r\n updateInternalDataLabels(viewport: IViewport, redrawDataLabels: boolean): void;\r\n getDataPointPadding(): number;\r\n clearDataPoints(): void;\r\n }\r\n\r\n export interface DataViewMetadataAutoGeneratedColumn extends DataViewMetadataColumn {\r\n /**\r\n * Indicates that the column was added manually.\r\n */\r\n isAutoGeneratedColumn?: boolean;\r\n }\r\n\r\n export const MaxLevelOfDetail = 23;\r\n export const MinLevelOfDetail = 1;\r\n export const DefaultFillOpacity = 0.5;\r\n export const DefaultBackgroundColor = \"#000000\";\r\n export const LeaderLineColor = \"#000000\";\r\n\r\n export class MapBubbleDataPointRenderer implements IMapDataPointRenderer {\r\n private mapControl: Microsoft.Maps.Map;\r\n private mapData: MapData;\r\n private maxDataPointRadius: number;\r\n private svg: D3.Selection;\r\n private clearSvg: D3.Selection;\r\n private clearCatcher: D3.Selection;\r\n private bubbleGraphicsContext: D3.Selection;\r\n private sliceGraphicsContext: D3.Selection;\r\n private labelGraphicsContext: D3.Selection;\r\n private labelBackgroundGraphicsContext: D3.Selection;\r\n private sliceLayout: D3.Layout.PieLayout;\r\n private arc: D3.Svg.Arc;\r\n private dataLabelsSettings: PointDataLabelsSettings;\r\n private tooltipsEnabled: boolean;\r\n private static validLabelPositions: NewPointLabelPosition[] = [NewPointLabelPosition.Above, NewPointLabelPosition.Below, NewPointLabelPosition.Left, NewPointLabelPosition.Right];\r\n private mapRendererData: MapRendererData;\r\n private root: JQuery;\r\n\r\n public constructor(tooltipsEnabled: boolean) {\r\n this.tooltipsEnabled = tooltipsEnabled;\r\n }\r\n\r\n public init(mapControl: Microsoft.Maps.Map, mapDiv: JQuery, addClearCatcher: boolean): void {\r\n /*\r\n The layout of the visual would look like :\r\n <div class=\"visual mapControl\">\r\n <div class=\"MicrosoftMap\">\r\n <!-- Bing maps stuff -->\r\n <svg>\r\n <rect class=\"clearCatcher\"></rect>\r\n </svg>\r\n </div>\r\n <svg>\r\n <g class=\"mapBubbles>\r\n <!-- our geometry -->\r\n </g>\r\n <g class=\"mapSlices>\r\n <!-- our geometry -->\r\n </g>\r\n </svg>\r\n </div>\r\n\r\n */\r\n\r\n this.mapControl = mapControl;\r\n this.root = mapDiv;\r\n let root = d3.select(mapDiv[0]);\r\n root.attr(\"drag-resize-disabled\", \"true\"); // Enable panning within the maps in IE\r\n let svg = this.svg = root\r\n .append('svg')\r\n .style(\"position\", \"absolute\") // Absolute position so that the svg will overlap with the canvas.\r\n .style(\"pointer-events\", \"none\");\r\n if (addClearCatcher) {\r\n let clearSvg = this.clearSvg = d3.select(<HTMLElement>this.mapControl.getRootElement())\r\n .append('svg')\r\n .style('position', 'absolute'); // Absolute position so that the svg will overlap with the canvas.\r\n this.clearCatcher = appendClearCatcher(clearSvg);\r\n }\r\n this.bubbleGraphicsContext = svg\r\n .append(\"g\")\r\n .classed(\"mapBubbles\", true);\r\n this.sliceGraphicsContext = svg\r\n .append(\"g\")\r\n .classed(\"mapSlices\", true);\r\n this.labelBackgroundGraphicsContext = svg\r\n .append(\"g\")\r\n .classed(NewDataLabelUtils.labelBackgroundGraphicsContextClass.class, true);\r\n this.labelGraphicsContext = svg\r\n .append(\"g\")\r\n .classed(NewDataLabelUtils.labelGraphicsContextClass.class, true);\r\n this.sliceLayout = d3.layout.pie()\r\n .sort(null)\r\n .value((d: MapSlice) => {\r\n return d.value;\r\n });\r\n this.arc = d3.svg.arc();\r\n this.clearMaxDataPointRadius();\r\n this.dataLabelsSettings = dataLabelUtils.getDefaultMapLabelSettings();\r\n }\r\n\r\n public setData(data: MapData): void {\r\n this.mapData = data;\r\n }\r\n\r\n public clearDataPoints(): void {\r\n this.mapData = {\r\n dataPoints: [],\r\n geocodingCategory: null,\r\n hasDynamicSeries: false,\r\n hasSize: false,\r\n };\r\n }\r\n\r\n public getDataPointCount(): number {\r\n if (!this.mapData)\r\n return 0;\r\n // Filter out any data points without a location since those aren't actually being drawn\r\n return _.filter(this.mapData.dataPoints, (value: MapDataPoint) => !!value.location).length;\r\n }\r\n\r\n public getDataPointPadding(): number {\r\n return this.maxDataPointRadius * 2;\r\n }\r\n\r\n private clearMaxDataPointRadius(): void {\r\n this.maxDataPointRadius = 0;\r\n }\r\n\r\n private setMaxDataPointRadius(dataPointRadius: number): void {\r\n this.maxDataPointRadius = Math.max(dataPointRadius, this.maxDataPointRadius);\r\n }\r\n\r\n public getDefaultMap(geocodingCategory: string, dataPointCount: number): void {\r\n this.clearDataPoints();\r\n }\r\n\r\n public converter(viewport: IViewport, dataView: DataView, labelSettings: PointDataLabelsSettings, interactivityService: IInteractivityService, tooltipsEnabled: boolean = true): MapRendererData {\r\n let mapControl = this.mapControl;\r\n let widthOverTwo = viewport.width / 2;\r\n let heightOverTwo = viewport.height / 2;\r\n\r\n let strokeWidth = 1;\r\n\r\n //update data label settings\r\n this.dataLabelsSettings = labelSettings;\r\n\r\n // See MapSeriesPresenter::GetDataPointRadius for the PV behavior\r\n let radiusScale = Math.min(viewport.width, viewport.height) / 384;\r\n this.clearMaxDataPointRadius();\r\n\r\n let bubbleData: MapBubble[] = [];\r\n let sliceData: MapSlice[][] = [];\r\n let categorical: DataViewCategorical = dataView ? dataView.categorical : null;\r\n\r\n let grouped: DataViewValueColumnGroup[];\r\n let sizeIndex = -1;\r\n let dataValuesSource: DataViewMetadataColumn;\r\n if (categorical && categorical.values) {\r\n grouped = categorical.values.grouped();\r\n sizeIndex = DataRoleHelper.getMeasureIndexOfRole(grouped, \"Size\");\r\n dataValuesSource = categorical.values.source;\r\n }\r\n\r\n let dataPoints = this.mapData ? this.mapData.dataPoints : [];\r\n let hasSize = this.mapData.hasSize;\r\n for (let categoryIndex = 0, categoryCount = dataPoints.length; categoryIndex < categoryCount; categoryIndex++) {\r\n let dataPoint = dataPoints[categoryIndex];\r\n let categoryValue = dataPoint.categoryValue;\r\n let location = dataPoint.location;\r\n\r\n if (location) {\r\n let xy = mapControl.tryLocationToPixel(new Microsoft.Maps.Location(location.latitude, location.longitude));\r\n let x = xy.x + widthOverTwo;\r\n let y = xy.y + heightOverTwo;\r\n\r\n let radius = dataPoint.radius * radiusScale;\r\n this.setMaxDataPointRadius(radius);\r\n let subDataPoints = dataPoint.subDataPoints;\r\n\r\n let seriesCount = subDataPoints.length;\r\n if (seriesCount === 1) {\r\n let subDataPoint: MapSubDataPoint = subDataPoints[0];\r\n\r\n bubbleData.push({\r\n x: x,\r\n y: y,\r\n labeltext: categoryValue,\r\n radius: radius,\r\n fill: subDataPoint.fill,\r\n stroke: subDataPoint.stroke,\r\n strokeWidth: strokeWidth,\r\n tooltipInfo: subDataPoint.tooltipInfo,\r\n identity: subDataPoint.identity,\r\n selected: false,\r\n labelFill: labelSettings.labelColor,\r\n });\r\n }\r\n else {\r\n let slices = [];\r\n\r\n for (var seriesIndex = 0; seriesIndex < seriesCount; seriesIndex++) {\r\n let subDataPoint: MapSubDataPoint = subDataPoints[seriesIndex];\r\n let value = hasSize ? subDataPoint.value : 1; // Normalize values if there is no size in the data\r\n\r\n slices.push({\r\n x: x,\r\n y: y,\r\n labeltext: categoryValue,\r\n radius: radius,\r\n fill: subDataPoint.fill,\r\n stroke: subDataPoint.stroke,\r\n strokeWidth: strokeWidth,\r\n value: value,\r\n tooltipInfo: subDataPoint.tooltipInfo,\r\n identity: subDataPoint.identity,\r\n selected: false,\r\n labelFill: labelSettings.labelColor,\r\n });\r\n }\r\n if (interactivityService) {\r\n interactivityService.applySelectionStateToData(slices);\r\n }\r\n sliceData.push(slices);\r\n }\r\n }\r\n }\r\n\r\n if (interactivityService) {\r\n interactivityService.applySelectionStateToData(bubbleData);\r\n }\r\n\r\n return { bubbleData: bubbleData, sliceData: sliceData };\r\n }\r\n\r\n public updateInternal(data: MapRendererData, viewport: IViewport, dataChanged: boolean, interactivityService: IInteractivityService, redrawDataLabels: boolean): MapBehaviorOptions {\r\n debug.assertValue(viewport, \"viewport\");\r\n Map.removeTransform3d(this.root);\r\n\r\n this.mapRendererData = data;\r\n if (this.svg) {\r\n this.svg\r\n .style(\"width\", viewport.width.toString() + \"px\")\r\n .style(\"height\", viewport.height.toString() + \"px\");\r\n }\r\n if (this.clearSvg) {\r\n this.clearSvg\r\n .style(\"width\", viewport.width.toString() + \"px\")\r\n .style(\"height\", viewport.height.toString() + \"px\");\r\n }\r\n\r\n let arc = this.arc;\r\n\r\n let hasSelection = interactivityService && interactivityService.hasSelection();\r\n\r\n let bubbles = this.bubbleGraphicsContext.selectAll(\".bubble\").data(data.bubbleData, (d: MapBubble) => d.identity.getKey());\r\n\r\n bubbles.enter()\r\n .append(\"circle\")\r\n .classed(\"bubble\", true);\r\n bubbles\r\n .attr(\"cx\", (d: MapBubble) => d.x)\r\n .attr(\"cy\", (d: MapBubble) => d.y)\r\n .attr(\"r\", (d: MapBubble) => d.radius)\r\n .style(\"fill\", (d: MapBubble) => d.fill)\r\n .style(\"stroke\", (d: MapBubble) => d.stroke)\r\n .style(\"fill-opacity\", (d: MapBubble) => ColumnUtil.getFillOpacity(d.selected, false, hasSelection, false))\r\n .style(\"strokeWidth\", (d: MapBubble) => d.strokeWidth)\r\n .style(\"stroke-opacity\", (d: MapBubble) => ColumnUtil.getFillOpacity(d.selected, false, hasSelection, false))\r\n .style(\"cursor\", \"default\");\r\n bubbles.exit().remove();\r\n\r\n if (this.tooltipsEnabled) {\r\n TooltipManager.addTooltip(this.bubbleGraphicsContext, (tooltipEvent: TooltipEvent) => tooltipEvent.data.tooltipInfo);\r\n bubbles.style(\"pointer-events\", \"all\");\r\n }\r\n\r\n let sliceData = data.sliceData;\r\n\r\n let sliceContainers = this.sliceGraphicsContext.selectAll(\".sliceContainer\").data(sliceData);\r\n sliceContainers.enter()\r\n .append(\"g\")\r\n .classed(\"sliceContainer\", true);\r\n\r\n sliceContainers.exit().remove();\r\n\r\n let sliceLayout = this.sliceLayout;\r\n let slices = sliceContainers.selectAll(\".slice\")\r\n .data(function (d) {\r\n return sliceLayout(d);\r\n }, (d: MapSliceContainer) => d.data.identity.getKey());\r\n\r\n slices.enter()\r\n .append(\"path\")\r\n .classed(\"slice\", true);\r\n\r\n slices\r\n .style(\"fill\", (t: MapSliceContainer) => t.data.fill)\r\n .style(\"fill-opacity\", (d) => ColumnUtil.getFillOpacity(d.data.selected, false, hasSelection, false))\r\n .style(\"stroke\", (t: MapSliceContainer) => t.data.stroke)\r\n .style(\"strokeWidth\", (t: MapSliceContainer) => t.data.strokeWidth)\r\n .style(\"stroke-opacity\", (d) => ColumnUtil.getFillOpacity(d.data.selected, false, hasSelection, false))\r\n .style(\"cursor\", \"default\")\r\n .attr(\"transform\", (t: MapSliceContainer) => SVGUtil.translate(t.data.x, t.data.y))\r\n .attr('d', (t: MapSliceContainer) => {\r\n return arc.innerRadius(0).outerRadius((t: MapSliceContainer) => t.data.radius)(t);\r\n });\r\n\r\n slices.exit().remove();\r\n\r\n this.updateInternalDataLabels(viewport, redrawDataLabels);\r\n\r\n if (this.tooltipsEnabled) {\r\n TooltipManager.addTooltip(this.sliceGraphicsContext, (tooltipEvent: TooltipEvent) => tooltipEvent.data.data.tooltipInfo);\r\n slices.style(\"pointer-events\", \"all\");\r\n }\r\n\r\n let allData: SelectableDataPoint[] = data.bubbleData.slice();\r\n for (let i = 0, ilen = sliceData.length; i < ilen; i++) {\r\n allData.push.apply(allData, sliceData[i]);\r\n }\r\n\r\n let behaviorOptions: MapBehaviorOptions = {\r\n bubbleEventGroup: this.bubbleGraphicsContext,\r\n sliceEventGroup: this.sliceGraphicsContext,\r\n bubbles: bubbles,\r\n slices: slices,\r\n clearCatcher: this.clearCatcher,\r\n dataPoints: allData,\r\n };\r\n return behaviorOptions;\r\n }\r\n\r\n public updateInternalDataLabels(viewport: IViewport, redrawDataLabels: boolean): void {\r\n let labelSettings = this.dataLabelsSettings;\r\n let dataLabels: Label[] = [];\r\n if (labelSettings && (labelSettings.show || labelSettings.showCategory)) {\r\n let labelDataPoints = this.createLabelDataPoints();\r\n let labelLayout = new LabelLayout({\r\n maximumOffset: NewDataLabelUtils.maxLabelOffset,\r\n startingOffset: NewDataLabelUtils.startingLabelOffset\r\n });\r\n let labelDataPointsGroup: LabelDataPointGroup = {\r\n labelDataPoints: labelDataPoints,\r\n maxNumberOfLabels: labelDataPoints.length\r\n };\r\n dataLabels = labelLayout.layout([labelDataPointsGroup], { width: viewport.width, height: viewport.height });\r\n }\r\n\r\n NewDataLabelUtils.drawLabelBackground(this.labelGraphicsContext, dataLabels, powerbi.visuals.DefaultBackgroundColor, powerbi.visuals.DefaultFillOpacity);\r\n NewDataLabelUtils.drawDefaultLabels(this.labelGraphicsContext, dataLabels, false); // Once we properly split up and handle show and showCategory, the false here should change to !labelSettings.showCategory\r\n }\r\n\r\n private createLabelDataPoints(): LabelDataPoint[] {\r\n let data = this.mapRendererData;\r\n let labelDataPoints: LabelDataPoint[] = [];\r\n let dataPoints = data.bubbleData;\r\n dataPoints = dataPoints.concat(_.map(data.sliceData, (value: MapSlice[]) => value[0]));\r\n let labelSettings = this.dataLabelsSettings;\r\n\r\n for (let dataPoint of dataPoints) {\r\n debug.assertValue(dataPoint, 'dataPoint should never be null/undefined');\r\n let text = dataPoint.labeltext;\r\n\r\n let properties: TextProperties = {\r\n text: text,\r\n fontFamily: NewDataLabelUtils.LabelTextProperties.fontFamily,\r\n fontSize: PixelConverter.fromPoint(labelSettings.fontSize),\r\n fontWeight: NewDataLabelUtils.LabelTextProperties.fontWeight,\r\n };\r\n let textWidth = TextMeasurementService.measureSvgTextWidth(properties);\r\n let textHeight = TextMeasurementService.estimateSvgTextHeight(properties);\r\n\r\n labelDataPoints.push({\r\n isPreferred: true,\r\n text: text,\r\n textSize: {\r\n width: textWidth,\r\n height: textHeight,\r\n },\r\n outsideFill: labelSettings.labelColor ? labelSettings.labelColor : NewDataLabelUtils.defaultInsideLabelColor, // Use inside for outside colors because we draw backgrounds for map labels\r\n insideFill: NewDataLabelUtils.defaultInsideLabelColor,\r\n parentType: LabelDataPointParentType.Point,\r\n parentShape: {\r\n point: {\r\n x: dataPoint.x,\r\n y: dataPoint.y,\r\n },\r\n radius: dataPoint.radius,\r\n validPositions: MapBubbleDataPointRenderer.validLabelPositions,\r\n },\r\n fontSize: labelSettings.fontSize,\r\n identity: undefined,\r\n hasBackground: true,\r\n });\r\n }\r\n\r\n return labelDataPoints;\r\n }\r\n }\r\n\r\n export interface FilledMapParams {\r\n level: number;\r\n maxPolygons: number;\r\n strokeWidth: number;\r\n }\r\n\r\n export class MapShapeDataPointRenderer implements IMapDataPointRenderer {\r\n private mapControl: Microsoft.Maps.Map;\r\n private svg: D3.Selection;\r\n private clearSvg: D3.Selection;\r\n private clearCatcher: D3.Selection;\r\n private polygonInfo: MapPolygonInfo;\r\n private mapData: MapData;\r\n private shapeGraphicsContext: D3.Selection;\r\n private labelGraphicsContext: D3.Selection;\r\n private labelBackgroundGraphicsContext: D3.Selection;\r\n private maxShapeDimension: number;\r\n private mapRendererData: MapRendererData;\r\n private dataLabelsSettings: PointDataLabelsSettings;\r\n private filledMapDataLabelsEnabled: boolean;\r\n private tooltipsEnabled: boolean;\r\n private labelLayout: FilledMapLabelLayout;\r\n private static validLabelPolygonPositions: NewPointLabelPosition[] = [NewPointLabelPosition.Center, NewPointLabelPosition.Below, NewPointLabelPosition.Above, NewPointLabelPosition.Right, NewPointLabelPosition.Left, NewPointLabelPosition.BelowRight, NewPointLabelPosition.BelowLeft, NewPointLabelPosition.AboveRight, NewPointLabelPosition.AboveLeft];\r\n private root: JQuery;\r\n\r\n public static getFilledMapParams(category: string, dataCount: number): FilledMapParams {\r\n switch (category) {\r\n case MapUtil.CategoryTypes.Continent:\r\n case MapUtil.CategoryTypes.CountryRegion:\r\n if (dataCount < 10) {\r\n return { level: 1, maxPolygons: 50, strokeWidth: 0 };\r\n }\r\n else if (dataCount < 30) {\r\n return { level: 1, maxPolygons: 20, strokeWidth: 0 };\r\n }\r\n return { level: 1, maxPolygons: 5, strokeWidth: 0 };\r\n default:\r\n if (dataCount < 100) {\r\n return { level: 1, maxPolygons: 5, strokeWidth: 6 };\r\n }\r\n if (dataCount < 200) {\r\n return { level: 0, maxPolygons: 5, strokeWidth: 6 };\r\n }\r\n return { level: 0, maxPolygons: 5, strokeWidth: 0 };\r\n }\r\n }\r\n\r\n public static buildPaths(locations: IGeocodeBoundaryPolygon[]): IGeocodeBoundaryPolygon[] {\r\n let paths = [];\r\n for (let i = 0; i < locations.length; i++) {\r\n let location = locations[i];\r\n let polygon = location.geographic;\r\n\r\n if (polygon.length > 2) {\r\n paths.push(location);\r\n }\r\n }\r\n\r\n return paths;\r\n }\r\n\r\n public constructor(fillMapDataLabelsEnabled: boolean, tooltipsEnabled: boolean) {\r\n this.filledMapDataLabelsEnabled = fillMapDataLabelsEnabled;\r\n this.tooltipsEnabled = tooltipsEnabled;\r\n }\r\n\r\n public init(mapControl: Microsoft.Maps.Map, mapDiv: JQuery, addClearCatcher: boolean): void {\r\n /*\r\n The layout of the visual would look like :\r\n <div class=\"visual mapControl\">\r\n <div class=\"MicrosoftMap\">\r\n <!-- Bing maps stuff -->\r\n <svg>\r\n <rect class=\"clearCatcher\"></rect>\r\n </svg>\r\n </div>\r\n <svg>\r\n <g class=\"mapShapes>\r\n <!-- our geometry -->\r\n </g>\r\n </svg>\r\n </div>\r\n\r\n */\r\n\r\n this.mapControl = mapControl;\r\n this.polygonInfo = new MapPolygonInfo();\r\n\r\n this.root = mapDiv;\r\n let root = d3.select(mapDiv[0]);\r\n root.attr('drag-resize-disabled', 'true'); // Enable panning within the maps in IE\r\n let svg = this.svg = root\r\n .append('svg')\r\n .style('position', 'absolute') // Absolute position so that the svg will overlap with the canvas.\r\n .style(\"pointer-events\", \"none\");\r\n if (addClearCatcher) {\r\n let clearSvg = this.clearSvg = d3.select(<HTMLElement>this.mapControl.getRootElement())\r\n .append('svg')\r\n .style('position', 'absolute'); // Absolute position so that the svg will overlap with the canvas.\r\n this.clearCatcher = appendClearCatcher(clearSvg);\r\n }\r\n this.shapeGraphicsContext = svg\r\n .append('g')\r\n .classed('mapShapes', true);\r\n this.labelBackgroundGraphicsContext = svg\r\n .append(\"g\")\r\n .classed(NewDataLabelUtils.labelBackgroundGraphicsContextClass.class, true);\r\n this.labelGraphicsContext = svg\r\n .append(\"g\")\r\n .classed(NewDataLabelUtils.labelGraphicsContextClass.class, true);\r\n\r\n this.clearMaxShapeDimension();\r\n this.dataLabelsSettings = dataLabelUtils.getDefaultMapLabelSettings();\r\n }\r\n\r\n public setData(data: MapData): void {\r\n this.mapData = data;\r\n }\r\n\r\n public clearDataPoints(): void {\r\n this.mapData = {\r\n dataPoints: [],\r\n geocodingCategory: null,\r\n hasDynamicSeries: false,\r\n hasSize: false,\r\n };\r\n }\r\n\r\n public getDataPointCount(): number {\r\n if (!this.mapData)\r\n return 0;\r\n // Filter out any data points without a location since those aren't actually being drawn\r\n return _.filter(this.mapData.dataPoints, (value: MapDataPoint) => !!value.paths).length;\r\n }\r\n\r\n public converter(viewport: IViewport, dataView: DataView, labelSettings: PointDataLabelsSettings, interactivityService?: IInteractivityService): MapRendererData {\r\n this.clearMaxShapeDimension();\r\n this.dataLabelsSettings = labelSettings;\r\n let strokeWidth = 1;\r\n\r\n let shapeData: MapShape[] = [];\r\n\r\n let dataPoints = this.mapData ? this.mapData.dataPoints : [];\r\n for (let categoryIndex = 0, categoryCount = dataPoints.length; categoryIndex < categoryCount; categoryIndex++) {\r\n let categorical: DataViewCategorical = dataView ? dataView.categorical : null;\r\n let dataPoint: MapDataPoint = dataPoints[categoryIndex];\r\n let subDataPoint = dataPoint.subDataPoints[0];\r\n let paths = dataPoint.paths;\r\n\r\n let grouped: DataViewValueColumnGroup[];\r\n let sizeIndex = -1;\r\n let dataValuesSource: DataViewMetadataColumn;\r\n\r\n if (categorical && categorical.values) {\r\n grouped = categorical.values.grouped();\r\n sizeIndex = DataRoleHelper.getMeasureIndexOfRole(grouped, \"Size\");\r\n dataValuesSource = categorical.values.source;\r\n }\r\n\r\n if (paths) {\r\n let value = dataPoint.value;\r\n let categoryValue = dataPoint.categoryValue;\r\n\r\n let identity = subDataPoint.identity;\r\n let idKey = identity.getKey();\r\n let formattersCache = NewDataLabelUtils.createColumnFormatterCacheManager();\r\n\r\n //Determine Largest Shape\r\n let mainShapeIndex = MapShapeDataPointRenderer.getIndexOfLargestShape(paths);\r\n\r\n for (let pathIndex = 0, pathCount = paths.length; pathIndex < pathCount; pathIndex++) {\r\n let path = paths[pathIndex];\r\n let labelFormatString = (dataView && dataView.categorical && !_.isEmpty(dataView.categorical.values)) ? valueFormatter.getFormatString(dataView.categorical.values[0].source, filledMapProps.general.formatString) : undefined;\r\n this.setMaxShapeDimension(path.absoluteBounds.width, path.absoluteBounds.height);\r\n let formatter = formattersCache.getOrCreate(labelFormatString, labelSettings);\r\n\r\n shapeData.push({\r\n absolutePointArray: path.absolute,\r\n path: path.absoluteString,\r\n fill: subDataPoint.fill,\r\n stroke: subDataPoint.stroke,\r\n strokeWidth: strokeWidth,\r\n tooltipInfo: subDataPoint.tooltipInfo,\r\n identity: identity,\r\n selected: false,\r\n key: JSON.stringify({ id: idKey, pIdx: pathIndex }),\r\n displayLabel: pathIndex === mainShapeIndex,\r\n labeltext: categoryValue,\r\n catagoryLabeltext: (value != null) ? NewDataLabelUtils.getLabelFormattedText(formatter.format(value)) : undefined,\r\n labelFormatString: labelFormatString,\r\n });\r\n }\r\n }\r\n }\r\n\r\n if (interactivityService)\r\n interactivityService.applySelectionStateToData(shapeData);\r\n\r\n return { shapeData: shapeData };\r\n }\r\n\r\n public updateInternal(data: MapRendererData, viewport: IViewport, dataChanged: boolean, interactivityService: IInteractivityService, redrawDataLabels: boolean): MapBehaviorOptions {\r\n debug.assertValue(viewport, \"viewport\");\r\n Map.removeTransform3d(this.root);\r\n\r\n this.mapRendererData = data;\r\n if (this.svg) {\r\n this.svg\r\n .style(\"width\", viewport.width.toString() + \"px\")\r\n .style(\"height\", viewport.height.toString() + \"px\");\r\n }\r\n if (this.clearSvg) {\r\n this.clearSvg\r\n .style(\"width\", viewport.width.toString() + \"px\")\r\n .style(\"height\", viewport.height.toString() + \"px\");\r\n }\r\n\r\n this.polygonInfo.reCalc(this.mapControl, viewport.width, viewport.height);\r\n this.shapeGraphicsContext.attr(\"transform\", this.polygonInfo.transformToString(this.polygonInfo.transform));\r\n\r\n let hasSelection = interactivityService && interactivityService.hasSelection();\r\n\r\n let shapes = this.shapeGraphicsContext.selectAll(\"polygon\").data(data.shapeData, (d: MapShape) => d.key);\r\n\r\n shapes.enter()\r\n .append(\"polygon\")\r\n .classed(\"shape\", true)\r\n .attr(\"points\", (d: MapShape) => { // Always add paths to any new data points\r\n return d.path;\r\n });\r\n\r\n shapes\r\n .style(\"fill\", (d: MapShape) => d.fill)\r\n .style(\"fill-opacity\", (d: MapShape) => ColumnUtil.getFillOpacity(d.selected, false, hasSelection, false))\r\n .style(\"cursor\", \"default\");\r\n\r\n if (dataChanged) {\r\n // We only update the paths of existing shapes if we have a change in the data. Updating the lengthy path\r\n // strings every update during resize or zooming/panning is extremely bad for performance.\r\n shapes\r\n .attr(\"points\", (d: MapShape) => {\r\n return d.path;\r\n });\r\n }\r\n\r\n shapes.exit()\r\n .remove();\r\n\r\n this.updateInternalDataLabels(viewport, redrawDataLabels);\r\n\r\n if (this.tooltipsEnabled) {\r\n TooltipManager.addTooltip(this.shapeGraphicsContext, (tooltipEvent: TooltipEvent) => tooltipEvent.data.tooltipInfo);\r\n shapes.style(\"pointer-events\", \"all\");\r\n }\r\n\r\n let behaviorOptions: MapBehaviorOptions = {\r\n shapeEventGroup: this.shapeGraphicsContext,\r\n shapes: shapes,\r\n clearCatcher: this.clearCatcher,\r\n dataPoints: data.shapeData,\r\n };\r\n\r\n return behaviorOptions;\r\n }\r\n\r\n public getDataPointPadding() {\r\n return 12;\r\n }\r\n\r\n public static getIndexOfLargestShape(paths: IGeocodeBoundaryPolygon[]): number {\r\n let largestShapeIndex = 0;\r\n let largestShapeArea = 0;\r\n\r\n for (let pathIndex = 0, pathCount = paths.length; pathIndex < pathCount; pathIndex++) {\r\n let path = paths[pathIndex];\r\n\r\n // Using the area of the polygon (and taking the largest)\r\n let polygon = new Polygon(path.absolute);\r\n let currentShapeArea = Math.abs(Polygon.calculateAbsolutePolygonArea(polygon.polygonPoints));\r\n\r\n if (currentShapeArea > largestShapeArea) {\r\n largestShapeIndex = pathIndex;\r\n largestShapeArea = currentShapeArea;\r\n }\r\n }\r\n\r\n return largestShapeIndex;\r\n }\r\n\r\n public updateInternalDataLabels(viewport: IViewport, redrawDataLabels: boolean): void {\r\n let labelSettings = this.dataLabelsSettings;\r\n let labels: Label[];\r\n\r\n if (labelSettings && (labelSettings.show || labelSettings.showCategory)) {\r\n let labelDataPoints = this.createLabelDataPoints();\r\n\r\n if (this.labelLayout === undefined) {\r\n this.labelLayout = new FilledMapLabelLayout();\r\n }\r\n labels = this.labelLayout.layout(labelDataPoints, { width: viewport.width, height: viewport.height }, this.polygonInfo.transform, redrawDataLabels);\r\n }\r\n\r\n this.drawLabelStems(this.labelGraphicsContext, labels, labelSettings.show, labelSettings.showCategory);\r\n NewDataLabelUtils.drawLabelBackground(this.labelGraphicsContext, labels, powerbi.visuals.DefaultBackgroundColor, powerbi.visuals.DefaultFillOpacity);\r\n NewDataLabelUtils.drawDefaultLabels(this.labelGraphicsContext, labels, false, labelSettings.show && labelSettings.showCategory);\r\n }\r\n\r\n private clearMaxShapeDimension(): void {\r\n this.maxShapeDimension = 0;\r\n }\r\n\r\n private setMaxShapeDimension(width: number, height: number): void {\r\n this.maxShapeDimension = Math.max(width, this.maxShapeDimension);\r\n this.maxShapeDimension = Math.max(height, this.maxShapeDimension);\r\n }\r\n\r\n private createLabelDataPoints(): LabelDataPoint[] {\r\n let data = this.mapRendererData;\r\n let labelDataPoints: LabelDataPoint[] = [];\r\n if (this.filledMapDataLabelsEnabled) {\r\n let dataShapes = data.shapeData;\r\n let labelSettings = this.dataLabelsSettings;\r\n\r\n for (let dataShape of dataShapes) {\r\n\r\n if (!dataShape.displayLabel) {\r\n continue;\r\n }\r\n let text, secondRowText: string;\r\n let secondRowTextWidth: number = 0;\r\n let hasSecondRow: boolean = false;\r\n\r\n if (this.dataLabelsSettings.show && !this.dataLabelsSettings.showCategory) {\r\n text = dataShape.catagoryLabeltext;\r\n if (text === undefined)\r\n continue;\r\n } else if (this.dataLabelsSettings.showCategory && !this.dataLabelsSettings.show) {\r\n text = dataShape.labeltext;\r\n if (text === undefined)\r\n continue;\r\n } else if (this.dataLabelsSettings.showCategory && this.dataLabelsSettings.show) {\r\n text = dataShape.catagoryLabeltext;\r\n secondRowText = dataShape.labeltext;\r\n if (text === undefined && secondRowText === undefined)\r\n continue;\r\n hasSecondRow = true;\r\n }\r\n\r\n if (hasSecondRow) {\r\n let secondRowProperties: TextProperties = {\r\n text: secondRowText,\r\n fontFamily: NewDataLabelUtils.LabelTextProperties.fontFamily,\r\n fontSize: NewDataLabelUtils.LabelTextProperties.fontSize,\r\n fontWeight: NewDataLabelUtils.LabelTextProperties.fontWeight,\r\n };\r\n secondRowTextWidth = TextMeasurementService.measureSvgTextWidth(secondRowProperties);\r\n }\r\n\r\n let firstRowProperties: TextProperties = {\r\n text: text,\r\n fontFamily: NewDataLabelUtils.LabelTextProperties.fontFamily,\r\n fontSize: NewDataLabelUtils.LabelTextProperties.fontSize,\r\n fontWeight: NewDataLabelUtils.LabelTextProperties.fontWeight,\r\n };\r\n let textWidth = TextMeasurementService.measureSvgTextWidth(firstRowProperties);\r\n let textHeight = TextMeasurementService.estimateSvgTextHeight(firstRowProperties);\r\n\r\n if (secondRowText && dataShape.labeltext !== undefined && dataShape.catagoryLabeltext !== undefined) {\r\n textHeight = textHeight * 2;\r\n }\r\n\r\n let labelDataPoint: LabelDataPoint = {\r\n parentType: LabelDataPointParentType.Polygon,\r\n parentShape:\r\n {\r\n polygon: new Polygon(dataShape.absolutePointArray),\r\n validPositions: MapShapeDataPointRenderer.validLabelPolygonPositions,\r\n },\r\n text: text,\r\n secondRowText: secondRowText,\r\n textSize: {\r\n width: Math.max(textWidth, secondRowTextWidth),\r\n height: textHeight,\r\n },\r\n insideFill: labelSettings.labelColor,\r\n outsideFill: labelSettings.labelColor ? labelSettings.labelColor : NewDataLabelUtils.defaultInsideLabelColor, // Use inside for outside colors because we draw backgrounds for map labels\r\n isPreferred: false,\r\n identity: undefined,\r\n hasBackground: true,\r\n };\r\n labelDataPoints.push(labelDataPoint);\r\n }\r\n }\r\n return labelDataPoints;\r\n }\r\n\r\n private drawLabelStems(labelsContext: D3.Selection, dataLabels: Label[], showText: boolean, showCategory: boolean) {\r\n let filteredLabels = _.filter(dataLabels, (d: Label) => d.isVisible);\r\n let key = (d: Label, index: number) => { return d.identity ? d.identity.getKeyWithoutHighlight() : index; };\r\n NewDataLabelUtils.drawLabelLeaderLines(labelsContext, filteredLabels, key, LeaderLineColor);\r\n }\r\n }\r\n\r\n /** Note: public for UnitTest */\r\n export interface SimpleRange {\r\n min: number;\r\n max: number;\r\n }\r\n\r\n /**\r\n * Interface used to track geocoding requests, a new context being created on each data change.\r\n * Its identity is used to track whether the context has changed so that we don't add data points\r\n * for old geocode requests.\r\n */\r\n interface GeocodingContext {\r\n // Deferred whose promise is used to timeout/cancel geocoding requests\r\n timeout: IDeferred<any>;\r\n }\r\n\r\n const DefaultLocationZoomLevel = 11;\r\n\r\n export class Map implements IVisual {\r\n public currentViewport: IViewport;\r\n\r\n private pendingGeocodingRender: boolean;\r\n private mapControl: Microsoft.Maps.Map;\r\n private minLongitude: number;\r\n private maxLongitude: number;\r\n private minLatitude: number;\r\n private maxLatitude: number;\r\n private style: IVisualStyle;\r\n private colors: IDataColorPalette;\r\n private dataPointRenderer: IMapDataPointRenderer;\r\n private geocodingCategory: string;\r\n private legend: ILegend;\r\n private legendHeight;\r\n private legendData: LegendData;\r\n private element: JQuery;\r\n private dataView: DataView;\r\n private dataLabelsSettings: PointDataLabelsSettings;\r\n private static MapContainer = {\r\n cssClass: 'mapControl',\r\n selector: '.mapControl'\r\n };\r\n public static StrokeDarkenColorValue = 255 * 0.25;\r\n public static ScheduleRedrawInterval = 3000;\r\n private interactivityService: IInteractivityService;\r\n private behavior: MapBehavior;\r\n private defaultDataPointColor: string;\r\n private showAllDataPoints: boolean;\r\n private dataPointsToEnumerate: LegendDataPoint[];\r\n private hasDynamicSeries: boolean;\r\n private geoTaggingAnalyzerService: powerbi.IGeoTaggingAnalyzerService;\r\n private isFilledMap: boolean;\r\n private host: IVisualHostServices;\r\n private geocoder: IGeocoder;\r\n private promiseFactory: IPromiseFactory;\r\n private mapControlFactory: IMapControlFactory;\r\n private tooltipsEnabled: boolean;\r\n private tooltipBucketEnabled: boolean;\r\n private filledMapDataLabelsEnabled: boolean;\r\n private disableZooming: boolean;\r\n private disablePanning: boolean;\r\n private locale: string;\r\n private isLegendScrollable: boolean;\r\n private viewChangeThrottleInterval: number;\r\n private root: JQuery;\r\n private enableCurrentLocation: boolean;\r\n private isCurrentLocation: boolean;\r\n private boundsHaveBeenUpdated: boolean;\r\n private geocodingContext: GeocodingContext;\r\n private isDestroyed: boolean = false;\r\n\r\n constructor(options: MapConstructionOptions) {\r\n if (options.filledMap) {\r\n this.dataPointRenderer = new MapShapeDataPointRenderer(options.filledMapDataLabelsEnabled, options.tooltipsEnabled);\r\n this.filledMapDataLabelsEnabled = options.filledMapDataLabelsEnabled;\r\n this.isFilledMap = true;\r\n }\r\n else {\r\n this.dataPointRenderer = new MapBubbleDataPointRenderer(options.tooltipsEnabled);\r\n this.isFilledMap = false;\r\n }\r\n this.mapControlFactory = options.mapControlFactory ? options.mapControlFactory : this.getDefaultMapControlFactory();\r\n this.behavior = options.behavior;\r\n this.tooltipsEnabled = options.tooltipsEnabled;\r\n this.tooltipBucketEnabled = options.tooltipBucketEnabled;\r\n this.disableZooming = options.disableZooming;\r\n this.disablePanning = options.disablePanning;\r\n this.isLegendScrollable = !!options.behavior;\r\n this.viewChangeThrottleInterval = options.viewChangeThrottleInterval;\r\n this.enableCurrentLocation = options.enableCurrentLocation;\r\n this.boundsHaveBeenUpdated = false;\r\n }\r\n\r\n public init(options: VisualInitOptions) {\r\n debug.assertValue(options, 'options');\r\n let element = this.element = $(\"<div>\");\r\n element.appendTo(options.element);\r\n this.pendingGeocodingRender = false;\r\n this.currentViewport = options.viewport;\r\n this.style = options.style;\r\n this.colors = this.style.colorPalette.dataColors;\r\n if (this.behavior)\r\n this.interactivityService = createInteractivityService(options.host);\r\n this.dataLabelsSettings = dataLabelUtils.getDefaultMapLabelSettings();\r\n this.legend = powerbi.visuals.createLegend(element, options.interactivity && options.interactivity.isInteractiveLegend, this.interactivityService, this.isLegendScrollable);\r\n this.legendHeight = 0;\r\n this.legendData = { dataPoints: [] };\r\n this.geoTaggingAnalyzerService = powerbi.createGeoTaggingAnalyzerService(options.host.getLocalizedString);\r\n this.host = options.host;\r\n if (options.host.locale)\r\n this.locale = options.host.locale();\r\n this.geocoder = options.host.geocoder();\r\n this.promiseFactory = options.host.promiseFactory();\r\n\r\n this.resetBounds();\r\n\r\n this.isDestroyed = false;\r\n\r\n this.mapControlFactory.ensureMap(this.locale, () => {\r\n if (this.isDestroyed)\r\n return;\r\n\r\n Map.removeHillShading();\r\n Microsoft.Maps.loadModule('Microsoft.Maps.Overlays.Style', {\r\n callback: () => {\r\n this.initialize(element[0]);\r\n if (this.enableCurrentLocation) {\r\n this.createCurrentLocation(element);\r\n }\r\n }\r\n });\r\n });\r\n }\r\n\r\n public destroy(): void {\r\n this.isDestroyed = true;\r\n\r\n if (this.geocodingContext && this.geocodingContext.timeout) {\r\n this.geocodingContext.timeout.resolve(null);\r\n }\r\n }\r\n\r\n private createCurrentLocation(element: JQuery): void {\r\n let myLocBtn = InJs.DomFactory.div().addClass(\"mapCurrentLocation\").appendTo(element);\r\n let pushpin: Microsoft.Maps.Pushpin;\r\n\r\n myLocBtn.on('click',() => {\r\n\r\n if (this.isCurrentLocation) {\r\n // Restore previous map view and remove pushpin\r\n if (pushpin) {\r\n this.mapControl.entities.remove(pushpin);\r\n }\r\n this.updateInternal(false, false);\r\n this.isCurrentLocation = false;\r\n } else {\r\n this.host.geolocation().getCurrentPosition((position: Position) => {\r\n let location = new Microsoft.Maps.Location(position.coords.latitude, position.coords.longitude);\r\n if (pushpin) {\r\n this.mapControl.entities.remove(pushpin);\r\n }\r\n pushpin = MapUtil.CurrentLocation.createPushpin(location);\r\n this.mapControl.entities.push(pushpin);\r\n this.updateMapView(location, DefaultLocationZoomLevel);\r\n this.isCurrentLocation = true;\r\n });\r\n }\r\n\r\n });\r\n }\r\n\r\n private addDataPoint(dataPoint: MapDataPoint): void {\r\n let location = dataPoint.location;\r\n this.updateBounds(location.latitude, location.longitude);\r\n\r\n this.scheduleRedraw();\r\n }\r\n\r\n private scheduleRedraw(): void {\r\n if (!this.pendingGeocodingRender && this.mapControl) {\r\n this.pendingGeocodingRender = true;\r\n // Maintain a 3 second delay between redraws from geocoded geometry\r\n setTimeout(() => {\r\n this.updateInternal(true, true);\r\n this.pendingGeocodingRender = false;\r\n }, Map.ScheduleRedrawInterval);\r\n }\r\n }\r\n\r\n private enqueueGeoCode(dataPoint: MapDataPoint): void {\r\n let location = this.geocoder.tryGeocodeImmediate(dataPoint.geocodingQuery, this.geocodingCategory);\r\n if (location)\r\n this.completeGeoCode(dataPoint, location);\r\n else {\r\n let geocodingContext = this.geocodingContext;\r\n this.geocoder.geocode(dataPoint.geocodingQuery, this.geocodingCategory, { timeout: geocodingContext.timeout.promise }).then((location) => {\r\n if (!this.isDestroyed && location && geocodingContext === this.geocodingContext) {\r\n this.completeGeoCode(dataPoint, location);\r\n }\r\n });\r\n }\r\n }\r\n\r\n private completeGeoCode(dataPoint: MapDataPoint, location: IGeocodeCoordinate): void {\r\n dataPoint.location = location;\r\n this.addDataPoint(dataPoint);\r\n }\r\n\r\n private enqueueGeoCodeAndGeoShape(dataPoint: MapDataPoint, params: FilledMapParams): void {\r\n let location = this.geocoder.tryGeocodeImmediate(dataPoint.geocodingQuery, this.geocodingCategory);\r\n if (location)\r\n this.completeGeoCodeAndGeoShape(dataPoint, params, location);\r\n else {\r\n let geocodingContext = this.geocodingContext;\r\n this.geocoder.geocode(dataPoint.geocodingQuery, this.geocodingCategory, { timeout: geocodingContext.timeout.promise }).then((location) => {\r\n if (!this.isDestroyed && location && geocodingContext === this.geocodingContext) {\r\n this.completeGeoCodeAndGeoShape(dataPoint, params, location);\r\n }\r\n });\r\n }\r\n }\r\n\r\n private completeGeoCodeAndGeoShape(dataPoint: MapDataPoint, params: FilledMapParams, location: IGeocodeCoordinate): void {\r\n dataPoint.location = location;\r\n this.enqueueGeoShape(dataPoint, params);\r\n }\r\n\r\n private enqueueGeoShape(dataPoint: MapDataPoint, params: FilledMapParams): void {\r\n debug.assertValue(dataPoint.location, \"cachedLocation\");\r\n let result = this.geocoder.tryGeocodeBoundaryImmediate(dataPoint.location.latitude, dataPoint.location.longitude, this.geocodingCategory, params.level, params.maxPolygons);\r\n if (result)\r\n this.completeGeoShape(dataPoint, params, result);\r\n else {\r\n let geocodingContext = this.geocodingContext;\r\n this.geocoder.geocodeBoundary(dataPoint.location.latitude, dataPoint.location.longitude, this.geocodingCategory, params.level, params.maxPolygons, { timeout: geocodingContext.timeout.promise })\r\n .then((result: IGeocodeBoundaryCoordinate) => {\r\n if (!this.isDestroyed && result && geocodingContext === this.geocodingContext)\r\n this.completeGeoShape(dataPoint, params, result); \r\n });\r\n }\r\n }\r\n\r\n private completeGeoShape(dataPoint: MapDataPoint, params: FilledMapParams, result: IGeocodeBoundaryCoordinate): void {\r\n let paths;\r\n if (result.locations.length === 0 || result.locations[0].geographic) {\r\n paths = MapShapeDataPointRenderer.buildPaths(result.locations);\r\n }\r\n else {\r\n MapUtil.calcGeoData(result);\r\n paths = MapShapeDataPointRenderer.buildPaths(result.locations);\r\n }\r\n dataPoint.paths = paths;\r\n this.addDataPoint(dataPoint);\r\n }\r\n\r\n private getOptimumLevelOfDetail(width: number, height: number): number {\r\n let dataPointCount = this.dataPointRenderer.getDataPointCount();\r\n if (dataPointCount === 0)\r\n return MapUtil.MinLevelOfDetail;\r\n\r\n let threshold: number = this.dataPointRenderer.getDataPointPadding();\r\n\r\n for (let levelOfDetail = MapUtil.MaxLevelOfDetail; levelOfDetail >= MapUtil.MinLevelOfDetail; levelOfDetail--) {\r\n let minXmaxY = MapUtil.latLongToPixelXY(this.minLatitude, this.minLongitude, levelOfDetail);\r\n let maxXminY = MapUtil.latLongToPixelXY(this.maxLatitude, this.maxLongitude, levelOfDetail);\r\n\r\n if (maxXminY.x - minXmaxY.x + threshold <= width && minXmaxY.y - maxXminY.y + threshold <= height) {\r\n // if we have less than 2 data points we should not zoom in \"too much\"\r\n if (dataPointCount < 2)\r\n levelOfDetail = Math.min(MapUtil.MaxAutoZoomLevel, levelOfDetail);\r\n\r\n return levelOfDetail;\r\n }\r\n }\r\n\r\n return MapUtil.MinLevelOfDetail;\r\n }\r\n private getViewCenter(levelOfDetail: number): Microsoft.Maps.Location {\r\n let minXmaxY = MapUtil.latLongToPixelXY(this.minLatitude, this.minLongitude, levelOfDetail);\r\n let maxXminY = MapUtil.latLongToPixelXY(this.maxLatitude, this.maxLongitude, levelOfDetail);\r\n return MapUtil.pixelXYToLocation((minXmaxY.x + maxXminY.x) / 2.0, (maxXminY.y + minXmaxY.y) / 2.0, levelOfDetail);\r\n }\r\n\r\n private resetBounds(): void {\r\n this.boundsHaveBeenUpdated = false;\r\n this.minLongitude = MapUtil.MaxAllowedLongitude;\r\n this.maxLongitude = MapUtil.MinAllowedLongitude;\r\n this.minLatitude = MapUtil.MaxAllowedLatitude;\r\n this.maxLatitude = MapUtil.MinAllowedLatitude;\r\n }\r\n\r\n private updateBounds(latitude: number, longitude: number): void {\r\n this.boundsHaveBeenUpdated = true;\r\n if (longitude < this.minLongitude) {\r\n this.minLongitude = longitude;\r\n }\r\n\r\n if (longitude > this.maxLongitude) {\r\n this.maxLongitude = longitude;\r\n }\r\n\r\n if (latitude < this.minLatitude) {\r\n this.minLatitude = latitude;\r\n }\r\n\r\n if (latitude > this.maxLatitude) {\r\n this.maxLatitude = latitude;\r\n }\r\n }\r\n\r\n public static legendObject(dataView: DataView): DataViewObject {\r\n return dataView &&\r\n dataView.metadata &&\r\n dataView.metadata.objects &&\r\n <DataViewObject>dataView.metadata.objects['legend'];\r\n }\r\n\r\n public static isLegendHidden(dataView: DataView): boolean {\r\n let legendObject = Map.legendObject(dataView);\r\n return legendObject != null && legendObject[legendProps.show] === false;\r\n }\r\n\r\n public static legendPosition(dataView: DataView): LegendPosition {\r\n let legendObject = Map.legendObject(dataView);\r\n return legendObject && LegendPosition[<string>legendObject[legendProps.position]];\r\n }\r\n\r\n public static getLegendFontSize(dataView: DataView): number {\r\n let legendObject = Map.legendObject(dataView);\r\n return (legendObject && <number>legendObject[legendProps.fontSize]) || SVGLegend.DefaultFontSizeInPt;\r\n }\r\n\r\n public static isShowLegendTitle(dataView: DataView): boolean {\r\n let legendObject = Map.legendObject(dataView);\r\n return legendObject && <boolean>legendObject[legendProps.showTitle];\r\n }\r\n\r\n private legendTitle(): string {\r\n let legendObject = Map.legendObject(this.dataView);\r\n return (legendObject && <string>legendObject[legendProps.titleText]) || this.legendData.title;\r\n }\r\n\r\n private renderLegend(legendData: LegendData): void {\r\n let hideLegend = Map.isLegendHidden(this.dataView);\r\n let showTitle = Map.isShowLegendTitle(this.dataView);\r\n let title = this.legendTitle();\r\n // Update the legendData based on the hide flag. Cartesian passes in no-datapoints. OnResize reuses the legendData, so this can't mutate.\r\n let clonedLegendData: LegendData = {\r\n dataPoints: hideLegend ? [] : legendData.dataPoints,\r\n grouped: legendData.grouped,\r\n title: showTitle ? title : \"\",\r\n fontSize: Map.getLegendFontSize(this.dataView)\r\n };\r\n\r\n // Update the orientation to match what's in the dataView\r\n let targetOrientation = Map.legendPosition(this.dataView);\r\n if (targetOrientation !== undefined) {\r\n this.legend.changeOrientation(targetOrientation);\r\n } else {\r\n this.legend.changeOrientation(LegendPosition.Top);\r\n }\r\n\r\n this.legend.drawLegend(clonedLegendData, this.currentViewport);\r\n }\r\n\r\n /** Note: public for UnitTest */\r\n public static calculateGroupSizes(categorical: DataViewCategorical, grouped: DataViewValueColumnGroup[], groupSizeTotals: number[], sizeMeasureIndex: number, currentValueScale: SimpleRange): SimpleRange {\r\n let categoryCount = categorical.values[0].values.length;\r\n let seriesCount = grouped.length;\r\n\r\n for (let i = 0, len = categoryCount; i < len; ++i) {\r\n let groupTotal = null;\r\n if (sizeMeasureIndex >= 0) {\r\n for (let j = 0; j < seriesCount; ++j) {\r\n let value = grouped[j].values[sizeMeasureIndex].values[i];\r\n if (value) {\r\n if (groupTotal === null) {\r\n groupTotal = value;\r\n } else {\r\n groupTotal += value;\r\n }\r\n }\r\n }\r\n }\r\n\r\n groupSizeTotals.push(groupTotal);\r\n\r\n if (groupTotal) {\r\n if (!currentValueScale) {\r\n currentValueScale = {\r\n min: groupTotal,\r\n max: groupTotal\r\n };\r\n } else {\r\n currentValueScale.min = Math.min(currentValueScale.min, groupTotal);\r\n currentValueScale.max = Math.max(currentValueScale.max, groupTotal);\r\n }\r\n }\r\n }\r\n\r\n return currentValueScale;\r\n }\r\n\r\n /** Note: public for UnitTest */\r\n public static calculateRadius(range: SimpleRange, value?: number): number {\r\n let rangeDiff = range ? range.max - range.min : 0;\r\n let radius = 6;\r\n if (range != null && value != null && rangeDiff !== 0) {\r\n radius = (14 * ((value - range.min) / rangeDiff)) + 6;\r\n }\r\n\r\n return radius;\r\n }\r\n\r\n /** Note: public for UnitTest */\r\n public static getGeocodingCategory(categorical: DataViewCategorical, geoTaggingAnalyzerService: IGeoTaggingAnalyzerService): string {\r\n if (categorical && categorical.categories && categorical.categories.length > 0 && categorical.categories[0].source) {\r\n // Check categoryString for manually specified information in the model\r\n let type = <ValueType>categorical.categories[0].source.type;\r\n if (type && type.categoryString) {\r\n return geoTaggingAnalyzerService.getFieldType(type.categoryString);\r\n }\r\n\r\n // Check the category name\r\n let categoryName = categorical.categories[0].source.displayName;\r\n let geotaggedResult = geoTaggingAnalyzerService.getFieldType(categoryName);\r\n if (geotaggedResult)\r\n return geotaggedResult;\r\n\r\n // Checking roles for VRM backwards compatibility\r\n let roles = categorical.categories[0].source.roles;\r\n if (roles) {\r\n let roleNames = Object.keys(roles);\r\n for (let i = 0, len = roleNames.length; i < len; ++i) {\r\n let typeFromRoleName = geoTaggingAnalyzerService.getFieldType(roleNames[i]);\r\n if (typeFromRoleName)\r\n return typeFromRoleName;\r\n }\r\n }\r\n }\r\n\r\n return undefined;\r\n }\r\n\r\n /** Note: public for UnitTest */\r\n public static hasSizeField(values: DataViewValueColumns, defaultIndexIfNoRole?: number): boolean {\r\n if (_.isEmpty(values))\r\n return false;\r\n\r\n for (let i = 0, ilen = values.length; i < ilen; i++) {\r\n let roles = values[i].source.roles;\r\n\r\n // case for Power Q&A since Power Q&A does not assign role to measures.\r\n if (!roles && i === defaultIndexIfNoRole && values[i].source.type.numeric)\r\n return true;\r\n\r\n if (roles) {\r\n let roleNames = Object.keys(roles);\r\n for (let j = 0, jlen = roleNames.length; j < jlen; j++) {\r\n let role = roleNames[j];\r\n if (role === \"Size\")\r\n return true;\r\n }\r\n }\r\n }\r\n return false;\r\n }\r\n\r\n public static shouldEnumerateDataPoints(dataView: DataView, usesSizeForGradient: boolean): boolean {\r\n let hasSeries = DataRoleHelper.hasRoleInDataView(dataView, 'Series');\r\n let gradientRole = usesSizeForGradient ? 'Size' : 'Gradient';\r\n let hasGradientRole = DataRoleHelper.hasRoleInDataView(dataView, gradientRole);\r\n return hasSeries || !hasGradientRole;\r\n }\r\n\r\n public static shouldEnumerateCategoryLabels(isFilledMap: boolean, filledMapDataLabelsEnabled: boolean): boolean {\r\n return (!isFilledMap || filledMapDataLabelsEnabled);\r\n }\r\n\r\n public enumerateObjectInstances(options: EnumerateVisualObjectInstancesOptions): VisualObjectInstanceEnumeration {\r\n let enumeration = new ObjectEnumerationBuilder();\r\n switch (options.objectName) {\r\n case 'dataPoint':\r\n if (Map.shouldEnumerateDataPoints(this.dataView, this.isFilledMap)) {\r\n let bubbleData: MapBubble[] = [];\r\n //TODO: better way of getting this data\r\n let hasDynamicSeries = this.hasDynamicSeries;\r\n if (!hasDynamicSeries) {\r\n let mapData = this.dataPointRenderer.converter(this.getMapViewPort(), this.dataView, this.dataLabelsSettings, this.interactivityService, this.tooltipsEnabled);\r\n bubbleData = mapData.bubbleData;\r\n }\r\n Map.enumerateDataPoints(enumeration, this.dataPointsToEnumerate, this.colors, hasDynamicSeries, this.defaultDataPointColor, this.showAllDataPoints, bubbleData);\r\n }\r\n break;\r\n case 'categoryLabels':\r\n if (Map.shouldEnumerateCategoryLabels(this.isFilledMap, this.filledMapDataLabelsEnabled)) {\r\n dataLabelUtils.enumerateCategoryLabels(enumeration, this.dataLabelsSettings, true, true);\r\n }\r\n break;\r\n case 'legend':\r\n if (this.hasDynamicSeries) {\r\n Map.enumerateLegend(enumeration, this.dataView, this.legend, this.legendTitle());\r\n }\r\n break;\r\n case 'labels':\r\n if (this.filledMapDataLabelsEnabled) {\r\n this.dataLabelsSettings = this.dataLabelsSettings ? this.dataLabelsSettings : dataLabelUtils.getDefaultMapLabelSettings();\r\n let labelSettingOptions: VisualDataLabelsSettingsOptions = {\r\n enumeration: enumeration,\r\n dataLabelsSettings: this.dataLabelsSettings,\r\n show: true,\r\n displayUnits: true,\r\n precision: true,\r\n };\r\n dataLabelUtils.enumerateDataLabels(labelSettingOptions);\r\n }\r\n break;\r\n }\r\n return enumeration.complete();\r\n }\r\n\r\n public static enumerateDataPoints(enumeration: ObjectEnumerationBuilder, dataPoints: LegendDataPoint[], colors: IDataColorPalette, hasDynamicSeries: boolean, defaultDataPointColor: string, showAllDataPoints: boolean, bubbleData: MapBubble[]): void {\r\n let seriesLength = dataPoints && dataPoints.length;\r\n\r\n if (hasDynamicSeries) {\r\n for (let i = 0; i < seriesLength; i++) {\r\n\r\n let dataPoint = dataPoints[i];\r\n enumeration.pushInstance({\r\n objectName: 'dataPoint',\r\n displayName: dataPoint.label,\r\n selector: ColorHelper.normalizeSelector(dataPoint.identity.getSelector()),\r\n properties: {\r\n fill: { solid: { color: dataPoint.color } }\r\n },\r\n });\r\n }\r\n }\r\n else {\r\n enumeration.pushInstance({\r\n objectName: 'dataPoint',\r\n selector: null,\r\n properties: {\r\n defaultColor: { solid: { color: defaultDataPointColor || colors.getColorByIndex(0).value } }\r\n },\r\n }).pushInstance({\r\n objectName: 'dataPoint',\r\n selector: null,\r\n properties: {\r\n showAllDataPoints: !!showAllDataPoints\r\n },\r\n });\r\n\r\n if (bubbleData) {\r\n for (let i = 0; i < bubbleData.length; i++) {\r\n let bubbleDataPoint = bubbleData[i];\r\n enumeration.pushInstance({\r\n objectName: 'dataPoint',\r\n displayName: bubbleDataPoint.labeltext,\r\n selector: ColorHelper.normalizeSelector(bubbleDataPoint.identity.getSelector()),\r\n properties: {\r\n fill: { solid: { color: Color.normalizeToHexString(bubbleDataPoint.fill) } }\r\n },\r\n });\r\n }\r\n }\r\n\r\n }\r\n }\r\n\r\n public static enumerateLegend(enumeration: ObjectEnumerationBuilder, dataView: DataView, legend: ILegend, legendTitle: string): void {\r\n enumeration.pushInstance({\r\n selector: null,\r\n properties: {\r\n show: !Map.isLegendHidden(dataView),\r\n position: LegendPosition[legend.getOrientation()],\r\n showTitle: Map.isShowLegendTitle(dataView),\r\n titleText: legendTitle,\r\n fontSize: Map.getLegendFontSize(dataView)\r\n },\r\n objectName: 'legend'\r\n });\r\n }\r\n\r\n public onDataChanged(options: VisualDataChangedOptions): void {\r\n debug.assertValue(options, 'options');\r\n\r\n this.resetBounds();\r\n\r\n if (this.geocodingContext && this.geocodingContext.timeout) {\r\n this.geocodingContext.timeout.resolve(null);\r\n }\r\n this.geocodingContext = {\r\n timeout: this.promiseFactory.defer<any>(),\r\n };\r\n\r\n if (this.behavior)\r\n this.behavior.resetZoomPan();\r\n\r\n this.dataLabelsSettings = dataLabelUtils.getDefaultMapLabelSettings();\r\n this.defaultDataPointColor = null;\r\n this.showAllDataPoints = null;\r\n let dataView = this.dataView = options.dataViews[0];\r\n let isFilledMap = this.isFilledMap;\r\n let warnings = [];\r\n let data: MapData = {\r\n dataPoints: [],\r\n geocodingCategory: null,\r\n hasDynamicSeries: false,\r\n hasSize: false,\r\n };\r\n\r\n if (dataView) {\r\n // Handle object-based settings\r\n if (dataView.metadata && dataView.metadata.objects) {\r\n let objects = dataView.metadata.objects;\r\n\r\n this.defaultDataPointColor = DataViewObjects.getFillColor(objects, mapProps.dataPoint.defaultColor);\r\n this.showAllDataPoints = DataViewObjects.getValue<boolean>(objects, mapProps.dataPoint.showAllDataPoints);\r\n\r\n this.dataLabelsSettings.showCategory = DataViewObjects.getValue<boolean>(objects, filledMapProps.categoryLabels.show, this.dataLabelsSettings.showCategory);\r\n\r\n if (isFilledMap) {\r\n this.dataLabelsSettings.precision = DataViewObjects.getValue(objects, filledMapProps.labels.labelPrecision, this.dataLabelsSettings.precision);\r\n this.dataLabelsSettings.precision = (this.dataLabelsSettings.precision !== dataLabelUtils.defaultLabelPrecision && this.dataLabelsSettings.precision < 0) ? 0 : this.dataLabelsSettings.precision;\r\n this.dataLabelsSettings.displayUnits = DataViewObjects.getValue<number>(objects, filledMapProps.labels.labelDisplayUnits, this.dataLabelsSettings.displayUnits);\r\n let datalabelsObj = objects['labels'];\r\n if (datalabelsObj) {\r\n this.dataLabelsSettings.show = (datalabelsObj['show'] !== undefined) ? <boolean>datalabelsObj['show'] : this.dataLabelsSettings.show;\r\n if (datalabelsObj['color'] !== undefined) {\r\n this.dataLabelsSettings.labelColor = (<Fill>datalabelsObj['color']).solid.color;\r\n }\r\n }\r\n }\r\n else {\r\n let categoryLabelsObj = <DataLabelObject>objects['categoryLabels'];\r\n if (categoryLabelsObj)\r\n dataLabelUtils.updateLabelSettingsFromLabelsObject(categoryLabelsObj, this.dataLabelsSettings);\r\n }\r\n }\r\n\r\n // Convert data\r\n let colorHelper = new ColorHelper(this.colors, mapProps.dataPoint.fill, this.defaultDataPointColor);\r\n data = Map.converter(dataView, colorHelper, this.geoTaggingAnalyzerService, isFilledMap, this.tooltipBucketEnabled);\r\n this.hasDynamicSeries = data.hasDynamicSeries;\r\n\r\n // Create legend\r\n this.legendData = Map.createLegendData(dataView, colorHelper);\r\n this.dataPointsToEnumerate = this.legendData.dataPoints;\r\n this.renderLegend(this.legendData);\r\n\r\n // Start geocoding or geoshaping\r\n if (data != null) {\r\n this.geocodingCategory = data.geocodingCategory;\r\n this.mapControlFactory.ensureMap(this.locale, () => {\r\n if (this.isDestroyed)\r\n return;\r\n\r\n Map.removeHillShading();\r\n let params;\r\n if (isFilledMap) {\r\n params = MapShapeDataPointRenderer.getFilledMapParams(this.geocodingCategory, data.dataPoints.length);\r\n }\r\n for (let dataPoint of data.dataPoints) {\r\n if (!dataPoint.location) {\r\n let categoryValue = dataPoint.categoryValue;\r\n if (categoryValue != null && categoryValue !== \"\") { // If we don't have a location, but the category value is non-null and not an empty string\r\n if (isFilledMap)\r\n this.enqueueGeoCodeAndGeoShape(dataPoint, params);\r\n else\r\n this.enqueueGeoCode(dataPoint);\r\n }\r\n }\r\n else if (isFilledMap && !dataPoint.paths) {\r\n this.enqueueGeoShape(dataPoint, params);\r\n }\r\n else {\r\n this.addDataPoint(dataPoint);\r\n }\r\n }\r\n });\r\n }\r\n else {\r\n // No data from conversion, so clear data points\r\n this.clearDataPoints();\r\n }\r\n\r\n if (isFilledMap) {\r\n if (!this.geocodingCategory || !this.geoTaggingAnalyzerService.isGeoshapable(this.geocodingCategory)) {\r\n warnings.push(new FilledMapWithoutValidGeotagCategoryWarning());\r\n }\r\n }\r\n }\r\n else {\r\n this.clearDataPoints();\r\n this.renderLegend({\r\n dataPoints: [],\r\n title: undefined,\r\n });\r\n this.dataPointsToEnumerate = [];\r\n }\r\n\r\n if (!_.isEmpty(warnings))\r\n this.host.setWarnings(warnings);\r\n\r\n this.dataPointRenderer.setData(data);\r\n\r\n this.updateInternal(true /* dataChanged */, true /* redrawDataLabels */);\r\n }\r\n\r\n public static converter(dataView: DataView, colorHelper: ColorHelper, geoTaggingAnalyzerService: IGeoTaggingAnalyzerService, isFilledMap: boolean, tooltipBucketEnabled?: boolean): MapData {\r\n let reader = powerbi.data.createIDataViewCategoricalReader(dataView);\r\n let dataPoints: MapDataPoint[] = [];\r\n let hasDynamicSeries = reader.hasDynamicSeries();\r\n let seriesColumnIdentifier = reader.getSeriesColumnIdentityFields();\r\n let sizeQueryName = reader.getMeasureQueryName('Size');\r\n if (sizeQueryName == null)\r\n sizeQueryName = '';\r\n let hasSize = reader.hasValues('Size');\r\n let geocodingCategory = null;\r\n let formatStringProp = mapProps.general.formatString;\r\n\r\n if (reader.hasCategories()) {\r\n // Calculate category totals and range for radius calculation\r\n let categoryTotals: number[] = [];\r\n let categoryTotalRange: SimpleRange;\r\n if (hasSize) {\r\n let categoryMin: number = undefined;\r\n let categoryMax: number = undefined;\r\n for (let categoryIndex = 0, categoryCount = reader.getCategoryCount(); categoryIndex < categoryCount; categoryIndex++) {\r\n let categoryTotal: number;\r\n for (let seriesIndex = 0, seriesCount = reader.getSeriesCount(); seriesIndex < seriesCount; seriesIndex++) {\r\n let currentValue = reader.getValue('Size', categoryIndex, seriesIndex);\r\n // Dont initialze categoryTotal to zero until you find a null value so that it remains undefined for categories that have no non-null values (0 is rendered by filled map while null is not)\r\n if (categoryTotal == null && currentValue != null)\r\n categoryTotal = 0;\r\n if (categoryTotal != null)\r\n categoryTotal += currentValue;\r\n }\r\n categoryTotals.push(categoryTotal);\r\n if (categoryTotal != null) {\r\n if (categoryMin === undefined || categoryTotal < categoryMin)\r\n categoryMin = categoryTotal;\r\n if (categoryMax === undefined || categoryTotal > categoryMax)\r\n categoryMax = categoryTotal;\r\n }\r\n }\r\n categoryTotalRange = (categoryMin !== undefined && categoryMax !== undefined) ? {\r\n max: categoryMax,\r\n min: categoryMin,\r\n } : undefined;\r\n }\r\n\r\n let hasLatLongGroup = reader.hasCompositeCategories() && reader.hasCategoryWithRole('X') && reader.hasCategoryWithRole('Y');\r\n let hasCategoryGroup = reader.hasCategoryWithRole('Category');\r\n\r\n geocodingCategory = Map.getGeocodingCategory(dataView.categorical, geoTaggingAnalyzerService);\r\n\r\n if (hasLatLongGroup || hasCategoryGroup) {\r\n // Create data points\r\n for (let categoryIndex = 0, categoryCount = reader.getCategoryCount(); categoryIndex < categoryCount; categoryIndex++) {\r\n // Get category information\r\n let categoryValue: string = undefined;\r\n // The category objects should come from whichever category exists; in the case of a composite category, the objects should be the same for\r\n // both categories, so we only need to obtain them from one role.\r\n let categoryObjects = hasCategoryGroup ? reader.getCategoryObjects('Category', categoryIndex) : reader.getCategoryObjects('Y', categoryIndex);\r\n let location: IGeocodeCoordinate;\r\n let categoryTooltipItem: TooltipDataItem;\r\n let latitudeTooltipItem: TooltipDataItem;\r\n let longitudeTooltipItem: TooltipDataItem;\r\n let seriesTooltipItem: TooltipDataItem;\r\n let sizeTooltipItem: TooltipDataItem;\r\n let gradientTooltipItem: TooltipDataItem;\r\n if (hasCategoryGroup) {\r\n // Set category value\r\n categoryValue = converterHelper.formatFromMetadataColumn(reader.getCategoryValue('Category', categoryIndex), reader.getCategoryMetadataColumn('Category'), formatStringProp);\r\n categoryTooltipItem = {\r\n displayName: reader.getCategoryDisplayName('Category'),\r\n value: categoryValue,\r\n };\r\n\r\n // Create location from latitude and longitude if they exist as values\r\n if (reader.hasValues('Y') && reader.hasValues('X')) {\r\n let latitude = reader.getFirstNonNullValueForCategory('Y', categoryIndex);\r\n let longitude = reader.getFirstNonNullValueForCategory('X', categoryIndex);\r\n if (latitude != null && longitude != null) {\r\n location = { latitude: latitude, longitude: longitude };\r\n }\r\n latitudeTooltipItem = {\r\n displayName: reader.getValueDisplayName('Y'),\r\n value: converterHelper.formatFromMetadataColumn(latitude, reader.getValueMetadataColumn('Y'), formatStringProp),\r\n };\r\n longitudeTooltipItem = {\r\n displayName: reader.getValueDisplayName('X'),\r\n value: converterHelper.formatFromMetadataColumn(longitude, reader.getValueMetadataColumn('X'), formatStringProp),\r\n };\r\n }\r\n }\r\n else {\r\n let latitude = reader.getCategoryValue('Y', categoryIndex);\r\n let longitude = reader.getCategoryValue('X', categoryIndex);\r\n\r\n if (latitude != null && longitude != null) {\r\n // Combine latitude and longitude to create the category value\r\n categoryValue = latitude + ', ' + longitude;\r\n // Create location from latitude and longitude\r\n location = { latitude: latitude, longitude: longitude };\r\n latitudeTooltipItem = {\r\n displayName: reader.getCategoryDisplayName('Y'),\r\n value: converterHelper.formatFromMetadataColumn(latitude, reader.getCategoryMetadataColumn('Y'), formatStringProp),\r\n };\r\n longitudeTooltipItem = {\r\n displayName: reader.getCategoryDisplayName('X'),\r\n value: converterHelper.formatFromMetadataColumn(longitude, reader.getCategoryMetadataColumn('X'), formatStringProp),\r\n };\r\n }\r\n }\r\n let value: number = hasSize ? categoryTotals[categoryIndex] : undefined;\r\n\r\n // Calculate sub data points by series\r\n let subDataPoints: MapSubDataPoint[] = [];\r\n let seriesCount = reader.getSeriesCount();\r\n if (!hasSize && !hasDynamicSeries) {\r\n seriesCount = 1;\r\n }\r\n for (let seriesIndex = 0; seriesIndex < seriesCount; seriesIndex++) {\r\n let color: string;\r\n\r\n if (hasDynamicSeries) {\r\n color = colorHelper.getColorForSeriesValue(reader.getSeriesObjects(seriesIndex), seriesColumnIdentifier, <string>(reader.getSeriesName(seriesIndex)));\r\n }\r\n else if (reader.hasCategoryWithRole('Series')) {\r\n color = colorHelper.getColorForSeriesValue(reader.getCategoryObjects('Series', categoryIndex), reader.getCategoryColumnIdentityFields('Series'), categoryValue);\r\n }\r\n else {\r\n color = colorHelper.getColorForMeasure(categoryObjects, sizeQueryName);\r\n }\r\n\r\n let colorRgb = Color.parseColorString(color);\r\n let stroke = Color.hexString(Color.darken(colorRgb, Map.StrokeDarkenColorValue));\r\n colorRgb.A = 0.6;\r\n let fill = Color.rgbString(colorRgb);\r\n\r\n let identityBuilder = new SelectionIdBuilder()\r\n .withCategory(reader.getCategoryColumn(hasCategoryGroup ? 'Category' : 'Y'), categoryIndex)\r\n .withMeasure(sizeQueryName);\r\n if (hasDynamicSeries) {\r\n identityBuilder = identityBuilder.withSeries(reader.getSeriesValueColumns(), reader.getSeriesValueColumnGroup(seriesIndex));\r\n }\r\n\r\n if (hasDynamicSeries) {\r\n seriesTooltipItem = {\r\n displayName: reader.getSeriesDisplayName(),\r\n value: converterHelper.formatFromMetadataColumn(reader.getSeriesName(seriesIndex), reader.getSeriesMetadataColumn(), formatStringProp),\r\n };\r\n }\r\n\r\n let subsliceValue: number;\r\n if (hasSize) {\r\n subsliceValue = reader.getValue('Size', categoryIndex, seriesIndex);\r\n sizeTooltipItem = {\r\n displayName: reader.getValueDisplayName('Size'),\r\n value: converterHelper.formatFromMetadataColumn(subsliceValue, reader.getValueMetadataColumn('Size', seriesIndex), formatStringProp),\r\n };\r\n }\r\n if (reader.hasValues('Gradient')) {\r\n gradientTooltipItem = {\r\n displayName: reader.getValueDisplayName('Gradient'),\r\n value: converterHelper.formatFromMetadataColumn(reader.getValue('Gradient', categoryIndex, seriesIndex), reader.getValueMetadataColumn('Gradient', seriesIndex), formatStringProp),\r\n };\r\n }\r\n\r\n // Combine any existing tooltip items\r\n let tooltipInfo: TooltipDataItem[] = [];\r\n if (categoryTooltipItem)\r\n tooltipInfo.push(categoryTooltipItem);\r\n if (seriesTooltipItem)\r\n tooltipInfo.push(seriesTooltipItem);\r\n if (latitudeTooltipItem)\r\n tooltipInfo.push(latitudeTooltipItem);\r\n if (longitudeTooltipItem)\r\n tooltipInfo.push(longitudeTooltipItem);\r\n if (sizeTooltipItem)\r\n tooltipInfo.push(sizeTooltipItem);\r\n if (gradientTooltipItem)\r\n tooltipInfo.push(gradientTooltipItem);\r\n\r\n if (tooltipBucketEnabled) {\r\n TooltipBuilder.addTooltipBucketItem(reader, tooltipInfo, categoryIndex, seriesIndex);\r\n }\r\n\r\n // Do not create subslices for data points with null or zero if not filled map\r\n if (subsliceValue || !hasSize || (subsliceValue === 0 && isFilledMap)) {\r\n subDataPoints.push({\r\n value: subsliceValue,\r\n fill: fill,\r\n stroke: stroke,\r\n identity: identityBuilder.createSelectionId(),\r\n tooltipInfo: tooltipInfo,\r\n });\r\n }\r\n }\r\n\r\n // Skip data points that have a null or zero if not filled map\r\n if (value || !hasSize || (value === 0 && isFilledMap)) {\r\n dataPoints.push({\r\n geocodingQuery: categoryValue,\r\n value: value,\r\n categoryValue: categoryValue,\r\n subDataPoints: subDataPoints,\r\n radius: Map.calculateRadius(categoryTotalRange, value),\r\n location: location,\r\n });\r\n }\r\n }\r\n }\r\n }\r\n\r\n let mapData: MapData = {\r\n dataPoints: dataPoints,\r\n geocodingCategory: geocodingCategory,\r\n hasDynamicSeries: hasDynamicSeries,\r\n hasSize: hasSize,\r\n };\r\n\r\n return mapData;\r\n }\r\n\r\n public static createLegendData(dataView: DataView, colorHelper: ColorHelper): LegendData {\r\n let reader = powerbi.data.createIDataViewCategoricalReader(dataView);\r\n let legendDataPoints: LegendDataPoint[] = [];\r\n let legendTitle: string;\r\n if (reader.hasDynamicSeries()) {\r\n legendTitle = reader.getSeriesDisplayName();\r\n let seriesColumnIdentifier = reader.getSeriesColumnIdentityFields();\r\n for (let seriesIndex = 0, seriesCount = reader.getSeriesCount(); seriesIndex < seriesCount; seriesIndex++) {\r\n let color = colorHelper.getColorForSeriesValue(reader.getSeriesObjects(seriesIndex), seriesColumnIdentifier, <string>reader.getSeriesName(seriesIndex));\r\n let identity = new SelectionIdBuilder().withSeries(reader.getSeriesValueColumns(), reader.getSeriesValueColumnGroup(seriesIndex)).createSelectionId();\r\n legendDataPoints.push({\r\n color: color,\r\n label: valueFormatter.format(reader.getSeriesName(seriesIndex)),\r\n icon: LegendIcon.Circle,\r\n identity: identity,\r\n selected: false,\r\n });\r\n }\r\n }\r\n let legendData: LegendData = {\r\n dataPoints: legendDataPoints,\r\n title: legendTitle,\r\n };\r\n return legendData;\r\n }\r\n\r\n private swapLogoContainerChildElement() {\r\n // This is a workaround that allow maps to be printed from the IE and Edge browsers.\r\n // For some unknown reason, the presence of an <a> child element in the .LogoContainer\r\n // prevents dashboard map visuals from showing up when printed.\r\n // The trick is to swap out the <a> element with a <div> container.\r\n // There are no user impacts or visual changes.\r\n let logoContainer = this.element.find('.LogoContainer');\r\n\r\n if (logoContainer) {\r\n let aNode = logoContainer.find('a');\r\n if (aNode == null)\r\n return;\r\n\r\n let divNode = $('<div>');\r\n aNode.children().clone().appendTo(divNode);\r\n aNode.remove();\r\n divNode.appendTo(logoContainer);\r\n }\r\n }\r\n\r\n public onResizing(viewport: IViewport): void {\r\n if (this.currentViewport.width !== viewport.width || this.currentViewport.height !== viewport.height) {\r\n this.currentViewport = viewport;\r\n this.renderLegend(this.legendData);\r\n this.updateInternal(false /* dataChanged */, false);\r\n }\r\n }\r\n\r\n private initialize(container: HTMLElement): void {\r\n let mapOptions = {\r\n credentials: MapUtil.Settings.BingKey,\r\n showMapTypeSelector: false,\r\n enableClickableLogo: false,\r\n enableSearchLogo: false,\r\n mapTypeId: Microsoft.Maps.MapTypeId.road,\r\n customizeOverlays: true,\r\n showDashboard: false,\r\n showScalebar: false,\r\n disableKeyboardInput: true, // Workaround for the BingMaps control moving focus from QnA\r\n disableZooming: this.disableZooming,\r\n disablePanning: this.disablePanning,\r\n };\r\n let divQuery = this.root = InJs.DomFactory.div().addClass(Map.MapContainer.cssClass).appendTo(container);\r\n this.mapControl = this.mapControlFactory.createMapControl(divQuery[0], mapOptions);\r\n\r\n if (this.viewChangeThrottleInterval !== undefined) {\r\n Microsoft.Maps.Events.addThrottledHandler(this.mapControl, 'viewchange', () => { this.onViewChanged(); },\r\n this.viewChangeThrottleInterval);\r\n } else {\r\n Microsoft.Maps.Events.addHandler(this.mapControl, 'viewchange', () => { this.onViewChanged(); });\r\n }\r\n\r\n Microsoft.Maps.Events.addHandler(this.mapControl, \"viewchangeend\", () => { this.onViewChangeEnded(); });\r\n this.dataPointRenderer.init(this.mapControl, divQuery, !!this.behavior);\r\n\r\n if (!this.pendingGeocodingRender) {\r\n this.updateInternal(true /* dataChanged */, true);\r\n }\r\n }\r\n\r\n private onViewChanged() {\r\n this.updateOffsets(false, false /* dataChanged */);\r\n if (this.behavior)\r\n this.behavior.viewChanged();\r\n\r\n this.swapLogoContainerChildElement();\r\n }\r\n\r\n private onViewChangeEnded() {\r\n\r\n this.dataPointRenderer.updateInternalDataLabels(this.currentViewport, true);\r\n }\r\n\r\n private getMapViewPort(): IViewport {\r\n let currentViewport = this.currentViewport;\r\n let legendMargins = this.legend.getMargins();\r\n\r\n let mapViewport = {\r\n width: currentViewport.width - legendMargins.width,\r\n height: currentViewport.height - legendMargins.height,\r\n };\r\n\r\n return mapViewport;\r\n }\r\n\r\n public static removeTransform3d(mapRoot: JQuery): void {\r\n // don't remove transform3d from bing maps images in safari (using applewebkit engine)\r\n let userAgent = window.navigator.userAgent.toLowerCase();\r\n if (mapRoot && userAgent.indexOf('applewebkit') === -1) {\r\n let imageTiles = mapRoot.find('img');\r\n imageTiles.css('transform', '');\r\n }\r\n }\r\n\r\n private updateInternal(dataChanged: boolean, redrawDataLabels: boolean) {\r\n if (this.mapControl) {\r\n let isLegendVisible = this.legend.isVisible();\r\n\r\n if (!isLegendVisible)\r\n this.legendData = { dataPoints: [] };\r\n\r\n let mapDiv = this.element.children(Map.MapContainer.selector);\r\n let mapViewport = this.getMapViewPort();\r\n mapDiv.height(mapViewport.height);\r\n mapDiv.width(mapViewport.width);\r\n\r\n // With the risk of double drawing, if the position updates to nearly the same, the map control won't call viewchange, so explicitly update the points\r\n this.updateOffsets(dataChanged, redrawDataLabels);\r\n\r\n // Set zoom level after we rendered that map as we need the max size of the bubbles/ pie slices to calculate it\r\n if (this.boundsHaveBeenUpdated && !(this.behavior && this.behavior.hasReceivedZoomOrPanEvent())) {\r\n let levelOfDetail = this.getOptimumLevelOfDetail(mapViewport.width, mapViewport.height);\r\n let center = this.getViewCenter(levelOfDetail);\r\n\r\n this.updateMapView(center, levelOfDetail);\r\n }\r\n }\r\n }\r\n\r\n private updateMapView(center: Microsoft.Maps.Location, levelOfDetail: number): void {\r\n this.mapControl.setView({ center: center, zoom: levelOfDetail, animate: true });\r\n }\r\n\r\n private updateOffsets(dataChanged: boolean, redrawDataLabels: boolean) {\r\n let dataView = this.dataView;\r\n let data: MapRendererData;\r\n let viewport = this.getMapViewPort();\r\n if (dataView && dataView.categorical) {\r\n // currentViewport may not exist in UnitTests\r\n data = this.dataPointRenderer.converter(viewport, this.dataView, this.dataLabelsSettings, this.interactivityService, this.tooltipsEnabled);\r\n }\r\n else {\r\n data = {\r\n bubbleData: [],\r\n shapeData: [],\r\n sliceData: [],\r\n };\r\n }\r\n\r\n let behaviorOptions = this.dataPointRenderer.updateInternal(data, viewport, dataChanged, this.interactivityService, redrawDataLabels);\r\n Legend.positionChartArea(d3.select(this.root[0]), this.legend);\r\n\r\n if (this.interactivityService && behaviorOptions) {\r\n this.interactivityService.bind(behaviorOptions.dataPoints, this.behavior, behaviorOptions);\r\n }\r\n }\r\n\r\n public onClearSelection(): void {\r\n this.interactivityService.clearSelection();\r\n this.updateOffsets(false, false /* dataChanged */);\r\n }\r\n\r\n private clearDataPoints(): void {\r\n this.dataPointRenderer.clearDataPoints();\r\n this.legend.drawLegend({ dataPoints: [] }, this.currentViewport);\r\n }\r\n\r\n private getDefaultMapControlFactory(): IMapControlFactory {\r\n return {\r\n createMapControl: (element: HTMLElement, options: Microsoft.Maps.MapOptions) => new Microsoft.Maps.Map(element, options),\r\n ensureMap: jsCommon.ensureMap,\r\n };\r\n }\r\n\r\n private static removeHillShading() {\r\n Microsoft.Maps.Globals.roadUriFormat = Microsoft.Maps.Globals.roadUriFormat.replace('&shading=hill', '');\r\n }\r\n }\r\n}\n\n\n/** WEBPACK FOOTER **\n ** ./src/Clients/Visuals/visuals/map.ts\n **/","/*\r\n * Power BI Visualizations\r\n *\r\n * Copyright (c) Microsoft Corporation\r\n * All rights reserved.\r\n * MIT License\r\n *\r\n * Permission is hereby granted, free of charge, to any person obtaining a copy\r\n * of this software and associated documentation files (the \"\"Software\"\"), to deal\r\n * in the Software without restriction, including without limitation the rights\r\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\r\n * copies of the Software, and to permit persons to whom the Software is\r\n * furnished to do so, subject to the following conditions:\r\n *\r\n * The above copyright notice and this permission notice shall be included in\r\n * all copies or substantial portions of the Software.\r\n *\r\n * THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\r\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\r\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\r\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\r\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\r\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\r\n * THE SOFTWARE.\r\n */\r\n\r\n/// <reference path=\"../_references.ts\"/>\r\n\r\nmodule powerbi.visuals {\r\n import getKpiImageMetadata = powerbi.visuals.KpiUtil.getKpiImageMetadata;\r\n import ClassAndSelector = jsCommon.CssConstants.ClassAndSelector;\r\n import createClassAndSelector = jsCommon.CssConstants.createClassAndSelector;\r\n import PixelConverter = jsCommon.PixelConverter;\r\n import UrlUtils = jsCommon.UrlUtils;\r\n import Surround = powerbi.visuals.controls.internal.TablixUtils.Surround;\r\n import EdgeSettings = powerbi.visuals.controls.internal.TablixUtils.EdgeSettings;\r\n\r\n const TitleFontFamily = Font.Family.semibold.css;\r\n const DefaultFontFamily = Font.Family.regular.css;\r\n const DefaultCaptionFontSizeInPt = 10;\r\n const DefaultTitleFontSizeInPt = 13;\r\n const DefaultDetailFontSizeInPt = 9;\r\n \r\n const DefaultTitleColor = '#767676';\r\n const DefaultTextColor = '#333333';\r\n const DefaultCategoryColor = '#ACACAC';\r\n const DefaultOutline = outline.none;\r\n const DefaultOutlineColor = '#E8E8E8';\r\n const DefaultOutlineWeight = 1;\r\n const DefaultBarShow = true;\r\n const DefaultBarColor = '#A6A6A6';\r\n const DefaultBarOutline = outline.leftOnly;\r\n const DefaultBarWeight = 3;\r\n\r\n export interface CardItemData {\r\n caption: string;\r\n details: string;\r\n showURL: boolean;\r\n showImage: boolean;\r\n showKPI: boolean;\r\n columnIndex: number;\r\n }\r\n \r\n export interface CardSettings {\r\n outlineSettings: OutlineSettings;\r\n barSettings: OutlineSettings;\r\n cardPadding: number;\r\n cardBackground: string;\r\n }\r\n \r\n export interface OutlineSettings{\r\n outline: string;\r\n color: string;\r\n weight: number;\r\n }\r\n \r\n export interface MultiRowCardData {\r\n dataModel: CardData[];\r\n dataColumnCount: number;\r\n cardTitleSettings: VisualDataLabelsSettings;\r\n dataLabelsSettings: VisualDataLabelsSettings;\r\n categoryLabelsSettings: VisualDataLabelsSettings;\r\n cardSettings: CardSettings;\r\n }\r\n\r\n export interface CardData {\r\n title?: string;\r\n showTitleAsURL?: boolean;\r\n showTitleAsImage?: boolean;\r\n showTitleAsKPI?: boolean;\r\n cardItemsData: CardItemData[];\r\n }\r\n\r\n interface ImageStyle {\r\n maxWidth?: number;\r\n maxHeight?: number;\r\n }\r\n\r\n interface MediaQuery {\r\n maxWidth?: number;\r\n style?: MultiRowCardStyle;\r\n }\r\n\r\n interface MultiRowCardStyle {\r\n row?: {\r\n border?: Surround<EdgeSettings>;\r\n padding?: Surround<number>;\r\n marginBottom?: number;\r\n background?: string\r\n };\r\n card?: {\r\n border?: Surround<EdgeSettings>;\r\n padding?: Surround<number>;\r\n };\r\n cardItemContainer?: {\r\n paddingRight?: number;\r\n minWidth?: number;\r\n padding?: Surround<number>;\r\n };\r\n details?: {\r\n fontSize?: number;\r\n color?: string,\r\n isVisible?: boolean;\r\n };\r\n caption?: {\r\n fontSize?: number;\r\n color?: string,\r\n };\r\n title?: {\r\n fontSize?: number;\r\n color?: string,\r\n };\r\n imageCaption?: ImageStyle;\r\n imageTitle?: ImageStyle;\r\n }\r\n\r\n export class MultiRowCard implements IVisual {\r\n private currentViewport: IViewport;\r\n private options: VisualInitOptions;\r\n private dataView: DataView;\r\n private style: IVisualStyle;\r\n private element: JQuery;\r\n private listView: IListView;\r\n /**\r\n * This includes card height with margin that will be passed to list view.\r\n */\r\n private interactivity: InteractivityOptions;\r\n private isInteractivityOverflowHidden: boolean = false;\r\n private waitingForData: boolean;\r\n private cardHasTitle: boolean;\r\n private isSingleRowCard: boolean;\r\n private maxColPerRow: number;\r\n private data: MultiRowCardData;\r\n\r\n /**\r\n * Note: Public for testability.\r\n */\r\n public static formatStringProp: DataViewObjectPropertyIdentifier = {\r\n objectName: 'general',\r\n propertyName: 'formatString',\r\n };\r\n\r\n private static MultiRowCardRoot = createClassAndSelector('multiRowCard');\r\n private static Card: ClassAndSelector = createClassAndSelector('card');\r\n private static Title: ClassAndSelector = createClassAndSelector('title');\r\n private static CardItemContainer: ClassAndSelector = createClassAndSelector('cardItemContainer');\r\n private static Caption: ClassAndSelector = createClassAndSelector('caption');\r\n private static Details: ClassAndSelector = createClassAndSelector('details');\r\n private static TitleUrlSelector: string = MultiRowCard.Title.selector + ' a';\r\n private static CaptionUrlSelector: string = MultiRowCard.Caption.selector + ' a';\r\n private static TitleImageSelector: string = MultiRowCard.Title.selector + ' img';\r\n private static CaptionImageSelector: string = MultiRowCard.Caption.selector + ' img';\r\n private static KPITitle: ClassAndSelector = createClassAndSelector('kpiTitle');\r\n private static ValuesRole: string = 'Values';\r\n\r\n /**\r\n * Cards have specific styling so defined inline styles and also to support theming and improve performance.\r\n */\r\n private static DefaultStyle: MultiRowCardStyle = {\r\n row: {\r\n border: null,\r\n marginBottom: 20,\r\n background: undefined,\r\n padding: {\r\n top: 5,\r\n right: 5,\r\n bottom: 5,\r\n left: 5\r\n }\r\n },\r\n card: {\r\n border: null,\r\n padding: {\r\n top: 10,\r\n right: 10,\r\n bottom: 10,\r\n left: 10\r\n }\r\n },\r\n cardItemContainer: {\r\n paddingRight: 20,\r\n minWidth: 120,\r\n padding: {\r\n top: 7\r\n }\r\n },\r\n imageCaption: {\r\n maxHeight: 75,\r\n maxWidth: 100,\r\n },\r\n imageTitle: {\r\n maxHeight: 75,\r\n maxWidth: 100,\r\n }\r\n };\r\n\r\n // queries should be ordered by maxWidth in ascending order\r\n private static tileMediaQueries: MediaQuery[] = [\r\n {\r\n maxWidth: 250,\r\n style: {\r\n cardItemContainer: {\r\n minWidth: 110,\r\n },\r\n imageCaption: {\r\n maxHeight: 45,\r\n }\r\n }\r\n },\r\n {\r\n maxWidth: 490,\r\n style: {\r\n cardItemContainer: {\r\n minWidth: 130,\r\n },\r\n imageCaption: {\r\n maxHeight: 52,\r\n }\r\n }\r\n },\r\n {\r\n maxWidth: 750,\r\n style: {\r\n cardItemContainer: {\r\n minWidth: 120,\r\n },\r\n imageCaption: {\r\n maxHeight: 53,\r\n }\r\n }\r\n },\r\n {\r\n maxWidth: Number.MAX_VALUE,\r\n style: {\r\n cardItemContainer: {\r\n padding: {\r\n top: 5\r\n }\r\n }\r\n }\r\n }\r\n ];\r\n\r\n public init(options: VisualInitOptions) {\r\n debug.assertValue(options, 'options');\r\n this.options = options;\r\n this.style = options.style;\r\n let viewport = this.currentViewport = options.viewport;\r\n let interactivity = this.interactivity = options.interactivity;\r\n\r\n if (interactivity && interactivity.overflow === 'hidden')\r\n this.isInteractivityOverflowHidden = true;\r\n\r\n let multiRowCardDiv = this.element = $('<div/>')\r\n .addClass(MultiRowCard.MultiRowCardRoot.class)\r\n .css({\r\n 'height': getPixelString(viewport.height),\r\n });\r\n options.element.append(multiRowCardDiv);\r\n this.initializeCardRowSelection();\r\n }\r\n\r\n public onDataChanged(options: VisualDataChangedOptions): void {\r\n debug.assertValue(options, 'options');\r\n\r\n let dataViews = options.dataViews;\r\n if (dataViews && dataViews.length > 0) {\r\n let dataView = this.dataView = dataViews[0];\r\n let columnMetadata: DataViewMetadataColumn[] = dataView.table.columns;\r\n let tableRows: any[][] = dataView.table.rows;\r\n let resetScrollbarPosition = options.operationKind !== VisualDataChangeOperationKind.Append;\r\n let data = this.data = MultiRowCard.converter(dataView, columnMetadata.length, tableRows.length, this.isInteractivityOverflowHidden);\r\n this.setCardDimensions();\r\n this.listView.data(data.dataModel, (d: CardData) => data.dataModel.indexOf(d), resetScrollbarPosition);\r\n }\r\n else {\r\n this.data = {\r\n dataModel: [],\r\n dataColumnCount: 0,\r\n cardTitleSettings: dataLabelUtils.getDefaultLabelSettings(true, DefaultTitleColor, DefaultTitleFontSizeInPt),\r\n categoryLabelsSettings: dataLabelUtils.getDefaultLabelSettings(true, DefaultCategoryColor, DefaultDetailFontSizeInPt),\r\n dataLabelsSettings: dataLabelUtils.getDefaultLabelSettings(true, DefaultTextColor, DefaultCaptionFontSizeInPt),\r\n cardSettings: MultiRowCard.getCardSettings(null)\r\n };\r\n }\r\n\r\n this.waitingForData = false;\r\n }\r\n \r\n private static getCardSettings(dataView: DataView): CardSettings {\r\n\r\n let objects = dataView && dataView.metadata && dataView.metadata.objects ? dataView.metadata.objects : null;\r\n\r\n let outlineSettings: OutlineSettings = {\r\n outline: DataViewObjects.getValue(objects, multiRowCardProps.card.outline, DefaultOutline),\r\n color: DataViewObjects.getFillColor(objects, multiRowCardProps.card.outlineColor, DefaultOutlineColor),\r\n weight: DataViewObjects.getValue(objects, multiRowCardProps.card.outlineWeight, DefaultOutlineWeight),\r\n };\r\n\r\n let barShow = DataViewObjects.getValue(objects, multiRowCardProps.card.barShow, DefaultBarShow);\r\n\r\n let barSettings: OutlineSettings = {\r\n // If the bar is hidden, set the outline to none\r\n outline: barShow ? DefaultBarOutline : outline.none,\r\n color: DataViewObjects.getFillColor(objects, multiRowCardProps.card.barColor, DefaultBarColor),\r\n weight: DataViewObjects.getValue(objects, multiRowCardProps.card.barWeight, DefaultBarWeight),\r\n };\r\n\r\n let cardPadding = DataViewObjects.getValue(objects, multiRowCardProps.card.cardPadding, MultiRowCard.DefaultStyle.row.marginBottom);\r\n let cardBackground = DataViewObjects.getFillColor(objects, multiRowCardProps.card.cardBackground, MultiRowCard.DefaultStyle.row.background);\r\n\r\n return {\r\n outlineSettings: outlineSettings,\r\n barSettings: barSettings,\r\n cardPadding: cardPadding,\r\n cardBackground: cardBackground\r\n };\r\n }\r\n\r\n public onResizing(viewport: IViewport): void {\r\n let heightNotChanged = (this.currentViewport.height === viewport.height);\r\n this.currentViewport = viewport;\r\n this.element.css('height', getPixelString(viewport.height));\r\n if (!this.dataView)\r\n return;\r\n\r\n let previousMaxColPerRow = this.maxColPerRow;\r\n this.maxColPerRow = this.getMaxColPerRow();\r\n let widthNotChanged = (previousMaxColPerRow === this.maxColPerRow);\r\n if (heightNotChanged && widthNotChanged)\r\n return;\r\n\r\n this.listView.viewport(viewport);\r\n }\r\n \r\n public static converter(dataView: DataView, columnCount: number, maxCards: number, isDashboardVisual: boolean = false): MultiRowCardData {\r\n let details: CardData[] = [];\r\n let tableDataRows = dataView.table.rows;\r\n let columnMetadata: DataViewMetadataColumn[] = dataView.table.columns;\r\n let cardTitleSettings: VisualDataLabelsSettings ,\r\n dataLabelsSettings: VisualDataLabelsSettings,\r\n categoryLabelsSettings: VisualDataLabelsSettings;\r\n\r\n cardTitleSettings = dataLabelUtils.getDefaultLabelSettings(true, DefaultTitleColor, DefaultTitleFontSizeInPt);\r\n dataLabelsSettings = dataLabelUtils.getDefaultLabelSettings(true, DefaultTextColor, DefaultCaptionFontSizeInPt);\r\n categoryLabelsSettings = dataLabelUtils.getDefaultLabelSettings(true, DefaultCategoryColor, DefaultDetailFontSizeInPt);\r\n\r\n if (dataView.metadata && dataView.metadata.objects) {\r\n let cardTitleLabelObjects = <DataLabelObject>DataViewObjects.getObject(dataView.metadata.objects, 'cardTitle');\r\n dataLabelUtils.updateLabelSettingsFromLabelsObject(cardTitleLabelObjects, cardTitleSettings);\r\n\r\n let dataLabelObject = <DataLabelObject>DataViewObjects.getObject(dataView.metadata.objects, 'dataLabels');\r\n dataLabelUtils.updateLabelSettingsFromLabelsObject(dataLabelObject, dataLabelsSettings);\r\n\r\n let categoryLabelObject = <DataLabelObject>DataViewObjects.getObject(dataView.metadata.objects, 'categoryLabels');\r\n dataLabelUtils.updateLabelSettingsFromLabelsObject(categoryLabelObject, categoryLabelsSettings);\r\n }\r\n\r\n for (let i = 0, len = maxCards; i < len; i++) {\r\n let row = tableDataRows[i];\r\n let isValuePromoted: boolean = undefined;\r\n var title: string = undefined;\r\n let showTitleAsURL: boolean = false;\r\n let showTitleAsImage: boolean = false;\r\n let showTitleAsKPI: boolean = false;\r\n let cardData: CardItemData[] = [];\r\n for (let j = 0; j < columnCount; j++) {\r\n let column = columnMetadata[j];\r\n\r\n let statusGraphicInfo = getKpiImageMetadata(column, row[j]);\r\n let columnCaption: string;\r\n let statusGraphic: string;\r\n\r\n if (statusGraphicInfo) {\r\n columnCaption = statusGraphicInfo.class;\r\n statusGraphic = statusGraphicInfo.statusGraphic;\r\n }\r\n\r\n //TODO: seems we are duplicating this logic in many places. Consider putting it in KPIUtil\r\n if (!columnCaption)\r\n columnCaption = valueFormatter.format(row[j], valueFormatter.getFormatString(column, MultiRowCard.formatStringProp));\r\n\r\n let showKPI = statusGraphicInfo !== undefined && statusGraphicInfo.caption !== undefined;\r\n\r\n // The columnDetail represents column name. In card the column name is shown as details\r\n let columnDetail: string = columnMetadata[j].displayName;\r\n\r\n //Title is shown only on Canvas and only if there is one Category field.\r\n if (!isDashboardVisual && !column.type.numeric) {\r\n if (isValuePromoted === undefined) {\r\n isValuePromoted = true;\r\n title = columnCaption;\r\n showTitleAsURL = converterHelper.isWebUrlColumn(column) && UrlUtils.isValidUrl(title);\r\n showTitleAsImage = converterHelper.isImageUrlColumn(column) && UrlUtils.isValidImageUrl(columnCaption);\r\n showTitleAsKPI = showKPI;\r\n }\r\n else if (isValuePromoted) {\r\n isValuePromoted = false;\r\n }\r\n }\r\n cardData.push({\r\n caption: columnCaption,\r\n details: columnDetail,\r\n showURL: converterHelper.isWebUrlColumn(column) && UrlUtils.isValidUrl(columnCaption),\r\n showImage: converterHelper.isImageUrlColumn(column) && UrlUtils.isValidImageUrl(columnCaption),\r\n showKPI: showKPI,\r\n columnIndex: j\r\n });\r\n }\r\n details.push({\r\n title: isValuePromoted ? title : undefined,\r\n showTitleAsURL: showTitleAsURL,\r\n showTitleAsImage: showTitleAsImage,\r\n showTitleAsKPI: showTitleAsKPI,\r\n cardItemsData: isValuePromoted ? cardData.filter((d: CardItemData) => d.caption !== title) : cardData\r\n });\r\n }\r\n return {\r\n dataModel: details,\r\n dataColumnCount: details[0] ? details[0].cardItemsData.length : 0,\r\n cardTitleSettings: cardTitleSettings,\r\n categoryLabelsSettings: categoryLabelsSettings,\r\n dataLabelsSettings: dataLabelsSettings,\r\n cardSettings: MultiRowCard.getCardSettings(dataView)\r\n };\r\n }\r\n \r\n public static getSortableRoles(options: VisualSortableOptions): string[] {\r\n \r\n if (!options || !options.dataViewMappings || _.isEmpty(options.dataViewMappings)) {\r\n return;\r\n }\r\n\r\n for (let dataViewMapping of options.dataViewMappings) {\r\n if (dataViewMapping.table) {\r\n\r\n let rows = <powerbi.data.CompiledDataViewRoleForMappingWithReduction>dataViewMapping.table.rows;\r\n\r\n if (rows && rows.for && rows.for.in && rows.for.in.items) {\r\n return [MultiRowCard.ValuesRole];\r\n }\r\n }\r\n }\r\n\r\n return;\r\n }\r\n\r\n private initializeCardRowSelection() {\r\n let isDashboardVisual = this.isInteractivityOverflowHidden;\r\n\r\n let rowEnter = (rowSelection: D3.Selection) => {\r\n let cardRow = rowSelection\r\n .append(\"div\")\r\n .classed(MultiRowCard.Card.class, true);\r\n\r\n // The card top padding is not needed when card items are wrapped as top padding is added to each carditemcontainer when wrapped\r\n if (isDashboardVisual) {\r\n cardRow.classed('mrtile', true);\r\n }\r\n else {\r\n if (this.cardHasTitle) {\r\n cardRow.append(\"div\").classed(MultiRowCard.Title.class, true)\r\n .each(function (d: CardData) {\r\n if (d.showTitleAsImage)\r\n appendImage(d3.select(this));\r\n else if (d.showTitleAsURL)\r\n d3.select(this).append('a');\r\n else if (d.showTitleAsKPI)\r\n d3.select(this).append('div')\r\n .classed(MultiRowCard.KPITitle.class, true)\r\n .classed(d.title, true)\r\n .style({\r\n display: 'inline-block',\r\n verticalAlign: 'sub'\r\n });\r\n });\r\n }\r\n }\r\n\r\n let cardItem = cardRow\r\n .selectAll(MultiRowCard.CardItemContainer.selector)\r\n .data((d: CardData) => d.cardItemsData)\r\n .enter()\r\n .append('div')\r\n .classed(MultiRowCard.CardItemContainer.class, true);\r\n\r\n cardItem\r\n .append('div')\r\n .classed(MultiRowCard.Caption.class, true)\r\n .each(function (d: CardItemData) {\r\n if (d.showURL) {\r\n d3.select(this).append('a');\r\n }\r\n else if (d.showImage) {\r\n appendImage(d3.select(this));\r\n }\r\n else if (d.showKPI) {\r\n d3.select(this).append('div')\r\n .classed(d.caption, true)\r\n .style({\r\n display: 'inline-block',\r\n verticalAlign: 'sub'\r\n });\r\n }\r\n });\r\n\r\n cardItem\r\n .append('div')\r\n .classed(MultiRowCard.Details.class, true);\r\n };\r\n\r\n /**\r\n * Row update should:\r\n * 1. bind Data\r\n * 2. Manipulate DOM (likely just updating CSS properties) affected by data\r\n */\r\n let rowUpdate = (rowSelection: D3.Selection) => {\r\n let style = this.getStyle();\r\n let dataLabelHeight = TextMeasurementService.estimateSvgTextHeight(MultiRowCard.getTextProperties(false, style.caption.fontSize));\r\n let categoryLabelHeight = TextMeasurementService.estimateSvgTextHeight(MultiRowCard.getTextProperties(false, style.details.fontSize));\r\n let titleLabelHeight = TextMeasurementService.estimateSvgTextHeight(MultiRowCard.getTextProperties(true, style.title.fontSize));\r\n let rowBorderStyle = this.getBorderStyles(style.row.border, style.row.padding);\r\n \r\n rowSelection\r\n .style(rowBorderStyle)\r\n .style({\r\n 'margin-bottom': isDashboardVisual ? '0px' : (this.isSingleRowCard ? '0px' : getPixelString(style.row.marginBottom)),\r\n 'background': style.row.background\r\n });\r\n\r\n if (!isDashboardVisual && this.cardHasTitle) {\r\n rowSelection.selectAll(MultiRowCard.Title.selector)\r\n .filter((d: CardData) => !d.showTitleAsImage && !d.showTitleAsKPI)\r\n .style({\r\n 'font-size': PixelConverter.fromPoint(style.title.fontSize),\r\n 'line-height': PixelConverter.toString(titleLabelHeight),\r\n 'color': style.title.color,\r\n });\r\n\r\n rowSelection.selectAll(MultiRowCard.Title.selector)\r\n .filter((d: CardData) => !d.showTitleAsURL && !d.showTitleAsImage && !d.showTitleAsKPI)\r\n .text((d: CardData) => d.title)\r\n .attr('title', (d: CardData) => d.title);\r\n\r\n rowSelection\r\n .selectAll(MultiRowCard.TitleUrlSelector)\r\n .text((d: CardData) => d.title)\r\n .attr({\r\n 'href': (d: CardData) => d.title,\r\n 'target': '_blank',\r\n });\r\n\r\n rowSelection\r\n .selectAll(MultiRowCard.TitleImageSelector)\r\n .attr('src', (d: CardData) => d.title);\r\n setImageStyle(rowSelection.selectAll(MultiRowCard.Title.selector), style.imageTitle);\r\n\r\n rowSelection\r\n .selectAll(MultiRowCard.KPITitle.selector)\r\n .each(function (d: CardData) {\r\n let element = d3.select(this);\r\n element.classed(d.title);\r\n });\r\n }\r\n \r\n let cardSelection = rowSelection.selectAll(MultiRowCard.Card.selector);\r\n let cardBorderStyle = this.getBorderStyles(style.card.border, style.card.padding);\r\n cardSelection.style(cardBorderStyle);\r\n\r\n cardSelection\r\n .selectAll(MultiRowCard.Caption.selector)\r\n .filter((d: CardItemData) => !d.showImage)\r\n .style({\r\n 'line-height': PixelConverter.toString(dataLabelHeight),\r\n 'font-size': PixelConverter.fromPoint(style.caption.fontSize),\r\n })\r\n .filter((d: CardItemData) => !d.showKPI)\r\n .style({\r\n 'color': style.caption.color,\r\n })\r\n .filter((d: CardItemData) => !d.showURL)\r\n .text((d: CardItemData) => d.caption)\r\n .attr('title', (d: CardItemData) => d.caption);\r\n\r\n cardSelection\r\n .selectAll(MultiRowCard.CaptionImageSelector)\r\n .attr('src', (d: CardItemData) => d.caption)\r\n .style(style.imageCaption);\r\n \r\n let cardPaddingTop = getPixelString(style.cardItemContainer.padding.top);\r\n\r\n cardSelection\r\n .selectAll(MultiRowCard.CardItemContainer.selector)\r\n .style({\r\n 'padding-top': (d: CardItemData) => {\r\n return this.isInFirstRow(d.columnIndex) ? '': cardPaddingTop;\r\n },\r\n 'padding-right': (d: CardItemData) => {\r\n return this.isLastRowItem(d.columnIndex, this.dataView.metadata.columns.length) ? '0px' : getPixelString(style.cardItemContainer.paddingRight);\r\n },\r\n 'width': (d: CardItemData) => {\r\n return this.getColumnWidth(d.columnIndex, this.data.dataColumnCount);\r\n }\r\n });\r\n\r\n setImageStyle(cardSelection.selectAll(MultiRowCard.Caption.selector), style.imageCaption);\r\n\r\n cardSelection\r\n .selectAll(MultiRowCard.CaptionUrlSelector)\r\n .attr({\r\n 'href': (d: CardItemData) => d.caption,\r\n 'target': '_blank',\r\n })\r\n .text((d: CardItemData) => d.caption);\r\n\r\n if (style.details.isVisible) {\r\n cardSelection\r\n .selectAll(MultiRowCard.Details.selector)\r\n .text((d: CardItemData) => d.details)\r\n .style({\r\n 'font-size': PixelConverter.fromPoint(style.details.fontSize),\r\n 'line-height': PixelConverter.toString(categoryLabelHeight),\r\n 'color': style.details.color\r\n })\r\n .attr('title', (d: CardItemData) => d.details);\r\n }\r\n };\r\n\r\n let rowExit = (rowSelection: D3.Selection) => {\r\n rowSelection.remove();\r\n };\r\n\r\n let listViewOptions: ListViewOptions = {\r\n rowHeight: undefined,\r\n enter: rowEnter,\r\n exit: rowExit,\r\n update: rowUpdate,\r\n loadMoreData: () => this.onLoadMoreData(),\r\n viewport: this.currentViewport,\r\n baseContainer: d3.select(this.element.get(0)),\r\n scrollEnabled: !this.isInteractivityOverflowHidden,\r\n isReadMode: () => {\r\n return (this.options.host.getViewMode() !== ViewMode.Edit);\r\n }\r\n };\r\n\r\n this.listView = ListViewFactory.createListView(listViewOptions);\r\n }\r\n \r\n private getBorderStyles(border: Surround<EdgeSettings>, padding?: Surround<number>): { [property: string]: string } {\r\n \r\n let hasBorder: Surround<boolean> = {\r\n top: border != null && border.top != null,\r\n right: border != null && border.right != null,\r\n bottom: border != null && border.bottom != null,\r\n left: border != null && border.left != null\r\n };\r\n \r\n let hasPadding: Surround<boolean> = {\r\n top: padding != null && padding.top != null,\r\n right: padding != null && padding.right != null,\r\n bottom: padding != null && padding.bottom != null,\r\n left: padding != null && padding.left != null\r\n };\r\n\r\n return {\r\n 'border-top': hasBorder.top ? border.top.getCSS() : '',\r\n 'border-right': hasBorder.right ? border.right.getCSS() : '',\r\n 'border-bottom': hasBorder.bottom ? border.bottom.getCSS() : '',\r\n 'border-left': hasBorder.left ? border.left.getCSS() : '',\r\n 'padding-top': hasBorder.top && hasPadding.top ? getPixelString(padding.top) : '',\r\n 'padding-right': hasBorder.right && hasPadding.right ? getPixelString(padding.right) : '',\r\n 'padding-bottom': hasBorder.bottom && hasPadding.bottom ? getPixelString(padding.bottom) : '',\r\n 'padding-left': hasBorder.left && hasPadding.left ? getPixelString(padding.left) : '',\r\n };\r\n } \r\n\r\n private getMaxColPerRow(): number {\r\n let rowWidth = this.currentViewport.width;\r\n let minColumnWidth = this.getStyle().cardItemContainer.minWidth;\r\n let columnCount = this.data.dataColumnCount;\r\n //atleast one column fits in a row\r\n let maxColumnPerRow = Math.floor(rowWidth / minColumnWidth) || 1;\r\n return Math.min(columnCount, maxColumnPerRow);\r\n }\r\n\r\n private getRowIndex(fieldIndex: number): number {\r\n return Math.floor((fieldIndex * 1.0) / this.getMaxColPerRow());\r\n }\r\n\r\n private getStyle(): MultiRowCardStyle {\r\n let defaultStyles = MultiRowCard.DefaultStyle;\r\n let customStyles = this.getCustomStyles();\r\n \r\n if (!this.isInteractivityOverflowHidden)\r\n return $.extend(true, {}, defaultStyles, customStyles);\r\n\r\n let viewportWidth = this.currentViewport.width;\r\n let overrideStyle: MultiRowCardStyle = {};\r\n for (let currentQuery of MultiRowCard.tileMediaQueries)\r\n if (viewportWidth <= currentQuery.maxWidth) {\r\n overrideStyle = currentQuery.style;\r\n break;\r\n }\r\n \r\n return $.extend(true, {}, defaultStyles, customStyles, overrideStyle);\r\n }\r\n \r\n private getSurroundSettings(outlineSettings: OutlineSettings): Surround<EdgeSettings>{\r\n \r\n let edge = new EdgeSettings(outlineSettings.weight, outlineSettings.color);\r\n let outlineProp = outlineSettings.outline;\r\n \r\n return {\r\n top: outline.showTop(outlineProp) ? edge : null,\r\n right: outline.showRight(outlineProp) ? edge : null,\r\n bottom: outline.showBottom(outlineProp) ? edge : null,\r\n left: outline.showLeft(outlineProp) ? edge : null,\r\n };\r\n }\r\n\r\n private getCustomStyles(): MultiRowCardStyle {\r\n let dataLabelsSettings = this.data.dataLabelsSettings;\r\n let categoryLabelSettings = this.data.categoryLabelsSettings;\r\n let titleLabelSettings = this.data.cardTitleSettings;\r\n let cardSettings = this.data.cardSettings;\r\n \r\n let customStyle: MultiRowCardStyle = {\r\n row: {\r\n border: this.getSurroundSettings(cardSettings.outlineSettings),\r\n marginBottom: cardSettings.cardPadding,\r\n background: cardSettings.cardBackground\r\n },\r\n card: {\r\n border: this.getSurroundSettings(cardSettings.barSettings)\r\n },\r\n details: {\r\n fontSize: categoryLabelSettings.fontSize,\r\n color: categoryLabelSettings.labelColor,\r\n isVisible: categoryLabelSettings.show,\r\n },\r\n caption: {\r\n fontSize: dataLabelsSettings.fontSize,\r\n color: dataLabelsSettings.labelColor,\r\n },\r\n title: {\r\n fontSize: titleLabelSettings.fontSize,\r\n color: titleLabelSettings.labelColor,\r\n }\r\n };\r\n\r\n return customStyle;\r\n }\r\n\r\n private static getTextProperties(isTitle: boolean, fontSizeInPt: number): TextProperties {\r\n return {\r\n fontFamily: isTitle ? TitleFontFamily : DefaultFontFamily,\r\n fontSize: PixelConverter.fromPoint(fontSizeInPt),\r\n };\r\n }\r\n\r\n private getColumnWidth(fieldIndex: number, columnCount: number): string {\r\n //atleast one column fits in a row\r\n let maxColumnPerRow = this.getMaxColPerRow();\r\n if (maxColumnPerRow >= columnCount)\r\n //all columns fit in the same row, divide the space equaly\r\n return (100.0 / columnCount) + '%';\r\n\r\n //calculate the number of items apearing in the same row as the columnIndex\r\n let rowIndex = this.getRowIndex(fieldIndex);\r\n\r\n let totalRows = Math.ceil((columnCount * 1.0) / maxColumnPerRow);\r\n let lastRowCount = columnCount % maxColumnPerRow;\r\n if (rowIndex < totalRows || lastRowCount === 0)\r\n // items is not on the last row or last row contains max columns allowed per row\r\n return (100.0 / maxColumnPerRow) + '%';\r\n\r\n // items is on the last row\r\n return (100.0 / lastRowCount) + '%';\r\n }\r\n\r\n private isLastRowItem(fieldIndex: number, columnCount: number): boolean {\r\n if (fieldIndex + 1 === columnCount)\r\n return true;\r\n let maxColumnPerRow = this.getMaxColPerRow();\r\n if (maxColumnPerRow - (fieldIndex % maxColumnPerRow) === 1)\r\n return true;\r\n\r\n return false;\r\n }\r\n\r\n private isInFirstRow(fieldIndex: number): boolean {\r\n return fieldIndex < this.getMaxColPerRow();\r\n }\r\n\r\n /**\r\n * This contains the card column wrapping logic.\r\n * Determines how many columns can be shown per each row inside a Card.\r\n * To place the fields evenly along the card,\r\n * the width of each card item is calculated based on the available viewport width.\r\n */\r\n private setCardDimensions(): void {\r\n this.cardHasTitle = false;\r\n\r\n let dataModel = this.data.dataModel;\r\n\r\n if (!this.isInteractivityOverflowHidden && dataModel && dataModel.length > 0) {\r\n this.cardHasTitle = dataModel[0].title !== undefined;\r\n this.isSingleRowCard = dataModel.length === 1 ? true : false;\r\n }\r\n }\r\n\r\n private onLoadMoreData(): void {\r\n if (!this.waitingForData && this.dataView.metadata && this.dataView.metadata.segment) {\r\n this.options.host.loadMoreData();\r\n this.waitingForData = true;\r\n }\r\n }\r\n\r\n private static getDataLabelSettingsOptions(enumeration: ObjectEnumerationBuilder, labelSettings: VisualDataLabelsSettings, show: boolean = false): VisualDataLabelsSettingsOptions {\r\n return {\r\n enumeration: enumeration,\r\n dataLabelsSettings: labelSettings,\r\n show: show,\r\n fontSize: true,\r\n };\r\n }\r\n\r\n public enumerateObjectInstances(options: EnumerateVisualObjectInstancesOptions): VisualObjectInstanceEnumeration {\r\n let enumeration = new ObjectEnumerationBuilder();\r\n\r\n let cardTitleSettings = this.data.cardTitleSettings;\r\n let dataLabelsSettings = this.data.dataLabelsSettings;\r\n let categoryLabelsSettings = this.data.categoryLabelsSettings;\r\n\r\n switch (options.objectName) {\r\n case 'cardTitle':\r\n //display title options only if title visible\r\n if (!this.isInteractivityOverflowHidden && this.cardHasTitle)\r\n dataLabelUtils.enumerateDataLabels(MultiRowCard.getDataLabelSettingsOptions(enumeration, cardTitleSettings));\r\n break;\r\n case 'dataLabels':\r\n dataLabelUtils.enumerateDataLabels(MultiRowCard.getDataLabelSettingsOptions(enumeration, dataLabelsSettings));\r\n break;\r\n case 'categoryLabels':\r\n dataLabelUtils.enumerateDataLabels(MultiRowCard.getDataLabelSettingsOptions(enumeration, categoryLabelsSettings, true));\r\n break;\r\n case multiRowCardProps.card.outline.objectName:\r\n this.enumerateCard(enumeration);\r\n break;\r\n }\r\n\r\n return enumeration.complete();\r\n }\r\n \r\n private enumerateCard(enumeration: ObjectEnumerationBuilder): void {\r\n\r\n let cardSettings = this.data.cardSettings;\r\n let propNames = multiRowCardProps.card;\r\n\r\n let properties: any = {};\r\n\r\n let outlineSettings = cardSettings.outlineSettings;\r\n properties[propNames.outline.propertyName] = outlineSettings.outline;\r\n\r\n if (outlineSettings.outline !== outline.none) {\r\n properties[propNames.outlineColor.propertyName] = outlineSettings.color;\r\n properties[propNames.outlineWeight.propertyName] = outlineSettings.weight;\r\n }\r\n\r\n let barSettings = cardSettings.barSettings;\r\n\r\n // The bar is shown if the outline value is not none\r\n let barShow = barSettings.outline !== outline.none;\r\n properties[propNames.barShow.propertyName] = barShow;\r\n\r\n if (barShow) {\r\n properties[propNames.barColor.propertyName] = barSettings.color;\r\n properties[propNames.barWeight.propertyName] = barSettings.weight;\r\n }\r\n\r\n properties[propNames.cardPadding.propertyName] = cardSettings.cardPadding;\r\n properties[propNames.cardBackground.propertyName] = cardSettings.cardBackground;\r\n\r\n enumeration.pushInstance({\r\n selector: null,\r\n objectName: propNames.outline.objectName,\r\n properties: properties\r\n });\r\n }\r\n }\r\n\r\n function appendImage(selection: D3.Selection): void {\r\n selection\r\n .append('div')\r\n .classed('imgCon', true)\r\n .append('img');\r\n }\r\n\r\n function setImageStyle(selection: D3.Selection, imageStyle: ImageStyle): void {\r\n selection\r\n .selectAll('.imgCon')\r\n .style({\r\n 'height': getPixelString(imageStyle.maxHeight),\r\n })\r\n .selectAll('img')\r\n .style({\r\n 'max-height': getPixelString(imageStyle.maxHeight),\r\n 'max-width': getPixelString(imageStyle.maxWidth),\r\n });\r\n }\r\n\r\n function getPixelString(value: number): string {\r\n return value + \"px\";\r\n }\r\n}\n\n\n/** WEBPACK FOOTER **\n ** ./src/Clients/Visuals/visuals/multiRowCard.ts\n **/","/*\r\n * Power BI Visualizations\r\n *\r\n * Copyright (c) Microsoft Corporation\r\n * All rights reserved. \r\n * MIT License\r\n *\r\n * Permission is hereby granted, free of charge, to any person obtaining a copy\r\n * of this software and associated documentation files (the \"\"Software\"\"), to deal\r\n * in the Software without restriction, including without limitation the rights\r\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\r\n * copies of the Software, and to permit persons to whom the Software is\r\n * furnished to do so, subject to the following conditions:\r\n * \r\n * The above copyright notice and this permission notice shall be included in \r\n * all copies or substantial portions of the Software.\r\n * \r\n * THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR \r\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, \r\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE \r\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER \r\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\r\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\r\n * THE SOFTWARE.\r\n */\r\n\r\n/// <reference path=\"../_references.ts\"/>\r\n\r\nmodule powerbi.visuals {\r\n import createClassAndSelector = jsCommon.CssConstants.createClassAndSelector;\r\n import KeyUtils = jsCommon.KeyUtils;\r\n import StringExtensions = jsCommon.StringExtensions;\r\n import UrlUtils = jsCommon.UrlUtils;\r\n\r\n export interface TextboxDataViewObjects extends DataViewObjects {\r\n general: TextboxDataViewObject;\r\n }\r\n\r\n export interface TextboxDataViewObject extends DataViewObject {\r\n paragraphs: Paragraphs;\r\n }\r\n \r\n /** Represents a rich text box that supports view & edit mode. */\r\n export class Textbox implements IVisual {\r\n private static ClassName = 'textbox';\r\n private editor: RichText.QuillWrapper;\r\n private element: JQuery;\r\n private host: IVisualHostServices;\r\n private viewport: IViewport;\r\n private readOnly: boolean;\r\n private paragraphs: Paragraphs;\r\n\r\n public init(options: VisualInitOptions) {\r\n this.element = options.element;\r\n this.host = options.host;\r\n this.viewport = options.viewport;\r\n\r\n this.readOnly = (this.host.getViewMode() === ViewMode.View);\r\n this.paragraphs = [];\r\n this.refreshView();\r\n }\r\n\r\n public onResizing(viewport: IViewport): void {\r\n this.viewport = viewport;\r\n this.updateSize();\r\n }\r\n\r\n public onDataChanged(options: VisualDataChangedOptions): void {\r\n debug.assertValue(options, 'options');\r\n let dataViews = options.dataViews;\r\n\r\n this.paragraphs = [];\r\n if (dataViews && dataViews.length > 0) {\r\n let objects = <TextboxDataViewObjects>dataViews[0].metadata.objects;\r\n\r\n if (objects && objects.general)\r\n this.paragraphs = objects.general.paragraphs;\r\n }\r\n\r\n this.refreshView();\r\n }\r\n\r\n public destroy(): void {\r\n }\r\n\r\n public focus(): boolean {\r\n if (!this.editor)\r\n return;\r\n\r\n this.editor.focus();\r\n return true;\r\n }\r\n\r\n public onViewModeChanged(viewMode: ViewMode): void {\r\n this.readOnly = (viewMode === ViewMode.View);\r\n this.refreshView();\r\n }\r\n\r\n public setSelection(start: number, end: number): void {\r\n debug.assertValue(this.editor, 'editor');\r\n\r\n if (this.editor)\r\n this.editor.setSelection(start, end);\r\n }\r\n\r\n private refreshView() {\r\n if (this.readOnly) {\r\n // Showing just HTML, no editor.\r\n // If we are in view-mode and we have an editor, we can remove it (after saving).\r\n if (this.editor) {\r\n this.saveContents();\r\n this.editor.destroy();\r\n this.editor = null;\r\n }\r\n\r\n this.element.empty();\r\n let htmlContent = RichTextConversion.convertParagraphsToHtml(this.paragraphs);\r\n htmlContent.addClass(Textbox.ClassName);\r\n htmlContent.css({\r\n 'font-family': RichText.defaultFont,\r\n 'font-size': RichText.defaultFontSize,\r\n });\r\n this.element.append(htmlContent);\r\n }\r\n else {\r\n // Showing the Quill editor.\r\n // If we are in edit-mode and we don't have an editor we need to create it.\r\n if (!this.editor) {\r\n this.editor = new RichText.QuillWrapper(this.readOnly, this.host);\r\n this.editor.textChanged = (delta, source) => this.saveContents();\r\n\r\n this.element.empty();\r\n let editorElement = this.editor.getElement();\r\n editorElement.addClass(Textbox.ClassName);\r\n editorElement.css({\r\n 'font-family': RichText.defaultFont,\r\n 'font-size': RichText.defaultFontSize,\r\n });\r\n this.element.append(editorElement);\r\n }\r\n\r\n this.editor.setContents(RichTextConversion.convertParagraphsToOps(this.paragraphs));\r\n }\r\n\r\n this.updateSize();\r\n }\r\n\r\n private saveContents(): void {\r\n // It's possible to get here via a throttled text-changed event after a view-mode change has occured and\r\n // we are now in view mode. Since we save changes on view-mode change it is safe to ignore this call.\r\n if (!this.editor)\r\n return;\r\n\r\n let contents: quill.Delta = this.editor.getContents();\r\n this.paragraphs = RichTextConversion.convertDeltaToParagraphs(contents);\r\n\r\n let changes: VisualObjectInstance[] = [{\r\n objectName: 'general',\r\n properties: {\r\n paragraphs: this.paragraphs\r\n },\r\n selector: null, // TODO: need something here?\r\n }];\r\n\r\n this.host.persistProperties(changes);\r\n }\r\n\r\n private updateSize(): void {\r\n if (this.editor)\r\n this.editor.resize(this.viewport);\r\n }\r\n }\r\n\r\n module RichTextConversion {\r\n export function convertDeltaToParagraphs(contents: quill.Delta): Paragraphs {\r\n let paragraphs: Paragraphs = [];\r\n let paragraph: Paragraph = { textRuns: [] };\r\n\r\n for (let i = 0, len = contents.ops.length; i < len; i++) {\r\n let insertOp = <quill.InsertOp>contents.ops[i];\r\n debug.assertValue(insertOp, \"operation should be an insert\");\r\n\r\n if (typeof insertOp.insert === \"string\") {\r\n // string insert values represent text.\r\n let text = <string>insertOp.insert;\r\n let attributes: quill.FormatAttributes = insertOp.attributes;\r\n\r\n if (attributes && attributes.align) {\r\n // Sometimes horizontal alignment is set after the first \"insert\" of the paragraph, which is likely a bug\r\n // in Quill. In any case we should never see different horizontal alignments in a single paragraph.\r\n debug.assert(\r\n paragraph.horizontalTextAlignment === undefined || paragraph.horizontalTextAlignment === attributes.align,\r\n 'paragraph should not have more than one horizontal alignment');\r\n paragraph.horizontalTextAlignment = attributes.align;\r\n }\r\n\r\n // Quill gives us back text runs that may have \\n's in them. We want to create a new paragraph for each \\n we see.\r\n let start = 0;\r\n let end = 0;\r\n let newParagraph: boolean;\r\n do {\r\n end = text.indexOf('\\n', start);\r\n if (end < 0) {\r\n newParagraph = false;\r\n end = text.length;\r\n }\r\n else {\r\n newParagraph = true;\r\n }\r\n\r\n if (end - start > 0) {\r\n let span = text.substring(start, end);\r\n let textRun: TextRun = { value: span };\r\n if (attributes) {\r\n if (attributes.link !== undefined && UrlUtils.isValidUrl(attributes.link))\r\n textRun.url = attributes.link;\r\n\r\n let textStyle = convertFormatAttributesToTextStyle(attributes);\r\n if (textStyle)\r\n textRun.textStyle = textStyle;\r\n }\r\n\r\n paragraph.textRuns.push(textRun);\r\n }\r\n\r\n // If we actually saw a '\\n' then create a new paragraph\r\n if (newParagraph) {\r\n if (paragraph.textRuns.length === 0)\r\n paragraph.textRuns.push({ value: '' });\r\n\r\n paragraphs.push(paragraph);\r\n paragraph = { textRuns: [] };\r\n }\r\n\r\n start = end + 1;\r\n } while (start < text.length);\r\n }\r\n else {\r\n // numeric insert values represent embeds.\r\n debug.assertFail(\"embeds not supported\");\r\n }\r\n }\r\n\r\n if (paragraph.textRuns.length > 0) {\r\n // Quill appears to always insert an extra '\\n' at the end of the text, skip it\r\n if (paragraph.textRuns[0].value.length > 0)\r\n paragraphs.push(paragraph);\r\n }\r\n\r\n return paragraphs;\r\n }\r\n\r\n export function convertParagraphsToHtml(paragraphs: Paragraphs): JQuery {\r\n let $paragraphs: JQuery = $();\r\n\r\n for (let paragraphIndex = 0, len = paragraphs.length; paragraphIndex < len; ++paragraphIndex) {\r\n let paragraphDef = paragraphs[paragraphIndex];\r\n let isParagraphEmpty = true;\r\n\r\n let $paragraph = $('<div>');\r\n\r\n if (paragraphDef.horizontalTextAlignment)\r\n $paragraph.css('text-align', paragraphDef.horizontalTextAlignment);\r\n\r\n for (let textRunIndex = 0, jlen = paragraphDef.textRuns.length; textRunIndex < jlen; ++textRunIndex) {\r\n let textRunDef = paragraphDef.textRuns[textRunIndex];\r\n\r\n let $textRun = $('<span>');\r\n\r\n let styleDef = textRunDef.textStyle;\r\n if (styleDef) {\r\n let css = {};\r\n if (styleDef.fontFamily) {\r\n css['font-family'] = RichText.getCssFontFamily(removeQuotes(styleDef.fontFamily));\r\n }\r\n\r\n if (styleDef.fontSize) {\r\n css['font-size'] = styleDef.fontSize;\r\n }\r\n\r\n if (styleDef.fontStyle) {\r\n css['font-style'] = styleDef.fontStyle;\r\n }\r\n\r\n if (styleDef.fontWeight) {\r\n css['font-weight'] = styleDef.fontWeight;\r\n }\r\n\r\n if (styleDef.textDecoration) {\r\n css['text-decoration'] = styleDef.textDecoration;\r\n }\r\n\r\n $textRun.css(css);\r\n }\r\n\r\n let text = textRunDef.value;\r\n if (!_.isEmpty(text))\r\n isParagraphEmpty = false;\r\n\r\n if (textRunDef.url !== undefined) {\r\n let $link: JQuery;\r\n if (UrlUtils.isValidUrl(textRunDef.url)) {\r\n $link = $('<a>')\r\n .attr('href', textRunDef.url)\r\n .attr('target', '_blank')\r\n .text(text);\r\n }\r\n else {\r\n $link = $('<span>').text(text);\r\n }\r\n\r\n $textRun.append($link);\r\n }\r\n else {\r\n $textRun.text(text);\r\n }\r\n\r\n $paragraph.append($textRun);\r\n }\r\n\r\n // If the entire paragraph is empty we need to make sure we enforce a line-break.\r\n if (isParagraphEmpty)\r\n $paragraph.append($('<br>'));\r\n\r\n $paragraphs = $paragraphs.add($paragraph);\r\n }\r\n\r\n return $paragraphs;\r\n }\r\n\r\n export function convertParagraphsToOps(paragraphs: Paragraphs): quill.Op[] {\r\n let ops: quill.InsertOp[] = [];\r\n\r\n for (let paragraphIndex = 0, len = paragraphs.length; paragraphIndex < len; ++paragraphIndex) {\r\n let paragraphDef = paragraphs[paragraphIndex];\r\n\r\n for (let textRunIndex = 0, jlen = paragraphDef.textRuns.length; textRunIndex < jlen; ++textRunIndex) {\r\n let textRunDef = paragraphDef.textRuns[textRunIndex];\r\n let formats: quill.FormatAttributes = {};\r\n\r\n if (paragraphDef.horizontalTextAlignment)\r\n formats.align = paragraphDef.horizontalTextAlignment;\r\n\r\n let styleDef = textRunDef.textStyle;\r\n if (styleDef) {\r\n if (styleDef.fontFamily) {\r\n formats.font = RichText.getCssFontFamily(removeQuotes(styleDef.fontFamily));\r\n }\r\n\r\n if (styleDef.fontSize) {\r\n formats.size = styleDef.fontSize;\r\n }\r\n\r\n formats.italic = (styleDef.fontStyle === 'italic');\r\n formats.bold = (styleDef.fontWeight === 'bold');\r\n formats.underline = (styleDef.textDecoration === 'underline');\r\n }\r\n\r\n let text = textRunDef.value;\r\n\r\n if (textRunDef.url && UrlUtils.isValidUrl(textRunDef.url))\r\n formats.link = textRunDef.url;\r\n\r\n let op: quill.InsertOp = {\r\n insert: text,\r\n attributes: formats,\r\n };\r\n\r\n ops.push(op);\r\n\r\n // The last text run of the paragraph needs to end with '\\n' to get Quill to handle the text alignment correctly.\r\n if (textRunIndex === (jlen - 1) && !StringExtensions.endsWith(text, '\\n')) {\r\n ops.push({\r\n insert: '\\n',\r\n attributes: formats,\r\n });\r\n }\r\n }\r\n }\r\n\r\n return ops;\r\n }\r\n\r\n function convertFormatAttributesToTextStyle(attributes: quill.FormatAttributes): TextRunStyle {\r\n let style: TextRunStyle = {};\r\n\r\n // NOTE: Align is taken care of when converting to paragraphs.\r\n if (attributes.bold) {\r\n style.fontWeight = 'bold';\r\n }\r\n if (attributes.font) {\r\n // We should always save font names without any quotes.\r\n let font = removeQuotes(attributes.font);\r\n\r\n // Convert built-in font families back into their proper font families (e.g. wf_segoe-ui_normal -> Segoe UI)\r\n font = RichText.getFontFamilyForBuiltInFont(font);\r\n\r\n style.fontFamily = font;\r\n }\r\n if (attributes.italic) {\r\n style.fontStyle = 'italic';\r\n }\r\n if (attributes.size) {\r\n style.fontSize = attributes.size;\r\n }\r\n if (attributes.underline) {\r\n style.textDecoration = 'underline';\r\n }\r\n /*\r\n TODO:\r\n if (attributes.background) {\r\n }\r\n if (attributes.color) {\r\n }\r\n */\r\n\r\n return style;\r\n }\r\n\r\n function removeQuotes(text: string): string {\r\n // If it doesn't start with a quote or contains a comma (multiple fonts), don't remove quotes.\r\n if (!StringExtensions.startsWith(text, \"'\") || StringExtensions.containsIgnoreCase(text, \",\"))\r\n return text;\r\n\r\n debug.assert(StringExtensions.endsWith(text, \"'\"), \"mismatched quotes\");\r\n\r\n return text.slice(1, text.length - 1);\r\n }\r\n }\r\n\r\n export module RichText {\r\n interface ListValueOption {\r\n label: string;\r\n value: string;\r\n }\r\n \r\n /**\r\n * These fonts are embedded using CSS, or are aliases to other fonts.\r\n */\r\n const fontMap = {\r\n 'Segoe (Bold)': Font.Family.bold.css,\r\n 'Segoe UI': Font.Family.regular.css,\r\n 'Segoe UI Light': Font.Family.light.css,\r\n 'Heading': Font.Family.light.css,\r\n 'Body': Font.Family.regular.css\r\n };\r\n\r\n const fonts: ListValueOption[] = [\r\n 'Arial',\r\n 'Arial Black',\r\n 'Arial Unicode MS',\r\n 'Calibri',\r\n 'Cambria',\r\n 'Cambria Math',\r\n 'Candara',\r\n 'Comic Sans MS',\r\n 'Consolas',\r\n 'Constantia',\r\n 'Corbel',\r\n 'Courier New',\r\n 'Georgia',\r\n 'Lucida Sans Unicode',\r\n 'Segoe (Bold)',\r\n 'Segoe UI',\r\n 'Segoe UI Light',\r\n 'Symbol',\r\n 'Tahoma',\r\n 'Times New Roman',\r\n 'Trebuchet MS',\r\n 'Verdana',\r\n 'Wingdings',\r\n\r\n // Fonts with numbers in the name do not work properly on Chrome. We'd need to quote the font names which causes problems with QuillJS.\r\n // TFS6832899\r\n //'Wingdings 2',\r\n //'Wingdings 3',\r\n\r\n ].map((font) => <ListValueOption>{ label: font, value: getCssFontFamily(font) });\r\n export let defaultFont = getCssFontFamily('Segoe UI Light');\r\n\r\n const fontSizes: ListValueOption[] = [\r\n '8', '9', '10', '10.5', '11', '12', '14', '16', '18', '20', '24', '28', '32', '36', '40', '42', '44', '54', '60', '66', '72', '80', '88', '96'\r\n ].map((size) => <ListValueOption> { label: size, value: size + 'px' });\r\n export const defaultFontSize = '14px';\r\n\r\n const textAlignments: ListValueOption[] = [\r\n 'Left',\r\n 'Center',\r\n 'Right',\r\n ].map((alignment) => <ListValueOption>{ label: alignment, value: alignment.toLowerCase() });\r\n\r\n /**\r\n * Given a font family returns the value we should use for the font-family css property.\r\n */\r\n export function getCssFontFamily(font: string): string {\r\n let family: string = fontMap[font];\r\n if (family == null)\r\n family = font;\r\n\r\n return family;\r\n }\r\n\r\n /**\r\n * Convert built-in font families back into their proper font families (e.g. wf_segoe-ui_normal -> Segoe UI)\r\n */\r\n export function getFontFamilyForBuiltInFont(font: string): string {\r\n let fontFamily = _.findKey(fontMap, (value) => value === font || value.indexOf(font) > 0);\r\n return fontFamily || font;\r\n }\r\n\r\n export class QuillWrapper {\r\n private editor: quill.Quill;\r\n private $editorDiv: JQuery;\r\n private $toolbarDiv: JQuery;\r\n private $container: JQuery;\r\n private dependenciesLoaded: JQueryDeferred<void>;\r\n private localizationProvider: jsCommon.IStringResourceProvider;\r\n private host: IVisualHostServices;\r\n private static textChangeThrottle = 200; // ms\r\n\r\n public static loadQuillResources: boolean = true;\r\n\r\n // TODO: How to choose between minified/unminified?\r\n // TODO: Consider loading this from the CDN.\r\n private static quillJsFiles = [powerbi.build + '/externals/quill.min.js'];\r\n private static quillCssFiles = [powerbi.build + '/externals/quill.base.css'];\r\n private QuillPackage: jsCommon.IDependency = {\r\n javaScriptFiles: QuillWrapper.quillJsFiles,\r\n cssFiles: QuillWrapper.quillCssFiles,\r\n };\r\n\r\n public initialized: boolean;\r\n public readOnly: boolean;\r\n public textChanged: (delta, source) => void = (d, s) => { };\r\n \r\n /**\r\n * JavaScript and CSS resources are typically resolved asynchronously.\r\n * This means we potentially defer certain events which typically occur\r\n * synchronously until resources are loaded.\r\n * Setting the global loadQuillResources flag to true will override\r\n * this behavior and cause the wrapper to assume these resources are already loaded\r\n * and not try to load them asynchronously (e.g. for use in unit tests).\r\n */ \r\n constructor(readOnly: boolean, host: IVisualHostServices) {\r\n this.host = host;\r\n this.$container = $('<div>');\r\n\r\n this.readOnly = readOnly;\r\n\r\n this.localizationProvider = {\r\n get: (stringId: string) => this.host.getLocalizedString(stringId),\r\n getOptional: (stringId: string) => this.host.getLocalizedString(stringId)\r\n };\r\n\r\n this.dependenciesLoaded = $.Deferred<void>();\r\n if (QuillWrapper.loadQuillResources) {\r\n // Defer creation of the editor until after resources are loaded.\r\n this.initialized = false;\r\n\r\n // Note that these are called in the order registered so this will always be called before other callbacks.\r\n this.dependenciesLoaded.done(() => {\r\n this.rebuildQuillEditor();\r\n this.initialized = true;\r\n });\r\n\r\n jsCommon.requires(this.QuillPackage, () => this.dependenciesLoaded.resolve());\r\n }\r\n else {\r\n this.rebuildQuillEditor();\r\n this.initialized = true;\r\n this.dependenciesLoaded.resolve();\r\n }\r\n }\r\n\r\n public addModule(name: any, options: any): any {\r\n if (this.editor)\r\n return this.editor.addModule(name, options);\r\n }\r\n\r\n public getElement(): JQuery {\r\n return this.$container;\r\n }\r\n\r\n public getContents(): quill.Delta {\r\n if (this.initialized)\r\n return this.editor.getContents();\r\n }\r\n\r\n public setContents(contents: quill.Delta | quill.Op[]): void {\r\n // If we haven't loaded the editor yet, defer this call until we do\r\n // TODO: prevent these from stacking up?\r\n if (!this.initialized) {\r\n this.dependenciesLoaded.done(() => this.setContents(contents));\r\n return;\r\n }\r\n\r\n this.editor.setHTML('', 'api'); // Clear contents\r\n if (contents)\r\n this.editor.setContents(contents, 'api');\r\n }\r\n\r\n public resize(viewport: IViewport): void {\r\n this.$container.width(viewport.width);\r\n this.$container.height(viewport.height);\r\n }\r\n\r\n public setReadOnly(readOnly: boolean): void {\r\n let readOnlyChanged = readOnly !== this.readOnly;\r\n this.readOnly = readOnly;\r\n\r\n if (this.initialized && readOnlyChanged) {\r\n this.rebuildQuillEditor();\r\n }\r\n }\r\n\r\n public setSelection(start: number, end: number): void {\r\n if (this.editor)\r\n this.editor.setSelection(start, end, 'api');\r\n }\r\n\r\n public getSelection(): quill.Range {\r\n if (this.editor)\r\n return this.editor.getSelection();\r\n }\r\n\r\n public focus(): void {\r\n if (!this.editor)\r\n return;\r\n\r\n if ($(document.activeElement).closest(this.$container).length === 0)\r\n this.editor.focus();\r\n }\r\n\r\n public destroy(): void {\r\n this.host.setToolbar(null);\r\n this.$container.remove();\r\n this.$container = null;\r\n this.$toolbarDiv = null;\r\n this.$editorDiv = null;\r\n this.editor = null;\r\n }\r\n\r\n public getSelectionAtCursor(): quill.Range {\r\n let text = this.getTextWithoutTrailingBreak();\r\n\r\n // Ensure editor has focus before selection interactions\r\n this.editor.focus();\r\n \r\n let selection = this.getSelection();\r\n if (selection && selection.start === selection.end) {\r\n return jsCommon.WordBreaker.find(selection.start, text);\r\n }\r\n\r\n return selection;\r\n }\r\n\r\n public getWord() {\r\n let selection = this.getSelectionAtCursor();\r\n return this.getTextWithoutTrailingBreak().slice(selection.start, selection.end);\r\n }\r\n\r\n public insertLinkAtCursor(link: string, index: number): number {\r\n let endIndex = index + link.length;\r\n \r\n this.editor.insertText(index, link, 'api');\r\n this.editor.formatText(index, endIndex, 'link', link, 'api');\r\n this.setSelection(index, endIndex);\r\n\r\n this.onTextChanged(null, null);\r\n\r\n return endIndex;\r\n }\r\n\r\n public getEditorContainer(): JQuery {\r\n if (this.editor)\r\n return $(this.editor.container);\r\n };\r\n\r\n private getTextWithoutTrailingBreak(): string {\r\n return this.editor.getText().slice(0, -1);\r\n }\r\n\r\n private rebuildQuillEditor(): void {\r\n // Preserve contents if we already have an editor.\r\n let contents: quill.Delta = null;\r\n if (this.editor) {\r\n this.editor.removeAllListeners();\r\n contents = this.editor.getContents();\r\n }\r\n\r\n this.$container.empty();\r\n\r\n // Prevent parent elements from handling keyboard shortcuts (e.g. ctrl+a) that have special meaning for textboxes.\r\n // Quill will also capture and prevent bubbling of some keyboard shortcuts, such as ctrl+c, ctrl+b, etc.\r\n this.$container.keydown((e) => {\r\n let which = e.which;\r\n\r\n if (e.ctrlKey && KeyUtils.isCtrlDefaultKey(which))\r\n e.stopPropagation();\r\n\r\n if (KeyUtils.isArrowKey(which) || KeyUtils.isNudgeModifierKey(which))\r\n e.stopPropagation();\r\n });\r\n\r\n let $editorDiv = this.$editorDiv = $('<div>');\r\n\r\n // HACK: Quill does not apply the correct default styling if you clear all the content and add new content.\r\n $editorDiv.css('font-family', defaultFont);\r\n $editorDiv.css('font-size', defaultFontSize);\r\n\r\n let configs = {\r\n readOnly: this.readOnly,\r\n formats: ['bold', 'italic', 'underline', 'font', 'size', 'link', 'align', /* TODO: 'color', 'background' */],\r\n styles: false,\r\n };\r\n this.editor = new Quill($editorDiv.get(0), configs);\r\n\r\n // If not readonly we add a toolbar and disable drag/resize\r\n if (!this.readOnly) {\r\n let $toolbarDiv = this.$toolbarDiv;\r\n if (!$toolbarDiv) {\r\n this.$toolbarDiv = $toolbarDiv = Toolbar.buildToolbar(this, this.localizationProvider);\r\n }\r\n\r\n $toolbarDiv.addClass('unselectable');\r\n this.host.setToolbar($toolbarDiv);\r\n this.editor.addModule('toolbar', { container: $toolbarDiv.get(0) });\r\n\r\n // Disable this so we can select text in the editor.\r\n $editorDiv.attr('drag-resize-disabled', 'true');\r\n }\r\n\r\n this.$container.append($editorDiv);\r\n\r\n if (contents)\r\n this.setContents(contents);\r\n\r\n // Throttle text-changed events to not more frequent than once per 200ms\r\n let textChangeThrottler = new jsCommon.ThrottleUtility(QuillWrapper.textChangeThrottle);\r\n this.editor.on('text-change', (delta, source) => {\r\n if (source !== 'api')\r\n textChangeThrottler.run(() => this.onTextChanged(delta, source));\r\n });\r\n\r\n /*\r\n Webkit browsers have a bug with regard to focus on div elements\r\n with the contenteditable attribute:\r\n\r\n https://bugs.webkit.org/show_bug.cgi?id=38696\r\n\r\n When we blur our rich text box editor the focus remains with the selection\r\n instead of the focused element. This allows the user to continue typing as\r\n if focus remains within the RichTextbox.\r\n\r\n To fix this issue we add an event listener to the contenteditable div\r\n which listens for the 'blur' event and will properly blur our quill\r\n editor as well.\r\n\r\n http://quilljs.com/docs/api/#quillprototypesetselection\r\n\r\n Verified in Chrome 43.0.2357.130 m\r\n\r\n In IE10+ the setSelection method explicitly sets focus to the body which\r\n causes a bug where the user must click twice when attempting to interact\r\n with a <select> element. To prevent this issue we explicitly do not call\r\n setSelection to blur if the user is changing focus to a <select> element.\r\n This issue is also present for link tooltips from the Quill module which\r\n will cause a blur onto the tooltip.\r\n */\r\n this.editor.root.addEventListener('blur', (event) => {\r\n let target: HTMLElement = <HTMLElement>(event.relatedTarget || document.activeElement);\r\n\r\n // The browser will handle moving the cursor and setting focus properly for these types of elements.\r\n if (target &&\r\n target.tagName === 'SELECT' || target.tagName === 'INPUT' || target.getAttribute('contentEditable')) {\r\n return;\r\n }\r\n\r\n this.setSelection(null, null);\r\n }, false);\r\n }\r\n\r\n private onTextChanged(delta, source): void {\r\n this.textChanged(delta, source);\r\n }\r\n }\r\n\r\n module Toolbar {\r\n const DefaultLinkInputValue = 'http://';\r\n\r\n export const selectors = {\r\n linkTooltip: createClassAndSelector('ql-link-tooltip'),\r\n toolbarUrlInput: createClassAndSelector('toolbar-url-input'),\r\n };\r\n\r\n export function buildToolbar(quillWrapper: QuillWrapper, localizationProvider: jsCommon.IStringResourceProvider) {\r\n // Module for adding custom hyperlinks\r\n let linkTooltipTemplate = buildToolbarLinkInputTemplate(localizationProvider);\r\n quillWrapper.addModule('link-tooltip', { template: linkTooltipTemplate });\r\n\r\n let toolbarLinkInput: JQuery = buildToolbarLinkInput(quillWrapper, getTooltip('Link', localizationProvider), localizationProvider.get('RichTextbox_Link_DefaultText'));\r\n\r\n let fontPicker = picker(getTooltip('Font', localizationProvider), fonts, 'font', defaultFont,\r\n // Show the fonts in their own font face.\r\n ($option, option) => { $option.css('font-family', option.value); return $option; }\r\n );\r\n\r\n let $container = div()\r\n .addClass('toolbar ql-toolbar')\r\n .append(\r\n formatGroup()\r\n .append(label(localizationProvider.get('RichTextbox_Font_Label')))\r\n .append(fontPicker)\r\n .append(picker(getTooltip('Size', localizationProvider), fontSizes, 'size', defaultFontSize))\r\n )\r\n .append(\r\n formatGroup()\r\n .append(formatButton(getTooltip('Bold', localizationProvider), 'bold'))\r\n .append(formatButton(getTooltip('Italic', localizationProvider), 'italic'))\r\n .append(formatButton(getTooltip('Underline', localizationProvider), 'underline'))\r\n )\r\n .append(\r\n formatGroup()\r\n .append(toggleGroup('Text Alignment', textAlignments, 'align', 'Left', localizationProvider))\r\n )\r\n .append(toolbarLinkInput);\r\n\r\n // Prevent mousedown from triggering subsequent blur on editor\r\n $container.on('mousedown', (event) => {\r\n let target = <HTMLElement>(event.target || document.activeElement);\r\n if (target.tagName !== 'INPUT' && target.tagName !== 'SELECT')\r\n event.preventDefault();\r\n });\r\n\r\n return $container;\r\n }\r\n\r\n export function setSelectValue($select: JQuery, value: any): void {\r\n $select.val(value);\r\n // NOTE: The 'change' event is not raised when the value of the SELECT element is changed programatically,\r\n // and Quill uses it's own, non-JQuery, method to hook up to the 'change' event, therefore, we need to dispatch\r\n // this event manually on the SELECT element.\r\n let evt = document.createEvent('UIEvent');\r\n evt.initUIEvent('change', false, false, null, 0);\r\n $select.get(0).dispatchEvent(evt);\r\n }\r\n\r\n function linkTooltipTemplateGenerator(removeText: string, doneText: string): JQuery {\r\n return $(`\r\n <a href=\"#\" class=\"url\" target=\"_blank\"></a>\r\n <input class=\"input\" type=\"text\">\r\n <span class=\"bar\">&nbsp;|&nbsp;</span>\r\n <a class=\"change\"></a>\r\n <a class=\"remove\">${removeText}</a>\r\n <a class=\"done\">${doneText}</a>\r\n `);\r\n };\r\n\r\n function buildToolbarLinkInputTemplate(localizationProvider: jsCommon.IStringResourceProvider): string {\r\n let template: JQuery = div();\r\n let doneText = localizationProvider.get('RichTextbox_Link_Done');\r\n let removeText = localizationProvider.get('RichTextbox_Link_Remove');\r\n\r\n template.append(linkTooltipTemplateGenerator(removeText, doneText));\r\n\r\n return template.html();\r\n }\r\n\r\n function formatGroup(): JQuery {\r\n return span()\r\n .addClass('ql-format-group')\r\n .attr('drag-resize-disabled', 'true');\r\n }\r\n\r\n function label(text: string): JQuery {\r\n return $('<label>').text(text);\r\n }\r\n\r\n function div(): JQuery {\r\n return $('<div>');\r\n }\r\n\r\n function span(): JQuery {\r\n return $('<span>');\r\n }\r\n\r\n function toggleGroup(title: string, list: ListValueOption[], format: string, defaultValue: string, localizationProvider: jsCommon.IStringResourceProvider): JQuery {\r\n let tooltip = getTooltip(title, localizationProvider);\r\n let $group = span()\r\n .attr('title', tooltip)\r\n .addClass('ql-toggle-group');\r\n\r\n // Hidden selector that Quill will use to hook up change listeners.\r\n let $select = selector(tooltip, list, defaultValue)\r\n .addClass('ql-picker ql-' + format)\r\n .css('display', 'none');\r\n\r\n let $buttons = list.map((option) => {\r\n let $button = formatButton(getTooltip(option.label, localizationProvider), 'align' + option.value)\r\n .attr('data-value', option.value)\r\n .click((e) => setSelectValue($select, option.value));\r\n return $button;\r\n });\r\n\r\n // Quill will change the value of the selector when the text selection changes, so we need to set the state of the buttons to match.\r\n $select.change((e) => {\r\n let newValue = $select.val();\r\n for (let i = 0; i < $buttons.length; i++) {\r\n $buttons[i].toggleClass('ql-active', $buttons[i].attr('data-value') === newValue);\r\n }\r\n });\r\n\r\n $group.append($select);\r\n $group.append($buttons);\r\n\r\n return $group;\r\n }\r\n\r\n function picker(tooltip: string, list: ListValueOption[], format: string, defaultValue: string, optionModifier?: (JQuery, ListValueOption) => JQuery): JQuery {\r\n let $selector = selector(tooltip, list, defaultValue, optionModifier)\r\n .addClass('ql-picker ql-' + format);\r\n\r\n return $selector;\r\n }\r\n\r\n function selector(tooltip: string, list: ListValueOption[], defaultValue?: string, optionModifier?: (JQuery, ListValueOption) => JQuery): JQuery {\r\n let $selector = $('<select>')\r\n .attr('title', tooltip);\r\n\r\n for (let i = 0; i < list.length; i++) {\r\n let option = list[i];\r\n let $option = $('<option>')\r\n .attr('value', option.value)\r\n .text(option.label);\r\n\r\n if (option.value === defaultValue)\r\n $option.attr('selected', 'selected');\r\n\r\n if (optionModifier !== undefined)\r\n $option = optionModifier($option, option);\r\n\r\n $selector.append($option);\r\n }\r\n\r\n return $selector;\r\n }\r\n\r\n function formatButton(tooltip?: string, format?: string) {\r\n let $button = span()\r\n .addClass('ql-format-button');\r\n\r\n if (tooltip != null)\r\n $button.attr('title', tooltip);\r\n\r\n if (format != null) {\r\n $button.addClass('ql-' + format);\r\n $button.addClass('powervisuals-glyph ' + format);\r\n }\r\n\r\n return $button;\r\n }\r\n\r\n function getTooltip(name: string, localizationProvider: jsCommon.IStringResourceProvider): string {\r\n return localizationProvider.get('RichTextbox_' + name + '_ToolTip');\r\n }\r\n\r\n function clearLinkInput(linkTooltip: JQuery): void {\r\n linkTooltip.removeClass('editing');\r\n linkTooltip.removeClass('blank-editing');\r\n linkTooltip.find('.input').val(DefaultLinkInputValue);\r\n }\r\n\r\n function buildToolbarLinkInput(quillWrapper: QuillWrapper, buttonTooltip: string, defaultLinkText: string): JQuery {\r\n // Pull out link tooltip\r\n let linkTooltip = quillWrapper.getEditorContainer().find(Toolbar.selectors.linkTooltip.selector);\r\n\r\n // Append link tooltip to a new toolbar format group\r\n let toolbarLinkInput: JQuery = formatGroup()\r\n .addClass(Toolbar.selectors.toolbarUrlInput.class)\r\n .append(formatButton(buttonTooltip, 'link').append('<div>'))\r\n .append(linkTooltip);\r\n\r\n // Special case for blank selection (no text near cursor) when enter key or done button clicked\r\n toolbarLinkInput.on('keydown mousedown', (event: JQueryEventObject) => {\r\n if (event.keyCode === jsCommon.DOMConstants.enterKeyCode || (<HTMLElement>event.target).classList.contains('done')) {\r\n if (!linkTooltip.hasClass('blank-editing'))\r\n return true;\r\n\r\n // Only perform these steps if tooltip was not in editing mode (special case for blank)\r\n let link = toolbarLinkInput.find('.input').val();\r\n let selection = quillWrapper.getSelectionAtCursor();\r\n let word = quillWrapper.getWord();\r\n if (!word) {\r\n // Insert the input text as a link\r\n let endCursor = quillWrapper.insertLinkAtCursor(link, selection.start);\r\n clearLinkInput(linkTooltip);\r\n quillWrapper.setSelection(endCursor, endCursor);\r\n return false;\r\n }\r\n }\r\n });\r\n\r\n toolbarLinkInput.find('.input').blur((event: JQueryEventObject) => {\r\n let blurTarget = event.relatedTarget;\r\n // Remove editing class from insert link tooltip (to hide via CSS)\r\n // only when we are not blurring to the 'done' button (tab from input field)\r\n if (blurTarget === null || blurTarget && !blurTarget.classList.contains('done'))\r\n clearLinkInput(linkTooltip);\r\n });\r\n\r\n toolbarLinkInput.find('.ql-link div')\r\n .click((event: JQueryEventObject) => {\r\n // Handle click on button before Quill removes link (default behavior)\r\n let target = (<HTMLElement>event.target).parentElement;\r\n if (target && target.classList.contains('ql-active')) {\r\n toolbarLinkInput.find('.change')[0].click();\r\n return false;\r\n }\r\n\r\n // If blank selection (no text near cursor), special case for link button\r\n let word = quillWrapper.getWord();\r\n if (!word) {\r\n linkTooltip.addClass('editing blank-editing');\r\n let inputElem = (<HTMLInputElement>toolbarLinkInput.find('.input').get(0));\r\n inputElem.value = DefaultLinkInputValue;\r\n inputElem.selectionStart = inputElem.selectionEnd = DefaultLinkInputValue.length;\r\n inputElem.focus();\r\n return false;\r\n }\r\n })\r\n .mousedown((event: JQueryEventObject) => {\r\n // Properly set selection before we handle the click\r\n let linkButton = (<HTMLElement>event.target).parentElement;\r\n if (linkButton && !linkButton.classList.contains('ql-active')) {\r\n let selection = quillWrapper.getSelectionAtCursor();\r\n quillWrapper.setSelection(selection.start, selection.end);\r\n }\r\n });\r\n\r\n return toolbarLinkInput;\r\n }\r\n }\r\n }\r\n}\n\n\n/** WEBPACK FOOTER **\n ** ./src/Clients/Visuals/visuals/textbox.ts\n **/","/*\r\n* Power BI Visualizations\r\n*\r\n* Copyright (c) Microsoft Corporation\r\n* All rights reserved. \r\n* MIT License\r\n*\r\n* Permission is hereby granted, free of charge, to any person obtaining a copy\r\n* of this software and associated documentation files (the \"\"Software\"\"), to deal\r\n* in the Software without restriction, including without limitation the rights\r\n* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\r\n* copies of the Software, and to permit persons to whom the Software is\r\n* furnished to do so, subject to the following conditions:\r\n* \r\n* The above copyright notice and this permission notice shall be included in \r\n* all copies or substantial portions of the Software.\r\n* \r\n* THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR \r\n* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, \r\n* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE \r\n* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER \r\n* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\r\n* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\r\n* THE SOFTWARE.\r\n*/\r\n\r\n/// <reference path=\"../_references.ts\"/>\r\n\r\nmodule powerbi.visuals {\r\nimport SelectionManager = utility.SelectionManager;\r\n\r\nexport const cheerMeterProps = {\r\n dataPoint: {\r\n defaultColor: <DataViewObjectPropertyIdentifier>{\r\n objectName: 'dataPoint',\r\n propertyName: 'defaultColor'\r\n },\r\n fill: <DataViewObjectPropertyIdentifier>{\r\n objectName: 'dataPoint',\r\n propertyName: 'fill'\r\n },\r\n },\r\n };\r\n\r\n export interface TeamData {\r\n name: string;\r\n value: number;\r\n color: string;\r\n identity: SelectionId;\r\n }\r\n\r\n export interface CheerData {\r\n teamA: TeamData;\r\n teamB: TeamData;\r\n background: string;\r\n }\r\n\r\n interface CheerLayout {\r\n x1: number;\r\n x2: number;\r\n y1: number;\r\n y2: number;\r\n fontSize: string;\r\n }\r\n\r\n export class CheerMeter implements IVisual {\r\n public static capabilities: VisualCapabilities = {\r\n dataRoles: [\r\n {\r\n displayName: 'Category',\r\n name: 'Category',\r\n kind: powerbi.VisualDataRoleKind.Grouping,\r\n },\r\n {\r\n displayName: 'Noise Measure',\r\n name: 'Y',\r\n kind: powerbi.VisualDataRoleKind.Measure,\r\n },\r\n ],\r\n dataViewMappings: [{\r\n categorical: {\r\n categories: {\r\n for: { in: 'Category' },\r\n },\r\n values: {\r\n select: [{ bind: { to: 'Y' } }]\r\n },\r\n },\r\n }],\r\n objects: {\r\n dataPoint: {\r\n displayName: data.createDisplayNameGetter('Visual_DataPoint'),\r\n description: data.createDisplayNameGetter('Visual_DataPointDescription'),\r\n properties: {\r\n fill: {\r\n displayName: data.createDisplayNameGetter('Visual_Fill'),\r\n type: { fill: { solid: { color: true } } }\r\n },\r\n width: {\r\n displayName: '',\r\n type: { numeric: true }\r\n }\r\n }\r\n },\r\n general: {\r\n displayName: 'General',\r\n properties: {\r\n fill: {\r\n displayName: 'Background color',\r\n type: { fill: { solid: { color: true } } }\r\n },\r\n\r\n }\r\n }\r\n }\r\n };\r\n\r\n private static DefaultFontFamily = 'cursive';\r\n private static DefaultFontColor = 'rgb(165, 172, 175)';\r\n private static DefaultBackgroundColor = '#243C18';\r\n private static PaddingBetweenText = 15;\r\n\r\n private textOne: D3.Selection;\r\n private textTwo: D3.Selection;\r\n private svg: D3.Selection;\r\n private isFirstTime: boolean = true;\r\n private data: CheerData;\r\n private selectionManager: SelectionManager;\r\n\r\n public static converter(dataView: DataView): CheerData {\r\n if (!dataView.categorical || !dataView.categorical.categories) return null;\r\n let cat = dataView.categorical.categories[0];\r\n if (!cat) return null;\r\n let catValues = cat.values;\r\n if (!catValues || _.isEmpty(dataView.categorical.values)) return null;\r\n let values = dataView.categorical.values[0].values;\r\n let objects = dataView.categorical.categories[0].objects;\r\n let object1 = objects && objects.length > 0 ? objects[0] : undefined;\r\n let object2 = objects && objects.length > 1 ? objects[1] : undefined;\r\n let metadataObjects = dataView.metadata.objects;\r\n let backgroundColor = CheerMeter.DefaultBackgroundColor;\r\n if (metadataObjects) {\r\n let general = metadataObjects['general'];\r\n if (general) {\r\n let fill = <Fill>general['fill'];\r\n if (fill) {\r\n backgroundColor = fill.solid.color;\r\n }\r\n }\r\n }\r\n\r\n let color1 = DataViewObjects.getFillColor(\r\n object1,\r\n cheerMeterProps.dataPoint.fill,\r\n CheerMeter.DefaultFontColor);\r\n\r\n let color2 = DataViewObjects.getFillColor(\r\n object2,\r\n cheerMeterProps.dataPoint.fill,\r\n CheerMeter.DefaultFontColor);\r\n\r\n let idn1 = SelectionIdBuilder.builder()\r\n .withCategory(cat, 0)\r\n .createSelectionId();\r\n let idn2 = SelectionIdBuilder.builder()\r\n .withCategory(cat, 1)\r\n .createSelectionId();\r\n\r\n let data = {\r\n teamA: {\r\n name: catValues[0],\r\n value: values[0],\r\n color: color1,\r\n identity: idn1\r\n },\r\n teamB: {\r\n name: catValues[1],\r\n value: values[1],\r\n color: color2,\r\n identity: idn2\r\n },\r\n background: backgroundColor\r\n };\r\n\r\n return data;\r\n }\r\n\r\n public init(options: VisualInitOptions): void {\r\n this.selectionManager = new SelectionManager({ hostServices: options.host });\r\n let svg = this.svg = d3.select(options.element.get(0)).append('svg');\r\n\r\n this.textOne = svg.append('text')\r\n .style('font-family', CheerMeter.DefaultFontFamily);\r\n\r\n this.textTwo = svg.append('text')\r\n .style('font-family', CheerMeter.DefaultFontFamily);\r\n }\r\n\r\n public update(options: VisualUpdateOptions) {\r\n if (!options.dataViews[0]) { return; }\r\n let data = this.data = CheerMeter.converter(options.dataViews[0]);\r\n if (!data) return;\r\n let duration = options.suppressAnimations ? 0 : AnimatorCommon.MinervaAnimationDuration;\r\n this.draw(data, duration, options.viewport);\r\n }\r\n\r\n private getRecomendedFontProperties(text1: string, text2: string, parentViewport: IViewport): TextProperties {\r\n let textProperties: TextProperties = {\r\n fontSize: '',\r\n fontFamily: CheerMeter.DefaultFontFamily,\r\n text: text1 + text2\r\n };\r\n\r\n let min = 1;\r\n let max = 1000;\r\n let i;\r\n let maxWidth = parentViewport.width;\r\n let width = 0;\r\n\r\n while (min <= max) {\r\n i = (min + max) / 2 | 0;\r\n\r\n textProperties.fontSize = i + 'px';\r\n width = TextMeasurementService.measureSvgTextWidth(textProperties);\r\n\r\n if (maxWidth > width)\r\n min = i + 1;\r\n else if (maxWidth < width)\r\n max = i - 1;\r\n else\r\n break;\r\n }\r\n\r\n textProperties.fontSize = i + 'px';\r\n width = TextMeasurementService.measureSvgTextWidth(textProperties);\r\n if (width > maxWidth) {\r\n i--;\r\n textProperties.fontSize = i + 'px';\r\n }\r\n\r\n return textProperties;\r\n }\r\n\r\n private calculateLayout(data: CheerData, viewport: IViewport): CheerLayout {\r\n let text1 = data.teamA.name;\r\n let text2 = data.teamB.name;\r\n\r\n let avaliableViewport: IViewport = {\r\n height: viewport.height,\r\n width: viewport.width - CheerMeter.PaddingBetweenText\r\n };\r\n let recomendedFontProperties = this.getRecomendedFontProperties(text1, text2, avaliableViewport);\r\n\r\n recomendedFontProperties.text = text1;\r\n let width1 = TextMeasurementService.measureSvgTextWidth(recomendedFontProperties) | 0;\r\n\r\n recomendedFontProperties.text = text2;\r\n let width2 = TextMeasurementService.measureSvgTextWidth(recomendedFontProperties) | 0;\r\n\r\n let padding = ((viewport.width - width1 - width2 - CheerMeter.PaddingBetweenText) / 2) | 0;\r\n\r\n recomendedFontProperties.text = text1 + text2;\r\n let offsetHeight = (TextMeasurementService.measureSvgTextHeight(recomendedFontProperties)) | 0;\r\n\r\n let max = data.teamA.value + data.teamB.value;\r\n let availableHeight = viewport.height - offsetHeight;\r\n let y1 = (((max - data.teamA.value) / max) * availableHeight + offsetHeight / 2) | 0;\r\n let y2 = (((max - data.teamB.value) / max) * availableHeight + offsetHeight / 2) | 0;\r\n\r\n return {\r\n x1: padding,\r\n x2: padding + width1 + CheerMeter.PaddingBetweenText,\r\n y1: y1,\r\n y2: y2,\r\n fontSize: recomendedFontProperties.fontSize\r\n };\r\n }\r\n\r\n private ensureStartState(layout: CheerLayout, viewport: IViewport) {\r\n if (this.isFirstTime) {\r\n this.isFirstTime = false;\r\n let startY = viewport.height / 2;\r\n this.textOne.attr(\r\n {\r\n 'x': layout.x1,\r\n 'y': startY\r\n });\r\n\r\n this.textTwo.attr(\r\n {\r\n 'x': layout.x2,\r\n 'y': startY\r\n });\r\n }\r\n }\r\n\r\n private clearSelection() {\r\n this.selectionManager.clear().then(() => {\r\n this.clearSelectionUI();\r\n });\r\n }\r\n\r\n private clearSelectionUI() {\r\n this.textOne.style('stroke', '#FFF').style('stroke-width', 0);\r\n this.textTwo.style('stroke', '#FFF').style('stroke-width', 0);\r\n }\r\n\r\n private updateSelectionUI(ids: SelectionId[]) {\r\n this.textOne.style('stroke', '#FFF').style('stroke-width', SelectionManager.containsSelection(ids, this.data.teamA.identity) ? '2px' : '0px');\r\n this.textTwo.style('stroke', '#FFF').style('stroke-width', SelectionManager.containsSelection(ids, this.data.teamB.identity) ? '2px' : '0px');\r\n }\r\n\r\n private draw(data: CheerData, duration: number, viewport: IViewport) {\r\n let easeName = 'back';\r\n let textOne = this.textOne;\r\n let textTwo = this.textTwo;\r\n\r\n this.svg\r\n .attr({\r\n 'height': viewport.height,\r\n 'width': viewport.width\r\n })\r\n .on('click', () => {\r\n this.clearSelection();\r\n })\r\n .style('background-color', data.background);\r\n\r\n let layout = this.calculateLayout(data, viewport);\r\n\r\n this.ensureStartState(layout, viewport);\r\n\r\n textOne\r\n .style('font-size', layout.fontSize)\r\n .style('fill', data.teamA.color)\r\n .on('click', () => {\r\n this.selectionManager.select(data.teamA.identity, d3.event.ctrlKey).then((ids) => {\r\n this.updateSelectionUI(ids);\r\n });\r\n d3.event.stopPropagation();\r\n })\r\n .text(data.teamA.name);\r\n\r\n textTwo\r\n .style('font-size', layout.fontSize)\r\n .style('fill', data.teamB.color)\r\n .on('click', () => {\r\n this.selectionManager.select(data.teamB.identity, d3.event.ctrlKey).then((ids) => {\r\n this.updateSelectionUI(ids);\r\n });\r\n d3.event.stopPropagation();\r\n })\r\n .text(data.teamB.name);\r\n\r\n textOne.transition()\r\n .duration(duration)\r\n .ease(easeName)\r\n .attr({\r\n y: layout.y1,\r\n x: layout.x1\r\n });\r\n\r\n textTwo.transition()\r\n .duration(duration)\r\n .ease(easeName)\r\n .attr({\r\n y: layout.y2,\r\n x: layout.x2\r\n });\r\n }\r\n\r\n public destroy(): void {\r\n this.svg = null;\r\n this.textOne = this.textTwo = null;\r\n }\r\n\r\n public enumerateObjectInstances(options: EnumerateVisualObjectInstancesOptions): VisualObjectInstance[] {\r\n let instances: VisualObjectInstance[] = [];\r\n let data = this.data;\r\n switch (options.objectName) {\r\n case 'dataPoint':\r\n if (data) {\r\n let teams = [data.teamA, data.teamB];\r\n\r\n for (let i = 0; i < teams.length; i++) {\r\n let slice = teams[i];\r\n\r\n let color = slice.color;\r\n let selector = slice.identity;\r\n\r\n let dataPointInstance: VisualObjectInstance = {\r\n objectName: 'dataPoint',\r\n displayName: slice.name,\r\n selector: selector,\r\n properties: {\r\n fill: { solid: { color: color } }\r\n },\r\n };\r\n\r\n instances.push(dataPointInstance);\r\n };\r\n }\r\n break;\r\n case 'general':\r\n let general: VisualObjectInstance = {\r\n objectName: 'general',\r\n displayName: 'General',\r\n selector: null,\r\n properties: {\r\n fill: { solid: { color: data ? data.background : CheerMeter.DefaultBackgroundColor } }\r\n }\r\n };\r\n instances.push(general);\r\n break;\r\n }\r\n\r\n return instances;\r\n }\r\n }\r\n}\n\n\n/** WEBPACK FOOTER **\n ** ./src/Clients/Visuals/visuals/sampleVisual.ts\n **/","/*\r\n * Power BI Visualizations\r\n *\r\n * Copyright (c) Microsoft Corporation\r\n * All rights reserved.\r\n * MIT License\r\n *\r\n * Permission is hereby granted, free of charge, to any person obtaining a copy\r\n * of this software and associated documentation files (the \"\"Software\"\"), to deal\r\n * in the Software without restriction, including without limitation the rights\r\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\r\n * copies of the Software, and to permit persons to whom the Software is\r\n * furnished to do so, subject to the following conditions:\r\n *\r\n * The above copyright notice and this permission notice shall be included in\r\n * all copies or substantial portions of the Software.\r\n *\r\n * THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\r\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\r\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\r\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\r\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\r\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\r\n * THE SOFTWARE.\r\n */\r\n\r\n/// <reference path=\"../_references.ts\"/>\r\n\r\nmodule powerbi.visuals {\r\n import ClassAndSelector = jsCommon.CssConstants.ClassAndSelector;\r\n import Color = jsCommon.Color;\r\n import createClassAndSelector = jsCommon.CssConstants.createClassAndSelector;\r\n import PixelConverter = jsCommon.PixelConverter;\r\n import DataRoleHelper = powerbi.data.DataRoleHelper;\r\n import IDataViewCategoricalReader = powerbi.data.IDataViewCategoricalReader;\r\n\r\n export interface ScatterChartConstructorOptions extends CartesianVisualConstructorOptions {\r\n }\r\n\r\n export interface ScatterChartDataPoint extends SelectableDataPoint, TooltipEnabledDataPoint, LabelEnabledDataPoint {\r\n x: any;\r\n y: any;\r\n size: any;\r\n radius: RadiusData;\r\n fill: string;\r\n formattedCategory: jsCommon.Lazy<string>;\r\n fontSize?: number;\r\n }\r\n\r\n export interface ScatterChartDataPointSeries {\r\n identityKey: string;\r\n dataPoints?: ScatterChartDataPoint[];\r\n hasSize?: boolean;\r\n fill?: string;\r\n }\r\n\r\n export interface RadiusData {\r\n sizeMeasure: DataViewValueColumn;\r\n index: number;\r\n }\r\n\r\n export interface DataRange {\r\n minRange: number;\r\n maxRange: number;\r\n delta: number;\r\n }\r\n\r\n export interface ScatterChartData extends PlayableChartData, ScatterBehaviorChartData {\r\n xCol: DataViewMetadataColumn;\r\n yCol: DataViewMetadataColumn;\r\n dataPoints: ScatterChartDataPoint[];\r\n dataPointSeries: ScatterChartDataPointSeries[];\r\n legendData: LegendData;\r\n axesLabels: ChartAxesLabels;\r\n size?: DataViewMetadataColumn;\r\n sizeRange: NumberRange;\r\n dataLabelsSettings: PointDataLabelsSettings;\r\n defaultDataPointColor?: string;\r\n showAllDataPoints?: boolean;\r\n hasDynamicSeries?: boolean;\r\n fillPoint?: boolean;\r\n colorBorder?: boolean;\r\n colorByCategory?: boolean;\r\n }\r\n\r\n export interface ScatterChartViewModel {\r\n xAxisProperties: IAxisProperties;\r\n yAxisProperties: IAxisProperties;\r\n viewport: IViewport;\r\n data: ScatterChartData;\r\n drawBubbles: boolean;\r\n isPlay: boolean;\r\n fillMarkers: boolean;\r\n hasSelection: boolean;\r\n animationDuration: number;\r\n animationOptions: AnimationOptions;\r\n easeType: string;\r\n suppressDataPointRendering: boolean;\r\n }\r\n\r\n interface ScatterChartMeasureMetadata {\r\n idx: {\r\n x?: number;\r\n y?: number;\r\n size?: number;\r\n };\r\n cols: {\r\n x?: DataViewMetadataColumn;\r\n y?: DataViewMetadataColumn;\r\n size?: DataViewMetadataColumn;\r\n };\r\n axesLabels: ChartAxesLabels;\r\n }\r\n\r\n interface MouseCoordinates {\r\n x: number;\r\n y: number;\r\n }\r\n\r\n export interface ScatterConverterOptions {\r\n viewport: IViewport;\r\n colors: any;\r\n interactivityService?: any;\r\n categoryAxisProperties?: any;\r\n valueAxisProperties?: any;\r\n }\r\n\r\n interface ScatterObjectProperties {\r\n fillPoint?: boolean;\r\n colorBorder?: boolean;\r\n showAllDataPoints?: boolean;\r\n defaultDataPointColor?: string;\r\n colorByCategory?: boolean;\r\n }\r\n\r\n /** Styles to apply to scatter chart data point marker */\r\n export interface ScatterMarkerStyle {\r\n 'stroke-opacity': number;\r\n stroke: string;\r\n fill: string;\r\n 'fill-opacity': number;\r\n }\r\n\r\n export interface CartesianExtents {\r\n minX: number;\r\n maxX: number;\r\n minY: number;\r\n maxY: number;\r\n }\r\n\r\n export class ScatterChart implements ICartesianVisual {\r\n private static BubbleRadius = 3 * 2;\r\n public static DefaultBubbleOpacity = 0.85;\r\n public static DimmedBubbleOpacity = 0.4;\r\n public static StrokeDarkenColorValue = 255 * 0.25;\r\n //label layout settings\r\n public static dataLabelLayoutStartingOffset: number = 2;\r\n public static dataLabelLayoutOffsetIterationDelta: number = 6;\r\n public static dataLabelLayoutMaximumOffset: number = ScatterChart.dataLabelLayoutStartingOffset + (2 * ScatterChart.dataLabelLayoutOffsetIterationDelta);\r\n // Chart Area and size range values as defined by PV charts\r\n private static AreaOf300By300Chart = 90000;\r\n private static MinSizeRange = 200;\r\n private static MaxSizeRange = 3000;\r\n private static ClassName = 'scatterChart';\r\n // Animated rendering threshold - if more than this number of data points, rendering is grouped by series and not animated\r\n public static NoAnimationThreshold = 1000;\r\n // No render resize threshold - if more than this number of data points, rendering is suppressed during resize\r\n public static NoRenderResizeThreshold = 1000;\r\n\r\n private svg: D3.Selection;\r\n private element: JQuery;\r\n private currentViewport: IViewport;\r\n private style: IVisualStyle;\r\n private data: ScatterChartData;\r\n private dataView: DataView;\r\n private host: IVisualHostServices;\r\n private margin: IMargin;\r\n\r\n private colors: IDataColorPalette;\r\n private options: CartesianVisualInitOptions;\r\n private interactivity: InteractivityOptions;\r\n private cartesianVisualHost: ICartesianVisualHost;\r\n private isMobileChart: boolean;\r\n private interactivityService: IInteractivityService;\r\n private categoryAxisProperties: DataViewObject;\r\n private valueAxisProperties: DataViewObject;\r\n private animator: IGenericAnimator;\r\n private tooltipsEnabled: boolean;\r\n private tooltipBucketEnabled: boolean;\r\n\r\n private xAxisProperties: IAxisProperties;\r\n private yAxisProperties: IAxisProperties;\r\n\r\n private renderer: SvgRenderer;\r\n private playAxis: PlayAxis<ScatterChartData>;\r\n\r\n constructor(options: ScatterChartConstructorOptions) {\r\n if (options) {\r\n this.tooltipsEnabled = options.tooltipsEnabled;\r\n this.tooltipBucketEnabled = options.tooltipBucketEnabled;\r\n this.interactivityService = options.interactivityService;\r\n this.animator = options.animator;\r\n }\r\n\r\n this.renderer = new SvgRenderer();\r\n }\r\n\r\n public init(options: CartesianVisualInitOptions) {\r\n this.options = options;\r\n this.element = options.element;\r\n this.currentViewport = options.viewport;\r\n this.style = options.style;\r\n this.host = options.host;\r\n this.colors = this.style.colorPalette.dataColors;\r\n this.interactivity = options.interactivity;\r\n this.cartesianVisualHost = options.cartesianHost;\r\n this.isMobileChart = options.interactivity && options.interactivity.isInteractiveLegend;\r\n\r\n let svg = this.svg = options.svg;\r\n\r\n // TODO: should we always be adding the playchart class name?\r\n svg.classed(ScatterChart.ClassName + ' ' + PlayChart.ClassName, true);\r\n\r\n this.renderer.init(svg, options.labelsContext, this.isMobileChart, this.tooltipsEnabled);\r\n }\r\n\r\n public static getAdditionalTelemetry(dataView: DataView): any {\r\n let telemetry = {\r\n hasSize: DataRoleHelper.hasRoleInDataView(dataView, 'Size'),\r\n hasPlayAxis: DataRoleHelper.hasRoleInDataView(dataView, 'Play'),\r\n };\r\n\r\n return telemetry;\r\n }\r\n\r\n private static getObjectProperties(dataView: DataView, dataLabelsSettings?: PointDataLabelsSettings): ScatterObjectProperties {\r\n let objects: DataViewObjects;\r\n if (dataView && dataView.metadata && dataView.metadata.objects)\r\n objects = dataView.metadata.objects;\r\n else\r\n objects = {};\r\n\r\n let objectProperties: ScatterObjectProperties = {};\r\n\r\n objectProperties.defaultDataPointColor = DataViewObjects.getFillColor(objects, columnChartProps.dataPoint.defaultColor);\r\n objectProperties.showAllDataPoints = DataViewObjects.getValue<boolean>(objects, columnChartProps.dataPoint.showAllDataPoints, false);\r\n\r\n let labelsObj = <DataLabelObject>objects['categoryLabels'];\r\n if (labelsObj && dataLabelsSettings)\r\n dataLabelUtils.updateLabelSettingsFromLabelsObject(labelsObj, dataLabelsSettings);\r\n\r\n // NOTE: \"fill point\" defaults to on when we have a gradient role.\r\n let hasGradient = dataView && GradientUtils.hasGradientRole(dataView.categorical);\r\n objectProperties.fillPoint = DataViewObjects.getValue(objects, scatterChartProps.fillPoint.show, hasGradient);\r\n\r\n objectProperties.colorBorder = DataViewObjects.getValue(objects, scatterChartProps.colorBorder.show, false);\r\n objectProperties.colorByCategory = DataViewObjects.getValue(objects, scatterChartProps.colorByCategory.show, false);\r\n\r\n return objectProperties;\r\n }\r\n\r\n public static converter(dataView: DataView, options: ScatterConverterOptions, playFrameInfo?: PlayFrameInfo, tooltipsEnabled: boolean = true, tooltipBucketEnabled?: boolean): ScatterChartData {\r\n let reader = powerbi.data.createIDataViewCategoricalReader(dataView);\r\n let categoryValues: any[],\r\n categoryFormatter: IValueFormatter,\r\n categoryObjects: DataViewObjects[],\r\n categoryIdentities: DataViewScopeIdentity[],\r\n categoryQueryName: string;\r\n\r\n let currentViewport = options.viewport;\r\n let colorPalette = options.colors;\r\n let interactivityService = options.interactivityService;\r\n let categoryAxisProperties = options.categoryAxisProperties;\r\n let valueAxisProperties = options.valueAxisProperties;\r\n\r\n let dataViewCategorical: DataViewCategorical = dataView.categorical;\r\n let gradientValueColumn: DataViewValueColumn = GradientUtils.getGradientValueColumn(dataViewCategorical);\r\n\r\n if (dataViewCategorical.categories && dataViewCategorical.categories.length > 0) {\r\n categoryValues = dataViewCategorical.categories[0].values;\r\n categoryFormatter = valueFormatter.create({ format: valueFormatter.getFormatString(dataViewCategorical.categories[0].source, scatterChartProps.general.formatString), value: categoryValues[0], value2: categoryValues[categoryValues.length - 1] });\r\n categoryIdentities = dataViewCategorical.categories[0].identity;\r\n categoryObjects = dataViewCategorical.categories[0].objects;\r\n categoryQueryName = dataViewCategorical.categories[0].source.queryName;\r\n }\r\n else {\r\n categoryValues = [null];\r\n // creating default formatter for null value (to get the right string of empty value from the locale)\r\n categoryFormatter = valueFormatter.createDefaultFormatter(null);\r\n }\r\n\r\n let categories = dataViewCategorical.categories;\r\n let dataValues = dataViewCategorical.values;\r\n let hasDynamicSeries = !!dataValues.source;\r\n let grouped = dataValues.grouped();\r\n let dvSource = dataValues.source;\r\n let scatterMetadata = ScatterChart.getMetadata(grouped, dvSource);\r\n let dataLabelsSettings = dataLabelUtils.getDefaultPointLabelSettings();\r\n let sizeRange = ScatterChart.getSizeRangeForGroups(grouped, scatterMetadata.idx.size);\r\n\r\n let objProps = ScatterChart.getObjectProperties(dataView, dataLabelsSettings);\r\n\r\n let dataPointSeries = ScatterChart.createDataPointSeries(\r\n reader,\r\n dataValues,\r\n scatterMetadata,\r\n categories,\r\n categoryValues,\r\n categoryFormatter,\r\n categoryIdentities,\r\n categoryObjects,\r\n colorPalette,\r\n currentViewport,\r\n hasDynamicSeries,\r\n dataLabelsSettings,\r\n gradientValueColumn,\r\n objProps.defaultDataPointColor,\r\n categoryQueryName,\r\n objProps.colorByCategory,\r\n playFrameInfo,\r\n tooltipsEnabled,\r\n tooltipBucketEnabled);\r\n let dataPoints = _.reduce(dataPointSeries, (a, s) => a.concat(s.dataPoints), []);\r\n\r\n let legendItems = hasDynamicSeries\r\n ? ScatterChart.createSeriesLegend(dataValues, colorPalette, dataValues, valueFormatter.getFormatString(dvSource, scatterChartProps.general.formatString), objProps.defaultDataPointColor)\r\n : [];\r\n\r\n let legendTitle = dataValues && dvSource ? dvSource.displayName : \"\";\r\n if (!legendTitle) {\r\n legendTitle = categories && categories.length > 0 && categories[0].source.displayName ? categories[0].source.displayName : \"\";\r\n }\r\n\r\n if (categoryAxisProperties && categoryAxisProperties[\"showAxisTitle\"] !== null && categoryAxisProperties[\"showAxisTitle\"] === false) {\r\n scatterMetadata.axesLabels.x = null;\r\n }\r\n if (valueAxisProperties && valueAxisProperties[\"showAxisTitle\"] !== null && valueAxisProperties[\"showAxisTitle\"] === false) {\r\n scatterMetadata.axesLabels.y = null;\r\n }\r\n\r\n if (interactivityService) {\r\n interactivityService.applySelectionStateToData(dataPoints);\r\n interactivityService.applySelectionStateToData(legendItems);\r\n }\r\n\r\n return {\r\n xCol: scatterMetadata.cols.x,\r\n yCol: scatterMetadata.cols.y,\r\n dataPoints: dataPoints,\r\n dataPointSeries: dataPointSeries,\r\n legendData: { title: legendTitle, dataPoints: legendItems, grouped: hasDynamicSeries},\r\n axesLabels: scatterMetadata.axesLabels,\r\n size: scatterMetadata.cols.size,\r\n sizeRange: sizeRange,\r\n dataLabelsSettings: dataLabelsSettings,\r\n defaultDataPointColor: objProps.defaultDataPointColor,\r\n hasDynamicSeries: hasDynamicSeries,\r\n showAllDataPoints: objProps.showAllDataPoints,\r\n fillPoint: objProps.fillPoint,\r\n colorBorder: objProps.colorBorder,\r\n colorByCategory: objProps.colorByCategory,\r\n };\r\n }\r\n\r\n private static getSizeRangeForGroups(\r\n dataViewValueGroups: DataViewValueColumnGroup[],\r\n sizeColumnIndex: number): NumberRange {\r\n\r\n let result: NumberRange = {};\r\n if (dataViewValueGroups) {\r\n dataViewValueGroups.forEach((group) => {\r\n let sizeColumn = ScatterChart.getMeasureValue(sizeColumnIndex, group.values);\r\n let currentRange: NumberRange = AxisHelper.getRangeForColumn(sizeColumn);\r\n if (result.min == null || result.min > currentRange.min) {\r\n result.min = currentRange.min;\r\n }\r\n if (result.max == null || result.max < currentRange.max) {\r\n result.max = currentRange.max;\r\n }\r\n });\r\n }\r\n return result;\r\n }\r\n\r\n private static createDataPointSeries(\r\n reader: IDataViewCategoricalReader,\r\n dataValues: DataViewValueColumns,\r\n metadata: ScatterChartMeasureMetadata,\r\n categories: DataViewCategoryColumn[],\r\n categoryValues: any[],\r\n categoryFormatter: IValueFormatter,\r\n categoryIdentities: DataViewScopeIdentity[],\r\n categoryObjects: DataViewObjects[],\r\n colorPalette: IDataColorPalette,\r\n viewport: IViewport,\r\n hasDynamicSeries: boolean,\r\n labelSettings: PointDataLabelsSettings,\r\n gradientValueColumn: DataViewValueColumn,\r\n defaultDataPointColor: string,\r\n categoryQueryName: string,\r\n colorByCategory: boolean,\r\n playFrameInfo: PlayFrameInfo,\r\n tooltipsEnabled: boolean,\r\n tooltipBucketEnabled?: boolean): ScatterChartDataPointSeries[] {\r\n\r\n let hasX = reader.hasValues(\"X\");\r\n let hasY = reader.hasValues(\"Y\");\r\n\r\n if (!hasX && !hasY) {\r\n return [];\r\n }\r\n\r\n let dataPointSeries: ScatterChartDataPointSeries[] = [],\r\n indicies = metadata.idx,\r\n formatStringProp = scatterChartProps.general.formatString,\r\n dataValueSource = dataValues.source,\r\n grouped = dataValues.grouped();\r\n\r\n let colorHelper = new ColorHelper(colorPalette, scatterChartProps.dataPoint.fill, defaultDataPointColor);\r\n\r\n for (let seriesIndex = 0, len = grouped.length; seriesIndex < len; seriesIndex++) {\r\n let grouping = grouped[seriesIndex];\r\n let seriesValues = grouping.values;\r\n let measureX = ScatterChart.getMeasureValue(indicies.x, seriesValues);\r\n let measureY = ScatterChart.getMeasureValue(indicies.y, seriesValues);\r\n let measureSize = ScatterChart.getMeasureValue(indicies.size, seriesValues);\r\n\r\n let seriesColor: string;\r\n if (hasDynamicSeries) {\r\n seriesColor = colorHelper.getColorForSeriesValue(grouping.objects, dataValues.identityFields, grouping.name);\r\n }\r\n else if (!colorByCategory && !categoryObjects) {\r\n // If we have no Size measure then use a blank query name\r\n let measureSource = (measureSize != null)\r\n ? measureSize.source.queryName\r\n : '';\r\n\r\n seriesColor = colorHelper.getColorForMeasure(null, measureSource);\r\n }\r\n\r\n let series: ScatterChartDataPointSeries = {\r\n identityKey: (grouping && grouping.identity && grouping.identity.key) || \"\",\r\n dataPoints: [],\r\n hasSize: !!(measureSize && measureSize.values),\r\n fill: seriesColor,\r\n };\r\n\r\n dataPointSeries.push(series);\r\n\r\n for (let categoryIndex = 0, ilen = categoryValues.length; categoryIndex < ilen; categoryIndex++) {\r\n let categoryValue = categoryValues[categoryIndex];\r\n\r\n // Zero out X and Y if the role doesn't exist, so you still get a set of vertical/horizontal dots\r\n let xVal = hasX ? AxisHelper.normalizeNonFiniteNumber(reader.getValue(\"X\", categoryIndex, seriesIndex)) : 0;\r\n let yVal = hasY ? AxisHelper.normalizeNonFiniteNumber(reader.getValue(\"Y\", categoryIndex, seriesIndex)) : 0;\r\n // Undefined size is handled later if we don't have a size role, so this is fine to just be undefined\r\n let size = AxisHelper.normalizeNonFiniteNumber(reader.getValue(\"Size\", categoryIndex, seriesIndex));\r\n\r\n // Do not render a dot if X or Y are null\r\n if (xVal == null || yVal == null)\r\n continue;\r\n\r\n let color: string;\r\n if (hasDynamicSeries) {\r\n color = colorHelper.getColorForSeriesValue(grouping.objects, dataValues.identityFields, grouping.name);\r\n }\r\n else if (colorByCategory) {\r\n color = colorHelper.getColorForSeriesValue(categoryObjects && categoryObjects[categoryIndex], dataValues.identityFields, categoryValue);\r\n }\r\n else {\r\n // If we have no Size measure then use a blank query name\r\n let measureSource = (measureSize != null)\r\n ? measureSize.source.queryName\r\n : '';\r\n\r\n color = colorHelper.getColorForMeasure(categoryObjects && categoryObjects[categoryIndex], measureSource);\r\n }\r\n\r\n let category = !_.isEmpty(categories) ? categories[0] : null;\r\n let identity = SelectionIdBuilder.builder()\r\n .withCategory(category, categoryIndex)\r\n .withSeries(dataValues, grouping)\r\n .createSelectionId();\r\n\r\n let tooltipInfo: TooltipDataItem[];\r\n\r\n if (tooltipsEnabled) {\r\n tooltipInfo = [];\r\n\r\n if (category) {\r\n tooltipInfo.push({\r\n displayName: category.source.displayName,\r\n value: converterHelper.formatFromMetadataColumn(categoryValue, category.source, formatStringProp),\r\n });\r\n }\r\n\r\n if (hasDynamicSeries) {\r\n // Dynamic series\r\n if ( !category || category.source !== dataValueSource) {\r\n tooltipInfo.push({\r\n displayName: dataValueSource.displayName,\r\n value: converterHelper.formatFromMetadataColumn(grouping.name, dataValueSource, formatStringProp),\r\n });\r\n }\r\n }\r\n\r\n if (measureX && xVal != null) {\r\n tooltipInfo.push({\r\n displayName: measureX.source.displayName,\r\n value: converterHelper.formatFromMetadataColumn(xVal, measureX.source, formatStringProp),\r\n });\r\n }\r\n\r\n if (measureY && yVal != null) {\r\n tooltipInfo.push({\r\n displayName: measureY.source.displayName,\r\n value: converterHelper.formatFromMetadataColumn(yVal, measureY.source, formatStringProp),\r\n });\r\n }\r\n\r\n if (measureSize && measureSize.values[categoryIndex] != null) {\r\n tooltipInfo.push({\r\n displayName: measureSize.source.displayName,\r\n value: converterHelper.formatFromMetadataColumn(measureSize.values[categoryIndex], measureSize.source, formatStringProp),\r\n });\r\n }\r\n\r\n if (gradientValueColumn && gradientValueColumn.values[categoryIndex] != null) {\r\n tooltipInfo.push({\r\n displayName: gradientValueColumn.source.displayName,\r\n value: converterHelper.formatFromMetadataColumn(gradientValueColumn.values[categoryIndex], gradientValueColumn.source, formatStringProp),\r\n });\r\n }\r\n\r\n if (playFrameInfo) {\r\n tooltipInfo.push({\r\n displayName: playFrameInfo.column.displayName,\r\n value: converterHelper.formatFromMetadataColumn(playFrameInfo.label, playFrameInfo.column, formatStringProp),\r\n });\r\n }\r\n\r\n if (tooltipBucketEnabled) {\r\n TooltipBuilder.addTooltipBucketItem(reader, tooltipInfo, categoryIndex, seriesIndex);\r\n }\r\n }\r\n\r\n let dataPoint: ScatterChartDataPoint = {\r\n x: xVal,\r\n y: yVal,\r\n size: size,\r\n radius: { sizeMeasure: measureSize, index: categoryIndex },\r\n fill: color,\r\n formattedCategory: ScatterChart.createLazyFormattedCategory(categoryFormatter, categories != null ? categoryValue : grouping.name),\r\n selected: false,\r\n identity: identity,\r\n tooltipInfo: tooltipInfo,\r\n labelFill: labelSettings.labelColor,\r\n };\r\n\r\n series.dataPoints.push(dataPoint);\r\n }\r\n }\r\n return dataPointSeries;\r\n }\r\n\r\n public static createLazyFormattedCategory(formatter: IValueFormatter, value: string): jsCommon.Lazy<string> {\r\n return new jsCommon.Lazy(() => formatter.format(value));\r\n }\r\n\r\n private static createSeriesLegend(\r\n dataValues: DataViewValueColumns,\r\n colorPalette: IDataColorPalette,\r\n categorical: DataViewValueColumns,\r\n formatString: string,\r\n defaultDataPointColor: string): LegendDataPoint[] {\r\n\r\n let grouped = dataValues.grouped();\r\n let colorHelper = new ColorHelper(colorPalette, scatterChartProps.dataPoint.fill, defaultDataPointColor);\r\n\r\n let legendItems: LegendDataPoint[] = [];\r\n for (let i = 0, len = grouped.length; i < len; i++) {\r\n let grouping = grouped[i];\r\n let color = colorHelper.getColorForSeriesValue(grouping.objects, dataValues.identityFields, grouping.name);\r\n legendItems.push({\r\n color: color,\r\n icon: LegendIcon.Circle,\r\n label: valueFormatter.format(grouping.name, formatString),\r\n identity: grouping.identity ? SelectionId.createWithId(grouping.identity) : SelectionId.createNull(),\r\n selected: false\r\n });\r\n }\r\n\r\n return legendItems;\r\n }\r\n\r\n public static getBubbleRadius(radiusData: RadiusData, sizeRange: NumberRange, viewport: IViewport): number {\r\n let actualSizeDataRange = null;\r\n let bubblePixelAreaSizeRange = null;\r\n let measureSize = radiusData.sizeMeasure;\r\n\r\n if (!measureSize)\r\n return ScatterChart.BubbleRadius;\r\n\r\n let minSize = sizeRange.min ? sizeRange.min : 0;\r\n let maxSize = sizeRange.max ? sizeRange.max : 0;\r\n\r\n let min = Math.min(minSize, 0);\r\n let max = Math.max(maxSize, 0);\r\n actualSizeDataRange = {\r\n minRange: min,\r\n maxRange: max,\r\n delta: max - min\r\n };\r\n\r\n bubblePixelAreaSizeRange = ScatterChart.getBubblePixelAreaSizeRange(viewport, ScatterChart.MinSizeRange, ScatterChart.MaxSizeRange);\r\n\r\n if (measureSize.values) {\r\n let sizeValue = measureSize.values[radiusData.index];\r\n if (sizeValue != null) {\r\n return ScatterChart.projectSizeToPixels(sizeValue, actualSizeDataRange, bubblePixelAreaSizeRange) / 2;\r\n }\r\n }\r\n\r\n return ScatterChart.BubbleRadius;\r\n }\r\n\r\n public static getMeasureValue(measureIndex: number, seriesValues: DataViewValueColumn[]): DataViewValueColumn {\r\n if (measureIndex >= 0)\r\n return seriesValues[measureIndex];\r\n\r\n return null;\r\n }\r\n\r\n private static getMetadata(grouped: DataViewValueColumnGroup[], source: DataViewMetadataColumn): ScatterChartMeasureMetadata {\r\n let xIndex = DataRoleHelper.getMeasureIndexOfRole(grouped, 'X');\r\n let yIndex = DataRoleHelper.getMeasureIndexOfRole(grouped, 'Y');\r\n let sizeIndex = DataRoleHelper.getMeasureIndexOfRole(grouped, 'Size');\r\n let xCol: DataViewMetadataColumn;\r\n let yCol: DataViewMetadataColumn;\r\n let sizeCol: DataViewMetadataColumn;\r\n let xAxisLabel = \"\";\r\n let yAxisLabel = \"\";\r\n\r\n if (grouped && grouped.length) {\r\n let firstGroup = grouped[0];\r\n if (xIndex >= 0) {\r\n xCol = firstGroup.values[xIndex].source;\r\n xAxisLabel = firstGroup.values[xIndex].source.displayName;\r\n }\r\n if (yIndex >= 0) {\r\n yCol = firstGroup.values[yIndex].source;\r\n yAxisLabel = firstGroup.values[yIndex].source.displayName;\r\n }\r\n if (sizeIndex >= 0) {\r\n sizeCol = firstGroup.values[sizeIndex].source;\r\n }\r\n }\r\n\r\n return {\r\n idx: {\r\n x: xIndex,\r\n y: yIndex,\r\n size: sizeIndex,\r\n },\r\n cols: {\r\n x: xCol,\r\n y: yCol,\r\n size: sizeCol,\r\n },\r\n axesLabels: {\r\n x: xAxisLabel,\r\n y: yAxisLabel\r\n }\r\n };\r\n }\r\n\r\n /** Create a new viewmodel with default data. */\r\n public static getDefaultData(): ScatterChartData {\r\n return {\r\n xCol: undefined,\r\n yCol: undefined,\r\n dataPoints: [],\r\n dataPointSeries: [],\r\n legendData: { dataPoints: [] },\r\n axesLabels: { x: '', y: '' },\r\n sizeRange: [],\r\n dataLabelsSettings: dataLabelUtils.getDefaultPointLabelSettings(),\r\n defaultDataPointColor: null,\r\n hasDynamicSeries: false,\r\n };\r\n }\r\n\r\n private renderAtFrame(data: ScatterChartData): void {\r\n this.data = data;\r\n this.cartesianVisualHost.triggerRender(false);\r\n }\r\n\r\n public setData(dataViews: DataView[]): void {\r\n this.data = ScatterChart.getDefaultData();\r\n\r\n if (this.playAxis && this.playAxis.isCurrentlyPlaying())\r\n this.playAxis.stop();\r\n\r\n if (dataViews.length > 0) {\r\n let dataView = dataViews[0] || dataViews[1];\r\n\r\n if (dataView) {\r\n this.categoryAxisProperties = CartesianHelper.getCategoryAxisProperties(dataView.metadata, true);\r\n this.valueAxisProperties = CartesianHelper.getValueAxisProperties(dataView.metadata, true);\r\n this.dataView = dataView;\r\n\r\n let converterOptions: ScatterConverterOptions = {\r\n viewport: this.currentViewport,\r\n colors: this.colors,\r\n interactivityService: this.interactivityService,\r\n categoryAxisProperties: this.categoryAxisProperties,\r\n valueAxisProperties: this.valueAxisProperties,\r\n };\r\n\r\n if (PlayChart.isDataViewPlayable(dataView)) {\r\n if (!this.playAxis) {\r\n this.playAxis = new PlayAxis<ScatterChartData>({\r\n animator: this.animator,\r\n interactivityService: this.interactivityService,\r\n isScrollable: false,\r\n });\r\n this.playAxis.init(this.options);\r\n }\r\n\r\n let playData = this.playAxis.setData(\r\n dataView,\r\n (dataView: DataView, playFrameInfo?: PlayFrameInfo) =>\r\n ScatterChart.converter(dataView, converterOptions, playFrameInfo, this.tooltipsEnabled, this.tooltipBucketEnabled));\r\n this.mergeSizeRanges(playData);\r\n this.data = playData.currentViewModel;\r\n\r\n this.playAxis.setRenderFunction((data) => this.renderAtFrame(data));\r\n }\r\n else {\r\n if (this.playAxis) {\r\n this.playAxis.remove();\r\n this.playAxis = null;\r\n }\r\n\r\n if (dataView.categorical && dataView.categorical.values) {\r\n this.data = ScatterChart.converter(dataView, converterOptions, undefined, this.tooltipsEnabled, this.tooltipBucketEnabled);\r\n }\r\n }\r\n }\r\n }\r\n else if (this.playAxis) {\r\n this.playAxis.remove();\r\n this.playAxis = null;\r\n }\r\n }\r\n\r\n private mergeSizeRanges(playData: PlayChartData<ScatterChartData>): void {\r\n if (playData && playData.currentViewModel) {\r\n let mergedSizeRange: NumberRange = playData.currentViewModel.sizeRange;\r\n for (let data of playData.allViewModels) {\r\n let sizeRange = data.sizeRange;\r\n if (sizeRange.min != null)\r\n mergedSizeRange.min = Math.min(mergedSizeRange.min, sizeRange.min);\r\n if (sizeRange.max != null)\r\n mergedSizeRange.max = Math.max(mergedSizeRange.max, sizeRange.max);\r\n }\r\n for (let data of playData.allViewModels) {\r\n data.sizeRange = mergedSizeRange;\r\n }\r\n }\r\n }\r\n\r\n public calculateLegend(): LegendData {\r\n return this.data && this.data.legendData;\r\n }\r\n\r\n public hasLegend(): boolean {\r\n return this.data && this.data.hasDynamicSeries;\r\n }\r\n\r\n public enumerateObjectInstances(enumeration: ObjectEnumerationBuilder, options: EnumerateVisualObjectInstancesOptions): void {\r\n switch (options.objectName) {\r\n case 'colorByCategory':\r\n if (this.data) {\r\n // Color by Legend takes precedent during render. Hide the slice but keep the colorByCategory value unchanged in case they remove the Legend field.\r\n if (!this.data.hasDynamicSeries) {\r\n enumeration.pushInstance({\r\n objectName: 'colorByCategory',\r\n selector: null,\r\n properties: {\r\n show: this.data.colorByCategory,\r\n },\r\n });\r\n }\r\n }\r\n break;\r\n case 'dataPoint':\r\n // TODO: DataViewMatix (for PlayAxis) doesn't support category- or series-specific properties yet.\r\n if (!this.playAxis) {\r\n let categoricalDataView: DataViewCategorical = this.dataView && this.dataView.categorical ? this.dataView.categorical : null;\r\n if (!GradientUtils.hasGradientRole(categoricalDataView))\r\n return this.enumerateDataPoints(enumeration);\r\n }\r\n break;\r\n case 'categoryAxis':\r\n enumeration.pushInstance({\r\n selector: null,\r\n properties: {\r\n showAxisTitle: !this.categoryAxisProperties || this.categoryAxisProperties[\"showAxisTitle\"] == null ? true : this.categoryAxisProperties[\"showAxisTitle\"]\r\n },\r\n objectName: 'categoryAxis'\r\n });\r\n break;\r\n case 'valueAxis':\r\n enumeration.pushInstance({\r\n selector: null,\r\n properties: {\r\n showAxisTitle: !this.valueAxisProperties || this.valueAxisProperties[\"showAxisTitle\"] == null ? true : this.valueAxisProperties[\"showAxisTitle\"]\r\n },\r\n objectName: 'valueAxis'\r\n });\r\n break;\r\n case 'categoryLabels':\r\n if (this.data)\r\n dataLabelUtils.enumerateCategoryLabels(enumeration, this.data.dataLabelsSettings, true);\r\n else\r\n dataLabelUtils.enumerateCategoryLabels(enumeration, null, true);\r\n break;\r\n case 'fillPoint':\r\n // Check if the card should be shown or not based on the existence of size measure\r\n if (this.hasSizeMeasure())\r\n return;\r\n\r\n enumeration.pushInstance({\r\n objectName: 'fillPoint',\r\n selector: null,\r\n properties: {\r\n show: this.data.fillPoint,\r\n },\r\n });\r\n break;\r\n case 'colorBorder':\r\n // Check if the card should be shown or not based on the existence of size measure\r\n if (this.hasSizeMeasure())\r\n enumeration.pushInstance({\r\n objectName: 'colorBorder',\r\n selector: null,\r\n properties: {\r\n show: this.data.colorBorder,\r\n },\r\n });\r\n break;\r\n }\r\n }\r\n\r\n private hasSizeMeasure(): boolean {\r\n let sizeRange = this.data.sizeRange;\r\n return sizeRange && sizeRange.min !== undefined;\r\n }\r\n\r\n private enumerateDataPoints(enumeration: ObjectEnumerationBuilder): void {\r\n let data = this.data;\r\n if (!data)\r\n return;\r\n\r\n let seriesCount = data.dataPoints.length;\r\n\r\n if (!data.hasDynamicSeries) {\r\n enumeration.pushInstance({\r\n objectName: 'dataPoint',\r\n selector: null,\r\n properties: {\r\n defaultColor: { solid: { color: data.defaultDataPointColor || this.colors.getColorByIndex(0).value } }\r\n }\r\n }).pushInstance({\r\n objectName: 'dataPoint',\r\n selector: null,\r\n properties: {\r\n showAllDataPoints: !!data.showAllDataPoints\r\n }\r\n });\r\n\r\n for (let i = 0; i < seriesCount; i++) {\r\n let seriesDataPoints = data.dataPoints[i];\r\n enumeration.pushInstance({\r\n objectName: 'dataPoint',\r\n displayName: seriesDataPoints.formattedCategory.getValue(),\r\n selector: ColorHelper.normalizeSelector(seriesDataPoints.identity.getSelector(), /*isSingleSeries*/ true),\r\n properties: {\r\n fill: { solid: { color: seriesDataPoints.fill } }\r\n },\r\n });\r\n }\r\n }\r\n else {\r\n let legendDataPointLength = data.legendData.dataPoints.length;\r\n for (let i = 0; i < legendDataPointLength; i++) {\r\n let series = data.legendData.dataPoints[i];\r\n enumeration.pushInstance({\r\n objectName: 'dataPoint',\r\n displayName: series.label,\r\n selector: ColorHelper.normalizeSelector(series.identity.getSelector()),\r\n properties: {\r\n fill: { solid: { color: series.color } }\r\n },\r\n });\r\n }\r\n }\r\n }\r\n\r\n public supportsTrendLine(): boolean {\r\n let data = this.data;\r\n if (!data)\r\n return false;\r\n let dataView = this.dataView;\r\n let reader = powerbi.data.createIDataViewCategoricalReader(dataView);\r\n return !this.hasSizeMeasure() && data.dataPointSeries.length > 0 && reader.hasValues(\"X\") && reader.hasValues(\"Y\");\r\n }\r\n\r\n private static getExtents(data: ScatterChartData): CartesianExtents {\r\n let dps = data.dataPoints;\r\n if (_.isEmpty(dps)) {\r\n return {\r\n minY: 0,\r\n maxY: 0,\r\n minX: 0,\r\n maxX: 0,\r\n };\r\n }\r\n\r\n return {\r\n minY: d3.min<ScatterChartDataPoint, number>(dps, d => d.y),\r\n maxY: d3.max<ScatterChartDataPoint, number>(dps, d => d.y),\r\n minX: d3.min<ScatterChartDataPoint, number>(dps, d => d.x),\r\n maxX: d3.max<ScatterChartDataPoint, number>(dps, d => d.x),\r\n };\r\n }\r\n\r\n public calculateAxesProperties(options: CalculateScaleAndDomainOptions): IAxisProperties[] {\r\n let data = this.data;\r\n let viewport = this.currentViewport = options.viewport;\r\n let margin = options.margin;\r\n\r\n this.currentViewport = viewport;\r\n this.margin = margin;\r\n\r\n let width = viewport.width - (margin.left + margin.right);\r\n let height = viewport.height - (margin.top + margin.bottom);\r\n\r\n let extents: CartesianExtents = {\r\n minY: 0,\r\n maxY: 10,\r\n minX: 0,\r\n maxX: 10\r\n };\r\n\r\n if (this.playAxis) {\r\n extents = this.playAxis.getCartesianExtents(extents, ScatterChart.getExtents);\r\n this.playAxis.setPlayControlPosition(options.playAxisControlLayout);\r\n }\r\n else if (!_.isEmpty(data.dataPoints)) {\r\n extents = ScatterChart.getExtents(data);\r\n }\r\n\r\n let xDomain = [extents.minX, extents.maxX];\r\n let combinedXDomain = AxisHelper.combineDomain(options.forcedXDomain, xDomain, options.ensureXDomain);\r\n\r\n this.xAxisProperties = AxisHelper.createAxis({\r\n pixelSpan: width,\r\n dataDomain: combinedXDomain,\r\n metaDataColumn: data.xCol,\r\n formatString: valueFormatter.getFormatString(data.xCol, scatterChartProps.general.formatString),\r\n outerPadding: 0,\r\n isScalar: true,\r\n isVertical: false,\r\n forcedTickCount: options.forcedTickCount,\r\n useTickIntervalForDisplayUnits: true,\r\n isCategoryAxis: true, //scatter doesn't have a categorical axis, but this is needed for the pane to react correctly to the x-axis toggle one/off\r\n scaleType: options.categoryAxisScaleType,\r\n axisDisplayUnits: options.categoryAxisDisplayUnits,\r\n axisPrecision: options.categoryAxisPrecision\r\n });\r\n this.xAxisProperties.axis.tickSize(-height, 0);\r\n this.xAxisProperties.axisLabel = this.data.axesLabels.x;\r\n\r\n let combinedDomain = AxisHelper.combineDomain(options.forcedYDomain, [extents.minY, extents.maxY], options.ensureYDomain);\r\n\r\n this.yAxisProperties = AxisHelper.createAxis({\r\n pixelSpan: height,\r\n dataDomain: combinedDomain,\r\n metaDataColumn: data.yCol,\r\n formatString: valueFormatter.getFormatString(data.yCol, scatterChartProps.general.formatString),\r\n outerPadding: 0,\r\n isScalar: true,\r\n isVertical: true,\r\n forcedTickCount: options.forcedTickCount,\r\n useTickIntervalForDisplayUnits: true,\r\n isCategoryAxis: false,\r\n scaleType: options.valueAxisScaleType,\r\n axisDisplayUnits: options.valueAxisDisplayUnits,\r\n axisPrecision: options.valueAxisPrecision\r\n });\r\n this.yAxisProperties.axisLabel = this.data.axesLabels.y;\r\n\r\n // TODO: these should be passed into the render method.\r\n return [this.xAxisProperties, this.yAxisProperties];\r\n }\r\n\r\n public overrideXScale(xProperties: IAxisProperties): void {\r\n this.xAxisProperties = xProperties;\r\n }\r\n\r\n public shouldSuppressAnimation(): boolean {\r\n return this.data && this.data.dataPoints && this.data.dataPoints.length > ScatterChart.NoAnimationThreshold;\r\n }\r\n\r\n public render(suppressAnimations: boolean, resizeMode?: ResizeMode): CartesianVisualRenderResult {\r\n if (!this.data)\r\n return;\r\n\r\n let data = this.data;\r\n\r\n let margin = this.margin;\r\n let viewport = this.currentViewport;\r\n\r\n let hasSelection = this.interactivityService && this.interactivityService.hasSelection();\r\n\r\n let plotArea: IViewport = {\r\n width: viewport.width - (margin.left + margin.right),\r\n height: viewport.height - (margin.top + margin.bottom)\r\n };\r\n\r\n let duration = AnimatorCommon.GetAnimationDuration(this.animator, suppressAnimations);\r\n if (this.playAxis && this.playAxis.isCurrentlyPlaying() && (this.isMobileChart || duration > 0)) {\r\n duration = PlayChart.FrameAnimationDuration;\r\n }\r\n\r\n let easeType = this.playAxis ? 'linear' : 'cubic-in-out'; // cubic-in-out is the d3.ease default\r\n let fillMarkers = (!data.sizeRange || !data.sizeRange.min) && data.fillPoint;\r\n let drawBubbles = this.hasSizeMeasure();\r\n let suppressDataPointRendering = resizeMode === ResizeMode.Resizing && data.dataPoints && data.dataPoints.length > ScatterChart.NoRenderResizeThreshold;\r\n\r\n let viewModel: ScatterChartViewModel = {\r\n data: data,\r\n drawBubbles: drawBubbles,\r\n isPlay: !!this.playAxis,\r\n xAxisProperties: this.xAxisProperties,\r\n yAxisProperties: this.yAxisProperties,\r\n viewport: plotArea,\r\n hasSelection: hasSelection,\r\n animationDuration: duration,\r\n animationOptions: this.options.animation,\r\n fillMarkers: fillMarkers,\r\n easeType: easeType,\r\n suppressDataPointRendering: suppressDataPointRendering,\r\n };\r\n\r\n if (drawBubbles) {\r\n // Bubbles must be drawn from largest to smallest.\r\n let sortedData = data.dataPoints.sort(ScatterChart.sortBubbles);\r\n viewModel.data = Prototype.inherit(viewModel.data);\r\n viewModel.data.dataPoints = sortedData;\r\n }\r\n\r\n let labelDataPoints: LabelDataPoint[] = [];\r\n if (data.dataLabelsSettings && data.dataLabelsSettings.show || data.dataLabelsSettings.showCategory) {\r\n labelDataPoints = ScatterChartDataLabels.createLabelDataPoints(viewModel);\r\n }\r\n\r\n let behaviorOptions = this.renderer.render(viewModel, this.interactivityService);\r\n\r\n if (this.isMobileChart) {\r\n behaviorOptions = <ScatterMobileBehaviorOptions> {\r\n data: behaviorOptions.data,\r\n dataPointsSelection: behaviorOptions.dataPointsSelection,\r\n eventGroup: behaviorOptions.eventGroup,\r\n plotContext: behaviorOptions.plotContext,\r\n host: this.cartesianVisualHost,\r\n root: this.svg,\r\n visualInitOptions: this.options,\r\n xAxisProperties: this.xAxisProperties,\r\n yAxisProperties: this.yAxisProperties,\r\n background: d3.select(this.element.get(0)),\r\n };\r\n }\r\n\r\n let playRenderResult: PlayChartRenderResult<ScatterChartData, ScatterChartViewModel>;\r\n if (this.playAxis) {\r\n playRenderResult = this.playAxis.render(suppressAnimations, viewModel, viewport, margin);\r\n\r\n if (this.interactivityService) {\r\n let playBehaviorOptions: PlayBehaviorOptions = {\r\n traceLineRenderer: this.renderer.createTraceLineRenderer(playRenderResult.viewModel),\r\n };\r\n\r\n if (hasSelection) {\r\n PlayChart.renderTraceLines(playRenderResult.allDataPoints, playBehaviorOptions.traceLineRenderer, !suppressAnimations);\r\n }\r\n\r\n behaviorOptions.playOptions = playBehaviorOptions;\r\n }\r\n }\r\n\r\n return {\r\n dataPoints: playRenderResult ? playRenderResult.allDataPoints : data.dataPoints,\r\n behaviorOptions: behaviorOptions,\r\n labelDataPoints: labelDataPoints,\r\n labelsAreNumeric: false,\r\n };\r\n }\r\n\r\n public static getStrokeFill(d: ScatterChartDataPoint, colorBorder: boolean): string {\r\n if (d.size != null && colorBorder) {\r\n let colorRgb = Color.parseColorString(d.fill);\r\n return Color.hexString(Color.darken(colorRgb, ScatterChart.StrokeDarkenColorValue));\r\n }\r\n return d.fill;\r\n }\r\n\r\n public static getBubblePixelAreaSizeRange(viewPort: IViewport, minSizeRange: number, maxSizeRange: number): DataRange {\r\n let ratio = 1.0;\r\n if (viewPort.height > 0 && viewPort.width > 0) {\r\n let minSize = Math.min(viewPort.height, viewPort.width);\r\n ratio = (minSize * minSize) / ScatterChart.AreaOf300By300Chart;\r\n }\r\n\r\n let minRange = Math.round(minSizeRange * ratio);\r\n let maxRange = Math.round(maxSizeRange * ratio);\r\n return {\r\n minRange: minRange,\r\n maxRange: maxRange,\r\n delta: maxRange - minRange\r\n };\r\n }\r\n\r\n public static project(value: number, actualSizeDataRange: DataRange, bubblePixelAreaSizeRange: DataRange): number {\r\n if (actualSizeDataRange.delta === 0 || bubblePixelAreaSizeRange.delta === 0) {\r\n return (ScatterChart.rangeContains(actualSizeDataRange, value)) ? bubblePixelAreaSizeRange.minRange : null;\r\n }\r\n\r\n let relativeX = (value - actualSizeDataRange.minRange) / actualSizeDataRange.delta;\r\n return bubblePixelAreaSizeRange.minRange + relativeX * bubblePixelAreaSizeRange.delta;\r\n }\r\n\r\n public static projectSizeToPixels(size: number, actualSizeDataRange: DataRange, bubblePixelAreaSizeRange: DataRange): number {\r\n let projectedSize = 0;\r\n if (actualSizeDataRange) {\r\n // Project value on the required range of bubble area sizes\r\n projectedSize = bubblePixelAreaSizeRange.maxRange;\r\n if (actualSizeDataRange.delta !== 0) {\r\n let value = Math.min(Math.max(size, actualSizeDataRange.minRange), actualSizeDataRange.maxRange);\r\n projectedSize = ScatterChart.project(value, actualSizeDataRange, bubblePixelAreaSizeRange);\r\n }\r\n\r\n projectedSize = Math.sqrt(projectedSize / Math.PI) * 2;\r\n }\r\n\r\n return Math.round(projectedSize);\r\n }\r\n\r\n public static rangeContains(range: DataRange, value: number): boolean {\r\n return range.minRange <= value && value <= range.maxRange;\r\n }\r\n\r\n public static getMarkerFillOpacity(hasSize: boolean, shouldEnableFill: boolean, hasSelection: boolean, isSelected: boolean): number {\r\n if (hasSize || shouldEnableFill) {\r\n if (hasSelection && !isSelected) {\r\n return ScatterChart.DimmedBubbleOpacity;\r\n }\r\n return ScatterChart.DefaultBubbleOpacity;\r\n } else {\r\n return 0;\r\n }\r\n }\r\n\r\n public static getMarkerStrokeOpacity(hasSize: boolean, colorBorder: boolean, hasSelection: boolean, isSelected: boolean): number {\r\n if (hasSize && colorBorder) {\r\n return 1;\r\n } else {\r\n if (hasSelection && !isSelected) {\r\n return ScatterChart.DimmedBubbleOpacity;\r\n }\r\n return ScatterChart.DefaultBubbleOpacity;\r\n }\r\n }\r\n\r\n public static getMarkerStrokeFill(hasSize: boolean, colorBorder: boolean, fill: string): string {\r\n if (hasSize && colorBorder) {\r\n let colorRgb = Color.parseColorString(fill);\r\n return Color.hexString(Color.darken(colorRgb, ScatterChart.StrokeDarkenColorValue));\r\n }\r\n return fill;\r\n }\r\n\r\n public static getMarkerStyle(d: ScatterChartDataPoint, colorBorder: boolean, hasSelection: boolean, fillMarkers: boolean): ScatterMarkerStyle {\r\n return {\r\n 'stroke-opacity': ScatterChart.getMarkerStrokeOpacity(d.size != null, colorBorder, hasSelection, d.selected),\r\n stroke: ScatterChart.getMarkerStrokeFill(d.size != null, colorBorder, d.fill),\r\n fill: d.fill,\r\n 'fill-opacity': ScatterChart.getMarkerFillOpacity(d.size != null, fillMarkers, hasSelection, d.selected),\r\n };\r\n }\r\n\r\n public static getSeriesStyle(hasSize: boolean, colorBorder: boolean, hasSelection: boolean, fillMarkers: boolean, fill: string): ScatterMarkerStyle {\r\n return {\r\n 'stroke-opacity': ScatterChart.getMarkerStrokeOpacity(hasSize, colorBorder, hasSelection, false),\r\n stroke: ScatterChart.getMarkerStrokeFill(hasSize, colorBorder, fill),\r\n fill: fill,\r\n 'fill-opacity': ScatterChart.getMarkerFillOpacity(hasSize, fillMarkers, hasSelection, false),\r\n };\r\n }\r\n\r\n public static getBubbleOpacity(d: ScatterChartDataPoint, hasSelection: boolean): number {\r\n if (hasSelection && !d.selected) {\r\n return ScatterChart.DimmedBubbleOpacity;\r\n }\r\n return ScatterChart.DefaultBubbleOpacity;\r\n }\r\n\r\n public onClearSelection(): void {\r\n if (this.interactivityService)\r\n this.interactivityService.clearSelection();\r\n }\r\n\r\n public getSupportedCategoryAxisType(): string {\r\n return axisType.scalar;\r\n }\r\n\r\n public static sortBubbles(a: ScatterChartDataPoint, b: ScatterChartDataPoint): number {\r\n let diff = (b.radius.sizeMeasure.values[b.radius.index] - a.radius.sizeMeasure.values[a.radius.index]);\r\n if (diff !== 0)\r\n return diff;\r\n\r\n // Tie-break equal size bubbles using identity.\r\n return b.identity.getKey().localeCompare(a.identity.getKey());\r\n }\r\n }\r\n\r\n class SvgRenderer {\r\n private static DotClass: ClassAndSelector = createClassAndSelector('dot');\r\n private static MainGraphicsContext = createClassAndSelector('mainGraphicsContext');\r\n private static ScatterMarkerSeriesGroup = createClassAndSelector('scatterMarkerSeriesGroup');\r\n\r\n private mainGraphicsContext: D3.Selection;\r\n private mainGraphicsG: D3.Selection;\r\n private mainGraphicsBackgroundRect: D3.Selection;\r\n private labelGraphicsContext: D3.Selection;\r\n private isMobileChart: boolean;\r\n private tooltipsEnabled: boolean;\r\n\r\n public init(element: D3.Selection, labelsContext: D3.Selection, isMobileChart: boolean, tooltipsEnabled: boolean): void {\r\n this.mainGraphicsG = element.append('g')\r\n .classed(SvgRenderer.MainGraphicsContext.class, true);\r\n\r\n this.isMobileChart = isMobileChart;\r\n if (isMobileChart) {\r\n // The backgroundRect catch user interactions when clicking/dragging on the background of the chart.\r\n this.mainGraphicsBackgroundRect = this.mainGraphicsG\r\n .append(\"rect\")\r\n .classed(\"backgroundRect\", true)\r\n .attr({ width: \"100%\", height: \"100%\" });\r\n }\r\n\r\n this.mainGraphicsContext = this.mainGraphicsG.append('svg');\r\n this.labelGraphicsContext = labelsContext;\r\n this.tooltipsEnabled = tooltipsEnabled;\r\n\r\n // common rendering attributes\r\n this.mainGraphicsContext.attr('stroke-width', \"1\");\r\n }\r\n\r\n public render(viewModel: ScatterChartViewModel, interactivityService: IInteractivityService): ScatterBehaviorOptions {\r\n let viewport = viewModel.viewport;\r\n\r\n this.mainGraphicsContext\r\n .attr({\r\n 'width': viewport.width,\r\n 'height': viewport.height\r\n });\r\n\r\n let scatterMarkers: D3.Selection;\r\n if (viewModel.suppressDataPointRendering) {\r\n scatterMarkers = this.removeScatterMarkers();\r\n }\r\n else if (viewModel.animationDuration > 0) {\r\n scatterMarkers = this.drawScatterMarkers(viewModel);\r\n }\r\n else {\r\n scatterMarkers = this.drawScatterMarkersNoAnimation(viewModel, viewModel.drawBubbles);\r\n }\r\n\r\n if (viewModel.drawBubbles)\r\n scatterMarkers.order();\r\n\r\n if (this.tooltipsEnabled) {\r\n TooltipManager.addTooltip(this.mainGraphicsContext, (tooltipEvent: TooltipEvent) => tooltipEvent.data.tooltipInfo);\r\n }\r\n SVGUtil.flushAllD3TransitionsIfNeeded(viewModel.animationOptions);\r\n\r\n return <ScatterBehaviorOptions> {\r\n dataPointsSelection: scatterMarkers,\r\n eventGroup: this.mainGraphicsG,\r\n data: viewModel.data,\r\n plotContext: this.mainGraphicsContext,\r\n };\r\n }\r\n\r\n public createTraceLineRenderer(viewModel: PlayChartViewModel<ScatterChartData, ScatterChartViewModel>): ScatterTraceLineRenderer {\r\n return new ScatterTraceLineRenderer(viewModel, this.mainGraphicsContext, this.tooltipsEnabled);\r\n }\r\n\r\n private removeScatterMarkers(): D3.Selection {\r\n this.mainGraphicsContext.selectAll(SvgRenderer.ScatterMarkerSeriesGroup.selector)\r\n .remove();\r\n\r\n return this.mainGraphicsContext.selectAll(SvgRenderer.DotClass.selector);\r\n }\r\n\r\n private drawScatterMarkers(viewModel: ScatterChartViewModel): D3.UpdateSelection {\r\n let data = viewModel.data;\r\n let xScale = viewModel.xAxisProperties.scale;\r\n let yScale = viewModel.yAxisProperties.scale;\r\n\r\n // put all the markers in a single fake group. keeps the dom structure consistent between\r\n // drawScatterMarkers and drawScatterMarkersGrouped.\r\n let fakeDataPointSeries: ScatterChartDataPointSeries[] = [\r\n {\r\n identityKey: \"\",\r\n dataPoints: data.dataPoints,\r\n },\r\n ];\r\n\r\n let fakeSeriesGroups = this.mainGraphicsContext.selectAll(SvgRenderer.ScatterMarkerSeriesGroup.selector)\r\n .data(fakeDataPointSeries, (s: ScatterChartDataPointSeries) => s.identityKey);\r\n\r\n fakeSeriesGroups.enter()\r\n .append('g')\r\n .classed(SvgRenderer.ScatterMarkerSeriesGroup.class, true);\r\n\r\n // groups for real series may have been inserted by drawScatterMarkersGrouped, remove them\r\n fakeSeriesGroups.exit()\r\n .remove();\r\n\r\n let markers = fakeSeriesGroups.selectAll(SvgRenderer.DotClass.selector)\r\n .data((s: ScatterChartDataPointSeries) => s.dataPoints, (d: ScatterChartDataPoint) => d.identity.getKey());\r\n\r\n markers.enter().append('circle')\r\n .classed(SvgRenderer.DotClass.class, true)\r\n .style('opacity', 0) // Fade new bubbles into visibility\r\n .attr('r', 0);\r\n\r\n markers\r\n .style({\r\n 'stroke-opacity': (d: ScatterChartDataPoint) => ScatterChart.getMarkerStrokeOpacity(d.size != null, data.colorBorder, viewModel.hasSelection, d.selected),\r\n 'stroke': (d: ScatterChartDataPoint) => ScatterChart.getStrokeFill(d, data.colorBorder),\r\n 'fill': (d: ScatterChartDataPoint) => d.fill,\r\n 'fill-opacity': (d: ScatterChartDataPoint) => ScatterChart.getMarkerFillOpacity(d.size != null, viewModel.fillMarkers, viewModel.hasSelection, d.selected),\r\n })\r\n .transition()\r\n .ease(viewModel.easeType)\r\n .duration(viewModel.animationDuration)\r\n .style('opacity', 1) // Fill-opacity is used for selected / highlight changes, opacity is for enter/exit fadein/fadeout\r\n .attr({\r\n r: (d: ScatterChartDataPoint) => ScatterChart.getBubbleRadius(d.radius, data.sizeRange, viewModel.viewport),\r\n cx: d => xScale(d.x),\r\n cy: d => yScale(d.y),\r\n });\r\n\r\n markers\r\n .exit()\r\n .transition()\r\n .ease(viewModel.easeType)\r\n .duration(viewModel.animationDuration)\r\n .style('opacity', 0) // Fade out bubbles that are removed\r\n .attr('r', 0)\r\n .remove();\r\n\r\n return markers;\r\n }\r\n\r\n private drawScatterMarkersNoAnimation(viewModel: ScatterChartViewModel, isBubble: boolean): D3.Selection {\r\n let data = viewModel.data;\r\n let xScale = viewModel.xAxisProperties.scale;\r\n let yScale = viewModel.yAxisProperties.scale;\r\n\r\n let seriesGroups: D3.UpdateSelection;\r\n if (isBubble) {\r\n let fakeDataPointSeries: ScatterChartDataPointSeries[] = [\r\n {\r\n identityKey: \"\",\r\n dataPoints: data.dataPoints,\r\n },\r\n ];\r\n\r\n seriesGroups = this.mainGraphicsContext.selectAll(SvgRenderer.ScatterMarkerSeriesGroup.selector)\r\n .data(fakeDataPointSeries, (s: ScatterChartDataPointSeries) => s.identityKey);\r\n }\r\n else {\r\n seriesGroups = this.mainGraphicsContext.selectAll(SvgRenderer.ScatterMarkerSeriesGroup.selector).data(data.dataPointSeries, (s: ScatterChartDataPointSeries) => s.identityKey);\r\n }\r\n\r\n // a group for each series\r\n seriesGroups.enter()\r\n .append('g')\r\n .classed(SvgRenderer.ScatterMarkerSeriesGroup.class, true);\r\n\r\n // this will also remove the fake group that might have been created by drawScatterMarkers\r\n seriesGroups.exit()\r\n .remove();\r\n\r\n seriesGroups\r\n .each(function (s: ScatterChartDataPointSeries): void {\r\n let seriesStyle: ScatterMarkerStyle = ScatterChart.getSeriesStyle(s.hasSize, data.colorBorder, viewModel.hasSelection, viewModel.fillMarkers, s.fill);\r\n\r\n let g = d3.select(<EventTarget>this);\r\n SvgRenderer.applyStyle(this, seriesStyle);\r\n\r\n let markers = g.selectAll(SvgRenderer.DotClass.selector).data(s.dataPoints, (m: ScatterChartDataPoint) => m.identity.getKey());\r\n\r\n markers.interrupt();\r\n\r\n markers.enter()\r\n .append('circle')\r\n .classed(SvgRenderer.DotClass.class, true);\r\n\r\n markers.exit()\r\n .remove();\r\n\r\n markers.each(function (d: ScatterChartDataPoint) {\r\n let style = ScatterChart.getMarkerStyle(d, data.colorBorder, viewModel.hasSelection, viewModel.fillMarkers);\r\n SvgRenderer.styleException(style, seriesStyle);\r\n SvgRenderer.applyStyle(this, style);\r\n });\r\n\r\n markers.attr({\r\n r: (d: ScatterChartDataPoint) => ScatterChart.getBubbleRadius(d.radius, data.sizeRange, viewModel.viewport),\r\n cx: d => xScale(d.x),\r\n cy: d => yScale(d.y),\r\n });\r\n });\r\n\r\n return this.mainGraphicsContext.selectAll(SvgRenderer.DotClass.selector);\r\n }\r\n\r\n private static styleException(elementStyle: ScatterMarkerStyle, seriesStyle: ScatterMarkerStyle): void {\r\n if (seriesStyle) {\r\n for (let name in elementStyle) {\r\n if (elementStyle[name] === seriesStyle[name]) {\r\n elementStyle[name] = null;\r\n }\r\n }\r\n }\r\n }\r\n\r\n private static applyStyle(element: SVGStylable, style: ScatterMarkerStyle): void {\r\n for (let name in style) {\r\n let elementValue = element.style[name];\r\n let styleValue = style[name];\r\n if (styleValue == null) {\r\n if (elementValue === \"\")\r\n continue;\r\n } else {\r\n styleValue = styleValue.toString();\r\n if (styleValue === elementValue)\r\n continue;\r\n }\r\n\r\n element.style[name] = styleValue;\r\n }\r\n }\r\n }\r\n\r\n module ScatterChartDataLabels {\r\n let validLabelPositions = [\r\n NewPointLabelPosition.Below,\r\n NewPointLabelPosition.Above,\r\n NewPointLabelPosition.Right,\r\n NewPointLabelPosition.Left,\r\n NewPointLabelPosition.BelowRight,\r\n NewPointLabelPosition.BelowLeft,\r\n NewPointLabelPosition.AboveRight,\r\n NewPointLabelPosition.AboveLeft\r\n ];\r\n\r\n /*\r\n * Represents standard Cartesian quadrant numbering:\r\n * 2 1\r\n * 3 4\r\n */\r\n export const enum QuadrantNumber {\r\n First,\r\n Second,\r\n Third,\r\n Fourth\r\n }\r\n\r\n export function createLabelDataPoints(viewModel: ScatterChartViewModel): LabelDataPoint[] {\r\n let xScale = viewModel.xAxisProperties.scale;\r\n let yScale = viewModel.yAxisProperties.scale;\r\n let sizeRange = viewModel.data.sizeRange;\r\n let labelDataPoints: LabelDataPoint[] = [];\r\n let dataPoints = viewModel.data.dataPoints;\r\n let labelSettings = viewModel.data.dataLabelsSettings;\r\n let preferredLabelsKeys = getPreferredLabelsKeys(viewModel);\r\n\r\n for (let dataPoint of dataPoints) {\r\n let text = dataPoint.formattedCategory.getValue();\r\n\r\n let properties: TextProperties = {\r\n text: text,\r\n fontFamily: NewDataLabelUtils.LabelTextProperties.fontFamily,\r\n fontSize: PixelConverter.fromPoint(labelSettings.fontSize || NewDataLabelUtils.DefaultLabelFontSizeInPt),\r\n fontWeight: NewDataLabelUtils.LabelTextProperties.fontWeight,\r\n };\r\n let textWidth = TextMeasurementService.measureSvgTextWidth(properties);\r\n let textHeight = TextMeasurementService.estimateSvgTextHeight(properties);\r\n\r\n labelDataPoints.push({\r\n isPreferred: preferredLabelsKeys ? isLabelPreferred(dataPoint.identity.getKey(), preferredLabelsKeys) : false,\r\n text: text,\r\n textSize: {\r\n width: textWidth,\r\n height: textHeight,\r\n },\r\n outsideFill: labelSettings.labelColor ? labelSettings.labelColor : NewDataLabelUtils.defaultLabelColor,\r\n insideFill: NewDataLabelUtils.defaultInsideLabelColor,\r\n parentType: LabelDataPointParentType.Point,\r\n parentShape: {\r\n point: {\r\n x: xScale(dataPoint.x),\r\n y: yScale(dataPoint.y),\r\n },\r\n radius: ScatterChart.getBubbleRadius(dataPoint.radius, sizeRange, viewModel.viewport),\r\n validPositions: validLabelPositions,\r\n },\r\n identity: dataPoint.identity,\r\n fontSize: labelSettings.fontSize || NewDataLabelUtils.DefaultLabelFontSizeInPt,\r\n });\r\n }\r\n\r\n return labelDataPoints;\r\n }\r\n\r\n function getPreferredLabelsKeys(viewModel: ScatterChartViewModel): string[] {\r\n let width = viewModel.viewport.width;\r\n let height = viewModel.viewport.height;\r\n\r\n let visualCenter = new Point(width / 2, height / 2);\r\n let quadrantsCenters: Point[] = getQuadrantsCenters(width, height);\r\n\r\n return getCandidateLabels(visualCenter, quadrantsCenters, viewModel);\r\n }\r\n\r\n function getQuadrantsCenters(visualWidth: number, visualHeight: number): Point[] {\r\n let quadrantsCenters: Point[] = [];\r\n let quarterWidth = visualWidth / 4;\r\n let quarterHeight = visualHeight / 4;\r\n\r\n quadrantsCenters.push(new Point(quarterWidth, quarterHeight));\r\n quadrantsCenters.push(new Point(quarterWidth * 3, quarterHeight));\r\n quadrantsCenters.push(new Point(quarterWidth, quarterHeight * 3));\r\n quadrantsCenters.push(new Point(quarterWidth * 3, quarterHeight * 3));\r\n\r\n return quadrantsCenters;\r\n }\r\n\r\n function getCandidateLabels(\r\n visualCenter: Point,\r\n quadrantsCenters: Point[],\r\n viewModel: ScatterChartViewModel): string[] {\r\n let minDistances: number[] = [Number.MAX_VALUE, Number.MAX_VALUE, Number.MAX_VALUE, Number.MAX_VALUE];\r\n let ids: SelectionId[] = [];\r\n\r\n let xScale = viewModel.xAxisProperties.scale;\r\n let yScale = viewModel.yAxisProperties.scale;\r\n\r\n let distance: number;\r\n\r\n for (let dp of viewModel.data.dataPoints) {\r\n let x = xScale(dp.x);\r\n let y = yScale(dp.y);\r\n let quadrantNumber = getPointQuadrantNumber(x, y, visualCenter);\r\n if (viewModel.drawBubbles) {\r\n // Since the array is sorted by size the preferred label will be the first label in the quadrant\r\n if (!ids[quadrantNumber])\r\n ids[quadrantNumber] = dp.identity;\r\n }\r\n else {\r\n distance = getDistanceBetweenPoints(quadrantsCenters[quadrantNumber].x, quadrantsCenters[quadrantNumber].y, x, y);\r\n if (distance < minDistances[quadrantNumber]) {\r\n ids[quadrantNumber] = dp.identity;\r\n minDistances[quadrantNumber] = distance;\r\n }\r\n }\r\n\r\n }\r\n\r\n let preferredLabelsKeys: string[] = [];\r\n for (let id of ids) {\r\n if (id)\r\n preferredLabelsKeys.push(id.getKey());\r\n }\r\n\r\n return preferredLabelsKeys;\r\n }\r\n\r\n function getPointQuadrantNumber(x: number, y: number, centerPoint: Point): number {\r\n if (x > centerPoint.x && y <= centerPoint.y)\r\n return QuadrantNumber.First;\r\n if (x <= centerPoint.x && y <= centerPoint.y)\r\n return QuadrantNumber.Second;\r\n if (x <= centerPoint.x && y > centerPoint.y)\r\n return QuadrantNumber.Third;\r\n else\r\n return QuadrantNumber.Fourth;\r\n }\r\n\r\n function getDistanceBetweenPoints(x1: number, y1: number, x2: number, y2: number): number {\r\n return Math.sqrt((x1 - x2) * (x1 - x2) + (y1 - y2) * (y1 - y2));\r\n }\r\n\r\n function isLabelPreferred(key: string, preferredLabelsKeys: string[]) {\r\n for (let preferredLabel of preferredLabelsKeys) {\r\n if (key.localeCompare(preferredLabel) === 0)\r\n return true;\r\n }\r\n return false;\r\n }\r\n }\r\n\r\n class ScatterTraceLineRenderer implements ITraceLineRenderer {\r\n private static TraceLine: ClassAndSelector = createClassAndSelector('traceLine');\r\n private static TraceBubble: ClassAndSelector = createClassAndSelector('traceBubble');\r\n\r\n private viewModel: PlayChartViewModel<ScatterChartData, ScatterChartViewModel>;\r\n private element: D3.Selection;\r\n private tooltipsEnabled: boolean;\r\n\r\n constructor(viewModel: PlayChartViewModel<ScatterChartData, ScatterChartViewModel>, element: D3.Selection, tooltipsEnabled: boolean) {\r\n this.viewModel = viewModel;\r\n this.element = element;\r\n this.tooltipsEnabled = tooltipsEnabled;\r\n }\r\n\r\n public remove() {\r\n this.element.selectAll(ScatterTraceLineRenderer.TraceLine.selector).remove();\r\n this.element.selectAll(ScatterTraceLineRenderer.TraceBubble.selector).remove();\r\n }\r\n\r\n public render(selectedPoints: SelectableDataPoint[], shouldAnimate: boolean): void {\r\n let viewModel = this.viewModel;\r\n let scatterViewModel = viewModel.viewModel;\r\n let seriesPoints: ScatterChartDataPoint[][] = [];\r\n\r\n if (!_.isEmpty(selectedPoints) && !scatterViewModel.suppressDataPointRendering) {\r\n let currentFrameIndex = viewModel.data.currentFrameIndex;\r\n\r\n // filter to the selected identity, only up to and including the current frame. Add frames during play.\r\n let hasBubbleAtCurrentFrame: boolean[] = [];\r\n for (var selectedIndex = 0, selectedLen = selectedPoints.length; selectedIndex < selectedLen; selectedIndex++) {\r\n seriesPoints[selectedIndex] = [];\r\n hasBubbleAtCurrentFrame[selectedIndex] = false;\r\n for (let frameIndex = 0, frameLen = viewModel.data.allViewModels.length; frameIndex < frameLen && frameIndex <= currentFrameIndex; frameIndex++) {\r\n let value = _.find(viewModel.data.allViewModels[frameIndex].dataPoints, (value, index) => {\r\n return value.identity.getKey() === selectedPoints[selectedIndex].identity.getKey();\r\n });\r\n\r\n if (value != null) {\r\n // TODO: Revisit this, we should be able to keep track without modifying Scatter's data points.\r\n (<PlayChartDataPoint>value).frameIndex = frameIndex;\r\n seriesPoints[selectedIndex].push(value);\r\n if (frameIndex === currentFrameIndex)\r\n hasBubbleAtCurrentFrame[selectedIndex] = true;\r\n }\r\n }\r\n }\r\n\r\n let xScale = scatterViewModel.xAxisProperties.scale;\r\n let yScale = scatterViewModel.yAxisProperties.scale;\r\n\r\n let line = d3.svg.line()\r\n .x((d: ScatterChartDataPoint) => xScale(d.x))\r\n .y((d: ScatterChartDataPoint) => yScale(d.y))\r\n .defined((d: ScatterChartDataPoint) => d.x !== null && d.y !== null);\r\n\r\n // Render Lines\r\n let traceLines = this.element.selectAll(ScatterTraceLineRenderer.TraceLine.selector)\r\n .data(selectedPoints, (sp: ScatterChartDataPoint) => sp.identity.getKey());\r\n\r\n traceLines.enter()\r\n .append('path')\r\n .classed(ScatterTraceLineRenderer.TraceLine.class, true);\r\n\r\n // prepare array of new/previous lengths\r\n // NOTE: can't use lambda because we need the \"this\" context to be the DOM Element associated with the .each()\r\n let previousLengths: number[] = [], newLengths: number[] = [];\r\n let reverse = false;\r\n traceLines.each(function (d, i) {\r\n let existingPath = (<SVGPathElement>this);\r\n let previousLength = existingPath.hasAttribute('d') ? existingPath.getTotalLength() : 0;\r\n previousLengths.push(previousLength);\r\n // create offline SVG for new path measurement\r\n let tempSvgPath = $('<svg><path></path></svg>');\r\n let tempPath = $('path', tempSvgPath);\r\n tempPath.attr('d', line(seriesPoints[i]));\r\n let newLength = seriesPoints[i].length > 0 ? (<SVGPathElement>tempPath.get()[0]).getTotalLength() : 0;\r\n newLengths.push(newLength);\r\n\r\n reverse = reverse || (newLength < previousLength);\r\n });\r\n\r\n // animate using stroke-dash* trick\r\n if (!reverse) {\r\n // growing line\r\n traceLines\r\n .style('stroke', (d: ScatterChartDataPoint) => ScatterChart.getStrokeFill(d, true))\r\n .attr({\r\n 'd': (d, i: number) => {\r\n return line(seriesPoints[i]);\r\n },\r\n 'stroke-dasharray': (d, i) => newLengths[i] + \" \" + newLengths[i],\r\n 'stroke-dashoffset': (d, i) => newLengths[i] - previousLengths[i],\r\n });\r\n if (shouldAnimate) {\r\n traceLines\r\n .transition()\r\n .ease('linear')\r\n .duration(PlayChart.FrameAnimationDuration)\r\n .attr('stroke-dashoffset', 0);\r\n }\r\n else {\r\n traceLines.attr('stroke-dashoffset', 0);\r\n }\r\n }\r\n else {\r\n // shrinking line\r\n if (shouldAnimate) {\r\n traceLines\r\n .transition()\r\n .ease('linear')\r\n .duration(PlayChart.FrameAnimationDuration)\r\n .attr('stroke-dashoffset', (d, i) => previousLengths[i] - newLengths[i])\r\n .transition()\r\n .ease('linear')\r\n .duration(1) // animate the shrink first, then update with new line properties\r\n .delay(PlayChart.FrameAnimationDuration)\r\n .style('stroke', (d: ScatterChartDataPoint) => ScatterChart.getStrokeFill(d, true))\r\n .attr({\r\n 'd': (d, i) => {\r\n return line(seriesPoints[i]);\r\n },\r\n 'stroke-dasharray': (d, i) => newLengths[i] + \" \" + newLengths[i],\r\n 'stroke-dashoffset': 0,\r\n });\r\n }\r\n else {\r\n traceLines\r\n .style('stroke', (d: ScatterChartDataPoint) => ScatterChart.getStrokeFill(d, true))\r\n .attr({\r\n 'd': (d, i) => {\r\n return line(seriesPoints[i]);\r\n },\r\n 'stroke-dasharray': (d, i) => newLengths[i] + \" \" + newLengths[i],\r\n 'stroke-dashoffset': 0,\r\n });\r\n }\r\n }\r\n\r\n traceLines.exit()\r\n .remove();\r\n\r\n // Render circles\r\n let circlePoints: ScatterChartDataPoint[] = [];\r\n for (let selectedIndex = 0; selectedIndex < seriesPoints.length; selectedIndex++) {\r\n let points = seriesPoints[selectedIndex];\r\n\r\n // slice to length-1 because we draw lines to the current bubble but we don't need to draw the current frame's bubble\r\n let newPoints = hasBubbleAtCurrentFrame[selectedIndex] ? points.slice(0, points.length - 1) : points;\r\n\r\n circlePoints = circlePoints.concat(newPoints);\r\n }\r\n\r\n let circles = this.element.selectAll(ScatterTraceLineRenderer.TraceBubble.selector)\r\n .data(circlePoints, (d: ScatterChartDataPoint) => d.identity.getKey() + d.x + d.y + d.size);\r\n\r\n circles.enter()\r\n .append('circle')\r\n .style('opacity', 0) //fade new bubbles into visibility\r\n .classed(ScatterTraceLineRenderer.TraceBubble.class, true);\r\n\r\n circles\r\n .attr('cx', (d: ScatterChartDataPoint) => xScale(d.x))\r\n .attr('cy', (d: ScatterChartDataPoint) => yScale(d.y))\r\n .attr('r', (d: ScatterChartDataPoint) => ScatterChart.getBubbleRadius(d.radius, (<ScatterChartData>viewModel.data.currentViewModel).sizeRange, viewModel.viewport))\r\n .style({\r\n 'stroke-opacity': (d: ScatterChartDataPoint) => ScatterChart.getBubbleOpacity(d, true),\r\n 'stroke': (d: ScatterChartDataPoint) => ScatterChart.getStrokeFill(d, viewModel.data.currentViewModel.colorBorder),\r\n 'fill': (d: ScatterChartDataPoint) => d.fill,\r\n // vary the opacity along the traceline from 0.20 to 0.80, with 0.85 left for the circle already drawn by scatterChart\r\n 'fill-opacity': (d: ScatterChartDataPoint) => d.size != null ? 0.20 + ((<PlayChartDataPoint>d).frameIndex / currentFrameIndex) * 0.60 : 0,\r\n })\r\n .transition()\r\n .ease('linear')\r\n .duration(PlayChart.FrameAnimationDuration)\r\n .style('opacity', 1);\r\n\r\n circles.exit()\r\n .transition()\r\n .ease('linear')\r\n .duration(PlayChart.FrameAnimationDuration)\r\n .style('opacity', 0) // fade exiting bubbles out\r\n .remove();\r\n\r\n if (this.tooltipsEnabled) {\r\n TooltipManager.addTooltip(circles, (tooltipEvent: TooltipEvent) => tooltipEvent.data.tooltipInfo);\r\n }\r\n\r\n // sort the z-order, smallest size on top\r\n circles.sort((d1: ScatterChartDataPoint, d2: ScatterChartDataPoint) => { return d2.size - d1.size; });\r\n }\r\n else {\r\n this.remove();\r\n }\r\n }\r\n }\r\n}\r\n\n\n\n/** WEBPACK FOOTER **\n ** ./src/Clients/Visuals/cartesian/scatterChart.ts\n **/","/*\r\n * Power BI Visualizations\r\n *\r\n * Copyright (c) Microsoft Corporation\r\n * All rights reserved. \r\n * MIT License\r\n *\r\n * Permission is hereby granted, free of charge, to any person obtaining a copy\r\n * of this software and associated documentation files (the \"\"Software\"\"), to deal\r\n * in the Software without restriction, including without limitation the rights\r\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\r\n * copies of the Software, and to permit persons to whom the Software is\r\n * furnished to do so, subject to the following conditions:\r\n * \r\n * The above copyright notice and this permission notice shall be included in \r\n * all copies or substantial portions of the Software.\r\n * \r\n * THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR \r\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, \r\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE \r\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER \r\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\r\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\r\n * THE SOFTWARE.\r\n */\r\n\r\n/// <reference path=\"../_references.ts\"/>\r\n\r\nmodule powerbi.visuals {\r\n import createClassAndSelector = jsCommon.CssConstants.createClassAndSelector;\r\n import createDataViewScopeIdentity = powerbi.data.createDataViewScopeIdentity;\r\n import DataViewConcatenateCategoricalColumns = powerbi.data.DataViewConcatenateCategoricalColumns;\r\n import DataViewMatrixUtils = powerbi.data.DataViewMatrixUtils;\r\n import SQExpr = powerbi.data.SQExpr;\r\n import SQExprBuilder = powerbi.data.SQExprBuilder;\r\n\r\n export interface PlayConstructorOptions extends CartesianVisualConstructorOptions {\r\n }\r\n\r\n export interface PlayInitOptions extends CartesianVisualInitOptions {\r\n }\r\n \r\n export interface PlayChartDataPoint {\r\n frameIndex?: number;\r\n };\r\n\r\n export interface PlayChartData<T extends PlayableChartData> {\r\n frameData: PlayChartFrameData[];\r\n allViewModels: T[];\r\n currentViewModel: T;\r\n currentFrameIndex: number;\r\n labelData: PlayAxisTickLabelData;\r\n }\r\n \r\n export interface PlayChartFrameData {\r\n escapedText: string;\r\n text: string;\r\n } \r\n\r\n export interface PlayChartViewModel<TData extends PlayableChartData, TViewModel> {\r\n data: PlayChartData<TData>;\r\n viewModel: TViewModel;\r\n viewport: IViewport;\r\n }\r\n\r\n // TODO: consider a template for the datapoint type <T> instead of any[]\r\n // I tried this an it is quite hard to express correctly with TypeScript.\r\n export interface PlayableChartData {\r\n dataPoints: any[];\r\n }\r\n\r\n interface PlayObjectProperties {\r\n currentFrameIndex?: number;\r\n }\r\n\r\n export interface PlayAxisTickLabelInfo {\r\n label: string;\r\n labelWidth: number;\r\n }\r\n\r\n export interface PlayAxisTickLabelData {\r\n labelInfo: PlayAxisTickLabelInfo[];\r\n anyWordBreaks: boolean;\r\n labelFieldName?: string;\r\n }\r\n\r\n export interface PlayChartRenderResult<TData extends PlayableChartData, TViewModel> {\r\n allDataPoints: SelectableDataPoint[];\r\n viewModel: PlayChartViewModel<TData, TViewModel>;\r\n }\r\n\r\n export interface PlayChartRenderFrameDelegate<T> {\r\n (data: T): void;\r\n }\r\n\r\n export interface PlayFrameInfo {\r\n label: string;\r\n column: DataViewMetadataColumn;\r\n }\r\n\r\n export interface VisualDataConverterDelegate<T> {\r\n (dataView: DataView, playFrameInfo?: PlayFrameInfo): T;\r\n }\r\n\r\n export interface ITraceLineRenderer {\r\n render(selectedPoints: SelectableDataPoint[], shouldAnimate: boolean): void;\r\n remove(): void;\r\n }\r\n \r\n interface SliderSize {\r\n width: number;\r\n marginLeft: number; \r\n };\r\n \r\n interface SliderPipFilter {\r\n skipStep: number;\r\n filter: (index: any, type: any) => number;\r\n }\r\n\r\n export class PlayAxis<T extends PlayableChartData> {\r\n private element: JQuery;\r\n private svg: D3.Selection;\r\n\r\n private playData: PlayChartData<T>;\r\n private renderDelegate: PlayChartRenderFrameDelegate<T>;\r\n private isPlaying: boolean;\r\n private lastViewport: IViewport;\r\n\r\n // do not call converter() when we call persistProperties and a new update() happens\r\n // NOTE: calling persistProperties will still cause a render() call to come from cartesianChart\r\n // TODO: make persist properties only optionally trigger a new onDataChagned, as most charts don't want this (only slicer needs it)\r\n private ridiculousFlagForPersistProperties: boolean;\r\n\r\n private playControl: PlayControl;\r\n\r\n private host: IVisualHostServices;\r\n private interactivityService: IInteractivityService;\r\n private isMobileChart: boolean;\r\n\r\n private static PlayCallout = createClassAndSelector('play-callout');\r\n private static calloutOffsetMultiplier = 0.3;\r\n\r\n constructor(options: PlayConstructorOptions) {\r\n if (options) {\r\n this.interactivityService = options.interactivityService;\r\n }\r\n }\r\n\r\n public init(options: PlayInitOptions) {\r\n debug.assertValue(options, 'options');\r\n\r\n this.element = options.element;\r\n this.svg = options.svg;\r\n this.host = options.host;\r\n\r\n this.isMobileChart = options.interactivity && options.interactivity.isInteractiveLegend;\r\n\r\n if (this.interactivityService) {\r\n this.playControl = new PlayControl(this.element, (frameIndex: number) => this.moveToFrameAndRender(frameIndex), this.isMobileChart);\r\n this.playControl.onPlay(() => this.play());\r\n }\r\n }\r\n\r\n public setData(dataView: DataView, visualConverter: VisualDataConverterDelegate<T>): PlayChartData<T> {\r\n if (dataView) {\r\n if (this.ridiculousFlagForPersistProperties && dataView.metadata) {\r\n // BUG FIX: customer feedback has been strong that we should always default to show the last frame.\r\n // This is essential for dashboard tiles to refresh properly.\r\n\r\n // Only copy frameIndex since it is the only property using persistProperties\r\n //let objectProps = getObjectProperties(dataView.metadata);\r\n //playData.currentFrameIndex = objectProps.currentFrameIndex;\r\n \r\n // Turn off the flag that was set by our persistProperties call\r\n this.ridiculousFlagForPersistProperties = false;\r\n return this.playData;\r\n }\r\n else if (dataView.matrix || dataView.categorical) {\r\n this.playData = PlayChart.converter<T>(dataView, visualConverter);\r\n }\r\n else {\r\n this.playData = PlayChart.getDefaultPlayData<T>();\r\n }\r\n }\r\n else {\r\n this.playData = PlayChart.getDefaultPlayData<T>();\r\n }\r\n\r\n // Next render should be a full one.\r\n this.lastViewport = undefined;\r\n\r\n return this.playData;\r\n }\r\n\r\n public render<TViewModel>(suppressAnimations: boolean, viewModel: TViewModel, viewport: IViewport, margin: IMargin): PlayChartRenderResult<T, TViewModel> {\r\n let playData = this.playData;\r\n\r\n let resized = !this.lastViewport || (this.lastViewport.height !== viewport.height || this.lastViewport.width !== viewport.width);\r\n this.lastViewport = viewport;\r\n\r\n if (resized)\r\n this.stop();\r\n\r\n if (!playData)\r\n return;\r\n\r\n let playViewModel: PlayChartViewModel<T, TViewModel> = {\r\n data: this.playData,\r\n viewModel: viewModel,\r\n viewport: viewport,\r\n };\r\n\r\n let hasSelection = false;\r\n if (this.interactivityService) {\r\n let data = <PlayableChartData>playData.currentViewModel;\r\n this.interactivityService.applySelectionStateToData(data.dataPoints);\r\n hasSelection = this.interactivityService.hasSelection();\r\n }\r\n\r\n this.updateCallout(viewport, margin);\r\n\r\n if (this.playControl && resized) {\r\n this.playControl.rebuild(playData, viewport);\r\n }\r\n\r\n let allDataPoints = playData.allViewModels.map((vm) => vm.dataPoints);\r\n let flatAllDataPoints = _.flatten<SelectableDataPoint>(allDataPoints);\r\n \r\n // NOTE: Return data points to keep track of current selected bubble even if it drops out for a few frames\r\n return {\r\n allDataPoints: flatAllDataPoints,\r\n viewModel: playViewModel,\r\n };\r\n }\r\n\r\n private updateCallout(viewport: IViewport, margin: IMargin): void {\r\n let playData = this.playData;\r\n let frameData = playData.frameData;\r\n let currentFrameIndex = playData.currentFrameIndex;\r\n let height = viewport.height;\r\n let plotAreaHeight = height - margin.top - margin.bottom;\r\n let width = viewport.width;\r\n let plotAreaWidth = width - margin.left - margin.right;\r\n\r\n let calloutDimension = Math.min(height, width * 1.3); //1.3 to compensate for tall, narrow-width viewport\r\n let fontSize = Math.max(12, Math.round(calloutDimension / 7));\r\n fontSize = Math.min(fontSize, 70);\r\n let textProperties = {\r\n fontSize: jsCommon.PixelConverter.toString(fontSize),\r\n text: frameData[currentFrameIndex].text || \"\",\r\n fontFamily: Font.Family.regular.css,\r\n };\r\n let textHeight = TextMeasurementService.estimateSvgTextHeight(textProperties) - TextMeasurementService.estimateSvgTextBaselineDelta(textProperties);\r\n\r\n let calloutData: string[] = [];\r\n if (currentFrameIndex < frameData.length && currentFrameIndex >= 0 && textHeight < plotAreaHeight) {\r\n let maxTextWidth = plotAreaWidth - (2 * PlayAxis.calloutOffsetMultiplier * textHeight);\r\n let calloutText = TextMeasurementService.getTailoredTextOrDefault(textProperties, maxTextWidth);\r\n calloutData = [calloutText];\r\n }\r\n\r\n let callout = this.svg.selectAll(PlayAxis.PlayCallout.selector).data(calloutData);\r\n\r\n callout.enter()\r\n .append('text')\r\n .classed(PlayAxis.PlayCallout.class, true);\r\n\r\n callout\r\n .text((d: string) => d)\r\n .attr({\r\n x: plotAreaWidth - PlayAxis.calloutOffsetMultiplier * textHeight,\r\n y: () => textHeight,\r\n })\r\n .style({\r\n 'font-size': fontSize + 'px',\r\n 'text-anchor': 'end',\r\n });\r\n\r\n callout.exit().remove();\r\n }\r\n\r\n public play(): void {\r\n let playData = this.playData;\r\n\r\n if (this.isPlaying) {\r\n this.stop();\r\n }\r\n else if (this.playControl) {\r\n this.isPlaying = true;\r\n this.playControl.play();\r\n\r\n let indexToShow = Math.round(this.playControl.getCurrentIndex());\r\n if (indexToShow >= playData.allViewModels.length - 1) {\r\n playData.currentFrameIndex = -1;\r\n } else {\r\n playData.currentFrameIndex = indexToShow - 1;\r\n }\r\n\r\n this.playNextFrame(playData);\r\n }\r\n }\r\n\r\n private playNextFrame(playData: PlayChartData<T>, startFrame?: number, endFrame?: number): void {\r\n if (!this.isPlaying) {\r\n this.stop();\r\n return;\r\n }\r\n\r\n let nextFrame = playData.currentFrameIndex + 1;\r\n if (startFrame != null && endFrame != null) {\r\n nextFrame = Math.abs(endFrame - startFrame + 1);\r\n startFrame = nextFrame;\r\n }\r\n\r\n if (nextFrame < playData.allViewModels.length && nextFrame > -1) {\r\n playData.currentFrameIndex = nextFrame;\r\n playData.currentViewModel = playData.allViewModels[nextFrame];\r\n\r\n this.renderDelegate(playData.currentViewModel);\r\n this.playControl.setFrame(nextFrame);\r\n\r\n if (nextFrame < playData.allViewModels.length) {\r\n window.setTimeout(() => {\r\n this.playNextFrame(playData, startFrame, endFrame);\r\n }, PlayChart.FrameStepDuration);\r\n }\r\n } else {\r\n this.stop();\r\n }\r\n }\r\n\r\n public stop(): void {\r\n if (this.playControl)\r\n this.playControl.pause();\r\n this.isPlaying = false;\r\n }\r\n\r\n public remove(): void {\r\n if (this.playControl)\r\n this.playControl.remove();\r\n d3.selectAll(PlayAxis.PlayCallout.selector).remove();\r\n\r\n // TODO: remove any tracelines\r\n }\r\n\r\n public setRenderFunction(fn: PlayChartRenderFrameDelegate<T>): void {\r\n this.renderDelegate = fn;\r\n }\r\n\r\n public getCartesianExtents(existingExtents: CartesianExtents, getExtents: (T) => CartesianExtents): CartesianExtents {\r\n if (this.playData && this.playData.allViewModels && this.playData.allViewModels.length > 0) {\r\n return PlayChart.getMinMaxForAllFrames(this.playData, getExtents);\r\n }\r\n\r\n return existingExtents;\r\n }\r\n\r\n public setPlayControlPosition(playControlLayout: IRect): void {\r\n if (this.playControl) {\r\n let container = this.playControl.getContainer();\r\n container.css('left', playControlLayout.left ? playControlLayout.left + 'px' : '');\r\n container.css('top', playControlLayout.top ? playControlLayout.top + 'px' : '');\r\n // width is set elsewhere (calculateSliderWidth), where we check for playaxis tick label overflow.\r\n // height is constant\r\n }\r\n }\r\n\r\n private moveToFrameAndRender(frameIndex: number): void {\r\n let playData = this.playData;\r\n\r\n this.isPlaying = true;\r\n\r\n if (playData && frameIndex >= 0 && frameIndex < playData.allViewModels.length && frameIndex !== playData.currentFrameIndex) {\r\n playData.currentFrameIndex = frameIndex;\r\n let data = playData.allViewModels[frameIndex];\r\n playData.currentViewModel = data;\r\n this.renderDelegate(data);\r\n }\r\n\r\n this.isPlaying = false;\r\n }\r\n\r\n public isCurrentlyPlaying(): boolean {\r\n return this.isPlaying;\r\n }\r\n }\r\n\r\n class PlayControl {\r\n private playAxisContainer: JQuery;\r\n private playButton: JQuery;\r\n private playButtonCircle: JQuery;\r\n private slider: JQuery;\r\n private noUiSlider: noUiSlider.noUiSlider;\r\n private renderDelegate: (index: number) => void;\r\n private isMobileChart: boolean;\r\n\r\n private static SliderMarginLeft = 24 + 10 * 2; // playButton width + playButton margin * 2\r\n private static SliderMarginRight = 20;\r\n private static SliderMaxMargin = 100;\r\n private static PlayControlHeight = 80; //tuned for two rows of label text to be perfectly clipped before the third row. Dependent on current font sizes in noui-pips.css\r\n \r\n constructor(element: JQuery, renderDelegate: (index: number) => void, isMobileChart: boolean) {\r\n this.isMobileChart = isMobileChart;\r\n this.createSliderDOM(element);\r\n this.renderDelegate = renderDelegate;\r\n }\r\n\r\n public getContainer(): JQuery {\r\n return this.playAxisContainer;\r\n }\r\n\r\n public remove(): void {\r\n if (this.playAxisContainer)\r\n this.playAxisContainer.remove();\r\n }\r\n\r\n public pause(): void {\r\n this.playButton.removeClass('pause').addClass('play');\r\n }\r\n\r\n public play(): void {\r\n this.playButton.removeClass('play').addClass('pause');\r\n }\r\n\r\n public getCurrentIndex(): number {\r\n // TODO: round() necessary?\r\n return Math.round(<number>this.noUiSlider.get());\r\n }\r\n\r\n public onPlay(handler: (eventObject: JQueryEventObject) => void): void {\r\n this.playButtonCircle.off('click');\r\n this.playButtonCircle.on('click', handler);\r\n }\r\n\r\n public setFrame(frameIndex: number): void {\r\n this.noUiSlider.set([frameIndex]);\r\n };\r\n\r\n public rebuild<T extends PlayableChartData>(playData: PlayChartData<T>, viewport: IViewport): void {\r\n let slider = this.slider;\r\n\r\n // re-create the slider\r\n if (this.noUiSlider)\r\n this.noUiSlider.destroy();\r\n\r\n let labelData = playData.labelData;\r\n let sliderSize: SliderSize = PlayControl.calucalateSliderSize(labelData, viewport.width);\r\n var container = this.getContainer();\r\n if(sliderSize.marginLeft > PlayControl.SliderMarginLeft) {\r\n container.css(\"padding-left\", sliderSize.marginLeft - PlayControl.SliderMarginLeft + \"px\");\r\n container.css(\"box-sizing\", \"border-box\"); \r\n }\r\n let skipStep: number = this.updateSliderControl(playData, sliderSize.width);\r\n let width: number = PlayControl.adjustWidthRegardingLastItem(labelData, skipStep, sliderSize.width);\r\n this.slider.css('width', width + 'px');\r\n\r\n this.noUiSlider.on('slide', () => {\r\n let indexToShow = this.getCurrentIndex();\r\n this.renderDelegate(indexToShow);\r\n });\r\n \r\n let nextLabelIndex = 0;\r\n // update the width and margin-left to center up each label\r\n $('.noUi-value', slider).each((idx, elem) => {\r\n let actualWidth = labelData.labelInfo[nextLabelIndex].labelWidth;\r\n $(elem).width(actualWidth);\r\n $(elem).css('margin-left', -actualWidth / 2 + 'px');\r\n nextLabelIndex += skipStep;\r\n });\r\n }\r\n\r\n /**\r\n * Updates slider control regarding new data.\r\n * @param playData {PlayChartData<T>} Data for the slider.\r\n * @param sliderWidth {number} Slider width. \r\n * @returns {number} skip mode for the slider.\r\n */\r\n private updateSliderControl<T extends PlayableChartData>(playData: PlayChartData<T>, sliderWidth: number): number {\r\n let labelData = playData.labelData;\r\n let sliderElement: HTMLElement = this.slider.get(0);\r\n let numFrames = playData.frameData.length;\r\n let options: noUiSlider.Options = {\r\n start: numFrames === 0 ? 0 : playData.currentFrameIndex,\r\n step: 1,\r\n range: {\r\n min: 0,\r\n max: numFrames === 0 ? 0 : numFrames - 1\r\n }\r\n };\r\n let pipOptions: noUiSlider.PipsOptions = null;\r\n let skipMode: number = 0;\r\n \r\n if (numFrames > 0) {\r\n let filterPipLabels = PlayControl.createPipsFilterFn(playData, sliderWidth, labelData);\r\n skipMode = filterPipLabels.skipStep;\r\n pipOptions = {\r\n mode: 'steps',\r\n density: Math.ceil(100 / numFrames), //only draw ticks where we have labels\r\n format: {\r\n to: (index) => playData.frameData[index].escapedText,\r\n from: (value) => playData.frameData.indexOf(value),\r\n },\r\n filter: filterPipLabels.filter,\r\n };\r\n }\r\n options.pips = pipOptions;\r\n noUiSlider.create(sliderElement, options);\r\n this.noUiSlider = (<noUiSlider.Instance>sliderElement).noUiSlider;\r\n\r\n return skipMode;\r\n }\r\n \r\n private static createPipsFilterFn<T extends PlayableChartData>(playData: PlayChartData<T>, sliderWidth: number, labelData: PlayAxisTickLabelData): SliderPipFilter {\r\n let maxLabelWidth = _.max(_.map(labelData.labelInfo, (l) => l.labelWidth));\r\n\r\n let pipSize = 1; //0=hide, 1=large, 2=small\r\n let skipMode = 1;\r\n let maxAllowedLabelWidth = playData.frameData.length > 1 ? sliderWidth / (playData.frameData.length - 1) : sliderWidth;\r\n let widthRatio = maxLabelWidth / maxAllowedLabelWidth;\r\n\r\n if (widthRatio > 1.25) {\r\n skipMode = Math.ceil(widthRatio);\r\n pipSize = 2;\r\n }\r\n else if (widthRatio > 1.0 || labelData.anyWordBreaks) {\r\n // wordbreak line wrapping is automatic, and we don't reserve enough space to show two lines of text with the larger font\r\n pipSize = 2;\r\n }\r\n\r\n let filterPipLabels = (index: any, type: any) => {\r\n // noUiSlider will word break / wrap to new lines, so max width is the max word length\r\n if (index % skipMode === 0) {\r\n return pipSize;\r\n }\r\n return 0; //hide\r\n };\r\n \r\n\r\n return { filter: filterPipLabels, skipStep: skipMode };\r\n }\r\n\r\n /**\r\n * Adjusts width regarding the last visible label size.\r\n * @param labelData label data for chart.\r\n * @param skipMode skip factor.\r\n * @param sliderWidth current width of slider.\r\n */\r\n private static adjustWidthRegardingLastItem(labelData: PlayAxisTickLabelData, skipMode: number, sliderWidth): number {\r\n let labelLenth: number = labelData.labelInfo.length;\r\n let lastVisibleItemIndex: number = Math.floor((labelLenth - 1) / skipMode) * skipMode;\r\n let distanceToEnd = sliderWidth + PlayControl.SliderMarginRight - (sliderWidth / labelLenth * (lastVisibleItemIndex + 1));\r\n let lastItemWidth = labelData.labelInfo[lastVisibleItemIndex].labelWidth;\r\n let requiredWidth = lastItemWidth / 2 - distanceToEnd;\r\n if (requiredWidth > 0) {\r\n let maxMargin = PlayControl.SliderMaxMargin - PlayControl.SliderMarginRight;\r\n requiredWidth = requiredWidth > maxMargin ? maxMargin : requiredWidth;\r\n return sliderWidth - requiredWidth;\r\n }\r\n\r\n return sliderWidth;\r\n }\r\n\r\n private createSliderDOM(element: JQuery): void {\r\n this.playAxisContainer = $('<div class=\"play-axis-container\"></div>')\r\n .appendTo(element)\r\n .css('height', PlayControl.PlayControlHeight + 'px');\r\n\r\n this.playButtonCircle = $('<div class=\"button-container\"></div>')\r\n .appendTo(this.playAxisContainer);\r\n\r\n if (this.isMobileChart) {\r\n this.playButtonCircle.addClass('mobile-button-container');\r\n }\r\n\r\n this.playButton = $('<div class=\"play\"></div>')\r\n .appendTo(this.playButtonCircle);\r\n\r\n this.slider = $('<div class=\"sliders\"></div>')\r\n .appendTo(this.playAxisContainer);\r\n }\r\n\r\n private static calucalateSliderSize(labelData: PlayAxisTickLabelData, viewportWidth: number): SliderSize {\r\n let leftMargin = 0;\r\n if (!_.isEmpty(labelData.labelInfo)) {\r\n leftMargin = _.first(labelData.labelInfo).labelWidth / 2;\r\n }\r\n\r\n let sliderLeftMargin = Math.max(leftMargin, PlayControl.SliderMarginLeft);\r\n sliderLeftMargin = Math.min(PlayControl.SliderMaxMargin, sliderLeftMargin);\r\n let sliderWidth = Math.max((viewportWidth - sliderLeftMargin - PlayControl.SliderMarginRight), 1);\r\n\r\n return { width: sliderWidth, marginLeft: sliderLeftMargin };\r\n }\r\n }\r\n\r\n export module PlayChart {\r\n // TODO: add speed control to property pane\r\n // NOTE: current noUiSlider speed is a CSS property of the class .noUi-state-tap, and also is hard-coded in noUiSlider.js. We'll need to add a new create param for transition time.\r\n // 800ms matches Silverlight frame speed\r\n export const FrameStepDuration = 800;\r\n export const FrameAnimationDuration = 750; //leave 50ms for the traceline animation - to avoid being cancelled. TODO: add a proper wait impl.\r\n\r\n export const ClassName = 'playChart';\r\n\r\n export function convertMatrixToCategorical(sourceDataView: DataView, frame: number): DataView {\r\n debug.assert(sourceDataView && sourceDataView.metadata && !!sourceDataView.matrix, 'sourceDataView && sourceDataView.metadata && !!sourceDataView.matrix');\r\n\r\n let matrix: DataViewMatrix = sourceDataView.matrix;\r\n\r\n let categorical: DataViewCategorical = {\r\n categories: [],\r\n values: powerbi.data.DataViewTransform.createValueColumns()\r\n };\r\n\r\n // If we don't have enough fields, just return early. We need at least:\r\n // 2 rows and 1 column: (play->category, measures)\r\n // or:\r\n // 1 row and 2 columns: (play, series->measures)\r\n if ((_.isEmpty(matrix.columns.levels)) || (matrix.rows.levels.length < 2 && matrix.columns.levels.length < 2)) {\r\n return { metadata: sourceDataView.metadata, categorical: categorical };\r\n }\r\n\r\n const CategoryRowLevelsStartingIndex = 1;\r\n\r\n let categories: DataViewCategoryColumn[] = [];\r\n // Ignore the play field (first row level); the Category field(s) starts from the second row group (play->category) or we don't use this variable (categories)\r\n // Note related to VSTS 6986788 and 6885783: there are multiple levels for category during drilldown and expand.\r\n for (let i = CategoryRowLevelsStartingIndex, ilen = matrix.rows.levels.length; i < ilen; i++) {\r\n // Consider: Change the following debug.assert() to retail.assert() when the infrastructure is ready.\r\n debug.assert(matrix.rows.levels[i].sources.length > 0, 'The sources is always expected to contain at least one metadata column.');\r\n\r\n let sourceColumn: DataViewMetadataColumn = matrix.rows.levels[i].sources[0];\r\n categories.push({\r\n source: sourceColumn,\r\n values: [],\r\n identity: [],\r\n objects: undefined,\r\n });\r\n }\r\n\r\n // Matrix shape for Play:\r\n //\r\n // Series1 | Series2 | ...\r\n // --------- -------- \r\n // Play1 | Category1 | values | values\r\n // | Category2 | values | values\r\n // | ...\r\n // Play2 | Category1 | values | values\r\n // | Category2 | values | values\r\n // ...\r\n // Or, with drilldown / expand on Category (e.g. expand Country -> Region):\r\n // Series1 | Series2 | ...\r\n // --------- -------- \r\n // Play1 | Country1 | Region1 | values | values\r\n // | | Region2 | values | values\r\n // | Country2 | Region3 | values | values\r\n // | | Region4 | values | values\r\n // | ...\r\n // Play2 | Country1 | Region1 | values | values\r\n // | | Region2 | values | values\r\n // | Country2 | Region3 | values | values\r\n // | | Region4 | values | values\r\n\r\n // we are guaranteed at least one row (it will be the Play field)\r\n let hasRowChildren = !_.isEmpty(matrix.rows.root.children);\r\n let hasColChildren = !_.isEmpty(matrix.columns.root.children);\r\n let hasSeries = matrix.columns.levels.length > 1 && hasColChildren;\r\n let hasPlayAndCategory = matrix.rows.levels.length > 1 && hasRowChildren;\r\n\r\n if (hasSeries && !hasPlayAndCategory) {\r\n // set categories to undefined\r\n categorical.categories = undefined;\r\n\r\n let node = matrix.columns.root;\r\n categorical.values.source = matrix.columns.levels[0].sources[0];\r\n let columnLength = matrix.valueSources.length;\r\n for (let i = 0, len = node.children.length; i < len; i++) {\r\n // add all the value sources for each series\r\n let columnNode = node.children[i];\r\n for (let j = 0; j < columnLength; j++) {\r\n // DEFECT 6547170: groupName must be null to turn into (Blank), undefined will use the field name\r\n let source = <any>_.create(matrix.valueSources[j], { groupName: columnNode.value === undefined ? null : columnNode.value });\r\n let dataViewColumn: DataViewValueColumn = {\r\n identity: columnNode.identity,\r\n values: [],\r\n source: source\r\n };\r\n categorical.values.push(dataViewColumn);\r\n }\r\n }\r\n\r\n // Copying the values from matrix intersection to the categorical values columns...\r\n // Given that this is the case without category levels, the matrix intersection values are stored in playFrameNode.values\r\n let playFrameNode = matrix.rows.root.children[frame];\r\n let matrixIntersectionValues = playFrameNode.values;\r\n for (var i = 0, len = node.children.length; i < len; i++) {\r\n for (let j = 0; j < columnLength; j++) {\r\n categorical.values[i * columnLength + j].values.push(matrixIntersectionValues[i * columnLength + j].value);\r\n }\r\n }\r\n }\r\n else if (hasSeries && hasRowChildren) {\r\n // series and categories\r\n let playFrameNode = matrix.rows.root.children[frame];\r\n \r\n // create the categories first\r\n DataViewMatrixUtils.forEachLeafNode(playFrameNode.children, (categoryGroupLeafNode, index, categoryHierarchicalGroupNodes) => {\r\n addMatrixHierarchicalGroupToCategories(categoryHierarchicalGroupNodes, categories);\r\n });\r\n categorical.categories = categories;\r\n\r\n // now add the series info\r\n categorical.values.source = matrix.columns.levels[0].sources[0];\r\n let nodeQueue = [];\r\n let columnNode = matrix.columns.root;\r\n let seriesIndex = -1;\r\n while (columnNode) {\r\n if (columnNode.children && columnNode.children[0].children) {\r\n for (let j = 0, jlen = columnNode.children.length; j < jlen; j++) {\r\n // each of these is a \"series\"\r\n nodeQueue.push(columnNode.children[j]);\r\n }\r\n } else if (columnNode.children && playFrameNode.children) {\r\n // Processing a single series under here, push all the value sources for every series.\r\n var columnLength = columnNode.children.length;\r\n for (let j = 0; j < columnLength; j++) {\r\n let source = <any>_.create(matrix.valueSources[j], { groupName: columnNode.value });\r\n let dataViewColumn: DataViewValueColumn = {\r\n identity: columnNode.identity,\r\n values: [],\r\n source: source,\r\n };\r\n categorical.values.push(dataViewColumn);\r\n }\r\n\r\n // Copying the values from matrix intersection to the categorical values columns...\r\n DataViewMatrixUtils.forEachLeafNode(playFrameNode.children, leafNode => {\r\n for (let j = 0; j < columnLength; j++) {\r\n categorical.values[seriesIndex * columnLength + j].values.push(leafNode.values[seriesIndex * columnLength + j].value);\r\n }\r\n });\r\n }\r\n\r\n if (nodeQueue.length > 0) {\r\n columnNode = nodeQueue[0];\r\n nodeQueue = nodeQueue.splice(1);\r\n seriesIndex++;\r\n } else\r\n columnNode = undefined;\r\n }\r\n }\r\n else if (hasPlayAndCategory) {\r\n // no series, just play and category\r\n let playFrameNode = matrix.rows.root.children[frame];\r\n let measureLength = matrix.valueSources.length;\r\n for (let j = 0; j < measureLength; j++) {\r\n let dataViewColumn: DataViewValueColumn = {\r\n identity: undefined,\r\n values: [],\r\n source: matrix.valueSources[j]\r\n };\r\n categorical.values.push(dataViewColumn);\r\n }\r\n\r\n DataViewMatrixUtils.forEachLeafNode(playFrameNode.children, (categoryGroupLeafNode, index, categoryHierarchicalGroupNodes) => {\r\n addMatrixHierarchicalGroupToCategories(categoryHierarchicalGroupNodes, categories);\r\n\r\n // Copying the values from matrix intersection to the categorical values columns...\r\n for (let j = 0; j < measureLength; j++) {\r\n categorical.values[j].values.push(categoryGroupLeafNode.values[j].value);\r\n }\r\n });\r\n\r\n categorical.categories = categories;\r\n }\r\n\r\n // the visual code today expects only 1 category column, hence apply DataViewConcatenateCategoricalColumns\r\n return DataViewConcatenateCategoricalColumns.applyToPlayChartCategorical(sourceDataView.metadata, scatterChartCapabilities.objects, 'Category', categorical);\r\n }\r\n\r\n function addMatrixHierarchicalGroupToCategories(sourceCategoryHierarchicalGroupNodes: DataViewMatrixNode[], destinationCategories: DataViewCategoryColumn[]): void {\r\n debug.assertNonEmpty(sourceCategoryHierarchicalGroupNodes, 'sourceCategoryHierarchicalGroupNodes');\r\n debug.assertNonEmpty(destinationCategories, 'destinationCategories');\r\n debug.assert(sourceCategoryHierarchicalGroupNodes.length === destinationCategories.length, 'pre-condition: there should be one category column per matrix row level for Category.');\r\n\r\n // Note: Before the Categorical concatenation logic got added to this playChart logic, the code did NOT populate\r\n // the ***DataViewCategoryColumn.identityFields*** property, and the playChart visual code does not seem to need it.\r\n // If we do want to populate that property, we might want to do reuse data.ISQExpr[] across nodes as much as possible \r\n // because all the child nodes under a given parent will have the exact same identityFields value, and a lot of \r\n // DataViewCategory objects can get created for a given playChart.\r\n\r\n let identity: DataViewScopeIdentity = sourceCategoryHierarchicalGroupNodes[0].identity;\r\n\r\n if (sourceCategoryHierarchicalGroupNodes.length > 1) {\r\n // if the hierarchical group has more than 1 level, create a composite identity from the nodes\r\n let identityExpr = <SQExpr>identity.expr;\r\n for (let i = 1, ilen = sourceCategoryHierarchicalGroupNodes.length; i < ilen; i++) {\r\n let identityExprToAdd = <SQExpr>sourceCategoryHierarchicalGroupNodes[i].identity.expr;\r\n identityExpr = SQExprBuilder.and(identityExpr, identityExprToAdd);\r\n }\r\n identity = createDataViewScopeIdentity(identityExpr);\r\n }\r\n\r\n // add the Category value of each matrix node into its respective category column\r\n for (let j = 0, jlen = destinationCategories.length; j < jlen; j++) {\r\n destinationCategories[j].identity.push(identity);\r\n\r\n let node = sourceCategoryHierarchicalGroupNodes[j];\r\n destinationCategories[j].values.push(node.value);\r\n }\r\n }\r\n\r\n function getObjectProperties(dataViewMetadata: DataViewMetadata, dataLabelsSettings?: PointDataLabelsSettings): PlayObjectProperties {\r\n let objectProperties: PlayObjectProperties = {};\r\n\r\n if (dataViewMetadata && dataViewMetadata.objects) {\r\n let objects = dataViewMetadata.objects;\r\n // TODO: remove?\r\n objectProperties.currentFrameIndex = DataViewObjects.getValue(objects, scatterChartProps.currentFrameIndex.index, null);\r\n }\r\n return objectProperties;\r\n }\r\n\r\n export function converter<T extends PlayableChartData>(dataView: DataView, visualConverter: VisualDataConverterDelegate<T>): PlayChartData<T> {\r\n let dataViewMetadata: DataViewMetadata = dataView.metadata;\r\n let dataLabelsSettings = dataLabelUtils.getDefaultPointLabelSettings();\r\n let objectProperties = getObjectProperties(dataViewMetadata, dataLabelsSettings);\r\n\r\n let allViewModels: T[] = [];\r\n let frameKeys: PlayChartFrameData[] = [];\r\n let convertedData: T = undefined;\r\n let matrixRows = dataView.matrix.rows;\r\n let rowChildrenLength = matrixRows.root.children ? matrixRows.root.children.length : 0;\r\n let keySourceColumn: DataViewMetadataColumn;\r\n if (dataView.matrix && rowChildrenLength > 0 && !_.isEmpty(matrixRows.levels) && !_.isEmpty(matrixRows.levels[0].sources) ) {\r\n keySourceColumn = matrixRows.levels[0].sources[0];\r\n\r\n // TODO: this should probably defer to the visual which knows how to format the categories.\r\n let formatString = valueFormatter.getFormatString(keySourceColumn, scatterChartProps.general.formatString);\r\n let keyFormatter: IValueFormatter;\r\n if (keySourceColumn.type.numeric) {\r\n // use value range, not actual values\r\n let valueRange = Math.abs(matrixRows.root.children[rowChildrenLength - 1].value - matrixRows.root.children[0].value);\r\n keyFormatter = valueFormatter.create({\r\n format: formatString,\r\n value: valueRange,\r\n value2: 0,\r\n });\r\n } else {\r\n keyFormatter = valueFormatter.createDefaultFormatter(formatString, true);\r\n }\r\n\r\n for (let i = 0, len = rowChildrenLength; i < len; i++) {\r\n let key = matrixRows.root.children[i];\r\n let frameLabelText = keyFormatter.format(key.value);\r\n // escaped html\r\n let frameLabelHtml = $(\"<div/>\").text(frameLabelText).html();\r\n frameKeys.push({ escapedText: frameLabelHtml, text: frameLabelText });\r\n \r\n let dataViewCategorical = convertMatrixToCategorical(dataView, i);\r\n let frameInfo = { label: frameLabelHtml, column: keySourceColumn };\r\n convertedData = visualConverter(dataViewCategorical, frameInfo);\r\n allViewModels.push(convertedData);\r\n }\r\n }\r\n else {\r\n let dataViewCategorical = convertMatrixToCategorical(dataView, 0);\r\n convertedData = visualConverter(dataViewCategorical);\r\n allViewModels.push(convertedData);\r\n }\r\n \r\n // NOTE: currentViewModel is already set to the last frame\r\n objectProperties.currentFrameIndex = frameKeys.length - 1;\r\n\r\n return {\r\n allViewModels: allViewModels,\r\n currentViewModel: convertedData,\r\n frameData: frameKeys,\r\n currentFrameIndex: objectProperties.currentFrameIndex,\r\n labelData: getLabelData(frameKeys, keySourceColumn),\r\n };\r\n }\r\n\r\n export function getDefaultPlayData<T extends PlayableChartData>(): PlayChartData<T> {\r\n let defaultData: PlayChartData<T> = {\r\n frameData: [],\r\n allViewModels: [],\r\n currentFrameIndex: 0,\r\n currentViewModel: undefined,\r\n labelData: {\r\n anyWordBreaks: false,\r\n labelInfo: [],\r\n },\r\n };\r\n return defaultData;\r\n }\r\n\r\n export function getMinMaxForAllFrames<T extends PlayableChartData>(playData: PlayChartData<T>, getExtents: (T) => CartesianExtents): CartesianExtents {\r\n let extents: CartesianExtents = {\r\n minY: 0,\r\n maxY: 10,\r\n minX: 0,\r\n maxX: 10,\r\n };\r\n\r\n if (playData.allViewModels && playData.allViewModels.length > 0) {\r\n extents.minY = extents.minX = Number.MAX_VALUE;\r\n extents.maxY = extents.maxX = Number.MIN_VALUE;\r\n for (let i = 0, len = playData.allViewModels.length; i < len; i++) {\r\n let data = playData.allViewModels[i];\r\n let e = getExtents(data);\r\n\r\n // NOTE: D3.min/max handle undefined and NaN nicely, as opposed to Math.min/max\r\n extents = {\r\n minY: d3.min([e.minY, extents.minY]),\r\n maxY: d3.max([e.maxY, extents.maxY]),\r\n minX: d3.min([e.minX, extents.minX]),\r\n maxX: d3.max([e.maxX, extents.maxX]),\r\n };\r\n }\r\n }\r\n\r\n return extents;\r\n }\r\n\r\n function getLabelData(keys: PlayChartFrameData[], keyColumn?: DataViewMetadataColumn): PlayAxisTickLabelData {\r\n let textProperties: TextProperties = {\r\n fontFamily: Font.Family.regular.css,\r\n fontSize: jsCommon.PixelConverter.toString(14),\r\n };\r\n\r\n let labelInfo: PlayAxisTickLabelInfo[] = [];\r\n let anyWordBreaks = false;\r\n for (let key of keys) {\r\n let labelWidth = jsCommon.WordBreaker.getMaxWordWidth(key.escapedText, TextMeasurementService.measureSvgTextWidth, textProperties);\r\n // TODO: Why isn't this last part included in hasBreakers()?\r\n anyWordBreaks = anyWordBreaks || jsCommon.WordBreaker.hasBreakers(key.escapedText) || (key.escapedText).indexOf('-') > -1; \r\n labelInfo.push({ label: key.escapedText, labelWidth });\r\n }\r\n\r\n return {\r\n labelInfo: labelInfo,\r\n anyWordBreaks: anyWordBreaks,\r\n labelFieldName: keyColumn && keyColumn.displayName,\r\n };\r\n }\r\n\r\n export function isDataViewPlayable(dataView: DataView, playRole: string = 'Play'): boolean {\r\n debug.assertValue(dataView, 'dataView');\r\n\r\n let firstRowSourceRoles = dataView.matrix &&\r\n dataView.matrix.rows &&\r\n dataView.matrix.rows.levels &&\r\n dataView.matrix.rows.levels[0] &&\r\n dataView.matrix.rows.levels[0].sources &&\r\n dataView.matrix.rows.levels[0].sources[0] &&\r\n dataView.matrix.rows.levels[0].sources[0].roles;\r\n\r\n return firstRowSourceRoles && firstRowSourceRoles[playRole];\r\n }\r\n\r\n /** Render trace-lines for selected data points. */\r\n export function renderTraceLines(allDataPoints: SelectableDataPoint[], traceLineRenderer: ITraceLineRenderer, shouldAnimate: boolean): void {\r\n let selectedDataPoints = _.filter(allDataPoints, (d: SelectableDataPoint) => d.selected);\r\n selectedDataPoints = _.uniq(selectedDataPoints, (d: SelectableDataPoint) => d.identity.getKey());\r\n traceLineRenderer.render(selectedDataPoints, shouldAnimate);\r\n }\r\n }\r\n}\n\n\n/** WEBPACK FOOTER **\n ** ./src/Clients/Visuals/cartesian/playChart.ts\n **/","/*\r\n * Power BI Visualizations\r\n *\r\n * Copyright (c) Microsoft Corporation\r\n * All rights reserved. \r\n * MIT License\r\n *\r\n * Permission is hereby granted, free of charge, to any person obtaining a copy\r\n * of this software and associated documentation files (the \"\"Software\"\"), to deal\r\n * in the Software without restriction, including without limitation the rights\r\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\r\n * copies of the Software, and to permit persons to whom the Software is\r\n * furnished to do so, subject to the following conditions:\r\n * \r\n * The above copyright notice and this permission notice shall be included in \r\n * all copies or substantial portions of the Software.\r\n * \r\n * THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR \r\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, \r\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE \r\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER \r\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\r\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\r\n * THE SOFTWARE.\r\n */\r\n\r\n/// <reference path=\"../_references.ts\"/>\r\n\r\nmodule powerbi.visuals {\r\n import PixelConverter = jsCommon.PixelConverter;\r\n import SlicerOrientation = slicerOrientation.Orientation;\r\n import SemanticFilter = powerbi.data.SemanticFilter;\r\n import SQExpr = powerbi.data.SQExpr;\r\n\r\n export interface CheckboxStyle {\r\n transform: string;\r\n 'transform-origin': string;\r\n 'font-size': string;\r\n }\r\n\r\n export class VerticalSlicerRenderer implements ISlicerRenderer, SlicerValueHandler {\r\n private element: JQuery;\r\n private currentViewport: IViewport;\r\n private dataView: DataView;\r\n private header: D3.Selection;\r\n private body: D3.Selection;\r\n private container: D3.Selection;\r\n private listView: IListView;\r\n private data: SlicerData;\r\n private settings: SlicerSettings;\r\n private behavior: IInteractiveBehavior;\r\n private hostServices: IVisualHostServices;\r\n private textProperties: TextProperties = {\r\n 'fontFamily': Font.Family.regular.css,\r\n 'fontSize': '14px',\r\n };\r\n private domHelper: SlicerUtil.DOMHelper;\r\n\r\n constructor(options?: SlicerConstructorOptions) {\r\n if (options) {\r\n this.behavior = options.behavior;\r\n }\r\n this.domHelper = options.domHelper;\r\n }\r\n\r\n // SlicerValueHandler\r\n public getDefaultValue(): data.SQConstantExpr {\r\n if (this.data && this.data.defaultValue)\r\n return <data.SQConstantExpr>this.data.defaultValue.value;\r\n }\r\n\r\n public getIdentityFields(): SQExpr[] {\r\n return SlicerUtil.DefaultValueHandler.getIdentityFields(this.dataView);\r\n }\r\n\r\n public getUpdatedSelfFilter(searchKey: string): SemanticFilter {\r\n if (_.isEmpty(searchKey) || this.data.searchKey === searchKey)\r\n return;\r\n \r\n let metadata = this.dataView && this.dataView.metadata;\r\n if (!metadata)\r\n return;\r\n\r\n debug.assert(_.size(metadata.columns) === 1, 'slicer should not have more than one column based on the capability');\r\n\r\n let column = _.first(metadata.columns);\r\n if (column && column.expr)\r\n return SlicerUtil.getContainsFilter(<SQExpr>column.expr, searchKey);\r\n }\r\n\r\n public init(slicerInitOptions: SlicerInitOptions): IInteractivityService {\r\n this.element = slicerInitOptions.visualInitOptions.element;\r\n this.currentViewport = slicerInitOptions.visualInitOptions.viewport;\r\n let hostServices = this.hostServices = slicerInitOptions.visualInitOptions.host;\r\n\r\n let settings = this.settings = Slicer.DefaultStyleProperties();\r\n let domHelper = this.domHelper;\r\n let bodyViewport = domHelper.getSlicerBodyViewport(this.currentViewport, settings, this.textProperties);\r\n let interactivityService: IInteractivityService;\r\n\r\n if (this.behavior)\r\n interactivityService = createInteractivityService(hostServices);\r\n\r\n let containerDiv = document.createElement('div');\r\n containerDiv.className = Selectors.Container.class;\r\n let container = this.container = d3.select(containerDiv);\r\n\r\n let header = domHelper.createSlicerHeader(hostServices);\r\n containerDiv.appendChild(header);\r\n this.header = d3.select(header);\r\n\r\n this.body = container.append('div').classed(SlicerUtil.Selectors.Body.class, true)\r\n .style({\r\n 'height': PixelConverter.toString(bodyViewport.height),\r\n 'width': PixelConverter.toString(bodyViewport.width),\r\n });\r\n\r\n let rowEnter = (rowSelection: D3.Selection) => {\r\n this.onEnterSelection(rowSelection);\r\n };\r\n\r\n let rowUpdate = (rowSelection: D3.Selection) => {\r\n this.onUpdateSelection(rowSelection, interactivityService);\r\n };\r\n\r\n let rowExit = (rowSelection: D3.Selection) => {\r\n rowSelection.remove();\r\n };\r\n\r\n let listViewOptions: ListViewOptions = {\r\n rowHeight: domHelper.getRowHeight(settings, this.textProperties),\r\n enter: rowEnter,\r\n exit: rowExit,\r\n update: rowUpdate,\r\n loadMoreData: () => slicerInitOptions.loadMoreData(),\r\n scrollEnabled: true,\r\n viewport: domHelper.getSlicerBodyViewport(this.currentViewport, settings, this.textProperties),\r\n baseContainer: this.body,\r\n isReadMode: () => {\r\n return (this.hostServices.getViewMode() !== ViewMode.Edit);\r\n }\r\n };\r\n\r\n this.listView = ListViewFactory.createListView(listViewOptions);\r\n\r\n // Append container to DOM\r\n this.element.get(0).appendChild(containerDiv);\r\n\r\n return interactivityService;\r\n }\r\n\r\n public render(options: SlicerRenderOptions): void {\r\n let data = this.data = options.data;\r\n this.currentViewport = options.viewport;\r\n let dataView = options.dataView;\r\n\r\n if (!dataView || !data) {\r\n this.listView.empty();\r\n return;\r\n }\r\n\r\n this.dataView = dataView;\r\n let settings = this.settings = data.slicerSettings;\r\n let domHelper = this.domHelper;\r\n\r\n domHelper.updateSlicerBodyDimensions(this.currentViewport, this.body, settings);\r\n this.updateSelectionStyle();\r\n this.listView\r\n .viewport(domHelper.getSlicerBodyViewport(this.currentViewport, settings, this.textProperties))\r\n .rowHeight(domHelper.getRowHeight(settings, this.textProperties))\r\n .data(\r\n data.slicerDataPoints,\r\n (d: SlicerDataPoint) => $.inArray(d, data.slicerDataPoints),\r\n options.resetScrollbarPosition\r\n );\r\n }\r\n\r\n private updateSelectionStyle(): void {\r\n let settings = this.settings;\r\n this.container.classed('isMultiSelectEnabled', settings && settings.selection && !settings.selection.singleSelect);\r\n }\r\n\r\n private onEnterSelection(rowSelection: D3.Selection): void {\r\n let settings = this.settings;\r\n let listItemElement = rowSelection.append('ul')\r\n .append('li')\r\n .classed(Selectors.ItemContainer.class, true);\r\n\r\n let labelElement = listItemElement.append('div')\r\n .classed(Selectors.Input.class, true);\r\n\r\n labelElement.append('input')\r\n .attr('type', 'checkbox');\r\n\r\n labelElement.append('span')\r\n .classed(Selectors.Checkbox.class, true);\r\n\r\n listItemElement.each(function (d: SlicerDataPoint, i: number) {\r\n let item = d3.select(this);\r\n if (d.isImage) {\r\n item.append('img')\r\n .classed(SlicerUtil.Selectors.LabelImage.class, true);\r\n }\r\n else {\r\n item.append('span')\r\n .classed(SlicerUtil.Selectors.LabelText.class, true);\r\n }\r\n\r\n if (d.count != null) {\r\n item.append('span')\r\n .classed(SlicerUtil.Selectors.CountText.class, true)\r\n .style('font-size', PixelConverter.fromPoint(settings.slicerText.textSize));\r\n }\r\n });\r\n }\r\n\r\n private onUpdateSelection(rowSelection: D3.Selection, interactivityService: IInteractivityService): void {\r\n let settings = this.settings;\r\n let data = this.data;\r\n if (data && settings) {\r\n // Style Slicer Header\r\n let domHelper = this.domHelper;\r\n domHelper.styleSlicerHeader(this.header, settings, data.categorySourceName);\r\n\r\n let headerText = this.header.select(SlicerUtil.Selectors.TitleHeader.selector);\r\n headerText.attr('title', data.categorySourceName);\r\n \r\n let labelText = rowSelection.selectAll(SlicerUtil.Selectors.LabelText.selector);\r\n labelText.text((d: SlicerDataPoint) => {\r\n return d.value;\r\n }).attr('title', (d: SlicerDataPoint) => {\r\n return d.tooltip;\r\n });\r\n domHelper.setSlicerTextStyle(labelText, settings);\r\n\r\n let labelImage = rowSelection.selectAll(SlicerUtil.Selectors.LabelImage.selector);\r\n if (!labelImage.empty()) {\r\n labelImage.attr('src', (d: SlicerDataPoint) => {\r\n return d.value;\r\n });\r\n }\r\n\r\n let countText = rowSelection.selectAll(SlicerUtil.Selectors.CountText.selector);\r\n if (!countText.empty()) {\r\n countText.text((d: SlicerDataPoint) => d.count);\r\n domHelper.setSlicerTextStyle(countText, settings);\r\n }\r\n\r\n if (interactivityService && this.body) {\r\n let body = this.body.attr('width', this.currentViewport.width);\r\n let slicerItemContainers = body.selectAll(Selectors.ItemContainer.selector);\r\n let slicerItemLabels = body.selectAll(SlicerUtil.Selectors.LabelText.selector);\r\n let slicerItemInputs = body.selectAll(Selectors.Input.selector);\r\n let slicerClear = this.header.select(SlicerUtil.Selectors.Clear.selector);\r\n let searchInput = this.header.select('input');\r\n if (!searchInput.empty()) {\r\n let element: HTMLInputElement = <HTMLInputElement>searchInput.node();\r\n let exisingSearchKey: string = element && element.value;\r\n\r\n // When the existingSearchKey is empty, try set it using the searchKey from data.\r\n // This is to ensure the search key is diplayed in the input box when the input box was first rendered.\r\n // If the search key was reset from exploreUI when search is turned off, then the data.searchkey will be ''\r\n // The input box value need to be reset to ''.\r\n if (_.isEmpty(exisingSearchKey) || _.isEmpty(data.searchKey))\r\n searchInput\r\n .property('value', data.searchKey);\r\n }\r\n\r\n let behaviorOptions: VerticalSlicerBehaviorOptions = {\r\n dataPoints: data.slicerDataPoints,\r\n slicerContainer: this.container,\r\n itemContainers: slicerItemContainers,\r\n itemLabels: slicerItemLabels,\r\n itemInputs: slicerItemInputs,\r\n clear: slicerClear,\r\n interactivityService: interactivityService,\r\n settings: data.slicerSettings,\r\n searchInput: searchInput,\r\n slicerValueHandler: this,\r\n };\r\n\r\n let orientationBehaviorOptions: SlicerOrientationBehaviorOptions = {\r\n behaviorOptions: behaviorOptions,\r\n orientation: SlicerOrientation.Vertical,\r\n };\r\n\r\n interactivityService.bind(\r\n data.slicerDataPoints,\r\n this.behavior,\r\n orientationBehaviorOptions,\r\n { overrideSelectionFromData: true, hasSelectionOverride: data.hasSelectionOverride, slicerValueHandler: this });\r\n SlicerWebBehavior.styleSlicerItems(rowSelection.select(Selectors.Input.selector), data.hasSelectionOverride, interactivityService.isSelectionModeInverted());\r\n }\r\n else {\r\n SlicerWebBehavior.styleSlicerItems(rowSelection.select(Selectors.Input.selector), false, false);\r\n }\r\n }\r\n }\r\n }\r\n\r\n module Selectors {\r\n import createClassAndSelector = jsCommon.CssConstants.createClassAndSelector;\r\n\r\n export const Container = createClassAndSelector('slicerContainer');\r\n export const ItemContainer = createClassAndSelector('slicerItemContainer');\r\n export const Input = createClassAndSelector('slicerCheckbox');\r\n export const Checkbox = createClassAndSelector('checkbox');\r\n }\r\n}\n\n\n/** WEBPACK FOOTER **\n ** ./src/Clients/Visuals/visuals/verticalSlicer.ts\n **/","/*\r\n * Power BI Visualizations\r\n *\r\n * Copyright (c) Microsoft Corporation\r\n * All rights reserved. \r\n * MIT License\r\n *\r\n * Permission is hereby granted, free of charge, to any person obtaining a copy\r\n * of this software and associated documentation files (the \"\"Software\"\"), to deal\r\n * in the Software without restriction, including without limitation the rights\r\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\r\n * copies of the Software, and to permit persons to whom the Software is\r\n * furnished to do so, subject to the following conditions:\r\n * \r\n * The above copyright notice and this permission notice shall be included in \r\n * all copies or substantial portions of the Software.\r\n * \r\n * THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR \r\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, \r\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE \r\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER \r\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\r\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\r\n * THE SOFTWARE.\r\n */\r\n\r\n/// <reference path=\"../_references.ts\"/>\r\n\r\nmodule powerbi.visuals {\r\n import PixelConverter = jsCommon.PixelConverter;\r\n import SlicerOrientation = slicerOrientation.Orientation;\r\n\r\n const ItemWidthSampleSize = 50;\r\n const MinTextWidth = 80;\r\n const LoadMoreDataThreshold = 0.8; // The value indicates the percentage of data already shown that triggers a loadMoreData call.\r\n const DefaultStyleProperties = {\r\n labelText: {\r\n marginRight: 2,\r\n paddingLeft: 8,\r\n paddingRight: 8,\r\n },\r\n };\r\n\r\n export class HorizontalSlicerRenderer implements ISlicerRenderer, SlicerValueHandler {\r\n private element: JQuery;\r\n private currentViewport: IViewport;\r\n private data: SlicerData;\r\n private interactivityService: IInteractivityService;\r\n private behavior: IInteractiveBehavior;\r\n private hostServices: IVisualHostServices;\r\n private dataView: DataView;\r\n private container: D3.Selection;\r\n private header: D3.Selection;\r\n private body: D3.Selection;\r\n private bodyViewport: IViewport;\r\n private itemsContainer: D3.Selection;\r\n private rightNavigationArrow: D3.Selection;\r\n private leftNavigationArrow: D3.Selection;\r\n private dataStartIndex: number;\r\n private itemsToDisplay: number;\r\n private textProperties: TextProperties = {\r\n fontFamily: Font.Family.regular.css,\r\n fontSize: '14px'\r\n };\r\n private maxItemWidth: number;\r\n private totalItemWidth: number;\r\n private loadMoreData: () => void;\r\n private domHelper: SlicerUtil.DOMHelper;\r\n\r\n constructor(options?: SlicerConstructorOptions) {\r\n if (options) {\r\n this.behavior = options.behavior;\r\n }\r\n this.domHelper = options.domHelper;\r\n this.dataStartIndex = 0;\r\n }\r\n\r\n // SlicerValueHandler\r\n public getDefaultValue(): data.SQConstantExpr {\r\n if (this.data && this.data.defaultValue)\r\n return <data.SQConstantExpr>this.data.defaultValue.value;\r\n }\r\n\r\n public getIdentityFields(): data.SQExpr[] {\r\n return SlicerUtil.DefaultValueHandler.getIdentityFields(this.dataView);\r\n }\r\n\r\n public getUpdatedSelfFilter(searchKey: string): data.SemanticFilter {\r\n // Search in horizontal Slicer is not implemented.\r\n return;\r\n }\r\n\r\n public init(slicerInitOptions: SlicerInitOptions): IInteractivityService {\r\n this.element = slicerInitOptions.visualInitOptions.element;\r\n this.currentViewport = slicerInitOptions.visualInitOptions.viewport;\r\n let hostServices = this.hostServices = slicerInitOptions.visualInitOptions.host;\r\n\r\n if (this.behavior) {\r\n this.interactivityService = createInteractivityService(hostServices);\r\n }\r\n this.loadMoreData = () => slicerInitOptions.loadMoreData();\r\n\r\n let containerDiv = document.createElement('div');\r\n containerDiv.className = Selectors.container.class;\r\n let container: D3.Selection = this.container = d3.select(containerDiv);\r\n\r\n let header = this.domHelper.createSlicerHeader(this.hostServices);\r\n containerDiv.appendChild(header);\r\n this.header = d3.select(header);\r\n\r\n let body = this.body = container.append('div').classed(SlicerUtil.Selectors.Body.class + \" \" + Selectors.FlexDisplay.class, true);\r\n\r\n this.leftNavigationArrow = body.append(\"button\")\r\n .classed(Selectors.NavigationArrow.class + \" \" + Selectors.LeftNavigationArrow.class, true);\r\n\r\n this.itemsContainer = body.append(\"div\")\r\n .classed(Selectors.ItemsContainer.class + \" \" + Selectors.FlexDisplay.class, true);\r\n\r\n this.rightNavigationArrow = body.append(\"button\")\r\n .classed(Selectors.NavigationArrow.class + \" \" + Selectors.RightNavigationArrow.class, true);\r\n\r\n // Append container to DOM\r\n this.element.get(0).appendChild(containerDiv);\r\n\r\n this.bindNavigationEvents();\r\n\r\n return this.interactivityService;\r\n }\r\n\r\n public render(options: SlicerRenderOptions): void {\r\n let data = options.data;\r\n let dataView = options.dataView;\r\n\r\n if (!dataView || !data) {\r\n this.itemsContainer.selectAll(\"*\").remove();\r\n return;\r\n }\r\n\r\n this.data = data;\r\n this.dataView = dataView;\r\n let resized = this.currentViewport && options.viewport\r\n && (this.currentViewport.height !== options.viewport.height || this.currentViewport.width !== options.viewport.width);\r\n if (!(this.isMaxWidthCalculated() && resized)) {\r\n // Max width calculation is not required during resize, but required on data changes like changes to formatting properties fontSize, outline, outline weight, etc...\r\n // So calculating only on data updates\r\n this.calculateAndSetMaxItemWidth();\r\n this.calculateAndSetTotalItemWidth();\r\n }\r\n\r\n this.currentViewport = options.viewport;\r\n this.updateStyle();\r\n let availableWidthForItemsContainer = this.element.find(Selectors.ItemsContainer.selector).width();\r\n this.itemsToDisplay = this.getNumberOfItemsToDisplay(availableWidthForItemsContainer);\r\n if (this.itemsToDisplay === 0)\r\n return;\r\n\r\n this.renderCore();\r\n }\r\n\r\n private renderCore(): void {\r\n let data = this.data;\r\n if (!data || !data.slicerDataPoints)\r\n return;\r\n\r\n this.normalizePosition(data.slicerDataPoints);\r\n\r\n let itemsToDisplay = this.itemsToDisplay;\r\n let dataStartIndex = this.dataStartIndex;\r\n // Update Navigation Arrows\r\n this.container.classed(Selectors.CanScrollRight.class, dataStartIndex + this.itemsToDisplay <= data.slicerDataPoints.length - 1);\r\n this.container.classed(Selectors.CanScrollLeft.class, dataStartIndex > 0);\r\n\r\n // Manipulate DOM\r\n this.renderItems(data.slicerSettings);\r\n\r\n // Bind Interactivity Service\r\n this.bindInteractivityService();\r\n\r\n // Load More Data\r\n if (dataStartIndex + itemsToDisplay >= data.slicerDataPoints.length * LoadMoreDataThreshold) {\r\n this.loadMoreData();\r\n }\r\n }\r\n\r\n private updateStyle(): void {\r\n let viewport = this.currentViewport;\r\n let data = this.data;\r\n let defaultSettings: SlicerSettings = data.slicerSettings;\r\n let domHelper = this.domHelper;\r\n\r\n this.container\r\n .classed(Selectors.MultiSelectEnabled.class, !defaultSettings.selection.singleSelect)\r\n .style({\r\n \"width\": PixelConverter.toString(viewport.width),\r\n \"height\": PixelConverter.toString(viewport.height),\r\n });\r\n\r\n // Style Slicer Header\r\n domHelper.styleSlicerHeader(this.header, defaultSettings, data.categorySourceName);\r\n let headerTextProperties = domHelper.getHeaderTextProperties(defaultSettings);\r\n this.header.attr('title', data.categorySourceName);\r\n \r\n // Update body width and height\r\n let bodyViewport = this.bodyViewport = domHelper.getSlicerBodyViewport(viewport, defaultSettings, headerTextProperties);\r\n this.body.style({\r\n \"height\": PixelConverter.toString(bodyViewport.height),\r\n \"width\": PixelConverter.toString(bodyViewport.width),\r\n });\r\n }\r\n\r\n private renderItems(defaultSettings: SlicerSettings): void {\r\n let itemsToDisplay = this.itemsToDisplay;\r\n debug.assert(itemsToDisplay > 0, 'items to display should be greater than zero');\r\n let dataStartIndex = this.dataStartIndex;\r\n let materializedDataPoints = this.data.slicerDataPoints.slice(dataStartIndex, dataStartIndex + itemsToDisplay);\r\n let items = this.itemsContainer\r\n .selectAll(SlicerUtil.Selectors.LabelText.selector)\r\n .data(materializedDataPoints, (d: SlicerDataPoint) => _.indexOf(this.data.slicerDataPoints, d));\r\n\r\n items\r\n .enter()\r\n .append(\"div\")\r\n .classed(SlicerUtil.Selectors.LabelText.class + \" \" + Selectors.FlexDisplay.class, true);\r\n\r\n items.order();\r\n\r\n items\r\n .style({\r\n \"font-family\": this.textProperties.fontFamily,\r\n \"padding-left\": PixelConverter.toString(DefaultStyleProperties.labelText.paddingLeft),\r\n \"padding-right\": PixelConverter.toString(DefaultStyleProperties.labelText.paddingRight),\r\n \"margin-right\": (d: SlicerDataPoint, i) => this.isLastRowItem(i, itemsToDisplay) ? \"0px\" : PixelConverter.toString(DefaultStyleProperties.labelText.marginRight),\r\n });\r\n\r\n // Default style settings from formatting pane settings\r\n this.domHelper.setSlicerTextStyle(items, defaultSettings);\r\n\r\n items.exit().remove();\r\n\r\n window.setTimeout(() => {\r\n items\r\n .attr(\"title\", (d: SlicerDataPoint) => d.tooltip)\r\n .text((d: SlicerDataPoint) => d.value);\r\n // Wrap long text into multiple columns based on height availbale\r\n let labels = this.element.find(SlicerUtil.Selectors.LabelText.selector);\r\n let item = labels.first();\r\n let itemWidth = item.width();\r\n let itemHeight = item.height();\r\n labels.each((i, element) => {\r\n TextMeasurementService.wordBreakOverflowingText(element, itemWidth, itemHeight);\r\n });\r\n });\r\n }\r\n\r\n private bindInteractivityService(): void {\r\n if (this.interactivityService && this.body) {\r\n let body = this.body;\r\n let itemsContainer = body.selectAll(Selectors.ItemsContainer.selector);\r\n let itemLabels = body.selectAll(SlicerUtil.Selectors.LabelText.selector);\r\n let clear = this.header.select(SlicerUtil.Selectors.Clear.selector);\r\n let data = this.data;\r\n\r\n let behaviorOptions: HorizontalSlicerBehaviorOptions = {\r\n dataPoints: data.slicerDataPoints,\r\n slicerContainer: this.container,\r\n itemsContainer: itemsContainer,\r\n itemLabels: itemLabels,\r\n clear: clear,\r\n interactivityService: this.interactivityService,\r\n settings: data.slicerSettings,\r\n slicerValueHandler: this,\r\n };\r\n\r\n let orientationBehaviorOptions: SlicerOrientationBehaviorOptions = {\r\n behaviorOptions: behaviorOptions,\r\n orientation: SlicerOrientation.Horizontal,\r\n };\r\n\r\n this.interactivityService.bind(data.slicerDataPoints, this.behavior, orientationBehaviorOptions, { overrideSelectionFromData: true, hasSelectionOverride: data.hasSelectionOverride });\r\n SlicerWebBehavior.styleSlicerItems(this.itemsContainer.selectAll(SlicerUtil.Selectors.LabelText.selector), this.interactivityService.hasSelection(), this.interactivityService.isSelectionModeInverted());\r\n }\r\n else {\r\n SlicerWebBehavior.styleSlicerItems(this.itemsContainer.selectAll(SlicerUtil.Selectors.LabelText.selector), false, false);\r\n }\r\n }\r\n\r\n private normalizePosition(points: SlicerDataPoint[]): void {\r\n let dataStartIndex = this.dataStartIndex;\r\n // if dataStartIndex >= points.length\r\n dataStartIndex = Math.min(dataStartIndex, points.length - 1);\r\n\r\n // if dataStartIndex < 0 \r\n this.dataStartIndex = Math.max(dataStartIndex, 0);\r\n }\r\n\r\n private bindNavigationEvents(): void {\r\n this.registerMouseWheelScrollEvents();\r\n this.registerMouseClickEvents();\r\n }\r\n\r\n private registerMouseClickEvents(): void {\r\n let rightNavigationArrow = this.container.selectAll(Selectors.RightNavigationArrow.selector);\r\n let leftNavigationArrow = this.container.selectAll(Selectors.LeftNavigationArrow.selector);\r\n\r\n rightNavigationArrow\r\n .on(\"click\", () => {\r\n this.scrollRight();\r\n });\r\n\r\n leftNavigationArrow\r\n .on(\"click\", () => {\r\n this.scrollLeft();\r\n });\r\n }\r\n\r\n // Register for mouse wheel scroll events\r\n private registerMouseWheelScrollEvents(): void {\r\n let scrollableElement = this.body.node();\r\n\r\n scrollableElement.addEventListener(\"mousewheel\", (e) => {\r\n this.onMouseWheel((<MouseWheelEvent>e).wheelDelta);\r\n });\r\n\r\n scrollableElement.addEventListener(\"DOMMouseScroll\", (e) => {\r\n this.onMouseWheel((<MouseWheelEvent>e).detail);\r\n });\r\n }\r\n\r\n private onMouseWheel(wheelDelta: number): void {\r\n if (wheelDelta < 0) {\r\n this.scrollRight();\r\n }\r\n else if (wheelDelta > 0) {\r\n this.scrollLeft();\r\n }\r\n }\r\n\r\n /* If there is only one item being displayed, we show the next item when navigation arrows are clicked \r\n * But when there are more than 1 item, n-1 items are shown say we have 10 items in total , in initial page if we show 1 to 5 items when right button is clicked we will show items from 5 to 10\r\n */\r\n private scrollRight(): void {\r\n let itemsToDisplay = this.itemsToDisplay;\r\n let startIndex = this.dataStartIndex;\r\n let dataPointsLength = this.data.slicerDataPoints.length;\r\n let lastItemIndex = dataPointsLength - 1;\r\n\r\n // If it is the last page stay on the same page and don't navigate\r\n if (itemsToDisplay + startIndex > lastItemIndex) {\r\n return;\r\n }\r\n\r\n if (itemsToDisplay === 1) {\r\n startIndex += itemsToDisplay;\r\n }\r\n else {\r\n startIndex += itemsToDisplay - 1;\r\n }\r\n\r\n // Adjust the startIndex to show last n items if startIndex + itemsToDisplay is greater than total datapoints\r\n if (itemsToDisplay + startIndex > lastItemIndex) {\r\n startIndex = lastItemIndex - itemsToDisplay + 1;\r\n }\r\n\r\n this.dataStartIndex = startIndex;\r\n this.renderCore();\r\n }\r\n\r\n /* If there is only one item being displayed, we show the next item when navigation arrows are clicked \r\n * But when there are more than 1 item, n-1 items are shown\r\n */\r\n private scrollLeft(): void {\r\n let itemsToDisplay = this.itemsToDisplay;\r\n let startIndex = this.dataStartIndex;\r\n let firstItemIndex = 0; \r\n // If it is the first page stay on the same page and don't navigate\r\n if (startIndex === 0) {\r\n return;\r\n } \r\n\r\n // If there is only item shown when left navigation button is clicked we want to navigate back to show previous item\r\n if (itemsToDisplay === 1) {\r\n startIndex -= itemsToDisplay;\r\n }\r\n\r\n if (startIndex - itemsToDisplay < firstItemIndex) {\r\n startIndex = firstItemIndex;\r\n }\r\n else {\r\n startIndex = startIndex - itemsToDisplay + 1;\r\n }\r\n this.dataStartIndex = startIndex;\r\n this.renderCore();\r\n }\r\n\r\n private isLastRowItem(fieldIndex: number, columnsToDisplay: number): boolean {\r\n return fieldIndex === columnsToDisplay - 1;\r\n }\r\n\r\n private getScaledTextWidth(textSize: number): number {\r\n return (textSize / jsCommon.TextSizeDefaults.TextSizeMin) * MinTextWidth;\r\n }\r\n\r\n private isMaxWidthCalculated(): boolean {\r\n return this.maxItemWidth !== undefined;\r\n }\r\n\r\n // Sampling a subset of total datapoints to calculate max item width\r\n private calculateAndSetMaxItemWidth(): void { \r\n let dataPointsLength: number = this.getDataPointsCount();\r\n let maxItemWidth = 0;\r\n if (dataPointsLength === 0) {\r\n this.maxItemWidth = maxItemWidth;\r\n return;\r\n }\r\n let data = this.data;\r\n let dataPoints = data.slicerDataPoints;\r\n let sampleSize = Math.min(dataPointsLength, ItemWidthSampleSize);\r\n let properties = jQuery.extend(true, {}, this.textProperties);\r\n let textSize = data.slicerSettings.slicerText.textSize; \r\n // Update text properties from formatting pane values\r\n properties.fontSize = PixelConverter.fromPoint(textSize);\r\n let getMaxWordWidth = jsCommon.WordBreaker.getMaxWordWidth;\r\n\r\n for (let i = 0; i < sampleSize; i++) {\r\n let itemText = dataPoints[i].value;\r\n properties.text = itemText;\r\n maxItemWidth = Math.max(maxItemWidth, getMaxWordWidth(itemText, TextMeasurementService.measureSvgTextWidth, properties));\r\n }\r\n\r\n this.maxItemWidth = Math.min(maxItemWidth, this.getScaledTextWidth(textSize));\r\n }\r\n\r\n private calculateAndSetTotalItemWidth(): void {\r\n let data = this.data;\r\n let itemPadding = DefaultStyleProperties.labelText.paddingLeft + DefaultStyleProperties.labelText.paddingRight + DefaultStyleProperties.labelText.marginRight;\r\n let borderWidth = this.domHelper.getRowsOutlineWidth(data.slicerSettings.slicerText.outline, data.slicerSettings.general.outlineWeight);\r\n this.totalItemWidth = this.maxItemWidth + itemPadding + borderWidth;\r\n }\r\n\r\n private getNumberOfItemsToDisplay(widthAvailable: number): number {\r\n let totalItemWidth = this.totalItemWidth;\r\n if (totalItemWidth === 0)\r\n return 0;\r\n\r\n let dataPointsLength = this.getDataPointsCount();\r\n let numberOfItems = Math.min(dataPointsLength, Math.round(widthAvailable / totalItemWidth));\r\n\r\n // Show atleast 1 item by default \r\n return Math.max(numberOfItems, 1);\r\n\r\n }\r\n\r\n private getDataPointsCount(): number {\r\n return _.size(this.data.slicerDataPoints);\r\n }\r\n }\r\n\r\n module Selectors {\r\n import createClassAndSelector = jsCommon.CssConstants.createClassAndSelector;\r\n\r\n export const container = createClassAndSelector('horizontalSlicerContainer');\r\n export const ItemsContainer = createClassAndSelector('slicerItemsContainer');\r\n export const NavigationArrow = createClassAndSelector('navigationArrow');\r\n export const LeftNavigationArrow = createClassAndSelector('left');\r\n export const RightNavigationArrow = createClassAndSelector('right');\r\n export const MultiSelectEnabled = createClassAndSelector('isMultiSelectEnabled');\r\n export const FlexDisplay = createClassAndSelector('flexDisplay');\r\n export const CanScrollRight = createClassAndSelector('canScrollRight');\r\n export const CanScrollLeft = createClassAndSelector('canScrollLeft');\r\n }\r\n}\n\n\n/** WEBPACK FOOTER **\n ** ./src/Clients/Visuals/visuals/horizontalSlicer.ts\n **/","/*\r\n * Power BI Visualizations\r\n *\r\n * Copyright (c) Microsoft Corporation\r\n * All rights reserved. \r\n * MIT License\r\n *\r\n * Permission is hereby granted, free of charge, to any person obtaining a copy\r\n * of this software and associated documentation files (the \"\"Software\"\"), to deal\r\n * in the Software without restriction, including without limitation the rights\r\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\r\n * copies of the Software, and to permit persons to whom the Software is\r\n * furnished to do so, subject to the following conditions:\r\n * \r\n * The above copyright notice and this permission notice shall be included in \r\n * all copies or substantial portions of the Software.\r\n * \r\n * THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR \r\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, \r\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE \r\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER \r\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\r\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\r\n * THE SOFTWARE.\r\n */\r\n\r\n/// <reference path=\"../_references.ts\"/>\r\n\r\nmodule powerbi.visuals {\r\n import DisplayNameKeys = SlicerUtil.DisplayNameKeys;\r\n import DOMHelper = SlicerUtil.DOMHelper;\r\n import SettingsHelper = SlicerUtil.SettingsHelper;\r\n import SlicerOrientation = slicerOrientation.Orientation;\r\n\r\n export interface SlicerValueHandler {\r\n getDefaultValue(): data.SQConstantExpr;\r\n getIdentityFields(): data.SQExpr[];\r\n\r\n /** gets updated self filter based on the searchKey. \r\n * If the searchKey didn't change, then the updated filter will be undefined. */\r\n getUpdatedSelfFilter(searchKey: string): data.SemanticFilter;\r\n }\r\n\r\n export interface SlicerConstructorOptions {\r\n domHelper?: DOMHelper;\r\n behavior?: IInteractiveBehavior;\r\n }\r\n\r\n export interface ISlicerRenderer {\r\n init(options: SlicerInitOptions): IInteractivityService;\r\n render(options: SlicerRenderOptions): void;\r\n }\r\n\r\n export interface SlicerRenderOptions {\r\n dataView: DataView;\r\n data: SlicerData;\r\n viewport: IViewport;\r\n resetScrollbarPosition?: boolean;\r\n }\r\n\r\n export interface SlicerData {\r\n categorySourceName: string;\r\n slicerDataPoints: SlicerDataPoint[];\r\n slicerSettings: SlicerSettings;\r\n hasSelectionOverride?: boolean;\r\n defaultValue?: DefaultValueDefinition;\r\n searchKey?: string;\r\n }\r\n\r\n export interface SlicerDataPoint extends SelectableDataPoint {\r\n value: string;\r\n tooltip: string;\r\n isSelectAllDataPoint?: boolean;\r\n count: number;\r\n isImage?: boolean;\r\n }\r\n\r\n export interface SlicerSettings {\r\n general: {\r\n outlineColor: string;\r\n outlineWeight: number;\r\n orientation: SlicerOrientation;\r\n };\r\n header: {\r\n borderBottomWidth: number;\r\n show: boolean;\r\n outline: string;\r\n fontColor: string;\r\n background?: string;\r\n textSize: number;\r\n };\r\n slicerText: {\r\n color: string;\r\n outline: string;\r\n background?: string;\r\n textSize: number;\r\n };\r\n selection: {\r\n selectAllCheckboxEnabled: boolean;\r\n singleSelect: boolean;\r\n };\r\n search: {\r\n enabled: boolean;\r\n };\r\n }\r\n\r\n export interface SlicerInitOptions {\r\n visualInitOptions: VisualInitOptions;\r\n loadMoreData: () => void;\r\n }\r\n\r\n export class Slicer implements IVisual {\r\n private element: JQuery;\r\n private currentViewport: IViewport;\r\n private dataView: DataView;\r\n private slicerData: SlicerData;\r\n private settings: SlicerSettings;\r\n private interactivityService: IInteractivityService;\r\n private behavior: IInteractiveBehavior;\r\n private hostServices: IVisualHostServices;\r\n private slicerRenderer: ISlicerRenderer;\r\n private slicerOrientation: SlicerOrientation;\r\n private waitingForData: boolean;\r\n private domHelper: DOMHelper;\r\n private initOptions: VisualInitOptions;\r\n public static DefaultStyleProperties(): SlicerSettings {\r\n return {\r\n general: {\r\n outlineColor: '#808080',\r\n outlineWeight: 1,\r\n orientation: SlicerOrientation.Vertical,\r\n },\r\n header: {\r\n borderBottomWidth: 1,\r\n show: true,\r\n outline: visuals.outline.bottomOnly,\r\n fontColor: '#000000',\r\n textSize: 10,\r\n },\r\n slicerText: {\r\n color: '#666666',\r\n outline: visuals.outline.none,\r\n textSize: 10,\r\n },\r\n selection: {\r\n selectAllCheckboxEnabled: false,\r\n singleSelect: true,\r\n },\r\n search: {\r\n enabled: false,\r\n },\r\n };\r\n }\r\n\r\n constructor(options?: SlicerConstructorOptions) {\r\n if (options) {\r\n this.behavior = options.behavior;\r\n }\r\n this.domHelper = new DOMHelper();\r\n }\r\n\r\n public init(options: VisualInitOptions): void {\r\n this.initOptions = options;\r\n this.element = options.element;\r\n this.currentViewport = options.viewport;\r\n this.hostServices = options.host;\r\n let settings = this.settings = Slicer.DefaultStyleProperties();\r\n this.slicerOrientation = settings.general.orientation;\r\n this.waitingForData = false;\r\n\r\n this.initializeSlicerRenderer(this.slicerOrientation);\r\n }\r\n\r\n public onDataChanged(options: VisualDataChangedOptions): void {\r\n debug.assertValue(options, 'options');\r\n\r\n let dataViews = options.dataViews;\r\n debug.assertValue(dataViews, 'dataViews');\r\n\r\n if (_.isEmpty(dataViews)) {\r\n return;\r\n }\r\n\r\n let existingDataView = this.dataView;\r\n this.dataView = dataViews[0];\r\n // Reset scrollbar by default, unless it's an Append operation or Selecting an item\r\n let resetScrollbarPosition = options.operationKind !== VisualDataChangeOperationKind.Append\r\n && !DataViewAnalysis.hasSameCategoryIdentity(existingDataView, this.dataView);\r\n\r\n this.render(resetScrollbarPosition, true);\r\n }\r\n\r\n public onResizing(finalViewport: IViewport): void {\r\n this.currentViewport = finalViewport;\r\n this.render(false /* resetScrollbarPosition */);\r\n }\r\n\r\n public enumerateObjectInstances(options: EnumerateVisualObjectInstancesOptions): VisualObjectInstance[] {\r\n return ObjectEnumerator.enumerateObjectInstances(options, this.slicerData, this.settings, this.dataView);\r\n }\r\n\r\n // public for testability\r\n public loadMoreData(): void {\r\n let dataView = this.dataView;\r\n if (!dataView)\r\n return;\r\n\r\n let dataViewMetadata = dataView.metadata;\r\n // Making sure that hostservices.loadMoreData is not invoked when waiting for server to load the next segment of data\r\n if (!this.waitingForData && dataViewMetadata && dataViewMetadata.segment) {\r\n this.hostServices.loadMoreData();\r\n this.waitingForData = true;\r\n }\r\n }\r\n\r\n public onClearSelection(): void {\r\n if (this.interactivityService) {\r\n this.interactivityService.clearSelection();\r\n // calls render so that default behavior can be applied after clear selection.\r\n this.render(false /* resetScrollbarPosition */);\r\n }\r\n }\r\n\r\n private render(resetScrollbarPosition: boolean, stopWaitingForData?: boolean): void {\r\n let localizedSelectAllText = this.hostServices.getLocalizedString(DisplayNameKeys.SelectAll);\r\n this.slicerData = DataConversion.convert(this.dataView, localizedSelectAllText, this.interactivityService, this.hostServices);\r\n if (this.slicerData) {\r\n this.slicerData.slicerSettings.general.outlineWeight = Math.max(this.slicerData.slicerSettings.general.outlineWeight, 0);\r\n this.settings = this.slicerData.slicerSettings;\r\n // TODO: Do we need to check SettingsHelper.areSettingsDefined(), etc. here? Can we just do value validation and coercion in the same place that we create the slicerSettings?\r\n let slicerOrientation = SettingsHelper.areSettingsDefined(this.slicerData) && this.slicerData.slicerSettings.general && this.slicerData.slicerSettings.general.orientation ?\r\n this.slicerData.slicerSettings.general.orientation : Slicer.DefaultStyleProperties().general.orientation;\r\n\r\n let orientationHasChanged = this.orientationHasChanged(slicerOrientation);\r\n if (orientationHasChanged) {\r\n this.slicerOrientation = slicerOrientation;\r\n // Clear the previous slicer type when rendering the new slicer type\r\n this.element.empty();\r\n this.initializeSlicerRenderer(slicerOrientation);\r\n }\r\n }\r\n this.slicerRenderer.render({ dataView: this.dataView, data: this.slicerData, viewport: this.currentViewport, resetScrollbarPosition: resetScrollbarPosition });\r\n\r\n if (stopWaitingForData)\r\n this.waitingForData = false;\r\n }\r\n\r\n private orientationHasChanged(slicerOrientation: SlicerOrientation): boolean {\r\n return this.slicerOrientation !== slicerOrientation;\r\n }\r\n\r\n private initializeSlicerRenderer(slicerOrientation: SlicerOrientation): void {\r\n switch (slicerOrientation) {\r\n case SlicerOrientation.Horizontal:\r\n this.initializeHorizontalSlicer();\r\n break;\r\n\r\n case SlicerOrientation.Vertical:\r\n this.initializeVerticalSlicer();\r\n break;\r\n }\r\n }\r\n\r\n private initializeVerticalSlicer(): void {\r\n let verticalSlicerRenderer = this.slicerRenderer = new VerticalSlicerRenderer({ domHelper: this.domHelper, behavior: this.behavior });\r\n let options = this.createInitOptions();\r\n this.interactivityService = verticalSlicerRenderer.init(options);\r\n }\r\n\r\n private initializeHorizontalSlicer(): void {\r\n let horizontalSlicerRenderer = this.slicerRenderer = new HorizontalSlicerRenderer({ domHelper: this.domHelper, behavior: this.behavior });\r\n let options = this.createInitOptions();\r\n this.interactivityService = horizontalSlicerRenderer.init(options);\r\n }\r\n\r\n private createInitOptions(): SlicerInitOptions {\r\n return {\r\n visualInitOptions: this.initOptions,\r\n loadMoreData: () => this.loadMoreData()\r\n };\r\n }\r\n }\r\n\r\n /** Helper class for calculating the current slicer settings. */\r\n module ObjectEnumerator {\r\n export function enumerateObjectInstances(options: EnumerateVisualObjectInstancesOptions, data: SlicerData, settings: SlicerSettings, dataView: DataView): VisualObjectInstance[] {\r\n if (!data)\r\n return;\r\n\r\n switch (options.objectName) {\r\n case 'items':\r\n return enumerateItems(data, settings);\r\n case 'header':\r\n return enumerateHeader(data, settings);\r\n case 'general':\r\n return enumerateGeneral(data, settings);\r\n case 'selection':\r\n if (shouldShowSelectionOption(dataView))\r\n return enumerateSelection(data, settings);\r\n }\r\n }\r\n\r\n function shouldShowSelectionOption(dataView: DataView): boolean {\r\n return !(dataView &&\r\n dataView.metadata &&\r\n dataView.metadata.columns &&\r\n _.some(dataView.metadata.columns, (column) => column.discourageAggregationAcrossGroups));\r\n }\r\n\r\n function enumerateSelection(data: SlicerData, settings: SlicerSettings): VisualObjectInstance[] {\r\n let slicerSettings = settings;\r\n let areSelectionSettingsDefined = SettingsHelper.areSettingsDefined(data) && data.slicerSettings.selection;\r\n let selectAllCheckboxEnabled = areSelectionSettingsDefined && data.slicerSettings.selection.selectAllCheckboxEnabled ?\r\n data.slicerSettings.selection.selectAllCheckboxEnabled : slicerSettings.selection.selectAllCheckboxEnabled;\r\n let singleSelect = data && data.slicerSettings && data.slicerSettings.selection && data.slicerSettings.selection.singleSelect !== undefined ?\r\n data.slicerSettings.selection.singleSelect : slicerSettings.selection.singleSelect;\r\n\r\n return [{\r\n selector: null,\r\n objectName: 'selection',\r\n properties: {\r\n selectAllCheckboxEnabled: selectAllCheckboxEnabled,\r\n singleSelect: singleSelect,\r\n }\r\n }];\r\n }\r\n\r\n function enumerateHeader(data: SlicerData, settings: SlicerSettings): VisualObjectInstance[] {\r\n let slicerSettings = settings;\r\n let areHeaderSettingsDefined = SettingsHelper.areSettingsDefined(data) && data.slicerSettings.header;\r\n let fontColor = areHeaderSettingsDefined && data.slicerSettings.header.fontColor ?\r\n data.slicerSettings.header.fontColor : slicerSettings.header.fontColor;\r\n let background = areHeaderSettingsDefined && data.slicerSettings.header.background ?\r\n data.slicerSettings.header.background : slicerSettings.header.background;\r\n return [{\r\n selector: null,\r\n objectName: 'header',\r\n properties: {\r\n show: slicerSettings.header.show,\r\n fontColor: fontColor,\r\n background: background,\r\n outline: slicerSettings.header.outline,\r\n textSize: slicerSettings.header.textSize,\r\n }\r\n }];\r\n }\r\n\r\n function enumerateItems(data: SlicerData, settings: SlicerSettings): VisualObjectInstance[] {\r\n let slicerSettings = settings;\r\n let areTextSettingsDefined = SettingsHelper.areSettingsDefined(data) && data.slicerSettings.slicerText;\r\n let fontColor = areTextSettingsDefined && data.slicerSettings.slicerText.color ?\r\n data.slicerSettings.slicerText.color : slicerSettings.slicerText.color;\r\n let background = areTextSettingsDefined && data.slicerSettings.slicerText.background ?\r\n data.slicerSettings.slicerText.background : slicerSettings.slicerText.background;\r\n return [{\r\n selector: null,\r\n objectName: 'items',\r\n properties: {\r\n fontColor: fontColor,\r\n background: background,\r\n outline: slicerSettings.slicerText.outline,\r\n textSize: slicerSettings.slicerText.textSize,\r\n }\r\n }];\r\n }\r\n\r\n function enumerateGeneral(data: SlicerData, settings: SlicerSettings): VisualObjectInstance[] {\r\n let slicerSettings = settings;\r\n let areGeneralSettingsDefined = SettingsHelper.areSettingsDefined(data) && data.slicerSettings.general != null;\r\n let outlineColor = areGeneralSettingsDefined && data.slicerSettings.general.outlineColor ?\r\n data.slicerSettings.general.outlineColor : slicerSettings.general.outlineColor;\r\n let outlineWeight = areGeneralSettingsDefined && data.slicerSettings.general.outlineWeight ?\r\n data.slicerSettings.general.outlineWeight : slicerSettings.general.outlineWeight;\r\n let orientation = areGeneralSettingsDefined && data.slicerSettings.general.orientation != null ?\r\n data.slicerSettings.general.orientation : slicerSettings.general.orientation;\r\n\r\n return [{\r\n selector: null,\r\n objectName: 'general',\r\n properties: {\r\n outlineColor: outlineColor,\r\n outlineWeight: outlineWeight,\r\n orientation: orientation,\r\n }\r\n }];\r\n }\r\n }\r\n}\n\n\n/** WEBPACK FOOTER **\n ** ./src/Clients/Visuals/visuals/slicer.ts\n **/","/*\r\n * Power BI Visualizations\r\n *\r\n * Copyright (c) Microsoft Corporation\r\n * All rights reserved.\r\n * MIT License\r\n *\r\n * Permission is hereby granted, free of charge, to any person obtaining a copy\r\n * of this software and associated documentation files (the \"\"Software\"\"), to deal\r\n * in the Software without restriction, including without limitation the rights\r\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\r\n * copies of the Software, and to permit persons to whom the Software is\r\n * furnished to do so, subject to the following conditions:\r\n *\r\n * The above copyright notice and this permission notice shall be included in\r\n * all copies or substantial portions of the Software.\r\n *\r\n * THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\r\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\r\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\r\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\r\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\r\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\r\n * THE SOFTWARE.\r\n */\r\n\r\n/// <reference path=\"../_references.ts\"/>\r\n\r\nmodule powerbi.visuals {\r\n import TablixFormattingProperties = powerbi.visuals.controls.TablixFormattingPropertiesTable;\r\n import TablixUtils = controls.internal.TablixUtils;\r\n import TablixObjects = controls.internal.TablixObjects;\r\n import EdgeSettings = TablixUtils.EdgeSettings;\r\n import EdgeType = TablixUtils.EdgeType;\r\n\r\n export interface DataViewVisualTable extends DataViewTable {\r\n visualRows?: DataViewVisualTableRow[];\r\n formattingProperties?: TablixFormattingProperties;\r\n }\r\n\r\n export interface DataViewVisualTableRow {\r\n index: number;\r\n values: DataViewTableRow;\r\n }\r\n\r\n export interface TableDataAdapter {\r\n update(table: DataViewTable, isDataComplete: boolean): void;\r\n }\r\n\r\n export interface TableTotal {\r\n totalCells: any[];\r\n }\r\n\r\n export class TableHierarchyNavigator implements controls.ITablixHierarchyNavigator, TableDataAdapter {\r\n private tableDataView: DataViewVisualTable;\r\n private formatter: ICustomValueColumnFormatter;\r\n\r\n /**\r\n * True if the model is not expecting more data\r\n */\r\n private isDataComplete: boolean;\r\n\r\n constructor(tableDataView: DataViewVisualTable, isDataComplete: boolean, formatter: ICustomValueColumnFormatter) {\r\n debug.assertValue(tableDataView, 'tableDataView');\r\n debug.assertValue(formatter, 'formatter');\r\n\r\n this.tableDataView = tableDataView;\r\n this.isDataComplete = isDataComplete;\r\n this.formatter = formatter;\r\n }\r\n\r\n /**\r\n * Returns the depth of the Columnm hierarchy.\r\n */\r\n public getColumnHierarchyDepth(): number {\r\n return 1;\r\n }\r\n\r\n /**\r\n * Returns the depth of the Row hierarchy.\r\n */\r\n public getRowHierarchyDepth(): number {\r\n return 1;\r\n }\r\n\r\n /**\r\n * Returns the leaf count of a hierarchy.\r\n */\r\n public getLeafCount(hierarchy: any): number {\r\n return hierarchy.length;\r\n }\r\n\r\n /**\r\n * Returns the leaf member of a hierarchy at a specified index.\r\n */\r\n public getLeafAt(hierarchy: any, index: number): any {\r\n return hierarchy[index];\r\n }\r\n\r\n /**\r\n * Returns the specified hierarchy member parent.\r\n */\r\n public getParent(item: any): any {\r\n return null;\r\n }\r\n\r\n /**\r\n * Returns the index of the hierarchy member relative to its parent.\r\n */\r\n public getIndex(item: any): number {\r\n if (!item)\r\n return -1;\r\n\r\n if (this.isRow(item))\r\n return (<DataViewVisualTableRow>item).index;\r\n\r\n return this.getColumnIndex(item);\r\n }\r\n\r\n private isRow(item: any): boolean {\r\n if (!item)\r\n return false;\r\n\r\n let row = <DataViewVisualTableRow>item;\r\n return row.index !== undefined && row.values !== undefined;\r\n }\r\n\r\n private getColumnIndex(item: any): number {\r\n return TableHierarchyNavigator.getIndex(this.tableDataView.columns, item);\r\n }\r\n\r\n /**\r\n * Checks whether a hierarchy member is a leaf.\r\n */\r\n public isLeaf(item: any): boolean {\r\n return true;\r\n }\r\n\r\n public isRowHierarchyLeaf(cornerItem: any): boolean {\r\n return false;\r\n }\r\n\r\n public isColumnHierarchyLeaf(cornerItem: any): boolean {\r\n return true;\r\n }\r\n\r\n public isFirstItem(item: MatrixVisualNode, items: MatrixVisualNode[]): boolean {\r\n // checking for item.index is unreliable because reordering the columns would cause a mismatch between index and items order\r\n return item === items[0];\r\n }\r\n\r\n public areAllParentsFirst(item: any, items: any): boolean {\r\n return this.isFirstItem(item, items);\r\n }\r\n\r\n /**\r\n * Checks whether a hierarchy member is the last item within its parent.\r\n */\r\n public isLastItem(item: any, items: any[]): boolean {\r\n debug.assertValue(item, 'item');\r\n\r\n // If it's a row, we need to check if data is complete\r\n return (items === this.tableDataView.columns || this.isDataComplete)\r\n && (item === _.last(items));\r\n }\r\n\r\n public areAllParentsLast(item: any, items: any[]): boolean {\r\n return this.isLastItem(item, items);\r\n }\r\n\r\n /**\r\n * Gets the children members of a hierarchy member.\r\n */\r\n public getChildren(item: any): any {\r\n return null;\r\n }\r\n\r\n public getChildrenLevelDifference(item: any) {\r\n return Infinity;\r\n }\r\n\r\n /**\r\n * Gets the members count in a specified collection.\r\n */\r\n public getCount(items: any): number {\r\n return items.length;\r\n }\r\n\r\n /**\r\n * Gets the member at the specified index.\r\n */\r\n public getAt(items: any, index: number): any {\r\n return items[index];\r\n }\r\n\r\n /**\r\n * Gets the hierarchy member level.\r\n */\r\n public getLevel(item: any): number {\r\n return 0;\r\n }\r\n\r\n /**\r\n * Returns the intersection between a row and a column item.\r\n */\r\n public getIntersection(rowItem: any, columnItem: DataViewMetadataColumn): TablixUtils.TablixVisualCell {\r\n let value: any;\r\n let isTotal: boolean = false;\r\n let position = new TablixUtils.CellPosition();\r\n\r\n let columnIndex: number = TableHierarchyNavigator.getIndex(this.tableDataView.columns, columnItem);;\r\n position.column.index = columnIndex;\r\n position.column.isFirst = columnIndex === 0 ? true : false;\r\n position.column.isLast = columnIndex === this.tableDataView.columns.length - 1;\r\n\r\n let totalRow = <TableTotal>rowItem;\r\n if (totalRow.totalCells != null) {\r\n isTotal = true;\r\n value = totalRow.totalCells[columnIndex];\r\n }\r\n else {\r\n let row = <DataViewVisualTableRow>rowItem;\r\n let rowIndex = row.index;\r\n position.row.index = rowIndex;\r\n position.row.isFirst = rowIndex === 0;\r\n position.row.isLast = this.isDataComplete && (rowIndex === this.tableDataView.rows.length - 1);\r\n value = row.values[columnIndex];\r\n }\r\n\r\n let cellItem = new TablixUtils.TablixVisualCell(value, isTotal, columnItem, this.formatter, false);\r\n cellItem.position = position;\r\n\r\n let tableRow = <DataViewVisualTableRow>rowItem;\r\n if (tableRow && tableRow.values) {\r\n let rowObjects = tableRow.values.objects;\r\n if (rowObjects) {\r\n let cellObject = rowObjects[columnIndex];\r\n if (cellObject) {\r\n cellItem.backColor = TablixObjects.PropValuesBackColor.getValue<string>(cellObject);\r\n }\r\n }\r\n }\r\n return cellItem;\r\n }\r\n\r\n /**\r\n * Returns the corner cell between a row and a column level.\r\n */\r\n public getCorner(rowLevel: number, columnLevel: number): TablixUtils.TablixVisualCell {\r\n return null;\r\n }\r\n\r\n public headerItemEquals(item1: any, item2: any): boolean {\r\n if (item1 === item2)\r\n return true;\r\n\r\n // Typechecking does not work with interfaces nor at runtime. We need to explicitly check for\r\n // properties of DataViewMetadataColumn to determine if we can use the column equivalency check.\r\n // We expect this method to handle either VisualTableRows or DataViewMetadataColumns so checking\r\n // for displayName should be sufficient.\r\n if (item1.displayName && item2.displayName) {\r\n let column1 = <powerbi.DataViewMetadataColumn>item1;\r\n let column2 = <powerbi.DataViewMetadataColumn>item2;\r\n return powerbi.DataViewAnalysis.areMetadataColumnsEquivalent(column1, column2);\r\n }\r\n\r\n if (this.isRow(item1) && this.isRow(item2))\r\n return item1.index === item2.index;\r\n\r\n return false;\r\n }\r\n\r\n public bodyCellItemEquals(item1: TablixUtils.TablixVisualCell, item2: TablixUtils.TablixVisualCell): boolean {\r\n //return (item1.dataPoint === item2.dataPoint);\r\n return (item1.isMatch(item2));\r\n }\r\n\r\n public cornerCellItemEquals(item1: any, item2: any): boolean {\r\n // Should not be called as we don't return any corner items for table\r\n return true;\r\n }\r\n\r\n public update(table: DataViewVisualTable, isDataComplete: boolean): void {\r\n this.tableDataView = table;\r\n this.isDataComplete = isDataComplete;\r\n }\r\n\r\n public static getIndex(items: any[], item: any): number {\r\n for (let index = 0, len = items.length; index < len; index++) {\r\n\r\n // For cases when the item was re-created during the DataTransformation phase,\r\n // we check for the item's index to verify equality.\r\n let arrayItem = items[index];\r\n if (arrayItem.index != null && item.index != null && arrayItem.index === item.index) {\r\n return index;\r\n }\r\n else {\r\n if (item === items[index])\r\n return index;\r\n }\r\n }\r\n\r\n return -1;\r\n }\r\n }\r\n\r\n export interface TableBinderOptions {\r\n onBindRowHeader?(item: any): void;\r\n onColumnHeaderClick?(queryName: string, sortDirection: SortDirection): void;\r\n layoutKind?: controls.TablixLayoutKind;\r\n }\r\n\r\n /**\r\n * Note: Public for testability.\r\n */\r\n export class TableBinder implements controls.ITablixBinder {\r\n private options: TableBinderOptions;\r\n private formattingProperties: TablixFormattingProperties;\r\n private tableDataView: DataViewVisualTable;\r\n\r\n private fontSizeHeader: number;\r\n private textPropsHeader: TextProperties;\r\n private textHeightHeader: number;\r\n\r\n private fontSizeValue: number;\r\n private textPropsValue: TextProperties;\r\n private textHeightValue: number;\r\n\r\n private fontSizeTotal: number;\r\n private textPropsTotal: TextProperties;\r\n private textHeightTotal: number;\r\n\r\n private rowHeight: number;\r\n\r\n constructor(options: TableBinderOptions, dataView?: DataViewVisualTable) {\r\n this.options = options;\r\n\r\n // Handling unit tests calling constructor without a dataView\r\n if (dataView) {\r\n this.updateDataView(dataView);\r\n }\r\n }\r\n\r\n public updateDataView(dataView: DataViewVisualTable): void {\r\n this.tableDataView = dataView;\r\n this.formattingProperties = dataView.formattingProperties;\r\n\r\n this.updateTextHeights();\r\n\r\n if (this.hasImage()) {\r\n this.rowHeight = Math.max(this.textHeightValue, this.formattingProperties.grid.imageHeight);\r\n }\r\n else {\r\n this.rowHeight = this.textHeightValue;\r\n }\r\n }\r\n\r\n private updateTextHeights(): void {\r\n this.fontSizeHeader = jsCommon.PixelConverter.fromPointToPixel(this.formattingProperties.general.textSize);\r\n this.textPropsHeader = {\r\n fontFamily: TablixUtils.FontFamilyHeader,\r\n fontSize: jsCommon.PixelConverter.toString(this.fontSizeHeader),\r\n };\r\n this.textHeightHeader = Math.ceil(TextMeasurementService.measureSvgTextHeight(this.textPropsHeader, \"a\"));\r\n\r\n this.fontSizeValue = jsCommon.PixelConverter.fromPointToPixel(this.formattingProperties.general.textSize);\r\n this.textPropsValue = {\r\n fontFamily: TablixUtils.FontFamilyCell,\r\n fontSize: jsCommon.PixelConverter.toString(this.fontSizeValue),\r\n };\r\n this.textHeightValue = Math.ceil(TextMeasurementService.measureSvgTextHeight(this.textPropsValue, \"a\"));\r\n\r\n this.fontSizeTotal = jsCommon.PixelConverter.fromPointToPixel(this.formattingProperties.general.textSize);\r\n this.textPropsTotal = {\r\n fontFamily: TablixUtils.FontFamilyTotal,\r\n fontSize: jsCommon.PixelConverter.toString(this.fontSizeTotal),\r\n };\r\n this.textHeightTotal = Math.ceil(TextMeasurementService.measureSvgTextHeight(this.textPropsTotal, \"a\"));\r\n }\r\n\r\n private hasImage(): boolean {\r\n return _.any(this.tableDataView.columns, (col) => converterHelper.isImageUrlColumn(col));\r\n }\r\n\r\n public onStartRenderingSession(): void {\r\n }\r\n\r\n public onEndRenderingSession(): void {\r\n }\r\n\r\n /**\r\n * Row Header.\r\n */\r\n public bindRowHeader(item: any, cell: controls.ITablixCell): void {\r\n // Check if it's a total row\r\n if (item.totalCells) {\r\n cell.contentHeight = this.textHeightTotal;\r\n }\r\n else {\r\n cell.contentHeight = this.rowHeight;\r\n }\r\n\r\n // To clear the CSS classes that adds paddings\r\n TablixUtils.clearCellStyle(cell);\r\n\r\n if (this.options.onBindRowHeader)\r\n this.options.onBindRowHeader(item);\r\n }\r\n\r\n public unbindRowHeader(item: any, cell: controls.ITablixCell): void {\r\n\r\n }\r\n\r\n /**\r\n * Column Header.\r\n */\r\n\r\n public bindColumnHeader(item: DataViewMetadataColumn, cell: controls.ITablixCell): void {\r\n cell.extension.disableDragResize();\r\n TablixUtils.resetCellCssClass(cell);\r\n\r\n TablixUtils.addCellCssClass(cell, TablixUtils.CssClassTablixHeader);\r\n TablixUtils.addCellCssClass(cell, TablixUtils.CssClassTablixColumnHeaderLeaf);\r\n\r\n let cellStyle = new TablixUtils.CellStyle();\r\n // Set default style\r\n cellStyle.fontFamily = TablixUtils.FontFamilyHeader;\r\n cellStyle.fontColor = TablixUtils.FontColorHeaders;\r\n cellStyle.borders.bottom = new EdgeSettings(TablixObjects.PropGridOutlineWeight.defaultValue, TablixObjects.PropGridOutlineColor.defaultValue);\r\n\r\n cell.contentHeight = this.textHeightHeader;\r\n\r\n let element = cell.extension.contentHost;\r\n if (this.sortIconsEnabled()) {\r\n element = TablixUtils.addSortIconToColumnHeader(item.sort, element);\r\n if (item.sort) {\r\n // Glyph font has all characters width/height same as font size\r\n cell.contentWidth = this.fontSizeHeader + TablixUtils.SortIconPadding;\r\n }\r\n }\r\n\r\n TablixUtils.setCellTextAndTooltip(item.displayName, element, cell.extension.contentHost);\r\n cell.contentWidth += TextMeasurementService.measureSvgTextWidth(this.textPropsHeader, item.displayName);\r\n cell.contentWidth = Math.ceil(cell.contentWidth);\r\n\r\n if (this.options.onColumnHeaderClick) {\r\n let handler = (e: MouseEvent) => {\r\n if (TablixUtils.isValidSortClick(e)) {\r\n let sortDirection: SortDirection = TablixUtils.reverseSort(item.sort);\r\n this.options.onColumnHeaderClick(item.queryName ? item.queryName : item.displayName, sortDirection);\r\n }\r\n };\r\n cell.extension.registerClickHandler(handler);\r\n }\r\n this.setColumnHeaderStyle(cell, cellStyle);\r\n\r\n cell.applyStyle(cellStyle);\r\n }\r\n\r\n private setColumnHeaderStyle(cell: controls.ITablixCell, style: TablixUtils.CellStyle): void {\r\n let propsGrid = this.formattingProperties.grid;\r\n let props = this.formattingProperties.columnHeaders;\r\n let propsTotal = this.formattingProperties.total;\r\n let propsValues = this.formattingProperties.values;\r\n\r\n style.borders.top = new EdgeSettings();\r\n style.borders.top.applyParams(outline.showTop(props.outline), propsGrid.outlineWeight, propsGrid.outlineColor, EdgeType.Outline);\r\n\r\n style.borders.bottom = new EdgeSettings();\r\n style.borders.bottom.applyParams(outline.showBottom(props.outline), propsGrid.outlineWeight, propsGrid.outlineColor, EdgeType.Outline);\r\n\r\n style.borders.left = new EdgeSettings();\r\n if (cell.position.column.isFirst) {\r\n style.borders.left.applyParams(outline.showLeft(props.outline), propsGrid.outlineWeight, propsGrid.outlineColor, EdgeType.Outline);\r\n\r\n // If we dont have left border, but Footer or Body has, we need to apply extra padding\r\n if (!outline.showLeft(props.outline) && (outline.showLeft(propsTotal.outline) || outline.showLeft(propsValues.outline)))\r\n style.paddings.left += propsGrid.outlineWeight;\r\n } // else: do nothing\r\n\r\n style.borders.right = new EdgeSettings();\r\n if (cell.position.column.isLast) {\r\n style.borders.right.applyParams(outline.showRight(props.outline), propsGrid.outlineWeight, propsGrid.outlineColor, EdgeType.Outline);\r\n\r\n // If we dont have right border, but Footer or Body has, we need to apply extra padding\r\n if (!outline.showRight(props.outline) && (outline.showRight(propsTotal.outline) || outline.showRight(propsValues.outline)))\r\n style.paddings.right += propsGrid.outlineWeight;\r\n }\r\n else {\r\n style.borders.right.applyParams(propsGrid.gridVertical, propsGrid.gridVerticalWeight, propsGrid.gridVerticalColor, EdgeType.Gridline);\r\n }\r\n\r\n style.fontColor = props.fontColor;\r\n style.backColor = props.backColor;\r\n style.paddings.top = style.paddings.bottom = propsGrid.rowPadding;\r\n }\r\n\r\n public unbindColumnHeader(item: any, cell: controls.ITablixCell): void {\r\n TablixUtils.clearCellStyle(cell);\r\n TablixUtils.clearCellTextAndTooltip(cell);\r\n\r\n if (this.sortIconsEnabled())\r\n TablixUtils.removeSortIcons(cell);\r\n\r\n if (this.options.onColumnHeaderClick) {\r\n cell.extension.unregisterClickHandler();\r\n }\r\n }\r\n\r\n /**\r\n * Body Cell.\r\n */\r\n public bindBodyCell(item: TablixUtils.TablixVisualCell, cell: controls.ITablixCell): void {\r\n TablixUtils.resetCellCssClass(cell);\r\n\r\n this.setBodyContent(item, cell);\r\n cell.contentWidth = Math.ceil(cell.contentWidth);\r\n\r\n let cellStyle = new TablixUtils.CellStyle();\r\n\r\n if (item.isTotal) {\r\n TablixUtils.addCellCssClass(cell, TablixUtils.CssClassTablixValueTotal);\r\n TablixUtils.addCellCssClass(cell, TablixUtils.CssClassTableFooter);\r\n\r\n cellStyle.fontFamily = TablixUtils.FontFamilyTotal;\r\n cellStyle.borders.top = new EdgeSettings(TablixObjects.PropGridOutlineWeight.defaultValue, TablixObjects.PropGridOutlineColor.defaultValue);\r\n }\r\n else if (item.position.row.isLast) {\r\n TablixUtils.addCellCssClass(cell, TablixUtils.CssClassTableBodyCellBottom);\r\n }\r\n else {\r\n TablixUtils.addCellCssClass(cell, TablixUtils.CssClassTableBodyCell);\r\n cellStyle.borders.bottom = new EdgeSettings(TablixObjects.PropGridHorizontalWeight.defaultValue, TablixObjects.PropGridHorizontalColor.defaultValue);\r\n }\r\n\r\n if (item.isNumeric)\r\n TablixUtils.addCellCssClass(cell, TablixUtils.CssClassTablixValueNumeric);\r\n\r\n if (item.isTotal)\r\n this.setFooterStyle(cell, cellStyle);\r\n else\r\n this.setBodyStyle(item, cell, cellStyle);\r\n\r\n cell.applyStyle(cellStyle);\r\n }\r\n\r\n public setBodyContent(item: TablixUtils.TablixVisualCell, cell: controls.ITablixCell): void {\r\n let element = cell.extension.contentHost;\r\n let imgHeight = this.formattingProperties.grid.imageHeight;\r\n\r\n let text = item.textContent;\r\n\r\n // #region Setting Height\r\n if (item.isTotal) {\r\n cell.contentHeight = this.textHeightTotal;\r\n }\r\n else if (item.isImage) {\r\n cell.contentHeight = imgHeight;\r\n }\r\n else {\r\n cell.contentHeight = this.textHeightValue;\r\n }\r\n // #endregion\r\n\r\n if (item.isUrl && item.isValidUrl) {\r\n let showUrlIcon = this.formattingProperties.values.urlIcon;\r\n TablixUtils.appendATagToBodyCell(text, element, showUrlIcon);\r\n if (showUrlIcon)\r\n cell.contentWidth = this.fontSizeValue;\r\n else\r\n cell.contentWidth = TextMeasurementService.measureSvgTextWidth(this.textPropsValue, text);\r\n return;\r\n }\r\n\r\n if (item.isImage && item.isValidUrl) {\r\n TablixUtils.appendImgTagToBodyCell(text, element, imgHeight);\r\n cell.contentWidth = imgHeight * TablixUtils.ImageDefaultAspectRatio;\r\n return;\r\n }\r\n\r\n let kpi = item.kpiContent;\r\n if (kpi) {\r\n $(element).append(kpi);\r\n\r\n // Glyph font has all characters width/height same as font size\r\n cell.contentWidth = this.fontSizeValue;\r\n return;\r\n }\r\n\r\n if (text) {\r\n TablixUtils.setCellTextAndTooltip(text, element);\r\n if (item.isTotal)\r\n cell.contentWidth = TextMeasurementService.measureSvgTextWidth(this.textPropsTotal, text);\r\n else\r\n cell.contentWidth = TextMeasurementService.measureSvgTextWidth(this.textPropsValue, text);\r\n return;\r\n }\r\n\r\n TablixUtils.setCellTextAndTooltip(\" \", element);\r\n cell.contentWidth = 0;\r\n }\r\n\r\n private setBodyStyle(item: TablixUtils.TablixVisualCell, cell: controls.ITablixCell, style: TablixUtils.CellStyle): void {\r\n let propsGrid = this.formattingProperties.grid;\r\n let props = this.formattingProperties.values;\r\n let propsTotal = this.formattingProperties.total;\r\n let propsColumns = this.formattingProperties.columnHeaders;\r\n\r\n style.borders.top = new EdgeSettings();\r\n if (cell.position.row.isFirst) { // First Row\r\n style.borders.top.applyParams(outline.showTop(props.outline), propsGrid.outlineWeight, propsGrid.outlineColor, EdgeType.Outline);\r\n } // else: do nothing\r\n\r\n style.borders.bottom = new EdgeSettings();\r\n if (cell.position.row.isLast) { // Last Row\r\n style.borders.bottom.applyParams(outline.showBottom(props.outline), propsGrid.outlineWeight, propsGrid.outlineColor, EdgeType.Outline);\r\n }\r\n else {\r\n style.borders.bottom.applyParams(propsGrid.gridHorizontal, propsGrid.gridHorizontalWeight, propsGrid.gridHorizontalColor, EdgeType.Gridline);\r\n }\r\n\r\n style.borders.left = new EdgeSettings();\r\n if (cell.position.column.isFirst) { // First Column\r\n style.borders.left.applyParams(outline.showLeft(props.outline), propsGrid.outlineWeight, propsGrid.outlineColor, EdgeType.Outline);\r\n\r\n // If we dont have left border, but Footer or Header has, we need to apply extra padding\r\n if (!outline.showLeft(props.outline) && (outline.showLeft(propsTotal.outline) || outline.showLeft(propsColumns.outline)))\r\n style.paddings.left += propsGrid.outlineWeight;\r\n } // else: do nothing\r\n\r\n style.borders.right = new EdgeSettings();\r\n if (cell.position.column.isLast) { // Last Column\r\n style.borders.right.applyParams(outline.showRight(props.outline), propsGrid.outlineWeight, propsGrid.outlineColor, EdgeType.Outline);\r\n\r\n // If we dont have right border, but Footer has, we need to apply extra padding\r\n if (!outline.showRight(props.outline) && (outline.showRight(propsTotal.outline) || outline.showRight(propsColumns.outline)))\r\n style.paddings.right += propsGrid.outlineWeight;\r\n }\r\n else {\r\n style.borders.right.applyParams(propsGrid.gridVertical, propsGrid.gridVerticalWeight, propsGrid.gridVerticalColor, EdgeType.Gridline);\r\n }\r\n\r\n style.fontColor = cell.position.row.index % 2 === 0 ? props.fontColorPrimary : props.fontColorSecondary;\r\n\r\n // Conditional formatting on the cell overrides primary/secondary background colors.\r\n if (item.backColor)\r\n style.backColor = item.backColor;\r\n else\r\n style.backColor = cell.position.row.index % 2 === 0 ? props.backColorPrimary : props.backColorSecondary;\r\n\r\n style.paddings.top = style.paddings.bottom = propsGrid.rowPadding;\r\n }\r\n\r\n private setFooterStyle(cell: controls.ITablixCell, style: TablixUtils.CellStyle): void {\r\n let props = this.formattingProperties.total;\r\n let propsGrid = this.formattingProperties.grid;\r\n let propsValues = this.formattingProperties.values;\r\n let propsColumns = this.formattingProperties.columnHeaders;\r\n\r\n style.borders.top = new EdgeSettings();\r\n style.borders.top.applyParams(outline.showTop(props.outline), propsGrid.outlineWeight, propsGrid.outlineColor, EdgeType.Outline);\r\n\r\n style.borders.bottom = new EdgeSettings();\r\n style.borders.bottom.applyParams(outline.showBottom(props.outline), propsGrid.outlineWeight, propsGrid.outlineColor, EdgeType.Outline);\r\n\r\n style.borders.left = new EdgeSettings();\r\n if (cell.position.column.isFirst) { // First Column\r\n style.borders.left.applyParams(outline.showLeft(props.outline), propsGrid.outlineWeight, propsGrid.outlineColor, EdgeType.Outline);\r\n\r\n // If we dont have left border, but values or column headers have, we need to apply padding\r\n if (!outline.showLeft(props.outline) && (outline.showLeft(propsValues.outline) || outline.showLeft(propsColumns.outline)))\r\n style.paddings.left += propsGrid.outlineWeight;\r\n\r\n } // else: do nothing\r\n\r\n style.borders.right = new EdgeSettings();\r\n if (cell.position.column.isLast) { // Last Column\r\n style.borders.right.applyParams(outline.showRight(props.outline), propsGrid.outlineWeight, propsGrid.outlineColor, EdgeType.Outline);\r\n\r\n // If we dont have left border, but values or column headers have, we need to apply padding\r\n if (!outline.showRight(props.outline) && (outline.showRight(propsValues.outline) || outline.showRight(propsColumns.outline)))\r\n style.paddings.right += propsGrid.outlineWeight;\r\n }\r\n else {\r\n style.borders.right.applyParams(propsGrid.gridVertical, propsGrid.gridVerticalWeight, propsGrid.gridVerticalColor, EdgeType.Gridline);\r\n }\r\n\r\n style.fontColor = props.fontColor;\r\n style.backColor = props.backColor;\r\n\r\n style.paddings.top = style.paddings.bottom = propsGrid.rowPadding;\r\n }\r\n\r\n public unbindBodyCell(item: TablixUtils.TablixVisualCell, cell: controls.ITablixCell): void {\r\n TablixUtils.clearCellStyle(cell);\r\n TablixUtils.clearCellTextAndTooltip(cell);\r\n }\r\n\r\n /**\r\n * Corner Cell.\r\n */\r\n public bindCornerCell(item: any, cell: controls.ITablixCell): void {\r\n cell.contentWidth = 0;\r\n }\r\n\r\n public unbindCornerCell(item: any, cell: controls.ITablixCell): void {\r\n }\r\n\r\n public bindEmptySpaceHeaderCell(cell: controls.ITablixCell): void {\r\n // Not needed for Table\r\n }\r\n\r\n public unbindEmptySpaceHeaderCell(cell: controls.ITablixCell): void {\r\n // Not needed for Table\r\n }\r\n\r\n public bindEmptySpaceFooterCell(cell: controls.ITablixCell): void {\r\n // Not needed for Table\r\n }\r\n\r\n public unbindEmptySpaceFooterCell(cell: controls.ITablixCell): void {\r\n // Not needed for Table\r\n }\r\n\r\n /**\r\n * Measurement Helper.\r\n */\r\n public getHeaderLabel(item: DataViewMetadataColumn): string {\r\n return item ? item.displayName : \"\";\r\n }\r\n\r\n public getCellContent(item: any): string {\r\n return item;\r\n }\r\n\r\n public hasRowGroups(): boolean {\r\n return false;\r\n }\r\n\r\n private sortIconsEnabled(): boolean {\r\n return this.options.layoutKind === controls.TablixLayoutKind.Canvas;\r\n }\r\n }\r\n\r\n export class Table implements IVisual {\r\n private static preferredLoadMoreThreshold: number = 0.8;\r\n\r\n private element: JQuery;\r\n private currentViewport: IViewport;\r\n private style: IVisualStyle;\r\n private formatter: ICustomValueColumnFormatter;\r\n private isInteractive: boolean;\r\n private getLocalizedString: (stringId: string) => string;\r\n private hostServices: IVisualHostServices;\r\n\r\n private tablixControl: controls.TablixControl;\r\n private hierarchyNavigator: TableHierarchyNavigator;\r\n private waitingForData: boolean;\r\n private lastAllowHeaderResize: boolean;\r\n private waitingForSort: boolean;\r\n private columnWidthManager: controls.TablixColumnWidthManager;\r\n private dataView: DataView;\r\n\r\n /**\r\n * Flag indicating that we are persisting objects, so that next onDataChanged can be safely ignored.\r\n */\r\n public persistingObjects: boolean;\r\n\r\n constructor() {\r\n }\r\n\r\n public static customizeQuery(options: CustomizeQueryOptions): void {\r\n let dataViewMapping = options.dataViewMappings[0];\r\n if (!dataViewMapping || !dataViewMapping.table || !dataViewMapping.metadata)\r\n return;\r\n\r\n let dataViewTableRows: data.CompiledDataViewRoleForMapping = <data.CompiledDataViewRoleForMapping>dataViewMapping.table.rows;\r\n let objects = dataViewMapping.metadata.objects;\r\n dataViewTableRows.for.in.subtotalType = TablixObjects.shouldShowTableTotals(objects) ? data.CompiledSubtotalType.Before : data.CompiledSubtotalType.None;\r\n }\r\n\r\n public static getSortableRoles(): string[] {\r\n return ['Values'];\r\n }\r\n\r\n public init(options: VisualInitOptions): void {\r\n this.element = options.element;\r\n this.style = options.style;\r\n this.updateViewport(options.viewport);\r\n this.formatter = valueFormatter.formatVariantMeasureValue;\r\n this.isInteractive = options.interactivity && options.interactivity.selection != null;\r\n this.getLocalizedString = options.host.getLocalizedString;\r\n this.hostServices = options.host;\r\n this.persistingObjects = false;\r\n\r\n this.waitingForData = false;\r\n this.lastAllowHeaderResize = true;\r\n this.waitingForSort = false;\r\n }\r\n\r\n /**\r\n * Note: Public for testability.\r\n */\r\n public static converter(dataView: DataView): DataViewVisualTable {\r\n let table = dataView.table;\r\n debug.assertValue(table, 'table');\r\n debug.assertValue(table.rows, 'table.rows');\r\n\r\n let visualTable = Prototype.inherit<DataViewVisualTable>(table);\r\n visualTable.visualRows = [];\r\n\r\n for (let i: number = 0; i < table.rows.length; i++) {\r\n let visualRow: DataViewVisualTableRow = {\r\n index: i,\r\n values: table.rows[i]\r\n };\r\n visualTable.visualRows.push(visualRow);\r\n }\r\n visualTable.formattingProperties = TablixObjects.getTableObjects(dataView);\r\n\r\n return visualTable;\r\n }\r\n\r\n public onResizing(finalViewport: IViewport): void {\r\n this.updateViewport(finalViewport);\r\n }\r\n\r\n // Public for testability\r\n public getColumnWidthManager(): controls.TablixColumnWidthManager {\r\n return this.columnWidthManager;\r\n }\r\n\r\n public onDataChanged(options: VisualDataChangedOptions): void {\r\n debug.assertValue(options, 'options');\r\n\r\n let dataViews = options.dataViews;\r\n\r\n if (dataViews && dataViews.length > 0) {\r\n let previousDataView = this.dataView;\r\n this.dataView = dataViews[0];\r\n\r\n /* To avoid OnDataChanged being called every time we persist Objects. If:\r\n * AutoSizeColumns options was flipped\r\n * A Column was resized manually\r\n * A Column was auto-sized\r\n */\r\n if (this.persistingObjects) {\r\n this.persistingObjects = false;\r\n return;\r\n }\r\n\r\n let visualTable = Table.converter(this.dataView);\r\n\r\n if (options.operationKind === VisualDataChangeOperationKind.Append) {\r\n this.createOrUpdateHierarchyNavigator(visualTable);\r\n this.tablixControl.updateModels(/*resetScrollOffsets*/false, visualTable.visualRows, visualTable.columns);\r\n this.refreshControl(/*clear*/false);\r\n } else {\r\n this.createOrUpdateHierarchyNavigator(visualTable);\r\n this.createColumnWidthManager();\r\n this.createTablixControl(visualTable);\r\n this.updateInternal(previousDataView, visualTable);\r\n }\r\n }\r\n\r\n this.waitingForData = false;\r\n this.waitingForSort = false;\r\n }\r\n\r\n private createColumnWidthManager(): void {\r\n if (!this.columnWidthManager) {\r\n this.columnWidthManager = new controls.TablixColumnWidthManager(this.dataView,\r\n false /* isMatrix */,\r\n (objectInstances: VisualObjectInstancesToPersist) => this.persistColumnWidths(objectInstances));\r\n }\r\n else {\r\n this.columnWidthManager.updateDataView(this.dataView);\r\n }\r\n }\r\n\r\n private persistColumnWidths(objectInstances: VisualObjectInstancesToPersist): void {\r\n this.persistingObjects = true;\r\n this.hostServices.persistProperties(objectInstances);\r\n }\r\n\r\n private updateViewport(newViewport: IViewport): void {\r\n this.currentViewport = newViewport;\r\n\r\n if (this.tablixControl) {\r\n this.tablixControl.viewport = this.currentViewport;\r\n this.verifyHeaderResize();\r\n this.refreshControl(false);\r\n }\r\n }\r\n\r\n private refreshControl(clear: boolean): void {\r\n if (visibilityHelper.partiallyVisible(this.element) || this.getLayoutKind() === controls.TablixLayoutKind.DashboardTile) {\r\n this.tablixControl.refresh(clear);\r\n }\r\n }\r\n\r\n private getLayoutKind(): controls.TablixLayoutKind {\r\n return this.isInteractive ? controls.TablixLayoutKind.Canvas : controls.TablixLayoutKind.DashboardTile;\r\n }\r\n\r\n private createOrUpdateHierarchyNavigator(visualTable: DataViewVisualTable): void {\r\n let isDataComplete = !this.dataView.metadata.segment;\r\n\r\n if (!this.tablixControl) {\r\n let dataNavigator = new TableHierarchyNavigator(visualTable, isDataComplete, this.formatter);\r\n this.hierarchyNavigator = dataNavigator;\r\n }\r\n else {\r\n this.hierarchyNavigator.update(visualTable, isDataComplete);\r\n }\r\n }\r\n\r\n private createTablixControl(visualTable: DataViewVisualTable): void {\r\n if (!this.tablixControl) {\r\n // Create the control\r\n this.tablixControl = this.createControl(this.hierarchyNavigator, visualTable);\r\n }\r\n else {\r\n let binder = <TableBinder>this.tablixControl.getBinder();\r\n binder.updateDataView(visualTable);\r\n }\r\n }\r\n\r\n private createControl(dataNavigator: TableHierarchyNavigator, visualTable: DataViewVisualTable): controls.TablixControl {\r\n let layoutKind = this.getLayoutKind();\r\n let textSize = visualTable.formattingProperties.general.textSize;\r\n\r\n let tableBinderOptions: TableBinderOptions = {\r\n onBindRowHeader: (item: any) => this.onBindRowHeader(item),\r\n onColumnHeaderClick: (queryName: string, sortDirection: SortDirection) => this.onColumnHeaderClick(queryName, sortDirection),\r\n layoutKind: layoutKind\r\n };\r\n\r\n let tableBinder = new TableBinder(tableBinderOptions, visualTable);\r\n let layoutManager: controls.internal.TablixLayoutManager = layoutKind === controls.TablixLayoutKind.DashboardTile\r\n ? controls.internal.DashboardTablixLayoutManager.createLayoutManager(tableBinder)\r\n : controls.internal.CanvasTablixLayoutManager.createLayoutManager(tableBinder, this.columnWidthManager);\r\n\r\n // Create Host element\r\n let tablixContainer = document.createElement('div');\r\n this.element.append(tablixContainer);\r\n\r\n let tablixOptions: controls.TablixOptions = {\r\n interactive: this.isInteractive,\r\n enableTouchSupport: true,\r\n layoutKind: layoutKind,\r\n fontSize: TablixObjects.getTextSizeInPx(textSize),\r\n };\r\n\r\n return new controls.TablixControl(dataNavigator, layoutManager, tableBinder, tablixContainer, tablixOptions);\r\n }\r\n\r\n private updateInternal(previousDataView: DataView, visualTable: DataViewVisualTable) {\r\n let textSize = visualTable.formattingProperties.general.textSize;\r\n\r\n if (this.getLayoutKind() === controls.TablixLayoutKind.DashboardTile) {\r\n this.tablixControl.layoutManager.adjustContentSize(converterHelper.hasImageUrlColumn(this.dataView));\r\n }\r\n\r\n this.tablixControl.fontSize = TablixObjects.getTextSizeInPx(textSize);\r\n this.verifyHeaderResize();\r\n\r\n // Update models before the viewport to make sure column widths are computed correctly\r\n this.tablixControl.updateModels(/*resetScrollOffsets*/true, visualTable.visualRows, visualTable.columns);\r\n\r\n let totals = this.createTotalsRow(this.dataView);\r\n this.tablixControl.rowDimension.setFooter(totals);\r\n\r\n this.tablixControl.viewport = this.currentViewport;\r\n let shouldClearControl = this.shouldClearControl(previousDataView, this.dataView);\r\n\r\n // Render\r\n // We need the layout for the DIV to be done so that the control can measure items correctly.\r\n setTimeout(() => {\r\n // Render\r\n this.refreshControl(shouldClearControl);\r\n\r\n let widthChanged = this.columnWidthManager.onColumnsRendered(this.tablixControl.layoutManager.columnWidthsToPersist);\r\n\r\n // At this point, all columns are rendered with proper width\r\n // Resetting the flag unless any unknown columnn width was persisted\r\n if (this.persistingObjects && !widthChanged) {\r\n this.persistingObjects = false;\r\n }\r\n }, 0);\r\n }\r\n\r\n private shouldClearControl(previousDataView: DataView, newDataView: DataView) {\r\n if (!this.waitingForSort || !previousDataView || !newDataView)\r\n return true;\r\n\r\n return !DataViewAnalysis.isMetadataEquivalent(previousDataView.metadata, newDataView.metadata);\r\n }\r\n\r\n private createTotalsRow(dataView: DataView): TableTotal {\r\n if (!TablixObjects.shouldShowTableTotals(dataView.metadata.objects))\r\n return null;\r\n\r\n let totals = dataView.table.totals;\r\n if (!totals || totals.length === 0)\r\n return null;\r\n\r\n let totalRow: any[] = [];\r\n let columns = dataView.table.columns;\r\n\r\n // Add totals for measure columns, blank for non-measure columns unless it's the first column\r\n for (let i = 0, len = columns.length; i < len; ++i) {\r\n let column = columns[i];\r\n\r\n let totalValue = totals[column.index];\r\n if (totalValue != null) {\r\n totalRow.push(totalValue);\r\n }\r\n else {\r\n // If the first column is a non-measure column, we put 'Total' as the text similar to PV.\r\n // Note that if the first column is a measure column we don't render any Total text at\r\n // all, once again similar to PV.\r\n totalRow.push((i === 0) ? this.getLocalizedString('TableTotalLabel') : '');\r\n }\r\n }\r\n\r\n return <TableTotal>{ totalCells: totalRow };\r\n }\r\n\r\n private onBindRowHeader(item: any): void {\r\n if (this.needsMoreData(item)) {\r\n this.hostServices.loadMoreData();\r\n this.waitingForData = true;\r\n }\r\n }\r\n\r\n private onColumnHeaderClick(queryName: string, sortDirection: SortDirection): void {\r\n this.waitingForSort = true;\r\n this.hostServices.onCustomSort(TablixUtils.getCustomSortEventArgs(queryName, sortDirection));\r\n }\r\n\r\n /**\r\n * Note: Public for testability.\r\n */\r\n public needsMoreData(item: any): boolean {\r\n if (this.waitingForData || !this.dataView.metadata || !this.dataView.metadata.segment)\r\n return false;\r\n\r\n let leafCount = this.tablixControl.rowDimension.getItemsCount();\r\n let loadMoreThreshold = leafCount * Table.preferredLoadMoreThreshold;\r\n\r\n return this.hierarchyNavigator.getIndex(item) >= loadMoreThreshold;\r\n }\r\n\r\n public enumerateObjectInstances(options: EnumerateVisualObjectInstancesOptions): VisualObjectInstanceEnumeration {\r\n let enumeration = new ObjectEnumerationBuilder();\r\n\r\n // Visuals are initialized with an empty data view before queries are run, therefore we need to make sure that\r\n // we are resilient here when we do not have data view.\r\n if (this.dataView) {\r\n TablixObjects.enumerateObjectInstances(options, enumeration, this.dataView, controls.TablixType.Table);\r\n }\r\n\r\n return enumeration.complete();\r\n }\r\n\r\n public enumerateObjectRepetition(): VisualObjectRepetition[] {\r\n let enumeration: VisualObjectRepetition[] = [];\r\n\r\n // Visuals are initialized with an empty data view before queries are run, therefore we need to make sure that\r\n // we are resilient here when we do not have data view.\r\n if (this.dataView) {\r\n TablixObjects.enumerateObjectRepetition(enumeration, this.dataView, controls.TablixType.Table);\r\n }\r\n\r\n return enumeration;\r\n }\r\n\r\n private shouldAllowHeaderResize(): boolean {\r\n return this.hostServices.getViewMode() === ViewMode.Edit;\r\n }\r\n\r\n public onViewModeChanged(viewMode: ViewMode): void {\r\n /* Refreshes the column headers to enable/disable Column resizing */\r\n this.updateViewport(this.currentViewport);\r\n }\r\n\r\n private verifyHeaderResize() {\r\n let currentAllowHeaderResize = this.shouldAllowHeaderResize();\r\n if (currentAllowHeaderResize !== this.lastAllowHeaderResize) {\r\n this.lastAllowHeaderResize = currentAllowHeaderResize;\r\n this.tablixControl.layoutManager.setAllowHeaderResize(currentAllowHeaderResize);\r\n }\r\n }\r\n }\r\n}\n\n\n/** WEBPACK FOOTER **\n ** ./src/Clients/Visuals/visuals/table.ts\n **/","/*\r\n * Power BI Visualizations\r\n *\r\n * Copyright (c) Microsoft Corporation\r\n * All rights reserved. \r\n * MIT License\r\n *\r\n * Permission is hereby granted, free of charge, to any person obtaining a copy\r\n * of this software and associated documentation files (the \"\"Software\"\"), to deal\r\n * in the Software without restriction, including without limitation the rights\r\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\r\n * copies of the Software, and to permit persons to whom the Software is\r\n * furnished to do so, subject to the following conditions:\r\n * \r\n * The above copyright notice and this permission notice shall be included in \r\n * all copies or substantial portions of the Software.\r\n * \r\n * THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR \r\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, \r\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE \r\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER \r\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\r\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\r\n * THE SOFTWARE.\r\n */\r\n\r\n/// <reference path=\"../_references.ts\"/>\r\n\r\nmodule powerbi.visuals {\r\n\r\n import TablixFormattingPropertiesMatrix = powerbi.visuals.controls.TablixFormattingPropertiesMatrix;\r\n import TablixUtils = controls.internal.TablixUtils;\r\n import TablixObjects = controls.internal.TablixObjects;\r\n import UrlUtils = jsCommon.UrlUtils;\r\n import EdgeSettings = TablixUtils.EdgeSettings;\r\n import EdgeType = TablixUtils.EdgeType;\r\n\r\n /**\r\n * Extension of the Matrix node for Matrix visual.\r\n */\r\n\r\n export interface MatrixVisualNode extends DataViewMatrixNode {\r\n /**\r\n * Index of the node in its parent's children collection.\r\n * \r\n * Note: For size optimization, we could also look this item up in the parent's \r\n * children collection, but we may need to pay the perf penalty.\r\n */\r\n index?: number;\r\n\r\n /**\r\n * Global index of the node as a leaf node.\r\n * If the node is not a leaf, the value is undefined.\r\n */\r\n leafIndex?: number;\r\n\r\n /**\r\n * Parent of the node.\r\n * Undefined for outermost nodes (children of the one root node).\r\n */\r\n parent?: MatrixVisualNode;\r\n\r\n /**\r\n * Children of the same parent\r\n */\r\n siblings?: MatrixVisualNode[];\r\n\r\n /**\r\n * queryName of the node.\r\n * If the node is not a leaf, the value is undefined.\r\n */\r\n queryName?: string;\r\n\r\n /**\r\n * Formatted text to show for the Node\r\n */\r\n valueFormatted?: string;\r\n }\r\n\r\n export interface MatrixCornerItem {\r\n metadata: DataViewMetadataColumn;\r\n displayName: string;\r\n isColumnHeaderLeaf: boolean;\r\n isRowHeaderLeaf: boolean;\r\n }\r\n\r\n export class MatrixVisualBodyItem extends TablixUtils.TablixVisualCell {\r\n public get isMeasure(): boolean {\r\n return true;\r\n };\r\n\r\n public get isValidUrl(): boolean {\r\n return false;\r\n };\r\n\r\n public get isValidImage(): boolean {\r\n return false;\r\n };\r\n }\r\n\r\n /**\r\n * Interface for refreshing Matrix Data View.\r\n */\r\n export interface MatrixDataAdapter {\r\n update(dataViewMatrix?: DataViewMatrix, isDataComplete?: boolean, updateColumns?: boolean): void;\r\n }\r\n\r\n export interface IMatrixHierarchyNavigator extends controls.ITablixHierarchyNavigator, MatrixDataAdapter {\r\n getDataViewMatrix(): DataViewMatrix;\r\n getLeafCount(hierarchy: MatrixVisualNode[]): number;\r\n getLeafAt(hierarchy: MatrixVisualNode[], index: number): any;\r\n getLeafIndex(item: MatrixVisualNode): number;\r\n getParent(item: MatrixVisualNode): MatrixVisualNode;\r\n getIndex(item: MatrixVisualNode): number;\r\n isLeaf(item: MatrixVisualNode): boolean;\r\n isRowHierarchyLeaf(item: any): boolean;\r\n isColumnHierarchyLeaf(item: any): boolean;\r\n isLastItem(item: MatrixVisualNode, items: MatrixVisualNode[]): boolean;\r\n getChildren(item: MatrixVisualNode): MatrixVisualNode[];\r\n getCount(items: MatrixVisualNode[]): number;\r\n getAt(items: MatrixVisualNode[], index: number): MatrixVisualNode;\r\n getLevel(item: MatrixVisualNode): number;\r\n getIntersection(rowItem: MatrixVisualNode, columnItem: MatrixVisualNode): MatrixVisualBodyItem;\r\n getCorner(rowLevel: number, columnLevel: number): MatrixCornerItem;\r\n headerItemEquals(item1: MatrixVisualNode, item2: MatrixVisualNode): boolean;\r\n }\r\n\r\n interface MatrixHierarchy extends DataViewHierarchy {\r\n leafNodes?: MatrixVisualNode[];\r\n }\r\n \r\n /**\r\n * Factory method used by unit tests.\r\n */\r\n export function createMatrixHierarchyNavigator(matrix: DataViewMatrix,\r\n isDataComplete: boolean,\r\n formatter: ICustomValueColumnFormatter,\r\n compositeGroupSeparator: string): IMatrixHierarchyNavigator {\r\n\r\n return new MatrixHierarchyNavigator(matrix, isDataComplete, formatter, compositeGroupSeparator);\r\n }\r\n\r\n class MatrixHierarchyNavigator implements IMatrixHierarchyNavigator {\r\n private matrix: DataViewMatrix;\r\n private rowHierarchy: MatrixHierarchy;\r\n private columnHierarchy: MatrixHierarchy;\r\n private formatter: ICustomValueColumnFormatter;\r\n private compositeGroupSeparator: string;\r\n\r\n /**\r\n * True if the model is not expecting more data\r\n */\r\n private isDataComplete: boolean;\r\n\r\n constructor(matrix: DataViewMatrix,\r\n isDataComplete: boolean,\r\n formatter: ICustomValueColumnFormatter,\r\n compositeGroupSeparator: string) {\r\n\r\n this.matrix = matrix;\r\n this.rowHierarchy = MatrixHierarchyNavigator.wrapMatrixHierarchy(matrix.rows);\r\n this.columnHierarchy = MatrixHierarchyNavigator.wrapMatrixHierarchy(matrix.columns);\r\n this.formatter = formatter;\r\n this.compositeGroupSeparator = compositeGroupSeparator;\r\n this.isDataComplete = isDataComplete;\r\n\r\n this.update();\r\n }\r\n \r\n /**\r\n * Returns the data view matrix.\r\n */\r\n public getDataViewMatrix(): DataViewMatrix {\r\n return this.matrix;\r\n }\r\n /**\r\n * Returns the depth of the column hierarchy.\r\n */\r\n public getColumnHierarchyDepth(): number {\r\n return Math.max(this.columnHierarchy.levels.length, 1);\r\n }\r\n\r\n /**\r\n * Returns the depth of the Row hierarchy.\r\n */\r\n public getRowHierarchyDepth(): number {\r\n return Math.max(this.rowHierarchy.levels.length, 1);\r\n }\r\n \r\n /**\r\n * Returns the leaf count of a hierarchy.\r\n */\r\n public getLeafCount(hierarchy: MatrixVisualNode[]): number {\r\n let matrixHierarchy = this.getMatrixHierarchy(hierarchy);\r\n if (matrixHierarchy)\r\n return matrixHierarchy.leafNodes.length;\r\n\r\n return 0;\r\n }\r\n \r\n /**\r\n * Returns the leaf member of a hierarchy at a specified index.\r\n */\r\n public getLeafAt(hierarchy: MatrixVisualNode[], index: number): MatrixVisualNode {\r\n let matrixHierarchy = this.getMatrixHierarchy(hierarchy);\r\n if (matrixHierarchy)\r\n return matrixHierarchy.leafNodes[index];\r\n\r\n return null;\r\n }\r\n \r\n /**\r\n * Returns the leaf index of the visual node.\r\n */\r\n public getLeafIndex(item: MatrixVisualNode): number {\r\n debug.assertValue(item, 'item');\r\n\r\n return item.leafIndex;\r\n }\r\n \r\n /**\r\n * Returns the specified hierarchy member parent.\r\n */\r\n public getParent(item: MatrixVisualNode): MatrixVisualNode {\r\n debug.assertValue(item, 'item');\r\n\r\n // Return null for outermost nodes\r\n if (item.level === 0)\r\n return null;\r\n\r\n return item.parent;\r\n }\r\n \r\n /**\r\n * Returns the index of the hierarchy member relative to its parent.\r\n */\r\n public getIndex(item: MatrixVisualNode): number {\r\n debug.assertValue(item, 'item');\r\n return item ? item.index : -1;\r\n }\r\n \r\n /**\r\n * Checks whether a hierarchy member is a leaf.\r\n */\r\n public isLeaf(item: MatrixVisualNode): boolean {\r\n debug.assertValue(item, 'item');\r\n\r\n return !item.children || item.children.length === 0;\r\n }\r\n\r\n public isRowHierarchyLeaf(item: MatrixCornerItem): boolean {\r\n return true;\r\n }\r\n\r\n public isColumnHierarchyLeaf(item: MatrixCornerItem): boolean {\r\n return false;\r\n }\r\n\r\n public isFirstItem(item: MatrixVisualNode, items: MatrixVisualNode[]): boolean {\r\n return item === _.first(items);\r\n }\r\n\r\n public areAllParentsFirst(item: MatrixVisualNode, items: MatrixVisualNode[]): boolean {\r\n if (!item)\r\n return false;\r\n\r\n let parent = this.getParent(item);\r\n if (!parent) {\r\n return this.isFirstItem(item, item.siblings);\r\n }\r\n else {\r\n return this.isFirstItem(item, item.siblings) && this.areAllParentsFirst(parent, parent.siblings);\r\n }\r\n }\r\n\r\n /**\r\n * Checks whether a hierarchy member is the last item within its parent. \r\n */\r\n public isLastItem(item: MatrixVisualNode, items: MatrixVisualNode[]): boolean {\r\n debug.assertValue(item, 'item');\r\n\r\n if(item !== _.last(items))\r\n return false;\r\n\r\n // if item is a row, we need to check that data is complete\r\n return !this.isItemRow(item) || this.isDataComplete;\r\n }\r\n\r\n private isItemRow(item: MatrixVisualNode): boolean {\r\n if (!item)\r\n return false;\r\n\r\n let firstLevelParent = item;\r\n while (firstLevelParent.parent)\r\n firstLevelParent = firstLevelParent.parent;\r\n\r\n return firstLevelParent.siblings === this.rowHierarchy.root.children;\r\n }\r\n\r\n public areAllParentsLast(item: MatrixVisualNode, items: MatrixVisualNode[]): boolean {\r\n if (!item)\r\n return false;\r\n\r\n let parent = this.getParent(item);\r\n if (!parent) {\r\n return this.isLastItem(item, item.siblings);\r\n }\r\n else {\r\n return this.isLastItem(item, item.siblings) && this.areAllParentsLast(parent, parent.siblings);\r\n }\r\n }\r\n \r\n /**\r\n * Gets the children members of a hierarchy member.\r\n */\r\n public getChildren(item: MatrixVisualNode): MatrixVisualNode[] {\r\n debug.assertValue(item, 'item');\r\n\r\n return item.children;\r\n }\r\n \r\n /**\r\n * Gets the difference between current level and highest child's level. Can be > 1 if there are multiple values\r\n * @param {MatrixVisualNode} item\r\n * @returns\r\n */\r\n public getChildrenLevelDifference(item: MatrixVisualNode): number {\r\n let diff = Infinity;\r\n let children = this.getChildren(item);\r\n for (let i = 0, ilen = children.length; i < ilen; i++) {\r\n diff = Math.min(diff, children[i].level - item.level);\r\n }\r\n\r\n return diff;\r\n }\r\n \r\n /**\r\n * Gets the members count in a specified collection.\r\n */\r\n public getCount(items: MatrixVisualNode[]): number {\r\n debug.assertValue(items, 'items');\r\n\r\n return items.length;\r\n }\r\n \r\n /**\r\n * Gets the member at the specified index.\r\n */\r\n public getAt(items: MatrixVisualNode[], index: number): MatrixVisualNode {\r\n debug.assertValue(items, 'items');\r\n\r\n return items[index];\r\n }\r\n \r\n /**\r\n * Gets the hierarchy member level.\r\n */\r\n public getLevel(item: MatrixVisualNode): number {\r\n debug.assertValue(item, 'item');\r\n\r\n return item.level;\r\n }\r\n \r\n /**\r\n * Returns the intersection between a row and a column item.\r\n */\r\n public getIntersection(rowItem: MatrixVisualNode, columnItem: MatrixVisualNode): MatrixVisualBodyItem {\r\n debug.assertValue(rowItem, 'rowItem');\r\n debug.assertValue(columnItem, 'columnItem');\r\n let isSubtotalItem = rowItem.isSubtotal === true || columnItem.isSubtotal === true;\r\n\r\n let node: DataViewMatrixNodeValue;\r\n let valueSource: DataViewMetadataColumn;\r\n let rowIndex: number = rowItem.leafIndex;\r\n let colIndex: number = columnItem.leafIndex;\r\n let bodyCell: MatrixVisualBodyItem;\r\n\r\n if (!rowItem.values) {\r\n node = undefined;\r\n }\r\n else {\r\n node = rowItem.values[columnItem.leafIndex];\r\n }\r\n\r\n if (node) {\r\n valueSource = this.matrix.valueSources[node.valueSourceIndex || 0];\r\n bodyCell = new MatrixVisualBodyItem(node.value, isSubtotalItem, valueSource, this.formatter, false);\r\n }\r\n else {\r\n bodyCell = new MatrixVisualBodyItem(undefined, isSubtotalItem, undefined, this.formatter, false);\r\n }\r\n\r\n bodyCell.position.row.index = rowIndex;\r\n bodyCell.position.row.indexInSiblings = rowItem.siblings.indexOf(rowItem);\r\n bodyCell.position.row.isFirst = rowIndex === 0;\r\n bodyCell.position.row.isLast = this.isDataComplete && (rowIndex === this.rowHierarchy.leafNodes.length - 1);\r\n bodyCell.position.column.index = colIndex;\r\n bodyCell.position.column.indexInSiblings = columnItem.siblings.indexOf(columnItem);\r\n bodyCell.position.column.isFirst = colIndex === 0;\r\n bodyCell.position.column.isLast = colIndex === this.columnHierarchy.leafNodes.length - 1;\r\n\r\n return bodyCell;\r\n }\r\n \r\n /**\r\n * Returns the corner cell between a row and a column level.\r\n */\r\n public getCorner(rowLevel: number, columnLevel: number): MatrixCornerItem {\r\n debug.assert(rowLevel >= 0, 'rowLevel');\r\n debug.assert(columnLevel >= 0, 'columnLevel');\r\n\r\n let columnLevels = this.columnHierarchy.levels;\r\n let rowLevels = this.rowHierarchy.levels;\r\n\r\n if (columnLevel === columnLevels.length - 1 || columnLevels.length === 0) {\r\n let levelSource = rowLevels[rowLevel];\r\n if (levelSource)\r\n return {\r\n metadata: levelSource.sources.length === 1 ? levelSource.sources[0] : null,\r\n displayName: _.map(levelSource.sources, (source) => { return source.displayName; }).join(this.compositeGroupSeparator),\r\n isColumnHeaderLeaf: true,\r\n isRowHeaderLeaf: rowLevel === rowLevels.length - 1,\r\n };\r\n }\r\n\r\n if (rowLevel === rowLevels.length - 1) {\r\n let levelSource = columnLevels[columnLevel];\r\n if (levelSource)\r\n return {\r\n metadata: levelSource.sources.length === 1 ? levelSource.sources[0] : null,\r\n displayName: _.map(levelSource.sources, (source) => { return source.displayName; }).join(this.compositeGroupSeparator),\r\n isColumnHeaderLeaf: false,\r\n isRowHeaderLeaf: true,\r\n };\r\n }\r\n\r\n return {\r\n metadata: null,\r\n displayName: '',\r\n isColumnHeaderLeaf: false,\r\n isRowHeaderLeaf: false,\r\n };\r\n }\r\n\r\n public headerItemEquals(item1: MatrixVisualNode, item2: MatrixVisualNode): boolean {\r\n if (item1 && item2)\r\n return (item1 === item2);\r\n else\r\n return false;\r\n }\r\n\r\n public bodyCellItemEquals(item1: MatrixVisualBodyItem, item2: MatrixVisualBodyItem): boolean {\r\n return (item1.position.isMatch(item2.position));\r\n }\r\n\r\n public cornerCellItemEquals(item1: any, item2: any): boolean {\r\n let corner1 = <MatrixCornerItem>item1;\r\n let corner2 = <MatrixCornerItem>item2;\r\n\r\n if (!corner1 || !corner2)\r\n return false;\r\n\r\n return corner1.displayName === corner2.displayName &&\r\n corner1.isColumnHeaderLeaf === corner2.isColumnHeaderLeaf &&\r\n corner1.isRowHeaderLeaf === corner2.isRowHeaderLeaf &&\r\n corner1.metadata === corner2.metadata;\r\n }\r\n\r\n public getMatrixColumnHierarchy(): MatrixHierarchy {\r\n return this.columnHierarchy;\r\n }\r\n\r\n public getMatrixRowHierarchy(): MatrixHierarchy {\r\n return this.rowHierarchy;\r\n }\r\n \r\n /**\r\n * Implementation for MatrixDataAdapter interface.\r\n */\r\n public update(dataViewMatrix?: DataViewMatrix, isDataComplete?: boolean, updateColumns: boolean = true): void {\r\n if (dataViewMatrix) {\r\n this.matrix = dataViewMatrix;\r\n if (isDataComplete != null)\r\n this.isDataComplete = isDataComplete;\r\n this.rowHierarchy = MatrixHierarchyNavigator.wrapMatrixHierarchy(dataViewMatrix.rows);\r\n if (updateColumns)\r\n this.columnHierarchy = MatrixHierarchyNavigator.wrapMatrixHierarchy(dataViewMatrix.columns);\r\n }\r\n this.updateHierarchy(this.rowHierarchy);\r\n if (updateColumns) {\r\n this.updateHierarchy(this.columnHierarchy);\r\n MatrixHierarchyNavigator.updateStaticColumnHeaders(this.columnHierarchy);\r\n }\r\n }\r\n\r\n private static wrapMatrixHierarchy(hierarchy: DataViewHierarchy): MatrixHierarchy {\r\n let matrixHierarchy = Prototype.inherit<MatrixHierarchy>(hierarchy);\r\n matrixHierarchy.leafNodes = [];\r\n\r\n return matrixHierarchy;\r\n }\r\n\r\n private updateHierarchy(hierarchy: MatrixHierarchy): void {\r\n if (hierarchy.leafNodes.length > 0)\r\n hierarchy.leafNodes.length = 0;\r\n\r\n if (hierarchy.root.children)\r\n this.updateRecursive(hierarchy, hierarchy.root.children, null, hierarchy.leafNodes);\r\n }\r\n\r\n private updateRecursive(hierarchy: MatrixHierarchy, nodes: MatrixVisualNode[], parent: MatrixVisualNode, cache: MatrixVisualNode[]): void {\r\n let level: DataViewHierarchyLevel;\r\n let formatStringPropID = TablixObjects.PropColumnFormatString.getPropertyID();\r\n for (let i = 0, ilen = nodes.length; i < ilen; i++) {\r\n let node = nodes[i];\r\n node.siblings = nodes;\r\n\r\n if (parent)\r\n node.parent = parent;\r\n\r\n if (!level)\r\n level = hierarchy.levels[node.level];\r\n\r\n if (level) {\r\n /**\r\n * Handling Composite-groups\r\n * Setting the name as the comma separated joining of the formatted strings\r\n * Setting QueryName only for non-composite groups\r\n */\r\n if (node.levelValues) {\r\n let displayNames = _.map(node.levelValues, component => {\r\n let source = level.sources[component.levelSourceIndex || 0];\r\n return this.formatter(component.value, source, formatStringPropID, false);\r\n });\r\n\r\n node.valueFormatted = displayNames.join(this.compositeGroupSeparator);\r\n // Explicitly set queryName to undefined for composite groups to suppress sorting and resizing\r\n node.queryName = level.sources.length !== 1 ? undefined : level.sources[0].queryName;\r\n }\r\n else { // Level is a Value\r\n let source = level.sources[node.levelSourceIndex || 0];\r\n node.valueFormatted = source.displayName;\r\n node.queryName = source.queryName;\r\n }\r\n }\r\n\r\n node.index = i;\r\n if (node.children && node.children.length > 0) {\r\n this.updateRecursive(hierarchy, node.children, node, cache);\r\n }\r\n else {\r\n node.leafIndex = cache.length;\r\n cache.push(node);\r\n }\r\n }\r\n }\r\n\r\n private static updateStaticColumnHeaders(columnHierarchy: MatrixHierarchy): void {\r\n let columnLeafNodes = columnHierarchy.leafNodes;\r\n if (columnLeafNodes && columnLeafNodes.length > 0) {\r\n let columnLeafSources = columnHierarchy.levels[columnLeafNodes[0].level].sources;\r\n\r\n for (let i = 0, ilen = columnLeafNodes.length; i < ilen; i++) {\r\n let columnLeafNode = columnLeafNodes[i];\r\n\r\n // Static leaf may need to get label from it's definition for the measures level\r\n if (!columnLeafNode.identity && _.isEmpty(columnLeafNode.levelValues)) {\r\n // We make distincion between null and undefined. Null can be considered as legit value, undefined means we need to fall back to metadata\r\n let source = columnLeafSources[columnLeafNode.levelSourceIndex ? columnLeafNode.levelSourceIndex : 0];\r\n if (source)\r\n columnLeafNode.valueFormatted = source.displayName;\r\n }\r\n }\r\n }\r\n }\r\n\r\n private getMatrixHierarchy(rootNodes: MatrixVisualNode[]): MatrixHierarchy {\r\n let rowHierarchyRootNodes = this.rowHierarchy.root.children;\r\n if (rowHierarchyRootNodes && rootNodes === rowHierarchyRootNodes)\r\n return this.rowHierarchy;\r\n\r\n let columnHierarchyRootNodes = this.columnHierarchy.root.children;\r\n if (columnHierarchyRootNodes && rootNodes === columnHierarchyRootNodes)\r\n return this.columnHierarchy;\r\n\r\n return null;\r\n }\r\n }\r\n\r\n export interface MatrixBinderOptions {\r\n onBindRowHeader?(item: MatrixVisualNode): void;\r\n totalLabel?: string;\r\n onColumnHeaderClick?(queryName: string, sortDirection: SortDirection): void;\r\n showSortIcons?: boolean;\r\n }\r\n\r\n export class MatrixBinder implements controls.ITablixBinder {\r\n private formattingProperties: TablixFormattingPropertiesMatrix;\r\n private hierarchyNavigator: IMatrixHierarchyNavigator;\r\n private options: MatrixBinderOptions;\r\n\r\n private fontSizeHeader: number;\r\n private textPropsHeader: TextProperties;\r\n private textHeightHeader: number;\r\n\r\n private fontSizeValue: number;\r\n private textPropsValue: TextProperties;\r\n private textHeightValue: number;\r\n\r\n private fontSizeTotal: number;\r\n private textPropsTotal: TextProperties;\r\n private textHeightTotal: number;\r\n\r\n constructor(hierarchyNavigator: IMatrixHierarchyNavigator, options: MatrixBinderOptions) {\r\n\r\n // We pass the hierarchy navigator in here because it is the object that will\r\n // survive data changes and gets updated with the latest data view.\r\n this.hierarchyNavigator = hierarchyNavigator;\r\n this.options = options;\r\n }\r\n\r\n public onDataViewChanged(formattingProperties: TablixFormattingPropertiesMatrix): void {\r\n this.formattingProperties = formattingProperties;\r\n this.updateTextHeights();\r\n }\r\n\r\n private updateTextHeights(): void {\r\n this.fontSizeHeader = jsCommon.PixelConverter.fromPointToPixel(this.formattingProperties.general.textSize);\r\n this.textPropsHeader = {\r\n fontFamily: TablixUtils.FontFamilyHeader,\r\n fontSize: jsCommon.PixelConverter.toString(this.fontSizeHeader),\r\n };\r\n this.textHeightHeader = Math.ceil(TextMeasurementService.measureSvgTextHeight(this.textPropsHeader, \"a\"));\r\n\r\n this.fontSizeValue = jsCommon.PixelConverter.fromPointToPixel(this.formattingProperties.general.textSize);\r\n this.textPropsValue = {\r\n fontFamily: TablixUtils.FontFamilyCell,\r\n fontSize: jsCommon.PixelConverter.toString(this.fontSizeValue),\r\n };\r\n this.textHeightValue = Math.ceil(TextMeasurementService.measureSvgTextHeight(this.textPropsValue, \"a\"));\r\n\r\n this.fontSizeTotal = jsCommon.PixelConverter.fromPointToPixel(this.formattingProperties.general.textSize);\r\n this.textPropsTotal = {\r\n fontFamily: TablixUtils.FontFamilyTotal,\r\n fontSize: jsCommon.PixelConverter.toString(this.fontSizeTotal),\r\n };\r\n this.textHeightTotal = Math.ceil(TextMeasurementService.measureSvgTextHeight(this.textPropsTotal, \"a\"));\r\n }\r\n\r\n public onStartRenderingSession(): void {\r\n }\r\n\r\n public onEndRenderingSession(): void {\r\n }\r\n \r\n /**\r\n * Row Header.\r\n */\r\n public bindRowHeader(item: MatrixVisualNode, cell: controls.ITablixCell): void {\r\n TablixUtils.resetCellCssClass(cell);\r\n\r\n let cellStyle = new TablixUtils.CellStyle();\r\n\r\n let isLeaf = this.hierarchyNavigator && this.hierarchyNavigator.isLeaf(item);\r\n if (isLeaf) {\r\n TablixUtils.addCellCssClass(cell, TablixUtils.CssClassMatrixRowHeaderLeaf);\r\n cellStyle.borders.right = new EdgeSettings(TablixObjects.PropGridOutlineWeight.defaultValue, TablixObjects.PropGridOutlineColor.defaultValue);\r\n }\r\n\r\n if (item.isSubtotal) {\r\n cellStyle.paddings.left = TablixUtils.CellPaddingLeftMatrixTotal;\r\n }\r\n\r\n cell.contentWidth = 0;\r\n this.bindHeader(item, cell, cell.extension.contentHost, this.getRowHeaderMetadata(item), cellStyle);\r\n cell.contentWidth = Math.ceil(cell.contentWidth);\r\n\r\n if (this.options.onBindRowHeader)\r\n this.options.onBindRowHeader(item);\r\n\r\n this.setRowHeaderStyle(cell, cellStyle);\r\n\r\n cell.applyStyle(cellStyle);\r\n }\r\n\r\n private setRowHeaderStyle(cell: controls.ITablixCell, style: TablixUtils.CellStyle): void {\r\n let propsGrid = this.formattingProperties.grid;\r\n let props = this.formattingProperties.rowHeaders;\r\n let propsValues = this.formattingProperties.values;\r\n let propsCols = this.formattingProperties.columnHeaders;\r\n\r\n style.borders.top = new EdgeSettings();\r\n if (cell.position.row.isFirst) {\r\n style.borders.top.applyParams(outline.showTop(props.outline), propsGrid.outlineWeight, propsGrid.outlineColor, EdgeType.Outline);\r\n\r\n // If we dont have top border, but Values have, we need to apply extra padding\r\n if (!outline.showTop(props.outline) && outline.showTop(propsValues.outline))\r\n style.paddings.top += propsGrid.outlineWeight;\r\n } // else: do nothing\r\n\r\n style.borders.bottom = new EdgeSettings();\r\n if (cell.position.row.isLast) {\r\n style.borders.bottom.applyParams(outline.showBottom(props.outline), propsGrid.outlineWeight, propsGrid.outlineColor, EdgeType.Outline);\r\n\r\n // If we dont have bottom border, but Values have, we need to apply extra padding\r\n if (!outline.showBottom(props.outline) && outline.showBottom(propsValues.outline))\r\n style.paddings.bottom += propsGrid.outlineWeight;\r\n }\r\n else {\r\n style.borders.bottom.applyParams(propsGrid.gridHorizontal, propsGrid.gridHorizontalWeight, propsGrid.gridHorizontalColor, EdgeType.Gridline);\r\n }\r\n\r\n style.borders.left = new EdgeSettings();\r\n if (cell.position.column.isFirst) {\r\n style.borders.left.applyParams(outline.showLeft(props.outline), propsGrid.outlineWeight, propsGrid.outlineColor, EdgeType.Outline);\r\n\r\n // If we dont have left border, but Column Headers have, we need to apply extra padding\r\n if (!outline.showLeft(props.outline) && outline.showLeft(propsCols.outline))\r\n style.paddings.left += propsGrid.outlineWeight;\r\n } // else: do nothing\r\n\r\n style.borders.right = new EdgeSettings();\r\n if (cell.position.column.isLast) {\r\n style.borders.right.applyParams(outline.showRight(props.outline), propsGrid.outlineWeight, propsGrid.outlineColor, EdgeType.Outline);\r\n }\r\n else {\r\n style.borders.right.applyParams(propsGrid.gridVertical, propsGrid.gridVerticalWeight, propsGrid.gridVerticalColor, EdgeType.Gridline);\r\n }\r\n\r\n style.fontColor = props.fontColor;\r\n style.backColor = props.backColor;\r\n style.paddings.top = style.paddings.bottom = propsGrid.rowPadding;\r\n }\r\n\r\n public unbindRowHeader(item: any, cell: controls.ITablixCell): void {\r\n TablixUtils.clearCellStyle(cell);\r\n TablixUtils.clearCellTextAndTooltip(cell);\r\n }\r\n \r\n /**\r\n * Column Header.\r\n */\r\n public bindColumnHeader(item: MatrixVisualNode, cell: controls.ITablixCell): void {\r\n TablixUtils.resetCellCssClass(cell);\r\n\r\n // Set default style\r\n let cellStyle = new TablixUtils.CellStyle();\r\n\r\n let overwriteTotalLabel = false;\r\n\r\n let cellElement = cell.extension.contentHost;\r\n\r\n cell.contentWidth = 0;\r\n let isLeaf = this.hierarchyNavigator && this.hierarchyNavigator.isLeaf(item);\r\n if (isLeaf) {\r\n cellStyle.borders.bottom = new EdgeSettings(TablixObjects.PropGridOutlineWeight.defaultValue, TablixObjects.PropGridOutlineColor.defaultValue);\r\n\r\n TablixUtils.addCellCssClass(cell, TablixUtils.CssClassTablixColumnHeaderLeaf);\r\n TablixUtils.addCellCssClass(cell, TablixUtils.CssClassTablixValueNumeric);\r\n\r\n if (this.options.showSortIcons) {\r\n let sortableHeaderColumnMetadata = this.getSortableHeaderColumnMetadata(item);\r\n if (sortableHeaderColumnMetadata) {\r\n this.registerColumnHeaderClickHandler(sortableHeaderColumnMetadata, cell);\r\n cellElement = TablixUtils.addSortIconToColumnHeader(sortableHeaderColumnMetadata.sort, cellElement);\r\n\r\n if (sortableHeaderColumnMetadata.sort) {\r\n // Glyph font has all characters width/height same as font size\r\n cell.contentWidth = this.fontSizeHeader + TablixUtils.SortIconPadding;\r\n }\r\n }\r\n }\r\n\r\n // Overwrite only if the there are subtotal siblings (like in the multimeasure case), which means ALL siblings are subtotals.\r\n if (item.isSubtotal && item.parent && item.parent.children.length > 1 && (<MatrixVisualNode>item.parent.children[0]).isSubtotal)\r\n overwriteTotalLabel = true;\r\n }\r\n\r\n cell.extension.disableDragResize();\r\n this.bindHeader(item, cell, cellElement, this.getColumnHeaderMetadata(item), cellStyle, overwriteTotalLabel);\r\n cell.contentWidth = Math.ceil(cell.contentWidth);\r\n\r\n this.setColumnHeaderStyle(cell, cellStyle);\r\n\r\n cell.applyStyle(cellStyle);\r\n }\r\n\r\n private setColumnHeaderStyle(cell: controls.ITablixCell, style: TablixUtils.CellStyle): void {\r\n let propsGrid = this.formattingProperties.grid;\r\n let props = this.formattingProperties.columnHeaders;\r\n let propsValues = this.formattingProperties.values;\r\n\r\n style.fontColor = props.fontColor;\r\n style.backColor = props.backColor;\r\n style.paddings.top = style.paddings.bottom = propsGrid.rowPadding;\r\n\r\n style.borders.top = new EdgeSettings();\r\n if (cell.position.row.isFirst) {\r\n style.borders.top.applyParams(outline.showTop(props.outline), propsGrid.outlineWeight, propsGrid.outlineColor, EdgeType.Outline);\r\n } // else: do nothing\r\n\r\n style.borders.bottom = new EdgeSettings();\r\n if (cell.position.row.isLast) {\r\n style.borders.bottom.applyParams(outline.showBottom(props.outline), propsGrid.outlineWeight, propsGrid.outlineColor, EdgeType.Outline);\r\n }\r\n else {\r\n style.borders.bottom.applyParams(propsGrid.gridHorizontal, propsGrid.gridHorizontalWeight, propsGrid.gridHorizontalColor, EdgeType.Gridline);\r\n }\r\n\r\n style.borders.left = new EdgeSettings();\r\n if (cell.position.column.isFirst) {\r\n // If we dont have left border, but Values have, we need to apply extra padding\r\n if (!outline.showLeft(props.outline) && outline.showLeft(propsValues.outline))\r\n style.paddings.left += propsGrid.outlineWeight;\r\n }\r\n\r\n style.borders.right = new EdgeSettings();\r\n if (cell.position.column.isLast) {\r\n style.borders.right.applyParams(outline.showRight(props.outline), propsGrid.outlineWeight, propsGrid.outlineColor, EdgeType.Outline);\r\n\r\n // If we dont have right border, but Values have, we need to apply extra padding\r\n if (!outline.showRight(props.outline) && outline.showRight(propsValues.outline))\r\n style.paddings.right += propsGrid.outlineWeight;\r\n }\r\n else {\r\n style.borders.right.applyParams(propsGrid.gridVertical, propsGrid.gridVerticalWeight, propsGrid.gridVerticalColor, EdgeType.Gridline);\r\n }\r\n }\r\n\r\n public unbindColumnHeader(item: MatrixVisualNode, cell: controls.ITablixCell): void {\r\n TablixUtils.clearCellStyle(cell);\r\n TablixUtils.clearCellTextAndTooltip(cell);\r\n\r\n let sortableHeaderColumnMetadata = this.getSortableHeaderColumnMetadata(item);\r\n if (sortableHeaderColumnMetadata) {\r\n this.unregisterColumnHeaderClickHandler(cell);\r\n }\r\n\r\n if (this.options.showSortIcons)\r\n TablixUtils.removeSortIcons(cell);\r\n }\r\n\r\n private bindHeader(item: MatrixVisualNode,\r\n cell: controls.ITablixCell,\r\n cellElement: HTMLElement,\r\n metadata: DataViewMetadataColumn,\r\n style: TablixUtils.CellStyle,\r\n overwriteSubtotalLabel?: boolean): void {\r\n TablixUtils.addCellCssClass(cell, TablixUtils.CssClassTablixHeader);\r\n\r\n style.fontFamily = TablixUtils.FontFamilyHeader;\r\n style.fontColor = TablixUtils.FontColorHeaders;\r\n\r\n let imgHeight = this.formattingProperties.grid.imageHeight;\r\n\r\n if (converterHelper.isImageUrlColumn(metadata))\r\n cell.contentHeight = imgHeight;\r\n else if (item.isSubtotal)\r\n cell.contentHeight = this.textHeightTotal;\r\n else\r\n cell.contentHeight = this.textHeightValue;\r\n\r\n if (item.isSubtotal) {\r\n TablixUtils.addCellCssClass(cell, TablixUtils.CssClassTablixValueTotal);\r\n style.fontFamily = TablixUtils.FontFamilyTotal;\r\n\r\n if (!overwriteSubtotalLabel) {\r\n TablixUtils.setCellTextAndTooltip(this.options.totalLabel, cellElement, cell.extension.contentHost);\r\n cell.contentWidth = TextMeasurementService.measureSvgTextWidth(this.textPropsTotal, this.options.totalLabel);\r\n return;\r\n }\r\n }\r\n\r\n let value = this.getHeaderLabel(item);\r\n // If item is empty text, set text to a space to maintain height\r\n if (!value) {\r\n cellElement.innerHTML = TablixUtils.StringNonBreakingSpace;\r\n // Keep cell.width assigned as 0\r\n return;\r\n }\r\n\r\n // if item is a Valid URL, set an Anchor tag\r\n if (converterHelper.isWebUrlColumn(metadata) && UrlUtils.isValidUrl(value)) {\r\n TablixUtils.appendATagToBodyCell(value, cellElement);\r\n cell.contentWidth += TextMeasurementService.measureSvgTextWidth(this.textPropsHeader, value);\r\n return;\r\n }\r\n\r\n // if item is an Image, if it's valid create an Img tag, if not insert text\r\n if (converterHelper.isImageUrlColumn(metadata) && UrlUtils.isValidImageUrl(value)) {\r\n TablixUtils.appendImgTagToBodyCell(item.valueFormatted, cellElement, imgHeight);\r\n cell.contentWidth += imgHeight * TablixUtils.ImageDefaultAspectRatio;\r\n return;\r\n }\r\n\r\n // if item is text, insert it\r\n TablixUtils.setCellTextAndTooltip(value, cellElement, cell.extension.contentHost);\r\n cell.contentWidth += TextMeasurementService.measureSvgTextWidth(\r\n item.isSubtotal ? this.textPropsTotal : this.textPropsHeader,\r\n value);\r\n }\r\n\r\n private registerColumnHeaderClickHandler(columnMetadata: DataViewMetadataColumn, cell: controls.ITablixCell): void {\r\n if (this.options.onColumnHeaderClick) {\r\n let handler = (e: MouseEvent) => {\r\n if (TablixUtils.isValidSortClick(e)) {\r\n let sortDirection: SortDirection = TablixUtils.reverseSort(columnMetadata.sort);\r\n this.options.onColumnHeaderClick(columnMetadata.queryName ? columnMetadata.queryName : columnMetadata.displayName, sortDirection);\r\n }\r\n };\r\n cell.extension.registerClickHandler(handler);\r\n }\r\n }\r\n\r\n private unregisterColumnHeaderClickHandler(cell: controls.ITablixCell) {\r\n if (this.options.onColumnHeaderClick) {\r\n cell.extension.unregisterClickHandler();\r\n }\r\n }\r\n\r\n /**\r\n * Body Cell.\r\n */\r\n public bindBodyCell(item: MatrixVisualBodyItem, cell: controls.ITablixCell): void {\r\n TablixUtils.resetCellCssClass(cell);\r\n\r\n let cellStyle = new TablixUtils.CellStyle();\r\n\r\n cell.contentHeight = this.textHeightValue;\r\n\r\n let kpi = item.kpiContent;\r\n if (kpi) {\r\n $(cell.extension.contentHost).append(kpi);\r\n\r\n // Glyph font has all characters width/height same as font size\r\n cell.contentWidth = this.fontSizeValue;\r\n }\r\n else\r\n {\r\n let textProps = this.textPropsValue;\r\n\r\n TablixUtils.addCellCssClass(cell, TablixUtils.CssClassTablixValueNumeric);\r\n if (item.isTotal) {\r\n TablixUtils.addCellCssClass(cell, TablixUtils.CssClassTablixValueTotal);\r\n cellStyle.fontFamily = TablixUtils.FontFamilyTotal;\r\n cell.contentHeight = this.textHeightTotal;\r\n textProps = this.textPropsTotal;\r\n }\r\n\r\n let textContent = item.textContent;\r\n\r\n if (textContent) {\r\n TablixUtils.setCellTextAndTooltip(textContent, cell.extension.contentHost);\r\n cell.contentWidth = TextMeasurementService.measureSvgTextWidth(textProps, textContent);\r\n }\r\n }\r\n\r\n cell.contentWidth = Math.ceil(cell.contentWidth);\r\n\r\n this.setBodyCellStyle(cell, item, cellStyle);\r\n cell.applyStyle(cellStyle);\r\n }\r\n\r\n private setBodyCellStyle(cell: controls.ITablixCell, item: MatrixVisualBodyItem, style: TablixUtils.CellStyle): void {\r\n let propsGrid = this.formattingProperties.grid;\r\n let props = this.formattingProperties.values;\r\n let propsTotal = this.formattingProperties.subtotals;\r\n let propsRows = this.formattingProperties.rowHeaders;\r\n let propsColumns = this.formattingProperties.columnHeaders;\r\n\r\n style.paddings.top = style.paddings.bottom = propsGrid.rowPadding;\r\n\r\n style.borders.top = new EdgeSettings();\r\n if (cell.position.row.isFirst) { // First Row\r\n style.borders.top.applyParams(outline.showTop(props.outline), propsGrid.outlineWeight, propsGrid.outlineColor, EdgeType.Outline);\r\n\r\n // If we dont have top border, but Row Headers have, we need to apply extra padding\r\n if (!outline.showTop(props.outline) && outline.showTop(propsRows.outline))\r\n style.paddings.top += propsGrid.outlineWeight;\r\n\r\n } // else: do nothing\r\n\r\n style.borders.bottom = new EdgeSettings();\r\n if (cell.position.row.isLast) { // Last Row\r\n style.borders.bottom.applyParams(outline.showBottom(props.outline), propsGrid.outlineWeight, propsGrid.outlineColor, EdgeType.Outline);\r\n\r\n // If we dont have bottom border, but Row Headers have, we need to apply extra padding\r\n if (!outline.showBottom(props.outline) && outline.showBottom(propsRows.outline))\r\n style.paddings.bottom += propsGrid.outlineWeight;\r\n }\r\n else {\r\n style.borders.bottom.applyParams(propsGrid.gridHorizontal, propsGrid.gridHorizontalWeight, propsGrid.gridHorizontalColor);\r\n }\r\n\r\n style.borders.left = new EdgeSettings();\r\n if (cell.position.column.isFirst) { // First Column \r\n style.borders.left.applyParams(outline.showLeft(props.outline), propsGrid.outlineWeight, propsGrid.outlineColor, EdgeType.Outline);\r\n } // else: do nothing\r\n\r\n style.borders.right = new EdgeSettings();\r\n if (cell.position.column.isLast) { // Last Column\r\n style.borders.right.applyParams(outline.showRight(props.outline), propsGrid.outlineWeight, propsGrid.outlineColor, EdgeType.Outline);\r\n\r\n // If we dont have right border, but Column Headers have, we need to apply extra padding\r\n if (!outline.showRight(props.outline) && outline.showRight(propsColumns.outline))\r\n style.paddings.right += propsGrid.outlineWeight;\r\n }\r\n else {\r\n style.borders.right.applyParams(propsGrid.gridVertical, propsGrid.gridVerticalWeight, propsGrid.gridVerticalColor, EdgeType.Gridline);\r\n }\r\n\r\n let rowBandingIndex: number;\r\n if (this.formattingProperties.general.rowSubtotals && propsTotal.backColor) // Totals breaking banding sequence\r\n rowBandingIndex = item.position.row.indexInSiblings;\r\n else\r\n rowBandingIndex = item.position.row.index;\r\n\r\n if (item.isTotal && propsTotal.fontColor) {\r\n style.fontColor = propsTotal.fontColor;\r\n }\r\n else {\r\n style.fontColor = rowBandingIndex % 2 === 0 ? props.fontColorPrimary : props.fontColorSecondary;\r\n }\r\n\r\n if (item.isTotal && propsTotal.backColor) {\r\n style.backColor = propsTotal.backColor;\r\n }\r\n else {\r\n style.backColor = rowBandingIndex % 2 === 0 ? props.backColorPrimary : props.backColorSecondary;\r\n }\r\n }\r\n\r\n public unbindBodyCell(item: MatrixVisualBodyItem, cell: controls.ITablixCell): void {\r\n TablixUtils.clearCellStyle(cell);\r\n TablixUtils.clearCellTextAndTooltip(cell);\r\n }\r\n \r\n /**\r\n * Corner Cell.\r\n */\r\n public bindCornerCell(item: MatrixCornerItem, cell: controls.ITablixCell): void {\r\n TablixUtils.resetCellCssClass(cell);\r\n\r\n let cellStyle = new TablixUtils.CellStyle();\r\n\r\n cellStyle.fontFamily = TablixUtils.FontFamilyHeader;\r\n cellStyle.fontColor = TablixUtils.FontColorHeaders;\r\n\r\n cell.contentHeight = this.textHeightHeader;\r\n cell.contentWidth = 0;\r\n\r\n let cellElement = cell.extension.contentHost;\r\n if (item.isColumnHeaderLeaf) {\r\n TablixUtils.addCellCssClass(cell, TablixUtils.CssClassTablixColumnHeaderLeaf);\r\n\r\n cellStyle.borders.bottom = new EdgeSettings(TablixObjects.PropGridOutlineWeight.defaultValue, TablixObjects.PropGridOutlineColor.defaultValue);\r\n\r\n if (this.options.showSortIcons) {\r\n let cornerHeaderMetadata = this.getSortableCornerColumnMetadata(item);\r\n if (cornerHeaderMetadata) {\r\n this.registerColumnHeaderClickHandler(cornerHeaderMetadata, cell);\r\n cellElement = TablixUtils.addSortIconToColumnHeader((cornerHeaderMetadata ? cornerHeaderMetadata.sort : undefined), cellElement);\r\n\r\n if (cornerHeaderMetadata.sort) {\r\n // Glyph font has all characters width/height same as font size\r\n cell.contentWidth = this.fontSizeHeader + TablixUtils.SortIconPadding;\r\n }\r\n }\r\n }\r\n }\r\n\r\n TablixUtils.setCellTextAndTooltip(item.displayName, cellElement, cell.extension.contentHost);\r\n cell.contentWidth += TextMeasurementService.measureSvgTextWidth(this.textPropsHeader, item.displayName);\r\n cell.contentWidth = Math.ceil(cell.contentWidth);\r\n\r\n if (item.isRowHeaderLeaf) {\r\n TablixUtils.addCellCssClass(cell, TablixUtils.CssClassMatrixRowHeaderLeaf);\r\n }\r\n\r\n TablixUtils.addCellCssClass(cell, TablixUtils.CssClassTablixHeader);\r\n\r\n this.setCornerCellsStyle(cell, cellStyle);\r\n\r\n cell.applyStyle(cellStyle);\r\n cell.extension.disableDragResize();\r\n }\r\n\r\n private setCornerCellsStyle(cell: controls.ITablixCell, style: TablixUtils.CellStyle): void {\r\n let propsGrid = this.formattingProperties.grid;\r\n let propsCol = this.formattingProperties.columnHeaders;\r\n let propsRow = this.formattingProperties.rowHeaders;\r\n\r\n style.fontColor = propsCol.fontColor || propsRow.fontColor;\r\n style.backColor = propsCol.backColor || propsRow.backColor;\r\n\r\n style.paddings.top = style.paddings.bottom = propsGrid.rowPadding;\r\n\r\n style.borders.top = new EdgeSettings();\r\n if (cell.position.row.isFirst) {\r\n style.borders.top.applyParams(outline.showTop(propsCol.outline), propsGrid.outlineWeight, propsGrid.outlineColor, EdgeType.Outline);\r\n } // else: do nothing\r\n\r\n style.borders.bottom = new EdgeSettings();\r\n if (cell.position.row.isLast) {\r\n style.borders.bottom.applyParams(outline.showBottom(propsCol.outline), propsGrid.outlineWeight, propsGrid.outlineColor, EdgeType.Outline);\r\n }\r\n else {\r\n style.borders.bottom.applyParams(propsGrid.gridHorizontal, propsGrid.gridHorizontalWeight, propsGrid.gridHorizontalColor, EdgeType.Gridline);\r\n }\r\n\r\n style.borders.left = new EdgeSettings();\r\n if (cell.position.column.isFirst) {\r\n style.borders.left.applyParams(outline.showLeft(propsCol.outline), propsGrid.outlineWeight, propsGrid.outlineColor, EdgeType.Outline);\r\n\r\n // If we dont have left border, but Row Headers have, we need to apply extra padding\r\n if (!outline.showLeft(propsCol.outline) && outline.showLeft(propsRow.outline))\r\n style.paddings.left += propsGrid.outlineWeight;\r\n } // else: do nothing\r\n\r\n style.borders.right = new EdgeSettings();\r\n style.borders.right.applyParams(propsGrid.gridVertical, propsGrid.gridVerticalWeight, propsGrid.gridVerticalColor, EdgeType.Gridline);\r\n }\r\n\r\n public unbindCornerCell(item: MatrixCornerItem, cell: controls.ITablixCell): void {\r\n TablixUtils.clearCellStyle(cell);\r\n TablixUtils.clearCellTextAndTooltip(cell);\r\n\r\n if (this.options.showSortIcons)\r\n TablixUtils.removeSortIcons(cell);\r\n\r\n if (item.isColumnHeaderLeaf) {\r\n this.unregisterColumnHeaderClickHandler(cell);\r\n }\r\n }\r\n\r\n public bindEmptySpaceHeaderCell(cell: controls.ITablixCell): void {\r\n }\r\n\r\n public unbindEmptySpaceHeaderCell(cell: controls.ITablixCell): void {\r\n }\r\n\r\n public bindEmptySpaceFooterCell(cell: controls.ITablixCell): void {\r\n }\r\n\r\n public unbindEmptySpaceFooterCell(cell: controls.ITablixCell): void {\r\n }\r\n \r\n /**\r\n * Measurement Helper.\r\n */\r\n public getHeaderLabel(item: MatrixVisualNode): string {\r\n return item.valueFormatted;\r\n }\r\n\r\n public getCellContent(item: MatrixVisualBodyItem): string {\r\n return item.textContent || '';\r\n }\r\n\r\n public hasRowGroups(): boolean {\r\n // Figure out whether we have a static row header, i.e., not row groups\r\n let dataView = this.hierarchyNavigator.getDataViewMatrix();\r\n\r\n if (!dataView || !dataView.rows || !dataView.rows.levels || dataView.rows.levels.length === 0)\r\n return false;\r\n\r\n return true;\r\n }\r\n\r\n /**\r\n * Returns the column metadata of the column that needs to be sorted for the specified matrix corner node.\r\n * \r\n * @return Column metadata or null if the specified corner node does not represent a sortable header.\r\n */\r\n private getSortableCornerColumnMetadata(item: MatrixCornerItem): DataViewMetadataColumn {\r\n if (item.isColumnHeaderLeaf)\r\n return item.metadata;\r\n }\r\n\r\n private getRowHeaderMetadata(item: MatrixVisualNode): DataViewMetadataColumn {\r\n if (!this.hierarchyNavigator || !item)\r\n return;\r\n\r\n let dataView = this.hierarchyNavigator.getDataViewMatrix();\r\n\r\n if (!dataView || !dataView.rows)\r\n return;\r\n\r\n return this.getHierarchyMetadata(dataView.rows, item.level);\r\n }\r\n\r\n private getColumnHeaderMetadata(item: MatrixVisualNode): DataViewMetadataColumn {\r\n if (!this.hierarchyNavigator || !item)\r\n return;\r\n\r\n let dataView = this.hierarchyNavigator.getDataViewMatrix();\r\n if (!dataView || !dataView.columns)\r\n return;\r\n\r\n return this.getHierarchyMetadata(dataView.columns, item.level);\r\n }\r\n\r\n private getHierarchyMetadata(hierarchy: DataViewHierarchy, level: number): DataViewMetadataColumn {\r\n if (!hierarchy || !hierarchy.levels || hierarchy.levels.length < level)\r\n return;\r\n\r\n let levelInfo = hierarchy.levels[level];\r\n if (!levelInfo || !levelInfo.sources || levelInfo.sources.length === 0)\r\n return;\r\n\r\n // This assumes the source will always be the first item in the array of sources.\r\n return levelInfo.sources[0];\r\n }\r\n \r\n /**\r\n * Returns the column metadata of the column that needs to be sorted for the specified header node.\r\n * \r\n * @return Column metadata or null if the specified header node does not represent a sortable header.\r\n */\r\n private getSortableHeaderColumnMetadata(item: MatrixVisualNode): DataViewMetadataColumn {\r\n let dataView = this.hierarchyNavigator.getDataViewMatrix();\r\n\r\n // If there are no row groups, sorting is not supported (as it does not make sense).\r\n if (!dataView.rows || !dataView.rows.levels || dataView.rows.levels.length === 0)\r\n return null;\r\n\r\n let isMultiMeasure: boolean = dataView.valueSources && dataView.valueSources.length > 1;\r\n\r\n let columnGroupCount = dataView.columns ? dataView.columns.levels.length : 0;\r\n\r\n // If we have multiple values, they establish an extra level, so need to subtract 1\r\n if (isMultiMeasure) {\r\n columnGroupCount--;\r\n }\r\n // Check if it has only 1 measure with no column groups\r\n else if (columnGroupCount === 1 &&\r\n dataView.columns.levels[0] &&\r\n dataView.columns.levels[0].sources && dataView.columns.levels[0].sources[0] &&\r\n dataView.columns.levels[0].sources[0].roles && dataView.columns.levels[0].sources[0].roles[\"Values\"]) {\r\n columnGroupCount = 0;\r\n }\r\n\r\n let valueIndex: number = -1;\r\n if (columnGroupCount === 0) {\r\n // Matrices without column groups, support sorting on all columns (which are then measure columns).\r\n valueIndex = item.levelSourceIndex;\r\n }\r\n else if (item.isSubtotal) {\r\n // Matrices with column groups support sorting only on the column grand total.\r\n if (isMultiMeasure) {\r\n // In the multi-measure case we need to check if the parent's level is 0 in order\r\n // to determine whether this is the column grand total. The cells are layed out such\r\n // that the clickable cells are at the innermost level, but the parent for the column\r\n // grand total will have level 0.\r\n if (item.parent && item.parent.level === 0)\r\n valueIndex = item.levelSourceIndex;\r\n }\r\n else {\r\n // In the single-measure case we can directly check the level of the subtotal to\r\n // detect the column grand total (at level 0).\r\n if (item.level === 0)\r\n valueIndex = item.levelSourceIndex;\r\n }\r\n }\r\n\r\n if (valueIndex !== -1) {\r\n // NOTE: if the valueIndex is undefined it implicitly means that it is 0 based on the \r\n // visual node contract\r\n valueIndex = valueIndex ? valueIndex : 0;\r\n return dataView.valueSources[valueIndex];\r\n }\r\n\r\n return null;\r\n }\r\n }\r\n\r\n export class Matrix implements IVisual {\r\n private static preferredLoadMoreThreshold: number = 0.8;\r\n \r\n /**\r\n * Note: Public only for testing.\r\n */\r\n public static TotalLabel = 'TableTotalLabel';\r\n\r\n private element: JQuery;\r\n private currentViewport: IViewport;\r\n private style: IVisualStyle;\r\n private dataView: DataView;\r\n private formatter: ICustomValueColumnFormatter;\r\n private isInteractive: boolean;\r\n private hostServices: IVisualHostServices;\r\n private hierarchyNavigator: IMatrixHierarchyNavigator;\r\n private waitingForData: boolean;\r\n private tablixControl: controls.TablixControl;\r\n private lastAllowHeaderResize: boolean;\r\n private waitingForSort: boolean;\r\n private columnWidthManager: controls.TablixColumnWidthManager;\r\n\r\n /**\r\n * Flag indicating that we are persisting objects, so that next onDataChanged can be safely ignored.\r\n */\r\n public persistingObjects: boolean;\r\n\r\n constructor() {\r\n }\r\n\r\n public static customizeQuery(options: CustomizeQueryOptions): void {\r\n let dataViewMapping = options.dataViewMappings[0];\r\n if (!dataViewMapping || !dataViewMapping.matrix || !dataViewMapping.metadata)\r\n return;\r\n\r\n let dataViewMatrix: data.CompiledDataViewMatrixMapping = <data.CompiledDataViewMatrixMapping>dataViewMapping.matrix;\r\n\r\n // If Columns Hierarchy is not empty, set Window DataReduction Count to 100\r\n if (!_.isEmpty(dataViewMatrix.columns.for.in.items)) {\r\n dataViewMatrix.rows.dataReductionAlgorithm.window.count = 100;\r\n }\r\n let objects: DataViewObjects = dataViewMapping.metadata.objects;\r\n (<data.CompiledDataViewRoleForMappingWithReduction>dataViewMatrix.rows).for.in.subtotalType = TablixObjects.shouldShowRowSubtotals(objects) ? data.CompiledSubtotalType.After : data.CompiledSubtotalType.None;\r\n dataViewMatrix.columns.for.in.subtotalType = TablixObjects.shouldShowColumnSubtotals(objects) ? data.CompiledSubtotalType.After : data.CompiledSubtotalType.None;\r\n }\r\n\r\n public static getSortableRoles(): string[] {\r\n return ['Rows', 'Values'];\r\n }\r\n\r\n public init(options: VisualInitOptions): void {\r\n this.element = options.element;\r\n this.style = options.style;\r\n this.updateViewport(options.viewport);\r\n this.formatter = valueFormatter.formatVariantMeasureValue;\r\n this.isInteractive = options.interactivity && options.interactivity.selection != null;\r\n this.hostServices = options.host;\r\n this.persistingObjects = false;\r\n\r\n this.waitingForData = false;\r\n this.lastAllowHeaderResize = true;\r\n this.waitingForSort = false;\r\n }\r\n\r\n public static converter(dataView: DataView): TablixFormattingPropertiesMatrix {\r\n debug.assertValue(dataView, 'dataView');\r\n\r\n return TablixObjects.getMatrixObjects(dataView);\r\n }\r\n\r\n public onResizing(finalViewport: IViewport): void {\r\n this.updateViewport(finalViewport);\r\n }\r\n\r\n /*\r\n Public for testing\r\n */\r\n public getColumnWidthManager(): controls.TablixColumnWidthManager {\r\n return this.columnWidthManager;\r\n }\r\n\r\n public onDataChanged(options: VisualDataChangedOptions): void {\r\n debug.assertValue(options, 'options');\r\n\r\n let dataViews = options.dataViews;\r\n\r\n if (dataViews && dataViews.length > 0) {\r\n let previousDataView = this.dataView;\r\n this.dataView = dataViews[0];\r\n\r\n // We don't check for persisting flag\r\n // Any change to the Column Widths need to go through to update all column group instances\r\n // ToDo: Consider not resetting scrollbar everytime\r\n\r\n let formattingProperties = Matrix.converter(this.dataView);\r\n let textSize = formattingProperties.general.textSize;\r\n\r\n if (options.operationKind === VisualDataChangeOperationKind.Append) {\r\n // If Root for Rows or Columns has changed by the DataViewTransform (e.g. when having reorders in values)\r\n let rootChanged = previousDataView.matrix.rows.root !== this.dataView.matrix.rows.root;\r\n this.createOrUpdateHierarchyNavigator(rootChanged);\r\n \r\n if (rootChanged)\r\n this.tablixControl.updateModels(/*resetScrollOffsets*/false, this.dataView.matrix.rows.root.children, this.dataView.matrix.columns.root.children);\r\n\r\n this.refreshControl(/*clear*/false);\r\n } else {\r\n this.createOrUpdateHierarchyNavigator(true);\r\n this.createColumnWidthManager();\r\n this.createTablixControl(textSize);\r\n let binder = <MatrixBinder>this.tablixControl.getBinder();\r\n binder.onDataViewChanged(formattingProperties);\r\n\r\n this.updateInternal(textSize, previousDataView);\r\n }\r\n }\r\n\r\n this.waitingForData = false;\r\n this.waitingForSort = false;\r\n }\r\n\r\n private createColumnWidthManager(): void {\r\n let columnHierarchy: MatrixHierarchy = (<MatrixHierarchyNavigator>this.hierarchyNavigator).getMatrixColumnHierarchy();\r\n if (!this.columnWidthManager) {\r\n this.columnWidthManager = new controls.TablixColumnWidthManager(this.dataView, true /* isMatrix */, (objectInstances: VisualObjectInstancesToPersist) => this.persistColumnWidths(objectInstances), columnHierarchy.leafNodes);\r\n }\r\n // Dont update if dataView is coming from persisting\r\n else if (!this.persistingObjects) {\r\n this.columnWidthManager.updateDataView(this.dataView, columnHierarchy.leafNodes);\r\n }\r\n }\r\n\r\n private persistColumnWidths(objectInstances: VisualObjectInstancesToPersist): void {\r\n this.persistingObjects = true;\r\n this.hostServices.persistProperties(objectInstances);\r\n }\r\n\r\n private updateViewport(newViewport: IViewport) {\r\n this.currentViewport = newViewport;\r\n\r\n if (this.tablixControl) {\r\n this.tablixControl.viewport = this.currentViewport;\r\n this.verifyHeaderResize();\r\n\r\n this.refreshControl(/*clear*/false);\r\n }\r\n }\r\n\r\n private refreshControl(clear: boolean) {\r\n if (visibilityHelper.partiallyVisible(this.element) || this.getLayoutKind() === controls.TablixLayoutKind.DashboardTile) {\r\n this.tablixControl.refresh(clear);\r\n }\r\n }\r\n\r\n private getLayoutKind() {\r\n return this.isInteractive ? controls.TablixLayoutKind.Canvas : controls.TablixLayoutKind.DashboardTile;\r\n }\r\n\r\n private createOrUpdateHierarchyNavigator(rootChanged: boolean): void {\r\n let isDataComplete = !this.dataView.metadata.segment;\r\n\r\n if (!this.tablixControl) {\r\n let matrixNavigator = createMatrixHierarchyNavigator(this.dataView.matrix, isDataComplete, this.formatter, this.hostServices.getLocalizedString('ListJoin_Separator'));\r\n this.hierarchyNavigator = matrixNavigator;\r\n }\r\n else {\r\n this.hierarchyNavigator.update(this.dataView.matrix, isDataComplete, rootChanged);\r\n }\r\n }\r\n\r\n private createTablixControl(textSize: number): void {\r\n if (!this.tablixControl) {\r\n // Create the control\r\n this.tablixControl = this.createControl(this.hierarchyNavigator, textSize);\r\n }\r\n }\r\n\r\n private createControl(matrixNavigator: IMatrixHierarchyNavigator, textSize: number): controls.TablixControl {\r\n let layoutKind = this.getLayoutKind();\r\n\r\n let matrixBinderOptions: MatrixBinderOptions = {\r\n onBindRowHeader: (item: MatrixVisualNode) => { this.onBindRowHeader(item); },\r\n totalLabel: this.hostServices.getLocalizedString(Matrix.TotalLabel),\r\n onColumnHeaderClick: (queryName: string, sortDirection: SortDirection) => this.onColumnHeaderClick(queryName, sortDirection),\r\n showSortIcons: layoutKind === controls.TablixLayoutKind.Canvas,\r\n };\r\n let matrixBinder = new MatrixBinder(this.hierarchyNavigator, matrixBinderOptions);\r\n\r\n let layoutManager: controls.internal.TablixLayoutManager = layoutKind === controls.TablixLayoutKind.DashboardTile\r\n ? controls.internal.DashboardTablixLayoutManager.createLayoutManager(matrixBinder)\r\n : controls.internal.CanvasTablixLayoutManager.createLayoutManager(matrixBinder, this.columnWidthManager);\r\n\r\n let tablixContainer = document.createElement('div');\r\n this.element.append(tablixContainer);\r\n\r\n let tablixOptions: controls.TablixOptions = {\r\n interactive: this.isInteractive,\r\n enableTouchSupport: true,\r\n layoutKind: layoutKind,\r\n fontSize: TablixObjects.getTextSizeInPx(textSize),\r\n };\r\n\r\n return new controls.TablixControl(matrixNavigator, layoutManager, matrixBinder, tablixContainer, tablixOptions);\r\n }\r\n\r\n private updateInternal(textSize: number, previousDataView: DataView) {\r\n if (this.getLayoutKind() === controls.TablixLayoutKind.DashboardTile) {\r\n this.tablixControl.layoutManager.adjustContentSize(converterHelper.hasImageUrlColumn(this.dataView));\r\n }\r\n\r\n this.tablixControl.fontSize = TablixObjects.getTextSizeInPx(textSize);\r\n this.verifyHeaderResize();\r\n\r\n /* To avoid resetting scrollbar every time we persist Objects. If:\r\n * AutoSizeColumns options was flipped\r\n * A Column was resized manually\r\n * A Column was auto-sized\r\n */\r\n\r\n // Update models before the viewport to make sure column widths are computed correctly\r\n // if a persisting operation is going, don't reset the scrollbar (column resize)\r\n this.tablixControl.updateModels(/*resetScrollOffsets*/!this.persistingObjects, this.dataView.matrix.rows.root.children, this.dataView.matrix.columns.root.children);\r\n this.tablixControl.viewport = this.currentViewport;\r\n let shouldClearControl = this.shouldClearControl(previousDataView, this.dataView);\r\n\r\n // We need the layout for the DIV to be done so that the control can measure items correctly.\r\n setTimeout(() => {\r\n // Render\r\n this.refreshControl(shouldClearControl);\r\n let widthChanged = this.columnWidthManager.onColumnsRendered(this.tablixControl.layoutManager.columnWidthsToPersist);\r\n\r\n // At this point, all columns are rendered with proper width\r\n // Resetting the flag unless any unknown columnn width was persisted\r\n if (this.persistingObjects && !widthChanged) {\r\n this.persistingObjects = false;\r\n }\r\n }, 0);\r\n }\r\n\r\n private shouldClearControl(previousDataView: DataView, newDataView: DataView) {\r\n if (!this.waitingForSort || !previousDataView || !newDataView)\r\n return true;\r\n\r\n // ToDo: Get better criteria\r\n return !DataViewAnalysis.isMetadataEquivalent(previousDataView.metadata, newDataView.metadata);\r\n }\r\n\r\n private onBindRowHeader(item: MatrixVisualNode): void {\r\n if (this.needsMoreData(item)) {\r\n this.hostServices.loadMoreData();\r\n this.waitingForData = true;\r\n }\r\n }\r\n\r\n private onColumnHeaderClick(queryName: string, sortDirection: SortDirection): void {\r\n this.waitingForSort = true;\r\n this.hostServices.onCustomSort(TablixUtils.getCustomSortEventArgs(queryName, sortDirection));\r\n }\r\n \r\n /**\r\n * Note: Public for testability.\r\n */\r\n public needsMoreData(item: MatrixVisualNode): boolean {\r\n if (this.waitingForData || !this.hierarchyNavigator.isLeaf(item) || !this.dataView.metadata || !this.dataView.metadata.segment)\r\n return false;\r\n\r\n let leafCount = this.tablixControl.rowDimension.getItemsCount();\r\n let loadMoreThreshold = leafCount * Matrix.preferredLoadMoreThreshold;\r\n\r\n return this.hierarchyNavigator.getLeafIndex(item) >= loadMoreThreshold;\r\n }\r\n\r\n public enumerateObjectInstances(options: EnumerateVisualObjectInstancesOptions): VisualObjectInstanceEnumeration {\r\n let enumeration = new ObjectEnumerationBuilder();\r\n\r\n // Visuals are initialized with an empty data view before queries are run, therefore we need to make sure that\r\n // we are resilient here when we do not have data view.\r\n if (this.dataView) {\r\n TablixObjects.enumerateObjectInstances(options, enumeration, this.dataView, controls.TablixType.Matrix);\r\n }\r\n\r\n return enumeration.complete();\r\n }\r\n\r\n private shouldAllowHeaderResize(): boolean {\r\n return this.hostServices.getViewMode() === ViewMode.Edit;\r\n }\r\n\r\n public onViewModeChanged(viewMode: ViewMode): void {\r\n /* Refreshes the column headers to enable/disable Column resizing */\r\n this.updateViewport(this.currentViewport);\r\n }\r\n\r\n private verifyHeaderResize() {\r\n let currentAllowHeaderResize = this.shouldAllowHeaderResize();\r\n if (currentAllowHeaderResize !== this.lastAllowHeaderResize) {\r\n this.lastAllowHeaderResize = currentAllowHeaderResize;\r\n this.tablixControl.layoutManager.setAllowHeaderResize(currentAllowHeaderResize);\r\n }\r\n }\r\n }\r\n}\n\n\n/** WEBPACK FOOTER **\n ** ./src/Clients/Visuals/visuals/matrix.ts\n **/","/*\r\n * Power BI Visualizations\r\n *\r\n * Copyright (c) Microsoft Corporation\r\n * All rights reserved. \r\n * MIT License\r\n *\r\n * Permission is hereby granted, free of charge, to any person obtaining a copy\r\n * of this software and associated documentation files (the \"\"Software\"\"), to deal\r\n * in the Software without restriction, including without limitation the rights\r\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\r\n * copies of the Software, and to permit persons to whom the Software is\r\n * furnished to do so, subject to the following conditions:\r\n * \r\n * The above copyright notice and this permission notice shall be included in \r\n * all copies or substantial portions of the Software.\r\n * \r\n * THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR \r\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, \r\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE \r\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER \r\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\r\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\r\n * THE SOFTWARE.\r\n */\r\n\r\n/// <reference path=\"../_references.ts\"/>\r\n\r\nmodule powerbi.visuals {\r\n import CssConstants = jsCommon.CssConstants;\r\n\r\n export interface TreemapConstructorOptions {\r\n animator?: ITreemapAnimator;\r\n isScrollable: boolean;\r\n behavior?: TreemapWebBehavior;\r\n tooltipsEnabled?: boolean;\r\n tooltipBucketEnabled?: boolean;\r\n }\r\n\r\n export interface TreemapData {\r\n root: TreemapNode;\r\n hasHighlights: boolean;\r\n legendData: LegendData;\r\n dataLabelsSettings: VisualDataLabelsSettings;\r\n legendObjectProperties?: DataViewObject;\r\n dataWasCulled: boolean;\r\n hasNegativeValues?: boolean;\r\n allValuesAreNegative?: boolean;\r\n }\r\n\r\n /**\r\n * Treemap node (we extend D3 node (GraphNode) because treemap layout methods rely on the type).\r\n */\r\n export interface TreemapNode extends D3.Layout.GraphNode, SelectableDataPoint, TooltipEnabledDataPoint, LabelEnabledDataPoint {\r\n key: any;\r\n originalValue: number;\r\n highlightMultiplier?: number;\r\n highlightValue?: number;\r\n originalHighlightValue?: number;\r\n color: string;\r\n highlightedTooltipInfo?: TooltipDataItem[];\r\n }\r\n\r\n interface TreemapRawData {\r\n values: number[][];\r\n highlights?: number[][];\r\n highlightsOverflow?: boolean;\r\n totalValue: number;\r\n hasNegativeValues?: boolean;\r\n allValuesAreNegative?: boolean;\r\n }\r\n\r\n export interface ITreemapLayout {\r\n shapeClass: (d: TreemapNode) => string;\r\n shapeLayout: {\r\n x: (d: TreemapNode) => number;\r\n y: (d: TreemapNode) => number;\r\n width: (d: TreemapNode) => number;\r\n height: (d: TreemapNode) => number;\r\n };\r\n highlightShapeClass: (d: TreemapNode) => string;\r\n highlightShapeLayout: {\r\n x: (d: TreemapNode) => number;\r\n y: (d: TreemapNode) => number;\r\n width: (d: TreemapNode) => number;\r\n height: (d: TreemapNode) => number;\r\n };\r\n zeroShapeLayout: {\r\n x: (d: TreemapNode) => number;\r\n y: (d: TreemapNode) => number;\r\n width: (d: TreemapNode) => number;\r\n height: (d: TreemapNode) => number;\r\n };\r\n majorLabelClass: (d: TreemapNode) => string;\r\n majorLabelLayout: {\r\n x: (d: TreemapNode) => number;\r\n y: (d: TreemapNode) => number;\r\n };\r\n majorLabelText: (d: TreemapNode) => string;\r\n minorLabelClass: (d: TreemapNode) => string;\r\n minorLabelLayout: {\r\n x: (d: TreemapNode) => number;\r\n y: (d: TreemapNode) => number;\r\n };\r\n minorLabelText: (d: TreemapNode) => string;\r\n areMajorLabelsEnabled: () => boolean;\r\n areMinorLabelsEnabled: () => boolean;\r\n }\r\n\r\n // Todo: move to shared location\r\n interface DataPointObject extends DataViewObject {\r\n fill: Fill;\r\n }\r\n\r\n export interface ValueShape {\r\n validShape: boolean;\r\n dataWasCulled: boolean;\r\n }\r\n\r\n /**\r\n * Renders an interactive treemap visual from categorical data.\r\n */\r\n export class Treemap implements IVisual {\r\n public static DimmedShapeOpacity = 0.4;\r\n\r\n private static ClassName = 'treemap';\r\n public static LabelsGroupClassName = \"labels\";\r\n public static MajorLabelClassName = 'majorLabel';\r\n public static MinorLabelClassName = 'minorLabel';\r\n public static ShapesClassName = \"shapes\";\r\n public static TreemapNodeClassName = \"treemapNode\";\r\n public static RootNodeClassName = 'rootNode';\r\n public static ParentGroupClassName = 'parentGroup';\r\n public static NodeGroupClassName = 'nodeGroup';\r\n public static HighlightNodeClassName = 'treemapNodeHighlight';\r\n private static TextMargin = 5;\r\n private static MinorLabelTextSize = 10;\r\n private static MinTextWidthForMinorLabel = 18;\r\n private static MajorLabelTextSize = 12;\r\n private static MinTextWidthForMajorLabel = 22;\r\n private static MajorLabelTextProperties: TextProperties = {\r\n fontFamily: Font.Family.regular.css,\r\n fontSize: Treemap.MajorLabelTextSize + 'px'\r\n };\r\n\r\n /**\r\n * A rect with an area of 9 is a treemap rectangle of only\r\n * a single pixel in the middle with a 1 pixel stroke on each edge.\r\n */\r\n private static CullableArea = 9;\r\n\r\n private svg: D3.Selection;\r\n private treemap: D3.Layout.TreeMapLayout;\r\n private shapeGraphicsContext: D3.Selection;\r\n private labelGraphicsContext: D3.Selection;\r\n // TODO: Remove this once all visuals have implemented update.\r\n private currentViewport: IViewport;\r\n private legend: ILegend;\r\n\r\n private data: TreemapData;\r\n private style: IVisualStyle;\r\n private colors: IDataColorPalette;\r\n private element: JQuery;\r\n private options: VisualInitOptions;\r\n private isScrollable: boolean;\r\n private hostService: IVisualHostServices;\r\n private tooltipsEnabled: boolean;\r\n private tooltipBucketEnabled: boolean;\r\n\r\n /**\r\n * Note: Public for testing.\r\n */\r\n public animator: ITreemapAnimator;\r\n private interactivityService: IInteractivityService;\r\n private behavior: TreemapWebBehavior;\r\n\r\n // TODO: Remove this once all visuals have implemented update.\r\n private dataViews: DataView[];\r\n\r\n public static getLayout(labelsSettings: VisualDataLabelsSettings, alternativeScale: number): ITreemapLayout {\r\n let formattersCache = dataLabelUtils.createColumnFormatterCacheManager();\r\n let majorLabelsEnabled = labelsSettings.showCategory;\r\n let minorLabelsEnabled = labelsSettings.show || labelsSettings.showCategory;\r\n\r\n return {\r\n shapeClass: (d) => Treemap.getNodeClass(d, false),\r\n shapeLayout: Treemap.createTreemapShapeLayout(false),\r\n highlightShapeClass: (d) => Treemap.getNodeClass(d, true),\r\n highlightShapeLayout: Treemap.createTreemapShapeLayout(true),\r\n zeroShapeLayout: Treemap.createTreemapZeroShapeLayout(),\r\n majorLabelClass: (d) => Treemap.MajorLabelClassName,\r\n majorLabelLayout: {\r\n x: (d) => d.x + Treemap.TextMargin,\r\n y: (d) => d.y + Treemap.TextMargin + Treemap.MajorLabelTextSize,\r\n },\r\n majorLabelText: (d) => Treemap.createMajorLabelText(d, labelsSettings, alternativeScale, formattersCache),\r\n\r\n minorLabelClass: (d) => Treemap.MinorLabelClassName,\r\n minorLabelLayout: {\r\n x: (d) => d.x + Treemap.TextMargin,\r\n y: (d) => d.y + d.dy - Treemap.TextMargin,\r\n },\r\n minorLabelText: (d) => Treemap.createMinorLabelText(d, labelsSettings, alternativeScale, formattersCache),\r\n areMajorLabelsEnabled: () => majorLabelsEnabled,\r\n areMinorLabelsEnabled: () => minorLabelsEnabled,\r\n };\r\n }\r\n\r\n constructor(options?: TreemapConstructorOptions) {\r\n this.tooltipsEnabled = options && options.tooltipsEnabled;\r\n this.tooltipBucketEnabled = options && options.tooltipBucketEnabled;\r\n if (options && options.animator) {\r\n this.animator = options.animator;\r\n this.isScrollable = options.isScrollable ? options.isScrollable : false;\r\n this.behavior = options.behavior;\r\n }\r\n }\r\n\r\n public init(options: VisualInitOptions): void {\r\n this.options = options;\r\n let element = options.element;\r\n\r\n // Ensure viewport is empty on init\r\n element.empty();\r\n\r\n this.svg = d3.select(element.get(0))\r\n .append('svg')\r\n .style('position', 'absolute')\r\n .classed(Treemap.ClassName, true);\r\n this.shapeGraphicsContext = this.svg\r\n .append('g')\r\n .classed(Treemap.ShapesClassName, true);\r\n this.labelGraphicsContext = this.svg\r\n .append('g')\r\n .classed(Treemap.LabelsGroupClassName, true);\r\n\r\n this.element = element;\r\n\r\n // avoid deep copy\r\n this.currentViewport = {\r\n height: options.viewport.height,\r\n width: options.viewport.width,\r\n };\r\n\r\n this.style = options.style;\r\n\r\n this.treemap = d3.layout.treemap()\r\n .sticky(false)\r\n .sort((a, b) => a.size - b.size)\r\n .value((d) => d.size)\r\n .round(false);\r\n\r\n if (this.behavior) {\r\n this.interactivityService = createInteractivityService(options.host);\r\n }\r\n this.legend = createLegend(element, options.interactivity && options.interactivity.isInteractiveLegend, this.interactivityService, this.isScrollable);\r\n this.colors = this.style.colorPalette.dataColors;\r\n\r\n this.hostService = options.host;\r\n }\r\n\r\n /**\r\n * Note: Public for testing purposes.\r\n */\r\n public static converter(dataView: DataView, colors: IDataColorPalette, labelSettings: VisualDataLabelsSettings, interactivityService: IInteractivityService, viewport: IViewport, legendObjectProperties?: DataViewObject, tooltipsEnabled: boolean = true, tooltipBucketEnabled?: boolean): TreemapData {\r\n let reader = data.createIDataViewCategoricalReader(dataView);\r\n let hasNegativeValues: boolean;\r\n let allValuesAreNegative: boolean;\r\n\r\n let rootNode: TreemapNode = {\r\n key: \"root\",\r\n name: \"root\",\r\n originalValue: undefined,\r\n children: [],\r\n selected: false,\r\n highlightMultiplier: 0,\r\n identity: SelectionId.createNull(),\r\n color: undefined,\r\n };\r\n\r\n let allNodes: TreemapNode[] = [];\r\n let hasHighlights: boolean;\r\n let legendDataPoints: LegendDataPoint[] = [];\r\n let legendTitle = \"\";\r\n let colorHelper = new ColorHelper(colors, treemapProps.dataPoint.fill);\r\n let dataWasCulled = undefined;\r\n \r\n if (dataView && dataView.metadata && dataView.metadata.objects) {\r\n let objects = dataView.metadata.objects;\r\n\r\n labelSettings.show = DataViewObjects.getValue(objects, treemapProps.labels.show, labelSettings.show);\r\n labelSettings.labelColor = DataViewObjects.getFillColor(objects, treemapProps.labels.color, labelSettings.labelColor);\r\n labelSettings.displayUnits = DataViewObjects.getValue(objects, treemapProps.labels.labelDisplayUnits, labelSettings.displayUnits);\r\n labelSettings.precision = DataViewObjects.getValue(objects, treemapProps.labels.labelPrecision, labelSettings.precision);\r\n labelSettings.showCategory = DataViewObjects.getValue(objects, treemapProps.categoryLabels.show, labelSettings.showCategory);\r\n }\r\n\r\n // If we values or a gradient, render the tree map\r\n if (reader.hasValues(treemapRoles.values) || reader.hasValues(treemapRoles.gradient)) {\r\n \r\n // If we don't have a values column, but do have a gradient one, use that as values\r\n let valueColumnRoleName = reader.hasValues(treemapRoles.values) ? treemapRoles.values : treemapRoles.gradient;\r\n let categorical = dataView.categorical;\r\n hasHighlights = reader.hasHighlights(valueColumnRoleName);\r\n\r\n let formatStringProp = treemapProps.general.formatString;\r\n let result = Treemap.getValuesFromCategoricalDataView(dataView, hasHighlights, valueColumnRoleName);\r\n let values = result.values;\r\n let highlights = result.highlights;\r\n let totalValue = result.totalValue;\r\n if (result.highlightsOverflow) {\r\n hasHighlights = false;\r\n values = highlights;\r\n }\r\n hasNegativeValues = result.hasNegativeValues;\r\n allValuesAreNegative = result.allValuesAreNegative;\r\n\r\n let cullableValue = Treemap.getCullableValue(totalValue, viewport);\r\n\r\n let hasDynamicSeries = reader.hasDynamicSeries();\r\n dataWasCulled = false;\r\n let gradientValueColumn: DataViewValueColumn = reader.getValueColumn(treemapRoles.gradient, 0); // Gradient is only used if we have only one series or series are nondynamic (and therefore don't affect gradient)\r\n \r\n if ((categorical.categories == null) && !_.isEmpty(values)) {\r\n // No categories, sliced by series and measures\r\n for (let seriesIndex = 0, seriesCount = reader.getSeriesCount(valueColumnRoleName); seriesIndex < seriesCount; seriesIndex++) {\r\n\r\n let valueColumn = reader.getValueColumn(valueColumnRoleName, seriesIndex);\r\n\r\n let value = values[0][seriesIndex];\r\n\r\n let valueShape = Treemap.checkValueForShape(value, cullableValue, allValuesAreNegative, dataWasCulled);\r\n dataWasCulled = valueShape.dataWasCulled;\r\n if (!valueShape.validShape)\r\n continue;\r\n \r\n let nodeName = hasDynamicSeries ? converterHelper.formatFromMetadataColumn(reader.getSeriesValueColumnGroup(seriesIndex).name, reader.getSeriesMetadataColumn(), formatStringProp) : converterHelper.formatFromMetadataColumn(reader.getValueDisplayName(valueColumnRoleName, seriesIndex), valueColumn.source, formatStringProp);\r\n\r\n let identity = new SelectionIdBuilder()\r\n .withSeries(dataView.categorical.values, hasDynamicSeries ? valueColumn : undefined)\r\n .withMeasure(valueColumn.source.queryName)\r\n .createSelectionId();\r\n\r\n let key = identity.getKey();\r\n\r\n let color = hasDynamicSeries\r\n ? colorHelper.getColorForSeriesValue(reader.getSeriesObjects(seriesIndex), categorical.values.identityFields, converterHelper.getSeriesName(valueColumn.source))\r\n : colorHelper.getColorForMeasure(valueColumn.source.objects, valueColumn.source.queryName);\r\n\r\n let highlightedValue = hasHighlights ? highlights[0][seriesIndex] : undefined;\r\n\r\n let tooltipInfo: TooltipDataItem[];\r\n if (tooltipsEnabled) {\r\n tooltipInfo = [];\r\n\r\n if (hasDynamicSeries) {\r\n let seriesMetadataColumn = reader.getSeriesMetadataColumn();\r\n let seriesValue = reader.getSeriesValueColumnGroup(seriesIndex).name;\r\n tooltipInfo.push({\r\n displayName: seriesMetadataColumn.displayName,\r\n value: converterHelper.formatFromMetadataColumn(seriesValue, seriesMetadataColumn, formatStringProp),\r\n });\r\n }\r\n\r\n if (value != null) {\r\n tooltipInfo.push({\r\n displayName: valueColumn.source.displayName,\r\n value: converterHelper.formatFromMetadataColumn(value, valueColumn.source, formatStringProp),\r\n });\r\n }\r\n\r\n if (highlightedValue != null) {\r\n tooltipInfo.push({\r\n displayName: ToolTipComponent.localizationOptions.highlightedValueDisplayName,\r\n value: converterHelper.formatFromMetadataColumn(highlightedValue, valueColumn.source, formatStringProp),\r\n });\r\n }\r\n if (tooltipBucketEnabled) {\r\n TooltipBuilder.addTooltipBucketItem(reader, tooltipInfo, 0, hasDynamicSeries ? seriesIndex : undefined);\r\n }\r\n }\r\n \r\n let node: TreemapNode = {\r\n key: key,\r\n name: nodeName,\r\n size: allValuesAreNegative ? Math.abs(value) : value,\r\n originalValue: value,\r\n color: color,\r\n selected: false,\r\n identity: identity,\r\n tooltipInfo: tooltipInfo,\r\n highlightedTooltipInfo: tooltipInfo,\r\n labelFormatString: valueFormatter.getFormatString(valueColumn.source, formatStringProp),\r\n };\r\n if (hasHighlights && highlights) {\r\n node.highlightMultiplier = value !== 0 ? highlightedValue / value : 0;\r\n node.highlightValue = (allValuesAreNegative && highlightedValue != null) ? Math.abs(highlightedValue) : highlightedValue;\r\n node.originalHighlightValue = highlightedValue;\r\n }\r\n rootNode.children.push(node);\r\n allNodes.push(node);\r\n legendDataPoints.push({\r\n label: nodeName,\r\n color: color,\r\n icon: LegendIcon.Box,\r\n identity: identity,\r\n selected: false\r\n });\r\n }\r\n }\r\n else if (categorical.categories && categorical.categories.length > 0) {\r\n // Count the columns that have the value roles\r\n let seriesCount = reader.getSeriesCount(valueColumnRoleName);\r\n\r\n // Do not add second level if there's only one series.\r\n let omitSecondLevel = seriesCount === 1;\r\n\r\n // Create the first level from categories\r\n let categoryColumn = categorical.categories[0];\r\n\r\n legendTitle = categoryColumn.source.displayName;\r\n let categoryFormat = valueFormatter.getFormatString(categoryColumn.source, formatStringProp);\r\n\r\n for (let categoryIndex = 0, categoryLen = values.length; categoryIndex < categoryLen; categoryIndex++) {\r\n let objects = categoryColumn.objects && categoryColumn.objects[categoryIndex];\r\n\r\n let color = colorHelper.getColorForSeriesValue(objects, categoryColumn.identityFields, categoryColumn.values[categoryIndex]);\r\n \r\n let categoryValue = valueFormatter.format(categoryColumn.values[categoryIndex], categoryFormat);\r\n\r\n let currentValues = values[categoryIndex];\r\n\r\n // This section area builds the tooltip for the parent node. It's only displayed if the node doesn't have any children (essentially if omitSecondLevel is true).\r\n // seriesIndex is the index of the 1st series with the role Values.\r\n let seriesIndex = 0;\r\n let value = currentValues[seriesIndex];\r\n let highlightValue = hasHighlights && highlights ? highlights[categoryIndex][seriesIndex] : undefined;\r\n \r\n let tooltipInfo: TooltipDataItem[];\r\n let categoryTooltipItem: TooltipDataItem;\r\n\r\n if (tooltipsEnabled) {\r\n tooltipInfo = [];\r\n\r\n categoryTooltipItem = {\r\n displayName: categoryColumn.source.displayName,\r\n value: categoryValue,\r\n };\r\n tooltipInfo.push(categoryTooltipItem);\r\n\r\n let valueColumnMetadata: DataViewMetadataColumn = reader.getValueMetadataColumn(valueColumnRoleName, seriesIndex); \r\n if (value != null) {\r\n tooltipInfo.push({\r\n displayName: valueColumnMetadata.displayName,\r\n value: converterHelper.formatFromMetadataColumn(value, valueColumnMetadata, formatStringProp),\r\n });\r\n }\r\n\r\n if (highlightValue != null) {\r\n tooltipInfo.push({\r\n displayName: ToolTipComponent.localizationOptions.highlightedValueDisplayName,\r\n value: converterHelper.formatFromMetadataColumn(highlightValue, valueColumnMetadata, formatStringProp),\r\n });\r\n }\r\n\r\n let gradientValueColumnMetadata = gradientValueColumn ? gradientValueColumn.source : undefined;\r\n if (omitSecondLevel && gradientValueColumnMetadata && gradientValueColumnMetadata !== valueColumnMetadata && gradientValueColumn.values[categoryIndex] != null ) {\r\n tooltipInfo.push({\r\n displayName: gradientValueColumnMetadata.displayName,\r\n value: converterHelper.formatFromMetadataColumn(gradientValueColumn.values[categoryIndex] , gradientValueColumnMetadata, formatStringProp),\r\n });\r\n }\r\n\r\n if (tooltipBucketEnabled) {\r\n TooltipBuilder.addTooltipBucketItem(reader, tooltipInfo, categoryIndex, seriesIndex);\r\n }\r\n }\r\n\r\n let identity: SelectionId = SelectionIdBuilder.builder()\r\n .withCategory(categoryColumn, categoryIndex)\r\n .withMeasure(omitSecondLevel ? reader.getValueMetadataColumn(valueColumnRoleName, seriesIndex).queryName : undefined)\r\n .createSelectionId();\r\n\r\n let key = JSON.stringify({ nodeKey: identity.getKey(), depth: 1 });\r\n\r\n let node: TreemapNode = {\r\n key: key,\r\n name: categoryValue,\r\n originalValue: undefined,\r\n color: color,\r\n selected: false,\r\n identity: identity,\r\n tooltipInfo: tooltipInfo,\r\n highlightedTooltipInfo: tooltipInfo,\r\n labelFormatString: seriesCount === 1 ? valueFormatter.getFormatString(reader.getValueMetadataColumn(valueColumnRoleName, seriesIndex), formatStringProp) : categoryFormat,\r\n };\r\n if (hasHighlights) {\r\n node.highlightMultiplier = value !== 0 ? highlightValue / value : 0;\r\n node.highlightValue = (allValuesAreNegative && highlightValue != null) ? Math.abs(highlightValue) : highlightValue;\r\n node.originalHighlightValue = highlightValue;\r\n }\r\n\r\n legendDataPoints.push({\r\n label: categoryValue,\r\n color: color,\r\n icon: LegendIcon.Box,\r\n identity: identity,\r\n selected: false\r\n });\r\n\r\n let total = 0;\r\n let highlightTotal = 0; // Used if omitting second level\r\n\r\n for (; seriesIndex < seriesCount; seriesIndex++) {\r\n\r\n let valueColumn = reader.getValueColumn(valueColumnRoleName, seriesIndex);\r\n\r\n let value = currentValues[seriesIndex];\r\n\r\n let highlight: number;\r\n\r\n let valueShape = Treemap.checkValueForShape(value, cullableValue, allValuesAreNegative, dataWasCulled);\r\n dataWasCulled = valueShape.dataWasCulled;\r\n if (!valueShape.validShape)\r\n continue;\r\n\r\n total += value;\r\n\r\n if (hasHighlights) {\r\n highlight = highlights[categoryIndex][seriesIndex];\r\n highlightTotal += highlight;\r\n }\r\n\r\n if (!omitSecondLevel) {\r\n let childName: string = null;\r\n if (reader.hasDynamicSeries()) {\r\n // Measure: use name and index\r\n childName = converterHelper.formatFromMetadataColumn(reader.getSeriesName(seriesIndex), reader.getSeriesMetadataColumn(), formatStringProp);\r\n }\r\n else {\r\n // Series group instance\r\n childName = converterHelper.formatFromMetadataColumn(reader.getValueDisplayName(valueColumnRoleName, seriesIndex), reader.getValueMetadataColumn(valueColumnRoleName, seriesIndex), formatStringProp);\r\n }\r\n\r\n let categoricalValues = categorical ? categorical.values : null;\r\n let measureId = valueColumn.source.queryName;\r\n let childIdentity = SelectionIdBuilder.builder()\r\n .withCategory(categoryColumn, categoryIndex)\r\n .withSeries(categoricalValues, valueColumn)\r\n .withMeasure(measureId)\r\n .createSelectionId();\r\n let childKey = JSON.stringify({ nodeKey: childIdentity.getKey(), depth: 2 });\r\n\r\n let highlightedValue = hasHighlights && highlight !== 0 ? highlight : undefined;\r\n\r\n let tooltipInfo: TooltipDataItem[];\r\n if (tooltipsEnabled) {\r\n tooltipInfo = [];\r\n\r\n tooltipInfo.push(categoryTooltipItem);\r\n\r\n if (hasDynamicSeries) {\r\n if (!categoryColumn || categoryColumn.source !== categoricalValues.source) {\r\n // Category/series on the same column -- don't repeat its value in the tooltip.\r\n tooltipInfo.push({\r\n displayName: categoricalValues.source.displayName,\r\n value: converterHelper.formatFromMetadataColumn(reader.getSeriesName(seriesIndex), categoricalValues.source, formatStringProp),\r\n });\r\n }\r\n }\r\n\r\n if (value != null) {\r\n tooltipInfo.push({\r\n displayName: valueColumn.source.displayName,\r\n value: converterHelper.formatFromMetadataColumn(value, valueColumn.source, formatStringProp),\r\n });\r\n }\r\n\r\n if (highlightValue != null) {\r\n tooltipInfo.push({\r\n displayName: ToolTipComponent.localizationOptions.highlightedValueDisplayName,\r\n value: converterHelper.formatFromMetadataColumn(highlightedValue, valueColumn.source, formatStringProp),\r\n });\r\n }\r\n\r\n if (tooltipBucketEnabled) {\r\n TooltipBuilder.addTooltipBucketItem(reader, tooltipInfo, categoryIndex, hasDynamicSeries ? seriesIndex : undefined);\r\n }\r\n }\r\n\r\n let childNode: TreemapNode = {\r\n key: childKey,\r\n name: childName,\r\n size: allValuesAreNegative ? Math.abs(value) : value,\r\n originalValue: value,\r\n color: color,\r\n selected: false,\r\n identity: childIdentity,\r\n tooltipInfo: tooltipInfo,\r\n highlightedTooltipInfo: tooltipInfo,\r\n labelFormatString: valueFormatter.getFormatString(valueColumn.source, formatStringProp),\r\n };\r\n if (hasHighlights) {\r\n childNode.highlightMultiplier = value !== 0 ? highlight / value : 0;\r\n childNode.highlightValue = (allValuesAreNegative && highlight != null) ? Math.abs(highlight) : null;\r\n childNode.originalHighlightValue = highlight;\r\n }\r\n if (node.children == null)\r\n node.children = [];\r\n\r\n node.children.push(childNode);\r\n allNodes.push(childNode);\r\n }\r\n }\r\n\r\n if (total) {\r\n node.size = allValuesAreNegative ? Math.abs(total) : total;\r\n node.originalValue = total;\r\n rootNode.children.push(node);\r\n allNodes.push(node);\r\n }\r\n if (hasHighlights)\r\n node.highlightMultiplier = total ? highlightTotal / total : 0;\r\n }\r\n }\r\n }\r\n\r\n if (interactivityService) {\r\n interactivityService.applySelectionStateToData(allNodes);\r\n interactivityService.applySelectionStateToData(legendDataPoints);\r\n }\r\n\r\n return {\r\n root: rootNode,\r\n hasHighlights: hasHighlights,\r\n legendData: { title: legendTitle, dataPoints: legendDataPoints, fontSize: SVGLegend.DefaultFontSizeInPt },\r\n dataLabelsSettings: labelSettings,\r\n legendObjectProperties: legendObjectProperties,\r\n dataWasCulled: dataWasCulled,\r\n hasNegativeValues: hasNegativeValues,\r\n allValuesAreNegative: allValuesAreNegative,\r\n };\r\n }\r\n\r\n private static normalizedValue(value: number, allValuesAreNegative: boolean): number {\r\n if (value == null || isNaN(value))\r\n return 0;\r\n else if (value === Number.POSITIVE_INFINITY)\r\n return Number.MAX_VALUE;\r\n else if (value === Number.NEGATIVE_INFINITY)\r\n return -Number.MAX_VALUE;\r\n else if (allValuesAreNegative)\r\n return Math.abs(value);\r\n else if (value < 0)\r\n return 0;\r\n else\r\n return value;\r\n }\r\n\r\n private static getValuesFromCategoricalDataView(dataView: DataView, hasHighlights: boolean, valueColumnRoleName: string): TreemapRawData {\r\n let reader = data.createIDataViewCategoricalReader(dataView);\r\n let categoryCount = reader.getCategoryCount() || 1;\r\n let seriesCount = reader.getSeriesCount(valueColumnRoleName);\r\n\r\n let values: number[][] = [];\r\n let highlights: number[][] = [];\r\n let totalValue = 0;\r\n for (let i = 0; i < categoryCount; i++) {\r\n values.push([]);\r\n if (hasHighlights)\r\n highlights.push([]);\r\n }\r\n\r\n let hasNegativeValues: boolean;\r\n let allValuesAreNegative: boolean = undefined;\r\n\r\n let highlightsOverflow: boolean;\r\n for (let seriesIndex = 0; seriesIndex < seriesCount; seriesIndex++) {\r\n for (let categoryIndex = 0; categoryIndex < categoryCount; categoryIndex++) {\r\n let value = reader.getValue(valueColumnRoleName, categoryIndex, seriesIndex);\r\n values[categoryIndex].push(value);\r\n let highlight: any;\r\n if (hasHighlights) {\r\n highlight = reader.getHighlight(valueColumnRoleName, categoryIndex, seriesIndex);\r\n highlights[categoryIndex].push(highlight);\r\n if (highlight == null)\r\n highlight = 0;\r\n }\r\n\r\n if (allValuesAreNegative === undefined) {\r\n allValuesAreNegative = ((hasHighlights ? highlight <= 0 : true) && value <= 0) ? true: false;\r\n }\r\n else {\r\n allValuesAreNegative = allValuesAreNegative && (hasHighlights ? highlight <= 0 : true) && value <= 0;\r\n }\r\n \r\n if (!hasNegativeValues)\r\n hasNegativeValues = value < 0 || (hasHighlights ? highlight < 0 : false);\r\n }\r\n }\r\n\r\n allValuesAreNegative = !!allValuesAreNegative;\r\n\r\n for (let seriesIndex = 0; seriesIndex < seriesCount; seriesIndex++) {\r\n for (let categoryIndex = 0; categoryIndex < categoryCount; categoryIndex++) {\r\n let value = values[categoryIndex][seriesIndex];\r\n value = Treemap.normalizedValue(value, allValuesAreNegative);\r\n totalValue += value; \r\n if (hasHighlights) {\r\n let highlight = highlights[categoryIndex][seriesIndex];\r\n highlight = Treemap.normalizedValue(highlight, allValuesAreNegative);\r\n if (!highlightsOverflow && highlight > value) {\r\n highlightsOverflow = true;\r\n }\r\n }\r\n }\r\n }\r\n\r\n return {\r\n values: values,\r\n highlights: hasHighlights ? highlights : undefined,\r\n highlightsOverflow: hasHighlights ? highlightsOverflow : undefined,\r\n totalValue: allValuesAreNegative ? Math.abs(totalValue) : totalValue,\r\n hasNegativeValues: hasNegativeValues,\r\n allValuesAreNegative: allValuesAreNegative,\r\n };\r\n }\r\n\r\n private static getCullableValue(totalValue: number, viewport: IViewport): number {\r\n let totalArea = viewport.width * viewport.height;\r\n let culledPercent = Treemap.CullableArea / totalArea;\r\n return culledPercent * totalValue;\r\n }\r\n\r\n public update(options: VisualUpdateOptions) {\r\n debug.assertValue(options, 'options');\r\n\r\n let dataViews = this.dataViews = options.dataViews;\r\n this.currentViewport = options.viewport;\r\n let dataViewCategorical = dataViews && dataViews.length > 0 && dataViews[0].categorical ? dataViews[0].categorical : undefined;\r\n let labelSettings = dataLabelUtils.getDefaultTreemapLabelSettings();\r\n let legendObjectProperties = null;\r\n\r\n if (dataViewCategorical) {\r\n let dataView = dataViews[0];\r\n let dataViewMetadata = dataView.metadata;\r\n let objects: DataViewObjects;\r\n if (dataViewMetadata)\r\n objects = dataViewMetadata.objects;\r\n\r\n if (objects) {\r\n legendObjectProperties = objects['legend'];\r\n }\r\n\r\n this.data = Treemap.converter(dataView, this.colors, labelSettings, this.interactivityService, this.currentViewport, legendObjectProperties, this.tooltipsEnabled, this.tooltipBucketEnabled);\r\n }\r\n else {\r\n let rootNode: TreemapNode = {\r\n key: \"root\",\r\n name: \"root\",\r\n originalValue: undefined,\r\n children: [],\r\n selected: false,\r\n highlightMultiplier: 0,\r\n identity: SelectionId.createNull(),\r\n color: undefined,\r\n };\r\n let legendData: LegendData = { title: \"\", dataPoints: [] };\r\n let treeMapData: TreemapData = {\r\n root: rootNode,\r\n hasHighlights: false,\r\n legendData: legendData,\r\n dataLabelsSettings: labelSettings,\r\n dataWasCulled: false,\r\n hasNegativeValues: false,\r\n allValuesAreNegative: false,\r\n };\r\n this.data = treeMapData;\r\n }\r\n\r\n this.updateInternal(options.suppressAnimations);\r\n\r\n if (dataViews) {\r\n let warnings = getInvalidValueWarnings(\r\n dataViews,\r\n false /*supportsNaN*/,\r\n false /*supportsNegativeInfinity*/,\r\n false /*supportsPositiveInfinity*/);\r\n\r\n if (this.data.allValuesAreNegative) {\r\n warnings.push(new AllNegativeValuesWarning());\r\n }\r\n else if (this.data.hasNegativeValues) {\r\n warnings.push(new NegativeValuesNotSupportedWarning());\r\n }\r\n this.hostService.setWarnings(warnings);\r\n }\r\n }\r\n\r\n // TODO: Remove this once all visuals have implemented update.\r\n public onDataChanged(options: VisualDataChangedOptions): void {\r\n this.update({\r\n suppressAnimations: options.suppressAnimations,\r\n dataViews: options.dataViews,\r\n viewport: this.currentViewport\r\n });\r\n }\r\n\r\n // TODO: Remove this once all visuals have implemented update.\r\n public onResizing(viewport: IViewport): void {\r\n this.update({\r\n suppressAnimations: true,\r\n dataViews: this.dataViews,\r\n viewport: viewport\r\n });\r\n }\r\n\r\n public onClearSelection(): void {\r\n if (this.interactivityService)\r\n this.interactivityService.clearSelection();\r\n }\r\n\r\n public enumerateObjectInstances(options: EnumerateVisualObjectInstancesOptions): VisualObjectInstanceEnumeration {\r\n let data = this.data;\r\n if (!data)\r\n return;\r\n\r\n let objectName = options.objectName,\r\n enumeration = new ObjectEnumerationBuilder();\r\n\r\n let dataLabelsSettings = this.data.dataLabelsSettings\r\n ? this.data.dataLabelsSettings\r\n : dataLabelUtils.getDefaultTreemapLabelSettings();\r\n\r\n switch (objectName) {\r\n case 'dataPoint':\r\n let dataViewCat: DataViewCategorical = this.dataViews && this.dataViews.length > 0 && this.dataViews[0] && this.dataViews[0].categorical;\r\n let hasGradientRole = GradientUtils.hasGradientRole(dataViewCat);\r\n if (!hasGradientRole)\r\n this.enumerateDataPoints(enumeration, data);\r\n break;\r\n case 'legend':\r\n return this.enumerateLegend(data);\r\n case 'labels':\r\n let labelSettingOptions: VisualDataLabelsSettingsOptions = {\r\n enumeration: enumeration,\r\n dataLabelsSettings: dataLabelsSettings,\r\n show: true,\r\n displayUnits: true,\r\n precision: true,\r\n };\r\n dataLabelUtils.enumerateDataLabels(labelSettingOptions);\r\n break;\r\n case 'categoryLabels':\r\n dataLabelUtils.enumerateCategoryLabels(enumeration, dataLabelsSettings, false /* withFill */, true /* isShowCategory */);\r\n break;\r\n }\r\n\r\n return enumeration.complete();\r\n }\r\n\r\n private enumerateDataPoints(enumeration: ObjectEnumerationBuilder, data: TreemapData): void {\r\n let rootChildren = data.root.children;\r\n if (_.isEmpty(rootChildren))\r\n return;\r\n\r\n for (let y = 0; y < rootChildren.length; y++) {\r\n let treemapNode = <TreemapNode>rootChildren[y];\r\n enumeration.pushInstance({\r\n displayName: treemapNode.name,\r\n selector: ColorHelper.normalizeSelector(treemapNode.identity.getSelector()),\r\n properties: {\r\n fill: { solid: { color: treemapNode.color } }\r\n },\r\n objectName: 'dataPoint'\r\n });\r\n }\r\n }\r\n\r\n private enumerateLegend(data: TreemapData): VisualObjectInstance[] {\r\n let legendObjectProperties: DataViewObjects = { legend: data.legendObjectProperties };\r\n\r\n let show = DataViewObjects.getValue(legendObjectProperties, treemapProps.legend.show, this.legend.isVisible());\r\n let showTitle = DataViewObjects.getValue(legendObjectProperties, treemapProps.legend.showTitle, true);\r\n let titleText = DataViewObjects.getValue(legendObjectProperties, treemapProps.legend.titleText, this.data.legendData.title);\r\n let labelColor = DataViewObject.getValue(legendObjectProperties, legendProps.labelColor, this.data.legendData ? this.data.legendData.labelColor : LegendData.DefaultLegendLabelFillColor);\r\n let labelFontSize = DataViewObject.getValue(legendObjectProperties, legendProps.fontSize, this.data.legendData && this.data.legendData.fontSize ? this.data.legendData.fontSize : SVGLegend.DefaultFontSizeInPt);\r\n\r\n return [{\r\n selector: null,\r\n objectName: 'legend',\r\n properties: {\r\n show: show,\r\n position: LegendPosition[this.legend.getOrientation()],\r\n showTitle: showTitle,\r\n titleText: titleText,\r\n labelColor: labelColor,\r\n fontSize: labelFontSize,\r\n }\r\n }];\r\n }\r\n\r\n public static checkValueForShape(value: any, cullableValue: number, allValuesAreNegative: boolean, dataWasCulled: boolean): ValueShape {\r\n let shouldCullValue = undefined;\r\n if (!value) {\r\n return {\r\n validShape: false, \r\n dataWasCulled: dataWasCulled,\r\n };\r\n }\r\n else {\r\n if (!allValuesAreNegative)\r\n shouldCullValue = value < cullableValue;\r\n else\r\n shouldCullValue = Math.abs(value) < cullableValue;\r\n\r\n if (shouldCullValue) {\r\n dataWasCulled = dataWasCulled || shouldCullValue;\r\n return {\r\n validShape: false,\r\n dataWasCulled: dataWasCulled,\r\n };\r\n }\r\n return {\r\n validShape: true,\r\n dataWasCulled: dataWasCulled,\r\n };\r\n }\r\n }\r\n\r\n private calculateTreemapSize(): IViewport {\r\n let legendMargins = this.legend.getMargins();\r\n return {\r\n height: this.currentViewport.height - legendMargins.height,\r\n width: this.currentViewport.width - legendMargins.width\r\n };\r\n }\r\n\r\n private initViewportDependantProperties(duration: number = 0): void {\r\n\r\n let viewport = this.calculateTreemapSize();\r\n\r\n this.svg.attr({\r\n width: viewport.width,\r\n height: viewport.height\r\n });\r\n Legend.positionChartArea(this.svg, this.legend);\r\n\r\n SVGUtil.flushAllD3TransitionsIfNeeded(this.options);\r\n }\r\n\r\n private static hasChildrenWithIdentity(node: D3.Layout.GraphNode): boolean {\r\n let children = node.children;\r\n if (!children)\r\n return false;\r\n\r\n let count = children.length;\r\n if (count === 0)\r\n return false;\r\n\r\n for (let i = count - 1; i >= 0; i--) {\r\n if ((<TreemapNode>children[i]).identity.hasIdentity())\r\n return true;\r\n }\r\n\r\n return false;\r\n }\r\n\r\n private static canDisplayMajorLabel(node: TreemapNode): boolean {\r\n // Only display major labels for level 1\r\n if (node.depth !== 1)\r\n return false;\r\n\r\n if (_.isEmpty(node.name))\r\n return false;\r\n\r\n // Check if the room is enough for text with or without ellipse\r\n let availableWidth = node.dx - Treemap.TextMargin * 2;\r\n if (availableWidth < Treemap.MinTextWidthForMajorLabel)\r\n return false;\r\n \r\n // Check if the shape is high enough for label\r\n let textHeightWithMargin = Treemap.MajorLabelTextSize + Treemap.TextMargin * 2;\r\n if (node.dy < textHeightWithMargin)\r\n return false;\r\n\r\n return true;\r\n }\r\n\r\n private static canDisplayMinorLabel(node: TreemapNode, labelSettings: VisualDataLabelsSettings): boolean {\r\n // Only display minor labels for level 1 and 2\r\n if (node.depth < 1 || node.depth > 2)\r\n return false;\r\n\r\n // If a depth 1 node has children or is not showing data labels, do not show minor labels\r\n if (node.depth === 1 && (node.children || !labelSettings.show)) {\r\n return false;\r\n }\r\n\r\n if (_.isEmpty(node.name))\r\n return false;\r\n \r\n // Check if the room is enough for text with or without ellipse\r\n let availableWidth = node.dx - Treemap.TextMargin * 2;\r\n if (availableWidth < Treemap.MinTextWidthForMinorLabel)\r\n return false;\r\n \r\n // Check if the shape is high enough for label\r\n let textHeightWithMargin = Treemap.MinorLabelTextSize + Treemap.TextMargin * 2;\r\n if (node.dy < textHeightWithMargin)\r\n return false;\r\n if (node.depth === 1) {\r\n let roomTop = node.y + Treemap.MajorLabelTextSize + Treemap.TextMargin * 2;\r\n if (node.y + node.dy - roomTop < textHeightWithMargin)\r\n return false;\r\n }\r\n else if (node.depth === 2) {\r\n let parent = node.parent;\r\n let roomTop = Math.max(parent.y + Treemap.MajorLabelTextSize + Treemap.TextMargin * 2, node.y);\r\n\r\n // Parent's label needs the room\r\n if (node.y + node.dy - roomTop < textHeightWithMargin)\r\n return false;\r\n }\r\n\r\n return true;\r\n }\r\n\r\n private static createMajorLabelText(node: TreemapNode, labelsSettings: VisualDataLabelsSettings, alternativeScale: number, formattersCache: IColumnFormatterCacheManager): string {\r\n let spaceAvaliableForLabels = node.dx - Treemap.TextMargin * 2;\r\n let baseTextProperties = Treemap.MajorLabelTextProperties;\r\n let textProperties: powerbi.TextProperties = {\r\n text: node.name,\r\n fontFamily: baseTextProperties.fontFamily,\r\n fontSize: baseTextProperties.fontSize\r\n };\r\n\r\n return TextMeasurementService.getTailoredTextOrDefault(textProperties, spaceAvaliableForLabels);\r\n }\r\n\r\n private static createMinorLabelText(node: TreemapNode, labelsSettings: VisualDataLabelsSettings, alternativeScale: number, formattersCache: IColumnFormatterCacheManager): string {\r\n let spaceAvaliableForLabels = node.dx - Treemap.TextMargin * 2;\r\n let label = node.name;\r\n if (labelsSettings.show) {\r\n let measureFormatter = formattersCache.getOrCreate(node.labelFormatString, labelsSettings, alternativeScale);\r\n // Create measure label\r\n label = dataLabelUtils.getLabelFormattedText({\r\n label: node.originalHighlightValue != null ? node.originalHighlightValue : node.originalValue, maxWidth:\r\n spaceAvaliableForLabels, formatter: measureFormatter\r\n });\r\n // Add category if needed (we're showing category and the node depth is 2)\r\n if (labelsSettings.showCategory && node.depth === 2)\r\n label = dataLabelUtils.getLabelFormattedText({\r\n label: node.name,\r\n maxWidth: spaceAvaliableForLabels\r\n }) + \" \" + label;\r\n }\r\n\r\n return dataLabelUtils.getLabelFormattedText({\r\n label: label,\r\n maxWidth: spaceAvaliableForLabels,\r\n fontSize: labelsSettings.fontSize\r\n });\r\n }\r\n\r\n public static getFill(d: TreemapNode, isHighlightRect: boolean): string {\r\n // NOTE: only painted shapes will catch click event. We either paint children or their parent but not both.\r\n\r\n // If it's a leaf with no category, parent will be painted instead (and support interactivity)\r\n if (d.depth > 1 && !d.identity.hasIdentity() && !isHighlightRect)\r\n return CssConstants.noneValue;\r\n\r\n // If it's not a leaf and it has children with a category, children will be painted\r\n if (Treemap.hasChildrenWithIdentity(d))\r\n return CssConstants.noneValue;\r\n\r\n return d.color;\r\n }\r\n\r\n public static getFillOpacity(d: TreemapNode, hasSelection: boolean, hasHighlights: boolean, isHighlightRect: boolean): string {\r\n if (hasHighlights) {\r\n if (isHighlightRect)\r\n return null;\r\n return Treemap.DimmedShapeOpacity.toString();\r\n }\r\n\r\n if (!hasSelection || d.selected)\r\n return null;\r\n\r\n // Parent node is selected (as an optimization, we only check below level 1 because root node cannot be selected anyway)\r\n if (d.depth > 1 && (<TreemapNode>d.parent).selected)\r\n return null;\r\n\r\n // It's a parent node with interactive children, fall back to default opacity\r\n if (Treemap.hasChildrenWithIdentity(d))\r\n return null;\r\n\r\n return Treemap.DimmedShapeOpacity.toString();\r\n }\r\n\r\n private updateInternal(suppressAnimations: boolean): void {\r\n let data = this.data;\r\n let hasHighlights = data && data.hasHighlights;\r\n let labelSettings = data ? data.dataLabelsSettings : null;\r\n let duration = AnimatorCommon.GetAnimationDuration(this.animator, suppressAnimations);\r\n\r\n if (!(this.options.interactivity && this.options.interactivity.isInteractiveLegend) && this.data) {\r\n this.renderLegend();\r\n }\r\n\r\n this.initViewportDependantProperties(duration);\r\n let viewport = this.calculateTreemapSize();\r\n\r\n this.treemap.size([viewport.width, viewport.height]);\r\n\r\n // Shapes are drawn for all nodes\r\n let nodes = (data && data.root) ? this.treemap.nodes(data.root) : [];\r\n // Highlight shapes are drawn only for nodes with non-null/undefed highlightMultipliers that have no children\r\n let highlightNodes = nodes.filter((value: TreemapNode) => value.highlightMultiplier != null && (!value.children || value.children.length === 0));\r\n let majorLabeledNodes = [];\r\n let minorLabeledNodes = [];\r\n let alternativeScale: number = null;\r\n\r\n // Only populate major labels if category labels are turned on\r\n if (labelSettings.showCategory) {\r\n majorLabeledNodes = nodes.filter((d: TreemapNode) => Treemap.canDisplayMajorLabel(d));\r\n }\r\n\r\n // Only populate minor labels if category or data labels are turned on\r\n if (labelSettings.show || labelSettings.showCategory) {\r\n minorLabeledNodes = nodes.filter((d: TreemapNode) => Treemap.canDisplayMinorLabel(d, labelSettings));\r\n\r\n // If the display unit is 0 we calculate the format scale using the maximum value available\r\n if (labelSettings.displayUnits === 0)\r\n alternativeScale = <number>d3.max(minorLabeledNodes, (d: TreemapNode) => Math.abs(d.value));\r\n }\r\n\r\n let treemapLayout = Treemap.getLayout(labelSettings, alternativeScale);\r\n let shapes: D3.UpdateSelection;\r\n let highlightShapes: D3.UpdateSelection;\r\n let majorLabels: D3.UpdateSelection;\r\n let minorLabels: D3.UpdateSelection;\r\n let result: TreemapAnimationResult;\r\n if (this.animator && !suppressAnimations) {\r\n let options: TreemapAnimationOptions = {\r\n viewModel: data,\r\n nodes: nodes,\r\n highlightNodes: highlightNodes,\r\n majorLabeledNodes: majorLabeledNodes,\r\n minorLabeledNodes: minorLabeledNodes,\r\n shapeGraphicsContext: this.shapeGraphicsContext,\r\n labelGraphicsContext: this.labelGraphicsContext,\r\n interactivityService: this.interactivityService,\r\n layout: treemapLayout,\r\n labelSettings: labelSettings,\r\n };\r\n result = this.animator.animate(options);\r\n shapes = result.shapes;\r\n highlightShapes = result.highlightShapes;\r\n majorLabels = result.majorLabels;\r\n minorLabels = result.minorLabels;\r\n }\r\n if (!this.animator || suppressAnimations || result.failed) {\r\n let hasSelection = this.interactivityService && this.interactivityService.hasSelection();\r\n let shapeGraphicsContext = this.shapeGraphicsContext;\r\n shapes = Treemap.drawDefaultShapes(shapeGraphicsContext, nodes, hasSelection, hasHighlights, treemapLayout);\r\n highlightShapes = Treemap.drawDefaultHighlightShapes(shapeGraphicsContext, highlightNodes, hasSelection, hasHighlights, treemapLayout);\r\n let labelGraphicsContext = this.labelGraphicsContext;\r\n majorLabels = Treemap.drawDefaultMajorLabels(labelGraphicsContext, majorLabeledNodes, labelSettings, treemapLayout);\r\n minorLabels = Treemap.drawDefaultMinorLabels(labelGraphicsContext, minorLabeledNodes, labelSettings, treemapLayout);\r\n }\r\n\r\n if (this.interactivityService) {\r\n let behaviorOptions: TreemapBehaviorOptions = {\r\n shapes: shapes,\r\n highlightShapes: highlightShapes,\r\n majorLabels: majorLabels,\r\n minorLabels: minorLabels,\r\n nodes: <TreemapNode[]>nodes,\r\n hasHighlights: data.hasHighlights,\r\n };\r\n\r\n this.interactivityService.bind(<TreemapNode[]>nodes, this.behavior, behaviorOptions);\r\n }\r\n\r\n if (this.tooltipsEnabled) {\r\n TooltipManager.addTooltip(shapes, (tooltipEvent: TooltipEvent) => tooltipEvent.data.highlightedTooltipInfo ? tooltipEvent.data.highlightedTooltipInfo : tooltipEvent.data.tooltipInfo);\r\n TooltipManager.addTooltip(highlightShapes, (tooltipEvent: TooltipEvent) => tooltipEvent.data.highlightedTooltipInfo);\r\n }\r\n\r\n SVGUtil.flushAllD3TransitionsIfNeeded(this.options);\r\n }\r\n\r\n private renderLegend(): void {\r\n let legendObjectProperties = this.data.legendObjectProperties;\r\n if (legendObjectProperties) {\r\n let legendData = this.data.legendData;\r\n LegendData.update(legendData, legendObjectProperties);\r\n let position = <string>legendObjectProperties[legendProps.position];\r\n if (position)\r\n this.legend.changeOrientation(LegendPosition[position]);\r\n\r\n this.legend.drawLegend(legendData, this.currentViewport);\r\n } else {\r\n // TODO: Draw should be the only API. Visuals should only call that with orientation, props, etc \r\n // instead of managing state. Will follow up with another change.\r\n this.legend.changeOrientation(LegendPosition.Top);\r\n this.legend.drawLegend({ dataPoints: [] }, this.currentViewport);\r\n }\r\n }\r\n\r\n private static getNodeClass(d: TreemapNode, highlight?: boolean): string {\r\n let nodeClass: string;\r\n switch (d.depth) {\r\n case 1:\r\n nodeClass = Treemap.ParentGroupClassName;\r\n break;\r\n case 2:\r\n nodeClass = Treemap.NodeGroupClassName;\r\n break;\r\n case 0:\r\n nodeClass = Treemap.RootNodeClassName;\r\n break;\r\n default:\r\n debug.assertFail('Treemap only supports 2 levels maxiumum');\r\n }\r\n nodeClass += \" \" + (highlight ? Treemap.HighlightNodeClassName : Treemap.TreemapNodeClassName);\r\n return nodeClass;\r\n }\r\n\r\n private static createTreemapShapeLayout(isHighlightRect = false) {\r\n return {\r\n x: (d: TreemapNode) => d.x,\r\n y: (d: TreemapNode) => d.y + (isHighlightRect ? d.dy * (1 - d.highlightMultiplier) : 0),\r\n width: (d: TreemapNode) => Math.max(0, d.dx),\r\n height: (d: TreemapNode) => Math.max(0, d.dy * (isHighlightRect ? d.highlightMultiplier : 1)),\r\n };\r\n }\r\n\r\n private static createTreemapZeroShapeLayout() {\r\n return {\r\n x: (d: TreemapNode) => d.x,\r\n y: (d: TreemapNode) => d.y + d.dy,\r\n width: (d: TreemapNode) => Math.max(0, d.dx),\r\n height: (d: TreemapNode) => 0,\r\n };\r\n }\r\n\r\n public static drawDefaultShapes(context: D3.Selection, nodes: D3.Layout.GraphNode[], hasSelection: boolean, hasHighlights: boolean, layout: ITreemapLayout): D3.UpdateSelection {\r\n let isHighlightShape = false;\r\n let shapes = context.selectAll('.' + Treemap.TreemapNodeClassName)\r\n .data(nodes, (d: TreemapNode) => d.key);\r\n\r\n shapes.enter().append('rect')\r\n .attr('class', layout.shapeClass);\r\n\r\n shapes\r\n .style(\"fill\", (d: TreemapNode) => Treemap.getFill(d, isHighlightShape))\r\n .style(\"fill-opacity\", (d: TreemapNode) => Treemap.getFillOpacity(d, hasSelection, hasHighlights, isHighlightShape))\r\n .attr(layout.shapeLayout);\r\n\r\n shapes.exit().remove();\r\n\r\n return shapes;\r\n }\r\n\r\n public static drawDefaultHighlightShapes(context: D3.Selection, nodes: D3.Layout.GraphNode[], hasSelection: boolean, hasHighlights: boolean, layout: ITreemapLayout): D3.UpdateSelection {\r\n let isHighlightShape = true;\r\n let highlightShapes = context.selectAll('.' + Treemap.HighlightNodeClassName)\r\n .data(nodes, (d) => d.key + \"highlight\");\r\n\r\n highlightShapes.enter().append('rect')\r\n .attr('class', layout.highlightShapeClass);\r\n\r\n highlightShapes\r\n .style(\"fill\", (d: TreemapNode) => Treemap.getFill(d, isHighlightShape))\r\n .style(\"fill-opacity\", (d: TreemapNode) => Treemap.getFillOpacity(d, hasSelection, hasHighlights, isHighlightShape))\r\n .attr(layout.highlightShapeLayout);\r\n\r\n highlightShapes.exit().remove();\r\n return highlightShapes;\r\n }\r\n\r\n public static drawDefaultMajorLabels(context: D3.Selection, nodes: D3.Layout.GraphNode[], labelSettings: VisualDataLabelsSettings, layout: ITreemapLayout): D3.UpdateSelection {\r\n let labels = context\r\n .selectAll('.' + Treemap.MajorLabelClassName)\r\n .data(nodes, (d: TreemapNode) => d.key);\r\n\r\n labels.enter().append('text')\r\n .attr('class', layout.majorLabelClass);\r\n\r\n labels\r\n .attr(layout.majorLabelLayout)\r\n .text(layout.majorLabelText)\r\n .style('fill', () => labelSettings.labelColor);\r\n\r\n labels.exit().remove();\r\n\r\n return labels;\r\n }\r\n\r\n public static drawDefaultMinorLabels(context: D3.Selection, nodes: D3.Layout.GraphNode[], labelSettings: VisualDataLabelsSettings, layout: ITreemapLayout): D3.UpdateSelection {\r\n let labels = context\r\n .selectAll('.' + Treemap.MinorLabelClassName)\r\n .data(nodes, (d: TreemapNode) => d.key);\r\n\r\n labels.enter().append('text')\r\n .attr('class', layout.minorLabelClass);\r\n\r\n labels\r\n .attr(layout.minorLabelLayout)\r\n .text(layout.minorLabelText)\r\n .style('fill', () => labelSettings.labelColor);\r\n\r\n labels.exit().remove();\r\n\r\n return labels;\r\n }\r\n\r\n public static cleanMinorLabels(context: D3.Selection) {\r\n let empty = [];\r\n let labels = context\r\n .selectAll('.' + Treemap.LabelsGroupClassName)\r\n .selectAll('.' + Treemap.MinorLabelClassName)\r\n .data(empty);\r\n labels.exit().remove();\r\n }\r\n }\r\n}\n\n\n/** WEBPACK FOOTER **\n ** ./src/Clients/Visuals/visuals/treemap.ts\n **/","/*\r\n * Power BI Visualizations\r\n *\r\n * Copyright (c) Microsoft Corporation\r\n * All rights reserved.\r\n * MIT License\r\n *\r\n * Permission is hereby granted, free of charge, to any person obtaining a copy\r\n * of this software and associated documentation files (the \"\"Software\"\"), to deal\r\n * in the Software without restriction, including without limitation the rights\r\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\r\n * copies of the Software, and to permit persons to whom the Software is\r\n * furnished to do so, subject to the following conditions:\r\n *\r\n * The above copyright notice and this permission notice shall be included in\r\n * all copies or substantial portions of the Software.\r\n *\r\n * THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\r\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\r\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\r\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\r\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\r\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\r\n * THE SOFTWARE.\r\n */\r\n\r\n/// <reference path=\"../_references.ts\"/>\r\n\r\nmodule powerbi.visuals {\r\n import KpiImageSize = powerbi.visuals.KpiUtil.KpiImageSize;\r\n import KpiImageMetadata = powerbi.visuals.KpiUtil.KpiImageMetadata;\r\n import getKpiImageMetadata = powerbi.visuals.KpiUtil.getKpiImageMetadata;\r\n import ClassAndSelector = jsCommon.CssConstants.ClassAndSelector;\r\n import createClassAndSelector = jsCommon.CssConstants.createClassAndSelector;\r\n\r\n export interface CardStyleText {\r\n textSize: number;\r\n color: string;\r\n paddingTop?: number;\r\n }\r\n\r\n export interface CardStyleValue extends CardStyleText {\r\n fontFamily: string;\r\n }\r\n\r\n export interface CardStyle {\r\n card: {\r\n maxFontSize: number;\r\n };\r\n label: CardStyleText;\r\n value: CardStyleValue;\r\n }\r\n\r\n export interface CardSmallViewportProperties {\r\n cardSmallViewportWidth: number;\r\n }\r\n\r\n export interface CardConstructorOptions {\r\n isScrollable?: boolean;\r\n displayUnitSystemType?: DisplayUnitSystemType;\r\n animator?: IGenericAnimator;\r\n cardSmallViewportProperties?: CardSmallViewportProperties;\r\n }\r\n\r\n export interface CardFormatSetting {\r\n textSize: number;\r\n labelSettings: VisualDataLabelsSettings;\r\n wordWrap: boolean;\r\n }\r\n\r\n export class Card extends AnimatedText implements IVisual {\r\n private static cardClassName: string = 'card';\r\n private static Label: ClassAndSelector = createClassAndSelector('label');\r\n private static Value: ClassAndSelector = createClassAndSelector('value');\r\n private static KPIImage: ClassAndSelector = createClassAndSelector('caption');\r\n\r\n private static cardTextProperties: TextProperties = {\r\n fontSize: null,\r\n text: null,\r\n fontFamily: dataLabelUtils.LabelTextProperties.fontFamily,\r\n };\r\n\r\n public static DefaultStyle: CardStyle = {\r\n card: {\r\n maxFontSize: 200\r\n },\r\n label: {\r\n textSize: 12,\r\n color: '#a6a6a6',\r\n paddingTop: 8\r\n },\r\n value: {\r\n textSize: 27,\r\n color: '#333333',\r\n fontFamily: Font.Family.regularSecondary.css\r\n }\r\n };\r\n\r\n private animationOptions: AnimationOptions;\r\n private displayUnitSystemType: DisplayUnitSystemType;\r\n private isScrollable: boolean;\r\n private graphicsContext: D3.Selection;\r\n private labelContext: D3.Selection;\r\n private cardFormatSetting: CardFormatSetting;\r\n private kpiImage: D3.Selection;\r\n private cardSmallViewportProperties: CardSmallViewportProperties;\r\n\r\n public constructor(options?: CardConstructorOptions) {\r\n super(Card.cardClassName);\r\n this.isScrollable = false;\r\n this.displayUnitSystemType = DisplayUnitSystemType.WholeUnits;\r\n\r\n if (options) {\r\n this.isScrollable = !!options.isScrollable;\r\n if (options.animator)\r\n this.animator = options.animator;\r\n if (options.displayUnitSystemType != null)\r\n this.displayUnitSystemType = options.displayUnitSystemType;\r\n if (options.cardSmallViewportProperties) {\r\n this.cardSmallViewportProperties = options.cardSmallViewportProperties;\r\n }\r\n }\r\n }\r\n\r\n public init(options: VisualInitOptions) {\r\n debug.assertValue(options, 'options');\r\n this.animationOptions = options.animation;\r\n let element = options.element;\r\n\r\n this.kpiImage = d3.select(element.get(0)).append('div')\r\n .classed(Card.KPIImage.class, true);\r\n let svg = this.svg = d3.select(element.get(0)).append('svg');\r\n this.graphicsContext = svg.append('g');\r\n this.currentViewport = options.viewport;\r\n this.hostServices = options.host;\r\n this.style = options.style;\r\n\r\n this.updateViewportProperties();\r\n\r\n if (this.isScrollable) {\r\n svg.attr('class', Card.cardClassName);\r\n this.labelContext = svg.append('g');\r\n }\r\n }\r\n\r\n public onDataChanged(options: VisualDataChangedOptions): void {\r\n debug.assertValue(options, 'options');\r\n\r\n let dataView = options.dataViews[0];\r\n let value: any;\r\n if (dataView) {\r\n this.getMetaDataColumn(dataView);\r\n if (dataView.single) {\r\n value = dataView.single.value;\r\n }\r\n\r\n // Update settings based on new metadata column\r\n this.cardFormatSetting = this.getDefaultFormatSettings();\r\n\r\n let dataViewMetadata = dataView.metadata;\r\n if (dataViewMetadata) {\r\n let objects: DataViewObjects = dataViewMetadata.objects;\r\n if (objects) {\r\n let labelSettings = this.cardFormatSetting.labelSettings;\r\n\r\n labelSettings.labelColor = DataViewObjects.getFillColor(objects, cardProps.labels.color, labelSettings.labelColor);\r\n labelSettings.precision = DataViewObjects.getValue(objects, cardProps.labels.labelPrecision, labelSettings.precision);\r\n labelSettings.fontSize = DataViewObjects.getValue(objects, cardProps.labels.fontSize, labelSettings.fontSize);\r\n\r\n // The precision can't go below 0\r\n if (labelSettings.precision !== dataLabelUtils.defaultLabelPrecision && labelSettings.precision < 0) {\r\n labelSettings.precision = 0;\r\n }\r\n\r\n labelSettings.displayUnits = DataViewObjects.getValue(objects, cardProps.labels.labelDisplayUnits, labelSettings.displayUnits);\r\n\r\n //category labels\r\n labelSettings.showCategory = DataViewObjects.getValue(objects, cardProps.categoryLabels.show, labelSettings.showCategory);\r\n labelSettings.categoryLabelColor = DataViewObjects.getFillColor(objects, cardProps.categoryLabels.color, labelSettings.categoryLabelColor);\r\n\r\n this.cardFormatSetting.wordWrap = DataViewObjects.getValue(objects, cardProps.wordWrap.show, this.cardFormatSetting.wordWrap);\r\n this.cardFormatSetting.textSize = DataViewObjects.getValue(objects, cardProps.categoryLabels.fontSize, this.cardFormatSetting.textSize);\r\n }\r\n }\r\n }\r\n\r\n this.updateInternal(value, true /* suppressAnimations */, true /* forceUpdate */);\r\n }\r\n\r\n public onResizing(viewport: IViewport): void {\r\n this.currentViewport = viewport;\r\n this.updateViewportProperties();\r\n this.updateInternal(this.value, true /* suppressAnimations */, true /* forceUpdate */);\r\n }\r\n\r\n private updateViewportProperties() {\r\n let viewport = this.currentViewport;\r\n this.svg.attr('width', viewport.width)\r\n .attr('height', viewport.height);\r\n }\r\n\r\n private setTextProperties(text: string, fontSize: number): void {\r\n Card.cardTextProperties.fontSize = jsCommon.PixelConverter.fromPoint(fontSize);\r\n Card.cardTextProperties.text = text;\r\n }\r\n\r\n private getCardFormatTextSize(): number {\r\n return this.cardFormatSetting.textSize;\r\n }\r\n\r\n private isSmallViewport(): boolean {\r\n if (this.cardSmallViewportProperties) {\r\n if (this.currentViewport.width < this.cardSmallViewportProperties.cardSmallViewportWidth) {\r\n return true;\r\n }\r\n }\r\n return false;\r\n }\r\n\r\n private getCardPrecision(isSmallViewport: boolean = false): number {\r\n return isSmallViewport ? dataLabelUtils.defaultLabelPrecision : this.cardFormatSetting.labelSettings.precision;\r\n }\r\n\r\n private getCardDisplayUnits(isSmallViewport: boolean = false): number {\r\n return isSmallViewport ? 0 : this.cardFormatSetting.labelSettings.displayUnits;\r\n }\r\n\r\n public getAdjustedFontHeight(availableWidth: number, textToMeasure: string, seedFontHeight: number) {\r\n let adjustedFontHeight = super.getAdjustedFontHeight(availableWidth, textToMeasure, seedFontHeight);\r\n\r\n return Math.min(adjustedFontHeight, Card.DefaultStyle.card.maxFontSize);\r\n }\r\n\r\n public clear(valueOnly: boolean = false) {\r\n this.svg.select(Card.Value.selector).text('');\r\n\r\n if (!valueOnly)\r\n this.svg.select(Card.Label.selector).text('');\r\n\r\n super.clear();\r\n }\r\n\r\n private updateInternal(target: any, suppressAnimations: boolean, forceUpdate: boolean = false) {\r\n let start = this.value;\r\n let duration = AnimatorCommon.GetAnimationDuration(this.animator, suppressAnimations);\r\n\r\n if (target === undefined) {\r\n if (start !== undefined)\r\n this.clear();\r\n return;\r\n }\r\n\r\n let metaDataColumn = this.metaDataColumn;\r\n let labelSettings = this.cardFormatSetting.labelSettings;\r\n let isSmallViewport = this.isSmallViewport();\r\n let precision = this.getCardPrecision(isSmallViewport);\r\n let displayUnits = this.getCardDisplayUnits(isSmallViewport);\r\n\r\n let isDefaultDisplayUnit = displayUnits === 0;\r\n let format = this.getFormatString(metaDataColumn);\r\n let formatter = valueFormatter.create({\r\n format: format,\r\n value: isDefaultDisplayUnit ? target : displayUnits,\r\n precision: dataLabelUtils.getLabelPrecision(precision, format),\r\n displayUnitSystemType: isDefaultDisplayUnit && precision === dataLabelUtils.defaultLabelPrecision ? this.displayUnitSystemType : DisplayUnitSystemType.WholeUnits, // keeps this.displayUnitSystemType as the displayUnitSystemType unless the user changed the displayUnits or the precision\r\n formatSingleValues: isDefaultDisplayUnit ? true : false,\r\n allowFormatBeautification: true,\r\n columnType: metaDataColumn ? metaDataColumn.type : undefined\r\n });\r\n\r\n let formatSettings = this.cardFormatSetting;\r\n let valueTextHeightInPx = jsCommon.PixelConverter.fromPointToPixel(labelSettings.fontSize);\r\n let valueStyles = Card.DefaultStyle.value;\r\n this.setTextProperties(target, this.getCardFormatTextSize());\r\n let labelTextHeightInPx = labelSettings.showCategory ? TextMeasurementService.estimateSvgTextHeight(Card.cardTextProperties) : 0;\r\n let labelHeightWithPadding = labelTextHeightInPx + Card.DefaultStyle.label.paddingTop;\r\n\r\n let width = this.currentViewport.width;\r\n let height = this.currentViewport.height;\r\n let translateX = this.getTranslateX(width);\r\n let translateY = (height - labelHeightWithPadding - valueTextHeightInPx) / 2;\r\n let statusGraphicInfo: KpiImageMetadata = getKpiImageMetadata(metaDataColumn, target, KpiImageSize.Big);\r\n\r\n if (this.isScrollable) {\r\n if (!forceUpdate && start === target)\r\n return;\r\n\r\n // We want to format for null/blank/empty string and anything that is not a string\r\n if (start !== target && (_.isEmpty(target) || typeof (target) !== \"string\"))\r\n target = formatter.format(target);\r\n\r\n if (statusGraphicInfo) {\r\n // Display card KPI icon\r\n this.graphicsContext.selectAll('text').remove();\r\n this.displayStatusGraphic(statusGraphicInfo, translateX, translateY, valueTextHeightInPx);\r\n }\r\n else {\r\n // Display card text value\r\n this.kpiImage.selectAll('div').remove();\r\n let translatedValueY = this.getTranslateY(valueTextHeightInPx + translateY);\r\n let valueElement = this.graphicsContext\r\n .attr('transform', SVGUtil.translate(translateX, translatedValueY))\r\n .selectAll('text')\r\n .data([target]);\r\n\r\n valueElement\r\n .enter()\r\n .append('text')\r\n .attr('class', Card.Value.class);\r\n\r\n valueElement\r\n .text((d: any) => d)\r\n .style({\r\n 'font-size': jsCommon.PixelConverter.fromPoint(labelSettings.fontSize),\r\n 'fill': labelSettings.labelColor,\r\n 'font-family': valueStyles.fontFamily,\r\n 'text-anchor': this.getTextAnchor(),\r\n });\r\n\r\n if (formatSettings.wordWrap) {\r\n let valueElementNode = <SVGTextElement>valueElement.node();\r\n TextMeasurementService.wordBreak(valueElementNode, width, height - labelHeightWithPadding);\r\n\r\n let numLines = valueElementNode.childElementCount;\r\n\r\n if (numLines > 1) {\r\n let valueTextLineHeight = valueTextHeightInPx;\r\n valueTextHeightInPx *= numLines;\r\n // Use the full height of all the text to figure out the top of the vertically centered container (translateY)\r\n translateY = (height - labelHeightWithPadding - valueTextHeightInPx) / 2;\r\n // But only use height of one line when figuring out the anchor point for the text since its vertical anchor is on\r\n // the baseline of the 1st row.\r\n translatedValueY = this.getTranslateY(valueTextLineHeight + translateY);\r\n this.graphicsContext.attr('transform', SVGUtil.translate(translateX, translatedValueY));\r\n }\r\n }\r\n else {\r\n valueElement.call(AxisHelper.LabelLayoutStrategy.clip,\r\n width,\r\n TextMeasurementService.svgEllipsis);\r\n }\r\n\r\n valueElement\r\n .append('title')\r\n .text((d) => d);\r\n\r\n valueElement.exit().remove();\r\n }\r\n\r\n // Show the label if it's enabled and we have a value to display\r\n if (labelSettings.showCategory && metaDataColumn && metaDataColumn.displayName) {\r\n let labelData = [metaDataColumn.displayName];\r\n\r\n let translatedLabelY = this.getTranslateY(valueTextHeightInPx + labelHeightWithPadding + translateY);\r\n let labelElement = this.labelContext\r\n .attr('transform', SVGUtil.translate(translateX, translatedLabelY))\r\n .selectAll('text')\r\n .data(labelData);\r\n\r\n labelElement\r\n .enter()\r\n .append('text')\r\n .attr('class', Card.Label.class);\r\n\r\n labelElement\r\n .text((d) => d)\r\n .style({\r\n 'font-size': jsCommon.PixelConverter.fromPoint(this.getCardFormatTextSize()),\r\n 'fill': labelSettings.categoryLabelColor,\r\n 'text-anchor': this.getTextAnchor()\r\n });\r\n\r\n let labelElementNode = <SVGTextElement>labelElement.node();\r\n if (labelElementNode) {\r\n if (formatSettings.wordWrap)\r\n TextMeasurementService.wordBreak(labelElementNode, width / 2, height - translatedLabelY);\r\n else\r\n labelElement.call(AxisHelper.LabelLayoutStrategy.clip,\r\n width,\r\n TextMeasurementService.svgEllipsis);\r\n }\r\n\r\n labelElement\r\n .append('title')\r\n .text((d) => d);\r\n\r\n labelElement.exit().remove();\r\n }\r\n else {\r\n // Otherwise, remove any existing labels we may have been displaying\r\n this.labelContext.selectAll('text').remove();\r\n }\r\n }\r\n else {\r\n if (statusGraphicInfo) {\r\n // Display card KPI icon\r\n this.graphicsContext.selectAll('text').remove();\r\n this.displayStatusGraphic(statusGraphicInfo, translateX, translateY, valueTextHeightInPx);\r\n }\r\n else {\r\n this.kpiImage.selectAll('div').remove();\r\n this.doValueTransition(\r\n start,\r\n target,\r\n this.displayUnitSystemType,\r\n this.animationOptions,\r\n duration,\r\n forceUpdate,\r\n formatter\r\n );\r\n\r\n //in order to remove duplicated title values we first remove all and than add a new one\r\n this.graphicsContext.call(tooltipUtils.tooltipUpdate, [target]);\r\n }\r\n }\r\n\r\n this.value = target;\r\n }\r\n\r\n private displayStatusGraphic(statusGraphicInfo: KpiImageMetadata, translateX: number, translateY: number, labelTextSizeInPx: number) {\r\n // Remove existing text\r\n this.graphicsContext.selectAll('text').remove();\r\n\r\n // Create status graphic, if necessary\r\n let kpiImageDiv = this.kpiImage.select('div');\r\n if (!kpiImageDiv || kpiImageDiv.empty())\r\n kpiImageDiv = this.kpiImage.append('div');\r\n\r\n // Style status graphic\r\n kpiImageDiv\r\n .attr('class', statusGraphicInfo.class)\r\n .style('position', 'absolute')\r\n .style('font-size', labelTextSizeInPx + 'px');\r\n\r\n // Layout thrash to get image dimensions (could set as a const in future when icon font is fixed)\r\n let imageWidth = (<HTMLElement>kpiImageDiv.node()).offsetWidth;\r\n let imageHeight = (<HTMLElement>kpiImageDiv.node()).offsetHeight;\r\n\r\n // Position based on image height\r\n kpiImageDiv.style('transform', SVGUtil.translateWithPixels((translateX - (imageWidth / 2)), this.getTranslateY(labelTextSizeInPx + translateY) - imageHeight));\r\n }\r\n\r\n private getDefaultFormatSettings(): CardFormatSetting {\r\n return {\r\n labelSettings: dataLabelUtils.getDefaultCardLabelSettings(Card.DefaultStyle.value.color, Card.DefaultStyle.label.color, Card.DefaultStyle.value.textSize),\r\n wordWrap: false,\r\n textSize: Card.DefaultStyle.label.textSize,\r\n };\r\n }\r\n\r\n public enumerateObjectInstances(options: EnumerateVisualObjectInstancesOptions): VisualObjectInstanceEnumeration {\r\n if (!this.cardFormatSetting)\r\n this.cardFormatSetting = this.getDefaultFormatSettings();\r\n\r\n let formatSettings = this.cardFormatSetting;\r\n let enumeration = new ObjectEnumerationBuilder();\r\n\r\n switch (options.objectName) {\r\n case 'categoryLabels':\r\n dataLabelUtils.enumerateCategoryLabels(enumeration, formatSettings.labelSettings, true /* withFill */, true /* isShowCategory */, formatSettings.textSize);\r\n break;\r\n case 'labels':\r\n let labelSettingOptions: VisualDataLabelsSettingsOptions = {\r\n enumeration: enumeration,\r\n dataLabelsSettings: formatSettings.labelSettings,\r\n show: true,\r\n displayUnits: true,\r\n precision: true,\r\n fontSize: true,\r\n };\r\n dataLabelUtils.enumerateDataLabels(labelSettingOptions);\r\n break;\r\n case 'wordWrap':\r\n enumeration.pushInstance({\r\n objectName: 'wordWrap',\r\n selector: null,\r\n properties: {\r\n show: formatSettings.wordWrap,\r\n },\r\n });\r\n break;\r\n }\r\n\r\n return enumeration.complete();\r\n }\r\n }\r\n}\n\n\n/** WEBPACK FOOTER **\n ** ./src/Clients/Visuals/visuals/card.ts\n **/","/*\r\n * Power BI Visualizations\r\n *\r\n * Copyright (c) Microsoft Corporation\r\n * All rights reserved. \r\n * MIT License\r\n *\r\n * Permission is hereby granted, free of charge, to any person obtaining a copy\r\n * of this software and associated documentation files (the \"\"Software\"\"), to deal\r\n * in the Software without restriction, including without limitation the rights\r\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\r\n * copies of the Software, and to permit persons to whom the Software is\r\n * furnished to do so, subject to the following conditions:\r\n * \r\n * The above copyright notice and this permission notice shall be included in \r\n * all copies or substantial portions of the Software.\r\n * \r\n * THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR \r\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, \r\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE \r\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER \r\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\r\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\r\n * THE SOFTWARE.\r\n */\r\n\r\n/// <reference path=\"../_references.ts\"/>\r\n\r\nmodule powerbi.visuals {\r\n enum OwlHappiness {\r\n Sad = 0,\r\n Meh = 1,\r\n Happy = 2\r\n }\r\n\r\n export class OwlGauge implements IVisual {\r\n private static owlBodySvg = '<svg version=\"1.1\" class=\"owlGaugeBody\" xmlns=\"http://www.w3.org/2000/svg\" xmlns:xlink=\"http://www.w3.org/1999/xlink\" x=\"0px\" y=\"0px\" viewBox=\"0 0 267.7 291.2\" style=\"enable-background:new 0 0 267.7 291.2;\" xml:space=\"preserve\"> <style type=\"text/css\"> .owlGaugeBody .st0{fill:#A87D50;} .owlGaugeBody .st1{fill:#C2B59B;} .owlGaugeBody .st2{fill:#EB2227;} .owlGaugeBody .st3{fill:#FFFFFF;} .owlGaugeBody .st4{fill:#F9D018;} .owlGaugeBody .st5{fill:none;} .owlGaugeBody .st6{fill:#83381B;} .owlGaugeBody .st7{fill:#231F20;} </style> <g id=\"XMLID_31_\"> <g id=\"XMLID_34_\"> <ellipse id=\"XMLID_21_\" transform=\"matrix(0.9998 1.947640e-02 -1.947640e-02 0.9998 2.8614 -2.5802)\" class=\"st0\" cx=\"133.9\" cy=\"145.6\" rx=\"133.9\" ry=\"145.6\"/> <polygon id=\"XMLID_20_\" class=\"st0\" points=\"199.2,32.8 184,11.3 209,9.7 \"/> <polygon id=\"XMLID_19_\" class=\"st0\" points=\"73.9,31.2 62.1,7.7 87.1,9.8 \"/> <circle id=\"XMLID_18_\" class=\"st1\" cx=\"134.8\" cy=\"189.2\" r=\"89.8\"/> <path id=\"XMLID_17_\" class=\"st2\" d=\"M140.1,88c-2.7,3.8-7.9,4.7-11.7,2c-2.7-1.9-3.9-5.1-3.4-8.1c0,0,9.6-41.8,9.6-41.8l6.9,40.8 C142,83.2,141.6,85.8,140.1,88z\"/> <path id=\"XMLID_16_\" class=\"st3\" d=\"M164.6,16.2c-14.2,0-26.3,9.2-30.6,21.9c-4.1-13.1-16.3-22.6-30.8-22.6 C85.4,15.6,71,30,71,47.8s14.4,32.3,32.3,32.3c14.2,0,26.3-9.2,30.6-21.9c4.1,13.1,16.3,22.6,30.8,22.6 c17.8,0,32.3-14.4,32.3-32.3S182.4,16.2,164.6,16.2z\"/> <path id=\"XMLID_15_\" class=\"st4\" d=\"M122,58.7l23.3-0.1c0,0-9,14.8-10.2,16.6c-1.2,1.9-2.2,0.1-2.2,0.1L122,58.7z\"/> <rect id=\"XMLID_14_\" x=\"-11.4\" y=\"-68.8\" class=\"st5\" width=\"288.3\" height=\"259.7\"/> <g id=\"XMLID_37_\"> <path id=\"XMLID_13_\" class=\"st6\" d=\"M121.6,125.5c0,3.7-3.5,6.6-7.7,6.6c-4.2,0-7.7-3-7.7-6.6\"/> <path id=\"XMLID_12_\" class=\"st6\" d=\"M160.1,126.5c0,3.7-3.5,6.6-7.7,6.6s-7.7-3-7.7-6.6\"/> <path id=\"XMLID_11_\" class=\"st6\" d=\"M142.4,148.1c0,3.7-3.5,6.6-7.7,6.6c-4.2,0-7.7-3-7.7-6.6\"/> <path id=\"XMLID_10_\" class=\"st6\" d=\"M183.1,148.8c0,3.7-3.5,6.6-7.7,6.6c-4.2,0-7.7-3-7.7-6.6\"/> <path id=\"XMLID_9_\" class=\"st6\" d=\"M160.9,177.4c0,3.7-3.5,6.6-7.7,6.6s-7.7-3-7.7-6.6\"/> <path id=\"XMLID_8_\" class=\"st6\" d=\"M201.6,178c0,3.7-3.5,6.6-7.7,6.6s-7.7-3-7.7-6.6\"/> <path id=\"XMLID_7_\" class=\"st6\" d=\"M76.4,177.4c0,3.7-3.5,6.6-7.7,6.6c-4.2,0-7.7-3-7.7-6.6\"/> <path id=\"XMLID_6_\" class=\"st6\" d=\"M117,178c0,3.7-3.5,6.6-7.7,6.6s-7.7-3-7.7-6.6\"/> <path id=\"XMLID_5_\" class=\"st6\" d=\"M98.6,148.1c0,3.7-3.5,6.6-7.7,6.6c-4.2,0-7.7-3-7.7-6.6\"/> </g> <circle id=\"XMLID_4_\" class=\"st7\" cx=\"164.1\" cy=\"49\" r=\"6.4\"/> <circle id=\"XMLID_3_\" class=\"st7\" cx=\"102.7\" cy=\"47.7\" r=\"6.4\"/> </g> <path id=\"XMLID_2_\" class=\"st0\" d=\"M160.1,140.9c11.1-8.4,55.6-36,55.6-36l4.7,0.8l10.2,38.8c0,0-3,3-9.2,3.1 c-5.1,0.1-45.9-2.6-60.2-3.5C158.1,143.9,157.7,142.7,160.1,140.9z\"/> <path id=\"XMLID_1_\" class=\"st0\" d=\"M110.6,140.8c-11.1-8.4-55.6-36-55.6-36l-4.7,0.8L40,144.4c0,0,3,3,9.2,3.1 c5.1,0.1,45.9-2.6,60.2-3.5C112.5,143.8,113,142.6,110.6,140.8z\"/> </g> </svg>';\r\n private static owlTailSvg = '<svg version=\"1.1\" class=\"owlGaugeTail\" xmlns=\"http://www.w3.org/2000/svg\" xmlns:xlink=\"http://www.w3.org/1999/xlink\" x=\"0px\" y=\"0px\" viewBox=\"0 0 587.8 295.5\" style=\"enable-background:new 0 0 587.8 295.5;\" xml:space=\"preserve\"> <style type=\"text/css\"> .owlGaugeTail .st0{fill:#3B2416;} .owlGaugeTail .st1{fill:#5B4B43;} .owlGaugeTail .st2{fill:#603A17;} .owlGaugeTail .st3{fill:#726659;} </style> <g id=\"XMLID_55_\"> <path id=\"XMLID_29_\" class=\"st0\" d=\"M85.2,106.2c-27.1,0-49.2,22-49.2,49.2c0,19.1,10.9,35.7,26.9,43.8c0,0,231.2,95.9,231.2,95.9 l-171-171C114.1,113.2,100.5,106.2,85.2,106.2z\"/> <g id=\"XMLID_56_\"> <path id=\"XMLID_28_\" class=\"st1\" d=\"M482.5,86.4c0-27.1-22-49.2-49.2-49.2c-19.1,0-35.7,10.9-43.8,26.9c0,0-95.9,231.2-95.9,231.2 l171-171C475.5,115.3,482.5,101.7,482.5,86.4z\"/> <path id=\"XMLID_27_\" class=\"st2\" d=\"M573.5,281.3c19.2-19.2,19.2-50.3,0-69.5c-13.5-13.5-33-17.5-50-12c0,0-231.3,95.7-231.3,95.7 l241.8,0C548,296.9,562.6,292.1,573.5,281.3z\"/> <path id=\"XMLID_26_\" class=\"st3\" d=\"M279.9,14.4c-19.2-19.2-50.3-19.2-69.5,0c-13.5,13.5-17.5,33-12,50c0,0,95.7,231.3,95.7,231.3 L294,54C295.4,39.8,290.7,25.2,279.9,14.4z\"/> <path id=\"XMLID_25_\" class=\"st2\" d=\"M105.3,86.4c0-27.1,22-49.2,49.2-49.2c19.1,0,35.7,10.9,43.8,26.9c0,0,95.9,231.2,95.9,231.2 l-171-171C112.3,115.3,105.3,101.7,105.3,86.4z\"/> <path id=\"XMLID_24_\" class=\"st2\" d=\"M14.4,281.4c-19.2-19.2-19.2-50.3,0-69.5c13.5-13.5,33-17.5,50-12c0,0,231.3,95.7,231.3,95.7 l-241.8,0C39.8,297,25.2,292.3,14.4,281.4z\"/> <path id=\"XMLID_23_\" class=\"st2\" d=\"M308.2,14c19.2-19.2,50.3-19.2,69.5,0c13.5,13.5,17.5,33,12,50c0,0-95.7,231.3-95.7,231.3 l0-241.8C292.6,39.4,297.4,24.8,308.2,14z\"/> <path id=\"XMLID_22_\" class=\"st0\" d=\"M503.2,106c27.1,0,49.2,22,49.2,49.2c0,19.1-10.9,35.7-26.9,43.8c0,0-231.2,95.9-231.2,95.9 l171-171C474.2,113,487.8,106,503.2,106z\"/> </g> </g> </svg>';\r\n private static visualBgSvg = '<svg version=\"1.1\" class=\"owlGaugeBg\" xmlns=\"http://www.w3.org/2000/svg\" xmlns:xlink=\"http://www.w3.org/1999/xlink\" x=\"0px\" y=\"0px\" viewBox=\"123.8 94.9 349.1 175.3\" style=\"enable-background:new 123.8 94.9 349.1 175.3;\" xml:space=\"preserve\"> <style type=\"text/css\"> .owlGaugeBg .st0{fill:#EF4137;} .owlGaugeBg .st1{fill:#FAAF42;} .owlGaugeBg .st2{fill:#F15B2A;} .owlGaugeBg .st3{fill:#F69321;} </style> <g id=\"XMLID_10_\"> <path id=\"XMLID_8_\" class=\"st0\" d=\"M174.3,158c-16.1,0-29.2,13.1-29.2,29.2c0,11.4,6.5,21.2,16,26.1l137.3,57L196.9,168.7 C191.5,162.2,183.4,158,174.3,158z\"/> <g id=\"XMLID_11_\"> <path id=\"XMLID_7_\" class=\"st1\" d=\"M410.2,146.3c0-16.1-13.1-29.2-29.2-29.2c-11.4,0-21.2,6.5-26,16l-57,137.5L399.5,169 C406.1,163.5,410.2,155.4,410.2,146.3z\"/> <path id=\"XMLID_6_\" class=\"st0\" d=\"M464.3,262.2c11.4-11.4,11.4-29.9,0-41.3c-8-8-19.6-10.4-29.7-7.1l-137.4,56.9h143.6 C449.2,271.4,457.9,268.6,464.3,262.2z\"/> <path id=\"XMLID_5_\" class=\"st2\" d=\"M290,103.5c-11.4-11.4-29.9-11.4-41.3,0c-8,8-10.4,19.6-7.1,29.7l56.8,137.5V127 C299.2,118.6,296.4,109.9,290,103.5z\"/> <path id=\"XMLID_4_\" class=\"st3\" d=\"M186.3,146.3c0-16.1,13.1-29.2,29.2-29.2c11.4,0,21.2,6.5,26,16l57,137.5L197,168.8 C190.5,163.5,186.3,155.4,186.3,146.3z\"/> <path id=\"XMLID_3_\" class=\"st2\" d=\"M132.3,262.2c-11.4-11.4-11.4-29.9,0-41.3c8-8,19.6-10.4,29.7-7.1l137.4,56.9H155.8 C147.4,271.5,138.7,268.7,132.3,262.2z\"/> <path id=\"XMLID_2_\" class=\"st3\" d=\"M306.8,103.2c11.4-11.4,29.9-11.4,41.3,0c8,8,10.4,19.6,7.1,29.7l-56.8,137.5V126.7 C297.5,118.3,300.3,109.7,306.8,103.2z\"/> <path id=\"XMLID_1_\" class=\"st2\" d=\"M422.5,157.9c16.1,0,29.2,13.1,29.2,29.2c0,11.4-6.5,21.2-16,26.1l-137.3,57L400,168.6 C405.3,162.1,413.4,157.9,422.5,157.9z\"/> </g> </g> </svg>';\r\n\r\n private static owlBodyHeightMultiplier = 291.2 / 267.7;\r\n private static owlTailHeightMultiplier = 295.5 / 587.8;\r\n private static visualBgHeightMultiplier = 295.5 / 587.8;\r\n\r\n private static OwlDemoMode = false;\r\n\r\n public static capabilities: VisualCapabilities = {\r\n dataRoles: [\r\n {\r\n displayName: 'Category',\r\n name: 'Category',\r\n kind: powerbi.VisualDataRoleKind.Grouping,\r\n },\r\n {\r\n displayName: 'Y Axis',\r\n name: 'Y',\r\n kind: powerbi.VisualDataRoleKind.Measure,\r\n },\r\n ],\r\n dataViewMappings: [{\r\n categorical: {\r\n categories: {\r\n for: { in: 'Category' },\r\n dataReductionAlgorithm: { top: {} }\r\n },\r\n values: {\r\n select: [{ bind: { to: 'Y' } }]\r\n },\r\n }\r\n }]\r\n };\r\n\r\n public static converter(dataView: DataView): any {\r\n return {};\r\n }\r\n\r\n private static getGaugeData(dataView: DataView): GaugeTargetData {\r\n var settings: GaugeTargetData = {\r\n max: 100,\r\n min: 0,\r\n target: undefined,\r\n value: 0,\r\n tooltipItems: []\r\n };\r\n\r\n if (dataView && dataView.categorical && dataView.categorical.values && dataView.metadata && dataView.metadata.columns) {\r\n var values = dataView.categorical.values;\r\n var metadataColumns = dataView.metadata.columns;\r\n\r\n debug.assert(metadataColumns.length >= values.length, 'length');\r\n\r\n for (var i = 0; i < values.length; i++) {\r\n var col = metadataColumns[i],\r\n value = values[i].values[0] || 0;\r\n if (col && col.roles) {\r\n if (col.roles[gaugeRoleNames.y]) {\r\n settings.value = value;\r\n if (value)\r\n settings.tooltipItems.push({ displayName: values[i].source.displayName, value: converterHelper.formatFromMetadataColumn(value, values[i].source, Gauge.formatStringProp) });\r\n } else if (col.roles[gaugeRoleNames.minValue]) {\r\n settings.min = value;\r\n } else if (col.roles[gaugeRoleNames.maxValue]) {\r\n settings.max = value;\r\n } else if (col.roles[gaugeRoleNames.targetValue]) {\r\n settings.target = value;\r\n if (value)\r\n settings.tooltipItems.push({ displayName: values[i].source.displayName, value: converterHelper.formatFromMetadataColumn(value, values[i].source, Gauge.formatStringProp) });\r\n }\r\n }\r\n }\r\n }\r\n\r\n return settings;\r\n }\r\n\r\n private rootElem: JQuery;\r\n private svgBgElem: JQuery;\r\n private svgBodyElem: JQuery;\r\n private svgTailElem: JQuery;\r\n\r\n public init(options: VisualInitOptions): void {\r\n this.rootElem = options.element;\r\n this.rootElem.addClass('owlGaugeVisual');\r\n\r\n this.svgTailElem = $(OwlGauge.owlTailSvg);\r\n this.svgBgElem = $(OwlGauge.visualBgSvg);\r\n this.svgBodyElem = $(OwlGauge.owlBodySvg);\r\n\r\n this.rootElem.append(this.svgBgElem).append(this.svgTailElem).append(this.svgBodyElem);\r\n\r\n if (OwlGauge.OwlDemoMode) {\r\n window.setInterval(() => {\r\n var randomPercentage = Math.random() * 100 + 1;\r\n this.updateGauge(randomPercentage);\r\n }, 2000);\r\n }\r\n\r\n this.updateViewportSize(options.viewport.width, options.viewport.height);\r\n }\r\n\r\n public update(options: VisualUpdateOptions) {\r\n this.updateViewportSize(options.viewport.width, options.viewport.height);\r\n\r\n var dataView = options.dataViews.length > 0 ? options.dataViews[0] : null;\r\n\r\n if (dataView) {\r\n var gaugeData = OwlGauge.getGaugeData(options.dataViews[0]);\r\n\r\n var percentage = (gaugeData.value - gaugeData.min) / (gaugeData.max - gaugeData.min);\r\n this.updateGauge(percentage * 100 | 0);\r\n }\r\n else this.updateGauge(0);\r\n }\r\n\r\n private updateGauge(percentage: number) {\r\n if (percentage >= 0 && percentage <= 100) {\r\n var rotationDeg = -180 + (180 * percentage / 100);\r\n this.svgBgElem.css({ transform: 'rotate(' + rotationDeg + 'deg)' });\r\n\r\n if (percentage >= 66) {\r\n this.happinessLevel = OwlHappiness.Happy;\r\n }\r\n else if (percentage >= 33) {\r\n this.happinessLevel = OwlHappiness.Meh;\r\n }\r\n else {\r\n this.happinessLevel = OwlHappiness.Sad;\r\n }\r\n }\r\n }\r\n\r\n private set happinessLevel(level: OwlHappiness) {\r\n this.rootElem.removeClass('sad').removeClass('meh').removeClass('happy');\r\n\r\n switch (level) {\r\n case OwlHappiness.Sad:\r\n this.rootElem.addClass('sad');\r\n break;\r\n case OwlHappiness.Meh:\r\n this.rootElem.addClass('meh');\r\n break;\r\n case OwlHappiness.Happy:\r\n this.rootElem.addClass('happy');\r\n break;\r\n default:\r\n console.log('Well, this is interesting...');\r\n }\r\n }\r\n\r\n private updateViewportSize(width: number, height: number) {\r\n var smoothingFn = window.setImmediate || window.requestAnimationFrame;\r\n\r\n smoothingFn(() => {\r\n this.rootElem.css({\r\n height: height,\r\n width: width\r\n });\r\n\r\n this.svgBodyElem.height(this.svgBodyElem.width() * OwlGauge.owlBodyHeightMultiplier);\r\n this.svgBgElem.height(this.svgBgElem.width() * OwlGauge.visualBgHeightMultiplier);\r\n this.svgTailElem.height(this.svgTailElem.width() * OwlGauge.owlTailHeightMultiplier);\r\n });\r\n }\r\n }\r\n}\n\n\n/** WEBPACK FOOTER **\n ** ./src/Clients/Visuals/visuals/owlGauge.ts\n **/","/*\r\n * Power BI Visualizations\r\n *\r\n * Copyright (c) Microsoft Corporation\r\n * All rights reserved. \r\n * MIT License\r\n *\r\n * Permission is hereby granted, free of charge, to any person obtaining a copy\r\n * of this software and associated documentation files (the \"\"Software\"\"), to deal\r\n * in the Software without restriction, including without limitation the rights\r\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\r\n * copies of the Software, and to permit persons to whom the Software is\r\n * furnished to do so, subject to the following conditions:\r\n * \r\n * The above copyright notice and this permission notice shall be included in \r\n * all copies or substantial portions of the Software.\r\n * \r\n * THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR \r\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, \r\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE \r\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER \r\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\r\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\r\n * THE SOFTWARE.\r\n */\r\n\r\n/// <reference path=\"../_references.ts\"/>\r\n\r\nmodule powerbi.visuals {\r\n import IStringResourceProvider = jsCommon.IStringResourceProvider;\r\n\r\n export class NoMapLocationWarning implements IVisualWarning {\r\n public get code(): string {\r\n return 'NoMapLocation';\r\n }\r\n\r\n public getMessages(resourceProvider: IStringResourceProvider): IVisualErrorMessage {\r\n let messageKey: string = 'NoMapLocationMessage';\r\n let titleKey: string = 'NoMapLocationKey';\r\n let detailKey: string = 'NoMapLocationValue';\r\n\r\n let visualMessage: IVisualErrorMessage = {\r\n message: resourceProvider.get(messageKey),\r\n title: resourceProvider.get(titleKey),\r\n detail: resourceProvider.get(detailKey),\r\n };\r\n\r\n return visualMessage;\r\n }\r\n }\r\n\r\n export class FilledMapWithoutValidGeotagCategoryWarning implements IVisualWarning {\r\n public get code(): string {\r\n return 'NoValidGeotaggedCategory';\r\n }\r\n\r\n public getMessages(resourceProvider: IStringResourceProvider): IVisualErrorMessage {\r\n let messageKey: string = 'NoValidGeotaggedCategoryMessage';\r\n let titleKey: string = 'NoValidGeotaggedCategoryKey';\r\n let detailKey: string = 'NoValidGeotaggedCategoryValue';\r\n\r\n let visualMessage: IVisualErrorMessage = {\r\n message: resourceProvider.get(messageKey),\r\n title: resourceProvider.get(titleKey),\r\n detail: resourceProvider.get(detailKey),\r\n };\r\n\r\n return visualMessage;\r\n }\r\n }\r\n\r\n export class GeometryCulledWarning implements IVisualWarning {\r\n public get code(): string {\r\n return 'GeometryCulledWarning';\r\n }\r\n\r\n public getMessages(resourceProvider: IStringResourceProvider): IVisualErrorMessage {\r\n let messageKey: string = 'GeometryCulledWarningMessage';\r\n let titleKey: string = 'GeometryCulledWarningKey';\r\n let detailKey: string = 'GeometryCulledWarningVal';\r\n\r\n let visualMessage: IVisualErrorMessage = {\r\n message: resourceProvider.get(messageKey),\r\n title: resourceProvider.get(titleKey),\r\n detail: resourceProvider.get(detailKey),\r\n };\r\n\r\n return visualMessage;\r\n }\r\n }\r\n\r\n export class NegativeValuesNotSupportedWarning implements IVisualWarning {\r\n public get code(): string {\r\n return 'NegativeValuesNotSupported';\r\n }\r\n\r\n public getMessages(resourceProvider: IStringResourceProvider): IVisualErrorMessage {\r\n const messageKey: string = 'VisualWarning_NegativeValues';\r\n\r\n const visualMessage: IVisualErrorMessage = {\r\n message: resourceProvider.get(messageKey),\r\n title: '',\r\n detail: '',\r\n };\r\n\r\n return visualMessage;\r\n }\r\n }\r\n\r\n export class AllNegativeValuesWarning implements IVisualWarning {\r\n public get code(): string {\r\n return 'AllNegativeValuesNotSupported';\r\n }\r\n\r\n public getMessages(resourceProvider: IStringResourceProvider): IVisualErrorMessage {\r\n let messageKey: string = 'VisualWarning_AllNegativeValues';\r\n\r\n let visualMessage: IVisualErrorMessage = {\r\n message: resourceProvider.get(messageKey),\r\n title: '',\r\n detail: '',\r\n };\r\n\r\n return visualMessage;\r\n }\r\n }\r\n\r\n export class NaNNotSupportedWarning implements IVisualWarning {\r\n public get code(): string {\r\n return 'NaNNotSupported';\r\n }\r\n\r\n public getMessages(resourceProvider: IStringResourceProvider): IVisualErrorMessage {\r\n let messageKey: string = 'VisualWarning_NanValues';\r\n\r\n let visualMessage: IVisualErrorMessage = {\r\n message: resourceProvider.get(messageKey),\r\n title: '',\r\n detail: '',\r\n };\r\n\r\n return visualMessage;\r\n }\r\n }\r\n\r\n export class InfinityValuesNotSupportedWarning implements IVisualWarning {\r\n public get code(): string {\r\n return 'InfinityValuesNotSupported';\r\n }\r\n\r\n public getMessages(resourceProvider: IStringResourceProvider): IVisualErrorMessage {\r\n let messageKey: string = 'VisualWarning_InfinityValues';\r\n\r\n let visualMessage: IVisualErrorMessage = {\r\n message: resourceProvider.get(messageKey),\r\n title: '',\r\n detail: '',\r\n };\r\n\r\n return visualMessage;\r\n }\r\n }\r\n\r\n export class ValuesOutOfRangeWarning implements IVisualWarning {\r\n public get code(): string {\r\n return 'ValuesOutOfRange';\r\n }\r\n\r\n public getMessages(resourceProvider: IStringResourceProvider): IVisualErrorMessage {\r\n let messageKey: string = 'VisualWarning_VisualizationOutOfRange';\r\n\r\n let visualMessage: IVisualErrorMessage = {\r\n message: resourceProvider.get(messageKey),\r\n title: '',\r\n detail: '',\r\n };\r\n\r\n return visualMessage;\r\n }\r\n }\r\n\r\n export class ZeroValueWarning implements IVisualWarning {\r\n public get code(): string {\r\n return \"ZeroValuesNotSupported\";\r\n }\r\n\r\n public getMessages(resourceProvider: IStringResourceProvider): IVisualErrorMessage {\r\n let messageKey: string = 'VisualWarning_ZeroValues'; \r\n\r\n let visualMessage: IVisualErrorMessage = {\r\n message: resourceProvider.get(messageKey),\r\n title: '',\r\n detail: '',\r\n };\r\n\r\n return visualMessage;\r\n }\r\n }\r\n\r\n export class VisualKPIDataMissingWarning implements IVisualWarning {\r\n public get code(): string {\r\n return \"VisualKPIDataMissing\";\r\n }\r\n\r\n public getMessages(resourceProvider: IStringResourceProvider): IVisualErrorMessage {\r\n let messageKey: string = 'Visual_KPI_DataMissing';\r\n\r\n let visualMessage: IVisualErrorMessage = {\r\n message: resourceProvider.get(messageKey),\r\n title: '',\r\n detail: '',\r\n };\r\n\r\n return visualMessage;\r\n }\r\n }\r\n\r\n export class ScriptVisualRefreshWarning implements IVisualWarning {\r\n public get code(): string {\r\n return \"ScriptVisualNotRefreshed\";\r\n }\r\n\r\n public getMessages(resourceProvider: IStringResourceProvider): IVisualErrorMessage {\r\n let messageKey: string = 'ScriptVisualRefreshWarningMessage';\r\n let detailKey: string = 'ScriptVisualRefreshWarningValue';\r\n\r\n let visualMessage: IVisualErrorMessage = {\r\n message: resourceProvider.get(messageKey),\r\n title: resourceProvider.get(messageKey),\r\n detail: resourceProvider.get(detailKey),\r\n };\r\n\r\n return visualMessage;\r\n }\r\n }\r\n\r\n}\n\n\n/** WEBPACK FOOTER **\n ** ./src/Clients/Visuals/warnings/visualWarnings.ts\n **/","/*\r\n * Power BI Visualizations\r\n *\r\n * Copyright (c) Microsoft Corporation\r\n * All rights reserved. \r\n * MIT License\r\n *\r\n * Permission is hereby granted, free of charge, to any person obtaining a copy\r\n * of this software and associated documentation files (the \"\"Software\"\"), to deal\r\n * in the Software without restriction, including without limitation the rights\r\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\r\n * copies of the Software, and to permit persons to whom the Software is\r\n * furnished to do so, subject to the following conditions:\r\n * \r\n * The above copyright notice and this permission notice shall be included in\r\n * all copies or substantial portions of the Software.\r\n * \r\n * THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR \r\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, \r\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE \r\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER \r\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\r\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\r\n * THE SOFTWARE.\r\n */\r\n\r\n/// <reference path=\"../_references.ts\"/>\r\n\r\nmodule powerbi.visuals {\r\n import ClassAndSelector = jsCommon.CssConstants.ClassAndSelector;\r\n import PixelConverter = jsCommon.PixelConverter;\r\n import createClassAndSelector = jsCommon.CssConstants.createClassAndSelector;\r\n\r\n export interface WaterfallChartData extends CartesianData {\r\n series: WaterfallChartSeries[];\r\n categories: any[];\r\n valuesMetadata: DataViewMetadataColumn;\r\n legend: LegendData;\r\n hasHighlights: boolean;\r\n categoryMetadata: DataViewMetadataColumn;\r\n positionMax: number;\r\n positionMin: number;\r\n sentimentColors: WaterfallChartSentimentColors;\r\n dataLabelsSettings: VisualDataLabelsSettings;\r\n axesLabels: ChartAxesLabels;\r\n }\r\n\r\n export interface WaterfallChartSeries extends CartesianSeries {\r\n data: WaterfallChartDataPoint[];\r\n }\r\n\r\n export interface WaterfallChartDataPoint extends CartesianDataPoint, SelectableDataPoint, TooltipEnabledDataPoint, LabelEnabledDataPoint {\r\n position: number;\r\n color: string;\r\n highlight: boolean;\r\n key: string;\r\n isTotal?: boolean;\r\n }\r\n\r\n export interface WaterfallChartConstructorOptions extends CartesianVisualConstructorOptions {\r\n }\r\n\r\n export interface WaterfallChartSentimentColors {\r\n increaseFill: Fill;\r\n decreaseFill: Fill;\r\n totalFill: Fill;\r\n }\r\n\r\n export interface WaterfallLayout extends CategoryLayout, ILabelLayout {\r\n categoryWidth: number;\r\n }\r\n\r\n export class WaterfallChart implements ICartesianVisual {\r\n public static formatStringProp: DataViewObjectPropertyIdentifier = { objectName: 'general', propertyName: 'formatString' };\r\n private static WaterfallClassName = 'waterfallChart';\r\n private static MainGraphicsContextClassName = 'mainGraphicsContext';\r\n private static IncreaseLabel = \"Waterfall_IncreaseLabel\";\r\n private static DecreaseLabel = \"Waterfall_DecreaseLabel\";\r\n private static TotalLabel = \"Waterfall_TotalLabel\";\r\n private static CategoryValueClasses: ClassAndSelector = createClassAndSelector('column');\r\n private static WaterfallConnectorClasses: ClassAndSelector = createClassAndSelector('waterfall-connector');\r\n\r\n private static defaultTotalColor = \"#00b8aa\";\r\n private static validLabelPositions = [RectLabelPosition.OutsideEnd, RectLabelPosition.InsideEnd];\r\n private static validZeroLabelPosition = [RectLabelPosition.OutsideEnd, RectLabelPosition.OutsideBase];\r\n\r\n private svg: D3.Selection;\r\n private mainGraphicsContext: D3.Selection;\r\n private labelGraphicsContext: D3.Selection;\r\n private mainGraphicsSVG: D3.Selection;\r\n private xAxisProperties: IAxisProperties;\r\n private yAxisProperties: IAxisProperties;\r\n private currentViewport: IViewport;\r\n private margin: IMargin;\r\n private data: WaterfallChartData;\r\n private element: JQuery;\r\n private isScrollable: boolean;\r\n private tooltipsEnabled: boolean;\r\n private tooltipBucketEnabled: boolean;\r\n\r\n /**\r\n * Note: If we overflowed horizontally then this holds the subset of data we should render.\r\n */\r\n private clippedData: WaterfallChartData;\r\n\r\n private style: IVisualStyle;\r\n private colors: IDataColorPalette;\r\n private hostServices: IVisualHostServices;\r\n private cartesianVisualHost: ICartesianVisualHost;\r\n private interactivity: InteractivityOptions;\r\n private options: CartesianVisualInitOptions;\r\n private interactivityService: IInteractivityService;\r\n private layout: WaterfallLayout;\r\n\r\n constructor(options: WaterfallChartConstructorOptions) {\r\n this.isScrollable = options.isScrollable;\r\n this.tooltipsEnabled = options.tooltipsEnabled;\r\n this.tooltipBucketEnabled = options.tooltipBucketEnabled;\r\n this.interactivityService = options.interactivityService;\r\n }\r\n\r\n public init(options: CartesianVisualInitOptions): void {\r\n debug.assertValue(options, 'options');\r\n\r\n this.svg = options.svg;\r\n this.svg.classed(WaterfallChart.WaterfallClassName, true);\r\n this.style = options.style;\r\n this.currentViewport = options.viewport;\r\n this.hostServices = options.host;\r\n this.interactivity = options.interactivity;\r\n this.cartesianVisualHost = options.cartesianHost;\r\n this.options = options;\r\n this.element = options.element;\r\n this.colors = this.style.colorPalette.dataColors;\r\n this.mainGraphicsSVG = this.svg.append('svg');\r\n this.mainGraphicsContext = this.mainGraphicsSVG.append('g')\r\n .classed(WaterfallChart.MainGraphicsContextClassName, true);\r\n this.labelGraphicsContext = this.mainGraphicsSVG.append('g')\r\n .classed(NewDataLabelUtils.labelGraphicsContextClass.class, true);\r\n }\r\n\r\n public static converter(\r\n dataView: DataView,\r\n palette: IDataColorPalette,\r\n hostServices: IVisualHostServices,\r\n dataLabelSettings: VisualDataLabelsSettings,\r\n sentimentColors: WaterfallChartSentimentColors,\r\n interactivityService: IInteractivityService,\r\n tooltipsEnabled: boolean = true,\r\n tooltipBucketEnabled?: boolean): WaterfallChartData {\r\n debug.assertValue(palette, 'palette');\r\n\r\n let reader = data.createIDataViewCategoricalReader(dataView);\r\n\r\n let formatStringProp = WaterfallChart.formatStringProp;\r\n let categories = dataView.categorical.categories || [];\r\n\r\n let increaseColor = sentimentColors.increaseFill.solid.color;\r\n let decreaseColor = sentimentColors.decreaseFill.solid.color;\r\n let totalColor = sentimentColors.totalFill.solid.color;\r\n\r\n let totalLabel = hostServices.getLocalizedString(WaterfallChart.TotalLabel);\r\n let increaseLabel = hostServices.getLocalizedString(WaterfallChart.IncreaseLabel);\r\n let decreaseLabel = hostServices.getLocalizedString(WaterfallChart.DecreaseLabel);\r\n\r\n let legend: LegendDataPoint[] = [\r\n {\r\n label: increaseLabel,\r\n color: increaseColor,\r\n icon: LegendIcon.Box,\r\n identity: SelectionIdBuilder.builder().withMeasure('increase').createSelectionId(),\r\n selected: false,\r\n }, {\r\n label: decreaseLabel,\r\n color: decreaseColor,\r\n icon: LegendIcon.Box,\r\n identity: SelectionIdBuilder.builder().withMeasure('decrease').createSelectionId(),\r\n selected: false,\r\n }, {\r\n label: totalLabel,\r\n color: totalColor,\r\n icon: LegendIcon.Box,\r\n identity: SelectionIdBuilder.builder().withMeasure('total').createSelectionId(),\r\n selected: false,\r\n }];\r\n\r\n /**\r\n * The position represents the starting point for each bar,\r\n * for any value it is the sum of all previous values.\r\n * Values > 0 are considered gains, values < 0 are losses.\r\n */\r\n let pos = 0, posMin = 0, posMax = 0;\r\n let dataPoints: WaterfallChartDataPoint[] = [];\r\n let categoryValues: any[] = [];\r\n let categoryMetadata: DataViewMetadataColumn;\r\n let valuesMetadata: DataViewMetadataColumn = undefined;\r\n\r\n let totalTooltips: number[];\r\n let tooltipsCount: number;\r\n let tooltipMetadataColumns: DataViewMetadataColumn[];\r\n if (reader.hasValues(\"Tooltips\")) {\r\n tooltipMetadataColumns = reader.getAllValueMetadataColumnsForRole(\"Tooltips\", undefined);\r\n }\r\n\r\n if (reader.hasValues(\"Y\")) {\r\n valuesMetadata = reader.getValueMetadataColumn(\"Y\");\r\n let labelFormatString = valuesMetadata.format;\r\n if (_.isEmpty(categories)) {\r\n // We have values but no category, just show the total bar.\r\n pos = posMax = reader.getValue(\"Y\", 0);\r\n posMin = 0;\r\n }\r\n else {\r\n let categoryColumn = categories[0];\r\n categoryMetadata = categoryColumn.source;\r\n categoryValues = categoryColumn.values.slice();\r\n categoryValues.push(totalLabel);\r\n\r\n if (reader.hasValues(\"Tooltips\")) {\r\n tooltipsCount = reader.getSeriesCount(\"Tooltips\");\r\n totalTooltips = _.map(new Array(tooltipsCount), () => 0);\r\n }\r\n for (var categoryIndex = 0, catLen = reader.getCategoryCount(); categoryIndex < catLen; categoryIndex++) {\r\n let category = categoryValues[categoryIndex];\r\n let value = reader.getValue(\"Y\", categoryIndex) || 0;\r\n let identity = SelectionIdBuilder.builder()\r\n .withCategory(categoryColumn, categoryIndex)\r\n .withMeasure(valuesMetadata.queryName)\r\n .createSelectionId();\r\n\r\n let tooltipInfo: TooltipDataItem[];\r\n\r\n if (tooltipsEnabled) {\r\n tooltipInfo = [];\r\n\r\n tooltipInfo.push({\r\n displayName: categoryMetadata.displayName,\r\n value: converterHelper.formatFromMetadataColumn(category, categoryMetadata, formatStringProp),\r\n });\r\n\r\n if (value != null) {\r\n tooltipInfo.push({\r\n displayName: valuesMetadata.displayName,\r\n value: converterHelper.formatFromMetadataColumn(value, valuesMetadata, formatStringProp),\r\n });\r\n }\r\n if (tooltipBucketEnabled) {\r\n let tooltipValues = reader.getAllValuesForRole(\"Tooltips\", categoryIndex);\r\n if (tooltipValues && tooltipMetadataColumns) {\r\n for (let i = 0; i < tooltipValues.length; i++) {\r\n totalTooltips[i] += tooltipValues[i];\r\n }\r\n for (let j = 0; j < tooltipValues.length; j++) {\r\n if (tooltipValues[j] != null && tooltipMetadataColumns[j]) {\r\n tooltipInfo.push({\r\n displayName: tooltipMetadataColumns[j].displayName,\r\n value: converterHelper.formatFromMetadataColumn(tooltipValues[j], tooltipMetadataColumns[j], formatStringProp),\r\n });\r\n }\r\n }\r\n }\r\n }\r\n }\r\n let color = value > 0 ? increaseColor : decreaseColor;\r\n\r\n dataPoints.push({\r\n value: value,\r\n position: pos,\r\n color: color,\r\n categoryValue: category,\r\n categoryIndex: categoryIndex,\r\n seriesIndex: 0,\r\n selected: false,\r\n identity: identity,\r\n highlight: false,\r\n key: identity.getKey(),\r\n tooltipInfo: tooltipInfo,\r\n labelFill: dataLabelSettings.labelColor,\r\n labelFormatString: labelFormatString,\r\n });\r\n\r\n pos += value;\r\n if (pos > posMax)\r\n posMax = pos;\r\n if (pos < posMin)\r\n posMin = pos;\r\n }\r\n }\r\n\r\n let tooltipInfo: TooltipDataItem[];\r\n\r\n if (tooltipsEnabled) {\r\n tooltipInfo = [];\r\n if (categoryMetadata) {\r\n tooltipInfo.push({\r\n displayName: categoryMetadata.displayName,\r\n value: totalLabel,\r\n });\r\n }\r\n if (pos != null) {\r\n tooltipInfo.push({\r\n displayName: valuesMetadata.displayName,\r\n value: converterHelper.formatFromMetadataColumn(pos, valuesMetadata, formatStringProp),\r\n });\r\n }\r\n\r\n if (tooltipBucketEnabled) {\r\n let tooltipValues = reader.getAllValuesForRole(\"Tooltips\", 0, undefined);\r\n totalTooltips = totalTooltips ? totalTooltips : tooltipValues;\r\n if (tooltipValues && tooltipMetadataColumns) {\r\n for (let j = 0; j < totalTooltips.length; j++) {\r\n if (totalTooltips[j] != null) {\r\n tooltipInfo.push({\r\n displayName: tooltipMetadataColumns[j].displayName,\r\n value: converterHelper.formatFromMetadataColumn(totalTooltips[j], tooltipMetadataColumns[j], formatStringProp),\r\n });\r\n }\r\n }\r\n }\r\n }\r\n }\r\n let totalIdentity = SelectionId.createNull();\r\n dataPoints.push({\r\n value: pos,\r\n position: 0,\r\n color: totalColor,\r\n categoryValue: totalLabel,\r\n categoryIndex: categoryIndex,\r\n identity: totalIdentity,\r\n seriesIndex: 0,\r\n selected: false,\r\n highlight: false,\r\n key: totalIdentity.getKey(),\r\n tooltipInfo: tooltipInfo,\r\n labelFill: dataLabelSettings.labelColor,\r\n labelFormatString: labelFormatString,\r\n isTotal: true,\r\n });\r\n }\r\n\r\n if (interactivityService) {\r\n interactivityService.applySelectionStateToData(dataPoints);\r\n }\r\n\r\n let xAxisProperties = CartesianHelper.getCategoryAxisProperties(dataView.metadata);\r\n let yAxisProperties = CartesianHelper.getValueAxisProperties(dataView.metadata);\r\n let axesLabels = converterHelper.createAxesLabels(xAxisProperties, yAxisProperties, categoryMetadata, [valuesMetadata]);\r\n\r\n return {\r\n series: [{ data: dataPoints }],\r\n categories: categoryValues,\r\n categoryMetadata: categoryMetadata,\r\n valuesMetadata: valuesMetadata,\r\n legend: { dataPoints: legend },\r\n hasHighlights: false,\r\n positionMin: posMin,\r\n positionMax: posMax,\r\n dataLabelsSettings: dataLabelSettings,\r\n sentimentColors: sentimentColors,\r\n axesLabels: { x: axesLabels.xAxisLabel, y: axesLabels.yAxisLabel },\r\n };\r\n }\r\n\r\n public setData(dataViews: DataView[]): void {\r\n debug.assertValue(dataViews, \"dataViews\");\r\n\r\n let sentimentColors = this.getSentimentColorsFromObjects(null);\r\n let dataView = dataViews.length > 0 ? dataViews[0] : undefined;\r\n\r\n this.data = <WaterfallChartData> {\r\n series: [{ data: [] }],\r\n categories: [],\r\n valuesMetadata: null,\r\n legend: { dataPoints: [], },\r\n hasHighlights: false,\r\n categoryMetadata: null,\r\n scalarCategoryAxis: false,\r\n positionMax: 0,\r\n positionMin: 0,\r\n dataLabelsSettings: dataLabelUtils.getDefaultLabelSettings(/* show */ false, /* labelColor */ undefined),\r\n sentimentColors: sentimentColors,\r\n axesLabels: { x: null, y: null },\r\n };\r\n\r\n if (dataView) {\r\n if (dataView.metadata && dataView.metadata.objects) {\r\n let objects = dataView.metadata.objects;\r\n\r\n let labelsObj = <DataLabelObject>objects['labels'];\r\n if (labelsObj) {\r\n dataLabelUtils.updateLabelSettingsFromLabelsObject(labelsObj, this.data.dataLabelsSettings);\r\n }\r\n sentimentColors = this.getSentimentColorsFromObjects(objects);\r\n }\r\n\r\n if (dataView.categorical) {\r\n this.data = WaterfallChart.converter(dataView, this.colors, this.hostServices, this.data.dataLabelsSettings, sentimentColors, this.interactivityService, this.tooltipsEnabled, this.tooltipBucketEnabled); \r\n }\r\n }\r\n }\r\n\r\n public enumerateObjectInstances(enumeration: ObjectEnumerationBuilder, options: EnumerateVisualObjectInstancesOptions): void {\r\n switch (options.objectName) {\r\n case 'sentimentColors':\r\n this.enumerateSentimentColors(enumeration);\r\n break;\r\n case 'labels':\r\n let labelSettingOptions: VisualDataLabelsSettingsOptions = {\r\n enumeration: enumeration,\r\n dataLabelsSettings: this.data.dataLabelsSettings,\r\n show: true,\r\n displayUnits: true,\r\n precision: true,\r\n fontSize: true,\r\n };\r\n dataLabelUtils.enumerateDataLabels(labelSettingOptions);\r\n break;\r\n }\r\n }\r\n\r\n private enumerateSentimentColors(enumeration: ObjectEnumerationBuilder): void {\r\n let sentimentColors = this.data.sentimentColors;\r\n\r\n enumeration.pushInstance({\r\n selector: null,\r\n properties: {\r\n increaseFill: sentimentColors.increaseFill,\r\n decreaseFill: sentimentColors.decreaseFill,\r\n totalFill: sentimentColors.totalFill\r\n },\r\n objectName: 'sentimentColors'\r\n });\r\n }\r\n\r\n public calculateLegend(): LegendData {\r\n // TODO: support interactive legend\r\n return this.data.legend;\r\n }\r\n\r\n public hasLegend(): boolean {\r\n // Waterfall legend is more like a color-key, so just return true\r\n return true;\r\n }\r\n\r\n private static createClippedDataIfOverflowed(data: WaterfallChartData, renderableDataCount: number): WaterfallChartData {\r\n let clipped: WaterfallChartData = data;\r\n let dataPoints: WaterfallChartDataPoint[] = data.series[0].data;\r\n\r\n if (data && renderableDataCount < dataPoints.length) {\r\n clipped = Prototype.inherit(data);\r\n clipped.series = [{ data: dataPoints.slice(0, renderableDataCount) }];\r\n clipped.categories = data.categories.slice(0, renderableDataCount);\r\n }\r\n\r\n return clipped;\r\n }\r\n\r\n public calculateAxesProperties(options: CalculateScaleAndDomainOptions): IAxisProperties[] {\r\n debug.assertValue(options, 'options');\r\n\r\n this.currentViewport = options.viewport;\r\n this.margin = options.margin;\r\n let data = this.clippedData = this.data;\r\n let categoryCount = data.categories.length;\r\n let preferredPlotArea = this.getPreferredPlotArea(false, categoryCount, CartesianChart.MinOrdinalRectThickness);\r\n\r\n let cartesianLayout = CartesianChart.getLayout(\r\n null,\r\n {\r\n availableWidth: preferredPlotArea.width,\r\n categoryCount: categoryCount,\r\n domain: null,\r\n isScalar: false,\r\n isScrollable: this.isScrollable,\r\n trimOrdinalDataOnOverflow: options.trimOrdinalDataOnOverflow\r\n });\r\n\r\n // In the case that we have overflowed horizontally we want to clip the data and use that to calculate the axes on the dashboard. \r\n if (!this.isScrollable) {\r\n data = this.clippedData = WaterfallChart.createClippedDataIfOverflowed(data, cartesianLayout.categoryCount);\r\n }\r\n\r\n let xAxisCreationOptions = WaterfallChart.getXAxisCreationOptions(data, preferredPlotArea.width, cartesianLayout, options);\r\n let yAxisCreationOptions = WaterfallChart.getYAxisCreationOptions(data, preferredPlotArea.height, options);\r\n\r\n let xAxisProperties = this.xAxisProperties = AxisHelper.createAxis(xAxisCreationOptions);\r\n let yAxisProperties = this.yAxisProperties = AxisHelper.createAxis(yAxisCreationOptions);\r\n\r\n let categoryWidth = this.xAxisProperties.categoryThickness * (1 - CartesianChart.InnerPaddingRatio);\r\n\r\n let formattersCache = dataLabelUtils.createColumnFormatterCacheManager();\r\n let labelSettings = data.dataLabelsSettings;\r\n let value2: number = WaterfallChart.getDisplayUnitValueFromAxisFormatter(yAxisProperties, labelSettings);\r\n\r\n this.layout = {\r\n categoryCount: cartesianLayout.categoryCount,\r\n categoryThickness: cartesianLayout.categoryThickness,\r\n isScalar: cartesianLayout.isScalar,\r\n outerPaddingRatio: cartesianLayout.outerPaddingRatio,\r\n categoryWidth: categoryWidth,\r\n labelText: (d: WaterfallChartDataPoint) => {\r\n //total value has no identity\r\n let formatter = formattersCache.getOrCreate(d.labelFormatString, labelSettings, value2);\r\n return dataLabelUtils.getLabelFormattedText({ label: formatter.format(d.value) });\r\n },\r\n labelLayout: dataLabelUtils.getLabelLayoutXYForWaterfall(xAxisProperties, categoryWidth, yAxisProperties, yAxisCreationOptions.dataDomain),\r\n filter: (d: WaterfallChartDataPoint) => {\r\n return dataLabelUtils.doesDataLabelFitInShape(d, yAxisProperties, this.layout);\r\n },\r\n style: {\r\n 'fill': (d: WaterfallChartDataPoint) => {\r\n if (d.isLabelInside)\r\n return dataLabelUtils.defaultInsideLabelColor;\r\n return d.labelFill;\r\n },\r\n },\r\n };\r\n\r\n this.xAxisProperties.axisLabel = options.showCategoryAxisLabel ? data.axesLabels.x : null;\r\n this.yAxisProperties.axisLabel = options.showValueAxisLabel ? data.axesLabels.y : null;\r\n\r\n return [xAxisProperties, yAxisProperties];\r\n }\r\n\r\n private static getDisplayUnitValueFromAxisFormatter(yAxisProperties: IAxisProperties, labelSettings: VisualDataLabelsSettings): number {\r\n return (yAxisProperties.formatter && yAxisProperties.formatter.displayUnit && labelSettings.displayUnits === 0) ? yAxisProperties.formatter.displayUnit.value : null;\r\n }\r\n\r\n private static lookupXValue(data: WaterfallChartData, index: number, type: ValueType): any {\r\n let dataPoints: WaterfallChartDataPoint[] = data.series[0].data;\r\n\r\n if (index === dataPoints.length - 1)\r\n // Total\r\n return dataPoints[index].categoryValue;\r\n else\r\n return CartesianHelper.lookupXValue(data, index, type, false);\r\n }\r\n\r\n public static getXAxisCreationOptions(data: WaterfallChartData, width: number, layout: CategoryLayout, options: CalculateScaleAndDomainOptions): CreateAxisOptions {\r\n debug.assertValue(data, 'data');\r\n debug.assertValue(options, 'options');\r\n\r\n let categoryDataType: ValueType = AxisHelper.getCategoryValueType(data.categoryMetadata);\r\n\r\n let domain = AxisHelper.createDomain(data.series, categoryDataType, /* isScalar */ false, options.forcedXDomain, options.ensureXDomain);\r\n\r\n let categoryThickness = layout.categoryThickness;\r\n let outerPadding = categoryThickness * layout.outerPaddingRatio;\r\n\r\n return <CreateAxisOptions> {\r\n pixelSpan: width,\r\n dataDomain: domain,\r\n metaDataColumn: data.categoryMetadata,\r\n formatString: valueFormatter.getFormatString(data.categoryMetadata, WaterfallChart.formatStringProp),\r\n isScalar: false,\r\n outerPadding: outerPadding,\r\n categoryThickness: categoryThickness,\r\n getValueFn: (index, type) => WaterfallChart.lookupXValue(data, index, type),\r\n forcedTickCount: options.forcedTickCount,\r\n isCategoryAxis: true,\r\n axisDisplayUnits: options.categoryAxisDisplayUnits,\r\n axisPrecision: options.categoryAxisPrecision\r\n };\r\n }\r\n\r\n public static getYAxisCreationOptions(data: WaterfallChartData, height: number, options: CalculateScaleAndDomainOptions): CreateAxisOptions {\r\n debug.assertValue(data, 'data');\r\n debug.assertValue(options, 'options');\r\n\r\n let combinedDomain = AxisHelper.combineDomain(options.forcedYDomain, [data.positionMin, data.positionMax], options.ensureYDomain);\r\n\r\n return <CreateAxisOptions> {\r\n pixelSpan: height,\r\n dataDomain: combinedDomain,\r\n isScalar: true,\r\n isVertical: true,\r\n metaDataColumn: data.valuesMetadata,\r\n formatString: valueFormatter.getFormatString(data.valuesMetadata, WaterfallChart.formatStringProp),\r\n outerPadding: 0,\r\n forcedTickCount: options.forcedTickCount,\r\n useTickIntervalForDisplayUnits: true,\r\n isCategoryAxis: false,\r\n axisDisplayUnits: options.valueAxisDisplayUnits,\r\n axisPrecision: options.valueAxisPrecision\r\n };\r\n }\r\n\r\n public getPreferredPlotArea(isScalar: boolean, categoryCount: number, categoryThickness: number): IViewport {\r\n return CartesianChart.getPreferredPlotArea(\r\n categoryCount,\r\n categoryThickness,\r\n this.currentViewport,\r\n this.isScrollable,\r\n isScalar,\r\n this.margin);\r\n }\r\n\r\n public getVisualCategoryAxisIsScalar(): boolean {\r\n return false;\r\n }\r\n\r\n public overrideXScale(xProperties: IAxisProperties): void {\r\n this.xAxisProperties = xProperties;\r\n }\r\n\r\n public setFilteredData(startIndex: number, endIndex: number): any {\r\n let data = this.clippedData = Prototype.inherit(this.data);\r\n\r\n data.series = [{ data: data.series[0].data.slice(startIndex, endIndex) }];\r\n data.categories = data.categories.slice(startIndex, endIndex);\r\n\r\n return data;\r\n }\r\n\r\n private createRects(data: WaterfallChartDataPoint[]): D3.UpdateSelection {\r\n let mainGraphicsContext = this.mainGraphicsContext;\r\n let colsSelection = mainGraphicsContext.selectAll(WaterfallChart.CategoryValueClasses.selector);\r\n let cols = colsSelection.data(data, (d: WaterfallChartDataPoint) => d.key);\r\n\r\n cols\r\n .enter()\r\n .append('rect')\r\n .attr('class', (d: WaterfallChartDataPoint) => WaterfallChart.CategoryValueClasses.class.concat(d.highlight ? 'highlight' : ''));\r\n\r\n cols.exit().remove();\r\n\r\n return cols;\r\n }\r\n\r\n private createConnectors(data: WaterfallChartDataPoint[]): D3.UpdateSelection {\r\n let mainGraphicsContext = this.mainGraphicsContext;\r\n let connectorSelection = mainGraphicsContext.selectAll(WaterfallChart.WaterfallConnectorClasses.selector);\r\n\r\n let connectors = connectorSelection.data(data.slice(0, data.length - 1), (d: WaterfallChartDataPoint) => d.key);\r\n\r\n connectors\r\n .enter()\r\n .append('line')\r\n .classed(WaterfallChart.WaterfallConnectorClasses.class, true);\r\n\r\n connectors.exit().remove();\r\n\r\n return connectors;\r\n }\r\n\r\n public render(suppressAnimations: boolean): CartesianVisualRenderResult {\r\n let dataPoints = this.clippedData.series[0].data;\r\n let bars = this.createRects(dataPoints);\r\n let connectors = this.createConnectors(dataPoints);\r\n\r\n if (this.tooltipsEnabled)\r\n TooltipManager.addTooltip(bars, (tooltipEvent: TooltipEvent) => tooltipEvent.data.tooltipInfo);\r\n\r\n let hasSelection = this.interactivityService && this.interactivityService.hasSelection();\r\n\r\n let xScale = this.xAxisProperties.scale;\r\n let yScale = this.yAxisProperties.scale;\r\n let y0 = yScale(0);\r\n\r\n this.mainGraphicsSVG.attr('height', this.getAvailableHeight())\r\n .attr('width', this.getAvailableWidth());\r\n\r\n /**\r\n * The y-value is always at the top of the rect. If the data value is negative then we can\r\n * use the scaled position directly since we are drawing down. If the data value is positive\r\n * we have to calculate the top of the rect and use that as the y-value. Since the y-value \r\n * is always the top of the rect, height should always be positive.\r\n */\r\n bars\r\n .style('fill', (d: WaterfallChartDataPoint) => d.color)\r\n .style('fill-opacity', (d: WaterfallChartDataPoint) => d.isTotal ? ColumnUtil.DefaultOpacity : ColumnUtil.getFillOpacity(d.selected, d.highlight, hasSelection, this.data.hasHighlights))\r\n .attr('width', this.layout.categoryWidth)\r\n .attr('x', (d: WaterfallChartDataPoint) => xScale(d.categoryIndex))\r\n .attr('y', (d: WaterfallChartDataPoint) => WaterfallChart.getRectTop(yScale, d.position, d.value))\r\n .attr('height', (d: WaterfallChartDataPoint) => y0 - yScale(Math.abs(d.value)));\r\n\r\n connectors\r\n .attr({\r\n 'x1': (d: WaterfallChartDataPoint) => xScale(d.categoryIndex),\r\n 'y1': (d: WaterfallChartDataPoint) => yScale(d.position + d.value),\r\n 'x2': (d: WaterfallChartDataPoint) => xScale(d.categoryIndex + 1) + this.layout.categoryWidth,\r\n 'y2': (d: WaterfallChartDataPoint) => yScale(d.position + d.value),\r\n });\r\n\r\n let labelSettings = this.data.dataLabelsSettings;\r\n let labelDataPoints: LabelDataPoint[] = [];\r\n if (labelSettings && labelSettings.show || labelSettings.showCategory) {\r\n labelDataPoints = this.createLabelDataPoints();\r\n }\r\n\r\n let behaviorOptions: WaterfallChartBehaviorOptions = undefined;\r\n if (this.interactivityService) {\r\n behaviorOptions = {\r\n bars: bars\r\n };\r\n }\r\n\r\n // This should always be the last line in the render code.\r\n SVGUtil.flushAllD3TransitionsIfNeeded(this.options);\r\n\r\n return { dataPoints: dataPoints, behaviorOptions: behaviorOptions, labelDataPoints: labelDataPoints, labelsAreNumeric: true };\r\n }\r\n\r\n public onClearSelection(): void {\r\n if (this.interactivityService)\r\n this.interactivityService.clearSelection();\r\n }\r\n\r\n public getSupportedCategoryAxisType(): string {\r\n return axisType.categorical;\r\n }\r\n\r\n public static getRectTop(scale: D3.Scale.GenericScale<any>, pos: number, value: number): number {\r\n if (value < 0)\r\n return scale(pos);\r\n else\r\n return scale(pos) - (scale(0) - scale(value));\r\n }\r\n\r\n private getAvailableWidth(): number {\r\n return this.currentViewport.width - (this.margin.left + this.margin.right);\r\n }\r\n\r\n private getAvailableHeight(): number {\r\n return this.currentViewport.height - (this.margin.top + this.margin.bottom);\r\n }\r\n\r\n private getSentimentColorsFromObjects(objects: DataViewObjects): WaterfallChartSentimentColors {\r\n let defaultSentimentColors = this.colors.getSentimentColors();\r\n let increaseColor = DataViewObjects.getFillColor(objects, waterfallChartProps.sentimentColors.increaseFill, defaultSentimentColors[2].value);\r\n let decreaseColor = DataViewObjects.getFillColor(objects, waterfallChartProps.sentimentColors.decreaseFill, defaultSentimentColors[0].value);\r\n let totalColor = DataViewObjects.getFillColor(objects, waterfallChartProps.sentimentColors.totalFill, WaterfallChart.defaultTotalColor);\r\n\r\n return <WaterfallChartSentimentColors> {\r\n increaseFill: { solid: { color: increaseColor } },\r\n decreaseFill: { solid: { color: decreaseColor } },\r\n totalFill: { solid: { color: totalColor } }\r\n };\r\n }\r\n\r\n // Public for testing\r\n public createLabelDataPoints(): LabelDataPoint[]{\r\n let labelDataPoints: LabelDataPoint[] = [];\r\n\r\n let data = this.data;\r\n let xScale = this.xAxisProperties.scale;\r\n let yScale = this.yAxisProperties.scale;\r\n let y0 = yScale(0);\r\n let series = data.series;\r\n let formattersCache = NewDataLabelUtils.createColumnFormatterCacheManager();\r\n let axisFormatter: number = NewDataLabelUtils.getDisplayUnitValueFromAxisFormatter(this.yAxisProperties.formatter, data.dataLabelsSettings);\r\n let labelSettings = this.data.dataLabelsSettings;\r\n\r\n for (let currentSeries of series) {\r\n for (let dataPoint of currentSeries.data) {\r\n // Calculate parent rectangle\r\n let parentRect: IRect = {\r\n left: xScale(dataPoint.categoryIndex),\r\n top: WaterfallChart.getRectTop(yScale, dataPoint.position, dataPoint.value),\r\n width: this.layout.categoryWidth,\r\n height: y0 - yScale(Math.abs(dataPoint.value)),\r\n };\r\n\r\n // Calculate label text\r\n let formatString = dataPoint.labelFormatString;\r\n let formatter = formattersCache.getOrCreate(formatString, this.data.dataLabelsSettings, axisFormatter);\r\n let text = NewDataLabelUtils.getLabelFormattedText(formatter.format(dataPoint.value));\r\n\r\n // Calculate text size\r\n let properties: TextProperties = {\r\n text: text,\r\n fontFamily: NewDataLabelUtils.LabelTextProperties.fontFamily,\r\n fontSize: PixelConverter.fromPoint(labelSettings.fontSize || NewDataLabelUtils.DefaultLabelFontSizeInPt),\r\n fontWeight: NewDataLabelUtils.LabelTextProperties.fontWeight,\r\n };\r\n\r\n let textWidth = TextMeasurementService.measureSvgTextWidth(properties);\r\n let textHeight = TextMeasurementService.estimateSvgTextHeight(properties, true /* tightFitForNumeric */);\r\n\r\n labelDataPoints.push({\r\n isPreferred: true,\r\n text: text,\r\n textSize: {\r\n width: textWidth,\r\n height: textHeight,\r\n },\r\n outsideFill: labelSettings.labelColor ? labelSettings.labelColor : NewDataLabelUtils.defaultLabelColor,\r\n insideFill: NewDataLabelUtils.defaultInsideLabelColor,\r\n parentType: LabelDataPointParentType.Rectangle,\r\n parentShape: {\r\n rect: parentRect,\r\n orientation: dataPoint.value >= 0 ? NewRectOrientation.VerticalBottomBased : NewRectOrientation.VerticalTopBased,\r\n validPositions: dataPoint.value === 0 ? WaterfallChart.validZeroLabelPosition : WaterfallChart.validLabelPositions,\r\n },\r\n fontSize: labelSettings.fontSize,\r\n identity: undefined,\r\n });\r\n }\r\n }\r\n\r\n return labelDataPoints;\r\n }\r\n }\r\n}\n\n\n/** WEBPACK FOOTER **\n ** ./src/Clients/Visuals/cartesian/waterfallChart.ts\n **/","/*\r\n * Power BI Visualizations\r\n *\r\n * Copyright (c) Microsoft Corporation\r\n * All rights reserved. \r\n * MIT License\r\n *\r\n * Permission is hereby granted, free of charge, to any person obtaining a copy\r\n * of this software and associated documentation files (the \"\"Software\"\"), to deal\r\n * in the Software without restriction, including without limitation the rights\r\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\r\n * copies of the Software, and to permit persons to whom the Software is\r\n * furnished to do so, subject to the following conditions:\r\n * \r\n * The above copyright notice and this permission notice shall be included in \r\n * all copies or substantial portions of the Software.\r\n * \r\n * THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR \r\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, \r\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE \r\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER \r\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\r\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\r\n * THE SOFTWARE.\r\n */\r\n\r\n/// <reference path=\"./_references.ts\"/>\r\n\r\nmodule powerbi.visuals {\r\n\r\n import TouchUtils = powerbi.visuals.controls.TouchUtils;\r\n import ClassAndSelector = jsCommon.CssConstants.ClassAndSelector;\r\n import createClassAndSelector = jsCommon.CssConstants.createClassAndSelector;\r\n\r\n export interface TooltipDataItem {\r\n displayName: string;\r\n value: string;\r\n color?: string;\r\n header?: string;\r\n opacity?: string;\r\n }\r\n\r\n export interface TooltipOptions {\r\n opacity: number;\r\n animationDuration: number;\r\n offsetX: number;\r\n offsetY: number;\r\n }\r\n\r\n export interface TooltipEnabledDataPoint {\r\n tooltipInfo?: TooltipDataItem[];\r\n }\r\n\r\n export interface TooltipCategoryDataItem {\r\n value?: any;\r\n metadata: DataViewMetadataColumn[];\r\n }\r\n\r\n export interface TooltipSeriesDataItem {\r\n value?: any;\r\n highlightedValue?: any;\r\n metadata: DataViewValueColumn;\r\n }\r\n\r\n export interface TooltipLocalizationOptions {\r\n highlightedValueDisplayName: string;\r\n }\r\n\r\n export interface TooltipEvent {\r\n data: any;\r\n coordinates: number[];\r\n elementCoordinates: number[];\r\n context: HTMLElement;\r\n isTouchEvent: boolean;\r\n }\r\n\r\n const enum ScreenArea {\r\n TopLeft,\r\n TopRight,\r\n BottomRight,\r\n BottomLeft\r\n };\r\n\r\n const ContainerClassName: ClassAndSelector = createClassAndSelector(\"tooltip-container\");\r\n const ContentContainerClassName: ClassAndSelector = createClassAndSelector(\"tooltip-content-container\");\r\n const ArrowClassName: ClassAndSelector = createClassAndSelector(\"arrow\");\r\n const TooltipHeaderClassName: ClassAndSelector = createClassAndSelector(\"tooltip-header\");\r\n const TooltipRowClassName: ClassAndSelector = createClassAndSelector(\"tooltip-row\");\r\n const TooltipColorCellClassName: ClassAndSelector = createClassAndSelector(\"tooltip-color-cell\");\r\n const TooltipTitleCellClassName: ClassAndSelector = createClassAndSelector(\"tooltip-title-cell\");\r\n const TooltipValueCellClassName: ClassAndSelector = createClassAndSelector(\"tooltip-value-cell\");\r\n\r\n export class ToolTipComponent {\r\n\r\n private static DefaultTooltipOptions: TooltipOptions = {\r\n opacity: 1,\r\n animationDuration: 250,\r\n offsetX: 10,\r\n offsetY: 10\r\n };\r\n\r\n private tooltipContainer: D3.Selection;\r\n private isTooltipVisible: boolean = false;\r\n private currentTooltipData: TooltipDataItem[];\r\n\r\n private customScreenWidth: number;\r\n private customScreenHeight: number;\r\n\r\n public static parentContainerSelector: string = \"body\";\r\n public static highlightedValueDisplayNameResorceKey: string = \"Tooltip_HighlightedValueDisplayName\";\r\n public static localizationOptions: TooltipLocalizationOptions;\r\n\r\n constructor(public tooltipOptions?: TooltipOptions) {\r\n if (!tooltipOptions) {\r\n this.tooltipOptions = ToolTipComponent.DefaultTooltipOptions;\r\n }\r\n }\r\n\r\n public isTooltipComponentVisible(): boolean {\r\n return this.isTooltipVisible;\r\n }\r\n\r\n /** Note: For tests only */\r\n public setTestScreenSize(width: number, height: number) {\r\n this.customScreenWidth = width;\r\n this.customScreenHeight = height;\r\n }\r\n\r\n public show(tooltipData: TooltipDataItem[], clickedArea: TouchUtils.Rectangle) {\r\n this.isTooltipVisible = true;\r\n\r\n if (!this.tooltipContainer) {\r\n this.tooltipContainer = this.createTooltipContainer();\r\n }\r\n\r\n this.setTooltipContent(tooltipData);\r\n\r\n this.tooltipContainer\r\n .style(\"visibility\", \"visible\")\r\n .transition()\r\n .duration(0) // Cancel previous transitions\r\n .style(\"opacity\", this.tooltipOptions.opacity);\r\n\r\n this.setPosition(clickedArea);\r\n }\r\n\r\n public move(tooltipData: TooltipDataItem[], clickedArea: TouchUtils.Rectangle) {\r\n if (this.isTooltipVisible) {\r\n if (tooltipData) {\r\n this.setTooltipContent(tooltipData);\r\n }\r\n\r\n this.setPosition(clickedArea);\r\n }\r\n }\r\n\r\n public hide() {\r\n if (this.isTooltipVisible) {\r\n this.isTooltipVisible = false;\r\n this.tooltipContainer\r\n .transition()\r\n .duration(this.tooltipOptions.animationDuration)\r\n .style(\"opacity\", 0)\r\n .each('end', function () { this.style.visibility = \"hidden\"; });\r\n }\r\n }\r\n\r\n private createTooltipContainer(): D3.Selection {\r\n let container: D3.Selection = d3.select(ToolTipComponent.parentContainerSelector)\r\n .append(\"div\")\r\n .attr(\"class\", ContainerClassName.class);\r\n\r\n container.append(\"div\").attr(\"class\", ArrowClassName.class);\r\n container.append(\"div\").attr(\"class\", ContentContainerClassName.class);\r\n\r\n return container;\r\n }\r\n\r\n private setTooltipContent(tooltipData: TooltipDataItem[]): void {\r\n if (_.isEqual(tooltipData, this.currentTooltipData))\r\n return;\r\n this.currentTooltipData = tooltipData;\r\n\r\n let rowsSelector: string = TooltipRowClassName.selector;\r\n let contentContainer = this.tooltipContainer.select(ContentContainerClassName.selector);\r\n\r\n // Clear existing content\r\n contentContainer.selectAll(TooltipHeaderClassName.selector).remove();\r\n contentContainer.selectAll(TooltipRowClassName.selector).remove();\r\n\r\n if (tooltipData.length === 0) return;\r\n\r\n if (tooltipData[0].header) {\r\n contentContainer.append(\"div\").attr(\"class\", TooltipHeaderClassName.class).text(tooltipData[0].header);\r\n }\r\n let tooltipRow: D3.UpdateSelection = contentContainer.selectAll(rowsSelector).data(tooltipData);\r\n let newRow: D3.Selection = tooltipRow.enter().append(\"div\").attr(\"class\", TooltipRowClassName.class);\r\n if (_.any(tooltipData, (tooltipItem) => tooltipItem.color)) {\r\n let newColorCell: D3.Selection = newRow.filter((d) => d.color).append(\"div\").attr(\"class\", TooltipColorCellClassName.class);\r\n\r\n newColorCell\r\n .append('svg')\r\n .attr({\r\n 'width': '100%',\r\n 'height': '15px'\r\n })\r\n .append('circle')\r\n .attr({\r\n 'cx': '5',\r\n 'cy': '8',\r\n 'r': '5'\r\n })\r\n .style({\r\n 'fill': (d: TooltipDataItem) => d.color,\r\n 'fill-opacity': (d: TooltipDataItem) => d.opacity != null ? d.opacity : 1,\r\n });\r\n }\r\n let newTitleCell: D3.Selection = newRow.append(\"div\").attr(\"class\", TooltipTitleCellClassName.class);\r\n let newValueCell: D3.Selection = newRow.append(\"div\").attr(\"class\", TooltipValueCellClassName.class);\r\n\r\n newTitleCell.text(function (d: TooltipDataItem) { return d.displayName; });\r\n newValueCell.text(function (d: TooltipDataItem) { return d.value; });\r\n }\r\n\r\n private getTooltipPosition(clickedArea: TouchUtils.Rectangle, clickedScreenArea: ScreenArea): TouchUtils.Point {\r\n let tooltipContainerBounds: ClientRect = this.tooltipContainer.node().getBoundingClientRect();\r\n let centerPointOffset: number = Math.floor(clickedArea.width / 2);\r\n let offsetX: number = 0;\r\n let offsetY: number = 0;\r\n let centerPoint: TouchUtils.Point = new TouchUtils.Point(clickedArea.x + centerPointOffset, clickedArea.y + centerPointOffset);\r\n let arrowOffset: number = 7;\r\n\r\n if (clickedScreenArea === ScreenArea.TopLeft) {\r\n offsetX += 3 * arrowOffset + centerPointOffset;\r\n offsetY -= 2 * arrowOffset + centerPointOffset;\r\n }\r\n else if (clickedScreenArea === ScreenArea.TopRight) {\r\n offsetX -= (2 * arrowOffset + tooltipContainerBounds.width + centerPointOffset);\r\n offsetY -= 2 * arrowOffset + centerPointOffset;\r\n }\r\n else if (clickedScreenArea === ScreenArea.BottomLeft) {\r\n offsetX += 3 * arrowOffset + centerPointOffset;\r\n offsetY -= (tooltipContainerBounds.height - 2 * arrowOffset + centerPointOffset);\r\n }\r\n else if (clickedScreenArea === ScreenArea.BottomRight) {\r\n offsetX -= (2 * arrowOffset + tooltipContainerBounds.width + centerPointOffset);\r\n offsetY -= (tooltipContainerBounds.height - 2 * arrowOffset + centerPointOffset);\r\n }\r\n\r\n centerPoint.offset(offsetX, offsetY);\r\n\r\n return centerPoint;\r\n }\r\n\r\n private setPosition(clickedArea: TouchUtils.Rectangle): void {\r\n let clickedScreenArea: ScreenArea = this.getClickedScreenArea(clickedArea);\r\n\r\n let tooltipPosition: TouchUtils.Point = this.getTooltipPosition(clickedArea, clickedScreenArea);\r\n this.setTooltipContainerClass(clickedScreenArea);\r\n this.tooltipContainer.style({ \"left\": tooltipPosition.x + \"px\", \"top\": tooltipPosition.y + \"px\" });\r\n\r\n this.setArrowPosition(clickedScreenArea);\r\n }\r\n\r\n private setTooltipContainerClass(clickedScreenArea: ScreenArea): void {\r\n let tooltipContainerClassName: string;\r\n switch (clickedScreenArea) {\r\n case ScreenArea.TopLeft:\r\n case ScreenArea.BottomLeft:\r\n tooltipContainerClassName = 'left';\r\n break;\r\n case ScreenArea.TopRight:\r\n case ScreenArea.BottomRight:\r\n tooltipContainerClassName = 'right';\r\n break;\r\n }\r\n this.tooltipContainer\r\n .attr('class', ContainerClassName.class) // Reset all classes\r\n .classed(tooltipContainerClassName, true);\r\n }\r\n\r\n private setArrowPosition(clickedScreenArea: ScreenArea): void {\r\n let arrow: D3.Selection = this.getArrowElement();\r\n let arrowClassName: string;\r\n\r\n if (clickedScreenArea === ScreenArea.TopLeft) {\r\n arrowClassName = \"top left\";\r\n }\r\n else if (clickedScreenArea === ScreenArea.TopRight) {\r\n arrowClassName = \"top right\";\r\n }\r\n else if (clickedScreenArea === ScreenArea.BottomLeft) {\r\n arrowClassName = \"bottom left\";\r\n }\r\n else {\r\n arrowClassName = \"bottom right\";\r\n }\r\n\r\n arrow\r\n .attr('class', 'arrow') // Reset all classes\r\n .classed(arrowClassName, true);\r\n }\r\n\r\n private getArrowElement(): D3.Selection {\r\n return this.tooltipContainer.select(ArrowClassName.selector);\r\n }\r\n\r\n private getClickedScreenArea(clickedArea: TouchUtils.Rectangle): ScreenArea {\r\n let screenWidth: number = this.customScreenWidth || window.innerWidth;\r\n let screenHeight: number = this.customScreenHeight || window.innerHeight;\r\n let centerPointOffset: number = clickedArea.width / 2;\r\n let centerPoint: TouchUtils.Point = new TouchUtils.Point(clickedArea.x + centerPointOffset, clickedArea.y + centerPointOffset);\r\n let halfWidth: number = screenWidth / 2;\r\n let halfHeight: number = screenHeight / 2;\r\n\r\n if (centerPoint.x < halfWidth && centerPoint.y < halfHeight) {\r\n return ScreenArea.TopLeft;\r\n }\r\n else if (centerPoint.x >= halfWidth && centerPoint.y < halfHeight) {\r\n return ScreenArea.TopRight;\r\n }\r\n else if (centerPoint.x < halfWidth && centerPoint.y >= halfHeight) {\r\n return ScreenArea.BottomLeft;\r\n }\r\n else if (centerPoint.x >= halfWidth && centerPoint.y >= halfHeight) {\r\n return ScreenArea.BottomRight;\r\n }\r\n }\r\n }\r\n\r\n export module TooltipManager {\r\n\r\n export let ShowTooltips: boolean = true;\r\n export let ToolTipInstance: ToolTipComponent = new ToolTipComponent();\r\n let GlobalTooltipEventsAttached: boolean = false;\r\n const tooltipMouseOverDelay: number = 350;\r\n const tooltipMouseOutDelay: number = 500;\r\n const tooltipTouchDelay: number = 350;\r\n let tooltipTimeoutId: number;\r\n const handleTouchDelay: number = 1000;\r\n let handleTouchTimeoutId: number = 0;\r\n let mouseCoordinates: number[];\r\n let tooltipData: TooltipDataItem[];\r\n\r\n export function addTooltip(\r\n selection: D3.Selection,\r\n getTooltipInfoDelegate: (tooltipEvent: TooltipEvent) => TooltipDataItem[],\r\n reloadTooltipDataOnMouseMove?: boolean,\r\n onMouseOutDelegate?: () => void): void {\r\n\r\n if (!ShowTooltips) {\r\n return;\r\n }\r\n\r\n debug.assertValue(selection, \"selection\");\r\n\r\n let rootNode = d3.select(ToolTipComponent.parentContainerSelector).node();\r\n\r\n // Mouse events\r\n selection.on(\"mouseover\", () => {\r\n let target = <HTMLElement>d3.event.target;\r\n let data = d3.select(target).datum();\r\n \r\n // Ignore mouseover while handling touch events\r\n if (handleTouchTimeoutId || !canDisplayTooltip(d3.event))\r\n return;\r\n\r\n mouseCoordinates = getCoordinates(rootNode, true);\r\n let elementCoordinates: number[] = getCoordinates(target, true);\r\n let tooltipEvent: TooltipEvent = {\r\n data: data,\r\n coordinates: mouseCoordinates,\r\n elementCoordinates: elementCoordinates,\r\n context: target,\r\n isTouchEvent: false\r\n };\r\n\r\n clearTooltipTimeout();\r\n \r\n // if it is already visible, change contents immediately (use 16ms minimum perceivable frame rate to prevent thrashing)\r\n let delay = ToolTipInstance.isTooltipComponentVisible() ? 16 : tooltipMouseOverDelay;\r\n tooltipTimeoutId = showDelayedTooltip(tooltipEvent, getTooltipInfoDelegate, delay);\r\n });\r\n\r\n selection.on(\"mouseout\", () => {\r\n if (!handleTouchTimeoutId) {\r\n clearTooltipTimeout();\r\n tooltipTimeoutId = hideDelayedTooltip();\r\n }\r\n\r\n if (onMouseOutDelegate) {\r\n onMouseOutDelegate();\r\n }\r\n });\r\n\r\n selection.on(\"mousemove\", () => {\r\n let target = <HTMLElement>d3.event.target;\r\n let data = d3.select(target).datum();\r\n \r\n // Ignore mousemove while handling touch events\r\n if (handleTouchTimeoutId || !canDisplayTooltip(d3.event))\r\n return;\r\n\r\n mouseCoordinates = getCoordinates(rootNode, true);\r\n let elementCoordinates: number[] = getCoordinates(target, true);\r\n let tooltipEvent: TooltipEvent = {\r\n data: data,\r\n coordinates: mouseCoordinates,\r\n elementCoordinates: elementCoordinates,\r\n context: target,\r\n isTouchEvent: false\r\n };\r\n\r\n moveTooltipEventHandler(tooltipEvent, getTooltipInfoDelegate, reloadTooltipDataOnMouseMove);\r\n });\r\n \r\n // --- Touch events ---\r\n \r\n // TODO: static?\r\n let touchStartEventName: string = getTouchStartEventName();\r\n let touchEndEventName: string = getTouchEndEventName();\r\n let isPointerEvent: boolean = touchStartEventName === \"pointerdown\" || touchStartEventName === \"MSPointerDown\";\r\n\r\n if (!GlobalTooltipEventsAttached) {\r\n // Add root container hide tooltip event\r\n attachGlobalEvents(touchStartEventName);\r\n GlobalTooltipEventsAttached = true;\r\n }\r\n\r\n selection.on(touchStartEventName, () => {\r\n let target = <HTMLElement>d3.event.target;\r\n let data = d3.select(target).datum();\r\n\r\n hideTooltipEventHandler();\r\n let coordinates: number[] = getCoordinates(rootNode, isPointerEvent);\r\n let elementCoordinates: number[] = getCoordinates(target, isPointerEvent);\r\n let tooltipEvent: TooltipEvent = {\r\n data: data,\r\n coordinates: coordinates,\r\n elementCoordinates: elementCoordinates,\r\n context: target,\r\n isTouchEvent: true\r\n };\r\n clearTooltipTimeout();\r\n tooltipTimeoutId = showDelayedTooltip(tooltipEvent, getTooltipInfoDelegate, tooltipTouchDelay);\r\n });\r\n\r\n selection.on(touchEndEventName, () => {\r\n\r\n clearTooltipTimeout();\r\n if (handleTouchTimeoutId)\r\n clearTimeout(handleTouchTimeoutId);\r\n\r\n // At the end of touch action, set a timeout that will let us ignore the incoming mouse events for a small amount of time\r\n handleTouchTimeoutId = setTimeout(() => {\r\n handleTouchTimeoutId = 0;\r\n }, handleTouchDelay);\r\n });\r\n }\r\n\r\n export function showDelayedTooltip(tooltipEvent: TooltipEvent, getTooltipInfoDelegate: (tooltipEvent: TooltipEvent) => TooltipDataItem[], delayInMs: number): number {\r\n return setTimeout(() => showTooltipEventHandler(tooltipEvent, getTooltipInfoDelegate), delayInMs);\r\n }\r\n\r\n export function hideDelayedTooltip(): number {\r\n return setTimeout(() => hideTooltipEventHandler(), tooltipMouseOutDelay);\r\n }\r\n\r\n export function setLocalizedStrings(localizationOptions: TooltipLocalizationOptions): void {\r\n ToolTipComponent.localizationOptions = localizationOptions;\r\n }\r\n\r\n function showTooltipEventHandler(tooltipEvent: TooltipEvent, getTooltipInfoDelegate: (tooltipEvent: TooltipEvent) => TooltipDataItem[]) {\r\n let tooltipInfo: TooltipDataItem[] = tooltipData || getTooltipInfoDelegate(tooltipEvent);\r\n if (!_.isEmpty(tooltipInfo)) {\r\n let coordinates: number[] = mouseCoordinates || tooltipEvent.coordinates;\r\n let clickedArea: TouchUtils.Rectangle = getClickedArea(coordinates[0], coordinates[1], tooltipEvent.isTouchEvent);\r\n ToolTipInstance.show(tooltipInfo, clickedArea);\r\n }\r\n }\r\n\r\n function moveTooltipEventHandler(tooltipEvent: TooltipEvent, getTooltipInfoDelegate: (tooltipEvent: TooltipEvent) => TooltipDataItem[], reloadTooltipDataOnMouseMove: boolean) {\r\n tooltipData = undefined;\r\n if (reloadTooltipDataOnMouseMove) {\r\n tooltipData = getTooltipInfoDelegate(tooltipEvent);\r\n }\r\n let clickedArea: TouchUtils.Rectangle = getClickedArea(tooltipEvent.coordinates[0], tooltipEvent.coordinates[1], tooltipEvent.isTouchEvent);\r\n ToolTipInstance.move(tooltipData, clickedArea);\r\n };\r\n\r\n function hideTooltipEventHandler() {\r\n ToolTipInstance.hide();\r\n };\r\n\r\n function clearTooltipTimeout() {\r\n if (tooltipTimeoutId) {\r\n clearTimeout(tooltipTimeoutId);\r\n }\r\n }\r\n\r\n function canDisplayTooltip(d3Event: any): boolean {\r\n let cadDisplay: boolean = true;\r\n let mouseEvent: MouseEvent = <MouseEvent>d3Event;\r\n if (mouseEvent.buttons !== undefined) {\r\n // Check mouse buttons state\r\n let hasMouseButtonPressed = mouseEvent.buttons !== 0;\r\n cadDisplay = !hasMouseButtonPressed;\r\n }\r\n return cadDisplay;\r\n }\r\n\r\n function getTouchStartEventName(): string {\r\n let eventName: string = \"touchstart\";\r\n\r\n if (window[\"PointerEvent\"]) {\r\n // IE11\r\n eventName = \"pointerdown\";\r\n } else if (window[\"MSPointerEvent\"]) {\r\n // IE10\r\n eventName = \"MSPointerDown\";\r\n }\r\n\r\n return eventName;\r\n }\r\n\r\n function getTouchEndEventName(): string {\r\n let eventName: string = \"touchend\";\r\n\r\n if (window[\"PointerEvent\"]) {\r\n // IE11\r\n eventName = \"pointerup\";\r\n } else if (window[\"MSPointerEvent\"]) {\r\n // IE10\r\n eventName = \"MSPointerUp\";\r\n }\r\n\r\n return eventName;\r\n }\r\n\r\n function getCoordinates(rootNode: Element, isPointerEvent: boolean): number[] {\r\n let coordinates: number[];\r\n\r\n if (isPointerEvent) {\r\n // DO NOT USE - WebKit bug in getScreenCTM with nested SVG results in slight negative coordinate shift\r\n // Also, IE will incorporate transform scale but WebKit does not, forcing us to detect browser and adjust appropriately.\r\n // Just use non-scaled coordinates for all browsers, and adjust for the transform scale later (see lineChart.findIndex)\r\n //coordinates = d3.mouse(rootNode);\r\n\r\n // copied from d3_eventSource (which is not exposed)\r\n let e = d3.event, s;\r\n while (s = e.sourceEvent) e = s;\r\n let rect = rootNode.getBoundingClientRect();\r\n coordinates = [e.clientX - rect.left - rootNode.clientLeft, e.clientY - rect.top - rootNode.clientTop];\r\n }\r\n else {\r\n let touchCoordinates = d3.touches(rootNode);\r\n if (touchCoordinates && touchCoordinates.length > 0) {\r\n coordinates = touchCoordinates[0];\r\n }\r\n }\r\n\r\n return coordinates;\r\n }\r\n\r\n function attachGlobalEvents(touchStartEventName: string): void {\r\n d3.select(ToolTipComponent.parentContainerSelector).on(touchStartEventName, function (d, i) {\r\n ToolTipInstance.hide();\r\n });\r\n }\r\n\r\n function getClickedArea(x: number, y: number, isTouchEvent: boolean): TouchUtils.Rectangle {\r\n let width: number = 0;\r\n let pointX: number = x;\r\n let pointY: number = y;\r\n\r\n if (isTouchEvent) {\r\n width = 12;\r\n let offset: number = width / 2;\r\n pointX = Math.max(x - offset, 0);\r\n pointY = Math.max(y - offset, 0);\r\n }\r\n\r\n return new TouchUtils.Rectangle(pointX, pointY, width, width);\r\n }\r\n }\r\n\r\n export module TooltipBuilder {\r\n\r\n // TODO: implement options bag as input parameter\r\n export function createTooltipInfo(\r\n formatStringProp: DataViewObjectPropertyIdentifier,\r\n dataViewCat: DataViewCategorical,\r\n categoryValue: any,\r\n value?: any,\r\n categories?: DataViewCategoryColumn[],\r\n seriesData?: TooltipSeriesDataItem[],\r\n seriesIndex?: number,\r\n categoryIndex?: number,\r\n highlightedValue?: any,\r\n gradientValueColumn?: DataViewValueColumn): TooltipDataItem[] {\r\n let categorySource: TooltipCategoryDataItem;\r\n let seriesSource: TooltipSeriesDataItem[] = [];\r\n let valuesSource: DataViewMetadataColumn = undefined;\r\n seriesIndex = seriesIndex | 0;\r\n\r\n let categoriesData = dataViewCat ? dataViewCat.categories : categories;\r\n if (categoriesData && categoriesData.length > 0) {\r\n if (categoriesData.length > 1) {\r\n let compositeCategoriesData = [];\r\n for (let i = 0, ilen = categoriesData.length; i < ilen; i++) {\r\n compositeCategoriesData.push(categoriesData[i].source);\r\n }\r\n categorySource = { value: categoryValue, metadata: compositeCategoriesData };\r\n }\r\n else {\r\n categorySource = { value: categoryValue, metadata: [categoriesData[0].source] };\r\n }\r\n }\r\n if (dataViewCat && dataViewCat.values) {\r\n if (categorySource && categorySource.metadata[0] === dataViewCat.values.source) {\r\n // Category/series on the same column -- don't repeat its value in the tooltip.\r\n }\r\n else {\r\n valuesSource = dataViewCat.values.source;\r\n }\r\n if (dataViewCat.values.length > 0) {\r\n let valueColumn: DataViewValueColumn = dataViewCat.values[seriesIndex];\r\n let isAutoGeneratedColumn: boolean = !!(valueColumn && valueColumn.source && (<DataViewMetadataAutoGeneratedColumn>valueColumn.source).isAutoGeneratedColumn);\r\n\r\n if (!isAutoGeneratedColumn) {\r\n seriesSource.push({ value: value, highlightedValue: highlightedValue, metadata: valueColumn });\r\n }\r\n }\r\n\r\n //Create Gradient tooltip value\r\n let gradientToolTipData = createGradientToolTipData(gradientValueColumn, categoryIndex);\r\n if (gradientToolTipData != null)\r\n seriesSource.push(gradientToolTipData);\r\n }\r\n if (seriesData) {\r\n for (let i: number = 0, len: number = seriesData.length; i < len; i++) {\r\n let singleSeriesData: TooltipSeriesDataItem = seriesData[i];\r\n if (categorySource && categorySource.metadata[0] === singleSeriesData.metadata.source)\r\n continue;\r\n\r\n seriesSource.push({ value: singleSeriesData.value, metadata: singleSeriesData.metadata });\r\n }\r\n }\r\n\r\n let tooltipInfo: TooltipDataItem[] = createTooltipData(formatStringProp, categorySource, valuesSource, seriesSource);\r\n\r\n return tooltipInfo;\r\n }\r\n\r\n export function createGradientToolTipData(gradientValueColumn: DataViewValueColumn, categoryIndex: number): TooltipSeriesDataItem {\r\n if (gradientValueColumn) {\r\n // Saturation color\r\n return { value: gradientValueColumn.values[categoryIndex], metadata: { source: gradientValueColumn.source, values: [] } };\r\n }\r\n return null;\r\n }\r\n\r\n function createTooltipData(\r\n formatStringProp: DataViewObjectPropertyIdentifier,\r\n categoryValue: TooltipCategoryDataItem,\r\n valuesSource: DataViewMetadataColumn,\r\n seriesValues: TooltipSeriesDataItem[]): TooltipDataItem[] {\r\n\r\n debug.assertValue(seriesValues, \"seriesSource\");\r\n debug.assertValue(ToolTipComponent.localizationOptions, \"ToolTipComponent.localizationOptions\");\r\n debug.assertAnyValue(formatStringProp, 'formatStringProp');\r\n\r\n let items: TooltipDataItem[] = [];\r\n\r\n if (categoryValue) {\r\n if (categoryValue.metadata.length > 1) {\r\n let displayName = '';\r\n // This is being done simply for lat/long for now, as that's the only composite category we use. If we ever have tooltips\r\n // involving other composite categories, we need to do a more thorough design and be more careful here.\r\n for (let i = 0, ilen = categoryValue.metadata.length; i < ilen; i++) {\r\n if (i !== 0)\r\n displayName += '/';\r\n displayName += categoryValue.metadata[i].displayName;\r\n }\r\n let categoryFormattedValue: string = getFormattedValue(categoryValue.metadata[0], formatStringProp, categoryValue.value);\r\n items.push({ displayName: displayName, value: categoryFormattedValue });\r\n }\r\n else {\r\n let categoryFormattedValue: string = getFormattedValue(categoryValue.metadata[0], formatStringProp, categoryValue.value);\r\n items.push({ displayName: categoryValue.metadata[0].displayName, value: categoryFormattedValue });\r\n }\r\n }\r\n\r\n if (valuesSource) {\r\n // Dynamic series value\r\n let dynamicValue: string;\r\n if (seriesValues.length > 0) {\r\n let dynamicValueMetadata: DataViewMetadataColumn = seriesValues[0].metadata.source;\r\n dynamicValue = getFormattedValue(valuesSource, formatStringProp, dynamicValueMetadata.groupName);\r\n }\r\n items.push({ displayName: valuesSource.displayName, value: dynamicValue });\r\n }\r\n\r\n for (let i = 0; i < seriesValues.length; i++) {\r\n let seriesData = seriesValues[i];\r\n\r\n if (seriesData && seriesData.metadata) {\r\n let seriesMetadataColumn = seriesData.metadata.source;\r\n let value = seriesData.value;\r\n let highlightedValue = seriesData.highlightedValue;\r\n\r\n if (value || value === 0) {\r\n let formattedValue: string = getFormattedValue(seriesMetadataColumn, formatStringProp, value);\r\n items.push({ displayName: seriesMetadataColumn.displayName, value: formattedValue });\r\n }\r\n\r\n if (highlightedValue || highlightedValue === 0) {\r\n let formattedHighlightedValue: string = getFormattedValue(seriesMetadataColumn, formatStringProp, highlightedValue);\r\n let displayName = ToolTipComponent.localizationOptions.highlightedValueDisplayName;\r\n items.push({ displayName: displayName, value: formattedHighlightedValue });\r\n }\r\n }\r\n }\r\n\r\n return items;\r\n }\r\n\r\n export function addTooltipBucketItem(reader: data.IDataViewCategoricalReader, tooltipInfo: TooltipDataItem[], categoryIndex: number, seriesIndex?: number): void{\r\n let tooltipValues = reader.getAllValuesForRole(\"Tooltips\", categoryIndex, seriesIndex);\r\n let tooltipMetadataColumns = reader.getAllValueMetadataColumnsForRole(\"Tooltips\", seriesIndex);\r\n\r\n if (tooltipValues && tooltipMetadataColumns) {\r\n for (let j = 0; j < tooltipValues.length; j++) {\r\n if (tooltipValues[j] != null && tooltipMetadataColumns[j]) {\r\n tooltipInfo.push({\r\n displayName: tooltipMetadataColumns[j].displayName,\r\n value: converterHelper.formatFromMetadataColumn(tooltipValues[j], tooltipMetadataColumns[j], Gauge.formatStringProp),\r\n });\r\n }\r\n }\r\n }\r\n }\r\n \r\n function getFormattedValue(column: DataViewMetadataColumn, formatStringProp: DataViewObjectPropertyIdentifier, value: any) {\r\n let formatString: string = getFormatStringFromColumn(column, formatStringProp);\r\n return valueFormatter.format(value, formatString);\r\n }\r\n\r\n function getFormatStringFromColumn(column: DataViewMetadataColumn, formatStringProp: DataViewObjectPropertyIdentifier): string {\r\n if (column) {\r\n let formatString: string = valueFormatter.getFormatString(column, formatStringProp, true);\r\n return formatString || column.format;\r\n }\r\n return null;\r\n }\r\n }\r\n}\n\n\n/** WEBPACK FOOTER **\n ** ./src/Clients/Visuals/tooltip.ts\n **/","/*\r\n * Power BI Visualizations\r\n *\r\n * Copyright (c) Microsoft Corporation\r\n * All rights reserved. \r\n * MIT License\r\n *\r\n * Permission is hereby granted, free of charge, to any person obtaining a copy\r\n * of this software and associated documentation files (the \"\"Software\"\"), to deal\r\n * in the Software without restriction, including without limitation the rights\r\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\r\n * copies of the Software, and to permit persons to whom the Software is\r\n * furnished to do so, subject to the following conditions:\r\n * \r\n * The above copyright notice and this permission notice shall be included in \r\n * all copies or substantial portions of the Software.\r\n * \r\n * THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR \r\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, \r\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE \r\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER \r\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\r\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\r\n * THE SOFTWARE.\r\n */\r\n\r\n/// <reference path=\"../_references.ts\"/>\r\n\r\nmodule powerbi.visuals {\r\n export module visualStyles {\r\n export function create(dataColors?: IDataColorPalette): IVisualStyle {\r\n if (dataColors === undefined)\r\n dataColors = new visuals.DataColorPalette();\r\n\r\n return {\r\n titleText: {\r\n color: { value: '#333' }\r\n },\r\n subTitleText: {\r\n color: { value: '#919191' }\r\n },\r\n colorPalette: {\r\n foreground: { value: '#333' },\r\n background: { value: '#fff' },\r\n dataColors: dataColors,\r\n },\r\n labelText: {\r\n color: {\r\n value: '#333',\r\n },\r\n fontSize: '11px'\r\n },\r\n isHighContrast: false,\r\n };\r\n }\r\n }\r\n}\n\n\n/** WEBPACK FOOTER **\n ** ./src/Clients/Visuals/styles/visualStyles.ts\n **/","/*\r\n * Power BI Visualizations\r\n *\r\n * Copyright (c) Microsoft Corporation\r\n * All rights reserved. \r\n * MIT License\r\n *\r\n * Permission is hereby granted, free of charge, to any person obtaining a copy\r\n * of this software and associated documentation files (the \"\"Software\"\"), to deal\r\n * in the Software without restriction, including without limitation the rights\r\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\r\n * copies of the Software, and to permit persons to whom the Software is\r\n * furnished to do so, subject to the following conditions:\r\n * \r\n * The above copyright notice and this permission notice shall be included in \r\n * all copies or substantial portions of the Software.\r\n * \r\n * THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR \r\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, \r\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE \r\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER \r\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\r\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\r\n * THE SOFTWARE.\r\n */\r\n\r\n/// <reference path=\"../_references.ts\"/>\r\n\r\nmodule powerbi.visuals {\r\n import ClassAndSelector = jsCommon.CssConstants.ClassAndSelector;\r\n import createClassAndSelector = jsCommon.CssConstants.createClassAndSelector;\r\n import ISize = shapes.ISize;\r\n import IDataViewCategoricalReader = data.IDataViewCategoricalReader;\r\n\r\n export interface DonutSmallViewPortProperties {\r\n maxHeightToScaleDonutLegend: number;\r\n }\r\n\r\n export interface DonutConstructorOptions {\r\n sliceWidthRatio?: number;\r\n animator?: IDonutChartAnimator;\r\n isScrollable?: boolean;\r\n disableGeometricCulling?: boolean;\r\n behavior?: IInteractiveBehavior;\r\n tooltipsEnabled?: boolean;\r\n tooltipBucketEnabled?: boolean;\r\n smallViewPortProperties?: DonutSmallViewPortProperties;\r\n }\r\n\r\n /**\r\n * Used because data points used in D3 pie layouts are placed within a container with pie information.\r\n */\r\n export interface DonutArcDescriptor extends D3.Layout.ArcDescriptor {\r\n data: DonutDataPoint;\r\n }\r\n\r\n export interface DonutDataPoint extends SelectableDataPoint, TooltipEnabledDataPoint {\r\n measure: number;\r\n originalMeasure: number;\r\n measureFormat?: string;\r\n percentage: number;\r\n highlightRatio?: number;\r\n highlightValue?: number;\r\n originalHighlightValue?: number;\r\n label: string;\r\n index: number;\r\n /** Data points that may be drilled into */\r\n internalDataPoints?: DonutDataPoint[];\r\n color: string;\r\n strokeWidth: number;\r\n //taken from column metadata\r\n labelFormatString: string;\r\n /** This is set to true only when it's the last slice and all slices have the same color*/\r\n isLastInDonut?: boolean;\r\n }\r\n\r\n export interface DonutData {\r\n dataPointsToDeprecate: DonutDataPoint[];\r\n dataPoints: DonutArcDescriptor[]; // The data points will be culled based on viewport size to remove invisible slices\r\n unCulledDataPoints: DonutDataPoint[]; // The unculled data points will never be culled\r\n dataPointsToEnumerate?: LegendDataPoint[];\r\n legendData: LegendData;\r\n hasHighlights: boolean;\r\n highlightsOverflow: boolean;\r\n dataLabelsSettings: VisualDataLabelsSettings;\r\n legendObjectProperties?: DataViewObject;\r\n maxValue?: number;\r\n visibleGeometryCulled?: boolean;\r\n defaultDataPointColor?: string;\r\n hasNegativeValues?: boolean;\r\n allValuesAreNegative?: boolean;\r\n }\r\n\r\n interface DonutChartSettings {\r\n /**\r\n * The duration for a long animation displayed after a user interaction with an interactive chart. \r\n */\r\n chartRotationAnimationDuration?: number;\r\n /**\r\n * The duration for a short animation displayed after a user interaction with an interactive chart.\r\n */\r\n legendTransitionAnimationDuration?: number;\r\n }\r\n\r\n interface InteractivityState {\r\n interactiveLegend: DonutChartInteractiveLegend;\r\n valueToAngleFactor: number; // Ratio between 360 and the sum of the angles\r\n sliceAngles: number[]; // Saves the angle to rotate of each slice\r\n currentRotate: number; // Keeps how much the donut is rotated by\r\n interactiveChosenSliceFinishedSetting: boolean; // flag indicating whether the chosen interactive slice was set\r\n lastChosenInteractiveSliceIndex: number; // keeps the index of the selected slice\r\n donutCenter: { // center of the chart\r\n x: number; y: number;\r\n };\r\n totalDragAngleDifference: number; // keeps how much of a rotation happened in the drag\r\n previousDragAngle: number; // previous angle of the drag event\r\n currentIndexDrag: number; // index of the slice that is currently showing in the legend \r\n previousIndexDrag: number; // index of the slice that was showing in the legend before current drag event\r\n }\r\n\r\n export interface DonutLayout {\r\n shapeLayout: {\r\n d: (d: DonutArcDescriptor) => string;\r\n };\r\n highlightShapeLayout: {\r\n d: (d: DonutArcDescriptor) => string;\r\n };\r\n zeroShapeLayout: {\r\n d: (d: DonutArcDescriptor) => string;\r\n };\r\n }\r\n\r\n /**\r\n * Renders a donut chart.\r\n */\r\n export class DonutChart implements IVisual {\r\n private static ClassName = 'donutChart';\r\n private static InteractiveLegendClassName = 'donutLegend';\r\n private static InteractiveLegendArrowClassName = 'donutLegendArrow';\r\n private static OuterArcRadiusRatio = 0.9;\r\n private static InnerArcRadiusRatio = 0.8;\r\n private static OpaqueOpacity = 1.0;\r\n private static SemiTransparentOpacity = 0.6;\r\n private static defaultSliceWidthRatio: number = 0.48;\r\n private static invisibleArcLengthInPixels: number = 3;\r\n private static sliceClass: ClassAndSelector = createClassAndSelector('slice');\r\n private static sliceHighlightClass: ClassAndSelector = createClassAndSelector('slice-highlight');\r\n private static twoPi = 2 * Math.PI;\r\n\r\n public static InteractiveLegendContainerHeight = 70;\r\n public static EffectiveZeroValue = 0.000000001; // Very small multiplier so that we have a properly shaped zero arc to animate to/from.\r\n public static PolylineOpacity = 0.5;\r\n\r\n private dataViews: DataView[];\r\n private sliceWidthRatio: number;\r\n private svg: D3.Selection;\r\n private mainGraphicsContext: D3.Selection;\r\n private labelGraphicsContext: D3.Selection;\r\n private clearCatcher: D3.Selection;\r\n private legendContainer: D3.Selection;\r\n private interactiveLegendArrow: D3.Selection;\r\n private parentViewport: IViewport;\r\n private currentViewport: IViewport;\r\n private formatter: ICustomValueFormatter;\r\n private data: DonutData;\r\n private pie: D3.Layout.PieLayout;\r\n private arc: D3.Svg.Arc;\r\n private outerArc: D3.Svg.Arc;\r\n private radius: number;\r\n private previousRadius: number;\r\n private key: any;\r\n private colors: IDataColorPalette;\r\n private style: IVisualStyle;\r\n private drilled: boolean;\r\n private allowDrilldown: boolean;\r\n private options: VisualInitOptions;\r\n private isInteractive: boolean;\r\n private interactivityState: InteractivityState;\r\n private chartRotationAnimationDuration: number;\r\n private interactivityService: IInteractivityService;\r\n private behavior: IInteractiveBehavior;\r\n private legend: ILegend;\r\n private hasSetData: boolean;\r\n private isScrollable: boolean;\r\n private disableGeometricCulling: boolean;\r\n private hostService: IVisualHostServices;\r\n private settings: DonutChartSettings;\r\n private tooltipsEnabled: boolean;\r\n private tooltipBucketEnabled: boolean;\r\n private donutProperties: DonutChartProperties;\r\n private maxHeightToScaleDonutLegend: number;\r\n\r\n /**\r\n * Note: Public for testing.\r\n */\r\n public animator: IDonutChartAnimator;\r\n\r\n constructor(options?: DonutConstructorOptions) {\r\n if (options) {\r\n this.sliceWidthRatio = options.sliceWidthRatio;\r\n this.animator = options.animator;\r\n this.isScrollable = options.isScrollable ? options.isScrollable : false;\r\n this.disableGeometricCulling = options.disableGeometricCulling ? options.disableGeometricCulling : false;\r\n this.behavior = options.behavior;\r\n this.tooltipsEnabled = options.tooltipsEnabled;\r\n this.tooltipBucketEnabled = options.tooltipBucketEnabled;\r\n if (options.smallViewPortProperties) {\r\n this.maxHeightToScaleDonutLegend = options.smallViewPortProperties.maxHeightToScaleDonutLegend;\r\n }\r\n }\r\n if (this.sliceWidthRatio == null) {\r\n this.sliceWidthRatio = DonutChart.defaultSliceWidthRatio;\r\n }\r\n }\r\n\r\n public static converter(dataView: DataView, colors: IDataColorPalette, defaultDataPointColor?: string, viewport?: IViewport, disableGeometricCulling?: boolean, interactivityService?: IInteractivityService, tooltipsEnabled: boolean = true, tooltipBucketEnabled?: boolean): DonutData {\r\n let converter = new DonutChartConversion.DonutChartConverter(dataView, colors, defaultDataPointColor, tooltipsEnabled, tooltipBucketEnabled);\r\n converter.convert();\r\n let d3PieLayout = d3.layout.pie()\r\n .sort(null)\r\n .value((d: DonutDataPoint) => {\r\n return d.percentage;\r\n });\r\n\r\n if (interactivityService) {\r\n interactivityService.applySelectionStateToData(converter.dataPoints);\r\n interactivityService.applySelectionStateToData(converter.legendData.dataPoints);\r\n }\r\n\r\n let culledDataPoints = (!disableGeometricCulling && viewport) ? DonutChart.cullDataByViewport(converter.dataPoints, converter.maxValue, viewport) : converter.dataPoints;\r\n return {\r\n dataPointsToDeprecate: culledDataPoints,\r\n dataPoints: d3PieLayout(culledDataPoints),\r\n unCulledDataPoints: converter.dataPoints,\r\n dataPointsToEnumerate: converter.legendData.dataPoints,\r\n legendData: converter.legendData,\r\n hasHighlights: converter.hasHighlights,\r\n highlightsOverflow: converter.highlightsOverflow,\r\n dataLabelsSettings: converter.dataLabelsSettings,\r\n legendObjectProperties: converter.legendObjectProperties,\r\n maxValue: converter.maxValue,\r\n visibleGeometryCulled: converter.dataPoints.length !== culledDataPoints.length,\r\n hasNegativeValues: converter.hasNegativeValues,\r\n allValuesAreNegative: converter.allValuesAreNegative,\r\n };\r\n }\r\n\r\n public init(options: VisualInitOptions) {\r\n this.options = options;\r\n let element = options.element;\r\n // Ensure viewport is empty on init\r\n element.empty();\r\n this.parentViewport = options.viewport;\r\n // avoid deep copy\r\n this.currentViewport = {\r\n height: options.viewport.height,\r\n width: options.viewport.width,\r\n };\r\n\r\n this.formatter = valueFormatter.format;\r\n this.data = {\r\n dataPointsToDeprecate: [],\r\n dataPointsToEnumerate: [],\r\n dataPoints: [],\r\n unCulledDataPoints: [],\r\n legendData: { title: \"\", dataPoints: [], fontSize: SVGLegend.DefaultFontSizeInPt },\r\n hasHighlights: false,\r\n highlightsOverflow: false,\r\n dataLabelsSettings: dataLabelUtils.getDefaultDonutLabelSettings(),\r\n hasNegativeValues: false,\r\n allValuesAreNegative: false,\r\n };\r\n this.drilled = false;\r\n // Leaving this false for now, will depend on the datacategory in the future\r\n this.allowDrilldown = false;\r\n this.style = options.style;\r\n this.colors = this.style.colorPalette.dataColors;\r\n this.radius = 0;\r\n this.isInteractive = options.interactivity && options.interactivity.isInteractiveLegend;\r\n let donutChartSettings = this.settings;\r\n\r\n if (this.behavior) {\r\n this.interactivityService = createInteractivityService(options.host);\r\n }\r\n this.legend = createLegend(element, options.interactivity && options.interactivity.isInteractiveLegend, this.interactivityService, this.isScrollable);\r\n\r\n this.hostService = options.host;\r\n\r\n if (this.isInteractive) {\r\n this.chartRotationAnimationDuration = (donutChartSettings && donutChartSettings.chartRotationAnimationDuration) ? donutChartSettings.chartRotationAnimationDuration : 0;\r\n\r\n // Create interactive legend\r\n let legendContainer = this.legendContainer = d3.select(element.get(0))\r\n .append('div')\r\n .classed(DonutChart.InteractiveLegendClassName, true);\r\n this.interactivityState = {\r\n interactiveLegend: new DonutChartInteractiveLegend(this, legendContainer, this.colors, options, this.settings),\r\n valueToAngleFactor: 0,\r\n sliceAngles: [],\r\n currentRotate: 0,\r\n interactiveChosenSliceFinishedSetting: false,\r\n lastChosenInteractiveSliceIndex: 0,\r\n totalDragAngleDifference: 0,\r\n currentIndexDrag: 0,\r\n previousIndexDrag: 0,\r\n previousDragAngle: 0,\r\n donutCenter: { x: 0, y: 0 },\r\n };\r\n }\r\n\r\n this.svg = d3.select(element.get(0))\r\n .append('svg')\r\n .style('position', 'absolute')\r\n .classed(DonutChart.ClassName, true);\r\n\r\n if (this.behavior)\r\n this.clearCatcher = appendClearCatcher(this.svg);\r\n\r\n this.mainGraphicsContext = this.svg.append('g');\r\n this.mainGraphicsContext.append(\"g\")\r\n .classed('slices', true);\r\n\r\n this.labelGraphicsContext = this.svg\r\n .append(\"g\")\r\n .classed(NewDataLabelUtils.labelGraphicsContextClass.class, true);\r\n\r\n this.pie = d3.layout.pie()\r\n .sort(null)\r\n .value((d: DonutDataPoint) => {\r\n return d.percentage;\r\n });\r\n }\r\n\r\n public update(options: VisualUpdateOptions): void {\r\n debug.assertValue(options, 'options');\r\n\r\n // Viewport resizing\r\n let viewport = options.viewport;\r\n this.parentViewport = viewport;\r\n\r\n let dataViews = this.dataViews = options.dataViews;\r\n if (dataViews && dataViews.length > 0 && dataViews[0].categorical) {\r\n let dataViewMetadata = dataViews[0].metadata;\r\n let defaultDataPointColor = undefined;\r\n if (dataViewMetadata) {\r\n let objects: DataViewObjects = dataViewMetadata.objects;\r\n\r\n if (objects) {\r\n defaultDataPointColor = DataViewObjects.getFillColor(objects, donutChartProps.dataPoint.defaultColor);\r\n }\r\n }\r\n\r\n this.data = DonutChart.converter(dataViews[0], this.colors, defaultDataPointColor, this.currentViewport, this.disableGeometricCulling, this.interactivityService, this.tooltipsEnabled, this.tooltipBucketEnabled);\r\n this.data.defaultDataPointColor = defaultDataPointColor;\r\n if (!(this.options.interactivity && this.options.interactivity.isInteractiveLegend))\r\n this.renderLegend();\r\n }\r\n else {\r\n this.data = {\r\n dataPointsToDeprecate: [],\r\n dataPointsToEnumerate: [],\r\n dataPoints: [],\r\n unCulledDataPoints: [],\r\n legendData: { title: \"\", dataPoints: [] },\r\n hasHighlights: false,\r\n highlightsOverflow: false,\r\n dataLabelsSettings: dataLabelUtils.getDefaultDonutLabelSettings(),\r\n hasNegativeValues: false,\r\n allValuesAreNegative: false,\r\n };\r\n }\r\n\r\n this.initViewportDependantProperties();\r\n this.initDonutProperties();\r\n this.updateInternal(this.data, options.suppressAnimations);\r\n this.hasSetData = true;\r\n\r\n if (dataViews) {\r\n let warnings = getInvalidValueWarnings(\r\n dataViews,\r\n false /*supportsNaN*/,\r\n false /*supportsNegativeInfinity*/,\r\n false /*supportsPositiveInfinity*/);\r\n\r\n if (this.data.allValuesAreNegative) {\r\n warnings.push(new AllNegativeValuesWarning());\r\n }\r\n else if (this.data.hasNegativeValues) {\r\n warnings.push(new NegativeValuesNotSupportedWarning());\r\n }\r\n\r\n this.hostService.setWarnings(warnings);\r\n }\r\n }\r\n\r\n public onDataChanged(options: VisualDataChangedOptions): void {\r\n debug.assertValue(options, 'options');\r\n\r\n this.update({\r\n dataViews: options.dataViews,\r\n suppressAnimations: options.suppressAnimations,\r\n viewport: this.currentViewport,\r\n });\r\n }\r\n\r\n public onResizing(viewport: IViewport): void {\r\n this.update({\r\n dataViews: this.dataViews,\r\n suppressAnimations: true,\r\n viewport: viewport,\r\n });\r\n }\r\n\r\n public enumerateObjectInstances(options: EnumerateVisualObjectInstancesOptions): VisualObjectInstanceEnumeration {\r\n let enumeration = new ObjectEnumerationBuilder();\r\n\r\n let dataLabelsSettings = this.data && this.data.dataLabelsSettings\r\n ? this.data.dataLabelsSettings\r\n : dataLabelUtils.getDefaultDonutLabelSettings();\r\n\r\n switch (options.objectName) {\r\n case 'legend':\r\n this.enumerateLegend(enumeration);\r\n break;\r\n case 'dataPoint':\r\n this.enumerateDataPoints(enumeration);\r\n break;\r\n case 'labels':\r\n let labelSettingOptions: VisualDataLabelsSettingsOptions = {\r\n enumeration: enumeration,\r\n dataLabelsSettings: dataLabelsSettings,\r\n show: true,\r\n displayUnits: true,\r\n precision: true,\r\n fontSize: true,\r\n labelStyle: true,\r\n };\r\n dataLabelUtils.enumerateDataLabels(labelSettingOptions);\r\n break;\r\n }\r\n return enumeration.complete();\r\n }\r\n\r\n private enumerateDataPoints(enumeration: ObjectEnumerationBuilder): void {\r\n let data = this.data;\r\n if (!data)\r\n return;\r\n\r\n let dataPoints = data.dataPointsToEnumerate;\r\n let dataPointsLength = dataPoints.length;\r\n\r\n for (let i = 0; i < dataPointsLength; i++) {\r\n let dataPoint = dataPoints[i];\r\n enumeration.pushInstance({\r\n objectName: 'dataPoint',\r\n displayName: dataPoint.label,\r\n selector: ColorHelper.normalizeSelector(dataPoint.identity.getSelector()),\r\n properties: {\r\n fill: { solid: { color: dataPoint.color } }\r\n },\r\n });\r\n }\r\n }\r\n\r\n private enumerateLegend(enumeration: ObjectEnumerationBuilder): ObjectEnumerationBuilder {\r\n let data = this.data;\r\n if (!data)\r\n return;\r\n\r\n let legendObjectProperties: DataViewObjects = { legend: data.legendObjectProperties };\r\n\r\n let show = DataViewObjects.getValue(legendObjectProperties, donutChartProps.legend.show, this.legend.isVisible());\r\n let showTitle = DataViewObjects.getValue(legendObjectProperties, donutChartProps.legend.showTitle, true);\r\n let titleText = DataViewObjects.getValue(legendObjectProperties, donutChartProps.legend.titleText, this.data.legendData.title);\r\n let labelColor = DataViewObject.getValue(legendObjectProperties, legendProps.labelColor, this.data.legendData.labelColor);\r\n let labelFontSize = DataViewObject.getValue(legendObjectProperties, legendProps.fontSize, this.data.legendData.fontSize);\r\n\r\n enumeration.pushInstance({\r\n selector: null,\r\n objectName: 'legend',\r\n properties: {\r\n show: show,\r\n position: LegendPosition[this.legend.getOrientation()],\r\n showTitle: showTitle,\r\n titleText: titleText,\r\n labelColor: labelColor,\r\n fontSize: labelFontSize\r\n }\r\n });\r\n }\r\n\r\n public setInteractiveChosenSlice(sliceIndex: number): void {\r\n if (this.interactivityState.sliceAngles.length === 0) return;\r\n\r\n this.interactivityState.lastChosenInteractiveSliceIndex = sliceIndex;\r\n this.interactivityState.interactiveChosenSliceFinishedSetting = false;\r\n let viewport = this.currentViewport;\r\n let moduledIndex = sliceIndex % this.data.dataPoints.length;\r\n let angle = this.interactivityState.sliceAngles[moduledIndex];\r\n\r\n this.svg.select('g')\r\n .transition()\r\n .duration(this.chartRotationAnimationDuration)\r\n .ease('elastic')\r\n .attr('transform', SVGUtil.translateAndRotate(viewport.width / 2, viewport.height / 2, 0, 0, angle))\r\n .each('end', () => { this.interactivityState.interactiveChosenSliceFinishedSetting = true; });\r\n\r\n this.interactivityState.currentRotate = angle;\r\n this.interactivityState.interactiveLegend.updateLegend(moduledIndex);\r\n // Set the opacity of chosen slice to full and the others to semi-transparent\r\n this.svg.selectAll('.slice').attr('opacity', (d, index) => {\r\n return index === moduledIndex ? 1 : 0.6;\r\n });\r\n\r\n SVGUtil.flushAllD3TransitionsIfNeeded(this.options);\r\n }\r\n\r\n private calculateRadius(): number {\r\n let viewport = this.currentViewport;\r\n if (!this.isInteractive && this.data && this.data.dataLabelsSettings.show) {\r\n // if we have category or data labels, use a sigmoid to blend the desired denominator from 2 to 3.\r\n // if we are taller than we are wide, we need to use a larger denominator to leave horizontal room for the labels.\r\n let hw = viewport.height / viewport.width;\r\n let denom = 2 + (1 / (1 + Math.exp(-5 * (hw - 1))));\r\n return Math.min(viewport.height, viewport.width) / denom;\r\n }\r\n\r\n // no labels (isInteractive does not have labels since the interactive legend shows extra info)\r\n return Math.min(viewport.height, viewport.width) / 2;\r\n }\r\n\r\n private getScaleForLegendArrow() {\r\n let ratio = 1.0;\r\n if (this.maxHeightToScaleDonutLegend && this.currentViewport.height < this.maxHeightToScaleDonutLegend) {\r\n ratio = this.currentViewport.height / this.maxHeightToScaleDonutLegend;\r\n }\r\n return ratio;\r\n }\r\n\r\n private initViewportDependantProperties(duration: number = 0) {\r\n this.currentViewport.height = this.parentViewport.height;\r\n this.currentViewport.width = this.parentViewport.width;\r\n let viewport = this.currentViewport;\r\n\r\n if (this.isInteractive) {\r\n viewport.height -= DonutChart.InteractiveLegendContainerHeight; // leave space for the legend\r\n }\r\n else {\r\n let legendMargins = this.legend.getMargins();\r\n viewport.height -= legendMargins.height;\r\n viewport.width -= legendMargins.width;\r\n }\r\n\r\n this.svg.attr({\r\n 'width': viewport.width,\r\n 'height': viewport.height\r\n });\r\n\r\n if (this.isInteractive) {\r\n this.legendContainer\r\n .style({\r\n 'width': '100%',\r\n 'height': DonutChart.InteractiveLegendContainerHeight + 'px',\r\n 'overflow': 'hidden',\r\n 'top': 0\r\n });\r\n this.svg\r\n .style('top', DonutChart.InteractiveLegendContainerHeight);\r\n } else {\r\n Legend.positionChartArea(this.svg, this.legend);\r\n }\r\n\r\n this.previousRadius = this.radius;\r\n let radius = this.radius = this.calculateRadius();\r\n let halfViewportWidth = viewport.width / 2;\r\n let halfViewportHeight = viewport.height / 2;\r\n\r\n this.arc = d3.svg.arc();\r\n\r\n this.outerArc = d3.svg.arc()\r\n .innerRadius(radius * DonutChart.OuterArcRadiusRatio)\r\n .outerRadius(radius * DonutChart.OuterArcRadiusRatio);\r\n\r\n if (this.isInteractive) {\r\n this.mainGraphicsContext.attr('transform', SVGUtil.translate(halfViewportWidth, halfViewportHeight));\r\n this.labelGraphicsContext.attr('transform', SVGUtil.translate(halfViewportWidth, halfViewportHeight));\r\n } else {\r\n this.mainGraphicsContext.transition().duration(duration).attr('transform', SVGUtil.translate(halfViewportWidth, halfViewportHeight));\r\n this.labelGraphicsContext.transition().duration(duration).attr('transform', SVGUtil.translate(halfViewportWidth, halfViewportHeight));\r\n }\r\n\r\n SVGUtil.flushAllD3TransitionsIfNeeded(this.options);\r\n }\r\n\r\n private initDonutProperties() {\r\n this.donutProperties = {\r\n viewport: this.currentViewport,\r\n radius: this.radius,\r\n arc: this.arc.innerRadius(0).outerRadius(this.radius * DonutChart.InnerArcRadiusRatio),\r\n outerArc: this.outerArc,\r\n innerArcRadiusRatio: DonutChart.InnerArcRadiusRatio,\r\n outerArcRadiusRatio: DonutChart.OuterArcRadiusRatio,\r\n dataLabelsSettings: this.data.dataLabelsSettings,\r\n };\r\n }\r\n\r\n private mergeDatasets(first: any[], second: any[]): any[] {\r\n let secondSet = d3.set();\r\n second.forEach((d) => {\r\n secondSet.add(d.identity ? d.identity.getKey() : d.data.identity.getKey());\r\n });\r\n\r\n let onlyFirst = first.filter((d) => {\r\n return !secondSet.has(d.identity ? d.identity.getKey() : d.data.identity.getKey());\r\n }).map((d) => {\r\n let derived = Prototype.inherit(d);\r\n derived.percentage === undefined ? derived.data.percentage = 0 : derived.percentage = 0;\r\n return derived;\r\n });\r\n\r\n return d3.merge([second, onlyFirst]);\r\n }\r\n\r\n private updateInternal(data: DonutData, suppressAnimations: boolean, duration: number = 0) {\r\n let viewport = this.currentViewport;\r\n duration = duration || AnimatorCommon.GetAnimationDuration(this.animator, suppressAnimations);\r\n if (this.animator) {\r\n let layout = DonutChart.getLayout(this.radius, this.sliceWidthRatio, viewport);\r\n let result: DonutChartAnimationResult;\r\n let shapes: D3.UpdateSelection;\r\n let highlightShapes: D3.UpdateSelection;\r\n let labelSettings = data.dataLabelsSettings;\r\n let labels: Label[] = [];\r\n if (labelSettings && labelSettings.show) {\r\n labels = this.createLabels();\r\n }\r\n if (!suppressAnimations) {\r\n let animationOptions: DonutChartAnimationOptions = {\r\n viewModel: data,\r\n colors: this.colors,\r\n graphicsContext: this.mainGraphicsContext,\r\n labelGraphicsContext: this.labelGraphicsContext,\r\n interactivityService: this.interactivityService,\r\n layout: layout,\r\n radius: this.radius,\r\n sliceWidthRatio: this.sliceWidthRatio,\r\n viewport: viewport,\r\n labels: labels,\r\n innerArcRadiusRatio: DonutChart.InnerArcRadiusRatio,\r\n };\r\n result = this.animator.animate(animationOptions);\r\n shapes = result.shapes;\r\n highlightShapes = result.highlightShapes;\r\n }\r\n if (suppressAnimations || result.failed) {\r\n shapes = DonutChart.drawDefaultShapes(this.svg, data, layout, this.colors, this.radius, this.interactivityService && this.interactivityService.hasSelection(), this.sliceWidthRatio, this.data.defaultDataPointColor);\r\n highlightShapes = DonutChart.drawDefaultHighlightShapes(this.svg, data, layout, this.colors, this.radius, this.sliceWidthRatio);\r\n NewDataLabelUtils.drawDefaultLabels(this.labelGraphicsContext, labels, false, true, true /*has tooltip */);\r\n NewDataLabelUtils.drawLabelLeaderLines(this.labelGraphicsContext, labels);\r\n }\r\n\r\n this.assignInteractions(shapes, highlightShapes, data);\r\n\r\n if (this.tooltipsEnabled) {\r\n TooltipManager.addTooltip(shapes, (tooltipEvent: TooltipEvent) => tooltipEvent.data.data.tooltipInfo);\r\n TooltipManager.addTooltip(highlightShapes, (tooltipEvent: TooltipEvent) => tooltipEvent.data.data.tooltipInfo);\r\n }\r\n }\r\n else {\r\n this.updateInternalToMove(data, duration);\r\n }\r\n\r\n SVGUtil.flushAllD3TransitionsIfNeeded(this.options);\r\n }\r\n\r\n private createLabels(): Label[] {\r\n let labelLayout = new DonutLabelLayout({\r\n maximumOffset: NewDataLabelUtils.maxLabelOffset,\r\n startingOffset: NewDataLabelUtils.startingLabelOffset\r\n }, this.donutProperties);\r\n\r\n let labelDataPoints: DonutLabelDataPoint[] = this.createLabelDataPoints();\r\n\r\n return labelLayout.layout(labelDataPoints);\r\n }\r\n\r\n private createLabelDataPoints(): DonutLabelDataPoint[] {\r\n let data = this.data;\r\n let labelDataPoints: DonutLabelDataPoint[] = [];\r\n let measureFormatterCache = dataLabelUtils.createColumnFormatterCacheManager();\r\n let alternativeScale: number = null;\r\n if (data.dataLabelsSettings.displayUnits === 0)\r\n alternativeScale = <number>d3.max(data.dataPoints, d => Math.abs(d.data.measure));\r\n\r\n for (let i = 0; i < this.data.dataPoints.length; i++) {\r\n let label = this.createLabelDataPoint(data.dataPoints[i], alternativeScale, measureFormatterCache);\r\n labelDataPoints.push(label);\r\n }\r\n return labelDataPoints;\r\n }\r\n\r\n private createLabelDataPoint(d: DonutArcDescriptor, alternativeScale: number, measureFormatterCache: IColumnFormatterCacheManager): DonutLabelDataPoint {\r\n let labelPoint = this.outerArc.centroid(d);\r\n let labelX = DonutLabelUtils.getXPositionForDonutLabel(labelPoint[0]);\r\n let labelY = labelPoint[1];\r\n let labelSettings = this.data.dataLabelsSettings;\r\n let measureFormatter = measureFormatterCache.getOrCreate(d.data.labelFormatString, labelSettings, alternativeScale);\r\n\r\n let position = labelX < 0 ? NewPointLabelPosition.Left : NewPointLabelPosition.Right;\r\n let pointPosition: LabelParentPoint = {\r\n point: {\r\n x: labelX,\r\n y: labelY,\r\n },\r\n validPositions: [position],\r\n radius: 0,\r\n };\r\n\r\n let outsideFill = labelSettings.labelColor ? labelSettings.labelColor : NewDataLabelUtils.defaultLabelColor;\r\n\r\n let dataLabel: string;\r\n let dataLabelSize: ISize;\r\n let categoryLabel: string;\r\n let categoryLabelSize: ISize;\r\n let textSize: ISize;\r\n let labelSettingsStyle = labelSettings.labelStyle;\r\n let fontSize = labelSettings.fontSize;\r\n let tooltip: string = \"\";\r\n\r\n if (labelSettingsStyle === labelStyle.both || labelSettingsStyle === labelStyle.data) {\r\n dataLabel = measureFormatter.format(d.data.originalHighlightValue != null ? d.data.originalHighlightValue : d.data.originalMeasure);\r\n dataLabelSize = NewDataLabelUtils.getTextSize(dataLabel, fontSize);\r\n }\r\n\r\n if (labelSettingsStyle === labelStyle.both || labelSettingsStyle === labelStyle.category) {\r\n categoryLabel = d.data.label;\r\n categoryLabelSize = NewDataLabelUtils.getTextSize(categoryLabel, fontSize);\r\n }\r\n\r\n switch (labelSettingsStyle) {\r\n case labelStyle.both:\r\n let text = categoryLabel + \" (\" + dataLabel + \")\";\r\n tooltip = text;\r\n textSize = NewDataLabelUtils.getTextSize(text, fontSize);\r\n break;\r\n case labelStyle.category:\r\n textSize = _.clone(categoryLabelSize);\r\n tooltip = categoryLabel;\r\n break;\r\n case labelStyle.data:\r\n textSize = _.clone(dataLabelSize);\r\n tooltip = dataLabel;\r\n break;\r\n }\r\n\r\n let leaderLinePoints = DonutLabelUtils.getLabelLeaderLineForDonutChart(d, this.donutProperties, pointPosition.point);\r\n let leaderLinesSize: ISize[] = DonutLabelUtils.getLabelLeaderLinesSizeForDonutChart(leaderLinePoints);\r\n\r\n return {\r\n isPreferred: true,\r\n text: \"\",\r\n tooltip: tooltip,\r\n textSize: textSize,\r\n outsideFill: outsideFill,\r\n fontSize: fontSize,\r\n identity: d.data.identity,\r\n parentShape: pointPosition,\r\n insideFill: NewDataLabelUtils.defaultInsideLabelColor,\r\n parentType: LabelDataPointParentType.Point,\r\n alternativeScale: alternativeScale,\r\n donutArcDescriptor: d,\r\n angle: (d.startAngle + d.endAngle) / 2 - (Math.PI / 2),\r\n dataLabel: dataLabel,\r\n dataLabelSize: dataLabelSize,\r\n categoryLabel: categoryLabel,\r\n categoryLabelSize: categoryLabelSize,\r\n leaderLinePoints: leaderLinePoints,\r\n linesSize: leaderLinesSize,\r\n };\r\n }\r\n\r\n private renderLegend(): void {\r\n if (!this.isInteractive) {\r\n let legendObjectProperties = this.data.legendObjectProperties;\r\n if (legendObjectProperties) {\r\n let legendData = this.data.legendData;\r\n LegendData.update(legendData, legendObjectProperties);\r\n let position = <string>legendObjectProperties[legendProps.position];\r\n if (position)\r\n this.legend.changeOrientation(LegendPosition[position]);\r\n\r\n this.legend.drawLegend(legendData, this.parentViewport);\r\n } else {\r\n this.legend.changeOrientation(LegendPosition.Top);\r\n this.legend.drawLegend({ dataPoints: [] }, this.parentViewport);\r\n }\r\n }\r\n }\r\n\r\n private addInteractiveLegendArrow(): void {\r\n if (!this.data || !this.data.dataPoints || this.data.dataPoints.length === 0) return;\r\n\r\n const arrowHeightOffset = 11;\r\n const arrowWidthOffset = 33 / 2;\r\n if (!this.interactiveLegendArrow) {\r\n let interactiveLegendArrow = this.svg.append('g');\r\n interactiveLegendArrow.append('path')\r\n .classed(DonutChart.InteractiveLegendArrowClassName, true)\r\n .attr('d', 'M1.5,2.6C0.65,1.15,1.85,0,3,0l27,0c1.65,0,2.35,1.15,1.5,2.6L18,26.45c-0.8,1.45-2.15,1.45-2.95,0L1.95,2.6z');\r\n this.interactiveLegendArrow = interactiveLegendArrow;\r\n }\r\n let viewport = this.currentViewport;\r\n // Calculate the offsets from the legend container to the arrow.\r\n let scaleRatio = this.getScaleForLegendArrow();\r\n\r\n let distanceBetweenLegendAndArrow = (viewport.height - 2 * this.radius) / 2 + (arrowHeightOffset * scaleRatio);\r\n let middleOfChart = viewport.width / 2 - (arrowWidthOffset * scaleRatio);\r\n\r\n this.interactiveLegendArrow.attr('transform', SVGUtil.translateAndScale(middleOfChart, distanceBetweenLegendAndArrow, scaleRatio));\r\n }\r\n\r\n private calculateSliceAngles(): void {\r\n let angles: number[] = [];\r\n let data = this.data.dataPoints;\r\n\r\n if (data.length === 0) {\r\n this.interactivityState.valueToAngleFactor = 0;\r\n this.interactivityState.sliceAngles = [];\r\n return;\r\n }\r\n\r\n let sum = 0;\r\n for (let i = 0, ilen = data.length; i < ilen; i++) {\r\n sum += data[i].data.percentage; // value is an absolute number\r\n }\r\n debug.assert(sum !== 0, 'sum of slices values cannot be zero');\r\n this.interactivityState.valueToAngleFactor = 360 / sum; // Calculate the ratio between 360 and the sum to know the angles to rotate by\r\n\r\n let currentAngle = 0;\r\n for (let i = 0, ilen = data.length; i < ilen; i++) {\r\n let relativeAngle = data[i].data.percentage * this.interactivityState.valueToAngleFactor;\r\n currentAngle += relativeAngle;\r\n angles.push((relativeAngle / 2) - currentAngle);\r\n }\r\n\r\n this.interactivityState.sliceAngles = angles;\r\n }\r\n\r\n private assignInteractions(slices: D3.Selection, highlightSlices: D3.Selection, data: DonutData): void {\r\n // assign interactions according to chart interactivity type\r\n if (this.isInteractive) {\r\n this.assignInteractiveChartInteractions(slices);\r\n }\r\n else if (this.interactivityService) {\r\n let dataPoints = data.dataPoints.map((value: DonutArcDescriptor) => value.data);\r\n let behaviorOptions: DonutBehaviorOptions = {\r\n clearCatcher: this.clearCatcher,\r\n slices: slices,\r\n highlightSlices: highlightSlices,\r\n allowDrilldown: this.allowDrilldown,\r\n visual: this,\r\n hasHighlights: data.hasHighlights,\r\n };\r\n\r\n this.interactivityService.bind(dataPoints, this.behavior, behaviorOptions);\r\n }\r\n }\r\n\r\n private assignInteractiveChartInteractions(slice: D3.Selection) {\r\n let svg = this.svg;\r\n\r\n this.interactivityState.interactiveChosenSliceFinishedSetting = true;\r\n let svgRect = svg.node().getBoundingClientRect();\r\n this.interactivityState.donutCenter = { x: svgRect.left + svgRect.width / 2, y: svgRect.top + svgRect.height / 2 }; // Center of the donut chart\r\n this.interactivityState.totalDragAngleDifference = 0;\r\n this.interactivityState.currentRotate = 0;\r\n\r\n this.calculateSliceAngles();\r\n\r\n // Set the on click method for the slices so thsete pie chart will turn according to each slice's corresponding angle [the angle its on top]\r\n slice.on('click', (d: DonutArcDescriptor, clickedIndex: number) => {\r\n if (d3.event.defaultPrevented) return; // click was suppressed, for example from drag event\r\n this.setInteractiveChosenSlice(clickedIndex);\r\n });\r\n\r\n // Set the drag events\r\n let drag = d3.behavior.drag()\r\n .origin(Object)\r\n .on('dragstart', () => this.interactiveDragStart())\r\n .on('drag', () => this.interactiveDragMove())\r\n .on('dragend', () => this.interactiveDragEnd());\r\n svg\r\n .style('touch-action', 'none')\r\n .call(drag);\r\n }\r\n\r\n /**\r\n * Get the angle (in degrees) of the drag event coordinates.\r\n * The angle is calculated against the plane of the center of the donut\r\n * (meaning, when the center of the donut is at (0,0) coordinates).\r\n */\r\n private getAngleFromDragEvent(): number {\r\n let interactivityState = this.interactivityState;\r\n\r\n // get pageX and pageY (coordinates of the drag event) according to event type\r\n let pageX, pageY;\r\n let sourceEvent = <any>d3.event.sourceEvent;\r\n // check if that's a touch event or not\r\n if (sourceEvent.type.toLowerCase().indexOf('touch') !== -1) {\r\n if (sourceEvent.touches.length !== 1) return null; // in case there isn't a single touch - return null and do nothing.\r\n // take the first, single, touch surface.\r\n let touch = sourceEvent.touches[0];\r\n pageX = touch.pageX;\r\n pageY = touch.pageY;\r\n } else {\r\n pageX = sourceEvent.pageX;\r\n pageY = sourceEvent.pageY;\r\n }\r\n\r\n // Adjust the coordinates, putting the donut center as the (0,0) coordinates\r\n let adjustedCoordinates = { x: pageX - interactivityState.donutCenter.x, y: -pageY + interactivityState.donutCenter.y };\r\n // Move to polar axis - take only the angle (theta), and convert to degrees\r\n let angleToThePlane = Math.atan2(adjustedCoordinates.y, adjustedCoordinates.x) * 180 / Math.PI;\r\n return angleToThePlane;\r\n }\r\n\r\n private interactiveDragStart(): void {\r\n this.interactivityState.totalDragAngleDifference = 0;\r\n this.interactivityState.previousDragAngle = this.getAngleFromDragEvent();\r\n }\r\n\r\n private interactiveDragMove(): void {\r\n let data = this.data.dataPoints;\r\n let viewport = this.currentViewport;\r\n\r\n let interactivityState = this.interactivityState;\r\n\r\n if (interactivityState.interactiveChosenSliceFinishedSetting === true) {\r\n // get current angle from the drag event\r\n let currentDragAngle = this.getAngleFromDragEvent();\r\n if (!currentDragAngle) return; // if no angle was returned, do nothing\r\n // compare it to the previous drag event angle\r\n let angleDragDiff = interactivityState.previousDragAngle - currentDragAngle;\r\n\r\n interactivityState.totalDragAngleDifference += angleDragDiff;\r\n interactivityState.previousDragAngle = currentDragAngle;\r\n\r\n // Rotate the chart by the difference in angles\r\n interactivityState.currentRotate += angleDragDiff;\r\n\r\n // Rotate the chart to the current rotate angle\r\n this.svg.select('g')\r\n .attr('transform', SVGUtil.translateAndRotate(viewport.width / 2, viewport.height / 2, 0, 0, this.interactivityState.currentRotate));\r\n\r\n let currentHigherLimit = data[0].data.percentage * interactivityState.valueToAngleFactor;\r\n let currentAngle = interactivityState.currentRotate <= 0 ? (interactivityState.currentRotate * -1) % 360 : (360 - (interactivityState.currentRotate % 360));\r\n\r\n interactivityState.currentIndexDrag = 0;\r\n //consider making this ++interactivityState.currentIndexDrag ? then you don't need the if statement, the interactivityState.currentIndexDrag +1 and interactivityState.currentIndexDrag++\r\n // Check the current index according to the angle \r\n let dataLength = data.length;\r\n while ((interactivityState.currentIndexDrag < dataLength) && (currentAngle > currentHigherLimit)) {\r\n if (interactivityState.currentIndexDrag < (dataLength - 1)) {\r\n currentHigherLimit += (data[interactivityState.currentIndexDrag + 1].data.percentage * interactivityState.valueToAngleFactor);\r\n }\r\n interactivityState.currentIndexDrag++;\r\n }\r\n\r\n // If the index changed update the legend and opacity\r\n if (interactivityState.currentIndexDrag !== interactivityState.previousIndexDrag) {\r\n interactivityState.interactiveLegend.updateLegend(interactivityState.currentIndexDrag);\r\n // set the opacticity of the top slice to full and the others to semi-transparent\r\n this.svg.selectAll('.slice').attr('opacity', (d, index) => {\r\n return index === interactivityState.currentIndexDrag ? DonutChart.OpaqueOpacity : DonutChart.SemiTransparentOpacity;\r\n });\r\n interactivityState.previousIndexDrag = interactivityState.currentIndexDrag;\r\n }\r\n }\r\n }\r\n\r\n private interactiveDragEnd(): void {\r\n // If totalDragDifference was changed, means we have a drag event (compared to a click event)\r\n if (this.interactivityState.totalDragAngleDifference !== 0) {\r\n this.setInteractiveChosenSlice(this.interactivityState.currentIndexDrag);\r\n // drag happened - disable click event\r\n d3.event.sourceEvent.stopPropagation();\r\n }\r\n }\r\n\r\n private updateInternalToMove(data: DonutData, duration: number = 0) {\r\n // Cache for performance\r\n let svg = this.svg;\r\n let pie = this.pie;\r\n let key = this.key;\r\n let arc = this.arc;\r\n let radius = this.radius;\r\n let previousRadius = this.previousRadius;\r\n let sliceWidthRatio = this.sliceWidthRatio;\r\n\r\n let existingData = this.svg.select('.slices')\r\n .selectAll('path' + DonutChart.sliceClass.selector)\r\n .data().map((d: DonutArcDescriptor) => d.data);\r\n\r\n if (existingData.length === 0) {\r\n existingData = data.dataPointsToDeprecate;\r\n }\r\n\r\n let is = this.mergeDatasets(existingData, data.dataPointsToDeprecate);\r\n\r\n let slice = svg.select('.slices')\r\n .selectAll('path' + DonutChart.sliceClass.selector)\r\n .data(pie(data.dataPointsToDeprecate), key);\r\n\r\n slice.enter()\r\n .insert('path')\r\n .classed(DonutChart.sliceClass.class, true)\r\n .each(function (d) { this._current = d; });\r\n\r\n slice = svg.select('.slices')\r\n .selectAll('path' + DonutChart.sliceClass.selector)\r\n .data(pie(is), key);\r\n\r\n let innerRadius = radius * sliceWidthRatio;\r\n DonutChart.isSingleColor(data.dataPoints);\r\n\r\n slice\r\n .style('fill', (d: DonutArcDescriptor) => d.data.color)\r\n .style('fill-opacity', (d: DonutArcDescriptor) => ColumnUtil.getFillOpacity(d.data.selected, false, false, data.hasHighlights))\r\n .style('stroke', 'white')\r\n .style('stroke-dasharray', (d: DonutArcDescriptor) => DonutChart.drawStrokeForDonutChart(radius, DonutChart.InnerArcRadiusRatio, d, sliceWidthRatio))\r\n .style('stroke-width', (d: DonutArcDescriptor) => d.data.strokeWidth)\r\n .transition().duration(duration)\r\n .attrTween('d', function (d) {\r\n let i = d3.interpolate(this._current, d),\r\n k = d3.interpolate(previousRadius * DonutChart.InnerArcRadiusRatio\r\n , radius * DonutChart.InnerArcRadiusRatio);\r\n\r\n this._current = i(0);\r\n\r\n return function (t) {\r\n return arc.innerRadius(innerRadius).outerRadius(k(t))(i(t));\r\n };\r\n });\r\n\r\n slice = svg.select('.slices')\r\n .selectAll('path' + DonutChart.sliceClass.selector)\r\n .data(pie(data.dataPointsToDeprecate), key);\r\n\r\n slice.exit()\r\n .transition()\r\n .delay(duration)\r\n .duration(0)\r\n .remove();\r\n\r\n // For interactive chart, there shouldn't be slice labels (as you have the legend).\r\n if (!this.isInteractive) {\r\n let labelSettings = data.dataLabelsSettings;\r\n let labels: Label[] = [];\r\n if (labelSettings && labelSettings.show) {\r\n labels = this.createLabels();\r\n }\r\n NewDataLabelUtils.drawDefaultLabels(this.labelGraphicsContext, labels, false, true);\r\n NewDataLabelUtils.drawLabelLeaderLines(this.labelGraphicsContext, labels);\r\n }\r\n let highlightSlices = undefined;\r\n if (data.hasHighlights) {\r\n // Draw partial highlight slices.\r\n highlightSlices = svg\r\n .select('.slices')\r\n .selectAll('path' + DonutChart.sliceHighlightClass.selector)\r\n .data(pie(data.dataPointsToDeprecate), key);\r\n\r\n highlightSlices\r\n .enter()\r\n .insert('path')\r\n .classed(DonutChart.sliceHighlightClass.class, true)\r\n .each(function (d) { this._current = d; });\r\n\r\n DonutChart.isSingleColor(data.dataPoints);\r\n\r\n highlightSlices\r\n .style('fill', (d: DonutArcDescriptor) => d.data.color)\r\n .style('fill-opacity', 1.0)\r\n .style('stroke', 'white')\r\n .style('stroke-dasharray', (d: DonutArcDescriptor) => DonutChart.drawStrokeForDonutChart(radius, DonutChart.InnerArcRadiusRatio, d, sliceWidthRatio, d.data.highlightRatio))\r\n .style('stroke-width', (d: DonutArcDescriptor) => d.data.highlightRatio === 0 ? 0 : d.data.strokeWidth)\r\n .transition().duration(duration)\r\n .attrTween('d', function (d: DonutArcDescriptor) {\r\n let i = d3.interpolate(this._current, d),\r\n k = d3.interpolate(\r\n previousRadius * DonutChart.InnerArcRadiusRatio,\r\n DonutChart.getHighlightRadius(radius, sliceWidthRatio, d.data.highlightRatio));\r\n\r\n this._current = i(0);\r\n\r\n return function (t) {\r\n return arc.innerRadius(innerRadius).outerRadius(k(t))(i(t));\r\n };\r\n });\r\n\r\n highlightSlices\r\n .exit()\r\n .transition()\r\n .delay(duration)\r\n .duration(0)\r\n .remove();\r\n }\r\n else {\r\n svg\r\n .selectAll('path' + DonutChart.sliceHighlightClass.selector)\r\n .transition()\r\n .delay(duration)\r\n .duration(0)\r\n .remove();\r\n }\r\n\r\n this.assignInteractions(slice, highlightSlices, data);\r\n\r\n if (this.tooltipsEnabled) {\r\n TooltipManager.addTooltip(slice, (tooltipEvent: TooltipEvent) => tooltipEvent.data.data.tooltipInfo);\r\n if (data.hasHighlights) {\r\n TooltipManager.addTooltip(highlightSlices, (tooltipEvent: TooltipEvent) => tooltipEvent.data.data.tooltipInfo);\r\n }\r\n }\r\n\r\n SVGUtil.flushAllD3TransitionsIfNeeded(this.options);\r\n\r\n if (this.isInteractive) {\r\n this.addInteractiveLegendArrow();\r\n this.interactivityState.interactiveLegend.drawLegend(this.data.dataPointsToDeprecate);\r\n this.setInteractiveChosenSlice(this.interactivityState.lastChosenInteractiveSliceIndex ? this.interactivityState.lastChosenInteractiveSliceIndex : 0);\r\n }\r\n }\r\n\r\n public static drawDefaultShapes(graphicsContext: D3.Selection, donutData: DonutData, layout: DonutLayout, colors: IDataColorPalette, radius: number, hasSelection: boolean, sliceWidthRatio: number, defaultColor?: string): D3.UpdateSelection {\r\n let shapes = graphicsContext.select('.slices')\r\n .selectAll('path' + DonutChart.sliceClass.selector)\r\n .data(donutData.dataPoints, (d: DonutArcDescriptor) => d.data.identity.getKey());\r\n\r\n shapes.enter()\r\n .insert('path')\r\n .classed(DonutChart.sliceClass.class, true);\r\n\r\n DonutChart.isSingleColor(donutData.dataPoints);\r\n\r\n shapes\r\n .style('fill', (d: DonutArcDescriptor) => d.data.color)\r\n .style('fill-opacity', (d: DonutArcDescriptor) => ColumnUtil.getFillOpacity(d.data.selected, false, hasSelection, donutData.hasHighlights))\r\n .style('stroke-dasharray', (d: DonutArcDescriptor) => DonutChart.drawStrokeForDonutChart(radius, DonutChart.InnerArcRadiusRatio, d, sliceWidthRatio))\r\n .style('stroke-width', (d: DonutArcDescriptor) => d.data.strokeWidth)\r\n .attr(layout.shapeLayout);\r\n\r\n shapes.exit()\r\n .remove();\r\n\r\n return shapes;\r\n }\r\n\r\n public static drawDefaultHighlightShapes(graphicsContext: D3.Selection, donutData: DonutData, layout: DonutLayout, colors: IDataColorPalette, radius: number, sliceWidthRatio: number): D3.UpdateSelection {\r\n let shapes = graphicsContext.select('.slices')\r\n .selectAll('path' + DonutChart.sliceHighlightClass.selector)\r\n .data(donutData.dataPoints.filter((value: DonutArcDescriptor) => value.data.highlightRatio != null), (d: DonutArcDescriptor) => d.data.identity.getKey());\r\n\r\n shapes.enter()\r\n .insert('path')\r\n .classed(DonutChart.sliceHighlightClass.class, true)\r\n .each(function (d) { this._current = d; });\r\n\r\n DonutChart.isSingleColor(donutData.dataPoints);\r\n\r\n shapes\r\n .style('fill', (d: DonutArcDescriptor) => d.data.color)\r\n .style('fill-opacity', (d: DonutArcDescriptor) => ColumnUtil.getFillOpacity(d.data.selected, true, false, donutData.hasHighlights))\r\n .style('stroke', 'white')\r\n .style('stroke-dasharray', (d: DonutArcDescriptor) => DonutChart.drawStrokeForDonutChart(radius, DonutChart.InnerArcRadiusRatio, d, sliceWidthRatio, d.data.highlightRatio))\r\n .style('stroke-width', (d: DonutArcDescriptor) => d.data.highlightRatio === 0 ? 0 : d.data.strokeWidth)\r\n .attr(layout.highlightShapeLayout);\r\n\r\n shapes.exit()\r\n .remove();\r\n\r\n return shapes;\r\n }\r\n \r\n /**\r\n Set true to the last data point when all slices have the same color\r\n */\r\n public static isSingleColor(dataPoints: DonutArcDescriptor[]): void {\r\n if (dataPoints.length > 1) {\r\n let lastPoint = dataPoints.length - 1;\r\n dataPoints[lastPoint].data.isLastInDonut = dataPoints[lastPoint].data.color === dataPoints[0].data.color;\r\n }\r\n }\r\n\r\n public static drawStrokeForDonutChart(radius: number, innerArcRadiusRatio: number, d: DonutArcDescriptor, sliceWidthRatio: number, highlightRatio: number = 1): string {\r\n let sliceRadius = radius * innerArcRadiusRatio * highlightRatio;\r\n let sliceArc = (d.endAngle - d.startAngle) * sliceRadius;\r\n let sectionWithoutStroke: number;\r\n let sectionWithStroke: number;\r\n\r\n /*Donut chart*/\r\n if (sliceWidthRatio) {\r\n let innerRadius = radius * sliceWidthRatio;\r\n let outerRadius = highlightRatio * radius * (DonutChart.InnerArcRadiusRatio - sliceWidthRatio);\r\n let innerSliceArc = (d.endAngle - d.startAngle) * innerRadius;\r\n if (d.data.highlightRatio)\r\n sliceArc = (d.endAngle - d.startAngle) * (outerRadius + innerRadius);\r\n\r\n if (d.data.isLastInDonut) {\r\n //if all slices have the same color, the stroke of the last slice needs to be drawn on both radiuses\r\n return 0 + \" \" + sliceArc + \" \" + outerRadius + \" \" + innerSliceArc + \" \" + outerRadius;\r\n }\r\n sectionWithoutStroke = sliceArc + outerRadius + innerSliceArc;\r\n sectionWithStroke = outerRadius;\r\n }\r\n\r\n /*Pie Chart*/\r\n else {\r\n if (d.data.isLastInDonut) {\r\n //if all slices have the same color, the stroke of the last slice needs to be drawn on both radiuses\r\n sectionWithoutStroke = sliceArc;\r\n sectionWithStroke = sliceRadius * 2;\r\n }\r\n else {\r\n sectionWithoutStroke = sliceArc + sliceRadius;\r\n sectionWithStroke = sliceRadius;\r\n }\r\n }\r\n\r\n return 0 + \" \" + sectionWithoutStroke + \" \" + sectionWithStroke;\r\n }\r\n\r\n public onClearSelection() {\r\n if (this.interactivityService)\r\n this.interactivityService.clearSelection();\r\n }\r\n\r\n public static getLayout(radius: number, sliceWidthRatio: number, viewport: IViewport): DonutLayout {\r\n let innerRadius = radius * sliceWidthRatio;\r\n let arc = d3.svg.arc().innerRadius(innerRadius);\r\n let arcWithRadius = arc.outerRadius(radius * DonutChart.InnerArcRadiusRatio);\r\n return {\r\n shapeLayout: {\r\n d: (d: DonutArcDescriptor) => {\r\n return arcWithRadius(d);\r\n }\r\n },\r\n highlightShapeLayout: {\r\n d: (d: DonutArcDescriptor) => {\r\n let highlightArc = arc.outerRadius(DonutChart.getHighlightRadius(radius, sliceWidthRatio, d.data.highlightRatio));\r\n return highlightArc(d);\r\n }\r\n },\r\n zeroShapeLayout: {\r\n d: (d: DonutArcDescriptor) => {\r\n let zeroWithZeroRadius = arc.outerRadius(innerRadius || DonutChart.EffectiveZeroValue);\r\n return zeroWithZeroRadius(d);\r\n }\r\n },\r\n };\r\n }\r\n\r\n private static getHighlightRadius(radius: number, sliceWidthRatio: number, highlightRatio: number): number {\r\n let innerRadius = radius * sliceWidthRatio;\r\n return innerRadius + highlightRatio * radius * (DonutChart.InnerArcRadiusRatio - sliceWidthRatio);\r\n }\r\n\r\n public static cullDataByViewport(dataPoints: DonutDataPoint[], maxValue: number, viewport: IViewport): DonutDataPoint[] {\r\n let estimatedRadius = Math.min(viewport.width, viewport.height) / 2;\r\n // Ratio of slice too small to show (invisible) = invisbleArcLength / circumference\r\n let cullRatio = this.invisibleArcLengthInPixels / (estimatedRadius * DonutChart.twoPi);\r\n let cullableValue = cullRatio * maxValue;\r\n let culledDataPoints: DonutDataPoint[] = [];\r\n let prevPointColor: string;\r\n for (let datapoint of dataPoints) {\r\n if (datapoint.measure >= cullableValue) {\r\n //updates the stroke width\r\n datapoint.strokeWidth = prevPointColor === datapoint.color ? 1 : 0;\r\n prevPointColor = datapoint.color;\r\n culledDataPoints.push(datapoint);\r\n }\r\n }\r\n\r\n return culledDataPoints;\r\n }\r\n }\r\n\r\n /**\r\n * This class is an interactive legend for the Donut Chart. \r\n * \r\n * Features: It is scrollable indefinitely, using drag gesture\r\n * when you interact with it, it updates the donut chart itself.\r\n */\r\n class DonutChartInteractiveLegend {\r\n\r\n private static LegendContainerClassName = 'legend-container';\r\n private static LegendContainerSelector = '.legend-container';\r\n private static LegendItemClassName = 'legend-item';\r\n private static LegendItemSelector = '.legend-item';\r\n private static LegendItemCategoryClassName = 'category';\r\n private static LegendItemPercentageClassName = 'percentage';\r\n private static LegendItemValueClassName = 'value';\r\n\r\n private static MaxLegendItemBoxSize = 160;\r\n private static ItemMargin = 30; // Margin between items\r\n private static MinimumSwipeDX = 15; // Minimup swipe gesture to create a change in the legend\r\n private static MinimumItemsInLegendForCycled = 3; // Minimum items in the legend before we cycle it\r\n\r\n private donutChart: DonutChart;\r\n private legendContainerParent: D3.Selection;\r\n private legendContainer: D3.Selection;\r\n private legendContainerWidth: number;\r\n private data: DonutDataPoint[];\r\n private colors: IDataColorPalette;\r\n private visualInitOptions: VisualInitOptions;\r\n\r\n private currentNumberOfLegendItems: number;\r\n private currentIndex: number;\r\n private leftMostIndex: number;\r\n private rightMostIndex: number;\r\n private currentXOffset: number;\r\n private legendItemsPositions: { startX: number; boxWidth: number; }[];\r\n private legendTransitionAnimationDuration: number;\r\n\r\n constructor(donutChart: DonutChart, legendContainer: D3.Selection, colors: IDataColorPalette, visualInitOptions: VisualInitOptions, settings?: DonutChartSettings) {\r\n this.legendContainerParent = legendContainer;\r\n this.colors = colors;\r\n this.donutChart = donutChart;\r\n this.visualInitOptions = visualInitOptions;\r\n this.legendItemsPositions = [];\r\n\r\n this.legendTransitionAnimationDuration = settings && settings.legendTransitionAnimationDuration ? settings.legendTransitionAnimationDuration : 0;\r\n }\r\n\r\n public drawLegend(data: DonutDataPoint[]): void {\r\n this.data = data;\r\n\r\n this.currentNumberOfLegendItems = data.length;\r\n this.currentIndex = 0;\r\n this.leftMostIndex = 0;\r\n this.rightMostIndex = data.length - 1;\r\n\r\n if (this.legendContainerParent.select(DonutChartInteractiveLegend.LegendContainerSelector).empty()) {\r\n this.legendContainer = this.legendContainerParent.append('div').classed(DonutChartInteractiveLegend.LegendContainerClassName, true);\r\n }\r\n\r\n let legendItems = this.legendContainer.selectAll(DonutChartInteractiveLegend.LegendItemSelector).data(data);\r\n let legendContainerWidth = this.legendContainerWidth = this.legendContainer.node().getBoundingClientRect().width;\r\n let initialXOffset = legendContainerWidth / 2 - (legendContainerWidth * 0.4 / 2) + DonutChartInteractiveLegend.ItemMargin;\r\n let currX = initialXOffset;\r\n this.currentXOffset = initialXOffset;\r\n\r\n // Given the legend item div, create the item values (category, percentage and measure) on top of it.\r\n let createLegendItem = (itemDiv: JQuery, datum: DonutDataPoint) => {\r\n // position the legend item\r\n itemDiv\r\n .attr('data-legend-index', datum.index) // assign index for later use\r\n .css({\r\n 'position': 'absolute',\r\n 'left': currX,\r\n //'margin-right': DonutChartInteractiveLegend.ItemMargin + 'px',\r\n });\r\n\r\n // Add the category, percentage and value\r\n let itemCategory = valueFormatter.format(datum.label);\r\n let itemValue = valueFormatter.format(datum.originalMeasure, datum.measureFormat);\r\n let itemPercentage = valueFormatter.format(datum.percentage, '0.00 %;-0.00 %;0.00 %');\r\n let itemColor = datum.color;\r\n\r\n // Create basic spans for width calculations\r\n let itemValueSpan = DonutChartInteractiveLegend.createBasicLegendItemSpan(DonutChartInteractiveLegend.LegendItemValueClassName, itemValue, 11);\r\n let itemCategorySpan = DonutChartInteractiveLegend.createBasicLegendItemSpan(DonutChartInteractiveLegend.LegendItemCategoryClassName, itemCategory, 11);\r\n let itemPercentageSpan = DonutChartInteractiveLegend.createBasicLegendItemSpan(DonutChartInteractiveLegend.LegendItemPercentageClassName, itemPercentage, 20);\r\n\r\n // Calculate Legend Box size according to widths and set the width accordingly\r\n let valueSpanWidth = DonutChartInteractiveLegend.spanWidth(itemValueSpan);\r\n let categorySpanWidth = DonutChartInteractiveLegend.spanWidth(itemCategorySpan);\r\n let precentageSpanWidth = DonutChartInteractiveLegend.spanWidth(itemPercentageSpan);\r\n let currentLegendBoxWidth = DonutChartInteractiveLegend.legendBoxSize(valueSpanWidth, categorySpanWidth, precentageSpanWidth);\r\n itemDiv.css('width', currentLegendBoxWidth);\r\n\r\n // Calculate margins so that all the spans will be placed in the middle\r\n let getLeftValue = (spanWidth: number) => {\r\n return currentLegendBoxWidth - spanWidth > 0 ? (currentLegendBoxWidth - spanWidth) / 2 : 0;\r\n };\r\n let marginLeftValue = getLeftValue(valueSpanWidth);\r\n let marginLeftCategory = getLeftValue(categorySpanWidth);\r\n let marginLeftPrecentage = getLeftValue(precentageSpanWidth);\r\n\r\n // Create the actual spans with the right styling and margins so it will be center aligned and add them\r\n DonutChartInteractiveLegend.createLegendItemSpan(itemCategorySpan, marginLeftCategory);\r\n DonutChartInteractiveLegend.createLegendItemSpan(itemValueSpan, marginLeftValue);\r\n DonutChartInteractiveLegend.createLegendItemSpan(itemPercentageSpan, marginLeftPrecentage).css('color', itemColor);\r\n\r\n itemDiv.append(itemCategorySpan);\r\n itemDiv.append(itemPercentageSpan);\r\n itemDiv.append(itemValueSpan);\r\n\r\n this.legendItemsPositions.push({\r\n startX: currX,\r\n boxWidth: currentLegendBoxWidth,\r\n });\r\n currX += currentLegendBoxWidth + DonutChartInteractiveLegend.ItemMargin;\r\n };\r\n\r\n // Create the Legend Items\r\n legendItems.enter()\r\n .insert('div')\r\n .classed(DonutChartInteractiveLegend.LegendItemClassName, true)\r\n .each(function (d: DonutDataPoint) {\r\n createLegendItem($(this), d);\r\n });\r\n\r\n legendItems.exit().remove();\r\n\r\n // Assign interactions on the legend\r\n this.assignInteractions();\r\n }\r\n\r\n public updateLegend(sliceIndex): void {\r\n let legendContainerWidth = this.legendContainerWidth;\r\n\r\n this.currentIndex = sliceIndex;\r\n // \"rearrange\" legend items if needed, so we would have contnious endless scrolling\r\n this.updateLabelBlocks(sliceIndex);\r\n let legendTransitionAnimationDuration = this.legendTransitionAnimationDuration;\r\n // Transform the legend so that the selected slice would be in the middle\r\n let nextXOffset = (this.legendItemsPositions[sliceIndex].startX + (this.legendItemsPositions[sliceIndex].boxWidth / 2) - (legendContainerWidth / 2)) * (-1);\r\n this.legendContainer\r\n .transition()\r\n .styleTween('-webkit-transform', (d: any, i: number, a: any) => {\r\n return d3.interpolate(\r\n SVGUtil.translateWithPixels(this.currentXOffset, 0),\r\n SVGUtil.translateWithPixels(nextXOffset, 0));\r\n })\r\n .styleTween('transform', (d: any, i: number, a: any) => {\r\n return d3.interpolate(\r\n SVGUtil.translateWithPixels(this.currentXOffset, 0),\r\n SVGUtil.translateWithPixels(nextXOffset, 0));\r\n })\r\n .duration(legendTransitionAnimationDuration)\r\n .ease('bounce')\r\n .each('end', () => {\r\n this.currentXOffset = nextXOffset;\r\n });\r\n SVGUtil.flushAllD3TransitionsIfNeeded(this.visualInitOptions);\r\n }\r\n\r\n private assignInteractions() {\r\n let currentDX = 0; // keep how much drag had happened\r\n let hasChanged = false; // flag to indicate if we changed the \"center\" value in the legend. We only change it once per swipe.\r\n\r\n let dragStart = () => {\r\n currentDX = 0; // start of drag gesture\r\n hasChanged = false;\r\n };\r\n\r\n let dragMove = () => {\r\n currentDX += d3.event.dx;\r\n // Detect if swipe occured and if the index already changed in this drag\r\n if (hasChanged || Math.abs(currentDX) < DonutChartInteractiveLegend.MinimumSwipeDX) return;\r\n\r\n let dragDirectionLeft = (currentDX < 0);\r\n this.dragLegend(dragDirectionLeft);\r\n hasChanged = true;\r\n };\r\n\r\n let drag = d3.behavior.drag()\r\n .origin(Object)\r\n .on('drag', dragMove)\r\n .on('dragstart', dragStart);\r\n\r\n this.legendContainer\r\n .style({\r\n 'touch-action': 'none',\r\n 'cursor': 'pointer'\r\n })\r\n .call(drag);\r\n }\r\n\r\n private dragLegend(dragDirectionLeft: boolean): void {\r\n\r\n if (this.currentNumberOfLegendItems > (DonutChartInteractiveLegend.MinimumItemsInLegendForCycled - 1)) {\r\n this.currentIndex = this.getCyclingCurrentIndex(dragDirectionLeft);\r\n } else {\r\n if (this.shouldChangeIndexInNonCycling(dragDirectionLeft)) {\r\n if (dragDirectionLeft) {\r\n this.currentIndex++;\r\n } else {\r\n this.currentIndex--;\r\n }\r\n }\r\n }\r\n this.donutChart.setInteractiveChosenSlice(this.currentIndex);\r\n }\r\n\r\n private shouldChangeIndexInNonCycling(dragDirectionLeft: boolean): boolean {\r\n if ((this.currentIndex === 0 && !dragDirectionLeft) || (this.currentIndex === (this.currentNumberOfLegendItems - 1) && dragDirectionLeft)) {\r\n return false;\r\n }\r\n return true;\r\n }\r\n\r\n private getCyclingCurrentIndex(dragDirectionLeft: boolean): number {\r\n let dataLen = this.data.length;\r\n let delta = dragDirectionLeft ? 1 : -1;\r\n let newIndex = (this.currentIndex + delta) % (dataLen || 1); // modolu of negative number stays negative on javascript\r\n return (newIndex < 0) ? newIndex + dataLen : newIndex;\r\n }\r\n\r\n private updateLegendItemsBlocks(rightSidedShift: boolean, numberOfLegendItemsBlocksToShift: number) {\r\n let legendContainer$ = $(this.legendContainer[0]);\r\n\r\n if (rightSidedShift) {\r\n let smallestItem = legendContainer$.find('[data-legend-index=' + this.leftMostIndex + ']');\r\n smallestItem.remove().insertAfter(legendContainer$.find('[data-legend-index=' + this.rightMostIndex + ']'));\r\n let newX = this.legendItemsPositions[this.rightMostIndex].startX + this.legendItemsPositions[this.rightMostIndex].boxWidth + DonutChartInteractiveLegend.ItemMargin;\r\n this.legendItemsPositions[this.leftMostIndex].startX = newX;\r\n smallestItem.css('left', newX);\r\n\r\n this.rightMostIndex = this.leftMostIndex;\r\n this.leftMostIndex = (this.leftMostIndex + 1) % this.data.length;\r\n } else {\r\n let highestItem = legendContainer$.find('[data-legend-index=' + this.rightMostIndex + ']');\r\n highestItem.remove().insertBefore(legendContainer$.find('[data-legend-index=' + this.leftMostIndex + ']'));\r\n let newX = this.legendItemsPositions[this.leftMostIndex].startX - this.legendItemsPositions[this.rightMostIndex].boxWidth - DonutChartInteractiveLegend.ItemMargin;\r\n this.legendItemsPositions[this.rightMostIndex].startX = newX;\r\n highestItem.css('left', newX);\r\n\r\n this.leftMostIndex = this.rightMostIndex;\r\n this.rightMostIndex = (this.rightMostIndex - 1) === -1 ? (this.legendItemsPositions.length - 1) : (this.rightMostIndex - 1);\r\n }\r\n\r\n if ((numberOfLegendItemsBlocksToShift - 1) !== 0) {\r\n this.updateLegendItemsBlocks(rightSidedShift, (numberOfLegendItemsBlocksToShift - 1));\r\n }\r\n }\r\n\r\n /** Update the legend items, allowing for endless rotation */\r\n private updateLabelBlocks(index: number) {\r\n\r\n if (this.currentNumberOfLegendItems > DonutChartInteractiveLegend.MinimumItemsInLegendForCycled) {\r\n // The idea of the four if's is to keep two labels before and after the current one so it will fill the screen.\r\n\r\n // If the index of the slice is the highest currently availble add 2 labels \"ahead\" of it\r\n if (this.rightMostIndex === index) this.updateLegendItemsBlocks(true, 2);\r\n\r\n // If the index of the slice is the lowest currently availble add 2 labels \"before\" it\r\n if (this.leftMostIndex === index) this.updateLegendItemsBlocks(false, 2);\r\n\r\n // If the index of the slice is the second highest currently availble add a labels \"ahead\" of it\r\n if (this.rightMostIndex === (index + 1) || ((this.rightMostIndex === 0) && (index === (this.currentNumberOfLegendItems - 1)))) this.updateLegendItemsBlocks(true, 1);\r\n\r\n // If the index of the slice is the second lowest currently availble add a labels \"before\" it\r\n if (this.leftMostIndex === (index - 1) || ((this.leftMostIndex === (this.currentNumberOfLegendItems - 1) && (index === 0)))) this.updateLegendItemsBlocks(false, 1);\r\n\r\n } else {\r\n\r\n if (this.currentNumberOfLegendItems === DonutChartInteractiveLegend.MinimumItemsInLegendForCycled) {\r\n // If the index of the slice is the highest currently availble add a label \"ahead\" of it\r\n if (this.rightMostIndex === index) this.updateLegendItemsBlocks(true, 1);\r\n\r\n // If the index of the slice is the lowest currently availble add a label \"before\" it\r\n if (this.leftMostIndex === index) this.updateLegendItemsBlocks(false, 1);\r\n }\r\n }\r\n }\r\n\r\n private static createBasicLegendItemSpan(spanClass: string, text: string, fontSize: number): JQuery {\r\n return $('<span/>')\r\n .addClass(spanClass)\r\n .css({\r\n 'white-space': 'nowrap',\r\n 'font-size': fontSize + 'px',\r\n })\r\n .text(text);\r\n }\r\n\r\n /** This method alters the given span and sets it to the final legen item span style. */\r\n private static createLegendItemSpan(existingSpan: JQuery, marginLeft: number): JQuery {\r\n existingSpan\r\n .css({\r\n 'overflow': 'hidden',\r\n 'text-overflow': 'ellipsis',\r\n 'display': 'inline-block',\r\n 'width': '100%',\r\n 'margin-left': marginLeft\r\n });\r\n return existingSpan;\r\n }\r\n\r\n /** Caclulte entire legend box size according to its building spans */\r\n private static legendBoxSize(valueSpanWidth: number, categorySpanWidth: number, precentageSpanWidth: number): number {\r\n let boxSize = valueSpanWidth > categorySpanWidth ? valueSpanWidth : categorySpanWidth;\r\n boxSize = boxSize > precentageSpanWidth ? boxSize : precentageSpanWidth;\r\n boxSize = boxSize > DonutChartInteractiveLegend.MaxLegendItemBoxSize ? DonutChartInteractiveLegend.MaxLegendItemBoxSize : (boxSize + 2);\r\n return boxSize;\r\n }\r\n\r\n private static FakeElementSpan: JQuery;\r\n private static spanWidth(span: JQuery): any {\r\n if (!this.FakeElementSpan) {\r\n this.FakeElementSpan = $('<span>').hide().appendTo(document.body);\r\n }\r\n this.FakeElementSpan.empty();\r\n this.FakeElementSpan.append(span);\r\n return this.FakeElementSpan.width();\r\n }\r\n }\r\n\r\n module DonutChartConversion {\r\n\r\n interface ConvertedDataPoint {\r\n identity: SelectionId;\r\n measureFormat: string;\r\n measure: number;\r\n highlightMeasure: number;\r\n index: number;\r\n label: any;\r\n categoryLabel: string;\r\n color: string;\r\n categoryIndex?: number;\r\n seriesIndex?: number;\r\n };\r\n\r\n export class DonutChartConverter {\r\n private reader: IDataViewCategoricalReader;\r\n private dataViewCategorical: DataViewCategorical;\r\n private dataViewMetadata: DataViewMetadata;\r\n private total: number;\r\n private highlightTotal: number;\r\n private isDynamicSeries: boolean;\r\n private seriesCount: number;\r\n private categoryIdentities: DataViewScopeIdentity[];\r\n private categoryValues: any[];\r\n private allCategoryObjects: DataViewObjects[];\r\n private categoryColumnRef: data.SQExpr[];\r\n private legendDataPoints: LegendDataPoint[];\r\n private colorHelper: ColorHelper;\r\n private categoryFormatString: string;\r\n private tooltipsEnabled: boolean;\r\n private tooltipBucketEnabled: boolean;\r\n public hasHighlights: boolean;\r\n public highlightsOverflow: boolean;\r\n public dataPoints: DonutDataPoint[];\r\n public legendData: LegendData;\r\n public dataLabelsSettings: VisualDataLabelsSettings;\r\n public legendObjectProperties: DataViewObject;\r\n public maxValue: number;\r\n public hasNegativeValues: boolean;\r\n public allValuesAreNegative: boolean;\r\n\r\n public constructor(dataView: DataView, colors: IDataColorPalette, defaultDataPointColor?: string, tooltipsEnabled: boolean = true, tooltipBucketEnabled?: boolean) {\r\n let reader = this.reader = data.createIDataViewCategoricalReader(dataView);\r\n let dataViewCategorical = dataView.categorical;\r\n this.dataViewCategorical = dataViewCategorical;\r\n this.dataViewMetadata = dataView.metadata;\r\n this.tooltipsEnabled = tooltipsEnabled;\r\n this.tooltipBucketEnabled = tooltipBucketEnabled;\r\n this.colorHelper = new ColorHelper(colors, donutChartProps.dataPoint.fill, defaultDataPointColor);\r\n this.maxValue = 0;\r\n this.hasNegativeValues = false;\r\n this.allValuesAreNegative = false;\r\n\r\n if (dataViewCategorical.categories && dataViewCategorical.categories.length > 0) {\r\n let category = dataViewCategorical.categories[0];\r\n this.categoryIdentities = category.identity;\r\n this.categoryValues = category.values;\r\n this.allCategoryObjects = category.objects;\r\n this.categoryColumnRef = <data.SQExpr[]>category.identityFields;\r\n this.categoryFormatString = valueFormatter.getFormatString(category.source, donutChartProps.general.formatString);\r\n }\r\n\r\n this.isDynamicSeries = reader.hasDynamicSeries();\r\n\r\n this.highlightsOverflow = false;\r\n this.total = 0;\r\n this.highlightTotal = 0;\r\n this.dataPoints = [];\r\n this.legendDataPoints = [];\r\n this.dataLabelsSettings = null;\r\n\r\n // TODO: this should be shared with TreeMap\r\n if (reader.hasValues(\"Y\")) {\r\n let seriesCount = this.seriesCount = reader.getSeriesCount(\"Y\");\r\n this.hasHighlights = reader.hasHighlights(\"Y\");\r\n let categoryCount = reader.getCategoryCount() || 1;\r\n this.allValuesAreNegative = undefined;\r\n \r\n for (let categoryIndex = 0; categoryIndex < categoryCount; categoryIndex++) {\r\n for (let seriesIndex = 0; seriesIndex < seriesCount; seriesIndex++) {\r\n let value = reader.getValue(\"Y\", categoryIndex, seriesIndex);\r\n let highlight: any;\r\n if (this.hasHighlights) {\r\n highlight = reader.getHighlight(\"Y\", categoryIndex, seriesIndex);\r\n if (highlight == null)\r\n highlight = 0;\r\n }\r\n if (this.allValuesAreNegative === undefined) {\r\n this.allValuesAreNegative = ((this.hasHighlights ? highlight <= 0 : true) && value <= 0) ? true : false;\r\n }\r\n else {\r\n this.allValuesAreNegative = this.allValuesAreNegative && (this.hasHighlights ? highlight <= 0 : true) && value <= 0;\r\n }\r\n\r\n if (!this.hasNegativeValues)\r\n this.hasNegativeValues = value < 0 || (this.hasHighlights ? highlight < 0 : false);\r\n }\r\n } \r\n\r\n this.allValuesAreNegative = !!this.allValuesAreNegative;\r\n\r\n // We iterate over all categories, or if we have no categories, we just iterate over the series (category index = 0 is fine in that case)\r\n for (let categoryIndex = 0; categoryIndex < categoryCount; categoryIndex++) {\r\n for (let seriesIndex = 0; seriesIndex < seriesCount; seriesIndex++) {\r\n let value = reader.getValue(\"Y\", categoryIndex, seriesIndex);\r\n value = DonutChartConverter.normalizedValue(value, this.allValuesAreNegative);\r\n this.total += value;\r\n if (this.hasHighlights) {\r\n let highlight = reader.getHighlight(\"Y\", categoryIndex, seriesIndex);\r\n highlight = DonutChartConverter.normalizedValue(highlight, this.allValuesAreNegative);\r\n this.highlightTotal += highlight;\r\n if (!this.highlightsOverflow && highlight > value) {\r\n this.highlightsOverflow = true;\r\n }\r\n }\r\n }\r\n }\r\n }\r\n }\r\n\r\n // For public test\r\n public static normalizedValue(value: number, allValuesAreNegative: boolean): number {\r\n if (value == null || isNaN(value))\r\n return 0;\r\n else if (value === Number.POSITIVE_INFINITY)\r\n return Number.MAX_VALUE;\r\n else if (value === Number.NEGATIVE_INFINITY)\r\n return -Number.MAX_VALUE;\r\n else if (allValuesAreNegative)\r\n return Math.abs(value);\r\n else if (value < 0)\r\n return 0;\r\n else \r\n return value;\r\n }\r\n\r\n public convert(): void {\r\n let reader = this.reader;\r\n let convertedData: ConvertedDataPoint[];\r\n if (this.total !== 0) {\r\n // We render based on categories, series, or measures in that order of preference\r\n if (this.categoryValues) {\r\n convertedData = this.convertCategoricalWithSlicing();\r\n }\r\n else if (this.isDynamicSeries) {\r\n // Series but no category.\r\n convertedData = this.convertSeries();\r\n }\r\n else {\r\n // No category or series; only measures.\r\n convertedData = this.convertMeasures();\r\n }\r\n }\r\n else {\r\n convertedData = [];\r\n }\r\n\r\n // Create data labels settings\r\n this.dataLabelsSettings = this.convertDataLabelSettings();\r\n\r\n let dataViewMetadata = this.dataViewMetadata;\r\n if (dataViewMetadata) {\r\n let objects: DataViewObjects = dataViewMetadata.objects;\r\n if (objects) {\r\n this.legendObjectProperties = <DataViewObject>objects['legend'];\r\n }\r\n }\r\n\r\n let category = !_.isEmpty(this.dataViewCategorical.categories) ? this.dataViewCategorical.categories[0] : null;\r\n this.dataPoints = [];\r\n let formatStringProp = donutChartProps.general.formatString;\r\n let prevPointColor: string;\r\n let pctFormatString = valueFormatter.getLocalizedString('Percentage');\r\n\r\n for (let i = 0, dataPointCount = convertedData.length; i < dataPointCount; i++) {\r\n let point = convertedData[i];\r\n\r\n let originalMeasure = point.measure;\r\n let normalizedMeasure = DonutChartConverter.normalizedValue(point.measure, this.allValuesAreNegative);\r\n let originalHighlight = point.highlightMeasure;\r\n let normalizedHighlight = DonutChartConverter.normalizedValue(point.highlightMeasure, this.allValuesAreNegative);\r\n\r\n let percentage = (this.total > 0) ? normalizedMeasure / this.total : 0.0;\r\n let highlightRatio: number;\r\n let highlightPercentage: number;\r\n if (normalizedMeasure > this.maxValue)\r\n this.maxValue = normalizedMeasure;\r\n if (normalizedHighlight > this.maxValue)\r\n this.maxValue = normalizedHighlight ;\r\n\r\n if (this.hasHighlights) {\r\n // When any highlight value is greater than the corresponding non-highlight value\r\n // we just render all of the highlight values and discard the non-highlight values.\r\n if (this.highlightsOverflow) {\r\n originalMeasure = originalHighlight;\r\n normalizedMeasure = normalizedHighlight;\r\n\r\n percentage = (this.highlightTotal > 0) ? normalizedHighlight / this.highlightTotal : 0.0;\r\n highlightRatio = 1;\r\n }\r\n else {\r\n highlightRatio = normalizedMeasure !== 0 ? normalizedHighlight / normalizedMeasure : 0;\r\n }\r\n\r\n if (!highlightRatio) {\r\n highlightRatio = DonutChart.EffectiveZeroValue;\r\n }\r\n highlightPercentage = percentage * highlightRatio;\r\n }\r\n\r\n let categoryValue = point.categoryLabel;\r\n let categorical = this.dataViewCategorical;\r\n let valueIndex: number;\r\n if (point.seriesIndex != null) {\r\n valueIndex = point.seriesIndex;\r\n }\r\n else {\r\n // Static series with no categories\r\n valueIndex = i;\r\n }\r\n\r\n let valuesMetadata = reader.getValueMetadataColumn(\"Y\", valueIndex);\r\n let value: number = this.hasHighlights && this.highlightsOverflow ? originalHighlight : originalMeasure;\r\n let highlightValue: number = this.hasHighlights && !this.highlightsOverflow ? originalHighlight : undefined;\r\n let formatString = valueFormatter.getFormatString(valuesMetadata, formatStringProp);\r\n let pct: string = valueFormatter.format(percentage, pctFormatString);\r\n let valueAndPct: string;\r\n if (value != null && pct != null) {\r\n valueAndPct = valueFormatter.format(value, formatString) + ' (' + pct + ')';\r\n }\r\n\r\n let highlightValueAndPct: string;\r\n if (highlightValue != null && highlightPercentage != null) {\r\n let highlightedPct: string = valueFormatter.format(highlightPercentage, pctFormatString); \r\n highlightValueAndPct = valueFormatter.format(highlightValue, formatString) + ' (' + highlightedPct + ')';\r\n }\r\n\r\n let tooltipInfo: TooltipDataItem[];\r\n if (this.tooltipsEnabled) {\r\n tooltipInfo = [];\r\n\r\n if (category) {\r\n tooltipInfo.push({\r\n displayName: category.source.displayName,\r\n value: categoryValue,\r\n });\r\n }\r\n\r\n if (this.isDynamicSeries) {\r\n if (!category || category.source !== categorical.values.source) {\r\n // Category/series on the same column -- don't repeat its value in the tooltip.\r\n tooltipInfo.push({\r\n displayName: categorical.values.source.displayName,\r\n value: point.label,\r\n });\r\n }\r\n }\r\n\r\n if (valueAndPct != null) {\r\n tooltipInfo.push({\r\n displayName: valuesMetadata.displayName,\r\n value: valueAndPct,\r\n });\r\n }\r\n\r\n if (highlightValueAndPct != null) {\r\n tooltipInfo.push({\r\n displayName: ToolTipComponent.localizationOptions.highlightedValueDisplayName,\r\n value: highlightValueAndPct,\r\n });\r\n }\r\n\r\n if (this.tooltipBucketEnabled) {\r\n // SeriesIndex is not needed for static series.\r\n TooltipBuilder.addTooltipBucketItem(reader, tooltipInfo, this.categoryValues ? point.categoryIndex : 0, this.isDynamicSeries ? point.seriesIndex : undefined);\r\n }\r\n }\r\n\r\n let strokeWidth = (prevPointColor === point.color && percentage && percentage > 0) ? 1 : 0;\r\n prevPointColor = (percentage && percentage > 0 ) ? point.color : prevPointColor;\r\n this.dataPoints.push({\r\n identity: point.identity,\r\n measure: normalizedMeasure,\r\n originalMeasure: originalMeasure,\r\n measureFormat: point.measureFormat,\r\n percentage: percentage,\r\n index: point.index,\r\n label: point.label,\r\n highlightRatio: highlightRatio,\r\n highlightValue: (this.hasHighlights && !this.highlightsOverflow) ? normalizedHighlight : undefined,\r\n originalHighlightValue: (this.hasHighlights && !this.highlightsOverflow) ? originalHighlight : undefined,\r\n selected: false,\r\n tooltipInfo: tooltipInfo,\r\n color: point.color,\r\n strokeWidth: strokeWidth,\r\n labelFormatString: valuesMetadata.format,\r\n });\r\n }\r\n\r\n this.legendData = this.convertLegendData();\r\n }\r\n\r\n private getLegendTitle(): string {\r\n if (this.total !== 0) {\r\n // If category exists, we render title using category source. If not, we render title\r\n // using measure.\r\n let dvValuesSourceName = this.dataViewCategorical.values && this.dataViewCategorical.values.source\r\n ? this.dataViewCategorical.values.source.displayName : \"\";\r\n let dvCategorySourceName = this.dataViewCategorical.categories && this.dataViewCategorical.categories.length > 0 && this.dataViewCategorical.categories[0].source\r\n ? this.dataViewCategorical.categories[0].source.displayName : \"\";\r\n if (this.categoryValues) {\r\n return dvCategorySourceName;\r\n }\r\n else {\r\n return dvValuesSourceName;\r\n }\r\n }\r\n else {\r\n return \"\";\r\n }\r\n }\r\n\r\n private convertCategoricalWithSlicing(): ConvertedDataPoint[] {\r\n let reader = this.reader;\r\n let dataViewCategorical = this.dataViewCategorical;\r\n let formatStringProp = donutChartProps.general.formatString;\r\n let dataPoints: ConvertedDataPoint[] = [];\r\n\r\n for (let categoryIndex = 0, categoryCount = this.categoryValues.length; categoryIndex < categoryCount; categoryIndex++) {\r\n let categoryValue = this.categoryValues[categoryIndex];\r\n let thisCategoryObjects = this.allCategoryObjects ? this.allCategoryObjects[categoryIndex] : undefined;\r\n\r\n let legendIdentity = SelectionId.createWithId(this.categoryIdentities[categoryIndex]);\r\n let color = this.colorHelper.getColorForSeriesValue(thisCategoryObjects, this.categoryColumnRef, categoryValue);\r\n let categoryLabel = valueFormatter.format(categoryValue, this.categoryFormatString);\r\n\r\n // Series are either measures in the multi-measure case, or the single series otherwise\r\n for (let seriesIndex = 0; seriesIndex < this.seriesCount; seriesIndex++) {\r\n let value = reader.getValue(\"Y\", categoryIndex, seriesIndex);\r\n let highlightValue = this.hasHighlights ? reader.getHighlight(\"Y\", categoryIndex, seriesIndex) : undefined;\r\n\r\n let valueColumn = reader.getValueColumn(\"Y\", seriesIndex);\r\n\r\n let label = categoryLabel;\r\n if (this.isDynamicSeries || reader.getSeriesCount(\"Y\") > 1) {\r\n label = converterHelper.getFormattedLegendLabel(valueColumn.source, dataViewCategorical.values, formatStringProp);\r\n }\r\n\r\n let measure: string = valueColumn.source.queryName;\r\n let identity: SelectionId = SelectionIdBuilder.builder()\r\n .withCategory(dataViewCategorical.categories[0], categoryIndex)\r\n .withSeries(dataViewCategorical.values, this.isDynamicSeries ? valueColumn : undefined)\r\n .withMeasure(measure)\r\n .createSelectionId();\r\n\r\n let dataPoint: ConvertedDataPoint = {\r\n identity: identity,\r\n measureFormat: valueFormatter.getFormatString(valueColumn.source, formatStringProp, true),\r\n measure: value,\r\n highlightMeasure: highlightValue,\r\n index: categoryIndex * this.seriesCount + seriesIndex,\r\n label: label,\r\n categoryLabel: categoryLabel,\r\n color: color,\r\n categoryIndex: categoryIndex,\r\n seriesIndex: seriesIndex\r\n };\r\n dataPoints.push(dataPoint);\r\n }\r\n\r\n this.legendDataPoints.push({\r\n label: categoryLabel,\r\n color: color,\r\n icon: LegendIcon.Box,\r\n identity: legendIdentity,\r\n selected: false\r\n });\r\n }\r\n\r\n return dataPoints;\r\n }\r\n\r\n private convertMeasures(): ConvertedDataPoint[] {\r\n let reader = this.reader;\r\n let dataPoints: ConvertedDataPoint[] = [];\r\n let formatStringProp = donutChartProps.general.formatString;\r\n\r\n for (let measureIndex = 0; measureIndex < this.seriesCount; measureIndex++) {\r\n let value = reader.getValue(\"Y\", 0, measureIndex);\r\n let highlightValue = this.hasHighlights ? reader.getHighlight(\"Y\", 0, measureIndex) : undefined;\r\n\r\n let valueColumn = reader.getValueColumn(\"Y\", measureIndex);\r\n let measureFormat = valueFormatter.getFormatString(valueColumn.source, formatStringProp, true);\r\n let measureLabel = valueColumn.source.displayName;\r\n let identity = SelectionId.createWithMeasure(valueColumn.source.queryName);\r\n\r\n let color = this.colorHelper.getColorForMeasure(valueColumn.source.objects, valueColumn.source.queryName);\r\n\r\n let dataPoint: ConvertedDataPoint = {\r\n identity: identity,\r\n measureFormat: measureFormat,\r\n measure: value,\r\n highlightMeasure: highlightValue,\r\n index: measureIndex,\r\n label: measureLabel,\r\n categoryLabel: measureLabel,\r\n color: color\r\n };\r\n dataPoints.push(dataPoint);\r\n\r\n this.legendDataPoints.push({\r\n label: dataPoint.label,\r\n color: dataPoint.color,\r\n icon: LegendIcon.Box,\r\n identity: dataPoint.identity,\r\n selected: false\r\n });\r\n }\r\n\r\n return dataPoints;\r\n }\r\n\r\n private convertSeries(): ConvertedDataPoint[] {\r\n let reader = this.reader;\r\n let dataViewCategorical = this.dataViewCategorical;\r\n let dataPoints: ConvertedDataPoint[] = [];\r\n let formatStringProp = donutChartProps.general.formatString;\r\n\r\n for (let seriesIndex = 0; seriesIndex < this.seriesCount; seriesIndex++) {\r\n let value = reader.getValue(\"Y\", 0, seriesIndex);\r\n let highlightValue = this.hasHighlights ? reader.getHighlight(\"Y\", 0, seriesIndex) : undefined;\r\n\r\n let valueColumn = reader.getValueColumn(\"Y\", seriesIndex);\r\n let seriesFormat = valueFormatter.getFormatString(valueColumn.source, formatStringProp, true);\r\n let label = converterHelper.getFormattedLegendLabel(valueColumn.source, dataViewCategorical.values, formatStringProp);\r\n let identity = new SelectionIdBuilder()\r\n .withSeries(dataViewCategorical.values, valueColumn)\r\n .withMeasure(valueColumn.source.queryName)\r\n .createSelectionId();\r\n let seriesName = converterHelper.getSeriesName(valueColumn.source);\r\n let objects = reader.getSeriesObjects(seriesIndex);\r\n \r\n let color = this.colorHelper.getColorForSeriesValue(objects, dataViewCategorical.values.identityFields, seriesName);\r\n\r\n let dataPoint: ConvertedDataPoint = {\r\n identity: identity,\r\n measureFormat: seriesFormat,\r\n measure: value,\r\n highlightMeasure: highlightValue,\r\n index: seriesIndex,\r\n label: label,\r\n categoryLabel: label,\r\n color: color,\r\n seriesIndex: seriesIndex\r\n };\r\n dataPoints.push(dataPoint);\r\n\r\n this.legendDataPoints.push({\r\n label: dataPoint.label,\r\n color: dataPoint.color,\r\n icon: LegendIcon.Box,\r\n identity: dataPoint.identity,\r\n selected: false\r\n });\r\n }\r\n\r\n return dataPoints;\r\n }\r\n\r\n private convertDataLabelSettings(): VisualDataLabelsSettings {\r\n let dataViewMetadata = this.dataViewMetadata;\r\n let dataLabelsSettings = dataLabelUtils.getDefaultDonutLabelSettings();\r\n\r\n if (dataViewMetadata) {\r\n let objects: DataViewObjects = dataViewMetadata.objects;\r\n if (objects) {\r\n // Handle lables settings\r\n let labelsObj = <DataLabelObject>objects['labels'];\r\n if (labelsObj) {\r\n dataLabelUtils.updateLabelSettingsFromLabelsObject(labelsObj, dataLabelsSettings);\r\n }\r\n }\r\n }\r\n\r\n return dataLabelsSettings;\r\n }\r\n\r\n private convertLegendData(): LegendData {\r\n return {\r\n dataPoints: this.legendDataPoints,\r\n labelColor: LegendData.DefaultLegendLabelFillColor,\r\n title: this.getLegendTitle(),\r\n fontSize: SVGLegend.DefaultFontSizeInPt,\r\n };\r\n }\r\n }\r\n }\r\n}\n\n\n/** WEBPACK FOOTER **\n ** ./src/Clients/Visuals/visuals/donutChart.ts\n **/","/*\r\n * Power BI Visualizations\r\n *\r\n * Copyright (c) Microsoft Corporation\r\n * All rights reserved. \r\n * MIT License\r\n *\r\n * Permission is hereby granted, free of charge, to any person obtaining a copy\r\n * of this software and associated documentation files (the \"\"Software\"\"), to deal\r\n * in the Software without restriction, including without limitation the rights\r\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\r\n * copies of the Software, and to permit persons to whom the Software is\r\n * furnished to do so, subject to the following conditions:\r\n * \r\n * The above copyright notice and this permission notice shall be included in \r\n * all copies or substantial portions of the Software.\r\n * \r\n * THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR \r\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, \r\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE \r\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER \r\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\r\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\r\n * THE SOFTWARE.\r\n */\r\n\r\n/// <reference path=\"../_references.ts\"/>\r\n\r\nmodule powerbi.visuals {\r\n import Utility = jsCommon.Utility;\r\n\r\n export interface ScriptVisualDataViewObjects extends DataViewObjects {\r\n script: ScriptObject;\r\n }\r\n\r\n export interface ScriptObject extends DataViewObject {\r\n provider: string;\r\n source: string;\r\n }\r\n\r\n export interface ScriptVisualOptions {\r\n canRefresh: boolean;\r\n }\r\n\r\n export class ScriptVisual implements IVisual {\r\n private element: JQuery;\r\n private imageBackgroundElement: JQuery;\r\n private hostServices: IVisualHostServices;\r\n private canRefresh: boolean;\r\n\r\n public constructor(options: ScriptVisualOptions) {\r\n this.canRefresh = options.canRefresh;\r\n }\r\n\r\n public init(options: VisualInitOptions): void {\r\n this.element = options.element;\r\n this.hostServices = options.host;\r\n\r\n if (!this.canRefresh) {\r\n this.hostServices.setWarnings([new ScriptVisualRefreshWarning()]);\r\n }\r\n }\r\n\r\n public update(options: VisualUpdateOptions): void {\r\n debug.assertValue(options, 'options');\r\n\r\n let dataViews: DataView[] = options.dataViews;\r\n if (!dataViews || dataViews.length === 0)\r\n return;\r\n\r\n let dataView: DataView = dataViews[0];\r\n if (!dataView || !dataView.metadata)\r\n return;\r\n\r\n let imageUrl = this.getImageUrl(dataView);\r\n let div = this.ensureHtmlElement();\r\n\r\n if (imageUrl && Utility.isValidImageDataUrl(imageUrl)) {\r\n let viewport = options.viewport;\r\n\r\n div.css({ height: viewport.height, width: viewport.width, backgroundImage: 'url(' + imageUrl + ')' });\r\n } else {\r\n div.css({ backgroundImage: 'none' });\r\n }\r\n }\r\n\r\n public onResizing(finalViewport: IViewport): void {\r\n let div = this.ensureHtmlElement();\r\n div.css({ height: finalViewport.height, width: finalViewport.width });\r\n }\r\n\r\n private getImageUrl(dataView: DataView): string {\r\n debug.assertValue(dataView, 'dataView');\r\n\r\n if (dataView.scriptResult && dataView.scriptResult.imageBase64) {\r\n return \"data:image/png;base64,\" + dataView.scriptResult.imageBase64;\r\n }\r\n\r\n return null;\r\n }\r\n\r\n private ensureHtmlElement(): JQuery {\r\n let div: JQuery = this.imageBackgroundElement;\r\n if (!div) {\r\n div = $(\"<div class='imageBackground' />\");\r\n this.imageBackgroundElement = div;\r\n this.imageBackgroundElement.appendTo(this.element);\r\n }\r\n\r\n return div;\r\n }\r\n }\r\n}\r\n\n\n\n/** WEBPACK FOOTER **\n ** ./src/Clients/Visuals/visuals/scriptVisual.ts\n **/","/*\r\n * Power BI Visualizations\r\n *\r\n * Copyright (c) Microsoft Corporation\r\n * All rights reserved. \r\n * MIT License\r\n *\r\n * Permission is hereby granted, free of charge, to any person obtaining a copy\r\n * of this software and associated documentation files (the \"\"Software\"\"), to deal\r\n * in the Software without restriction, including without limitation the rights\r\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\r\n * copies of the Software, and to permit persons to whom the Software is\r\n * furnished to do so, subject to the following conditions:\r\n * \r\n * The above copyright notice and this permission notice shall be included in \r\n * all copies or substantial portions of the Software.\r\n * \r\n * THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR \r\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, \r\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE \r\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER \r\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\r\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\r\n * THE SOFTWARE.\r\n */\r\n\r\n/// <reference path=\"../../_references.ts\"/>\r\n\r\nmodule powerbi.visuals.system {\r\n\r\n interface VisualErrorMessageOptions {\r\n message: string;\r\n moreMessage: string;\r\n moreLinkHref: string;\r\n moreLinkText: string;\r\n type: string;\r\n }\r\n\r\n export class DebugVisual implements IVisual {\r\n public static capabilities: VisualCapabilities = {};\r\n\r\n private static autoReloadPollTime = 300;\r\n private static errorMessageTemplate = `\r\n <div class=\"errorContainer\">\r\n <div class=\"errorMessage\">\r\n <div ng-switch=\"$ctrl.errorInfo.overlayType\">\r\n <div class=\"glyphicon pbi-glyph-<%= type %> glyph-med\"></div>\r\n </div>\r\n <div>\r\n <div class=\"errorSpan\"><%= message %></div>\r\n <span class=\"errorSeeMore\"><%= moreMessage %></span>\r\n <a class=\"errorSeeMore\" href=\"<%= moreLinkHref %>\" target=\"_blank\"><%= moreLinkText %></a>\r\n </div>\r\n </div>\r\n </div>\r\n `;\r\n\r\n private adapter: IVisual;\r\n private container: JQuery;\r\n private visualContainer: JQuery;\r\n private optionsForVisual: VisualInitOptions;\r\n private host: IVisualHostServices;\r\n private autoRefreshBtn: JQuery;\r\n private refreshBtn: JQuery;\r\n private dataBtn: JQuery;\r\n private lastUpdateOptions: VisualUpdateOptions;\r\n private lastUpdateStatus: string;\r\n private visualGuid: string;\r\n private autoReloadInterval: number;\r\n private statusLoading: boolean;\r\n private dataViewShowing: boolean;\r\n\r\n private reloadAdapter(auto: boolean = false): void {\r\n if (this.dataViewShowing) {\r\n if (auto) {\r\n return;\r\n }\r\n this.toggleDataview(false);\r\n }\r\n\r\n let developerMode = localStorageService.getData('DEVELOPER_MODE_ENABLED');\r\n if (!developerMode) {\r\n this.toggleAutoReload(false);\r\n let errorMessage = this.buildErrorMessage({\r\n message: this.host.getLocalizedString('DebugVisual_Enabled_Error_Message'),\r\n moreMessage: this.host.getLocalizedString('DebugVisual_Enabled_Error_Learn_More'),\r\n moreLinkHref: \"https://aka.ms/powerbideveloperenablederror\",\r\n moreLinkText: this.host.getLocalizedString('DebugVisual_Enabled_Error_Learn_More_Link'),\r\n type: 'blockedsite'\r\n });\r\n this.container.html(errorMessage);\r\n this.setCapabilities({});\r\n return;\r\n }\r\n\r\n let baseUrl = localStorageService.getData('DEVELOPMENT_SERVER_URL') || 'https://localhost:8080/assets/';\r\n\r\n if (this.statusLoading) {\r\n return;\r\n }\r\n this.statusLoading = true;\r\n $.get(baseUrl + 'status').done((status) => {\r\n if (!status) {\r\n return;\r\n }\r\n\r\n if (auto && this.lastUpdateStatus === status) {\r\n return;\r\n }\r\n this.lastUpdateStatus = status;\r\n\r\n if (status === 'error') {\r\n let errorMessage = this.buildErrorMessage({\r\n message: this.host.getLocalizedString('DebugVisual_Compile_Error_Message'),\r\n moreMessage: this.host.getLocalizedString('DebugVisual_Compile_Error_Learn_More'),\r\n moreLinkHref: \"https://aka.ms/powerbideveloperpbivizcompileerror\",\r\n moreLinkText: this.host.getLocalizedString('DebugVisual_Compile_Error_Learn_More_Link'),\r\n type: 'repair'\r\n });\r\n this.container.html(errorMessage);\r\n this.setCapabilities({});\r\n return;\r\n }\r\n\r\n $.getJSON(baseUrl + 'pbiviz.json').done((pbivizJson) => {\r\n debug.assertValue(pbivizJson.capabilities, \"DebugVisual - pbiviz capabilities missing\");\r\n debug.assertValue(pbivizJson.visual && pbivizJson.visual.guid, \"DebugVisual - pbiviz visual guid missing\");\r\n if (!pbivizJson.capabilities || !pbivizJson.visual || !pbivizJson.visual.guid) {\r\n return;\r\n }\r\n\r\n //update guid if needed\r\n if (this.visualGuid !== pbivizJson.visual.guid) {\r\n this.visualGuid = pbivizJson.visual.guid;\r\n }\r\n\r\n //loaded separately for sourcemap support\r\n $.getScript(baseUrl + 'visual.js').done(() => {\r\n debug.assertValue(powerbi.visuals.plugins[this.visualGuid], \"DebugVisual - Plugin not found\");\r\n if (!powerbi.visuals.plugins[this.visualGuid]) {\r\n return;\r\n }\r\n //attach json capabilities to plugin\r\n powerbi.visuals.plugins[this.visualGuid].capabilities = pbivizJson.capabilities;\r\n //translate plugin\r\n powerbi.extensibility.translateVisualPlugin(powerbi.visuals.plugins[this.visualGuid]);\r\n //loaded separately for sourcemap support\r\n $.get(baseUrl + 'visual.css').done((data) => {\r\n $('#css-DEBUG').remove();\r\n $(\"<style/>\", {\r\n id: 'css-DEBUG',\r\n html: data,\r\n }).appendTo($('head'));\r\n\r\n this.loadVisual(this.visualGuid);\r\n\r\n //override debugVisual capabilities with user's\r\n this.setCapabilities(powerbi.visuals.plugins[this.visualGuid].capabilities);\r\n });\r\n });\r\n });\r\n }).fail(() => {\r\n this.toggleAutoReload(false);\r\n let errorMessage = this.buildErrorMessage({\r\n message: this.host.getLocalizedString('DebugVisual_Server_Error_Message'),\r\n moreMessage: this.host.getLocalizedString('DebugVisual_Server_Error_Learn_More'),\r\n moreLinkHref: \"https://aka.ms/powerbideveloperpbivizservererror\",\r\n moreLinkText: this.host.getLocalizedString('DebugVisual_Server_Error_Learn_More_Link'),\r\n type: 'error'\r\n });\r\n this.container.html(errorMessage);\r\n this.setCapabilities({});\r\n }).always(() => {\r\n this.statusLoading = false;\r\n });\r\n }\r\n\r\n private loadVisual(guid: string) {\r\n this.visualContainer.attr('class', 'visual-' + guid);\r\n this.visualContainer.empty();\r\n this.container.empty().append(this.visualContainer);\r\n let adapter = this.adapter = extensibility.createVisualAdapter(powerbi.visuals.plugins[guid]);\r\n if (adapter.init) {\r\n adapter.init(this.optionsForVisual);\r\n }\r\n if (adapter.update && this.lastUpdateOptions) {\r\n let lastUpdateOptions = Prototype.inherit(this.lastUpdateOptions);\r\n lastUpdateOptions.type = VisualUpdateType.All;\r\n adapter.update(lastUpdateOptions);\r\n }\r\n }\r\n\r\n /**\r\n * Toggles auto reload\r\n * if value is set it sets it to true = on / false = off \r\n */\r\n private toggleAutoReload(value?: boolean): void {\r\n if (this.autoReloadInterval && value !== true) {\r\n this.autoRefreshBtn.addClass('pbi-glyph-play');\r\n this.autoRefreshBtn.removeClass('pbi-glyph-stop');\r\n this.refreshBtn.show();\r\n clearInterval(this.autoReloadInterval);\r\n this.autoReloadInterval = undefined;\r\n } else if (!this.autoReloadInterval && value !== false) {\r\n this.autoRefreshBtn.removeClass('pbi-glyph-play');\r\n this.autoRefreshBtn.addClass('pbi-glyph-stop');\r\n this.refreshBtn.hide();\r\n this.autoReloadInterval = setInterval(() => this.reloadAdapter(true), DebugVisual.autoReloadPollTime);\r\n }\r\n }\r\n\r\n /**\r\n * Toggles dataViewer\r\n * if value is set it sets it to true = on / false = off\r\n */\r\n private toggleDataview(value?: boolean): void {\r\n if (this.dataViewShowing && value !== true) {\r\n this.dataViewShowing = false;\r\n this.dataBtn.toggleClass('active', false);\r\n this.reloadAdapter();\r\n } else if (!this.dataViewShowing && value !== false) {\r\n this.dataViewShowing = true;\r\n this.dataBtn.toggleClass('active', true);\r\n this.loadVisual('dataViewer');\r\n }\r\n }\r\n\r\n private createRefreshBtn(): JQuery {\r\n let label = this.host.getLocalizedString('DebugVisual_Reload_Visual_Button_Title');\r\n let refreshBtn = this.refreshBtn = $(`<i title=\"${label}\" class=\"controlBtn glyphicon pbi-glyph-refresh\"></i>`);\r\n refreshBtn.on('click', () => this.reloadAdapter());\r\n return refreshBtn;\r\n }\r\n\r\n private createAutoRefreshBtn(): JQuery {\r\n let label = this.host.getLocalizedString('DebugVisual_Toggle_Auto_Reload_Button_Title');\r\n let autoRefreshBtn = this.autoRefreshBtn = $(`<i title=\"${label}\" class=\"controlBtn glyphicon pbi-glyph-play\"></i>`);\r\n autoRefreshBtn.on('click', () => this.toggleAutoReload());\r\n return autoRefreshBtn;\r\n }\r\n\r\n private createDataBtn(): JQuery {\r\n let label = this.host.getLocalizedString('DebugVisual_Show_Dataview_Button_Title');\r\n let dataBtn = this.dataBtn = $(`<i title=\"${label}\" class=\"controlBtn glyphicon pbi-glyph-seedata\"></i>`);\r\n dataBtn.on('click', () => this.toggleDataview());\r\n return dataBtn;\r\n }\r\n\r\n private createHelpBtn(): JQuery {\r\n let label = this.host.getLocalizedString('DebugVisual_Help_Button_Title');\r\n let helpBtn = $(`<a href=\"https://aka.ms/powerbideveloperhelp\" target=\"_blank\"><i title=\"${label}\" class=\"controlBtn glyphicon pbi-glyph-question\"></i></a>`);\r\n return helpBtn;\r\n }\r\n\r\n private createSmilyBtn(): JQuery {\r\n let label = this.host.getLocalizedString('DebugVisual_Feedback_Button_Title');\r\n let smilyBtn = $(`<a href=\"https://aka.ms/powerbideveloperfeedback\" target=\"_blank\"><i title=\"${label}\" class=\"controlBtn glyphicon pbi-glyph-smiley\"></i></a>`);\r\n return smilyBtn;\r\n }\r\n\r\n private buildControls(): JQuery {\r\n let controlsContainer = $('<div class=\"debugVisual-controlsContainer\"></div>');\r\n controlsContainer\r\n .append(\r\n this.createRefreshBtn(),\r\n this.createAutoRefreshBtn(),\r\n this.createDataBtn(),\r\n this.createHelpBtn(),\r\n this.createSmilyBtn());\r\n\r\n return controlsContainer;\r\n }\r\n\r\n private buildErrorMessage(options: VisualErrorMessageOptions): string {\r\n return _.template(DebugVisual.errorMessageTemplate)(options);\r\n }\r\n\r\n private setCapabilities(capabilities: VisualCapabilities): void {\r\n powerbi.visuals.plugins.debugVisual.capabilities = capabilities;\r\n this.host.visualCapabilitiesChanged();\r\n }\r\n\r\n public init(options: VisualInitOptions): void {\r\n this.host = options.host;\r\n let container = this.container = $('<div class=\"debugVisualContainer\"></div>');\r\n let visualContainer = this.visualContainer = $('<div class=\"visual\"></div>');\r\n this.dataViewShowing = false;\r\n container.append(visualContainer);\r\n options.element.append(container);\r\n\r\n let optionsForVisual = this.optionsForVisual = Prototype.inherit(options);\r\n optionsForVisual.element = visualContainer;\r\n\r\n this.host.setToolbar(this.buildControls());\r\n this.reloadAdapter();\r\n }\r\n\r\n public update(options: VisualUpdateOptions): void {\r\n let visualOptions = this.lastUpdateOptions = Prototype.inherit(options);\r\n let height = options.viewport.height;\r\n let width = options.viewport.width;\r\n this.visualContainer\r\n .height(height)\r\n .width(width);\r\n if (this.adapter && this.adapter.update) {\r\n this.adapter.update(visualOptions);\r\n }\r\n }\r\n\r\n public enumerateObjectInstances(options: EnumerateVisualObjectInstancesOptions): VisualObjectInstance[] {\r\n if (this.adapter && this.adapter.enumerateObjectInstances) {\r\n return <VisualObjectInstance[]>this.adapter.enumerateObjectInstances(options);\r\n }\r\n return [];\r\n }\r\n\r\n public destroy(): void {\r\n if (this.adapter && this.adapter.destroy) {\r\n this.adapter.destroy();\r\n };\r\n this.toggleAutoReload(false);\r\n this.container = null;\r\n this.visualContainer = null;\r\n this.host.setToolbar(null);\r\n }\r\n }\r\n}\n\n\n/** WEBPACK FOOTER **\n ** ./src/Clients/Visuals/visuals/system/debugVisual.ts\n **/","/*\r\n * Power BI Visualizations\r\n *\r\n * Copyright (c) Microsoft Corporation\r\n * All rights reserved. \r\n * MIT License\r\n *\r\n * Permission is hereby granted, free of charge, to any person obtaining a copy\r\n * of this software and associated documentation files (the \"\"Software\"\"), to deal\r\n * in the Software without restriction, including without limitation the rights\r\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\r\n * copies of the Software, and to permit persons to whom the Software is\r\n * furnished to do so, subject to the following conditions:\r\n * \r\n * The above copyright notice and this permission notice shall be included in \r\n * all copies or substantial portions of the Software.\r\n * \r\n * THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR \r\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, \r\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE \r\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER \r\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\r\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\r\n * THE SOFTWARE.\r\n */\r\n\r\n/// <reference path=\"./_references.ts\"/>\r\n\r\nmodule powerbi.visuals.plugins {\r\n // This file registers the built-in visualizations\r\n\r\n export const animatedNumber: IVisualPlugin = {\r\n name: 'animatedNumber',\r\n capabilities: capabilities.animatedNumber,\r\n create: () => new AnimatedNumber()\r\n };\r\n\r\n export let areaChart: IVisualPlugin = {\r\n name: 'areaChart',\r\n watermarkKey: 'area',\r\n capabilities: capabilities.lineChart,\r\n create: () => new CartesianChart({ chartType: CartesianChartType.Area }),\r\n customizeQuery: LineChart.customizeQuery,\r\n getSortableRoles: (visualSortableOptions?: VisualSortableOptions) => ColumnChart.getSortableRoles(visualSortableOptions),\r\n getAdditionalTelemetry: (dataView) => CartesianChart.getAdditionalTelemetry(dataView),\r\n };\r\n\r\n export let barChart: IVisualPlugin = {\r\n name: 'barChart',\r\n watermarkKey: 'bar',\r\n capabilities: capabilities.barChart,\r\n create: () => new CartesianChart({ chartType: CartesianChartType.StackedBar }),\r\n customizeQuery: ColumnChart.customizeQuery,\r\n getSortableRoles: (visualSortableOptions?: VisualSortableOptions) => ColumnChart.getSortableRoles(visualSortableOptions),\r\n getAdditionalTelemetry: (dataView) => CartesianChart.getAdditionalTelemetry(dataView),\r\n };\r\n\r\n export let basicShape: IVisualPlugin = {\r\n name: 'basicShape',\r\n capabilities: basicShapeCapabilities,\r\n create: () => new BasicShapeVisual()\r\n };\r\n\r\n export let card: IVisualPlugin = {\r\n name: 'card',\r\n watermarkKey: 'card',\r\n capabilities: capabilities.card,\r\n create: () => new Card()\r\n };\r\n\r\n export let multiRowCard: IVisualPlugin = {\r\n name: 'multiRowCard',\r\n watermarkKey: 'multiRowCard',\r\n capabilities: capabilities.multiRowCard,\r\n create: () => new MultiRowCard(),\r\n getSortableRoles: (visualSortableOptions?: VisualSortableOptions) => MultiRowCard.getSortableRoles(visualSortableOptions),\r\n };\r\n\r\n export let clusteredBarChart: IVisualPlugin = {\r\n name: 'clusteredBarChart',\r\n watermarkKey: 'clusteredBar',\r\n capabilities: capabilities.clusteredBarChart,\r\n create: () => new CartesianChart({ chartType: CartesianChartType.ClusteredBar }),\r\n customizeQuery: ColumnChart.customizeQuery,\r\n getSortableRoles: (visualSortableOptions?: VisualSortableOptions) => ColumnChart.getSortableRoles(visualSortableOptions),\r\n getAdditionalTelemetry: (dataView) => CartesianChart.getAdditionalTelemetry(dataView),\r\n };\r\n\r\n export let clusteredColumnChart: IVisualPlugin = {\r\n name: 'clusteredColumnChart',\r\n watermarkKey: 'clusteredColumn',\r\n capabilities: capabilities.clusteredColumnChart,\r\n create: () => new CartesianChart({ chartType: CartesianChartType.ClusteredColumn }),\r\n customizeQuery: ColumnChart.customizeQuery,\r\n getSortableRoles: (visualSortableOptions?: VisualSortableOptions) => ColumnChart.getSortableRoles(visualSortableOptions),\r\n getAdditionalTelemetry: (dataView) => CartesianChart.getAdditionalTelemetry(dataView),\r\n };\r\n\r\n export let columnChart: IVisualPlugin = {\r\n name: 'columnChart',\r\n watermarkKey: 'column',\r\n capabilities: capabilities.columnChart,\r\n create: () => new CartesianChart({ chartType: CartesianChartType.StackedColumn }),\r\n customizeQuery: ColumnChart.customizeQuery,\r\n getSortableRoles: (visualSortableOptions?: VisualSortableOptions) => ColumnChart.getSortableRoles(visualSortableOptions),\r\n getAdditionalTelemetry: (dataView) => CartesianChart.getAdditionalTelemetry(dataView),\r\n };\r\n\r\n export let comboChart: IVisualPlugin = {\r\n name: 'comboChart',\r\n watermarkKey: 'combo',\r\n capabilities: capabilities.comboChart,\r\n customizeQuery: ComboChart.customizeQuery,\r\n create: () => new CartesianChart({ chartType: CartesianChartType.ComboChart }),\r\n getSortableRoles: (visualSortableOptions?: VisualSortableOptions) => ComboChart.getSortableRoles(visualSortableOptions),\r\n getAdditionalTelemetry: (dataView) => CartesianChart.getAdditionalTelemetry(dataView),\r\n };\r\n\r\n export let dataDotChart: IVisualPlugin = {\r\n name: 'dataDotChart',\r\n capabilities: capabilities.dataDotChart,\r\n create: () => new CartesianChart({ chartType: CartesianChartType.DataDot }),\r\n getAdditionalTelemetry: (dataView) => CartesianChart.getAdditionalTelemetry(dataView),\r\n };\r\n\r\n export let dataDotClusteredColumnComboChart: IVisualPlugin = {\r\n name: 'dataDotClusteredColumnComboChart',\r\n watermarkKey: 'combo',\r\n capabilities: capabilities.dataDotClusteredColumnComboChart,\r\n customizeQuery: ComboChart.customizeQuery,\r\n create: () => new CartesianChart({ chartType: CartesianChartType.DataDotClusteredColumnCombo }),\r\n getAdditionalTelemetry: (dataView) => CartesianChart.getAdditionalTelemetry(dataView),\r\n };\r\n\r\n export let dataDotStackedColumnComboChart: IVisualPlugin = {\r\n name: 'dataDotStackedColumnComboChart',\r\n watermarkKey: 'combo',\r\n capabilities: capabilities.dataDotStackedColumnComboChart,\r\n customizeQuery: ComboChart.customizeQuery,\r\n create: () => new CartesianChart({ chartType: CartesianChartType.DataDotStackedColumnCombo }),\r\n getAdditionalTelemetry: (dataView) => CartesianChart.getAdditionalTelemetry(dataView),\r\n };\r\n\r\n export let donutChart: IVisualPlugin = {\r\n name: 'donutChart',\r\n watermarkKey: 'donut',\r\n capabilities: capabilities.donutChart,\r\n create: () => new DonutChart()\r\n };\r\n\r\n export let funnel: IVisualPlugin = {\r\n name: 'funnel',\r\n watermarkKey: 'funnel',\r\n capabilities: capabilities.funnel,\r\n create: () => new FunnelChart()\r\n };\r\n\r\n export let gauge: IVisualPlugin = {\r\n name: 'gauge',\r\n watermarkKey: 'gauge',\r\n capabilities: capabilities.gauge,\r\n create: () => new Gauge()\r\n };\r\n\r\n export let hundredPercentStackedBarChart: IVisualPlugin = {\r\n name: 'hundredPercentStackedBarChart',\r\n watermarkKey: '100stackedbar',\r\n capabilities: capabilities.hundredPercentStackedBarChart,\r\n create: () => new CartesianChart({ chartType: CartesianChartType.HundredPercentStackedBar }),\r\n customizeQuery: ColumnChart.customizeQuery,\r\n getSortableRoles: (visualSortableOptions?: VisualSortableOptions) => ColumnChart.getSortableRoles(visualSortableOptions),\r\n getAdditionalTelemetry: (dataView) => CartesianChart.getAdditionalTelemetry(dataView),\r\n };\r\n\r\n export let hundredPercentStackedColumnChart: IVisualPlugin = {\r\n name: 'hundredPercentStackedColumnChart',\r\n watermarkKey: '100stackedcolumn',\r\n capabilities: capabilities.hundredPercentStackedColumnChart,\r\n create: () => new CartesianChart({ chartType: CartesianChartType.HundredPercentStackedColumn }),\r\n customizeQuery: ColumnChart.customizeQuery,\r\n getSortableRoles: (visualSortableOptions?: VisualSortableOptions) => ColumnChart.getSortableRoles(visualSortableOptions),\r\n getAdditionalTelemetry: (dataView) => CartesianChart.getAdditionalTelemetry(dataView),\r\n };\r\n\r\n export let image: IVisualPlugin = {\r\n name: 'image',\r\n capabilities: capabilities.image,\r\n create: () => new ImageVisual()\r\n };\r\n\r\n export let lineChart: IVisualPlugin = {\r\n name: 'lineChart',\r\n watermarkKey: 'line',\r\n capabilities: capabilities.lineChart,\r\n create: () => new CartesianChart({ chartType: CartesianChartType.Line }),\r\n customizeQuery: LineChart.customizeQuery,\r\n getSortableRoles: (visualSortableOptions?: VisualSortableOptions) => LineChart.getSortableRoles(visualSortableOptions),\r\n getAdditionalTelemetry: (dataView) => CartesianChart.getAdditionalTelemetry(dataView),\r\n };\r\n\r\n export let lineStackedColumnComboChart: IVisualPlugin = {\r\n name: 'lineStackedColumnComboChart',\r\n watermarkKey: 'combo',\r\n capabilities: capabilities.lineStackedColumnComboChart,\r\n customizeQuery: ComboChart.customizeQuery,\r\n create: () => new CartesianChart({ chartType: CartesianChartType.LineStackedColumnCombo }),\r\n getSortableRoles: (visualSortableOptions?: VisualSortableOptions) => ComboChart.getSortableRoles(visualSortableOptions),\r\n getAdditionalTelemetry: (dataView) => CartesianChart.getAdditionalTelemetry(dataView),\r\n };\r\n\r\n export let lineClusteredColumnComboChart: IVisualPlugin = {\r\n name: 'lineClusteredColumnComboChart',\r\n watermarkKey: 'combo',\r\n capabilities: capabilities.lineClusteredColumnComboChart,\r\n customizeQuery: ComboChart.customizeQuery,\r\n create: () => new CartesianChart({ chartType: CartesianChartType.LineClusteredColumnCombo }),\r\n getSortableRoles: (visualSortableOptions?: VisualSortableOptions) => ComboChart.getSortableRoles(visualSortableOptions),\r\n getAdditionalTelemetry: (dataView) => CartesianChart.getAdditionalTelemetry(dataView),\r\n };\r\n\r\n export let map: IVisualPlugin = {\r\n name: 'map',\r\n watermarkKey: 'map',\r\n capabilities: capabilities.map,\r\n create: () => new Map({ filledMap: false })\r\n };\r\n\r\n export let filledMap: IVisualPlugin = {\r\n name: 'filledMap',\r\n watermarkKey: 'filledMap',\r\n capabilities: capabilities.filledMap,\r\n create: () => new Map({ filledMap: true })\r\n };\r\n\r\n export let treemap: IVisualPlugin = {\r\n name: 'treemap',\r\n watermarkKey: 'tree',\r\n capabilities: capabilities.treemap,\r\n create: () => new Treemap()\r\n };\r\n\r\n export let pieChart: IVisualPlugin = {\r\n name: 'pieChart',\r\n watermarkKey: 'pie',\r\n capabilities: capabilities.donutChart,\r\n create: () => new DonutChart({ sliceWidthRatio: 0 })\r\n };\r\n\r\n export let scatterChart: IVisualPlugin = {\r\n name: 'scatterChart',\r\n watermarkKey: 'scatterplot',\r\n capabilities: capabilities.scatterChart,\r\n create: () => new CartesianChart({ chartType: CartesianChartType.Scatter }),\r\n getAdditionalTelemetry: (dataView: DataView) => ScatterChart.getAdditionalTelemetry(dataView),\r\n };\r\n\r\n export let stackedAreaChart: IVisualPlugin = {\r\n name: 'stackedAreaChart',\r\n watermarkKey: 'stackedarea',\r\n capabilities: capabilities.lineChart,\r\n create: () => new CartesianChart({ chartType: CartesianChartType.StackedArea }),\r\n customizeQuery: LineChart.customizeQuery,\r\n getSortableRoles: (visualSortableOptions?: VisualSortableOptions) => ColumnChart.getSortableRoles(visualSortableOptions),\r\n getAdditionalTelemetry: (dataView) => CartesianChart.getAdditionalTelemetry(dataView),\r\n };\r\n\r\n export let table: IVisualPlugin = {\r\n name: 'table',\r\n watermarkKey: 'table',\r\n capabilities: capabilities.table,\r\n create: () => new Table(),\r\n customizeQuery: Table.customizeQuery,\r\n getSortableRoles: (visualSortableOptions?: VisualSortableOptions) => Table.getSortableRoles(),\r\n };\r\n\r\n export let matrix: IVisualPlugin = {\r\n name: 'matrix',\r\n watermarkKey: 'matrix',\r\n capabilities: capabilities.matrix,\r\n create: () => new Matrix(),\r\n customizeQuery: Matrix.customizeQuery,\r\n getSortableRoles: (visualSortableOptions?: VisualSortableOptions) => Matrix.getSortableRoles(),\r\n };\r\n\r\n export let slicer: IVisualPlugin = {\r\n name: 'slicer',\r\n watermarkKey: 'slicer',\r\n capabilities: capabilities.slicer,\r\n create: () => new Slicer()\r\n };\r\n\r\n export let textbox: IVisualPlugin = {\r\n name: 'textbox',\r\n capabilities: capabilities.textbox,\r\n create: () => new Textbox()\r\n };\r\n\r\n export let waterfallChart: IVisualPlugin = {\r\n name: 'waterfallChart',\r\n watermarkKey: 'waterfall',\r\n capabilities: capabilities.waterfallChart,\r\n create: () => new CartesianChart({ chartType: CartesianChartType.Waterfall }),\r\n getAdditionalTelemetry: (dataView) => CartesianChart.getAdditionalTelemetry(dataView),\r\n };\r\n\r\n export let cheerMeter: IVisualPlugin = {\r\n name: 'cheerMeter',\r\n capabilities: CheerMeter.capabilities,\r\n create: () => new CheerMeter()\r\n };\r\n\r\n export let consoleWriter: IVisualPlugin = {\r\n name: 'consoleWriter',\r\n capabilities: samples.consoleWriterCapabilities,\r\n create: () => new samples.ConsoleWriter()\r\n };\r\n\r\n export let helloIVisual: IVisualPlugin = {\r\n name: 'helloIVisual',\r\n capabilities: samples.HelloIVisual.capabilities,\r\n create: () => new samples.HelloIVisual()\r\n };\r\n\r\n export let owlGauge: IVisualPlugin = {\r\n name: 'owlGauge',\r\n watermarkKey: 'gauge',\r\n capabilities: OwlGauge.capabilities,\r\n create: () => new OwlGauge()\r\n };\r\n\r\n export let scriptVisual: IVisualPlugin = {\r\n name: 'scriptVisual',\r\n watermarkKey: 'scriptvisual',\r\n capabilities: capabilities.scriptVisual,\r\n create: () => new ScriptVisual({ canRefresh: false })\r\n };\r\n\r\n export let kpi: IVisualPlugin = {\r\n name: 'kpi',\r\n watermarkKey: 'kpi',\r\n capabilities: capabilities.kpi,\r\n create: () => new KPIStatusWithHistory()\r\n };\r\n \r\n export let debugVisual: IVisualPlugin = {\r\n name: 'debugVisual',\r\n // TODO: Create new watermark (waiting on design)\r\n watermarkKey: 'kpi',\r\n capabilities: system.DebugVisual.capabilities,\r\n create: () => new system.DebugVisual()\r\n };\r\n\r\n}\n\n\n/** WEBPACK FOOTER **\n ** ./src/Clients/Visuals/plugins.ts\n **/","/*\r\n * Power BI Visualizations\r\n *\r\n * Copyright (c) Microsoft Corporation\r\n * All rights reserved. \r\n * MIT License\r\n *\r\n * Permission is hereby granted, free of charge, to any person obtaining a copy\r\n * of this software and associated documentation files (the \"\"Software\"\"), to deal\r\n * in the Software without restriction, including without limitation the rights\r\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\r\n * copies of the Software, and to permit persons to whom the Software is\r\n * furnished to do so, subject to the following conditions:\r\n * \r\n * The above copyright notice and this permission notice shall be included in \r\n * all copies or substantial portions of the Software.\r\n * \r\n * THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR \r\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, \r\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE \r\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER \r\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\r\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\r\n * THE SOFTWARE.\r\n */\r\n\r\n/// <reference path=\"../_references.ts\"/>\r\n\r\nmodule powerbi.visuals {\r\n\r\n export module CanvasBackgroundHelper {\r\n export function getDefaultColor(): string {\r\n return '#FFFFFF';\r\n }\r\n\r\n export function getDefaultValues() {\r\n return {\r\n color: getDefaultColor(),\r\n };\r\n }\r\n }\r\n} \n\n\n/** WEBPACK FOOTER **\n ** ./src/Clients/Visuals/common/canvasBackgroundHelper.ts\n **/","/*\r\n * Power BI Visualizations\r\n *\r\n * Copyright (c) Microsoft Corporation\r\n * All rights reserved. \r\n * MIT License\r\n *\r\n * Permission is hereby granted, free of charge, to any person obtaining a copy\r\n * of this software and associated documentation files (the \"\"Software\"\"), to deal\r\n * in the Software without restriction, including without limitation the rights\r\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\r\n * copies of the Software, and to permit persons to whom the Software is\r\n * furnished to do so, subject to the following conditions:\r\n * \r\n * The above copyright notice and this permission notice shall be included in \r\n * all copies or substantial portions of the Software.\r\n * \r\n * THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR \r\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, \r\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE \r\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER \r\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\r\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\r\n * THE SOFTWARE.\r\n */\r\n\r\n/// <reference path=\"../_references.ts\"/>\r\n\r\nmodule powerbi.visuals {\r\n const defaultMaxValue: number = 100;\r\n\r\n export interface IScaledRange<T> {\r\n // initial data type\r\n getValue(): ValueRange<T>;\r\n setValue(value: ValueRange<T>);\r\n\r\n // scaled to 0..100 number.\r\n setScaledValue(value: ValueRange<number>);\r\n getScaledValue(): ValueRange<number>;\r\n }\r\n\r\n /**\r\n * Implements IRange interface for the Date type. \r\n */\r\n export class DateRange implements IScaledRange<Date> {\r\n private value: ValueRange<Date>;\r\n private scaledValue: ValueRange<number>;\r\n private scale: D3.Scale.TimeScale;\r\n\r\n constructor(min: Date, max: Date, start?: Date, end?: Date) {\r\n debug.assert(max > min, \"Requires max date to be bigger than min date.\");\r\n debug.assert((!start || start >= min) && (!end || end <= max), \"Specified date is out of boundaries\");\r\n let interval = <any>d3.time.day;\r\n\r\n this.scale = d3.time.scale()\r\n .domain([min, max])\r\n .range([0, defaultMaxValue])\r\n .nice(interval);\r\n\r\n this.value = {\r\n min: start || min,\r\n max: end || max\r\n };\r\n this.setValue(this.value);\r\n }\r\n\r\n public getScaledValue(): ValueRange<number> {\r\n return this.scaledValue;\r\n }\r\n\r\n public setValue(original: ValueRange<Date>): void {\r\n debug.assert(original != null, \"Value can't be null\");\r\n debug.assert(original.min != null, \"Min can't be null\");\r\n debug.assert(original.max != null, \"Max can't be null\");\r\n\r\n this.value = original;\r\n\r\n this.scaledValue = {\r\n min: this.scale(original.min),\r\n max: this.scale(original.max)\r\n };\r\n\r\n }\r\n\r\n public getValue(): ValueRange<Date> {\r\n return this.value;\r\n }\r\n\r\n /**\r\n * Updates scaled value. \r\n * Value should in range [0 .. 100].\r\n */\r\n public setScaledValue(value: ValueRange<number>): void {\r\n debug.assert(value.min <= 100 && value.min >= 0 &&\r\n value.max >= 0 && value.max <= 100 && value.max >= value.min, \"Value is out of range\");\r\n\r\n this.scaledValue = value;\r\n\r\n this.value = {\r\n min: this.scale.invert(value.min),\r\n max: this.scale.invert(value.max)\r\n };\r\n }\r\n }\r\n}\n\n\n/** WEBPACK FOOTER **\n ** ./src/Clients/Visuals/common/scaleRange.ts\n **/","/*\r\n * Power BI Visualizations\r\n *\r\n * Copyright (c) Microsoft Corporation\r\n * All rights reserved.\r\n * MIT License\r\n *\r\n * Permission is hereby granted, free of charge, to any person obtaining a copy\r\n * of this software and associated documentation files (the \"\"Software\"\"), to deal\r\n * in the Software without restriction, including without limitation the rights\r\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\r\n * copies of the Software, and to permit persons to whom the Software is\r\n * furnished to do so, subject to the following conditions:\r\n *\r\n * The above copyright notice and this permission notice shall be included in\r\n * all copies or substantial portions of the Software.\r\n *\r\n * THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\r\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\r\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\r\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\r\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\r\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\r\n * THE SOFTWARE.\r\n */\r\n\r\n/// <reference path=\"../_references.ts\"/>\r\n\r\nmodule powerbi.visuals {\r\n import TablixObjects = visuals.controls.internal.TablixObjects;\r\n import DataViewObjectDefinitions = data.DataViewObjectDefinitions;\r\n import TableFormattingProperties = visuals.controls.TablixFormattingPropertiesTable;\r\n import Color = jsCommon.Color;\r\n import SQExprBuilder = data.SQExprBuilder;\r\n\r\n let valueOrDefault = jsCommon.Utility.valueOrDefault;\r\n\r\n // Default for values common to most presets to avoid repeating declaring them\r\n const defaultOutlineWeight = TablixObjects.PropGridOutlineWeight.defaultValue;\r\n const defaultGridlineHorizontalWeight = TablixObjects.PropGridHorizontalWeight.defaultValue;\r\n const defaultGridlineVerticalWeight = TablixObjects.PropGridVerticalWeight.defaultValue;\r\n const defaultColumnsOutline = TablixObjects.PropColumnsOutline.defaultValue;\r\n const defaultValuesOutline = TablixObjects.PropValuesOutline.defaultValue;\r\n const defaultTotalOutline = TablixObjects.PropTotalOutline.defaultValue;\r\n const rowPaddingNormal = 3;\r\n\r\n function wrapFormattingElements(elements: TableFormattingProperties): DataViewObjectDefinitions {\r\n return {\r\n grid: [{\r\n properties: {\r\n outlineColor: createSolidFillDefinition(elements.grid.outlineColor),\r\n outlineWeight: SQExprBuilder.integer(valueOrDefault(elements.grid.outlineWeight, defaultOutlineWeight)),\r\n gridVertical: SQExprBuilder.boolean(elements.grid.gridVertical),\r\n gridVerticalColor: createSolidFillDefinition(elements.grid.gridVerticalColor),\r\n gridVerticalWeight: SQExprBuilder.integer(valueOrDefault(elements.grid.gridVerticalWeight, defaultGridlineVerticalWeight)),\r\n gridHorizontal: SQExprBuilder.boolean(elements.grid.gridHorizontal),\r\n gridHorizontalColor: createSolidFillDefinition(elements.grid.gridHorizontalColor),\r\n gridHorizontalWeight: SQExprBuilder.integer(valueOrDefault(elements.grid.gridHorizontalWeight, defaultGridlineHorizontalWeight)),\r\n rowPadding: SQExprBuilder.integer(elements.grid.rowPadding),\r\n },\r\n }],\r\n\r\n columnHeaders: [{\r\n properties: {\r\n outline: SQExprBuilder.text(elements.columnHeaders.outline),\r\n fontColor: createSolidFillDefinition(elements.columnHeaders.fontColor),\r\n backColor: createSolidFillDefinition(elements.columnHeaders.backColor),\r\n }\r\n }],\r\n\r\n values: [{\r\n properties: {\r\n outline: SQExprBuilder.text(elements.values.outline),\r\n fontColorPrimary: createSolidFillDefinition(elements.values.fontColorPrimary),\r\n backColorPrimary: createSolidFillDefinition(elements.values.backColorPrimary),\r\n fontColorSecondary: createSolidFillDefinition(elements.values.fontColorSecondary),\r\n backColorSecondary: createSolidFillDefinition(elements.values.backColorSecondary),\r\n }\r\n }],\r\n\r\n total: [{\r\n properties: {\r\n outline: SQExprBuilder.text(elements.total.outline),\r\n fontColor: createSolidFillDefinition(elements.total.fontColor),\r\n backColor: createSolidFillDefinition(elements.total.backColor),\r\n }\r\n }],\r\n };\r\n }\r\n\r\n export const tableStylePresets: VisualStylePresets = {\r\n displayName: data.createDisplayNameGetter('Visual_Table_StylePreset_SectionTitle'),\r\n presets: {\r\n None: {\r\n displayName: data.createDisplayNameGetter('Visual_Table_StylePreset_None'),\r\n\r\n evaluate: (theme: IVisualStyle) => {\r\n return wrapFormattingElements({\r\n grid: {\r\n outlineColor: TablixObjects.PropGridOutlineColor.defaultValue,\r\n gridVertical: TablixObjects.PropGridVertical.defaultValue,\r\n gridVerticalColor: TablixObjects.PropGridVerticalColor.defaultValue,\r\n gridHorizontal: TablixObjects.PropGridHorizontalTable.defaultValue,\r\n gridHorizontalColor: TablixObjects.PropGridHorizontalColor.defaultValue,\r\n rowPadding: TablixObjects.PropGridRowPadding.defaultValue,\r\n },\r\n\r\n columnHeaders: {\r\n outline: defaultColumnsOutline,\r\n fontColor: TablixObjects.PropColumnsFontColor.defaultValue,\r\n backColor: TablixObjects.PropColumnsBackColor.defaultValue,\r\n },\r\n\r\n values: {\r\n outline: defaultValuesOutline,\r\n fontColorPrimary: TablixObjects.PropValuesOutline.defaultValue,\r\n backColorPrimary: TablixObjects.PropValuesOutline.defaultValue,\r\n fontColorSecondary: TablixObjects.PropValuesOutline.defaultValue,\r\n backColorSecondary: TablixObjects.PropValuesOutline.defaultValue,\r\n },\r\n\r\n total: {\r\n outline: defaultTotalOutline,\r\n fontColor: TablixObjects.PropTotalFontColor.defaultValue,\r\n backColor: TablixObjects.PropTotalBackColor.defaultValue,\r\n },\r\n });\r\n },\r\n },\r\n\r\n Minimal: {\r\n displayName: data.createDisplayNameGetter('Visual_Table_StylePreset_Minimal'),\r\n\r\n evaluate: (theme: IVisualStyle) => {\r\n let backColor = theme.colorPalette.background.value;\r\n let foreColor = theme.colorPalette.foreground.value;\r\n let outlineColor = theme.colorPalette.tableAccent.value;\r\n let gridColor = Color.hexBlend(foreColor, 0.12, backColor);\r\n\r\n return wrapFormattingElements({\r\n grid: {\r\n outlineColor: outlineColor,\r\n gridVertical: false,\r\n gridVerticalColor: gridColor,\r\n gridVerticalWeight: defaultGridlineVerticalWeight,\r\n gridHorizontal: true,\r\n gridHorizontalColor: gridColor,\r\n gridHorizontalWeight: defaultGridlineHorizontalWeight,\r\n rowPadding: rowPaddingNormal,\r\n },\r\n\r\n columnHeaders: {\r\n outline: defaultColumnsOutline,\r\n fontColor: foreColor,\r\n backColor: backColor,\r\n },\r\n\r\n values: {\r\n outline: defaultValuesOutline,\r\n fontColorPrimary: foreColor,\r\n backColorPrimary: backColor,\r\n fontColorSecondary: foreColor,\r\n backColorSecondary: backColor,\r\n },\r\n\r\n total: {\r\n outline: defaultTotalOutline,\r\n fontColor: foreColor,\r\n backColor: backColor,\r\n },\r\n });\r\n },\r\n },\r\n },\r\n };\r\n}\n\n\n/** WEBPACK FOOTER **\n ** ./src/Clients/Visuals/stylePresets/table.stylePresets.ts\n **/"],"mappings":";AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;ACtCA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AAEA;AACA;AACA;;;;;;;ACfA;;;;;;ACAA;;;;;;ACAA;;;;;;ACAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;;;;;;;;;;;;;ACvLA;;;;;;;;;;;;;;;;;;;;;;;;AAwBA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;;;;;;;;;;;;;ACrCA;;;;;;;;;;;;;;;;;;;;;;;;AAwBA;AAEA;AAEA;AAAA;AAAA;AAAA;AACA;AAIA;AACA;AACA;AACA;AACA;AAAA;AARA;AASA;AAAA;AACA;;;;;;;;;;;;;;;;ACvCA;;;;;;;;;;;;;;;;;;;;;;;;AAwBA;AAEA;AAEA;AAAA;AAAA;AAAA;AAEA;AAOA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAAA;AAdA;AAeA;AAAA;AACA;;;;;;;;;;;;;;;;AC9CA;;;;;;;;;;;;;;;;;;;;;;;;AAwBA;AAEA;AAEA;AAAA;AAAA;AAAA;AACA;AAAA;AAEA;AACA;AAAA;AAAA;;AAKA;AAHA;;AAEA;AACA;AACA;AACA;;;AAAA;AAEA;;;AAGA;AACA;AAAA;AACA;AACA;AACA;AAEA;AACA;AAKA;AAHA;;AAEA;AACA;AACA;AACA;;;AAAA;AAEA;;AAEA;AACA;AACA;AACA;AACA;AAAA;AAnCA;AAqCA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAAA;AACA;;;;;;;;;;;;;;;;ACjFA;;;;;;;;;;;;;;;;;;;;;;;;AAwBA;AAEA;AAEA;AAAA;AAAA;AAAA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AAJA;AAMA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAVA;AAoCA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AAGA;AACA;AACA;AAAA;AACA;AAAA;AACA;AAPA;AAsBA;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AARA;AAUA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AARA;AAUA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAPA;AAQA;AAqCA;AAgDA;AAvCA;AACA;AAIA;AACA;AACA;AACA;AACA;AAoCA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AAAA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAAA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AAEA;AACA;AACA;AACA;AAEA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AAEA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AAEA;AAEA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AAAA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AAEA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;;AAEA;AACA;AAEA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AAAA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAAA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AAEA;AAAA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAAA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AAAA;AAEA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AAEA;AACA;AAEA;AAAA;AAKA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAGA;AACA;AAEA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AAEA;AACA;AAEA;AACA;AAEA;AAAA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAAA;AACA;AACA;AACA;AArwBA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AA4uBA;AAAA;AA1xBA;AA4xBA;AAcA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAAA;AAEA;AACA;AACA;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AAEA;;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;;AAEA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAAA;AACA;AACA;AACA;AAjLA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAyKA;AAAA;AAEA;AAAA;AAEA;AAEA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AAEA;AACA;AAEA;AACA;AAEA;AAEA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AA/BA;AAgCA;AACA;AAAA;AACA;;;;;;;;;;;;;;;;ACnqCA;;;;;;;;;;;;;;;;;;;;;;;;AAwBA;AAEA;AAEA;AAAA;AAAA;AAAA;AACA;AAAA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAAA;AACA;;;;;;;;;;;;;;;;ACvCA;;;;;;;;;;;;;;;;;;;;;;;;AAwBA;AAEA;AAEA;AAAA;AAAA;AAAA;AACA;AAAA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AAAA;AACA;;;;;;;;;;;;;;;;ACzCA;;;;;;;;;;;;;;;;;;;;;;;;AAwBA;AAEA;AAEA;AAAA;AAAA;AAAA;AACA;AAAA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAAA;AACA;;;;;;;;;;;;;;;;ACxCA;;;;;;;;;;;;;;;;;;;;;;;;AAwBA;AAEA;AAEA;AAAA;AAAA;AAAA;AACA;AAAA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAAA;AACA;;;;;;;;;;;;;;;;AC7CA;;;;;;;;;;;;;;;;;;;;;;;;AAwBA;AAEA;AAEA;AAAA;AAAA;AAAA;AACA;AAAA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AAAA;AACA;;;;;;;;;;;;;;;;ACzCA;;;;;;;;;;;;;;;;;;;;;;;;AAwBA;AAEA;AAEA;AAAA;AAAA;AAAA;AAEA;AAAA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAAA;AACA;;;;;;;;;;;;;;;;AC7CA;;;;;;;;;;;;;;;;;;;;;;;;AAwBA;AAEA;AAEA;AAAA;AAAA;AAAA;AACA;AAAA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AAAA;AACA;;;;;;;;;;;;;;;;ACzCA;;;;;;;;;;;;;;;;;;;;;;;;AAwBA;AAEA;AAEA;AAAA;AAAA;AAAA;AACA;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAAA;AACA;;;;;;;;;;;;;;;;ACnDA;;;;;;;;;;;;;;;;;;;;;;;;AAwBA;AAEA;AAEA;AAAA;AAAA;AAAA;AACA;AAAA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAAA;AACA;;;;;;;;;;;;;;;;ACvCA;;;;;;;;;;;;;;;;;;;;;;;;AAwBA;AAEA;AAEA;AAAA;AAAA;AAAA;AACA;AAAA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AAAA;AACA;;;;;;;;;;;;;;;;ACzCA;;;;;;;;;;;;;;;;;;;;;;;;AAwBA;AAEA;AAEA;AAAA;AAAA;AAAA;AACA;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAFA;AAIA;AACA;AACA;AAFA;AAIA;AACA;AACA;AAFA;AAIA;AACA;AACA;AAFA;AAIA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAAA;AACA;;;;;;;;;;;;;;;;ACnEA;;;;;;;;;;;;;;;;;;;;;;;;AAwBA;AAEA;AAEA;AAAA;AAAA;AAAA;AACA;AAAA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AAAA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AAAA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAAA;AACA;;;;;;;;;;;;;;;;AC3DA;;;;;;;;;;;;;;;;;;;;;;;;AAwBA;AAEA;AAEA;AAAA;AAAA;AAAA;AACA;AAAA;AAMA;AACA;AACA;AACA;AACA;AACA;AAAA;AACA;;;;;;;;;;;;;;;;ACzCA;;;;;;;;;;;;;;;;;;;;;;;;AAwBA;AAEA;AAEA;AAAA;AAAA;AAAA;AACA;AAAA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAAA;AACA;;;;;;;;;;;;;;;;ACvCA;;;;;;;;;;;;;;;;;;;;;;;;AAwBA;AAEA;AAEA;AAAA;AAAA;AAAA;AACA;AAAA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AAAA;AACA;;;;;;;;;;;;;;;;ACzCA;;;;;;;;;;;;;;;;;;;;;;;;AAwBA;AAEA;AAEA;AAAA;AAAA;AAAA;AACA;AAAA;AACA;AAEA;AACA;AAEA;AACA;AACA;AAFA;AAGA;AAuBA;;;AAGA;AACA;AAGA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AAAA;AAtBA;AAuBA;AAAA;AACA;;;;;;;;;;;;;;;;ACzFA;;;;;;;;;;;;;;;;;;;;;;;;AAwBA;;;;;;AAEA;AAEA;AAAA;AAAA;AAAA;AAkBA;AAAA;AAGA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AAAA;AAvIA;AAwIA;AAAA;AACA;;;;;;;;;;;;;;;;ACvLA;;;;;;;;;;;;;;;;;;;;;;;;AAwBA;;;;;;AAEA;AAEA;AAAA;AAAA;AAAA;AAqBA;AAAA;AAGA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAAA;AAEA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AAAA;AAnNA;AAoNA;AAAA;AACA;;;;;;;;;;;;;;;;ACtQA;;;;;;;;;;;;;;;;;;;;;;;;AAwBA;;;;;;AAEA;AAEA;AAAA;AAAA;AAAA;AAsBA;AAAA;AAGA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AAEA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AAEA;AAEA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AAEA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AAEA;AACA;AACA;AACA;AACA;AAEA;AAEA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AAEA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AAEA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AAEA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AAEA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AAAA;AA/QA;AAgRA;AAAA;AACA;;;;;;;;;;;;;;;;ACnUA;;;;;;;;;;;;;;;;;;;;;;;;AAwBA;;;;;;AAEA;AAEA;AAAA;AAAA;AAAA;AAsBA;AAAA;AAGA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AAEA;AAEA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AAEA;AACA;AAEA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AAEA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AAEA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AAEA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AAEA;AACA;AACA;AAAA;AA7NA;AA8NA;AAAA;AACA;;;;;;;;;;;;;;;;ACjRA;;;;;;;;;;;;;;;;;;;;;;;;AAwBA;AAEA;AAEA;AAAA;AAAA;AAAA;AACA;;;AAGA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAAA;AACA;;;;;;;;;;;;;;;;AC3MA;;;;;;;;;;;;;;;;;;;;;;;;AAwBA;AAEA;AAEA;AAAA;AAAA;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAAA;AACA;;;;;;;;;;;;;;;;AC7CA;;;;;;;;;;;;;;;;;;;;;;;;AAwBA;AAEA;AAEA;AAAA;AAAA;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAAA;AACA;;;;;;;;;;;;;;;;ACxGA;;;;;;;;;;;;;;;;;;;;;;;;AAwBA;AAEA;AAEA;AAAA;AAAA;AAAA;AACA;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AA/PA;AAiQA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAAA;AACA;;;;;;;;;;;;;;;;ACnTA;;;;;;;;;;;;;;;;;;;;;;;;AAwBA;AAEA;AAEA;AAAA;AAAA;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAAA;AACA;;;;;;;;;;;;;;;;ACjUA;;;;;;;;;;;;;;;;;;;;;;;;AAwBA;AAEA;AAEA;AAAA;AAAA;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAAA;AACA;;;;;;;;;;;;;;;;AChJA;;;;;;;;;;;;;;;;;;;;;;;;AAwBA;AAEA;AAEA;AAAA;AAAA;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAAA;AACA;;;;;;;;;;;;;;;;ACtEA;;;;;;;;;;;;;;;;;;;;;;;;AAwBA;AAEA;AAEA;AAAA;AAAA;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAAA;AACA;;;;;;;;;;;;;;;;ACjMA;;;;;;;;;;;;;;;;;;;;;;;;AAwBA;AAEA;AAEA;AAAA;AAAA;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAAA;AACA;;;;;;;;;;;;;;;;AC7IA;;;;;;;;;;;;;;;;;;;;;;;;AAwBA;AAEA;AAEA;AAAA;AAAA;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AAAA;AACA;;;;;;;;;;;;;;;;AC/IA;;;;;;;;;;;;;;;;;;;;;;;;AAwBA;AAEA;AAEA;AAAA;AAAA;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAAA;AACA;;;;;;;;;;;;;;;;ACrDA;;;;;;;;;;;;;;;;;;;;;;;;AAwBA;AAEA;AAEA;AAAA;AAAA;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAAA;AACA;;;;;;;;;;;;;;;;AC5EA;;;;;;;;;;;;;;;;;;;;;;;;AAwBA;AAEA;AAEA;AAAA;AAAA;AAAA;AAAA;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAAA;AAAA;AACA;;;;;;;;;;;;;;;;ACrDA;;;;;;;;;;;;;;;;;;;;;;;;AAwBA;AAEA;AAEA;AAAA;AAAA;AAAA;AAAA;AAAA;AAEA;AAAA;AA4BA;AA1BA;AACA;AACA;AAEA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AAEA;AACA;AACA;AACA;AACA;AAAA;AA5BA;AA6BA;AAAA;AAAA;AACA;;;;;;;;;;;;;;;;AC5DA;;;;;;;;;;;;;;;;;;;;;;;;AAwBA;AAEA;AAEA;AAAA;AAAA;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAAA;AACA;;;;;;;;;;;;;;;;ACnSA;;;;;;;;;;;;;;;;;;;;;;;;AAwBA;AAEA;AAEA;AAAA;AAAA;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAAA;AACA;;;;;;;;;;;;;;;;ACnNA;;;;;;;;;;;;;;;;;;;;;;;;AAwBA;AAEA;AAEA;AAAA;AAAA;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAAA;AACA;;;;;;;;;;;;;;;;AC/IA;;;;;;;;;;;;;;;;;;;;;;;;AAwBA;AAEA;AAEA;AAAA;AAAA;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAAA;AACA;;;;;;;;;;;;;;;;AC5CA;;;;;;;;;;;;;;;;;;;;;;;;AAwBA;AAEA;AAEA;AAAA;AAAA;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAAA;AACA;;;;;;;;;;;;;;;;AC/DA;;;;;;;;;;;;;;;;;;;;;;;;AAwBA;AAEA;AAEA;AAAA;AAAA;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAAA;AACA;;;;;;;;;;;;;;;;AC1YA;;;;;;;;;;;;;;;;;;;;;;;;AAwBA;AAEA;AAEA;AAAA;AAAA;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAAA;AACA;;;;;;;;;;;;;;;;ACjKA;;;;;;;;;;;;;;;;;;;;;;;;AAwBA;AAEA;AAEA;AAAA;AAAA;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAAA;AACA;;;;;;;;;;;;;;;;ACnLA;;;;;;;;;;;;;;;;;;;;;;;;AAwBA;AAEA;AAEA;AAAA;AAAA;AAAA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAAA;AACA;;;;;;;;;;;;;;;;AC7NA;;;;;;;;;;;;;;;;;;;;;;;;AAwBA;AAEA;AAEA;AAAA;AAAA;AAAA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAAA;AACA;;;;;;;;;;;;;;;;ACjLA;;;;;;;;;;;;;;;;;;;;;;;;AAwBA;AAEA;AAEA;AAAA;AAAA;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AAAA;AACA;;;;;;;;;;;;;;;;AClGA;;;;;;;;;;;;;;;;;;;;;;;;AAwBA;AAEA;AAEA;AAAA;AAAA;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAAA;AACA;;;;;;;;;;;;;;;;ACnMA;;;;;;;;;;;;;;;;;;;;;;;;AAwBA;AAEA;AAEA;AAAA;AAAA;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAAA;AACA;;;;;;;;;;;;;;;;AC7HA;;;;;;;;;;;;;;;;;;;;;;;;AAwBA;AAEA;AAEA;AAAA;AAAA;AAAA;AAAA;AAAA;AACA;AACA;AAEA;AAEA;AAEA;AAEA;AAEA;AAEA;AAEA;AAEA;AAEA;AAEA;AAEA;AAEA;AAEA;AAEA;AAEA;AAEA;AAEA;AAEA;AAEA;AAEA;AAEA;AAEA;AAEA;AAEA;AAEA;AAEA;AAEA;AAEA;AAEA;AAEA;AAEA;AAEA;AAEA;AAEA;AACA;AAAA;AAAA;AACA;;;;;;;;;;;;;;;;ACpGA;;;;;;;;;;;;;;;;;;;;;;;;AAwBA;AAEA;AAEA;AAAA;AAAA;AAAA;AAYA;AAAA;AAmCA;AAhCA;AACA;AACA;AAEA;AACA;AAEA;AACA;AAEA;AACA;AACA;AAEA;AAEA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAAA;AAnCA;AAoCA;AAAA;AACA;;;;;;;;;;;;;;;;AC7EA;;;;;;;;;;;;;;;;;;;;;;;;AAwBA;AAEA;AAEA;AAAA;AAAA;AAAA;AAQA;AAAA;AAiBA;AAdA;AACA;AACA;AAEA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAAA;AAjBA;AAkBA;AAAA;AACA;;;;;;;;;;;;;;;;ACvDA;;;;;;;;;;;;;;;;;;;;;;;;AAwBA;AAEA;AAEA;AAAA;AAAA;AAAA;AAUA;AAAA;AAwCA;AAnCA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAAA;AAxCA;AAyCA;AAAA;AACA;;;;;;;;;;;;;;;;AChFA;;;;;;;;;;;;;;;;;;;;;;;;AAwBA;AAEA;AAEA;AAAA;AAAA;AAAA;AAQA;AAAA;AAyBA;AApBA;AAEA;AACA;AACA;AAEA;AAEA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAAA;AAzBA;AA0BA;AAAA;AACA;;;;;;;;;;;;;;;;AC/DA;;;;;;;;;;;;;;;;;;;;;;;;AAwBA;AAIA;AACA;;;;;;;;;;;;;;;;AC7BA;;;;;;;;;;;;;;;;;;;;;;;;AAwBA;AAEA;AAEA;AAAA;AAAA;AAAA;AAUA;AAAA;AA8BA;AAxBA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AAAA;AA9BA;AA+BA;AAAA;AACA;;;;;;;;;;;;;;;;ACtEA;;;;;;;;;;;;;;;;;;;;;;;;AAwBA;AAEA;AAEA;AAAA;AAAA;AAAA;AAcA;AAAA;AAIA;AACA;AACA;AACA;AA6JA;AA3JA;AAAA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AAAA;AApKA;AAqKA;AAAA;AACA;;;;;;;;;;;;;;;;AChNA;;;;;;;;;;;;;;;;;;;;;;;;AAwBA;AAEA;AAEA;AAAA;AAAA;AAAA;AAEA;AA+BA;AAAA;AA+CA;AAzCA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AAAA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAAA;AA/CA;AA2DA;AAAA;AA4RA;AAzQA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AAEA;AAEA;AAAA;AAAA;AAAA;AAAA;;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AAAA;AACA;AACA;AACA;AACA;AAEA;AAAA;AAAA;AAAA;AAAA;;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAAA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AAAA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAAA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAAA;AACA;AACA;AAAA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AA1RA;AACA;AACA;AACA;AAEA;AACA;AAqRA;AAAA;AA5RA;AA6RA;AAAA;AACA;;;;;;;;;;;;;;;;ACtZA;;;;;;;;;;;;;;;;;;;;;;;;AAwBA;AAEA;AAEA;AAAA;AAAA;AAAA;AAKA;AAAA;AAkBA;AAZA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AAAA;AAlBA;AAmBA;AAAA;AACA;;;;;;;;;;;;;;;;ACrDA;;;;;;;;;;;;;;;;;;;;;;;;AAwBA;AAEA;AAEA;AAAA;AAAA;AAAA;AAOA;AAAA;AAsBA;AAfA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AAAA;AAtBA;AAuBA;AAAA;AACA;;;;;;;;;;;;;;;;AC3DA;;;;;;;;;;;;;;;;;;;;;;;;AAwBA;AAEA;AAEA;AAAA;AAAA;AAAA;AACA;AACA;AAkBA;AAAA;AAqKA;AAjKA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AASA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AAlKA;AAmKA;AAAA;AArKA;AAsKA;AAAA;AACA;;;;;;;;;;;;;;;;ACvNA;;;;;;;;;;;;;;;;;;;;;;;;AAwBA;AAEA;AAEA;AAAA;AAAA;AAAA;AAOA;AAAA;AAmCA;AA/BA;AACA;AACA;AACA;AAEA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAjCA;AAkCA;AAAA;AAnCA;AAoCA;AAAA;AACA;;;;;;;;;;;;;;;;ACxEA;;;;;;;;;;;;;;;;;;;;;;;;AAwBA;AAEA;AAEA;AAAA;AAAA;AAAA;AAUA;AAAA;AAgCA;AA3BA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAAA;AAhCA;AAiCA;AAAA;AACA;;;;;;;;;;;;;;;;ACxEA;;;;;;;;;;;;;;;;;;;;;;;;AAwBA;AAEA;AAEA;AAAA;AAAA;AAAA;AAKA;AAAA;AA4BA;AAzBA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAAA;AA5BA;AA6BA;AAAA;AACA;;;;;;;;;;;;;;;;AC/DA;;;;;;;;;;;;;;;;;;;;;;;;AAwBA;AAEA;AAEA;AAAA;AAAA;AAAA;AAKA;AAAA;AA4BA;AAvBA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AA1BA;AACA;AA0BA;AAAA;AA5BA;AA6BA;AAAA;AACA;;;;;;;;;;;;;;;;AC/DA;;;;;;;;;;;;;;;;;;;;;;;;AAwBA;AAEA;AAEA;AAAA;AAAA;AAAA;AAMA;AAGA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AAAA;AACA;AACA;AACA;AACA;AAAA;AAvBA;AAwBA;AAAA;AACA;;;;;;;;;;;;;;;;AC3DA;;;;;;;;;;;;;;;;;;;;;;;;AAwBA;AAIA;AACA;;;;;;;;;;;;;;;;AC7BA;;;;;;;;;;;;;;;;;;;;;;;;AAwBA;AAEA;AAEA;AAAA;AAAA;AAAA;AAGA;;;AAGA;AACA;AAkKA;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AAPA;AASA;AACA;AACA;AACA;AACA;AAEA;AACA;AAPA;AASA;;;;;;;;;AASA;AACA;AACA;AAEA;AACA;AAEA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AAvBA;AAyBA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AATA;AAWA;AAMA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAbA;AAeA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAfA;AAiBA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AAEA;AACA;AArCA;AAuCA;;;;;;;;;;;AAWA;AACA;AAAA;AACA;AACA;AAEA;AAEA;AACA;AAEA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AAjBA;AAmBA;AACA;AAcA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AAIA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAzJA;AA2JA;AACA;AACA;AAFA;AAIA;AACA;AACA;AAFA;AAIA;AACA;AACA;AAFA;AAIA;AACA;AACA;AAFA;AAIA;AACA;AACA;AAFA;AAIA;AACA;AACA;AACA;AACA;AACA;AALA;AAOA;AACA;AACA;AACA;AACA;AACA;AALA;AAOA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AAZA;AAcA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAXA;AAaA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAjBA;AAmBA;AACA;AAIA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AAbA;AAeA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAXA;AAaA;AACA;AACA;AACA;AACA;AACA;AACA;AANA;AAQA;;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AATA;AAWA;;;AAGA;AACA;AACA;AASA;AAMA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AAaA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AApGA;AAsGA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AAWA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAGA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AAEA;AACA;AACA;AACA;AACA;AACA;AAnFA;AAqFA;AASA;AAIA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAjEA;AAmEA;;AAEA;AACA;AAOA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAdA;AAgBA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AAEA;;;;;AAKA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AAXA;AAaA;AACA;AACA;AAEA;AACA;AACA;AACA;AAAA;AACA;AACA;AACA;AACA;AACA;AAEA;AAAA;AACA;AAMA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAlBA;AAoBA;AAQA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AAEA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AArDA;AAuDA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AAWA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AAEA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AAAA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AAnEA;AAqEA;AAKA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AArBA;AAuBA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AARA;AASA;AAEA;AAAA;AACA;AACA;AAEA;AACA;AACA;AACA;AAPA;AASA;AACA;AACA;AACA;AACA;AAEA;AACA;AAPA;AASA;AACA;AACA;AAKA;AAIA;AACA;AACA;AACA;AACA;AACA;AACA;AAfA;AAiBA;AAAA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AAEA;AACA;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAXA;AAaA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAXA;AAaA;;AAEA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AA5BA;AA8BA;;;AAGA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AAEA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAzBA;AA2BA;AAAA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAjBA;AAmBA;AACA;AACA;AACA;AACA;AACA;AALA;AAOA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AATA;AAWA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AARA;AASA;AACA;AAAA;AACA;;;;;;;;;;;;;;;;ACv6CA;;;;;;;;;;;;;;;;;;;;;;;;AAwBA;AAEA;AAEA;AAAA;AAAA;AAAA;AACA;AAAA;AAEA;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AATA;AAWA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AAEA;AACA;AAVA;AAYA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAAA;AACA;AAEA;AACA;AACA;AACA;AACA;AAAA;AACA;AAEA;AACA;AACA;AACA;AACA;AAAA;AACA;AAEA;AACA;AACA;AACA;AACA;AAAA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AA/DA;AAiEA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAjBA;AAmBA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AAhCA;AAkCA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAAA;AACA;AACA;AAAA;AACA;AACA;AAAA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAAA;AACA;AACA;AAAA;AACA;AACA;AAAA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAAA;AACA;;;;;;;;;;;;;;;;ACnRA;;;;;;;;;;;;;;;;;;;;;;;;AAwBA;AAEA;AAEA;AAAA;AAAA;AAAA;AACA;AAAA;AACA;AACA;AACA;AACA;AAEA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AA3BA;AA6BA;AACA;AACA;AACA;AAEA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAnCA;AAqCA;AACA;AACA;AACA;AACA;AACA;AACA;AANA;AAQA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AARA;AAUA;AACA;AACA;AAEA;AAEA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AArCA;AAuCA;AACA;AACA;AACA;AACA;AACA;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAZA;AAaA;AACA;AAAA;AACA;;;;;;;;;;;;;;;;ACxKA;;;;;;;;;;;;;;;;;;;;;;;;AAwBA;AAEA;AAEA;AAAA;AAAA;AAAA;AACA;AAEA;AAMA;AACA;AACA;AACA;AACA;AACA;AAEA;;;;AAIA;AACA;AACA;AACA;AACA;AACA;AAEA;;AAEA;AACA;AACA;AACA;AAEA;;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AAAA;AApDA;AAqDA;AAAA;AACA;;;;;;;;;;;;;;;;ACrFA;;;;;;;;;;;;;;;;;;;;;;;;AAwBA;AAEA;AAEA;AAAA;AAAA;AAAA;AAGA;AAEA;AAAA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AATA;AAWA;AACA;AACA;AAEA;AACA;AAEA;AACA;AAEA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AA3EA;AA6EA;AAYA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AAtCA;AAwCA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AARA;AAUA;AACA;AACA;AACA;AACA;AAJA;AAMA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAZA;AAcA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAZA;AAcA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AAnBA;AAqBA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AAhCA;AAkCA;AAAA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAdA;AAgBA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAbA;AAeA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AAnBA;AAoBA;AAEA;AAAA;AAEA;AAIA;AACA;AAEA;AACA;AAEA;AACA;AAXA;AAYA;AAOA;AAAA;AACA;AAEA;AAAA;AACA;AACA;AAFA;AAIA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAtBA;AAwBA;AAMA;AACA;AAEA;AAGA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AA3BA;AA6BA;AAIA;AACA;AAEA;AACA;AAEA;AACA;AAXA;AAYA;AACA;AAAA;AACA;;;;;;;;;;;;;;;;AC5ZA;;;;;;;;;;;;;;;;;;;;;;;;AAwBA;AAEA;AAEA;AAAA;AAAA;AAAA;AACA;AASA;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAVA;AAYA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AA5BA;AA8BA;AACA;AAEA;AACA;AACA;AACA;AANA;AAQA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAZA;AAcA;AACA;AACA;AACA;AACA;AACA;AAEA;AAIA;AACA;AAEA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAzBA;AA2BA;AACA;AACA;AACA;AAHA;AAKA;AACA;AACA;AACA;AAHA;AAKA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AALA;AAOA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAPA;AAQA;AACA;AAAA;AACA;;;;;;;;;;;;;;;;AC1KA;;;;;;;;;;;;;;;;;;;;;;;;AAwBA;AAEA;AAEA;AAAA;AAAA;AAAA;AAEA;AACA;AACA;AAoGA;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAtBA;AAwBA;AAEA;AAEA;AACA;AACA;AANA;AAQA;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AATA;AAWA;AACA;AACA;AACA;AACA;AACA;AALA;AAOA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AARA;AAUA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AATA;AAWA;AACA;AACA;AACA;AACA;AACA;AALA;AAOA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AATA;AAWA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAVA;AAYA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAVA;AAYA;AACA;AACA;AACA;AACA;AAJA;AAMA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AATA;AAWA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AATA;AAWA;AACA;AAEA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AApBA;AAsBA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AA1CA;AA4CA;AAAA;AAAA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAVA;AAYA;AACA;AACA;AACA;AACA;AACA;AALA;AAOA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAVA;AAYA;AACA;AACA;AACA;AACA;AACA;AALA;AAOA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AAEA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AAEA;AACA;AACA;AAEA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AA5BA;AA8BA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAxBA;AA0BA;AAUA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AA/BA;AAiCA;;;AAGA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAAA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AALA;AAOA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAvBA;AAyBA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AArBA;AAuBA;AAGA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AArDA;AAuDA;AAAA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AA3BA;AA6BA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAvBA;AAyBA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AATA;AAWA;AACA;AACA;AAFA;AAEA;AAEA;AACA;AACA;AAFA;AAEA;AACA;AACA;AAAA;AACA;;;;;;;;;;;;;;;;ACzzBA;;;;;;;;;;;;;;;;;;;;;;;;AAwBA;AAEA;AAEA;AAAA;AAAA;AAAA;AAGA;AAAA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AA1BA;AA4BA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAdA;AAgBA;AACA;AACA;AACA;AAHA;AAKA;AACA;AACA;AAFA;AAGA;AACA;AAAA;AACA;;;;;;;;;;;;;;;;AC5FA;;;;;;;;;;;;;;;;;;;;;;;;AAwBA;AAEA;AAEA;AAAA;AAAA;AAAA;AAEA;AACA;AAGA;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AAEA;AAAA;AAAA;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AA/EA;AAiFA;AAAA;AAAA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AAxCA;AA0CA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AAlCA;AAoCA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAvBA;AAyBA;AACA;AACA;AAFA;AAIA;AACA;AACA;AACA;AACA;AAJA;AAMA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAvBA;AAyBA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AA5BA;AA8BA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAjBA;AAmBA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAZA;AAcA;;;AAGA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAAA;AACA;;;;;;;;;;;;;;;;ACxYA;;;;;;;;;;;;;;;;;;;;;;;;AAwBA;AAEA;AAEA;AAAA;AAAA;AAAA;AAEA;AAAA;AAkBA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AAEA;AAEA;AACA;AAEA;AAEA;AACA;AACA;AAEA;AACA;AAEA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AANA;AAQA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAbA;AAcA;AACA;AAAA;AACA;;;;;;;;;;;;;;;;ACxMA;;;;;;;;;;;;;;;;;;;;;;;;AAwBA;AAEA;AAEA;AAAA;AAAA;AAAA;AACA;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AATA;AAUA;AACA;AAAA;AACA;;;;;;;;;;;;;;;;AC1CA;;;;;;;;;;;;;;;;;;;;;;;;AAwBA;AAEA;AAEA;AAAA;AAAA;AAAA;AA2BA;AAAA;AACA;AACA;AACA;AACA;AAgBA;AAYA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AAEA;;;;;;;;;;;;;;;;;;;;;;;;;AAyBA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAAA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAzaA;AACA;AAEA;AACA;AAsaA;AAAA;AAhbA;AAibA;AAAA;AACA;;;;;;;;;;;;;;;;AC7fA;;;;;;;;;;;;;;;;;;;;;;;;AAwBA;AAEA;AAEA;AAAA;AAAA;AAAA;AACA;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AAEA;AACA;AAEA;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAzEA;AA2EA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AAEA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAhEA;AAkEA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAhGA;AAkGA;AACA;AAEA;AACA;AAEA;AACA;AAPA;AAQA;AACA;AAAA;AACA;;;;;;;;;;;;;;;;ACtSA;;;;;;;;;;;;;;;;;;;;;;;;AAwBA;AAEA;AAEA;AAAA;AAAA;AAAA;AACA;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AALA;AAOA;AACA;AACA;AACA;AAHA;AAKA;AACA;AACA;AAFA;AAIA;AACA;AACA;AAFA;AAIA;AACA;AACA;AACA;AAHA;AAKA;AACA;AACA;AACA;AACA;AACA;AACA;AANA;AAQA;AACA;AACA;AACA;AACA;AACA;AACA;AANA;AAQA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAAA;AACA;;;;;;;;;;;;;;;;ACrFA;;;;;;;;;;;;;;;;;;;;;;;;AAwBA;AAEA;AAEA;AAAA;AAAA;AAAA;AAGA;AAMA;AAKA;AACA;AACA;AAbA;AAeA;AAUA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAAA;AACA;AAAA;AACA;;;;;;;;;;;;;;;;ACzHA;;;;;;;;;;;;;;;;;;;;;;;;AAwBA;AAEA;AAEA;AAAA;AAAA;AAAA;AAUA;AAAA;AACA;AACA;AACA;AAFA;AAGA;AAcA;;;AAGA;AACA;AAmBA;AAnBA;AAoBA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AAEA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AAAA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AAAA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;;;;;AAKA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AAGA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AAEA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AA1NA;;;AAGA;AACA;AACA;AAsNA;AAAA;AACA;AAAA;AACA;;;;;;;;;;;;;;;;ACrSA;;;;;;;;;;;;;;;;;;;;;;;;AAwBA;AAEA;AAEA;AAAA;AAAA;AAAA;AACA;AAEA;AAAA;AAMA;AACA;AACA;AAEA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AAEA;AACA;AACA;AAFA;AAIA;AACA;AACA;AAEA;AACA;AAEA;AACA;AARA;AAUA;;;;;AAKA;AACA;AAEA;;;;AAIA;AACA;AAAA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAhCA;AAkCA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AAGA;AArBA;AAuBA;;;;AAIA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AAEA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AAAA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAnDA;AAqDA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AApBA;AAsBA;AACA;AACA;AAFA;AAIA;AACA;AACA;AACA;AACA;AAJA;AAMA;AACA;AACA;AACA;AAHA;AAKA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAPA;AASA;AAAA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAdA;AAeA;AACA;AAEA;AAKA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AAAA;AACA;AACA;AACA;AACA;AACA;;;AAAA;AAEA;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;AAAA;AAEA;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;AAAA;AAEA;AACA;AACA;AACA;AACA;AAEA;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;AAAA;AAEA;AACA;AACA;AACA;AACA;AAAA;AAvEA;AAwEA;AAAA;AACA;;;;;;;;;;;;;;;;AC7WA;;;;;;;;;;;;;;;;;;;;;;;;AAwBA;AAEA;AAEA;AAAA;AAAA;AAAA;AAAA;AAAA;AAGA;AAEA;AAIA;AACA;AACA;AACA;AAEA;AAAA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AAEA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AAEA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAAA;AAvGA;AAwGA;AAAA;AAAA;AACA;;;;;;;;;;;;;;;;AC1IA;;;;;;;;;;;;;;;;;;;;;;;;AAwBA;AAEA;AAEA;AAAA;AAAA;AAAA;AACA;AAEA;AAAA;AACA;AAEA;AAOA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;;;;;;AAMA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;;;;AAIA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;;;;AAIA;AACA;AACA;AACA;AACA;AAEA;AAEA;AACA;AACA;AACA;AAEA;AACA;AAEA;;;;AAIA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;;;;AAIA;AACA;AACA;AACA;AAEA;;;;;;;AAOA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAAA;AACA;AACA;AAEA;AACA;AACA;AAAA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;;;;;;AAMA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAAA;AAvPA;AAyPA;AAAA;AAEA;AACA;AACA;AACA;AACA;AAJA;AAMA;AACA;AACA;AAFA;AAIA;AACA;AACA;AAFA;AAIA;AACA;AACA;AAFA;AAIA;AACA;AACA;AAFA;AAIA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAPA;AASA;AACA;AACA;AACA;AAHA;AAKA;AACA;AACA;AACA;AAAA;AACA;AACA;AAAA;AACA;AACA;AACA;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AArBA;AAsBA;AAEA;AAAA;AAEA;AACA;AACA;AAFA;AAIA;AACA;AACA;AAFA;AAIA;AACA;AACA;AAFA;AAIA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAPA;AASA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAbA;AAeA;AACA;AACA;AACA;AACA;AACA;AACA;AANA;AAQA;AACA;AACA;AAFA;AAIA;AACA;AACA;AAFA;AAIA;AACA;AACA;AACA;AAHA;AAKA;AACA;AACA;AACA;AAHA;AAKA;AACA;AACA;AACA;AAAA;AACA;AACA;AAAA;AACA;AACA;AACA;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAAA;AACA;AACA;AACA;AACA;AArBA;AAsBA;AAEA;AAAA;AAEA;AACA;AACA;AAFA;AAIA;AACA;AACA;AAFA;AAIA;AACA;AACA;AACA;AAHA;AAKA;AACA;AACA;AAFA;AAIA;AACA;AACA;AAFA;AAIA;AACA;AACA;AAFA;AAIA;AACA;AACA;AAFA;AAIA;AACA;AACA;AAFA;AAIA;AACA;AACA;AAFA;AAIA;AACA;AACA;AACA;AAHA;AAKA;AACA;AACA;AAFA;AAIA;AACA;AACA;AAFA;AAIA;AACA;AACA;AAEA;AACA;AALA;AAOA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AATA;AAWA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AATA;AAWA;AACA;AACA;AAFA;AAIA;AACA;AACA;AAFA;AAIA;;;;;;AAMA;AACA;AACA;AACA;AACA;AACA;AACA;AALA;AAOA;AACA;AACA;AACA;AAHA;AAKA;AACA;AACA;AACA;AACA;AACA;AALA;AAOA;AACA;AACA;AAFA;AAIA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AARA;AAUA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAZA;AAcA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAAA;AACA;AACA;AACA;AAhBA;AAkBA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAbA;AAeA;AACA;AACA;AACA;AAAA;AACA;AACA;AAAA;AACA;AACA;AACA;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAxBA;AAyBA;AAEA;AAAA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AATA;AAWA;AACA;AACA;AAFA;AAIA;AACA;AACA;AAFA;AAIA;AACA;AACA;AAFA;AAIA;AACA;AACA;AAFA;AAIA;AACA;AACA;AACA;AACA;AAJA;AAMA;AACA;AACA;AACA;AACA;AAJA;AAMA;AACA;AACA;AAFA;AAIA;AACA;AACA;AAFA;AAIA;AACA;AACA;AAFA;AAIA;AACA;AACA;AACA;AAHA;AAKA;AACA;AACA;AACA;AACA;AACA;AALA;AAOA;AACA;AACA;AACA;AAAA;AACA;AACA;AAAA;AACA;AACA;AACA;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AArBA;AAsBA;AAEA;AAAA;AAEA;AACA;AACA;AAFA;AAIA;AACA;AACA;AAFA;AAIA;AACA;AACA;AAFA;AAIA;AACA;AACA;AAFA;AAIA;AACA;AACA;AAFA;AAIA;AACA;AACA;AAFA;AAIA;AACA;AACA;AAFA;AAIA;AACA;AACA;AAFA;AAIA;AACA;AACA;AAFA;AAIA;AACA;AACA;AAFA;AAIA;AACA;AACA;AACA;AACA;AAJA;AAMA;AACA;AACA;AACA;AAHA;AAKA;AACA;AACA;AACA;AAHA;AAKA;AACA;AACA;AACA;AACA;AACA;AALA;AAOA;AACA;AACA;AACA;AACA;AACA;AALA;AAOA;AACA;AACA;AACA;AACA;AACA;AALA;AAOA;AACA;AACA;AACA;AACA;AACA;AALA;AAMA;AAEA;AACA;AAAA;AACA;;;;;;;;;;;;;;;;AC9yBA;;;;;;;;;;;;;;;;;;;;;;;;AAwBA;AAEA;AAEA;AAAA;AAAA;AAAA;AACA;AAEA;AACA;AAEA;AACA;AAAA;AACA;AACA;AAAA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAAA;AACA;AACA;AACA;AACA;AAEA;AACA;AAAA;AACA;AACA;AACA;AAFA;AAGA;AAEA;AACA;AAAA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AATA;AAUA;AAEA;AACA;AACA;AACA;AACA;AAJA;AAMA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAbA;AAeA;AACA;AAAA;AAkJA;AA/IA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAAA;AACA;AACA;AAEA;AACA;AACA;AACA;AAAA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAhJA;AAiJA;AAAA;AAlJA;AAmJA;AACA;AAAA;AACA;;;;;;;;;;;;;;;;ACnQA;;;;;;;;;;;;;;;;;;;;;;;;AAwBA;AAEA;AAEA;AAAA;AAAA;AAAA;AACA;;AAEA;AACA;AAAA;AAEA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AAXA;AAYA;AACA;AAAA;AACA;;;;;;;;;;;;;;;;AChDA;;;;;;;;;;;;;;;;;;;;;;;;AAwBA;AAEA;AAEA;AAAA;AAAA;AAAA;AACA;;AAEA;AACA;AAAA;AACA;;;;;;;AAOA;AACA;AAEA;;AAEA;AACA;AACA;AACA;AACA;AACA;AAJA;AAMA;;AAEA;AACA;AACA;AACA;AACA;AAHA;AAKA;AACA;AACA;AACA;AACA;AAJA;AAMA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAVA;AAYA;;AAEA;AACA;AACA;AAEA;AACA;AAJA;AAMA;;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AARA;AAUA;;AAEA;AACA;AACA;AACA;AAEA;AACA;AALA;AAOA;;;;;;;;AAQA;AACA;AACA;AACA;AACA;AACA;AACA;AALA;AAOA;;AAEA;AACA;AACA;AACA;AAEA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AAbA;AAeA;;;AAGA;AACA;AACA;AACA;AACA;AACA;AAJA;AAMA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAPA;AASA;;;;AAIA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAAA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AA/BA;AAiCA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AATA;AAWA;;;AAGA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAdA;AAeA;AAEA;AAGA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAAA;AA/BA;AAgCA;AAAA;AACA;;;;;;;;;;;;;;;;ACnRA;;;;;;;;;;;;;;;;;;;;;;;;AAwBA;AAEA;AAEA;AAAA;AAAA;AAAA;AACA;;AAEA;AACA;AAAA;AAEA;;AAEA;AACA;AACA;AACA;AAFA;AAIA;;AAEA;AACA;AACA;AACA;AAFA;AAIA;;;;AAIA;AACA;AACA;AACA;AACA;AAHA;AAIA;AACA;AAAA;AACA;;;;;;;;;;;;;;;;AC3DA;;;;;;;;;;;;;;;;;;;;;;;;AAwBA;AAEA;AAEA;AAAA;AAAA;AAAA;AACA;AAUA;AAQA;AAAA;AAEA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAbA;AAeA;AACA;AACA;AACA;AAHA;AAKA;AACA;AAEA;AAEA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AAtBA;AAwBA;AACA;AACA;AACA;AACA;AACA;AACA;AANA;AAQA;AACA;AAAA;AACA;AACA;AACA;AACA;AACA;AANA;AAQA;AACA;AACA;AACA;AAHA;AAKA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAdA;AAgBA;AACA;AACA;AAFA;AAIA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AAAA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAlDA;AAoDA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAPA;AASA;AACA;AACA;AACA;AAHA;AAKA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AArDA;AAuDA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAbA;AAeA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAAA;AACA;AAAA;AACA;;;;;;;;;;;;;;;;AC1ZA;;;;;;;;;;;;;;;;;;;;;;;;AAwBA;AAEA;AAEA;AAAA;AAAA;AAAA;AAMA;AAAA;AACA;AACA;AACA;AAFA;AAIA;AACA;AACA;AAFA;AAIA;AACA;AACA;AAFA;AAIA;AACA;AACA;AACA;AACA;AACA;AACA;AANA;AAQA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAfA;AAiBA;AAIA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AA3EA;AA4EA;AACA;AAAA;AACA;;;;;;;;;;;;;;;;ACtJA;;;;;;;;;;;;;;;;;;;;;;;;AAwBA;AAEA;AAEA;AAAA;AAAA;AAAA;AACA;AAEA;;;;AAIA;AACA;AAAA;AAsJA;AAjJA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AAAA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AAEA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AAEA;AACA;AAEA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AAEA;AACA;AAEA;AAEA;AAAA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AAEA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AAAA;AAtJA;AAuJA;AAAA;AACA;;;;;;;;;;;;;;;;AC5LA;;;;;;;;;;;;;;;;;;;;;;;;AAwBA;AAEA;AAEA;AAAA;AAAA;AAAA;AACA;AACA;AAAA;AACA;;;;;AAKA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAtBA;AAuBA;AACA;AAAA;AACA;;;;;;;;;;;;;;;;AC9DA;;;;;;;;;;;;;;;;;;;;;;;;AAwBA;AAEA;AAEA;AAAA;AAAA;AAAA;AAYA;AACA;AAMA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AAAA;AAlHA;AAoHA;AACA;AACA;AACA;AACA;AACA;AALA;AAOA;AACA;AACA;AACA;AACA;AACA;AALA;AAOA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AARA;AAUA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAZA;AAcA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAAA;AACA;;;;;;;;;;;;;;;;AC9MA;;;;;;;;;;;;;;;;;;;;;;;;AAwBA;AAEA;AAEA;AAAA;AAAA;AAAA;AACA;AACA;AAaA;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AApCA;AAsCA;AACA;AACA;AAFA;AAIA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AA9FA;AAgGA;AACA;AACA;AACA;AACA;AAJA;AAMA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AAEA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AApDA;AAqDA;AACA;AAAA;AACA;;;;;;;;;;;;;;;;ACvQA;;;;;;;;;;;;;;;;;;;;;;;;AAwBA;AAEA;AAEA;AAAA;AAAA;AAAA;AACA;AAAA;AACA;;;AAGA;AACA;AACA;AACA;AAFA;AAGA;AACA;AAAA;AACA;;;;;;;;;;;;;;;;ACvCA;;;;;;;;;;;;;;;;;;;;;;;;AAwBA;AAEA;AAEA;AAAA;AACA;AAEA;AAAA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AAEA;AACA;AAEA;AACA;AAEA;AACA;AAlBA;AAmBA;AACA;AACA;;;;;;;;;;;;;;;;ACtDA;;;;;;;;;;;;;;;;;;;;;;;;AAwBA;;;;;;AAEA;AAEA;AAAA;AAAA;AAAA;AACA;AACA;AAKA;AAEA;AACA;AAAA;AACA;AACA;AACA;AACA;AAEA;AAEA;AACA;AAEA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AA5CA;AA8CA;AAKA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AAEA;AACA;AACA;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AAEA;AAAA;AAAA;AAAA;AA0BA;AAtBA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAAA;AACA;AACA;AAAA;AACA;;;;;;;;;;;;;;;;ACtRA;;;;;;;;;;;;;;;;;;;;;;;;AAwBA;AAEA;AAEA;AAAA;AAEA;AAsEA;;;AAGA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AAEA;AACA;AAEA;AACA;AACA;AAfA;AAiBA;;;AAGA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AATA;AAqFA;;;;AAIA;AACA;AAAA;AAEA;AACA;AAMA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAoTA;AAlTA;AAAA;AACA;AACA;;;AAAA;AAEA;AACA;AAAA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AAEA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AAEA;AAEA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AAEA;;;AAGA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AAKA;AAEA;AACA;AAMA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AAEA;AACA;AACA;AAEA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAnUA;AACA;AACA;AACA;AAiUA;AAAA;AAxUA;AA0UA;;AAEA;AACA;AAWA;;;AAGA;AACA;AAbA;AAcA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;;;;AAIA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;;;;AAIA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;;;;;;;AAOA;AACA;AACA;AACA;AAEA;;;;AAIA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AA/HA;AACA;AA+HA;AAAA;AAxIA;AA0IA;;AAEA;AACA;AAAA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAPA;AASA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAPA;AASA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAPA;AASA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAPA;AASA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAPA;AASA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAPA;AASA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAPA;AASA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAPA;AASA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAPA;AASA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAPA;AAQA;AACA;AACA;;;;;;;;;;;;;;;;AC9wBA;;;;;;;;;;;;;;;;;;;;;;;;AAwBA;AAEA;AAEA;AAAA;AAEA;AAKA;AACA;AA0OA;AAaA;AACA;AAEA;AACA;AAEA;AAAA;AACA;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;;;AAGA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;;;;AAIA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;;;AAGA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;;;;;AAKA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;;;;AAIA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AAvLA;;;AAGA;AACA;AAoLA;AAAA;AA/LA;AAkNA;AAqBA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;;;;;;;;;;;;;;;;;;;AAmBA;AACA;AACA;AACA;AAAA;AACA;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AAAA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAAA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;;;;AAIA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AApRA;AACA;AACA;AACA;AAkRA;AAAA;AArSA;AAuSA;;AAEA;AACA;AAAA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AA5EA;AA8EA;AACA;AACA;AACA;AAHA;AAKA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAVA;AAYA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAPA;AASA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAPA;AASA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAPA;AASA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAPA;AASA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAPA;AASA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAPA;AASA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAPA;AASA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAPA;AASA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAPA;AASA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAPA;AAQA;AAEA;AAAA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAlCA;AAoCA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAPA;AASA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAPA;AASA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAPA;AASA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAPA;AASA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAPA;AASA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAPA;AASA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAPA;AASA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAPA;AAQA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAPA;AASA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAnCA;AAoCA;AACA;AACA;;;;;;;;;;;;;;;;ACpmCA;;;;;;;;;;;;;;;;;;;;;;;;AAwBA;AAEA;AAEA;AAAA;AAMA;AACA;AACA;AAsCA;AAeA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;;;;;;;;;;;;;;;;;AAiBA;AACA;AACA;AACA;AAAA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AAEA;;;AAGA;AACA;AACA;AACA;AACA;AAEA;AAEA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;;;;AAIA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AAAA;AA5bA;AA6bA;AACA;;;;;;;;;;;;;;;;ACxgBA;;;;;;;;;;;;;;;;;;;;;;;;AAwBA;AAEA;AAEA;AAAA;AAOA;AAEA;AACA;AACA;AACA;AAmBA;AAAA;AA0VA;AAvVA;AACA;AACA;AACA;AACA;AACA;AAEA;AAAA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AAAA;AACA;AACA;AACA;AACA;AAEA;AAEA;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AAEA;AACA;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AAEA;AAEA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AAGA;AACA;AACA;AAEA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;;;;AAIA;AACA;AACA;AACA;AAEA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AAAA;AACA;AACA;AAEA;AACA;AAEA;AACA;AAEA;AACA;AACA;AAAA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAAA;AA1VA;AA4VA;AAaA;AACA;AACA;AACA;AAEA;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AAEA;AAAA;AACA;AACA;AAEA;AAEA;AACA;AACA;AACA;AACA;AACA;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AAtFA;;;AAGA;AACA;AAoFA;AAAA;AA/FA;AAiGA;AACA;;;;;;;;;;;;;;;;ACzfA;;;;;;;;;;;;;;;;;;;;;;;;AAwBA;AAEA;AAEA;AAAA;AAAA;AAAA;AACA;AACA;AACA;AAFA;AAIA;AAAA;AAUA;AATA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAAA;AAEA;AAIA;AACA;AAEA;AAEA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAAA;AAEA;AAIA;AACA;AAEA;AAEA;AAGA;AACA;AACA;AACA;AACA;AAEA;AACA;AAIA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAAA;AAEA;AAKA;AACA;AAEA;AAEA;AAGA;;;;AAIA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAIA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAAA;AACA;AAAA;AACA;;;;;;;;;;;;;;;;AC3JA;;;;;;;;;;;;;;;;;;;;;;;;AAwBA;AAEA;AAEA;AAAA;AAAA;AAAA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AAAA;AAiFA;AAhFA;AACA;AACA;AACA;AACA;AAEA;;;;AAIA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AAAA;AAjFA;AAmFA;AACA;AAAA;AACA;;;;;;;;;;;;;;;;AChMA;;;;;;;;;;;;;;;;;;;;;;;;AAwBA;AAEA;AAEA;AAAA;AAAA;AAAA;AACA;AACA;AAOA;;AAEA;AACA;AACA;AACA;AAFA;AAIA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AALA;AAOA;AACA;AACA;AACA;AACA;AAJA;AAMA;AACA;AACA;AACA;AACA;AACA;AACA;AANA;AA0EA;AAyBA;AAtBA;AACA;AACA;AAEA;AACA;AACA;AAiBA;AAEA;AACA;AAEA;AAEA;AACA;AAAA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAAA;AACA;AACA;AAEA;AACA;AAEA;;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AAEA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AAEA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AAEA;AACA;AAEA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AAEA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AAEA;AACA;AAEA;AAAA;AACA;AACA;AACA;AACA;AAEA;;;;;;;;;AASA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AAAA;AACA;AACA;AACA;AACA;AACA;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAAA;AACA;AACA;AACA;AAEA;AACA;AAAA;AACA;AACA;AACA;AAEA;AACA;AAAA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AAAA;AACA;AAEA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AAAA;AAreA;AAqeA;AACA;AAAA;AACA;;;;;;;;;;;;;;;;AC7mBA;;;;;;;;;;;;;;;;;;;;;;;;AAwBA;;;;;;AAEA;AAEA;AAAA;AAAA;AAAA;AAAA;AAAA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AARA;AAaA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAFA;AAIA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AA8BA;AACA;AACA;AACA;AACA;AACA;AAEA;AAKA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAAA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AAAA;AAzEA;AA2EA;AAAA;AAKA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAGA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAAA;AAvBA;AAyBA;AAAA;AAMA;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAAA;AA3CA;AA6CA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AAAA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AAAA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAtBA;AAwBA;AAAA;AACA;AACA;AAFA;AAIA;AAAA;AAAA;AAAA;AACA;AACA;AAFA;AAIA;AACA;AACA;AAFA;AAIA;AAAA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AAEA;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAPA;AASA;AACA;AAAA;AAAA;AACA;;;;;;;;;;;;;;;;AC7fA;;;;;;;;;;;;;;;;;;;;;;;;AAwBA;AAEA;AAEA;AAAA;AAAA;AAAA;AAAA;AAAA;AAYA;AACA;AACA;AACA;AACA;AAJA;AAMA;AAOA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AAAA;AACA;AAAA;AAAA;AACA;;;;;;;;;;;;;;;;ACvIA;;;;;;;;;;;;;;;;;;;;;;;;AAwBA;AAEA;AAEA;AAAA;AAAA;AAAA;AAAA;AAAA;AAEA;AACA;AACA;AAFA;AAIA;;AAEA;AACA;AAGA;AACA;AACA;AAEA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AAEA;AAEA;AACA;AACA;AAEA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AAEA;AAEA;AAAA;AACA;AAAA;AAAA;AACA;;;;;;;;;;;;;;;;AC5EA;;;;;;;;;;;;;;;;;;;;;;;;AAwBA;;;;;;AAEA;AAEA;AAAA;AAAA;AAAA;AAAA;AAAA;AAEA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAPA;AASA;AAeA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAGA;AADA;AACA;AACA;AACA;;;AAAA;AAEA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAxFA;AACA;AACA;AACA;AAsFA;AAAA;AA1FA;AA4FA;AACA;AAoDA;AA1CA;AAEA;AACA;AACA;AACA;AACA;AAEA;AAiBA;AAkBA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AAEA;AAMA;AACA;AACA;AARA;AACA;AACA;AACA;AACA;;;AAAA;AAMA;AAMA;AACA;AACA;AARA;AACA;AACA;AACA;AACA;;;AAAA;AAMA;AACA;AACA;AAEA;AAAA;AACA;AACA;;;AAAA;AAEA;AAAA;AACA;AACA;;;AAAA;AAEA;AAAA;AACA;AACA;;;AAAA;AAEA;AACA;AACA;AAEA;AAAA;AACA;AACA;;;AAAA;AAEA;AAAA;AACA;AACA;;;AAAA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AAEA;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AAAA;AACA;AACA;AACA;AACA;AACA;;;AAAA;AAEA;AAAA;AACA;AACA;AACA;AACA;AACA;;;AAAA;AAEA;AAAA;AACA;AACA;AACA;AACA;AACA;;;AAAA;AAEA;AAAA;AACA;AACA;AACA;AACA;AACA;;;AAAA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AA1eA;AACA;AACA;AACA;AAEA;AACA;AACA;AAoeA;AAAA;AA5eA;AA8eA;AACA;AAAA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AAAA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AAAA;AApGA;AAsGA;AACA;AAAA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AAAA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AAAA;AAnGA;AAoGA;AAAA;AAAA;AACA;;;;;;;;;;;;;;;;ACj0BA;;;;;;;;;;;;;;;;;;;;;;;;AAwBA;;;;;;AAEA;AAEA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAEA;AAEA;AACA;AAeA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AAEA;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AAAA;AACA;AACA;AACA;AACA;;;AAAA;AAEA;AAAA;AACA;AACA;;;AAAA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AAAA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AAEA;AACA;AAEA;AACA;AAEA;AACA;AAEA;AACA;AAEA;AACA;AApKA;AACA;AAoKA;AAAA;AAjLA;AAmLA;AAAA;AAEA;AACA;AACA;AACA;AAEA;AAAA;AACA;AACA;;;AAAA;AAEA;AACA;AACA;AACA;AACA;AAAA;AAfA;AAiBA;AAgBA;AACA;AACA;AAEA;AACA;AAEA;AACA;AAEA;AAEA;AAEA;AACA;AAEA;AACA;AACA;AAEA;AAAA;AACA;AACA;;;AAAA;AAEA;AACA;AAEA;AACA;AAEA;AAAA;AACA;AACA;;;AAAA;AAKA;AAHA;;AAEA;AACA;AACA;AACA;;;AAAA;AAKA;AAHA;;AAEA;AACA;AACA;AACA;;;AAAA;AAEA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AAAA;AACA;AACA;AACA;AACA;AAEA;;;AAGA;AACA;AACA;AACA;AArIA;AACA;AAqIA;AAAA;AAvIA;AAyIA;AAKA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AAAA;AACA;AACA;;;AAAA;AACA;AAAA;AAzEA;AA2EA;AAAA;AAGA;AACA;AAEA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AAEA;AAAA;AAjBA;AAmBA;AAAA;AAAA;AAAA;AASA;AARA;AACA;AACA;AAEA;AACA;AACA;AAEA;AAAA;AATA;AAWA;AAAA;AAwBA;AArBA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAAA;AAxBA;AA0BA;AAAA;AAGA;AACA;AAEA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AAAA;AAhBA;AAkBA;AAAA;AAIA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AAEA;AACA;AAEA;AACA;AACA;AAAA;AAtBA;AAwBA;AAOA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAAA;AA/HA;AAiIA;AAAA;AAGA;AACA;AAEA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AAEA;AAAA;AACA;AACA;;;AAAA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AAAA;AA5BA;AA8BA;AAAA;AAEA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AAAA;AArBA;AAsBA;AAAA;AAAA;AAAA;AACA;;;;;;;;;;;;;;;;ACjtBA;;;;;;;;;;;;;;;;;;;;;;;;AAwBA;;;;;;AAEA;AAEA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAEA;;AAEA;AACA;AAOA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AAAA;AACA;AACA;;;AAAA;AAEA;AAAA;AACA;AACA;;;AAAA;AAEA;AAAA;AACA;AACA;AAEA;AACA;AACA;;;AAJA;AAMA;AAAA;AACA;AACA;AAEA;AACA;AACA;;;AAJA;AAMA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AAAA;AACA;AACA;;;AAAA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAAA;AA/EA;AAiFA;;AAEA;AACA;AAAA;AAAA;AAAA;AA0FA;AAvFA;AAAA;AACA;AACA;;;AAAA;AAEA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AAEA;AACA;AAEA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAAA;AA1FA;AA4FA;;AAEA;AACA;AAAA;AAAA;AAAA;AAsFA;AAnFA;AAAA;AACA;AACA;;;AAAA;AAEA;AACA;AACA;AAEA;AACA;AACA;AAEA;AAAA;AACA;AACA;;;AAAA;AAEA;AACA;AACA;AAEA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AAEA;AACA;AAEA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AAAA;AAtFA;AAwFA;AAIA;AACA;AACA;AACA;AACA;AAAA;AARA;AAUA;AAAA;AACA;AACA;AACA;AAAA;AAAA;AAHA;AAIA;AAAA;AAAA;AAAA;AACA;;;;;;;;;;;;;;;;AC3TA;;;;;;;;;;;;;;;;;;;;;;;;AAwBA;AAEA;AAEA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAuCA;AA2BA;AAnBA;AAoBA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AAAA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;;;AARA;AAUA;AAAA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;;;AARA;AAUA;AACA;AACA;AAEA;AAAA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;;;AAPA;AASA;AAAA;AACA;AACA;;;AAAA;AAEA;AAAA;AACA;AACA;;;AAAA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AAEA;AAAA;AACA;AACA;;;AAAA;AAEA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AAEA;AAAA;AACA;AACA;;;AAAA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAAA;AA7LA;AA+LA;AAoBA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AAAA;AACA;AACA;;;AAAA;AAEA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AAEA;AAEA;AACA;AACA;AACA;AAEA;AACA;AAEA;AAAA;AACA;AACA;;;AAAA;AAEA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AAAA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AAMA;AACA;AACA;AARA;AACA;AACA;AACA;AACA;;;AAAA;AAMA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AAEA;AACA;AAEA;AACA;AAEA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AAEA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AAEA;AAAA;AACA;AACA;AACA;AAEA;AAAA;AACA;AACA;AACA;AAEA;AAAA;AACA;AACA;AAEA;AAAA;AACA;AACA;AAEA;AACA;AAEA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AAAA;AACA;AACA;AACA;AAEA;AAAA;AACA;AACA;AACA;AAEA;AAAA;AACA;AACA;AAEA;AAAA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AAAA;AAjWA;AAmWA;AAgBA;AANA;AAOA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AAAA;AACA;AACA;;;AAAA;AAEA;AAAA;AACA;AACA;;;AAAA;AAEA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AAAA;AACA;AACA;AAAA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AAAA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AAEA;AACA;AACA;AAEA;AAEA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AAEA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AAAA;AAjYA;AAmYA;AAkBA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AAAA;AACA;AACA;;;AAAA;AAEA;AAQA;AACA;AACA;AAVA;AACA;AACA;AAEA;AACA;AACA;;;AAAA;AAMA;AAAA;AACA;AACA;AAEA;AACA;AACA;;;AAJA;AAMA;AAAA;AACA;AACA;AAEA;AACA;AACA;;;AAJA;AAMA;AAAA;AACA;AACA;;;AAAA;AAEA;AAAA;AACA;AACA;;;AAAA;AAEA;AAAA;AACA;AACA;;;AAAA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AAEA;AAEA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AAEA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AAAA;AA5SA;AA6SA;AAAA;AAAA;AAAA;AACA;;;;;;;;;;;;;;;;ACtxCA;;;;;;;;;;;;;;;;;;;;;;;;AAwBA;;;;;;AAEA;AAEA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AACA;AAEA;;;;AAIA;AACA;AAAA;AAyGA;AA7FA;AAAA;AACA;AACA;;;AAAA;AAEA;AAAA;AACA;AACA;;;AAAA;AAEA;AAAA;AACA;AACA;;;AAAA;AAEA;AAAA;AACA;AACA;;;AAAA;AAEA;AAAA;AACA;AACA;;;AAAA;AAEA;AAAA;AACA;AACA;;;AAAA;AAEA;AAAA;AACA;AACA;;;AAAA;AAEA;AAAA;AACA;AACA;;;AAAA;AAEA;AAAA;AACA;AACA;;;AAAA;AAEA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AAEA;AACA;AAEA;AACA;AAEA;AACA;AAEA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AAvGA;AACA;AAuGA;AAAA;AAzGA;AA2GA;AAkBA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AAAA;AACA;AACA;AAEA;AACA;AACA;;;AAJA;AAMA;AAAA;AACA;AACA;;;AAAA;AAEA;AAAA;AACA;AACA;AAEA;AACA;AACA;;;AAJA;AAMA;AACA;AACA;AAEA;AACA;AACA;AAEA;AAAA;AACA;AACA;;;AAAA;AAEA;AACA;AACA;AAEA;AAAA;AACA;AACA;;;AAAA;AAEA;AAAA;AACA;AACA;;;AAAA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;;AAEA;AACA;AACA;AACA;AAEA;AAAA;AACA;AACA;;;AAAA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AAAA;AACA;AACA;;;AAAA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AAEA;AACA;AAEA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AAEA;AAEA;AAEA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AAEA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AAEA;AAEA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AAEA;AAEA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AAAA;AACA;AACA;AACA;;;AAAA;AAEA;AAAA;AACA;AACA;;;AAAA;AAEA;AAAA;AACA;AACA;;;AAAA;AAEA;AACA;AACA;AACA;AAEA;AAAA;AACA;AACA;AACA;;;AAAA;AAEA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AAEA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AAEA;AACA;AACA;AACA;AAEA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAniBA;AACA;AAmiBA;AAAA;AAriBA;AAuiBA;AASA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAAA;AAtBA;AAwBA;AAAA;AAIA;AACA;AAEA;AAEA;AACA;AACA;AAEA;AAAA;AACA;AACA;;;AAAA;AAEA;AACA;AACA;AAEA;AAIA;AACA;AACA;AANA;AACA;AACA;;;AAAA;AAMA;AACA;AACA;AAEA;AAAA;AACA;AACA;AACA;AACA;AACA;;;AAAA;AAEA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AAEA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AAEA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AAGA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AArNA;AAsNA;AAAA;AAvNA;AAyNA;AAAA;AAAA;AAAA;AAqCA;AApCA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAAA;AArCA;AAuCA;AAAA;AAAA;AAAA;AA2CA;AA1CA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAAA;AA3CA;AA6CA;AAAA;AACA;AACA;AAEA;AACA;AAEA;AAAA;AACA;AACA;;;AAAA;AAEA;AACA;AACA;AAEA;AAAA;AACA;AACA;AACA;AACA;AACA;;;AAAA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AAKA;AAEA;AACA;AACA;AACA;AACA;AAAA;AArIA;AAuIA;AAAA;AAAA;AAAA;AAsBA;AArBA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAAA;AAtBA;AAwBA;AAAA;AAAA;AAAA;AAeA;AAdA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAAA;AAfA;AAiBA;AAcA;AAHA;AAQA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AAAA;AACA;AACA;;;AAAA;AAEA;AAAA;AACA;AACA;;;AAAA;AAEA;AAAA;AACA;AACA;AAEA;AACA;AACA;;;AAJA;AAMA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;;;;;;AAMA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AAEA;AACA;AAEA;AAAA;AACA;AACA;;;AAAA;AAEA;AAAA;AACA;AACA;;;AAAA;AAEA;AAAA;AACA;AACA;;;AAAA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AAEA;AAEA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AAEA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AAEA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAAA;AAlYA;AAoYA;AAAA;AAIA;AAOA;AACA;AAEA;AAEA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AAMA;AAEA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AAEA;AAEA;AACA;AAEA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AAAA;AAvGA;AAyGA;AAAA;AAIA;AAMA;AACA;AAEA;AAEA;AACA;AACA;AAEA;AACA;AAKA;AAEA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAAA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AAAA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AAAA;AAzGA;AA0GA;AAAA;AAAA;AAAA;AACA;;;;;;;;;;;;;;;;ACnwDA;;;;;;;;;;;;;;;;;;;;;;;;AAwBA;AAEA;AAEA;AAAA;AAAA;AAAA;AAAA;AAAA;AACA;AAAA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AARA;AAUA;AACA;AACA;AAFA;AAIA;AACA;AACA;AAFA;AAIA;AACA;AACA;AACA;AACA;AACA;AALA;AAOA;AACA;AACA;AACA;AACA;AACA;AALA;AAOA;AACA;AACA;AAFA;AAIA;AACA;AACA;AAFA;AAIA;AACA;AACA;AAFA;AAIA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AARA;AAUA;;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AAEA;AAEA;AAbA;AAcA;AACA;AAAA;AAAA;AAEA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AACA;AACA;AACA;AAEA;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAMA;;AAEA;AACA;AAMA;;;;;;AAMA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;;;AAGA;AACA;AACA;AACA;AAEA;;;;;AAKA;AACA;AACA;AACA;AACA;AACA;AAAA;AAtCA;AAwCA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;;;;AAIA;AACA;AACA;AACA;AAEA;AACA;AALA;AAOA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AACA;AACA;AAEA;AACA;;AACA;AAxBA;AA0BA;AACA;AAEA;AAEA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AA3CA;AA6CA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AA5BA;AA8BA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AAEA;AACA;AAEA;AACA;AAnCA;AAqCA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAVA;AAYA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAVA;AAYA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AAjBA;AAmBA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAVA;AAYA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AATA;AAWA;AACA;AAEA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AA/CA;AAiDA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AApDA;AAsDA;;;;AAIA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAVA;AAYA;AACA;AACA;AAFA;AAIA;AACA;AACA;AAFA;AAIA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AAFA;AAIA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAFA;AAIA;AACA;AACA;AACA;AACA;AACA;AALA;AAOA;AACA;AACA;AACA;AACA;AACA;AACA;AANA;AAOA;AAEA;AAAA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AAEA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AASA;AAAA;AAAA;AAAA;AAAA;AAAA;AAEA;AAQA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AAEA;;;;;;;;AAQA;AACA;AACA;AACA;AAAA;AACA;AAAA;AAEA;AACA;AAAA;AACA;AAAA;AAEA;AACA;AAEA;AACA;AAEA;AACA;AAEA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AAAA;AA/EA;AAiFA;;AAEA;AACA;AAuBA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AAEA;;;AAGA;AACA;AAAA;AACA;AACA;AAEA;AAEA;AACA;AAEA;AAEA;AACA;AACA;AACA;AAEA;;;AAGA;AACA;AACA;AAAA;AACA;AAEA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AAAA;AAjHA;AAmHA;;AAEA;AACA;AAAA;AAiBA;AAAA;AAAA;AAjBA;AAkBA;;AAEA;AACA;AAIA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAAA;AAbA;AAeA;AASA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;AAAA;;AAEA;AAAA;AACA;AACA;AACA;;;AAAA;;AAEA;AAAA;AACA;AACA;AACA;;;AAAA;;AAEA;AAAA;AACA;AACA;AACA;;;AAAA;;AAEA;AAAA;AACA;AACA;AACA;;;AAAA;AAEA;AAAA;AACA;AACA;;;AAAA;;AAEA;AACA;AACA;AACA;AAAA;AAvDA;AAyDA;AACA;AACA;AAFA;AAIA;AACA;AACA;AACA;AACA;AAJA;AAMA;AACA;AACA;AACA;AAHA;AAKA;AACA;AACA;AAFA;AAIA;;;AAGA;AACA;AACA;AACA;AACA;AAHA;AAKA;AACA;AACA;AACA;AACA;AACA;AALA;AAOA;;;;;AAKA;AACA;AACA;AACA;AACA;AACA;AAJA;AAMA;AACA;AACA;AACA;AACA;AAJA;AAMA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AArBA;AAuBA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAbA;AAeA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAXA;AAaA;AACA;AACA;AACA;AAEA;AACA;AANA;AAQA;AACA;AACA;AACA;AACA;AACA;AACA;AANA;AAQA;AACA;AACA;AAFA;AAIA;;;;AAIA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AAhBA;AAkBA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAJA;AAKA;AACA;AAAA;AAAA;AAAA;AACA;;;;;;;;;;;;;;;;AC/lCA;;;;;;;;;;;;;;;;;;;;;;;;AAwBA;AAIA;AACA;;;;;;;;;;;;;;;;AC7BA;;;;;;;;;;;;;;;;;;;;;;;;AAwBA;AAIA;AACA;;;;;;;;;;;;;;;;AC7BA;;;;;;;;;;;;;;;;;;;;;;;;AAwBA;AAIA;AACA;;;;;;;;;;;;;;;;AC7BA;;;;;;;;;;;;;;;;;;;;;;;;AAwBA;AAEA;AAEA;AAAA;AAAA;AAAA;AAAA;AAAA;AACA;AAwCA;AAwDA;AAxDA;AAyBA;AAsCA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AAEA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AAEA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AAAA;AACA;AACA;;;AAAA;AAEA;AAAA;AACA;AACA;;;AAAA;AAEA;AAAA;AACA;AACA;;;AAAA;AAEA;AAAA;AACA;AACA;;;AAAA;AAEA;AAAA;AACA;AACA;;;AAAA;AAEA;AACA;AACA;AAEA;AAAA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;;;AARA;AAUA;AAAA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;;;AANA;AAQA;AAAA;AACA;AACA;AAEA;AACA;AACA;AACA;;;AALA;AAOA;AAAA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;;;AAXA;AAaA;AAAA;AACA;AACA;AAEA;AACA;AACA;AACA;;;AALA;AAOA;AAAA;AACA;AACA;AAEA;AACA;AACA;AACA;;;AALA;AAOA;AAAA;AACA;AACA;AAEA;AACA;AACA;AACA;;;AALA;AAOA;AAAA;AACA;AACA;AAEA;AACA;AACA;AACA;;;AALA;AAOA;AAAA;AACA;AACA;AAEA;AACA;AACA;AACA;;;AALA;AAOA;AAAA;AACA;AACA;AAEA;AACA;AACA;AACA;;;AALA;AAOA;AAAA;AACA;AACA;AACA;AACA;;;AAAA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AAEA;AACA;AAEA;AAEA;AAGA;AACA;AACA;AAEA;AACA;AAGA;AACA;AACA;AAEA;AACA;AAGA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAIA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AAAA;AACA;AACA;;;AAAA;AAEA;AAAA;AACA;AACA;;;AAAA;AAEA;AAAA;AACA;AACA;;;AAAA;AAEA;AACA;AACA;AAEA;AAAA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAAA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAAA;AACA;AACA;AACA;AACA;AAEA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AAAA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AAEA;AACA;AAEA;AACA;AAEA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AAAA;AACA;AACA;AAAA;AACA;AACA;AACA;AACA;AACA;AA9pBA;AACA;AACA;AACA;AACA;AACA;;;;;AAKA;AACA;AAopBA;AAAA;AAhqBA;AAiqBA;AAAA;AAAA;AACA;;;;;;;;;;;;;;;;ACvuBA;;;;;;;;;;;;;;;;;;;;;;;;AAwBA;;;;;;AAEA;AAEA;AAAA;AAAA;AAAA;AAAA;AAAA;AAEA;AAmBA;AANA;AAOA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AAEA;AAAA;AACA;AACA;;;AAAA;AAEA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AAAA;AACA;AACA;AACA;AACA;AAEA;AAAA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AAAA;AACA;AACA;;;AAAA;AAEA;AAAA;AACA;AACA;;;AAAA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AAEA;AACA;AACA;AACA;AACA;AACA;AAAA;AAnIA;AAqIA;AAAA;AAGA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;;;;AAIA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AAEA;;;;;;AAMA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAAA;AAlLA;AAoLA;AAAA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAAA;AA1EA;AA2EA;AAAA;AAAA;AACA;;;;;;;;;;;;;;;;ACnaA;;;;;;;;;;;;;;;;;;;;;;;;AAwBA;AAEA;AAEA;AAAA;AAAA;AAAA;AAAA;AAAA;AAEA;;;;;AAKA;AACA;AAmBA;;;AAGA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AAAA;AACA;AACA;;;AAAA;AAEA;;;;AAIA;AACA;AACA;AACA;AAEA;;;;;;;AAOA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;;AAEA;AACA;AACA;AACA;AAEA;;;;AAIA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;;;;;AAKA;AACA;AACA;AACA;AACA;AACA;AAAA;AA5FA;AA8FA;;;;;AAKA;AACA;AAqBA;;;AAGA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AAAA;AACA;AACA;;;AAAA;AAEA;;;;AAIA;AACA;AACA;AACA;AAEA;;;;;;AAMA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;;AAEA;AACA;AACA;AAEA;AACA;AAEA;;;;AAIA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;;;;;AAKA;AACA;AACA;AACA;AACA;AACA;AAAA;AA/FA;AAiGA;;;;AAIA;AACA;AA6BA;;;AAGA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAOA;AALA;;;;AAIA;AACA;AACA;AACA;;;AAAA;AAEA;;;;;AAKA;AACA;AACA;AACA;AACA;AAEA;;;;;;;AAOA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;;AAEA;AACA;AACA;AACA;AAEA;;;;AAIA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;;;;;AAKA;AACA;AACA;AACA;AACA;AA1GA;AACA;AA0GA;AAAA;AA5GA;AA6GA;AAAA;AAAA;AACA;;;;;;;;;;;;;;;;AC5VA;;;;;;;;;;;;;;;;;;;;;;;;AAwBA;;;;;;AAEA;AAEA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAEA;AAIA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAAA;AAbA;AAeA;AAAA;AAIA;AACA;AACA;AACA;AACA;AAEA;AAAA;AACA;AACA;;;AAAA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAAA;AA5BA;AA+CA;AACA;AACA;AACA;AACA;AACA;AALA;AAwBA;;AAEA;AACA;AA2BA;;;;;;;AAOA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AAAA;AACA;AACA;;;AAAA;AAEA;AAAA;AACA;AACA;;;AAAA;AAEA;AAAA;AACA;AACA;;;AAAA;AAEA;AAAA;AACA;AACA;;;AAAA;AAQA;AANA;;;;;AAKA;AACA;AACA;AACA;;;AAAA;AACA;AAAA;AApEA;AAiFA;;;;;AAKA;AACA;AA+BA;;;;AAIA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AAAA;AACA;AACA;;;AAAA;AAEA;;;;;;AAMA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAGA;AACA;AAEA;AAEA;AAEA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AAEA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AAEA;AACA;AACA;AACA;AAEA;;;AAGA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAAA;AAtOA;AAkPA;AACA;AAEA;;;;;AAKA;AACA;AAwDA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AAAA;AACA;AAEA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AAAA;AACA;AAEA;AACA;AAEA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AAEA;AACA;AAEA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;;AAEA;AACA;AAAA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAAA;AA7RA;AA8RA;AAAA;AAAA;AAAA;AACA;;;;;;;;;;;;;;;;ACxuBA;;;;;;;;;;;;;;;;;;;;;;;;AAwBA;AAEA;AAEA;AAAA;AAAA;AAAA;AAAA;AAAA;AACA;AACA;AACA;AACA;AAHA;AAqLA;AAAA;AAAA;AACA;;;;;;;;;;;;;;;;ACnNA;;;;;;;;;;;;;;;;;;;;;;;;AAwBA;AAEA;AAEA;AAAA;AAAA;AAAA;AAAA;AAAA;AACA;AACA;AA6CA;AA+CA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;;;;AAIA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AAEA;AAEA;AACA;AAEA;;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAGA;AACA;AACA;AACA;AACA;AAEA;;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;;;AAGA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;;AAEA;AACA;AACA;AACA;AACA;AAGA;AACA;AACA;AAEA;;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;;;;AAIA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;;;AAGA;AACA;AACA;AACA;AAEA;;;AAGA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;;;;AAIA;AACA;AACA;AACA;AACA;AAEA;;;AAGA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;;;;AAIA;AACA;AACA;AACA;AACA;AAEA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;;;;AAIA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AA1XA;;AAEA;AACA;AAwXA;AAAA;AA5XA;AA6XA;AAAA;AAAA;AACA;;;;;;;;;;;;;;;;ACzcA;;;;;;;;;;;;;;;;;;;;;;;;AAwBA;AAEA;AAEA;AAAA;AAAA;AAAA;AAMA;;AAEA;AACA;AAuBA;AAFA;AAGA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AAKA;AACA;AACA;AACA;AACA;AAEA;AAMA;AACA;AAEA;AAMA;AACA;AACA;AACA;AAEA;AAEA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AAEA;AAQA;AACA;AAEA;AACA;AAEA;AAUA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAGA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AAxNA;AACA;AACA;AACA;AACA;AAqNA;AAAA;AA3NA;AA4NA;AAAA;AACA;;;;;;;;;;;;;;;;AClQA;;;;;;;;;;;;;;;;;;;;;;;;AAwBA;;;;;;AAEA;AAEA;AAAA;AAAA;AAAA;AACA;;AAEA;AACA;AAAA;AAOA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AAAA;AACA;AACA;AAEA;AAGA;AAMA;AACA;AACA;AAAA;AA9FA;AA+FA;AAAA;AACA;;;;;;;;;;;;;;;;AChIA;;;;;;;;;;;;;;;;;;;;;;;;AAwBA;AAEA;AAEA;AAAA;AAAA;AAAA;AA0CA;AAuFA;AACA;AAvEA;AADA;AACA;AACA;AACA;AACA;AACA;AACA;;;AAHA;AAMA;AADA;AACA;AACA;AACA;AACA;AACA;AACA;;;AAHA;AAKA;AADA;AACA;AACA;AACA;AACA;AACA;AACA;;;AAHA;AAKA;AADA;AACA;AACA;AACA;AACA;AACA;AACA;;;AAHA;AAKA;AADA;AACA;AACA;AACA;AACA;AACA;AACA;;;AAHA;AAKA;AADA;AACA;AACA;AACA;AACA;AACA;AACA;;;AAHA;AAKA;AADA;AACA;AACA;AACA;AACA;AACA;AACA;;;AAHA;AAKA;AADA;AACA;AACA;AACA;AACA;AACA;AACA;;;AAHA;AAKA;AADA;AACA;AACA;AACA;AACA;AACA;AACA;;;AAHA;AAKA;AACA;AACA;AACA;AACA;AAKA;AACA;AAEA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AAAA;AAEA;AACA;AAEA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AAEA;AAEA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAlNA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AA2MA;AAAA;AAzNA;AA0NA;AAAA;AACA;;;;;;;;;;;;;;;;ACjSA;;;;;;;;;;;;;;;;;;;;;;;;AAwBA;AAEA;AAEA;AAAA;AAAA;AAAA;AACA;AAEA;AAEA;AACA;AACA;AACA;AACA;AA4NA;;AAEA;AACA;AA+EA;AA/BA;AACA;AA+BA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAzCA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAgCA;AACA;AACA;AAEA;AAEA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AAEA;AACA;AAEA;AAEA;AAMA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AAEA;AACA;AAEA;AAEA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AAAA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AAEA;AAEA;AACA;AAMA;AAEA;AACA;AACA;;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AAEA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AAAA;AACA;AACA;AAWA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;;;AAlBA;;AAmBA;AAEA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AAAA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AAEA;AAEA;AAWA;AAEA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AAEA;AAQA;AACA;AACA;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;;;AAGA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AAIA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAAA;AACA;AACA;AACA;AAEA;AAMA;AAEA;AACA;AACA;AACA;AACA;AAEA;AAEA;AACA;AACA;AACA;AAEA;AAEA;AAEA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAAA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAMA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AAOA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;;;AAGA;AACA;AASA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;;AAEA;AACA;AACA;AAOA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;;;;;;;;AAQA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AAEA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AAEA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAhxCA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAkwCA;AAAA;AAlxCA;AAoxCA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AAEA;AACA;AAEA;;;;AAIA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AAEA;AAEA;AACA;AACA;AAEA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAqBA;AAWA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AAAA;AAMA;AACA;AACA;AACA;AACA;AAEA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AApHA;AAqHA;AAAA;AAEA;AAUA;AACA;AACA;AACA;AAEA;AAQA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;;;;AAIA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AAAA;AAQA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AAEA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AAtQA;AAuQA;AAAA;AAEA;AA0BA;AAAA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;;;;;;;;;;;;;;AAcA;AAEA;AACA;AAEA;AACA;AACA;AAEA;AACA;AAEA;AACA;AAEA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAAA;AACA;AACA;AASA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AAEA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AAEA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AAEA;AAGA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AAEA;AAGA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AAEA;AAGA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AAEA;AACA;AAEA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;;;;AAIA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AApeA;AACA;AACA;AACA;AACA;AACA;AACA;AAcA;AACA;AACA;AACA;AA8cA;AAAA;AAteA;AAweA;AA6BA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;;AAEA;AACA;AAWA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AAEA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAAA;AACA;AACA;AAMA;AACA;AAQA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;;;;;;;;;;;AAWA;AACA;AAWA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAMA;AACA;AAKA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAQA;AAMA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AAMA;AACA;AAKA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAcA;AAEA;AACA;AAMA;AAMA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAMA;AAKA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AASA;AACA;AAIA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AAEA;AACA;AAEA;AAAA;AACA;AACA;AAEA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AA3lBA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAolBA;AAAA;AA7lBA;AA+lBA;AAAA;AAEA;AAKA;AAMA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAvFA;AAyFA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AAKA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AAAA;AAnDA;AAqDA;AASA;;;;;;AAMA;AACA;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAAA;AA7DA;AA8DA;AAAA;AACA;;;;;;;;;;;;;;;;AC72GA;;;;;;;;;;;;;;;;;;;;;;;;AAwBA;AAEA;AAEA;AAAA;AAAA;AAAA;AACA;AACA;AA6DA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAPA;AA4FA;AACA;AACA;AACA;AACA;AAEA;;AAEA;AACA;AAiCA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAGA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AAGA;AACA;AACA;AAGA;AAEA;AACA;AAEA;AACA;AACA;AACA;AAEA;AAEA;AACA;AAKA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AAiBA;AAEA;AACA;AACA;AACA;AAEA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAAA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AAQA;AACA;AAQA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AAEA;AACA;AAIA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AAGA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AAIA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AAUA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAKA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AAGA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AASA;AASA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AAAA;AACA;AAAA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAAA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AA1oCA;AACA;AACA;AACA;AAwoCA;AAAA;AA5oCA;AA8oCA;AAIA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AAEA;AACA;AACA;AAIA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AAEA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAAA;AACA;AAAA;AACA;;;;;;;;;;;;;;;;ACv7CA;;;;;;;;;;;;;;;;;;;;;;;;AAwBA;AAEA;AAEA;AAAA;AAAA;AAAA;AACA;AAEA;AAAA;AAgUA;AAtSA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AAEA;AAEA;AACA;AACA;AACA;AAEA;AAYA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AAEA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;;AAEA;AACA;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AAAA;AACA;AACA;AACA;AAEA;AACA;AAAA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AA9TA;AACA;AACA;AACA;AACA;AACA;AA0TA;AAAA;AAhUA;AAkUA;AAAA;AAkUA;AAxSA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AAaA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AAEA;AACA;AAEA;AACA;AACA;AAEA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;;AAEA;AACA;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AAAA;AACA;AACA;AACA;AAEA;AACA;AAAA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAhUA;AACA;AACA;AACA;AACA;AACA;AA4TA;AAAA;AAlUA;AAmUA;AAAA;AACA;;;;;;;;;;;;;;;;ACrqBA;;;;;;;;;;;;;;;;;;;;;;;;AAwBA;AAEA;AAEA;AAAA;AAAA;AAAA;AACA;AAEA;AAAA;AAiUA;AApSA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AAEA;AAEA;AACA;AACA;AACA;AAEA;AAYA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AAEA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;;AAEA;AACA;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AAAA;AACA;AACA;AACA;AAEA;AACA;AAAA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AA/TA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAuTA;AAAA;AAjUA;AAmUA;AAAA;AAoUA;AAxSA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AAEA;AAEA;AACA;AACA;AACA;AAEA;AAYA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AAEA;AACA;AAEA;AACA;AACA;AAEA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;;AAEA;AACA;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AAAA;AACA;AACA;AACA;AAEA;AACA;AAAA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAlUA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AA0TA;AAAA;AApUA;AAqUA;AAAA;AACA;;;;;;;;;;;;;;;;ACxqBA;;;;;;;;;;;;;;;;;;;;;;;;AAwBA;AAEA;AAEA;AAAA;AAAA;AAAA;AAAA;AAAA;AACA;AASA;AAAA;AAyKA;AAlIA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAAA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AAAA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAAA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AAvKA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AAwIA;AAAA;AAzKA;AA0KA;AAAA;AAAA;AACA;;;;;;;;;;;;;;;;ACjNA;;;;;;;;;;;;;;;;;;;;;;;;AAwBA;AAEA;AAEA;AAAA;AAAA;AAAA;AAUA;;;AAGA;AACA;AAAA;AACA;AAEA;;AAEA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AAtBA;AAwBA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAlCA;AAoCA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAXA;AAaA;AACA;AACA;AACA;AACA;AACA;AACA;AANA;AAOA;AACA;AAAA;AACA;;;;;;;;;;;;;;;;AClIA;;;;;;;;;;;;;;;;;;;;;;;;AAwBA;AAEA;AAEA;AAAA;AAAA;AAAA;AACA;AAEA;AAmBA;;AAEA;AACA;AAjBA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAMA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AAAA;AA/DA;AAiEA;AAGA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AAAA;AA5BA;AA8BA;AACA;AAAA;AAgFA;AAjBA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AA9EA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAIA;AACA;AACA;AACA;AACA;AAmBA;AAAA;AAhFA;AAiFA;AAAA;AACA;;;;;;;;;;;;;;;;ACjNA;;;;;;;;;;;;;;;;;;;;;;;;AAwBA;AAEA;;AAEA;AAEA;AAEA;AAAA;AAAA;AAAA;AA2BA;;;;;;;;;AASA;AACA;AAyCA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AAEA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AAEA;AACA;AAEA;AAAA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AAEA;AAGA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AAEA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AAEA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AAEA;AAAA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AAEA;AACA;AAEA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AAEA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AAEA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AAEA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AAEA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AAEA;AAEA;AACA;AAEA;AAEA;AACA;AACA;AACA;AAEA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AA/cA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AAscA;AAAA;AAjdA;AAkdA;AAAA;AACA;;;;;;;;;;;;;;;;ACxhBA;;;;;;;;;;;;;;;;;;;;;;;;AAwBA;AAEA;AAEA;AAAA;AAAA;AAAA;AAEA;AACA;AACA;AAmHA;;AAEA;AACA;AAiEA;AAZA;AAaA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AAEA;AAMA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AAEA;AAGA;AAGA;AAGA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAAA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AAEA;AACA;AAEA;AAAA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AAEA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAGA;AAAA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AAjrCA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AA+oCA;AAAA;AAnrCA;AAorCA;AAAA;AACA;;;;;;;;;;;;;;;;AC30CA;;;;;;;;;;;;;;;;;;;;;;;;AAwBA;AAEA;AAEA;AAAA;AAAA;AAAA;AAEA;AACA;AACA;AAsGA;;AAEA;AACA;AAgGA;AApBA;AAqBA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AAEA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AAEA;AAIA;AACA;AACA;AACA;AACA;AAEA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AAEA;AACA;AAEA;AACA;AAKA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AAEA;AACA;AACA;AACA;AAEA;AAMA;AACA;AAEA;AACA;AACA;AAKA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AAEA;AACA;AAAA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AAEA;AACA;AACA;AAEA;AAEA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AAEA;AACA;AAEA;AACA;AACA;AAEA;;AAEA;AACA;AACA;AACA;AAEA;AAEA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AAEA;AAEA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAGA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AAEA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAAA;AACA;AACA;AACA;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AAEA;AACA;AACA;AAEA;AACA;AAEA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AAAA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAGA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AA//BA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AA28BA;AAAA;AAjgCA;AAkgCA;AAAA;AACA;;;;;;;;;;;;;;;;AC5oCA;;;;;;;;;;;;;;;;;;;;;;;;AAwBA;AAEA;AAEA;AAAA;AAAA;AAAA;AACA;AAeA;AAAA;AAIA;AA2DA;AAzDA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAAA;AA/DA;AAgEA;AAAA;AACA;;;;;;;;;;;;;;;;AC7GA;;;;;;;;;;;;;;;;;;;;;;;;AAwBA;AAEA;AAEA;AAAA;AAAA;AAAA;AACA;AAyBA;AAAA;AA2eA;AA7bA;AACA;AACA;AACA;AACA;AACA;AAEA;AAEA;AAEA;AAEA;AAEA;AAEA;AACA;AAEA;AACA;AAAA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AAKA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AAEA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AAEA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAAA;AACA;AACA;AAEA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AAEA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AAEA;AAEA;AAAA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AAEA;AAEA;AAEA;AAEA;AAEA;AAEA;AACA;AACA;AAEA;AAEA;AACA;AAEA;AACA;AACA;AAEA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AAEA;AAEA;AAEA;AAEA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAxeA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AAEA;AAEA;AAEA;AAkdA;AAAA;AA3eA;AA6eA;AACA;AACA;AACA;AAEA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AAEA;AAEA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AAEA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AAAA;AACA;;;;;;;;;;;;;;;;AC1oBA;;;;;;;;;;;;;;;;;;;;;;;;AAwBA;AAEA;AAEA;AAAA;AAAA;AAAA;AACA;AAEA;AACA;AAkFA;AAEA;;AAEA;AACA;AAwYA;AAxYA;AAyDA;AAWA;AACA;AACA;AACA;AACA;AACA;AACA;AA+TA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AArUA;AACA;AACA;AACA;AAEA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AAQA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AAEA;AACA;AACA;AAEA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AAQA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AAaA;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AAUA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AAAA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAKA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AAGA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AAEA;AAAA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAAA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AAAA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AAEA;AAEA;AACA;AACA;AAEA;AACA;AACA;AAEA;AAEA;AACA;AAEA;AAEA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AAEA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AAAA;AACA;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAAA;AACA;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAAA;AACA;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAAA;AACA;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AAEA;;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAQA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AAEA;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;;;AAGA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AAAA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;;;AAGA;AACA;AAAA;AACA;AAAA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAAA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AAAA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAAA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;;;;;;;AAOA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AAAA;AACA;AAAA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AAEA;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;;;AA9FA;;;AA+FA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;;;;;;;;;;;AAWA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;;;;;;;;;AASA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AA58DA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AA0CA;AAg5DA;AAAA;AA98DA;AA+8DA;AAAA;AACA;;;;;;;;;;;;;;;;ACvkEA;;;;;;;;;;;;;;;;;;;;;;;;AAwBA;AAEA;AAEA;AAAA;AAAA;AAAA;AACA;AACA;AACA;AACA;AAwHA;AACA;AACA;AACA;AACA;AAEA;AAmBA;AACA;AACA;AAEA;AACA;;;;;;;;;;;;;;;;;;;AAmBA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AAEA;AAAA;AACA;AACA;AACA;AAEA;AAEA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AAEA;AAEA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AAEA;AACA;AACA;AACA;AAEA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AAEA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AAAA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AA3WA;AA4WA;AAAA;AA3XA;AAmYA;AAuDA;AACA;AACA;AACA;AAvCA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AAOA;AACA;;;;;;;;;;;;;;;;AAgBA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AAEA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AAAA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAAA;AACA;AACA;AACA;AACA;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AA1XA;AA2XA;AAAA;AA3YA;AA6ZA;AAEA;AAoDA;AAFA;AAGA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AAEA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AAEA;AAAA;AACA;AACA;AAEA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AAEA;AACA;AAEA;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAAA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAAA;AACA;AACA;AACA;AACA;AACA;AAEA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AAAA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AAAA;AACA;AAEA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AAEA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AAAA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AAEA;AAEA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AA1jCA;AACA;AACA;AACA;AACA;AACA;AAsjCA;AAAA;AA9kCA;AA+kCA;AAAA;AACA;;;;;;;;;;;;;;;;AChhEA;;;;;;;;;;;;;;;;;;;;;;;;AAwBA;AAEA;AAEA;AAAA;AAAA;AAAA;AACA;AAEA;AACA;AACA;AAEA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAoFA;AAAA;AAWA;AA2vBA;AAvoBA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AAEA;AAEA;AACA;AACA;AACA;AACA;AAEA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AAAA;AACA;AACA;AACA;AACA;AAIA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AAEA;AACA;AACA;AAEA;AAAA;AACA;AAEA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AAAA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;;;;AAIA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AAAA;AACA;AACA;AACA;AACA;AAAA;AAEA;AACA;AAEA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AAEA;;;;;AAKA;AACA;AACA;AAEA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AAEA;AACA;AAEA;AAEA;AACA;AAEA;AACA;AACA;AACA;AAEA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAnvBA;;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAyoBA;AAAA;AAtwBA;AAwwBA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAAA;AACA;;;;;;;;;;;;;;;;ACx6BA;;;;;;;;;;;;;;;;;;;;;;;;AAwBA;AAEA;AAEA;AAAA;AAAA;AAAA;AACA;AACA;AACA;AACA;AAUA;AACA;AAAA;AAgIA;AAvHA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AAEA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AAEA;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AA9HA;AA+HA;AAAA;AAhIA;AAkIA;AAAA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAGA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AA5EA;AA8EA;AACA;AAEA;AACA;AACA;AAEA;AAEA;AACA;AAEA;AACA;AAEA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AAEA;AACA;AAEA;AACA;AA5EA;AA8EA;AACA;AAEA;AACA;AAEA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AAEA;AACA;AAEA;AACA;AACA;AACA;AAEA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAnDA;AAqDA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;;;AAMA;AAEA;AACA;AAEA;AACA;AACA;AACA;AAEA;AAEA;AACA;AACA;AAEA;AAAA;AAMA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAOA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AANA;AAQA;;AAEA;AACA;AACA;AACA;AACA;AAHA;AAKA;AAyBA;;;;;;;AAOA;AACA;AAjCA;AAgBA;AACA;AACA;AACA;AAIA;AAWA;AACA;AAEA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AAEA;AAEA;AACA;AAEA;AACA;AACA;AACA;;AAEA;AACA;AACA;AAEA;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AAEA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AAEA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AAEA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;;;;;;;;;;;;;;;;;;;;;;;;AAwBA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AA1QA;AAEA;AAEA;AACA;AACA;AACA;AAoQA;AAAA;AAnRA;AAqRA;AAAA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AAEA;AACA;AACA;AAGA;AACA;AACA;AAEA;AACA;AACA;AAEA;AAEA;AACA;AACA;AAEA;AAEA;AAEA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAxCA;AA0CA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AARA;AAUA;AACA;AAQA;AAAA;AAEA;AACA;AACA;AACA;AAEA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AAEA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AAEA;AACA;AAEA;AACA;AAEA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAAA;AACA;;;;;;;;;;;;;;;;ACphCA;;;;;;;;;;;;;;;;;;;;;;;;AAwBA;AAEA;AAEA;AAAA;AAAA;AAAA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAuBA;AAAA;AA4DA;AAoSA;AAhSA;AACA;AAAA;AACA;AACA;AAAA;AACA;AACA;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AAKA;AAKA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AAEA;AACA;AAAA;AAAA;AACA;AACA;AAAA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AAEA;AAEA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AAEA;AACA;AACA;AACA;AACA;AAEA;AAAA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AAAA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AAEA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AA9VA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAySA;AAAA;AAhWA;AAiWA;AAAA;AACA;;;;;;;;;;;;;;;;ACnaA;;;;;;;;;;;;;;;;;;;;;;;;AAwBA;AAEA;AAEA;AAAA;AAAA;AAAA;AAEA;AACA;AACA;AACA;AAqHA;AA8CA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AAEA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AAEA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AAEA;AACA;AAEA;AAAA;AACA;AACA;AAMA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AAEA;AAoBA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AAIA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AAqBA;AACA;AAEA;AACA;AACA;AAEA;AAMA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AAOA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AAAA;AACA;AAEA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AAGA;AAAA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAAA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AAEA;AAEA;AACA;AAEA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AAAA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AA5jCA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AA6iCA;AAAA;AA9jCA;AAgkCA;AAAA;AAwOA;AA5NA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AAEA;AAEA;AAEA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAAA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAtOA;AACA;AACA;AAqOA;AAAA;AAEA;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAcA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AAAA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AA7CA;AA+CA;AACA;AACA;AAEA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AAEA;AAIA;AACA;AAEA;AACA;AAEA;AAEA;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AAEA;AACA;AAAA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AAQA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AAhMA;AACA;AAgMA;AAAA;AACA;AAAA;AACA;;;;;;;;;;;;;;;;AClyDA;;;;;;;;;;;;;;;;;;;;;;;;AAwBA;AAEA;AAEA;AAAA;AAAA;AAAA;AACA;AACA;AACA;AACA;AAEA;AAUA;AAoEA;AAOA;AAuBA;AACA;AACA;AACA;AACA;AAEA;AAAA;AACA;AAEA;AACA;AACA;AAEA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AAEA;AACA;AAEA;AACA;AAEA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AAEA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAAA;AACA;AACA;AAEA;AACA;AACA;AAEA;AAAA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAAA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AAGA;AACA;AAEA;AACA;AAEA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AArPA;AACA;AAqPA;AAAA;AA1QA;AA4QA;AAcA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;;AAEA;AAAA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;;;;;AAKA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AAGA;AACA;AAEA;;;;;AAKA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AApMA;AACA;AACA;AACA;AAkMA;AAAA;AAEA;AAAA;AACA;AACA;AACA;AACA;AACA;AAEA;AAEA;AACA;AAEA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AA9KA;AAgLA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAAA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AA1DA;AA4DA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAZA;AAcA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AA1BA;AA4BA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAAA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AAAA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAZA;AAcA;AACA;AACA;AACA;AACA;AACA;AAJA;AAKA;AACA;AAAA;AACA;;;;;;;;;;;;;;;;ACz8BA;;;;;;;;;;;;;;;;;;;;;;;;AAwBA;AAEA;AAEA;AAAA;AAAA;AAAA;AACA;AAWA;AAkBA;AANA;AACA;AACA;AACA;AAIA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AAEA;AAEA;AACA;AACA;AACA;AAEA;AAAA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AAEA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAKA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AAKA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAAA;AAlQA;AAoQA;AAAA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAAA;AACA;;;;;;;;;;;;;;;;ACrTA;;;;;;;;;;;;;;;;;;;;;;;;AAwBA;AAEA;AAEA;AAAA;AAAA;AAAA;AACA;AAGA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AA0BA;AATA;AACA;AACA;AACA;AAOA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AAEA;AAEA;AACA;AAEA;AACA;AAEA;AACA;AAEA;AACA;AAEA;AAEA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AAEA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AAAA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AAAA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AAEA;AAEA;AACA;AACA;AACA;AAAA;AA5ZA;AA8ZA;AAAA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAAA;AACA;;;;;;;;;;;;;;;;ACvdA;;;;;;;;;;;;;;;;;;;;;;;;AAwBA;AAEA;AAEA;AAAA;AAAA;AAAA;AACA;AACA;AACA;AAgFA;AA2CA;AACA;AACA;AACA;AACA;AACA;AAlCA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AASA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AAEA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AAAA;AA1KA;AA4KA;AACA;AAAA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAfA;AAiBA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAAA;AACA;;;;;;;;;;;;;;;;ACpYA;;;;;;;;;;;;;;;;;;;;;;;;AAwBA;AAEA;AAEA;AAAA;AAAA;AAAA;AAEA;AACA;AACA;AACA;AAoBA;AASA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;;AAEA;AACA;AACA;AACA;AAEA;;AAEA;AACA;AACA;AACA;AAEA;;AAEA;AACA;AACA;AACA;AAEA;;AAEA;AACA;AACA;AACA;AAEA;;AAEA;AACA;AACA;AACA;AAEA;;AAEA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AAEA;;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;;AAEA;AACA;AACA;AACA;AAEA;;AAEA;AACA;AACA;AACA;AAEA;;AAEA;AACA;AACA;AACA;AAEA;;AAEA;AACA;AACA;AACA;AACA;AAEA;AAAA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAAA;AA3PA;AAmQA;;AAEA;AACA;AAmBA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AAEA;AACA;AAEA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AAEA;AAEA;AAEA;;AAEA;AAEA;AAAA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AAEA;;AAEA;AACA;AACA;AAEA;AACA;AAEA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AAEA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AAEA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AAEA;;AAEA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AAEA;;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AAAA;AA3aA;AA6aA;AAwBA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AAEA;AAEA;AACA;AACA;AAEA;;;;AAIA;AACA;AACA;AACA;AACA;AAEA;AAEA;AACA;AACA;AACA;AACA;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AAAA;AACA;AACA;AAGA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AAAA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AAAA;AACA;AAEA;AACA;AACA;AAEA;AACA;AAEA;AACA;AAEA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;;AAEA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AA5VA;AA6VA;AAAA;AA9VA;AA+VA;AAAA;AACA;;;;;;;;;;;;;;;;ACxkCA;;;;;;;;;;;;;;;;;;;;;;;;AAwBA;;;;;;AAEA;AAEA;AAAA;AAAA;AAAA;AAGA;AACA;AACA;AACA;AACA;AAmDA;AAAA;AAAA;AAAA;AAYA;AAXA;AAAA;AACA;AACA;;;AAAA;;AAEA;AAAA;AACA;AACA;;;AAAA;;AAEA;AAAA;AACA;AACA;;;AAAA;;AACA;AAAA;AAZA;AA6CA;;AAEA;AACA;AAKA;AACA;AANA;AAQA;AAYA;AAKA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;;AAEA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AAEA;;AAEA;AACA;AACA;AACA;AAEA;;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;;AAEA;AACA;AACA;AAEA;AACA;AAEA;;AAEA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AAEA;;AAEA;AACA;AACA;AACA;AACA;AAEA;;AAEA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;;AAEA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;;AAEA;AACA;AACA;AAEA;AACA;AAEA;;;;AAIA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;;AAEA;AACA;AACA;AAEA;AACA;AAEA;;AAEA;AACA;AACA;AAEA;AACA;AAEA;;AAEA;AACA;AACA;AAEA;AACA;AAEA;;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;;AAEA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AAEA;;AAEA;AACA;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AAEA;AAAA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AAEA;AACA;;;;AAIA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AAAA;AASA;AAiBA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AAEA;;AAEA;AACA;AACA;AAEA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AAEA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;;AAEA;AACA;AACA;AAEA;AACA;AAEA;AAEA;AAEA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AAEA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AAMA;AAEA;AACA;AAEA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAGA;AAEA;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;;AAEA;AACA;AACA;AAEA;AAEA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AAEA;AACA;AACA;AACA;AACA;AAEA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AAEA;AACA;AACA;AAEA;AACA;AACA;AAEA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;;AAEA;AACA;AACA;AAEA;AAEA;AACA;AAEA;AACA;AAEA;AACA;AACA;AAEA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AAEA;AAEA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AAEA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AAEA;AACA;AAEA;AACA;AAEA;;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AAEA;AACA;AAEA;;;;AAIA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AAEA;AACA;AAEA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AAEA;;;;AAIA;AACA;AACA;AAEA;AACA;AACA;AAEA;AAEA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAAA;AArqBA;AAuqBA;AA2BA;AACA;AAEA;AACA;AACA;AACA;AAEA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AAEA;AACA;AACA;AAEA;;AAEA;AACA;AACA;AACA;AAEA;AACA;AAEA;AAEA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AAAA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AAEA;AAAA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AAAA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AAAA;AACA;AACA;AACA;AAEA;AACA;AAEA;;;;AAIA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;;AAEA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AA1SA;AAEA;;AAEA;AACA;AAsSA;AAAA;AA5SA;AA6SA;AAAA;AACA;;;;;;;;;;;;;;;;ACziDA;;;;;;;;;;;;;;;;;;;;;;;;AAwBA;AAEA;AAEA;AAAA;AAAA;AAAA;AACA;AA0FA;;AAEA;AACA;AAsFA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AArCA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAYA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AAEA;AACA;AACA;AACA;AACA;AAEA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;;AAEA;AACA;AAAA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AAEA;AACA;AACA;AAEA;AACA;AACA;AAEA;AAEA;AAEA;AACA;AACA;AACA;AAEA;AAEA;AACA;AACA;AACA;AAEA;AAEA;AACA;AACA;AAEA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AAEA;AACA;AAEA;AACA;AAEA;AAEA;AAEA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AAEA;AAEA;AAEA;AAEA;AACA;AACA;AACA;AAEA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AAEA;AACA;AACA;AAEA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AAEA;AACA;AAMA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AAGA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AAAA;AAEA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AAEA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AAEA;AAEA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AAEA;AAEA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AAEA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAnrCA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;;;AAGA;AACA;AA0pCA;AAAA;AArrCA;AAsrCA;AAAA;AACA;;;;;;;;;;;;;;;;ACjzCA;;;;;;;;;;;;;;;;;;;;;;;;AAwBA;;;;;;AAEA;AAEA;AAAA;AAAA;AAAA;AAGA;AAEA;AAqCA;AAAA;AAqCA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AAAA;AACA;AACA;AAEA;AAAA;AACA;AACA;AAEA;AACA;AAEA;AACA;AAEA;AAAA;AACA;AAEA;AACA;AAEA;AACA;AAEA;AAAA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAGA;AAEA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAGA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAUA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AA7ZA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAqYA;AAAA;AA/ZA;AAgaA;AAAA;AACA;;;;;;;;;;;;;;;;ACveA;;;;;;;;;;;;;;;;;;;;;;;;AAwBA;AAEA;AAEA;AAAA;AAAA;AAAA;AACA;AAAA;AACA;AACA;AACA;AACA;AAEA;AAAA;AAwKA;AAnIA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAAA;AACA;AACA;AAAA;AACA;AACA;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAOA;AAAA;AACA;AACA;AAEA;AACA;AACA;AAEA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AAEA;AAEA;AACA;AAEA;AACA;AACA;AACA;AAAA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AAAA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;AAAA;AAEA;AAAA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAtKA;AACA;AACA;AAEA;AACA;AACA;AAEA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAqIA;AAAA;AAxKA;AAyKA;AAAA;AACA;;;;;;;;;;;;;;;;AC7MA;;;;;;;;;;;;;;;;;;;;;;;;AAwBA;AAEA;AAEA;AAAA;AAAA;AAAA;AAGA;AAAA;AAkBA;AAjBA;AAAA;AACA;AACA;;;AAAA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAAA;AAlBA;AAoBA;AAAA;AAkBA;AAjBA;AAAA;AACA;AACA;;;AAAA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAAA;AAlBA;AAoBA;AAAA;AAkBA;AAjBA;AAAA;AACA;AACA;;;AAAA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAAA;AAlBA;AAoBA;AAAA;AAgBA;AAfA;AAAA;AACA;AACA;;;AAAA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAAA;AAhBA;AAkBA;AAAA;AAgBA;AAfA;AAAA;AACA;AACA;;;AAAA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAAA;AAhBA;AAkBA;AAAA;AAgBA;AAfA;AAAA;AACA;AACA;;;AAAA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAAA;AAhBA;AAkBA;AAAA;AAgBA;AAfA;AAAA;AACA;AACA;;;AAAA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAAA;AAhBA;AAkBA;AAAA;AAgBA;AAfA;AAAA;AACA;AACA;;;AAAA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAAA;AAhBA;AAkBA;AAAA;AAgBA;AAfA;AAAA;AACA;AACA;;;AAAA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAAA;AAhBA;AAkBA;AAAA;AAgBA;AAfA;AAAA;AACA;AACA;;;AAAA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAAA;AAhBA;AAkBA;AAAA;AAiBA;AAhBA;AAAA;AACA;AACA;;;AAAA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAAA;AAjBA;AAmBA;AAAA;AACA;;;;;;;;;;;;;;;;AC7OA;;;;;;;;;;;;;;;;;;;;;;;;AAwBA;AAEA;AAEA;AAAA;AAAA;AAAA;AAEA;AACA;AAyCA;AA0CA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AAOA;AAEA;AAEA;AAEA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;;;;AAIA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AAEA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AAAA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AAGA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AAEA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AAEA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AAEA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAOA;AAEA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AAEA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AAEA;AACA;AAEA;AACA;AACA;AAEA;AAEA;AACA;AACA;AACA;AAEA;AAEA;AACA;AAEA;AAAA;AACA;AACA;AACA;AAEA;AACA;AAEA;AAEA;AACA;AACA;AAEA;AACA;AAEA;;;;;AAKA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AAAA;AACA;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAxtBA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AA8sBA;AAAA;AA1tBA;AA2tBA;AAAA;AACA;;;;;;;;;;;;;;;;ACpyBA;;;;;;;;;;;;;;;;;;;;;;;;AAwBA;AAEA;AAEA;AAAA;AAAA;AAAA;AAEA;AAEA;AAiDA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AAoBA;AAAA;AAVA;AAWA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AAEA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AAEA;AAAA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AAEA;AACA;AAEA;AACA;AAEA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAzOA;AACA;AACA;AACA;AACA;AACA;AASA;AACA;AA2NA;AAAA;AA5OA;AA8OA;AAAA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AAMA;AACA;AACA;AAEA;AAEA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAlHA;AAoHA;AACA;AACA;AAFA;AAIA;AACA;AACA;AAFA;AAIA;AACA;AACA;AAFA;AAIA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAAA;AAEA;AACA;AACA;AAAA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AAAA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AAAA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AAAA;AAEA;AACA;AAWA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AAEA;AACA;AA/DA;AAiEA;AACA;AACA;AACA;AACA;AACA;AACA;AANA;AAQA;AAMA;AACA;AACA;AAEA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAdA;AAgBA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAAA;AACA;;;;;;;;;;;;;;;;ACrvBA;;;;;;;;;;;;;;;;;;;;;;;;AAwBA;AAEA;AAEA;AAAA;AAAA;AAAA;AACA;AAAA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAxBA;AAyBA;AACA;AAAA;AACA;;;;;;;;;;;;;;;;ACzDA;;;;;;;;;;;;;;;;;;;;;;;;AAwBA;AAEA;AAEA;AAAA;AAAA;AAAA;AAEA;AAsGA;;AAEA;AACA;AA8DA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AAMA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AAAA;AACA;AAAA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AAAA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAAA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AAAA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAAA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAAA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AAAA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AAEA;AACA;AACA;AAAA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;;;;AAIA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAAA;AACA;AACA;AACA;AACA;AACA;AAAA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AAEA;AACA;AACA;AACA;AAAA;AACA;AACA;AAEA;AACA;AAEA;AACA;AAEA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AAEA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAIA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAKA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AAEA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AAAA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AAGA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AA5nCA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AA8mCA;AAAA;AA9nCA;AAgoCA;;;;;AAKA;AACA;AA+BA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AAAA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AAEA;AACA;AACA;AAEA;AAAA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAGA;AACA;AACA;AAGA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AAAA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAAA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AAEA;AACA;AACA;AAAA;AACA;AACA;AACA;AACA;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAAA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AAEA;AACA;AAAA;AAEA;AACA;AAAA;AAEA;AACA;AAAA;AAEA;AACA;AAAA;AAEA;AAAA;AAEA;AACA;AACA;AAAA;AAEA;AACA;AAAA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AAGA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAxTA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AA8SA;AAAA;AAEA;AAAA;AAaA;AAEA;AA2BA;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAAA;AAjeA;AAkeA;AACA;AAAA;AACA;;;;;;;;;;;;;;;;AC7jEA;;;;;;;;;;;;;;;;;;;;;;;;AAwBA;AAEA;AAEA;AAAA;AAAA;AAAA;AACA;AAeA;AAMA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AAEA;AACA;AAEA;AACA;AAAA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAAA;AAnEA;AAoEA;AAAA;AACA;;;;;;;;;;;;;;;;ACjHA;;;;;;;;;;;;;;;;;;;;;;;;AAwBA;AAEA;AAEA;AAAA;AAAA;AAAA;AAAA;AAAA;AAUA;AAAA;AA+RA;AA7PA;AAAA;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;;;AAGA;AACA;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;;;AAGA;AACA;AACA;AACA;AACA;AACA;AACA;AAAA;AACA;AACA;AACA;AACA;AACA;AAEA;AAAA;AACA;AACA;AACA;AACA;AACA;AAEA;AAAA;AACA;AACA;AACA;AACA;AACA;AAEA;AAAA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAOA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAAA;AACA;AACA;AACA;AACA;AACA;AA7RA;AAEA;AACA;AA2RA;AAAA;AA/RA;AAgSA;AAAA;AAAA;AACA;;;;;;;;;;;;;;;;ACvUA;;;;;;;;;;;;;;;;;;;;;;;;AAwBA;AAEA;AAEA;AAAA;AAAA;AAAA;AAAA;AAAA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AAAA;AAAA;AACA;;;;;;;;;;;;;;;;ACjWA;;;;;;;;;;;;;;;;;;;;;;;;AAwBA;AAEA;AAEA;AAAA;AAAA;AAAA;AAEA;AAAA;AACA;AACA;AACA;AAFA;AAIA;AACA;AACA;AACA;AACA;AAJA;AAKA;AACA;AAAA;AACA;;;;;;;;;;;;;;;;AC1CA;;;;;;;;;;;;;;;;;;;;;;;;AAwBA;AAEA;AAEA;AAAA;AAAA;AAAA;AACA;AAYA;;AAEA;AACA;AAKA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AAEA;AACA;AACA;AACA;AAEA;AAEA;AACA;AACA;AAEA;;;AAGA;AACA;AACA;AACA;AAEA;AAEA;AACA;AACA;AACA;AACA;AACA;AAAA;AA3DA;AA4DA;AAAA;AACA;;;;;;;;;;;;;;;;ACzGA;;;;;;;;;;;;;;;;;;;;;;;;AAwBA;AAEA;AAEA;AAAA;AAAA;AAAA;AACA;AAGA;AACA;AAEA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAAA;AACA;;;;","sourceRoot":""}
\No newline at end of file